@opengsd/gsd-core 1.12.0 → 1.13.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 (286) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +12 -0
  4. package/agents/gsd-executor.md +63 -35
  5. package/agents/gsd-plan-checker.md +76 -57
  6. package/agents/gsd-planner.md +14 -0
  7. package/agents/gsd-ui-checker.md +19 -3
  8. package/agents/gsd-ui-researcher.md +29 -0
  9. package/agents/gsd-verifier.md +23 -1
  10. package/bin/install.js +239 -67
  11. package/commands/gsd/execute-phase.md +1 -1
  12. package/commands/gsd/ns-workflow.md +2 -1
  13. package/commands/gsd/phase.md +1 -1
  14. package/commands/gsd/quick-batch.md +105 -0
  15. package/commands/gsd/surface.md +18 -8
  16. package/gsd-core/bin/gsd-tools.cjs +195 -50
  17. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  18. package/gsd-core/bin/lib/capability-registry.cjs +514 -114
  19. package/gsd-core/bin/lib/capability-state.cjs +7 -1
  20. package/gsd-core/bin/lib/capability-validator.cjs +120 -4
  21. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  22. package/gsd-core/bin/lib/check-command-router.cjs +85 -2
  23. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  24. package/gsd-core/bin/lib/clusters.cjs +1 -0
  25. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  26. package/gsd-core/bin/lib/commands.cjs +337 -13
  27. package/gsd-core/bin/lib/config-loader.cjs +3 -0
  28. package/gsd-core/bin/lib/core-utils.cjs +34 -7
  29. package/gsd-core/bin/lib/decisions.cjs +213 -1
  30. package/gsd-core/bin/lib/edge-probe.cjs +14 -1
  31. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  32. package/gsd-core/bin/lib/frontmatter.cjs +137 -23
  33. package/gsd-core/bin/lib/gap-checker.cjs +22 -13
  34. package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
  35. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  36. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
  37. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
  38. package/gsd-core/bin/lib/host-integration.cjs +57 -5
  39. package/gsd-core/bin/lib/init-command-router.cjs +14 -0
  40. package/gsd-core/bin/lib/init.cjs +132 -15
  41. package/gsd-core/bin/lib/install-engine.cjs +184 -12
  42. package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
  43. package/gsd-core/bin/lib/install-profiles.cjs +22 -14
  44. package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
  45. package/gsd-core/bin/lib/io.cjs +35 -0
  46. package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
  47. package/gsd-core/bin/lib/markdown-table.cjs +123 -0
  48. package/gsd-core/bin/lib/milestone.cjs +22 -2
  49. package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
  50. package/gsd-core/bin/lib/phase-id.cjs +251 -9
  51. package/gsd-core/bin/lib/phase.cjs +774 -35
  52. package/gsd-core/bin/lib/plan-document.cjs +10 -0
  53. package/gsd-core/bin/lib/planning-snapshot.cjs +147 -20
  54. package/gsd-core/bin/lib/planning-workspace.cjs +103 -28
  55. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  56. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  57. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  58. package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
  59. package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
  60. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  61. package/gsd-core/bin/lib/roadmap-parser.cjs +499 -26
  62. package/gsd-core/bin/lib/roadmap.cjs +187 -58
  63. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +233 -33
  64. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
  65. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +286 -108
  66. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -43
  67. package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
  68. package/gsd-core/bin/lib/smart-entry.cjs +7 -9
  69. package/gsd-core/bin/lib/state-document.cjs +30 -5
  70. package/gsd-core/bin/lib/state-md-schema.cjs +23 -13
  71. package/gsd-core/bin/lib/state-transition.cjs +333 -44
  72. package/gsd-core/bin/lib/state.cjs +684 -125
  73. package/gsd-core/bin/lib/surface.cjs +23 -8
  74. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  75. package/gsd-core/bin/lib/uat.cjs +1419 -515
  76. package/gsd-core/bin/lib/update-context.cjs +6 -2
  77. package/gsd-core/bin/lib/validate.cjs +230 -12
  78. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  79. package/gsd-core/bin/lib/verification.cjs +273 -12
  80. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  81. package/gsd-core/bin/lib/verify.cjs +346 -16
  82. package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
  83. package/gsd-core/bin/lib/worktree-safety.cjs +8 -0
  84. package/gsd-core/bin/shared/config-schema.manifest.json +8 -0
  85. package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
  86. package/gsd-core/references/agent-contracts.md +3 -3
  87. package/gsd-core/references/edge-probe.md +17 -13
  88. package/gsd-core/references/execute-mvp-tdd.md +18 -16
  89. package/gsd-core/references/execute-phase-response-language.md +6 -0
  90. package/gsd-core/references/executor-examples.md +42 -0
  91. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  92. package/gsd-core/references/mvp-concepts.md +2 -2
  93. package/gsd-core/references/plan-checker-examples.md +41 -0
  94. package/gsd-core/references/planner-antipatterns.md +25 -0
  95. package/gsd-core/references/planner-chunked.md +5 -1
  96. package/gsd-core/references/planner-coupling.md +42 -0
  97. package/gsd-core/references/planner-quick-batch.md +71 -0
  98. package/gsd-core/references/planner-reviews.md +47 -0
  99. package/gsd-core/references/planner-revision.md +75 -2
  100. package/gsd-core/references/planning-config.md +2 -1
  101. package/gsd-core/references/response-language-directive.md +9 -0
  102. package/gsd-core/references/revision-loop.md +118 -11
  103. package/gsd-core/references/tdd.md +14 -9
  104. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  105. package/gsd-core/templates/phase-prompt.md +4 -0
  106. package/gsd-core/templates/verification-report.md +5 -0
  107. package/gsd-core/workflows/add-backlog.md +2 -0
  108. package/gsd-core/workflows/add-phase.md +2 -0
  109. package/gsd-core/workflows/add-tests.md +1 -1
  110. package/gsd-core/workflows/add-todo.md +1 -1
  111. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  112. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  113. package/gsd-core/workflows/audit-fix.md +2 -0
  114. package/gsd-core/workflows/audit-milestone.md +2 -0
  115. package/gsd-core/workflows/audit-uat.md +2 -0
  116. package/gsd-core/workflows/autonomous.md +2 -0
  117. package/gsd-core/workflows/check-todos.md +1 -1
  118. package/gsd-core/workflows/cleanup.md +1 -1
  119. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +15 -13
  120. package/gsd-core/workflows/code-review-fix.md +2 -0
  121. package/gsd-core/workflows/code-review.md +73 -31
  122. package/gsd-core/workflows/complete-milestone.md +13 -4
  123. package/gsd-core/workflows/debug.md +1 -1
  124. package/gsd-core/workflows/diagnose-issues.md +5 -1
  125. package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
  126. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  127. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  128. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  129. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  130. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
  131. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  132. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  133. package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
  134. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  135. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  136. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  137. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  138. package/gsd-core/workflows/discuss-phase.md +1 -1
  139. package/gsd-core/workflows/do.md +43 -13
  140. package/gsd-core/workflows/docs-update.md +1 -1
  141. package/gsd-core/workflows/edit-phase.md +2 -0
  142. package/gsd-core/workflows/eval-review.md +1 -1
  143. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +2 -0
  144. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +17 -1
  145. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
  146. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
  147. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  148. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  149. package/gsd-core/workflows/execute-phase.md +32 -14
  150. package/gsd-core/workflows/execute-plan.md +8 -8
  151. package/gsd-core/workflows/explore.md +2 -0
  152. package/gsd-core/workflows/extract-learnings.md +2 -0
  153. package/gsd-core/workflows/fast.md +6 -0
  154. package/gsd-core/workflows/forensics.md +2 -0
  155. package/gsd-core/workflows/graduation.md +1 -1
  156. package/gsd-core/workflows/health.md +1 -1
  157. package/gsd-core/workflows/help/modes/brief.md +2 -0
  158. package/gsd-core/workflows/help/modes/default.md +2 -0
  159. package/gsd-core/workflows/help/modes/full.md +12 -0
  160. package/gsd-core/workflows/help/modes/topic.md +2 -0
  161. package/gsd-core/workflows/help.md +2 -0
  162. package/gsd-core/workflows/import.md +3 -3
  163. package/gsd-core/workflows/inbox.md +1 -1
  164. package/gsd-core/workflows/ingest-docs.md +1 -1
  165. package/gsd-core/workflows/insert-phase.md +2 -0
  166. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  167. package/gsd-core/workflows/list-seeds.md +2 -0
  168. package/gsd-core/workflows/list-workspaces.md +2 -0
  169. package/gsd-core/workflows/manager.md +3 -3
  170. package/gsd-core/workflows/map-codebase.md +2 -0
  171. package/gsd-core/workflows/milestone-summary.md +2 -0
  172. package/gsd-core/workflows/mvp-phase.md +1 -1
  173. package/gsd-core/workflows/new-milestone.md +1 -1
  174. package/gsd-core/workflows/new-project.md +5 -3
  175. package/gsd-core/workflows/new-workspace.md +1 -1
  176. package/gsd-core/workflows/next.md +2 -0
  177. package/gsd-core/workflows/node-repair.md +2 -0
  178. package/gsd-core/workflows/note.md +2 -0
  179. package/gsd-core/workflows/onboard.md +1 -1
  180. package/gsd-core/workflows/pause-work.md +19 -4
  181. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  182. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
  183. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
  184. package/gsd-core/workflows/plan-phase.md +130 -12
  185. package/gsd-core/workflows/plan-review-convergence.md +102 -10
  186. package/gsd-core/workflows/plant-seed.md +1 -1
  187. package/gsd-core/workflows/pr-branch.md +11 -3
  188. package/gsd-core/workflows/profile-user.md +1 -1
  189. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  190. package/gsd-core/workflows/progress.md +25 -3
  191. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
  192. package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
  193. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  194. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  195. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  196. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  197. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  198. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  199. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  200. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  201. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  202. package/gsd-core/workflows/quick-batch.md +203 -0
  203. package/gsd-core/workflows/quick.md +13 -3
  204. package/gsd-core/workflows/reapply-patches.md +2 -0
  205. package/gsd-core/workflows/remove-phase.md +2 -0
  206. package/gsd-core/workflows/remove-workspace.md +1 -1
  207. package/gsd-core/workflows/resume-project.md +6 -2
  208. package/gsd-core/workflows/review.md +215 -10
  209. package/gsd-core/workflows/scan.md +2 -0
  210. package/gsd-core/workflows/section-manifest.json +12 -0
  211. package/gsd-core/workflows/secure-phase.md +1 -1
  212. package/gsd-core/workflows/session-report.md +2 -0
  213. package/gsd-core/workflows/settings-advanced.md +2 -0
  214. package/gsd-core/workflows/settings-integrations.md +9 -8
  215. package/gsd-core/workflows/settings.md +1 -1
  216. package/gsd-core/workflows/ship.md +10 -10
  217. package/gsd-core/workflows/sketch-wrap-up.md +2 -0
  218. package/gsd-core/workflows/sketch.md +1 -1
  219. package/gsd-core/workflows/smart-entry.md +1 -1
  220. package/gsd-core/workflows/spec-phase.md +24 -19
  221. package/gsd-core/workflows/spike-wrap-up.md +2 -0
  222. package/gsd-core/workflows/spike.md +1 -1
  223. package/gsd-core/workflows/stats.md +2 -0
  224. package/gsd-core/workflows/sync-skills.md +12 -4
  225. package/gsd-core/workflows/thread.md +2 -0
  226. package/gsd-core/workflows/transition.md +2 -0
  227. package/gsd-core/workflows/ui-phase.md +26 -5
  228. package/gsd-core/workflows/ui-review.md +1 -1
  229. package/gsd-core/workflows/ultraplan-phase.md +2 -0
  230. package/gsd-core/workflows/undo.md +1 -1
  231. package/gsd-core/workflows/update.md +41 -38
  232. package/gsd-core/workflows/validate-phase.md +1 -1
  233. package/gsd-core/workflows/verify-work.md +49 -3
  234. package/hooks/dist/gsd-check-update-worker.js +19 -2
  235. package/hooks/dist/gsd-context-monitor.js +283 -12
  236. package/hooks/dist/gsd-node-runner.sh +1 -0
  237. package/hooks/dist/gsd-prompt-guard.js +30 -5
  238. package/hooks/dist/gsd-read-guard.js +2 -0
  239. package/hooks/dist/gsd-read-injection-scanner.js +5 -5
  240. package/hooks/dist/gsd-secret-read-guard.js +1079 -0
  241. package/hooks/dist/gsd-statusline.js +7 -3
  242. package/hooks/dist/gsd-validate-commit.sh +444 -7
  243. package/hooks/dist/gsd-workflow-guard.js +2 -1
  244. package/hooks/dist/lib/git-cmd.js +210 -1
  245. package/hooks/dist/lib/injection-patterns.js +36 -6
  246. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  247. package/hooks/gsd-check-update-worker.js +19 -2
  248. package/hooks/gsd-context-monitor.js +283 -12
  249. package/hooks/gsd-node-runner.sh +1 -0
  250. package/hooks/gsd-prompt-guard.js +30 -5
  251. package/hooks/gsd-read-guard.js +2 -0
  252. package/hooks/gsd-read-injection-scanner.js +5 -5
  253. package/hooks/gsd-secret-read-guard.js +1079 -0
  254. package/hooks/gsd-statusline.js +7 -3
  255. package/hooks/gsd-validate-commit.sh +444 -7
  256. package/hooks/gsd-workflow-guard.js +2 -1
  257. package/hooks/hooks.json +6 -0
  258. package/hooks/lib/git-cmd.js +210 -1
  259. package/hooks/lib/injection-patterns.js +36 -6
  260. package/hooks/managed-hooks-registry.cjs +1 -0
  261. package/package.json +5 -5
  262. package/scripts/build-hooks.js +11 -4
  263. package/scripts/ci-test-scope.cjs +7 -0
  264. package/scripts/docs-guard-registry.cjs +10 -0
  265. package/scripts/gen-loop-host-contract.cjs +67 -15
  266. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  267. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  268. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  269. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  270. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +5 -0
  271. package/scripts/lint-phase-enumeration-drift.cjs +24 -6
  272. package/scripts/lint-phase-id-drift.cjs +133 -8
  273. package/scripts/lint-portable-grep.cjs +176 -0
  274. package/scripts/lint-response-language-coverage.cjs +524 -0
  275. package/scripts/lint-test-file-count.allowlist.json +3 -1
  276. package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
  277. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  278. package/scripts/npm-audit-baseline.cjs +376 -0
  279. package/scripts/prompt-injection-scan.sh +8 -0
  280. package/scripts/require-issue-link-policy.cjs +16 -1
  281. package/skills/gsd-execute-phase/SKILL.md +1 -1
  282. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  283. package/skills/gsd-phase/SKILL.md +1 -1
  284. package/skills/gsd-quick-batch/SKILL.md +105 -0
  285. package/skills/gsd-surface/SKILL.md +18 -8
  286. package/vscode/package.json +1 -1
@@ -367,6 +367,35 @@ gsd_run query commit "docs($PHASE): UI design contract" --files "$PHASE_DIR/$PAD
367
367
  UI-SPEC complete. Checker can now validate.
368
368
  ```
369
369
 
370
+ ## Revision Conflict
371
+
372
+ Revision mode only. Emit this INSTEAD OF `## UI-SPEC COMPLETE` when a checker `fix_hint`
373
+ contradicts a locked user answer, active capability guidance, or a constraint this UI-SPEC already
374
+ encodes — or when the `required_property` is unreachable without breaking one. Resolve every
375
+ non-conflicting issue first. This is not a failure: `/gsd:ui-phase` routes it to the user and does
376
+ not spend a revision iteration on it.
377
+
378
+ ```markdown
379
+ ## REVISION_CONFLICT
380
+
381
+ **Conflicts:** {N} | **Issues resolved anyway:** {M}
382
+
383
+ | Issue | required_property | Conflicts with | Why the hint cannot be applied |
384
+ |-------|-------------------|----------------|-------------------------------|
385
+ | Dimension {N} | {property} | {locked answer / CLAUDE.md rule / spec constraint} | {one line} |
386
+
387
+ ### Alternatives Considered
388
+
389
+ | Issue | Alternative | Satisfies required_property? | Cost of adopting |
390
+ |-------|-------------|------------------------------|------------------|
391
+ | Dimension {N} | {smaller or different mechanism} | {yes / partially — how} | {what it changes} |
392
+ ```
393
+
394
+ **Every field is one line of plain text.** No newlines inside a cell, and never begin a field with
395
+ `#`, `-`, `|` or a code fence. This table is presented directly to the user in ui-phase's revision
396
+ step, not persisted to a shared file; a field that opens a heading, list item, table cell, or
397
+ fence would corrupt that presentation.
398
+
370
399
  ## UI-SPEC Blocked
371
400
 
372
401
  ```markdown
@@ -42,6 +42,7 @@ Every truth must resolve to VERIFIED, FAILED (BLOCKER), or UNCERTAIN (WARNING wi
42
42
  @~/.claude/gsd-core/references/verification-overrides.md
43
43
  @~/.claude/gsd-core/references/gates.md
44
44
  @~/.claude/gsd-core/references/verifier-phase-gates.md
45
+ @~/.claude/gsd-core/references/verifier-evidence-gate.md
45
46
  </required_reading>
46
47
 
47
48
  This agent implements the **Escalation Gate** pattern (surfaces unresolvable gaps to the developer for decision).
@@ -410,7 +411,9 @@ grep -n -B 2 -A 2 "console\.log" "$file" 2>/dev/null | grep -E "^\s*(const|funct
410
411
 
411
412
  **Debt marker gate:** Any `TBD`, `FIXME`, or `XXX` marker in a file modified by this phase is a 🛑 BLOCKER unless the same line references formal follow-up work (`issue #123`, `PR #123`, `#123`, or `DEF-*`). Unreferenced markers mean completion is not auditable; set `status: gaps_found` and list each marker under `gaps`.
412
413
 
413
- Categorize: 🛑 Blocker (prevents goal or unresolved debt marker) | ⚠️ Warning (incomplete) | ℹ️ Info (notable)
414
+ **Re-verification evidence gate (#3304):** in re-verification mode, a 🛑 Blocker other than an unresolved debt marker (always self-evidencing) blocks unconditionally only if it is a carried-forward gap (Step 0's `gaps:`) or the flagged file was git-modified since the prior `verified:` timestamp (fail closed: unresolvable history counts as modified). Otherwise it predates the gap-closure round unflagged and needs deterministic evidence — a named test run red, or another concrete reproducible artifact — to stay blocking. Full algorithm: @gsd-core/references/verifier-evidence-gate.md. Unevidenced → 📋 Advisory: record in `advisory:` frontmatter, exclude from Step 9 Rule 1, never revert a completed must-have.
415
+
416
+ Categorize: 🛑 Blocker (prevents goal or unresolved debt marker) | ⚠️ Warning (incomplete) | ℹ️ Info (notable) | 📋 Advisory (re-verification only — new-scope, unevidenced; see above)
414
417
 
415
418
  ## Step 7b: Behavioral Spot-Checks
416
419
 
@@ -665,6 +668,8 @@ If `valid != true`, refuse to verify. Surface the discrepancy and ask the user t
665
668
 
666
669
  **ALWAYS use the Write tool to create files** — never use `Bash(cat << 'EOF')` or heredoc commands for file creation.
667
670
 
671
+ **#4155:** `covered_files`: every phase PLAN/SUMMARY (+superseded, nested `plans/`), mapped requirement, changed impl file — ROOT-relative. `gsd_run query verification.fingerprint {phaseDir} {file}...`, copy output — never hand-write `covered_digest`.
672
+
668
673
  Create `.planning/phases/{phase_dir}/{phase_num}-VERIFICATION.md`:
669
674
 
670
675
  ```markdown
@@ -673,6 +678,8 @@ phase: XX-name
673
678
  verified: YYYY-MM-DDTHH:MM:SSZ
674
679
  status: passed | gaps_found | human_needed
675
680
  score: N/M must-haves verified
681
+ covered_files: [...]
682
+ covered_digest: "v1:sha256:..."
676
683
  behavior_unverified: 0 # Count of ⚠️ PRESENT_BEHAVIOR_UNVERIFIED truths (present + wired, behavior not exercised); each is detailed in behavior_unverified_items below (and in human_verification when status is human_needed)
677
684
  overrides_applied: 0 # Count of PASSED (override) items included in score
678
685
  overrides: # Only if overrides exist — carried forward or newly added
@@ -700,6 +707,11 @@ deferred: # Only if deferred items exist (Step 9b)
700
707
  - truth: "Observable truth addressed in a later phase"
701
708
  addressed_in: "Phase N"
702
709
  evidence: "Matching goal or success criteria text"
710
+ advisory: # Only if unevidenced new-scope findings exist (Step 7, re-verification only)
711
+ - finding: "Short description of the new-scope concern"
712
+ category: architectural | security | other
713
+ reason: "Why raised; what would resolve it"
714
+ evidence_status: "none provided"
703
715
  behavior_unverified_items: # Only if behavior_unverified > 0 — emitted regardless of overall status, so these survive a gaps_found phase
704
716
  - truth: "Observable truth whose state transition or cancellation/cleanup/ordering invariant no test exercises"
705
717
  test: "What to trigger"
@@ -744,6 +756,15 @@ Only include this section if deferred items exist (from Step 9b).
744
756
  |---|------|-------------|----------|
745
757
  | 1 | {truth} | Phase {N} | {matching goal or success criteria} |
746
758
 
759
+ ### Advisory (New Scope, Unevidenced)
760
+
761
+ New-scope findings from Step 7 with no deterministic evidence — reported,
762
+ not blocking. Include this section (even "None") whenever re-verification ran.
763
+
764
+ | # | Finding | Category | Why Advisory |
765
+ |---|---------|----------|--------------|
766
+ | 1 | {finding} | {category} | new-scope, no deterministic evidence |
767
+
747
768
  ### Required Artifacts
748
769
 
749
770
  | Artifact | Expected | Status | Details |
@@ -918,6 +939,7 @@ return <div>No messages</div> // Always shows "no messages"
918
939
  - [ ] Gaps structured in YAML frontmatter (if gaps_found)
919
940
  - [ ] Deferred items structured in YAML frontmatter (if deferred items exist)
920
941
  - [ ] Re-verification metadata included (if previous existed)
942
+ - [ ] fingerprint fields written via verification.fingerprint (#4155)
921
943
  - [ ] VERIFICATION.md created with complete report
922
944
  - [ ] Results returned to orchestrator (NOT committed)
923
945
  </success_criteria>
package/bin/install.js CHANGED
@@ -183,10 +183,11 @@ function isCodexHooksFeatureKey(key) {
183
183
  return CODEX_HOOKS_FEATURE_ALL_KEYS.includes(key);
184
184
  }
185
185
 
186
- // #768 \u2014 Claude Code permissions.allow / permissions.deny entries.
186
+ // #768 \u2014 Claude Code permissions.allow entries.
187
187
  // Pre-populated during Claude installs to eliminate first-run approval friction
188
- // for gsd-core's own known-safe tool calls, and to add defense-in-depth deny
189
- // entries for common credential files.
188
+ // for gsd-core's own known-safe tool calls. (The defense-in-depth deny entries
189
+ // for credential files that #768 also wrote are retired \u2014 see
190
+ // GSD_CLAUDE_LEGACY_DENY_PERMISSIONS below.)
190
191
  //
191
192
  // Format: each string uses Claude Code's documented permission rule syntax \u2014
192
193
  // "Tool(pattern)" e.g. "Bash(npx gsd-core *)", "Read(.planning/*)"
@@ -204,7 +205,20 @@ const GSD_CLAUDE_ALLOW_PERMISSIONS = Object.freeze([
204
205
  'Read(STATE.md)',
205
206
  'Edit(STATE.md)',
206
207
  ]);
207
- const GSD_CLAUDE_DENY_PERMISSIONS = Object.freeze([
208
+ // #4221 \u2014 Retired deny rules. #768 wrote these three `Read()` deny rules
209
+ // into settings.json; Claude Code 2.1.259 hardened the Bash-side enforcement
210
+ // of Read() deny rules so that ANY such rule makes every
211
+ // `cd DIR && grep/cat relative-path` compound prompt for approval, even in
212
+ // `auto` permission mode \u2014 and GSD subagents emit hundreds of those per
213
+ // session. The same protection now ships as the managed PreToolUse hook
214
+ // hooks/gsd-secret-read-guard.js (a hook denial is not a permission rule and
215
+ // never arms that check). Unlike the #2278 allow-side migration, there is no
216
+ // surviving "current" deny list: the constant is RENAMED to its legacy role
217
+ // and only ever filtered, never added. Byte-equal strings only \u2014 a user's
218
+ // own hand-written identical rule is indistinguishable and is removed too
219
+ // (the install manifest never recorded permission strings, so a
220
+ // manifest-gated cleanup is not possible).
221
+ const GSD_CLAUDE_LEGACY_DENY_PERMISSIONS = Object.freeze([
208
222
  'Read(.env)',
209
223
  'Read(.env.*)',
210
224
  'Read(.secrets)',
@@ -236,6 +250,13 @@ const GSD_CLAUDE_LEGACY_ALLOW_PERMISSIONS = Object.freeze([
236
250
  * so existing installs end up with the working `Edit(...)` forms instead of
237
251
  * both the dead legacy entry and its replacement sitting side by side.
238
252
  *
253
+ * Migration (#4221): the retired GSD_CLAUDE_LEGACY_DENY_PERMISSIONS entries
254
+ * are removed from permissions.deny (byte-equal only). Nothing is added to
255
+ * deny any more: an absent `deny` key is left absent (never created as an
256
+ * empty array), and a `deny` array emptied BY THIS FILTER is deleted so the
257
+ * retirement leaves no `"deny": []` residue; a user's pre-existing empty
258
+ * `deny: []` is untouched.
259
+ *
239
260
  * Defensive: if settings is not a plain object, returns immediately without
240
261
  * throwing. If permissions.allow / permissions.deny exist but are not arrays
241
262
  * (malformed settings), they are replaced with valid arrays.
@@ -252,7 +273,7 @@ function mergeClaudePermissions(settings) {
252
273
  if (!Array.isArray(settings.permissions.allow)) {
253
274
  settings.permissions.allow = [];
254
275
  }
255
- if (!Array.isArray(settings.permissions.deny)) {
276
+ if (settings.permissions.deny !== undefined && !Array.isArray(settings.permissions.deny)) {
256
277
  settings.permissions.deny = [];
257
278
  }
258
279
 
@@ -265,9 +286,13 @@ function mergeClaudePermissions(settings) {
265
286
  settings.permissions.allow.push(entry);
266
287
  }
267
288
  }
268
- for (const entry of GSD_CLAUDE_DENY_PERMISSIONS) {
269
- if (!settings.permissions.deny.includes(entry)) {
270
- settings.permissions.deny.push(entry);
289
+ if (Array.isArray(settings.permissions.deny)) {
290
+ const before = settings.permissions.deny.length;
291
+ settings.permissions.deny = settings.permissions.deny.filter(
292
+ (e) => !GSD_CLAUDE_LEGACY_DENY_PERMISSIONS.includes(e)
293
+ );
294
+ if (settings.permissions.deny.length === 0 && before > 0) {
295
+ delete settings.permissions.deny;
271
296
  }
272
297
  }
273
298
  }
@@ -411,7 +436,7 @@ const GSD_CHANGESET_FILES = [
411
436
  'github-release-notes.cjs', 'lint.cjs', 'new.cjs',
412
437
  'README.md', // documentation only — not user-authored
413
438
  ];
414
- const GSD_SCRIPTS_LIB_FILES = ['cli-exit.cjs', 'allowlist-ratchet.cjs', 'drift-scan.cjs', 'alias-drift-families.cjs', 'exit-code-registry.cjs', 'ndjson-reporter.cjs', 'ci-job-timing.cjs'];
439
+ const GSD_SCRIPTS_LIB_FILES = ['cli-exit.cjs', 'allowlist-ratchet.cjs', 'drift-scan.cjs', 'alias-drift-families.cjs', 'exit-code-registry.cjs', 'ndjson-reporter.cjs', 'ci-job-timing.cjs', 'shellcheck-fetch.cjs'];
415
440
 
416
441
  /**
417
442
  * Resolve a runtime's shared-hooks directory name from its descriptor.
@@ -790,6 +815,10 @@ const {
790
815
  const {
791
816
  resolveRuntimeArtifactLayout,
792
817
  } = require(path.join(_gsdLibDir, 'runtime-artifact-layout.cjs'));
818
+ const {
819
+ readSurface,
820
+ resolveSurface,
821
+ } = require(path.join(_gsdLibDir, 'surface.cjs'));
793
822
  const {
794
823
  assertDestWithinConfigHome,
795
824
  createRuntimeArtifactInstallPlan,
@@ -1796,8 +1825,20 @@ const kiloAgentPermissionOrder = [
1796
1825
  'lsp',
1797
1826
  ];
1798
1827
 
1828
+ const kiloMcpPermissionPattern = /^mcp__([A-Za-z0-9_-]+)__((?:[A-Za-z0-9_-]+)|\*)$/;
1829
+
1830
+ // Derives Kilo's native `{server}_{tool}` MCP permission key (kilo.ai/docs —
1831
+ // external, fixed format). Not injective: both capture groups allow `_`, so
1832
+ // e.g. `mcp__a_b__c` and `mcp__a__b_c` derive the same key. The `Set` below
1833
+ // resolves any such collision deterministically to first-seen-wins — see
1834
+ // src/runtime-artifact-conversion.cts's regression test. Mirrors that file's
1835
+ // convertClaudeToKiloPermissionTool exactly (#4032).
1799
1836
  function convertClaudeToKiloPermissionTool(claudeTool) {
1800
- return claudeToKiloAgentPermissions[claudeTool] || null;
1837
+ const builtinPermission = claudeToKiloAgentPermissions[claudeTool];
1838
+ if (builtinPermission) return builtinPermission;
1839
+
1840
+ const mcpPermission = kiloMcpPermissionPattern.exec(claudeTool);
1841
+ return mcpPermission ? `${mcpPermission[1]}_${mcpPermission[2]}` : null;
1801
1842
  }
1802
1843
 
1803
1844
  function buildKiloAgentPermissionBlock(claudeTools) {
@@ -1814,6 +1855,10 @@ function buildKiloAgentPermissionBlock(claudeTools) {
1814
1855
  for (const permission of kiloAgentPermissionOrder) {
1815
1856
  lines.push(` ${permission}: ${allowedPermissions.has(permission) ? 'allow' : 'deny'}`);
1816
1857
  }
1858
+ for (const permission of allowedPermissions) {
1859
+ if (kiloAgentPermissionOrder.includes(permission)) continue;
1860
+ lines.push(` ${permission}: allow`);
1861
+ }
1817
1862
 
1818
1863
  return lines;
1819
1864
  }
@@ -3849,26 +3894,37 @@ Execute mode fallback:
3849
3894
  ## C. Task() → spawn_agent Mapping
3850
3895
  GSD workflows use \`Task(...)\` (Claude Code syntax). Translate to Codex collaboration tools:
3851
3896
 
3852
- **Schema detection (required first step):** Codex exposes two \`spawn_agent\` schemas:
3853
- - **agent_type-capable schema** (e.g. \`multi_agent_v2\`): \`spawn_agent\` accepts \`agent_type\`, \`message\`, \`reasoning_effort\`, \`fork_context\`, etc. — typed GSD agent dispatch is available.
3854
- - **Generic schema** (\`multi_agent_v1\`): \`spawn_agent\` accepts only \`message\`, \`items\`, \`fork_context\` — there is **no \`agent_type\` field**. Typed GSD agent dispatch is unavailable in this session.
3897
+ **Schema detection (required first step):** Before spawning, inspect the \`spawn_agent\`
3898
+ tool's visible parameter schema (via \`tool_search\` or the tool list). Use the presence
3899
+ of \`agent_type\` only to choose typed dispatch versus the generic-agent workaround.
3900
+ Detect optional fields independently: \`model\`, \`reasoning_effort\`, \`task_name\`,
3901
+ \`fork_turns\`, and \`fork_context\` may be added or removed without \`agent_type\` changing.
3902
+ Never infer one field from a schema/version label or from the presence of another field.
3855
3903
 
3856
- Before spawning, inspect the \`spawn_agent\` tool's visible parameter schema (via \`tool_search\` or the tool list) to determine which form is active.
3904
+ - **agent_type-capable schema:** \`spawn_agent\` advertises \`agent_type\` — typed GSD agent dispatch is available.
3905
+ - **Generic schema:** \`spawn_agent\` does not advertise \`agent_type\` — typed GSD agent dispatch is unavailable in this session, even if other optional fields are present.
3857
3906
 
3858
3907
  Typed mapping (agent_type-capable schema only):
3859
3908
  - \`Task(subagent_type="X", prompt="Y")\` → \`spawn_agent(agent_type="X", message="Y")\`
3860
3909
  - \`Agent(subagent_type="X", prompt="Y")\` → \`spawn_agent(agent_type="X", message="Y")\`
3861
- - \`Task(model="...")\` → omit. \`spawn_agent\` has no inline \`model\` parameter;
3862
- GSD embeds the resolved per-agent model directly into each agent's \`.toml\`
3863
- at install time so \`model_overrides\` from \`.planning/config.json\` and
3864
- \`~/.gsd/defaults.json\` are honored automatically by Codex's agent router.
3865
- - Resolved \`reasoning_effort="low|medium|high|xhigh"\` (\`xhigh\` is a GSD/Codex tier, not a generic runtime enum) → pass \`reasoning_effort\`
3866
- to \`spawn_agent\` when the runtime/tool supports it. Omit missing, empty,
3867
- inherited, or unsupported values; do not invent one-off effort literals in
3868
- workflow prose.
3910
+ - \`Task(model="{resolved_model}")\` → pass \`model="{resolved_model}"\` when the
3911
+ visible \`spawn_agent\` schema advertises \`model\` and the resolved value is explicit.
3912
+ This is how \`model_profile\` tier routing, including \`adaptive\`, reaches the child agent.
3913
+ Omit \`model\` only when the schema does not advertise \`model\`, or when the value is
3914
+ missing, empty, or \`"inherit"\`; omission deliberately inherits the session/static agent
3915
+ configuration. Explicit \`model_overrides\` may also be embedded in agent \`.toml\` files,
3916
+ but ordinary profile-resolved models are not, so a TOML file is not a reason to discard
3917
+ an available inline value.
3918
+ - Before each typed spawn, obtain the paired effort for its role with
3919
+ \`gsd_run query resolve-model <subagent_type> --pick effort\` when the workflow has not
3920
+ already exposed it. The resolver's unified \`effort\` field maps to the Codex spawn argument
3921
+ \`reasoning_effort\`; do not look for a resolver field named \`reasoning_effort\`.
3922
+ Pass it when the visible \`spawn_agent\` schema advertises \`reasoning_effort\`. Omit the
3923
+ field when it is not advertised, or when the value is missing, empty, \`"inherit"\`, or
3924
+ unsupported; do not invent one-off effort literals in workflow prose.
3869
3925
  - \`fork_context: false\` by default — GSD agents load their own context via \`<required_reading>\` blocks
3870
- - \`task_name\` — required by the collaboration schema; provide a descriptive name for each spawned task
3871
- - \`fork_turns\` — optional parameter controlling turn-forking depth; coexists with \`fork_context\` (not a replacement)
3926
+ - \`task_name\` — when advertised, provide a descriptive name for each spawned task
3927
+ - \`fork_turns\` — when advertised, controls turn-forking depth; coexists with \`fork_context\` (not a replacement)
3872
3928
  - \`Task(isolation="worktree")\` / \`Agent(isolation="worktree")\` → no direct \`spawn_agent\` mapping,
3873
3929
  but Codex declares \`dispatch.isolation: orchestrator-worktree\` (#2584). Codex
3874
3930
  \`spawn_agent\` still does not create or bind a git worktree; instead GSD itself
@@ -7372,7 +7428,8 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
7372
7428
 
7373
7429
  if (isAgent && inAgentTools) {
7374
7430
  if (trimmed.startsWith('- ')) {
7375
- agentTools.push(trimmed.substring(2).trim());
7431
+ const tool = runtimeArtifactConversion._decodeToolScalar(trimmed.substring(2));
7432
+ if (tool !== null) agentTools.push(tool);
7376
7433
  continue;
7377
7434
  }
7378
7435
  if (trimmed && !trimmed.startsWith('-')) {
@@ -7384,8 +7441,12 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
7384
7441
  if (trimmed.startsWith('tools:')) {
7385
7442
  if (isAgent) {
7386
7443
  const toolsValue = trimmed.substring(6).trim();
7387
- if (toolsValue) {
7388
- const tools = toolsValue.split(',').map(t => t.trim()).filter(t => t);
7444
+ // A comment-only value (`tools: # note`) is not real inline content —
7445
+ // fall through to the block-list scan instead of decoding the
7446
+ // comment as a bogus tool name and dropping the list (mirrors
7447
+ // src/runtime-artifact-conversion.cts's convertClaudeToKiloFrontmatter, #4032).
7448
+ if (toolsValue && !toolsValue.startsWith('#')) {
7449
+ const tools = runtimeArtifactConversion._splitToolScalars(toolsValue).map(runtimeArtifactConversion._decodeToolScalar).filter(tool => tool !== null);
7389
7450
  agentTools.push(...tools);
7390
7451
  } else {
7391
7452
  inAgentTools = true;
@@ -7451,7 +7512,8 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
7451
7512
  if (trimmed.startsWith('- ')) {
7452
7513
  const tool = trimmed.substring(2).trim();
7453
7514
  if (isAgent) {
7454
- agentTools.push(tool);
7515
+ const decoded = runtimeArtifactConversion._decodeToolScalar(tool);
7516
+ if (decoded !== null) agentTools.push(decoded);
7455
7517
  } else {
7456
7518
  allowedTools.push(tool);
7457
7519
  }
@@ -9039,17 +9101,29 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
9039
9101
  );
9040
9102
  if (settings.permissions.allow.length !== before) {
9041
9103
  permissionsModified = true;
9104
+ // #4221: an array this filter emptied was GSD-only \u2014 remove the
9105
+ // key rather than leave an empty array behind (Antigravity symmetry).
9106
+ if (settings.permissions.allow.length === 0) {
9107
+ delete settings.permissions.allow;
9108
+ }
9042
9109
  }
9043
9110
  }
9044
9111
  if (Array.isArray(settings.permissions.deny)) {
9045
9112
  const before = settings.permissions.deny.length;
9113
+ // #4221: the deny rules are retired, so this is a legacy-only filter.
9046
9114
  settings.permissions.deny = settings.permissions.deny.filter(
9047
- (e) => !GSD_CLAUDE_DENY_PERMISSIONS.includes(e)
9115
+ (e) => !GSD_CLAUDE_LEGACY_DENY_PERMISSIONS.includes(e)
9048
9116
  );
9049
9117
  if (settings.permissions.deny.length !== before) {
9050
9118
  permissionsModified = true;
9119
+ if (settings.permissions.deny.length === 0) {
9120
+ delete settings.permissions.deny;
9121
+ }
9051
9122
  }
9052
9123
  }
9124
+ if (permissionsModified && Object.keys(settings.permissions).length === 0) {
9125
+ delete settings.permissions;
9126
+ }
9053
9127
  if (permissionsModified) {
9054
9128
  settingsModified = true;
9055
9129
  console.log(` ${green}✓${reset} Removed GSD permissions from settings.json`);
@@ -10111,19 +10185,57 @@ function saveLocalPatches(configDir, pristineCtx) {
10111
10185
  const modified = [];
10112
10186
  const pristineHashes = {};
10113
10187
 
10188
+ // #4086: skills/ manifest keys may live OUTSIDE configDir at the runtime's
10189
+ // ACTUAL skills root — codex global installs to $HOME/.agents/skills (the
10190
+ // ADR-1239 skills-kind `home` override), which writeManifest() already
10191
+ // hashes from via _resolveSkillsRootDir (#2088/#3738). Resolving every key
10192
+ // against configDir alone made every skills/ key miss here, so user
10193
+ // modifications to Codex skills were never hash-compared, never backed up,
10194
+ // and were silently overwritten by the next update. configDir stays FIRST
10195
+ // (Postel: runtimes whose skills genuinely live under configDir resolve
10196
+ // byte-identically to before); the skills root is a fallback, only for keys
10197
+ // under the SAME descriptor-driven manifest prefix writeManifest uses, and
10198
+ // only when that root resolves outside configDir. Containment + symlink
10199
+ // guards apply to the alternate root too (resolveInstallRelativePath).
10200
+ const patchRuntime = (pristineCtx && pristineCtx.runtime) || manifest.runtime || null;
10201
+ const patchScope = manifest.scope === 'local' ? 'local' : 'global';
10202
+ let skillsRedirect = null;
10203
+ if (patchRuntime) {
10204
+ const skillsRoot = _resolveSkillsRootDir(patchRuntime, configDir, patchScope);
10205
+ const resolvedConfig = path.resolve(configDir);
10206
+ if (
10207
+ skillsRoot &&
10208
+ skillsRoot !== resolvedConfig &&
10209
+ !skillsRoot.startsWith(resolvedConfig + path.sep)
10210
+ ) {
10211
+ const prefix = _hostBehaviors(patchRuntime).skillsManifestPrefix || 'skills/';
10212
+ skillsRedirect = { root: skillsRoot, prefix };
10213
+ }
10214
+ }
10215
+
10114
10216
  for (const [relPath, originalHash] of Object.entries(manifest.files || {})) {
10115
10217
  const safeRef = resolveInstallRelativePath(configDir, relPath);
10116
10218
  if (!safeRef) continue;
10117
10219
  const { relPath: safeRelPath, fullPath } = safeRef;
10118
- if (!fs.existsSync(fullPath)) continue;
10119
- const currentHash = fileHash(fullPath);
10220
+ let installedPath = fullPath;
10221
+ if (!fs.existsSync(installedPath) && skillsRedirect && safeRelPath.startsWith(skillsRedirect.prefix)) {
10222
+ const altRef = resolveInstallRelativePath(
10223
+ skillsRedirect.root,
10224
+ safeRelPath.slice(skillsRedirect.prefix.length)
10225
+ );
10226
+ if (altRef && fs.existsSync(altRef.fullPath)) {
10227
+ installedPath = altRef.fullPath;
10228
+ }
10229
+ }
10230
+ if (!fs.existsSync(installedPath)) continue;
10231
+ const currentHash = fileHash(installedPath);
10120
10232
  if (currentHash !== originalHash) {
10121
10233
  // Back up the user's modified version
10122
10234
  const backupRef = resolveInstallRelativePath(patchesDir, safeRelPath);
10123
10235
  if (!backupRef) continue;
10124
10236
  const backupPath = backupRef.fullPath;
10125
10237
  fs.mkdirSync(path.dirname(backupPath), { recursive: true });
10126
- fs.copyFileSync(fullPath, backupPath);
10238
+ fs.copyFileSync(installedPath, backupPath);
10127
10239
  modified.push(safeRelPath);
10128
10240
  pristineHashes[safeRelPath] = originalHash;
10129
10241
  }
@@ -10512,7 +10624,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
10512
10624
  // one) is honored on every profile, `full` included (#2322 blocker 2).
10513
10625
  const _commandsDir = path.join(src, 'commands', 'gsd');
10514
10626
  const _skillsManifest = _isCoreProfileAlias ? new Map() : loadSkillsManifest(_commandsDir);
10515
- const _resolvedProfile = resolveProfile({
10627
+ let _resolvedProfile = resolveProfile({
10516
10628
  modes: [_activeProfileName],
10517
10629
  manifest: _skillsManifest,
10518
10630
  registry: _installedCapabilityRegistry,
@@ -10863,6 +10975,28 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
10863
10975
  return Array.isArray(scopeLayout) && scopeLayout.length > 0;
10864
10976
  })();
10865
10977
 
10978
+ // Install the distribution-owned gsd-core tree before layout materialization.
10979
+ // installRuntimeArtifacts then provisions the durable Runtime Surface corpus
10980
+ // exactly once into this final tree instead of having that corpus overwritten
10981
+ // by a later whole-tree copy and needing a second provisioning pass.
10982
+ const skillSrc = path.join(src, 'gsd-core');
10983
+ const skillDest = path.join(targetDir, 'gsd-core');
10984
+ const _gsdArtifactsStagingRoot = _tryResolveUserArtifactStagingRoot(targetDir);
10985
+ if (_gsdArtifactsStagingRoot === null) {
10986
+ console.warn(` ${yellow}!${reset} Skipping gsd-core/${USER_OWNED_ARTIFACTS.join(', gsd-core/')} preservation (staging unavailable) — it will be lost if present.`);
10987
+ copyWithPathReplacement(skillSrc, skillDest, pathPrefix, runtime, false, isGlobal, targetDir);
10988
+ } else {
10989
+ const stagedGsdArtifacts = stageUserArtifacts(skillDest, USER_OWNED_ARTIFACTS, _gsdArtifactsStagingRoot);
10990
+ copyWithPathReplacement(skillSrc, skillDest, pathPrefix, runtime, false, isGlobal, targetDir);
10991
+ restoreStagedUserArtifacts(skillDest, stagedGsdArtifacts);
10992
+ discardStagedUserArtifacts(stagedGsdArtifacts);
10993
+ }
10994
+ if (verifyInstalled(skillDest, 'gsd-core')) {
10995
+ console.log(` ${green}✓${reset} Installed workflow assets`);
10996
+ } else {
10997
+ failures.push('gsd-core');
10998
+ }
10999
+
10866
11000
  // #2624: write the .gsd-source marker. Extracted from its former late position so it can be
10867
11001
  // called BEFORE staging reads the marker (see the call site below). Scoped to the Claude-global
10868
11002
  // layout (issue #1477) — the only install path that ships the skills layout without a
@@ -10879,6 +11013,9 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
10879
11013
  // ADR-1239 Phase B write-confinement: the descriptor-sourced marker filename
10880
11014
  // must resolve under targetDir (parity with the other descriptor-driven writes).
10881
11015
  const _markerPath = assertDestWithinConfigHome(targetDir, _hostBehaviors(runtime).sourceMarkerFile);
11016
+ if (hasExistingSymlinkBetween(path.resolve(targetDir), _markerPath, { allowOptInFollow: isSymlinkedDestOptIn() })) {
11017
+ throw new Error(`compatibility marker "${_markerPath}" contains an untrusted symlink`);
11018
+ }
10882
11019
  fs.writeFileSync(_markerPath, gsdSourceCommands + '\n', 'utf8');
10883
11020
  } catch (err) {
10884
11021
  // Non-fatal: install proceeds. But on the Claude-global layout walk-up
@@ -10905,6 +11042,19 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
10905
11042
  if (_isSkillsRuntime) {
10906
11043
  // Layout-driven install for skills-based runtimes (full and minimal modes)
10907
11044
  const scope = _installScopeId;
11045
+ // Preserve an existing Runtime Surface selection during upgrades. The
11046
+ // installer refreshes the source corpus first, then emits exactly the
11047
+ // already-committed selection instead of widening it to the base profile.
11048
+ const _committedSurface = readSurface(targetDir);
11049
+ if (_committedSurface) {
11050
+ _resolvedProfile = resolveSurface(
11051
+ targetDir,
11052
+ loadSkillsManifest(_commandsDir),
11053
+ undefined,
11054
+ _installedCapabilityRegistry,
11055
+ _committedSurface,
11056
+ );
11057
+ }
10908
11058
  // ADR-1239 upgrade 3 / #2088: a kind may declare an alternate install `home`
10909
11059
  // (e.g. Codex skills -> $HOME/.agents/skills) instead of the runtime's normal
10910
11060
  // configDir. Resolve the ACTUAL on-disk skills root here, descriptor-driven
@@ -11197,36 +11347,6 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
11197
11347
  _installNativePluginIfDeclared(runtime, targetDir, _hostBehaviors(runtime), src);
11198
11348
  }
11199
11349
 
11200
- // Copy gsd-core skill with path replacement
11201
- // Stage user-generated files DURABLY to disk before the wipe-and-copy so
11202
- // they survive re-install even if the process dies mid-copy (#2875 /
11203
- // #1874-F19) — copyWithPathReplacement wipes and recursively re-copies the
11204
- // entire gsd-core/ tree, the single longest operation in the install, on
11205
- // the path every user takes (40-design.md "Site 4 is far worse...").
11206
- const skillSrc = path.join(src, 'gsd-core');
11207
- const skillDest = path.join(targetDir, 'gsd-core');
11208
- // #2875 defect fix: this IS the mainline install step (installing
11209
- // gsd-core/ itself) — unlike the optional legacy-cleanup blocks above,
11210
- // install must still be able to proceed and actually write gsd-core/ even
11211
- // when the staging root cannot be resolved. Degrade by skipping ONLY the
11212
- // USER_OWNED_ARTIFACTS preserve/restore wrapper around the copy (warn),
11213
- // never the copy itself.
11214
- const _gsdArtifactsStagingRoot = _tryResolveUserArtifactStagingRoot(targetDir);
11215
- if (_gsdArtifactsStagingRoot === null) {
11216
- console.warn(` ${yellow}!${reset} Skipping gsd-core/${USER_OWNED_ARTIFACTS.join(', gsd-core/')} preservation (staging unavailable) — it will be lost if present.`);
11217
- copyWithPathReplacement(skillSrc, skillDest, pathPrefix, runtime, false, isGlobal, targetDir);
11218
- } else {
11219
- const stagedGsdArtifacts = stageUserArtifacts(skillDest, USER_OWNED_ARTIFACTS, _gsdArtifactsStagingRoot);
11220
- copyWithPathReplacement(skillSrc, skillDest, pathPrefix, runtime, false, isGlobal, targetDir);
11221
- restoreStagedUserArtifacts(skillDest, stagedGsdArtifacts);
11222
- discardStagedUserArtifacts(stagedGsdArtifacts);
11223
- }
11224
- if (verifyInstalled(skillDest, 'gsd-core')) {
11225
- console.log(` ${green}✓${reset} Installed workflow assets`);
11226
- } else {
11227
- failures.push('gsd-core');
11228
- }
11229
-
11230
11350
  // #2624: the .gsd-source marker is now written by _writeGsdSourceMarker()
11231
11351
  // BEFORE staging reads it (see the early call above the _isSkillsRuntime
11232
11352
  // block). The former write lived here — AFTER staging — which on an upgrade
@@ -11314,7 +11434,8 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
11314
11434
  } else if (_isSkillsRuntime) {
11315
11435
  console.log(` ${dim}↳${reset} Agents installed via descriptor-driven layout (${runtime})`);
11316
11436
  } else {
11317
- const _standaloneAgentsResult = installAgentsKindStandalone(runtime, targetDir, _installScopeId, _resolvedProfile, pathPrefix, getCommitAttribution, _installedCapabilityRegistry);
11437
+ const _standaloneProjectDir = isGlobal ? process.cwd() : targetDir;
11438
+ const _standaloneAgentsResult = installAgentsKindStandalone(runtime, targetDir, _installScopeId, _resolvedProfile, pathPrefix, getCommitAttribution, _installedCapabilityRegistry, _standaloneProjectDir);
11318
11439
  if (_standaloneAgentsResult) {
11319
11440
  // #2875 defect fix: installAgentsKindStandalone now returns `null`
11320
11441
  // (rather than a truthy result pointing at an empty destDir) whenever a
@@ -12064,8 +12185,18 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
12064
12185
  // require()s managed-hooks-registry.cjs for MANAGED_HOOKS — so all four must be
12065
12186
  // installed/refreshed together for every profile, or Codex is wired to a dependency
12066
12187
  // chain the same installer never delivers.
12067
- // We deliberately do *not* copy gsd-graphify-update.sh or hooks/lib/ for Codex
12068
- // in this change (graphify auto-update support for Codex is out of scope for #3579).
12188
+ // We deliberately do *not* copy gsd-graphify-update.sh for Codex in this
12189
+ // change (graphify auto-update support for Codex is out of scope for #3579).
12190
+ // hooks/lib/ WAS excluded here for the same reason, and that stopped being
12191
+ // correct when #3911 (2ea5efc15) gave gsd-context-monitor.js a real
12192
+ // `require('./lib/hook-exit.js')`: the allowlist below is flat and never
12193
+ // recursed, so the hook shipped without its helper and died with
12194
+ // MODULE_NOT_FOUND at load, before its own try/catch, on every registered
12195
+ // event (#4087, #4098). The libs are now derived from what the staged
12196
+ // scripts actually require rather than hand-listed — see the
12197
+ // stageTransitiveHookLibs call after the copy loop. The #3579 boundary is
12198
+ // preserved: helpers no staged Codex hook requires (graphify tooling among
12199
+ // them) are still not shipped.
12069
12200
  const CODEX_HOOKS_TO_COPY = [
12070
12201
  'gsd-check-update.js',
12071
12202
  'gsd-check-update-worker.js',
@@ -12081,6 +12212,12 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
12081
12212
  // is not the same as an allowlisted file landing in it — see the marker
12082
12213
  // gate below.
12083
12214
  let codexStagedHooks = false;
12215
+ // The entries THIS invocation actually staged. Seeding the lib scan from
12216
+ // `existsSync` over the destination instead would also pick up a file
12217
+ // left by a PREVIOUS install whose source is no longer staged — e.g. a
12218
+ // name dropped from the allowlist — and derive helpers for a hook that is
12219
+ // no longer shipped (review of #4087).
12220
+ const codexStagedEntries = [];
12084
12221
  for (const entry of fs.readdirSync(codexHooksSrc)) {
12085
12222
  if (!CODEX_HOOKS_TO_COPY.includes(entry)) continue;
12086
12223
  const srcFile = path.join(codexHooksSrc, entry);
@@ -12115,8 +12252,43 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
12115
12252
  try { fs.chmodSync(destFile, 0o755); } catch (e) { /* Windows */ }
12116
12253
  }
12117
12254
  codexStagedHooks = true;
12255
+ codexStagedEntries.push(entry);
12256
+ }
12257
+ // Stage the hooks/lib/ helpers the staged scripts require, transitively
12258
+ // (#4087, #4098). Shares writeCursorHooksJson's walker rather than a
12259
+ // second copy: both reduced bundles hand-pick SCRIPTS, and the identical
12260
+ // MODULE_NOT_FOUND was already fixed once for Cursor in 704859e9c. A flat
12261
+ // list of today's three helpers would re-break the next time a
12262
+ // Codex-bundled hook grows a lib dependency, which is exactly how this
12263
+ // regressed. Gated on codexStagedHooks for the same reason the CommonJS
12264
+ // marker below is: hooks/ is shared space, and staging nothing must not
12265
+ // leave a GSD-owned lib/ behind in a directory GSD created but did not
12266
+ // fill (#2544). Seeded from the copies staged by THIS invocation, so the
12267
+ // scan sees the same bytes Node will load and never derives helpers for a
12268
+ // hook left behind by an earlier install.
12269
+ let codexStagedLibs = [];
12270
+ if (codexStagedHooks) {
12271
+ codexStagedLibs = hooksSurface.stageTransitiveHookLibs({
12272
+ seedSources: codexStagedEntries
12273
+ .map((entry) => fs.readFileSync(path.join(codexHooksDest, entry), 'utf8')),
12274
+ srcLibDir: path.join(codexHooksSrc, 'lib'),
12275
+ destLibDir: path.join(codexHooksDest, 'lib'),
12276
+ runtimeLabel: 'Codex',
12277
+ // Same substitutions the .js branch above applies to hook scripts, so
12278
+ // a helper that ever gains a runtime path or version token is
12279
+ // rewritten identically instead of shipping a Claude-shaped path.
12280
+ // No-ops on today's helpers, which carry neither.
12281
+ transform: (content) => content
12282
+ .replace(/'\.claude'/g, configDirReplacement)
12283
+ .replace(/\/\.claude\//g, `/${getDirName(runtime)}/`)
12284
+ .replace(/\.claude\//g, `${getDirName(runtime)}/`)
12285
+ .replace(/\{\{GSD_VERSION\}\}/g, pkg.version),
12286
+ });
12118
12287
  }
12119
12288
  console.log(` ${green}✓${reset} Installed hooks (Codex)`);
12289
+ if (codexStagedLibs.length > 0) {
12290
+ console.log(` ${green}✓${reset} Installed hooks/lib/ helpers (${codexStagedLibs.join(', ')})`);
12291
+ }
12120
12292
  // #2717: write the CommonJS marker into hooks/ alongside the staged .js
12121
12293
  // scripts. Codex is excluded from installSharedHooksBundle by the
12122
12294
  // !isCodex gate, so it never received the marker the shared-bundle path
@@ -13881,7 +14053,7 @@ module.exports = {
13881
14053
  mergeClaudePermissions,
13882
14054
  GSD_CLAUDE_ALLOW_PERMISSIONS,
13883
14055
  GSD_CLAUDE_LEGACY_ALLOW_PERMISSIONS,
13884
- GSD_CLAUDE_DENY_PERMISSIONS,
14056
+ GSD_CLAUDE_LEGACY_DENY_PERMISSIONS,
13885
14057
  GSD_CODEX_MARKER,
13886
14058
  // #3897 rung 3 (ADR-3473 §8.3, HALT.md option 2)
13887
14059
  CODEX_SANDBOX_HOLDS,
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: gsd:execute-phase
3
- description: Execute all plans in a phase with wave-based parallelization
3
+ description: SDD phase execution — execute all plans in a phase with dependency-aware wave parallelization
4
4
  argument-hint: "<phase-number> [--wave N] [--gaps-only] [--interactive] [--tdd]"
5
5
  effort: max
6
6
  allowed-tools:
@@ -5,7 +5,7 @@ argument-hint: ""
5
5
  allowed-tools:
6
6
  - Read
7
7
  - Skill
8
- requires: [discuss-phase, spec-phase, plan-phase, execute-phase, verify-work, phase, progress, next, ultraplan-phase, plan-review-convergence, add-tests, ai-integration-phase, autonomous, fast, mvp-phase, quick]
8
+ requires: [discuss-phase, spec-phase, plan-phase, execute-phase, verify-work, phase, progress, next, ultraplan-phase, plan-review-convergence, add-tests, ai-integration-phase, autonomous, fast, mvp-phase, quick, quick-batch]
9
9
  ---
10
10
 
11
11
  Route to the appropriate phase-pipeline skill based on the user's intent.
@@ -33,5 +33,6 @@ workflow-advance command.
33
33
  | Execute a trivial task inline | gsd-fast |
34
34
  | Plan a phase as a vertical MVP slice | gsd-mvp-phase |
35
35
  | Execute a quick task with GSD guarantees | gsd-quick |
36
+ | Batch several quick-shaped tasks together | gsd-quick-batch |
36
37
 
37
38
  Invoke the matched skill directly using the Skill tool.