# 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 `/.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 `` block names a user-visible outcome AND `` 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 (**S**pike, **P**aths, **I**nterfaces, **D**ata, **R**ules) 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: ` 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}\`)` — if `someVar` can contain `.` or other metacharacters (e.g. phase id `5.1`), the pattern is wrong. Always `escapeRegex(someVar)`. The `escapeRegex` utility is in `core.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/.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 `), (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 ``. 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; `` attributes inside those files must match: `extract-learnings` not `extract_learnings`. Tests asserting `content.includes(' 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 #` `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/ 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/ (never .planning/projects/); 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/...` `RELEASE-NOTES.STANDARD.intro=optional one-paragraph framing for RC/feature releases; omit for pure-fix hotfixes` `RELEASE-NOTES.SOURCE.commits=git log .. --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 --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 --notes-file ` `RELEASE-NOTES.WORKFLOW.view=gh release view --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### \n- **** — . (#)\n\n---\n\nInstall/upgrade: \`npx get-shit-done-cc@latest\`\n\n**Full Changelog**: ` `RELEASE-NOTES.TEMPLATE.rc=\n\n## Added\n### \n- **** — . (#)\n\n## Changed\n### Architecture\n- **** — . (#)\n\n## Fixed\n### \n- **** — . (#)\n\n## Documentation\n- **** — . (#)\n\n---\n\nThis is a release candidate. Install for testing:\n\`\`\`bash\nnpx get-shit-done-cc@next\n\`\`\`\n\n**Full Changelog**: ` `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/--.md fragment ONLY` `PRED.k320.tool=npm run changeset -- --type --pr --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//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 --json mergeStateStatus,statusCheckRollup + pulls//reviews + graphql reviewThreads + issues//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 errors "already used by worktree at "` `PRED.k325.cure=detached-HEAD: git checkout --detach $(git ls-remote origin ); modify; commit; git push --force-with-lease=: origin HEAD:refs/heads/` `PRED.k325.cleanup=git worktree remove --force 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//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/--.md` `PRED.k329.frontmatter=---\\ntype: \\npr: \\n---` `PRED.k329.body=**** — . (#)` `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 with NO --comment flag` `PRED.k331.recovery=if violation lands, gh api -X DELETE repos///issues/comments/` `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 --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 --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/ 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 ; 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 (, ) — 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-.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/ — 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/ directly; do work from detached HEAD; push HEAD:` `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 "` `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 ` 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 ` (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 `-`) 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: `planMap` → `canonicalToId` → `shortFormToId` - 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 `` 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.]]` blocks. When the legacy table key was a `:::` 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 --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 `), `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