@opengsd/gsd-core 1.9.1 → 1.11.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 (426) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.opencode/plugins/gsd-core.js +8 -1
  4. package/agents/gsd-code-fixer.md +27 -3
  5. package/agents/gsd-debug-session-manager.md +11 -0
  6. package/agents/gsd-debugger.md +12 -246
  7. package/agents/gsd-doc-synthesizer.md +2 -4
  8. package/agents/gsd-executor.md +12 -10
  9. package/agents/gsd-integration-checker.md +3 -0
  10. package/agents/gsd-mempalace-curator.md +5 -2
  11. package/agents/gsd-phase-researcher.md +20 -1
  12. package/agents/gsd-plan-checker.md +46 -0
  13. package/agents/gsd-planner.md +49 -54
  14. package/agents/gsd-roadmapper.md +21 -3
  15. package/agents/gsd-user-profiler.md +3 -0
  16. package/agents/gsd-verifier.md +26 -73
  17. package/bin/install.js +1272 -1238
  18. package/bin/lib/ui-safety-gate.cjs +2 -0
  19. package/commands/gsd/code-review.md +1 -1
  20. package/commands/gsd/execute-phase.md +1 -1
  21. package/commands/gsd/map-codebase.md +1 -1
  22. package/commands/gsd/mempalace-capture.md +2 -2
  23. package/commands/gsd/mempalace-recall.md +1 -1
  24. package/commands/gsd/new-milestone.md +2 -2
  25. package/commands/gsd/plan-phase.md +1 -1
  26. package/commands/gsd/quick.md +1 -1
  27. package/commands/gsd/review-backlog.md +2 -1
  28. package/commands/gsd/verify-work.md +1 -1
  29. package/gsd-core/bin/gsd-tools.cjs +1009 -115
  30. package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
  31. package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
  32. package/gsd-core/bin/lib/api-coverage.cjs +123 -5
  33. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  34. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  35. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  36. package/gsd-core/bin/lib/audit.cjs +926 -202
  37. package/gsd-core/bin/lib/broken-windows.cjs +36 -6
  38. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  39. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  40. package/gsd-core/bin/lib/capability-registry.cjs +608 -148
  41. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  42. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  43. package/gsd-core/bin/lib/capability-validator.cjs +507 -24
  44. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  45. package/gsd-core/bin/lib/check-command-router.cjs +114 -38
  46. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  47. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  48. package/gsd-core/bin/lib/command-aliases.cjs +94 -0
  49. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  50. package/gsd-core/bin/lib/commands.cjs +665 -99
  51. package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
  52. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  53. package/gsd-core/bin/lib/config-loader.cjs +76 -0
  54. package/gsd-core/bin/lib/config.cjs +22 -2
  55. package/gsd-core/bin/lib/context-composer.cjs +278 -0
  56. package/gsd-core/bin/lib/context-predicates.cjs +506 -0
  57. package/gsd-core/bin/lib/core-utils.cjs +217 -40
  58. package/gsd-core/bin/lib/decisions.cjs +23 -0
  59. package/gsd-core/bin/lib/docs.cjs +3 -2
  60. package/gsd-core/bin/lib/external-job.cjs +19 -4
  61. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  62. package/gsd-core/bin/lib/frontmatter.cjs +239 -32
  63. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  64. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
  65. package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
  66. package/gsd-core/bin/lib/graphify.cjs +142 -27
  67. package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
  68. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  69. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  71. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  72. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  73. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  74. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  75. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  76. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  77. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  78. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  79. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  80. package/gsd-core/bin/lib/host-integration.cjs +13 -1
  81. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  82. package/gsd-core/bin/lib/init-command-router.cjs +83 -8
  83. package/gsd-core/bin/lib/init.cjs +1325 -169
  84. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  85. package/gsd-core/bin/lib/install-engine.cjs +805 -264
  86. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  87. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  88. package/gsd-core/bin/lib/install-profiles.cjs +160 -57
  89. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  90. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  91. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  92. package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
  93. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  94. package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
  95. package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
  96. package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
  97. package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
  98. package/gsd-core/bin/lib/io.cjs +38 -3
  99. package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
  100. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  101. package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
  102. package/gsd-core/bin/lib/mcp-server.cjs +135 -3
  103. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  104. package/gsd-core/bin/lib/milestone.cjs +821 -109
  105. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  106. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  107. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  108. package/gsd-core/bin/lib/pattern.cjs +122 -0
  109. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  110. package/gsd-core/bin/lib/phase-id.cjs +507 -36
  111. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  112. package/gsd-core/bin/lib/phase-locator.cjs +258 -58
  113. package/gsd-core/bin/lib/phase.cjs +891 -156
  114. package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
  115. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  116. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  117. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  118. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  119. package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
  120. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  121. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  122. package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
  123. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  124. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
  125. package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
  126. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  127. package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
  128. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  129. package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
  130. package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
  131. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  132. package/gsd-core/bin/lib/roadmap.cjs +405 -84
  133. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
  134. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  135. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
  136. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  137. package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
  138. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
  139. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  140. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  141. package/gsd-core/bin/lib/section-manifest.cjs +209 -0
  142. package/gsd-core/bin/lib/security.cjs +104 -5
  143. package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
  144. package/gsd-core/bin/lib/smart-entry.cjs +154 -22
  145. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  146. package/gsd-core/bin/lib/state-document.cjs +152 -8
  147. package/gsd-core/bin/lib/state-transition.cjs +424 -105
  148. package/gsd-core/bin/lib/state.cjs +1927 -401
  149. package/gsd-core/bin/lib/surface.cjs +35 -10
  150. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  151. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  152. package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
  153. package/gsd-core/bin/lib/uat.cjs +706 -64
  154. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  155. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  156. package/gsd-core/bin/lib/unusable-input.cjs +33 -0
  157. package/gsd-core/bin/lib/update-context.cjs +8 -2
  158. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  159. package/gsd-core/bin/lib/validate.cjs +20 -6
  160. package/gsd-core/bin/lib/vendor/README.md +37 -0
  161. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  162. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  163. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  164. package/gsd-core/bin/lib/verification.cjs +287 -20
  165. package/gsd-core/bin/lib/verify.cjs +368 -880
  166. package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
  167. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
  169. package/gsd-core/bin/lib/workstream.cjs +8 -2
  170. package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
  171. package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
  172. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  173. package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
  174. package/gsd-core/references/agent-contracts.md +43 -26
  175. package/gsd-core/references/artifact-types.md +10 -3
  176. package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
  177. package/gsd-core/references/checkpoints.md +2 -2
  178. package/gsd-core/references/context-budget.md +1 -1
  179. package/gsd-core/references/debugger-techniques.md +255 -0
  180. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  181. package/gsd-core/references/doc-conflict-engine.md +1 -1
  182. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  184. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  185. package/gsd-core/references/execute-phase-response-language.md +1 -1
  186. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  187. package/gsd-core/references/gate-prompts.md +1 -1
  188. package/gsd-core/references/git-planning-commit.md +2 -1
  189. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  190. package/gsd-core/references/model-profiles.md +12 -4
  191. package/gsd-core/references/mvp-concepts.md +9 -9
  192. package/gsd-core/references/planner-guidance.md +3 -9
  193. package/gsd-core/references/planner-preconditions.md +1 -1
  194. package/gsd-core/references/planner-reviews.md +1 -1
  195. package/gsd-core/references/planning-config.md +8 -6
  196. package/gsd-core/references/research-documentation-lookup.md +5 -3
  197. package/gsd-core/references/revision-loop.md +1 -1
  198. package/gsd-core/references/specless-probe-fallback.md +8 -7
  199. package/gsd-core/references/universal-anti-patterns.md +3 -3
  200. package/gsd-core/references/verifier-phase-gates.md +192 -0
  201. package/gsd-core/references/verifier-wiring-patterns.md +100 -0
  202. package/gsd-core/references/verify-mvp-mode.md +1 -1
  203. package/gsd-core/references/workstream-flag.md +22 -6
  204. package/gsd-core/references/worktree-branch-check.md +2 -2
  205. package/gsd-core/templates/discussion-log.md +1 -1
  206. package/gsd-core/templates/phase-prompt.md +2 -4
  207. package/gsd-core/templates/state.md +4 -4
  208. package/gsd-core/templates/summary-complex.md +2 -0
  209. package/gsd-core/templates/summary-minimal.md +2 -0
  210. package/gsd-core/templates/summary-standard.md +2 -0
  211. package/gsd-core/templates/summary.md +2 -0
  212. package/gsd-core/templates/verification-report.md +9 -1
  213. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  214. package/gsd-core/workflows/audit-milestone.md +3 -0
  215. package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
  216. package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
  217. package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
  218. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
  219. package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
  220. package/gsd-core/workflows/autonomous.md +33 -70
  221. package/gsd-core/workflows/cleanup.md +62 -3
  222. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
  223. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
  224. package/gsd-core/workflows/code-review-fix.md +37 -10
  225. package/gsd-core/workflows/code-review.md +74 -166
  226. package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
  227. package/gsd-core/workflows/complete-milestone.md +160 -95
  228. package/gsd-core/workflows/debug.md +16 -17
  229. package/gsd-core/workflows/diagnose-issues.md +56 -8
  230. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  231. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  232. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
  233. package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
  234. package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
  235. package/gsd-core/workflows/docs-update.md +8 -51
  236. package/gsd-core/workflows/edit-phase.md +26 -1
  237. package/gsd-core/workflows/eval-review.md +3 -5
  238. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
  239. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
  240. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
  241. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  242. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
  243. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
  245. package/gsd-core/workflows/execute-phase.md +103 -187
  246. package/gsd-core/workflows/execute-plan.md +36 -4
  247. package/gsd-core/workflows/explore.md +131 -4
  248. package/gsd-core/workflows/fast.md +10 -2
  249. package/gsd-core/workflows/health.md +73 -4
  250. package/gsd-core/workflows/help/modes/full.md +6 -1
  251. package/gsd-core/workflows/import.md +4 -4
  252. package/gsd-core/workflows/ingest-docs.md +7 -6
  253. package/gsd-core/workflows/mvp-phase.md +6 -3
  254. package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
  255. package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
  256. package/gsd-core/workflows/new-milestone.md +35 -47
  257. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
  258. package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
  259. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
  260. package/gsd-core/workflows/new-project.md +27 -240
  261. package/gsd-core/workflows/next.md +12 -0
  262. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
  263. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
  264. package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
  265. package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
  266. package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
  267. package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
  268. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
  269. package/gsd-core/workflows/plan-phase.md +89 -209
  270. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  271. package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
  272. package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
  273. package/gsd-core/workflows/progress.md +45 -159
  274. package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
  275. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
  276. package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
  277. package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
  278. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
  279. package/gsd-core/workflows/quick.md +55 -405
  280. package/gsd-core/workflows/resume-project.md +3 -0
  281. package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
  282. package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
  283. package/gsd-core/workflows/review.md +41 -13
  284. package/gsd-core/workflows/section-manifest.json +219 -0
  285. package/gsd-core/workflows/secure-phase.md +1 -1
  286. package/gsd-core/workflows/session-report.md +2 -1
  287. package/gsd-core/workflows/settings.md +66 -2
  288. package/gsd-core/workflows/ship.md +104 -44
  289. package/gsd-core/workflows/sketch.md +1 -1
  290. package/gsd-core/workflows/spec-phase.md +41 -20
  291. package/gsd-core/workflows/spike-wrap-up.md +20 -5
  292. package/gsd-core/workflows/spike.md +50 -16
  293. package/gsd-core/workflows/sync-skills.md +106 -13
  294. package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
  295. package/gsd-core/workflows/transition.md +53 -31
  296. package/gsd-core/workflows/ui-phase.md +13 -12
  297. package/gsd-core/workflows/ui-review.md +2 -2
  298. package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
  299. package/gsd-core/workflows/update.md +19 -8
  300. package/gsd-core/workflows/validate-phase.md +1 -1
  301. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
  302. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
  303. package/gsd-core/workflows/verify-work.md +17 -65
  304. package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
  305. package/hooks/dist/gsd-check-update-worker.js +64 -12
  306. package/hooks/dist/gsd-check-update.js +19 -1
  307. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  308. package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
  309. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  310. package/hooks/dist/gsd-prompt-guard.js +21 -20
  311. package/hooks/dist/gsd-read-injection-scanner.js +45 -24
  312. package/hooks/dist/gsd-statusline.js +90 -6
  313. package/hooks/dist/gsd-update-banner.js +22 -1
  314. package/hooks/dist/gsd-workflow-guard.js +134 -36
  315. package/hooks/dist/gsd-worktree-path-guard.js +2 -1
  316. package/hooks/dist/gsd-write-guard.js +359 -0
  317. package/hooks/dist/lib/git-cmd.js +92 -59
  318. package/hooks/dist/lib/injection-patterns.js +45 -0
  319. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  320. package/hooks/dist/lib/isolation-sentinel.js +277 -0
  321. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  322. package/hooks/gsd-agent-isolation-guard.js +517 -0
  323. package/hooks/gsd-check-update-worker.js +64 -12
  324. package/hooks/gsd-check-update.js +19 -1
  325. package/hooks/gsd-cursor-pre-tool.js +0 -3
  326. package/hooks/gsd-cursor-subagent-start.js +607 -26
  327. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  328. package/hooks/gsd-prompt-guard.js +21 -20
  329. package/hooks/gsd-read-injection-scanner.js +45 -24
  330. package/hooks/gsd-statusline.js +90 -6
  331. package/hooks/gsd-update-banner.js +22 -1
  332. package/hooks/gsd-workflow-guard.js +134 -36
  333. package/hooks/gsd-worktree-path-guard.js +2 -1
  334. package/hooks/gsd-write-guard.js +359 -0
  335. package/hooks/hooks.json +12 -0
  336. package/hooks/lib/git-cmd.js +92 -59
  337. package/hooks/lib/injection-patterns.js +45 -0
  338. package/hooks/lib/isolation-deny-reason.js +39 -0
  339. package/hooks/lib/isolation-sentinel.js +277 -0
  340. package/hooks/managed-hooks-registry.cjs +2 -0
  341. package/package.json +31 -10
  342. package/pi/gsd.cjs +71 -12
  343. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  344. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  345. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  346. package/scripts/build-hooks.js +9 -0
  347. package/scripts/changeset/lint.cjs +68 -6
  348. package/scripts/changeset/serialize.cjs +5 -1
  349. package/scripts/check-alias-drift.cjs +7 -43
  350. package/scripts/check-contract-drift.cjs +297 -0
  351. package/scripts/ci-test-scope.cjs +19 -2
  352. package/scripts/command-contract-helpers.cjs +903 -1
  353. package/scripts/gen-adr-index.cjs +728 -38
  354. package/scripts/gen-capability-matrix.cjs +1 -1
  355. package/scripts/gen-capability-registry.cjs +3 -15
  356. package/scripts/gen-context-index.cjs +439 -0
  357. package/scripts/gen-health-docs.cjs +390 -0
  358. package/scripts/gen-inventory-manifest.cjs +150 -4
  359. package/scripts/gen-loop-host-contract.cjs +4 -24
  360. package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
  361. package/scripts/gen-registry.cjs +3 -14
  362. package/scripts/gen-section-manifest.cjs +638 -0
  363. package/scripts/generate-package-identity.cjs +4 -2
  364. package/scripts/lib/alias-drift-families.cjs +46 -0
  365. package/scripts/lib/drift-scan.cjs +278 -0
  366. package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
  367. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  368. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  369. package/scripts/lint-canary-version-leak.cjs +73 -0
  370. package/scripts/lint-command-contract.cjs +96 -13
  371. package/scripts/lint-compiled-artifact-sync.cjs +6 -1
  372. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  373. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  374. package/scripts/lint-default-flip-documentation.cjs +193 -0
  375. package/scripts/lint-docs-command-form.cjs +195 -0
  376. package/scripts/lint-docs-required.cjs +9 -1
  377. package/scripts/lint-emitted-drift-ack.cjs +215 -20
  378. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  379. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  380. package/scripts/lint-example-parser-parity.cjs +395 -0
  381. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  382. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  383. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  384. package/scripts/lint-milestone-window-drift.cjs +468 -0
  385. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  386. package/scripts/lint-plan-count-drift.cjs +318 -0
  387. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  388. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  389. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  390. package/scripts/lint-regression-test-names.cjs +15 -13
  391. package/scripts/lint-removed-but-needed.cjs +320 -0
  392. package/scripts/lint-state-field-drift.cjs +805 -0
  393. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  394. package/scripts/lint-test-file-count.allowlist.json +40 -3
  395. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  396. package/scripts/lint-vendored-deps.cjs +124 -0
  397. package/scripts/mutation-matrix.cjs +13 -0
  398. package/scripts/pr-changed-files.cjs +63 -0
  399. package/scripts/pr-template-policy.cjs +14 -4
  400. package/scripts/prompt-injection-scan.sh +52 -6
  401. package/scripts/require-issue-link-policy.cjs +192 -0
  402. package/scripts/state-write-path-drift-baseline.json +19 -0
  403. package/scripts/sync-runtime-launcher.cjs +2 -4
  404. package/skills/gsd-autonomous/SKILL.md +0 -1
  405. package/skills/gsd-code-review/SKILL.md +1 -1
  406. package/skills/gsd-execute-phase/SKILL.md +1 -2
  407. package/skills/gsd-map-codebase/SKILL.md +1 -1
  408. package/skills/gsd-mempalace-capture/SKILL.md +2 -2
  409. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  410. package/skills/gsd-new-milestone/SKILL.md +2 -2
  411. package/skills/gsd-next/SKILL.md +0 -1
  412. package/skills/gsd-plan-phase/SKILL.md +1 -2
  413. package/skills/gsd-progress/SKILL.md +0 -1
  414. package/skills/gsd-quick/SKILL.md +1 -1
  415. package/skills/gsd-review-backlog/SKILL.md +2 -1
  416. package/skills/gsd-stats/SKILL.md +0 -1
  417. package/skills/gsd-verify-work/SKILL.md +1 -1
  418. package/vscode/package.json +1 -1
  419. package/gsd-core/workflows/discovery-phase.md +0 -298
  420. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  421. package/gsd-core/workflows/verify-phase.md +0 -577
  422. package/scripts/affected-tests-lib.cjs +0 -554
  423. package/scripts/gen-emitted-baseline.cjs +0 -145
  424. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  425. package/scripts/run-affected-tests.cjs +0 -7
  426. package/scripts/run-tests.cjs +0 -1050
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ /**
3
+ * install-scope.cts — Install Scope Module (#2870, ADR-2866, governed by
4
+ * ADR-2866, Phase 0 PR #3265).
5
+ *
6
+ * `resolveScope()` turns a bare `'global' | 'local'` string — previously
7
+ * re-derived at 12 `isGlobal ? 'global' : 'local'` sites in `bin/install.js`
8
+ * plus several downstream consumers — into ONE resolved value produced by
9
+ * ONE module. See `.gsd/phase/feat-2870-install-scope-module/40-design.md`
10
+ * for the full behavior table and rationale; the summary that matters for
11
+ * future readers is captured in the comments below.
12
+ *
13
+ * This module OWNS the `InstallScope` type name. It was previously declared
14
+ * (as a private, non-exported `TypeAlias`) inside
15
+ * `runtime-artifact-install-plan.cts`; that module now imports it from here
16
+ * instead of re-declaring it, so the codebase has one spelling of "install
17
+ * scope" instead of a fifth one appearing alongside the three that already
18
+ * existed (`'local' | 'global'` in the layout module, `'global' | 'project'`
19
+ * in capability-lifecycle, and the single literal `'project'` in
20
+ * capability-consent).
21
+ *
22
+ * ── Compose, never modify, resolveConfigHomeFromDescriptor ─────────────────
23
+ * `resolveConfigHomeFromDescriptor` (`runtime-homes.cts`) is rated CRITICAL
24
+ * blast radius: 60 dependents across 13 files and 2 process flows. Adding a
25
+ * `scope` parameter to it — the "obvious" refactor — would touch all 60 for
26
+ * no reason this module needs: it already resolves the GLOBAL config home
27
+ * correctly today. So this module calls it as-is for the global scope and
28
+ * derives the LOCAL scope's config dir independently (see
29
+ * `resolveScopeConfigHome` below) — genuine composition, not a rename. Every
30
+ * one of those 60 call sites stays byte-identical.
31
+ *
32
+ * ── Why `settingsFile: null` is correct, not a bug ──────────────────────────
33
+ * Only `claude` declares `hostBehaviors.settingsFileByScope` in the
34
+ * capability registry; the other 18 registered runtimes do not have a
35
+ * per-scope settings file at all. Returning `null` for them is honest —
36
+ * substituting `'settings.json'` (or any other Claude-shaped default) would
37
+ * invent a fact for every non-Claude runtime that asked. Callers that
38
+ * legitimately want a Claude-specific fallback (there is exactly one today,
39
+ * `bin/install.js:550`) apply it themselves; this module does not.
40
+ */
41
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42
+ return (mod && mod.__esModule) ? mod : { "default": mod };
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.SCOPE_ORDER = void 0;
46
+ exports.validateScopeId = validateScopeId;
47
+ exports.isInstallScopeId = isInstallScopeId;
48
+ exports.resolveScope = resolveScope;
49
+ exports.isGlobalScope = isGlobalScope;
50
+ exports.scopeRank = scopeRank;
51
+ const node_path_1 = __importDefault(require("node:path"));
52
+ const node_os_1 = __importDefault(require("node:os"));
53
+ const runtime_homes_cjs_1 = require("./runtime-homes.cjs");
54
+ // In .cts (CommonJS output) files, `require` is available as a global.
55
+ const _require = require;
56
+ // ── The `local` / `project` boundary (see CONTEXT.md glossary entry) ──────
57
+ //
58
+ // `ConsentRecord.scope: 'project'` (capability-consent.cts) and
59
+ // `capability-lifecycle.cts:163`'s `'global' | 'project'` are NOT renamed to
60
+ // match this module's `'local'` spelling. `ConsentRecord.scope` is persisted
61
+ // on disk in user-owned consent records outside this repo; renaming that
62
+ // literal would silently invalidate every existing project-scoped consent
63
+ // record on a user's machine the next time it is read back. The
64
+ // reconciliation is a documented boundary mapping, not a rename sweep:
65
+ // install scope 'local' ⇄ consent scope 'project'
66
+ // install scope 'global' ⇄ no consent record at all
67
+ // `consentRequired` above reports the install-scope side of that mapping;
68
+ // it is deliberately still the CLI's own vocabulary.
69
+ const VALID_SCOPE_IDS = new Set(['global', 'local']);
70
+ /**
71
+ * Single owner of the `'global' | 'local'` membership check. `resolveScope`,
72
+ * `isGlobalScope`, `scopeRank`, and `resolveTriggerSurface`
73
+ * (`runtime-artifact-layout.cts`, #2871 Phase 2) all call this instead of
74
+ * each carrying its own copy of the rule — one validator every scope-typed
75
+ * seam reads, not N validators that could silently diverge. Exported so a
76
+ * sibling module can reuse it directly rather than re-deriving the same
77
+ * membership check a second time.
78
+ */
79
+ function validateScopeId(id, caller) {
80
+ if (typeof id !== 'string' || !VALID_SCOPE_IDS.has(id)) {
81
+ throw new TypeError(`${caller}: id must be one of 'global' | 'local', got ${JSON.stringify(id)}`);
82
+ }
83
+ return id;
84
+ }
85
+ /**
86
+ * Non-throwing sibling of {@link validateScopeId}, for readers that must
87
+ * report an unrecognized scope as a value rather than fail (#2872). Reads the
88
+ * same `VALID_SCOPE_IDS` set, so the two can never disagree about what a
89
+ * scope is.
90
+ */
91
+ function isInstallScopeId(value) {
92
+ return typeof value === 'string' && VALID_SCOPE_IDS.has(value);
93
+ }
94
+ // Higher wins. Not exported as a public constant — only the resulting
95
+ // `hostPrecedenceRank` field on `ResolvedScope` is public API, so a future
96
+ // re-basing of the literal values (Phase 2, #2871) never requires touching
97
+ // an exported symbol.
98
+ const HOST_PRECEDENCE_RANK = {
99
+ global: 2,
100
+ local: 1,
101
+ };
102
+ /** Lazy registry accessor — mirrors the pattern in runtime-homes.cts /
103
+ * runtime-artifact-layout.cts (5b/5c/5d). */
104
+ function getRegistry() {
105
+ return _require('./capability-registry.cjs');
106
+ }
107
+ /**
108
+ * Normalize path separators UNCONDITIONALLY (never gated on `path.sep` /
109
+ * `process.platform`). A Windows-shaped `home` (`C:\Users\x`) can arrive on
110
+ * any host — via an injected test fixture, a cross-platform config sync, or
111
+ * a value copied from a Windows machine — so the normalization must not
112
+ * depend on which OS this process happens to be running on.
113
+ */
114
+ function normalizeSeparators(p) {
115
+ return p.replace(/\\/g, '/');
116
+ }
117
+ /**
118
+ * Minimal leading-`~` expansion for `explicitDir`. `runtime-homes.cts`'s own
119
+ * `expandTilde` is NOT exported (it is a private helper), and this module
120
+ * must not add exports to that CRITICAL-blast-radius file just to reuse
121
+ * three lines — so this is an intentionally small, independent
122
+ * reimplementation, not a fork of shared logic.
123
+ */
124
+ function expandTildeForExplicitDir(p, home) {
125
+ const resolvedHome = home ?? node_os_1.default.homedir();
126
+ if (p === '~')
127
+ return resolvedHome;
128
+ if (p.startsWith('~/'))
129
+ return node_path_1.default.join(resolvedHome, p.slice(2));
130
+ return p;
131
+ }
132
+ /**
133
+ * Resolve the config-home directory for one scope. `explicitDir` short-
134
+ * circuits both scopes identically (matches `getGlobalConfigDir`'s existing
135
+ * override behavior — the module must not regress it). Otherwise:
136
+ * - `global`: delegates entirely to `resolveConfigHomeFromDescriptor`
137
+ * (composition — see the module-level comment).
138
+ * - `local`: joins the registry's `localConfigDir` onto `cwd` (defaulting
139
+ * to the real process cwd) — the project-local dir, independent of
140
+ * `home`/`env`.
141
+ */
142
+ function resolveScopeConfigHome(id, descriptor, input) {
143
+ const explicitDir = input.explicitDir;
144
+ if (typeof explicitDir === 'string' && explicitDir.trim() !== '') {
145
+ return normalizeSeparators(expandTildeForExplicitDir(explicitDir, input.home));
146
+ }
147
+ if (id === 'local') {
148
+ // localConfigDir is guaranteed non-null here: the only registered
149
+ // runtime with `localConfigDir: null` is vscode, and vscode's
150
+ // `configHome.kind === 'none'` already causes resolveScope to throw
151
+ // before this function is ever called (see the 'none' guard below).
152
+ const localConfigDir = descriptor.localConfigDir;
153
+ const cwd = input.cwd ?? process.cwd();
154
+ return normalizeSeparators(node_path_1.default.join(cwd, localConfigDir));
155
+ }
156
+ return normalizeSeparators((0, runtime_homes_cjs_1.resolveConfigHomeFromDescriptor)(descriptor.configHome, {
157
+ env: input.env,
158
+ home: input.home,
159
+ existsSync: input.existsSync,
160
+ }));
161
+ }
162
+ /**
163
+ * Resolve a bare `'global' | 'local'` scope id plus a runtime into a single
164
+ * `ResolvedScope` value: the config directory, the per-scope settings
165
+ * filename (or `null`), whether the scope requires a consent record, and a
166
+ * precedence rank (data only this phase — see `hostPrecedenceRank` above).
167
+ *
168
+ * Pure: performs no writes and no I/O of its own beyond what
169
+ * `resolveConfigHomeFromDescriptor` already performs via the injected
170
+ * `existsSync` (for `global`) or the injected `cwd`, defaulting to
171
+ * `process.cwd()` (for `local`). Never mutates `input`. The returned object
172
+ * is frozen so a caller mutating the result cannot corrupt a subsequent
173
+ * call.
174
+ *
175
+ * Throws `TypeError` for:
176
+ * - an `id` outside `'global' | 'local'` — including wrong case, empty,
177
+ * missing, or any non-string value (no coercion, ever);
178
+ * - an unknown `runtime` (no matching capability-registry entry);
179
+ * - a `runtime` whose descriptor has `configHome.kind === 'none'`
180
+ * (vscode) — there is no installable config directory to resolve, so
181
+ * inventing one (or silently returning `configHome: null`) would be
182
+ * dishonest. All three cases share one catch shape (`instanceof
183
+ * TypeError`) with `resolveRuntimeArtifactLayout`'s existing contract
184
+ * for unknown runtimes, so callers of both never need two different
185
+ * catch blocks.
186
+ */
187
+ function resolveScope(input) {
188
+ const scopeId = validateScopeId(input?.id, 'resolveScope');
189
+ const runtime = input.runtime;
190
+ const registryEntry = typeof runtime === 'string'
191
+ ? getRegistry().runtimes[runtime]
192
+ : undefined;
193
+ const descriptor = registryEntry?.runtime;
194
+ if (!descriptor) {
195
+ throw new TypeError(`resolveScope: unknown runtime '${String(runtime)}' — not present in the capability registry`);
196
+ }
197
+ if (descriptor.configHome.kind === 'none') {
198
+ // #2103: vscode-shaped runtimes (Marketplace/VSIX, installSurface:
199
+ // 'none') have no file-projected config directory at all — the same
200
+ // carve-out tests/runtime-flags.test.cjs's NON_INSTALLABLE_RUNTIMES
201
+ // already documents. Throwing here matches
202
+ // resolveConfigHomeFromDescriptor's own deliberate throw on this kind,
203
+ // rather than silently inventing an install scope for a runtime that
204
+ // cannot be installed.
205
+ throw new TypeError(`resolveScope: runtime '${runtime}' has no installable config directory (configHome.kind === 'none')`);
206
+ }
207
+ const configHome = resolveScopeConfigHome(scopeId, descriptor, input);
208
+ const settingsFile = descriptor.hostBehaviors?.settingsFileByScope?.[scopeId] ?? null;
209
+ const consentRequired = scopeId === 'local';
210
+ const hostPrecedenceRank = HOST_PRECEDENCE_RANK[scopeId];
211
+ return Object.freeze({
212
+ id: scopeId,
213
+ configHome,
214
+ settingsFile,
215
+ consentRequired,
216
+ hostPrecedenceRank,
217
+ });
218
+ }
219
+ /**
220
+ * Project an `InstallScope` down to the boolean shape some downstream APIs
221
+ * still require. Four call sites (both kind-builder closures in
222
+ * `runtime-artifact-layout.cts`, plus one each in
223
+ * `runtime-artifact-install-plan.cts` and `surface.cts`) were each
224
+ * independently re-deriving this same `scope === 'global'` comparison — four
225
+ * copies of one rule that could silently drift apart (#2870). They exist
226
+ * because `runtime-artifact-conversion.cts`'s `_computePathPrefix` takes
227
+ * `isGlobal: boolean` at its API boundary, and that boundary is not changing
228
+ * here, so the boolean projection cannot be eliminated — only centralized to
229
+ * the one place below.
230
+ *
231
+ * Throws the same `TypeError`, with the same message shape, as
232
+ * `resolveScope` throws for an `id` outside `'global' | 'local'` — both call
233
+ * `validateScopeId` above, so the two error contracts cannot diverge.
234
+ *
235
+ * Deliberately throws, rather than returning `false`, for an out-of-union
236
+ * value — unlike the inline `scope === 'global'` comparison it replaced,
237
+ * which silently returned `false` for anything unrecognized. The
238
+ * alternative is silently treating an unknown scope as "not global" and
239
+ * writing artifacts to the wrong place, which is worse than failing loud.
240
+ * A caller holding an optional `scope` (e.g. a raw `Layout.scope`) must
241
+ * default it before calling this — see `surface.cts` for the pattern.
242
+ */
243
+ function isGlobalScope(scope) {
244
+ return validateScopeId(scope, 'isGlobalScope') === 'global';
245
+ }
246
+ /**
247
+ * Project a bare `InstallScope` down to its `hostPrecedenceRank` — the SAME
248
+ * `HOST_PRECEDENCE_RANK` table `resolveScope`'s `ResolvedScope.hostPrecedenceRank`
249
+ * field reads, exposed standalone so a caller that only needs the ranking (not a
250
+ * full config-home resolution, which touches the filesystem via
251
+ * `resolveConfigHomeFromDescriptor`) never has to re-derive `{global: 2, local:
252
+ * 1}` as a second copy of the same fact. First consumer: `resolveTriggerSurface`
253
+ * (`runtime-artifact-layout.cts`, #2871 Phase 2), which is documented pure — no
254
+ * filesystem — so it cannot call `resolveScope` itself. Same validation/error
255
+ * contract as `resolveScope` / `isGlobalScope`: all three share `validateScopeId`,
256
+ * so an out-of-union `id` throws the same `TypeError` shape everywhere.
257
+ */
258
+ function scopeRank(id) {
259
+ return HOST_PRECEDENCE_RANK[validateScopeId(id, 'scopeRank')];
260
+ }
261
+ /**
262
+ * Both scope ids, highest host precedence first. The ONE ordering of the
263
+ * install-scope axis: `runtime-artifact-layout.cts`'s trigger resolution and
264
+ * `installed-surface-resolver.cts`'s scope-record construction both consume
265
+ * this rather than each re-declaring `['global','local']` (#2872 review
266
+ * finding — this repo's recorded "generative fix divergence" class). Frozen so
267
+ * a caller cannot reorder it for everyone else. Ordering is not arbitrary: it
268
+ * is `scopeRank` descending, and a test locks that so the two cannot drift.
269
+ */
270
+ exports.SCOPE_ORDER = Object.freeze(['global', 'local']);
@@ -0,0 +1,385 @@
1
+ "use strict";
2
+ /**
3
+ * install-shadow-report.cts — Cross-Scope Shadow Report Module (#2873, epic
4
+ * #2866 Phase 4a — governed by
5
+ * `.gsd/phase/feat-2873-cross-scope-shadowing/40-design.md`).
6
+ *
7
+ * A read-only PROJECTION over `resolveInstalledSurfaces`
8
+ * (`installed-surface-resolver.cts`, #2872 Phase 3). That module answers
9
+ * "what is installed"; it is documented there as read-only, and rendering
10
+ * plus sanitization are a different concern with a different consumer set
11
+ * (installer + `/gsd-health`) — the design doc's "Rejected" #5 is why this is
12
+ * a separate leaf module rather than a second export bolted onto the
13
+ * resolver.
14
+ *
15
+ * ── What "shadowed" means here ──────────────────────────────────────────────
16
+ * A trigger is shadowed when `resolveTriggerSurface` (via the resolver)
17
+ * recorded a non-null `shadowedBy` for it: two scopes both installed a
18
+ * trigger-bearing artifact under the SAME trigger name, and only one wins.
19
+ * For claude (`skills`@global vs `commands`@local) the KINDS differ, so the
20
+ * loser's entire spec tree becomes unreachable through the trigger — the bug
21
+ * #2218 diagnosed. For the 12 both-scopes-`skills` runtimes the kinds are the
22
+ * SAME on both sides, so the loser is merely overridden, not vanished
23
+ * (design row #5 / "Not-corruption"). `kindsDiffer` on `ShadowReport` is what
24
+ * lets `renderShadowReport` word the two cases correctly.
25
+ *
26
+ * ── Report, don't correct (mirrors the resolver's own law) ─────────────────
27
+ * `mismatches` surfaces a declared runtime/scope that disagrees with the
28
+ * probed one (Postel's Law, design doc: liberal in what is accepted, but the
29
+ * mismatch is never silently absorbed). This module never substitutes a
30
+ * declared value for a probed one; it only reports the disagreement the
31
+ * resolver already computed.
32
+ *
33
+ * ── Sanitize at the render seam ─────────────────────────────────────────────
34
+ * `declaredRuntime` is attacker-influenceable (it comes from a manifest that
35
+ * may live inside a merely-cloned repository) and length-bounded but
36
+ * deliberately NOT charset-gated by the reader (`declaredRuntimeMatchesProbe`
37
+ * needs the raw value there). THIS module is what renders it to an operator,
38
+ * so this module owns the guard — `sanitizeForRender` strips ANSI escapes,
39
+ * C0/C1 controls, and Unicode bidi overrides/isolates, then collapses
40
+ * whitespace. It never truncates: `readInstallManifest` already caps at 64
41
+ * chars, and a second truncation here would double-truncate.
42
+ *
43
+ * Trigger names, by contrast, are already `SAFE_STEM`-gated upstream
44
+ * (`installed-surface-resolver.cts`'s `deriveStemsForKindEntry`) before they
45
+ * ever reach a `TriggerSurface` — this module does not re-gate them.
46
+ *
47
+ * ── Per-scope truth filter (why this lives HERE, not in the resolver) ──────
48
+ * `resolveOneRuntime` (`installed-surface-resolver.cts`) builds ONE union of
49
+ * every installed scope's `stems` and hands that single list to
50
+ * `resolveTriggerSurface`, which then synthesizes a candidate trigger for
51
+ * EVERY stem at EVERY installed scope's trigger-bearing kind entry —
52
+ * regardless of whether that specific scope's own manifest actually shipped
53
+ * that stem. Concretely: a global `full`-profile install (stems a, b, c)
54
+ * alongside a local `core`-profile install (stem a only) unions to
55
+ * `{a, b, c}`, and `resolveTriggerSurface` then reports `commands@local`
56
+ * candidates for b and c too — trigger names for artifacts that do not exist
57
+ * on disk at that scope. Left unfiltered, this module would tell the user
58
+ * `/gsd-b` and `/gsd-c` are shadowed local commands when there is no local
59
+ * artifact for either at all — over-reporting that is not cosmetic, since
60
+ * the whole point of this report is to make a real failure legible.
61
+ *
62
+ * `resolveTriggerSurface`'s API takes ONE stem list shared by every scope it
63
+ * is asked about, so per-scope truth cannot be expressed through it without
64
+ * either widening a shipped Phase-2 contract other callers may depend on, or
65
+ * calling it once per scope and re-implementing its winner computation
66
+ * (`isHigherPriority`) here as a second, driftable copy. `resolveOneRuntime`
67
+ * / `resolveInstalledSurfaces` (Phase 3, #2872) is likewise a shipped module
68
+ * this task deliberately leaves untouched. This module already receives the
69
+ * full `InstalledRuntimeSurface`, including each scope's own REAL `stems`
70
+ * list (`installed-surface-resolver.cts`'s `deriveStemsFromManifest`) — so
71
+ * the correction belongs here, as a filter over `resolveTriggerSurface`'s
72
+ * already-computed `shadowedBy` groups: a trigger is reported as shadowed
73
+ * only when its underlying stem is present in BOTH the winner's scope's own
74
+ * `stems` AND the shadowed side's scope's own `stems` — i.e. an artifact
75
+ * genuinely exists at both scopes, not merely "some stem exists somewhere in
76
+ * the union".
77
+ *
78
+ * `TriggerSurface` does not carry the originating stem OR the composing
79
+ * prefix on its output — only the already-composed `trigger` string
80
+ * (`${prefix}${stem}`) — so the stem cannot be read off it directly. Rather
81
+ * than hand-roll a fixed-offset `trigger.slice(4)` (which would silently
82
+ * assume every runtime's prefix is exactly `gsd-` — true today, but not a
83
+ * contract this module owns), the prefix is recovered the honest way: by
84
+ * re-resolving that scope's `ArtifactKind` layout (`resolveRuntimeArtifactLayout`
85
+ * / `resolveRuntimeArtifactLayoutFromRegistry`, the SAME layout descriptor
86
+ * `resolveTriggerSurface` itself reads its `entry.prefix` from) for the
87
+ * winner's and shadowed side's own `(scope, kind)`, and reading `.prefix`
88
+ * off the matching kind entry. This is metadata-only (constructing an
89
+ * `ArtifactKind` never touches the filesystem — see
90
+ * `runtime-artifact-layout.cts`'s kind-builder functions), so it costs
91
+ * nothing beyond a small per-`(scope,kind)` memo. If a prefix cannot be
92
+ * resolved at all (a `TypeError` from an unexpected registry shape), the
93
+ * trigger is conservatively DROPPED rather than kept — the same
94
+ * report-nothing-you-cannot-prove posture as the rest of this filter.
95
+ *
96
+ * ── Pure with respect to caller-visible state ───────────────────────────────
97
+ * `buildShadowReport` builds a fresh `ShadowReport` (fresh arrays, fresh
98
+ * objects) on every call, exactly as the resolver documents for itself
99
+ * (`installed-surface-resolver.cts`'s "Pure with respect to caller-visible
100
+ * state" paragraph) — no shared or cached state between calls.
101
+ */
102
+ Object.defineProperty(exports, "__esModule", { value: true });
103
+ exports.SHADOW_REASON = void 0;
104
+ exports.sanitizeForRender = sanitizeForRender;
105
+ exports.buildShadowReport = buildShadowReport;
106
+ exports.renderShadowReport = renderShadowReport;
107
+ const installed_surface_resolver_cjs_1 = require("./installed-surface-resolver.cjs");
108
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
109
+ const runtimeArtifactLayoutMod = require("./runtime-artifact-layout.cjs");
110
+ const { resolveRuntimeArtifactLayout, resolveRuntimeArtifactLayoutFromRegistry } = runtimeArtifactLayoutMod;
111
+ // ── Reason enum ─────────────────────────────────────────────────────────
112
+ exports.SHADOW_REASON = Object.freeze({
113
+ NOT_SHADOWED: 'not_shadowed',
114
+ SCOPE_SHADOWED: 'scope_shadowed',
115
+ RESOLVER_UNAVAILABLE: 'resolver_unavailable',
116
+ });
117
+ // ── Sanitization ────────────────────────────────────────────────────────
118
+ /** CSI (`\x1b[...final`) and OSC (`\x1b]...BEL-or-ST`) sequences. An
119
+ * unterminated/malformed sequence is left for the C0-control strip below to
120
+ * remove the bare `\x1b` byte — liberal, never a throw. */
121
+ const ANSI_RE = /\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\))/g;
122
+ /** C0 controls (`\x00`-`\x1f`, including any `\x1b` the ANSI strip above did
123
+ * not consume) and DEL/C1 (`\x7f`-`\x9f`). */
124
+ const CONTROL_RE = /[\x00-\x1f\x7f-\x9f]/g;
125
+ /** Unicode bidi embedding/override controls (U+202A-U+202E) and bidi
126
+ * isolates (U+2066-U+2069) — the RTL-spoofing class the design doc's row
127
+ * #13 names. */
128
+ const BIDI_RE = /[\u{202A}-\u{202E}\u{2066}-\u{2069}]/gu;
129
+ /** Combining marks (U+0300-U+036F) — "zalgo" text. Stacked onto the
130
+ * preceding base character, an unbounded run visually overflows into
131
+ * adjacent terminal cells/rows even though the string stays within the
132
+ * 64-char cap `readInstallManifest` enforces. Written as `\u{...}` escapes
133
+ * (not literal combining characters) so the source itself stays plain
134
+ * ASCII and does not visually combine in editors/diffs. */
135
+ const COMBINING_MARK_RE = /[\u{0300}-\u{036F}]/gu;
136
+ /** Zero-width characters: ZWSP (U+200B), ZWNJ (U+200C), ZWJ (U+200D), and
137
+ * BOM/ZWNBSP (U+FEFF). None of these are JS `\s`, so they survive both the
138
+ * char-count cap and the whitespace-collapse step below undetected. */
139
+ const ZERO_WIDTH_RE = /[\u{200B}-\u{200D}\u{FEFF}]/gu;
140
+ /**
141
+ * Sanitize a `declaredRuntime` (or any similarly attacker-influenceable
142
+ * string) for terminal/console rendering. `null` passes through as `null`;
143
+ * `''` passes through as `''`. Strips ANSI escapes, C0/C1 controls, Unicode
144
+ * bidi overrides/isolates, combining marks (zalgo), and zero-width
145
+ * characters (replacing each stripped run with nothing — never a space),
146
+ * then collapses any remaining whitespace run (including adjacent spaces
147
+ * left behind by a removed newline) to a single space and trims.
148
+ *
149
+ * Idempotent by construction: once ANSI/control/bidi/combining/zero-width
150
+ * bytes are gone and whitespace is collapsed to single internal spaces with
151
+ * no leading/trailing space, a second pass finds nothing left to strip or
152
+ * collapse. Pure character-class filter — never truncates; `readInstallManifest`
153
+ * already caps at 64 chars.
154
+ */
155
+ function sanitizeForRender(value) {
156
+ if (value === null)
157
+ return null;
158
+ const stripped = value
159
+ .replace(ANSI_RE, '')
160
+ .replace(CONTROL_RE, '')
161
+ .replace(BIDI_RE, '')
162
+ .replace(COMBINING_MARK_RE, '')
163
+ .replace(ZERO_WIDTH_RE, '');
164
+ return stripped.replace(/\s+/g, ' ').trim();
165
+ }
166
+ // ── Per-scope truth filter helpers ─────────────────────────────────────
167
+ /**
168
+ * Build a `(scope, kind) -> prefix | null` lookup for one runtime, memoized
169
+ * per call to `buildShadowReport` (never shared across calls — matches this
170
+ * module's "fresh objects on every call" contract). `null` means "could not
171
+ * be resolved" (unknown scope record, or a `TypeError` from the layout
172
+ * resolver) — the caller treats that as "cannot honestly attribute this
173
+ * trigger to a real stem here", not as "assume it is fine".
174
+ */
175
+ function buildPrefixLookup(runtime, scopeRecords, opts) {
176
+ const cache = new Map();
177
+ return (scope, kind) => {
178
+ const key = `${scope}:${kind}`;
179
+ if (cache.has(key))
180
+ return cache.get(key);
181
+ const record = scopeRecords.get(scope);
182
+ let prefix = null;
183
+ if (record) {
184
+ try {
185
+ const layout = opts.registry !== undefined
186
+ ? resolveRuntimeArtifactLayoutFromRegistry(opts.registry, runtime, record.configHome, record.scope)
187
+ : resolveRuntimeArtifactLayout(runtime, record.configHome, record.scope);
188
+ const kindEntry = layout.kinds.find((k) => k.kind === kind);
189
+ prefix = kindEntry ? kindEntry.prefix : null;
190
+ }
191
+ catch {
192
+ // Unknown runtime / malformed registry — degrade to "cannot resolve",
193
+ // never throw out of a report builder (matches this module's own
194
+ // RESOLVER_UNAVAILABLE degrade-not-propagate posture above).
195
+ prefix = null;
196
+ }
197
+ }
198
+ cache.set(key, prefix);
199
+ return prefix;
200
+ };
201
+ }
202
+ /** `trigger` minus `prefix`, or `null` when `prefix` is unknown, does not
203
+ * actually prefix `trigger`, or the remainder would be empty (a `prefix`
204
+ * covering the whole trigger string is not a real stem). */
205
+ function stemFromTrigger(trigger, prefix) {
206
+ if (prefix === null || !trigger.startsWith(prefix))
207
+ return null;
208
+ const stem = trigger.slice(prefix.length);
209
+ return stem === '' ? null : stem;
210
+ }
211
+ /**
212
+ * True when `t` (a `resolveTriggerSurface`-reported shadowed trigger) is a
213
+ * REAL cross-scope shadow: its stem is present in the winner's OWN scope
214
+ * `stems` and, independently, in the shadowed side's OWN scope `stems`. See
215
+ * the module-level "Per-scope truth filter" comment for why this check
216
+ * exists and why it lives here rather than in the resolver.
217
+ */
218
+ function isGenuinelyShadowed(t, scopeRecords, prefixFor) {
219
+ if (t.shadowedBy === null)
220
+ return false;
221
+ const winnerRecord = scopeRecords.get(t.shadowedBy.scope);
222
+ const shadowedRecord = scopeRecords.get(t.scope);
223
+ const winnerStem = stemFromTrigger(t.trigger, prefixFor(t.shadowedBy.scope, t.shadowedBy.kind));
224
+ const shadowedStem = stemFromTrigger(t.trigger, prefixFor(t.scope, t.kind));
225
+ if (winnerStem === null || shadowedStem === null)
226
+ return false;
227
+ return (winnerRecord?.stems ?? []).includes(winnerStem) && (shadowedRecord?.stems ?? []).includes(shadowedStem);
228
+ }
229
+ // ── Report builder ──────────────────────────────────────────────────────
230
+ /**
231
+ * Build a shadow report for one runtime. `opts` is forwarded VERBATIM to
232
+ * `resolveInstalledSurfaces` — this function adds no option of its own.
233
+ * Production call shape: `buildShadowReport('claude', { home, cwd })`.
234
+ *
235
+ * A `resolveInstalledSurfaces` `TypeError` (unknown runtime, or
236
+ * `configHome.kind === 'none'`, e.g. vscode — design row #7) degrades to
237
+ * `reason: RESOLVER_UNAVAILABLE` rather than propagating: an install-time or
238
+ * `/gsd-health` caller must never crash because a runtime has no installable
239
+ * config directory. Any other error type is rethrown — mirrors the
240
+ * resolver's own `TypeError` narrowing (`resolveInstalledSurfaces`'s sweep
241
+ * catch, and `buildScopeRecord`'s stem-derivation catch) so the two cannot
242
+ * drift apart.
243
+ */
244
+ function buildShadowReport(runtime, opts = {}) {
245
+ let surfaces;
246
+ try {
247
+ surfaces = (0, installed_surface_resolver_cjs_1.resolveInstalledSurfaces)(runtime, opts);
248
+ }
249
+ catch (error) {
250
+ if (!(error instanceof TypeError))
251
+ throw error;
252
+ return {
253
+ runtime,
254
+ reason: exports.SHADOW_REASON.RESOLVER_UNAVAILABLE,
255
+ shadowed: false,
256
+ winner: null,
257
+ shadowedSide: null,
258
+ kindsDiffer: false,
259
+ triggers: [],
260
+ mismatches: [],
261
+ };
262
+ }
263
+ // resolveInstalledSurfaces(runtime, opts) with an explicit string `runtime`
264
+ // always returns exactly one element (see its own doc comment).
265
+ const surface = surfaces[0];
266
+ // Per-scope truth filter (see module comment): `surface.triggers` may
267
+ // contain candidates synthesized from the CROSS-SCOPE stem union
268
+ // (`installed-surface-resolver.cts`'s `stemUnion`) that do not correspond
269
+ // to a real artifact at one or both scopes. Only a trigger whose stem is
270
+ // provably present in BOTH the winner's own `stems` and the shadowed
271
+ // side's own `stems` is reported.
272
+ const scopeRecords = new Map(surface.scopes.map((r) => [r.scope, r]));
273
+ const prefixFor = buildPrefixLookup(runtime, scopeRecords, opts);
274
+ const shadowedSurfaces = surface.triggers.filter((t) => isGenuinelyShadowed(t, scopeRecords, prefixFor));
275
+ const triggers = shadowedSurfaces
276
+ .map((t) => ({
277
+ trigger: t.trigger,
278
+ // `shadowedBy` is non-null by construction of the filter above.
279
+ winnerKind: t.shadowedBy.kind,
280
+ winnerScope: t.shadowedBy.scope,
281
+ shadowedKind: t.kind,
282
+ shadowedScope: t.scope,
283
+ }))
284
+ .sort((a, b) => (a.trigger < b.trigger ? -1 : a.trigger > b.trigger ? 1 : 0));
285
+ const mismatches = [];
286
+ for (const record of surface.scopes) {
287
+ if (record.declaredRuntimeMatchesProbe === false || record.declaredScopeMatchesProbe === false) {
288
+ mismatches.push({
289
+ scope: record.scope,
290
+ // Postel's Law (design doc): sanitized here because this is the
291
+ // render seam — never silently absorbed, always surfaced.
292
+ declaredRuntime: sanitizeForRender(record.declaredRuntime),
293
+ declaredRuntimeMatchesProbe: record.declaredRuntimeMatchesProbe,
294
+ declaredScope: record.declaredScope,
295
+ declaredScopeMatchesProbe: record.declaredScopeMatchesProbe,
296
+ });
297
+ }
298
+ }
299
+ if (triggers.length === 0) {
300
+ return {
301
+ runtime,
302
+ reason: exports.SHADOW_REASON.NOT_SHADOWED,
303
+ shadowed: false,
304
+ winner: null,
305
+ shadowedSide: null,
306
+ kindsDiffer: false,
307
+ triggers: [],
308
+ mismatches,
309
+ };
310
+ }
311
+ // Winner/shadowedSide are the (kind,scope) pair of the FIRST shadowed
312
+ // trigger (post-sort, for the same determinism reason the array itself is
313
+ // sorted). They are asserted-by-construction uniform across the whole set
314
+ // for every runtime this module has seen (every trigger shadowed by the
315
+ // SAME scope, with the SAME two kinds, on one machine) — but if a future
316
+ // registry shape ever produced a non-uniform set, this still returns the
317
+ // first pair rather than throwing; every distinct (kind,scope) pair is
318
+ // already visible per-entry in `triggers` itself, so nothing is lost.
319
+ const first = triggers[0];
320
+ const winner = { kind: first.winnerKind, scope: first.winnerScope };
321
+ const shadowedSide = { kind: first.shadowedKind, scope: first.shadowedScope };
322
+ return {
323
+ runtime,
324
+ reason: exports.SHADOW_REASON.SCOPE_SHADOWED,
325
+ shadowed: true,
326
+ winner,
327
+ shadowedSide,
328
+ kindsDiffer: winner.kind !== shadowedSide.kind,
329
+ triggers,
330
+ mismatches,
331
+ };
332
+ }
333
+ // ── Renderer ────────────────────────────────────────────────────────────
334
+ /**
335
+ * Render a `ShadowReport` to plain lines — no ANSI, no color, no leading
336
+ * indent. The caller (installer console output, `/gsd-health` text mode)
337
+ * owns terminal formatting; this keeps the module free of terminal concerns
338
+ * and testable without a spawned process. Structured (`--json`) health
339
+ * output (design row #17) consumes the typed `ShadowReport` directly and
340
+ * never calls this function.
341
+ *
342
+ * `reason !== SCOPE_SHADOWED` renders nothing — there is nothing to report
343
+ * (design rows #1, #2, #6, #7, #8, #11).
344
+ */
345
+ function renderShadowReport(report, opts = {}) {
346
+ if (report.reason !== exports.SHADOW_REASON.SCOPE_SHADOWED || report.winner === null || report.shadowedSide === null) {
347
+ return [];
348
+ }
349
+ const sampleLimit = opts.sampleLimit ?? 5;
350
+ const count = report.triggers.length;
351
+ const plural = count === 1 ? '' : 's';
352
+ const { winner, shadowedSide, kindsDiffer } = report;
353
+ const lines = [];
354
+ lines.push(kindsDiffer
355
+ ? `${count} trigger${plural} shadowed: the ${shadowedSide.scope} ${shadowedSide.kind} surface is unreachable through ${count === 1 ? 'that trigger' : 'those triggers'} — ${winner.scope} ${winner.kind} wins instead.`
356
+ : `${count} trigger${plural} shadowed: the ${shadowedSide.scope} ${shadowedSide.kind} ${count === 1 ? 'entry is' : 'entries are'} overridden by ${winner.scope} ${winner.kind}.`);
357
+ // Trigger names in `report.triggers` are already SAFE_STEM-gated upstream
358
+ // (installed-surface-resolver.cts's deriveStemsForKindEntry) — no re-gating
359
+ // needed here.
360
+ const sample = report.triggers.slice(0, sampleLimit);
361
+ for (const t of sample) {
362
+ lines.push(` - ${t.trigger}: ${t.shadowedScope}/${t.shadowedKind} shadowed by ${t.winnerScope}/${t.winnerKind}`);
363
+ }
364
+ const remaining = count - sample.length;
365
+ if (remaining > 0) {
366
+ lines.push(` ...and ${remaining} more`);
367
+ }
368
+ for (const m of report.mismatches) {
369
+ // Re-sanitized defensively: `buildShadowReport` already sanitizes
370
+ // `declaredRuntime` before it reaches a `ShadowReport`, and
371
+ // `sanitizeForRender` is idempotent, so this is a no-op in the normal
372
+ // path and a real guard against a hand-built `ShadowReport` (e.g. a
373
+ // renderer-only test) that skipped it.
374
+ const declaredRuntime = sanitizeForRender(m.declaredRuntime);
375
+ const parts = [];
376
+ if (m.declaredRuntimeMatchesProbe === false) {
377
+ parts.push(`declared runtime "${declaredRuntime}" does not match this runtime`);
378
+ }
379
+ if (m.declaredScopeMatchesProbe === false) {
380
+ parts.push(`declared scope "${m.declaredScope}" does not match the probed ${m.scope} scope`);
381
+ }
382
+ lines.push(`Note: ${m.scope} scope manifest mismatch — ${parts.join('; ')}.`);
383
+ }
384
+ return lines;
385
+ }