@opengsd/gsd-core 1.10.0 → 1.12.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 (544) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-code-fixer.md +1 -1
  4. package/agents/gsd-debug-session-manager.md +12 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-doc-synthesizer.md +2 -4
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +22 -14
  10. package/agents/gsd-framework-selector.md +1 -3
  11. package/agents/gsd-intel-updater.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +5 -3
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +23 -2
  15. package/agents/gsd-plan-checker.md +50 -53
  16. package/agents/gsd-planner.md +50 -50
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +2 -2
  19. package/agents/gsd-roadmapper.md +15 -11
  20. package/agents/gsd-ui-checker.md +63 -4
  21. package/agents/gsd-ui-researcher.md +41 -3
  22. package/agents/gsd-user-profiler.md +3 -0
  23. package/agents/gsd-verifier.md +13 -4
  24. package/bin/install.js +1448 -1103
  25. package/commands/gsd/code-review.md +1 -1
  26. package/commands/gsd/discuss-phase.md +1 -1
  27. package/commands/gsd/execute-phase.md +1 -1
  28. package/commands/gsd/import.md +1 -1
  29. package/commands/gsd/map-codebase.md +1 -1
  30. package/commands/gsd/mempalace-capture.md +1 -1
  31. package/commands/gsd/mempalace-recall.md +1 -1
  32. package/commands/gsd/new-milestone.md +1 -1
  33. package/commands/gsd/quick.md +9 -5
  34. package/commands/gsd/review-backlog.md +2 -1
  35. package/commands/gsd/verify-work.md +1 -1
  36. package/gsd-core/bin/gsd-tools.cjs +1035 -138
  37. package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
  38. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  39. package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
  40. package/gsd-core/bin/lib/api-coverage.cjs +33 -14
  41. package/gsd-core/bin/lib/artifacts.cjs +5 -0
  42. package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
  43. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  44. package/gsd-core/bin/lib/audit.cjs +1026 -268
  45. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  46. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  47. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  48. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  49. package/gsd-core/bin/lib/capability-registry.cjs +845 -130
  50. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  51. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  52. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  53. package/gsd-core/bin/lib/capability-validator.cjs +700 -40
  54. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  55. package/gsd-core/bin/lib/check-command-router.cjs +216 -42
  56. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  57. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  58. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  59. package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
  60. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  61. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  62. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  63. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  64. package/gsd-core/bin/lib/commands.cjs +1172 -108
  65. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  66. package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
  67. package/gsd-core/bin/lib/config-loader.cjs +187 -23
  68. package/gsd-core/bin/lib/config.cjs +102 -3
  69. package/gsd-core/bin/lib/configuration.cjs +129 -37
  70. package/gsd-core/bin/lib/core-utils.cjs +208 -33
  71. package/gsd-core/bin/lib/decisions.cjs +23 -0
  72. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  73. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  74. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  75. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  76. package/gsd-core/bin/lib/frontmatter.cjs +899 -229
  77. package/gsd-core/bin/lib/gap-checker.cjs +95 -10
  78. package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
  79. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  80. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  81. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  82. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
  83. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  84. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  85. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  86. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
  87. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  88. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  89. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
  90. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  91. package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
  92. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  93. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  94. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  95. package/gsd-core/bin/lib/init.cjs +439 -168
  96. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  97. package/gsd-core/bin/lib/install-engine.cjs +811 -259
  98. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  99. package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
  100. package/gsd-core/bin/lib/install-profiles.cjs +212 -61
  101. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  102. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  103. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  104. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  105. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  106. package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
  107. package/gsd-core/bin/lib/intel.cjs +101 -26
  108. package/gsd-core/bin/lib/io.cjs +170 -15
  109. package/gsd-core/bin/lib/learnings.cjs +85 -14
  110. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  111. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  112. package/gsd-core/bin/lib/markdown-table.cjs +183 -22
  113. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  114. package/gsd-core/bin/lib/milestone.cjs +842 -73
  115. package/gsd-core/bin/lib/model-catalog.cjs +232 -16
  116. package/gsd-core/bin/lib/model-resolver.cjs +193 -68
  117. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  118. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  119. package/gsd-core/bin/lib/pattern.cjs +122 -0
  120. package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
  121. package/gsd-core/bin/lib/phase-id.cjs +514 -40
  122. package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
  123. package/gsd-core/bin/lib/phase-locator.cjs +262 -34
  124. package/gsd-core/bin/lib/phase.cjs +1038 -214
  125. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  126. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  127. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  128. package/gsd-core/bin/lib/plan-scan.cjs +98 -3
  129. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  130. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  131. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  132. package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
  133. package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
  134. package/gsd-core/bin/lib/probe-core.cjs +5 -2
  135. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  136. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  137. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  138. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  139. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
  140. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  141. package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
  142. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  143. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  144. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  145. package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
  146. package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
  147. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  148. package/gsd-core/bin/lib/roadmap.cjs +442 -96
  149. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
  150. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  151. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
  152. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  153. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  154. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
  155. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  156. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  157. package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
  158. package/gsd-core/bin/lib/security.cjs +104 -5
  159. package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
  160. package/gsd-core/bin/lib/smart-entry.cjs +133 -23
  161. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  162. package/gsd-core/bin/lib/state-command-router.cjs +52 -19
  163. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  164. package/gsd-core/bin/lib/state-document.cjs +338 -8
  165. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  166. package/gsd-core/bin/lib/state-transition.cjs +846 -176
  167. package/gsd-core/bin/lib/state.cjs +2589 -369
  168. package/gsd-core/bin/lib/surface.cjs +33 -11
  169. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  170. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  171. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  172. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  173. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  174. package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
  175. package/gsd-core/bin/lib/uat.cjs +1761 -167
  176. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  177. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  178. package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
  179. package/gsd-core/bin/lib/unusable-input.cjs +37 -0
  180. package/gsd-core/bin/lib/update-context.cjs +8 -2
  181. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  182. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  183. package/gsd-core/bin/lib/validate.cjs +20 -6
  184. package/gsd-core/bin/lib/vendor/README.md +75 -0
  185. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  186. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  187. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  188. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  189. package/gsd-core/bin/lib/verification.cjs +272 -9
  190. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  191. package/gsd-core/bin/lib/verify.cjs +453 -918
  192. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  193. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  194. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  195. package/gsd-core/bin/lib/workstream.cjs +2 -2
  196. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  197. package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
  198. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
  199. package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
  200. package/gsd-core/bin/shared/exit-codes.json +8 -0
  201. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  202. package/gsd-core/bin/shared/model-catalog.json +8 -1
  203. package/gsd-core/references/agent-contracts.md +44 -26
  204. package/gsd-core/references/api-coverage.md +24 -2
  205. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  206. package/gsd-core/references/checkpoints.md +39 -21
  207. package/gsd-core/references/context-budget.md +1 -1
  208. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  209. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  210. package/gsd-core/references/doc-conflict-engine.md +1 -1
  211. package/gsd-core/references/edge-probe.md +8 -0
  212. package/gsd-core/references/execute-mvp-tdd.md +4 -6
  213. package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
  214. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  215. package/gsd-core/references/execute-phase-response-language.md +1 -1
  216. package/gsd-core/references/execute-phase-wave-guard.md +17 -11
  217. package/gsd-core/references/failing-direction.md +78 -0
  218. package/gsd-core/references/gate-prompts.md +1 -1
  219. package/gsd-core/references/git-integration.md +5 -5
  220. package/gsd-core/references/git-planning-commit.md +5 -4
  221. package/gsd-core/references/gsd-run-resolver.md +1 -1
  222. package/gsd-core/references/loop-hook-dispatch.md +61 -2
  223. package/gsd-core/references/model-profiles.md +12 -4
  224. package/gsd-core/references/mvp-concepts.md +9 -9
  225. package/gsd-core/references/nyquist-compliance.md +74 -0
  226. package/gsd-core/references/offer-next.md +3 -5
  227. package/gsd-core/references/phase-argument-parsing.md +3 -3
  228. package/gsd-core/references/planner-failing-direction.md +53 -0
  229. package/gsd-core/references/planner-guidance.md +3 -9
  230. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  231. package/gsd-core/references/planner-preconditions.md +1 -1
  232. package/gsd-core/references/planner-reviews.md +1 -1
  233. package/gsd-core/references/planner-revision.md +1 -1
  234. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  235. package/gsd-core/references/planning-config.md +44 -13
  236. package/gsd-core/references/reviewer-instances.md +31 -0
  237. package/gsd-core/references/revision-loop.md +1 -1
  238. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  239. package/gsd-core/references/specless-probe-fallback.md +1 -1
  240. package/gsd-core/references/tdd.md +1 -3
  241. package/gsd-core/references/ui-brand.md +65 -21
  242. package/gsd-core/references/ui-consideration-probe.md +1 -1
  243. package/gsd-core/references/universal-anti-patterns.md +5 -5
  244. package/gsd-core/references/verifier-phase-gates.md +192 -0
  245. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  246. package/gsd-core/references/verify-mvp-mode.md +2 -2
  247. package/gsd-core/references/workstream-flag.md +33 -17
  248. package/gsd-core/templates/README.md +1 -1
  249. package/gsd-core/templates/SECURITY.md +3 -3
  250. package/gsd-core/templates/UI-SPEC.md +25 -3
  251. package/gsd-core/templates/VALIDATION.md +3 -3
  252. package/gsd-core/templates/discussion-log.md +1 -1
  253. package/gsd-core/templates/phase-prompt.md +5 -4
  254. package/gsd-core/templates/state.md +11 -4
  255. package/gsd-core/templates/verification-report.md +9 -1
  256. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  257. package/gsd-core/workflows/add-backlog.md +1 -1
  258. package/gsd-core/workflows/add-phase.md +3 -3
  259. package/gsd-core/workflows/add-tests.md +3 -8
  260. package/gsd-core/workflows/add-todo.md +1 -1
  261. package/gsd-core/workflows/ai-integration-phase.md +13 -20
  262. package/gsd-core/workflows/audit-fix.md +12 -3
  263. package/gsd-core/workflows/audit-milestone.md +9 -9
  264. package/gsd-core/workflows/audit-uat.md +17 -2
  265. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  266. package/gsd-core/workflows/autonomous.md +11 -27
  267. package/gsd-core/workflows/check-todos.md +1 -1
  268. package/gsd-core/workflows/cleanup.md +64 -5
  269. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
  270. package/gsd-core/workflows/code-review-fix.md +38 -11
  271. package/gsd-core/workflows/code-review.md +159 -52
  272. package/gsd-core/workflows/complete-milestone.md +151 -23
  273. package/gsd-core/workflows/debug.md +12 -8
  274. package/gsd-core/workflows/diagnose-issues.md +47 -15
  275. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  276. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
  277. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  278. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  279. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  280. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  281. package/gsd-core/workflows/discuss-phase.md +1 -1
  282. package/gsd-core/workflows/do.md +3 -6
  283. package/gsd-core/workflows/docs-update.md +5 -4
  284. package/gsd-core/workflows/edit-phase.md +27 -2
  285. package/gsd-core/workflows/eval-review.md +7 -14
  286. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  287. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
  288. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  289. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  290. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  291. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
  292. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  293. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  294. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  295. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  296. package/gsd-core/workflows/execute-phase.md +72 -100
  297. package/gsd-core/workflows/execute-plan.md +52 -15
  298. package/gsd-core/workflows/explore.md +131 -4
  299. package/gsd-core/workflows/extract-learnings.md +1 -1
  300. package/gsd-core/workflows/fast.md +10 -2
  301. package/gsd-core/workflows/forensics.md +1 -1
  302. package/gsd-core/workflows/graduation.md +5 -5
  303. package/gsd-core/workflows/health.md +76 -10
  304. package/gsd-core/workflows/import.md +18 -15
  305. package/gsd-core/workflows/inbox.md +4 -5
  306. package/gsd-core/workflows/ingest-docs.md +49 -16
  307. package/gsd-core/workflows/insert-phase.md +5 -5
  308. package/gsd-core/workflows/list-seeds.md +5 -3
  309. package/gsd-core/workflows/list-workspaces.md +1 -1
  310. package/gsd-core/workflows/manager.md +12 -23
  311. package/gsd-core/workflows/map-codebase.md +1 -1
  312. package/gsd-core/workflows/milestone-summary.md +1 -1
  313. package/gsd-core/workflows/mvp-phase.md +8 -5
  314. package/gsd-core/workflows/new-milestone.md +22 -29
  315. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  316. package/gsd-core/workflows/new-project.md +26 -40
  317. package/gsd-core/workflows/new-workspace.md +1 -1
  318. package/gsd-core/workflows/next.md +14 -2
  319. package/gsd-core/workflows/pause-work.md +1 -1
  320. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  321. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  322. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  323. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  324. package/gsd-core/workflows/plan-phase.md +162 -59
  325. package/gsd-core/workflows/plan-review-convergence.md +96 -11
  326. package/gsd-core/workflows/plant-seed.md +2 -2
  327. package/gsd-core/workflows/pr-branch.md +187 -51
  328. package/gsd-core/workflows/profile-user.md +16 -14
  329. package/gsd-core/workflows/progress.md +61 -18
  330. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  331. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
  332. package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
  333. package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
  334. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  335. package/gsd-core/workflows/quick.md +55 -44
  336. package/gsd-core/workflows/remove-phase.md +4 -4
  337. package/gsd-core/workflows/remove-workspace.md +2 -2
  338. package/gsd-core/workflows/resume-project.md +8 -12
  339. package/gsd-core/workflows/review.md +219 -20
  340. package/gsd-core/workflows/scan.md +1 -1
  341. package/gsd-core/workflows/secure-phase.md +3 -3
  342. package/gsd-core/workflows/session-report.md +2 -1
  343. package/gsd-core/workflows/settings-advanced.md +7 -9
  344. package/gsd-core/workflows/settings-integrations.md +64 -31
  345. package/gsd-core/workflows/settings.md +69 -7
  346. package/gsd-core/workflows/ship.md +116 -50
  347. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  348. package/gsd-core/workflows/sketch.md +12 -18
  349. package/gsd-core/workflows/smart-entry.md +3 -5
  350. package/gsd-core/workflows/spec-phase.md +53 -13
  351. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  352. package/gsd-core/workflows/spike.md +20 -31
  353. package/gsd-core/workflows/stats.md +2 -2
  354. package/gsd-core/workflows/sync-skills.md +64 -9
  355. package/gsd-core/workflows/thread.md +11 -7
  356. package/gsd-core/workflows/transition.md +49 -14
  357. package/gsd-core/workflows/ui-phase.md +15 -21
  358. package/gsd-core/workflows/ui-review.md +8 -12
  359. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  360. package/gsd-core/workflows/undo.md +8 -16
  361. package/gsd-core/workflows/update.md +7 -11
  362. package/gsd-core/workflows/validate-phase.md +3 -3
  363. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  364. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  365. package/gsd-core/workflows/verify-work.md +66 -25
  366. package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
  367. package/hooks/dist/gsd-check-update-worker.js +56 -13
  368. package/hooks/dist/gsd-check-update.js +19 -1
  369. package/hooks/dist/gsd-config-reload.js +18 -12
  370. package/hooks/dist/gsd-context-monitor.js +19 -10
  371. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  372. package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
  373. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  374. package/hooks/dist/gsd-cursor-stop.js +2 -1
  375. package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
  376. package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
  377. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  378. package/hooks/dist/gsd-graphify-update.sh +22 -18
  379. package/hooks/dist/gsd-node-runner.sh +76 -0
  380. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  381. package/hooks/dist/gsd-prompt-guard.js +37 -27
  382. package/hooks/dist/gsd-read-guard.js +16 -7
  383. package/hooks/dist/gsd-read-injection-scanner.js +55 -32
  384. package/hooks/dist/gsd-session-state.sh +1 -0
  385. package/hooks/dist/gsd-statusline.js +231 -24
  386. package/hooks/dist/gsd-update-banner.js +22 -1
  387. package/hooks/dist/gsd-validate-commit.sh +80 -6
  388. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  389. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  390. package/hooks/dist/gsd-workflow-guard.js +162 -46
  391. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  392. package/hooks/dist/gsd-write-guard.js +35 -25
  393. package/hooks/dist/lib/cli-exit.js +560 -0
  394. package/hooks/dist/lib/exit-code-registry.js +98 -0
  395. package/hooks/dist/lib/git-cmd.js +92 -59
  396. package/hooks/dist/lib/git-probe.js +84 -0
  397. package/hooks/dist/lib/hook-exit.js +81 -0
  398. package/hooks/dist/lib/injection-patterns.js +45 -0
  399. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  400. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  401. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  402. package/hooks/gsd-agent-isolation-guard.js +158 -30
  403. package/hooks/gsd-check-update-worker.js +56 -13
  404. package/hooks/gsd-check-update.js +19 -1
  405. package/hooks/gsd-config-reload.js +18 -12
  406. package/hooks/gsd-context-monitor.js +19 -10
  407. package/hooks/gsd-cursor-post-tool.js +3 -1
  408. package/hooks/gsd-cursor-pre-tool.js +2 -3
  409. package/hooks/gsd-cursor-session-start.js +2 -1
  410. package/hooks/gsd-cursor-stop.js +2 -1
  411. package/hooks/gsd-cursor-subagent-start.js +83 -3
  412. package/hooks/gsd-cursor-subagent-stop.js +6 -3
  413. package/hooks/gsd-ensure-canonical-path.js +2 -1
  414. package/hooks/gsd-graphify-update.sh +22 -18
  415. package/hooks/gsd-node-runner.sh +76 -0
  416. package/hooks/gsd-phase-boundary.sh +1 -0
  417. package/hooks/gsd-prompt-guard.js +37 -27
  418. package/hooks/gsd-read-guard.js +16 -7
  419. package/hooks/gsd-read-injection-scanner.js +55 -32
  420. package/hooks/gsd-session-state.sh +1 -0
  421. package/hooks/gsd-statusline.js +231 -24
  422. package/hooks/gsd-update-banner.js +22 -1
  423. package/hooks/gsd-validate-commit.sh +80 -6
  424. package/hooks/gsd-windsurf-pre-command.js +16 -11
  425. package/hooks/gsd-windsurf-pre-write.js +22 -13
  426. package/hooks/gsd-workflow-guard.js +162 -46
  427. package/hooks/gsd-worktree-path-guard.js +36 -21
  428. package/hooks/gsd-write-guard.js +35 -25
  429. package/hooks/lib/cli-exit.js +560 -0
  430. package/hooks/lib/exit-code-registry.js +98 -0
  431. package/hooks/lib/git-cmd.js +92 -59
  432. package/hooks/lib/git-probe.js +84 -0
  433. package/hooks/lib/hook-exit.js +81 -0
  434. package/hooks/lib/injection-patterns.js +45 -0
  435. package/hooks/lib/isolation-deny-reason.js +39 -0
  436. package/hooks/lib/isolation-sentinel.js +9 -0
  437. package/hooks/managed-hooks-registry.cjs +3 -0
  438. package/package.json +28 -11
  439. package/pi/gsd.cjs +19 -5
  440. package/scripts/base64-scan.sh +74 -12
  441. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  442. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  443. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  444. package/scripts/build-hooks.js +5 -0
  445. package/scripts/changeset/lint.cjs +60 -5
  446. package/scripts/check-alias-drift.cjs +7 -43
  447. package/scripts/check-contract-drift.cjs +297 -0
  448. package/scripts/check-glossary-refs.cjs +77 -15
  449. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  450. package/scripts/ci-check-job-near-cap.cjs +49 -0
  451. package/scripts/ci-pr-mergeability.cjs +262 -0
  452. package/scripts/ci-test-scope.cjs +64 -14
  453. package/scripts/ci-timeout-report.cjs +230 -0
  454. package/scripts/command-contract-helpers.cjs +903 -1
  455. package/scripts/docs-guard-registry.cjs +396 -0
  456. package/scripts/gen-adr-index.cjs +728 -38
  457. package/scripts/gen-capability-registry.cjs +11 -21
  458. package/scripts/gen-context-index.cjs +2 -11
  459. package/scripts/gen-exit-code-docs.cjs +318 -0
  460. package/scripts/gen-exit-code-registry.cjs +891 -0
  461. package/scripts/gen-features.cjs +836 -0
  462. package/scripts/gen-health-docs.cjs +390 -0
  463. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  464. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  465. package/scripts/gen-inventory-manifest.cjs +50 -4
  466. package/scripts/gen-loop-host-contract.cjs +138 -25
  467. package/scripts/gen-registry.cjs +3 -14
  468. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  469. package/scripts/gen-state-md-docs.cjs +727 -0
  470. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  471. package/scripts/lib/alias-drift-families.cjs +46 -0
  472. package/scripts/lib/ci-job-timing.cjs +72 -0
  473. package/scripts/lib/cli-exit.cjs +546 -44
  474. package/scripts/lib/drift-scan.cjs +308 -0
  475. package/scripts/lib/exit-code-registry.cjs +98 -0
  476. package/scripts/lib/ndjson-reporter.cjs +119 -0
  477. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  478. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  479. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  480. package/scripts/lint-canary-version-leak.cjs +73 -0
  481. package/scripts/lint-command-contract.cjs +96 -13
  482. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  483. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  484. package/scripts/lint-default-flip-documentation.cjs +193 -0
  485. package/scripts/lint-docs-guard-registration.cjs +495 -0
  486. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  487. package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
  488. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  489. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  490. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  491. package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
  492. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  493. package/scripts/lint-milestone-window-drift.cjs +468 -0
  494. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  495. package/scripts/lint-phase-enumeration-drift.cjs +492 -0
  496. package/scripts/lint-plan-count-drift.cjs +318 -0
  497. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  498. package/scripts/lint-planning-prompt-drift.cjs +471 -0
  499. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  500. package/scripts/lint-regression-test-names.cjs +15 -13
  501. package/scripts/lint-removed-but-needed.cjs +488 -0
  502. package/scripts/lint-seam-enforcement.cjs +182 -0
  503. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  504. package/scripts/lint-source-test-name-collision.cjs +241 -0
  505. package/scripts/lint-state-field-drift.cjs +805 -0
  506. package/scripts/lint-state-write-path-drift.cjs +950 -0
  507. package/scripts/lint-test-file-count.allowlist.json +137 -8
  508. package/scripts/lint-test-file-count.cjs +25 -3
  509. package/scripts/lint-unreachable-guard-drift.cjs +830 -0
  510. package/scripts/lint-vendored-deps.cjs +297 -0
  511. package/scripts/mutation-matrix.cjs +599 -50
  512. package/scripts/pr-changed-files.cjs +63 -0
  513. package/scripts/pr-template-policy.cjs +14 -4
  514. package/scripts/prompt-injection-scan.sh +100 -14
  515. package/scripts/require-issue-link-policy.cjs +192 -0
  516. package/scripts/secret-scan.sh +75 -13
  517. package/scripts/select-docs-guards.cjs +56 -0
  518. package/scripts/sync-runtime-launcher.cjs +24 -7
  519. package/skills/gsd-autonomous/SKILL.md +0 -1
  520. package/skills/gsd-code-review/SKILL.md +1 -1
  521. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  522. package/skills/gsd-execute-phase/SKILL.md +1 -2
  523. package/skills/gsd-import/SKILL.md +1 -1
  524. package/skills/gsd-map-codebase/SKILL.md +1 -1
  525. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  526. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  527. package/skills/gsd-new-milestone/SKILL.md +1 -1
  528. package/skills/gsd-next/SKILL.md +0 -1
  529. package/skills/gsd-plan-phase/SKILL.md +0 -1
  530. package/skills/gsd-progress/SKILL.md +0 -1
  531. package/skills/gsd-quick/SKILL.md +9 -5
  532. package/skills/gsd-review-backlog/SKILL.md +2 -1
  533. package/skills/gsd-stats/SKILL.md +0 -1
  534. package/skills/gsd-verify-work/SKILL.md +1 -1
  535. package/vscode/package.json +1 -1
  536. package/bin/lib/ui-safety-gate.cjs +0 -107
  537. package/gsd-core/workflows/discovery-phase.md +0 -298
  538. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  539. package/gsd-core/workflows/verify-phase.md +0 -574
  540. package/scripts/affected-tests-lib.cjs +0 -554
  541. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  542. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  543. package/scripts/run-affected-tests.cjs +0 -7
  544. package/scripts/run-tests.cjs +0 -1051
@@ -16,51 +16,61 @@ const node_crypto_1 = __importDefault(require("node:crypto"));
16
16
  const installer_migration_authoring_cjs_1 = require("./installer-migration-authoring.cjs");
17
17
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
18
18
  const clock_cjs_1 = require("./clock.cjs");
19
+ const install_scope_cjs_1 = require("./install-scope.cjs");
20
+ // #2874 (ADR-58 cleanup phase): this file is the ~1200-line migration
21
+ // plan/apply/rollback/lock/journal engine — almost none of it is on the
22
+ // installRuntimeArtifacts call tree. Only `readInstallManifest` and
23
+ // `classifyArtifact` are reached (via install-engine.cts's
24
+ // _migrateLegacyOpencodeCommandDir and retired-artifact-cleanup.cts's
25
+ // pruneRetiredRuntimeArtifacts), so only those two entry points — plus their
26
+ // shared `readJsonIfPresent` helper and `classifyArtifact`'s `sha256File`
27
+ // hashing helper — are routed through the injectable seam. Everything else
28
+ // in this file (locking, journal, apply/rollback, migration discovery)
29
+ // keeps using real `fs` directly: it is not reachable from
30
+ // installRuntimeArtifacts, so routing it would grow this seam past what
31
+ // AC2 actually requires. See install-fs-adapter.cts's module doc.
32
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
33
+ const installFsAdapter = require("./install-fs-adapter.cjs");
34
+ const { installFs } = installFsAdapter;
19
35
  const MANIFEST_NAME = 'gsd-file-manifest.json';
20
36
  const INSTALL_STATE_NAME = 'gsd-install-state.json';
21
37
  const INSTALL_MIGRATION_LOCK_NAME = 'gsd-install-migration.lock';
22
38
  const DEFAULT_MIGRATIONS_DIR = node_path_1.default.join(__dirname, 'installer-migrations');
23
39
  const DEFAULT_LOCK_TIMEOUT_MS = 30_000;
24
40
  const STRICT_JSON = Symbol('strict-json');
41
+ // #2874: routed through installFs()'s openSync/readSync/closeSync trio
42
+ // instead of importing `node:fs` directly, so classifyArtifact — reachable
43
+ // from installRuntimeArtifacts — can be exercised against an injected
44
+ // adapter. This function was briefly converted to a single
45
+ // `installFs().readFileSync` call (buffering the whole file); that broke
46
+ // tests/installer-migrations.test.cjs's "classifies large files without
47
+ // loading the whole file through readFileSync", which monkeypatches real
48
+ // fs.readFileSync to throw for the file under test and asserts hashing still
49
+ // succeeds — an explicit, pre-existing contract that large files must be
50
+ // streamed, not buffered. Restored to the original raw-fd streaming shape,
51
+ // now going through the adapter instead of `node:fs` directly. This is the
52
+ // ONLY call site of sha256File in this file (confirmed by inspection) — no
53
+ // other caller is affected.
25
54
  function sha256File(filePath) {
26
55
  const hash = node_crypto_1.default.createHash('sha256');
27
56
  const buffer = Buffer.allocUnsafe(1024 * 1024);
28
- const fd = node_fs_1.default.openSync(filePath, 'r');
57
+ const fd = installFs().openSync(filePath, 'r');
29
58
  try {
30
59
  while (true) {
31
- const bytesRead = node_fs_1.default.readSync(fd, buffer, 0, buffer.length, null);
60
+ const bytesRead = installFs().readSync(fd, buffer, 0, buffer.length, null);
32
61
  if (bytesRead === 0)
33
62
  break;
34
63
  hash.update(buffer.subarray(0, bytesRead));
35
64
  }
36
65
  }
37
66
  finally {
38
- node_fs_1.default.closeSync(fd);
67
+ installFs().closeSync(fd);
39
68
  }
40
69
  return hash.digest('hex');
41
70
  }
42
71
  function sha256Text(value) {
43
72
  return node_crypto_1.default.createHash('sha256').update(value).digest('hex');
44
73
  }
45
- /**
46
- * Copy a managed path for the rollback snapshot or the user-facing backup,
47
- * WITHOUT dereferencing a symlink.
48
- *
49
- * `fs.copyFileSync` follows symlinks, so a managed path that has been replaced
50
- * by a link (tampering, or an unexpected user layout) would have had the
51
- * LINK TARGET's bytes copied into `gsd-migration-journal/…-backups/` — e.g. a
52
- * `gsd.cjs` symlinked at `~/.ssh/id_rsa` would land that key's contents in the
53
- * backup tree. Nothing GSD installs is ever a symlink, so the faithful snapshot
54
- * of a symlinked managed path is the link itself: recreating it preserves
55
- * rollback fidelity (restore re-creates the same link) while never reading the
56
- * referent. Deletion was already safe — `fs.rmSync` unlinks the link, never the
57
- * target.
58
- *
59
- * Windows note: `fs.symlinkSync` can throw EPERM for unprivileged users. That
60
- * surfaces as an apply failure and triggers the normal rollback path, which is
61
- * the correct outcome — refusing to proceed beats silently copying referent
62
- * bytes.
63
- */
64
74
  /**
65
75
  * Evaluate and, if safe, perform a `remove-empty-dir` action against `fullPath`.
66
76
  *
@@ -133,23 +143,54 @@ function evaluateRemoveEmptyDir(configDir, fullPath) {
133
143
  return 'left-in-place';
134
144
  }
135
145
  }
146
+ /**
147
+ * Copy a managed path for the rollback snapshot or the user-facing backup,
148
+ * WITHOUT dereferencing a symlink.
149
+ *
150
+ * `fs.copyFileSync` follows symlinks, so a managed path that has been replaced
151
+ * by a link (tampering, or an unexpected user layout) would have had the
152
+ * LINK TARGET's bytes copied into `gsd-migration-journal/…-backups/` — e.g. a
153
+ * `gsd.cjs` symlinked at `~/.ssh/id_rsa` would land that key's contents in the
154
+ * backup tree. Nothing GSD installs is ever a symlink, so the faithful snapshot
155
+ * of a symlinked managed path is the link itself: recreating it preserves
156
+ * rollback fidelity (restore re-creates the same link) while never reading the
157
+ * referent. Deletion was already safe — `fs.rmSync` unlinks the link, never the
158
+ * target.
159
+ *
160
+ * Windows note: `fs.symlinkSync` can throw EPERM for unprivileged users. That
161
+ * surfaces as an apply failure and triggers the normal rollback path, which is
162
+ * the correct outcome — refusing to proceed beats silently copying referent
163
+ * bytes.
164
+ *
165
+ * #2875 (epic #2866 Phase 6): all five fs calls routed through `installFs()`
166
+ * so this primitive can be reused on the routed install path (by
167
+ * user-artifact-staging.cts) without punching a hole through the seam Phase 5
168
+ * built. Every EXISTING caller of this function is on the migration
169
+ * plan/apply/rollback tree, which never wraps a call in `withInstallFs` — the
170
+ * ambient adapter there resolves to real `node:fs` by default, so this
171
+ * routing is behavior-preserving for them (test-matrix D2).
172
+ */
136
173
  function copyPreservingSymlink(srcPath, destPath) {
137
- if (node_fs_1.default.lstatSync(srcPath).isSymbolicLink()) {
174
+ if (installFs().lstatSync(srcPath).isSymbolicLink()) {
138
175
  // symlinkSync fails with EEXIST on an occupied path, so clear it first.
139
176
  // Scoped to this branch on purpose: the regular-file path below keeps
140
177
  // copyFileSync's overwrite-in-place, so a mid-restore failure cannot leave
141
178
  // the destination destroyed.
142
- node_fs_1.default.rmSync(destPath, { force: true });
143
- node_fs_1.default.symlinkSync(node_fs_1.default.readlinkSync(srcPath), destPath);
179
+ installFs().rmSync(destPath, { force: true });
180
+ installFs().symlinkSync(installFs().readlinkSync(srcPath), destPath);
144
181
  return;
145
182
  }
146
- node_fs_1.default.copyFileSync(srcPath, destPath);
183
+ installFs().copyFileSync(srcPath, destPath);
147
184
  }
185
+ // Shared by readInstallManifest (on the installRuntimeArtifacts call tree —
186
+ // routed) and readInstallState/readJson (not on that call tree — the
187
+ // ambient default resolves to real fs for those, unchanged). Routing once
188
+ // here is safe for all three callers.
148
189
  function readJsonIfPresent(filePath, fallback) {
149
- if (!node_fs_1.default.existsSync(filePath))
190
+ if (!installFs().existsSync(filePath))
150
191
  return fallback;
151
192
  try {
152
- return JSON.parse(node_fs_1.default.readFileSync(filePath, 'utf8'));
193
+ return JSON.parse(installFs().readFileSync(filePath, 'utf8'));
153
194
  }
154
195
  catch (error) {
155
196
  if (fallback === STRICT_JSON) {
@@ -158,17 +199,81 @@ function readJsonIfPresent(filePath, fallback) {
158
199
  return fallback;
159
200
  }
160
201
  }
202
+ /** Lowest manifest schema version that records `runtime`/`scope` (#2872). */
203
+ const MANIFEST_SCHEMA_VERSION = 2;
204
+ /**
205
+ * Longest `runtime` string this reader will report. Real runtime ids are
206
+ * registry keys (`claude`, `antigravity`, `kimi-code` — 11 chars at the
207
+ * longest), so this loses nothing legitimate; it exists because the manifest
208
+ * is attacker-influenceable (a project-local one lives inside a repository a
209
+ * user may merely have cloned) and the value reaches a consumer that renders
210
+ * it. Same 64-char convention as `truncatePostureValue`
211
+ * (`agent-install-check.cts`), deliberately, so the subsystem caps reported
212
+ * values one way.
213
+ */
214
+ const MAX_REPORTED_RUNTIME_LENGTH = 64;
215
+ /**
216
+ * A manifest's `runtime` is reported as a FACT about the file — it is
217
+ * deliberately NOT validated against the capability registry, because an
218
+ * unregistered id is exactly the kind of mismatch the Installed Surface
219
+ * Resolver exists to surface (#2872 design row B8). It is, however, LENGTH
220
+ * bounded: "report the fact" never required "report unbounded bytes".
221
+ */
222
+ function normalizeReportedRuntime(raw) {
223
+ if (typeof raw !== 'string')
224
+ return null;
225
+ if (raw.trim() === '')
226
+ return null;
227
+ return raw.length > MAX_REPORTED_RUNTIME_LENGTH
228
+ ? `${raw.slice(0, MAX_REPORTED_RUNTIME_LENGTH)}…`
229
+ : raw;
230
+ }
231
+ /**
232
+ * Normalize a raw `manifestVersion`. Only a finite integer >= 1 is a version
233
+ * claim; everything else (absent, `"2"`, `0`, `-1`, `2.5`, `NaN`, `Infinity`)
234
+ * reads as `1` — a pre-#2872 manifest. Liberal in what it accepts, but the
235
+ * normalization is a stated value rather than a silent guess: a caller can
236
+ * always tell v1 (`1`) from "no manifest at all" (`null`).
237
+ */
238
+ function normalizeManifestVersion(raw) {
239
+ if (typeof raw !== 'number')
240
+ return 1;
241
+ if (!Number.isInteger(raw))
242
+ return 1;
243
+ if (raw < 1)
244
+ return 1;
245
+ return raw;
246
+ }
161
247
  function readInstallManifest(configDir) {
162
248
  const manifest = readJsonIfPresent(node_path_1.default.join(configDir, MANIFEST_NAME), null);
163
- if (!manifest || typeof manifest !== 'object') {
164
- return { version: null, timestamp: null, mode: null, files: {} };
249
+ // `typeof [] === 'object'` in JS, so a bare `typeof !== 'object'` guard lets
250
+ // a top-level JSON array (valid JSON, but not the manifest's documented
251
+ // object shape) fall through to the field reads below — `m.manifestVersion`
252
+ // reads `undefined` off an array, which `normalizeManifestVersion` then
253
+ // reports as `1` (a v1 manifest), misclassifying "not an object" as
254
+ // "installed". `Array.isArray` closes that gap explicitly rather than
255
+ // relying on the object-shape checks below to catch it incidentally.
256
+ if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
257
+ return {
258
+ version: null,
259
+ timestamp: null,
260
+ mode: null,
261
+ files: {},
262
+ manifestVersion: null,
263
+ runtime: null,
264
+ scope: null,
265
+ };
165
266
  }
166
267
  const m = manifest;
268
+ const rawRuntime = m.runtime;
167
269
  return {
168
270
  version: typeof m.version === 'string' ? m.version : null,
169
271
  timestamp: typeof m.timestamp === 'string' ? m.timestamp : null,
170
272
  mode: typeof m.mode === 'string' ? m.mode : null,
171
273
  files: m.files && typeof m.files === 'object' ? m.files : {},
274
+ manifestVersion: normalizeManifestVersion(m.manifestVersion),
275
+ runtime: normalizeReportedRuntime(rawRuntime),
276
+ scope: (0, install_scope_cjs_1.isInstallScopeId)(m.scope) ? m.scope : null,
172
277
  };
173
278
  }
174
279
  function readInstallState(configDir) {
@@ -235,7 +340,7 @@ function classifyArtifact(configDir, relPath, manifest) {
235
340
  const normalized = normalizeRelPath(relPath);
236
341
  const originalHash = manifest.files[normalized] || null;
237
342
  const fullPath = node_path_1.default.join(configDir, normalized);
238
- if (!node_fs_1.default.existsSync(fullPath)) {
343
+ if (!installFs().existsSync(fullPath)) {
239
344
  return { classification: originalHash ? 'managed-missing' : 'missing', originalHash, currentHash: null };
240
345
  }
241
346
  const currentHash = sha256File(fullPath);
@@ -400,17 +505,20 @@ function acquireInstallMigrationLock(configDir, { timeoutMs = DEFAULT_LOCK_TIMEO
400
505
  let lockCreatedByUs = false;
401
506
  try {
402
507
  fd = node_fs_1.default.openSync(lockPath, 'wx');
403
- // Close the open descriptor before writing so the file handle is
404
- // released on Windows before the release closure unlinks it.
405
- // Write payload via writeFileSync with the path (not the fd) so we
406
- // don't hold an open fd across the lifetime of the lock.
407
- node_fs_1.default.closeSync(fd);
408
- fd = null;
409
508
  lockCreatedByUs = true; // we own the file; clean it up on any subsequent error
410
- node_fs_1.default.writeFileSync(lockPath, JSON.stringify({
509
+ // Write the payload through the exclusively-created descriptor: a
510
+ // second open-by-path here would be a TOCTOU window (CWE-367) where a
511
+ // co-writer of the directory could symlink-swap the just-created empty
512
+ // lock file before the payload lands.
513
+ node_fs_1.default.writeFileSync(fd, JSON.stringify({
411
514
  pid: process.pid,
412
515
  acquiredAt: new Date().toISOString(),
413
516
  }) + '\n');
517
+ // Close before returning so no handle stays open across the lock's
518
+ // lifetime — Windows cannot unlink a file with an open handle when the
519
+ // release closure runs.
520
+ node_fs_1.default.closeSync(fd);
521
+ fd = null;
414
522
  lockCreatedByUs = false; // release closure owns cleanup from here
415
523
  return () => {
416
524
  const failures = [];
@@ -937,8 +1045,10 @@ module.exports = {
937
1045
  acquireInstallMigrationLock,
938
1046
  applyInstallerMigrationPlan,
939
1047
  classifyArtifact,
1048
+ copyPreservingSymlink,
940
1049
  discoverInstallerMigrations,
941
1050
  evaluateRemoveEmptyDir,
1051
+ MANIFEST_SCHEMA_VERSION,
942
1052
  migrationChecksum,
943
1053
  planInstallerMigrations,
944
1054
  readInstallManifest,
@@ -23,6 +23,9 @@ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs")
23
23
  // eslint-disable-next-line @typescript-eslint/no-require-imports
24
24
  const capabilityStateMod = require("./capability-state.cjs");
25
25
  const { isCapabilityActive } = capabilityStateMod;
26
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
27
+ const ioMod = require("./io.cjs");
28
+ const { formatDiagnosticToken } = ioMod;
26
29
  // ─── Constants ───────────────────────────────────────────────────────────────
27
30
  const INTEL_DIR = '.planning/intel';
28
31
  const INTEL_FILES = {
@@ -32,6 +35,16 @@ const INTEL_FILES = {
32
35
  arch: 'arch-decisions.json',
33
36
  stack: 'stack.json',
34
37
  };
38
+ /**
39
+ * ADR-3473 §8.5 / #3885: recursion bound for the intel JSON search walk.
40
+ * Restored from the retired SDK lineage (`sdk/src/query/intel.ts` at `11918dcc3^`),
41
+ * lost in the ADR-0174 consolidation. Unlike the original, hitting the ceiling is
42
+ * NOT reported as a silent "no match" — the walk that stops early sets a
43
+ * `truncated` flag threaded back up to `intelQuery`'s result (Decision 4: a
44
+ * routine that discards an input says so). The bound is on DEPTH only; breadth
45
+ * (sibling count at any given depth) is unaffected.
46
+ */
47
+ const MAX_JSON_SEARCH_DEPTH = 48;
35
48
  // ─── Internal helpers ────────────────────────────────────────────────────────
36
49
  /**
37
50
  * Ensure the intel directory exists under the given planning dir.
@@ -71,16 +84,42 @@ function intelFilePath(planningDir, filename) {
71
84
  }
72
85
  /**
73
86
  * Safely read and parse a JSON intel file.
74
- * Returns null if file doesn't exist or can't be parsed.
87
+ *
88
+ * Returns null for THREE distinct on-disk states, only one of which is a
89
+ * "quiet" case (#3885, ADR-3473 §8.5):
90
+ * - ABSENT (ENOENT, via platformReadSync returning null): silent — not
91
+ * every project has every intel file, and callers already loop over
92
+ * the full INTEL_FILES set expecting misses. Never pushed to `errors`.
93
+ * - UNREADABLE (EACCES/EIO/... — platformReadSync rethrows anything that
94
+ * isn't ENOENT): surfaced, naming the file, when `errors` is supplied.
95
+ * - MALFORMED (JSON.parse throws on a file that WAS read successfully):
96
+ * also surfaced, naming the file — a corrupt intel file used to read
97
+ * identically to "no matches", which is the same defect one layer down.
98
+ *
99
+ * `errors` is an optional accumulator so callers that want to thread the
100
+ * outcome into their own result shape can pass an array and read it back
101
+ * after the call; callers that omit it keep the prior fold-to-null shape.
75
102
  */
76
- function safeReadJson(filePath) {
103
+ function safeReadJson(filePath, errors) {
104
+ let raw;
105
+ try {
106
+ raw = (0, shell_command_projection_cjs_1.platformReadSync)(filePath);
107
+ }
108
+ catch (err) {
109
+ if (errors) {
110
+ errors.push(`Could not read ${formatDiagnosticToken(filePath)}: ${formatDiagnosticToken(err?.message ?? String(err))}`);
111
+ }
112
+ return null;
113
+ }
114
+ if (raw === null)
115
+ return null; // ENOENT — genuinely absent, not an error.
77
116
  try {
78
- const raw = (0, shell_command_projection_cjs_1.platformReadSync)(filePath);
79
- if (raw === null)
80
- return null;
81
117
  return JSON.parse(raw);
82
118
  }
83
- catch {
119
+ catch (err) {
120
+ if (errors) {
121
+ errors.push(`Could not parse ${formatDiagnosticToken(filePath)}: ${formatDiagnosticToken(err?.message ?? String(err))}`);
122
+ }
84
123
  return null;
85
124
  }
86
125
  }
@@ -101,16 +140,17 @@ function hashFile(filePath) {
101
140
  }
102
141
  /**
103
142
  * Search for a term (case-insensitive) in a JSON object's keys and string values.
104
- * Returns an array of matching entries.
143
+ * Returns matching entries plus whether the walk hit MAX_JSON_SEARCH_DEPTH.
105
144
  */
106
145
  function searchJsonEntries(data, term) {
107
146
  if (!data || typeof data !== 'object')
108
- return [];
147
+ return { matches: [], truncated: false };
109
148
  const entries = data.entries || data;
110
149
  if (!entries || typeof entries !== 'object')
111
- return [];
150
+ return { matches: [], truncated: false };
112
151
  const lowerTerm = term.toLowerCase();
113
152
  const matches = [];
153
+ const state = { truncated: false };
114
154
  for (const [key, value] of Object.entries(entries)) {
115
155
  if (key === '_meta')
116
156
  continue;
@@ -119,25 +159,40 @@ function searchJsonEntries(data, term) {
119
159
  matches.push({ key, value });
120
160
  continue;
121
161
  }
122
- // Check string value match (recursive for objects)
123
- if (matchesInValue(value, lowerTerm)) {
162
+ // Check string value match (recursive for objects/arrays, bounded by depth)
163
+ if (matchesInValue(value, lowerTerm, 0, state)) {
124
164
  matches.push({ key, value });
125
165
  }
126
166
  }
127
- return matches;
167
+ return { matches, truncated: state.truncated };
128
168
  }
129
169
  /**
130
170
  * Recursively check if a term appears in any string value.
171
+ *
172
+ * `depth` counts container unwraps already performed (starts at 0 for the
173
+ * entry's own value). Strings never fail the ceiling check themselves — only
174
+ * a container (object/array) refuses to recurse one level deeper once
175
+ * `depth > MAX_JSON_SEARCH_DEPTH`, at which point `state.truncated` is set so
176
+ * the caller can report "I stopped looking" rather than a bare "no match".
177
+ * The bound is on nesting depth only, never on sibling breadth.
131
178
  */
132
- function matchesInValue(value, lowerTerm) {
179
+ function matchesInValue(value, lowerTerm, depth, state) {
133
180
  if (typeof value === 'string') {
134
181
  return value.toLowerCase().includes(lowerTerm);
135
182
  }
136
183
  if (Array.isArray(value)) {
137
- return value.some(v => matchesInValue(v, lowerTerm));
184
+ if (depth > MAX_JSON_SEARCH_DEPTH) {
185
+ state.truncated = true;
186
+ return false;
187
+ }
188
+ return value.some(v => matchesInValue(v, lowerTerm, depth + 1, state));
138
189
  }
139
190
  if (value && typeof value === 'object') {
140
- return Object.values(value).some(v => matchesInValue(v, lowerTerm));
191
+ if (depth > MAX_JSON_SEARCH_DEPTH) {
192
+ state.truncated = true;
193
+ return false;
194
+ }
195
+ return Object.values(value).some(v => matchesInValue(v, lowerTerm, depth + 1, state));
141
196
  }
142
197
  return false;
143
198
  }
@@ -150,19 +205,23 @@ function intelQuery(term, planningDir) {
150
205
  return disabledResponse();
151
206
  const matches = [];
152
207
  let total = 0;
208
+ let truncated = false;
209
+ const readErrors = [];
153
210
  // Search all JSON intel files
154
211
  for (const [_key, filename] of Object.entries(INTEL_FILES)) {
155
212
  const filePath = intelFilePath(planningDir, filename);
156
- const data = safeReadJson(filePath);
213
+ const data = safeReadJson(filePath, readErrors);
157
214
  if (!data)
158
215
  continue;
159
- const found = searchJsonEntries(data, term);
216
+ const { matches: found, truncated: fileTruncated } = searchJsonEntries(data, term);
217
+ if (fileTruncated)
218
+ truncated = true;
160
219
  if (found.length > 0) {
161
220
  matches.push({ source: filename, entries: found });
162
221
  total += found.length;
163
222
  }
164
223
  }
165
- return { matches, term, total };
224
+ return { matches, term, total, truncated, read_errors: readErrors };
166
225
  }
167
226
  /**
168
227
  * Report status and staleness of each intel file.
@@ -179,13 +238,14 @@ function intelStatus(planningDir) {
179
238
  const filePath = intelFilePath(planningDir, filename);
180
239
  const exists = node_fs_1.default.existsSync(filePath);
181
240
  if (!exists) {
182
- files[filename] = { exists: false, updated_at: null, stale: true };
241
+ files[filename] = { exists: false, updated_at: null, stale: true, read_error: null };
183
242
  overallStale = true;
184
243
  continue;
185
244
  }
186
245
  let updatedAt = null;
187
246
  // All intel files are JSON — read _meta.updated_at
188
- const data = safeReadJson(filePath);
247
+ const readErrors = [];
248
+ const data = safeReadJson(filePath, readErrors);
189
249
  if (data && data._meta && data._meta.updated_at) {
190
250
  updatedAt = data._meta.updated_at;
191
251
  }
@@ -196,7 +256,7 @@ function intelStatus(planningDir) {
196
256
  }
197
257
  if (stale)
198
258
  overallStale = true;
199
- files[filename] = { exists: true, updated_at: updatedAt, stale };
259
+ files[filename] = { exists: true, updated_at: updatedAt, stale, read_error: readErrors[0] ?? null };
200
260
  }
201
261
  return { files, overall_stale: overallStale };
202
262
  }
@@ -207,9 +267,15 @@ function intelDiff(planningDir) {
207
267
  if (!isIntelCapabilityActive(planningDir))
208
268
  return disabledResponse();
209
269
  const snapshotPath = intelFilePath(planningDir, '.last-refresh.json');
210
- const snapshot = safeReadJson(snapshotPath);
270
+ const readErrors = [];
271
+ const snapshot = safeReadJson(snapshotPath, readErrors);
211
272
  if (!snapshot) {
212
- return { no_baseline: true };
273
+ // #3885 (ADR-3473 §8.5): `no_baseline: true` stays true for BOTH a
274
+ // genuinely-never-snapshotted project (ENOENT, read_error stays null)
275
+ // AND a present-but-unreadable/malformed snapshot — collapsing those
276
+ // two into a bare `no_baseline: true` would manufacture "you've never
277
+ // run a refresh" out of a read failure. `read_error` names which one.
278
+ return { no_baseline: true, read_error: readErrors[0] ?? null };
213
279
  }
214
280
  const prevHashes = snapshot.hashes || {};
215
281
  const changed = [];
@@ -374,7 +440,9 @@ function intelApiSurface(planningDir) {
374
440
  const intelPath = ensureIntelDir(planningDir);
375
441
  const apiMapPath = node_path_1.default.join(intelPath, INTEL_FILES.apis);
376
442
  const outputPath = node_path_1.default.join(intelPath, 'API-SURFACE.md');
377
- const data = safeReadJson(apiMapPath);
443
+ const readErrors = [];
444
+ const data = safeReadJson(apiMapPath, readErrors);
445
+ const readError = readErrors[0] ?? null;
378
446
  const entries = (data && data.entries && typeof data.entries === 'object')
379
447
  ? Object.entries(data.entries)
380
448
  : [];
@@ -392,7 +460,14 @@ function intelApiSurface(planningDir) {
392
460
  lines.push('> Generated from `.planning/intel/api-map.json`. Do not edit by hand.');
393
461
  lines.push('');
394
462
  if (symbolCount === 0) {
395
- lines.push('> **Incomplete:** api-map.json has no entries (intel extraction is regex/JS-only or not yet populated).');
463
+ if (readError) {
464
+ // #3885: a read/parse failure is NOT "not yet populated" — say so,
465
+ // rather than manufacturing the wrong reason for the empty surface.
466
+ lines.push(`> **Incomplete:** ${readError}`);
467
+ }
468
+ else {
469
+ lines.push('> **Incomplete:** api-map.json has no entries (intel extraction is regex/JS-only or not yet populated).');
470
+ }
396
471
  lines.push('> Treat absence here as "unknown", not "does not exist".');
397
472
  lines.push('');
398
473
  }
@@ -414,7 +489,7 @@ function intelApiSurface(planningDir) {
414
489
  }
415
490
  }
416
491
  (0, shell_command_projection_cjs_1.platformWriteSync)(outputPath, lines.join('\n'));
417
- return { written: outputPath, symbolCount, stale };
492
+ return { written: outputPath, symbolCount, stale, read_error: readError };
418
493
  }
419
494
  /**
420
495
  * Patch _meta.updated_at in a JSON intel file to the current timestamp.