@opengsd/gsd-core 1.7.0-rc.5 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-executor.md +2 -1
  4. package/agents/gsd-security-auditor.md +13 -15
  5. package/agents/gsd-ui-checker.md +2 -0
  6. package/agents/gsd-ui-researcher.md +1 -0
  7. package/bin/install.js +975 -196
  8. package/commands/gsd/mempalace-capture.md +27 -1
  9. package/commands/gsd/surface.md +6 -6
  10. package/gsd-core/bin/gsd-tools.cjs +63 -2
  11. package/gsd-core/bin/lib/api-coverage.cjs +3 -4
  12. package/gsd-core/bin/lib/audit.cjs +7 -6
  13. package/gsd-core/bin/lib/capability-registry.cjs +503 -87
  14. package/gsd-core/bin/lib/capability-validator.cjs +56 -18
  15. package/gsd-core/bin/lib/check-command-router.cjs +1 -1
  16. package/gsd-core/bin/lib/clock.cjs +19 -0
  17. package/gsd-core/bin/lib/commands.cjs +48 -9
  18. package/gsd-core/bin/lib/config-loader.cjs +6 -2
  19. package/gsd-core/bin/lib/config.cjs +12 -0
  20. package/gsd-core/bin/lib/core-utils.cjs +8 -2
  21. package/gsd-core/bin/lib/drift.cjs +4 -4
  22. package/gsd-core/bin/lib/frontmatter.cjs +22 -0
  23. package/gsd-core/bin/lib/gsd2-import.cjs +2 -1
  24. package/gsd-core/bin/lib/host-integration.cjs +33 -8
  25. package/gsd-core/bin/lib/init.cjs +60 -53
  26. package/gsd-core/bin/lib/install-engine.cjs +93 -22
  27. package/gsd-core/bin/lib/installer-migration-authoring.cjs +2 -1
  28. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  29. package/gsd-core/bin/lib/installer-migrations.cjs +1 -1
  30. package/gsd-core/bin/lib/markdown-sectionizer.cjs +342 -0
  31. package/gsd-core/bin/lib/markdown-table.cjs +698 -0
  32. package/gsd-core/bin/lib/mcp-server.cjs +18 -7
  33. package/gsd-core/bin/lib/milestone.cjs +217 -31
  34. package/gsd-core/bin/lib/phase-command-router.cjs +50 -2
  35. package/gsd-core/bin/lib/phase-lifecycle.cjs +62 -36
  36. package/gsd-core/bin/lib/phase-locator.cjs +23 -2
  37. package/gsd-core/bin/lib/phase.cjs +436 -61
  38. package/gsd-core/bin/lib/plan-scan.cjs +3 -0
  39. package/gsd-core/bin/lib/review-reviewer-selection.cjs +24 -7
  40. package/gsd-core/bin/lib/roadmap-parser.cjs +218 -13
  41. package/gsd-core/bin/lib/roadmap.cjs +100 -49
  42. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +242 -44
  43. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -2
  44. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +24 -14
  45. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +19 -5
  46. package/gsd-core/bin/lib/runtime-homes.cjs +22 -0
  47. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +526 -29
  48. package/gsd-core/bin/lib/runtime-name-policy.cjs +63 -5
  49. package/gsd-core/bin/lib/schema-detect.cjs +2 -1
  50. package/gsd-core/bin/lib/security.cjs +7 -37
  51. package/gsd-core/bin/lib/shell-command-projection.cjs +176 -27
  52. package/gsd-core/bin/lib/smart-entry.cjs +4 -3
  53. package/gsd-core/bin/lib/stale-bake-guard.cjs +30 -10
  54. package/gsd-core/bin/lib/state-transition.cjs +100 -45
  55. package/gsd-core/bin/lib/state.cjs +391 -126
  56. package/gsd-core/bin/lib/surface.cjs +12 -8
  57. package/gsd-core/bin/lib/template.cjs +2 -1
  58. package/gsd-core/bin/lib/uat.cjs +54 -8
  59. package/gsd-core/bin/lib/ui-consideration-probe.cjs +249 -0
  60. package/gsd-core/bin/lib/ui-safety-gate.cjs +23 -1
  61. package/gsd-core/bin/lib/verify.cjs +4 -3
  62. package/gsd-core/bin/lib/workstream.cjs +3 -2
  63. package/gsd-core/bin/lib/worktree-safety.cjs +1 -1
  64. package/gsd-core/bin/lib/write-set.cjs +38 -0
  65. package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
  66. package/gsd-core/bin/shared/model-catalog.json +8 -3
  67. package/gsd-core/references/checkpoints.md +12 -0
  68. package/gsd-core/references/ui-consideration-probe.md +73 -0
  69. package/gsd-core/templates/UI-SPEC.md +25 -0
  70. package/gsd-core/templates/VALIDATION.md +2 -0
  71. package/gsd-core/workflows/add-tests.md +1 -1
  72. package/gsd-core/workflows/audit-milestone.md +7 -4
  73. package/gsd-core/workflows/debug.md +2 -0
  74. package/gsd-core/workflows/execute-phase.md +5 -3
  75. package/gsd-core/workflows/fast.md +8 -22
  76. package/gsd-core/workflows/plan-phase.md +6 -0
  77. package/gsd-core/workflows/progress.md +2 -2
  78. package/gsd-core/workflows/quick.md +2 -0
  79. package/gsd-core/workflows/review.md +42 -3
  80. package/gsd-core/workflows/secure-phase.md +1 -1
  81. package/gsd-core/workflows/settings-advanced.md +7 -4
  82. package/gsd-core/workflows/ship.md +8 -2
  83. package/gsd-core/workflows/spec-phase.md +1 -1
  84. package/gsd-core/workflows/transition.md +1 -1
  85. package/gsd-core/workflows/ui-phase.md +146 -1
  86. package/gsd-core/workflows/validate-phase.md +2 -2
  87. package/hooks/dist/gsd-statusline.js +164 -14
  88. package/hooks/dist/gsd-windsurf-pre-command.js +275 -0
  89. package/hooks/dist/gsd-windsurf-pre-write.js +132 -0
  90. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  91. package/hooks/gsd-statusline.js +164 -14
  92. package/hooks/gsd-windsurf-pre-command.js +275 -0
  93. package/hooks/gsd-windsurf-pre-write.js +132 -0
  94. package/hooks/managed-hooks-registry.cjs +2 -0
  95. package/package.json +10 -4
  96. package/pi/gsd.cjs +354 -0
  97. package/scripts/build-hooks.js +3 -0
  98. package/scripts/ci-test-scope.cjs +39 -1
  99. package/scripts/gen-golden-install-parity-zcode.cjs +35 -35
  100. package/scripts/gen-install-tree-fixtures.cjs +75 -0
  101. package/scripts/gen-registry.cjs +128 -0
  102. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
  103. package/scripts/lint-table-schema-drift.cjs +157 -0
  104. package/scripts/lint-test-file-count.allowlist.json +2 -1
  105. package/scripts/registry-schema.cjs +565 -0
  106. package/scripts/validate-registry.cjs +117 -0
  107. package/skills/gsd-mempalace-capture/SKILL.md +27 -1
  108. package/skills/gsd-surface/SKILL.md +6 -6
  109. package/vscode/browser.js +197 -0
  110. package/vscode/extension.js +383 -0
  111. package/vscode/host-binding.js +113 -0
  112. package/vscode/package.json +96 -0
@@ -0,0 +1,73 @@
1
+ # UI-Consideration Probe — Spec-Completeness Reference
2
+
3
+ The **third** adapter of the shared `probe-core` resolution model (ADR-550 Decision 7), on
4
+ the **UI element/state axis**. It surfaces the shape-rooted UI *state* considerations a
5
+ UI-SPEC must resolve before a dimension may PASS — the visual analog of the requirement-side
6
+ [edge-probe](./edge-probe.md), reusing its exact lifecycle, validators, and plan-phase lift
7
+ (see edge-probe.md for the shared status×verification model — this doc does not re-argue it).
8
+
9
+ **Axis boundary (this is a MIXED axis).** This compiled taxonomy covers ONLY the finite,
10
+ project-independent shape-rooted content/robustness states. The **open**, domain/UX-dependent
11
+ considerations — real-time/offline/optimistic-UI, deep accessibility (WCAG breadth),
12
+ internationalization / RTL depth, and emerging interaction paradigms — are open-ended and are
13
+ prose-owned in the companion [domain-probes.md](./domain-probes.md) technology/UX bank, NOT
14
+ here. Forcing them into a closed compiled taxonomy is the wrong model.
15
+
16
+ ## Inputs
17
+
18
+ A list of UI elements, each a `{ id, text, elements? }` record where `text` is the
19
+ researcher-authored description and `elements` is an optional author-supplied override of the
20
+ element classification. The six element kinds are: `form`, `list-collection`, `nav`, `media`,
21
+ `interactive-control`, `static-content`. When `elements` is absent, a heuristic classifier
22
+ proposes kinds from the prose (propose-then-confirm) — the author may correct the kind.
23
+
24
+ ## Taxonomy (8 categories)
25
+
26
+ Closed and small by design: the finite, project-independent content/robustness states every
27
+ UI surface must account for. Growth toward open UX topics happens in `domain-probes.md`, not by
28
+ bloating this closed core.
29
+
30
+ | id | name | applies to element kinds | consideration question |
31
+ |----|------|--------------------------|------------------------|
32
+ | empty | Empty / no data | form, list-collection, media | What is shown when there is no data — zero items, an unfilled form, or absent media? |
33
+ | loading | Loading / in-flight | form, list-collection, media, nav | What is shown while data or content is still loading (skeleton, spinner, progressive reveal)? |
34
+ | error | Error / failure | form, list-collection, media, nav | What is shown when the load or submit fails (message, retry affordance, partial fallback)? |
35
+ | populated | Populated / happy path | list-collection, media | What does the normal populated (happy-path) state look like at a typical volume of content? |
36
+ | partial | Partial / incomplete | form, list-collection | What is shown for partial or incomplete data — some fields or rows present, others missing? |
37
+ | overflow | Overflow / truncation | list-collection, nav, static-content | What happens when content exceeds its container — scroll, clip, wrap, or truncate? |
38
+ | zero-one-many | Zero / one / many | list-collection | How does the layout read at zero, one, and many items (singular vs plural copy, spacing)? |
39
+ | long-text | Long text | form, static-content, interactive-control, nav | What happens with unusually long text — truncation, wrapping, ellipsis, or reflow? |
40
+
41
+ ## Relevance filter + resolution states
42
+
43
+ The probe reuses the edge-probe rails verbatim (ADR-550 Decision 7 — see
44
+ [edge-probe.md](./edge-probe.md#relevance-filter--resolution-states) for the full model):
45
+
46
+ 1. **Relevance filter first.** Classify each element's kind(s), then raise only the categories
47
+ whose `applies to element kinds` intersect. A static label is never asked about loading or
48
+ empty state — that is what makes an unresolved consideration meaningful.
49
+ 2. **Dismissal requires a reason string.** Silence is not a resolution; the reason is the audit
50
+ trail.
51
+ 3. **Zero-classification surfaces one `unclassified` candidate (#1110).** An element whose prose
52
+ matched no kind cue yields exactly one soft `unclassified — review manually` item
53
+ (`category: "unclassified"`, `status: "unresolved"`) — never a silent drop, never a guessed
54
+ kind. `unclassified` is a review signal, **not** a ninth taxonomy category; an explicit
55
+ `elements: []` opt-out stays silent.
56
+
57
+ Each raised consideration carries the shared two orthogonal axes — `status`
58
+ (`resolved | dismissed | unresolved`) and, when resolved, a `verification` tier
59
+ (`explicit | backstop`). A `backstop` consideration lifts into `must_haves.truths` and, at
60
+ verify time, is confirmed only by explicit evidence (a wired held-out/property test) or routes
61
+ to `insufficient_spec → human_needed` — never a silent pass (the honest-verifier disposition,
62
+ #1154). See [honest-verifier.md](./honest-verifier.md).
63
+
64
+ ## Closed / open boundary
65
+
66
+ The **8 ids above are the closed, compiled subset** — finite and project-independent, so a
67
+ compiled taxonomy is legitimate (the same property that makes edge-probe's data-shape taxonomy
68
+ closed). The **open subset is prose-owned in [domain-probes.md](./domain-probes.md)**:
69
+ real-time/offline/optimistic-UI, deep accessibility (WCAG breadth), i18n / RTL depth, and
70
+ emerging interaction paradigms (gesture/voice/reduced-motion/print) are open-ended and
71
+ cue-triggered — they do not belong in this closed taxonomy. This probe **complements** the
72
+ `gsd-ui-checker` six quality dimensions (it adds a state-coverage axis); it does not change the
73
+ BLOCK/FLAG/PASS enum or the dimensions themselves.
@@ -79,6 +79,31 @@ Accent reserved for: {explicit list — never "all interactive elements"}
79
79
 
80
80
  ---
81
81
 
82
+ ## UI Considerations
83
+
84
+ > Populated by the ui-phase UI-consideration probe (Step 9.5) and lifted by plan-phase's
85
+ > `## UI Considerations` lift rule via the identical rule as SPEC `## Edge Coverage`. Shape-rooted UI *state*
86
+ > coverage (empty / loading / error / populated / partial / overflow / zero-one-many / long-text).
87
+ > Empty-state and error-state COPY live in `## Copywriting Contract` above — this section covers
88
+ > state coverage and REFERENCES those rows rather than restating the copy (de-dup).
89
+
90
+ Applicable state considerations resolved: {N covered, M backstop, K unresolved — or "none applicable"}
91
+
92
+ | Category | Element(s) | Status | Resolution / Reason |
93
+ |----------|------------|--------|---------------------|
94
+ | {empty} | {list-collection} | ✅ covered | {concrete truth string — e.g. "Empty results render the documented 'No results' copy"} |
95
+ | {long-text} | {static-content} | 🧪 backstop | {held-out/visual UI-state test — lifts as `{ statement, verification: backstop }`} |
96
+ | {overflow} | {list-collection} | ⚠ unresolved | {planner treats as assumption} |
97
+
98
+ <!-- Status vocabulary (locked by probe-core projectTruths):
99
+ ✅ covered → a plain truth string lifted into must_haves.truths
100
+ 🧪 backstop → a flat scalar { statement, verification: backstop }; at verify time, no explicit
101
+ evidence → insufficient_spec → human_needed (never a silent pass, #1154)
102
+ ⚠ unresolved → an explicit planner assumption (surfaced, never silently dropped)
103
+ Rows are REPLACED (not appended) on a probe re-run — idempotent. -->
104
+
105
+ ---
106
+
82
107
  ## Registry Safety
83
108
 
84
109
  | Registry | Blocks Used | Safety Gate |
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  phase: {N}
3
3
  slug: {phase-slug}
4
+ # status lifecycle: draft (seeded by plan-phase) → validated (set by validate-phase §6)
5
+ # audit-milestone §5.5 distinguishes NOT-VALIDATED (draft) from PARTIAL (validated + nyquist_compliant: false) (#2117)
4
6
  status: draft
5
7
  nyquist_compliant: false
6
8
  wave_0_complete: false
@@ -308,7 +308,7 @@ If there are passing tests to commit:
308
308
 
309
309
  ```bash
310
310
  git add {test files}
311
- git commit -m "test(phase-${phase_number}): add unit and E2E tests from add-tests command"
311
+ git commit -m "test(phase-${phase_number}): add unit and E2E tests from add-tests command" -- {test files}
312
312
  ```
313
313
 
314
314
  Present next steps:
@@ -153,17 +153,20 @@ Resolve active step hooks from `VERIFY_POST_HOOKS_JSON` where `kind == "step"` a
153
153
 
154
154
  If no active validate-phase step hook exists: skip entirely.
155
155
 
156
- For each phase directory, check `*-VALIDATION.md`. If exists, parse frontmatter (`nyquist_compliant`, `wave_0_complete`).
156
+ For each phase directory, check `*-VALIDATION.md`. If exists, parse frontmatter (`status`, `nyquist_compliant`, `wave_0_complete`).
157
157
 
158
158
  Classify per phase:
159
159
 
160
160
  | Status | Condition |
161
161
  |--------|-----------|
162
- | COMPLIANT | `nyquist_compliant: true` and all tasks green |
163
- | PARTIAL | VALIDATION.md exists, `nyquist_compliant: false` or red/pending |
162
+ | COMPLIANT | `status: validated` and `nyquist_compliant: true` and all tasks green |
163
+ | PARTIAL | `status: validated` and (`nyquist_compliant: false` or red/pending) |
164
+ | NOT-VALIDATED | `status: draft` (or absent) — validate-phase has not yet reconciled this file (#2117) |
164
165
  | MISSING | No VALIDATION.md |
165
166
 
166
- Add to audit YAML: `nyquist: { compliant_phases, partial_phases, missing_phases, overall }`
167
+ > **NOT-VALIDATED vs PARTIAL (#2117):** A phase reads `status: draft` when it was seeded by plan-phase but never reconciled by validate-phase, OR when its `VALIDATION.md` predates the `status` field (files written before #2117 stay `draft` whether or not validation ran). In both cases `nyquist_compliant` is not authoritative, so this is a coverage TODO ("run validate-phase") — not a compliance failure. Re-running validate-phase promotes the file to `status: validated` and yields the real COMPLIANT/PARTIAL verdict. Only `status: validated` + `nyquist_compliant: false` is a genuine PARTIAL.
168
+
169
+ Add to audit YAML: `nyquist: { compliant_phases, partial_phases, not_validated_phases, missing_phases, overall }`
167
170
 
168
171
  Discovery only — never auto-calls `/gsd:validate-phase`.
169
172
 
@@ -190,6 +190,8 @@ Create `.planning/debug/{slug}.md` with initial state using the Write tool (neve
190
190
 
191
191
  After initial context setup, spawn the session manager to handle the full checkpoint/continuation loop. The session manager handles specialist_hint dispatch internally: when gsd-debugger returns ROOT CAUSE FOUND it extracts the specialist_hint field and invokes the matching skill (e.g. typescript-expert, swift-concurrency) before offering fix options.
192
192
 
193
+ > **Foreground, blocking spawn — #2196.** The `Agent(subagent_type="gsd-debug-session-manager", …)` call below is FOREGROUND and BLOCKING — it returns the compact session summary directly. Wait for it; do not background it, and do not poll for it. Never pass an agent or session identifier to `TaskOutput` — an agent ID is NOT a task ID, so `TaskOutput <agent-id>` always returns `No task found with ID`. If the spawn returns no usable result (the handoff is lost), do NOT claim the session is still running: preserve the checkpoint at `.planning/debug/{slug}.md`, report the failed handoff plainly, and resume by re-spawning the session manager or via `/gsd:debug continue {slug}`.
194
+
193
195
  Print before spawning (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
194
196
  ```
195
197
  [debug] Delegating loop to session manager...
@@ -1056,11 +1056,13 @@ AUTO_MODE=$(gsd_run query check auto-mode --pick active 2>/dev/null || echo "fal
1056
1056
  ```
1057
1057
 
1058
1058
  When executor returns a checkpoint AND `AUTO_MODE` is `true`:
1059
- - **human-verify** → Auto-spawn continuation agent with `{user_response}` = `"approved"`. Log `⚡ Auto-approved checkpoint`.
1060
- - **decision** → Auto-spawn continuation agent with `{user_response}` = first option from checkpoint details. Log `⚡ Auto-selected: [option]`.
1059
+ - **human-verify** → Auto-spawn continuation agent with `{user_response}` = `"approved"`. Log `⚡ Auto-approved checkpoint`. **Except `blocking-human`.**
1060
+ - **decision** → Auto-spawn continuation agent with `{user_response}` = first option from checkpoint details. Log `⚡ Auto-selected: [option]`. **Except `blocking-human`.**
1061
1061
  - **human-action** → Present to user (existing behavior below). Auth gates cannot be automated.
1062
1062
 
1063
- **Standard flow (not auto-mode, or human-action type):**
1063
+ **Carve-out — overrides all branches above.** If the returned `Gate:` is `blocking-human`, or its `<what-built>` mentions `Package verification required before install` or `Package install failed — human verification required`, never auto-approve or auto-select, regardless of type. Present to user (standard flow below). Log `⛔ blocking-human gate — auto-mode suspended`.
1064
+
1065
+ **Standard flow (not auto-mode, human-action, or blocking-human):**
1064
1066
 
1065
1067
  1. Spawn agent for checkpoint plan
1066
1068
  2. Agent runs until checkpoint task or auth gate → returns structured state
@@ -64,32 +64,18 @@ Use conventional commit format: `fix:`, `feat:`, `docs:`, `chore:`, `refactor:`
64
64
 
65
65
  <step name="log_to_state">
66
66
  If `.planning/STATE.md` exists and has a "Quick Tasks Completed" table, append a row
67
- that matches the existing table's schema. If no table exists, skip silently.
68
- If the table's schema is unrecognized, skip with a brief log rather than append a
69
- malformed row.
67
+ that matches the existing table's schema via the schema-backed `gsd-tools
68
+ quick-tasks-append` helper (`markdown-table.cjs`'s `appendQuickTaskRow`; #2133,
69
+ ADR-2143 §3/§7). If no table exists, skip silently. If the table's schema is
70
+ unrecognized, the helper fails loud (non-zero exit) instead of silently guessing
71
+ a column count — this replaces the prior inline `awk NF-2` arithmetic that was
72
+ the root cause of #2133.
70
73
 
71
74
  ```bash
75
+ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
72
76
  # Detect whether STATE.md has a Quick Tasks Completed table
73
77
  if grep -q "Quick Tasks Completed" .planning/STATE.md 2>/dev/null; then
74
- # Read the table header line to determine the column schema.
75
- # quick.md Step 7 creates a 5-column table:
76
- # | # | Description | Date | Commit | Directory |
77
- # Count pipe characters in the header to determine column count.
78
- HEADER_LINE=$(grep -A2 "Quick Tasks Completed" .planning/STATE.md 2>/dev/null | grep "^|" | head -1)
79
- # Count columns: number of | separators minus 1 gives column count
80
- COL_COUNT=$(echo "$HEADER_LINE" | awk -F'|' '{print NF-1}')
81
-
82
- if [ "$COL_COUNT" -eq 5 ] && echo "$HEADER_LINE" | grep -qi "Description" && echo "$HEADER_LINE" | grep -qi "Commit" && echo "$HEADER_LINE" | grep -qi "Directory"; then
83
- # 5-column schema from quick.md Step 7: | # | Description | Date | Commit | Directory |
84
- # Determine the next row number by counting existing data rows (non-separator, non-header).
85
- NEXT_NUM=$(awk '/Quick Tasks Completed/{found=1} found && /^\|/ && !/^[|][-: |]*[|]$/ && !/Description/{count++} END{print count+1}' .planning/STATE.md 2>/dev/null || echo "1")
86
- # Get the latest commit hash (short)
87
- COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "—")
88
- echo "| $NEXT_NUM | $TASK | $(date +%Y-%m-%d) | $COMMIT_HASH | — |" >> .planning/STATE.md
89
- else
90
- # Unrecognized table schema — skip to avoid appending a malformed row.
91
- echo "⚠ fast.md log_to_state: Quick Tasks Completed table has unrecognized schema (${COL_COUNT} columns); skipping STATE.md update."
92
- fi
78
+ gsd_run quick-tasks-append --task "$TASK" || echo "⚠ fast.md log_to_state: could not append Quick Tasks row (see message above); continuing."
93
79
  fi
94
80
  ```
95
81
  </step>
@@ -610,6 +610,10 @@ SKETCH_FINDINGS_PATH=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/nul
610
610
  PHASE_DIR_FOR_SPEC=$(_gsd_field "$INIT" phase_dir)
611
611
  SPEC_FILE=$(ls "${PHASE_DIR_FOR_SPEC}"/*-SPEC.md 2>/dev/null | grep -Ev -- '-(AI|UI)-SPEC\.md$' | head -1)
612
612
  SPEC_PATH="${SPEC_FILE}"
613
+ # Resolve the phase UI-SPEC separately (the glob above excludes -UI-SPEC.md); it carries the
614
+ # ## UI Considerations section the planner lifts by the same rule as ## Edge Coverage (#1867).
615
+ UI_SPEC_FILE=$(ls "${PHASE_DIR_FOR_SPEC}"/*-UI-SPEC.md 2>/dev/null | head -1)
616
+ UI_SPEC_PATH="${UI_SPEC_FILE}"
613
617
  ```
614
618
 
615
619
  ## 7.5. Verify Nyquist Artifacts
@@ -808,6 +812,7 @@ Output consumed by /gsd:execute-phase. Plans need:
808
812
  - must_haves for goal-backward verification
809
813
  - If the SPEC has an `## Edge Coverage` section, lift every `covered` edge's acceptance criterion into `must_haves.truths` as a plain string, and every `backstop` edge **as a structured flat-scalar marker** — an object item `{ statement: <the check>, verification: backstop }`, NOT a prose note (the verifier branches deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110 fragility). Use a flat scalar `verification:` continuation key, never a nested object (ADR-550 #1278). At verify time a `backstop` truth the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason `insufficient_spec`), never a silent pass (#1154; see `references/honest-verifier.md`). `unresolved` edges are explicit assumptions — surface them in the plan, do not silently drop them. **Otherwise** (`EDGE_ABSENT`): apply the SAME lift to the fallback report `{COVERAGE}` (per §C of `references/specless-probe-fallback.md`); a SPEC-supplied section is never re-run.
810
814
  - If the SPEC has a `## Prohibitions` section, lift every resolved prohibition into the `must_haves.prohibitions:` sibling block (NOT `truths` — ADR-550 D3) with `statement`+`status`+`verification`, via the single `projectProhibitions` serializer (Hyrum — no second serializer); unresolved -> flagged assumptions, don't drop; never put a must-NOT under `truths`. **Otherwise** (`PROHIB_ABSENT`), author the recalled prohibitions into the SAME block via the SAME `projectProhibitions` contract but **descriptor-less** (no `check_*`) so each disposes flagged-unverified; never auto-dismiss. Section-level precedence + no-silent-drop equality apply (§C).
815
+ - If a `-UI-SPEC.md` exists (resolved above as `UI_SPEC_PATH`) with a `## UI Considerations` section, lift it by the **identical rule** as `## Edge Coverage` above — `covered` → `must_haves.truths` string, `backstop` → flat scalar `{ statement, verification: backstop }`, `unresolved` → explicit planner assumption (no new verb — ADR-550 #1278/#1154; #1867). Read it from `UI_SPEC_PATH` (the SPEC glob excludes `-UI-SPEC.md`).
811
816
  - **"Artifacts this phase produces" section (MANDATORY)** — list every symbol this phase creates: decorators, classes, functions, CLI flags, struct/dataclass fields, new file paths. The plan-review-convergence source-grounding pass reads this section to exclude newly-created symbols from drift verification; omitting it causes new symbols to be flagged for acknowledgement.
812
817
  </downstream_consumer>
813
818
 
@@ -854,6 +859,7 @@ Every task MUST include these fields — they are NOT optional:
854
859
  - [ ] must_haves derived from phase goal
855
860
  - [ ] Every PLAN.md includes an "Artifacts this phase produces" section listing symbols created by this phase (decorators, classes, functions, CLI flags, struct/dataclass fields, new file paths)
856
861
  - [ ] Every SPEC ## Edge Coverage covered/backstop edge is represented in a plan's must_haves (no silent drops)
862
+ - [ ] Every UI-SPEC ## UI Considerations covered/backstop consideration is represented in a plan's must_haves (no silent drops)
857
863
  - [ ] Every SPEC ## Prohibitions resolved item is represented in a plan's must_haves.prohibitions (no silent drops)
858
864
  </quality_gate>
859
865
  ```
@@ -536,7 +536,7 @@ State: "Current phase is {X}. Milestone has {N} phases (highest: {Y})."
536
536
  | Condition | Meaning | Action |
537
537
  |-----------|---------|--------|
538
538
  | current phase < highest phase | More phases remain | Go to **Route C** |
539
- | current phase = highest phase | Milestone complete | Go to **Route D** |
539
+ | current phase = highest phase | All phases complete | Go to **Route D** |
540
540
 
541
541
  ---
542
542
 
@@ -602,7 +602,7 @@ NEXT_HAS_UI=$(echo "$NEXT_PHASE_SECTION" | grep -qi "UI hint.*yes" && echo "true
602
602
 
603
603
  ---
604
604
 
605
- **Route D: Milestone complete**
605
+ **Route D: All phases complete (milestone ready to close)**
606
606
 
607
607
  ```
608
608
  ---
@@ -971,6 +971,8 @@ Use `date` from init:
971
971
  | ${quick_id} | ${DESCRIPTION} | ${date} | ${commit_hash} | [${quick_id}-${slug}](./quick/${quick_id}-${slug}/) |
972
972
  ```
973
973
 
974
+ For a schema-safe append outside this workflow (e.g. from fast.md), `gsd-tools quick-tasks-append --task <text>` performs the equivalent write via the shared, schema-backed `appendQuickTaskRow` helper (#2133, ADR-2143 §3/§7).
975
+
974
976
  **7d. Update "Last activity" line:**
975
977
 
976
978
  Use `date` from init:
@@ -255,6 +255,8 @@ shell-interpolated). Exact invocation in `gsd-core/references/reviewer-instances
255
255
 
256
256
  For each selected CLI, invoke in sequence (not parallel — avoid rate limits):
257
257
 
258
+ **Timeout guidance (#2194):** prompt-fed source-grounded reviews are slow — measured ~570s for Codex at `xhigh` effort and ~525s for headless Claude on a large plan set. Each of the Gemini / Claude / Codex blocks below MUST be invoked with a high Bash `timeout:` — at least `900000` (15 min), and `1200000` (20 min) for Codex `xhigh` or headless Claude — so a lane is not killed mid-review. On Claude Code, raise the host cap via `BASH_MAX_TIMEOUT_MS` if a review can exceed it. A silent empty output after a long run is a **timeout kill, not a crash** — the Codex `0xc0000142` misdiagnosis persisted because the empty-output branches below cannot distinguish the two; treat an empty result on a slow lane as a dropped lane and re-run with more time rather than diagnosing a CLI/sandbox failure. A cross-AI review that silently drops a lane is blind in one eye.
259
+
258
260
  **Gemini:**
259
261
  ```bash
260
262
  if [ -n "$GEMINI_MODEL" ] && [ "$GEMINI_MODEL" != "null" ]; then
@@ -364,7 +366,12 @@ fi
364
366
  # prompt as an ARGUMENT, not stdin. A full review prompt can exceed the OS argument limit, so
365
367
  # reference the prompt file by path rather than inlining it. Capture stderr so a failure is
366
368
  # diagnosable instead of a silent empty result.
367
- CURSOR_PROMPT_ARG="Read the file at /tmp/gsd-review-prompt-{phase}.md in full and carry out the review request it contains. Output only the resulting markdown review. Do not edit any files."
369
+ # #2176: same absolute-root anchor as the Antigravity block — cursor-agent runs
370
+ # in the repo cwd, but repo-relative references in the assembled prompt still
371
+ # need an explicit root to resolve against. rev-parse (not bare pwd) so the
372
+ # anchor is correct even when /gsd:review is invoked from a repo subdirectory.
373
+ _CURSOR_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
374
+ CURSOR_PROMPT_ARG="Read the file at /tmp/gsd-review-prompt-{phase}.md in full and carry out the review request it contains. The repository under review is at $_CURSOR_ROOT — resolve every relative file path in the review request against that absolute root. Output only the resulting markdown review. Do not edit any files."
368
375
  cursor-agent -p --mode ask --trust --output-format text "$CURSOR_PROMPT_ARG" 2>/tmp/gsd-review-cursor-{phase}.err > /tmp/gsd-review-cursor-{phase}.md
369
376
  if [ ! -s /tmp/gsd-review-cursor-{phase}.md ]; then
370
377
  echo "Cursor review failed or returned empty output. stderr:" > /tmp/gsd-review-cursor-{phase}.md
@@ -456,7 +463,19 @@ if [ -n "$AGY_MODEL" ] && [ "$AGY_MODEL" != "null" ]; then
456
463
  else
457
464
  set --
458
465
  fi
459
- _AGY_PROMPT="Read the file at /tmp/gsd-review-prompt-{phase}.md in full and carry out the review request it contains. Output only the resulting markdown review. Do not edit any files."
466
+ # #2176: grant the reviewer the repo under review. Without --add-dir, agy's
467
+ # permission context never receives the cwd repo — the agent anchors on its own
468
+ # ~/.gemini/antigravity-cli/scratch dir and reviews the plan text in isolation
469
+ # (the exact failure the Review Instructions forbid). Capability-probed like the
470
+ # Codex bypass flag so an older agy without --add-dir still runs; the prompt
471
+ # anchor below keeps absolute-path reads possible on that fallback.
472
+ if agy --help 2>/dev/null | grep -q -- '--add-dir'; then
473
+ set -- "$@" --add-dir "$_AGY_WS"
474
+ fi
475
+ # #2176: anchor the prompt to the absolute repo root so repo-relative references
476
+ # in the assembled review prompt resolve even on the no---add-dir fallback, and
477
+ # require an explicit self-report if the reviewer still cannot read the repo.
478
+ _AGY_PROMPT="Read the file at /tmp/gsd-review-prompt-{phase}.md in full and carry out the review request it contains. The repository under review is at $_AGY_WS — resolve every relative file path in the review request against that absolute root and verify claims against those files. If you cannot read files under $_AGY_WS, begin your output with the exact line REVIEWED-WITHOUT-REPO-ACCESS before the review. Output only the resulting markdown review. Do not edit any files."
460
479
  # Capability-probe an external wall-clock killer (GNU coreutils `timeout` or the
461
480
  # macOS Homebrew `gtimeout`). Stock macOS ships NEITHER — a bare `timeout …` would
462
481
  # fail with rc 127 ("command not found") and silently lose the reviewer, so fall
@@ -523,6 +542,26 @@ if [ ! -s /tmp/gsd-review-antigravity-{phase}.md ]; then
523
542
  echo "If no agy run started, that is the pre-session-stall case: check whether a new ~/.gemini/antigravity-cli/brain/<conv-id>/ dir appeared within ~30s of launch."
524
543
  } > /tmp/gsd-review-antigravity-{phase}.md
525
544
  fi
545
+
546
+ # #2176: blind-review marker. Two tells that the reviewer ran without repo
547
+ # access: the prompt's mandated REVIEWED-WITHOUT-REPO-ACCESS self-report in the
548
+ # first lines of output, or the agent DECLARING the scratch dir as its
549
+ # workspace. Both patterns are anchored — the self-report to the head of the
550
+ # file, the scratch tell to a workspace-declaration phrasing — so a grounded
551
+ # review that merely QUOTES these strings (e.g. reviewing this very file) is
552
+ # never mis-stamped. Stamp a machine-readable marker so the Consensus Summary
553
+ # down-weights the review instead of counting an ungrounded verdict at full
554
+ # weight. (Temp file + mv, no in-place sed — BSD/GNU safe.)
555
+ if [ -s /tmp/gsd-review-antigravity-{phase}.md ] && \
556
+ { head -5 /tmp/gsd-review-antigravity-{phase}.md | grep -q 'REVIEWED-WITHOUT-REPO-ACCESS' || \
557
+ grep -qiE '(workspace|working) (directory|dir).{0,40}antigravity-cli/scratch' /tmp/gsd-review-antigravity-{phase}.md; }; then
558
+ {
559
+ echo "> [reviewed-without-repo-access] This reviewer ran without visibility into the repo under review — down-weight its verdict in the Consensus Summary."
560
+ echo ""
561
+ cat /tmp/gsd-review-antigravity-{phase}.md
562
+ } > /tmp/gsd-review-antigravity-{phase}.md.tmp && \
563
+ mv /tmp/gsd-review-antigravity-{phase}.md.tmp /tmp/gsd-review-antigravity-{phase}.md
564
+ fi
526
565
  ```
527
566
 
528
567
  **Ollama (local, OpenAI-compatible):**
@@ -835,7 +874,7 @@ trimmed_reviewers: # only present if at least one reviewer was trimmed
835
874
 
836
875
  ## Consensus Summary
837
876
 
838
- {synthesize common concerns across all reviewers. CodeRabbit is a diff-only reviewer (it never received the source-grounding prompt), so do not weight its verdict as a grounded plan review — fold in its diff findings, but base plan-level consensus on the prompt-fed reviewers.}
877
+ {synthesize common concerns across all reviewers. CodeRabbit is a diff-only reviewer (it never received the source-grounding prompt), so do not weight its verdict as a grounded plan review — fold in its diff findings, but base plan-level consensus on the prompt-fed reviewers. A reviewer output carrying the `[reviewed-without-repo-access]` marker (or beginning with `REVIEWED-WITHOUT-REPO-ACCESS`) ran without repo access (#2176) — treat it the same way: note its concerns, but do not count its verdict at full consensus weight.}
839
878
 
840
879
  ### Agreed Strengths
841
880
  {strengths mentioned by 2+ reviewers}
@@ -108,7 +108,7 @@ Agent(
108
108
  "<files_to_read>{PLAN, SUMMARY, impl files, SECURITY.md}</files_to_read>" +
109
109
  "<threat_register>{threat register}</threat_register>" +
110
110
  "<config>asvs_level: {SECURITY_ASVS}, block_on: {SECURITY_BLOCK_ON}</config>" +
111
- "<constraints>Never modify implementation files. Verify mitigations exist — do not scan for new threats. Escalate implementation gaps.</constraints>" +
111
+ "<constraints>Never modify implementation files. Verify mitigations exist — do not scan for new threats. Escalate implementation gaps. Return a structured verdict only — do NOT write SECURITY.md (the orchestrator owns the file write).</constraints>" +
112
112
  "${AGENT_SKILLS_AUDITOR}",
113
113
  subagent_type="gsd-security-auditor",
114
114
  model="{AUDITOR_MODEL}",
@@ -360,7 +360,9 @@ Built-in tier defaults by runtime:
360
360
  | `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
361
361
  | `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` |
362
362
  | `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
363
- | Group B (`kilo`, `cline`, `cursor`, `windsurf`, `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | |
363
+ | `kilo` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
364
+ | `pi` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` |
365
+ | Group B (`cline`, `cursor`, `windsurf`, `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | |
364
366
 
365
367
  Display a table to the user showing the effective configuration:
366
368
 
@@ -387,8 +389,8 @@ AskUserQuestion([
387
389
  options: [
388
390
  { label: "Keep current (<runtime>)", description: "Configure overrides for the current runtime." },
389
391
  { label: "Common runtimes", description: "claude, codex, gemini, qwen" },
390
- { label: "Additional runtimes", description: "opencode, copilot, hermes" },
391
- { label: "Other (Group B or custom)", description: "kilo, cline, cursor, windsurf, augment, trae, codebuddy, antigravity, or a custom runtime string." }
392
+ { label: "Additional runtimes", description: "opencode, copilot, hermes, kilo" },
393
+ { label: "Other (Group B or custom)", description: "cline, cursor, windsurf, augment, trae, codebuddy, antigravity, or a custom runtime string." }
392
394
  ]
393
395
  }
394
396
  ])
@@ -423,7 +425,8 @@ AskUserQuestion([
423
425
  options: [
424
426
  { label: "opencode", description: "OpenCode (uses anthropic/ prefix)." },
425
427
  { label: "copilot", description: "GitHub Copilot." },
426
- { label: "hermes", description: "Hermes (uses anthropic/ prefix)." }
428
+ { label: "hermes", description: "Hermes (uses anthropic/ prefix)." },
429
+ { label: "kilo", description: "Kilo Code (uses anthropic/ prefix)." }
427
430
  ]
428
431
  }
429
432
  ])
@@ -394,9 +394,15 @@ gsd_run query state.update "Last Activity" "$(date +%Y-%m-%d)"
394
394
  gsd_run query state.update "Status" "Phase ${PHASE_NUMBER} shipped — PR #${PR_NUMBER}"
395
395
  ```
396
396
 
397
- If `commit_docs` is true:
397
+ If `commit_docs` is true, commit the ship-note AND push it onto the PR branch so
398
+ it reaches the default branch when the PR merges. Without this push the ship-note
399
+ commit stays local-only and is silently discarded when the branch is deleted on
400
+ merge (#2138). The `[ci skip]` trailer suppresses the redundant pipeline the push
401
+ would otherwise trigger (GitHub honors `[ci skip]` / `[skip ci]`):
402
+
398
403
  ```bash
399
- gsd_run query commit "docs(${padded_phase}): ship phase ${PHASE_NUMBER} — PR #${PR_NUMBER}" --files .planning/STATE.md
404
+ gsd_run query commit "docs(${padded_phase}): ship phase ${PHASE_NUMBER} — PR #${PR_NUMBER} [ci skip]" --files .planning/STATE.md
405
+ git push origin ${CURRENT_BRANCH} 2>&1 || echo "⚠ track_shipping: ship-note push failed — it is local-only; rerun: git push origin ${CURRENT_BRANCH}"
400
406
  ```
401
407
  </step>
402
408
 
@@ -456,7 +456,7 @@ Write to: `{phase_dir}/{padded_phase}-SPEC.md`
456
456
 
457
457
  ```bash
458
458
  git add "${phase_dir}/${padded_phase}-SPEC.md"
459
- git commit -m "spec(phase-${phase_number}): add SPEC.md for ${phase_name} — ${requirement_count} requirements (#2213)"
459
+ git commit -m "spec(phase-${phase_number}): add SPEC.md for ${phase_name} — ${requirement_count} requirements (#2213)" -- "${phase_dir}/${padded_phase}-SPEC.md"
460
460
  ```
461
461
 
462
462
  If `commit_docs` is false: Skip commit. Note that SPEC.md was written but not committed.
@@ -597,7 +597,7 @@ Do NOT auto-invoke any further slash commands.
597
597
 
598
598
  ---
599
599
 
600
- **Route B: Milestone complete (all phases done)**
600
+ **Route B: All phases complete (milestone ready to close)**
601
601
 
602
602
  **This route is only reached when:**
603
603
  - `is_last_phase: true` AND no other active workstreams exist (or flat mode)