@opengsd/gsd-core 1.12.0 → 1.14.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 (455) 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-advisor-researcher.compact.md +85 -0
  5. package/agents/gsd-ai-researcher.compact.md +96 -0
  6. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  7. package/agents/gsd-code-fixer.compact.md +458 -0
  8. package/agents/gsd-code-fixer.md +5 -5
  9. package/agents/gsd-code-reviewer.compact.md +269 -0
  10. package/agents/gsd-code-reviewer.md +15 -3
  11. package/agents/gsd-codebase-mapper.compact.md +760 -0
  12. package/agents/gsd-debug-session-manager.compact.md +345 -0
  13. package/agents/gsd-doc-classifier.compact.md +192 -0
  14. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  15. package/agents/gsd-doc-verifier.compact.md +143 -0
  16. package/agents/gsd-doc-writer.compact.md +440 -0
  17. package/agents/gsd-dom-verifier.compact.md +138 -0
  18. package/agents/gsd-domain-researcher.compact.md +141 -0
  19. package/agents/gsd-eval-auditor.compact.md +160 -0
  20. package/agents/gsd-eval-planner.compact.md +137 -0
  21. package/agents/gsd-executor.md +63 -35
  22. package/agents/gsd-framework-selector.compact.md +82 -0
  23. package/agents/gsd-integration-checker.compact.md +245 -0
  24. package/agents/gsd-intel-updater.compact.md +226 -0
  25. package/agents/gsd-mempalace-curator.compact.md +45 -0
  26. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  27. package/agents/gsd-pattern-mapper.compact.md +275 -0
  28. package/agents/gsd-plan-checker.md +76 -57
  29. package/agents/gsd-planner.md +14 -0
  30. package/agents/gsd-project-researcher.compact.md +587 -0
  31. package/agents/gsd-research-synthesizer.compact.md +212 -0
  32. package/agents/gsd-roadmapper.compact.md +454 -0
  33. package/agents/gsd-roadmapper.md +13 -0
  34. package/agents/gsd-security-auditor.compact.md +162 -0
  35. package/agents/gsd-ui-auditor.compact.md +404 -0
  36. package/agents/gsd-ui-checker.compact.md +277 -0
  37. package/agents/gsd-ui-checker.md +19 -3
  38. package/agents/gsd-ui-researcher.compact.md +282 -0
  39. package/agents/gsd-ui-researcher.md +29 -0
  40. package/agents/gsd-user-profiler.compact.md +108 -0
  41. package/agents/gsd-verifier.md +23 -1
  42. package/bin/install.js +444 -134
  43. package/commands/gsd/cleanup.md +1 -0
  44. package/commands/gsd/code-review.md +2 -1
  45. package/commands/gsd/complete-milestone.md +1 -0
  46. package/commands/gsd/config.md +1 -0
  47. package/commands/gsd/debug.md +1 -0
  48. package/commands/gsd/execute-phase.md +1 -1
  49. package/commands/gsd/graphify.md +1 -0
  50. package/commands/gsd/health.md +1 -0
  51. package/commands/gsd/mempalace-capture.md +1 -0
  52. package/commands/gsd/mempalace-recall.md +1 -0
  53. package/commands/gsd/new-milestone.md +1 -0
  54. package/commands/gsd/new-project.md +1 -0
  55. package/commands/gsd/next.md +1 -0
  56. package/commands/gsd/ns-workflow.md +2 -1
  57. package/commands/gsd/pause-work.md +1 -0
  58. package/commands/gsd/phase.md +2 -1
  59. package/commands/gsd/pr-branch.md +1 -0
  60. package/commands/gsd/quick-batch.md +105 -0
  61. package/commands/gsd/resume-work.md +1 -0
  62. package/commands/gsd/review-backlog.md +1 -0
  63. package/commands/gsd/settings.md +2 -1
  64. package/commands/gsd/stats.md +1 -0
  65. package/commands/gsd/surface.md +18 -8
  66. package/commands/gsd/thread.md +1 -0
  67. package/commands/gsd/workspace.md +1 -0
  68. package/commands/gsd/workstreams.md +1 -0
  69. package/gsd-core/bin/check-latest-version.cjs +8 -3
  70. package/gsd-core/bin/gsd-tools.cjs +532 -174
  71. package/gsd-core/bin/lib/adr-parser.cjs +1 -1
  72. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  73. package/gsd-core/bin/lib/audit.cjs +39 -22
  74. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  75. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  76. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  77. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  78. package/gsd-core/bin/lib/capability-registry.cjs +528 -116
  79. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  80. package/gsd-core/bin/lib/capability-state.cjs +7 -1
  81. package/gsd-core/bin/lib/capability-validator.cjs +134 -5
  82. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  83. package/gsd-core/bin/lib/check-command-router.cjs +198 -38
  84. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  85. package/gsd-core/bin/lib/clusters.cjs +1 -0
  86. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  87. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  88. package/gsd-core/bin/lib/commands.cjs +981 -79
  89. package/gsd-core/bin/lib/config-loader.cjs +4 -0
  90. package/gsd-core/bin/lib/config.cjs +153 -38
  91. package/gsd-core/bin/lib/core-utils.cjs +34 -7
  92. package/gsd-core/bin/lib/coverage.cjs +1 -1
  93. package/gsd-core/bin/lib/decisions.cjs +343 -28
  94. package/gsd-core/bin/lib/edge-probe.cjs +14 -1
  95. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  96. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  97. package/gsd-core/bin/lib/frontmatter.cjs +137 -23
  98. package/gsd-core/bin/lib/gap-checker.cjs +22 -13
  99. package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
  100. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  101. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  102. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
  103. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
  104. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  105. package/gsd-core/bin/lib/host-integration.cjs +57 -5
  106. package/gsd-core/bin/lib/init-command-router.cjs +14 -0
  107. package/gsd-core/bin/lib/init.cjs +539 -60
  108. package/gsd-core/bin/lib/install-engine.cjs +199 -14
  109. package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
  110. package/gsd-core/bin/lib/install-profiles.cjs +36 -14
  111. package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
  112. package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
  113. package/gsd-core/bin/lib/io.cjs +35 -0
  114. package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
  115. package/gsd-core/bin/lib/markdown-table.cjs +123 -0
  116. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  117. package/gsd-core/bin/lib/milestone.cjs +41 -10
  118. package/gsd-core/bin/lib/model-resolver.cjs +101 -10
  119. package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
  120. package/gsd-core/bin/lib/phase-id.cjs +412 -31
  121. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  122. package/gsd-core/bin/lib/phase.cjs +941 -98
  123. package/gsd-core/bin/lib/plan-document.cjs +10 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
  126. package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
  127. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  128. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  129. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  130. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  131. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  132. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  133. package/gsd-core/bin/lib/research-store.cjs +11 -12
  134. package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
  135. package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
  136. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  137. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  138. package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
  139. package/gsd-core/bin/lib/roadmap.cjs +292 -69
  140. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
  141. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
  142. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
  143. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
  144. package/gsd-core/bin/lib/security.cjs +126 -7
  145. package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
  146. package/gsd-core/bin/lib/smart-entry.cjs +7 -9
  147. package/gsd-core/bin/lib/state-document.cjs +159 -32
  148. package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
  149. package/gsd-core/bin/lib/state-transition.cjs +465 -62
  150. package/gsd-core/bin/lib/state.cjs +906 -151
  151. package/gsd-core/bin/lib/surface.cjs +83 -10
  152. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  153. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  154. package/gsd-core/bin/lib/uat.cjs +1420 -516
  155. package/gsd-core/bin/lib/update-context.cjs +36 -26
  156. package/gsd-core/bin/lib/validate.cjs +230 -12
  157. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  158. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  159. package/gsd-core/bin/lib/verification.cjs +316 -23
  160. package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
  161. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  162. package/gsd-core/bin/lib/verify.cjs +531 -36
  163. package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
  164. package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
  165. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  166. package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
  167. package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
  168. package/gsd-core/references/agent-contracts.md +3 -3
  169. package/gsd-core/references/compact-content-gate.md +66 -0
  170. package/gsd-core/references/edge-probe.md +17 -13
  171. package/gsd-core/references/execute-mvp-tdd.md +18 -16
  172. package/gsd-core/references/execute-phase-response-language.md +6 -0
  173. package/gsd-core/references/executor-examples.md +42 -0
  174. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  175. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  176. package/gsd-core/references/model-profiles.md +12 -3
  177. package/gsd-core/references/mvp-concepts.md +2 -2
  178. package/gsd-core/references/plan-checker-examples.md +41 -0
  179. package/gsd-core/references/planner-antipatterns.md +25 -0
  180. package/gsd-core/references/planner-chunked.md +5 -1
  181. package/gsd-core/references/planner-coupling.md +42 -0
  182. package/gsd-core/references/planner-quick-batch.md +71 -0
  183. package/gsd-core/references/planner-reviews.md +47 -0
  184. package/gsd-core/references/planner-revision.md +75 -2
  185. package/gsd-core/references/planning-config.md +5 -1
  186. package/gsd-core/references/response-language-directive.md +9 -0
  187. package/gsd-core/references/revision-loop.md +118 -11
  188. package/gsd-core/references/tdd.md +17 -9
  189. package/gsd-core/references/thinking-models-planning.md +18 -2
  190. package/gsd-core/references/verification-patterns.md +17 -4
  191. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  192. package/gsd-core/references/worktree-path-safety.md +112 -2
  193. package/gsd-core/templates/README.md +7 -1
  194. package/gsd-core/templates/phase-prompt.md +4 -0
  195. package/gsd-core/templates/state.md +6 -3
  196. package/gsd-core/templates/summary.compact.md +212 -0
  197. package/gsd-core/templates/user-setup.compact.md +199 -0
  198. package/gsd-core/templates/user-setup.md +0 -9
  199. package/gsd-core/templates/verification-report.md +5 -0
  200. package/gsd-core/workflows/add-backlog.md +2 -0
  201. package/gsd-core/workflows/add-phase.md +2 -0
  202. package/gsd-core/workflows/add-tests.md +1 -1
  203. package/gsd-core/workflows/add-todo.md +4 -3
  204. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  205. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  206. package/gsd-core/workflows/audit-fix.md +2 -0
  207. package/gsd-core/workflows/audit-milestone.md +2 -0
  208. package/gsd-core/workflows/audit-uat.md +2 -0
  209. package/gsd-core/workflows/autonomous.md +15 -10
  210. package/gsd-core/workflows/check-todos.md +5 -3
  211. package/gsd-core/workflows/cleanup.md +4 -2
  212. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
  213. package/gsd-core/workflows/code-review-fix.md +5 -3
  214. package/gsd-core/workflows/code-review.md +211 -43
  215. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  216. package/gsd-core/workflows/complete-milestone.md +40 -254
  217. package/gsd-core/workflows/debug.md +1 -1
  218. package/gsd-core/workflows/diagnose-issues.md +5 -1
  219. package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
  220. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  221. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  222. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  223. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  224. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
  225. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  226. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  227. package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
  228. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  229. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  230. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  231. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  232. package/gsd-core/workflows/discuss-phase.md +1 -1
  233. package/gsd-core/workflows/do.md +43 -13
  234. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  235. package/gsd-core/workflows/docs-update.md +15 -156
  236. package/gsd-core/workflows/edit-phase.md +2 -0
  237. package/gsd-core/workflows/eval-review.md +1 -1
  238. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  239. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
  240. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  241. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
  242. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  243. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
  245. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  246. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  247. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  248. package/gsd-core/workflows/execute-phase.md +78 -159
  249. package/gsd-core/workflows/execute-plan.md +28 -15
  250. package/gsd-core/workflows/explore.md +2 -0
  251. package/gsd-core/workflows/extract-learnings.md +2 -0
  252. package/gsd-core/workflows/fast.md +6 -0
  253. package/gsd-core/workflows/forensics.md +2 -0
  254. package/gsd-core/workflows/graduation.md +1 -1
  255. package/gsd-core/workflows/health.md +1 -1
  256. package/gsd-core/workflows/help/modes/brief.md +2 -0
  257. package/gsd-core/workflows/help/modes/default.md +2 -0
  258. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  259. package/gsd-core/workflows/help/modes/full.md +12 -0
  260. package/gsd-core/workflows/help/modes/topic.md +2 -0
  261. package/gsd-core/workflows/help.md +3 -1
  262. package/gsd-core/workflows/import.md +3 -3
  263. package/gsd-core/workflows/inbox.md +1 -1
  264. package/gsd-core/workflows/ingest-docs.md +1 -1
  265. package/gsd-core/workflows/insert-phase.md +2 -0
  266. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  267. package/gsd-core/workflows/list-seeds.md +2 -0
  268. package/gsd-core/workflows/list-workspaces.md +2 -0
  269. package/gsd-core/workflows/manager.md +3 -3
  270. package/gsd-core/workflows/map-codebase.md +52 -3
  271. package/gsd-core/workflows/milestone-summary.md +2 -0
  272. package/gsd-core/workflows/mvp-phase.md +1 -1
  273. package/gsd-core/workflows/new-milestone.md +55 -13
  274. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  275. package/gsd-core/workflows/new-project.md +37 -205
  276. package/gsd-core/workflows/new-workspace.md +1 -1
  277. package/gsd-core/workflows/next.md +2 -0
  278. package/gsd-core/workflows/node-repair.md +2 -0
  279. package/gsd-core/workflows/note.md +2 -0
  280. package/gsd-core/workflows/onboard.md +1 -1
  281. package/gsd-core/workflows/pause-work.md +19 -4
  282. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  283. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  284. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
  285. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
  286. package/gsd-core/workflows/plan-phase.md +144 -185
  287. package/gsd-core/workflows/plan-review-convergence.md +102 -10
  288. package/gsd-core/workflows/plant-seed.md +1 -1
  289. package/gsd-core/workflows/pr-branch.md +30 -10
  290. package/gsd-core/workflows/profile-user.md +1 -1
  291. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  292. package/gsd-core/workflows/progress.md +25 -3
  293. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
  294. package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
  295. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  296. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  297. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  298. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  299. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  300. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  301. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  302. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  303. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  304. package/gsd-core/workflows/quick-batch.md +203 -0
  305. package/gsd-core/workflows/quick.md +21 -4
  306. package/gsd-core/workflows/reapply-patches.md +79 -3
  307. package/gsd-core/workflows/remove-phase.md +2 -0
  308. package/gsd-core/workflows/remove-workspace.md +1 -1
  309. package/gsd-core/workflows/resume-project.md +6 -2
  310. package/gsd-core/workflows/review.md +215 -10
  311. package/gsd-core/workflows/scan.md +2 -0
  312. package/gsd-core/workflows/section-manifest.json +12 -0
  313. package/gsd-core/workflows/secure-phase.md +1 -1
  314. package/gsd-core/workflows/session-report.md +2 -0
  315. package/gsd-core/workflows/settings-advanced.md +2 -0
  316. package/gsd-core/workflows/settings-integrations.md +9 -8
  317. package/gsd-core/workflows/settings.md +19 -6
  318. package/gsd-core/workflows/ship.md +10 -10
  319. package/gsd-core/workflows/sketch-wrap-up.md +2 -0
  320. package/gsd-core/workflows/sketch.md +1 -1
  321. package/gsd-core/workflows/smart-entry.md +1 -1
  322. package/gsd-core/workflows/spec-phase.md +24 -19
  323. package/gsd-core/workflows/spike-wrap-up.md +2 -0
  324. package/gsd-core/workflows/spike.md +1 -1
  325. package/gsd-core/workflows/stats.md +2 -0
  326. package/gsd-core/workflows/sync-skills.md +12 -4
  327. package/gsd-core/workflows/thread.md +2 -0
  328. package/gsd-core/workflows/transition.md +2 -0
  329. package/gsd-core/workflows/ui-phase.md +26 -5
  330. package/gsd-core/workflows/ui-review.md +1 -1
  331. package/gsd-core/workflows/ultraplan-phase.md +2 -0
  332. package/gsd-core/workflows/undo.md +1 -1
  333. package/gsd-core/workflows/update.md +48 -43
  334. package/gsd-core/workflows/validate-phase.md +1 -1
  335. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  336. package/gsd-core/workflows/verify-work.md +68 -182
  337. package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
  338. package/hooks/dist/gsd-check-update-worker.js +19 -2
  339. package/hooks/dist/gsd-context-monitor.js +371 -27
  340. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  341. package/hooks/dist/gsd-node-runner.sh +1 -0
  342. package/hooks/dist/gsd-prompt-guard.js +30 -5
  343. package/hooks/dist/gsd-read-guard.js +2 -0
  344. package/hooks/dist/gsd-read-injection-scanner.js +5 -5
  345. package/hooks/dist/gsd-secret-read-guard.js +1105 -0
  346. package/hooks/dist/gsd-statusline.js +18 -10
  347. package/hooks/dist/gsd-validate-commit.sh +474 -7
  348. package/hooks/dist/gsd-workflow-guard.js +2 -1
  349. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  350. package/hooks/dist/gsd-write-guard.js +46 -1
  351. package/hooks/dist/lib/dispatch-identity.js +187 -0
  352. package/hooks/dist/lib/filename-classification.js +64 -0
  353. package/hooks/dist/lib/git-cmd.js +210 -1
  354. package/hooks/dist/lib/injection-patterns.js +36 -6
  355. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  356. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  357. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  358. package/hooks/gsd-agent-isolation-guard.js +42 -16
  359. package/hooks/gsd-check-update-worker.js +19 -2
  360. package/hooks/gsd-context-monitor.js +371 -27
  361. package/hooks/gsd-cursor-subagent-start.js +34 -14
  362. package/hooks/gsd-node-runner.sh +1 -0
  363. package/hooks/gsd-prompt-guard.js +30 -5
  364. package/hooks/gsd-read-guard.js +2 -0
  365. package/hooks/gsd-read-injection-scanner.js +5 -5
  366. package/hooks/gsd-secret-read-guard.js +1105 -0
  367. package/hooks/gsd-statusline.js +18 -10
  368. package/hooks/gsd-validate-commit.sh +474 -7
  369. package/hooks/gsd-workflow-guard.js +2 -1
  370. package/hooks/gsd-worktree-path-guard.js +25 -14
  371. package/hooks/gsd-write-guard.js +46 -1
  372. package/hooks/hooks.json +6 -0
  373. package/hooks/lib/dispatch-identity.js +187 -0
  374. package/hooks/lib/filename-classification.js +64 -0
  375. package/hooks/lib/git-cmd.js +210 -1
  376. package/hooks/lib/injection-patterns.js +36 -6
  377. package/hooks/lib/isolation-deny-reason.js +53 -1
  378. package/hooks/lib/isolation-sentinel.js +58 -19
  379. package/hooks/managed-hooks-registry.cjs +1 -0
  380. package/package.json +13 -9
  381. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  382. package/scripts/benchmark-compact-content.cjs +368 -0
  383. package/scripts/build-hooks.js +11 -4
  384. package/scripts/check-contract-drift.cjs +4 -1
  385. package/scripts/check-env.cjs +36 -8
  386. package/scripts/check-glossary-refs.cjs +25 -21
  387. package/scripts/ci-next-health.cjs +271 -0
  388. package/scripts/ci-prepare-test-scope.cjs +7 -7
  389. package/scripts/ci-test-scope.cjs +133 -20
  390. package/scripts/ci-timeout-report.cjs +1 -1
  391. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  392. package/scripts/docs-guard-registry.cjs +17 -2
  393. package/scripts/gen-adr-index.cjs +8 -2
  394. package/scripts/gen-inventory-manifest.cjs +12 -0
  395. package/scripts/gen-loop-host-contract.cjs +67 -15
  396. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  397. package/scripts/lib/drift-scan.cjs +1 -1
  398. package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
  399. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  400. package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
  401. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  402. package/scripts/lib/suite-detection.cjs +32 -0
  403. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  404. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  405. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  406. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  407. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
  408. package/scripts/lint-phase-enumeration-drift.cjs +24 -6
  409. package/scripts/lint-phase-id-drift.cjs +465 -15
  410. package/scripts/lint-portable-grep.cjs +176 -0
  411. package/scripts/lint-response-language-coverage.cjs +530 -0
  412. package/scripts/lint-source-test-name-collision.cjs +1 -1
  413. package/scripts/lint-test-file-count.allowlist.json +4 -1
  414. package/scripts/lint-vendored-deps.cjs +128 -17
  415. package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
  416. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  417. package/scripts/npm-audit-baseline.cjs +376 -0
  418. package/scripts/prompt-injection-scan.sh +22 -0
  419. package/scripts/require-issue-link-policy.cjs +16 -1
  420. package/scripts/workflow-size.cjs +139 -0
  421. package/skills/gsd-cleanup/SKILL.md +1 -0
  422. package/skills/gsd-code-review/SKILL.md +2 -1
  423. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  424. package/skills/gsd-config/SKILL.md +1 -0
  425. package/skills/gsd-debug/SKILL.md +1 -0
  426. package/skills/gsd-execute-phase/SKILL.md +1 -1
  427. package/skills/gsd-graphify/SKILL.md +1 -0
  428. package/skills/gsd-health/SKILL.md +1 -0
  429. package/skills/gsd-mempalace-capture/SKILL.md +1 -0
  430. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  431. package/skills/gsd-new-milestone/SKILL.md +1 -0
  432. package/skills/gsd-new-project/SKILL.md +1 -0
  433. package/skills/gsd-next/SKILL.md +1 -0
  434. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  435. package/skills/gsd-pause-work/SKILL.md +1 -0
  436. package/skills/gsd-phase/SKILL.md +2 -1
  437. package/skills/gsd-pr-branch/SKILL.md +1 -0
  438. package/skills/gsd-quick-batch/SKILL.md +105 -0
  439. package/skills/gsd-resume-work/SKILL.md +1 -0
  440. package/skills/gsd-review-backlog/SKILL.md +1 -0
  441. package/skills/gsd-settings/SKILL.md +2 -1
  442. package/skills/gsd-stats/SKILL.md +1 -0
  443. package/skills/gsd-surface/SKILL.md +18 -8
  444. package/skills/gsd-thread/SKILL.md +1 -0
  445. package/skills/gsd-workspace/SKILL.md +1 -0
  446. package/skills/gsd-workstreams/SKILL.md +1 -0
  447. package/vscode/package.json +1 -1
  448. package/gsd-core/templates/claude-md.md +0 -145
  449. package/gsd-core/templates/codebase/concerns.md +0 -310
  450. package/gsd-core/templates/codebase/conventions.md +0 -307
  451. package/gsd-core/templates/codebase/integrations.md +0 -280
  452. package/gsd-core/templates/codebase/structure.md +0 -285
  453. package/gsd-core/templates/codebase/testing.md +0 -480
  454. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  455. package/gsd-core/templates/discovery.md +0 -146
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', 'npm-version-check-diagnosis.cjs', 'platform-conformance-tier.generated.cjs', 'suite-detection.cjs', 'macos-conformance-tier.generated.cjs'];
415
440
 
416
441
  /**
417
442
  * Resolve a runtime's shared-hooks directory name from its descriptor.
@@ -519,6 +544,13 @@ const {
519
544
  RUNTIME_PROFILE_MAP: GSD_RUNTIME_PROFILE_MAP,
520
545
  isAnthropicFlavoredModel: gsdIsAnthropicFlavoredModel,
521
546
  } = require(path.join(_gsdLibDir, 'model-catalog.cjs'));
547
+ // #4145: shared hash-first recovery for gsd-pristine/ baselines stored at an
548
+ // unexpected path (e.g. without the gsd-core/ prefix an earlier release's
549
+ // writer dropped). Same module the reapply verifier uses, so the two readers
550
+ // cannot drift apart again.
551
+ const {
552
+ findPristineByHash: gsdFindPristineByHash,
553
+ } = require(path.join(_gsdLibDir, 'pristine-baseline.cjs'));
522
554
  // #2875 Part 2: MODEL_PROFILES + resolveTierEntry are now consumed only by
523
555
  // install-model-override-resolver.cjs's readGsdRuntimeProfileResolver
524
556
  // (required below) — this installer no longer needs its own bindings.
@@ -790,6 +822,10 @@ const {
790
822
  const {
791
823
  resolveRuntimeArtifactLayout,
792
824
  } = require(path.join(_gsdLibDir, 'runtime-artifact-layout.cjs'));
825
+ const {
826
+ readSurface,
827
+ resolveSurface,
828
+ } = require(path.join(_gsdLibDir, 'surface.cjs'));
793
829
  const {
794
830
  assertDestWithinConfigHome,
795
831
  createRuntimeArtifactInstallPlan,
@@ -1256,6 +1292,7 @@ const removeKimiHooksToml = hooksSurface.removeKimiHooksToml;
1256
1292
  // callers continue to work and there is a single implementation. (All call
1257
1293
  // sites are below this line, so the const binding has no TDZ hazard.)
1258
1294
  const processAttribution = runtimeArtifactConversion.processAttribution;
1295
+ const filterRuntimeNotesForTarget = runtimeArtifactConversion.filterRuntimeNotesForTarget;
1259
1296
  // computePathPrefix: implementation lives in runtimeArtifactConversion
1260
1297
  // (ADR-1508 / #1511 Phase 2 — single owner). Re-bound here so install.js call
1261
1298
  // sites continue to work. #2876 retired the sibling
@@ -1333,34 +1370,13 @@ function ensureCodexHooksJsonSessionStart(targetDir, opts = {}) {
1333
1370
  }
1334
1371
 
1335
1372
  /**
1336
- * Ensure hooks.json contains exactly one managed GSD hook entry for the given
1337
- * Codex event, wired to gsd-context-monitor.js. Preserves user-owned entries.
1338
- *
1339
- * Used for the new Codex events added in #772:
1340
- * SubagentStart — inject context / GSD_AGENT_NAME awareness at subagent open
1341
- * Stop — post-session context headroom tracking
1342
- * PostToolUse — mirror the Claude Code PostToolUse context monitor
1343
- *
1344
- * All three events are routed through gsd-context-monitor.js — the same hook
1345
- * used for PostToolUse in the Claude Code baseline — so context-headroom
1346
- * warnings surface at these key Codex session lifecycle moments.
1347
- *
1348
- * On Windows (#3426): writes a gsd-context-monitor.cmd shim alongside the .js
1349
- * file and uses the .cmd path as the hook command — exactly the same fix as
1350
- * SessionStart uses for gsd-check-update — to avoid the bash.exe POSIX-exec
1351
- * failure when Codex's hook dispatcher tries to run node.exe through Git Bash.
1352
- *
1353
- * @param {string} targetDir
1354
- * @param {string} eventName - One of 'SubagentStart', 'Stop', 'PostToolUse'.
1355
- * @param {{ absoluteRunner: string|null, platform?: NodeJS.Platform }} opts
1356
- * @returns {{ changed: boolean, wrote: boolean, path: string }}
1357
- */
1358
- function ensureCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
1359
- return hooksSurface.ensureCodexHooksJsonEvent(targetDir, eventName, opts);
1360
- }
1361
-
1362
- /**
1363
- * Remove a GSD-managed event entry from hooks.json. Called during uninstall.
1373
+ * Remove a GSD-managed event entry from hooks.json. Called during uninstall,
1374
+ * and (#2586) unconditionally during install/reinstall to clean up a
1375
+ * pre-#2586 install's stale gsd-context-monitor.js registrations — GSD no
1376
+ * longer ADDS entries for these events (see CODEX_HOOKS_TO_COPY /
1377
+ * cleanupOrphanedCodexContextMonitorScript in bin/install.js's Codex branch),
1378
+ * only removes recognized ones, so the `ensureCodexHooksJsonEvent` wrapper
1379
+ * that used to add them was removed as dead code.
1364
1380
  *
1365
1381
  * @param {string} targetDir
1366
1382
  * @param {string} eventName
@@ -1796,8 +1812,20 @@ const kiloAgentPermissionOrder = [
1796
1812
  'lsp',
1797
1813
  ];
1798
1814
 
1815
+ const kiloMcpPermissionPattern = /^mcp__([A-Za-z0-9_-]+)__((?:[A-Za-z0-9_-]+)|\*)$/;
1816
+
1817
+ // Derives Kilo's native `{server}_{tool}` MCP permission key (kilo.ai/docs —
1818
+ // external, fixed format). Not injective: both capture groups allow `_`, so
1819
+ // e.g. `mcp__a_b__c` and `mcp__a__b_c` derive the same key. The `Set` below
1820
+ // resolves any such collision deterministically to first-seen-wins — see
1821
+ // src/runtime-artifact-conversion.cts's regression test. Mirrors that file's
1822
+ // convertClaudeToKiloPermissionTool exactly (#4032).
1799
1823
  function convertClaudeToKiloPermissionTool(claudeTool) {
1800
- return claudeToKiloAgentPermissions[claudeTool] || null;
1824
+ const builtinPermission = claudeToKiloAgentPermissions[claudeTool];
1825
+ if (builtinPermission) return builtinPermission;
1826
+
1827
+ const mcpPermission = kiloMcpPermissionPattern.exec(claudeTool);
1828
+ return mcpPermission ? `${mcpPermission[1]}_${mcpPermission[2]}` : null;
1801
1829
  }
1802
1830
 
1803
1831
  function buildKiloAgentPermissionBlock(claudeTools) {
@@ -1814,6 +1842,10 @@ function buildKiloAgentPermissionBlock(claudeTools) {
1814
1842
  for (const permission of kiloAgentPermissionOrder) {
1815
1843
  lines.push(` ${permission}: ${allowedPermissions.has(permission) ? 'allow' : 'deny'}`);
1816
1844
  }
1845
+ for (const permission of allowedPermissions) {
1846
+ if (kiloAgentPermissionOrder.includes(permission)) continue;
1847
+ lines.push(` ${permission}: allow`);
1848
+ }
1817
1849
 
1818
1850
  return lines;
1819
1851
  }
@@ -2384,7 +2416,7 @@ function convertClaudeAgentToCopilotAgent(content, isGlobal = false) {
2384
2416
  * @param {boolean} [isGlobal=false] - Whether this is a global install
2385
2417
  */
2386
2418
  function convertClaudeToAntigravityContent(content, isGlobal = false) {
2387
- let c = content;
2419
+ let c = filterRuntimeNotesForTarget(content, 'antigravity');
2388
2420
  if (isGlobal) {
2389
2421
  // #3738: global skills install under ~/.gemini/config/skills (the dir AGY
2390
2422
  // scans for global discovery), so skills-path references must divert there
@@ -2520,7 +2552,7 @@ function convertSlashCommandsToCursorSkillMentions(content) {
2520
2552
  }
2521
2553
 
2522
2554
  function convertClaudeToCursorMarkdown(content) {
2523
- let converted = convertSlashCommandsToCursorSkillMentions(content);
2555
+ let converted = convertSlashCommandsToCursorSkillMentions(filterRuntimeNotesForTarget(content, 'cursor'));
2524
2556
  // Replace tool name references in body text
2525
2557
  converted = converted.replace(/\bBash\(/g, 'Shell(');
2526
2558
  converted = converted.replace(/\bEdit\(/g, 'StrReplace(');
@@ -2641,7 +2673,7 @@ function convertSlashCommandsToTraeSkillMentions(content) {
2641
2673
  }
2642
2674
 
2643
2675
  function convertClaudeToTraeMarkdown(content) {
2644
- let converted = convertSlashCommandsToTraeSkillMentions(content);
2676
+ let converted = convertSlashCommandsToTraeSkillMentions(filterRuntimeNotesForTarget(content, 'trae'));
2645
2677
  converted = converted.replace(/\bBash\(/g, 'Shell(');
2646
2678
  converted = converted.replace(/\bEdit\(/g, 'StrReplace(');
2647
2679
  // Replace general-purpose subagent type with Trae's equivalent "general_purpose_task"
@@ -2767,7 +2799,7 @@ function convertSlashCommandsToCodebuddySkillMentions(content) {
2767
2799
  }
2768
2800
 
2769
2801
  function convertClaudeToCodebuddyMarkdown(content) {
2770
- let converted = convertSlashCommandsToCodebuddySkillMentions(content);
2802
+ let converted = convertSlashCommandsToCodebuddySkillMentions(filterRuntimeNotesForTarget(content, 'codebuddy'));
2771
2803
  // CodeBuddy uses the same tool names as Claude Code (Bash, Edit, Read, Write, etc.)
2772
2804
  // No tool name conversion needed
2773
2805
  converted = converted.replace(/\$ARGUMENTS\b/g, '{{GSD_ARGS}}');
@@ -2859,7 +2891,7 @@ function convertClaudeAgentToCodebuddyAgent(content) {
2859
2891
  // ── Cline converters ────────────────────────────────────────────────────────
2860
2892
 
2861
2893
  function convertClaudeToCliineMarkdown(content) {
2862
- let converted = content;
2894
+ let converted = filterRuntimeNotesForTarget(content, 'cline');
2863
2895
  // Cline uses the same tool names as Claude Code — no tool name conversion needed
2864
2896
  converted = converted.replace(/`\.\/CLAUDE\.md`/g, '`.clinerules`');
2865
2897
  converted = converted.replace(/\.\/CLAUDE\.md/g, '.clinerules');
@@ -3782,7 +3814,7 @@ function rewriteBareGsdToolsCommandsForCodex(content) {
3782
3814
  }
3783
3815
 
3784
3816
  function convertClaudeToCodexMarkdown(content) {
3785
- let converted = convertSlashCommandsToCodexSkillMentions(content);
3817
+ let converted = convertSlashCommandsToCodexSkillMentions(filterRuntimeNotesForTarget(content, 'codex'));
3786
3818
  converted = converted.replace(/\$ARGUMENTS\b/g, '{{GSD_ARGS}}');
3787
3819
  // Remove /clear references — Codex has no equivalent command
3788
3820
  // Handle backtick-wrapped: `\/clear` then: → (removed)
@@ -3849,26 +3881,37 @@ Execute mode fallback:
3849
3881
  ## C. Task() → spawn_agent Mapping
3850
3882
  GSD workflows use \`Task(...)\` (Claude Code syntax). Translate to Codex collaboration tools:
3851
3883
 
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.
3884
+ **Schema detection (required first step):** Before spawning, inspect the \`spawn_agent\`
3885
+ tool's visible parameter schema (via \`tool_search\` or the tool list). Use the presence
3886
+ of \`agent_type\` only to choose typed dispatch versus the generic-agent workaround.
3887
+ Detect optional fields independently: \`model\`, \`reasoning_effort\`, \`task_name\`,
3888
+ \`fork_turns\`, and \`fork_context\` may be added or removed without \`agent_type\` changing.
3889
+ Never infer one field from a schema/version label or from the presence of another field.
3855
3890
 
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.
3891
+ - **agent_type-capable schema:** \`spawn_agent\` advertises \`agent_type\` — typed GSD agent dispatch is available.
3892
+ - **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
3893
 
3858
3894
  Typed mapping (agent_type-capable schema only):
3859
3895
  - \`Task(subagent_type="X", prompt="Y")\` → \`spawn_agent(agent_type="X", message="Y")\`
3860
3896
  - \`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.
3897
+ - \`Task(model="{resolved_model}")\` → pass \`model="{resolved_model}"\` when the
3898
+ visible \`spawn_agent\` schema advertises \`model\` and the resolved value is explicit.
3899
+ This is how \`model_profile\` tier routing, including \`adaptive\`, reaches the child agent.
3900
+ Omit \`model\` only when the schema does not advertise \`model\`, or when the value is
3901
+ missing, empty, or \`"inherit"\`; omission deliberately inherits the session/static agent
3902
+ configuration. Explicit \`model_overrides\` may also be embedded in agent \`.toml\` files,
3903
+ but ordinary profile-resolved models are not, so a TOML file is not a reason to discard
3904
+ an available inline value.
3905
+ - Before each typed spawn, obtain the paired effort for its role with
3906
+ \`gsd_run query resolve-model <subagent_type> --pick effort\` when the workflow has not
3907
+ already exposed it. The resolver's unified \`effort\` field maps to the Codex spawn argument
3908
+ \`reasoning_effort\`; do not look for a resolver field named \`reasoning_effort\`.
3909
+ Pass it when the visible \`spawn_agent\` schema advertises \`reasoning_effort\`. Omit the
3910
+ field when it is not advertised, or when the value is missing, empty, \`"inherit"\`, or
3911
+ unsupported; do not invent one-off effort literals in workflow prose.
3869
3912
  - \`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)
3913
+ - \`task_name\` — when advertised, provide a descriptive name for each spawned task
3914
+ - \`fork_turns\` — when advertised, controls turn-forking depth; coexists with \`fork_context\` (not a replacement)
3872
3915
  - \`Task(isolation="worktree")\` / \`Agent(isolation="worktree")\` → no direct \`spawn_agent\` mapping,
3873
3916
  but Codex declares \`dispatch.isolation: orchestrator-worktree\` (#2584). Codex
3874
3917
  \`spawn_agent\` still does not create or bind a git worktree; instead GSD itself
@@ -7152,7 +7195,7 @@ function neutralizeAgentReferences(content, instructionFile) {
7152
7195
 
7153
7196
  function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOverride = null } = {}) {
7154
7197
  // Replace tool name references in content (applies to all files)
7155
- let convertedContent = content;
7198
+ let convertedContent = filterRuntimeNotesForTarget(content, 'opencode');
7156
7199
  convertedContent = convertedContent.replace(/\bAskUserQuestion\b/g, 'question');
7157
7200
  convertedContent = convertedContent.replace(/\bSlashCommand\b/g, 'skill');
7158
7201
  convertedContent = convertedContent.replace(/\bTodoWrite\b/g, 'todowrite');
@@ -7314,7 +7357,7 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
7314
7357
  // tests/runtime-converters.test.cjs (#2093).
7315
7358
  function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverride = null } = {}) {
7316
7359
  // Replace tool name references in content (applies to all files)
7317
- let convertedContent = content;
7360
+ let convertedContent = filterRuntimeNotesForTarget(content, 'kilo');
7318
7361
  convertedContent = convertedContent.replace(/\bAskUserQuestion\b/g, 'question');
7319
7362
  convertedContent = convertedContent.replace(/\bSlashCommand\b/g, 'skill');
7320
7363
  convertedContent = convertedContent.replace(/\bTodoWrite\b/g, 'todowrite');
@@ -7372,7 +7415,8 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
7372
7415
 
7373
7416
  if (isAgent && inAgentTools) {
7374
7417
  if (trimmed.startsWith('- ')) {
7375
- agentTools.push(trimmed.substring(2).trim());
7418
+ const tool = runtimeArtifactConversion._decodeToolScalar(trimmed.substring(2));
7419
+ if (tool !== null) agentTools.push(tool);
7376
7420
  continue;
7377
7421
  }
7378
7422
  if (trimmed && !trimmed.startsWith('-')) {
@@ -7384,8 +7428,12 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
7384
7428
  if (trimmed.startsWith('tools:')) {
7385
7429
  if (isAgent) {
7386
7430
  const toolsValue = trimmed.substring(6).trim();
7387
- if (toolsValue) {
7388
- const tools = toolsValue.split(',').map(t => t.trim()).filter(t => t);
7431
+ // A comment-only value (`tools: # note`) is not real inline content —
7432
+ // fall through to the block-list scan instead of decoding the
7433
+ // comment as a bogus tool name and dropping the list (mirrors
7434
+ // src/runtime-artifact-conversion.cts's convertClaudeToKiloFrontmatter, #4032).
7435
+ if (toolsValue && !toolsValue.startsWith('#')) {
7436
+ const tools = runtimeArtifactConversion._splitToolScalars(toolsValue).map(runtimeArtifactConversion._decodeToolScalar).filter(tool => tool !== null);
7389
7437
  agentTools.push(...tools);
7390
7438
  } else {
7391
7439
  inAgentTools = true;
@@ -7451,7 +7499,8 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
7451
7499
  if (trimmed.startsWith('- ')) {
7452
7500
  const tool = trimmed.substring(2).trim();
7453
7501
  if (isAgent) {
7454
- agentTools.push(tool);
7502
+ const decoded = runtimeArtifactConversion._decodeToolScalar(tool);
7503
+ if (decoded !== null) agentTools.push(decoded);
7455
7504
  } else {
7456
7505
  allowedTools.push(tool);
7457
7506
  }
@@ -7862,6 +7911,8 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
7862
7911
  content = composeWorkflow(content, { sourcePath: srcPath });
7863
7912
  }
7864
7913
 
7914
+ content = filterRuntimeNotesForTarget(content, runtime);
7915
+
7865
7916
  if (!dispatch.mdSkipGenericRewrite) {
7866
7917
  const globalClaudeRegex = /~\/\.claude\//g;
7867
7918
  const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
@@ -8416,6 +8467,16 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
8416
8467
  console.log(` ${green}✓${reset} Removed managed Codex ${eventName} hook from hooks.json`);
8417
8468
  }
8418
8469
  }
8470
+ // #2586: uninstall's own symmetric half of the orphaned-script cleanup —
8471
+ // same GSD-owned + unreferenced gate as the install-time call.
8472
+ const uninstallMonitorCleanup = hooksSurface.cleanupOrphanedCodexContextMonitorScript(targetDir);
8473
+ for (const deletedPath of uninstallMonitorCleanup.deleted) {
8474
+ removedCount++;
8475
+ console.log(` ${green}✓${reset} Removed orphaned Codex hook script (${path.basename(deletedPath)})`);
8476
+ }
8477
+ for (const warning of uninstallMonitorCleanup.warnings) {
8478
+ console.warn(` ${yellow}⚠${reset} Could not remove orphaned Codex hook script ${warning.path}: ${warning.reason}`);
8479
+ }
8419
8480
  }
8420
8481
 
8421
8482
  // 1a-kimi. Non-layout Kimi side-effect (#2095 EoS/kimi Upgrade 1): kimi's
@@ -9039,17 +9100,29 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
9039
9100
  );
9040
9101
  if (settings.permissions.allow.length !== before) {
9041
9102
  permissionsModified = true;
9103
+ // #4221: an array this filter emptied was GSD-only \u2014 remove the
9104
+ // key rather than leave an empty array behind (Antigravity symmetry).
9105
+ if (settings.permissions.allow.length === 0) {
9106
+ delete settings.permissions.allow;
9107
+ }
9042
9108
  }
9043
9109
  }
9044
9110
  if (Array.isArray(settings.permissions.deny)) {
9045
9111
  const before = settings.permissions.deny.length;
9112
+ // #4221: the deny rules are retired, so this is a legacy-only filter.
9046
9113
  settings.permissions.deny = settings.permissions.deny.filter(
9047
- (e) => !GSD_CLAUDE_DENY_PERMISSIONS.includes(e)
9114
+ (e) => !GSD_CLAUDE_LEGACY_DENY_PERMISSIONS.includes(e)
9048
9115
  );
9049
9116
  if (settings.permissions.deny.length !== before) {
9050
9117
  permissionsModified = true;
9118
+ if (settings.permissions.deny.length === 0) {
9119
+ delete settings.permissions.deny;
9120
+ }
9051
9121
  }
9052
9122
  }
9123
+ if (permissionsModified && Object.keys(settings.permissions).length === 0) {
9124
+ delete settings.permissions;
9125
+ }
9053
9126
  if (permissionsModified) {
9054
9127
  settingsModified = true;
9055
9128
  console.log(` ${green}✓${reset} Removed GSD permissions from settings.json`);
@@ -10079,6 +10152,86 @@ function populatePristineDir({ packageSrc, pristineDir, modified, runtime, pathP
10079
10152
  return written;
10080
10153
  }
10081
10154
 
10155
+ /**
10156
+ * #4145: recover a pristine baseline from a hash-matching orphan stored at an
10157
+ * unexpected path under gsd-pristine/ (e.g. without the gsd-core/ prefix an
10158
+ * earlier release's writer dropped).
10159
+ *
10160
+ * The preserve-check's strict join (pristineDir + manifest-keyed relPath)
10161
+ * misses such snapshots, so they were pushed into regeneration from the
10162
+ * incoming release — and when the file changed upstream, the candidate's hash
10163
+ * could never satisfy the recorded outgoing hash, leaving the correct
10164
+ * baseline permanently unconsumed and unpruned (the self-perpetuating state
10165
+ * #4145 reports). Hash equality with pristine_hashes is the same authority
10166
+ * the #3657 drift guard trusts, so an exact match cannot be the wrong
10167
+ * baseline no matter where under gsd-pristine/ it lives.
10168
+ *
10169
+ * Recovery = relocation: copy the orphan to the canonical manifest-keyed path
10170
+ * (hash-verified after the copy) and remove the orphan only once the
10171
+ * canonical copy is verified in place. Returns true when the canonical path
10172
+ * ended up holding recorded-hash bytes. Never deletes anything it cannot
10173
+ * vouch for by hash, and never consumes a path that is the canonical path of
10174
+ * ANY manifest file (see canonicalSkip below) — only genuine orphans, which
10175
+ * no strict-join reader ever consults, are eligible for removal.
10176
+ */
10177
+ function recoverOrphanedPristine(pristineDir, relPath, recordedHash, canonicalSkip) {
10178
+ if (!recordedHash) return false;
10179
+ let orphanRel;
10180
+ try {
10181
+ // canonicalSkip = the normalized manifest keys: a file already sitting at
10182
+ // any canonical path can never be (re-)adopted through the scan. Without
10183
+ // this, two modified files sharing byte-identical outgoing content would
10184
+ // repeatedly "rescue" each other's canonical away (relocate + delete at
10185
+ // its home path) in alternating updates — bytes identical, state unstable.
10186
+ // It also keeps drift (#3657) / stale (#3407) territory with the caller.
10187
+ orphanRel = gsdFindPristineByHash(pristineDir, recordedHash, canonicalSkip);
10188
+ } catch {
10189
+ return false;
10190
+ }
10191
+ if (!orphanRel) return false;
10192
+ const outRef = resolveInstallRelativePath(pristineDir, relPath);
10193
+ if (!outRef) return false;
10194
+ try {
10195
+ fs.mkdirSync(path.dirname(outRef.fullPath), { recursive: true });
10196
+ fs.copyFileSync(path.join(pristineDir, orphanRel), outRef.fullPath);
10197
+ // Verify the relocated copy before removing the orphan — only a
10198
+ // hash-matching canonical counts as recovered.
10199
+ if (fileHash(outRef.fullPath) !== recordedHash) {
10200
+ try { fs.rmSync(outRef.fullPath, { force: true }); } catch { /* best-effort */ }
10201
+ return false;
10202
+ }
10203
+ // Orphan removal is best-effort: the canonical copy is already verified,
10204
+ // so a failed unlink leaves a harmless duplicate, never data loss.
10205
+ try { fs.rmSync(path.join(pristineDir, orphanRel), { force: true }); } catch { /* best-effort */ }
10206
+ return true;
10207
+ } catch {
10208
+ return false;
10209
+ }
10210
+ }
10211
+
10212
+ /**
10213
+ * #4135: honest N-of-M accounting for gsd-pristine/ baselines after an
10214
+ * update. The #3407 promotion rule keeps only hash-validated candidates
10215
+ * (byte-identical across the version span), so a multi-version update
10216
+ * legitimately ends with near-zero baselines — the collapse itself is NOT a
10217
+ * bug to hide; hiding it is. This renders the covered-of-total line the
10218
+ * update output prints either way, so "1 of 13" can never present like a
10219
+ * fully-covered run. Pure function (typed return) so tests lock the exact
10220
+ * contract without matching console prose.
10221
+ */
10222
+ function describeBaselineCoverage(totalModified, covered) {
10223
+ const total = Math.max(0, totalModified);
10224
+ const have = Math.min(Math.max(0, covered), total);
10225
+ const uncovered = total - have;
10226
+ return {
10227
+ complete: uncovered === 0,
10228
+ uncovered,
10229
+ text: uncovered === 0
10230
+ ? `gsd-pristine/ baselines cover ${have} of ${total} modified file(s)`
10231
+ : `gsd-pristine/ baselines cover ${have} of ${total} modified file(s) — ${uncovered} will be reported no_baseline by the reapply verifier`,
10232
+ };
10233
+ }
10234
+
10082
10235
  /**
10083
10236
  * Detect user-modified GSD files by comparing against install manifest.
10084
10237
  * Backs up modified files to gsd-local-patches/ for reapply after update.
@@ -10111,19 +10264,57 @@ function saveLocalPatches(configDir, pristineCtx) {
10111
10264
  const modified = [];
10112
10265
  const pristineHashes = {};
10113
10266
 
10267
+ // #4086: skills/ manifest keys may live OUTSIDE configDir at the runtime's
10268
+ // ACTUAL skills root — codex global installs to $HOME/.agents/skills (the
10269
+ // ADR-1239 skills-kind `home` override), which writeManifest() already
10270
+ // hashes from via _resolveSkillsRootDir (#2088/#3738). Resolving every key
10271
+ // against configDir alone made every skills/ key miss here, so user
10272
+ // modifications to Codex skills were never hash-compared, never backed up,
10273
+ // and were silently overwritten by the next update. configDir stays FIRST
10274
+ // (Postel: runtimes whose skills genuinely live under configDir resolve
10275
+ // byte-identically to before); the skills root is a fallback, only for keys
10276
+ // under the SAME descriptor-driven manifest prefix writeManifest uses, and
10277
+ // only when that root resolves outside configDir. Containment + symlink
10278
+ // guards apply to the alternate root too (resolveInstallRelativePath).
10279
+ const patchRuntime = (pristineCtx && pristineCtx.runtime) || manifest.runtime || null;
10280
+ const patchScope = manifest.scope === 'local' ? 'local' : 'global';
10281
+ let skillsRedirect = null;
10282
+ if (patchRuntime) {
10283
+ const skillsRoot = _resolveSkillsRootDir(patchRuntime, configDir, patchScope);
10284
+ const resolvedConfig = path.resolve(configDir);
10285
+ if (
10286
+ skillsRoot &&
10287
+ skillsRoot !== resolvedConfig &&
10288
+ !skillsRoot.startsWith(resolvedConfig + path.sep)
10289
+ ) {
10290
+ const prefix = _hostBehaviors(patchRuntime).skillsManifestPrefix || 'skills/';
10291
+ skillsRedirect = { root: skillsRoot, prefix };
10292
+ }
10293
+ }
10294
+
10114
10295
  for (const [relPath, originalHash] of Object.entries(manifest.files || {})) {
10115
10296
  const safeRef = resolveInstallRelativePath(configDir, relPath);
10116
10297
  if (!safeRef) continue;
10117
10298
  const { relPath: safeRelPath, fullPath } = safeRef;
10118
- if (!fs.existsSync(fullPath)) continue;
10119
- const currentHash = fileHash(fullPath);
10299
+ let installedPath = fullPath;
10300
+ if (!fs.existsSync(installedPath) && skillsRedirect && safeRelPath.startsWith(skillsRedirect.prefix)) {
10301
+ const altRef = resolveInstallRelativePath(
10302
+ skillsRedirect.root,
10303
+ safeRelPath.slice(skillsRedirect.prefix.length)
10304
+ );
10305
+ if (altRef && fs.existsSync(altRef.fullPath)) {
10306
+ installedPath = altRef.fullPath;
10307
+ }
10308
+ }
10309
+ if (!fs.existsSync(installedPath)) continue;
10310
+ const currentHash = fileHash(installedPath);
10120
10311
  if (currentHash !== originalHash) {
10121
10312
  // Back up the user's modified version
10122
10313
  const backupRef = resolveInstallRelativePath(patchesDir, safeRelPath);
10123
10314
  if (!backupRef) continue;
10124
10315
  const backupPath = backupRef.fullPath;
10125
10316
  fs.mkdirSync(path.dirname(backupPath), { recursive: true });
10126
- fs.copyFileSync(fullPath, backupPath);
10317
+ fs.copyFileSync(installedPath, backupPath);
10127
10318
  modified.push(safeRelPath);
10128
10319
  pristineHashes[safeRelPath] = originalHash;
10129
10320
  }
@@ -10187,6 +10378,15 @@ function saveLocalPatches(configDir, pristineCtx) {
10187
10378
  const stalePaths = new Set();
10188
10379
  // Track which relPaths were successfully regenerated (from either missing or stale).
10189
10380
  const regeneratedPaths = new Set();
10381
+ // #4145: track which relPaths were recovered by relocating a hash-matching
10382
+ // orphan (stored at an unexpected path, e.g. without the gsd-core/ prefix).
10383
+ const rescuedPaths = new Set();
10384
+ // #4145: the set of paths that are SOME file's canonical pristine path
10385
+ // (every normalized manifest key). The orphan scan must never consume
10386
+ // these — see recoverOrphanedPristine.
10387
+ const canonicalSkip = new Set(
10388
+ Object.keys(manifest.files || {}).map((k) => normalizeInstallRelativePath(k)).filter(Boolean),
10389
+ );
10190
10390
  const missingPaths = [];
10191
10391
  for (const relPath of modified) {
10192
10392
  const outRef = resolveInstallRelativePath(pristineDir, relPath);
@@ -10208,6 +10408,17 @@ function saveLocalPatches(configDir, pristineCtx) {
10208
10408
  stalePaths.add(relPath);
10209
10409
  }
10210
10410
  }
10411
+ // #4145: canonical absent (or just removed as stale) — before falling
10412
+ // into regeneration, try to recover the baseline from a hash-matching
10413
+ // orphan elsewhere under gsd-pristine/ and relocate it to the canonical
10414
+ // path. This is the self-heal for snapshots an earlier release stored
10415
+ // without the gsd-core/ prefix: without it the state repeats forever
10416
+ // (regeneration candidates from the incoming release can never satisfy
10417
+ // the recorded outgoing hash when upstream changed the file).
10418
+ if (recoverOrphanedPristine(pristineDir, relPath, pristineHashes[relPath], canonicalSkip)) {
10419
+ rescuedPaths.add(relPath);
10420
+ continue;
10421
+ }
10211
10422
  // File absent from gsd-pristine/ (or just removed above as stale):
10212
10423
  // attempt hash-validated regeneration from new-release source.
10213
10424
  missingPaths.push(relPath);
@@ -10250,19 +10461,38 @@ function saveLocalPatches(configDir, pristineCtx) {
10250
10461
  }
10251
10462
  // `regenerated` = total files successfully regenerated (from missing OR stale).
10252
10463
  const regenerated = regeneratedPaths.size;
10464
+ // `rescued` = files recovered by relocating a hash-matching orphan to its
10465
+ // canonical path (#4145) — distinct from preservation (canonical already
10466
+ // correct) and regeneration (bytes re-derived from new-release source).
10467
+ const rescued = rescuedPaths.size;
10253
10468
  // `removed` = stale entries that were deleted and NOT subsequently regenerated.
10254
10469
  // Entries that were stale-deleted but then successfully regenerated are counted
10255
- // only in `regenerated` — the counts are non-overlapping.
10256
- const removed = [...stalePaths].filter(p => !regeneratedPaths.has(p)).length;
10470
+ // only in `regenerated`; stale-deleted-then-orphan-rescued entries are counted
10471
+ // only in `rescued` — the counts are non-overlapping.
10472
+ const removed = [...stalePaths].filter(p => !regeneratedPaths.has(p) && !rescuedPaths.has(p)).length;
10257
10473
  if (preserved > 0) {
10258
10474
  console.log(' ' + green + '✓' + reset + ' Preserved ' + cyan + 'gsd-pristine/' + reset + ' (' + preserved + ' file(s)) for three-way merge');
10259
10475
  }
10476
+ if (rescued > 0) {
10477
+ console.log(' ' + green + '✓' + reset + ' Recovered ' + cyan + 'gsd-pristine/' + reset + ' (' + rescued + ' file(s)) by recorded hash from a legacy-path snapshot and relocated them (#4145)');
10478
+ }
10260
10479
  if (regenerated > 0) {
10261
10480
  console.log(' ' + green + '✓' + reset + ' Regenerated ' + cyan + 'gsd-pristine/' + reset + ' (' + regenerated + ' file(s)) via hash-validated new-release source');
10262
10481
  }
10263
10482
  if (removed > 0) {
10264
10483
  console.log(' ' + yellow + 'i' + reset + ' Removed ' + removed + ' stale gsd-pristine/ snapshot(s); regenerated ' + regenerated + ' of those — falls back to over-broad verify heuristic for the rest');
10265
10484
  }
10485
+ // #4135: the honest N-of-M coverage line. Preserved/rescued/regenerated
10486
+ // are disjoint buckets (see their accounting comments above), so their
10487
+ // sum is exactly the files that ended this update with a hash-valid
10488
+ // baseline. A partial result renders as an info line, not an error:
10489
+ // the collapse is legitimate (#3407), hiding it was the bug.
10490
+ const coverage = describeBaselineCoverage(modified.length, preserved + rescued + regenerated);
10491
+ if (coverage.complete) {
10492
+ console.log(' ' + green + '✓' + reset + ' ' + coverage.text);
10493
+ } else {
10494
+ console.log(' ' + yellow + 'i' + reset + ' ' + coverage.text);
10495
+ }
10266
10496
  }
10267
10497
  }
10268
10498
  return modified;
@@ -10512,7 +10742,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
10512
10742
  // one) is honored on every profile, `full` included (#2322 blocker 2).
10513
10743
  const _commandsDir = path.join(src, 'commands', 'gsd');
10514
10744
  const _skillsManifest = _isCoreProfileAlias ? new Map() : loadSkillsManifest(_commandsDir);
10515
- const _resolvedProfile = resolveProfile({
10745
+ let _resolvedProfile = resolveProfile({
10516
10746
  modes: [_activeProfileName],
10517
10747
  manifest: _skillsManifest,
10518
10748
  registry: _installedCapabilityRegistry,
@@ -10863,6 +11093,28 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
10863
11093
  return Array.isArray(scopeLayout) && scopeLayout.length > 0;
10864
11094
  })();
10865
11095
 
11096
+ // Install the distribution-owned gsd-core tree before layout materialization.
11097
+ // installRuntimeArtifacts then provisions the durable Runtime Surface corpus
11098
+ // exactly once into this final tree instead of having that corpus overwritten
11099
+ // by a later whole-tree copy and needing a second provisioning pass.
11100
+ const skillSrc = path.join(src, 'gsd-core');
11101
+ const skillDest = path.join(targetDir, 'gsd-core');
11102
+ const _gsdArtifactsStagingRoot = _tryResolveUserArtifactStagingRoot(targetDir);
11103
+ if (_gsdArtifactsStagingRoot === null) {
11104
+ console.warn(` ${yellow}!${reset} Skipping gsd-core/${USER_OWNED_ARTIFACTS.join(', gsd-core/')} preservation (staging unavailable) — it will be lost if present.`);
11105
+ copyWithPathReplacement(skillSrc, skillDest, pathPrefix, runtime, false, isGlobal, targetDir);
11106
+ } else {
11107
+ const stagedGsdArtifacts = stageUserArtifacts(skillDest, USER_OWNED_ARTIFACTS, _gsdArtifactsStagingRoot);
11108
+ copyWithPathReplacement(skillSrc, skillDest, pathPrefix, runtime, false, isGlobal, targetDir);
11109
+ restoreStagedUserArtifacts(skillDest, stagedGsdArtifacts);
11110
+ discardStagedUserArtifacts(stagedGsdArtifacts);
11111
+ }
11112
+ if (verifyInstalled(skillDest, 'gsd-core')) {
11113
+ console.log(` ${green}✓${reset} Installed workflow assets`);
11114
+ } else {
11115
+ failures.push('gsd-core');
11116
+ }
11117
+
10866
11118
  // #2624: write the .gsd-source marker. Extracted from its former late position so it can be
10867
11119
  // called BEFORE staging reads the marker (see the call site below). Scoped to the Claude-global
10868
11120
  // layout (issue #1477) — the only install path that ships the skills layout without a
@@ -10879,6 +11131,9 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
10879
11131
  // ADR-1239 Phase B write-confinement: the descriptor-sourced marker filename
10880
11132
  // must resolve under targetDir (parity with the other descriptor-driven writes).
10881
11133
  const _markerPath = assertDestWithinConfigHome(targetDir, _hostBehaviors(runtime).sourceMarkerFile);
11134
+ if (hasExistingSymlinkBetween(path.resolve(targetDir), _markerPath, { allowOptInFollow: isSymlinkedDestOptIn() })) {
11135
+ throw new Error(`compatibility marker "${_markerPath}" contains an untrusted symlink`);
11136
+ }
10882
11137
  fs.writeFileSync(_markerPath, gsdSourceCommands + '\n', 'utf8');
10883
11138
  } catch (err) {
10884
11139
  // Non-fatal: install proceeds. But on the Claude-global layout walk-up
@@ -10905,6 +11160,19 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
10905
11160
  if (_isSkillsRuntime) {
10906
11161
  // Layout-driven install for skills-based runtimes (full and minimal modes)
10907
11162
  const scope = _installScopeId;
11163
+ // Preserve an existing Runtime Surface selection during upgrades. The
11164
+ // installer refreshes the source corpus first, then emits exactly the
11165
+ // already-committed selection instead of widening it to the base profile.
11166
+ const _committedSurface = readSurface(targetDir);
11167
+ if (_committedSurface) {
11168
+ _resolvedProfile = resolveSurface(
11169
+ targetDir,
11170
+ loadSkillsManifest(_commandsDir),
11171
+ undefined,
11172
+ _installedCapabilityRegistry,
11173
+ _committedSurface,
11174
+ );
11175
+ }
10908
11176
  // ADR-1239 upgrade 3 / #2088: a kind may declare an alternate install `home`
10909
11177
  // (e.g. Codex skills -> $HOME/.agents/skills) instead of the runtime's normal
10910
11178
  // configDir. Resolve the ACTUAL on-disk skills root here, descriptor-driven
@@ -11197,36 +11465,6 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
11197
11465
  _installNativePluginIfDeclared(runtime, targetDir, _hostBehaviors(runtime), src);
11198
11466
  }
11199
11467
 
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
11468
  // #2624: the .gsd-source marker is now written by _writeGsdSourceMarker()
11231
11469
  // BEFORE staging reads it (see the early call above the _isSkillsRuntime
11232
11470
  // block). The former write lived here — AFTER staging — which on an upgrade
@@ -11314,7 +11552,8 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
11314
11552
  } else if (_isSkillsRuntime) {
11315
11553
  console.log(` ${dim}↳${reset} Agents installed via descriptor-driven layout (${runtime})`);
11316
11554
  } else {
11317
- const _standaloneAgentsResult = installAgentsKindStandalone(runtime, targetDir, _installScopeId, _resolvedProfile, pathPrefix, getCommitAttribution, _installedCapabilityRegistry);
11555
+ const _standaloneProjectDir = isGlobal ? process.cwd() : targetDir;
11556
+ const _standaloneAgentsResult = installAgentsKindStandalone(runtime, targetDir, _installScopeId, _resolvedProfile, pathPrefix, getCommitAttribution, _installedCapabilityRegistry, _standaloneProjectDir);
11318
11557
  if (_standaloneAgentsResult) {
11319
11558
  // #2875 defect fix: installAgentsKindStandalone now returns `null`
11320
11559
  // (rather than a truthy result pointing at an empty destDir) whenever a
@@ -12064,13 +12303,29 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
12064
12303
  // require()s managed-hooks-registry.cjs for MANAGED_HOOKS — so all four must be
12065
12304
  // installed/refreshed together for every profile, or Codex is wired to a dependency
12066
12305
  // 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).
12306
+ // We deliberately do *not* copy gsd-graphify-update.sh for Codex in this
12307
+ // change (graphify auto-update support for Codex is out of scope for #3579).
12308
+ // hooks/lib/ WAS excluded here for the same reason, and that stopped being
12309
+ // correct when #3911 (2ea5efc15) gave gsd-context-monitor.js a real
12310
+ // `require('./lib/hook-exit.js')`: the allowlist below is flat and never
12311
+ // recursed, so the hook shipped without its helper and died with
12312
+ // MODULE_NOT_FOUND at load, before its own try/catch, on every registered
12313
+ // event (#4087, #4098). The libs are now derived from what the staged
12314
+ // scripts actually require rather than hand-listed — see the
12315
+ // stageTransitiveHookLibs call after the copy loop. The #3579 boundary is
12316
+ // preserved: helpers no staged Codex hook requires (graphify tooling among
12317
+ // them) are still not shipped.
12318
+ // #2586: gsd-context-monitor.js is deliberately NOT copied for Codex.
12319
+ // It reads the statusline bridge file (${TMPDIR}/claude-ctx-{session_id}.json)
12320
+ // written only by hooks/gsd-statusline.js, which Codex never installs — so
12321
+ // every registered event was a guaranteed silent no-op (readSentinel throws
12322
+ // ENOENT -> allow(undefined), every invocation, every event, no exceptions).
12323
+ // A pre-#2586 install's stale copy + hooks.json registrations are cleaned
12324
+ // up below (see the CODEX_EXTENDED_HOOK_EVENTS loop), not re-added here.
12069
12325
  const CODEX_HOOKS_TO_COPY = [
12070
12326
  'gsd-check-update.js',
12071
12327
  'gsd-check-update-worker.js',
12072
12328
  'managed-hooks-registry.cjs',
12073
- 'gsd-context-monitor.js',
12074
12329
  ];
12075
12330
  const codexHooksSrc = path.join(src, 'hooks', 'dist');
12076
12331
  if (fs.existsSync(codexHooksSrc)) {
@@ -12081,6 +12336,12 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
12081
12336
  // is not the same as an allowlisted file landing in it — see the marker
12082
12337
  // gate below.
12083
12338
  let codexStagedHooks = false;
12339
+ // The entries THIS invocation actually staged. Seeding the lib scan from
12340
+ // `existsSync` over the destination instead would also pick up a file
12341
+ // left by a PREVIOUS install whose source is no longer staged — e.g. a
12342
+ // name dropped from the allowlist — and derive helpers for a hook that is
12343
+ // no longer shipped (review of #4087).
12344
+ const codexStagedEntries = [];
12084
12345
  for (const entry of fs.readdirSync(codexHooksSrc)) {
12085
12346
  if (!CODEX_HOOKS_TO_COPY.includes(entry)) continue;
12086
12347
  const srcFile = path.join(codexHooksSrc, entry);
@@ -12115,8 +12376,43 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
12115
12376
  try { fs.chmodSync(destFile, 0o755); } catch (e) { /* Windows */ }
12116
12377
  }
12117
12378
  codexStagedHooks = true;
12379
+ codexStagedEntries.push(entry);
12380
+ }
12381
+ // Stage the hooks/lib/ helpers the staged scripts require, transitively
12382
+ // (#4087, #4098). Shares writeCursorHooksJson's walker rather than a
12383
+ // second copy: both reduced bundles hand-pick SCRIPTS, and the identical
12384
+ // MODULE_NOT_FOUND was already fixed once for Cursor in 704859e9c. A flat
12385
+ // list of today's three helpers would re-break the next time a
12386
+ // Codex-bundled hook grows a lib dependency, which is exactly how this
12387
+ // regressed. Gated on codexStagedHooks for the same reason the CommonJS
12388
+ // marker below is: hooks/ is shared space, and staging nothing must not
12389
+ // leave a GSD-owned lib/ behind in a directory GSD created but did not
12390
+ // fill (#2544). Seeded from the copies staged by THIS invocation, so the
12391
+ // scan sees the same bytes Node will load and never derives helpers for a
12392
+ // hook left behind by an earlier install.
12393
+ let codexStagedLibs = [];
12394
+ if (codexStagedHooks) {
12395
+ codexStagedLibs = hooksSurface.stageTransitiveHookLibs({
12396
+ seedSources: codexStagedEntries
12397
+ .map((entry) => fs.readFileSync(path.join(codexHooksDest, entry), 'utf8')),
12398
+ srcLibDir: path.join(codexHooksSrc, 'lib'),
12399
+ destLibDir: path.join(codexHooksDest, 'lib'),
12400
+ runtimeLabel: 'Codex',
12401
+ // Same substitutions the .js branch above applies to hook scripts, so
12402
+ // a helper that ever gains a runtime path or version token is
12403
+ // rewritten identically instead of shipping a Claude-shaped path.
12404
+ // No-ops on today's helpers, which carry neither.
12405
+ transform: (content) => content
12406
+ .replace(/'\.claude'/g, configDirReplacement)
12407
+ .replace(/\/\.claude\//g, `/${getDirName(runtime)}/`)
12408
+ .replace(/\.claude\//g, `${getDirName(runtime)}/`)
12409
+ .replace(/\{\{GSD_VERSION\}\}/g, pkg.version),
12410
+ });
12118
12411
  }
12119
12412
  console.log(` ${green}✓${reset} Installed hooks (Codex)`);
12413
+ if (codexStagedLibs.length > 0) {
12414
+ console.log(` ${green}✓${reset} Installed hooks/lib/ helpers (${codexStagedLibs.join(', ')})`);
12415
+ }
12120
12416
  // #2717: write the CommonJS marker into hooks/ alongside the staged .js
12121
12417
  // scripts. Codex is excluded from installSharedHooksBundle by the
12122
12418
  // !isCodex gate, so it never received the marker the shared-bundle path
@@ -12231,35 +12527,48 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
12231
12527
  }
12232
12528
  }
12233
12529
 
12234
- // ── Codex extended hook events (#772, #2088) ─────────────────────────
12235
- // Codex CLI stabilised a full hook-event set in rust-v0.137.0. GSD
12236
- // registers CODEX_EXTENDED_HOOK_EVENTS (#2088 adds the 6 documented
12237
- // events beyond the original #772 three) — all routed through
12238
- // gsd-context-monitor.js so context-headroom warnings surface at each
12239
- // lifecycle point: SubagentStart/SubagentStop (subagent open/close),
12240
- // Stop (final-response), PreToolUse/PostToolUse (tool boundaries),
12241
- // PermissionRequest (approval prompts), Pre/PostCompact (context
12242
- // compaction), and UserPromptSubmit (per-turn context injection). The
12243
- // context-monitor script decides per-payload what to do; unregistered
12244
- // events simply never fire.
12245
- //
12246
- // Guard: only register when the context-monitor file exists and the node
12247
- // runner is available — same guards as the SessionStart path above.
12248
- const contextMonitorFile = path.join(targetDir, 'hooks', 'gsd-context-monitor.js');
12249
- if (codexNodeRunner && fs.existsSync(contextMonitorFile)) {
12250
- for (const codexEvent of CODEX_EXTENDED_HOOK_EVENTS) {
12251
- const eventWrite = ensureCodexHooksJsonEvent(targetDir, codexEvent, {
12252
- absoluteRunner: codexNodeRunner,
12253
- platform: process.platform,
12254
- });
12255
- if (eventWrite.wrote) {
12256
- console.log(` ${green}✓${reset} Configured Codex hooks (${codexEvent} via hooks.json)`);
12257
- } else if (eventWrite.changed) {
12258
- console.log(` ${green}✓${reset} Verified Codex hooks (${codexEvent} via hooks.json)`);
12259
- }
12530
+ // #2586: Codex's hook payload carries no context/token-usage field
12531
+ // (confirmed against codex-rs/hooks/src/schema.rs), so agent-facing
12532
+ // context warnings and GSD phase/lifecycle display cannot be
12533
+ // supported on this runtime — state that plainly during install
12534
+ // rather than silently omitting the capability. Matches
12535
+ // capabilities/codex/capability.json's hostBehaviors.unsupportedFeatures.
12536
+ console.log(` ${dim}↳${reset} Codex: agent-facing context warnings and GSD phase/lifecycle display are unsupported (Codex's hook payload has no context-usage metric)`);
12537
+
12538
+ // ── Codex extended hook events (#772, #2088) — REMOVED by #2586 ──────
12539
+ // gsd-context-monitor.js is no longer copied or registered for Codex
12540
+ // (see the CODEX_HOOKS_TO_COPY comment above): every one of these
12541
+ // events was a guaranteed silent no-op, since the metrics bridge file
12542
+ // it reads is only ever written by Claude's own statusline hook.
12543
+ // Every event in CODEX_EXTENDED_HOOK_EVENTS is unconditionally
12544
+ // reconciled here — not gated on the script existing — so a
12545
+ // pre-#2586 install's stale registrations (exact current shape, or a
12546
+ // recognized legacy shape via isManagedHookCommand's
12547
+ // includeLegacyAliases) are stripped on reinstall. Mirrors the
12548
+ // unconditional uninstall-time loop over the same constant. A
12549
+ // registration whose command does not match the managed shape (a
12550
+ // hand-customized entry) survives untouched — see
12551
+ // reconcileCodexHooksJsonEvent's isManagedHookCommand filter.
12552
+ for (const codexEvent of CODEX_EXTENDED_HOOK_EVENTS) {
12553
+ const eventCleanup = removeCodexHooksJsonEvent(targetDir, codexEvent);
12554
+ if (eventCleanup.changed) {
12555
+ console.log(` ${green}✓${reset} Removed stale Codex ${codexEvent} context-monitor hook from hooks.json`);
12260
12556
  }
12261
- } else if (!codexNodeRunner) {
12262
- console.warn(` ${yellow}⚠${reset} Skipped Codex extended hook-event registration — Node runner unavailable.`);
12557
+ }
12558
+ // Delete the orphaned script (+ Windows .cmd shim) left by a
12559
+ // pre-#2586 install, but ONLY once no surviving hooks.json
12560
+ // registration under any event still references it, and only when
12561
+ // the on-disk file is GSD's own (see design doc's Ownership check —
12562
+ // a content-signature check, not manifest membership, so this works
12563
+ // on the very first reinstall after upgrading, with no bootstrap
12564
+ // gap). A deletion failure never reverts the (already safe,
12565
+ // already-written) hooks.json cleanup above — must-have #8.
12566
+ const monitorCleanup = hooksSurface.cleanupOrphanedCodexContextMonitorScript(targetDir);
12567
+ for (const deletedPath of monitorCleanup.deleted) {
12568
+ console.log(` ${green}✓${reset} Removed orphaned Codex hook script (${path.basename(deletedPath)})`);
12569
+ }
12570
+ for (const warning of monitorCleanup.warnings) {
12571
+ console.warn(` ${yellow}⚠${reset} Could not remove orphaned Codex hook script ${warning.path}: ${warning.reason}`);
12263
12572
  }
12264
12573
  // ── end Codex extended hook events ────────────────────────────────────
12265
12574
  }
@@ -13881,7 +14190,7 @@ module.exports = {
13881
14190
  mergeClaudePermissions,
13882
14191
  GSD_CLAUDE_ALLOW_PERMISSIONS,
13883
14192
  GSD_CLAUDE_LEGACY_ALLOW_PERMISSIONS,
13884
- GSD_CLAUDE_DENY_PERMISSIONS,
14193
+ GSD_CLAUDE_LEGACY_DENY_PERMISSIONS,
13885
14194
  GSD_CODEX_MARKER,
13886
14195
  // #3897 rung 3 (ADR-3473 §8.3, HALT.md option 2)
13887
14196
  CODEX_SANDBOX_HOLDS,
@@ -13945,6 +14254,7 @@ module.exports = {
13945
14254
  reportLocalPatches,
13946
14255
  validateHookFields,
13947
14256
  populatePristineDir,
14257
+ describeBaselineCoverage,
13948
14258
  _resolveUserArtifactStagingRoot,
13949
14259
  _tryResolveUserArtifactStagingRoot,
13950
14260
  finishInstall,