Files
msd-core/examples/dynamic-context-management/CONTEXT-INDEX.json

2408 lines
118 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"schemaVersion": 1,
"count": 393,
"classes": {
"ARCH": 1,
"CI": 2,
"CONFIG": 1,
"DEFECT": 161,
"EXEC": 8,
"GSD-RESEARCH": 6,
"LEARNING": 1,
"META": 4,
"PLANNING": 3,
"PR": 2,
"PRED": 68,
"PROC": 14,
"RELEASE-NOTES": 31,
"RULESET": 59,
"SESSION": 9,
"WAVE": 5,
"WORKSTREAM": 5,
"WORKTREE": 13
},
"predicates": [
{
"id": "ARCH.SKILL.improve-codebase.next-candidates",
"klass": "ARCH",
"value": "[Workstream Name Policy Module, Workstream Progress Projection Module, Active Workstream Pointer Store Module]",
"line": 448
},
{
"id": "CI.GATE.changeset-lint",
"klass": "CI",
"value": "hard-fail for user-facing code diffs unless .changeset/* or PR has no-changelog label",
"line": 432
},
{
"id": "CI.GATE.issue-link-required",
"klass": "CI",
"value": "hard-fail if PR body lacks closes/fixes/resolves #<issue>",
"line": 431
},
{
"id": "CONFIG.SEAM.loadConfig-context",
"klass": "CONFIG",
"value": "loadConfig(cwd,{workstream}) replaces env-mutation fallback; no temporary process.env GSD_WORKSTREAM rewrites",
"line": 463
},
{
"id": "DEFECT.AGENT-FILE-SIZE-CAP-BREACH.detect",
"klass": "DEFECT",
"value": "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\")",
"line": 665
},
{
"id": "DEFECT.AGENT-FILE-SIZE-CAP-BREACH.fix-forward",
"klass": "DEFECT",
"value": "mirror MVP mode pattern — extract full rules to gsd-core/references/planner-<mode>.md, leave a slim Detection section in the agent file with @-reference to the new file",
"line": 666
},
{
"id": "DEFECT.AGENT-FILE-SIZE-CAP-BREACH.state",
"klass": "DEFECT",
"value": "gsd-planner.md is already 49,121 chars on main (over 45K); test fails on main; net-new content makes it strictly worse",
"line": 664
},
{
"id": "DEFECT.AGENT-FILE-SIZE-CAP-BREACH.symptom",
"klass": "DEFECT",
"value": "adding to agents/gsd-planner.md (or other large agent files) exceeds the 45K char extraction-evidence threshold",
"line": 663
},
{
"id": "DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.detect",
"klass": "DEFECT",
"value": "tests/bug-2543-gsd-slash-namespace.test.cjs prints \"Found N retired /gsd-<cmd> reference(s) — use /gsd:<cmd> instead\" with line-number-precise violations",
"line": 864
},
{
"id": "DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.examples",
"klass": "DEFECT",
"value": "#3541 implementation included a typical /gsd-update path comment in installer-migration-report.cjs; caught by tests/bug-2543-gsd-slash-namespace.test.cjs (#3443 invariant)",
"line": 863
},
{
"id": "DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.fix-forward",
"klass": "DEFECT",
"value": "replace /gsd-<cmd> with /gsd:<cmd> at the cited file:line; healthy emergent property — project-wide invariant test catches drift agents would never self-correct",
"line": 865
},
{
"id": "DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.lesson",
"klass": "DEFECT",
"value": "agent-trust-but-verify is load-bearing — sub-agent reporting \"done\" is not a substitute for running the full suite; the invariant test surfaces drift even in doc-only changes",
"line": 866
},
{
"id": "DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.symptom",
"klass": "DEFECT",
"value": "sub-agent writes /gsd-<cmd> (legacy hyphen syntax) in code comments or doc strings while implementing a fix; lands as part of the implementation diff",
"line": 862
},
{
"id": "DEFECT.BOT-BRANCH-STALE-BASE.detect",
"klass": "DEFECT",
"value": "git merge-base origin/<bot-branch> origin/main returns the bot branch tip — confirms the bot branch is an ancestor of main, just stale",
"line": 645
},
{
"id": "DEFECT.BOT-BRANCH-STALE-BASE.examples",
"klass": "DEFECT",
"value": "#3309 fix/3309-checkpoint-type-human-verify-burns-token (was at e14ef535; main at 2e87c60a)",
"line": 644
},
{
"id": "DEFECT.BOT-BRANCH-STALE-BASE.fix-forward",
"klass": "DEFECT",
"value": "git checkout --detach origin/main; do work; git checkout -b <same-branch-name>; force-push with --force-with-lease",
"line": 646
},
{
"id": "DEFECT.BOT-BRANCH-STALE-BASE.symptom",
"klass": "DEFECT",
"value": "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",
"line": 643
},
{
"id": "DEFECT.CANARY-VERSION-LEAK.detect",
"klass": "DEFECT",
"value": "jq -r .version package.json on origin/main shows a -canary suffix; OR npm view <pkg> dist-tags shows latest != main's version",
"line": 819
},
{
"id": "DEFECT.CANARY-VERSION-LEAK.examples",
"klass": "DEFECT",
"value": "2026-05-16 audit found origin/main + origin/feat/3575-enforcement-hardening both at \"version\": \"1.50.0-canary.0\" in sdk/package.json AND root package.json; npm view @opengsd/gsd-sdk versions returned [\"0.1.0\"] only, dist-tag latest=0.1.0, @1.50.0-canary.0 404 — confirms the string is metadata-only, never published. git log -S '\"version\": \"1.50.0-canary.0\"' origin/main blamed commit 2d32ad82 fix(plan-phase)... (#3206), a fix PR that accidentally carried the version bump from a dev-branch base",
"line": 818
},
{
"id": "DEFECT.CANARY-VERSION-LEAK.fix-forward",
"klass": "DEFECT",
"value": "open a chore/* PR against main that resets the version strings to the canonical pre-canary stable; rebase open PRs to pick it up; gate at PR open with a CI check that rejects -canary versions on PRs targeting main",
"line": 820
},
{
"id": "DEFECT.CANARY-VERSION-LEAK.symptom",
"klass": "DEFECT",
"value": "package.json version on main carries a -canary.<N> suffix that per release policy belongs to the dev branch only; nothing publishable depends on the version string at runtime, but every consumer of the version metadata (release flow, install banners, statusline) sees the dev-channel label",
"line": 817
},
{
"id": "DEFECT.CHANGESET-PR-FIELD-DRIFT.detect",
"klass": "DEFECT",
"value": "changeset pr: value mismatches the actual PR number returned by gh api POST /pulls",
"line": 670
},
{
"id": "DEFECT.CHANGESET-PR-FIELD-DRIFT.examples",
"klass": "DEFECT",
"value": "#3316 (pr:3312 was the issue), #3325 (pr:3319 was a guess); already covered in CONTEXT.md L94 + L186 but recurs every cycle",
"line": 669
},
{
"id": "DEFECT.CHANGESET-PR-FIELD-DRIFT.fix-forward",
"klass": "DEFECT",
"value": "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",
"line": 671
},
{
"id": "DEFECT.CHANGESET-PR-FIELD-DRIFT.symptom",
"klass": "DEFECT",
"value": ".changeset/*.md frontmatter pr: value is the issue number, a guess made before PR opened, or a stale stacked-PR number",
"line": 668
},
{
"id": "DEFECT.DEFAULT-FLIP-DOCUMENTATION.detect",
"klass": "DEFECT",
"value": "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",
"line": 705
},
{
"id": "DEFECT.DEFAULT-FLIP-DOCUMENTATION.examples",
"klass": "DEFECT",
"value": "#3309 v2 default flip from mid-flight to end-of-phase",
"line": 704
},
{
"id": "DEFECT.DEFAULT-FLIP-DOCUMENTATION.fix-forward",
"klass": "DEFECT",
"value": "template — \"new default takes effect when .planning/config.json is rewritten (config-set, fresh project, regenerated config); existing artifacts continue to work; opt-back-in: gsd config-set <key> <old-value>\"",
"line": 706
},
{
"id": "DEFECT.DEFAULT-FLIP-DOCUMENTATION.symptom",
"klass": "DEFECT",
"value": "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)",
"line": 703
},
{
"id": "DEFECT.FORMAT",
"klass": "DEFECT",
"value": "class.sub-key=value | classes are greppable; each class carries detect / fix / anchor sub-keys when applicable",
"line": 620
},
{
"id": "DEFECT.FRONTMATTER-SCALAR-BROAD-GREP.detect",
"klass": "DEFECT",
"value": "grep \"^<key>:\" on a *.md whose result is compared to exact tokens, with no frontmatter scoping and no -m1; one body line beginning <key>: is enough to break it",
"line": 718
},
{
"id": "DEFECT.FRONTMATTER-SCALAR-BROAD-GREP.examples",
"klass": "DEFECT",
"value": "#586/PR #650 ship.md verification gate — grep \"^status:\" also matched body status: lines, yielding passed+gaps_found+human_needed instead of passed and blocking a passed phase; the same broad-grep still lives in execute-phase.md (consolidation tracked by #651)",
"line": 717
},
{
"id": "DEFECT.FRONTMATTER-SCALAR-BROAD-GREP.fix-forward",
"klass": "DEFECT",
"value": "scope to the leading frontmatter block and take the first match: sed -n '/^---$/,/^---$/p' \"$f\" | grep -m1 \"^<key>:\" | cut -d: -f2 | tr -d ' '; fix every parallel copy in the same change or consolidate behind one queryable seam (#651)",
"line": 719
},
{
"id": "DEFECT.FRONTMATTER-SCALAR-BROAD-GREP.symptom",
"klass": "DEFECT",
"value": "a YAML-frontmatter scalar (e.g. VERIFICATION.md status) read with grep \"^key:\" over the WHOLE markdown report instead of the frontmatter block; a key: line in the body (code block, copied artifact, example) returns extra matches that concatenate after cut|tr into a value matching no expected token, so a valid state is misrouted",
"line": 716
},
{
"id": "DEFECT.GENERATIVE-EXEMPLAR",
"klass": "DEFECT",
"value": "tests/runtime-launcher-parity.test.cjs (asserts every workflow bash block uses the canonical gsd_run launcher — the in-repo pattern for enforcing equality across parallel surfaces)",
"line": 714
},
{
"id": "DEFECT.GENERATIVE-FIX",
"klass": "DEFECT",
"value": "for any new constant/array/parser shared between two parallel surfaces (two workflow surfaces, or a generated artifact and its hand-authored source), the same commit MUST add a parity assertion that fails when the two diverge",
"line": 713
},
{
"id": "DEFECT.GENERATIVE-PRIORITY",
"klass": "DEFECT",
"value": "these defect classes share a common root: parallel implementations diverge silently because no parity test enforces equality at the test layer",
"line": 712
},
{
"id": "DEFECT.GSD-TEST-CONCURRENT-OUTPUT-COLLISION.detect",
"klass": "DEFECT",
"value": "two gsd-test-summary --both runs in flight; UnicodeDecodeError in parse_events_from_string traceback; /tmp/gsd-test-*.jsonl size mismatch vs total events emitted",
"line": 855
},
{
"id": "DEFECT.GSD-TEST-CONCURRENT-OUTPUT-COLLISION.fix-forward",
"klass": "DEFECT",
"value": "set per-invocation LOCAL_OUT=/tmp/gsd-test-<tag>-local.jsonl DOCKER_OUT=/tmp/gsd-test-<tag>-docker.jsonl env vars; or serialize the runs; upstream fix tracked in #3545 (default to tempfile.mkstemp + advisory flock)",
"line": 856
},
{
"id": "DEFECT.GSD-TEST-CONCURRENT-OUTPUT-COLLISION.root-cause",
"klass": "DEFECT",
"value": "gsd-test-summary lines 126-127 default LOCAL_OUT/DOCKER_OUT to fixed /tmp/gsd-test-{local,docker}.jsonl; concurrent line-buffered writers interleave bytes mid-multibyte → split UTF-8 sequence → decoder explodes on f.read()",
"line": 854
},
{
"id": "DEFECT.GSD-TEST-CONCURRENT-OUTPUT-COLLISION.symptom",
"klass": "DEFECT",
"value": "two simultaneous gsd-test-summary --both invocations (e.g. one per worktree) both crash with UnicodeDecodeError in parse_events_from_file; \"local exit=1 docker exit=1\" reported even though remote containers ran fine",
"line": 853
},
{
"id": "DEFECT.GSD-TEST-CONCURRENT-OUTPUT-COLLISION.upstream",
"klass": "DEFECT",
"value": "open-gsd/gsd-core#3545",
"line": 857
},
{
"id": "DEFECT.GSD-TEST-HOST-MID-RUN-DEATH.detect",
"klass": "DEFECT",
"value": "gsd-test-summary's task output file at /private/tmp/claude-*/tasks/<id>.output stays 0 bytes for >5 min after launch; ps shows the test still alive; ssh -o ConnectTimeout=5 <probed-host> true now times out",
"line": 823
},
{
"id": "DEFECT.GSD-TEST-HOST-MID-RUN-DEATH.examples",
"klass": "DEFECT",
"value": "2026-05-16 redshirt probed up at 12:48 UTC, gsd-test-summary picked it, docker container spawned, then redshirt's ssh daemon stopped responding — banner-exchange timeout. Test stalled 20+ minutes with the wrapper's output file at 0 bytes",
"line": 822
},
{
"id": "DEFECT.GSD-TEST-HOST-MID-RUN-DEATH.fix-forward",
"klass": "DEFECT",
"value": "TaskStop the wrapper; pkill -f gsd-test-summary + pkill -f \"ssh <dead-host>\"; re-run gsd-test-summary so pick_host re-randomizes from the live set (probe each ~/.config/gsd-test/hosts entry first to confirm). Upstream fix candidate: gsd-test should add a heartbeat read on the ssh-stdin channel and abort + retry on a different host after N silent seconds",
"line": 824
},
{
"id": "DEFECT.GSD-TEST-HOST-MID-RUN-DEATH.related",
"klass": "DEFECT",
"value": "DEFECT.GSD-TEST-MIRROR-POISONED (legacy bind-mount ownership); GSD-TEST-CONCURRENT-OUTPUT-COLLISION (file collision) — host-mid-run-death is the third independent gsd-test infra failure mode this month",
"line": 825
},
{
"id": "DEFECT.GSD-TEST-HOST-MID-RUN-DEATH.symptom",
"klass": "DEFECT",
"value": "pick_host succeeds at probe time (ssh -o ConnectTimeout=3 -o BatchMode=yes \"$h\" true); subsequent ssh \"$h\" 'docker run ...' hangs indefinitely because the chosen host went unreachable between probe and exec; gsd-test-summary buffers stderr until the wrapper exits, so the operator sees no progress at all",
"line": 821
},
{
"id": "DEFECT.GSD-TEST-MIRROR-POISONED.detect",
"klass": "DEFECT",
"value": "docker stderr shows rsync: [generator] delete_file: unlink(...) failed: Permission denied (13) OR [receiver] mkstemp \".gsd-*.<suffix>\" failed",
"line": 847
},
{
"id": "DEFECT.GSD-TEST-MIRROR-POISONED.recovery",
"klass": "DEFECT",
"value": "ssh <host> 'docker run --rm -v ~/gsd-mirror-gsd-core:/work gsd-test:node22 chown -R <remote-uid>:<remote-gid> /work'; remote-uid is the SSH user's uid on the remote (1000 on holodeck, NOT local Mac 501)",
"line": 849
},
{
"id": "DEFECT.GSD-TEST-MIRROR-POISONED.root-cause",
"klass": "DEFECT",
"value": "container ran without --user; build:hooks wrote into bind-mount as root; chown-back-before-exec patch closes forward path but not legacy hosts",
"line": 848
},
{
"id": "DEFECT.GSD-TEST-MIRROR-POISONED.symptom",
"klass": "DEFECT",
"value": "gsd-test-summary --both exits docker=23 (rsync partial transfer) with mkstemp Permission denied on remote mirror files; mirror has root-owned artifacts from prior cold runs",
"line": 846
},
{
"id": "DEFECT.GSD-TEST-MIRROR-POISONED.upstream",
"klass": "DEFECT",
"value": "trek-e/gsd-test-runner#1 — proposes self-healing init-time chown probe",
"line": 850
},
{
"id": "DEFECT.HALT-COST-PATTERN.detect",
"klass": "DEFECT",
"value": "any subagent-spawning workflow with mid-flight pause-and-resume that does not preserve subagent context",
"line": 695
},
{
"id": "DEFECT.HALT-COST-PATTERN.examples",
"klass": "DEFECT",
"value": "#3309 checkpoint:human-verify (mid-flight halt = full executor cold-start per round-trip; reporter measured \"tens of thousands of tokens\" per halt)",
"line": 694
},
{
"id": "DEFECT.HALT-COST-PATTERN.fix-forward",
"klass": "DEFECT",
"value": "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",
"line": 696
},
{
"id": "DEFECT.HALT-COST-PATTERN.symptom",
"klass": "DEFECT",
"value": "architecturally-sound checkpoint pattern produces hidden token cost because subagent context is discarded across the pause and respawn",
"line": 693
},
{
"id": "DEFECT.HOOK-OVER-ENFORCEMENT.detect",
"klass": "DEFECT",
"value": "hook re-fires on each invocation regardless of session-state read receipts",
"line": 700
},
{
"id": "DEFECT.HOOK-OVER-ENFORCEMENT.examples",
"klass": "DEFECT",
"value": "this session repeatedly hit \"Refusing to run gh issue create|edit / gh pr create|edit\" despite reading every listed file",
"line": 699
},
{
"id": "DEFECT.HOOK-OVER-ENFORCEMENT.fix-forward",
"klass": "DEFECT",
"value": "use gh api -X PATCH repos/{owner}/{repo}/pulls/{N} or repos/{owner}/{repo}/issues/{N} directly — same effect, hook regex does not match",
"line": 701
},
{
"id": "DEFECT.HOOK-OVER-ENFORCEMENT.read-tool-tracking",
"klass": "DEFECT",
"value": "gh-templates-first PreToolUse hook tracks Read tool invocations specifically; Bash cat/head of the same file does NOT satisfy the hook; future-self must use Read tool from the first contact with template files",
"line": 852
},
{
"id": "DEFECT.HOOK-OVER-ENFORCEMENT.symptom",
"klass": "DEFECT",
"value": "PreToolUse hook keeps blocking gh pr edit / gh issue edit even after all required files are read in the session",
"line": 698
},
{
"id": "DEFECT.HOOK-OVER-ENFORCEMENT.write-bypass",
"klass": "DEFECT",
"value": "security_reminder_hook can block Write on substring match (e.g. a literal child-process call-expression token); workaround is heredoc to /tmp then mv into place, or use Edit instead — Edit hooks are more lenient than Write hooks",
"line": 871
},
{
"id": "DEFECT.INVENTORY-DRIFT.detect",
"klass": "DEFECT",
"value": "tests/inventory-manifest-sync.test.cjs fails with \"New surfaces not in manifest\"; tests/inventory-headings-countfree.test.cjs fails if a (N shipped) count is re-added to a heading",
"line": 660
},
{
"id": "DEFECT.INVENTORY-DRIFT.examples",
"klass": "DEFECT",
"value": "#3309 planner-human-verify-mode.md (caught by tests/inventory-manifest-sync.test.cjs)",
"line": 659
},
{
"id": "DEFECT.INVENTORY-DRIFT.fix-forward",
"klass": "DEFECT",
"value": "update INVENTORY.md row entry; run node scripts/gen-inventory-manifest.cjs --write to regen INVENTORY-MANIFEST.json (all six families.* arrays are canonical — see RULESET.MANIFEST-CANONICAL-KEY)",
"line": 661
},
{
"id": "DEFECT.INVENTORY-DRIFT.symptom",
"klass": "DEFECT",
"value": "new file added under gsd-core/references/ or gsd-core/workflows/ without updating docs/INVENTORY.md row AND docs/INVENTORY-MANIFEST.json",
"line": 658
},
{
"id": "DEFECT.NAME-COLLISION.detect",
"klass": "DEFECT",
"value": "trace every CLI/test caller of the canonical name → if any caller's argv shape differs from the rebound handler's args[0] expectation, the migration broke the legacy contract",
"line": 795
},
{
"id": "DEFECT.NAME-COLLISION.examples",
"klass": "DEFECT",
"value": "#3577 config-ensure-section (legacy = no-arg full-default init via ensureConfigFile→buildNewProjectConfig; the rebound configEnsureSection = single-section ensure requiring args[0]; all CLI callers pass no args; handler throws \"Usage: config-ensure-section <section>\")",
"line": 794
},
{
"id": "DEFECT.NAME-COLLISION.fix-forward",
"klass": "DEFECT",
"value": "either (a) bind the dispatch to a handler whose body mirrors legacy semantics (e.g. configNewProject when no args), or (b) keep the dispatch case calling the original handler directly (precedent: 7d5dfa9d codex runtime carve-out). Whichever path, add a behavioral test that round-trips the legacy invocation shape to lock the contract",
"line": 796
},
{
"id": "DEFECT.NAME-COLLISION.symptom",
"klass": "DEFECT",
"value": "a router migration rebinds CLI dispatch for a canonical command name to a handler with a different positional-arg shape; every legacy no-arg / wrong-arg caller then errors out at the new handler's own validation throw",
"line": 793
},
{
"id": "DEFECT.PARSER-BRITTLE-MARKER-WHITELIST.detect",
"klass": "DEFECT",
"value": "any parser with hard-coded marker list; any parser that returns empty for non-matching input without warning",
"line": 690
},
{
"id": "DEFECT.PARSER-BRITTLE-MARKER-WHITELIST.examples",
"klass": "DEFECT",
"value": "ac518646/#3263 code-review SUMMARY parser rejected BL-/blocker variants",
"line": 689
},
{
"id": "DEFECT.PARSER-BRITTLE-MARKER-WHITELIST.fix-forward",
"klass": "DEFECT",
"value": "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",
"line": 691
},
{
"id": "DEFECT.PARSER-BRITTLE-MARKER-WHITELIST.symptom",
"klass": "DEFECT",
"value": "human-output parser whitelists known markers (severity, status); silently drops unfamiliar markers as malformed",
"line": 688
},
{
"id": "DEFECT.PHASE-DIR-PREFIX-DRIFT.anchor",
"klass": "DEFECT",
"value": "tests/bug-3298-phase-dir-prefix-drift-in-workflows.test.cjs (broad regression across workflow surfaces)",
"line": 636
},
{
"id": "DEFECT.PHASE-DIR-PREFIX-DRIFT.detect",
"klass": "DEFECT",
"value": "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",
"line": 634
},
{
"id": "DEFECT.PHASE-DIR-PREFIX-DRIFT.examples",
"klass": "DEFECT",
"value": "#3287 (init.phase-op + init.plan-phase first-touch), #3306/PRED.k015 (plan-milestone-gaps + import + add-backlog), #3297/#3298 (sibling reports)",
"line": 633
},
{
"id": "DEFECT.PHASE-DIR-PREFIX-DRIFT.fix-forward",
"klass": "DEFECT",
"value": "consume expected_phase_dir from init.phase-op / init.plan-phase output; never re-construct from padded_phase + slug in workflow steps",
"line": 635
},
{
"id": "DEFECT.PHASE-DIR-PREFIX-DRIFT.symptom",
"klass": "DEFECT",
"value": "multiple workflow files independently construct .planning/phases/{NN}-{slug} paths; project_code prefix or slug normalization missing in some surfaces",
"line": 632
},
{
"id": "DEFECT.PROMPT-INJECTION-SCAN-COLLISION-WITH-TESTS.detect",
"klass": "DEFECT",
"value": "CI security lane (Prompt injection scan step) reports FAIL: tests/<not-in-allowlist>.test.cjs with a line number pointing at a string literal; the literal is inside an assert.throws() or array of malicious inputs; the test file name is not in scripts/prompt-injection-scan.sh ALLOWLIST",
"line": 747
},
{
"id": "DEFECT.PROMPT-INJECTION-SCAN-COLLISION-WITH-TESTS.examples",
"klass": "DEFECT",
"value": "PR #1622 commit 4ed208e74 added convertClaudeCommandToWindsurfWorkflow commandName validation with 22 malicious-name fixtures; scanner matched an instruction-override phrase at tests/windsurf-conversion.test.cjs:122; CI security lane failed even though the test is the security control",
"line": 746
},
{
"id": "DEFECT.PROMPT-INJECTION-SCAN-COLLISION-WITH-TESTS.fix-forward",
"klass": "DEFECT",
"value": "ADD the test file to scripts/prompt-injection-scan.sh ALLOWLIST array with a comment citing this defect class; for large fixture sets, move them to tests/fixtures/adversarial/security/ (auto-allowlisted dir) and load via readFileSync; never weaken or fragment the payload to evade the scanner — that defeats the test's purpose; ALSO when documenting this defect in CONTEXT.md, do NOT quote the literal pattern — describe it generically (the scanner scans CONTEXT.md too)",
"line": 748
},
{
"id": "DEFECT.PROMPT-INJECTION-SCAN-COLLISION-WITH-TESTS.prevention",
"klass": "DEFECT",
"value": "when writing a security regression test that uses real injection payloads as fixtures, immediately add the test file path to scripts/prompt-injection-scan.sh ALLOWLIST in the same commit; when documenting this defect class anywhere under scanner scope (CONTEXT.md, docs/, agent .md), use descriptive references like 'scanner-matching payload' rather than quoting the literal pattern; ref DEFECT.PROMPT-INJECTION-SCAN-COLLISION (the older XML-tag-collision variant)",
"line": 749
},
{
"id": "DEFECT.PROMPT-INJECTION-SCAN-COLLISION-WITH-TESTS.symptom",
"klass": "DEFECT",
"value": "scripts/prompt-injection-scan.sh flags a NEW test file as a finding because the test contains real injection payloads as fixtures (strings that match one of the scanner's PATTERNS — see scripts/prompt-injection-scan.sh lines 18-64) to prove the validator under test rejects them; scanner cannot distinguish fixture from real injection; CI security lane fails on the test that ADDS the security validation",
"line": 745
},
{
"id": "DEFECT.PROMPT-INJECTION-SCAN-COLLISION.detect",
"klass": "DEFECT",
"value": "any new bare <system|assistant|human|user> tag in agents/*.md",
"line": 655
},
{
"id": "DEFECT.PROMPT-INJECTION-SCAN-COLLISION.examples",
"klass": "DEFECT",
"value": "#3309 added a bare 'human' element (angle-bracket-wrapped) for verify-block harvesting; tests/prompt-injection-scan.security.test.cjs flags angle-bracket-wrapped names matching system|assistant|human (open or close form)",
"line": 654
},
{
"id": "DEFECT.PROMPT-INJECTION-SCAN-COLLISION.fix-forward",
"klass": "DEFECT",
"value": "hyphenate the tag (<human-check>, <assistant-prompt>) — scanner regex matches bare names only",
"line": 656
},
{
"id": "DEFECT.PROMPT-INJECTION-SCAN-COLLISION.symptom",
"klass": "DEFECT",
"value": "custom XML element name in agent .md file matches scripts/scan-prompt-injection regex; legitimate agent vocabulary trips the security gate",
"line": 653
},
{
"id": "DEFECT.REMOVED-BUT-NEEDED.detect",
"klass": "DEFECT",
"value": "before deletion, grep filename across .github/workflows, gsd-core/, docs/, package.json scripts; if any reference exists removal is incomplete",
"line": 624
},
{
"id": "DEFECT.REMOVED-BUT-NEEDED.examples",
"klass": "DEFECT",
"value": "#3316 root package-lock.json (root package.json declares deps; workflows use cache:'npm' + npm ci), e3b52c70 docs referenced removed /gsd-new-workspace",
"line": 623
},
{
"id": "DEFECT.REMOVED-BUT-NEEDED.fix-forward",
"klass": "DEFECT",
"value": "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",
"line": 625
},
{
"id": "DEFECT.REMOVED-BUT-NEEDED.symptom",
"klass": "DEFECT",
"value": "file/key removed because \"no longer used\" without verifying every consumer (workflows, docs, manifests, npm scripts)",
"line": 622
},
{
"id": "DEFECT.RESEARCH-PROVIDER-PROSE-DRIFT",
"klass": "DEFECT",
"value": "provider waterfall duplicated across N researcher agent .md files drifts independently (META.RULE.brief-no-paraphrase); fix-forward=research-provider.cjs single source of truth + generated agents (#657)",
"line": 285
},
{
"id": "DEFECT.SCOPE.window",
"klass": "DEFECT",
"value": "PRs #3306..#3325 + sibling fixes #3240/#3242/#3245/#3257/#3261/#3267/#3286/#3287",
"line": 619
},
{
"id": "DEFECT.SDK-PORT-NAME-COLLISION.generative-tie",
"klass": "DEFECT",
"value": "instance of DEFECT.GENERATIVE-PRIORITY — parity assertion at the test layer between CJS handler shape and SDK handler shape would have failed at PR open",
"line": 797
},
{
"id": "DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.detect",
"klass": "DEFECT",
"value": "grep tests for fs.unlinkSync|rmSync|writeFileSync|renameSync|cpSync targeting paths resolved from the repo root (join(__dirname,'..',...)) under gsd-core/bin/lib or a shared committed fixture, instead of a mkdtempSync temp dir; any build helper (e.g. ensureBuiltArtifacts) invoked with real-tree paths during the concurrent test phase; any tsBuildInfoFile / build-cache path that lands inside a copied/shipped dir (gsd-core/bin/)",
"line": 807
},
{
"id": "DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.examples",
"klass": "DEFECT",
"value": "#996/88e30d53 — bug-969 hardening tests fs.unlinkSync'd + restored the real gsd-core/bin/lib/core.cjs and set tsBuildInfoFile inside gsd-core/bin/ → next red across the full-test matrix (macOS/Windows) + ubuntu-24 coverage leg, ~40-50 MODULE_NOT_FOUND/ENOENT per leg; reproduced locally on iteration 1; fixed #1001/#1002",
"line": 806
},
{
"id": "DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.fix-forward",
"klass": "DEFECT",
"value": "tests mutate ONLY isolated mkdtempSync copies — never delete/rewrite shared real build outputs while node --test runs files concurrently; parameterize build helpers to accept {root,srcDir,outDir,tsBuildInfoPath,tsconfigPath} overrides and point the test at a throwaway temp project (precedent: #1002 ensureBuiltArtifacts(overrides)); keep mutable build state (tsbuildinfo) OUTSIDE copied/shipped trees (repo root, gitignored) + best-effort self-heal of stale bin-local copies; this is the concrete instance of the RULESET.TESTS.delete-bad-tests real-race class",
"line": 808
},
{
"id": "DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.symptom",
"klass": "DEFECT",
"value": "a test deletes/rewrites a SHARED REAL build artifact or fixture (e.g. gsd-core/bin/lib/*.cjs, the build tsbuildinfo) that other test files require; node --test runs files concurrently, so innocent concurrent tests intermittently fail with \"Cannot find module\" / ENOENT while the racy test itself passes (victim-not-culprit, leg-asymmetric red); placing mutable build state inside a copied/shipped tree (gsd-core/bin/) additionally races install-test fs.cpSync copies → copyfile ENOENT",
"line": 805
},
{
"id": "DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.test-anchor",
"klass": "DEFECT",
"value": "tests/bug-969-test-infra-flake-hardening.test.cjs (hermetic temp-project rewrite); regression gate = 10x concurrent run of that suite + tests/state.test.cjs + tests/install.test.cjs must be clean (reproduces on iter 1 when racy)",
"line": 809
},
{
"id": "DEFECT.SOURCE-GREP-IN-NEW-TESTS.detect",
"klass": "DEFECT",
"value": "scripts/lint-no-source-grep.cjs (npm run lint:tests) fails with line-number-precise violation",
"line": 709
},
{
"id": "DEFECT.SOURCE-GREP-IN-NEW-TESTS.fix-forward",
"klass": "DEFECT",
"value": "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",
"line": 710
},
{
"id": "DEFECT.SOURCE-GREP-IN-NEW-TESTS.symptom",
"klass": "DEFECT",
"value": "new test file uses readFileSync + .includes() / .match() against source code (CONTEXT.md L82); contradicts the test rule lint script",
"line": 708
},
{
"id": "DEFECT.STACKED-PR-AUTO-RETARGET.detect",
"klass": "DEFECT",
"value": "ls-remote shows base ref absent; PR base still points at the deleted ref; mergeable=CONFLICTING with no real diff conflicts",
"line": 640
},
{
"id": "DEFECT.STACKED-PR-AUTO-RETARGET.examples",
"klass": "DEFECT",
"value": "#3311 base fix/3255-add-json-errors-mode-gsd-tools deleted after #3304 merged",
"line": 639
},
{
"id": "DEFECT.STACKED-PR-AUTO-RETARGET.fix-forward",
"klass": "DEFECT",
"value": "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)",
"line": 641
},
{
"id": "DEFECT.STACKED-PR-AUTO-RETARGET.symptom",
"klass": "DEFECT",
"value": "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",
"line": 638
},
{
"id": "DEFECT.STACKED-PR-CANNOT-STAND-ALONE.anti-pattern",
"klass": "DEFECT",
"value": "blindly running git rebase --onto origin/main on the patch branch — produces \"conflicts\" that are really \"the scaffolding doesn't exist yet\"; resolving them means reinventing the upstream PR's contribution, which duplicates work and creates merge hazards. Recognize the shape early via cat-file probe before rebasing",
"line": 815
},
{
"id": "DEFECT.STACKED-PR-CANNOT-STAND-ALONE.detect",
"klass": "DEFECT",
"value": "gh pr view <n> --json baseRefName shows non-main base; OR git rebase --onto origin/main <upstream-pr-branch> <patch-pr-branch> produces real (not whitespace) conflicts at files the patch claims to modify; OR git cat-file -e origin/main:<patch-target-file> errors with \"does not exist in origin/main\"",
"line": 813
},
{
"id": "DEFECT.STACKED-PR-CANNOT-STAND-ALONE.examples",
"klass": "DEFECT",
"value": "#3639 + #3637 both targeted base=feat/3575-enforcement-hardening (the Phase 6 PR #3577); #3639 modifies SDK-bridge calls in 6 family-router files that on main do NOT have any SDK-bridge call yet; #3637 patches scripts/lint-shared-module-handsync.cjs which does not exist on main at all",
"line": 812
},
{
"id": "DEFECT.STACKED-PR-CANNOT-STAND-ALONE.fix-forward",
"klass": "DEFECT",
"value": "user policy (this session, 2026-05-16): every PR must stand alone. Resolution = cherry-pick the patch's unique commits onto the upstream PR head, push to upstream PR branch, close patch PR with \"subsumed by #<upstream>\". Alternatives explicitly rejected: leaving stacked open (\"no, fold them in\") and closing-without-folding (\"we want the fix\")",
"line": 814
},
{
"id": "DEFECT.STACKED-PR-CANNOT-STAND-ALONE.symptom",
"klass": "DEFECT",
"value": "patch PR was authored against scaffolding (handler files, lint scripts, generated modules) that exists only on an unmerged upstream feature branch; the PR's \"base\" on GitHub is the feature branch, not main; merging requires the upstream PR to land first",
"line": 811
},
{
"id": "DEFECT.STATE-TRAMPLE.detect",
"klass": "DEFECT",
"value": "any state writer that calls buildStateFrontmatter without preserving existing progress.* keys; any mutation surface that does not honor shouldPreserveExistingProgress",
"line": 629
},
{
"id": "DEFECT.STATE-TRAMPLE.examples",
"klass": "DEFECT",
"value": "#3242 (Last Activity overwrote progress.completed_plans), #3257 (nested plans/ files uncounted), #3261 (buildStateFrontmatter), #3265 (canonical fields), #3286 (record-metric/add-decision sections)",
"line": 628
},
{
"id": "DEFECT.STATE-TRAMPLE.fix-forward",
"klass": "DEFECT",
"value": "route through state-document.cjs/.ts shouldPreserveExistingProgress + normalizeProgressNumbers (extracted in #3316 SDK-first seams)",
"line": 630
},
{
"id": "DEFECT.STATE-TRAMPLE.symptom",
"klass": "DEFECT",
"value": "state-mutation paths overwrite curated values when body-derived computation is narrower than what's stored in frontmatter",
"line": 627
},
{
"id": "DEFECT.SUBAGENT-LONG-RUNNING-BG-STALL.anchor",
"klass": "DEFECT",
"value": "project CLAUDE.md \"Top-level orchestrator (cross-turn notifications available) vs Sub-agent worker (no cross-turn notifications)\" guidance — load-bearing for multi-worktree parallel fix dispatch",
"line": 861
},
{
"id": "DEFECT.SUBAGENT-LONG-RUNNING-BG-STALL.detect",
"klass": "DEFECT",
"value": "sub-agent returns prematurely with text like \"I should wait for the notification per CLAUDE.md\" and incomplete work in its worktree (commits absent, push absent, PR absent)",
"line": 859
},
{
"id": "DEFECT.SUBAGENT-LONG-RUNNING-BG-STALL.fix-forward",
"klass": "DEFECT",
"value": "keep gsd-test-summary --both at the top-level orchestrator; sub-agents either run it foreground with timeout: 1500000 (25min) and block, OR delegate the test step back to the orchestrator (write commits + return); never have a sub-agent fire-and-await a backgrounded long task",
"line": 860
},
{
"id": "DEFECT.SUBAGENT-LONG-RUNNING-BG-STALL.symptom",
"klass": "DEFECT",
"value": "spawned sub-agent kicks off gsd-test-summary --both via Bash run_in_background, then stops on the harness \"you will be notified\" message; never receives the notification because cross-turn task-notifications are only delivered to the top-level orchestrator",
"line": 858
},
{
"id": "DEFECT.SUPERSEDED-CONCURRENT-PRS.detect",
"klass": "DEFECT",
"value": "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",
"line": 650
},
{
"id": "DEFECT.SUPERSEDED-CONCURRENT-PRS.examples",
"klass": "DEFECT",
"value": "#3303 + #3307 superseded by #3306 (all addressing #3297/#3298 project_code prefix family)",
"line": 649
},
{
"id": "DEFECT.SUPERSEDED-CONCURRENT-PRS.fix-forward",
"klass": "DEFECT",
"value": "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",
"line": 651
},
{
"id": "DEFECT.SUPERSEDED-CONCURRENT-PRS.symptom",
"klass": "DEFECT",
"value": "multiple in-flight PRs attack overlapping subsets of the same issue; the broadest one merges first; narrower siblings remain open with phantom conflicts",
"line": 648
},
{
"id": "DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE.detect",
"klass": "DEFECT",
"value": "test does readFileSync(md).match for a bash fence with literal \\n, OR execFileSync('bash',...) gated only on a bash-presence probe; also verifying a new test with a file-scoped run instead of the full suite hides repo-wide static guards",
"line": 722
},
{
"id": "DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE.examples",
"klass": "DEFECT",
"value": "#586/PR #650 tests/ship-586-verification-routing.test.cjs — the fence \\n offender failed ubuntu-24/macos/coverage, then the Windows tmpdir-path glob failed full test (windows-latest,22) at fail 3; both were invisible to file-scoped gsd-test-both runs because the parity guard is only scanned by the full suite",
"line": 721
},
{
"id": "DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE.fix-forward",
"klass": "DEFECT",
"value": "match the fence with \\r?\\n and normalize the captured block to LF; gate pipeline execution on process.platform !== 'win32' && hasBash since the extraction LOGIC is platform-independent and POSIX coverage suffices; run the full suite (or the parity/lint guards) before push when adding a test file",
"line": 723
},
{
"id": "DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE.symptom",
"klass": "DEFECT",
"value": "a test that parses a workflow bash block out of a *.md and runs it via execFileSync('bash',...) breaks on Windows two ways: the fence regex uses a literal \\n after the bash fence that will not match CRLF and trips windows-test-parity-guard (fenceRegexLiteralNewline); and git-bash exists so a bash-presence probe is true, but an os.tmpdir() Windows path (C:\\...) is un-globbable in bash so the pipeline returns empty and assertions fail",
"line": 720
},
{
"id": "DEFECT.UNBOUNDED-SUBPROCESS.detect",
"klass": "DEFECT",
"value": "execSync/execFileSync/spawnSync without timeout option in non-test code; especially git list-worktrees, git fetch, npm view",
"line": 685
},
{
"id": "DEFECT.UNBOUNDED-SUBPROCESS.examples",
"klass": "DEFECT",
"value": "a33cbe72 worktree fix bound git subprocesses with timeout",
"line": 684
},
{
"id": "DEFECT.UNBOUNDED-SUBPROCESS.fix-forward",
"klass": "DEFECT",
"value": "add timeout (5-30s for git, 60s for npm); on timeout return degraded result + structured warning rather than throw",
"line": 686
},
{
"id": "DEFECT.UNBOUNDED-SUBPROCESS.symptom",
"klass": "DEFECT",
"value": "git/npm subprocess shelled out without timeout; CLI hangs indefinitely on stuck remote, large repo, or missing network",
"line": 683
},
{
"id": "DEFECT.WINDOWS-ARGV-OVERFLOW.detect",
"klass": "DEFECT",
"value": "Windows CI job at \"Run unit tests\" exits with code 1 within seconds of starting, no node:test output between \"run-tests: suite=… files=N: …\" line and \"Process completed with exit code 1\"; same job on Linux/macOS runs full duration",
"line": 801
},
{
"id": "DEFECT.WINDOWS-ARGV-OVERFLOW.examples",
"klass": "DEFECT",
"value": "#3649 scripts/run-tests.cjs spawning 546 paths (~85 chars each ≈ 46 KB); Linux ARG_MAX 2 MB allows it, Windows aborts in ~70 ms with zero test output making the failure look like the runner itself crashed",
"line": 800
},
{
"id": "DEFECT.WINDOWS-ARGV-OVERFLOW.fix-forward",
"klass": "DEFECT",
"value": "chunk argv into batches whose total length stays under 28,000 chars (headroom under the 32,767 ceiling); run each chunk sequentially; aggregate exit codes (first non-zero wins). Expose RUN_TESTS_MAX_CMDLINE_CHARS env override so cross-platform regression tests can force chunking with short tmp paths",
"line": 802
},
{
"id": "DEFECT.WINDOWS-ARGV-OVERFLOW.symptom",
"klass": "DEFECT",
"value": "execFileSync(node, ['--test', ...N paths]) succeeds on Linux/macOS, instantly exits with code 1 and no test output on Windows when N×avg(path_len) exceeds 32,767 chars (CreateProcess lpCommandLine cap)",
"line": 799
},
{
"id": "DEFECT.WINDOWS-ARGV-OVERFLOW.test-anchor",
"klass": "DEFECT",
"value": "tests/run-tests-harness.test.cjs \"Windows argv-overflow chunking (issue #3597)\" — 30 long-named fixture files + RUN_TESTS_MAX_CMDLINE_CHARS=2000 → asserts run-tests: chunk N/M marker in stderr; pattern works on every platform",
"line": 803
},
{
"id": "DEFECT.WINDOWS-FS-OPS.detect",
"klass": "DEFECT",
"value": "any rename/copy in build/install path without try/catch fallback",
"line": 680
},
{
"id": "DEFECT.WINDOWS-FS-OPS.examples",
"klass": "DEFECT",
"value": "c47c2c5d build-hooks rename → copy fallback, d2412271 install Windows persistent SDK shim",
"line": 679
},
{
"id": "DEFECT.WINDOWS-FS-OPS.fix-forward",
"klass": "DEFECT",
"value": "catch EPERM/EBUSY/EACCES, fall back to copy + unlink with retry, surface degraded-mode message; never silently swallow",
"line": 681
},
{
"id": "DEFECT.WINDOWS-FS-OPS.symptom",
"klass": "DEFECT",
"value": "fs.renameSync / fs.copyFileSync hits EPERM/EBUSY on Windows when antivirus or another process holds a transient handle on the target",
"line": 678
},
{
"id": "DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT.detect",
"klass": "DEFECT",
"value": "any function returning a filesystem path that flows into markdown/text body substitution; grep for path.join/raw resolvedTarget/${configDir}/ in code paths writing workflow .md, agent .md, or generated docs; smoke pattern is ${resolvedTarget}/ or ${configDir}/... templates that bypass normalization",
"line": 739
},
{
"id": "DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT.examples",
"klass": "DEFECT",
"value": "PR #1622 computePathPrefix returned ${resolvedTarget}/ verbatim — rewrites of @~/.claude/gsd-core/commands/gsd/X.md wrote @C:\\...\\gsd-ial-windsurf-XXX\\gsd-core/commands/gsd/help.md (trailing forward slashes from the original literal survived, prefix backslashes did not); tests/install-runtime-artifacts.test.cjs:318 + tests/install.test.cjs:1323 failed on windows-latest only",
"line": 738
},
{
"id": "DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT.fix-forward",
"klass": "DEFECT",
"value": "normalize at the SOURCE not the test: posixTarget=String(resolvedTarget).replace(/\\\\/g,'/'), posixHome=homeDir?String(homeDir).replace(/\\\\/g,'/'):homeDir; markdown body is POSIX-only; .replace(/\\\\/g,'/') is idempotent on POSIX (no backslashes present) so safe to apply unconditionally; isWindowsHost arg is a no-op tripwire (enh-1511) — do NOT branch on it, normalize always",
"line": 740
},
{
"id": "DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT.prevention",
"klass": "DEFECT",
"value": "RULESET.CONTENT-PATH-NORMALIZATION; tests are downstream signal, never the fix; ref DEFECT.WINDOWS-TEST-PORTABILITY for test-side parity (normalize expected substrings too: ${configDir}/foo.replace(/\\\\/g,'/'))",
"line": 741
},
{
"id": "DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT.symptom",
"klass": "DEFECT",
"value": "path.join() result on Windows (backslashes) substituted verbatim into markdown body (@-references, workflow files, generated docs); content gains mixed separators; cross-platform substring assertions fail on windows-latest CI lane only; macOS/Linux CI green so defect ships undetected",
"line": 737
},
{
"id": "DEFECT.WINDOWS-POSIX-MODE-BIT-ASSERT.detect",
"klass": "DEFECT",
"value": "grep tests for \\`.mode & 0o777\\` / \\`.mode) === 0o\\` / \\`writeFileSync(...{ mode: 0o\\` / \\`chmodSync\\` paired with a strict-equality assertion on the resulting mode; any such assertion is a POSIX-only fact that will diverge on Windows (write reads back as 0o666)",
"line": 733
},
{
"id": "DEFECT.WINDOWS-POSIX-MODE-BIT-ASSERT.examples",
"klass": "DEFECT",
"value": "#1634/PR #1638 tests/capability-lifecycle.test.cjs \"a .cjs hook command is node-prefixed so it runs without the executable bit\" failed windows-latest,24 on \"precondition: file staged without +x\" (expected 420/0o644, got 438/0o666); the node-prefix behavioral assertion was correct — only the mode-bit precondition was the POSIX-only fact",
"line": 732
},
{
"id": "DEFECT.WINDOWS-POSIX-MODE-BIT-ASSERT.fix-forward",
"klass": "DEFECT",
"value": "gate the mode-bit precondition on if (process.platform !== 'win32') — the executable-bit/mode is a POSIX concept meaningless on Windows; KEEP the platform-independent behavioral assertion (the actual behavior under test) running on every OS; do NOT delete the precondition, scope it to POSIX",
"line": 734
},
{
"id": "DEFECT.WINDOWS-POSIX-MODE-BIT-ASSERT.prevention",
"klass": "DEFECT",
"value": "ref DEFECT.WINDOWS-TEST-PORTABILITY — gsd-test is Mac/Linux only (no Windows host), only the CI windows-latest lane catches this; run npm run lint:ci (local/no-unguarded-nonportable-exec now enforces this via ESLint) before push; prefer asserting the BEHAVIOR (command shape, runnability) over the filesystem mode bit",
"line": 735
},
{
"id": "DEFECT.WINDOWS-POSIX-MODE-BIT-ASSERT.symptom",
"klass": "DEFECT",
"value": "a test writes a file with a POSIX mode (fs.writeFileSync(p, data, {mode: 0o644}) or fs.chmodSync) then asserts fs.statSync(p).mode & 0o777 === <that exact octal>; passes on macOS/Linux/ubuntu CI, FAILS on the windows-latest CI lane — Windows fs does NOT honor POSIX write modes, Node reports the mode derived from the DOS readonly attribute (0o666 for writable / 0o444 for readonly), never the requested 0o644/0o755",
"line": 731
},
{
"id": "DEFECT.WINDOWS-TEST-PORTABILITY.detect",
"klass": "DEFECT",
"value": "local/no-unguarded-nonportable-exec ESLint rule (enforced in tests/**/*.test.cjs via npm run lint); flags tests combining chmod exec-bit with sh/bash -c and no platform guard; watch CI windows matrix green before declaring a PR done",
"line": 727
},
{
"id": "DEFECT.WINDOWS-TEST-PORTABILITY.examples",
"klass": "DEFECT",
"value": "PR #1084 (chmod 0o755 + bare-command execution failed on windows lane); test files that assert path.join result without normalizing to forward slashes",
"line": 726
},
{
"id": "DEFECT.WINDOWS-TEST-PORTABILITY.fix-forward",
"klass": "DEFECT",
"value": "gate platform-specific execution with if (process.platform !== 'win32') — there is no opt-out annotation; structure any platform-specific code behind this guard; normalize path expectations to forward slashes with .replace(/\\\\/g, '/'); invoke scripts via explicit interpreter (sh <path>) rather than relying on exec-bit; see local/no-unguarded-nonportable-exec ESLint rule and docs/how-to/windows-portability.md for details",
"line": 728
},
{
"id": "DEFECT.WINDOWS-TEST-PORTABILITY.prevention",
"klass": "DEFECT",
"value": "run lint:ci before opening a PR; treat the CI windows lane as the only true Windows signal — gsd-test (Mac/Linux only) cannot substitute for it",
"line": 729
},
{
"id": "DEFECT.WINDOWS-TEST-PORTABILITY.symptom",
"klass": "DEFECT",
"value": "local gsd-test runs Mac+Linux only (no Windows host); Windows-only test failures (chmod exec-bit not honored for PATH-executing extension-less scripts in Git Bash msys2; / vs \\ path-separator in assertions; Git Bash msys2 shell semantics) surface ONLY in CI test (windows-latest,*) / full test (windows-latest,*) lanes, never locally",
"line": 725
},
{
"id": "DEFECT.WORKFLOW-DELEGATION-TARGET-NOT-INSTALLED.detect",
"klass": "DEFECT",
"value": "after install, for every workflow .md file under <targetDir>/<runtime-config-dir>/workflows/, extract the @<path> reference from the body and assert fs.existsSync(path); if any reference target is absent, this defect is present",
"line": 753
},
{
"id": "DEFECT.WORKFLOW-DELEGATION-TARGET-NOT-INSTALLED.examples",
"klass": "DEFECT",
"value": "PR #1622 (issue #1615) shipped Windsurf /gsd-* workflow wrappers that all reference <targetDir>/.windsurf/gsd-core/commands/gsd/X.md; that directory was never populated; none of the reviews (security, Codex adversarial, Memtrace) caught it; a #1629 regression test verifying 'every workflow @- reference target exists on disk' surfaced it post-merge",
"line": 752
},
{
"id": "DEFECT.WORKFLOW-DELEGATION-TARGET-NOT-INSTALLED.fix-forward",
"klass": "DEFECT",
"value": "copy the canonical command source (commands/gsd/*.md) into <targetDir>/gsd-core/commands/gsd/ during install, gated on the runtime that uses workflow delegation (currently Windsurf local only); use copyWithPathReplacement to apply the same path+brand rewrites as the rest of the install; verify with a regression test that every workflow's @-reference resolves",
"line": 754
},
{
"id": "DEFECT.WORKFLOW-DELEGATION-TARGET-NOT-INSTALLED.prevention",
"klass": "DEFECT",
"value": "any new converter that emits a wrapper file delegating to another file MUST verify the delegation target is actually written by the same install; add a post-install invariant test: for every @<path> reference in every generated wrapper, assert the target exists; the workflow converter's hardcoded path was copy-pasted from Claude's skill pattern without verifying the target exists for the new runtime",
"line": 755
},
{
"id": "DEFECT.WORKFLOW-DELEGATION-TARGET-NOT-INSTALLED.symptom",
"klass": "DEFECT",
"value": "workflow wrapper file (e.g. Windsurf convertClaudeCommandToWindsurfWorkflow) delegates to a command body at <targetDir>/gsd-core/commands/gsd/X.md via a hardcoded @~/.claude/gsd-core/commands/gsd/ path that _applyRuntimeRewrites rewrites to the install target; the source gsd-core/ dir ships without commands/ (it lives at package-root commands/gsd/); install completes successfully, workflow files appear in the / menu, but invocation tells the LLM to read a file that does not exist; the slash commands silently fail",
"line": 751
},
{
"id": "DEFECT.WORKTREE-FETCH-SHA-DIVERGENCE.detect",
"klass": "DEFECT",
"value": "git rev-parse HEAD~1 vs git rev-parse origin/<actual-branch-ref> — if they differ despite fetch the local copy was rewritten by some checkout-time hook",
"line": 675
},
{
"id": "DEFECT.WORKTREE-FETCH-SHA-DIVERGENCE.examples",
"klass": "DEFECT",
"value": "this session, branch fix/3309-... and pr-3316",
"line": 674
},
{
"id": "DEFECT.WORKTREE-FETCH-SHA-DIVERGENCE.fix-forward",
"klass": "DEFECT",
"value": "git checkout --detach origin/<actual-remote-branch> directly; do work from detached HEAD; push HEAD:<remote-branch>",
"line": 676
},
{
"id": "DEFECT.WORKTREE-FETCH-SHA-DIVERGENCE.symptom",
"klass": "DEFECT",
"value": "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",
"line": 673
},
{
"id": "EXEC.CLASSIFY.classes",
"klass": "EXEC",
"value": "{class:'quota-exceeded'|'classify-handoff-bug'|'unknown-failure', sentinel?, retryAfterSeconds?}",
"line": 839
},
{
"id": "EXEC.CLASSIFY.cross-runtime",
"klass": "EXEC",
"value": "Anthropic/CC: usage limit|rate limit|quota|429|retry-after; Copilot CLI: rate_limit (stem); Codex CLI: 429|usage_limit_reached|too many requests; Gemini CLI: RESOURCE_EXHAUSTED|exceeded your",
"line": 841
},
{
"id": "EXEC.CLASSIFY.handler",
"klass": "EXEC",
"value": "gsd-core/bin/lib/agent-command-router.cjs:classifyAgentFailure (registered via command-aliases.cjs; mutation:false outputMode:json)",
"line": 837
},
{
"id": "EXEC.CLASSIFY.precedence",
"klass": "EXEC",
"value": "quota sentinel wins over classifyHandoffIfNeeded bug when both appear",
"line": 842
},
{
"id": "EXEC.CLASSIFY.proactive-signal-not-usable",
"klass": "EXEC",
"value": "Anthropic exposes anthropic-ratelimit-* headers + Agent SDK RateLimitEvent; Claude Code subprocess does NOT forward to hooks/statusline today (upstream #33820, #22407, #32796)",
"line": 844
},
{
"id": "EXEC.CLASSIFY.retry-after-parser",
"klass": "EXEC",
"value": "\\bretry[-_ ]after[:\\s]+(\\d+)\\b avoids embedded-word false matches like noretry-after",
"line": 843
},
{
"id": "EXEC.CLASSIFY.sentinel-order",
"klass": "EXEC",
"value": "most specific first: 429 beats too-many-requests; quota beats resource_exhausted; case-insensitive; canonical sentinel value is lower-cased form",
"line": 840
},
{
"id": "EXEC.CLASSIFY.workflow",
"klass": "EXEC",
"value": "gsd-core/workflows/execute-phase.md step 7; class-distinct prompts (quota-to-wait-for-reset; classify-handoff-bug-to-spot-check; unknown-to-continue/stop)",
"line": 838
},
{
"id": "GSD-RESEARCH.CONTEXT-DISCIPLINE",
"klass": "GSD-RESEARCH",
"value": "less-context levers: subagent isolation + compact provider output + fetches-to-disk + cache-returns-digest; API clear_tool_uses/memory tool are the conceptual model, not a Claude Code harness knob",
"line": 284
},
{
"id": "GSD-RESEARCH.INTEGRATION.L2-hybrid",
"klass": "GSD-RESEARCH",
"value": "code owns cache+legitimacy+confidence+provider-pick (gsd-tools query research-plan/research-store/package-legitimacy); MCP owns the fetch; agent returns RESEARCH.md path, never raw fetches",
"line": 282
},
{
"id": "GSD-RESEARCH.MODULE.package-legitimacy",
"klass": "GSD-RESEARCH",
"value": "registry-API verdicts (npm/PyPI/crates.io injectable adapters) computed from thresholds {minAgeDays:30,minWeeklyDownloads:1000,requireRepo:true}; verdict OK|SUS|SLOP per package; slopcheck=optional adapter that can only escalate, never the install-or-degrade gate",
"line": 281
},
{
"id": "GSD-RESEARCH.MODULE.research-provider",
"klass": "GSD-RESEARCH",
"value": "single source of truth PROVIDER_WATERFALL (docs Context7->Ref->Jina->websearch; web Exa->Tavily->Perplexity->Brave->websearch; scrape Firecrawl->Jina); planResearch returns cache-hits+fetch-plan; classifyConfidence stamps HIGH|MEDIUM|LOW by provider AUTHORITY + verification EVIDENCE (HIGH requires code-computed ground-truth corroboration e.g. legitimacyVerdict OK; provider authority alone caps at MEDIUM; SLOP caps at LOW); Firecrawl is scrape-only (not in docs/web discovery)",
"line": 280
},
{
"id": "GSD-RESEARCH.MODULE.research-store",
"klass": "GSD-RESEARCH",
"value": "content-addressed cache; key=sha256(ecosystem+library+version+query+kind); getResearch->{hit,stale} never throws (mirrors graphify staleness); ttlForSource curated HIGH 30d|MED 7d|web LOW 1d; tiers: curated-doc kinds -> ~/.gsd/research-cache (cross-project), web/synthesis -> project .planning/research/.cache",
"line": 279
},
{
"id": "GSD-RESEARCH.PROVIDER.availability",
"klass": "GSD-RESEARCH",
"value": "config flags brave_search/exa_search/firecrawl/tavily_search/ref_search/perplexity/jina (env <X>_API_KEY or ~/.gsd/<x>_api_key); context7/jina/websearch always available; planResearch falls through waterfall to websearch terminal",
"line": 283
},
{
"id": "LEARNING.prompt-budget.boundary-gap",
"klass": "LEARNING",
"value": "PR #3708 commit 2df566ed reserved NOTE_RESERVE_TOKENS in pressure-threshold AND in minSet pre-check; both buggy paths only fire when baseTokens ∈ (effectiveBudget - NOTE_RESERVE_TOKENS, effectiveBudget]; original test suite used budgets far from that band so neither path was exercised; fix bde1ae8f confines NOTE_RESERVE accounting to post-trim assembly path only; future budget/limit code MUST add boundary fixtures per RULESET.TESTS.boundary-coverage.fixtures",
"line": 373
},
{
"id": "META.RULE.brief-must-cite-doc",
"klass": "META",
"value": "agent prompts MUST quote the canonical doc line being applied; paraphrasing from predicate memory drifts and produces violations",
"line": 514
},
{
"id": "META.RULE.brief-no-paraphrase",
"klass": "META",
"value": "writing \"k040 — never leave changelog box unchecked\" caused 5 of 8 agents to edit CHANGELOG.md in violation of CONTRIBUTING.md L110",
"line": 515
},
{
"id": "META.RULE.canonical-source-precedence",
"klass": "META",
"value": "CONTRIBUTING.md > docs/adr/* > CONTEXT.md > agent memory",
"line": 512
},
{
"id": "META.RULE.read-contributing-first",
"klass": "META",
"value": "read CONTRIBUTING.md sections \"Pull Request Guidelines\" + \"CHANGELOG Entries\" before EVERY agent dispatch",
"line": 513
},
{
"id": "PLANNING.PATH.PARITY.project-scope",
"klass": "PLANNING",
"value": ".planning/<project> (never .planning/projects/<project>); mirror planning-workspace.cjs planningDir()",
"line": 458
},
{
"id": "PLANNING.PATH.SEAM.helpers",
"klass": "PLANNING",
"value": "helpers.planningPaths delegates to workspacePlanningPaths + resolveWorkspaceContext; precedence explicit-ws > env-ws > env-project > root",
"line": 459
},
{
"id": "PLANNING.PATH.SEAM.init-handlers",
"klass": "PLANNING",
"value": "[initExecutePhase, initPlanPhase, initPhaseOp, initMilestoneOp] consume helpers.planningPaths().planning (no direct relPlanningPath join)",
"line": 460
},
{
"id": "PR.3267.POSTMORTEM.recovery",
"klass": "PR",
"value": "[issue#3270 created, label approved-enhancement applied, PR reopened, body includes \"Closes #3270\", label no-changelog applied]",
"line": 436
},
{
"id": "PR.3267.POSTMORTEM.root-cause",
"klass": "PR",
"value": "[missing issue link, missing changeset/no-changelog]",
"line": 435
},
{
"id": "PRED.k320.canonical-source",
"klass": "PRED",
"value": "CONTRIBUTING.md L110-123",
"line": 518
},
{
"id": "PRED.k320.ci-enforcement",
"klass": "PRED",
"value": "scripts/changeset/lint.cjs",
"line": 524
},
{
"id": "PRED.k320.ci-paths-monitored",
"klass": "PRED",
"value": "bin/ gsd-core/ agents/ commands/ docs/ hooks/ tests/ scripts/",
"line": 525
},
{
"id": "PRED.k320.cure",
"klass": "PRED",
"value": "drop .changeset/<adj>-<noun>-<noun>.md fragment ONLY",
"line": 520
},
{
"id": "PRED.k320.evidence",
"klass": "PRED",
"value": "PR #3302 merge-conflict against #3308 CHANGELOG.md row 2026-05-09",
"line": 527
},
{
"id": "PRED.k320.opt-out-label",
"klass": "PRED",
"value": "no-changelog",
"line": 523
},
{
"id": "PRED.k320.recovery",
"klass": "PRED",
"value": "open Removed-typed cleanup PR deleting only the redundant row",
"line": 526
},
{
"id": "PRED.k320.rule",
"klass": "PRED",
"value": "do not edit CHANGELOG.md in feature/fix/enhancement PRs",
"line": 519
},
{
"id": "PRED.k320.signal",
"klass": "PRED",
"value": "changelog-direct-edit-forbidden",
"line": 517
},
{
"id": "PRED.k320.tool",
"klass": "PRED",
"value": "npm run changeset -- --type <T> --pr <NNN> --body \"...\"",
"line": 521
},
{
"id": "PRED.k320.types",
"klass": "PRED",
"value": "Added|Changed|Deprecated|Removed|Fixed|Security",
"line": 522
},
{
"id": "PRED.k321.evidence",
"klass": "PRED",
"value": "PRs #3304/#3305 (2026-05-09): real Minor/Major findings in body, 0 threads",
"line": 533
},
{
"id": "PRED.k321.poll-shape",
"klass": "PRED",
"value": "parse pulls/<n>/reviews body AND graphql reviewThreads",
"line": 531
},
{
"id": "PRED.k321.resolution",
"klass": "PRED",
"value": "address in code; no GraphQL resolveReviewThread needed for body-only findings",
"line": 532
},
{
"id": "PRED.k321.shape",
"klass": "PRED",
"value": "CR posts \"[!CAUTION] outside the diff\" findings in review BODY, not in reviewThreads",
"line": 530
},
{
"id": "PRED.k321.signal",
"klass": "PRED",
"value": "cr-outside-diff-range-finding",
"line": 529
},
{
"id": "PRED.k322.cure-1",
"klass": "PRED",
"value": "2nd retrigger ~10min after first ack",
"line": 538
},
{
"id": "PRED.k322.cure-2",
"klass": "PRED",
"value": "if silent at 50min, treat as silent-pass with maintainer flag in merge-commit body",
"line": 539
},
{
"id": "PRED.k322.distinct-from",
"klass": "PRED",
"value": "k080",
"line": 536
},
{
"id": "PRED.k322.evidence",
"klass": "PRED",
"value": "PR #3306 (2026-05-09): 0 reviews after 50min + 2 retriggers",
"line": 541
},
{
"id": "PRED.k322.merge-gate-impact",
"klass": "PRED",
"value": "k070 real_coderabbit_review_present unsatisfied; requires maintainer judgment",
"line": 540
},
{
"id": "PRED.k322.shape",
"klass": "PRED",
"value": "ack posted, real review never lands within [5s, 410s] cooldown after burst of N PRs <15min",
"line": 537
},
{
"id": "PRED.k322.signal",
"klass": "PRED",
"value": "cr-sustained-throttle",
"line": 535
},
{
"id": "PRED.k323.cure-alt",
"klass": "PRED",
"value": "consolidate into single PR when 2+ issues share root cause",
"line": 546
},
{
"id": "PRED.k323.cure-pre-dispatch",
"klass": "PRED",
"value": "brief one agent canonical-owner; brief others to EXCLUDE shared site",
"line": 545
},
{
"id": "PRED.k323.evidence",
"klass": "PRED",
"value": "#3300 (#3297) overlapped #3306 (#3298) on add-backlog.md hunks 2026-05-09",
"line": 548
},
{
"id": "PRED.k323.recovery",
"klass": "PRED",
"value": "close smaller PR as \"subsumed by #N\" or rebase second to drop overlap hunk",
"line": 547
},
{
"id": "PRED.k323.shape",
"klass": "PRED",
"value": "2+ open issues touch same canonical bug site; each fix's sibling-audit produces overlapping diff",
"line": 544
},
{
"id": "PRED.k323.signal",
"klass": "PRED",
"value": "sibling-audit-cross-pr-overlap",
"line": 543
},
{
"id": "PRED.k324.cure",
"klass": "PRED",
"value": "verify via gh api on every agent-completion notification; never trust narrative",
"line": 552
},
{
"id": "PRED.k324.evidence",
"klass": "PRED",
"value": "2026-05-09 session: 5+ mid-monitor terminations across PRs #3232/#3271/#3251/#3255/#3262",
"line": 554
},
{
"id": "PRED.k324.k095-restatement",
"klass": "PRED",
"value": "k095 confirmed shape: agent reports \"waiting for monitor\" / \"tests still running\" then terminates",
"line": 551
},
{
"id": "PRED.k324.poll-shape",
"klass": "PRED",
"value": "gh pr view <n> --json mergeStateStatus,statusCheckRollup + pulls/<n>/reviews + graphql reviewThreads + issues/<n>/comments tail",
"line": 553
},
{
"id": "PRED.k324.signal",
"klass": "PRED",
"value": "agent-terminates-mid-monitor",
"line": 550
},
{
"id": "PRED.k325.cleanup",
"klass": "PRED",
"value": "git worktree remove --force <path> for aged agent worktrees",
"line": 559
},
{
"id": "PRED.k325.cure",
"klass": "PRED",
"value": "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>",
"line": 558
},
{
"id": "PRED.k325.evidence",
"klass": "PRED",
"value": "2026-05-09 CHANGELOG.md strip on PRs #3300/#3302/#3304/#3305 required detached-HEAD",
"line": 560
},
{
"id": "PRED.k325.shape",
"klass": "PRED",
"value": "git checkout <branch> errors \"already used by worktree at <agent-worktree>\"",
"line": 557
},
{
"id": "PRED.k325.signal",
"klass": "PRED",
"value": "worktree-branch-lock-on-force-push",
"line": 556
},
{
"id": "PRED.k326.cure",
"klass": "PRED",
"value": "quote canonical doc verbatim in brief; mentally simulate \"if all N agents follow this brief literally, do they violate any rule?\"",
"line": 564
},
{
"id": "PRED.k326.evidence",
"klass": "PRED",
"value": "2026-05-09 brief \"k040 — update CHANGELOG.md\" → 5 of 8 agents violated CONTRIBUTING.md L110",
"line": 565
},
{
"id": "PRED.k326.shape",
"klass": "PRED",
"value": "N parallel agents amplify a single brief-vs-doc contradiction into N violations",
"line": 563
},
{
"id": "PRED.k326.signal",
"klass": "PRED",
"value": "brief-contradicts-canonical-doc",
"line": 562
},
{
"id": "PRED.k327.ack-shape",
"klass": "PRED",
"value": "body \"✅ Actions performed - Full review triggered\"",
"line": 568
},
{
"id": "PRED.k327.cooldown-normal",
"klass": "PRED",
"value": "[5s, 410s]",
"line": 571
},
{
"id": "PRED.k327.cooldown-throttled",
"klass": "PRED",
"value": "k322",
"line": 572
},
{
"id": "PRED.k327.distinguish-key",
"klass": "PRED",
"value": "len(pulls/<n>/reviews) — ack=0, real=≥1",
"line": 570
},
{
"id": "PRED.k327.real-review-shape",
"klass": "PRED",
"value": "body starts \"Actionable comments posted: N\" OR \"[!CAUTION] Some comments are outside the diff\"",
"line": 569
},
{
"id": "PRED.k327.signal",
"klass": "PRED",
"value": "cr-ack-vs-real-review",
"line": 567
},
{
"id": "PRED.k328.audit-list",
"klass": "PRED",
"value": "[heading-matches-class, closing-keyword-present, changeset-fragment-or-no-changelog-label]",
"line": 577
},
{
"id": "PRED.k328.canonical-source",
"klass": "PRED",
"value": "CONTRIBUTING.md L101",
"line": 575
},
{
"id": "PRED.k328.k100-restatement",
"klass": "PRED",
"value": "heading must match issue class: bug→## Fix PR, enhancement→## Enhancement PR, feature→## Feature PR",
"line": 576
},
{
"id": "PRED.k328.signal",
"klass": "PRED",
"value": "pr-template-typed-heading-required",
"line": 574
},
{
"id": "PRED.k329.body",
"klass": "PRED",
"value": "**<Bold user-visible change>** — <symptom-led explanation>. (#<NNN>)",
"line": 583
},
{
"id": "PRED.k329.canonical-source",
"klass": "PRED",
"value": "CONTRIBUTING.md L112-117 + .changeset/README.md",
"line": 580
},
{
"id": "PRED.k329.filename",
"klass": "PRED",
"value": ".changeset/<adj>-<noun>-<noun>.md",
"line": 581
},
{
"id": "PRED.k329.frontmatter",
"klass": "PRED",
"value": "---\\\\ntype: <Added|Changed|Deprecated|Removed|Fixed|Security>\\\\npr: <NNN>\\\\n---",
"line": 582
},
{
"id": "PRED.k329.observed-clean",
"klass": "PRED",
"value": "#3299 sunny-ibex-wave, #3301 sturdy-rams-caper, #3306 3298-phase-dir-prefix-drift-workflows",
"line": 584
},
{
"id": "PRED.k329.signal",
"klass": "PRED",
"value": "changeset-fragment-canonical-shape",
"line": 579
},
{
"id": "PRED.k330.fallback",
"klass": "PRED",
"value": "append predicate-format findings directly to CONTEXT.md",
"line": 588
},
{
"id": "PRED.k330.shape",
"klass": "PRED",
"value": "mempalace MCP tools require explicit user call; AI cannot trigger",
"line": 587
},
{
"id": "PRED.k330.signal",
"klass": "PRED",
"value": "mempalace-diary-not-callable-by-ai",
"line": 586
},
{
"id": "PRED.k331.cure",
"klass": "PRED",
"value": "gh pr close <n> with NO --comment flag",
"line": 593
},
{
"id": "PRED.k331.evidence",
"klass": "PRED",
"value": "2026-05-09 wave-3: violation on #3300 close, deleted within 30s",
"line": 595
},
{
"id": "PRED.k331.k101-restatement",
"klass": "PRED",
"value": "k101 includes close-time --comment flag; rationale belongs in subsuming PR's squash-merge body",
"line": 592
},
{
"id": "PRED.k331.recovery",
"klass": "PRED",
"value": "if violation lands, gh api -X DELETE repos/<o>/<r>/issues/comments/<id>",
"line": 594
},
{
"id": "PRED.k331.shape",
"klass": "PRED",
"value": "instruction \"close with no comment (rationale)\" — parenthetical is rationale, NOT comment body",
"line": 591
},
{
"id": "PRED.k331.signal",
"klass": "PRED",
"value": "close-with-no-comment-is-literal",
"line": 590
},
{
"id": "PROC.AGENT-DISPATCH.completion-verify",
"klass": "PROC",
"value": "run k324.poll-shape on every agent-completion notification",
"line": 599
},
{
"id": "PROC.AGENT-DISPATCH.parallel-overlap-audit",
"klass": "PROC",
"value": "before dispatching N sibling-audit fixers, compute file-set union and assign canonical owners",
"line": 598
},
{
"id": "PROC.AGENT-DISPATCH.preflight",
"klass": "PROC",
"value": "[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]",
"line": 597
},
{
"id": "PROC.MERGE-WAVE.changelog-strip-pattern",
"klass": "PROC",
"value": "detached-HEAD per k325 + git checkout main -- CHANGELOG.md + commit + force-with-lease",
"line": 603
},
{
"id": "PROC.MERGE-WAVE.merge-tool",
"klass": "PROC",
"value": "gh pr merge <n> --squash --delete-branch",
"line": 604
},
{
"id": "PROC.MERGE-WAVE.merge-tool-warning",
"klass": "PROC",
"value": "delete-branch may fail with \"used by worktree at\" — harmless; remote branch still deleted",
"line": 605
},
{
"id": "PROC.MERGE-WAVE.ordering",
"klass": "PROC",
"value": "[wave1: isolated-files, wave2: CHANGELOG-only-overlap (better: strip per k320), wave3: same-file-overlap with explicit decision]",
"line": 601
},
{
"id": "PROC.MERGE-WAVE.preflight",
"klass": "PROC",
"value": "gh pr view <n> --json files for every PR; identify overlap pairs; surface to maintainer",
"line": 602
},
{
"id": "PROC.PARALLEL-FIX-DISPATCH.observed",
"klass": "PROC",
"value": "#3541 + #3542 dispatched simultaneously this session; PRs #3546 #3547 opened green; one syntax slip caught by AGENT-RETIRED-SLASH-SYNTAX-DRIFT and fixed before second PR opened",
"line": 869
},
{
"id": "PROC.PARALLEL-FIX-DISPATCH.pattern",
"klass": "PROC",
"value": "bot triage brief → worktree per branch → parallel sub-agents do rubber-duck/RCA/TDD implementation only → top-level orchestrator owns commit + gsd-test-summary --both + push + PR + changeset-pr-backfill",
"line": 867
},
{
"id": "PROC.PARALLEL-FIX-DISPATCH.rationale",
"klass": "PROC",
"value": "long-running test runs need cross-turn notifications (orchestrator-only); CONTRIBUTING.md gh-templates-first hook requires session-scoped Read calls sub-agents wouldn't otherwise make; sequencing test runs avoids GSD-TEST-CONCURRENT-OUTPUT-COLLISION",
"line": 868
},
{
"id": "PROC.TRIAGE.comment-shape",
"klass": "PROC",
"value": "lead with \"duplicate of #NNNN, fixed by PR #MMMM, in v1.X.Y\"; show current code snippet proving bug-surface gone; give @latest and @next upgrade commands; close",
"line": 874
},
{
"id": "PROC.TRIAGE.no-duplicate-label",
"klass": "PROC",
"value": "this repo has no duplicate label; framing lives in comment text + closing the issue",
"line": 875
},
{
"id": "PROC.TRIAGE.routing-incoming",
"klass": "PROC",
"value": "stale-bug-already-fixed to close as duplicate of originating issue + cite fix PR + first stable tag; release-publish-or-backport to ready-for-human; reporter-can-self-test to awaiting-retest",
"line": 873
},
{
"id": "RELEASE-NOTES.ANTI-PATTERN",
"klass": "RELEASE-NOTES",
"value": "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",
"line": 494
},
{
"id": "RELEASE-NOTES.ANTI-PATTERN.implementation-first",
"klass": "RELEASE-NOTES",
"value": "do not lead bullet with file path or function name; lead with symptom/user-visible behavior",
"line": 495
},
{
"id": "RELEASE-NOTES.ANTI-PATTERN.risk-commentary",
"klass": "RELEASE-NOTES",
"value": "do not include \"may break\", \"be careful\", \"test thoroughly\" - per global CLAUDE.md no-risk-commentary rule",
"line": 496
},
{
"id": "RELEASE-NOTES.DEFAULT-STATE",
"klass": "RELEASE-NOTES",
"value": "auto-generated body is \"What's Changed\" PR list + Full Changelog link; treat as draft, not final",
"line": 470
},
{
"id": "RELEASE-NOTES.EXAMPLE.hotfix",
"klass": "RELEASE-NOTES",
"value": "v1.41.1 (https://github.com/open-gsd/gsd-core/releases/tag/v1.41.1) - 14 fixes grouped by 6 subgroups",
"line": 498
},
{
"id": "RELEASE-NOTES.EXAMPLE.minor-auto-acceptable",
"klass": "RELEASE-NOTES",
"value": "v1.41.0 - kept auto-generated body; many small fixes with clean conventional-commit titles",
"line": 500
},
{
"id": "RELEASE-NOTES.EXAMPLE.rc",
"klass": "RELEASE-NOTES",
"value": "v1.42.0-rc1 (https://github.com/open-gsd/gsd-core/releases/tag/v1.42.0-rc1) - intro + Added/Changed/Fixed/Documentation taxonomy",
"line": 499
},
{
"id": "RELEASE-NOTES.GATE.hotfix",
"klass": "RELEASE-NOTES",
"value": "manual edit required; auto-generated body for vX.Y.{Z>0} is \"Full Changelog only\" and must be replaced with structured body",
"line": 471
},
{
"id": "RELEASE-NOTES.GATE.minor",
"klass": "RELEASE-NOTES",
"value": "auto-generated body acceptable when PR titles are clean; promote to structured body when >20 PRs or contains feature+refactor+fix mix",
"line": 473
},
{
"id": "RELEASE-NOTES.GATE.rc",
"klass": "RELEASE-NOTES",
"value": "manual edit recommended; auto-generated PR list is acceptable for early RCs but final RC before vX.Y.0 should match standard",
"line": 472
},
{
"id": "RELEASE-NOTES.RELEASE-STREAM.main-branch",
"klass": "RELEASE-NOTES",
"value": "next (RCs) + latest (stable); install via @next or @latest",
"line": 505
},
{
"id": "RELEASE-NOTES.RELEASE-STREAM.rule",
"klass": "RELEASE-NOTES",
"value": "streams do not mix; do not document @next in hotfix/stable notes",
"line": 506
},
{
"id": "RELEASE-NOTES.SCOPE",
"klass": "RELEASE-NOTES",
"value": "GitHub Releases body for tags vX.Y.Z, vX.Y.Z-rcN; not CHANGELOG.md (changeset workflow owns that)",
"line": 469
},
{
"id": "RELEASE-NOTES.SOURCE.changesets",
"klass": "RELEASE-NOTES",
"value": ".changeset/*.md (frontmatter pr: + body bullets)",
"line": 485
},
{
"id": "RELEASE-NOTES.SOURCE.commits",
"klass": "RELEASE-NOTES",
"value": "git log <prev-tag>..<this-tag> --pretty=format:'%s%n%n%b' --no-merges",
"line": 484
},
{
"id": "RELEASE-NOTES.SOURCE.pr-bodies",
"klass": "RELEASE-NOTES",
"value": "gh pr view <NNN> --json title,body for fixes lacking a changeset",
"line": 486
},
{
"id": "RELEASE-NOTES.SOURCE.precedence",
"klass": "RELEASE-NOTES",
"value": "changeset body > commit body > PR body > commit subject (prefer authored content over auto-generated)",
"line": 487
},
{
"id": "RELEASE-NOTES.STANDARD.bullet-shape",
"klass": "RELEASE-NOTES",
"value": "**Bold user-visible change** — explanation of what was broken or what's new, leading with symptom not implementation. Trailing (#NNN) PR ref.",
"line": 477
},
{
"id": "RELEASE-NOTES.STANDARD.footer.full-changelog",
"klass": "RELEASE-NOTES",
"value": "**Full Changelog**: https://github.com/open-gsd/gsd-core/compare/<prev>...<this>",
"line": 481
},
{
"id": "RELEASE-NOTES.STANDARD.footer.hotfix",
"klass": "RELEASE-NOTES",
"value": "Install/upgrade: \\`npx @opengsd/gsd-core@latest\\`",
"line": 479
},
{
"id": "RELEASE-NOTES.STANDARD.footer.rc",
"klass": "RELEASE-NOTES",
"value": "Install for testing: \\`npx @opengsd/gsd-core@next\\` (per branch->dist-tag policy)",
"line": 480
},
{
"id": "RELEASE-NOTES.STANDARD.heading-level",
"klass": "RELEASE-NOTES",
"value": "## for category, ### for subgroup (area), - for bullet",
"line": 476
},
{
"id": "RELEASE-NOTES.STANDARD.intro",
"klass": "RELEASE-NOTES",
"value": "optional one-paragraph framing for RC/feature releases; omit for pure-fix hotfixes",
"line": 482
},
{
"id": "RELEASE-NOTES.STANDARD.subgroups",
"klass": "RELEASE-NOTES",
"value": "phase-planning-state | workstream | query-dispatch-cli | code-review | install | capture | docs | architecture | security",
"line": 478
},
{
"id": "RELEASE-NOTES.STANDARD.taxonomy",
"klass": "RELEASE-NOTES",
"value": "Keep-a-Changelog 1.1.0: Added | Changed | Deprecated | Removed | Fixed | Security | Documentation",
"line": 475
},
{
"id": "RELEASE-NOTES.TEMPLATE.hotfix",
"klass": "RELEASE-NOTES",
"value": "## Fixed\\n\\n### <subgroup>\\n- **<bold change>** — <explanation>. (#<PR>)\\n\\n---\\n\\nInstall/upgrade: \\`npx @opengsd/gsd-core@latest\\`\\n\\n**Full Changelog**: <compare-url>",
"line": 502
},
{
"id": "RELEASE-NOTES.TEMPLATE.rc",
"klass": "RELEASE-NOTES",
"value": "<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 @opengsd/gsd-core@next\\n\\`\\`\\`\\n\\n**Full Changelog**: <compare-url>",
"line": 503
},
{
"id": "RELEASE-NOTES.WORKFLOW.edit",
"klass": "RELEASE-NOTES",
"value": "gh release edit <tag> --notes-file <path>",
"line": 489
},
{
"id": "RELEASE-NOTES.WORKFLOW.idempotency",
"klass": "RELEASE-NOTES",
"value": "gh release edit overwrites body wholesale; safe to re-run after refining",
"line": 492
},
{
"id": "RELEASE-NOTES.WORKFLOW.token",
"klass": "RELEASE-NOTES",
"value": "must use .envrc GITHUB_TOKEN per project CLAUDE.md; never ambient gh auth",
"line": 491
},
{
"id": "RELEASE-NOTES.WORKFLOW.view",
"klass": "RELEASE-NOTES",
"value": "gh release view <tag> --json body --jq .body",
"line": 490
},
{
"id": "RULESET.ADR-HEADER",
"klass": "RULESET",
"value": "every docs/adr/NNNN-*.md must open with - **Status:** Accepted|Proposed|Deprecated + - **Date:** YYYY-MM-DD immediately after title",
"line": 399
},
{
"id": "RULESET.AGENT_SIZE_BUDGET",
"klass": "RULESET",
"value": "agent-size-budget (#1074; sibling of WORKFLOW_SIZE_BUDGET; BYTES not lines per #717/#683, rebased from lines in PR 3/3) = per-file baseline (PRIMARY anti-creep: tests/agent-size-baseline.json pins each agents/gsd-*.md exact byte size) + loose tier hard caps (red lines, never raised on approach: XL<=57344 / LARGE<=49152 / DEFAULT<=24576); net-new agents are DEFAULT-tier (no separate new-file cap). One 'npm run size:baseline' regenerates BOTH workflow and agent baselines via the shared scripts/workflow-size.cjs measureMdFiles(dir,predicate) counter. A grown agent fails the baseline guard — regenerate + justify, or extract LAZILY to gsd-core/references/. DISTINCT from DEFECT.AGENT-FILE-SIZE-CAP-BREACH (a separate 45K-CHAR extraction-evidence threshold on gsd-planner via planner-decomposition/reachability tests): that guard proves mode-sections were extracted; this one bounds total agent bytes. Two guards, two units (chars vs bytes), two purposes",
"line": 386
},
{
"id": "RULESET.ALLOWED-TOOLS-FRONTMATTER",
"klass": "RULESET",
"value": "command's allowed-tools must cover every tool the workflow calls (including Write for file creation); thin-wrapper pattern makes this easy to miss",
"line": 392
},
{
"id": "RULESET.ARGUMENTS-SANITIZE",
"klass": "RULESET",
"value": "any workflow step constructing .planning/.../{SLUG}.md path from user input ($ARGUMENTS, parsed remainder) must sanitize inline ([a-z0-9-] only, reject ..//\\\\, max-length) — \"(already sanitized)\" must trace back to explicit guard; RESUME/fallback modes need own guards",
"line": 393
},
{
"id": "RULESET.AUDIT.search-source-not-generated",
"klass": "RULESET",
"value": "verify an invariant/validation EXISTS by searching the AUTHORED source (src/*.cts OR the scripts/gen-*.cjs generator), never the generated bin/lib/*.cjs (gitignored, ADR-457); gen-time checks live in gen-*.cjs not the .cts it consumes → search BOTH before declaring absent; read generated .cjs only for output drift. Repro: grep src/*.cts for VALID_CONVERTER_NAMES → false \"5e ConverterName unenforced\"; actually enforced in gen-capability-registry.cjs. cf RULESET.TESTS.no-source-grep",
"line": 382
},
{
"id": "RULESET.CAPABILITY.cutover-self-gating",
"klass": "RULESET",
"value": "a phase-6 per-feature cutover moves the host's phase-context detection + mode/flag logic INTO the skill (self-gating, per ADR-894); the loop hook is intentionally COARSE — \"invoke skill X at point Y when config Z\" — and carries no detection/mode. WORKED EXAMPLE: plan-phase.md §5.6 UI gate (frontend-detection via ui-safety-gate.cjs + --auto/manual branch + --skip-ui bypass) must move into gsd-ui-phase before its plan:pre hook can replace the inline call without behavior loss. Spike #1018 finding.",
"line": 231
},
{
"id": "RULESET.CAPABILITY.off-means-off",
"klass": "RULESET",
"value": "the host derives shared outputs from the ACTIVE hook set (via loop.render-hooks); a hook may ADD a labeled block or be COUNTED into a host-computed aggregate (e.g. a score denominator), but NEVER mutates host source — so a disabled capability yields the base output by construction, not by authoring discipline. Ratify in ADR-894; proven by spike #1018.",
"line": 229
},
{
"id": "RULESET.CAPABILITY.precedence-engine-single-owner",
"klass": "RULESET",
"value": "the config-key four-level precedence walk (loadConfig result → workstream config.json → root config.json → registry.configSchema default → absent) is owned solely by src/capability-activation.cts: raw-value primitive resolveConfigKey(dotKey, {config,cwd,registry}) and boolean wrapper _resolveActivationValue(dotKey,config,cwd,registry); loop-resolver.cts imports the engine (no duplicate); resolveConfigValues in loop-resolver.cts delegates to resolveConfigKey; resolveCapabilityRuntimeState does NOT return registry/config — callers import capability-registry.cjs and call loadConfig(cwd) directly.",
"line": 235
},
{
"id": "RULESET.CAPABILITY.step-additive-gate-blocks",
"klass": "RULESET",
"value": "a `step` hook is purely additive (invoke skill + produce artifacts, NEVER halts the host); host-blocking preconditions are `gate`s (blocking:true, onError:halt); runtime/mode context (auto/chain vs manual) self-gates IN THE SKILL, not via `when` (config-only). §5.6 = plan:pre step (ui-phase; skill self-gates on frontend+pipeline, auto-fires only in pipelines) + a NEW plan:pre gate (frontend-and-no-UI-SPEC → halt, when:workflow.ui_safety_gate); the loop.render-hooks dispatch template handles steps AND gates. Resolves #1022.",
"line": 233
},
{
"id": "RULESET.CODERABBIT.GUARD.COMPLETE",
"klass": "RULESET",
"value": "required_checks_green && coderabbit_check_pass && graphQL(reviewThreads.unresolved_count)==0",
"line": 421
},
{
"id": "RULESET.CODERABBIT.GUARD.GRAPHQL",
"klass": "RULESET",
"value": "reviewThreads(first:100){nodes{id isResolved comments{nodes{author body path line originalLine url}}}}; use unresolved threads as authoritative, not badge text alone",
"line": 422
},
{
"id": "RULESET.CODERABBIT.GUARD.OPEN_PRS",
"klass": "RULESET",
"value": "gh pr list --repo open-gsd/gsd-core --author @me --state open; repeat near end because open PR set can change mid-run",
"line": 420
},
{
"id": "RULESET.CODERABBIT.GUARD.RERUN",
"klass": "RULESET",
"value": "after every push wait for CodeRabbit completion, then re-query unresolved threads; CodeRabbit can add new findings after earlier threads were resolved",
"line": 423
},
{
"id": "RULESET.CODERABBIT.GUARD.RESOLVE",
"klass": "RULESET",
"value": "fix validated finding -> focused tests -> commit/push -> resolveReviewThread(threadId) -> wait CI/CodeRabbit -> final unresolved_count query",
"line": 424
},
{
"id": "RULESET.CODERABBIT.GUARD.SCOPE",
"klass": "RULESET",
"value": "if a new @me open PR appears during final list, include it in the same guard pass before declaring all-open-PRs complete",
"line": 425
},
{
"id": "RULESET.CONTENT-PATH-NORMALIZATION",
"klass": "RULESET",
"value": "filesystem paths substituted into markdown body text (@-references, workflow .md, agent .md, generated docs, command bodies) MUST be normalized to POSIX forward slashes via .replace(/\\\\/g,'/') at the production source BEFORE substitution; never push normalization to tests; cross-platform content is POSIX-only; applies to: computePathPrefix output, install-path rewrites, generated shim paths emitted into .md bodies; idempotent on POSIX so unconditional",
"line": 743
},
{
"id": "RULESET.CONTRIB.CLASSIFY.enhancement",
"klass": "RULESET",
"value": "requires approved-enhancement before implementation",
"line": 414
},
{
"id": "RULESET.CONTRIB.CLASSIFY.feature",
"klass": "RULESET",
"value": "requires approved-feature before implementation",
"line": 415
},
{
"id": "RULESET.CONTRIB.CLASSIFY.fix",
"klass": "RULESET",
"value": "requires confirmed/confirmed-bug before implementation",
"line": 413
},
{
"id": "RULESET.CONTRIB.GATE.ORDER",
"klass": "RULESET",
"value": "issue-first -> approval-label -> code -> PR-link -> changeset/no-changelog",
"line": 412
},
{
"id": "RULESET.CR-THREAD-RESOLVE",
"klass": "RULESET",
"value": "after adding // allow-test-rule: to silence lint, resolve existing inline CR threads via graphql resolveReviewThread mutation before merge — open threads mislead future reviewers; pattern: gh api graphql -f query='mutation { resolveReviewThread(input:{threadId:\"PRRT_...\"}) { thread { isResolved } } }'",
"line": 406
},
{
"id": "RULESET.GEMINI.TEST_SENTINEL",
"klass": "RULESET",
"value": "convertClaudeToGeminiAgent regression should assert tools excludes ask_user, body excludes AskUserQuestion/ask_user, and Read still maps to read_file",
"line": 397
},
{
"id": "RULESET.GEMINI.TEST_SENTINEL",
"klass": "RULESET",
"value": "convertClaudeToGeminiAgent regression should assert tools excludes ask_user, body excludes AskUserQuestion/ask_user, and Read still maps to read_file",
"line": 429
},
{
"id": "RULESET.GEMINI.TOOLS.ask_user",
"klass": "RULESET",
"value": "Gemini CLI has no ask_user tool; filter both AskUserQuestion and lowercase ask_user from tools frontmatter and neutralize both names in body text",
"line": 396
},
{
"id": "RULESET.GEMINI.TOOLS.ask_user",
"klass": "RULESET",
"value": "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",
"line": 428
},
{
"id": "RULESET.GH.AUTH.DEFAULT",
"klass": "RULESET",
"value": "source .envrc GITHUB_TOKEN before gh; exception=ambient allowed only when user explicitly says machine-only fallback",
"line": 419
},
{
"id": "RULESET.HARNESS.test-memory-guard",
"klass": "RULESET",
"value": "~/.claude/hooks/test-memory-guard.sh fires on every Bash PreToolUse; if argv[0]∈{node|vitest|jest|mocha|tsx|ts-node|tap|ava|playwright|cypress} OR matches (npm|pnpm|yarn|bun) (run )?(t|test|tests|vitest|jest); blocks via hookSpecificOutput.permissionDecision=deny when sum(RSS of running matching procs, excluding tsserver|*-mcp|claude|Electron|...) ≥ 4 GiB OR when argv[0] basename matches a running process's argv[0]. Exception: node --version|-v|--help|-h|-p|-e are trivial probes and skip the check. Designed for a 24 GB Mac where prior accidental fan-out exhausted RAM",
"line": 827
},
{
"id": "RULESET.MANIFEST-CANONICAL-KEY",
"klass": "RULESET",
"value": "docs/INVENTORY-MANIFEST.json has a single top-level key: families; ALL SIX families.* arrays (agents/commands/workflows/references/cli_modules/hooks) are canonical, consumed by test suites — tests/inventory-manifest-sync.test.cjs reads all six, edit-phase/enh-2380/enh-2430 tests read commands+workflows; the old generated date field and the stale top-level workflows key are both gone; regen via node scripts/gen-inventory-manifest.cjs --write",
"line": 400
},
{
"id": "RULESET.PR-FLOW.docker-before-push",
"klass": "RULESET",
"value": "before ANY git push of any fix to any PR, run gsd-test-summary (docker on the remote, mirrors ubuntu CI) and confirm exit 0. macOS-local node --test is NOT a substitute — many failures are platform-specific (path separators, case sensitivity, locale, fs semantics). Watchdog with Monitor on the output log; never set a sleep/timer and walk away. Source: user feedback 2026-05-16 — \"we don't set a timer we actively watch and record results in real time as possible\"",
"line": 829
},
{
"id": "RULESET.PR-FLOW.templates-mandatory",
"klass": "RULESET",
"value": "every gh pr create|edit|gh issue create|edit MUST first invoke the gh-templates-first skill and Read (Read tool, not Bash cat — k321 read-tracking) the matching template in .github/. Apply ALL required sections; never write freeform bodies. Repo enforces this via gsd-pr-template-policy GitHub Action which flags any non-templated body — the bot allows the PR to stay open only because authors are contributors-or-higher, but the warning is a real complaint that must be cured. Source: user feedback 2026-05-16 (multi-message escalation) — \"the whole reason i have that github action is because you fucking blow through and ignore using the templates\"",
"line": 831
},
{
"id": "RULESET.PR-SCOPE.one-concern-per-pr",
"klass": "RULESET",
"value": "split unrelated changes into separate PRs; cherry-pick doc changes to dedicated docs/ branch immediately, then force-push original to remove the commit",
"line": 402
},
{
"id": "RULESET.SHARED-HELPERS-LINT-VS-TEST",
"klass": "RULESET",
"value": "when a lint script and test suite both implement same constant (CANONICAL_TOOLS) or parser (parseFrontmatter, executionContextRefs), extract to scripts/*-helpers.cjs required by both — silent divergence otherwise",
"line": 394
},
{
"id": "RULESET.TESTS.CODERABBIT_FIX",
"klass": "RULESET",
"value": "prefer exported-function behavioral tests over source-grep; lint-no-source-grep rejects readFileSync source assertions without allow-test-rule",
"line": 426
},
{
"id": "RULESET.TESTS.boundary-coverage",
"klass": "RULESET",
"value": "tests MUST exercise inputs at and near the threshold/limit, not only trivial-fit and trivial-overflow; pick inputs where N ∈ {limit-1, limit, limit+1} and where pre-trim/pre-check accumulators ≈ effective limit; \"very small\" and \"very large\" inputs alone do not constitute edge-case coverage and routinely miss off-by-one + reservation-accounting bugs",
"line": 370
},
{
"id": "RULESET.TESTS.boundary-coverage.anti-pattern",
"klass": "RULESET",
"value": "test suites that pair budget:1_000_000 (trivially fits) with budget:1 (trivially overflows) and skip the boundary region; failure mode that shipped PR #3708 UNNEEDED_TRIM + FALSE_HARDFAIL regressions (commit 2df566ed, fixed bde1ae8f)",
"line": 372
},
{
"id": "RULESET.TESTS.boundary-coverage.fixtures",
"klass": "RULESET",
"value": "for any code with budget/limit/quota/threshold parameter, test suite MUST include: (a) input where SUT estimate == limit exactly, (b) input where estimate == limit - 1, (c) input where estimate == limit + 1, (d) input where any internal reserve/safety constant pushes baseline within reserve-distance of limit (catches early-pressure firing)",
"line": 371
},
{
"id": "RULESET.TESTS.clock-seam",
"klass": "RULESET",
"value": "concurrency logic must accept an optional {clock=Date} parameter; tests control time via t.mock.timers.enable(['Date']) + t.mock.timers.setTime(0) + t.mock.timers.tick(N); real OS scheduler races are not a permitted test pattern after ADR 456 (2026-05-28); real-race tests are deleted once deterministic seam tests cover the same logical path; clock.cjs realClock adds nowIso() (→ new Date(this.now()).toISOString()) and today() (→ nowIso().split('T')[0]) so all date-stamping in state.cjs routes through the seam; subprocess time-pin adapter: set GSD_TEST_MODE=1 + GSD_NOW_MS=<epoch-ms> in runGsdTools env to pin the date written by the SUT without touching real wall-clock (issue #474)",
"line": 376
},
{
"id": "RULESET.TESTS.coderabbit-fix-prefer",
"klass": "RULESET",
"value": "behavioral tests (call exported fn, capture JSON, assert typed fields) over source-grep",
"line": 368
},
{
"id": "RULESET.TESTS.delete-bad-tests",
"klass": "RULESET",
"value": "pass-always / vacuous-truth / source-grep / elapsed-time / real-race / permanent-allow-test-rule tests are DELETED and replaced with compliant tests in the same PR; not skipped, not commented out, not permanently exempted; replacement must cover the same logical path via typed-surface assertion or clock-seam pattern",
"line": 379
},
{
"id": "RULESET.TESTS.diagnostics",
"klass": "RULESET",
"value": "after JSON.parse, assert output shape (Array.isArray(output.phases)) with raw-output-prefix diagnostics before .map() — prevents opaque TypeErrors when CLI output shape changes",
"line": 369
},
{
"id": "RULESET.TESTS.escape-regex",
"klass": "RULESET",
"value": "new RegExp(\"prefix${var}\") must escapeRegex(var); phase-id.cjs exports escapeRegex (core.cjs re-export spine retired in epic #1267); phase IDs like 5.1 contain . which is metacharacter",
"line": 365
},
{
"id": "RULESET.TESTS.eslint-harness",
"klass": "RULESET",
"value": "ADR 452 (2026-05-28): ESLint flat config + typescript-eslint + eslint-plugin-n + eslint-plugin-no-only-tests + local plugin at scripts/eslint-rules/; replaces scripts/lint-*.cjs regex scanners; three test-rigor rules (local/no-source-grep, local/no-magic-sleep-in-tests, local/no-elapsed-assertion) ship at warn, promoted to error after #453 cleanup sweep merges",
"line": 380
},
{
"id": "RULESET.TESTS.guard-toplevel-readFileSync",
"klass": "RULESET",
"value": "module-level const src = readFileSync(...) throws before any test() registers — wrap in try/catch in test() or use lazy load",
"line": 367
},
{
"id": "RULESET.TESTS.mutation-score",
"klass": "RULESET",
"value": "Stryker runs incremental (--since origin/next) on ubuntu-latest/Node24 CI leg; default threshold 80% killed/total; surviving mutants in scope block merge unless path is listed in stryker.config.mjs with documented reason; treat surviving mutant as a failing test specification",
"line": 378
},
{
"id": "RULESET.TESTS.no-dead-regex-in-includes",
"klass": "RULESET",
"value": "src.includes(\"foo.*bar\") is always false — .* is regex metacharacter not wildcard; use new RegExp(...).test(src) or delete",
"line": 366
},
{
"id": "RULESET.TESTS.no-source-grep",
"klass": "RULESET",
"value": "scripts/lint-no-source-grep.cjs rejects readFileSync source + .includes()/.match()/.startsWith() on the bound var; CI hard-fail",
"line": 360
},
{
"id": "RULESET.TESTS.no-source-grep.exemption",
"klass": "RULESET",
"value": "// allow-test-rule: <runtime-contract-is-the-product> with one-line justification; reserved for tests where the file content IS the product surface (STATE.md, config.toml, hooks.json, agent .md). Migration to typed-IR parser tracked in #2974.",
"line": 362
},
{
"id": "RULESET.TESTS.no-source-grep.stdout-extension",
"klass": "RULESET",
"value": "also flags assert.match/doesNotMatch on .stdout/.stderr — emit JSON from SUT, parse, assert on typed fields",
"line": 361
},
{
"id": "RULESET.TESTS.no-source-grep.tmp-file-traps",
"klass": "RULESET",
"value": "reading tmp files written by the SUT in tests still trips lint; round-trip through CLI (e.g. frontmatter get) instead of readFileSync+.includes()",
"line": 363
},
{
"id": "RULESET.TESTS.no-timing-assertion",
"klass": "RULESET",
"value": "do not assert on wall-clock elapsed time (Date.now() delta, performance.now(), process.hrtime() comparison); such assertions test the host machine not the SUT and flake on loaded CI runners; enforcement: local/no-elapsed-assertion ESLint rule (warn → error after #453); canonical replacement: clock-seam pattern with node:test mock.timers",
"line": 375
},
{
"id": "RULESET.TESTS.property-based-testing",
"klass": "RULESET",
"value": "modules implementing parsing / transformation / budget-limit / bijective contracts must include at least one fast-check (fc) property test asserting a domain invariant; invariant categories: round-trip, monotonicity, boundary-containment, idempotency; property tests live in *.test.cjs alongside unit tests; CI signal: Stryker mutation score below 80% blocks merge",
"line": 377
},
{
"id": "RULESET.TRIAGE-EXISTING-WORK",
"klass": "RULESET",
"value": "before writing agent brief for 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 — recover existing work rather than re-implement",
"line": 404
},
{
"id": "RULESET.WORKFLOW.COVERAGE-METADATA",
"klass": "RULESET",
"value": "#1602 SUMMARY frontmatter `coverage:` block (list of {id,description,requirement?,verification:[{kind∈unit|integration|e2e|automated_ui|manual_procedural|other, ref, status∈pass|fail|unknown}],human_judgment:bool,rationale?}) is the per-deliverable RTM consumed DETERMINISTICALLY by verify-work extract_tests via `gsd-tools uat classify-coverage --summary <f>` (src/coverage.cts → bin/lib/coverage.cjs). AUTHORING: execute-plan create_summary populates it from task <verify> results; every deliverable MUST be classified; fail-safe default = human_judgment:true + rationale. CLASSIFY CONTRACT: auto-pass (skip human) ONLY when human_judgment===false (strict boolean) AND verification non-empty AND every status==='pass' AND zero validation errors — else PRESENT to human. mode:legacy (no block) ⇒ byte-identical prose `## Accomplishments` fall-through; `coverage: []` ⇒ mode:coverage, zero entries (single-confirmation). Frozen IR: MODE/PRESENT_REASON/ERROR_CODE enums locked by tests/coverage-metadata-parser.test.cjs. extractFrontmatter CANNOT parse it (scalars-only `-` items) → dedicated parser, sibling of parseMustHavesBlock. Asymmetry by design: false-negative=redundant prompt (status quo); false-positive=shipped bug UAT existed to catch",
"line": 390
},
{
"id": "RULESET.WORKFLOW_EXECUTE_END_TO_END",
"klass": "RULESET",
"value": "ADR-0002 standard for single-workflow commands is \"Execute end-to-end.\" (no bolded **Follow the X workflow** fragments); flag-dispatch routing uses \"execute the X workflow end-to-end.\" in routing bullets",
"line": 389
},
{
"id": "RULESET.WORKFLOW_EXECUTION_CONTEXT",
"klass": "RULESET",
"value": "@-ref in commands/gsd/*.md must resolve to an existing file on disk; regression test in tests/bug-3135-capture-backlog-workflow.test.cjs; INVENTORY.md row + INVENTORY-MANIFEST.json families.workflows must stay in sync; \"Invoked by\" attribution must move when a flag absorbs a micro-skill",
"line": 388
},
{
"id": "RULESET.WORKFLOW_FILE_NAMES",
"klass": "RULESET",
"value": "workflow files use hyphens; <step name=\"...\"> XML attributes must match (extract-learnings not extract_learnings); tests should pin exact hyphenated name",
"line": 387
},
{
"id": "RULESET.WORKFLOW_MARKDOWN.FENCES",
"klass": "RULESET",
"value": "preserve opening language fence when editing shell snippets in workflow markdown; malformed fence creates fresh CR threads (MD040)",
"line": 384
},
{
"id": "RULESET.WORKFLOW_MARKDOWN.FENCES",
"klass": "RULESET",
"value": "when editing shell snippets inside workflow markdown, preserve the opening language fence; malformed fence can create fresh CodeRabbit threads",
"line": 427
},
{
"id": "RULESET.WORKFLOW_SIZE_BUDGET",
"klass": "RULESET",
"value": "workflow size enforcement (#1074; BYTES not lines per #717; LF-normalized per #683) = per-file baseline (PRIMARY anti-creep: tests/workflow-size-baseline.json pins each file's exact size) + loose tier hard caps (outer red lines, NEVER raised on approach: XL<=98304 / LARGE<=61440 / DEFAULT<=40960) + new-file cap (un-baselined files <32768, the Codex anchor) + discuss-phase<32000; a file that grew fails the baseline guard — fix with `npm run size:baseline`, commit the one-line diff, and justify the growth in the PR (or extract LAZILY-loaded content; eager @-imports don't reduce loaded context); crossing a hard cap means EXTRACT, not bump",
"line": 385
},
{
"id": "SESSION.2026-05-05",
"klass": "SESSION",
"value": "[PRED.k320..k331 introduced; DEFECT.SOURCE-GREP-IN-NEW-TESTS, DEFECT.CHANGESET-PR-FIELD-DRIFT, DEFECT.PHASE-DIR-PREFIX-DRIFT, DEFECT.PROMPT-INJECTION-SCAN-COLLISION; ADR-0002 thin-wrapper pattern findings folded into RULESET.WORKFLOW_*]",
"line": 783
},
{
"id": "SESSION.2026-05-05.sdk-bridge",
"klass": "SESSION",
"value": "PR #3158 SDK Runtime Bridge — observability isolation rule; strict-mode dispatchMode reporting invariant; transport decision ordering (guard before event emission); folded into Dispatch Policy Module glossary",
"line": 784
},
{
"id": "SESSION.2026-05-09",
"klass": "SESSION",
"value": "[8-PR triage wave, 7 merged + 1 subsumed; META.RULE.* introduced; WAVE.LESSON.* captured; k320/k322/k323/k326/k331 evidence; AI Ops Memory predicate format established]",
"line": 785
},
{
"id": "SESSION.2026-05-10",
"klass": "SESSION",
"value": "[ai-ops memory consolidation; release-notes standard taxonomy + templates; RELEASE-NOTES.* predicates introduced]",
"line": 786
},
{
"id": "SESSION.2026-05-13",
"klass": "SESSION",
"value": "[Shell Command Projection Module expansion (#3465-#3468); ADR-0009 superseded; new exports for subprocess dispatch and platform file I/O; phase-gated migration plan; PR #3464 three-gate invariant CI+CR+unresolved=0; PR #3470 stash-include-untracked rebase pattern]",
"line": 787
},
{
"id": "SESSION.2026-05-14",
"klass": "SESSION",
"value": "[#3095/PR #3490 EXEC.CLASSIFY.* introduced (Anthropic/Copilot/Codex/Gemini cross-runtime rate-limit sentinel coverage); #3489/PR #3499 DEFECT.STATE-TRAMPLE.idempotency-oracle (STATE.md current_phase field is oracle for state.complete-phase); #3488/PR #3501 DAG resolver same-phase short-form depends_on (shortFormToId index added to sdk/src/query/phase.ts); #3491/PR #3502 DEFECT.NESTED-GIT-INIT (gitWorktreeInfoInternal helper); #3493/PR #3500 extractCurrentMilestone generic Phase Details continuation past planned-milestone siblings; #3503/PR #3504 DEFECT.PATH-SUBSTRING-CHECK (trailing-slash anchor for homedir checks); #3346/PR #3505 codex AoT TOML leaf-key via extractFlatHookEventName; #3506/PR #3507 label-scoped stale-bot sub-job pattern; multi-PR triage operational lessons folded into PROC.TRIAGE.*; #3508 DEFECT.AGENT-ISOLATION-SILENT-FAIL; gsd-test image-missing auto-build (locally-built image via embedded heredoc Dockerfile); refined PRED.k322 threshold to 3 PRs/<10min]",
"line": 788
},
{
"id": "SESSION.2026-05-15",
"klass": "SESSION",
"value": "[#3537/PR #3538 DEFECT.PHASE-REGEX-FANOUT — phaseMarkdownRegexSource promoted to core.cjs and wired to 7 sites; parity-style regression test established as DEFECT.GENERATIVE-FIX exemplar; trek-e/gsd-test-runner#1 filed for DEFECT.GSD-TEST-MIRROR-POISONED — chown-back-before-exec legacy gap (poisoned holodeck mirror unstuck via authorized docker chown to remote 1000:1000); RULESET.PR-FLOW.* codified from project CLAUDE.md load-bearing rule; first dispatch under run-tests-before-create held cleanly (PR #3520 worker stopped on Docker exit 12 infra failure, orchestrator opened PR after unblock); CONTEXT.md refactored from 882 lines of mixed prose+predicates into ~500 lines of pure-predicate format with chronological session log]",
"line": 789
},
{
"id": "SESSION.2026-05-15.parallel-fix-dispatch",
"klass": "SESSION",
"value": "[#3542/PR #3546 prohibit git stash family in executor agents (shared refs/stash across worktrees); #3541/PR #3547 non-TTY resolution for installer prompt-user actions (default remove for SDK build artifacts, keep for skills/gsd-*/SKILL.md); #3545 filed for gsd-test-summary concurrent /tmp output collision; new predicates DEFECT.HOOK-OVER-ENFORCEMENT.read-tool-tracking, DEFECT.GSD-TEST-CONCURRENT-OUTPUT-COLLISION, DEFECT.SUBAGENT-LONG-RUNNING-BG-STALL, DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT, PROC.PARALLEL-FIX-DISPATCH; agent-trust-but-verify caught /gsd-update retired-syntax comment slip in #3541 implementation before PR open]",
"line": 790
},
{
"id": "SESSION.2026-05-16",
"klass": "SESSION",
"value": "[multi-PR triage wave (#3577/3581/3640/3641/3642/3648/3649/3637/3639). Established global PreToolUse hook ~/.claude/hooks/test-memory-guard.sh denying new node/test spawns when sum(RSS of node|vitest|jest|...) >= 4 GiB on the 24 GB Mac OR when a same-runner process is already in argv[0] — hard deny via hookSpecificOutput.permissionDecision=deny. PR #3577 fix: revert config-ensure-section dispatch to CJS cmdConfigEnsureSection (SDK author wrote single-section semantics under a name whose legacy callers expect full-default config init); plus 3 SDK parity carve-outs (configNewProject defaults align with sdk/shared/config-defaults.manifest.json, return relative .planning/config.json path, drop quotes from Unknown config key, lead malformed-JSON error with \"Failed to read config.json:\"). PR #3649 fix: chunk node --test spawn at 28K argv ceiling (Windows CreateProcess lpCommandLine cap 32,767 was instantly aborting unchunked spawn of 546 paths). Chunking fix surfaced 14 pre-existing Windows-only test bugs (4010 pass / 14 fail; vs 0/0 before — entire suite was un-runnable on Windows). PRs #3639 + #3637 confirmed unable to stand alone (legitimately depend on Phase 6 scaffolding only present on feat/3575-enforcement-hardening) — user decision: cherry-pick into #3577 and close. Five other PRs each had ≤1 unresolved CR thread of the changeset-pr-number / null-vs-throw / implicit-Claude-runtime / docs-stale-guidance / hardcoded-tests-path family — all quick wins. New predicates: DEFECT.SDK-PORT-NAME-COLLISION, DEFECT.WINDOWS-ARGV-OVERFLOW, DEFECT.STACKED-PR-CANNOT-STAND-ALONE, DEFECT.CANARY-VERSION-LEAK, DEFECT.GSD-TEST-HOST-MID-RUN-DEATH, RULESET.HARNESS.test-memory-guard, RULESET.PR-FLOW.docker-before-push, RULESET.PR-FLOW.templates-mandatory]",
"line": 791
},
{
"id": "WAVE.LESSON.agent-narrative-unreliable",
"klass": "WAVE",
"value": "k095/k324 confirmed at scale: 5 of 8 agents terminated mid-monitor with stale claims requiring direct verification",
"line": 612
},
{
"id": "WAVE.LESSON.changelog-policy-violation-multiplier",
"klass": "WAVE",
"value": "brief contradicting CONTRIBUTING.md L110 produced violations on 5 of 8 PRs (#3300, #3302, #3304, #3305, #3308); k326 + k320 capture",
"line": 609
},
{
"id": "WAVE.LESSON.cr-throttle-burst-correlation",
"klass": "WAVE",
"value": "8 PRs in <15min triggered k322 sustained-throttle on multiple PRs (#3306 worst case)",
"line": 610
},
{
"id": "WAVE.LESSON.k101-still-trips",
"klass": "WAVE",
"value": "even after CONTEXT.md k101 reinforcement, agent of record posted self-PR comment on close; k331 adds explicit close-time literal-instruction guard",
"line": 613
},
{
"id": "WAVE.LESSON.sibling-audit-overlap",
"klass": "WAVE",
"value": "k015-family parallel dispatch on #3297 + #3298 produced k323 add-backlog.md cross-PR overlap",
"line": 611
},
{
"id": "WORKSTREAM.INVARIANT.migrate-name",
"klass": "WORKSTREAM",
"value": "must normalize through canonical slug policy",
"line": 444
},
{
"id": "WORKSTREAM.INVARIANT.slug-contract",
"klass": "WORKSTREAM",
"value": "all .planning/workstreams/<name> must be addressable by set/get/status/complete",
"line": 445
},
{
"id": "WORKSTREAM.NAME.POLICY.cjs-module",
"klass": "WORKSTREAM",
"value": "gsd-core/bin/lib/workstream-name-policy.cjs owns toWorkstreamSlug + active-name/path-segment validation",
"line": 461
},
{
"id": "WORKSTREAM.POINTER.SEAM.cjs-module",
"klass": "WORKSTREAM",
"value": "gsd-core/bin/lib/active-workstream-store.cjs owns read/write self-heal for .planning/active-workstream",
"line": 462
},
{
"id": "WORKSTREAM.REGRESSION.test-anchor",
"klass": "WORKSTREAM",
"value": "tests/workstream.test.cjs::normalizes --migrate-name to a valid workstream slug",
"line": 446
},
{
"id": "WORKTREE.SEAM.caller-rule",
"klass": "WORKTREE",
"value": "verify.cjs must consume inspectWorktreeHealth for W017 classification; no ad-hoc porcelain parsing in callers",
"line": 455
},
{
"id": "WORKTREE.SEAM.current",
"klass": "WORKTREE",
"value": "Worktree Safety Policy Module",
"line": 438
},
{
"id": "WORKTREE.SEAM.decision-1",
"klass": "WORKTREE",
"value": "retain non-destructive default; destructive path only as explicit future opt-in scaffold",
"line": 442
},
{
"id": "WORKTREE.SEAM.default-prune-policy",
"klass": "WORKTREE",
"value": "metadata_prune_only (non-destructive)",
"line": 441
},
{
"id": "WORKTREE.SEAM.execution-rule",
"klass": "WORKTREE",
"value": "prefer node --test tests/worktree-safety-policy.test.cjs for fast seam validation; avoid full npm test loop for seam-only changes",
"line": 453
},
{
"id": "WORKTREE.SEAM.files",
"klass": "WORKTREE",
"value": "[gsd-core/bin/lib/worktree-safety.cjs]",
"line": 439
},
{
"id": "WORKTREE.SEAM.interface",
"klass": "WORKTREE",
"value": "[resolveWorktreeContext, parseWorktreePorcelain, planWorktreePrune, executeWorktreePrunePlan, planWorktreeRecordAgent, cmdWorktreeRecordAgent]",
"line": 440
},
{
"id": "WORKTREE.SEAM.invariant",
"klass": "WORKTREE",
"value": "parser failure must degrade to metadata_prune_only and never escalate to destructive removal",
"line": 452
},
{
"id": "WORKTREE.SEAM.inventory-interface",
"klass": "WORKTREE",
"value": "[listLinkedWorktreePaths, inspectWorktreeHealth]",
"line": 454
},
{
"id": "WORKTREE.SEAM.inventory-snapshot",
"klass": "WORKTREE",
"value": "snapshotWorktreeInventory(repoRoot,{staleAfterMs,nowMs}) is canonical linked-worktree health snapshot for callers",
"line": 457
},
{
"id": "WORKTREE.SEAM.test-anchor-w017",
"klass": "WORKTREE",
"value": "tests/orphan-worktree-detection.test.cjs + tests/worktree-safety-policy.test.cjs",
"line": 456
},
{
"id": "WORKTREE.SEAM.test-anchors",
"klass": "WORKTREE",
"value": "[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]",
"line": 451
},
{
"id": "WORKTREE.SEAM.test-policy",
"klass": "WORKTREE",
"value": "cover all decision branches in policy module before changing prune behavior",
"line": 450
}
],
"duplicates": [
{
"id": "RULESET.GEMINI.TEST_SENTINEL",
"lines": [
397,
429
]
},
{
"id": "RULESET.GEMINI.TOOLS.ask_user",
"lines": [
396,
428
]
},
{
"id": "RULESET.WORKFLOW_MARKDOWN.FENCES",
"lines": [
384,
427
]
}
]
}