@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
@@ -290,6 +290,56 @@ function _resetRuntimeWarningCacheForTests() {
290
290
  _warnedConfigKeys.clear();
291
291
  _warnedUnknownConfigKeys.clear();
292
292
  _warnedUnusableConfig.clear();
293
+ _warnedShadowedGlobalKeys.clear();
294
+ }
295
+ // ─── #3532 (10b): shadowed global-defaults diagnostic ────────────────────────
296
+ // The keys Branch D's `_globalBaseCfg` demonstrably honors from
297
+ // ~/.gsd/defaults.json when no project config exists. Under a project
298
+ // .planning/config.json (Branch A — every real project) the global file is
299
+ // never opened, so each of these set globally is silently inert for resolution.
300
+ // `effort` is in Branch D's honored set but is EXCLUDED from the shadow warning:
301
+ // the install-time effort sync (readGsdEffectiveEffortConfig) DOES merge the
302
+ // global file, so warning on it would be false for the channel users actually
303
+ // control via `effort sync`. Keep this list in lockstep with `_globalBaseCfg`
304
+ // below — the per-key canary in tests/config-loader.test.cjs fails first on
305
+ // drift in either direction.
306
+ const GLOBAL_DEFAULTS_RESOLUTION_KEYS = [
307
+ 'model_profile', 'commit_docs', 'research', 'plan_checker', 'verifier',
308
+ 'nyquist_validation', 'post_planning_gaps', 'parallelization', 'text_mode',
309
+ 'resolve_model_ids', 'context_window', 'subagent_timeout', 'model_overrides',
310
+ 'models', 'granularity', 'granularities', 'planning', 'dynamic_routing',
311
+ 'effort', 'fast_mode', 'agent_skills', 'response_language', 'runtime',
312
+ 'model_profile_overrides', 'model_policy',
313
+ ];
314
+ // Module-level dedup keyed on the SORTED shadowed-key set: a later call with
315
+ // the same shadowed set stays quiet, while a config that grows a new shadowed
316
+ // key re-arms the warning. Stronger than _warnedUnknownConfigKeys (which keys
317
+ // on insertion order) — same discipline, order-independent key.
318
+ const _warnedShadowedGlobalKeys = new Set();
319
+ function _warnShadowedGlobalDefaults(globalDefaults, globalPath) {
320
+ const shadowed = GLOBAL_DEFAULTS_RESOLUTION_KEYS.filter(k => k !== 'effort' && Object.prototype.hasOwnProperty.call(globalDefaults, k));
321
+ // Branch D also honors the nested alias workflow.post_planning_gaps (the
322
+ // `?? globalDefaults['workflow']?.['post_planning_gaps']` fallback in
323
+ // _globalBaseCfg) — a global file using only the nested form is equally
324
+ // shadowed, so it reports under its dotted name.
325
+ if (!shadowed.includes('post_planning_gaps')) {
326
+ const wf = globalDefaults['workflow'];
327
+ if (wf && typeof wf === 'object' && !Array.isArray(wf) &&
328
+ Object.prototype.hasOwnProperty.call(wf, 'post_planning_gaps')) {
329
+ shadowed.push('workflow.post_planning_gaps');
330
+ }
331
+ }
332
+ if (shadowed.length === 0)
333
+ return;
334
+ const dedupKey = shadowed.slice().sort().join(',');
335
+ if (_warnedShadowedGlobalKeys.has(dedupKey))
336
+ return;
337
+ _warnedShadowedGlobalKeys.add(dedupKey);
338
+ try {
339
+ process.stderr.write(`gsd-tools: warning: ${globalPath} sets ${shadowed.join(', ')} but a project config ` +
340
+ `takes precedence here — those global keys are ignored for model resolution. (#3532)\n`);
341
+ }
342
+ catch { /* stderr might be closed in some test harnesses */ }
293
343
  }
294
344
  // ─── FIX 2: Federated overlay helpers ────────────────────────────────────────
295
345
  /**
@@ -751,10 +801,18 @@ function loadConfigResolved(cwd, options = {}) {
751
801
  fast_mode: (parsed['fast_mode']) || null,
752
802
  agent_skills: (parsed['agent_skills']) || {},
753
803
  agent_skills_security: (parsed['agent_skills_security']) || null,
804
+ // #3587: phase_commit_docs.<phase-id> — a dynamic-key family shaped like
805
+ // agent_skills above (`{ "<phase-id>": boolean }`). Must be threaded here
806
+ // explicitly: `_baseConfig` is a hand-maintained allowlist, so a key that
807
+ // is only in config-schema.manifest.json's dynamicKeyPatterns (and not
808
+ // projected here) is silently dropped on read — the exact `features`-key
809
+ // failure mode this module's own A3 test guards against.
810
+ phase_commit_docs: (parsed['phase_commit_docs']) || {},
754
811
  manager: (parsed['manager']) || {},
755
812
  response_language: get('response_language') || null,
756
813
  claude_md_path: get('claude_md_path') || null,
757
814
  claude_md_assembly: (parsed['claude_md_assembly']) || null,
815
+ phase_id_convention: get('phase_id_convention') ?? null,
758
816
  };
759
817
  // ADR-857 phase 3b: federated config overlay
760
818
  try {
@@ -776,6 +834,22 @@ function loadConfigResolved(cwd, options = {}) {
776
834
  // A1 vs A2: disambiguate by whether a real workstream was requested.
777
835
  // Fix 4: empty-string ws ('') resolves the root path → source:'root'.
778
836
  const source = wsRequested ? 'workstream' : 'root';
837
+ // #3532 (10b): a parsed project config means Branch D never runs, so every
838
+ // key ~/.gsd/defaults.json sets that Branch D would honor is silently inert
839
+ // here. Observation only — one deduped stderr warning; precedence is
840
+ // untouched. Faults in the global file stay silent in this branch (the
841
+ // project config governs; the nearer file is the actionable one).
842
+ try {
843
+ const shadowHome = process.env['GSD_HOME'] || node_os_1.default.homedir();
844
+ const shadowPath = node_path_1.default.join(shadowHome, '.gsd', 'defaults.json');
845
+ const shadowRead = _readConfigFile(shadowPath);
846
+ if (shadowRead.kind === 'ok') {
847
+ _warnShadowedGlobalDefaults(shadowRead.data, shadowPath);
848
+ }
849
+ }
850
+ catch {
851
+ // Observation only — never let the diagnostic perturb resolution.
852
+ }
779
853
  // This config parsed — but a DIFFERENT file on the resolution path may not
780
854
  // have. A workstream config that loads cleanly while the root config it
781
855
  // inherits from is corrupt is still a degraded resolution: the root's
@@ -907,6 +981,8 @@ module.exports = {
907
981
  _getNestedConfigDefault,
908
982
  _deepMergeConfig,
909
983
  _warnedUnknownConfigKeys,
984
+ _warnedShadowedGlobalKeys,
985
+ GLOBAL_DEFAULTS_RESOLUTION_KEYS,
910
986
  _warnUnknownProfileOverrides,
911
987
  _resetRuntimeWarningCacheForTests,
912
988
  _warnedConfigKeys,
@@ -65,7 +65,12 @@ const SCHEMA_DEFAULTS = {
65
65
  'context_window': 200000,
66
66
  'executor.stall_detect_interval_minutes': 5,
67
67
  'executor.stall_threshold_minutes': 10,
68
+ 'planner.stall_detect_interval_minutes': 5,
69
+ 'planner.stall_threshold_minutes': 10,
68
70
  'git.create_tag': true,
71
+ // #1689: per-plan agent_hint executor routing — default-on. A no-op for plans
72
+ // without an agent_hint field, so existing dispatch is byte-identical.
73
+ 'workflow.agent_hint_routing': true,
69
74
  // Derived from the defaults manifest rather than restated, so the manifest
70
75
  // stays the single source of truth for the smart-zone budget (#2630).
71
76
  'workflow.smart_zone_tokens': CONFIG_DEFAULTS.smart_zone_tokens,
@@ -289,10 +294,19 @@ function buildNewProjectConfig(userChoices) {
289
294
  const ud = userDefaults;
290
295
  const ch = choices;
291
296
  const hd = hardcoded;
297
+ // #2840: `runtime` is host-specific (written by the installer for whichever
298
+ // runtime's install ran last). On a machine with 2+ runtimes, it poisons every
299
+ // new project config — e.g. a Codex install's `runtime:"codex"` leaks into
300
+ // Claude Code projects, resolving agents to wrong model IDs. `resolve_model_ids`
301
+ // already has a per-install guard (#2297); `runtime` gets the same treatment
302
+ // by excluding it from the defaults spread. Projects detect the runtime from
303
+ // the install path / .gsd-runtime marker, not from a copied config key.
304
+ const safeDefaults = { ...userDefaults };
305
+ delete safeDefaults['runtime'];
292
306
  // Three-level deep merge: hardcoded <- userDefaults <- choices
293
307
  const config = {
294
308
  ...hardcoded,
295
- ...userDefaults,
309
+ ...safeDefaults,
296
310
  ...choices,
297
311
  git: {
298
312
  ...hd['git'],
@@ -641,7 +655,7 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
641
655
  const val = value;
642
656
  validateKnownConfigKeyPath(kp);
643
657
  if (!isValidConfigKey(kp, cwd)) {
644
- error(`Unknown config key: "${kp}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>, features.<feature_name>`, ERROR_REASON.CONFIG_INVALID_KEY);
658
+ error(`Unknown config key: "${kp}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>, features.<feature_name>, phase_commit_docs.<phase-id>`, ERROR_REASON.CONFIG_INVALID_KEY);
645
659
  }
646
660
  // Parse value (handle booleans, numbers, and JSON arrays/objects)
647
661
  let parsedValue = val;
@@ -725,6 +739,12 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
725
739
  error(`Invalid workflow.post_planning_gaps '${val}'. Must be a boolean (true or false).`);
726
740
  }
727
741
  }
742
+ // Per-plan executor routing via agent_hint frontmatter (#1689)
743
+ if (kp === 'workflow.agent_hint_routing') {
744
+ if (typeof parsedValue !== 'boolean') {
745
+ error(`Invalid workflow.agent_hint_routing '${val}'. Must be a boolean (true or false).`);
746
+ }
747
+ }
728
748
  // #3086 — git.create_tag: boolean only
729
749
  if (kp === 'git.create_tag') {
730
750
  if (typeof parsedValue !== 'boolean') {
@@ -0,0 +1,278 @@
1
+ "use strict";
2
+ /**
3
+ * Context Composer — shared budget-trim seam (ADR-1671 Decision item 2).
4
+ *
5
+ * Extracted from the five-step trim ladder in `prompt-budget.cts` by issue
6
+ * #2929 (epic #1671 Phase 2) so a second consumer — per-runtime artifact
7
+ * emission, in a later phase — can reuse the exact same trimming behavior
8
+ * without re-deriving it. `prompt-budget.applyBudget` is routed through this
9
+ * module in a follow-up commit, which asserts byte-identical output against
10
+ * a committed corpus; the algorithm here is a characterization of that
11
+ * existing behavior, not a redesign.
12
+ *
13
+ * THE COMPOSER DECIDES, THE CALLER RENDERS. `composeWithinBudget` never
14
+ * joins fragments and never returns a rendered string — it returns a plan of
15
+ * surviving fragments with their (possibly trimmed) content, plus metadata
16
+ * describing what happened. Rendering is prompt-specific: the review-prompt
17
+ * consumer assembles Markdown sections with blank-line separators; the
18
+ * per-runtime emission consumer renders differently. Keeping the decision
19
+ * (what survives, in what shape) separate from the render (how it is joined)
20
+ * is what lets both consumers share this module.
21
+ *
22
+ * The budget unit is injected via `measure(text) => number` rather than
23
+ * hardcoded to a token estimator: `prompt-budget` passes its chars/4
24
+ * estimator, per-runtime emission passes a byte counter. `charsPerUnit` in
25
+ * `ComposeOptions` is the inverse of `measure` (4 for chars/4, 1 for a byte
26
+ * counter) and is what lets `proportional-truncate`'s char-budget math stay
27
+ * unit-agnostic.
28
+ *
29
+ * ADR-1671's literal contract describes "priority + binary-search cutoff"
30
+ * trimming. That description cannot express this module's actual shape:
31
+ * head-shrink (structural, line-bounded) and proportional-truncate-with-floor
32
+ * (per-fragment guaranteed minimum, not a single global cutoff) are not
33
+ * binary-search cutoffs. `ShrinkStrategy` — not a numeric priority field — is
34
+ * the core abstraction here, and a `cutoff` strategy is expected to join this
35
+ * closed set once per-runtime emission needs binary-search behavior. Relatedly,
36
+ * `verbatim` and `drop` fragments are never reordered by importance: survival
37
+ * and drop order both follow DECLARATION order (the order fragments appear in
38
+ * `ComposeInput.fragments`), not a numeric priority.
39
+ */
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.headShrink = headShrink;
42
+ exports.tailTruncate = tailTruncate;
43
+ exports.composeWithinBudget = composeWithinBudget;
44
+ /**
45
+ * Head-shrink a string to at most `maxLines` lines.
46
+ *
47
+ * Copied verbatim from `prompt-budget.cts` (formerly private) — do not
48
+ * "improve" it; a committed corpus depends on this exact behavior.
49
+ */
50
+ function headShrink(text, maxLines) {
51
+ if (maxLines <= 0)
52
+ return '';
53
+ let idx = -1;
54
+ let seen = 0;
55
+ while (seen < maxLines) {
56
+ idx = text.indexOf('\n', idx + 1);
57
+ if (idx === -1)
58
+ return text;
59
+ seen += 1;
60
+ }
61
+ return text.slice(0, idx);
62
+ }
63
+ /**
64
+ * Tail-truncate a string to at most `maxChars` characters.
65
+ *
66
+ * Copied verbatim from `prompt-budget.cts` (formerly private) — do not
67
+ * "improve" it; a committed corpus depends on this exact behavior.
68
+ */
69
+ function tailTruncate(text, maxChars) {
70
+ if (text.length <= maxChars)
71
+ return text;
72
+ return text.slice(0, maxChars);
73
+ }
74
+ const isPresent = (w) => w.required || Boolean(w.content);
75
+ const costOf = (w, measure) => isPresent(w) ? measure(w.wrapper) + measure(w.content) : 0;
76
+ const totalOf = (working, measure) => working.reduce((sum, w) => sum + costOf(w, measure), 0);
77
+ /**
78
+ * Trim a set of fragments to fit within `budget`, following the exact
79
+ * five-step ladder characterized from `prompt-budget.applyBudget`:
80
+ * head-shrink, then group proportional-truncate, then declaration-order
81
+ * drops — each pass individually guarded by the running total against the
82
+ * content budget, walked once in declaration order.
83
+ *
84
+ * Returns a plan (surviving fragment content + metadata); it never renders.
85
+ */
86
+ function composeWithinBudget(input) {
87
+ const { fragments, budget, measure, options = {} } = input;
88
+ const { safetyMarginPct = 0, reserve = 0, charsPerUnit = 1, minimumFor } = options;
89
+ const effectiveBudget = Math.floor(budget * (1 - safetyMarginPct / 100));
90
+ const seenIds = new Set();
91
+ const working = [];
92
+ let sawNonIsolate = false;
93
+ let isolatePrefix = '';
94
+ for (const f of fragments) {
95
+ if (seenIds.has(f.id)) {
96
+ throw new TypeError(`composeWithinBudget: duplicate fragment id "${f.id}"`);
97
+ }
98
+ seenIds.add(f.id);
99
+ const isolate = f.isolate === true;
100
+ if (isolate) {
101
+ if (sawNonIsolate) {
102
+ throw new TypeError(`composeWithinBudget: isolate fragment "${f.id}" declared after a non-isolate fragment; ` +
103
+ 'isolate fragments must form a leading, contiguous prefix of ComposeInput.fragments');
104
+ }
105
+ isolatePrefix += (f.wrapper ?? '') + f.content;
106
+ }
107
+ else {
108
+ sawNonIsolate = true;
109
+ }
110
+ working.push({
111
+ id: f.id,
112
+ wrapper: f.wrapper ?? '',
113
+ strategy: f.strategy,
114
+ required: f.required === true,
115
+ group: f.group,
116
+ originalLength: f.content.length,
117
+ flexReserve: f.flexReserve ?? 0,
118
+ isolate,
119
+ content: f.content,
120
+ shrunk: false,
121
+ truncated: false,
122
+ });
123
+ }
124
+ // Minimum-set pre-check. Wrappers are deliberately NOT counted here, and
125
+ // the reserve is deliberately EXCLUDED — this checks the floor, not the
126
+ // steady-state budget.
127
+ if (minimumFor) {
128
+ const minTotal = fragments.reduce((sum, f) => sum + measure(minimumFor(f) ?? ''), 0);
129
+ if (minTotal > effectiveBudget) {
130
+ return {
131
+ fragments: [],
132
+ metadata: {
133
+ budget,
134
+ effectiveBudget,
135
+ contentBudget: effectiveBudget,
136
+ underPressure: false,
137
+ omitted: [],
138
+ shrunk: [],
139
+ floored: [],
140
+ truncationPct: 0,
141
+ hardFailed: true,
142
+ hardFailReason: 'minimum-set',
143
+ isolatePrefix,
144
+ },
145
+ };
146
+ }
147
+ }
148
+ const total = () => totalOf(working, measure);
149
+ const baseline = total();
150
+ const underPressure = baseline > effectiveBudget;
151
+ // The reserve is deducted ONLY when under pressure — deducting it
152
+ // unconditionally drops sections `reserve` units early
153
+ // (CONTEXT.md LEARNING.prompt-budget.boundary-gap, PR #3708).
154
+ const contentBudget = underPressure ? effectiveBudget - reserve : effectiveBudget;
155
+ const omitted = [];
156
+ const shrunk = [];
157
+ const floored = [];
158
+ const truncation = { pct: 0 };
159
+ const processedGroups = new Set();
160
+ for (const w of working) {
161
+ // Isolate fragments are the byte-stable canonical prefix (ADR-1671):
162
+ // never trimmed, shrunk, or dropped, regardless of declared strategy.
163
+ // They still count toward the budget via total()/costOf above.
164
+ if (w.isolate) {
165
+ continue;
166
+ }
167
+ switch (w.strategy.kind) {
168
+ case 'verbatim': {
169
+ continue;
170
+ }
171
+ case 'head-shrink': {
172
+ if (total() > contentBudget && isPresent(w)) {
173
+ const next = headShrink(w.content, w.strategy.maxLines);
174
+ if (next !== w.content) {
175
+ if (w.flexReserve > 0 && measure(next) < w.flexReserve) {
176
+ // Shrinking would undershoot the floor — leave untouched.
177
+ floored.push(w.id);
178
+ }
179
+ else {
180
+ w.content = next;
181
+ w.shrunk = true;
182
+ shrunk.push(w.id);
183
+ }
184
+ }
185
+ }
186
+ continue;
187
+ }
188
+ case 'proportional-truncate': {
189
+ const groupKey = w.group ?? w.id;
190
+ if (processedGroups.has(groupKey))
191
+ continue;
192
+ processedGroups.add(groupKey);
193
+ if (total() > contentBudget) {
194
+ const members = working.filter((m) => m.strategy.kind === 'proportional-truncate' && (m.group ?? m.id) === groupKey && !m.isolate);
195
+ const memberContentTotal = members.reduce((sum, m) => sum + measure(m.content), 0);
196
+ const overhead = total() - memberContentTotal;
197
+ const groupBudget = contentBudget - overhead;
198
+ const groupContentTotal = memberContentTotal;
199
+ if (groupBudget > 0 && groupBudget < groupContentTotal) {
200
+ const originalChars = members.reduce((sum, m) => sum + m.originalLength, 0);
201
+ const charsBudget = groupBudget * charsPerUnit;
202
+ for (const m of members) {
203
+ const floorChars = m.strategy.kind === 'proportional-truncate' ? m.strategy.floorChars : 0;
204
+ const share = originalChars > 0 ? Math.floor((m.originalLength / originalChars) * charsBudget) : 0;
205
+ const charsForReserve = m.flexReserve * charsPerUnit;
206
+ const capWithoutReserve = Math.max(share, floorChars);
207
+ const maxChars = Math.max(capWithoutReserve, charsForReserve);
208
+ const beforeTruncate = m.content;
209
+ const next = tailTruncate(m.content, maxChars);
210
+ if (next !== beforeTruncate) {
211
+ m.content = next;
212
+ m.truncated = true;
213
+ }
214
+ // flexReserve raised the cap above what the unreserved cap
215
+ // would have allowed, and that raised cap actually mattered
216
+ // (the unreserved cap would have cut into the content) —
217
+ // this holds whether the reserve prevented a trim outright
218
+ // (next === beforeTruncate) or merely shrank the trim below
219
+ // what the unreserved cap would have produced. Either way a
220
+ // cut was prevented, so it belongs in `floored`.
221
+ if (m.flexReserve > 0 &&
222
+ maxChars > capWithoutReserve &&
223
+ capWithoutReserve < beforeTruncate.length &&
224
+ !floored.includes(m.id)) {
225
+ floored.push(m.id);
226
+ }
227
+ }
228
+ const newChars = members.reduce((sum, m) => sum + m.content.length, 0);
229
+ if (originalChars > 0) {
230
+ truncation.pct = ((originalChars - newChars) / originalChars) * 100;
231
+ }
232
+ }
233
+ }
234
+ continue;
235
+ }
236
+ case 'drop': {
237
+ if (total() > contentBudget && isPresent(w) && Boolean(w.content)) {
238
+ // Dropping always takes a fragment to 0, and 0 is below any
239
+ // positive floor — so ANY flexReserve > 0 disables drop entirely
240
+ // for this fragment, not just when its current size already sits
241
+ // at/under the floor.
242
+ if (w.flexReserve > 0) {
243
+ floored.push(w.id);
244
+ }
245
+ else {
246
+ w.content = '';
247
+ omitted.push(w.id);
248
+ }
249
+ }
250
+ continue;
251
+ }
252
+ }
253
+ }
254
+ const composedFragments = working.map((w) => ({
255
+ id: w.id,
256
+ content: w.content,
257
+ wrapper: w.wrapper,
258
+ present: isPresent(w),
259
+ shrunk: w.shrunk,
260
+ truncated: w.truncated,
261
+ }));
262
+ return {
263
+ fragments: composedFragments,
264
+ metadata: {
265
+ budget,
266
+ effectiveBudget,
267
+ contentBudget,
268
+ underPressure,
269
+ omitted,
270
+ shrunk,
271
+ floored,
272
+ truncationPct: truncation.pct,
273
+ hardFailed: false,
274
+ hardFailReason: null,
275
+ isolatePrefix,
276
+ },
277
+ };
278
+ }