@opengsd/gsd-core 1.10.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (544) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-code-fixer.md +1 -1
  4. package/agents/gsd-debug-session-manager.md +12 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-doc-synthesizer.md +2 -4
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +22 -14
  10. package/agents/gsd-framework-selector.md +1 -3
  11. package/agents/gsd-intel-updater.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +5 -3
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +23 -2
  15. package/agents/gsd-plan-checker.md +50 -53
  16. package/agents/gsd-planner.md +50 -50
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +2 -2
  19. package/agents/gsd-roadmapper.md +15 -11
  20. package/agents/gsd-ui-checker.md +63 -4
  21. package/agents/gsd-ui-researcher.md +41 -3
  22. package/agents/gsd-user-profiler.md +3 -0
  23. package/agents/gsd-verifier.md +13 -4
  24. package/bin/install.js +1448 -1103
  25. package/commands/gsd/code-review.md +1 -1
  26. package/commands/gsd/discuss-phase.md +1 -1
  27. package/commands/gsd/execute-phase.md +1 -1
  28. package/commands/gsd/import.md +1 -1
  29. package/commands/gsd/map-codebase.md +1 -1
  30. package/commands/gsd/mempalace-capture.md +1 -1
  31. package/commands/gsd/mempalace-recall.md +1 -1
  32. package/commands/gsd/new-milestone.md +1 -1
  33. package/commands/gsd/quick.md +9 -5
  34. package/commands/gsd/review-backlog.md +2 -1
  35. package/commands/gsd/verify-work.md +1 -1
  36. package/gsd-core/bin/gsd-tools.cjs +1035 -138
  37. package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
  38. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  39. package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
  40. package/gsd-core/bin/lib/api-coverage.cjs +33 -14
  41. package/gsd-core/bin/lib/artifacts.cjs +5 -0
  42. package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
  43. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  44. package/gsd-core/bin/lib/audit.cjs +1026 -268
  45. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  46. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  47. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  48. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  49. package/gsd-core/bin/lib/capability-registry.cjs +845 -130
  50. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  51. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  52. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  53. package/gsd-core/bin/lib/capability-validator.cjs +700 -40
  54. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  55. package/gsd-core/bin/lib/check-command-router.cjs +216 -42
  56. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  57. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  58. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  59. package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
  60. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  61. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  62. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  63. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  64. package/gsd-core/bin/lib/commands.cjs +1172 -108
  65. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  66. package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
  67. package/gsd-core/bin/lib/config-loader.cjs +187 -23
  68. package/gsd-core/bin/lib/config.cjs +102 -3
  69. package/gsd-core/bin/lib/configuration.cjs +129 -37
  70. package/gsd-core/bin/lib/core-utils.cjs +208 -33
  71. package/gsd-core/bin/lib/decisions.cjs +23 -0
  72. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  73. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  74. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  75. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  76. package/gsd-core/bin/lib/frontmatter.cjs +899 -229
  77. package/gsd-core/bin/lib/gap-checker.cjs +95 -10
  78. package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
  79. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  80. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  81. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  82. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
  83. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  84. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  85. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  86. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
  87. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  88. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  89. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
  90. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  91. package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
  92. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  93. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  94. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  95. package/gsd-core/bin/lib/init.cjs +439 -168
  96. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  97. package/gsd-core/bin/lib/install-engine.cjs +811 -259
  98. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  99. package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
  100. package/gsd-core/bin/lib/install-profiles.cjs +212 -61
  101. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  102. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  103. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  104. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  105. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  106. package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
  107. package/gsd-core/bin/lib/intel.cjs +101 -26
  108. package/gsd-core/bin/lib/io.cjs +170 -15
  109. package/gsd-core/bin/lib/learnings.cjs +85 -14
  110. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  111. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  112. package/gsd-core/bin/lib/markdown-table.cjs +183 -22
  113. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  114. package/gsd-core/bin/lib/milestone.cjs +842 -73
  115. package/gsd-core/bin/lib/model-catalog.cjs +232 -16
  116. package/gsd-core/bin/lib/model-resolver.cjs +193 -68
  117. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  118. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  119. package/gsd-core/bin/lib/pattern.cjs +122 -0
  120. package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
  121. package/gsd-core/bin/lib/phase-id.cjs +514 -40
  122. package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
  123. package/gsd-core/bin/lib/phase-locator.cjs +262 -34
  124. package/gsd-core/bin/lib/phase.cjs +1038 -214
  125. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  126. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  127. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  128. package/gsd-core/bin/lib/plan-scan.cjs +98 -3
  129. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  130. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  131. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  132. package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
  133. package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
  134. package/gsd-core/bin/lib/probe-core.cjs +5 -2
  135. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  136. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  137. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  138. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  139. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
  140. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  141. package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
  142. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  143. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  144. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  145. package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
  146. package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
  147. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  148. package/gsd-core/bin/lib/roadmap.cjs +442 -96
  149. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
  150. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  151. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
  152. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  153. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  154. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
  155. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  156. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  157. package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
  158. package/gsd-core/bin/lib/security.cjs +104 -5
  159. package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
  160. package/gsd-core/bin/lib/smart-entry.cjs +133 -23
  161. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  162. package/gsd-core/bin/lib/state-command-router.cjs +52 -19
  163. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  164. package/gsd-core/bin/lib/state-document.cjs +338 -8
  165. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  166. package/gsd-core/bin/lib/state-transition.cjs +846 -176
  167. package/gsd-core/bin/lib/state.cjs +2589 -369
  168. package/gsd-core/bin/lib/surface.cjs +33 -11
  169. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  170. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  171. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  172. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  173. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  174. package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
  175. package/gsd-core/bin/lib/uat.cjs +1761 -167
  176. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  177. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  178. package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
  179. package/gsd-core/bin/lib/unusable-input.cjs +37 -0
  180. package/gsd-core/bin/lib/update-context.cjs +8 -2
  181. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  182. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  183. package/gsd-core/bin/lib/validate.cjs +20 -6
  184. package/gsd-core/bin/lib/vendor/README.md +75 -0
  185. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  186. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  187. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  188. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  189. package/gsd-core/bin/lib/verification.cjs +272 -9
  190. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  191. package/gsd-core/bin/lib/verify.cjs +453 -918
  192. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  193. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  194. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  195. package/gsd-core/bin/lib/workstream.cjs +2 -2
  196. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  197. package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
  198. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
  199. package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
  200. package/gsd-core/bin/shared/exit-codes.json +8 -0
  201. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  202. package/gsd-core/bin/shared/model-catalog.json +8 -1
  203. package/gsd-core/references/agent-contracts.md +44 -26
  204. package/gsd-core/references/api-coverage.md +24 -2
  205. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  206. package/gsd-core/references/checkpoints.md +39 -21
  207. package/gsd-core/references/context-budget.md +1 -1
  208. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  209. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  210. package/gsd-core/references/doc-conflict-engine.md +1 -1
  211. package/gsd-core/references/edge-probe.md +8 -0
  212. package/gsd-core/references/execute-mvp-tdd.md +4 -6
  213. package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
  214. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  215. package/gsd-core/references/execute-phase-response-language.md +1 -1
  216. package/gsd-core/references/execute-phase-wave-guard.md +17 -11
  217. package/gsd-core/references/failing-direction.md +78 -0
  218. package/gsd-core/references/gate-prompts.md +1 -1
  219. package/gsd-core/references/git-integration.md +5 -5
  220. package/gsd-core/references/git-planning-commit.md +5 -4
  221. package/gsd-core/references/gsd-run-resolver.md +1 -1
  222. package/gsd-core/references/loop-hook-dispatch.md +61 -2
  223. package/gsd-core/references/model-profiles.md +12 -4
  224. package/gsd-core/references/mvp-concepts.md +9 -9
  225. package/gsd-core/references/nyquist-compliance.md +74 -0
  226. package/gsd-core/references/offer-next.md +3 -5
  227. package/gsd-core/references/phase-argument-parsing.md +3 -3
  228. package/gsd-core/references/planner-failing-direction.md +53 -0
  229. package/gsd-core/references/planner-guidance.md +3 -9
  230. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  231. package/gsd-core/references/planner-preconditions.md +1 -1
  232. package/gsd-core/references/planner-reviews.md +1 -1
  233. package/gsd-core/references/planner-revision.md +1 -1
  234. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  235. package/gsd-core/references/planning-config.md +44 -13
  236. package/gsd-core/references/reviewer-instances.md +31 -0
  237. package/gsd-core/references/revision-loop.md +1 -1
  238. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  239. package/gsd-core/references/specless-probe-fallback.md +1 -1
  240. package/gsd-core/references/tdd.md +1 -3
  241. package/gsd-core/references/ui-brand.md +65 -21
  242. package/gsd-core/references/ui-consideration-probe.md +1 -1
  243. package/gsd-core/references/universal-anti-patterns.md +5 -5
  244. package/gsd-core/references/verifier-phase-gates.md +192 -0
  245. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  246. package/gsd-core/references/verify-mvp-mode.md +2 -2
  247. package/gsd-core/references/workstream-flag.md +33 -17
  248. package/gsd-core/templates/README.md +1 -1
  249. package/gsd-core/templates/SECURITY.md +3 -3
  250. package/gsd-core/templates/UI-SPEC.md +25 -3
  251. package/gsd-core/templates/VALIDATION.md +3 -3
  252. package/gsd-core/templates/discussion-log.md +1 -1
  253. package/gsd-core/templates/phase-prompt.md +5 -4
  254. package/gsd-core/templates/state.md +11 -4
  255. package/gsd-core/templates/verification-report.md +9 -1
  256. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  257. package/gsd-core/workflows/add-backlog.md +1 -1
  258. package/gsd-core/workflows/add-phase.md +3 -3
  259. package/gsd-core/workflows/add-tests.md +3 -8
  260. package/gsd-core/workflows/add-todo.md +1 -1
  261. package/gsd-core/workflows/ai-integration-phase.md +13 -20
  262. package/gsd-core/workflows/audit-fix.md +12 -3
  263. package/gsd-core/workflows/audit-milestone.md +9 -9
  264. package/gsd-core/workflows/audit-uat.md +17 -2
  265. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  266. package/gsd-core/workflows/autonomous.md +11 -27
  267. package/gsd-core/workflows/check-todos.md +1 -1
  268. package/gsd-core/workflows/cleanup.md +64 -5
  269. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
  270. package/gsd-core/workflows/code-review-fix.md +38 -11
  271. package/gsd-core/workflows/code-review.md +159 -52
  272. package/gsd-core/workflows/complete-milestone.md +151 -23
  273. package/gsd-core/workflows/debug.md +12 -8
  274. package/gsd-core/workflows/diagnose-issues.md +47 -15
  275. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  276. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
  277. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  278. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  279. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  280. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  281. package/gsd-core/workflows/discuss-phase.md +1 -1
  282. package/gsd-core/workflows/do.md +3 -6
  283. package/gsd-core/workflows/docs-update.md +5 -4
  284. package/gsd-core/workflows/edit-phase.md +27 -2
  285. package/gsd-core/workflows/eval-review.md +7 -14
  286. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  287. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
  288. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  289. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  290. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  291. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
  292. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  293. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  294. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  295. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  296. package/gsd-core/workflows/execute-phase.md +72 -100
  297. package/gsd-core/workflows/execute-plan.md +52 -15
  298. package/gsd-core/workflows/explore.md +131 -4
  299. package/gsd-core/workflows/extract-learnings.md +1 -1
  300. package/gsd-core/workflows/fast.md +10 -2
  301. package/gsd-core/workflows/forensics.md +1 -1
  302. package/gsd-core/workflows/graduation.md +5 -5
  303. package/gsd-core/workflows/health.md +76 -10
  304. package/gsd-core/workflows/import.md +18 -15
  305. package/gsd-core/workflows/inbox.md +4 -5
  306. package/gsd-core/workflows/ingest-docs.md +49 -16
  307. package/gsd-core/workflows/insert-phase.md +5 -5
  308. package/gsd-core/workflows/list-seeds.md +5 -3
  309. package/gsd-core/workflows/list-workspaces.md +1 -1
  310. package/gsd-core/workflows/manager.md +12 -23
  311. package/gsd-core/workflows/map-codebase.md +1 -1
  312. package/gsd-core/workflows/milestone-summary.md +1 -1
  313. package/gsd-core/workflows/mvp-phase.md +8 -5
  314. package/gsd-core/workflows/new-milestone.md +22 -29
  315. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  316. package/gsd-core/workflows/new-project.md +26 -40
  317. package/gsd-core/workflows/new-workspace.md +1 -1
  318. package/gsd-core/workflows/next.md +14 -2
  319. package/gsd-core/workflows/pause-work.md +1 -1
  320. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  321. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  322. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  323. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  324. package/gsd-core/workflows/plan-phase.md +162 -59
  325. package/gsd-core/workflows/plan-review-convergence.md +96 -11
  326. package/gsd-core/workflows/plant-seed.md +2 -2
  327. package/gsd-core/workflows/pr-branch.md +187 -51
  328. package/gsd-core/workflows/profile-user.md +16 -14
  329. package/gsd-core/workflows/progress.md +61 -18
  330. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  331. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
  332. package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
  333. package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
  334. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  335. package/gsd-core/workflows/quick.md +55 -44
  336. package/gsd-core/workflows/remove-phase.md +4 -4
  337. package/gsd-core/workflows/remove-workspace.md +2 -2
  338. package/gsd-core/workflows/resume-project.md +8 -12
  339. package/gsd-core/workflows/review.md +219 -20
  340. package/gsd-core/workflows/scan.md +1 -1
  341. package/gsd-core/workflows/secure-phase.md +3 -3
  342. package/gsd-core/workflows/session-report.md +2 -1
  343. package/gsd-core/workflows/settings-advanced.md +7 -9
  344. package/gsd-core/workflows/settings-integrations.md +64 -31
  345. package/gsd-core/workflows/settings.md +69 -7
  346. package/gsd-core/workflows/ship.md +116 -50
  347. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  348. package/gsd-core/workflows/sketch.md +12 -18
  349. package/gsd-core/workflows/smart-entry.md +3 -5
  350. package/gsd-core/workflows/spec-phase.md +53 -13
  351. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  352. package/gsd-core/workflows/spike.md +20 -31
  353. package/gsd-core/workflows/stats.md +2 -2
  354. package/gsd-core/workflows/sync-skills.md +64 -9
  355. package/gsd-core/workflows/thread.md +11 -7
  356. package/gsd-core/workflows/transition.md +49 -14
  357. package/gsd-core/workflows/ui-phase.md +15 -21
  358. package/gsd-core/workflows/ui-review.md +8 -12
  359. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  360. package/gsd-core/workflows/undo.md +8 -16
  361. package/gsd-core/workflows/update.md +7 -11
  362. package/gsd-core/workflows/validate-phase.md +3 -3
  363. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  364. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  365. package/gsd-core/workflows/verify-work.md +66 -25
  366. package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
  367. package/hooks/dist/gsd-check-update-worker.js +56 -13
  368. package/hooks/dist/gsd-check-update.js +19 -1
  369. package/hooks/dist/gsd-config-reload.js +18 -12
  370. package/hooks/dist/gsd-context-monitor.js +19 -10
  371. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  372. package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
  373. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  374. package/hooks/dist/gsd-cursor-stop.js +2 -1
  375. package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
  376. package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
  377. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  378. package/hooks/dist/gsd-graphify-update.sh +22 -18
  379. package/hooks/dist/gsd-node-runner.sh +76 -0
  380. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  381. package/hooks/dist/gsd-prompt-guard.js +37 -27
  382. package/hooks/dist/gsd-read-guard.js +16 -7
  383. package/hooks/dist/gsd-read-injection-scanner.js +55 -32
  384. package/hooks/dist/gsd-session-state.sh +1 -0
  385. package/hooks/dist/gsd-statusline.js +231 -24
  386. package/hooks/dist/gsd-update-banner.js +22 -1
  387. package/hooks/dist/gsd-validate-commit.sh +80 -6
  388. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  389. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  390. package/hooks/dist/gsd-workflow-guard.js +162 -46
  391. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  392. package/hooks/dist/gsd-write-guard.js +35 -25
  393. package/hooks/dist/lib/cli-exit.js +560 -0
  394. package/hooks/dist/lib/exit-code-registry.js +98 -0
  395. package/hooks/dist/lib/git-cmd.js +92 -59
  396. package/hooks/dist/lib/git-probe.js +84 -0
  397. package/hooks/dist/lib/hook-exit.js +81 -0
  398. package/hooks/dist/lib/injection-patterns.js +45 -0
  399. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  400. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  401. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  402. package/hooks/gsd-agent-isolation-guard.js +158 -30
  403. package/hooks/gsd-check-update-worker.js +56 -13
  404. package/hooks/gsd-check-update.js +19 -1
  405. package/hooks/gsd-config-reload.js +18 -12
  406. package/hooks/gsd-context-monitor.js +19 -10
  407. package/hooks/gsd-cursor-post-tool.js +3 -1
  408. package/hooks/gsd-cursor-pre-tool.js +2 -3
  409. package/hooks/gsd-cursor-session-start.js +2 -1
  410. package/hooks/gsd-cursor-stop.js +2 -1
  411. package/hooks/gsd-cursor-subagent-start.js +83 -3
  412. package/hooks/gsd-cursor-subagent-stop.js +6 -3
  413. package/hooks/gsd-ensure-canonical-path.js +2 -1
  414. package/hooks/gsd-graphify-update.sh +22 -18
  415. package/hooks/gsd-node-runner.sh +76 -0
  416. package/hooks/gsd-phase-boundary.sh +1 -0
  417. package/hooks/gsd-prompt-guard.js +37 -27
  418. package/hooks/gsd-read-guard.js +16 -7
  419. package/hooks/gsd-read-injection-scanner.js +55 -32
  420. package/hooks/gsd-session-state.sh +1 -0
  421. package/hooks/gsd-statusline.js +231 -24
  422. package/hooks/gsd-update-banner.js +22 -1
  423. package/hooks/gsd-validate-commit.sh +80 -6
  424. package/hooks/gsd-windsurf-pre-command.js +16 -11
  425. package/hooks/gsd-windsurf-pre-write.js +22 -13
  426. package/hooks/gsd-workflow-guard.js +162 -46
  427. package/hooks/gsd-worktree-path-guard.js +36 -21
  428. package/hooks/gsd-write-guard.js +35 -25
  429. package/hooks/lib/cli-exit.js +560 -0
  430. package/hooks/lib/exit-code-registry.js +98 -0
  431. package/hooks/lib/git-cmd.js +92 -59
  432. package/hooks/lib/git-probe.js +84 -0
  433. package/hooks/lib/hook-exit.js +81 -0
  434. package/hooks/lib/injection-patterns.js +45 -0
  435. package/hooks/lib/isolation-deny-reason.js +39 -0
  436. package/hooks/lib/isolation-sentinel.js +9 -0
  437. package/hooks/managed-hooks-registry.cjs +3 -0
  438. package/package.json +28 -11
  439. package/pi/gsd.cjs +19 -5
  440. package/scripts/base64-scan.sh +74 -12
  441. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  442. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  443. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  444. package/scripts/build-hooks.js +5 -0
  445. package/scripts/changeset/lint.cjs +60 -5
  446. package/scripts/check-alias-drift.cjs +7 -43
  447. package/scripts/check-contract-drift.cjs +297 -0
  448. package/scripts/check-glossary-refs.cjs +77 -15
  449. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  450. package/scripts/ci-check-job-near-cap.cjs +49 -0
  451. package/scripts/ci-pr-mergeability.cjs +262 -0
  452. package/scripts/ci-test-scope.cjs +64 -14
  453. package/scripts/ci-timeout-report.cjs +230 -0
  454. package/scripts/command-contract-helpers.cjs +903 -1
  455. package/scripts/docs-guard-registry.cjs +396 -0
  456. package/scripts/gen-adr-index.cjs +728 -38
  457. package/scripts/gen-capability-registry.cjs +11 -21
  458. package/scripts/gen-context-index.cjs +2 -11
  459. package/scripts/gen-exit-code-docs.cjs +318 -0
  460. package/scripts/gen-exit-code-registry.cjs +891 -0
  461. package/scripts/gen-features.cjs +836 -0
  462. package/scripts/gen-health-docs.cjs +390 -0
  463. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  464. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  465. package/scripts/gen-inventory-manifest.cjs +50 -4
  466. package/scripts/gen-loop-host-contract.cjs +138 -25
  467. package/scripts/gen-registry.cjs +3 -14
  468. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  469. package/scripts/gen-state-md-docs.cjs +727 -0
  470. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  471. package/scripts/lib/alias-drift-families.cjs +46 -0
  472. package/scripts/lib/ci-job-timing.cjs +72 -0
  473. package/scripts/lib/cli-exit.cjs +546 -44
  474. package/scripts/lib/drift-scan.cjs +308 -0
  475. package/scripts/lib/exit-code-registry.cjs +98 -0
  476. package/scripts/lib/ndjson-reporter.cjs +119 -0
  477. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  478. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  479. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  480. package/scripts/lint-canary-version-leak.cjs +73 -0
  481. package/scripts/lint-command-contract.cjs +96 -13
  482. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  483. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  484. package/scripts/lint-default-flip-documentation.cjs +193 -0
  485. package/scripts/lint-docs-guard-registration.cjs +495 -0
  486. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  487. package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
  488. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  489. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  490. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  491. package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
  492. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  493. package/scripts/lint-milestone-window-drift.cjs +468 -0
  494. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  495. package/scripts/lint-phase-enumeration-drift.cjs +492 -0
  496. package/scripts/lint-plan-count-drift.cjs +318 -0
  497. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  498. package/scripts/lint-planning-prompt-drift.cjs +471 -0
  499. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  500. package/scripts/lint-regression-test-names.cjs +15 -13
  501. package/scripts/lint-removed-but-needed.cjs +488 -0
  502. package/scripts/lint-seam-enforcement.cjs +182 -0
  503. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  504. package/scripts/lint-source-test-name-collision.cjs +241 -0
  505. package/scripts/lint-state-field-drift.cjs +805 -0
  506. package/scripts/lint-state-write-path-drift.cjs +950 -0
  507. package/scripts/lint-test-file-count.allowlist.json +137 -8
  508. package/scripts/lint-test-file-count.cjs +25 -3
  509. package/scripts/lint-unreachable-guard-drift.cjs +830 -0
  510. package/scripts/lint-vendored-deps.cjs +297 -0
  511. package/scripts/mutation-matrix.cjs +599 -50
  512. package/scripts/pr-changed-files.cjs +63 -0
  513. package/scripts/pr-template-policy.cjs +14 -4
  514. package/scripts/prompt-injection-scan.sh +100 -14
  515. package/scripts/require-issue-link-policy.cjs +192 -0
  516. package/scripts/secret-scan.sh +75 -13
  517. package/scripts/select-docs-guards.cjs +56 -0
  518. package/scripts/sync-runtime-launcher.cjs +24 -7
  519. package/skills/gsd-autonomous/SKILL.md +0 -1
  520. package/skills/gsd-code-review/SKILL.md +1 -1
  521. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  522. package/skills/gsd-execute-phase/SKILL.md +1 -2
  523. package/skills/gsd-import/SKILL.md +1 -1
  524. package/skills/gsd-map-codebase/SKILL.md +1 -1
  525. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  526. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  527. package/skills/gsd-new-milestone/SKILL.md +1 -1
  528. package/skills/gsd-next/SKILL.md +0 -1
  529. package/skills/gsd-plan-phase/SKILL.md +0 -1
  530. package/skills/gsd-progress/SKILL.md +0 -1
  531. package/skills/gsd-quick/SKILL.md +9 -5
  532. package/skills/gsd-review-backlog/SKILL.md +2 -1
  533. package/skills/gsd-stats/SKILL.md +0 -1
  534. package/skills/gsd-verify-work/SKILL.md +1 -1
  535. package/vscode/package.json +1 -1
  536. package/bin/lib/ui-safety-gate.cjs +0 -107
  537. package/gsd-core/workflows/discovery-phase.md +0 -298
  538. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  539. package/gsd-core/workflows/verify-phase.md +0 -574
  540. package/scripts/affected-tests-lib.cjs +0 -554
  541. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  542. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  543. package/scripts/run-affected-tests.cjs +0 -7
  544. package/scripts/run-tests.cjs +0 -1051
@@ -0,0 +1,1192 @@
1
+ "use strict";
2
+ /**
3
+ * Complexity-triggered refactor extension point — analyzer, evaluator, and
4
+ * baseline persistence (issue #1953).
5
+ *
6
+ * LEAF MODULE — imports ONLY: node:fs, node:path. No other src/ imports.
7
+ *
8
+ * Pipeline: analyzeSource (decision-point complexity per function, via
9
+ * stripLiterals to blank comments/string/regex content while preserving
10
+ * length and line numbers) -> evaluateCandidates (threshold/jump-delta
11
+ * scoring against a stable per-function baseline anchor) -> nextBaseline /
12
+ * reanchorBaseline (anchor bookkeeping; the anchor never advances on a
13
+ * plain evaluate — only an explicit disposition moves it, via
14
+ * reanchorBaseline) -> renderProposal / parseProposal (the on-disk
15
+ * `-REFACTOR.md` artifact) -> readBaseline / writeBaseline (I/O, atomic
16
+ * temp-then-rename, never throws).
17
+ *
18
+ * Authoritative surface: .gsd/phase/feat-1953-complexity-triggered-refactor/41-api-contract.md
19
+ * Executable spec: tests/complexity-trigger.test.cjs
20
+ *
21
+ * Out of scope for this module: git invocation, config reads,
22
+ * capability-activation checks, CLI arg parsing, and the ship-gate query —
23
+ * those live in their own modules (command router / ship gate, not this
24
+ * leaf module).
25
+ *
26
+ * Exports:
27
+ * Constants: BASELINE_FILE_NAME, PROPOSAL_SUFFIX, SCHEMA_VERSION,
28
+ * DEFAULTS, ANALYZABLE_EXTENSIONS, VERDICT, REASON (includes
29
+ * REFACTOR_FILE_UNREADABLE for an unreadable/confined-escape file)
30
+ * Pure: stripLiterals, analyzeSource, isAnalyzablePath,
31
+ * evaluateCandidates, nextBaseline, reanchorBaseline,
32
+ * renderProposal, parseProposal
33
+ * I/O: readBaseline, writeBaseline
34
+ */
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.REASON = exports.VERDICT = exports.ANALYZABLE_EXTENSIONS = exports.DEFAULTS = exports.SCHEMA_VERSION = exports.PROPOSAL_SUFFIX = exports.BASELINE_FILE_NAME = void 0;
40
+ exports.stripLiterals = stripLiterals;
41
+ exports.analyzeSource = analyzeSource;
42
+ exports.isAnalyzablePath = isAnalyzablePath;
43
+ exports.evaluateCandidates = evaluateCandidates;
44
+ exports.nextBaseline = nextBaseline;
45
+ exports.reanchorBaseline = reanchorBaseline;
46
+ exports.renderProposal = renderProposal;
47
+ exports.parseProposal = parseProposal;
48
+ exports.readBaseline = readBaseline;
49
+ exports.writeBaseline = writeBaseline;
50
+ const node_fs_1 = __importDefault(require("node:fs"));
51
+ const node_path_1 = __importDefault(require("node:path"));
52
+ // ─── Constants ─────────────────────────────────────────────────────────────
53
+ exports.BASELINE_FILE_NAME = 'complexity-baseline.json'; // under .planning/
54
+ exports.PROPOSAL_SUFFIX = '-REFACTOR.md'; // ${PHASE_DIR}/${PADDED}-REFACTOR.md
55
+ exports.SCHEMA_VERSION = 1;
56
+ exports.DEFAULTS = Object.freeze({ threshold: 15, jumpDelta: 5 });
57
+ exports.ANALYZABLE_EXTENSIONS = Object.freeze(['.js', '.cjs', '.mjs', '.ts', '.cts', '.mts']);
58
+ exports.VERDICT = Object.freeze({
59
+ TRIGGERED: 'triggered',
60
+ BELOW_THRESHOLD: 'below_threshold',
61
+ SKIPPED: 'skipped',
62
+ });
63
+ /**
64
+ * Frozen reason enum. Adding a code = three coordinated changes: this enum,
65
+ * the emitting call site, and the Object.keys(REASON).sort() lock test.
66
+ */
67
+ exports.REASON = Object.freeze({
68
+ REFACTOR_OK: 'refactor_ok',
69
+ REFACTOR_DISABLED: 'refactor_disabled',
70
+ REFACTOR_NO_TOUCHED_FILES: 'refactor_no_touched_files',
71
+ REFACTOR_GIT_UNAVAILABLE: 'refactor_git_unavailable',
72
+ REFACTOR_ANALYZER_UNSUPPORTED: 'refactor_analyzer_unsupported',
73
+ REFACTOR_ANALYZER_UNPARSEABLE: 'refactor_analyzer_unparseable',
74
+ REFACTOR_FILE_UNREADABLE: 'refactor_file_unreadable',
75
+ REFACTOR_BASELINE_MALFORMED: 'refactor_baseline_malformed',
76
+ REFACTOR_BASELINE_WRITE_FAILED: 'refactor_baseline_write_failed',
77
+ REFACTOR_STRICT_NOT_ENFORCING: 'refactor_strict_not_enforcing',
78
+ REFACTOR_ARTIFACT_NOT_FOUND: 'refactor_artifact_not_found',
79
+ REFACTOR_ALREADY_DISPOSITIONED: 'refactor_already_dispositioned',
80
+ REFACTOR_DECLINE_REASON_EMPTY: 'refactor_decline_reason_empty',
81
+ REFACTOR_INVALID_PHASE: 'refactor_invalid_phase',
82
+ REFACTOR_USAGE: 'refactor_usage',
83
+ });
84
+ // ─── stripLiterals ──────────────────────────────────────────────────────────
85
+ /**
86
+ * Words after which a following `/` must be interpreted as the start of an
87
+ * expression (so a `/` there is a regex literal, never division).
88
+ */
89
+ const REGEX_KEYWORD_CONTEXT_WORDS = new Set([
90
+ 'return', 'typeof', 'instanceof', 'in', 'of', 'new', 'delete', 'void',
91
+ 'throw', 'case', 'do', 'else', 'yield', 'await', 'default',
92
+ ]);
93
+ function blankChar(ch) {
94
+ return ch === '\n' ? '\n' : ' ';
95
+ }
96
+ /**
97
+ * Replaces comment and literal *content* with spaces, preserving length and
98
+ * every newline so line numbers survive. Handles `//`, `/* *\/`, `'`, `"`,
99
+ * backtick templates (`${...}` interpolations are kept as code), regex
100
+ * literals (disambiguated from division by the preceding significant
101
+ * token), and backslash escapes. Unterminated string / template / block
102
+ * comment => { ok:false, reason: REFACTOR_ANALYZER_UNPARSEABLE }.
103
+ */
104
+ function stripLiterals(source) {
105
+ const len = source.length;
106
+ const out = new Array(len);
107
+ let i = 0;
108
+ let lastTokenIsValue = false;
109
+ const templateStack = [];
110
+ while (i < len) {
111
+ const top = templateStack[templateStack.length - 1];
112
+ if (top && top.state === 'literal') {
113
+ const ch = source[i];
114
+ if (ch === '\\') {
115
+ out[i] = blankChar(ch);
116
+ i++;
117
+ if (i < len) {
118
+ out[i] = blankChar(source[i]);
119
+ i++;
120
+ }
121
+ continue;
122
+ }
123
+ if (ch === '`') {
124
+ out[i] = ' ';
125
+ templateStack.pop();
126
+ i++;
127
+ lastTokenIsValue = true;
128
+ continue;
129
+ }
130
+ if (ch === '$' && source[i + 1] === '{') {
131
+ out[i] = '$';
132
+ out[i + 1] = '{';
133
+ top.state = 'interp';
134
+ top.braceDepth = 0;
135
+ i += 2;
136
+ lastTokenIsValue = false;
137
+ continue;
138
+ }
139
+ out[i] = blankChar(ch);
140
+ i++;
141
+ continue;
142
+ }
143
+ const ch = source[i];
144
+ // Line comment.
145
+ if (ch === '/' && source[i + 1] === '/') {
146
+ while (i < len && source[i] !== '\n') {
147
+ out[i] = ' ';
148
+ i++;
149
+ }
150
+ continue;
151
+ }
152
+ // Block comment.
153
+ if (ch === '/' && source[i + 1] === '*') {
154
+ out[i] = ' ';
155
+ out[i + 1] = ' ';
156
+ i += 2;
157
+ let closed = false;
158
+ while (i < len) {
159
+ if (source[i] === '*' && source[i + 1] === '/') {
160
+ out[i] = ' ';
161
+ out[i + 1] = ' ';
162
+ i += 2;
163
+ closed = true;
164
+ break;
165
+ }
166
+ out[i] = blankChar(source[i]);
167
+ i++;
168
+ }
169
+ if (!closed)
170
+ return { ok: false, reason: exports.REASON.REFACTOR_ANALYZER_UNPARSEABLE };
171
+ lastTokenIsValue = false;
172
+ continue;
173
+ }
174
+ // Single/double-quoted string. A raw (unescaped) newline before the
175
+ // closing quote is treated as unterminated (matches real JS syntax).
176
+ if (ch === '\'' || ch === '"') {
177
+ const quote = ch;
178
+ out[i] = ' ';
179
+ i++;
180
+ let closed = false;
181
+ while (i < len) {
182
+ const c = source[i];
183
+ if (c === '\n')
184
+ break;
185
+ if (c === '\\') {
186
+ out[i] = ' ';
187
+ i++;
188
+ if (i < len && source[i] !== '\n') {
189
+ out[i] = ' ';
190
+ i++;
191
+ }
192
+ continue;
193
+ }
194
+ if (c === quote) {
195
+ out[i] = ' ';
196
+ i++;
197
+ closed = true;
198
+ break;
199
+ }
200
+ out[i] = ' ';
201
+ i++;
202
+ }
203
+ if (!closed)
204
+ return { ok: false, reason: exports.REASON.REFACTOR_ANALYZER_UNPARSEABLE };
205
+ lastTokenIsValue = true;
206
+ continue;
207
+ }
208
+ // Template literal open.
209
+ if (ch === '`') {
210
+ out[i] = ' ';
211
+ templateStack.push({ state: 'literal', braceDepth: 0 });
212
+ i++;
213
+ continue;
214
+ }
215
+ // Regex literal candidate — only when the preceding significant token
216
+ // indicates an expression context (never after a value-producing token).
217
+ if (ch === '/' && !lastTokenIsValue) {
218
+ const start = i;
219
+ out[i] = ' ';
220
+ i++;
221
+ let inClass = false;
222
+ let closed = false;
223
+ while (i < len) {
224
+ const c = source[i];
225
+ if (c === '\n')
226
+ break;
227
+ if (c === '\\') {
228
+ out[i] = ' ';
229
+ i++;
230
+ if (i < len && source[i] !== '\n') {
231
+ out[i] = ' ';
232
+ i++;
233
+ }
234
+ continue;
235
+ }
236
+ if (c === '[') {
237
+ inClass = true;
238
+ out[i] = ' ';
239
+ i++;
240
+ continue;
241
+ }
242
+ if (c === ']') {
243
+ inClass = false;
244
+ out[i] = ' ';
245
+ i++;
246
+ continue;
247
+ }
248
+ if (c === '/' && !inClass) {
249
+ out[i] = ' ';
250
+ i++;
251
+ closed = true;
252
+ break;
253
+ }
254
+ out[i] = ' ';
255
+ i++;
256
+ }
257
+ if (closed) {
258
+ while (i < len && /[a-zA-Z]/.test(source[i])) {
259
+ out[i] = ' ';
260
+ i++;
261
+ }
262
+ lastTokenIsValue = true;
263
+ continue;
264
+ }
265
+ // Not actually a regex literal (never closed) — fall back to
266
+ // treating the '/' as division and re-scan normally from there.
267
+ i = start;
268
+ out[i] = source[i];
269
+ i++;
270
+ lastTokenIsValue = false;
271
+ continue;
272
+ }
273
+ // Template interpolation brace tracking (real code inside ${...}).
274
+ if (top && top.state === 'interp') {
275
+ if (ch === '{') {
276
+ top.braceDepth++;
277
+ out[i] = ch;
278
+ i++;
279
+ lastTokenIsValue = false;
280
+ continue;
281
+ }
282
+ if (ch === '}') {
283
+ if (top.braceDepth === 0) {
284
+ out[i] = '}';
285
+ top.state = 'literal';
286
+ i++;
287
+ lastTokenIsValue = false;
288
+ continue;
289
+ }
290
+ top.braceDepth--;
291
+ out[i] = ch;
292
+ i++;
293
+ lastTokenIsValue = true;
294
+ continue;
295
+ }
296
+ }
297
+ // Identifier / keyword.
298
+ if (/[A-Za-z_$]/.test(ch)) {
299
+ let j = i;
300
+ while (j < len && /[A-Za-z0-9_$]/.test(source[j]))
301
+ j++;
302
+ const word = source.slice(i, j);
303
+ for (let k = i; k < j; k++)
304
+ out[k] = source[k];
305
+ lastTokenIsValue = !REGEX_KEYWORD_CONTEXT_WORDS.has(word);
306
+ i = j;
307
+ continue;
308
+ }
309
+ // Numeric literal.
310
+ if (/[0-9]/.test(ch)) {
311
+ let j = i;
312
+ while (j < len && /[0-9a-fA-FxXoObB.]/.test(source[j]))
313
+ j++;
314
+ for (let k = i; k < j; k++)
315
+ out[k] = source[k];
316
+ lastTokenIsValue = true;
317
+ i = j;
318
+ continue;
319
+ }
320
+ if (ch === ')' || ch === ']') {
321
+ out[i] = ch;
322
+ i++;
323
+ lastTokenIsValue = true;
324
+ continue;
325
+ }
326
+ if (ch === '\n') {
327
+ out[i] = '\n';
328
+ i++;
329
+ continue;
330
+ }
331
+ out[i] = ch;
332
+ i++;
333
+ if (!/\s/.test(ch))
334
+ lastTokenIsValue = false;
335
+ }
336
+ if (templateStack.length > 0) {
337
+ return { ok: false, reason: exports.REASON.REFACTOR_ANALYZER_UNPARSEABLE };
338
+ }
339
+ return { ok: true, stripped: out.join('') };
340
+ }
341
+ // ─── analyzeSource ──────────────────────────────────────────────────────────
342
+ const DECISION_KEYWORDS = new Set(['if', 'for', 'while', 'do', 'case', 'catch']);
343
+ // Words that can textually precede `(...) {` without being a method/function
344
+ // definition — must be excluded from the method-shorthand heuristic so
345
+ // `if (x) {` / `switch (x) {` etc. are never mistaken for a function.
346
+ const NON_METHOD_WORDS = new Set([...DECISION_KEYWORDS, 'switch', 'with']);
347
+ function findMatchingParen(s, openIdx) {
348
+ let depth = 0;
349
+ for (let k = openIdx; k < s.length; k++) {
350
+ if (s[k] === '(')
351
+ depth++;
352
+ else if (s[k] === ')') {
353
+ depth--;
354
+ if (depth === 0)
355
+ return k;
356
+ }
357
+ }
358
+ return -1;
359
+ }
360
+ /**
361
+ * Attempts to skip a generic type-parameter list starting at `s[idx] === '<'`
362
+ * (e.g. `<T>`, `<T extends X>`, `<T = Y>`, `<T, U>`, nested `<T extends
363
+ * Record<string, X>>`). Returns the index just past the matching top-level
364
+ * `>`, or -1 when the content is not shaped like one — either the brackets
365
+ * never balance, or an operator token that never appears inside a type
366
+ * parameter list (`&&`, `||`, `==`, `<=`, `>=`, arithmetic, a bare `?`)
367
+ * shows up first. This — plus the caller only ever committing to the skip
368
+ * when a `(` immediately follows the close — is what keeps a `<` used as a
369
+ * less-than comparison from being mistaken for generics: a real comparison
370
+ * either fails to balance before an unrelated bracket closes, contains one
371
+ * of the disallowed operator tokens, or is not immediately followed by a
372
+ * parameter list.
373
+ */
374
+ function skipGenericParamList(s, idx) {
375
+ const len = s.length;
376
+ let i = idx + 1;
377
+ const stack = ['>'];
378
+ while (i < len) {
379
+ const ch = s[i];
380
+ const next = s[i + 1];
381
+ if (ch === '=' && next === '>') {
382
+ i += 2;
383
+ continue;
384
+ } // nested function-type arrow (e.g. a default's `(a:X)=>Y`)
385
+ if ((ch === '=' && next === '=')
386
+ || (ch === '<' && next === '=')
387
+ || (ch === '>' && next === '=')
388
+ || (ch === '&' && next === '&')
389
+ || (ch === '|' && next === '|')
390
+ || ch === '+' || ch === '*' || ch === '%' || ch === '!'
391
+ || ch === '?' || ch === ';' || ch === '/' || ch === '-')
392
+ return -1;
393
+ if (ch === '<') {
394
+ stack.push('>');
395
+ i++;
396
+ continue;
397
+ }
398
+ if (ch === '(') {
399
+ stack.push(')');
400
+ i++;
401
+ continue;
402
+ }
403
+ if (ch === '[') {
404
+ stack.push(']');
405
+ i++;
406
+ continue;
407
+ }
408
+ if (ch === '{') {
409
+ stack.push('}');
410
+ i++;
411
+ continue;
412
+ }
413
+ if (ch === '>' || ch === ')' || ch === ']' || ch === '}') {
414
+ if (stack.length === 0 || stack[stack.length - 1] !== ch)
415
+ return -1;
416
+ stack.pop();
417
+ i++;
418
+ if (stack.length === 0)
419
+ return i;
420
+ continue;
421
+ }
422
+ i++;
423
+ }
424
+ return -1;
425
+ }
426
+ /**
427
+ * Attempts to skip a return-type / type-predicate annotation starting at
428
+ * `s[idx] === ':'` (e.g. `: Promise<void>`, `: Record<string, X>`, `: A |
429
+ * B`, `: { a: number }`, `: string[]`, `: x is Foo`). Returns the index of
430
+ * the terminator that follows — a function-body `{`, an arrow `=>`, or a
431
+ * statement-ending `;` — never consuming the terminator itself, or -1 when
432
+ * the content is not shaped like a type. `expectStart` tracks whether a
433
+ * fresh type token may begin here so an object-type literal's own `{`/`}`
434
+ * (`: { a: number }`) is never mistaken for the function body, and an
435
+ * unexpected/unmatched closing bracket (e.g. the real end of an enclosing
436
+ * `switch`/ternary this `:` was actually a member of, not a return type)
437
+ * aborts rather than guesses.
438
+ */
439
+ function skipTypeExpr(s, idx) {
440
+ const len = s.length;
441
+ let i = idx + 1;
442
+ const stack = [];
443
+ let expectStart = true;
444
+ while (i < len) {
445
+ const ch = s[i];
446
+ if (/\s/.test(ch)) {
447
+ i++;
448
+ continue;
449
+ }
450
+ const next = s[i + 1];
451
+ if (stack.length === 0) {
452
+ if (ch === ';')
453
+ return i;
454
+ if (ch === '=' && next === '>')
455
+ return i;
456
+ if (ch === '{' && !expectStart)
457
+ return i;
458
+ if (ch === ',' && !expectStart)
459
+ return i;
460
+ if (ch === '?')
461
+ return -1; // a bare top-level '?' means this was a ternary, not a return type
462
+ }
463
+ if (ch === '=' && next === '>') {
464
+ i += 2;
465
+ expectStart = true;
466
+ continue;
467
+ } // nested function-type arrow
468
+ if ((ch === '=' && next === '=')
469
+ || (ch === '<' && next === '=')
470
+ || (ch === '>' && next === '=')
471
+ || (ch === '&' && next === '&')
472
+ || (ch === '|' && next === '|')
473
+ || ch === '+' || ch === '*' || ch === '%' || ch === '!')
474
+ return -1;
475
+ if (ch === '<') {
476
+ stack.push('>');
477
+ i++;
478
+ expectStart = true;
479
+ continue;
480
+ }
481
+ if (ch === '(') {
482
+ stack.push(')');
483
+ i++;
484
+ expectStart = true;
485
+ continue;
486
+ }
487
+ if (ch === '[') {
488
+ stack.push(']');
489
+ i++;
490
+ expectStart = true;
491
+ continue;
492
+ }
493
+ if (ch === '{') {
494
+ stack.push('}');
495
+ i++;
496
+ expectStart = true;
497
+ continue;
498
+ }
499
+ if (ch === '>' || ch === ')' || ch === ']' || ch === '}') {
500
+ if (stack.length === 0 || stack[stack.length - 1] !== ch)
501
+ return -1;
502
+ stack.pop();
503
+ i++;
504
+ expectStart = false;
505
+ continue;
506
+ }
507
+ if (ch === '|' || ch === '&' || ch === ',' || ch === '.' || ch === ':') {
508
+ i++;
509
+ expectStart = true;
510
+ continue;
511
+ }
512
+ if (/[A-Za-z0-9_$]/.test(ch)) {
513
+ let j = i;
514
+ while (j < len && /[A-Za-z0-9_$]/.test(s[j]))
515
+ j++;
516
+ i = j;
517
+ expectStart = false;
518
+ continue;
519
+ }
520
+ i++;
521
+ }
522
+ return -1;
523
+ }
524
+ function newlinesBetween(s, from, to) {
525
+ let n = 0;
526
+ for (let k = from; k < to; k++)
527
+ if (s[k] === '\n')
528
+ n++;
529
+ return n;
530
+ }
531
+ /** Best-effort name inference for an arrow function from `NAME = ` or `NAME: ` immediately preceding it. */
532
+ function inferArrowName(s, pos) {
533
+ let j = pos - 1;
534
+ while (j >= 0 && /\s/.test(s[j]))
535
+ j--;
536
+ const isPlainEquals = j >= 0 && s[j] === '='
537
+ && s[j - 1] !== '=' && s[j - 1] !== '!' && s[j - 1] !== '<' && s[j - 1] !== '>';
538
+ const isPropColon = j >= 0 && s[j] === ':';
539
+ if (!isPlainEquals && !isPropColon)
540
+ return '';
541
+ j--;
542
+ while (j >= 0 && /\s/.test(s[j]))
543
+ j--;
544
+ const end = j + 1;
545
+ while (j >= 0 && /[A-Za-z0-9_$]/.test(s[j]))
546
+ j--;
547
+ return s.slice(j + 1, end);
548
+ }
549
+ function scanFunctions(stripped) {
550
+ const len = stripped.length;
551
+ let i = 0;
552
+ let line = 1;
553
+ let depth = 0;
554
+ const stack = [];
555
+ const results = [];
556
+ function currentFunctionFrame() {
557
+ for (let k = stack.length - 1; k >= 0; k--) {
558
+ const f = stack[k];
559
+ if (f.kind === 'function')
560
+ return f;
561
+ }
562
+ return null;
563
+ }
564
+ function bump() {
565
+ const f = currentFunctionFrame();
566
+ if (f)
567
+ f.score += 1;
568
+ }
569
+ function closeExprFramesIfNeeded(atDepth) {
570
+ for (;;) {
571
+ const top = stack[stack.length - 1];
572
+ if (top && top.kind === 'function' && top.mode === 'expr' && top.entryDepth === atDepth) {
573
+ stack.pop();
574
+ results.push({ name: top.name, startLine: top.startLine, endLine: line, score: top.score });
575
+ }
576
+ else {
577
+ break;
578
+ }
579
+ }
580
+ }
581
+ while (i < len) {
582
+ const ch = stripped[i];
583
+ if (ch === '\n') {
584
+ line++;
585
+ i++;
586
+ continue;
587
+ }
588
+ if (/\s/.test(ch)) {
589
+ i++;
590
+ continue;
591
+ }
592
+ if (ch === '&' && stripped[i + 1] === '&') {
593
+ bump();
594
+ i += 2;
595
+ continue;
596
+ }
597
+ if (ch === '|' && stripped[i + 1] === '|') {
598
+ bump();
599
+ i += 2;
600
+ continue;
601
+ }
602
+ if (ch === '?') {
603
+ if (stripped[i + 1] === '.') {
604
+ i += 2;
605
+ continue;
606
+ }
607
+ if (stripped[i + 1] === '?') {
608
+ i += 2;
609
+ continue;
610
+ }
611
+ bump();
612
+ i += 1;
613
+ continue;
614
+ }
615
+ if (ch === '(') {
616
+ const closeIdx = findMatchingParen(stripped, i);
617
+ if (closeIdx !== -1) {
618
+ let k = closeIdx + 1;
619
+ while (k < len && /\s/.test(stripped[k]))
620
+ k++;
621
+ if (stripped[k] === ':') {
622
+ const afterType = skipTypeExpr(stripped, k);
623
+ if (afterType !== -1) {
624
+ k = afterType;
625
+ while (k < len && /\s/.test(stripped[k]))
626
+ k++;
627
+ }
628
+ }
629
+ if (stripped[k] === '=' && stripped[k + 1] === '>') {
630
+ const name = inferArrowName(stripped, i);
631
+ const startLine = line;
632
+ let bodyStart = k + 2;
633
+ while (bodyStart < len && /\s/.test(stripped[bodyStart]))
634
+ bodyStart++;
635
+ const nl = newlinesBetween(stripped, i, bodyStart);
636
+ if (stripped[bodyStart] === '{') {
637
+ line += nl;
638
+ stack.push({ kind: 'function', name, startLine, score: 1, mode: 'brace' });
639
+ depth++;
640
+ i = bodyStart + 1;
641
+ continue;
642
+ }
643
+ line += nl;
644
+ stack.push({ kind: 'function', name, startLine, score: 1, mode: 'expr', entryDepth: depth });
645
+ i = bodyStart;
646
+ continue;
647
+ }
648
+ }
649
+ depth++;
650
+ i++;
651
+ continue;
652
+ }
653
+ if (ch === '[') {
654
+ depth++;
655
+ i++;
656
+ continue;
657
+ }
658
+ if (ch === ')' || ch === ']') {
659
+ closeExprFramesIfNeeded(depth);
660
+ depth--;
661
+ i++;
662
+ continue;
663
+ }
664
+ if (ch === '{') {
665
+ stack.push({ kind: 'block' });
666
+ depth++;
667
+ i++;
668
+ continue;
669
+ }
670
+ if (ch === '}') {
671
+ closeExprFramesIfNeeded(depth);
672
+ depth--;
673
+ const top = stack.pop();
674
+ if (top && top.kind === 'function' && top.mode === 'brace') {
675
+ results.push({ name: top.name, startLine: top.startLine, endLine: line, score: top.score });
676
+ }
677
+ i++;
678
+ continue;
679
+ }
680
+ if (ch === ';' || ch === ',') {
681
+ closeExprFramesIfNeeded(depth);
682
+ i++;
683
+ continue;
684
+ }
685
+ if (/[A-Za-z_$]/.test(ch)) {
686
+ let j = i;
687
+ while (j < len && /[A-Za-z0-9_$]/.test(stripped[j]))
688
+ j++;
689
+ const word = stripped.slice(i, j);
690
+ const wordLine = line;
691
+ if (word === 'function') {
692
+ let k = j;
693
+ while (k < len && /\s/.test(stripped[k]))
694
+ k++;
695
+ if (stripped[k] === '*') {
696
+ k++;
697
+ while (k < len && /\s/.test(stripped[k]))
698
+ k++;
699
+ }
700
+ let name = '';
701
+ if (k < len && /[A-Za-z_$]/.test(stripped[k])) {
702
+ let m = k;
703
+ while (m < len && /[A-Za-z0-9_$]/.test(stripped[m]))
704
+ m++;
705
+ name = stripped.slice(k, m);
706
+ k = m;
707
+ while (k < len && /\s/.test(stripped[k]))
708
+ k++;
709
+ }
710
+ if (stripped[k] === '<') {
711
+ const afterGenerics = skipGenericParamList(stripped, k);
712
+ if (afterGenerics !== -1) {
713
+ let kk = afterGenerics;
714
+ while (kk < len && /\s/.test(stripped[kk]))
715
+ kk++;
716
+ if (stripped[kk] === '(')
717
+ k = kk; // only commit if genuinely followed by params
718
+ }
719
+ }
720
+ if (stripped[k] === '(') {
721
+ const closeIdx = findMatchingParen(stripped, k);
722
+ if (closeIdx !== -1) {
723
+ let p = closeIdx + 1;
724
+ while (p < len && /\s/.test(stripped[p]))
725
+ p++;
726
+ if (stripped[p] === ':') {
727
+ const afterType = skipTypeExpr(stripped, p);
728
+ if (afterType !== -1) {
729
+ p = afterType;
730
+ while (p < len && /\s/.test(stripped[p]))
731
+ p++;
732
+ }
733
+ }
734
+ if (stripped[p] === '{') {
735
+ line += newlinesBetween(stripped, i, p);
736
+ stack.push({ kind: 'function', name, startLine: wordLine, score: 1, mode: 'brace' });
737
+ depth++;
738
+ i = p + 1;
739
+ continue;
740
+ }
741
+ }
742
+ }
743
+ i = j;
744
+ continue;
745
+ }
746
+ if (NON_METHOD_WORDS.has(word)) {
747
+ if (DECISION_KEYWORDS.has(word))
748
+ bump();
749
+ i = j;
750
+ continue;
751
+ }
752
+ {
753
+ let k = j;
754
+ while (k < len && /\s/.test(stripped[k]))
755
+ k++;
756
+ if (stripped[k] === '<') {
757
+ const afterGenerics = skipGenericParamList(stripped, k);
758
+ if (afterGenerics !== -1) {
759
+ let kk = afterGenerics;
760
+ while (kk < len && /\s/.test(stripped[kk]))
761
+ kk++;
762
+ if (stripped[kk] === '(')
763
+ k = kk; // only commit if genuinely followed by params
764
+ }
765
+ }
766
+ if (stripped[k] === '(') {
767
+ const closeIdx = findMatchingParen(stripped, k);
768
+ if (closeIdx !== -1) {
769
+ let p = closeIdx + 1;
770
+ while (p < len && /\s/.test(stripped[p]))
771
+ p++;
772
+ if (stripped[p] === ':') {
773
+ const afterType = skipTypeExpr(stripped, p);
774
+ if (afterType !== -1) {
775
+ p = afterType;
776
+ while (p < len && /\s/.test(stripped[p]))
777
+ p++;
778
+ }
779
+ }
780
+ if (stripped[p] === '{') {
781
+ line += newlinesBetween(stripped, i, p);
782
+ stack.push({ kind: 'function', name: word, startLine: wordLine, score: 1, mode: 'brace' });
783
+ depth++;
784
+ i = p + 1;
785
+ continue;
786
+ }
787
+ }
788
+ }
789
+ else if (stripped[k] === '=' && stripped[k + 1] === '>') {
790
+ const name = inferArrowName(stripped, i);
791
+ let bodyStart = k + 2;
792
+ while (bodyStart < len && /\s/.test(stripped[bodyStart]))
793
+ bodyStart++;
794
+ const nl = newlinesBetween(stripped, i, bodyStart);
795
+ if (stripped[bodyStart] === '{') {
796
+ line += nl;
797
+ stack.push({ kind: 'function', name, startLine: wordLine, score: 1, mode: 'brace' });
798
+ depth++;
799
+ i = bodyStart + 1;
800
+ continue;
801
+ }
802
+ line += nl;
803
+ stack.push({ kind: 'function', name, startLine: wordLine, score: 1, mode: 'expr', entryDepth: depth });
804
+ i = bodyStart;
805
+ continue;
806
+ }
807
+ }
808
+ i = j;
809
+ continue;
810
+ }
811
+ i++;
812
+ }
813
+ closeExprFramesIfNeeded(depth);
814
+ return results;
815
+ }
816
+ /**
817
+ * Normalizes CRLF -> LF *before* analysis so scores and line numbers are
818
+ * identical either way. Base score 1 per function, +1 for each of: if,
819
+ * else-if (the if, not the else), for, for..of, for..in, while, do, case
820
+ * (not default), catch, &&, ||, ?:. Word-boundary matched. Not counted:
821
+ * ?., ??, bare else, default:. Decision points are attributed to the
822
+ * innermost enclosing function; top-level code belongs to no function.
823
+ */
824
+ function analyzeSource(source) {
825
+ const normalized = source.replace(/\r\n/g, '\n');
826
+ const stripped = stripLiterals(normalized);
827
+ if (!stripped.ok)
828
+ return { ok: false, reason: stripped.reason };
829
+ return { ok: true, method: 'decision-points', functions: scanFunctions(stripped.stripped) };
830
+ }
831
+ // ─── isAnalyzablePath ───────────────────────────────────────────────────────
832
+ /**
833
+ * Normalizes separators unconditionally (never via path.sep). True iff the
834
+ * extension is analyzable and the path is not under tests/ or
835
+ * gsd-core/bin/lib/.
836
+ */
837
+ function isAnalyzablePath(relPath) {
838
+ const normalized = relPath.replace(/\\/g, '/');
839
+ const ext = node_path_1.default.posix.extname(normalized);
840
+ if (!exports.ANALYZABLE_EXTENSIONS.includes(ext))
841
+ return false;
842
+ if (normalized === 'tests' || normalized.startsWith('tests/'))
843
+ return false;
844
+ if (normalized === 'gsd-core/bin/lib' || normalized.startsWith('gsd-core/bin/lib/'))
845
+ return false;
846
+ return true;
847
+ }
848
+ // ─── evaluateCandidates ─────────────────────────────────────────────────────
849
+ function coercePositiveNumber(v, fallback) {
850
+ const n = typeof v === 'number' ? v : Number(v);
851
+ return Number.isFinite(n) && n > 0 ? n : fallback;
852
+ }
853
+ function evaluateCandidates(input) {
854
+ const thresholdUsed = coercePositiveNumber(input.threshold, exports.DEFAULTS.threshold);
855
+ const jumpDeltaUsed = coercePositiveNumber(input.jumpDelta, exports.DEFAULTS.jumpDelta);
856
+ const candidates = [];
857
+ const skipped = [];
858
+ for (const entry of input.analyzed) {
859
+ if (!entry.ok) {
860
+ skipped.push({ file: entry.file, reason: entry.reason });
861
+ continue;
862
+ }
863
+ for (const fn of entry.functions) {
864
+ const key = `${entry.file}::${fn.name}`;
865
+ const baselineEntry = input.baseline[key];
866
+ const baselineScore = baselineEntry ? baselineEntry.score : null;
867
+ const reasons = [];
868
+ if (fn.score > thresholdUsed)
869
+ reasons.push('threshold');
870
+ let delta = null;
871
+ if (baselineScore !== null) {
872
+ delta = fn.score - baselineScore;
873
+ if (delta > jumpDeltaUsed)
874
+ reasons.push('jump');
875
+ }
876
+ if (reasons.length > 0) {
877
+ candidates.push({
878
+ file: entry.file,
879
+ name: fn.name,
880
+ startLine: fn.startLine,
881
+ score: fn.score,
882
+ baseline: baselineScore,
883
+ delta,
884
+ reasons,
885
+ });
886
+ }
887
+ }
888
+ }
889
+ candidates.sort((a, b) => {
890
+ if (b.score !== a.score)
891
+ return b.score - a.score;
892
+ const ad = a.delta === null ? -Infinity : a.delta;
893
+ const bd = b.delta === null ? -Infinity : b.delta;
894
+ if (bd !== ad)
895
+ return bd - ad;
896
+ if (a.file !== b.file)
897
+ return a.file < b.file ? -1 : 1;
898
+ if (a.name !== b.name)
899
+ return a.name < b.name ? -1 : 1;
900
+ return a.startLine - b.startLine;
901
+ });
902
+ return {
903
+ verdict: candidates.length > 0 ? exports.VERDICT.TRIGGERED : exports.VERDICT.BELOW_THRESHOLD,
904
+ candidates,
905
+ target: candidates[0] ?? null,
906
+ skipped,
907
+ thresholdUsed,
908
+ jumpDeltaUsed,
909
+ };
910
+ }
911
+ // ─── Baseline anchor bookkeeping ────────────────────────────────────────────
912
+ /**
913
+ * Anchor semantics (see 41-api-contract.md "Anchor semantics" — corrected
914
+ * 2026-08-09): for each successfully analyzed function, no prior entry
915
+ * inserts an anchor at the current score (first observation); a prior
916
+ * entry carries forward UNCHANGED. The anchor never advances on a plain
917
+ * evaluate, whether or not the function triggered — only reanchorBaseline
918
+ * (called by an explicit disposition) moves it.
919
+ */
920
+ function nextBaseline(prev, analyzed, opts = {}) {
921
+ const next = {};
922
+ const seenKeys = new Set();
923
+ for (const entry of analyzed) {
924
+ if (!entry.ok)
925
+ continue;
926
+ for (const fn of entry.functions) {
927
+ const key = `${entry.file}::${fn.name}`;
928
+ seenKeys.add(key);
929
+ const existing = prev[key];
930
+ if (existing) {
931
+ next[key] = existing;
932
+ }
933
+ else {
934
+ next[key] = opts.phase ? { score: fn.score, phase: opts.phase } : { score: fn.score };
935
+ }
936
+ }
937
+ }
938
+ const analyzedFileSet = new Set(opts.analyzedFiles ?? []);
939
+ for (const [key, entry] of Object.entries(prev)) {
940
+ if (seenKeys.has(key))
941
+ continue;
942
+ const file = key.slice(0, key.lastIndexOf('::'));
943
+ if (analyzedFileSet.has(file))
944
+ continue; // function no longer exists -> prune
945
+ next[key] = entry; // file untouched this run -> never prune
946
+ }
947
+ return next;
948
+ }
949
+ /** Moves the anchor for `key` to `score` — accept re-anchors to the post-refactor score, decline re-anchors to the current (higher) score. */
950
+ function reanchorBaseline(prev, key, score, opts = {}) {
951
+ return {
952
+ ...prev,
953
+ [key]: opts.phase ? { score, phase: opts.phase } : { score },
954
+ };
955
+ }
956
+ // ─── Proposal render/parse ──────────────────────────────────────────────────
957
+ const PROPOSAL_JSON_FENCE_OPEN = '````json';
958
+ const PROPOSAL_JSON_FENCE_CLOSE = '````';
959
+ // Reader-side fence tolerance (#3657 — same defect class as the WINDOWS.md
960
+ // ledger): CommonMark formatters (Prettier et al.) narrow the written
961
+ // 4-backtick fence to the shortest legal width (3) whenever the body holds no
962
+ // backtick run, and a canonical-JSON candidates array never does. Locate the
963
+ // block by a line-anchored 3+ fence and close on a run at least as wide
964
+ // (CommonMark: a shorter run does not close). The writer above is unchanged.
965
+ // This module is a leaf (CONTEXT.md — imports only node:fs/node:path), so the
966
+ // span logic is local rather than imported from broken-windows.
967
+ const PROPOSAL_FENCE_OPEN_RE = /^(`{3,})json[ \t]*\r?$/m;
968
+ function locateProposalJsonBlock(text) {
969
+ const open = text.match(PROPOSAL_FENCE_OPEN_RE);
970
+ if (!open || open.index === undefined)
971
+ return null;
972
+ const width = open[1].length;
973
+ const bodyStart = open.index + open[0].length;
974
+ for (const close of text.slice(bodyStart).matchAll(/^(`{3,})[ \t]*\r?$/gm)) {
975
+ if (close[1].length < width)
976
+ continue;
977
+ const bodyEnd = close.index ?? 0;
978
+ return { jsonText: text.slice(bodyStart, bodyStart + bodyEnd).trim() };
979
+ }
980
+ return null;
981
+ }
982
+ function renderProposal(p) {
983
+ const fm = [
984
+ '---',
985
+ `schema_version: ${p.schema_version}`,
986
+ `status: ${p.status}`,
987
+ `phase: ${p.phase}`,
988
+ `target_file: ${p.target_file}`,
989
+ `target_function: ${p.target_function}`,
990
+ `score: ${p.score}`,
991
+ `baseline: ${p.baseline === null ? 'null' : p.baseline}`,
992
+ `delta: ${p.delta === null ? 'null' : p.delta}`,
993
+ `metric: ${p.metric}`,
994
+ `recorded_at: ${p.recorded_at}`,
995
+ `resolved_at: ${p.resolved_at === null ? 'null' : p.resolved_at}`,
996
+ `reason: ${JSON.stringify(p.reason)}`,
997
+ '---',
998
+ '',
999
+ ].join('\n');
1000
+ const header = [
1001
+ `# Refactor proposal: ${p.target_file}::${p.target_function}`,
1002
+ '',
1003
+ `Score ${p.score}${p.baseline !== null ? ` (baseline ${p.baseline}, delta ${p.delta})` : ''} — status: ${p.status}.`,
1004
+ '',
1005
+ ].join('\n');
1006
+ const jsonBlock = [
1007
+ PROPOSAL_JSON_FENCE_OPEN,
1008
+ JSON.stringify(p.candidates, null, 2),
1009
+ PROPOSAL_JSON_FENCE_CLOSE,
1010
+ '',
1011
+ ].join('\n');
1012
+ return [fm, header, jsonBlock].join('\n');
1013
+ }
1014
+ /**
1015
+ * Parses a rendered proposal back into its IR. Frontmatter is the fast
1016
+ * scalar path; the JSON block is authoritative for candidates. Fails
1017
+ * closed (returns null) on any structural drift rather than guessing.
1018
+ */
1019
+ function parseProposal(text) {
1020
+ try {
1021
+ if (!text.startsWith('---\n') && !text.startsWith('---\r\n'))
1022
+ return null;
1023
+ const headerEnd = text.startsWith('---\r\n') ? 5 : 4;
1024
+ const closeIdx = text.indexOf('\n---', headerEnd);
1025
+ if (closeIdx === -1)
1026
+ return null;
1027
+ const yamlBody = text.slice(headerEnd, closeIdx);
1028
+ const fm = {};
1029
+ for (const rawLine of yamlBody.split(/\r?\n/)) {
1030
+ const line = rawLine.replace(/\r$/, '');
1031
+ if (line.trim() === '')
1032
+ continue;
1033
+ const m = line.match(/^([a-zA-Z0-9_]+):\s*(.*)$/);
1034
+ if (!m)
1035
+ return null;
1036
+ fm[m[1]] = m[2].trim();
1037
+ }
1038
+ const span = locateProposalJsonBlock(text);
1039
+ if (span === null)
1040
+ return null;
1041
+ const jsonText = span.jsonText;
1042
+ let candidates;
1043
+ try {
1044
+ candidates = JSON.parse(jsonText);
1045
+ }
1046
+ catch {
1047
+ return null;
1048
+ }
1049
+ if (!Array.isArray(candidates))
1050
+ return null;
1051
+ const status = fm.status;
1052
+ if (status !== 'proposed' && status !== 'accepted' && status !== 'declined')
1053
+ return null;
1054
+ const schemaVersion = Number(fm.schema_version);
1055
+ if (!Number.isInteger(schemaVersion))
1056
+ return null;
1057
+ const score = Number(fm.score);
1058
+ if (!Number.isFinite(score))
1059
+ return null;
1060
+ const baseline = fm.baseline === 'null' ? null : Number(fm.baseline);
1061
+ if (baseline !== null && !Number.isFinite(baseline))
1062
+ return null;
1063
+ const delta = fm.delta === 'null' ? null : Number(fm.delta);
1064
+ if (delta !== null && !Number.isFinite(delta))
1065
+ return null;
1066
+ const resolvedAt = fm.resolved_at === 'null' || fm.resolved_at === undefined ? null : fm.resolved_at;
1067
+ let reason = '';
1068
+ try {
1069
+ const parsedReason = JSON.parse(fm.reason ?? '""');
1070
+ reason = typeof parsedReason === 'string' ? parsedReason : (fm.reason ?? '');
1071
+ }
1072
+ catch {
1073
+ reason = fm.reason ?? '';
1074
+ }
1075
+ return {
1076
+ schema_version: schemaVersion,
1077
+ status,
1078
+ phase: fm.phase ?? '',
1079
+ target_file: fm.target_file ?? '',
1080
+ target_function: fm.target_function ?? '',
1081
+ score,
1082
+ baseline,
1083
+ delta,
1084
+ metric: 'decision-points',
1085
+ recorded_at: fm.recorded_at ?? '',
1086
+ resolved_at: resolvedAt,
1087
+ reason,
1088
+ candidates: candidates,
1089
+ };
1090
+ }
1091
+ catch {
1092
+ return null;
1093
+ }
1094
+ }
1095
+ function baselinePath(planningDir) {
1096
+ return node_path_1.default.join(planningDir, exports.BASELINE_FILE_NAME);
1097
+ }
1098
+ const RENAME_RETRY_ERRNOS = new Set(['EPERM', 'EBUSY', 'EACCES']);
1099
+ const RENAME_MAX_ATTEMPTS = 5;
1100
+ const RENAME_BACKOFF_MS = 25;
1101
+ function renameWithRetry(fsImpl, tmp, target) {
1102
+ let lastErr;
1103
+ for (let attempt = 0; attempt < RENAME_MAX_ATTEMPTS; attempt++) {
1104
+ try {
1105
+ fsImpl.renameSync(tmp, target);
1106
+ return;
1107
+ }
1108
+ catch (err) {
1109
+ lastErr = err;
1110
+ const code = (err && typeof err === 'object' && 'code' in err)
1111
+ ? String(err.code)
1112
+ : '';
1113
+ if (code && RENAME_RETRY_ERRNOS.has(code) && attempt < RENAME_MAX_ATTEMPTS - 1) {
1114
+ const delay = RENAME_BACKOFF_MS * Math.pow(2, attempt);
1115
+ const start = Date.now();
1116
+ while (Date.now() - start < delay) {
1117
+ // Busy-wait a very short time — transient locks usually clear quickly.
1118
+ }
1119
+ continue;
1120
+ }
1121
+ throw err;
1122
+ }
1123
+ }
1124
+ throw lastErr;
1125
+ }
1126
+ /**
1127
+ * Degrades to { ok:false, baseline:{}, reason: REFACTOR_BASELINE_MALFORMED }
1128
+ * on absent-but-unreadable, bad JSON, or a non-plain-object root — and
1129
+ * never rewrites the file on a failed read. Missing file is
1130
+ * { ok:true, baseline:{} } with no reason. Never throws.
1131
+ */
1132
+ function readBaseline(planningDir, deps = {}) {
1133
+ const fsImpl = deps.fs ?? node_fs_1.default;
1134
+ const p = baselinePath(planningDir);
1135
+ let raw;
1136
+ try {
1137
+ raw = fsImpl.readFileSync(p, 'utf8');
1138
+ }
1139
+ catch (e) {
1140
+ const code = (e && typeof e === 'object' && 'code' in e)
1141
+ ? String(e.code)
1142
+ : '';
1143
+ if (code === 'ENOENT')
1144
+ return { ok: true, baseline: {} };
1145
+ return { ok: false, baseline: {}, reason: exports.REASON.REFACTOR_BASELINE_MALFORMED };
1146
+ }
1147
+ let parsed;
1148
+ try {
1149
+ parsed = JSON.parse(raw);
1150
+ }
1151
+ catch {
1152
+ return { ok: false, baseline: {}, reason: exports.REASON.REFACTOR_BASELINE_MALFORMED };
1153
+ }
1154
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
1155
+ return { ok: false, baseline: {}, reason: exports.REASON.REFACTOR_BASELINE_MALFORMED };
1156
+ }
1157
+ return { ok: true, baseline: parsed };
1158
+ }
1159
+ /**
1160
+ * Writes `<name>.<pid>.tmp` then renames; on any failure unlinks the temp
1161
+ * file and returns { ok:false, reason: REFACTOR_BASELINE_WRITE_FAILED }.
1162
+ * Never throws.
1163
+ */
1164
+ function writeBaseline(planningDir, baseline, deps = {}) {
1165
+ const fsImpl = deps.fs ?? node_fs_1.default;
1166
+ const p = baselinePath(planningDir);
1167
+ const tmp = `${p}.${process.pid}.tmp`;
1168
+ try {
1169
+ if (!fsImpl.existsSync(planningDir)) {
1170
+ fsImpl.mkdirSync(planningDir, { recursive: true });
1171
+ }
1172
+ fsImpl.writeFileSync(tmp, JSON.stringify(baseline, null, 2), 'utf8');
1173
+ }
1174
+ catch {
1175
+ try {
1176
+ fsImpl.unlinkSync(tmp);
1177
+ }
1178
+ catch { /* best-effort cleanup */ }
1179
+ return { ok: false, reason: exports.REASON.REFACTOR_BASELINE_WRITE_FAILED };
1180
+ }
1181
+ try {
1182
+ renameWithRetry(fsImpl, tmp, p);
1183
+ }
1184
+ catch {
1185
+ try {
1186
+ fsImpl.unlinkSync(tmp);
1187
+ }
1188
+ catch { /* best-effort cleanup */ }
1189
+ return { ok: false, reason: exports.REASON.REFACTOR_BASELINE_WRITE_FAILED };
1190
+ }
1191
+ return { ok: true };
1192
+ }