- To regenerate the JavaScript SDK, run
./packages/sdk/js/script/build.ts. - The default branch in this repo is
main.
feat/**, fix/** ──PR(Typecheck + Unit Tests 门禁)──▶ dev ──push 触发全量测试──▶
dev ──手动 release-fork──▶ prerelease 测试版
dev ──PR(全量测试门禁)──▶ main ──手动 release-fork──▶ 正式版
分层门禁:dev 是快速集成层(Typecheck + Unit Tests (linux);E2E 不阻塞),main 是正式质量门禁(Typecheck + 全量 Unit Tests + E2E)。所有改动通过 PR 流转,禁止直推 main 和 dev(由 GitHub Rulesets 强制)。
| Branch | 直推 | PR 门禁 | CI 触发 | Purpose |
|---|---|---|---|---|
{type}/** |
✅ 允许 | — | push 不触发;PR 触发目标分支门禁 | 开发分支,频繁变更 |
dev |
❌ 禁止 | PR 必须通过 Typecheck + Unit Tests (linux) | ✅ push 触发 Typecheck + 全量测试 | 快速集成层 |
main |
❌ 禁止 | PR 必须通过 Typecheck + Unit Tests + E2E (linux + windows) + SpecGit Acceptance | ✅ push 触发全量 | 正式质量门禁 + 发版 |
流程:
- 从
main切出feat/**或fix/**分支开发 - PR →
dev(Typecheck + Unit Tests (linux) 门禁,快速合并) - push 到
dev自动触发全量测试验证 - 从
dev手动release-fork→ 产出 prerelease 测试版 - PR
dev→main(全量测试门禁:Typecheck + Unit Tests + E2E) - 合并到
main后手动release-fork→ 产出正式版
Rulesets(GitHub Settings → Rules → Rulesets):
protect-main:禁止直推/删除/force-push;PR 需通过 5 项检查(Typecheck、Unit Tests (linux)、E2E Tests (linux)、E2E Tests (windows)、SpecGit Acceptance)protect-dev:禁止直推/删除/force-push;PR 需通过 Typecheckbranch-naming:只允许创建feat/**、fix/**、chore/**、docs/**、refactor/**、test/**、release/**、hotfix/**前缀的新分支
CI 配置:
ci-typecheck.yml:push 到main/dev+ PR →main/dev时触发;除 lint + typecheck 外还跑test:dag-coreDAG 核心行为/覆盖率门禁(10min 超时)ci-test.yml:push 到main/dev+ PR →main/dev时触发全量测试(cancel-in-progress: false保证跑完);Linux unit-tests job 额外校验生成物新鲜度(packages/client与packages/sdk/js的check:generated)并跑 HttpAPI 契约门禁(test:httpapi:ci)specgit-accept.yml:PR →main和手动 dispatch 时运行当前提交的 SpecGit Acceptance;CLI 隔离安装、分支恢复和等待预算见下方 "SpecGit harness local specializations"。release-fork.yml:手动workflow_dispatch是唯一真实构建路径(push 到main/dev仅注册不构建);从dev发布自动产出X.Y.Z-dev.Nprerelease,从main发布X.Y.Z并标 Latest
新功能开发、Debug 等一切交付范畴恒定走此循环;后续所有工作必须遵守该方案,不得另起流程:
- 确立条目:明确条目的内容、范围、类型(
feat/fix/…)。一个 issue = 一个可独立验证的 WHY,无法独立验证的先拆分再立项。 - SpecGit 立项:
script/specgit-bootstrap.sh <title-or-number>创建/复用 issues 批次,确立交付分支与草稿 PR 脚手架(.specgit.yaml绑定);立项前先查重,避免同一 WHY 双开。wrapper 是 canonical 入口(见 "SpecGit harness local specializations");直跑裸specgit issue预期被 harness currency gate 以harness_stale(exit 2) 拒绝。 - 超流执行:安排 DAG workflow(超流)承载实现——并行开发 + 多角度 Review + 复合(synthesize),其产出作为交付证据基线。
- PR 过门禁:SpecGit 发起/推进 PR,过 TDD 与 CI 门禁(Typecheck、Unit Tests、DAG gate);
specgit finishexit 0 表示 accepted,合并和所有绑定 issue 关闭后才完成交付。 - 修复门禁问题:门禁失败在交付分支修代码/测试,永远不削弱门禁本身。
- 合并收尾:完成 PR 合并(目标分支遵循 Git Workflow,dev 为集成层),PR 正文
Closes #n自动关闭绑定 issues;版本确立与发布按 release train 既有节奏推进。
Format: {type}/{short-name} where type is one of: feat, fix, chore, docs, refactor, test, release, hotfix. The short name uses hyphens, at most three words. Enforced by GitHub Ruleset branch-naming.
Examples: feat/session-recovery, fix/scroll-state, docs/branch-naming, refactor/dag-spawn, test/auth-flow, chore/regenerate-sdk, release/v1.18, hotfix/critical-patch.
Use conventional commit-style messages and PR titles: type(scope): summary.
Valid types are feat, fix, docs, chore, refactor, and test. Scopes are optional; use the affected package or area when helpful, e.g. core, opencode, tui, app, desktop, sdk, or plugin.
Examples: fix(tui): simplify thinking toggle styling, docs: update contributing guide, chore(sdk): regenerate types.
- Keep things in one function unless composable or reusable
- Do not extract single-use helpers preemptively. Inline the logic at the call site unless the helper is reused, hides a genuinely complex boundary, or has a clear independent name that improves the caller.
- Avoid
try/catchwhere possible - Avoid using the
anytype - Use Bun APIs when possible, like
Bun.file() - Rely on type inference when possible; avoid explicit type annotations or interfaces unless necessary for exports or clarity
- Prefer functional array methods (flatMap, filter, map) over for loops; use type guards on filter to maintain type inference downstream
- In
src/config, follow the existing self-export pattern at the top of the file (for exampleexport * as ConfigAgent from "./agent") when adding a new config module. - In Effect generators, bind services to named variables before calling methods. Do not use nested service yields such as
yield* (yield* Foo.Service).bar().
Reduce total variable count by inlining when a value is only used once.
// Good
const journal = await Bun.file(path.join(dir, "journal.json")).json()
// Bad
const journalPath = path.join(dir, "journal.json")
const journal = await Bun.file(journalPath).json()Avoid unnecessary destructuring. Use dot notation to preserve context.
// Good
obj.a
obj.b
// Bad
const { a, b } = obj- Never alias imports. Do not use
import { foo as bar } from "..."or renamed imports likeresolve as pathResolve. - Never use star imports. Do not use
import * as Foo from "..."orimport type * as Foo from "...". - If a namespace-style value is needed, import the module's own exported namespace by name, for example
import { Project } from "@opencode-ai/core/project", then referenceProject.ID. - Prefer dynamic imports for heavy modules that are only needed in selected code paths, especially in startup-sensitive entrypoints. Destructure dynamic import bindings near the top of the narrowest scope that needs them so they read like normal imports. Avoid inline chains such as
await import("./module").then((mod) => mod.value())or(await import("./module")).value(). Keep branch-specific imports inside the branch that needs them to preserve lazy loading.
Prefer const over let. Use ternaries or early returns instead of reassignment.
// Good
const foo = condition ? 1 : 2
// Bad
let foo
if (condition) foo = 1
else foo = 2Avoid else statements. Prefer early returns.
// Good
function foo() {
if (condition) return 1
return 2
}
// Bad
function foo() {
if (condition) return 1
else return 2
}When a function has several validation branches or supporting details, make the main function read as the happy path and move supporting details into small helpers below it.
// Good
export function loadThing(input: unknown) {
const config = requireConfig(input)
const metadata = readMetadata(input)
return createThing({ config, metadata })
}
function requireConfig(input: unknown) {
...
}- Keep helpers close to the code they support, below the main export when that improves readability.
- Do not over-abstract simple expressions into many single-use helpers; extract only when it names a real concept like
requireConfigorreadMetadata. - Do not return
Effectfrom helpers unless they actually perform effectful work. Synchronous parsing, validation, and option building should stay synchronous. - Prefer Effect schema helpers such as
Schema.UnknownFromJsonStringandSchema.decodeUnknownOptionover manualJSON.parsewrapped inEffect.trywhen parsing untrusted JSON strings. - Add comments for non-obvious constraints and surprising behavior, not for obvious assignments or control flow.
Use snake_case for field names so column names don't need to be redefined as strings.
// Good
const table = sqliteTable("session", {
id: text().primaryKey(),
project_id: text().notNull(),
created_at: integer().notNull(),
})
// Bad
const table = sqliteTable("session", {
id: text("id").primaryKey(),
projectID: text("project_id").notNull(),
createdAt: integer("created_at").notNull(),
})- Avoid mocks as much as possible, you shouldn't be using globalThis.* at all unless it's the only option.
- Test actual implementation, do not duplicate logic into tests
- Tests cannot run from repo root (guard:
do-not-run-tests-from-root); run from package dirs likepackages/opencode. bun run test:dag-core(在packages/opencode):DAG 核心行为与覆盖率门禁,随 ci-typecheck 对每个 PR 强制执行;改状态机/持久化先本地跑它。
- Always run
bun typecheckfrom package directories (e.g.,packages/opencode), nevertscdirectly. Rootbun run typecheck(turbo) covers all packages. bun run builddoes not typecheck — esbuild transpiles only. A green build can still ship a missing import or a non-existent API, so it is not proof the code is sound.bun typecheck(tsgo --noEmit) is the commit gate.bun run lint(仓库根)=oxlint --max-warnings=4850警告数棘轮:任何新增 oxlint 警告都会撑破预算、炸掉 CI Typecheck job——修警告,永远不要抬上限。
Guiding invariants for adding services, HTTP API routes, or features. The build pipeline will not catch violations of these — only an understanding of the architecture will. Read the surrounding modules first (src/memory and src/config are good references for lightweight, self-contained services) before wiring new dependencies.
- Keep each
X.defaultLayerself-contained. It mustLayer.provideevery dependency its layer bodyyield*s at construction.Layer.provideMerge(self, layer)buildslayerin isolation — the context accumulated byselfis not fed to it — andLayer.mergeAlldoes not cross-provide siblings. A layer that quietly assumes an ambient service will construct in one entry point and crash in another, surfacing as a runtime crash or a blank/unresponsive TUI rather than a build error. LayerNode(.nodeexports,LayerNode.buildLayer) is a second, parallel composition system, separate fromdefaultLayer/AppLayer. The same self-containment rule applies per node, but the two systems don't share wiring. When adding a service that other services should see, find every consumer's.nodelist (not just itsdefaultLayer) and add the new service's node there.- Resolve optional or heavyweight cross-dependencies lazily. When a service needs something already built elsewhere in
AppLayer— especially something with deep transitive deps (Provider, MCP, HttpClient) — reach forEffect.serviceOption(Tag)at the call site instead of a hardyield* Tagin the layer body. This keeps the layer lightweight, leaves the consumer's requirements (R) empty, and stops transitive deps from being dragged into every entry point that builds the layer. A missing wire here compiles clean and fails silently (feature just no-ops) instead of erroring — grep everyEffect.serviceOption(X.Service)call site, confirm X's node/layer actually reaches it, and verify with an integration test that exercises the behavior, not just that the layer builds. - Regenerate the JS SDK after touching HTTP API routes. The SDK under
packages/sdk/jsis generated from the API's OpenAPI spec; adding or renaming a route does not update it. A stale SDK breaks the TUI at runtime — calling a client method that does not yet exist — in a way typecheck cannot catch, because the generated types are the client's source of truth. After route changes, run./packages/sdk/js/script/build.tsand rebuild the consumers. CI enforces this: theCheck generated SDKstep inci-test.ymlrunsbun run check:generatedinpackages/sdk/js(regenerate +git diff --exit-code -- src/v2/gen), so a forgotten regeneration fails the Linux unit-tests job instead of surfacing at TUI runtime. - Changing an HTTP API route's request/response shape requires updating its scenario in
test/server/httpapi-exercise/index.ts.bun run test:httpapi --fail-on-missingfails CI otherwise.
Invariants for extending the SolidJS/opentui TUI. The DAG inspector (src/feature-plugins/system/dag-inspector.tsx) plus its sidebar indicator and summary pipeline are the reference implementation for a server-driven TUI feature.
- TUI builtins live under
src/feature-plugins/and are registered infeature-plugins/builtins.ts. A builtin exports{ id, tui }where theTuiPluginfunction registers routes (api.route.register), palette commands (api.keymap.registerLayer), and sidebar slots (api.slots.register). Register only the*.openpalette command at plugin level; everything else belongs to the route component. - Route-scoped keyboard commands go inside the route component via
useBindings(fromsrc/keymap) withprops.api.tuiConfig.keybinds.gather("<prefix>", commandNames), so they are active only while the route is mounted and user overrides apply. Every command needs entries in bothDefinitionsandCommandMapinsrc/config/keybind.ts; a command missing there cannot be rebound and won't appear in keybind config schema. Follow the diff-viewer's key vocabulary (escape,qclose,j/kmove,enteractivate) for consistency. - Server-driven shared state lives in
src/context/sync.tsx: one store slice + one event reducer case per domain, plus an initial fetch during bootstrap as the safety net for events missed before the event stream subscribes.SyncProviderrequiresExitProvider(plus Args/KV/SDK/Project providers); any test harness mounting it must wrap with all of them — seetest/cli/cmd/tui/sync-fixture.tsx. - Every event type the TUI consumes must be defined with
define()inpackages/schemaand included inEventManifest.Definitions, or the generated SDK event union won't contain it and the reducer case can't typecheck. Ephemeral push events (e.g.dag.workflow.summary.updated) stay OUT of the durable-event manifest: emit them viaGlobalBus, never persist them, and design consumers to tolerate missed events (re-fetch on bootstrap). - Types shared between server and TUI come from the generated SDK (
@opencode-ai/sdk/v2). Do not hand-duplicate response/summary interfaces inpackages/plugin/src/tui.tsor TUI code — re-export the SDK type (export type TuiSidebarDagItem = DagWorkflowSummary), so a server schema change surfaces as a typecheck error instead of silent drift. - Prefer server-side aggregation for display data. The TUI renders
DagStore.getWorkflowSummariesoutput verbatim; it never aggregates rawdag.*events client-side. Derived-view publishers (server-sidepackages/opencode/src/dag/runtime/summary-publisher.ts) must stay stateless: recompute from the store on every emission, no module-level caches. - Extract non-trivial pure logic (topology layout, tree building) into a sibling
*-utils.tswith unit tests, mirroringdiff-viewer-file-tree-utils.ts/dag-inspector-utils.ts. Component files stay declarative. - Async fetches inside components must guard against stale responses (check the selection still matches before
setState) and clean up event subscriptions withonCleanup.
This section was removed: the SessionV2/SessionExecution/SessionRunner/SessionRunCoordinator vocabulary it described no longer exists in the codebase. The current session runtime lives in packages/opencode/src/session/ (prompt.ts, processor.ts, compaction.ts); read src/session/CONTEXT.md-adjacent module docs there before extending it.
The authoritative repository for curated DAG workflow YAML and configuration-owned block or prompt assets is LeXwDeX/opencode-dag-config. Inspect and update that repository when a task changes reference workflows, composable block configurations, or their embedded worker prompts; configuration-only changes do not belong in this runtime repository.
This repository owns the DAG schema, compiler, validator, runtime, and release integration. Changes that cross the boundary land runtime support first, then update the config repository's runtime-compat.json to the merged full runtime commit SHA and pass its template-validation CI.
- Built-in commands ship compiled into the binary:
/dag-auto(requirement → workflow routing: classify, match a saved DAG route, retarget, validate, start). Platform delivery (issues, PRs, CI, merge, release) is specgit's job — never part of/dag-*. User command files shadow built-ins by name; register new built-ins throughpackages/core/src/plugin/command.ts+packages/opencode/src/command/index.ts(Defaultregistry). - Templates come from
opencode-dag-config: 7 domains ×full/liteplus cross-domain routes (ultra-flow-route,release-route). Precedence: project.opencode/workflows/> global config dir > builtin snapshot (the release pipeline compiles the config repo into the binary viaDAG_TEMPLATES_DIR). ~/.config/opencode/dag.jsonc(全局用户配置,非仓库文件)supplies DAG node model tiers:advancedforrequired: trueand review nodes,standardotherwise. Never pinmodelinside saved workflow specs.
- Memory is fail-closed inert until the project is initialized: running
/initstampsproject.time_initialized, which/memory onandmemory_searchrequire./memory onsilently answering "Memory remains off" means the project never ran/init(or has no real git identity). - Model 与节奏配置在
~/.config/opencode/memory.jsonc(enabled、model、turn_interval、注入上限)。openai-compatible 供应商会把 JSON schema 渲染进 system prompt——schema-blind 模型也能产出合法 topic。写入验证看盘:~/.local/share/opencode/memory/projects/<hash>/generations/*/topic-*.yaml。
Releases follow .github/RELEASE_NOTES_TEMPLATE.md: keep section order and emoji headers, omit empty sections, fill the test summary from the CI gates, and end with the previous_tag...current_tag changelog link.
Mechanics(fail-closed,graphagent-v1.0.29 验证过):
- 版本由
packages/opencode/script/release-version.ts机械推导:只认graphagent-v*标签,下一个 stable 恒为 patch+1(graphagent-v1.0.28→1.0.29),dev 通道为X.Y.Z-dev.N;opencode 包版本号被忽略。 - 系列文件
.github/releases/v<推导版本>.md的文件名必须等于推导版本(命名错 = release job fail-closed 炸掉);正文用{VERSION}、{Prerelease/Stable}、{branch}、{previous_tag}、{current_tag}占位符,由packages/opencode/script/release-notes.ts渲染并校验不变量。 - 本地演练渲染:
bun run ./packages/opencode/script/release-notes.ts --notes-dir .github/releases --version <V> --channel main --branch main --tag graphagent-v<V> --previous-tag graphagent-v<P> --repo LeXwDeX/OpenCode-GraphAgent --out /tmp/notes.md
Issues and PRDs are tracked in this repository's GitHub Issues through the gh CLI. See docs/agents/issue-tracker.md.
Triage uses the five canonical labels needs-triage, needs-info, ready-for-agent, ready-for-human, and wontfix. See docs/agents/triage-labels.md.
This repository uses a multi-context domain-document layout rooted at CONTEXT-MAP.md. See docs/agents/domain.md.
Kept outside the managed block so routine bootstrap preserves the reviewed harness. The 1.13.1 refresh in #552 replaces the former 1.10.1 workarounds:
- Use the generated event-SHA checkout and branch-restoration steps together: the verdict evaluates the triggering commit while SpecGit sees the delivery branch. The generated dispatch path supplies its own ref and SHA.
- Install the pinned CLI under the generated isolated prefix, outside this Bun workspace, and use its bundled YAML parser. This avoids the workspace
catalog:installation failure and root-package resolution assumptions. Keep the generated Node version with that CLI. - Preserve the repository's 45-minute job timeout and 40-minute sibling-check deadline. The inner deadline stays below the job timeout so slow or missing checks produce a verdict diagnosis. This is the remaining template deviation; the bootstrap wrapper restores it after transient initialization.
- Read check identities and automation choices from
spec_git/policy.yaml. Keep the configured checks and current-head ownership checks intact; a harness refresh does not authorize enabling automatic merge.
script/specgit-bootstrap.sh <specgit issue args...> is THE canonical way to run specgit issue in this repository. Bare specgit issue is expected to fail with harness_stale (exit 2) whenever the pinned CLI's harness template moves — the wrapper satisfies that gate safely: it snapshots the full init write surface to a temp dir outside the repo, runs specgit init --force --no-protect (hardcoded, offline), then specgit issue "$@" with arguments, exit status, and diagnostics passed through verbatim, and restores the specialized bytes above on success and every failure path (EXIT/INT/TERM/HUP), verifying each file byte-for-byte via git hash-object.
- Use the wrapper for ordinary issue bootstrap. Intentional CLI/harness upgrades are tracked changes: review the generated diff and update these specializations together, while preserving the timeout budget and policy.
- Fail-closed rejections: dirty write-surface paths (tracked/staged/untracked) → exit 2 with the offending paths listed; no SpecGit binding (
.specgit.yamlorspec_git/policy.yamlmissing) → exit 3; restore hash mismatch → exit 3 with the snapshot kept for forensics. Rejection paths print plainspecgit-bootstrap:stderr lines and NEVER produce a--jsonenvelope. - The inner
.specgit.yamldelivery record is rolled back to its pre-run bytes when the innerspecgit issueexits nonzero (or a signal/init failure interrupts); a successful call keeps the new binding. Record-restore failure keeps the forensic snapshot and exits 3, overriding the inner exit code. Branches, commits, and remote side effects are never undone (#530). - Managed-block guidance referencing bare
specgit issuecommands is superseded by this section for this repository. Behavior tests:bash script/specgit-bootstrap.test.sh(stubbed CLI, zero network; not CI-wired).
Managed by specgit init. Everything between the markers is regenerated
whenever init writes the harness (a fresh init, or --force when a policy
already exists); keep manual guidance outside them.
- Start with
specgit issue <title-or-number>...: it creates or reuses the issues, writes and pushes the initial binding on the delivery branch, opens the draft pull or merge request with the supplied body, selected policy template, or built-in scaffold, then records and pushes its number. Re-running resumes; it is idempotent. - Use the issue and PR/MR templates explicitly selected by policy. With
validation.bodiesorrequired_sections, prepare complete content from the discussion before bootstrap and supply--body-file <path>per new title and--pr-body-file <path>. Without enforced body rules, the selected policy template or built-in scaffold can be filled after creation. Preserve everyCloses #n; enabled body rules apply at creation and acceptance. Resume keeps existing remote bodies and user edits. Unselected repository templates are not silently loaded. - A draft PR/MR always fails the verdict (
pr_draft): beforespecgit finish, mark it ready for review —gh pr ready <number>on GitHub,glab mr update <number> --readyon GitLab. specgit finishis read-only: its verdict comes from real git, PR/MR, and CI evidence; exit 0 means accepted. With automation enabled, the trusted remote workflow continues after CI without another confirmation.specgit pr --merge --jsonis the recovery path: it verifies the approvedtarget_branch, fresh acceptance, and all current-head CI, then confirms the merge and every bound issue closure before reporting completed. A failed closure remains recoverable and is never reported as completed.
- Follow the project's
languagefor issues and PRs/MRs. Enabledvalidationrules check titles and labels before creation and duringfinish.kindmode requires one catalog kind and only declared extras;projectmode selects only policytags. Users choose rule changes withspecgit init --force --configure-rules. - Every bootstrap applies the title's
kind::<type>member automatically; pass--tags <a,b>to choose the full set explicitly. - Selection is pool-first: existing on-spec labels win verbatim; anything
missing is seeded from the built-in
kind::catalog or the policy'stags:declarations. Unknown vocabulary exits 2 naming the universe. - Choose at most one label per axis; omit uncertain optional labels and
keep every label required by the selected policy. Existing pool labels
cannot override that policy —
off-spec pool labels are reported (
tag_pool_dirtywarnings are for humans) and never renamed by SpecGit.
specgit prrepairs the PR/MR binding: with no arguments it auto-discovers the request for this head branch, errors with a fix when none is found, and refuses with a list when several match.specgit statusshows local evidence only: record, state, drift, origin.specgit doctorprobes git, repository, origin, the configured provider CLI (gh, orglabfor a declared GitLab host), and policy.
- Ten commands:
specgit init,specgit setup,specgit issue,specgit pr,specgit finish,specgit bind,specgit unbind,specgit status,specgit accept,specgit doctor. specgit setupinstalls the agent entry points (commands for opencode, portable skills for other tools);specgit bind,specgit unbind, andspecgit acceptare automation aliases for scripts and CI.- Automation defaults to off (
--automation no). For a fresh policy, only when the user personally chooses yes may they enable it withspecgit init --automation yes --merge-target <branch>. To change an existing policy, usespecgit init --force --automation yes --merge-target <branch>; plaininit --forcepreserves its current choice and target. An agent must not answer yes for the user.
- Before running
specgit issuewith a new title, search the tracker for similar open work through the authenticated session: on GitHub usegh issue list --state open --search "<keywords>"; on GitLab useglab issue list --search "<keywords>" --in title. Narrow further with labels when useful. - Open and read every plausible candidate with
gh issue view <n>on GitHub orglab issue view <n>on GitLab — compare the WHY, not just the wording. - If a candidate covers the same WHY, continue that issue instead of creating a new one; if it is close but different, say how they differ.
- When unsure, ask the requester to decide between continuing the existing issue and creating a duplicate. The team ships one line of work per WHY, never two.
One issue = one independently verifiable WHY. If a deliverable cannot be verified on its own evidence, split it before binding.
specgit finishexit code other than 0: never request merge. Fix the delivery, not the gate.- Never weaken
spec_git/policy.yamlto make a verdict pass. --jsonis the only parse surface: stdout is exactly one JSON document; never scrape human-readable output.
- SpecGit is the default delivery workflow here. An intended tracked
change — a feature, a fix, a refactor, a docs change, or shared rules — is a delivery:
work items live in this tracker as issues, never in private task
lists or conversational checklists. The trigger is the decision to
start: the moment the conversation settles and you begin turning
the plan into changes, the FIRST action is
specgit issue <type>: <title>...— before tracked implementation edits. Preparing temporary body files for bootstrap is part of this first step. Working without a binding is a contract violation, not a style choice. After bootstrap, verify each issue contains the discussed Why / Scope / Approach / Acceptance and fill only missing content withgh issue editorglab issue update, then implement. Mid-conversation inventories ("let me list everything to do") become issues, not chat artifacts. Trivial replies and read-only questions need none of this. - Local maintenance: installing or upgrading the CLI and running
init/setupto refresh local configuration and entry points need no issue, PR/MR, product build, or release when no product or shared-rule change is intended for commit. After a package upgrade, a human may run plainspecgit initand approve its guided refresh when it proves drift; non-interactive agents runspecgit init --force --no-protect, thenspecgit setup --tool all, then verifyspecgit status --json. Append--no-ignoreto init when authoritative delivery files are intentionally tracked without the managed ignore block; setup preserves that proven choice. Review tracked diffs before choosing what to share; ignore rules are never CI exemptions. Follow the host project's verification policy for the actual changed inputs; documentation may itself be a product input. Publishing requires explicit release intent within existing user authorization; local maintenance and merging do not imply publication. specgit finishexit0means accepted. Report completed only after the configured target merge and every bound issue closure are confirmed. Never declare completion from task lists, file states, or tests alone. Track a failed PR/MR with a new repair issue; repeated causes reuse an open repair issue and do not require abandoning the original PR/MR.- Use existing user authorization to complete issue bodies, the PR/MR body and ready transition, CI repairs or retries, acceptance, and the authorized merge. When user authorization or platform permission is missing, present the prepared result and name the specific gap. Documentation and entry points do not grant permission themselves.
- Branch on exit codes, not phrasing:
1= evidence complete, fix what the gates named;3= evidence missing, so followerrors[].fixfirst. Runspecgit doctor --jsononly for git, repository, origin, configured provider CLI/auth, or policy probes. Never present exit3as success. - Keep the
Closes #nreferences in the PR/MR body intact; after changing the PR/MR body, head branch, or CI, re-runspecgit finish. Never bypass or reconfig a required check to make acceptance pass. - Forge evidence flows through the user's authenticated CLI session only
(
gh/glab): never read, log, or pass around tokens.
- Never fan out duplicate or near-duplicate queries. One question, one tool call; if the answer is already in context, make zero calls.
- Parallel tool batches must contain distinct, independently justified calls. Before sending a batch, verify no two calls answer the same question. A repeated identical call is a bug regardless of intent.
- Long CI waits use
sleep N && <single check>, never repeated watches of the same resource. One watch command, one result.