@entelligentsia/forgecli 0.19.0 → 0.20.3

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 (269) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/dist/bin/config.js +9 -9
  3. package/dist/bin/config.js.map +1 -1
  4. package/dist/bin/forge.js +3 -3
  5. package/dist/bin/forge.js.map +1 -1
  6. package/dist/bin/update-cli.js +2 -1
  7. package/dist/bin/update-cli.js.map +1 -1
  8. package/dist/extensions/forgecli/add-pipeline.js.map +1 -1
  9. package/dist/extensions/forgecli/add-task.js.map +1 -1
  10. package/dist/extensions/forgecli/approve.d.ts +2 -2
  11. package/dist/extensions/forgecli/approve.js +2 -2
  12. package/dist/extensions/forgecli/approve.js.map +1 -1
  13. package/dist/extensions/forgecli/ask-user-tool.js.map +1 -1
  14. package/dist/extensions/forgecli/audience-gate.js.map +1 -1
  15. package/dist/extensions/forgecli/calibrate.d.ts +1 -1
  16. package/dist/extensions/forgecli/calibrate.js +8 -10
  17. package/dist/extensions/forgecli/calibrate.js.map +1 -1
  18. package/dist/extensions/forgecli/collate.d.ts +2 -2
  19. package/dist/extensions/forgecli/collate.js +2 -2
  20. package/dist/extensions/forgecli/collate.js.map +1 -1
  21. package/dist/extensions/forgecli/commit.d.ts +2 -2
  22. package/dist/extensions/forgecli/commit.js +2 -2
  23. package/dist/extensions/forgecli/commit.js.map +1 -1
  24. package/dist/extensions/forgecli/config-command.js.map +1 -1
  25. package/dist/extensions/forgecli/config-layer.js +4 -2
  26. package/dist/extensions/forgecli/config-layer.js.map +1 -1
  27. package/dist/extensions/forgecli/config-tui/component.d.ts +1 -1
  28. package/dist/extensions/forgecli/config-tui/component.js +25 -21
  29. package/dist/extensions/forgecli/config-tui/component.js.map +1 -1
  30. package/dist/extensions/forgecli/config-tui/handler.js +2 -6
  31. package/dist/extensions/forgecli/config-tui/handler.js.map +1 -1
  32. package/dist/extensions/forgecli/config-tui/keys.js.map +1 -1
  33. package/dist/extensions/forgecli/config-tui/plugin-config-reader.js.map +1 -1
  34. package/dist/extensions/forgecli/config-tui/screens/advanced-menu.d.ts +2 -2
  35. package/dist/extensions/forgecli/config-tui/screens/advanced-menu.js +10 -8
  36. package/dist/extensions/forgecli/config-tui/screens/advanced-menu.js.map +1 -1
  37. package/dist/extensions/forgecli/config-tui/screens/confirm-quit.d.ts +2 -2
  38. package/dist/extensions/forgecli/config-tui/screens/confirm-quit.js +2 -5
  39. package/dist/extensions/forgecli/config-tui/screens/confirm-quit.js.map +1 -1
  40. package/dist/extensions/forgecli/config-tui/screens/override-editor.d.ts +2 -2
  41. package/dist/extensions/forgecli/config-tui/screens/override-editor.js +15 -11
  42. package/dist/extensions/forgecli/config-tui/screens/override-editor.js.map +1 -1
  43. package/dist/extensions/forgecli/config-tui/screens/overrides-list-phases.d.ts +2 -2
  44. package/dist/extensions/forgecli/config-tui/screens/overrides-list-phases.js +15 -11
  45. package/dist/extensions/forgecli/config-tui/screens/overrides-list-phases.js.map +1 -1
  46. package/dist/extensions/forgecli/config-tui/screens/overrides-list.d.ts +2 -2
  47. package/dist/extensions/forgecli/config-tui/screens/overrides-list.js +7 -4
  48. package/dist/extensions/forgecli/config-tui/screens/overrides-list.js.map +1 -1
  49. package/dist/extensions/forgecli/config-tui/screens/persona-editor.d.ts +2 -2
  50. package/dist/extensions/forgecli/config-tui/screens/persona-editor.js +6 -12
  51. package/dist/extensions/forgecli/config-tui/screens/persona-editor.js.map +1 -1
  52. package/dist/extensions/forgecli/config-tui/screens/persona-picker.d.ts +2 -2
  53. package/dist/extensions/forgecli/config-tui/screens/persona-picker.js +3 -6
  54. package/dist/extensions/forgecli/config-tui/screens/persona-picker.js.map +1 -1
  55. package/dist/extensions/forgecli/config-tui/screens/personas-list.d.ts +2 -2
  56. package/dist/extensions/forgecli/config-tui/screens/personas-list.js +2 -2
  57. package/dist/extensions/forgecli/config-tui/screens/personas-list.js.map +1 -1
  58. package/dist/extensions/forgecli/config-tui/screens/shared.d.ts +1 -1
  59. package/dist/extensions/forgecli/config-tui/screens/shared.js +4 -5
  60. package/dist/extensions/forgecli/config-tui/screens/shared.js.map +1 -1
  61. package/dist/extensions/forgecli/config-tui/screens/show-resolved.d.ts +2 -2
  62. package/dist/extensions/forgecli/config-tui/screens/show-resolved.js +9 -11
  63. package/dist/extensions/forgecli/config-tui/screens/show-resolved.js.map +1 -1
  64. package/dist/extensions/forgecli/config-tui/screens/tier-menu.d.ts +2 -2
  65. package/dist/extensions/forgecli/config-tui/screens/tier-menu.js +2 -2
  66. package/dist/extensions/forgecli/config-tui/screens/tier-menu.js.map +1 -1
  67. package/dist/extensions/forgecli/config-tui/screens/tier-picker.d.ts +2 -2
  68. package/dist/extensions/forgecli/config-tui/screens/tier-picker.js +12 -3
  69. package/dist/extensions/forgecli/config-tui/screens/tier-picker.js.map +1 -1
  70. package/dist/extensions/forgecli/config-tui/screens/types.d.ts +1 -1
  71. package/dist/extensions/forgecli/config-tui/screens.d.ts +10 -10
  72. package/dist/extensions/forgecli/config-tui/screens.js +16 -16
  73. package/dist/extensions/forgecli/config-tui/screens.js.map +1 -1
  74. package/dist/extensions/forgecli/config-tui/state/buffer.d.ts +2 -2
  75. package/dist/extensions/forgecli/config-tui/state/buffer.js +1 -2
  76. package/dist/extensions/forgecli/config-tui/state/buffer.js.map +1 -1
  77. package/dist/extensions/forgecli/config-tui/state/constants.js.map +1 -1
  78. package/dist/extensions/forgecli/config-tui/state/index.d.ts +3 -3
  79. package/dist/extensions/forgecli/config-tui/state/index.js +2 -2
  80. package/dist/extensions/forgecli/config-tui/state/index.js.map +1 -1
  81. package/dist/extensions/forgecli/config-tui/state/init.js.map +1 -1
  82. package/dist/extensions/forgecli/config-tui/state/reducer.js +2 -4
  83. package/dist/extensions/forgecli/config-tui/state/reducer.js.map +1 -1
  84. package/dist/extensions/forgecli/config-tui/state/selectors.d.ts +1 -1
  85. package/dist/extensions/forgecli/config-tui/state/selectors.js +7 -6
  86. package/dist/extensions/forgecli/config-tui/state/selectors.js.map +1 -1
  87. package/dist/extensions/forgecli/config-tui/state.d.ts +3 -3
  88. package/dist/extensions/forgecli/config-tui/state.js +2 -2
  89. package/dist/extensions/forgecli/config-tui/state.js.map +1 -1
  90. package/dist/extensions/forgecli/config-tui/theme.js +1 -1
  91. package/dist/extensions/forgecli/config-tui/theme.js.map +1 -1
  92. package/dist/extensions/forgecli/config-tui/tier-meta.js.map +1 -1
  93. package/dist/extensions/forgecli/config-writer.js +4 -2
  94. package/dist/extensions/forgecli/config-writer.js.map +1 -1
  95. package/dist/extensions/forgecli/enhance.d.ts +2 -2
  96. package/dist/extensions/forgecli/enhance.js +3 -3
  97. package/dist/extensions/forgecli/enhance.js.map +1 -1
  98. package/dist/extensions/forgecli/fix-bug.d.ts +1 -1
  99. package/dist/extensions/forgecli/fix-bug.js +148 -58
  100. package/dist/extensions/forgecli/fix-bug.js.map +1 -1
  101. package/dist/extensions/forgecli/forge-artifact-tool.d.ts +8 -1
  102. package/dist/extensions/forgecli/forge-artifact-tool.js +85 -15
  103. package/dist/extensions/forgecli/forge-artifact-tool.js.map +1 -1
  104. package/dist/extensions/forgecli/forge-commands.js.map +1 -1
  105. package/dist/extensions/forgecli/forge-header.d.ts +1 -1
  106. package/dist/extensions/forgecli/forge-header.js +1 -1
  107. package/dist/extensions/forgecli/forge-header.js.map +1 -1
  108. package/dist/extensions/forgecli/forge-init/phase-descriptors.js +14 -5
  109. package/dist/extensions/forgecli/forge-init/phase-descriptors.js.map +1 -1
  110. package/dist/extensions/forgecli/forge-init/phase4-register.js +20 -1
  111. package/dist/extensions/forgecli/forge-init/phase4-register.js.map +1 -1
  112. package/dist/extensions/forgecli/forge-init.js +5 -7
  113. package/dist/extensions/forgecli/forge-init.js.map +1 -1
  114. package/dist/extensions/forgecli/forge-subagent.d.ts +1 -1
  115. package/dist/extensions/forgecli/forge-subagent.js +4 -2
  116. package/dist/extensions/forgecli/forge-subagent.js.map +1 -1
  117. package/dist/extensions/forgecli/forge-tools.js +10 -4
  118. package/dist/extensions/forgecli/forge-tools.js.map +1 -1
  119. package/dist/extensions/forgecli/forge-update-command.d.ts +32 -11
  120. package/dist/extensions/forgecli/forge-update-command.js +207 -155
  121. package/dist/extensions/forgecli/forge-update-command.js.map +1 -1
  122. package/dist/extensions/forgecli/friction-emit.js +3 -5
  123. package/dist/extensions/forgecli/friction-emit.js.map +1 -1
  124. package/dist/extensions/forgecli/health-check.js +10 -6
  125. package/dist/extensions/forgecli/health-check.js.map +1 -1
  126. package/dist/extensions/forgecli/hook-dispatcher.js +2 -2
  127. package/dist/extensions/forgecli/hook-dispatcher.js.map +1 -1
  128. package/dist/extensions/forgecli/hooks/check-update.js.map +1 -1
  129. package/dist/extensions/forgecli/hooks/forge-permissions.js.map +1 -1
  130. package/dist/extensions/forgecli/hooks/post-init-hook.js +1 -1
  131. package/dist/extensions/forgecli/hooks/post-init-hook.js.map +1 -1
  132. package/dist/extensions/forgecli/hooks/post-sprint-hook.js +1 -1
  133. package/dist/extensions/forgecli/hooks/post-sprint-hook.js.map +1 -1
  134. package/dist/extensions/forgecli/hooks/write-guard.js +5 -3
  135. package/dist/extensions/forgecli/hooks/write-guard.js.map +1 -1
  136. package/dist/extensions/forgecli/implement.d.ts +2 -2
  137. package/dist/extensions/forgecli/implement.js +2 -2
  138. package/dist/extensions/forgecli/implement.js.map +1 -1
  139. package/dist/extensions/forgecli/index.js +47 -41
  140. package/dist/extensions/forgecli/index.js.map +1 -1
  141. package/dist/extensions/forgecli/input-router.js +4 -1
  142. package/dist/extensions/forgecli/input-router.js.map +1 -1
  143. package/dist/extensions/forgecli/lib/catalog-helpers.js +2 -1
  144. package/dist/extensions/forgecli/lib/catalog-helpers.js.map +1 -1
  145. package/dist/extensions/forgecli/lib/catalog-loader.js.map +1 -1
  146. package/dist/extensions/forgecli/lib/catalog-types.js +1 -6
  147. package/dist/extensions/forgecli/lib/catalog-types.js.map +1 -1
  148. package/dist/extensions/forgecli/lib/exec-helpers.js +1 -1
  149. package/dist/extensions/forgecli/lib/exec-helpers.js.map +1 -1
  150. package/dist/extensions/forgecli/lib/forge-config.js.map +1 -1
  151. package/dist/extensions/forgecli/lib/orchestrator-preflight.js.map +1 -1
  152. package/dist/extensions/forgecli/lib/parsers.js.map +1 -1
  153. package/dist/extensions/forgecli/lib/spawn-store-cli.js +17 -7
  154. package/dist/extensions/forgecli/lib/spawn-store-cli.js.map +1 -1
  155. package/dist/extensions/forgecli/materialize.js.map +1 -1
  156. package/dist/extensions/forgecli/migrate.js +1 -1
  157. package/dist/extensions/forgecli/migrate.js.map +1 -1
  158. package/dist/extensions/forgecli/migration-engine.js +3 -6
  159. package/dist/extensions/forgecli/migration-engine.js.map +1 -1
  160. package/dist/extensions/forgecli/model-resolver.js.map +1 -1
  161. package/dist/extensions/forgecli/model-validator.js.map +1 -1
  162. package/dist/extensions/forgecli/parsers/persona-skill-loader.js +1 -1
  163. package/dist/extensions/forgecli/parsers/workflow-loader.js +2 -6
  164. package/dist/extensions/forgecli/parsers/workflow-loader.js.map +1 -1
  165. package/dist/extensions/forgecli/paths/migrator.js +2 -7
  166. package/dist/extensions/forgecli/paths/migrator.js.map +1 -1
  167. package/dist/extensions/forgecli/plan.d.ts +2 -2
  168. package/dist/extensions/forgecli/plan.js +2 -2
  169. package/dist/extensions/forgecli/plan.js.map +1 -1
  170. package/dist/extensions/forgecli/quiz-agent.js.map +1 -1
  171. package/dist/extensions/forgecli/read-command.js +1 -1
  172. package/dist/extensions/forgecli/read-command.js.map +1 -1
  173. package/dist/extensions/forgecli/regenerate.js +1 -3
  174. package/dist/extensions/forgecli/regenerate.js.map +1 -1
  175. package/dist/extensions/forgecli/remove-command.js.map +1 -1
  176. package/dist/extensions/forgecli/report-bug.js +2 -2
  177. package/dist/extensions/forgecli/report-bug.js.map +1 -1
  178. package/dist/extensions/forgecli/retrospective.js +2 -2
  179. package/dist/extensions/forgecli/retrospective.js.map +1 -1
  180. package/dist/extensions/forgecli/review-code.d.ts +2 -2
  181. package/dist/extensions/forgecli/review-code.js +2 -2
  182. package/dist/extensions/forgecli/review-code.js.map +1 -1
  183. package/dist/extensions/forgecli/review-plan.d.ts +2 -2
  184. package/dist/extensions/forgecli/review-plan.js +2 -2
  185. package/dist/extensions/forgecli/review-plan.js.map +1 -1
  186. package/dist/extensions/forgecli/review-server.js +3 -5
  187. package/dist/extensions/forgecli/review-server.js.map +1 -1
  188. package/dist/extensions/forgecli/run-sprint.d.ts +1 -1
  189. package/dist/extensions/forgecli/run-sprint.js +27 -21
  190. package/dist/extensions/forgecli/run-sprint.js.map +1 -1
  191. package/dist/extensions/forgecli/run-task.js +102 -42
  192. package/dist/extensions/forgecli/run-task.js.map +1 -1
  193. package/dist/extensions/forgecli/session-registry.js.map +1 -1
  194. package/dist/extensions/forgecli/skill-curator-subagent.js +4 -8
  195. package/dist/extensions/forgecli/skill-curator-subagent.js.map +1 -1
  196. package/dist/extensions/forgecli/skill-retriever.js +1 -1
  197. package/dist/extensions/forgecli/skill-retriever.js.map +1 -1
  198. package/dist/extensions/forgecli/skill-usage-tracker.js +1 -1
  199. package/dist/extensions/forgecli/skill-usage-tracker.js.map +1 -1
  200. package/dist/extensions/forgecli/status-command.js.map +1 -1
  201. package/dist/extensions/forgecli/store-error-remediation.js +16 -8
  202. package/dist/extensions/forgecli/store-error-remediation.js.map +1 -1
  203. package/dist/extensions/forgecli/store-query.js.map +1 -1
  204. package/dist/extensions/forgecli/store-repair.js.map +1 -1
  205. package/dist/extensions/forgecli/store-resolver.js +1 -1
  206. package/dist/extensions/forgecli/store-resolver.js.map +1 -1
  207. package/dist/extensions/forgecli/store-validator.js +1 -1
  208. package/dist/extensions/forgecli/store-validator.js.map +1 -1
  209. package/dist/extensions/forgecli/subagent/agents.js +1 -1
  210. package/dist/extensions/forgecli/subagent/agents.js.map +1 -1
  211. package/dist/extensions/forgecli/subagent/index.js +1 -1
  212. package/dist/extensions/forgecli/subagent/index.js.map +1 -1
  213. package/dist/extensions/forgecli/test-orchestrate.js +1 -1
  214. package/dist/extensions/forgecli/test-orchestrate.js.map +1 -1
  215. package/dist/extensions/forgecli/thread-switcher.js +8 -18
  216. package/dist/extensions/forgecli/thread-switcher.js.map +1 -1
  217. package/dist/extensions/forgecli/transition-guard.js +1 -1
  218. package/dist/extensions/forgecli/transition-guard.js.map +1 -1
  219. package/dist/extensions/forgecli/update-tools.js +1 -2
  220. package/dist/extensions/forgecli/update-tools.js.map +1 -1
  221. package/dist/extensions/forgecli/validate.d.ts +2 -2
  222. package/dist/extensions/forgecli/validate.js +2 -2
  223. package/dist/extensions/forgecli/validate.js.map +1 -1
  224. package/dist/extensions/forgecli/viewport-events.js +2 -2
  225. package/dist/extensions/forgecli/viewport-events.js.map +1 -1
  226. package/dist/extensions/forgecli/wf-engine/engine.d.ts +1 -1
  227. package/dist/extensions/forgecli/wf-engine/engine.js +51 -31
  228. package/dist/extensions/forgecli/wf-engine/engine.js.map +1 -1
  229. package/dist/extensions/forgecli/wf-engine/event-parser.js.map +1 -1
  230. package/dist/extensions/forgecli/wf-engine/id-gen.js +4 -1
  231. package/dist/extensions/forgecli/wf-engine/id-gen.js.map +1 -1
  232. package/dist/extensions/forgecli/wf-engine/loader.js +7 -7
  233. package/dist/extensions/forgecli/wf-engine/loader.js.map +1 -1
  234. package/dist/extensions/forgecli/wf-engine/predicate.js +14 -7
  235. package/dist/extensions/forgecli/wf-engine/predicate.js.map +1 -1
  236. package/dist/extensions/forgecli/wf-engine/prompt-compiler.js.map +1 -1
  237. package/dist/extensions/forgecli/wf-engine/register.js +1 -1
  238. package/dist/extensions/forgecli/wf-engine/register.js.map +1 -1
  239. package/dist/extensions/forgecli/wf-engine/remit-check.js +3 -3
  240. package/dist/extensions/forgecli/wf-engine/remit-check.js.map +1 -1
  241. package/dist/extensions/forgecli/wf-engine/state-store.js +7 -3
  242. package/dist/extensions/forgecli/wf-engine/state-store.js.map +1 -1
  243. package/dist/extensions/forgecli/wf-engine/worker.js +3 -3
  244. package/dist/extensions/forgecli/wf-engine/worker.js.map +1 -1
  245. package/dist/extensions/forgecli/whats-new-widget.js +8 -6
  246. package/dist/extensions/forgecli/whats-new-widget.js.map +1 -1
  247. package/dist/extensions/forgecli/whats-new.js +1 -2
  248. package/dist/extensions/forgecli/whats-new.js.map +1 -1
  249. package/dist/forge-payload/commands/update.md +23 -1
  250. package/dist/forge-payload/schemas/_defs/phaseSummary.schema.json +18 -0
  251. package/dist/forge-payload/schemas/bug.schema.json +40 -0
  252. package/dist/forge-payload/schemas/collation-state.schema.json +16 -0
  253. package/dist/forge-payload/schemas/config.schema.json +215 -0
  254. package/dist/forge-payload/schemas/enum-catalog.json +71 -0
  255. package/dist/forge-payload/schemas/event-sidecar.schema.json +22 -0
  256. package/dist/forge-payload/schemas/event.schema.json +184 -0
  257. package/dist/forge-payload/schemas/feature.schema.json +22 -0
  258. package/dist/forge-payload/schemas/progress-entry.schema.json +16 -0
  259. package/dist/forge-payload/schemas/project-context.schema.json +167 -0
  260. package/dist/forge-payload/schemas/project-overlay.schema.json +25 -0
  261. package/dist/forge-payload/schemas/proposal.schema.json +40 -0
  262. package/dist/forge-payload/schemas/sprint.schema.json +27 -0
  263. package/dist/forge-payload/schemas/structure-versions.schema.json +57 -0
  264. package/dist/forge-payload/schemas/task.schema.json +43 -0
  265. package/dist/forge-payload/schemas/transitions/bug.json +31 -0
  266. package/dist/forge-payload/schemas/transitions/sprint.json +46 -0
  267. package/dist/forge-payload/schemas/transitions/task.json +109 -0
  268. package/dist/forge-payload/tools/manage-config.cjs +120 -2
  269. package/package.json +3 -3
@@ -36,29 +36,29 @@
36
36
  // Reference: lib/orchestrator-preflight.ts (N-H-H, FORGE-S25-T17).
37
37
  //
38
38
  // N-H-E tag: see inline comment at the materialization skip (~line 707 / checkMaterialization).
39
+ import { spawnSync } from "node:child_process";
39
40
  import * as fs from "node:fs";
40
41
  import * as path from "node:path";
41
42
  import { fileURLToPath } from "node:url";
42
- import { spawnSync } from "node:child_process";
43
+ import { assertAudience, CallerContextStore } from "./audience-gate.js";
43
44
  // ModelRegistry/AuthStorage no longer instantiated here — use ctx.modelRegistry
44
45
  // so extension-registered providers (registered against the live session) are
45
46
  // visible to validateModelConfig. Creating a fresh registry here would miss
46
47
  // them and produce spurious MODEL_UNAVAILABLE warnings (FORGE-BUG-001).
47
48
  import { loadLayeredConfig } from "./config-layer.js";
48
- import { resolveModelForPhase } from "./model-resolver.js";
49
- import { runOrchestratorPreflight } from "./lib/orchestrator-preflight.js";
50
- import { assertAudience, CallerContextStore } from "./audience-gate.js";
51
- import { resolveToCanonicalId, resolveToolDir } from "./store-resolver.js";
52
- import { checkMaterialization } from "./lib/manifest-checker.js";
53
- import { readPersonaDir as readPersonaDirBug, readPipelineNames as readPipelineNamesBug } from "./lib/catalog-helpers.js";
54
49
  import { loadForgePersona, runForgeSubagent } from "./forge-subagent.js";
55
50
  import { getSubagentTools } from "./forge-tools.js";
51
+ import { readPersonaDir as readPersonaDirBug, readPipelineNames as readPipelineNamesBug, } from "./lib/catalog-helpers.js";
56
52
  import { discoverForgeConfigCached } from "./lib/forge-config.js";
53
+ import { checkMaterialization } from "./lib/manifest-checker.js";
54
+ import { runOrchestratorPreflight } from "./lib/orchestrator-preflight.js";
55
+ import { resolveModelForPhase } from "./model-resolver.js";
57
56
  import { loadWorkflow } from "./parsers/workflow-loader.js";
57
+ import { buildPhaseEvent, drainFrictionFile, emitEvent, findPredecessorIndex, formatLocalTime, isNonInteractive, judgementFromSummary, runPreflightGate, validateId, } from "./run-task.js";
58
58
  import { getSessionRegistry } from "./session-registry.js";
59
+ import { resolveToCanonicalId, resolveToolDir } from "./store-resolver.js";
59
60
  import { attachViewportObserver } from "./viewport-events.js";
60
61
  import { fmtPhaseSummary } from "./viewport-renderer.js";
61
- import { validateId, isNonInteractive, formatLocalTime, emitEvent, findPredecessorIndex, runPreflightGate, buildPhaseEvent, drainFrictionFile, judgementFromSummary, } from "./run-task.js";
62
62
  // ── Bug phase descriptor table ──────────────────────────────────────────────
63
63
  //
64
64
  // Decoded from .forge/workflows/fix_bug.md and the task prompt's BUG_PHASES.
@@ -79,25 +79,25 @@ export const BUG_PHASES = [
79
79
  // Phases mapped to null use update-status bug instead of set-bug-summary
80
80
  // for verdict tracking (Option B).
81
81
  export const BUG_SUMMARY_KEY_BY_ROLE = {
82
- "triage": "triage",
82
+ triage: "triage",
83
83
  "plan-fix": "plan",
84
84
  "review-plan": "review_plan",
85
- "implement": "implementation",
85
+ implement: "implementation",
86
86
  "review-code": "code_review",
87
- "approve": "approve", // read from bug.summaries.approve (set-bug-summary)
88
- "commit": null, // commit transitions bug.status → fixed (terminal), no summaries entry
87
+ approve: "approve", // read from bug.summaries.approve (set-bug-summary)
88
+ commit: null, // commit transitions bug.status → fixed (terminal), no summaries entry
89
89
  };
90
90
  // Bug-event type tokens — explicit mapping per review finding #3.
91
91
  // Non-review phases always emit the pass token. Review phases select
92
92
  // pass or fail based on ec.judgement.verdict.
93
93
  export const BUG_TYPE_TOKENS = {
94
- "triage": { pass: "bug-triaged", fail: "bug-triaged" },
94
+ triage: { pass: "bug-triaged", fail: "bug-triaged" },
95
95
  "plan-fix": { pass: "fix-planned", fail: "fix-planned" },
96
96
  "review-plan": { pass: "fix-review-passed", fail: "fix-review-failed" },
97
- "implement": { pass: "fix-implemented", fail: "fix-implemented" },
97
+ implement: { pass: "fix-implemented", fail: "fix-implemented" },
98
98
  "review-code": { pass: "fix-code-review-passed", fail: "fix-code-review-failed" },
99
- "approve": { pass: "fix-approved", fail: "fix-revision-requested" },
100
- "commit": { pass: "bug-committed", fail: "bug-commit-failed" },
99
+ approve: { pass: "fix-approved", fail: "fix-revision-requested" },
100
+ commit: { pass: "bug-committed", fail: "bug-commit-failed" },
101
101
  };
102
102
  // ── Bug FSM transitions ────────────────────────────────────────────────────
103
103
  // Mirrors store-cli BUG_TRANSITIONS. Terminal: `fixed`.
@@ -144,9 +144,7 @@ export function readBugState(cwd, bugId, sessionId) {
144
144
  bestFile = fp;
145
145
  }
146
146
  }
147
- catch {
148
- continue;
149
- }
147
+ catch { }
150
148
  }
151
149
  }
152
150
  catch {
@@ -183,7 +181,9 @@ export function deleteBugState(cwd, bugId) {
183
181
  try {
184
182
  fs.unlinkSync(path.join(cacheDir, entry));
185
183
  }
186
- catch { /* non-fatal */ }
184
+ catch {
185
+ /* non-fatal */
186
+ }
187
187
  }
188
188
  }
189
189
  }
@@ -228,7 +228,9 @@ export function assignNextBugId(storeCli, cwd) {
228
228
  }
229
229
  }
230
230
  }
231
- catch { /* empty store — start from 1 */ }
231
+ catch {
232
+ /* empty store — start from 1 */
233
+ }
232
234
  }
233
235
  const next = maxNum + 1;
234
236
  return `FORGE-BUG-${String(next).padStart(3, "0")}`;
@@ -322,13 +324,13 @@ export function composeBugBody(subWorkflowMd, bugId, phaseRole, bugStatusBeforeP
322
324
  ` node "$FORGE_ROOT/tools/store-cli.cjs" set-bug-summary ${bugId} approve <APPROVE-SUMMARY.json>`,
323
325
  ` The summary's "verdict" field MUST be "approved" or "revision". The downstream commit gate reads this, not bug.status.`,
324
326
  `- Commit phase: on successful git commit, run \`node "$FORGE_ROOT/tools/store-cli.cjs" update-status bug ${bugId} status fixed\` (terminal).`,
325
- `- Do NOT write \"approved\" or \"verified\" to bug.status — those values were removed from the schema in forge v0.44.0.`,
326
- `- Do NOT reference task-specific status values (e.g., \"committed\") or task entity kind.`,
327
+ `- Do NOT write "approved" or "verified" to bug.status — those values were removed from the schema in forge v0.44.0.`,
328
+ `- Do NOT reference task-specific status values (e.g., "committed") or task entity kind.`,
327
329
  "- CRITICAL: All `set-summary` calls must use `set-bug-summary` (not `set-summary`).",
328
330
  ` e.g. node "$FORGE_ROOT/tools/store-cli.cjs" set-bug-summary ${bugId} review_plan <jsonFile>`,
329
331
  `- Preflight gate: use \`--bug\` flag (not \`--task\`). e.g. node "$FORGE_ROOT/tools/preflight-gate.cjs" --phase review-plan --bug ${bugId}`,
330
332
  "- Skip re-running preflight-gate — the orchestrator already checked it. Proceed directly to the review.",
331
- "Any workflow text that says \"task\" should be read as \"bug\" for this context.",
333
+ 'Any workflow text that says "task" should be read as "bug" for this context.',
332
334
  ];
333
335
  // Phase-specific reinforcement when the orchestrator can name the current status.
334
336
  if (phaseRole === "approve" && bugStatusBeforePhase) {
@@ -338,7 +340,7 @@ export function composeBugBody(subWorkflowMd, bugId, phaseRole, bugStatusBeforeP
338
340
  entityKindLines.push(`- Commit phase: after the git commit lands, transition bug.status from '${bugStatusBeforePhase}' to 'fixed'.`);
339
341
  }
340
342
  if (phaseRole === "triage") {
341
- entityKindLines.push("- Triage phase: in addition to writing TRIAGE.md and TRIAGE-SUMMARY.json, the summary MUST include a `route` field set to `\"A\"` or `\"B\"`.", " Path A (short-circuit): severity == minor AND single-file fix ≤ ~20 lines AND no schema/API/migration AND regression test obvious from repro.", " Path B (default): everything else. When in doubt, choose B.", " The orchestrator reads bug.summaries.triage.route to select the downstream phase list.");
343
+ entityKindLines.push('- Triage phase: in addition to writing TRIAGE.md and TRIAGE-SUMMARY.json, the summary MUST include a `route` field set to `"A"` or `"B"`.', " Path A (short-circuit): severity == minor AND single-file fix ≤ ~20 lines AND no schema/API/migration AND regression test obvious from repro.", " Path B (default): everything else. When in doubt, choose B.", " The orchestrator reads bug.summaries.triage.route to select the downstream phase list.");
342
344
  }
343
345
  return [
344
346
  `Read the workflow below and follow it. Bug ID: ${bugId}.`,
@@ -414,14 +416,14 @@ export function extractBugIdFromEvents(events) {
414
416
  const STATUS_KEY = "forge:fix-bug";
415
417
  const MESSAGE_KEY = "forge:fix-bug:message";
416
418
  export async function runBugPipeline(opts) {
417
- const { bugId: initialBugId, originalArg, isNewBug, cwd, ctx, forgeRoot, storeCli, preflightGate, registry, resumeFromState } = opts;
419
+ const { bugId: initialBugId, originalArg, isNewBug, cwd, ctx, forgeRoot, storeCli, preflightGate, registry, resumeFromState, } = opts;
418
420
  // Mutable bugId — for new bugs, pre-assign a real FORGE-BUG-NNN ID
419
421
  // before triage so the subagent never needs to create or discover one.
420
422
  // This replaces the fragile PENDING→capture pattern where the subagent was
421
423
  // expected to create the bug record and we'd fish the ID from events.
422
424
  let bugId = initialBugId;
423
425
  let currentPhaseIndex = resumeFromState?.phaseIndex ?? 0;
424
- let iterationCounts = resumeFromState?.iterationCounts ?? {};
426
+ const iterationCounts = resumeFromState?.iterationCounts ?? {};
425
427
  let lastModel;
426
428
  let lastProvider;
427
429
  // ── Per-persona model routing (Plan 16) ─────────────────────────────────
@@ -436,7 +438,12 @@ export async function runBugPipeline(opts) {
436
438
  for (const e of layeredConfigErrors) {
437
439
  ctx.ui.notify(`× forge:fix-bug — forge-cli config schema error: ${e}`, "error");
438
440
  }
439
- return { status: "failed", lastPhaseIndex: currentPhaseIndex, iterationCounts, lastError: `forge-cli config schema errors: ${layeredConfigErrors.join("; ")}` };
441
+ return {
442
+ status: "failed",
443
+ lastPhaseIndex: currentPhaseIndex,
444
+ iterationCounts,
445
+ lastError: `forge-cli config schema errors: ${layeredConfigErrors.join("; ")}`,
446
+ };
440
447
  }
441
448
  // Pre-flight validation — same shape as run-task / run-sprint.
442
449
  // FORGE-S25-T17: delegated to lib/orchestrator-preflight.ts (H-13).
@@ -484,7 +491,12 @@ export async function runBugPipeline(opts) {
484
491
  const phase = BUG_PHASES[currentPhaseIndex];
485
492
  if (!phase) {
486
493
  ctx.ui.notify(`× forge:fix-bug — invalid phase index ${currentPhaseIndex}`, "error");
487
- return { status: "failed", lastPhaseIndex: currentPhaseIndex, iterationCounts, lastError: `invalid phase index ${currentPhaseIndex}` };
494
+ return {
495
+ status: "failed",
496
+ lastPhaseIndex: currentPhaseIndex,
497
+ iterationCounts,
498
+ lastError: `invalid phase index ${currentPhaseIndex}`,
499
+ };
488
500
  }
489
501
  ctx.ui.setStatus?.(STATUS_KEY, `fix-bug ${bugId}: phase ${currentPhaseIndex + 1}/${BUG_PHASES.length} (${phase.role})`);
490
502
  ctx.ui.notify(`→ ${bugId}: ${phase.role} (phase ${currentPhaseIndex + 1}/${BUG_PHASES.length})`, "info");
@@ -508,7 +520,12 @@ export async function runBugPipeline(opts) {
508
520
  lastError: `sub-workflow read failed: ${e.message ?? "unknown"}`,
509
521
  savedAt: new Date().toISOString(),
510
522
  });
511
- return { status: "failed", lastPhaseIndex: currentPhaseIndex, iterationCounts, lastError: `sub-workflow read failed: ${e.message ?? "unknown"}` };
523
+ return {
524
+ status: "failed",
525
+ lastPhaseIndex: currentPhaseIndex,
526
+ iterationCounts,
527
+ lastError: `sub-workflow read failed: ${e.message ?? "unknown"}`,
528
+ };
512
529
  }
513
530
  // ── 6a. Phase skip (state-aware, defense-in-depth) ─────────────
514
531
  // Belt-and-suspenders alongside the explicit summaries.triage.route
@@ -523,8 +540,8 @@ export async function runBugPipeline(opts) {
523
540
  // removed.
524
541
  const PHASE_SKIP_STATES = {
525
542
  "plan-fix": new Set(["fixed"]),
526
- "implement": new Set(["fixed"]),
527
- "commit": new Set(["fixed"]), // commit writes the terminal status; skip if already there
543
+ implement: new Set(["fixed"]),
544
+ commit: new Set(["fixed"]), // commit writes the terminal status; skip if already there
528
545
  };
529
546
  const bugNow = readBugRecord(bugId, storeCli, cwd);
530
547
  const skipStates = PHASE_SKIP_STATES[phase.role];
@@ -546,14 +563,19 @@ export async function runBugPipeline(opts) {
546
563
  };
547
564
  const synthFile = path.join(cwd, ".forge", "cache", `synthetic-summary-${bugId}-${summaryKey}.json`);
548
565
  fs.writeFileSync(synthFile, JSON.stringify(synthSummary, null, 2), "utf8");
549
- const synthResult = spawnSync("node", [storeCli, "set-bug-summary", bugId, summaryKey, synthFile], { cwd, encoding: "utf8" });
566
+ const synthResult = spawnSync("node", [storeCli, "set-bug-summary", bugId, summaryKey, synthFile], {
567
+ cwd,
568
+ encoding: "utf8",
569
+ });
550
570
  if (synthResult.status !== 0) {
551
571
  ctx.ui.notify(`⚠ forge:fix-bug — synthetic summary write failed for ${phase.role}: ${String(synthResult.stderr).trim()}`, "warning");
552
572
  }
553
573
  try {
554
574
  fs.unlinkSync(synthFile);
555
575
  }
556
- catch { /* non-fatal */ }
576
+ catch {
577
+ /* non-fatal */
578
+ }
557
579
  }
558
580
  currentPhaseIndex++;
559
581
  continue;
@@ -583,7 +605,12 @@ export async function runBugPipeline(opts) {
583
605
  lastError: `preflight gate exit 1 for ${phase.role}`,
584
606
  savedAt: new Date().toISOString(),
585
607
  });
586
- return { status: "halted", lastPhaseIndex: currentPhaseIndex, iterationCounts, lastError: `preflight gate exit 1 for ${phase.role}` };
608
+ return {
609
+ status: "halted",
610
+ lastPhaseIndex: currentPhaseIndex,
611
+ iterationCounts,
612
+ lastError: `preflight gate exit 1 for ${phase.role}`,
613
+ };
587
614
  }
588
615
  if (preflightResult === "escalate") {
589
616
  ctx.ui.notify(`× forge:fix-bug — preflight gate escalated for phase ${phase.role} (exit 2); manual intervention required.`, "error");
@@ -595,7 +622,12 @@ export async function runBugPipeline(opts) {
595
622
  lastError: `preflight gate exit 2 (escalate) for ${phase.role}`,
596
623
  savedAt: new Date().toISOString(),
597
624
  });
598
- return { status: "escalated", lastPhaseIndex: currentPhaseIndex, iterationCounts, lastError: `preflight gate exit 2 (escalate) for ${phase.role}` };
625
+ return {
626
+ status: "escalated",
627
+ lastPhaseIndex: currentPhaseIndex,
628
+ iterationCounts,
629
+ lastError: `preflight gate exit 2 (escalate) for ${phase.role}`,
630
+ };
599
631
  }
600
632
  }
601
633
  // ── 6. Materialization-marker check ───────────────────────────
@@ -611,7 +643,12 @@ export async function runBugPipeline(opts) {
611
643
  for (const marker of markerCheck.missing) {
612
644
  ctx.ui.notify(`× workflow regression: ${marker} not found in ${subWorkflowPath}`, "error");
613
645
  }
614
- return { status: "failed", lastPhaseIndex: currentPhaseIndex, iterationCounts, lastError: `materialization markers missing: ${markerCheck.missing.join(", ")}` };
646
+ return {
647
+ status: "failed",
648
+ lastPhaseIndex: currentPhaseIndex,
649
+ iterationCounts,
650
+ lastError: `materialization markers missing: ${markerCheck.missing.join(", ")}`,
651
+ };
615
652
  }
616
653
  }
617
654
  // ── 5. Audience check ─────────────────────────────────────────
@@ -620,7 +657,8 @@ export async function runBugPipeline(opts) {
620
657
  // Skip the audience gate for the monolithic fix_bug.md; only check the
621
658
  // true sub-workflows (review_plan, review_code, architect_approve, commit_task)
622
659
  // which the subagent does run directly.
623
- const audienceOk = phase.workflowFile === "fix_bug" || CallerContextStore.asSubagent(() => assertAudience({ workflowName: phase.workflowFile, audience: subWorkflowAudience }, ctx));
660
+ const audienceOk = phase.workflowFile === "fix_bug" ||
661
+ CallerContextStore.asSubagent(() => assertAudience({ workflowName: phase.workflowFile, audience: subWorkflowAudience }, ctx));
624
662
  if (!audienceOk) {
625
663
  writeBugState(cwd, {
626
664
  bugId,
@@ -630,7 +668,12 @@ export async function runBugPipeline(opts) {
630
668
  lastError: `audience check failed for ${phase.workflowFile}`,
631
669
  savedAt: new Date().toISOString(),
632
670
  });
633
- return { status: "failed", lastPhaseIndex: currentPhaseIndex, iterationCounts, lastError: `audience check failed for ${phase.workflowFile}` };
671
+ return {
672
+ status: "failed",
673
+ lastPhaseIndex: currentPhaseIndex,
674
+ iterationCounts,
675
+ lastError: `audience check failed for ${phase.workflowFile}`,
676
+ };
634
677
  }
635
678
  // ── Persona load ──────────────────────────────────────────────
636
679
  let persona;
@@ -649,7 +692,12 @@ export async function runBugPipeline(opts) {
649
692
  lastError: `persona load failed: ${e.message ?? "unknown"}`,
650
693
  savedAt: new Date().toISOString(),
651
694
  });
652
- return { status: "failed", lastPhaseIndex: currentPhaseIndex, iterationCounts, lastError: `persona load failed: ${e.message ?? "unknown"}` };
695
+ return {
696
+ status: "failed",
697
+ lastPhaseIndex: currentPhaseIndex,
698
+ iterationCounts,
699
+ lastError: `persona load failed: ${e.message ?? "unknown"}`,
700
+ };
653
701
  }
654
702
  // ── Read bug record for current status ────────────────────────
655
703
  // Skip for PENDING bugIds (bug doesn't exist yet).
@@ -691,7 +739,9 @@ export async function runBugPipeline(opts) {
691
739
  fs.writeFileSync(debugLogPath, lines.slice(-keep).join("\n"), "utf8");
692
740
  }
693
741
  }
694
- catch { /* file may not exist yet */ }
742
+ catch {
743
+ /* file may not exist yet */
744
+ }
695
745
  fs.appendFileSync(debugLogPath, `${JSON.stringify({ ts: new Date().toISOString(), phase: phase.role, ...rec })}\n`, "utf8");
696
746
  }
697
747
  catch {
@@ -759,7 +809,9 @@ export async function runBugPipeline(opts) {
759
809
  // Keeps every phase of this bug-fix pipeline in a single cache
760
810
  // namespace so the system-prompt + persona prefix stays warm
761
811
  // across the ~10-minute phases.
762
- cacheSessionId: typeof bugRecordBefore?.sprintId === "string" ? `forge:${bugRecordBefore.sprintId}` : `forge:bug:${bugId}`,
812
+ cacheSessionId: typeof bugRecordBefore?.sprintId === "string"
813
+ ? `forge:${bugRecordBefore.sprintId}`
814
+ : `forge:bug:${bugId}`,
763
815
  onEvent: onSubagentEvent,
764
816
  requestedModel: modelResolution.model,
765
817
  modelRegistry: ctx.modelRegistry,
@@ -778,7 +830,12 @@ export async function runBugPipeline(opts) {
778
830
  lastError: `runForgeSubagent threw: ${e.message ?? "unknown"}`,
779
831
  savedAt: new Date().toISOString(),
780
832
  });
781
- return { status: "failed", lastPhaseIndex: currentPhaseIndex, iterationCounts, lastError: `runForgeSubagent threw: ${e.message ?? "unknown"}` };
833
+ return {
834
+ status: "failed",
835
+ lastPhaseIndex: currentPhaseIndex,
836
+ iterationCounts,
837
+ lastError: `runForgeSubagent threw: ${e.message ?? "unknown"}`,
838
+ };
782
839
  }
783
840
  // ── Post-subagent abort detection ─────────────────────────────────
784
841
  if (result.stopReason === "aborted" || opts.signal?.aborted) {
@@ -810,7 +867,12 @@ export async function runBugPipeline(opts) {
810
867
  lastError: result.errorMessage ?? result.stopReason ?? "subagent exit non-zero",
811
868
  savedAt: new Date().toISOString(),
812
869
  });
813
- return { status: "failed", lastPhaseIndex: currentPhaseIndex, iterationCounts, lastError: result.errorMessage ?? result.stopReason ?? "subagent exit non-zero" };
870
+ return {
871
+ status: "failed",
872
+ lastPhaseIndex: currentPhaseIndex,
873
+ iterationCounts,
874
+ lastError: result.errorMessage ?? result.stopReason ?? "subagent exit non-zero",
875
+ };
814
876
  }
815
877
  // Capture model/provider from subagent result.
816
878
  if (result.model)
@@ -838,19 +900,29 @@ export async function runBugPipeline(opts) {
838
900
  const recent = bugs
839
901
  .filter((b) => b.reportedAt && b.reportedAt >= pipelineStartIso)
840
902
  .sort((a, b) => String(b.reportedAt).localeCompare(String(a.reportedAt)))[0];
841
- if (recent && recent.bugId && typeof recent.bugId === "string" && recent.bugId.startsWith("FORGE-BUG-")) {
903
+ if (recent &&
904
+ recent.bugId &&
905
+ typeof recent.bugId === "string" &&
906
+ recent.bugId.startsWith("FORGE-BUG-")) {
842
907
  bugId = recent.bugId;
843
908
  ctx.ui.notify(`forge:fix-bug — captured bug ID via store fallback: ${bugId}`, "info");
844
909
  }
845
910
  }
846
911
  }
847
- catch { /* parse failure — fall through to assertion */ }
912
+ catch {
913
+ /* parse failure — fall through to assertion */
914
+ }
848
915
  }
849
916
  }
850
917
  // Defensive guard: if bugId is still PENDING after triage, pipeline cannot proceed.
851
918
  if (bugId.startsWith("PENDING-")) {
852
919
  ctx.ui.notify("× forge:fix-bug — failed to capture real bug ID after triage. Cannot proceed with PENDING placeholder.", "error");
853
- return { status: "failed", lastPhaseIndex: currentPhaseIndex, iterationCounts, lastError: "bugId still PENDING after triage" };
920
+ return {
921
+ status: "failed",
922
+ lastPhaseIndex: currentPhaseIndex,
923
+ iterationCounts,
924
+ lastError: "bugId still PENDING after triage",
925
+ };
854
926
  }
855
927
  // Re-initialize debug log now that real bugId is available.
856
928
  if (!debugLogDisabled) {
@@ -868,7 +940,9 @@ export async function runBugPipeline(opts) {
868
940
  fs.writeFileSync(debugLogPath, lines.slice(-keep).join("\n"), "utf8");
869
941
  }
870
942
  }
871
- catch { /* file may not exist yet */ }
943
+ catch {
944
+ /* file may not exist yet */
945
+ }
872
946
  fs.appendFileSync(debugLogPath, `${JSON.stringify({ ts: new Date().toISOString(), phase: phase.role, ...rec })}\n`, "utf8");
873
947
  }
874
948
  catch {
@@ -981,7 +1055,12 @@ export async function runBugPipeline(opts) {
981
1055
  lastError: `verdict missing for ${phase.role}`,
982
1056
  savedAt: new Date().toISOString(),
983
1057
  });
984
- return { status: "failed", lastPhaseIndex: currentPhaseIndex, iterationCounts, lastError: `verdict missing for ${phase.role}` };
1058
+ return {
1059
+ status: "failed",
1060
+ lastPhaseIndex: currentPhaseIndex,
1061
+ iterationCounts,
1062
+ lastError: `verdict missing for ${phase.role}`,
1063
+ };
985
1064
  }
986
1065
  if (verdict === "revision") {
987
1066
  iterationCounts[phase.role] = (iterationCounts[phase.role] ?? 0) + 1;
@@ -996,7 +1075,12 @@ export async function runBugPipeline(opts) {
996
1075
  lastError: `revision cap reached for ${phase.role}`,
997
1076
  savedAt: new Date().toISOString(),
998
1077
  });
999
- return { status: "escalated", lastPhaseIndex: currentPhaseIndex, iterationCounts, lastError: `revision cap reached for ${phase.role}` };
1078
+ return {
1079
+ status: "escalated",
1080
+ lastPhaseIndex: currentPhaseIndex,
1081
+ iterationCounts,
1082
+ lastError: `revision cap reached for ${phase.role}`,
1083
+ };
1000
1084
  }
1001
1085
  // Transition bug back to in-progress before re-dispatching implement.
1002
1086
  // This is required for review-code → implement and approve → implement loops.
@@ -1065,7 +1149,13 @@ export async function runBugPipeline(opts) {
1065
1149
  }
1066
1150
  // ── All phases complete ───────────────────────────────────────────
1067
1151
  deleteBugState(cwd, bugId);
1068
- return { status: "completed", lastPhaseIndex: BUG_PHASES.length - 1, iterationCounts, model: lastModel, provider: lastProvider };
1152
+ return {
1153
+ status: "completed",
1154
+ lastPhaseIndex: BUG_PHASES.length - 1,
1155
+ iterationCounts,
1156
+ model: lastModel,
1157
+ provider: lastProvider,
1158
+ };
1069
1159
  }
1070
1160
  export function registerFixBug(pi, options = {}) {
1071
1161
  pi.registerCommand("forge:fix-bug", {
@@ -1097,8 +1187,7 @@ export function registerFixBug(pi, options = {}) {
1097
1187
  let isNewBug = false;
1098
1188
  // Check if arg looks like it could be a bug ID (prefixed or unprefixed).
1099
1189
  // Covers: FORGE-BUG-042, BUG-042, B042.
1100
- const looksLikeBugId = /^(?:[A-Z0-9]+-)?(?:BUG-?\d+|B\d+)$/i.test(rawArg) ||
1101
- /^BUG-\d+$/i.test(rawArg);
1190
+ const looksLikeBugId = /^(?:[A-Z0-9]+-)?(?:BUG-?\d+|B\d+)$/i.test(rawArg) || /^BUG-\d+$/i.test(rawArg);
1102
1191
  if (/^FORGE-BUG-\d+$/.test(rawArg)) {
1103
1192
  // Canonical bug ID — verify it exists
1104
1193
  bugId = rawArg;
@@ -1119,7 +1208,10 @@ export function registerFixBug(pi, options = {}) {
1119
1208
  // Unprefixed bug ID — resolve through the store cascade.
1120
1209
  // Issue #20: unprefixed entity IDs silently poisoned substitutions.
1121
1210
  const toolDir = resolveToolDir(forgeRoot);
1122
- const resolvedBugId = await resolveToCanonicalId(rawArg, toolDir, cwd, "bug", { ctx, commandLabel: "forge:fix-bug" });
1211
+ const resolvedBugId = await resolveToCanonicalId(rawArg, toolDir, cwd, "bug", {
1212
+ ctx,
1213
+ commandLabel: "forge:fix-bug",
1214
+ });
1123
1215
  if (!resolvedBugId) {
1124
1216
  // Error already emitted by resolver
1125
1217
  ctx.ui.setStatus?.(STATUS_KEY, undefined);
@@ -1189,9 +1281,7 @@ export function registerFixBug(pi, options = {}) {
1189
1281
  // (explicit failure), halted=false (cancelled/interrupted), and
1190
1282
  // any state with existing.status set.
1191
1283
  const stateStatus = existing.status ?? (existing.halted ? "halted" : "interrupted");
1192
- const statusLabel = stateStatus === "cancelled" ? "cancelled"
1193
- : stateStatus === "halted" ? "halted"
1194
- : "interrupted";
1284
+ const statusLabel = stateStatus === "cancelled" ? "cancelled" : stateStatus === "halted" ? "halted" : "interrupted";
1195
1285
  const phaseRole = BUG_PHASES[existing.phaseIndex]?.role ?? existing.phaseIndex;
1196
1286
  if (!isNonInteractive()) {
1197
1287
  const resume = await ctx.ui.confirm(`Resume ${bugId}?`, `Cached state — phase ${existing.phaseIndex} (${phaseRole}), ${statusLabel}, ` +
@@ -1247,7 +1337,7 @@ export function registerFixBug(pi, options = {}) {
1247
1337
  // needs the real ID before startSession is called.
1248
1338
  if (isNewBug && bugId.startsWith("PENDING-")) {
1249
1339
  const realBugId = assignNextBugId(storeCli, cwd);
1250
- const title = (rawArg && !rawArg.startsWith("@")) ? rawArg.slice(0, 120) : "New bug (pending triage)";
1340
+ const title = rawArg && !rawArg.startsWith("@") ? rawArg.slice(0, 120) : "New bug (pending triage)";
1251
1341
  if (preCreateBug(realBugId, title, storeCli, cwd)) {
1252
1342
  ctx.ui.notify(`forge:fix-bug — pre-assigned bug ID: ${realBugId}`, "info");
1253
1343
  bugId = realBugId;