Files
msd-core/CONTEXT.md
Tom Boucher 26dcdb1ad0 Refactor SDK-first architecture seams (#3316)
* refactor: tighten sdk-first architecture seams

Refs #3312

* refactor: finish state document seam cleanup

Refs #3312

* test: harden minimal install cleanup assertion

* ci: support sdk-scoped package lock

* fix(3316): restore root package-lock.json and align changeset pr ref

Reverts dec57a83 ("ci: support sdk-scoped package lock") and restores
the root package-lock.json that c249d34d deleted. The deletion was the
wrong direction:

- The root package.json declares its own runtime and dev deps
  (@anthropic-ai/claude-agent-sdk, ws, c8). Without a root lockfile,
  `npm install --no-package-lock` resolves whatever satisfies semver at
  install time — CI today and CI in six months can install different
  transitive trees, defeating reproducibility.
- The lockfile has been part of every release on this repo (long
  history on main); removing it loses the npm audit / Dependabot
  target without compensating benefit.
- The CI workaround pattern (cache-dependency-path: sdk/package-lock.json
  + `npm install --no-package-lock`) papered over the symptom rather
  than fix the cause.

Also fix the changeset pr: from 3312 (issue) to 3316 (PR). CONTEXT.md
flags this exact failure mode as a recurring CodeRabbit finding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address coderabbit review findings

* fix: close remaining coderabbit threads

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:21:42 -04:00

488 lines
40 KiB
Markdown

# 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.
### 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 (**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: <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}\`)` — 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/<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`
`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\n**Full 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\n**Full 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`