Back to Get Shit Done

Context

CONTEXT.md

1.42.173.2 KB
Original Source

Context

Domain terms

Dispatch Policy Module

Module owning dispatch error mapping, fallback policy, timeout classification, and CLI exit mapping contract.

Canonical error kind set:

  • unknown_command
  • native_failure
  • native_timeout
  • fallback_failure
  • validation_error
  • internal_error

Command Definition Module

Canonical command metadata Interface powering alias, catalog, and semantics generation.

Query Runtime Context Module

Module owning query-time context resolution for projectDir and ws, including precedence and validation policy used by query adapters.

Native Dispatch Adapter Module

Adapter Module that satisfies native query dispatch at the Dispatch Policy seam, so policy modules consume a focused dispatch Interface instead of closure-wired call sites.

Query CLI Output Module

Module owning projection from dispatch results/errors to CLI { exitCode, stdoutChunks, stderrLines } output contract.

STATE.md Document Module

Shared CJS/SDK pure transform Module owning STATE.md parse, field extraction, field replacement, status normalization, and frontmatter reconstruction. It does not scan .planning/phases and does not own persistence or locking; phase/plan/summary counts arrive from inventory/progress Modules as inputs, and CJS/SDK read-modify-write paths remain Adapters.

Query Execution Policy Module

Module owning query transport routing policy projection (preferNative, fallback policy, workstream subprocess forcing) at execution seam.

Query Subprocess Adapter Module

Adapter Module owning subprocess execution contract for query commands (JSON/raw invocation, @file: indirection parsing, timeout/exit error projection).

Query Command Resolution Module

Canonical command normalization and resolution Interface (query-command-resolution-strategy) used by internal query/transport paths after dead-wrapper convergence.

Command Topology Module

Module owning command resolution, policy projection (mutation, output_mode), unknown-command diagnosis, and handler Adapter binding at one seam for query dispatch.

CJS Command Router Adapter Module

Compatibility Adapter Module for gsd-tools.cjs command families. Uses generated command metadata plus small argument shapers to route to CJS handlers, rather than calling SDK Command Topology directly. Preserves CJS compatibility startup while reducing hand-written router drift.

Query Pre-Project Config Policy Module

Module policy that defines query-time behavior when .planning/config.json is absent: use built-in defaults for parity-sensitive query Interfaces, and emit parity-aligned empty model ids for pre-project model resolution surfaces.

Planning Workspace Module

Module owning .planning path resolution, active workstream pointer policy (session-scoped > shared), pointer self-heal behavior, and planning lock semantics for workstream-aware execution.

Workstream Inventory Module

Shared CJS/SDK Module owning workstream directory discovery, per-workstream state projection, phase/plan/summary counting, roadmap-declared phase count, active marker projection, and active-workstream collision inputs. Command handlers render list/status/progress outputs from this inventory instead of rescanning .planning/workstreams/* directly.

Planning Path Projection Module

SDK query Module owning projection from project/workstream context to concrete .planning paths. Policy precedence is explicit workstream > env workstream > env project > root. Invalid workspace context is a validation error at this seam rather than a silent fallback.

Worktree Root Resolution Adapter Module

Adapter Module owning linked-worktree root mapping and metadata-prune policy (git worktree prune non-destructive default) for planning/workstream callers.

SDK Package Seam Module

Module owning SDK-to-get-shit-done-cc compatibility policy: legacy asset discovery, install-layout probing, transition-only error messaging, and thin Adapter access for CJS-era assets that native SDK Modules have not replaced yet.

Runtime-Global Skills Policy Module

Module owning runtime-aware global skills directory policy for SDK query surfaces. Resolves runtime-global skills bases/skill paths from runtime + env precedence, renders display paths for warnings/manifests, and reports unsupported runtimes with no skills directory.

Installer Migration Authoring Guard Module

Module owning validation for Installer Migration Module records and planned actions. It enforces migration metadata, explicit install scopes, ownership evidence for destructive/config actions, and runtime contract citations for runtime config rewrites before a migration can enter planning or apply.

Skill Surface Budget Module

Module owning which skills and agents are written to runtime config directories at install time (Phase 1) and at runtime via cluster-level toggles (Phase 2). Phase 1: get-shit-done/bin/lib/install-profiles.cjs defines named profiles (core, standard, full), computes transitive closure over requires: frontmatter, stages skills/agents to runtime config dirs, and persists the chosen profile in a .gsd-profile marker. Profile resolution precedence: explicit --profile= flag > .gsd-profile marker > full. --minimal/--core-only are back-compat aliases for --profile=core. Phase 2: get-shit-done/bin/lib/surface.cjs implements the /gsd:surface slash command for cluster-level enable/disable without reinstall; cluster definitions live in get-shit-done/bin/lib/clusters.cjs; per-runtime state persists in <runtimeConfigDir>/.gsd-surface.json independent from the .gsd-profile marker. See ADR-0011.

MVP Mode

Phase-level planning mode that frames work as a vertical slice (UI → API → DB) of one user-visible capability instead of horizontal layers. Resolved at workflow init via the precedence chain: --mvp CLI flag → ROADMAP.md **Mode:** mvp field → workflow.mvp_mode config → false. All-or-nothing per phase (PRD #2826 Q1). Surfaced as MVP_MODE=true|false to the planner, executor, verifier, and discovery surfaces (progress, stats, graphify). Canonical parser: roadmap.cjs **Mode:** field; canonical resolution chain documented in workflows/plan-phase.md. Concept index: references/mvp-concepts.md.

User Story

Phase-goal format under MVP Mode: As a [role], I want to [capability], so that [outcome]. Required regex shape: /^As a .+, I want to .+, so that .+\.$/. Used as the framing input by gsd-planner (emits as bolded ## Phase Goal header in PLAN.md) and as the verification target by gsd-verifier (the [outcome] clause is the goal-backward verification anchor). Authored interactively by /gsd-mvp-phase, validated by SPIDR Splitting when too large.

Walking Skeleton

Phase 1 deliverable under --mvp on a new project: the thinnest end-to-end stack proving every layer (framework, DB, routing, deployment) works together. Emitted as SKELETON.md capturing the architectural decisions subsequent vertical slices inherit. Gate fires when phase_number == "01" AND prior_summaries == 0 AND MVP_MODE=true. Scope intentionally narrow (PRD #2826 Q2) — does not retrofit existing projects.

Vertical Slice

Single-feature task that moves one user capability from open-to-close (happy path) end-to-end. Contrast with the horizontal layer (all models, then all APIs, then all UI). The MVP Mode planning unit; SPIDR Splitting axes (Spike, Paths, Interfaces, Data, Rules) are the canonical decomposition tools when a slice is too large for one phase.

Behavior-Adding Task

Predicate over a PLAN.md task: tdd="true" frontmatter AND <behavior> block names a user-visible outcome AND <files> includes at least one non-*.md / non-*.json / non-*.test.* source file. Pure doc/config/test-only tasks are exempt. The MVP+TDD Gate (in references/execute-mvp-tdd.md) only halts execution on this predicate; the gsd-executor agent applies all three checks at runtime. Currently a prose-only specification — no shared utility.

MVP+TDD Gate

Per-task runtime gate in /gsd-execute-phase that, when both MVP_MODE and TDD_MODE are true, refuses to advance a Behavior-Adding Task until a failing-test commit (test({phase}-{plan})) exists for it. The tdd_review_checkpoint end-of-phase review escalates from advisory to blocking under the same condition. Documented contract: references/execute-mvp-tdd.md. Reserved escape hatch --force-mvp-gate is documented but not implemented.

SPIDR Splitting

Five-axis story decomposition discipline (Spike, Paths, Interfaces, Data, Rules) used by /gsd-mvp-phase when a User Story is too large for one phase. Full interactive flow per PRD #2826 Q3 (not a lightweight filter). Reference: get-shit-done/references/spidr-splitting.md.


Recurring PR mistakes (distilled from CodeRabbit reviews, 2026-05-05)

Tests — no source-grep

  • Rule: never bind readFileSync result to a var then call .includes() / .match() / .startsWith() on it. CI runs scripts/lint-no-source-grep.cjs and exits 1.
  • Escape: add // allow-test-rule: <reason> anywhere in the file to exempt the whole file. Use when reading product markdown or runtime output (not .cjs source).
  • Pattern to reach for instead: call the exported function, capture stdout/JSON, assert on typed fields.

Tests — no unescaped RegExp interpolation

  • new RegExp(\prefix${someVar}`)— ifsomeVarcan contain.or other metacharacters (e.g. phase id5.1), the pattern is wrong. Always escapeRegex(someVar). The escapeRegexutility is incore.cjs` and already imported in most modules.

Tests — no dead regex branches in .includes()

  • src.includes('foo.*bar') is always false — .* is a regex metacharacter, not a wildcard in includes. Either use new RegExp('foo.*bar').test(src) or delete the branch.

Tests — guard top-level readFileSync against ENOENT

  • Module-level const src = fs.readFileSync(...) throws before any test() registers, aborting the runner with an unhandled exception instead of a named failure. Wrap in try/catch and rethrow with a helpful message.

Changesets — pr: field must be the PR number, not the issue number

  • The pr: key in .changeset/*.md frontmatter must reference the PR introducing the fix (e.g. 3142), not the issue it closes (e.g. 3120). Changelog tooling links to GitHub PRs by this value.

Shell hooks — never interpolate $VAR into single-quoted JS strings

  • node -e "require('$HOOK_DIR/lib/foo.js')" breaks silently if $HOOK_DIR contains a single quote (POSIX-legal). Pass paths via env vars: GIT_CMD_LIB="$HOOK_DIR/lib/foo.js" node -e "require(process.env.GIT_CMD_LIB)".

Shell guards — [ -f .git ] does not detect worktrees from main repo

  • In the main repo .git is a directory, so [ -f .git ] is false and the entire guard is skipped. Use git rev-parse --git-dir and match *.git/worktrees/* in a case statement instead.

Shell guards — absolute-path containment must use root/ prefix, not glob

  • [[ "$PATH" != "$ROOT"* ]] matches sibling prefixes (/repo-extra passes when ROOT=/repo). Use [[ "$P" != "$ROOT" && "$P" != "$ROOT/"* ]]. Also: check [ -z "$ROOT" ] and exit 1 before the containment test. Warn → fail-closed for security-relevant path checks.

Workstream migration names — enforce one canonical slug contract

  • Invariant: every directory under .planning/workstreams/* must be addressable by workstream status/set/complete, so creation and migration must share the same name contract.
  • Failure class: accepting raw --migrate-name values created directories that later commands reject (e.g. Bad Name directory exists but CLI rejects it as invalid).
  • Rule: normalize --migrate-name through the same slug transform as workstream create ([a-z0-9-]), and fail fast if normalization yields empty.
  • TDD sentinel: keep regression asserting workstream create ... --migrate-name 'Bad Name' migrates to bad-name and does not leave Bad Name on disk.

Docs — keep internal reference counts consistent

  • When a heading says (N shipped) and a footnote says N-1 top-level references, update the footnote. CodeRabbit catches this every time.

Workflow learnings (distilled from triage + PR cycle, 2026-05-05)

Skill consolidation gap class — missing workflow files

  • When a command absorbs a micro-skill as a flag (e.g. capture --backlog), the old command's process steps must be ported to a get-shit-done/workflows/<name>.md file. The routing wrapper in commands/gsd/*.md declares an execution_context @-reference to that workflow — if the file doesn't exist the agent loads nothing and has no steps to follow.
  • Detection: tests/bug-3135-capture-backlog-workflow.test.cjs adds a broad regression — every execution_context @-reference in any commands/gsd/*.md must resolve to an existing file on disk. This test will catch all future gaps of this class immediately.
  • Prior art: reapply-patches.md was the first gap found and fixed in PR #2824 itself. add-backlog.md was missed in the same PR and caught later in #3135. Run the regression test after every consolidation PR.

CodeRabbit thread resolution — stale threads after allow-test-rule fixes

  • After adding // allow-test-rule: to silence lint, CodeRabbit's existing inline threads remain open even though the acknowledged fix is in place. Resolve them via resolveReviewThread GraphQL mutation before merging — open threads block clean merge history and mislead future reviewers.
  • Pattern: gh api graphql -f query='mutation { resolveReviewThread(input:{threadId:"PRRT_..."}) { thread { isResolved } } }'

PR discipline — split unrelated changes into separate PRs

  • A bug fix and a docs rewrite committed to the same branch produce a noisy diff and a PR that reviewers can't cleanly approve. Cherry-pick doc changes to a dedicated branch (docs/) immediately, then force-push the original branch to remove the commit. One concern per PR.

INVENTORY.md must be updated alongside every workflow file addition/removal

  • docs/INVENTORY.md tracks the shipped workflow count (## Workflows (N shipped)) and has one row per file. Adding or removing a workflow without updating INVENTORY produces an internally inconsistent doc.
  • Also update docs/INVENTORY-MANIFEST.json — it is the machine-readable manifest and must stay in sync with the filesystem.
  • When a flag absorbs a micro-skill, the old skill's Invoked by attribution in INVENTORY must move to the new parent (e.g. add-todo.md incorrectly claimed /gsd-capture --backlog until #3135 corrected it).

README — keep root README as storyline only; all detail lives in docs/

  • Root README.md should be ≤300 lines: hero, author note, 6-step loop, install, core command table, why-it-works bullets, config key dials, docs index, minimal troubleshooting.
  • Every removed detail section needs a link to the canonical doc that covers it. All doc links must resolve before committing.
  • Markdownlint rules to watch: MD001 (heading level skip — don't use ### directly inside admonitions; use bold instead), MD040 (fenced code blocks must declare a language identifier).

Issue triage — always check for existing work before filing as new

  • Before writing an agent brief for a confirmed bug, check: (1) local branches (git branch -a | grep <issue>), (2) untracked/modified files on that branch, (3) stash, (4) open PRs with matching head branch. A crash may have left work 90% done — recover and commit rather than re-implementing.

SDK-only verbs — golden-policy exemption required

  • Any gsd-sdk query verb implemented only in the SDK native registry (no gsd-tools.cjs mirror) must be added to NO_CJS_SUBPROCESS_REASON in sdk/src/golden/golden-policy.ts. Without this entry the golden-policy test fails, treating the verb as a missing implementation rather than an intentional SDK-only path.

Recurring findings from ADR-0002 PR review (2026-05-05)

allowed-tools must include every tool the workflow uses

When a command delegates to a workflow via execution_context, the command's allowed-tools must cover every tool the workflow calls — including Write for file creation. The thin wrapper pattern makes this easy to miss: the process steps live in the workflow, but the tool grant lives in the command frontmatter. Missing a tool silently fails at runtime.

User-supplied slug/path args always need sanitization before file path construction

Any workflow step that takes user input (subcommand argument, $ARGUMENTS, or parsed remainder) and constructs a .planning/…/{SLUG}.md path must sanitize first: strip non-[a-z0-9-] chars, reject ..///\, enforce max length. Document the sanitization inline at the step, not just in <security_notes>. Steps that say "(already sanitized)" must trace back to an explicit sanitization guard — not just a preceding describe block.

RESUME/fallback modes bypass sanitization guards written for primary modes

CLOSE and STATUS modes that document "(already sanitized)" do not automatically cover RESUME or default modes. Each mode that constructs a file path from user input needs its own guard — don't assume sibling modes share state.

Shared helpers prevent lint/test disagreement

When a lint script and a test suite both implement the same constant (CANONICAL_TOOLS) or parser (parseFrontmatter, executionContextRefs), they will silently diverge. Extract to a scripts/*-helpers.cjs module required by both. A tool added to the lint's allowlist but not the test's (or vice versa) causes one layer to pass while the other fails.

readFileSync outside test() crashes the runner before any test registers

Module-level or suite-registration-time readFileSync throws as an unhandled exception if the file is absent, aborting the runner with no test output. Move reads inside test() callbacks so failures surface as named test failures.

Global regex with g flag carries lastIndex state between calls

A const RE = /pattern/g shared across functions retains lastIndex after .test() or .exec(). Use a non-global pattern for boolean checks (/pattern/.test(s)) and create a new RegExp(pattern, 'g') per iteration when you need exec() loops. Forgetting lastIndex = 0 resets causes intermittent false negatives.

ADR files need Status + Date headers

Every docs/adr/NNNN-*.md file must open with - **Status:** Accepted (or Proposed/Deprecated) and - **Date:** YYYY-MM-DD immediately after the title. Without them the ADR is undatable and untriageable when the list grows.

Step names in workflow XML must use hyphens, not underscores

All workflow file names use hyphens; <step name="..."> attributes inside those files must match: extract-learnings not extract_learnings. Tests asserting content.includes('<step name=') should tighten to the exact hyphenated name so renames are caught.

INVENTORY-MANIFEST.json has two workflow lists — only families.workflows is canonical

docs/INVENTORY-MANIFEST.json has families.workflows (canonical, read by tooling) and a stale top-level workflows key (introduced by a node update script that wrote to the wrong key). Always update families.workflows. Delete any top-level workflows key if it appears.

"Follow the X workflow" prose fragments are non-standard — use "Execute end-to-end."

After stripping prose @-refs, some command <process> blocks retained bolded "Follow the X workflow" fragments. ADR-0002 standard is Execute end-to-end. for single-workflow commands. Routing commands with flag dispatch use execute the X workflow end-to-end. in routing bullets (no bold, no redundant path).


Recurring CodeRabbit review patterns (2026-05-05, PRs #3152/#3154/#3155)

Changeset metadata drift (pr: points at issue instead of PR)

  • In .changeset/*.md, reviewers repeatedly flag pr: values that accidentally reference issue ids.
  • Rule: pr: must equal the GitHub PR number carrying the change.
  • Pre-flight check: before push, verify each new changeset file against current branch PR number.

Test diagnostics quality for command-output parsing

  • Even when behavior is correct, CR requests clearer failure surfaces before .map() on parsed output.
  • Rule: after JSON.parse, assert output object shape (e.g., Array.isArray(output.phases)) with raw-output-prefix diagnostics.
  • This prevents opaque TypeError failures and shortens triage loops when CLI output shape changes.

Merge gate discipline: CodeRabbit pass is necessary but not sufficient

  • CI/checks can be green while unresolved review threads still block clean merge policy.
  • Rule: always gate on all three together: required checks green, CodeRabbit pass, unresolved thread count = 0.
  • Keep using GraphQL reviewThreads as authoritative unresolved state, not summary comments/check badge alone.

SDK Runtime Bridge review synthesis (PR #3158, 2026-05-05)

What we fixed

  • Deepened one SDK Runtime Bridge Module seam (sdk/src/query-runtime-bridge.ts) for dispatch routing and observability.
  • Replaced orphan event typing with a canonical union (RuntimeBridgeEvent).
  • Made bridge observability non-intrusive: onDispatchEvent now runs behind a safe emitter so callback failures cannot alter dispatch outcomes.
  • Corrected strict-mode event semantics: strict native-adapter rejection now reports dispatchMode: 'native' (no fake subprocess attempt).
  • Preserved execution policy defaults by passing allowFallbackToSubprocess through as undefined when unset (no forced override in GSDTools).
  • Fixed transport decision ordering: fallback-disabled guard now throws before emitting subprocess decision events.
  • Added explicit invariant in subprocessReason for impossible states (fail loud on contract drift).
  • Updated user-facing docs (README.md, docs/CLI-TOOLS.md, docs/ARCHITECTURE.md) and ADR narrative consistency.

What we should not do again

  • Do not let observability callbacks sit on the critical path without isolation.
  • Do not emit structured events that claim a transport mode that never happened.
  • Do not force option defaults at call sites when policy Modules already define defaults.
  • Do not keep duplicate/inert exported types; expose one canonical union Interface.
  • Do not emit decision events before guard checks that may reject the path.
  • Do not leave architectural docs with ambiguous seam ownership between CLI and SDK paths.

AI Ops Memory (2026-05-09, machine-oriented)

RULESET.CONTRIB.GATE.ORDER=issue-first -> approval-label -> code -> PR-link -> changeset/no-changelog RULESET.CONTRIB.CLASSIFY.fix=requires confirmed/confirmed-bug before implementation RULESET.CONTRIB.CLASSIFY.enhancement=requires approved-enhancement before implementation RULESET.CONTRIB.CLASSIFY.feature=requires approved-feature before implementation

AI Ops Memory (2026-05-10, machine-oriented)

RULESET.GH.AUTH.DEFAULT=source .envrc GITHUB_TOKEN before gh; exception=ambient allowed only when user explicitly says machine-only fallback RULESET.CODERABBIT.GUARD.OPEN_PRS=gh pr list --repo gsd-build/get-shit-done --author @me --state open; repeat near end because open PR set can change mid-run RULESET.CODERABBIT.GUARD.COMPLETE=required_checks_green && coderabbit_check_pass && graphQL(reviewThreads.unresolved_count)==0 RULESET.CODERABBIT.GUARD.GRAPHQL=reviewThreads(first:100){nodes{id isResolved comments{nodes{author body path line originalLine url}}}}; use unresolved threads as authoritative, not badge text alone RULESET.CODERABBIT.GUARD.RERUN=after every push wait for CodeRabbit completion, then re-query unresolved threads; CodeRabbit can add new findings after earlier threads were resolved RULESET.CODERABBIT.GUARD.RESOLVE=fix validated finding -> focused tests -> commit/push -> resolveReviewThread(threadId) -> wait CI/CodeRabbit -> final unresolved_count query RULESET.CODERABBIT.GUARD.SCOPE=if a new @me open PR appears during final list, include it in the same guard pass before declaring all-open-PRs complete RULESET.TESTS.CODERABBIT_FIX=prefer exported-function behavioral tests over source-grep; lint-no-source-grep rejects readFileSync source assertions without allow-test-rule RULESET.WORKFLOW_MARKDOWN.FENCES=when editing shell snippets inside workflow markdown, preserve the opening language fence; malformed fence can create fresh CodeRabbit threads RULESET.WORKFLOW_SIZE_BUDGET=workflow-size-budget can fail otherwise-valid review fixes; keep XL workflows <=1800 lines or trim prose in same PR before final checks RULESET.GEMINI.TOOLS.ask_user=Gemini CLI has no ask_user tool; filter both AskUserQuestion and lowercase ask_user from tools frontmatter and neutralize both names in Gemini body text RULESET.GEMINI.TEST_SENTINEL=convertClaudeToGeminiAgent regression should assert tools excludes ask_user, body excludes AskUserQuestion/ask_user, and Read still maps to read_file

CI.GATE.issue-link-required=hard-fail if PR body lacks closes/fixes/resolves #<issue> CI.GATE.changeset-lint=hard-fail for user-facing code diffs unless .changeset/* or PR has no-changelog label CI.GATE.repair-sequence(PR)=create issue -> apply approval label -> edit PR body w/ closing keyword -> apply no-changelog if appropriate -> re-run checks

PR.3267.POSTMORTEM.root-cause=[missing issue link, missing changeset/no-changelog] PR.3267.POSTMORTEM.recovery=[issue#3270 created, label approved-enhancement applied, PR reopened, body includes "Closes #3270", label no-changelog applied]

WORKTREE.SEAM.current=Worktree Safety Policy Module WORKTREE.SEAM.files=[get-shit-done/bin/lib/worktree-safety.cjs, get-shit-done/bin/lib/core.cjs] WORKTREE.SEAM.interface=[resolveWorktreeContext, parseWorktreePorcelain, planWorktreePrune, executeWorktreePrunePlan] WORKTREE.SEAM.default-prune-policy=metadata_prune_only (non-destructive) WORKTREE.SEAM.decision-1=retain non-destructive default; destructive path only as explicit future opt-in scaffold

WORKSTREAM.INVARIANT.migrate-name=must normalize through canonical slug policy WORKSTREAM.INVARIANT.slug-contract=all .planning/workstreams/<name> must be addressable by set/get/status/complete WORKSTREAM.REGRESSION.test-anchor=tests/workstream.test.cjs::normalizes --migrate-name to a valid workstream slug

ARCH.SKILL.improve-codebase.next-candidates=[Workstream Name Policy Module, Workstream Progress Projection Module, Active Workstream Pointer Store Module]

WORKTREE.SEAM.test-policy=cover all decision branches in policy module before changing prune behavior WORKTREE.SEAM.test-anchors=[resolveWorktreeContext:has_local_planning|linked_worktree|not_git_repo|main_worktree, planWorktreePrune:git_list_failed|worktrees_present|no_worktrees|parser_throw_fallback, executeWorktreePrunePlan:missing_plan|skip_passthrough|unsupported_action|metadata_prune_only] WORKTREE.SEAM.invariant=parser failure must degrade to metadata_prune_only and never escalate to destructive removal WORKTREE.SEAM.execution-rule=prefer node --test tests/worktree-safety-policy.test.cjs for fast seam validation; avoid full npm test loop for seam-only changes WORKTREE.SEAM.inventory-interface=[listLinkedWorktreePaths, inspectWorktreeHealth] WORKTREE.SEAM.caller-rule=verify.cjs must consume inspectWorktreeHealth for W017 classification; no ad-hoc porcelain parsing in callers WORKTREE.SEAM.test-anchor-w017=tests/orphan-worktree-detection.test.cjs + tests/worktree-safety-policy.test.cjs WORKTREE.SEAM.inventory-snapshot=snapshotWorktreeInventory(repoRoot,{staleAfterMs,nowMs}) is canonical linked-worktree health snapshot for callers PLANNING.PATH.PARITY.sdk-project-scope=.planning/<project> (never .planning/projects/<project>); mirror planning-workspace.cjs planningDir() PLANNING.PATH.SEAM.sdk=helpers.planningPaths delegates to workspacePlanningPaths + resolveWorkspaceContext; precedence explicit-ws > env-ws > env-project > root PLANNING.PATH.SEAM.init-handlers=[initExecutePhase, initPlanPhase, initPhaseOp, initMilestoneOp] consume helpers.planningPaths().planning (no direct relPlanningPath join) WORKSTREAM.NAME.POLICY.cjs-module=get-shit-done/bin/lib/workstream-name-policy.cjs owns toWorkstreamSlug + active-name/path-segment validation WORKSTREAM.POINTER.SEAM.sdk-module=sdk/src/query/active-workstream-store.ts owns read/write self-heal for .planning/active-workstream CONFIG.SEAM.loadConfig-context=loadConfig(cwd,{workstream}) replaces env-mutation fallback; no temporary process.env GSD_WORKSTREAM rewrites


Release Notes Standard (2026-05-09, machine-oriented)

RELEASE-NOTES.SCOPE=GitHub Releases body for tags vX.Y.Z, vX.Y.Z-rcN; not CHANGELOG.md (changeset workflow owns that) RELEASE-NOTES.DEFAULT-STATE=auto-generated body is "What's Changed" PR list + Full Changelog link; treat as draft, not final RELEASE-NOTES.GATE.hotfix=manual edit required; auto-generated body for vX.Y.{Z>0} is "Full Changelog only" and must be replaced with structured body RELEASE-NOTES.GATE.rc=manual edit recommended; auto-generated PR list is acceptable for early RCs but final RC before vX.Y.0 should match standard RELEASE-NOTES.GATE.minor=auto-generated body acceptable when PR titles are clean; promote to structured body when >20 PRs or contains feature+refactor+fix mix

RELEASE-NOTES.STANDARD.taxonomy=Keep-a-Changelog 1.1.0: Added | Changed | Deprecated | Removed | Fixed | Security | Documentation RELEASE-NOTES.STANDARD.heading-level=## for category, ### for subgroup (area), - for bullet RELEASE-NOTES.STANDARD.bullet-shape=**Bold user-visible change** — explanation of what was broken or what's new, leading with symptom not implementation. Trailing (#NNN) PR ref. RELEASE-NOTES.STANDARD.subgroups=phase-planning-state | workstream | query-dispatch-cli | code-review | install | capture | docs | architecture | security RELEASE-NOTES.STANDARD.footer.hotfix=Install/upgrade: \npx get-shit-done-cc@latest` RELEASE-NOTES.STANDARD.footer.rc=Install for testing: `npx get-shit-done-cc@next` (per branch->dist-tag policy) RELEASE-NOTES.STANDARD.footer.canary=Install: `npx get-shit-done-cc@canary` RELEASE-NOTES.STANDARD.footer.full-changelog=Full Changelog: https://github.com/gsd-build/get-shit-done/compare/<prev>...<this> RELEASE-NOTES.STANDARD.intro=optional one-paragraph framing for RC/feature releases; omit for pure-fix hotfixes`

RELEASE-NOTES.SOURCE.commits=git log <prev-tag>..<this-tag> --pretty=format:'%s%n%n%b' --no-merges RELEASE-NOTES.SOURCE.changesets=.changeset/*.md (frontmatter pr: + body bullets) RELEASE-NOTES.SOURCE.pr-bodies=gh pr view <NNN> --json title,body for fixes lacking a changeset RELEASE-NOTES.SOURCE.precedence=changeset body > commit body > PR body > commit subject (prefer authored content over auto-generated)

RELEASE-NOTES.WORKFLOW.edit=gh release edit <tag> --notes-file <path> RELEASE-NOTES.WORKFLOW.view=gh release view <tag> --json body --jq .body RELEASE-NOTES.WORKFLOW.token=must use .envrc GITHUB_TOKEN per project CLAUDE.md; never ambient gh auth RELEASE-NOTES.WORKFLOW.idempotency=gh release edit overwrites body wholesale; safe to re-run after refining

RELEASE-NOTES.ANTI-PATTERN=raw "What's Changed" PR list as final body for hotfix or feature release; "Full Changelog only" body for tagged release with >0 user-facing fixes RELEASE-NOTES.ANTI-PATTERN.implementation-first=do not lead bullet with file path or function name; lead with symptom/user-visible behavior RELEASE-NOTES.ANTI-PATTERN.risk-commentary=do not include "may break", "be careful", "test thoroughly" - per global CLAUDE.md no-risk-commentary rule

RELEASE-NOTES.EXAMPLE.hotfix=v1.41.1 (https://github.com/gsd-build/get-shit-done/releases/tag/v1.41.1) - 14 fixes grouped by 6 subgroups RELEASE-NOTES.EXAMPLE.rc=v1.42.0-rc1 (https://github.com/gsd-build/get-shit-done/releases/tag/v1.42.0-rc1) - intro + Added/Changed/Fixed/Documentation taxonomy RELEASE-NOTES.EXAMPLE.minor-auto-acceptable=v1.41.0 - kept auto-generated body; many small fixes with clean conventional-commit titles

RELEASE-NOTES.TEMPLATE.hotfix=## Fixed\n\n### <subgroup>\n- **<bold change>** — <explanation>. (#<PR>)\n\n---\n\nInstall/upgrade: \npx get-shit-done-cc@latest`\n\nFull Changelog: <compare-url> RELEASE-NOTES.TEMPLATE.rc=<one-paragraph intro>\n\n## Added\n### <subgroup>\n- <change><explanation>. (#<PR>)\n\n## Changed\n### Architecture\n- <refactor><user-visible benefit>. (#<PR>)\n\n## Fixed\n### <subgroup>\n- <fix><explanation>. (#<PR>)\n\n## Documentation\n- <docs change><reason>. (#<PR>)\n\n---\n\nThis is a release candidate. Install for testing:\n```bash\nnpx get-shit-done-cc@next\n```\n\nFull Changelog: <compare-url>`

RELEASE-NOTES.RELEASE-STREAM.dev-branch=canary dist-tag (only); install via @canary RELEASE-NOTES.RELEASE-STREAM.main-branch=next (RCs) + latest (stable); install via @next or @latest RELEASE-NOTES.RELEASE-STREAM.rule=streams do not mix; do not document @canary install in RC notes or @next in canary notes


Repo-Rule Reinforcement (2026-05-09, machine-oriented)

META.RULE.canonical-source-precedence=CONTRIBUTING.md > docs/adr/* > CONTEXT.md > agent memory META.RULE.read-contributing-first=read CONTRIBUTING.md sections "Pull Request Guidelines" + "CHANGELOG Entries" before EVERY agent dispatch META.RULE.brief-must-cite-doc=agent prompts MUST quote the canonical doc line being applied; paraphrasing from predicate memory drifts and produces violations META.RULE.brief-no-paraphrase=writing "k040 — never leave changelog box unchecked" caused 5 of 8 agents to edit CHANGELOG.md in violation of CONTRIBUTING.md L110

PRED.k320.signal=changelog-direct-edit-forbidden PRED.k320.canonical-source=CONTRIBUTING.md L110-123 PRED.k320.rule=do not edit CHANGELOG.md in feature/fix/enhancement PRs PRED.k320.cure=drop .changeset/<adj>-<noun>-<noun>.md fragment ONLY PRED.k320.tool=npm run changeset -- --type <T> --pr <NNN> --body "..." PRED.k320.types=Added|Changed|Deprecated|Removed|Fixed|Security PRED.k320.opt-out-label=no-changelog PRED.k320.ci-enforcement=scripts/changeset/lint.cjs PRED.k320.ci-paths-monitored=bin/ get-shit-done/ agents/ commands/ hooks/ sdk/src/ PRED.k320.recovery=open Removed-typed cleanup PR deleting only the redundant row PRED.k320.evidence=PR #3302 merge-conflict against #3308 CHANGELOG.md row 2026-05-09

PRED.k321.signal=cr-outside-diff-range-finding PRED.k321.shape=CR posts "[!CAUTION] outside the diff" findings in review BODY, not in reviewThreads PRED.k321.poll-shape=parse pulls/<n>/reviews body AND graphql reviewThreads PRED.k321.resolution=address in code; no GraphQL resolveReviewThread needed for body-only findings PRED.k321.evidence=PRs #3304/#3305 (2026-05-09): real Minor/Major findings in body, 0 threads

PRED.k322.signal=cr-sustained-throttle PRED.k322.distinct-from=k080 PRED.k322.shape=ack posted, real review never lands within [5s, 410s] cooldown after burst of N PRs <15min PRED.k322.cure-1=2nd retrigger ~10min after first ack PRED.k322.cure-2=if silent at 50min, treat as silent-pass with maintainer flag in merge-commit body PRED.k322.merge-gate-impact=k070 real_coderabbit_review_present unsatisfied; requires maintainer judgment PRED.k322.evidence=PR #3306 (2026-05-09): 0 reviews after 50min + 2 retriggers

PRED.k323.signal=sibling-audit-cross-pr-overlap PRED.k323.shape=2+ open issues touch same canonical bug site; each fix's sibling-audit produces overlapping diff PRED.k323.cure-pre-dispatch=brief one agent canonical-owner; brief others to EXCLUDE shared site PRED.k323.cure-alt=consolidate into single PR when 2+ issues share root cause PRED.k323.recovery=close smaller PR as "subsumed by #N" or rebase second to drop overlap hunk PRED.k323.evidence=#3300 (#3297) overlapped #3306 (#3298) on add-backlog.md hunks 2026-05-09

PRED.k324.signal=agent-terminates-mid-monitor PRED.k324.k095-restatement=k095 confirmed shape: agent reports "waiting for monitor" / "tests still running" then terminates PRED.k324.cure=verify via gh api on every agent-completion notification; never trust narrative PRED.k324.poll-shape=gh pr view <n> --json mergeStateStatus,statusCheckRollup + pulls/<n>/reviews + graphql reviewThreads + issues/<n>/comments tail PRED.k324.evidence=2026-05-09 session: 5+ mid-monitor terminations across PRs #3232/#3271/#3251/#3255/#3262

PRED.k325.signal=worktree-branch-lock-on-force-push PRED.k325.shape=git checkout <branch> errors "already used by worktree at <agent-worktree>" PRED.k325.cure=detached-HEAD: git checkout --detach $(git ls-remote origin <branch>); modify; commit; git push --force-with-lease=<branch>:<remote-sha> origin HEAD:refs/heads/<branch> PRED.k325.cleanup=git worktree remove --force <path> for aged agent worktrees PRED.k325.evidence=2026-05-09 CHANGELOG.md strip on PRs #3300/#3302/#3304/#3305 required detached-HEAD

PRED.k326.signal=brief-contradicts-canonical-doc PRED.k326.shape=N parallel agents amplify a single brief-vs-doc contradiction into N violations PRED.k326.cure=quote canonical doc verbatim in brief; mentally simulate "if all N agents follow this brief literally, do they violate any rule?" PRED.k326.evidence=2026-05-09 brief "k040 — update CHANGELOG.md" → 5 of 8 agents violated CONTRIBUTING.md L110

PRED.k327.signal=cr-ack-vs-real-review PRED.k327.ack-shape=body "✅ Actions performed - Full review triggered" PRED.k327.real-review-shape=body starts "Actionable comments posted: N" OR "[!CAUTION] Some comments are outside the diff" PRED.k327.distinguish-key=len(pulls/<n>/reviews) — ack=0, real=≥1 PRED.k327.cooldown-normal=[5s, 410s] PRED.k327.cooldown-throttled=k322

PRED.k328.signal=pr-template-typed-heading-required PRED.k328.canonical-source=CONTRIBUTING.md L101 PRED.k328.k100-restatement=heading must match issue class: bug→## Fix PR, enhancement→## Enhancement PR, feature→## Feature PR PRED.k328.audit-list=[heading-matches-class, closing-keyword-present, changeset-fragment-or-no-changelog-label]

PRED.k329.signal=changeset-fragment-canonical-shape PRED.k329.canonical-source=CONTRIBUTING.md L112-117 + .changeset/README.md PRED.k329.filename=.changeset/<adj>-<noun>-<noun>.md PRED.k329.frontmatter=---\\ntype: <Added|Changed|Deprecated|Removed|Fixed|Security>\\npr: <NNN>\\n--- PRED.k329.body=**<Bold user-visible change>** — <symptom-led explanation>. (#<NNN>) PRED.k329.observed-clean=#3299 sunny-ibex-wave, #3301 sturdy-rams-caper, #3306 3298-phase-dir-prefix-drift-workflows

PRED.k330.signal=mempalace-diary-not-callable-by-ai PRED.k330.shape=mempalace MCP tools require explicit user call; AI cannot trigger PRED.k330.fallback=append predicate-format findings directly to CONTEXT.md

PRED.k331.signal=close-with-no-comment-is-literal PRED.k331.shape=instruction "close with no comment (rationale)" — parenthetical is rationale, NOT comment body PRED.k331.k101-restatement=k101 includes close-time --comment flag; rationale belongs in subsuming PR's squash-merge body PRED.k331.cure=gh pr close <n> with NO --comment flag PRED.k331.recovery=if violation lands, gh api -X DELETE repos/<o>/<r>/issues/comments/<id> PRED.k331.evidence=2026-05-09 wave-3: violation on #3300 close, deleted within 30s

PROC.AGENT-DISPATCH.preflight=[read-CONTRIBUTING.md-fresh, read-relevant-ADRs, cite-specific-line-in-brief, require-closing-keyword, require-changeset-fragment, forbid-CHANGELOG.md-edit, require-isolation-worktree, forbid-self-PR-comment, mandate-trust-but-verify] PROC.AGENT-DISPATCH.parallel-overlap-audit=before dispatching N sibling-audit fixers, compute file-set union and assign canonical owners PROC.AGENT-DISPATCH.completion-verify=run k324.poll-shape on every agent-completion notification

PROC.MERGE-WAVE.ordering=[wave1: isolated-files, wave2: CHANGELOG-only-overlap (better: strip per k320), wave3: same-file-overlap with explicit decision] PROC.MERGE-WAVE.preflight=gh pr view <n> --json files for every PR; identify overlap pairs; surface to maintainer PROC.MERGE-WAVE.changelog-strip-pattern=detached-HEAD per k325 + git checkout main -- CHANGELOG.md + commit + force-with-lease PROC.MERGE-WAVE.merge-tool=gh pr merge <n> --squash --delete-branch PROC.MERGE-WAVE.merge-tool-warning=delete-branch may fail with "used by worktree at" — harmless; remote branch still deleted

Triage+Merge Wave Outcome (2026-05-09T15:47Z, machine-oriented)

WAVE.2026-05-09.scope=trek-e-authored issues, classes=[bug, enhancement, feature] WAVE.2026-05-09.dispatched=8 WAVE.2026-05-09.merged=7 WAVE.2026-05-09.closed-as-subsumed=1 WAVE.2026-05-09.skipped-mvp-epic=[#2826, #2885, #2882, #2879, #2877, #2875]

WAVE.PR.3299.issue=3290 WAVE.PR.3299.class=bug WAVE.PR.3299.fix=agents/gsd-intel-updater.md layout-detection block gated on framework-repo check WAVE.PR.3299.cr-state=clean (No actionable comments) WAVE.PR.3299.merged=2026-05-09T15:39:16Z

WAVE.PR.3301.issue=3232 WAVE.PR.3301.class=enhancement WAVE.PR.3301.fix=docs/contributor-standards.md first-cut + CONTRIBUTING.md cross-link + 1 CR thread resolved (MD040) WAVE.PR.3301.cr-state=clean post-fix WAVE.PR.3301.merged=2026-05-09T15:39:24Z

WAVE.PR.3308.issue=3262 WAVE.PR.3308.class=enhancement WAVE.PR.3308.fix=extract get-shit-done/bin/lib/plan-scan.cjs scanPhasePlans; port 4 call sites in init/state/roadmap/phase WAVE.PR.3308.cr-state=2 reviews real, 1 thread resolved WAVE.PR.3308.merged=2026-05-09T15:39:32Z WAVE.PR.3308.violation=carried redundant CHANGELOG.md row in violation of k320; cleanup task spawned

WAVE.PR.3302.issue=3271 WAVE.PR.3302.class=enhancement WAVE.PR.3302.fix=docs/adr/0005 + 0006 + README index + tests/enh-3271-sdk-adr-structure.test.cjs WAVE.PR.3302.cr-state=1 review, 1 thread resolved (ADR self-ref test) WAVE.PR.3302.changelog-strip=force-pushed 2026-05-09T15:35Z WAVE.PR.3302.merged=2026-05-09T15:46:28Z

WAVE.PR.3304.issue=3255 WAVE.PR.3304.class=enhancement WAVE.PR.3304.fix=get-shit-done/bin/gsd-tools.cjs --json-errors flag + GSD_JSON_ERRORS env + docs/json-errors.md taxonomy + usage-string disclosure (CR k321 finding addressed) WAVE.PR.3304.cr-state=1 review (k321 outside-diff finding fixed in code) WAVE.PR.3304.changelog-strip=force-pushed 2026-05-09T15:35Z WAVE.PR.3304.merged=2026-05-09T15:46:35Z

WAVE.PR.3305.issue=3251 WAVE.PR.3305.class=enhancement WAVE.PR.3305.fix=command-aliases.generated.cjs NON_FAMILY entries (40) + sdk gen-command-aliases.ts typed-export preservation (CR k321 Major finding addressed) WAVE.PR.3305.cr-state=1 review (k321 outside-diff finding fixed in code) WAVE.PR.3305.changelog-strip=force-pushed 2026-05-09T15:35Z WAVE.PR.3305.merged=2026-05-09T15:46:41Z

WAVE.PR.3306.issue=3298 WAVE.PR.3306.class=bug WAVE.PR.3306.fix=phase-dir prefix drift fixed in 3 sites (add-backlog.md + import.md + plan-milestone-gaps.md) per k015 sibling-audit WAVE.PR.3306.cr-state=k322 sustained-throttle silent pass — 0 reviews after 50min + 2 retriggers, CI green WAVE.PR.3306.subsumes=PR #3300 (#3297 add-backlog dedicated fix) WAVE.PR.3306.merged=2026-05-09T15:47:16Z

WAVE.PR.3300.issue=3297 WAVE.PR.3300.class=bug WAVE.PR.3300.fix=add-backlog.md project_code prefix (focused #3297 fix) WAVE.PR.3300.outcome=closed-as-subsumed by #3306; issue #3297 manually closed WAVE.PR.3300.k323-evidence=overlapped #3306 add-backlog.md hunks with different prefix idiom WAVE.PR.3300.k331-violation=close-with-comment violation, comment deleted within 30s

WAVE.LESSON.changelog-policy-violation-multiplier=brief contradicting CONTRIBUTING.md L110 produced violations on 5 of 8 PRs (#3300, #3302, #3304, #3305, #3308); k326 + k320 capture WAVE.LESSON.cr-throttle-burst-correlation=8 PRs in <15min triggered k322 sustained-throttle on multiple PRs (#3306 worst case) WAVE.LESSON.sibling-audit-overlap=k015-family parallel dispatch on #3297 + #3298 produced k323 add-backlog.md cross-PR overlap WAVE.LESSON.agent-narrative-unreliable=k095/k324 confirmed at scale: 5 of 8 agents terminated mid-monitor with stale claims requiring direct verification WAVE.LESSON.k101-still-trips=even after CONTEXT.md k101 reinforcement, agent of record posted self-PR comment on close; k331 adds explicit close-time literal-instruction guard


Recent Defect Anti-Patterns (2026-05-09, machine-oriented)

DEFECT.SCOPE.window=PRs #3306..#3325 + sibling fixes #3240/#3242/#3245/#3257/#3261/#3267/#3286/#3287 DEFECT.FORMAT=class.sub-key=value | classes are greppable; each class carries detect / fix / anchor sub-keys when applicable

DEFECT.PORT-DRIFT.cjs-sdk.symptom=SDK port (sdk/src/query/*.ts) cites bin/lib/*.cjs source in docstring; CJS gets a fix or new constant; SDK lags silently DEFECT.PORT-DRIFT.cjs-sdk.examples=#3317 (skills missing from SDK GSD_MANAGED_DIRS), #3240 (extractFrontmatter anchor), #3226 (phase.add --dry-run), #3243 (cjs dotted canonical), #3229 (model catalog source-of-truth) DEFECT.PORT-DRIFT.cjs-sdk.detect=grep canonical constant in CJS, then in SDK; if both present compare values; if only CJS present treat as port-gap until proven intentional DEFECT.PORT-DRIFT.cjs-sdk.fix-forward=add SDK-side behavioral test mirroring the CJS test; or extract shared JSON/TS module if both runtimes can consume it DEFECT.PORT-DRIFT.cjs-sdk.anchor=tests/config-schema-sdk-parity.test.cjs is the canonical pattern — replicate per port-pair

DEFECT.REMOVED-BUT-NEEDED.symptom=file/key removed because "scoped under sdk/" or "no longer used" without verifying every consumer (workflows, docs, manifests, npm scripts) DEFECT.REMOVED-BUT-NEEDED.examples=#3316 root package-lock.json (root package.json declares deps; workflows use cache:'npm' + npm ci), e3b52c70 docs referenced removed /gsd-new-workspace DEFECT.REMOVED-BUT-NEEDED.detect=before deletion, grep filename across .github/workflows, get-shit-done/, docs/, package.json scripts, sdk/scripts; if any reference exists removal is incomplete DEFECT.REMOVED-BUT-NEEDED.fix-forward=restore the file or update every consumer in the same commit; do not paper over with --no-package-lock or workflow workarounds that lose reproducibility

DEFECT.STATE-TRAMPLE.symptom=state-mutation paths overwrite curated values when body-derived computation is narrower than what's stored in frontmatter DEFECT.STATE-TRAMPLE.examples=#3242 (Last Activity overwrote progress.completed_plans), #3257 (nested plans/ files uncounted), #3261 (buildStateFrontmatter), #3265 (canonical fields), #3286 (record-metric/add-decision sections) DEFECT.STATE-TRAMPLE.detect=any state writer that calls buildStateFrontmatter without preserving existing progress.* keys; any mutation surface that does not honor shouldPreserveExistingProgress DEFECT.STATE-TRAMPLE.fix-forward=route through state-document.cjs/.ts shouldPreserveExistingProgress + normalizeProgressNumbers (extracted in #3316 SDK-first seams)

DEFECT.PHASE-DIR-PREFIX-DRIFT.symptom=multiple workflow files independently construct .planning/phases/{NN}-{slug} paths; project_code prefix or slug normalization missing in some surfaces DEFECT.PHASE-DIR-PREFIX-DRIFT.examples=#3287 (init.phase-op + init.plan-phase first-touch), #3306/PRED.k015 (plan-milestone-gaps + import + add-backlog), #3297/#3298 (sibling reports) DEFECT.PHASE-DIR-PREFIX-DRIFT.detect=grep mkdir/touch/path.join with {NN}-{slug} or padded_phase + phase_slug; if not consuming expected_phase_dir from init.* JSON it is drifting DEFECT.PHASE-DIR-PREFIX-DRIFT.fix-forward=consume expected_phase_dir from init.phase-op / init.plan-phase output; never re-construct from padded_phase + slug in workflow steps DEFECT.PHASE-DIR-PREFIX-DRIFT.anchor=tests/bug-3298-phase-dir-prefix-drift-in-workflows.test.cjs (broad regression across workflow surfaces)

DEFECT.STACKED-PR-AUTO-RETARGET.symptom=PR #N is stacked on branch B; branch B merges to main and is deleted; GitHub does not reliably auto-retarget #N to main; PR shows DIRTY/CONFLICTING with phantom conflicts DEFECT.STACKED-PR-AUTO-RETARGET.examples=#3311 base fix/3255-add-json-errors-mode-gsd-tools deleted after #3304 merged DEFECT.STACKED-PR-AUTO-RETARGET.detect=ls-remote shows base ref absent; PR base still points at the deleted ref; mergeable=CONFLICTING with no real diff conflicts DEFECT.STACKED-PR-AUTO-RETARGET.fix-forward=PATCH /repos/{owner}/{repo}/pulls/{N} -f base=main; rebase head onto current main; resolve carry-over commits (parent commits will auto-drop as patch contents already upstream)

DEFECT.BOT-BRANCH-STALE-BASE.symptom=auto-branch.yml creates fix/{N}-{slug} when issue is filed; branch is anchored to issue-creation main; by the time work begins, main has moved DEFECT.BOT-BRANCH-STALE-BASE.examples=#3309 fix/3309-checkpoint-type-human-verify-burns-token (was at e14ef535; main at 2e87c60a) DEFECT.BOT-BRANCH-STALE-BASE.detect=git merge-base origin/<bot-branch> origin/main returns the bot branch tip — confirms the bot branch is an ancestor of main, just stale DEFECT.BOT-BRANCH-STALE-BASE.fix-forward=git checkout --detach origin/main; do work; git checkout -b <same-branch-name>; force-push with --force-with-lease

DEFECT.SUPERSEDED-CONCURRENT-PRS.symptom=multiple in-flight PRs attack overlapping subsets of the same issue; the broadest one merges first; narrower siblings remain open with phantom conflicts DEFECT.SUPERSEDED-CONCURRENT-PRS.examples=#3303 + #3307 superseded by #3306 (all addressing #3297/#3298 project_code prefix family) DEFECT.SUPERSEDED-CONCURRENT-PRS.detect=after a fix lands on main, grep recently-merged PR title for shared keyword/issue; check open PRs touching same files; if open PRs are subsets of merged work they are superseded DEFECT.SUPERSEDED-CONCURRENT-PRS.fix-forward=close superseded PRs via gh api PATCH state=closed; do not comment on self-authored PRs (k101); the link to the merged PR makes supersession discoverable in PR history

DEFECT.PROMPT-INJECTION-SCAN-COLLISION.symptom=custom XML element name in agent .md file matches scripts/scan-prompt-injection regex; legitimate agent vocabulary trips the security gate DEFECT.PROMPT-INJECTION-SCAN-COLLISION.examples=#3309 added a bare 'human' element (angle-bracket-wrapped) for verify-block harvesting; tests/prompt-injection-scan.test.cjs flags angle-bracket-wrapped names matching system|assistant|human (open or close form) DEFECT.PROMPT-INJECTION-SCAN-COLLISION.detect=any new bare tag in agents/*.md DEFECT.PROMPT-INJECTION-SCAN-COLLISION.fix-forward=hyphenate the tag (<human-check>, <assistant-prompt>) — scanner regex matches bare names only

DEFECT.INVENTORY-DRIFT.symptom=new file added under get-shit-done/references/ or get-shit-done/workflows/ without updating docs/INVENTORY.md count + row AND docs/INVENTORY-MANIFEST.json DEFECT.INVENTORY-DRIFT.examples=#3309 planner-human-verify-mode.md (caught by tests/inventory-counts.test.cjs + tests/inventory-manifest-sync.test.cjs) DEFECT.INVENTORY-DRIFT.detect=tests/inventory-* fails with "References (N shipped) disagrees with filesystem" or "New surfaces not in manifest" DEFECT.INVENTORY-DRIFT.fix-forward=update INVENTORY.md headline count + row entry + footnote count; run node scripts/gen-inventory-manifest.cjs --write to regen INVENTORY-MANIFEST.json; only families.workflows is canonical (top-level workflows key is stale)

DEFECT.AGENT-FILE-SIZE-CAP-BREACH.symptom=adding to agents/gsd-planner.md (or other large agent files) exceeds the 45K char extraction-evidence threshold DEFECT.AGENT-FILE-SIZE-CAP-BREACH.state=gsd-planner.md is already 49,121 chars on main (over 45K); test fails on main; net-new content makes it strictly worse DEFECT.AGENT-FILE-SIZE-CAP-BREACH.detect=tests/planner-decomposition.test.cjs ("planner is under 45K chars (proves mode sections were extracted)") and tests/reachability-check.test.cjs ("file stays under 50000 char limit") DEFECT.AGENT-FILE-SIZE-CAP-BREACH.fix-forward=mirror MVP mode pattern — extract full rules to get-shit-done/references/planner-<mode>.md, leave a slim Detection section in the agent file with @-reference to the new file

DEFECT.CHANGESET-PR-FIELD-DRIFT.symptom=.changeset/*.md frontmatter pr: value is the issue number, a guess made before PR opened, or a stale stacked-PR number DEFECT.CHANGESET-PR-FIELD-DRIFT.examples=#3316 (pr:3312 was the issue), #3325 (pr:3319 was a guess); already covered in CONTEXT.md L94 + L186 but recurs every cycle DEFECT.CHANGESET-PR-FIELD-DRIFT.detect=changeset pr: value mismatches the actual PR number returned by gh api POST /pulls DEFECT.CHANGESET-PR-FIELD-DRIFT.fix-forward=author changeset with placeholder pr:0; immediately after gh api POST /pulls returns the number, edit changeset and amend or follow-up commit; never guess

DEFECT.WORKTREE-FETCH-SHA-DIVERGENCE.symptom=in a worktree, git fetch origin pull/N/head:pr-N produces commits with SHAs different from the actual remote PR head SHA; force-push rejected as non-fast-forward despite recent fetch DEFECT.WORKTREE-FETCH-SHA-DIVERGENCE.examples=this session, branch fix/3309-... and pr-3316 DEFECT.WORKTREE-FETCH-SHA-DIVERGENCE.detect=git rev-parse HEAD~1 vs git rev-parse origin/<actual-branch-ref> — if they differ despite fetch the local copy was rewritten by some checkout-time hook DEFECT.WORKTREE-FETCH-SHA-DIVERGENCE.fix-forward=git checkout --detach origin/<actual-remote-branch> directly; do work from detached HEAD; push HEAD:<remote-branch>

DEFECT.WINDOWS-FS-OPS.symptom=fs.renameSync / fs.copyFileSync hits EPERM/EBUSY on Windows when antivirus or another process holds a transient handle on the target DEFECT.WINDOWS-FS-OPS.examples=c47c2c5d build-hooks rename → copy fallback, d2412271 install Windows persistent SDK shim DEFECT.WINDOWS-FS-OPS.detect=any rename/copy in build/install path without try/catch fallback DEFECT.WINDOWS-FS-OPS.fix-forward=catch EPERM/EBUSY/EACCES, fall back to copy + unlink with retry, surface degraded-mode message; never silently swallow

DEFECT.UNBOUNDED-SUBPROCESS.symptom=git/npm subprocess shelled out without timeout; CLI hangs indefinitely on stuck remote, large repo, or missing network DEFECT.UNBOUNDED-SUBPROCESS.examples=a33cbe72 worktree fix bound git subprocesses with timeout DEFECT.UNBOUNDED-SUBPROCESS.detect=execSync/execFileSync/spawnSync without timeout option in non-test code; especially git list-worktrees, git fetch, npm view DEFECT.UNBOUNDED-SUBPROCESS.fix-forward=add timeout (5-30s for git, 60s for npm); on timeout return degraded result + structured warning rather than throw

DEFECT.PARSER-BRITTLE-MARKER-WHITELIST.symptom=human-output parser whitelists known markers (severity, status); silently drops unfamiliar markers as malformed DEFECT.PARSER-BRITTLE-MARKER-WHITELIST.examples=ac518646/#3263 code-review SUMMARY parser rejected BL-/blocker variants DEFECT.PARSER-BRITTLE-MARKER-WHITELIST.detect=any parser with hard-coded marker list; any parser that returns empty for non-matching input without warning DEFECT.PARSER-BRITTLE-MARKER-WHITELIST.fix-forward=accept variants explicitly (case-insensitive, hyphen/space alternatives); on unknown marker emit a structured WARN with the original line so the human can fix the source

DEFECT.HALT-COST-PATTERN.symptom=architecturally-sound checkpoint pattern produces hidden token cost because subagent context is discarded across the pause and respawn DEFECT.HALT-COST-PATTERN.examples=#3309 checkpoint:human-verify (mid-flight halt = full executor cold-start per round-trip; reporter measured "tens of thousands of tokens" per halt) DEFECT.HALT-COST-PATTERN.detect=any subagent-spawning workflow with mid-flight pause-and-resume that does not preserve subagent context DEFECT.HALT-COST-PATTERN.fix-forward=offer config flag for end-of-phase aggregation; if cost dominates make end-of-phase the default; route deferred items through existing verifier surface, do not invent new writer

DEFECT.HOOK-OVER-ENFORCEMENT.symptom=PreToolUse hook keeps blocking gh pr edit / gh issue edit even after all required files are read in the session DEFECT.HOOK-OVER-ENFORCEMENT.examples=this session repeatedly hit "Refusing to run gh issue create|edit / gh pr create|edit" despite reading every listed file DEFECT.HOOK-OVER-ENFORCEMENT.detect=hook re-fires on each invocation regardless of session-state read receipts DEFECT.HOOK-OVER-ENFORCEMENT.fix-forward=use gh api -X PATCH repos/{owner}/{repo}/pulls/{N} or repos/{owner}/{repo}/issues/{N} directly — same effect, hook regex does not match

DEFECT.DEFAULT-FLIP-DOCUMENTATION.symptom=PR flips a config default but does not call out the migration semantics (when does the new default take effect; existing configs vs new configs; what the opt-back-in looks like) DEFECT.DEFAULT-FLIP-DOCUMENTATION.examples=#3309 v2 default flip from mid-flight to end-of-phase DEFECT.DEFAULT-FLIP-DOCUMENTATION.detect=any PR that changes a default value in CONFIG_DEFAULTS or buildNewProjectConfig; check that PR body Breaking Changes section explicitly covers (a) when the new default takes effect, (b) opt-back-in command, (c) effect on in-flight artifacts DEFECT.DEFAULT-FLIP-DOCUMENTATION.fix-forward=template — "new default takes effect when .planning/config.json is rewritten (config-set, fresh project, regenerated config); existing artifacts continue to work; opt-back-in: gsd config-set <key> <old-value>"

DEFECT.SOURCE-GREP-IN-NEW-TESTS.symptom=new test file uses readFileSync + .includes() / .match() against source code (CONTEXT.md L82); contradicts the test rule lint script DEFECT.SOURCE-GREP-IN-NEW-TESTS.detect=tests/lint-no-source-grep.cjs (npm run lint:tests) fails with line-number-precise violation; or test reads sdk/dist/* artifacts in CI where dist may not exist DEFECT.SOURCE-GREP-IN-NEW-TESTS.fix-forward=replace with runGsdTools(...) behavioral test capturing JSON; if asserting agent .md content (which IS the runtime contract) add // allow-test-rule: source-text-is-the-product with one-line justification

DEFECT.GENERATIVE-PRIORITY=these defect classes share a common root: parallel implementations diverge silently because no parity test enforces equality at the test layer DEFECT.GENERATIVE-FIX=for any new constant/array/parser shared between CJS and SDK (or between two workflow surfaces), the same commit MUST add a parity assertion that fails when the two diverge DEFECT.GENERATIVE-EXEMPLAR=tests/config-schema-sdk-parity.test.cjs (asserts SDK VALID_CONFIG_KEYS == CJS VALID_CONFIG_KEYS); tests/bug-3298-phase-dir-prefix-drift-in-workflows.test.cjs (asserts every workflow surface uses expected_phase_dir)

Shell Command Projection Module

Module owning all OS-facing I/O for the tool: runtime-aware command-text rendering (hook commands, PATH action lines, shim scripts), subprocess dispatch (execGit, execNpm, execTool, probeTty), and platform file I/O (platformWriteSync, platformReadSync, platformEnsureDir). Single seam for platform-conditional logic — one place to fix any shell or file write regression across Windows, macOS, and Linux. Lives in get-shit-done/bin/lib/shell-command-projection.cjs. See ADR-0009.

Session learnings

2026-05-13 — Shell Command Projection Module expansion (issues #3465–#3468)

  • scope: shell-command-projection.cjs extended to own subprocess dispatch + platform file I/O
  • ADR-0009 "does not execute" constraint superseded; ADR-0010 File Operation Engine superseded
  • new exports: execGit, execNpm, execTool, probeTty, normalizeContent, platformWriteSync, platformReadSync, platformEnsureDir
  • result shape invariant: all exec* return { exitCode, stdout, stderr }; never throw on non-zero exit
  • platform policy owned at seam: shell: process.platform === 'win32' lives only in execNpm; probeTty returns null on Windows
  • normalization policy: platformWriteSync owns full normalizeMd for .md; CRLF→LF + trailing newline for all others; callers must not pre-call normalizeMd
  • normalizeContent(filePath, content) is the pure typed surface tests assert on — no file content read-back in tests (CONTRIBUTING.md rule)
  • _normalizeMd is re-implemented inline (not imported from core.cjs) to avoid circular dep
  • atomicWriteFileSync, safeReadFile, normalizeMd remain in core.cjs exports until Phase 4 (#3468)
  • phase gate: no call site migration until Phase 1 branch merged; Phase 2 (#3466) targets 6 subprocess files; Phase 3 (#3467) targets 15 fs files (215 call sites); Phase 4 (#3468) removes compat exports
  • branch: feat/3465-shell-projection-platform-io-seam
  • test file: tests/shell-command-projection-dispatch.test.cjs — 31 behavioral tests, node:test + node:assert/strict, no source-grep

2026-05-13 — CodeRabbit guard + merge recovery (PR #3464)

  • scope: gsd-build/get-shit-done only; run all gh checks with --repo gsd-build/get-shit-done.
  • invariant: review completion requires all three gates:
    • CI required checks green
    • CodeRabbit green
    • GraphQL unresolved review threads = 0
  • failure mode: mergeStateStatus=DIRTY can exist even when CodeRabbit + thread count are clean.
    • remediation: rebase/replay onto latest origin/main before treating PR as merge-ready.
  • failure mode: primary worktree rebase blocked by unrelated untracked files.
    • remediation: use isolated worktree seeded from origin/main, replay feature commits there, then push --force-with-lease to PR head.
  • replay conflict learned seam:
    • file: get-shit-done/bin/lib/config.cjs
    • keep both behaviors during conflict resolution:
      • existing ship.pr_body_sections and workflow.human_verify_mode validations
      • new review.default_reviewers normalization path
  • post-rebase CI drift classes to verify immediately:
    • SDK schema parity (tests/config-schema-sdk-parity.test.cjs)
    • docs inventory counts (tests/inventory-counts.test.cjs)
    • inventory manifest sync (tests/inventory-manifest-sync.test.cjs)
    • slash namespace invariant (tests/bug-2543-gsd-slash-namespace.test.cjs)
  • concrete regressions fixed in this session:
    • add review.default_reviewers to sdk/src/query/config-schema.ts
    • add review-reviewer-selection.cjs row + count update in docs/INVENTORY.md
    • regenerate docs/INVENTORY-MANIFEST.json
    • replace legacy /gsd-review mention with canonical /gsd:review in source comments

2026-05-13 — Phase 1 rebase + PR open (#3465)

  • branch: feat/3465-shell-projection-platform-io-seam; PR: #3470 on gsd-build/get-shit-done
  • rebase pattern: 4 sibling-branch commits (#3464) were skipped as "patch contents already upstream" — expected when a feature branch is cut before a sibling merges to main
  • untracked files block rebase: git stash --include-untracked before git rebase origin/main
  • add/add conflict resolution: when HEAD side is empty (feature didn't have the file) and ours has the content, git checkout --theirs <file> is correct
  • content conflict resolution: git checkout --ours CONTEXT.md + manual append of our new sections — preserves main's full content while adding our additions
  • docs/research/ was untracked pre-rebase but came in from main during rebase — already tracked, no action required
  • force-push after rebase: git push --force-with-lease origin <branch> (not --force)
  • stash pop can fail if main brought in the same files: drop the stash with git stash drop when files are already present
  • PR hook requires reading all listed contribution files before gh pr create will execute — including pull_request_template.md, all typed templates, and all issue templates
  • changeset type for seam additions: Changed (not Added) — expands existing module, not a new standalone feature

2026-05-14 — Executor failure classification + cross-runtime provider sentinels (#3095, PR #3490)

  • scope: new SDK query agent.classify-failure parses free-text agent return body and returns { class: 'quota-exceeded' | 'classify-handoff-bug' | 'unknown-failure', sentinel?, retryAfterSeconds? }
  • handler: sdk/src/query/agent-failure-classifier.ts; registered in command-static-catalog-foundation.ts (DECISION_ROUTING_STATIC_CATALOG) and command-manifest.non-family.ts (mutation: false, outputMode: 'json')
  • workflow wiring lives in get-shit-done/workflows/execute-phase.md step 7; replaces single-branch "real failure" routing with class-distinct prompts (quota → wait-for-reset; classify-handoff-bug → existing spot-check; unknown → existing continue/stop)
  • sentinel-order invariant: most specific first. 429 beats too many requests; quota beats resource_exhausted. All matches case-insensitive; canonical sentinel value is lower-cased form
  • cross-runtime sentinel coverage (catalogued in docs/research/provider-rate-limit-signals.md):
    • Anthropic / Claude Code: usage limit, rate limit, quota, 429, retry-after
    • GitHub Copilot CLI: rate_limit (stem covers rate_limited, rate_limit_error, user_weekly_rate_limited)
    • OpenAI Codex CLI: 429, usage_limit_reached, too many requests
    • Google Gemini CLI: RESOURCE_EXHAUSTED, exceeded your
  • precedence rule: quota sentinel wins over classifyHandoffIfNeeded bug when both appear — a quota-kill that also crashed the completion handler is still a quota event, recovery differs
  • proactive-signal forward path (NOT yet usable from GSD orchestrator): Anthropic exposes anthropic-ratelimit-{requests,tokens,input-tokens}-remaining + retry-after; Agent SDK emits RateLimitEvent with status: 'allowed' | 'allowed_warning' | 'rejected'. Claude Code subprocess does NOT forward these to hooks/statusline today — tracked in upstream anthropics/claude-code#33820, #22407, #32796
  • retry-after parser uses \bretry[-_ ]after[:\s]+(\d+)\b to avoid embedded-word false matches like noretry-after

2026-05-14 — STATE.md idempotency oracle for state.complete-phase (#3489, PR #3499)

  • cmdStateCompletePhase in get-shit-done/bin/lib/state.cjs previously wrote STATE.md unconditionally; re-invoking on a phase already superseded by a later one rewound Status / Last Activity / Current Position to stale values silently
  • invariant: STATE.md's canonical Current Phase field is the idempotency oracle. If the field names a phase distinct from the resolved target, return { updated: [], phase, idempotent: true, note: "phase already superseded; no-op" } without touching STATE.md
  • legacy-bridge banner 'state.complete-phase' not in native registry; falling back to gsd-tools.cjs is the user-visible signal that this path runs the .cjs handler, not a typed SDK port — when fixing handler bugs, the .cjs path is canonical until ported

2026-05-14 — Same-phase short-form depends_on requires its own lookup index (#3488, PR #3501)

  • bug: DAG resolver in sdk/src/query/phase.ts (phasePlanIndex Pass 2) only resolved depends_on via planMap (full-stem <phase>-<plan>) and canonicalToId (canonical-prefix); same-phase short-form [NN] was never indexed, so '01' never matched plan IDs keyed as '99.9-01'
  • fix: add third index shortFormToId built from extractCanonicalPlanId(p.id).slice(lastIndexOf('-') + 1) — works for integer, letter-suffixed, and decimal phase IDs. Resolution order: planMapcanonicalToIdshortFormToId
  • diagnostic invariant: when a depends_on reference cannot be resolved, the resolver MUST emit unresolvedDeps[] warnings (Plan X: unresolved depends_on reference 'NN' — no matching plan in phase). Without this, dropped edges hide behind a downstream wave-mismatch warning that misleadingly points at the wave declaration
  • reporter-claims-narrower-scope pattern: #3488 reporter believed integer-phase short-form worked; regression test proved it was equally broken — write the test for BOTH claimed-working and claimed-broken cases

2026-05-14 — Nested git worktree detection in new-project bootstrap (#3491, PR #3502)

  • pathExistsInternal(cwd, '.git') is the wrong primitive for detecting an existing git context — it only sees .git directly in cwd, missing the case where cwd is a subdirectory of an outer worktree
  • right primitive: git rev-parse --is-inside-work-tree + git rev-parse --show-toplevel, surfaced through the existing execGit seam in get-shit-done/bin/lib/core.cjs
  • new helper gitWorktreeInfoInternal(cwd) returns { has_git, git_worktree_root, in_nested_subdir }; surfaced via cmdInitNewProject and cmdInitIngestDocs (bin/lib/init.cjs) and mirrored in SDK sdk/src/query/init.ts / init-complex.ts
  • workflow gate: new-project.md and ingest-docs.md now refuse git init when in_nested_subdir: true and surface "planning files will track to the outer worktree" warning instead of creating nested .git
  • when adding a new large workflow that crosses the prompt-injection-scan size threshold, append to ALLOWLIST in tests/prompt-injection-scan.test.cjs alongside discuss-phase.md / execute-phase.md / plan-phase.md

2026-05-14 — extractCurrentMilestone generic Phase Details continuation (#3493, PR #3500)

  • nextMilestoneRegex in sdk/src/query/roadmap.ts only treats same-version sub-headings as continuations (from #2455 / #2422). Generic non-version-prefixed ## Phase Details placed AFTER a ### 📋 vX.Y+ (Planned) sibling fell outside the milestone slice — phase.insert N then reported "Phase N not found in ROADMAP.md" despite the heading being unambiguously present
  • fix: after the initial boundary scan, look forward for a literal ^#{1,3}\s+Phase\s+Details\b heading past sectionEnd and append that block (up to next real milestone boundary or EOF). Intervening planned-milestone content is skipped, not concatenated — cannot leak across milestones. Bounded to a single append
  • do not regress: same-version ## v2.0 Phase Details continuation from #2455 must still work; the existing bug-2422 regression test pins this

2026-05-14 — Path-replacement homedir check needs trailing-slash anchor (#3503, PR #3504)

  • tests/path-replacement.test.cjs:163 used content.includes(normalizedHomedir) — substring match false-positives when homedir is short and appears inside an identifier (e.g. Docker /root matches </root_cause_analysis> in agent markdown)
  • fix: content.includes(normalizedHomedir + '/') — real path leaks always have a trailing /; identifier substrings don't
  • general pattern: substring-containment checks on short absolute prefixes (/root, /tmp, /opt) need delimiter anchors

2026-05-14 — Codex AoT hooks migration: leaf TOML key must be event name (#3346, PR #3505)

  • migrateCodexHooksMapFormat in bin/install.js used s.path.slice('hooks.'.length) as the leaf key for emitted [[hooks.<EVENT>]] blocks. When the legacy table key was a <file>:<event>:<line>:<col> diagnostic location identifier and the real event lived in the body's event = "..." field, the location tuple passed through verbatim as the leaf — Codex 0.124.0+ refuses to load such keys (: and \ collide with TOML's .-separated key syntax)
  • fix: the mapOnlyBlocks and staleNamespacedAotBlocks branches now mirror the flat-AoT branch and prefer extractFlatHookEventName(body) over the raw path segment when the body declares event = "..."
  • post-write validator message "expected [[...]] array-of-tables" was a downstream symptom — the bracket form is correct, the leaf key chain was the bug. When validator-style errors describe a fix shape, verify the actual emitted string first; the validator may be misdiagnosing the cause
  • regression test fixture: legacy [hooks] block with a location-identifier table key → assert parsed result has hooks.state.session_start (event name) as the leaf path

2026-05-14 — Label-scoped stale-bot sub-job pattern (#3506, PR #3507)

  • two actions/stale jobs in one workflow do not conflict when each scopes via only-issue-labels (tight job) plus the other's labels in its exempt-issue-labels (broad job)
  • tight job pattern for "we asked, no response" enforcement:
    • only-issue-labels: 'awaiting-retest,needs-reproduction'
    • days-before-issue-stale: 5
    • days-before-issue-close: 0 (stale + close in same run)
    • days-before-pr-stale: -1 / days-before-pr-close: -1 (disable PR side)
    • remove-stale-when-updated: true
  • broad job (28+14) must add awaiting-retest,needs-reproduction to its exempt-issue-labels so the two jobs do not race or double-close
  • maintainers dry-run via gh workflow run "Stale Cleanup" before next cron tick; the action publishes a closes-summary

2026-05-14 — Multi-PR triage operational lessons

  • GitHub auto-close-keyword parsing: Fix #N / Fixes #N / Closes #N in PR body prose (e.g. "Suggested fix #2 — stopped_at derivation") triggers spurious closes. Neutralise with backticks or parens: `(2)` instead of #2. After every gh pr create, verify gh pr view <n> --json closingIssuesReferences -q '[.closingIssuesReferences[].number]'
  • gh-templates-first hook gates gh pr create / gh pr edit / gh issue create|edit until every PR/issue template has been Read in-session. Pre-empt with one parallel-Read of CONTRIBUTING.md, all .github/PULL_REQUEST_TEMPLATE/*.md, .github/pull_request_template.md, and all .github/ISSUE_TEMPLATE/*.yml before the first gh invocation
  • sub-agent capacity: 4 parallel test-running debugger agents @ ~4 GB each overflow a 24 GB system. For multi-bug triage waves, run isolated-worktree agents sequentially in foreground, not in parallel — the worktree isolation still buys clean branch separation
  • trust-but-verify on every sub-agent return: confirm PR opened (gh pr view <n>), closingIssuesReferences lists exactly the intended issue, regression test path exists, scope is clean. Agents describe intent, not action
  • when auto-generated PR-template branch (feat/NNNN-... from github-actions bot) is heavily diverged from main (hundreds of files, large negative diff), discard it and branch fresh off main — the auto-branch is a courtesy reference, not a base
  • escalation pattern: an issue asking for a release publish or backport (e.g. #3340 — fix already in main, awaiting npm publish) is ready-for-human, not ready-for-agent. No branch can resolve it