@entelligentsia/forgecli 1.0.25 → 1.0.40

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 (590) hide show
  1. package/CHANGELOG.md +341 -0
  2. package/README.md +2 -0
  3. package/dist/CHANGELOG-forge-plugin.md +264 -0
  4. package/dist/CHANGELOG-pi.md +143 -0
  5. package/dist/bin/argv.d.ts +2 -2
  6. package/dist/bin/argv.js +37 -0
  7. package/dist/bin/argv.js.map +1 -1
  8. package/dist/bin/forge.js +30 -16
  9. package/dist/bin/forge.js.map +1 -1
  10. package/dist/bin/init.d.ts +23 -0
  11. package/dist/bin/init.js +123 -0
  12. package/dist/bin/init.js.map +1 -0
  13. package/dist/bin/reset.d.ts +39 -0
  14. package/dist/bin/reset.js +101 -0
  15. package/dist/bin/reset.js.map +1 -0
  16. package/dist/bin/uninstall.d.ts +20 -0
  17. package/dist/bin/uninstall.js +141 -0
  18. package/dist/bin/uninstall.js.map +1 -0
  19. package/dist/extensions/forgecli/claude-bootstrap/bootstrap.d.ts +40 -0
  20. package/dist/extensions/forgecli/claude-bootstrap/bootstrap.js +384 -0
  21. package/dist/extensions/forgecli/claude-bootstrap/bootstrap.js.map +1 -0
  22. package/dist/extensions/forgecli/claude-bootstrap/settings-merge.d.ts +46 -0
  23. package/dist/extensions/forgecli/claude-bootstrap/settings-merge.js +245 -0
  24. package/dist/extensions/forgecli/claude-bootstrap/settings-merge.js.map +1 -0
  25. package/dist/extensions/forgecli/claude-bootstrap/uninstall.d.ts +23 -0
  26. package/dist/extensions/forgecli/claude-bootstrap/uninstall.js +235 -0
  27. package/dist/extensions/forgecli/claude-bootstrap/uninstall.js.map +1 -0
  28. package/dist/extensions/forgecli/commands/reset.d.ts +16 -0
  29. package/dist/extensions/forgecli/commands/reset.js +83 -0
  30. package/dist/extensions/forgecli/commands/reset.js.map +1 -0
  31. package/dist/extensions/forgecli/dashboard/component.js +10 -7
  32. package/dist/extensions/forgecli/dashboard/component.js.map +1 -1
  33. package/dist/extensions/forgecli/forge-commands.d.ts +7 -2
  34. package/dist/extensions/forgecli/forge-commands.js +19 -5
  35. package/dist/extensions/forgecli/forge-commands.js.map +1 -1
  36. package/dist/extensions/forgecli/forge-subagent.d.ts +4 -4
  37. package/dist/extensions/forgecli/hooks/forge-permissions.js +20 -6
  38. package/dist/extensions/forgecli/hooks/forge-permissions.js.map +1 -1
  39. package/dist/extensions/forgecli/index.js +6 -3
  40. package/dist/extensions/forgecli/index.js.map +1 -1
  41. package/dist/extensions/forgecli/lib/forge-root.d.ts +6 -0
  42. package/dist/extensions/forgecli/lib/forge-root.js +52 -0
  43. package/dist/extensions/forgecli/lib/forge-root.js.map +1 -1
  44. package/dist/extensions/forgecli/lib/payload-manifest.d.ts +62 -0
  45. package/dist/extensions/forgecli/lib/payload-manifest.js +151 -0
  46. package/dist/extensions/forgecli/lib/payload-manifest.js.map +1 -0
  47. package/dist/extensions/forgecli/orchestrators/advisory-render.d.ts +9 -0
  48. package/dist/extensions/forgecli/orchestrators/advisory-render.js +107 -0
  49. package/dist/extensions/forgecli/orchestrators/advisory-render.js.map +1 -0
  50. package/dist/extensions/forgecli/orchestrators/bug/bug-body.d.ts +1 -0
  51. package/dist/extensions/forgecli/orchestrators/bug/bug-body.js +65 -0
  52. package/dist/extensions/forgecli/orchestrators/bug/bug-body.js.map +1 -0
  53. package/dist/extensions/forgecli/orchestrators/bug/bug-id.d.ts +23 -0
  54. package/dist/extensions/forgecli/orchestrators/bug/bug-id.js +140 -0
  55. package/dist/extensions/forgecli/orchestrators/bug/bug-id.js.map +1 -0
  56. package/dist/extensions/forgecli/orchestrators/bug/bug-phase-dispatch.d.ts +54 -0
  57. package/dist/extensions/forgecli/orchestrators/bug/bug-phase-dispatch.js +349 -0
  58. package/dist/extensions/forgecli/orchestrators/bug/bug-phase-dispatch.js.map +1 -0
  59. package/dist/extensions/forgecli/orchestrators/bug/bug-phases.d.ts +11 -0
  60. package/dist/extensions/forgecli/orchestrators/bug/bug-phases.js +82 -0
  61. package/dist/extensions/forgecli/orchestrators/bug/bug-phases.js.map +1 -0
  62. package/dist/extensions/forgecli/orchestrators/bug/bug-state.d.ts +14 -0
  63. package/dist/extensions/forgecli/orchestrators/bug/bug-state.js +100 -0
  64. package/dist/extensions/forgecli/orchestrators/bug/bug-state.js.map +1 -0
  65. package/dist/extensions/forgecli/orchestrators/bug/bug-triage-routing.d.ts +72 -0
  66. package/dist/extensions/forgecli/orchestrators/bug/bug-triage-routing.js +204 -0
  67. package/dist/extensions/forgecli/orchestrators/bug/bug-triage-routing.js.map +1 -0
  68. package/dist/extensions/forgecli/orchestrators/bug/bug-verdict-loop.d.ts +38 -0
  69. package/dist/extensions/forgecli/orchestrators/bug/bug-verdict-loop.js +198 -0
  70. package/dist/extensions/forgecli/orchestrators/bug/bug-verdict-loop.js.map +1 -0
  71. package/dist/extensions/forgecli/orchestrators/bug/bug-verdict.d.ts +3 -0
  72. package/dist/extensions/forgecli/orchestrators/bug/bug-verdict.js +55 -0
  73. package/dist/extensions/forgecli/orchestrators/bug/bug-verdict.js.map +1 -0
  74. package/dist/extensions/forgecli/orchestrators/bug/run-bug-command.d.ts +7 -0
  75. package/dist/extensions/forgecli/orchestrators/bug/run-bug-command.js +293 -0
  76. package/dist/extensions/forgecli/orchestrators/bug/run-bug-command.js.map +1 -0
  77. package/dist/extensions/forgecli/orchestrators/bug/run-bug-pipeline.d.ts +2 -0
  78. package/dist/extensions/forgecli/orchestrators/bug/run-bug-pipeline.js +501 -0
  79. package/dist/extensions/forgecli/orchestrators/bug/run-bug-pipeline.js.map +1 -0
  80. package/dist/extensions/forgecli/orchestrators/bug/run-bug-types.d.ts +41 -0
  81. package/dist/extensions/forgecli/orchestrators/bug/run-bug-types.js +5 -0
  82. package/dist/extensions/forgecli/orchestrators/bug/run-bug-types.js.map +1 -0
  83. package/dist/extensions/forgecli/orchestrators/common/orchestrator-entry.d.ts +43 -0
  84. package/dist/extensions/forgecli/orchestrators/common/orchestrator-entry.js +85 -0
  85. package/dist/extensions/forgecli/orchestrators/common/orchestrator-entry.js.map +1 -0
  86. package/dist/extensions/forgecli/orchestrators/common/orchestrator-misc.d.ts +8 -0
  87. package/dist/extensions/forgecli/orchestrators/common/orchestrator-misc.js +37 -0
  88. package/dist/extensions/forgecli/orchestrators/common/orchestrator-misc.js.map +1 -0
  89. package/dist/extensions/forgecli/orchestrators/common/orchestrator-notify.d.ts +28 -0
  90. package/dist/extensions/forgecli/orchestrators/common/orchestrator-notify.js +45 -0
  91. package/dist/extensions/forgecli/orchestrators/common/orchestrator-notify.js.map +1 -0
  92. package/dist/extensions/forgecli/orchestrators/common/orchestrator-transcript-session.d.ts +26 -0
  93. package/dist/extensions/forgecli/orchestrators/common/orchestrator-transcript-session.js +75 -0
  94. package/dist/extensions/forgecli/orchestrators/common/orchestrator-transcript-session.js.map +1 -0
  95. package/dist/extensions/forgecli/orchestrators/common/recovery-menu.d.ts +24 -0
  96. package/dist/extensions/forgecli/orchestrators/common/recovery-menu.js +58 -0
  97. package/dist/extensions/forgecli/orchestrators/common/recovery-menu.js.map +1 -0
  98. package/dist/extensions/forgecli/orchestrators/common/reset-pipeline.d.ts +53 -0
  99. package/dist/extensions/forgecli/orchestrators/common/reset-pipeline.js +131 -0
  100. package/dist/extensions/forgecli/orchestrators/common/reset-pipeline.js.map +1 -0
  101. package/dist/extensions/forgecli/orchestrators/common/summary-recovery.d.ts +24 -0
  102. package/dist/extensions/forgecli/orchestrators/common/summary-recovery.js +37 -0
  103. package/dist/extensions/forgecli/orchestrators/common/summary-recovery.js.map +1 -0
  104. package/dist/extensions/forgecli/orchestrators/fix-bug.d.ts +9 -93
  105. package/dist/extensions/forgecli/orchestrators/fix-bug.js +23 -1721
  106. package/dist/extensions/forgecli/orchestrators/fix-bug.js.map +1 -1
  107. package/dist/extensions/forgecli/orchestrators/halt-advisor.js +25 -3
  108. package/dist/extensions/forgecli/orchestrators/halt-advisor.js.map +1 -1
  109. package/dist/extensions/forgecli/orchestrators/run-sprint.d.ts +3 -12
  110. package/dist/extensions/forgecli/orchestrators/run-sprint.js +48 -270
  111. package/dist/extensions/forgecli/orchestrators/run-sprint.js.map +1 -1
  112. package/dist/extensions/forgecli/orchestrators/run-task.d.ts +10 -214
  113. package/dist/extensions/forgecli/orchestrators/run-task.js +31 -1481
  114. package/dist/extensions/forgecli/orchestrators/run-task.js.map +1 -1
  115. package/dist/extensions/forgecli/orchestrators/sprint/sprint-ceremony.d.ts +33 -0
  116. package/dist/extensions/forgecli/orchestrators/sprint/sprint-ceremony.js +135 -0
  117. package/dist/extensions/forgecli/orchestrators/sprint/sprint-ceremony.js.map +1 -0
  118. package/dist/extensions/forgecli/orchestrators/sprint/sprint-state.d.ts +18 -0
  119. package/dist/extensions/forgecli/orchestrators/sprint/sprint-state.js +55 -0
  120. package/dist/extensions/forgecli/orchestrators/sprint/sprint-state.js.map +1 -0
  121. package/dist/extensions/forgecli/orchestrators/task/run-task-command.d.ts +9 -0
  122. package/dist/extensions/forgecli/orchestrators/task/run-task-command.js +174 -0
  123. package/dist/extensions/forgecli/orchestrators/task/run-task-command.js.map +1 -0
  124. package/dist/extensions/forgecli/orchestrators/task/run-task-pipeline.d.ts +2 -0
  125. package/dist/extensions/forgecli/orchestrators/task/run-task-pipeline.js +494 -0
  126. package/dist/extensions/forgecli/orchestrators/task/run-task-pipeline.js.map +1 -0
  127. package/dist/extensions/forgecli/orchestrators/task/run-task-types.d.ts +62 -0
  128. package/dist/extensions/forgecli/orchestrators/task/run-task-types.js +5 -0
  129. package/dist/extensions/forgecli/orchestrators/task/run-task-types.js.map +1 -0
  130. package/dist/extensions/forgecli/orchestrators/task/task-body.d.ts +4 -0
  131. package/dist/extensions/forgecli/orchestrators/task/task-body.js +48 -0
  132. package/dist/extensions/forgecli/orchestrators/task/task-body.js.map +1 -0
  133. package/dist/extensions/forgecli/orchestrators/task/task-events.d.ts +63 -0
  134. package/dist/extensions/forgecli/orchestrators/task/task-events.js +185 -0
  135. package/dist/extensions/forgecli/orchestrators/task/task-events.js.map +1 -0
  136. package/dist/extensions/forgecli/orchestrators/task/task-gates.d.ts +34 -0
  137. package/dist/extensions/forgecli/orchestrators/task/task-gates.js +78 -0
  138. package/dist/extensions/forgecli/orchestrators/task/task-gates.js.map +1 -0
  139. package/dist/extensions/forgecli/orchestrators/task/task-phase-dispatch.d.ts +42 -0
  140. package/dist/extensions/forgecli/orchestrators/task/task-phase-dispatch.js +370 -0
  141. package/dist/extensions/forgecli/orchestrators/task/task-phase-dispatch.js.map +1 -0
  142. package/dist/extensions/forgecli/orchestrators/task/task-phases.d.ts +17 -0
  143. package/dist/extensions/forgecli/orchestrators/task/task-phases.js +48 -0
  144. package/dist/extensions/forgecli/orchestrators/task/task-phases.js.map +1 -0
  145. package/dist/extensions/forgecli/orchestrators/task/task-record.d.ts +9 -0
  146. package/dist/extensions/forgecli/orchestrators/task/task-record.js +58 -0
  147. package/dist/extensions/forgecli/orchestrators/task/task-record.js.map +1 -0
  148. package/dist/extensions/forgecli/orchestrators/task/task-state.d.ts +14 -0
  149. package/dist/extensions/forgecli/orchestrators/task/task-state.js +35 -0
  150. package/dist/extensions/forgecli/orchestrators/task/task-state.js.map +1 -0
  151. package/dist/extensions/forgecli/orchestrators/task/task-verdict-loop.d.ts +36 -0
  152. package/dist/extensions/forgecli/orchestrators/task/task-verdict-loop.js +187 -0
  153. package/dist/extensions/forgecli/orchestrators/task/task-verdict-loop.js.map +1 -0
  154. package/dist/extensions/forgecli/store/store-resolver.d.ts +15 -0
  155. package/dist/extensions/forgecli/store/store-resolver.js +118 -18
  156. package/dist/extensions/forgecli/store/store-resolver.js.map +1 -1
  157. package/dist/extensions/forgecli/update/forge-update-command.js +10 -7
  158. package/dist/extensions/forgecli/update/forge-update-command.js.map +1 -1
  159. package/dist/forge-payload/.base-pack/workflows/collator_agent.md +5 -6
  160. package/dist/forge-payload/.base-pack/workflows/migrate_structural.md +1 -1
  161. package/dist/forge-payload/.base-pack/workflows-js/wfl-init.js +449 -0
  162. package/dist/forge-payload/.claude-plugin/plugin.json +1 -1
  163. package/dist/forge-payload/.schemas/enum-catalog.json +2 -2
  164. package/dist/forge-payload/.schemas/migrations.json +170 -0
  165. package/dist/forge-payload/.schemas/payload-manifest.schema.json +100 -0
  166. package/dist/forge-payload/commands/add-pipeline.md +1 -1
  167. package/dist/forge-payload/commands/add-task.md +3 -3
  168. package/dist/forge-payload/{.base-pack/commands → commands}/approve.md +2 -2
  169. package/dist/forge-payload/commands/ask.md +1 -1
  170. package/dist/forge-payload/commands/check-agent.md +8 -24
  171. package/dist/forge-payload/{.base-pack/commands → commands}/collate.md +2 -2
  172. package/dist/forge-payload/{.base-pack/commands → commands}/commit.md +2 -2
  173. package/dist/forge-payload/commands/config.md +1 -1
  174. package/dist/forge-payload/commands/enhance.md +31 -5
  175. package/dist/forge-payload/{.base-pack/commands → commands}/fix-bug.md +2 -2
  176. package/dist/forge-payload/commands/health.md +1 -1
  177. package/dist/forge-payload/{.base-pack/commands → commands}/implement.md +2 -2
  178. package/dist/forge-payload/commands/init.md +186 -67
  179. package/dist/forge-payload/{.base-pack/commands → commands}/new-sprint.md +2 -2
  180. package/dist/forge-payload/{.base-pack/commands → commands}/plan-sprint.md +2 -2
  181. package/dist/forge-payload/{.base-pack/commands → commands}/plan.md +2 -2
  182. package/dist/forge-payload/commands/rebuild.md +3 -3
  183. package/dist/forge-payload/commands/remove.md +1 -1
  184. package/dist/forge-payload/commands/repair.md +1 -1
  185. package/dist/forge-payload/commands/report-bug.md +1 -1
  186. package/dist/forge-payload/commands/reset.md +117 -0
  187. package/dist/forge-payload/{.base-pack/commands → commands}/retro.md +2 -2
  188. package/dist/forge-payload/{.base-pack/commands → commands}/review-code.md +2 -2
  189. package/dist/forge-payload/{.base-pack/commands → commands}/review-plan.md +2 -2
  190. package/dist/forge-payload/{.base-pack/commands → commands}/run-sprint.md +2 -2
  191. package/dist/forge-payload/{.base-pack/commands → commands}/run-task.md +2 -2
  192. package/dist/forge-payload/commands/status.md +1 -1
  193. package/dist/forge-payload/commands/update.md +3 -3
  194. package/dist/forge-payload/{.base-pack/commands → commands}/validate.md +2 -2
  195. package/dist/forge-payload/hooks/forge-permissions.cjs +29 -6
  196. package/dist/forge-payload/hooks/lib/common.cjs +228 -0
  197. package/dist/forge-payload/hooks/lib/plugin-detection.cjs +106 -0
  198. package/dist/forge-payload/hooks/lib/update-msg.cjs +23 -0
  199. package/dist/forge-payload/hooks/lib/update-url.cjs +46 -0
  200. package/dist/forge-payload/hooks/lib/write-registry.js +53 -0
  201. package/dist/forge-payload/init/discovery/discover-database.md +32 -0
  202. package/dist/forge-payload/init/discovery/discover-processes.md +31 -0
  203. package/dist/forge-payload/init/discovery/discover-routing.md +31 -0
  204. package/dist/forge-payload/init/discovery/discover-stack.md +33 -0
  205. package/dist/forge-payload/init/discovery/discover-testing.md +34 -0
  206. package/dist/forge-payload/init/generation/generate-commands.md +171 -0
  207. package/dist/forge-payload/init/generation/generate-kb-doc.md +60 -0
  208. package/dist/forge-payload/init/generation/generate-persona.md +73 -0
  209. package/dist/forge-payload/init/generation/generate-skill.md +66 -0
  210. package/dist/forge-payload/init/generation/generate-template.md +60 -0
  211. package/dist/forge-payload/init/generation/generate-tools.md +133 -0
  212. package/dist/forge-payload/init/generation/generate-workflows.md +78 -0
  213. package/dist/forge-payload/init/phases/phase-1-collect.md +10 -2
  214. package/dist/forge-payload/init/phases/phase-3-materialize.md +5 -1
  215. package/dist/forge-payload/init/phases/phase-4-register.md +8 -0
  216. package/dist/forge-payload/init/workflow-gen-plan.json +17 -0
  217. package/dist/forge-payload/integrity.json +33 -18
  218. package/dist/forge-payload/meta/workflows/meta-collate.md +5 -6
  219. package/dist/forge-payload/meta/workflows/meta-migrate.md +1 -1
  220. package/dist/forge-payload/payload-manifest.json +314 -0
  221. package/dist/forge-payload/schemas/enum-catalog.json +2 -2
  222. package/dist/forge-payload/schemas/payload-manifest.schema.json +100 -0
  223. package/dist/forge-payload/schemas/structure-manifest.json +5 -12
  224. package/dist/forge-payload/tools/forge-preflight.cjs +268 -0
  225. package/dist/forge-payload/tools/lib/paths.cjs +12 -11
  226. package/dist/forge-payload/tools/lib/pricing.cjs +31 -11
  227. package/dist/forge-payload/tools/query-logger.cjs +34 -0
  228. package/dist/forge-payload/tools/reset-plan.cjs +210 -0
  229. package/dist/forge-payload/tools/store.cjs +4 -1
  230. package/dist/forge-payload/tools/substitute-placeholders.cjs +14 -7
  231. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  232. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +8 -0
  233. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  234. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts +1 -1
  235. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
  236. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +1 -1
  237. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
  238. package/node_modules/@earendil-works/pi-agent-core/dist/harness/execution-env.d.ts +4 -0
  239. package/node_modules/@earendil-works/pi-agent-core/dist/harness/execution-env.d.ts.map +1 -0
  240. package/node_modules/@earendil-works/pi-agent-core/dist/harness/execution-env.js +3 -0
  241. package/node_modules/@earendil-works/pi-agent-core/dist/harness/execution-env.js.map +1 -0
  242. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/jsonl.d.ts +20 -0
  243. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/jsonl.d.ts.map +1 -0
  244. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/jsonl.js +92 -0
  245. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/jsonl.js.map +1 -0
  246. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/memory.d.ts +18 -0
  247. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/memory.d.ts.map +1 -0
  248. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/memory.js +42 -0
  249. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/memory.js.map +1 -0
  250. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/shared.d.ts +10 -0
  251. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/shared.d.ts.map +1 -0
  252. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/shared.js +31 -0
  253. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo/shared.js.map +1 -0
  254. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/jsonl.d.ts +30 -0
  255. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/jsonl.d.ts.map +1 -0
  256. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/jsonl.js +170 -0
  257. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/jsonl.js.map +1 -0
  258. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/memory.d.ts +26 -0
  259. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/memory.d.ts.map +1 -0
  260. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/memory.js +90 -0
  261. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/storage/memory.js.map +1 -0
  262. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +6 -1
  263. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  264. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  265. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  266. package/node_modules/@earendil-works/pi-ai/README.md +12 -4
  267. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  268. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +3 -0
  269. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
  270. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +45 -0
  271. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  272. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +45 -0
  273. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  274. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +1804 -815
  275. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  276. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +2031 -1384
  277. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  278. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts.map +1 -1
  279. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +71 -27
  280. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  281. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts +1 -1
  282. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  283. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +24 -16
  284. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
  285. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  286. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +1 -0
  287. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  288. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  289. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +3 -1
  290. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  291. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  292. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +35 -13
  293. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js.map +1 -1
  294. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.d.ts.map +1 -1
  295. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js +2 -1
  296. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js.map +1 -1
  297. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  298. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +1 -0
  299. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js.map +1 -1
  300. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +12 -4
  301. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  302. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  303. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  304. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +13 -1
  305. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  306. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.d.ts.map +1 -1
  307. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +4 -2
  308. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js.map +1 -1
  309. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +1 -1
  310. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  311. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +3 -2
  312. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  313. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  314. package/node_modules/@earendil-works/pi-coding-agent/CHANGELOG.md +143 -0
  315. package/node_modules/@earendil-works/pi-coding-agent/README.md +26 -4
  316. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.d.ts +1 -0
  317. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  318. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +11 -0
  319. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js.map +1 -1
  320. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/project-trust.d.ts +10 -0
  321. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/project-trust.d.ts.map +1 -0
  322. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/project-trust.js +48 -0
  323. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/project-trust.js.map +1 -0
  324. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/startup-ui.d.ts +17 -0
  325. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/startup-ui.d.ts.map +1 -0
  326. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/startup-ui.js +128 -0
  327. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/startup-ui.js.map +1 -0
  328. package/node_modules/@earendil-works/pi-coding-agent/dist/config.d.ts.map +1 -1
  329. package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +9 -1
  330. package/node_modules/@earendil-works/pi-coding-agent/dist/config.js.map +1 -1
  331. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-runtime.d.ts +3 -1
  332. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-runtime.d.ts.map +1 -1
  333. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-runtime.js +4 -1
  334. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-runtime.js.map +1 -1
  335. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.d.ts +2 -1
  336. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.d.ts.map +1 -1
  337. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js +2 -2
  338. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js.map +1 -1
  339. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.d.ts +4 -1
  340. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  341. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +16 -3
  342. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  343. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  344. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js +4 -3
  345. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  346. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts +3 -1
  347. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  348. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/branch-summarization.js +9 -3
  349. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
  350. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/utils.d.ts +1 -1
  351. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
  352. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/utils.js +1 -1
  353. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  354. package/node_modules/@earendil-works/pi-coding-agent/dist/core/experimental.d.ts +2 -0
  355. package/node_modules/@earendil-works/pi-coding-agent/dist/core/experimental.d.ts.map +1 -0
  356. package/node_modules/@earendil-works/pi-coding-agent/dist/core/experimental.js +4 -0
  357. package/node_modules/@earendil-works/pi-coding-agent/dist/core/experimental.js.map +1 -0
  358. package/node_modules/@earendil-works/pi-coding-agent/dist/core/export-html/template.js +19 -6
  359. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
  360. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  361. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  362. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.d.ts +1 -1
  363. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  364. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js +4 -4
  365. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  366. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.d.ts +10 -3
  367. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  368. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js +47 -1
  369. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  370. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts +28 -2
  371. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  372. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  373. package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.d.ts +2 -0
  374. package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.d.ts.map +1 -1
  375. package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.js +29 -1
  376. package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.js.map +1 -1
  377. package/node_modules/@earendil-works/pi-coding-agent/dist/core/index.d.ts +1 -0
  378. package/node_modules/@earendil-works/pi-coding-agent/dist/core/index.d.ts.map +1 -1
  379. package/node_modules/@earendil-works/pi-coding-agent/dist/core/index.js +1 -0
  380. package/node_modules/@earendil-works/pi-coding-agent/dist/core/index.js.map +1 -1
  381. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  382. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +1 -0
  383. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  384. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.d.ts +1 -0
  385. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  386. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +44 -5
  387. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  388. package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.d.ts +3 -0
  389. package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  390. package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js +47 -13
  391. package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  392. package/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.d.ts +15 -0
  393. package/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.d.ts.map +1 -0
  394. package/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.js +58 -0
  395. package/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.js.map +1 -0
  396. package/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.d.ts +2 -1
  397. package/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.d.ts.map +1 -1
  398. package/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js +24 -26
  399. package/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js.map +1 -1
  400. package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-attribution.d.ts +4 -0
  401. package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-attribution.d.ts.map +1 -0
  402. package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-attribution.js +72 -0
  403. package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-attribution.js.map +1 -0
  404. package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-display-names.d.ts.map +1 -1
  405. package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-display-names.js +3 -0
  406. package/node_modules/@earendil-works/pi-coding-agent/dist/core/provider-display-names.js.map +1 -1
  407. package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.d.ts +13 -2
  408. package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  409. package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js +112 -37
  410. package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  411. package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  412. package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +7 -33
  413. package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js.map +1 -1
  414. package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  415. package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +103 -70
  416. package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  417. package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.d.ts +20 -2
  418. package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  419. package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +97 -30
  420. package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  421. package/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  422. package/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.js +1 -0
  423. package/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  424. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  425. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js +1 -1
  426. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  427. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.d.ts.map +1 -1
  428. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.js +1 -1
  429. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.js.map +1 -1
  430. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.d.ts.map +1 -1
  431. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.js +1 -1
  432. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.js.map +1 -1
  433. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.d.ts.map +1 -1
  434. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +1 -1
  435. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js.map +1 -1
  436. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
  437. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +1 -1
  438. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js.map +1 -1
  439. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  440. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +1 -1
  441. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  442. package/node_modules/@earendil-works/pi-coding-agent/dist/core/trust-manager.d.ts +36 -0
  443. package/node_modules/@earendil-works/pi-coding-agent/dist/core/trust-manager.d.ts.map +1 -0
  444. package/node_modules/@earendil-works/pi-coding-agent/dist/core/trust-manager.js +202 -0
  445. package/node_modules/@earendil-works/pi-coding-agent/dist/core/trust-manager.js.map +1 -0
  446. package/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts +5 -4
  447. package/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts.map +1 -1
  448. package/node_modules/@earendil-works/pi-coding-agent/dist/index.js +2 -1
  449. package/node_modules/@earendil-works/pi-coding-agent/dist/index.js.map +1 -1
  450. package/node_modules/@earendil-works/pi-coding-agent/dist/main.d.ts.map +1 -1
  451. package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +72 -32
  452. package/node_modules/@earendil-works/pi-coding-agent/dist/main.js.map +1 -1
  453. package/node_modules/@earendil-works/pi-coding-agent/dist/migrations.d.ts.map +1 -1
  454. package/node_modules/@earendil-works/pi-coding-agent/dist/migrations.js +39 -34
  455. package/node_modules/@earendil-works/pi-coding-agent/dist/migrations.js.map +1 -1
  456. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/index.d.ts +1 -1
  457. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/index.d.ts.map +1 -1
  458. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/index.js.map +1 -1
  459. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  460. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +2 -2
  461. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
  462. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
  463. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -0
  464. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/first-time-setup.js +103 -0
  465. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/first-time-setup.js.map +1 -0
  466. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  467. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js +7 -0
  468. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  469. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/index.d.ts +2 -0
  470. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/index.d.ts.map +1 -1
  471. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/index.js +2 -0
  472. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/index.js.map +1 -1
  473. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +1 -1
  474. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  475. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +10 -13
  476. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  477. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  478. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  479. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +20 -0
  480. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  481. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  482. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +22 -0
  483. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  484. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/trust-selector.d.ts +23 -0
  485. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/trust-selector.d.ts.map +1 -0
  486. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/trust-selector.js +91 -0
  487. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/trust-selector.js.map +1 -0
  488. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +7 -0
  489. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  490. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +101 -5
  491. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  492. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/print-mode.d.ts.map +1 -1
  493. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/print-mode.js +1 -0
  494. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/print-mode.js.map +1 -1
  495. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  496. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -0
  497. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  498. package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.d.ts +6 -2
  499. package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.d.ts.map +1 -1
  500. package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js +111 -10
  501. package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js.map +1 -1
  502. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.d.ts +1 -0
  503. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.d.ts.map +1 -1
  504. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.js +78 -0
  505. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.js.map +1 -1
  506. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/git.d.ts.map +1 -1
  507. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/git.js +54 -22
  508. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/git.js.map +1 -1
  509. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/open-browser.d.ts +9 -0
  510. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/open-browser.d.ts.map +1 -0
  511. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/open-browser.js +22 -0
  512. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/open-browser.js.map +1 -0
  513. package/node_modules/@earendil-works/pi-coding-agent/docs/containerization.md +111 -0
  514. package/node_modules/@earendil-works/pi-coding-agent/docs/docs.json +8 -0
  515. package/node_modules/@earendil-works/pi-coding-agent/docs/extensions.md +67 -13
  516. package/node_modules/@earendil-works/pi-coding-agent/docs/index.md +2 -0
  517. package/node_modules/@earendil-works/pi-coding-agent/docs/models.md +4 -3
  518. package/node_modules/@earendil-works/pi-coding-agent/docs/packages.md +1 -1
  519. package/node_modules/@earendil-works/pi-coding-agent/docs/prompt-templates.md +9 -2
  520. package/node_modules/@earendil-works/pi-coding-agent/docs/providers.md +5 -0
  521. package/node_modules/@earendil-works/pi-coding-agent/docs/rpc.md +1 -1
  522. package/node_modules/@earendil-works/pi-coding-agent/docs/sdk.md +5 -0
  523. package/node_modules/@earendil-works/pi-coding-agent/docs/security.md +59 -0
  524. package/node_modules/@earendil-works/pi-coding-agent/docs/settings.md +15 -0
  525. package/node_modules/@earendil-works/pi-coding-agent/docs/skills.md +1 -1
  526. package/node_modules/@earendil-works/pi-coding-agent/docs/terminal-setup.md +36 -2
  527. package/node_modules/@earendil-works/pi-coding-agent/docs/themes.md +1 -1
  528. package/node_modules/@earendil-works/pi-coding-agent/docs/tmux.md +4 -2
  529. package/node_modules/@earendil-works/pi-coding-agent/docs/tui.md +10 -1
  530. package/node_modules/@earendil-works/pi-coding-agent/docs/usage.md +19 -2
  531. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/README.md +2 -0
  532. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-header.ts +1 -1
  533. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-anthropic/package.json +1 -1
  534. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  535. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/doom-overlay/index.ts +1 -1
  536. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/index.ts +531 -0
  537. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/package-lock.json +185 -0
  538. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/package.json +19 -0
  539. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/handoff.ts +1 -1
  540. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/interactive-shell.ts +1 -1
  541. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/overlay-qa-tests.ts +152 -81
  542. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/project-trust.ts +64 -0
  543. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/qna.ts +1 -1
  544. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/question.ts +1 -1
  545. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/questionnaire.ts +1 -1
  546. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/sandbox/package.json +1 -1
  547. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/snake.ts +1 -1
  548. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/space-invaders.ts +1 -1
  549. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/summarize.ts +1 -1
  550. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/tic-tac-toe.ts +1 -1
  551. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/todo.ts +1 -1
  552. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/tools.ts +5 -0
  553. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/with-deps/package.json +1 -1
  554. package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +12 -419
  555. package/node_modules/@earendil-works/pi-coding-agent/package.json +5 -8
  556. package/node_modules/@earendil-works/pi-tui/README.md +13 -1
  557. package/node_modules/@earendil-works/pi-tui/dist/autocomplete.d.ts +2 -0
  558. package/node_modules/@earendil-works/pi-tui/dist/autocomplete.d.ts.map +1 -1
  559. package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js.map +1 -1
  560. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +6 -1
  561. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  562. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +102 -43
  563. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  564. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts +2 -1
  565. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts.map +1 -1
  566. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +11 -1
  567. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js.map +1 -1
  568. package/node_modules/@earendil-works/pi-tui/dist/fuzzy.d.ts +1 -1
  569. package/node_modules/@earendil-works/pi-tui/dist/fuzzy.d.ts.map +1 -1
  570. package/node_modules/@earendil-works/pi-tui/dist/fuzzy.js +2 -2
  571. package/node_modules/@earendil-works/pi-tui/dist/fuzzy.js.map +1 -1
  572. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +1 -1
  573. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
  574. package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
  575. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +4 -7
  576. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  577. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +38 -77
  578. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  579. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts +20 -4
  580. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
  581. package/node_modules/@earendil-works/pi-tui/dist/tui.js +244 -42
  582. package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
  583. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts +1 -0
  584. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts.map +1 -1
  585. package/node_modules/@earendil-works/pi-tui/dist/utils.js +46 -15
  586. package/node_modules/@earendil-works/pi-tui/dist/utils.js.map +1 -1
  587. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  588. package/package.json +8 -7
  589. package/dist/forge-payload/.base-pack/commands/check-agent.md +0 -22
  590. package/dist/forge-payload/.base-pack/commands/enhance.md +0 -37
@@ -1,1731 +1,33 @@
1
1
  // fix-bug.ts — /forge:fix-bug Orchestrator native handler (FORGE-S21-T07).
2
2
  //
3
- // Promotes /forge:fix-bug from stub to a full TS-driven Orchestrator-archetype
4
- // native handler. Reads `.forge/workflows/fix_bug.md`, chains the bug-specific
5
- // phase sequence (triage → plan-fix review-plan implement review-code
6
- // approve commit) by spawning a fresh runForgeSubagent per phase (IL10).
3
+ // BARREL (FORGE-S31 file-size refactor). The implementation was decomposed into
4
+ // themed modules under ./bug/ to satisfy the per-file architectural line cap.
5
+ // This file re-exports the full public surface so existing importers
6
+ // (index.ts, bin/config.ts, the test suite) keep resolving unchanged.
7
7
  //
8
- // Iron Laws enforced here:
8
+ // Iron Laws (enforced in the extracted modules):
9
9
  // IL1 — code only under forge-cli/src/extensions/forgecli/
10
10
  // IL6 — no shell-string interpolation; all external calls via spawnSync argv arrays
11
11
  // IL7 — every failure path emits ctx.ui.notify and returns; no silent continuation
12
12
  // IL10 — ALL LLM dispatch goes through runForgeSubagent (NO sendKickoff calls here)
13
13
  //
14
- // sendKickoff is NEVER called from this file.
15
- // Audit-grep: grep -n "sendKickoff(" fix-bug.ts must return empty.
16
- //
17
- // N-H-C bugId dual-assignment lifecycle:
18
- // Phase 1 (handler entry, ~line 1372): bugId = `PENDING-${Date.now()}`, isNewBug = true.
19
- // A temporary placeholder; the timestamp is later used to find the real bug record.
20
- // Phase 2 (pre-init, ~line 1495–1500): preCreateBug() writes a minimal bug record with a
21
- // real FORGE-BUG-NNN ID so the triage subagent has a stable ID to reference.
22
- // If preCreateBug fails, the PENDING- placeholder is kept for fallback capture.
23
- // Phase 3 (post-triage, ~line 962–989): capture real ID from BugCreated events emitted by
24
- // the triage subagent; fall back to listing the most-recent bug after pipelineStart if
25
- // event capture fails. The PENDING- prefix is used throughout as a guard for
26
- // state-write paths (see ~line 176 and CallerContextStore guards).
27
- // Reference: PENDING- prefix semantics defined in CallerContextStore guards.
28
- //
29
- // N-H-H — Preflight gate design (closed by FORGE-S25-T17):
30
- // Entry-level: runOrchestratorPreflight is called at runBugPipeline entry (~line 523).
31
- // Validates persona/model config before any LLM dispatch (mirrors run-task.ts design).
32
- // Per-phase: runPreflightGate (store-cli gate) is called per phase (~line 667).
33
- // Evaluates declarative gate conditions from the workflow's gate block.
34
- // This two-level design ensures both structural validity (model/persona config) and
35
- // store-state validity (predecessor verdicts, status guards) are checked.
36
- // Reference: orchestrator-preflight.ts (N-H-H, FORGE-S25-T17).
37
- //
38
- // N-H-E tag: see inline comment at the materialization skip (~line 707 / checkMaterialization).
39
- import { spawnSync } from "node:child_process";
40
- import * as fs from "node:fs";
41
- import * as path from "node:path";
42
- import { fileURLToPath } from "node:url";
43
- import { assertAudience, CallerContextStore } from "../audience-gate.js";
44
- // ModelRegistry/AuthStorage no longer instantiated here — use ctx.modelRegistry
45
- // so extension-registered providers (registered against the live session) are
46
- // visible to validateModelConfig. Creating a fresh registry here would miss
47
- // them and produce spurious MODEL_UNAVAILABLE warnings (FORGE-BUG-001).
48
- import { loadLayeredConfig } from "../config/config-layer.js";
49
- import { resolveModelForPhase } from "../config/model-resolver.js";
50
- import { loadForgePersona, runForgeSubagent } from "../forge-subagent.js";
51
- import { getSubagentTools } from "../forge-tools.js";
52
- import { loadGovernorProjectConfig } from "../governor-config.js";
53
- import { readPersonaDir as readPersonaDirBug, readPipelineNames as readPipelineNamesBug, } from "../lib/catalog-helpers.js";
54
- import { discoverForgeConfigCached } from "../lib/forge-config.js";
55
- import { checkMaterialization } from "../lib/manifest-checker.js";
56
- import { getOrchestratorTree } from "../orchestrator-tree.js";
57
- import { loadWorkflow } from "../parsers/workflow-loader.js";
58
- import { getSessionRegistry } from "../session-registry.js";
59
- import { resolveToCanonicalId, resolveToolDir } from "../store/store-resolver.js";
60
- import { OrchestratorTranscriptWriter } from "../subagent/orchestrator-transcript.js";
61
- import { archiveRun, sweepProjectTranscripts } from "../transcript-archive.js";
62
- import { attachViewportObserver } from "../viewport/events.js";
63
- import { fmtPhaseSummary } from "../viewport/renderer.js";
64
- import { resolveAdvisorModel, runHaltAdvisor } from "./halt-advisor.js";
65
- import { runOrchestratorPreflight } from "./orchestrator-preflight.js";
66
- import { buildPhaseEvent, buildSummariesBlock, drainFrictionFile, emitEvent, emitIncompletePhaseEvent, findPredecessorIndex, formatLocalTime, isNonInteractive, judgementFromSummary, runPreflightGateWithData, transcriptOutcomeFor, validateId, } from "./run-task.js";
67
- // ── Bug phase descriptor table ──────────────────────────────────────────────
68
- //
69
- // Decoded from .forge/workflows/fix_bug.md and the task prompt's BUG_PHASES.
70
- // triage / plan-fix / implement all read the same fix_bug.md body — the
71
- // workflow handles all three phases through prose.
72
- // FORGE-S25-T16: readPersonaDirBug / readPipelineNamesBug extracted to
73
- // lib/catalog-helpers.ts and imported above with aliases (H-4, N-H-G).
74
- export const BUG_PHASES = [
75
- // FORGE-BUG-040: each phase points at its own phase-scoped subagent workflow.
76
- // Previously triage/plan-fix/implement all pointed at fix_bug.md (the
77
- // orchestrator-only body), which caused the triage subagent to execute
78
- // the full lifecycle in a single invocation. plan-fix and implement reuse
79
- // plan_task.md / implement_plan.md (bug-mode) per meta-fix-bug.md
80
- // § Pipeline Phases — the bug-mode entity-kind detection is built into
81
- // those workflows already.
82
- { role: "triage", workflowFile: "triage", personaNoun: "bug-fixer", isReview: false, maxIterations: 1 },
83
- { role: "plan-fix", workflowFile: "plan_task", personaNoun: "engineer", isReview: false, maxIterations: 1 },
84
- { role: "review-plan", workflowFile: "review_plan", personaNoun: "supervisor", isReview: true, maxIterations: 3 },
85
- { role: "implement", workflowFile: "implement_plan", personaNoun: "engineer", isReview: false, maxIterations: 1 },
86
- { role: "review-code", workflowFile: "review_code", personaNoun: "supervisor", isReview: true, maxIterations: 3 },
87
- { role: "approve", workflowFile: "architect_approve", personaNoun: "architect", isReview: true, maxIterations: 3 },
88
- { role: "commit", workflowFile: "commit_task", personaNoun: "engineer", isReview: false, maxIterations: 1 },
89
- ];
90
- // FORGE-BUG-040: BUG_SUMMARY_KEY_BY_ROLE lives in
91
- // subagent/phase-summary-map.ts so the new phase-guard.ts can import
92
- // it without dragging fix-bug.ts into a forge-tools import cycle.
93
- // Re-exported here for backwards-compatibility with existing call sites.
14
+ // sendKickoff is NEVER called from this file or its extracted modules.
15
+ // ── Bug phase descriptor table + FSM transitions + event tokens ────────────
16
+ export { BUG_PHASES, BUG_TYPE_TOKENS, postTriageTransitions } from "./bug/bug-phases.js";
17
+ // ── Post-triage routing (status transitions + Path A/B branch) ─────────────
18
+ export { routeAfterTriage, } from "./bug/bug-triage-routing.js";
19
+ // BUG_SUMMARY_KEY_BY_ROLE lives in subagent/phase-summary-map.ts; re-exported
20
+ // here for backwards-compatibility with existing call sites and tests.
94
21
  export { BUG_SUMMARY_KEY_BY_ROLE } from "../subagent/phase-summary-map.js";
95
- import { BUG_SUMMARY_KEY_BY_ROLE } from "../subagent/phase-summary-map.js";
96
- // Bug-event type tokens explicit mapping per review finding #3.
97
- // Non-review phases always emit the pass token. Review phases select
98
- // pass or fail based on ec.judgement.verdict.
99
- export const BUG_TYPE_TOKENS = {
100
- triage: { pass: "bug-triaged", fail: "bug-triaged" },
101
- "plan-fix": { pass: "fix-planned", fail: "fix-planned" },
102
- "review-plan": { pass: "fix-review-passed", fail: "fix-review-failed" },
103
- implement: { pass: "fix-implemented", fail: "fix-implemented" },
104
- "review-code": { pass: "fix-code-review-passed", fail: "fix-code-review-failed" },
105
- approve: { pass: "fix-approved", fail: "fix-revision-requested" },
106
- commit: { pass: "bug-committed", fail: "bug-commit-failed" },
107
- };
108
- // ── Bug FSM transitions ────────────────────────────────────────────────────
109
- // Mirrors store-cli BUG_TRANSITIONS. Terminal: `fixed`.
110
- // `approved` and `verified` enum values were dropped in forge v0.44.0
111
- // (FORGE-BUG-002 trap). The canonical source is store-cli.cjs.
112
- const BUG_TERMINAL_STATES = new Set(["fixed"]);
113
- // Post-triage status transitions the ORCHESTRATOR owns (meta-fix-bug.md
114
- // step 2: "On return, orchestrator transitions status: triaged then
115
- // in-progress" — a required two-step state-machine contract; the FSM
116
- // forbids the one-step reported → in-progress jump). First live firing of
117
- // commit-task.cjs exposed this as unimplemented: bugs reached the commit
118
- // phase still 'reported' and the terminal-status guard fired every run.
119
- // Returns the ordered list of statuses to write from the given status —
120
- // idempotent on resume (already in-progress / terminal → no-op).
121
- export function postTriageTransitions(status) {
122
- if (status === "reported")
123
- return ["triaged", "in-progress"];
124
- if (status === "triaged")
125
- return ["in-progress"];
126
- return [];
127
- }
128
- function bugStateFilePath(cwd, bugId, sessionId) {
129
- if (!validateId(bugId)) {
130
- throw new Error(`Invalid bugId for state file path: ${bugId}`);
131
- }
132
- const suffix = sessionId ?? process.env.FORGE_SESSION_ID ?? `${process.pid}`;
133
- return path.join(cwd, ".forge", "cache", `fix-bug-state-${bugId}-${suffix}.json`);
134
- }
135
- export function readBugState(cwd, bugId, sessionId) {
136
- // If a specific session ID is given, read that file directly.
137
- if (sessionId || process.env.FORGE_SESSION_ID) {
138
- const fp = bugStateFilePath(cwd, bugId, sessionId);
139
- try {
140
- if (!fs.existsSync(fp))
141
- return null;
142
- const raw = fs.readFileSync(fp, "utf8");
143
- return JSON.parse(raw);
144
- }
145
- catch {
146
- return null;
147
- }
148
- }
149
- // No specific session — glob for the most recent matching state file.
150
- // Single-writer assumption: normally only one session per bug.
151
- const cacheDir = path.join(cwd, ".forge", "cache");
152
- const prefix = `fix-bug-state-${bugId}-`;
153
- let bestFile = null;
154
- let bestMtime = 0;
155
- try {
156
- const entries = fs.readdirSync(cacheDir);
157
- for (const entry of entries) {
158
- if (!entry.startsWith(prefix) || !entry.endsWith(".json"))
159
- continue;
160
- const fp = path.join(cacheDir, entry);
161
- try {
162
- const st = fs.statSync(fp);
163
- if (st.mtimeMs > bestMtime) {
164
- bestMtime = st.mtimeMs;
165
- bestFile = fp;
166
- }
167
- }
168
- catch { }
169
- }
170
- }
171
- catch {
172
- return null;
173
- }
174
- if (!bestFile)
175
- return null;
176
- try {
177
- const raw = fs.readFileSync(bestFile, "utf8");
178
- return JSON.parse(raw);
179
- }
180
- catch {
181
- return null;
182
- }
183
- }
184
- export function writeBugState(cwd, state) {
185
- // Guard: never write state for PENDING bugIds — wait for real bugId capture.
186
- if (state.bugId.startsWith("PENDING-"))
187
- return;
188
- const fp = bugStateFilePath(cwd, state.bugId);
189
- const dir = path.dirname(fp);
190
- fs.mkdirSync(dir, { recursive: true });
191
- fs.writeFileSync(fp, JSON.stringify(state, null, 2), "utf8");
192
- }
193
- export function deleteBugState(cwd, bugId) {
194
- // Clean up all state files for this bug (all sessions)
195
- const cacheDir = path.join(cwd, ".forge", "cache");
196
- const statePrefix = `fix-bug-state-${bugId}-`;
197
- const debugPrefix = `fix-bug-debug-${bugId}`;
198
- try {
199
- const entries = fs.readdirSync(cacheDir);
200
- for (const entry of entries) {
201
- if ((entry.startsWith(statePrefix) && entry.endsWith(".json")) || entry.startsWith(debugPrefix)) {
202
- try {
203
- fs.unlinkSync(path.join(cacheDir, entry));
204
- }
205
- catch {
206
- /* non-fatal */
207
- }
208
- }
209
- }
210
- }
211
- catch {
212
- // non-fatal
213
- }
214
- }
215
- export function isBugStateStale(state) {
216
- const savedAt = new Date(state.savedAt).getTime();
217
- const ageMs = Date.now() - savedAt;
218
- const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
219
- return ageMs > sevenDaysMs;
220
- }
221
- export function readBugRecord(bugId, storeCli, cwd) {
222
- const result = spawnSync("node", [storeCli, "read", "bug", bugId], { cwd, encoding: "utf8" });
223
- if (result.status !== 0)
224
- return null;
225
- try {
226
- const raw = typeof result.stdout === "string" ? result.stdout : String(result.stdout);
227
- return JSON.parse(raw);
228
- }
229
- catch {
230
- return null;
231
- }
232
- }
233
- // Pure helper: next <PREFIX>-BUG-NNN ID given the existing bug IDs.
234
- // Only same-prefix bugs participate in the increment — exported for tests.
235
- // The prefix is config-owned (project.prefix) and identifier-validated by
236
- // loadGovernorProjectConfig, so it is safe to splice into a RegExp.
237
- export function computeNextBugId(bugIds, prefix) {
238
- const idPattern = new RegExp(`^${prefix}-BUG-(\\d+)$`);
239
- let maxNum = 0;
240
- for (const id of bugIds) {
241
- const m = idPattern.exec(id);
242
- if (m) {
243
- const n = parseInt(m[1], 10);
244
- if (n > maxNum)
245
- maxNum = n;
246
- }
247
- }
248
- return `${prefix}-BUG-${String(maxNum + 1).padStart(3, "0")}`;
249
- }
250
- // Pre-assigns a real <PREFIX>-BUG-NNN ID by listing existing bugs and
251
- // incrementing. Prefix comes from .forge/config.json project.prefix — the
252
- // hardcoded FORGE prefix minted phantom FORGE-BUG-* records in any project
253
- // with a different prefix (CART testbench incident, FORGE-BUG-043 class).
254
- export function assignNextBugId(storeCli, cwd, prefix = "FORGE") {
255
- const result = spawnSync("node", [storeCli, "list", "bug", "--json"], { cwd, encoding: "utf8" });
256
- let bugIds = [];
257
- if (result.status === 0 && result.stdout) {
258
- try {
259
- const bugs = JSON.parse(result.stdout);
260
- if (Array.isArray(bugs)) {
261
- bugIds = bugs.map((b) => String(b.bugId ?? ""));
262
- }
263
- }
264
- catch {
265
- /* empty store — start from 1 */
266
- }
267
- }
268
- return computeNextBugId(bugIds, prefix);
269
- }
270
- // Extracts the first canonical <PREFIX>-BUG-NNN referenced in a bug-report
271
- // text (e.g. the "**Bug ID**: CART-BUG-001" header line BUG_REPORT.md files
272
- // carry). Used by the @file intake path so /forge:fix-bug @BUG_REPORT.md
273
- // operates on the referenced store record instead of minting a duplicate.
274
- // Prefix is config-owned and identifier-validated (safe in a RegExp).
275
- export function extractBugIdFromReportText(text, prefix) {
276
- const m = text.match(new RegExp(`\\b${prefix}-BUG-\\d+\\b`));
277
- return m ? m[0] : null;
278
- }
279
- // Pre-creates a minimal bug record so the subagent has a real ID to work with.
280
- export function preCreateBug(bugId, title, storeCli, cwd) {
281
- const data = {
282
- bugId,
283
- title,
284
- severity: "minor",
285
- status: "reported",
286
- path: `engineering/bugs/${bugId}`,
287
- reportedAt: new Date().toISOString(),
288
- };
289
- const result = spawnSync("node", [storeCli, "write", "bug", JSON.stringify(data)], { cwd, encoding: "utf8" });
290
- return result.status === 0;
291
- }
292
- export function readBugVerdict(bugRecord, phaseRole, summaryKeyByRole) {
293
- if (!bugRecord)
294
- return "missing";
295
- // Approve phase: read approve summary verdict (set via set-bug-summary).
296
- // The forge v0.44.0 contract makes summaries.approve.verdict the canonical
297
- // approve signal for bugs — `bug.status` does NOT carry an "approved"
298
- // value (that enum was dropped). See read-verdict.cjs §
299
- // BUG_PHASE_VERDICT_SOURCE for the matching plugin-side wiring.
300
- if (phaseRole === "approve") {
301
- const summaryKey = summaryKeyByRole["approve"];
302
- if (summaryKey) {
303
- const summaries = bugRecord.summaries ?? {};
304
- const blob = summaries[summaryKey];
305
- if (blob && typeof blob === "object") {
306
- const verdict = blob?.verdict;
307
- if (typeof verdict === "string") {
308
- if (verdict === "approved")
309
- return "approved";
310
- if (verdict === "revision")
311
- return "revision";
312
- }
313
- }
314
- }
315
- return "missing";
316
- }
317
- // Commit phase: read bug status directly. Terminal target is `fixed`.
318
- if (phaseRole === "commit") {
319
- if (bugRecord.status === "fixed")
320
- return "approved";
321
- // in-progress means commit did not advance status — treat as revision-needed.
322
- if (bugRecord.status === "in-progress")
323
- return "revision";
324
- return "missing";
325
- }
326
- // Review phases: read from summaries via key map.
327
- const summaryKey = summaryKeyByRole[phaseRole];
328
- if (!summaryKey)
329
- return "missing";
330
- const summaries = bugRecord.summaries ?? {};
331
- const blob = summaries[summaryKey];
332
- if (!blob || typeof blob !== "object")
333
- return "missing";
334
- const verdict = blob?.verdict;
335
- if (typeof verdict !== "string")
336
- return "missing";
337
- if (verdict === "approved")
338
- return "approved";
339
- if (verdict === "revision")
340
- return "revision";
341
- return "missing";
342
- }
343
- // ── Bug body composition ──────────────────────────────────────────────────
344
- export function composeBugBody(subWorkflowMd, bugId, phaseRole, bugStatusBeforePhase, summariesBlock) {
345
- // Entity-kind override block prepended before workflow body.
346
- // Conforms to forge v0.44.x meta-fix-bug contract:
347
- // - bug.status enum is {reported, triaged, in-progress, fixed}; `fixed` is terminal.
348
- // - `approved` and `verified` are NOT valid bug status values (dropped in v0.44.0).
349
- // - Approve phase: NO status write. Architect writes summaries.approve.verdict
350
- // via set-bug-summary; verdict signal IS the summary (read by
351
- // read-verdict.cjs § BUG_PHASE_VERDICT_SOURCE).
352
- // - Commit phase: status → fixed (the only status transition post-triage).
353
- //
354
- // Earlier revisions of this prompt told the architect to write
355
- // `update-status bug ... approved` and the engineer to write `... verified`.
356
- // Those instructions produced the FORGE-BUG-002 trap (LLM-translation of
357
- // task-shaped approve workflow → illegal transition through a terminal state).
358
- // The new contract removes the trap at its source.
359
- const entityKindLines = [
360
- `Bug ID: ${bugId}`,
361
- "",
362
- "⚠ ENTITY KIND OVERRIDE: This is a bug, not a task.",
363
- "- All `update-status` calls must use entity kind `bug` (not `task`).",
364
- "- Approve phase: NO status write. Write the approval verdict via set-bug-summary:",
365
- ` node "$FORGE_ROOT/tools/store-cli.cjs" set-bug-summary ${bugId} approve <APPROVE-SUMMARY.json>`,
366
- ` The summary's "verdict" field MUST be "approved" or "revision". The downstream commit gate reads this, not bug.status.`,
367
- `- Commit phase: on successful git commit, run \`node "$FORGE_ROOT/tools/store-cli.cjs" update-status bug ${bugId} status fixed\` (terminal).`,
368
- `- Do NOT write "approved" or "verified" to bug.status — those values were removed from the schema in forge v0.44.0.`,
369
- `- Do NOT reference task-specific status values (e.g., "committed") or task entity kind.`,
370
- "- CRITICAL: All `set-summary` calls must use `set-bug-summary` (not `set-summary`).",
371
- ` e.g. node "$FORGE_ROOT/tools/store-cli.cjs" set-bug-summary ${bugId} review_plan <jsonFile>`,
372
- `- Preflight gate: use \`--bug\` flag (not \`--task\`). e.g. node "$FORGE_ROOT/tools/preflight-gate.cjs" --phase review-plan --bug ${bugId}`,
373
- "- Skip re-running preflight-gate — the orchestrator already checked it. Proceed directly to the review.",
374
- 'Any workflow text that says "task" should be read as "bug" for this context.',
375
- ];
376
- // Phase-specific reinforcement when the orchestrator can name the current status.
377
- if (phaseRole === "approve" && bugStatusBeforePhase) {
378
- entityKindLines.push(`- Approve phase (reinforce): bug.status is currently '${bugStatusBeforePhase}' and MUST NOT change in this phase. Record verdict in summaries.approve only.`);
379
- }
380
- if (phaseRole === "commit" && bugStatusBeforePhase) {
381
- entityKindLines.push(`- Commit phase: after the git commit lands, transition bug.status from '${bugStatusBeforePhase}' to 'fixed'.`);
382
- }
383
- // FORGE-BUG-040: the triage-phase hint block previously prepended here
384
- // compensated for the orchestrator-only fix_bug.md being delivered to
385
- // the triage subagent. With the new phase-scoped triage.md sub-workflow,
386
- // the route-field contract and Path A/B criteria are documented natively
387
- // in the workflow body — no compose-time injection required.
388
- const parts = [
389
- `Read the workflow below and follow it. Bug ID: ${bugId}.`,
390
- "",
391
- "---",
392
- "",
393
- entityKindLines.join("\n"),
394
- "",
395
- "---",
396
- "",
397
- ];
398
- if (summariesBlock) {
399
- parts.push(summariesBlock, "", "---", "");
400
- }
401
- parts.push(subWorkflowMd.trim());
402
- return parts.join("\n");
403
- }
404
- // ── BugId capture via tool_execution_end ──────────────────────────────────
405
- const BUG_WRITE_TOOL_NAMES = new Set(["write", "store-cli", "bash", "forge_store"]);
406
- /**
407
- * Scan tool_execution_end events to extract the bugId written by a triage
408
- * subagent. Returns the LAST matching tool call's bugId, or null if none found.
409
- *
410
- * In pi runtime, the forge_store tool is registered as "forge_store" (not
411
- * "store-cli"). In Claude Code runtime, subagents may shell out via Bash.
412
- * This function covers all three paths.
413
- */
414
- export function extractBugIdFromEvents(events, prefix = "FORGE") {
415
- // Prefix is config-owned (project.prefix) and identifier-validated by
416
- // loadGovernorProjectConfig — the previous hardcoded FORGE-BUG- pattern
417
- // missed every capture in differently-prefixed projects (CART incident).
418
- const idPattern = new RegExp(`${prefix}-BUG-\\d+`);
419
- const idPrefix = `${prefix}-BUG-`;
420
- let lastBugId = null;
421
- for (const event of events) {
422
- if (!event.toolName)
423
- continue;
424
- // Check for store-cli write bug calls (Claude Code runtime)
425
- if (event.toolName === "store-cli") {
426
- const result = event.result;
427
- if (typeof result === "string") {
428
- const match = result.match(idPattern);
429
- if (match)
430
- lastBugId = match[0];
431
- }
432
- else if (result && typeof result === "object") {
433
- const obj = result;
434
- if (typeof obj.bugId === "string" && obj.bugId.startsWith(idPrefix)) {
435
- lastBugId = obj.bugId;
436
- }
437
- }
438
- }
439
- // Check for forge_store tool calls (pi runtime)
440
- // The pi extension registers the tool as "forge_store", not "store-cli".
441
- if (event.toolName === "forge_store" && event.result != null) {
442
- const output = typeof event.result === "string" ? event.result : JSON.stringify(event.result);
443
- const match = output.match(idPattern);
444
- if (match)
445
- lastBugId = match[0];
446
- }
447
- // Also check for write operations to .forge/store/bugs/
448
- if (event.toolName === "write" && typeof event.result === "string") {
449
- const match = event.result.match(idPattern);
450
- if (match)
451
- lastBugId = match[0];
452
- }
453
- // Bash events: subagents shelling out via Bash may run "store-cli write bug".
454
- // Only match when output includes store-cli, write, and bug together
455
- // to avoid false positives from unrelated Bash commands that happen to
456
- // mention a bug ID in a different context.
457
- if (event.toolName === "bash" && event.result != null) {
458
- const output = typeof event.result === "string" ? event.result : JSON.stringify(event.result);
459
- if (output.includes("store-cli") && output.includes("write") && output.includes("bug")) {
460
- const match = output.match(idPattern);
461
- if (match)
462
- lastBugId = match[0];
463
- }
464
- }
465
- }
466
- return lastBugId;
467
- }
468
- const STATUS_KEY = "forge:fix-bug";
469
- const MESSAGE_KEY = "forge:fix-bug:message";
470
- export async function runBugPipeline(opts) {
471
- // Thin wrapper: capture the orchestrator transcript writer the inner
472
- // pipeline creates, surface its path on the result (so callers can
473
- // archive the run), and GUARANTEE a pipeline-end on every outcome —
474
- // cancel/halt/failure paths that return without recording one left runs
475
- // archived as "incomplete" instead of their true outcome.
476
- let orchTranscript;
477
- const result = await runBugPipelineInner(opts, (writer) => {
478
- orchTranscript = writer;
479
- });
480
- if (orchTranscript) {
481
- orchTranscript.close(transcriptOutcomeFor(result.status), result.lastError);
482
- result.orchestratorTranscriptPath = orchTranscript.filePath;
483
- }
484
- return result;
485
- }
486
- async function runBugPipelineInner(opts, onOrchestratorTranscript) {
487
- const { bugId: initialBugId, originalArg, isNewBug, cwd, ctx, forgeRoot, storeCli, preflightGate, registry, resumeFromState, } = opts;
488
- const tree = getOrchestratorTree();
489
- // Mutable bugId — for new bugs, pre-assign a real FORGE-BUG-NNN ID
490
- // before triage so the subagent never needs to create or discover one.
491
- // This replaces the fragile PENDING→capture pattern where the subagent was
492
- // expected to create the bug record and we'd fish the ID from events.
493
- let bugId = initialBugId;
494
- let currentPhaseIndex = resumeFromState?.phaseIndex ?? 0;
495
- const iterationCounts = resumeFromState?.iterationCounts ?? {};
496
- // Per-role dispatch counter for OrchestratorTree node identity. Distinct
497
- // from iterationCounts (which only tracks review-verdict revisions): every
498
- // dispatch of a role — including a plan-fix re-run after a review
499
- // loopback — gets its own `<bugId>:<role>:<attempt>` node, so the
500
- // dashboard renders one leaf per run in dispatch order instead of merging
501
- // attempts into one node (CART-BUG-003 dashboard regression). Seeded from
502
- // resume state so resumed runs continue numbering past prior attempts.
503
- const dispatchCounts = { ...(resumeFromState?.iterationCounts ?? {}) };
504
- let lastModel;
505
- let lastProvider;
506
- // ── Per-persona model routing (Plan 16) ─────────────────────────────────
507
- // Load layered routing config once at bug-pipeline entry. Empty / absent
508
- // config produces inherit for every phase — no behaviour change. Pipeline
509
- // name "fix-bug" lets users configure per-phase overrides distinctly from
510
- // task pipelines under pipelines["fix-bug"] in their routing config.
511
- // N-B-E: surface schema errors to caller (Decision 9 — orchestrators fail-fast).
512
- // See doc/decisions/layered-config-error-policy.md.
513
- const { merged: modelRoutingConfig, errors: layeredConfigErrors } = loadLayeredConfig(cwd);
514
- if (layeredConfigErrors.length > 0) {
515
- for (const e of layeredConfigErrors) {
516
- ctx.ui.notify(`× forge:fix-bug — forge-cli config schema error: ${e}`, "error");
517
- }
518
- return {
519
- status: "failed",
520
- lastPhaseIndex: currentPhaseIndex,
521
- iterationCounts,
522
- lastError: `forge-cli config schema errors: ${layeredConfigErrors.join("; ")}`,
523
- };
524
- }
525
- // Pre-flight validation — same shape as run-task / run-sprint.
526
- // FORGE-S25-T17: delegated to orchestrator-preflight.ts (H-13).
527
- {
528
- const personasDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "forge-payload", ".base-pack", "personas");
529
- const personaCatalogue = readPersonaDirBug(personasDir);
530
- const forgeCfgPath = path.join(cwd, ".forge", "config.json");
531
- const pipelineCatalogue = readPipelineNamesBug(forgeCfgPath);
532
- const availableModels = ctx.modelRegistry?.getAvailable?.() ?? [];
533
- const preflightResult = runOrchestratorPreflight({
534
- mode: "task",
535
- ctx,
536
- notifyPrefix: "forge:fix-bug",
537
- personaCatalogue,
538
- pipelineCatalogue,
539
- modelRoutingConfig,
540
- availableModels: availableModels.map((m) => ({ provider: m.provider, id: m.id })),
541
- });
542
- if (!preflightResult.proceed) {
543
- return {
544
- ...preflightResult.result,
545
- lastPhaseIndex: currentPhaseIndex,
546
- iterationCounts,
547
- };
548
- }
549
- }
550
- // ── Orchestrator transcript ──────────────────────────────────────────
551
- // One JSONL file per pipeline run, ISO-prefixed in its filename so
552
- // review-loop iterations (plan → review → plan → review) preserve
553
- // their own logs instead of overwriting each other. Captures every
554
- // ctx.ui.notify line plus structured phase-boundary events.
555
- const orchTranscript = new OrchestratorTranscriptWriter({
556
- cwd,
557
- entityKind: "bug",
558
- entityId: bugId,
559
- });
560
- onOrchestratorTranscript(orchTranscript);
561
- const __origNotify = ctx.ui.notify.bind(ctx.ui);
562
- ctx.ui.notify = ((msg, level) => {
563
- __origNotify(msg, level);
564
- orchTranscript.record({
565
- kind: "notify",
566
- ts: new Date().toISOString(),
567
- level: (level ?? "info"),
568
- message: typeof msg === "string" ? msg : String(msg),
569
- });
570
- });
571
- const pipelineStartMs = Date.now();
572
- try {
573
- while (currentPhaseIndex < BUG_PHASES.length) {
574
- // ── Between-phase cancellation gate ────────────────────────────
575
- if (opts.signal?.aborted) {
576
- ctx.ui.notify(`⊘ forge:fix-bug — ${bugId} cancelled by user.`, "info");
577
- registry.completePhase(bugId, BUG_PHASES[currentPhaseIndex]?.role ?? "unknown", "cancelled");
578
- registry.confirmCancelled(bugId);
579
- // ADR-S21-01: preserve state file so cancelled runs are resumable
580
- writeBugState(cwd, {
581
- bugId,
582
- phaseIndex: currentPhaseIndex,
583
- iterationCounts,
584
- halted: false,
585
- status: "cancelled",
586
- lastError: undefined,
587
- savedAt: new Date().toISOString(),
588
- });
589
- return { status: "cancelled", lastPhaseIndex: currentPhaseIndex, iterationCounts };
590
- }
591
- const phase = BUG_PHASES[currentPhaseIndex];
592
- if (!phase) {
593
- ctx.ui.notify(`× forge:fix-bug — invalid phase index ${currentPhaseIndex}`, "error");
594
- return {
595
- status: "failed",
596
- lastPhaseIndex: currentPhaseIndex,
597
- iterationCounts,
598
- lastError: `invalid phase index ${currentPhaseIndex}`,
599
- };
600
- }
601
- ctx.ui.setStatus?.(STATUS_KEY, `fix-bug ${bugId}: phase ${currentPhaseIndex + 1}/${BUG_PHASES.length} (${phase.role})`);
602
- ctx.ui.notify(`→ ${bugId}: ${phase.role} (phase ${currentPhaseIndex + 1}/${BUG_PHASES.length})`, "info");
603
- orchTranscript.record({
604
- kind: "phase-start",
605
- ts: new Date().toISOString(),
606
- phase: phase.role,
607
- phaseIndex: currentPhaseIndex,
608
- phaseCount: BUG_PHASES.length,
609
- attempt: (iterationCounts[phase.role] ?? 0) + 1,
610
- workflowFile: phase.workflowFile,
611
- persona: phase.personaNoun,
612
- });
613
- const subWorkflowPath = path.join(cwd, ".forge", "workflows", `${phase.workflowFile}.md`);
614
- // ── Read sub-workflow ─────────────────────────────────────────
615
- let subWorkflowMd;
616
- let subWorkflowAudience = "any";
617
- try {
618
- const loaded = loadWorkflow(subWorkflowPath);
619
- subWorkflowMd = loaded.rawMarkdown;
620
- subWorkflowAudience = loaded.audience;
621
- }
622
- catch (err) {
623
- const e = err;
624
- ctx.ui.notify(`× forge:fix-bug — failed to read sub-workflow for ${phase.role}: ${e.message ?? "unknown"}`, "error");
625
- writeBugState(cwd, {
626
- bugId,
627
- phaseIndex: currentPhaseIndex,
628
- iterationCounts,
629
- halted: true,
630
- lastError: `sub-workflow read failed: ${e.message ?? "unknown"}`,
631
- savedAt: new Date().toISOString(),
632
- });
633
- return {
634
- status: "failed",
635
- lastPhaseIndex: currentPhaseIndex,
636
- iterationCounts,
637
- lastError: `sub-workflow read failed: ${e.message ?? "unknown"}`,
638
- };
639
- }
640
- // ── 6a. Phase skip (state-aware, defense-in-depth) ─────────────
641
- // Belt-and-suspenders alongside the explicit summaries.triage.route
642
- // branch (handled in section 6c below). Some subagents in some
643
- // runtimes still go end-to-end during triage instead of just triaging
644
- // — rather than roll back the work they did, skip non-review phases
645
- // whose output is already reflected in the bug status. Review phases
646
- // are never skipped — they are quality gates that must always run.
647
- //
648
- // Post-v0.44.0: terminal status is `fixed` only. `approved` and
649
- // `verified` are no longer valid bug status values; references
650
- // removed.
651
- const PHASE_SKIP_STATES = {
652
- "plan-fix": new Set(["fixed"]),
653
- implement: new Set(["fixed"]),
654
- commit: new Set(["fixed"]), // commit writes the terminal status; skip if already there
655
- };
656
- const bugNow = readBugRecord(bugId, storeCli, cwd);
657
- const skipStates = PHASE_SKIP_STATES[phase.role];
658
- if (skipStates && bugNow?.status && skipStates.has(bugNow.status) && !phase.isReview) {
659
- ctx.ui.notify(`⊘ forge:fix-bug — skipping ${phase.role}: bug ${bugId} is already '${bugNow.status}' (work already done).`, "info");
660
- // Write a synthetic "approved" summary so downstream `after` predecessor
661
- // verdict checks find a verdict and don't block review phases.
662
- const summaryKey = BUG_SUMMARY_KEY_BY_ROLE[phase.role];
663
- if (summaryKey) {
664
- const synthSummary = {
665
- objective: `Phase ${phase.role} skipped — bug already ${bugNow.status}`,
666
- findings: ["Subagent completed fix during triage (Path A); phase output implicitly satisfied."],
667
- // Non-review phases should have verdict "n/a" — the phase
668
- // didn't produce a gate verdict. This matches the `after
669
- // <phase> = n/a` preflight gate contract. Review phases
670
- // use "approved" since they are gate phases.
671
- verdict: phase.isReview ? "approved" : "n/a",
672
- written_at: new Date().toISOString(),
673
- };
674
- const synthFile = path.join(cwd, ".forge", "cache", `synthetic-summary-${bugId}-${summaryKey}.json`);
675
- fs.writeFileSync(synthFile, JSON.stringify(synthSummary, null, 2), "utf8");
676
- const synthResult = spawnSync("node", [storeCli, "set-bug-summary", bugId, summaryKey, synthFile], {
677
- cwd,
678
- encoding: "utf8",
679
- });
680
- if (synthResult.status !== 0) {
681
- ctx.ui.notify(`⚠ forge:fix-bug — synthetic summary write failed for ${phase.role}: ${String(synthResult.stderr).trim()}`, "warning");
682
- }
683
- try {
684
- fs.unlinkSync(synthFile);
685
- }
686
- catch {
687
- /* non-fatal */
688
- }
689
- }
690
- currentPhaseIndex++;
691
- continue;
692
- }
693
- // ── 6b. Preflight gate ────────────────────────────────────────
694
- // Skip preflight gate for triage phase of new bugs (PENDING- placeholder)
695
- // because the bug record doesn't exist yet — gates referencing bug fields
696
- // would always fail.
697
- //
698
- // Also skip for review phases when the bug is already in a terminal
699
- // state ("fixed"). Path A bugs get fixed during triage, then the
700
- // preflight gate's `forbid bug.status == fixed` and `after implement
701
- // = n/a` checks block review-code/review-plan even though we
702
- // deliberately want to run those reviews. The review subagent handles
703
- // the already-fixed scenario internally.
704
- const pendingBugId = bugId.startsWith("PENDING-");
705
- const bugAlreadyFixed = bugNow?.status === "fixed" && phase.isReview;
706
- if (!pendingBugId && !bugAlreadyFixed && fs.existsSync(preflightGate)) {
707
- const preflightOutcome = runPreflightGateWithData(preflightGate, phase.role, bugId, cwd, "bug");
708
- if (preflightOutcome.result === "halt") {
709
- // Render structured failure reason if available.
710
- if (preflightOutcome.gateFailure) {
711
- ctx.ui.notify(`× forge:fix-bug — preflight gate failed for phase ${phase.role} ` +
712
- `[${preflightOutcome.gateFailure.reasonCode}]: ${preflightOutcome.gateFailure.detail}`, "error");
713
- }
714
- else {
715
- ctx.ui.notify(`× forge:fix-bug — preflight gate failed for phase ${phase.role} (exit 1); halting.`, "error");
716
- }
717
- writeBugState(cwd, {
718
- bugId,
719
- phaseIndex: currentPhaseIndex,
720
- iterationCounts,
721
- halted: true,
722
- lastError: `preflight gate exit 1 for ${phase.role}`,
723
- savedAt: new Date().toISOString(),
724
- });
725
- // Spawn halt-recovery advisor (Tier 1, best-effort — non-fatal).
726
- if (preflightOutcome.gateFailure) {
727
- const advisorModel = resolveAdvisorModel(modelRoutingConfig, ctx.model);
728
- void runHaltAdvisor({
729
- gateFailure: preflightOutcome.gateFailure,
730
- advisorModel,
731
- taskId: bugId,
732
- cwd,
733
- ctx: { ui: ctx.ui },
734
- forgeRoot,
735
- });
736
- }
737
- return {
738
- status: "halted",
739
- lastPhaseIndex: currentPhaseIndex,
740
- iterationCounts,
741
- lastError: `preflight gate exit 1 for ${phase.role}`,
742
- };
743
- }
744
- if (preflightOutcome.result === "escalate") {
745
- ctx.ui.notify(`× forge:fix-bug — preflight gate escalated for phase ${phase.role} (exit 2); manual intervention required.`, "error");
746
- writeBugState(cwd, {
747
- bugId,
748
- phaseIndex: currentPhaseIndex,
749
- iterationCounts,
750
- halted: true,
751
- lastError: `preflight gate exit 2 (escalate) for ${phase.role}`,
752
- savedAt: new Date().toISOString(),
753
- });
754
- return {
755
- status: "escalated",
756
- lastPhaseIndex: currentPhaseIndex,
757
- iterationCounts,
758
- lastError: `preflight gate exit 2 (escalate) for ${phase.role}`,
759
- };
760
- }
761
- }
762
- // ── 6. Materialization-marker check ───────────────────────────
763
- // FORGE-BUG-040: every BUG phase is now a true `audience: subagent`
764
- // sub-workflow — triage / plan-fix / implement no longer alias to
765
- // fix_bug.md. The marker check is therefore unconditional; a missing
766
- // marker is a hard failure on the first dispatch.
767
- {
768
- const markerCheck = checkMaterialization(subWorkflowPath, subWorkflowMd);
769
- if (!markerCheck.ok) {
770
- for (const marker of markerCheck.missing) {
771
- ctx.ui.notify(`× workflow regression: ${marker} not found in ${subWorkflowPath}`, "error");
772
- }
773
- return {
774
- status: "failed",
775
- lastPhaseIndex: currentPhaseIndex,
776
- iterationCounts,
777
- lastError: `materialization markers missing: ${markerCheck.missing.join(", ")}`,
778
- };
779
- }
780
- }
781
- // ── 5. Audience check ─────────────────────────────────────────
782
- // FORGE-BUG-040: every BUG phase is a true `audience: subagent`
783
- // workflow now; the previous `fix_bug.md` audience-bypass is gone.
784
- const audienceOk = CallerContextStore.asSubagent(phase.role, () => assertAudience({ workflowName: phase.workflowFile, audience: subWorkflowAudience }, ctx));
785
- if (!audienceOk) {
786
- writeBugState(cwd, {
787
- bugId,
788
- phaseIndex: currentPhaseIndex,
789
- iterationCounts,
790
- halted: true,
791
- lastError: `audience check failed for ${phase.workflowFile}`,
792
- savedAt: new Date().toISOString(),
793
- });
794
- return {
795
- status: "failed",
796
- lastPhaseIndex: currentPhaseIndex,
797
- iterationCounts,
798
- lastError: `audience check failed for ${phase.workflowFile}`,
799
- };
800
- }
801
- // ── Persona load ──────────────────────────────────────────────
802
- let persona;
803
- try {
804
- persona = loadForgePersona(phase.personaNoun, cwd);
805
- }
806
- catch (err) {
807
- const e = err;
808
- ctx.ui.notify(`× forge:fix-bug — persona '${phase.personaNoun}' not found for phase ${phase.role}: ${e.message ?? "unknown"}. ` +
809
- "Run /forge:regenerate to materialize persona files.", "error");
810
- writeBugState(cwd, {
811
- bugId,
812
- phaseIndex: currentPhaseIndex,
813
- iterationCounts,
814
- halted: true,
815
- lastError: `persona load failed: ${e.message ?? "unknown"}`,
816
- savedAt: new Date().toISOString(),
817
- });
818
- return {
819
- status: "failed",
820
- lastPhaseIndex: currentPhaseIndex,
821
- iterationCounts,
822
- lastError: `persona load failed: ${e.message ?? "unknown"}`,
823
- };
824
- }
825
- // ── Read bug record for current status ────────────────────────
826
- // Skip for PENDING bugIds (bug doesn't exist yet).
827
- const bugRecordBefore = pendingBugId ? null : readBugRecord(bugId, storeCli, cwd);
828
- const bugStatusBeforePhase = bugRecordBefore?.status;
829
- // ── 4. Dispatch via runForgeSubagent (IL10) ───────────────────
830
- // NEVER sendKickoff here — that would reproduce issue #30.
831
- // Carry forward prior phase summaries (forge-cli#19).
832
- const bugSummariesBlock = currentPhaseIndex > 0 ? buildSummariesBlock(bugRecordBefore?.summaries) || undefined : undefined;
833
- let bugBody = composeBugBody(subWorkflowMd, bugId, phase.role, bugStatusBeforePhase, bugSummariesBlock);
834
- // For new bugs in triage, prepend the original free-form text so the
835
- // subagent knows the user-provided bug description to triage.
836
- // The bug record already exists (pre-created with status "reported"),
837
- // so the subagent should update it, not create a new one.
838
- if (phase.role === "triage" && isNewBug && originalArg) {
839
- bugBody = `Bug description: ${originalArg}\n\n---\n\n${bugBody}`;
840
- }
841
- // Phase-scoped progress counters
842
- const phaseStart = Date.now();
843
- // Track tool_execution_end events for bugId capture (Findings #1, #2).
844
- const toolExecutionEvents = [];
845
- // Stabilization debug log
846
- // Skip for PENDING bugIds — create after real bugId is captured.
847
- // Disable entirely with FORGE_DEBUG_LOG=0.
848
- const debugLogDisabled = process.env.FORGE_DEBUG_LOG === "0";
849
- let debugLogPath = null;
850
- let writeDebug = () => { };
851
- if (!pendingBugId && !debugLogDisabled) {
852
- debugLogPath = path.join(cwd, ".forge", "cache", `fix-bug-debug-${bugId}.jsonl`);
853
- writeDebug = (rec) => {
854
- try {
855
- fs.mkdirSync(path.dirname(debugLogPath), { recursive: true });
856
- // Cap at 10 MB: truncate head when size exceeds the cap.
857
- try {
858
- const st = fs.statSync(debugLogPath);
859
- if (st.size > 10 * 1024 * 1024) {
860
- const all = fs.readFileSync(debugLogPath, "utf8");
861
- const lines = all.split("\n");
862
- // Keep last 80% of lines
863
- const keep = Math.floor(lines.length * 0.8);
864
- fs.writeFileSync(debugLogPath, lines.slice(-keep).join("\n"), "utf8");
865
- }
866
- }
867
- catch {
868
- /* file may not exist yet */
869
- }
870
- fs.appendFileSync(debugLogPath, `${JSON.stringify({ ts: new Date().toISOString(), phase: phase.role, ...rec })}\n`, "utf8");
871
- }
872
- catch {
873
- // non-fatal; debug log is best-effort
874
- }
875
- };
876
- }
877
- writeDebug({ kind: "phase_start", phaseIndex: currentPhaseIndex });
878
- registry.startPhase(bugId, phase.role, currentPhaseIndex);
879
- // Bridge: register phase in OrchestratorTree. Node identity is
880
- // per-dispatch (see dispatchCounts above) — never reuse an ID for
881
- // a re-dispatched role.
882
- const iteration = (dispatchCounts[phase.role] = (dispatchCounts[phase.role] ?? 0) + 1);
883
- const phaseNodeId = `${bugId}:${phase.role}:${iteration}`;
884
- tree.startNode(phaseNodeId, {
885
- parentId: bugId,
886
- label: `${phase.role}:${iteration}`,
887
- kind: "leaf",
888
- // Full body — display clamping/expansion is the view's decision
889
- // (the tree applies only a storage cap).
890
- promptPreview: bugBody,
891
- });
892
- const refreshStatus = () => {
893
- if (process.env.FORGE_VERBOSE !== "1")
894
- return;
895
- const elapsed = Math.floor((Date.now() - phaseStart) / 1000);
896
- const tail = observer.state.lastTool ? ` · ${observer.state.lastTool}` : "";
897
- ctx.ui.setStatus?.(STATUS_KEY, `fix-bug ${bugId}: ${phase.role} · t${observer.state.turn} · tools ${observer.state.toolCount}${observer.state.errCount ? ` · err ${observer.state.errCount}` : ""} · ${elapsed}s${tail}`);
898
- };
899
- const observer = attachViewportObserver({
900
- registry,
901
- sessionId: bugId,
902
- phaseRole: phase.role,
903
- nodeId: phaseNodeId,
904
- beginHeader: `─── phase ${phase.role} begin ───`,
905
- writeDebug,
906
- notify: (msg, level) => ctx.ui.notify(msg, level),
907
- setStatusVerbose: process.env.FORGE_VERBOSE === "1" ? (k, v) => ctx.ui.setStatus?.(k, v) : undefined,
908
- verboseKeys: { messageKey: `${STATUS_KEY}:message` },
909
- afterEach: refreshStatus,
910
- });
911
- // Wrap the observer's onEvent to also capture tool_execution_end events
912
- // for bugId capture downstream (findings #1, #2), plus the first turn_end
913
- // per phase (IL10 visibility — stream-observed model id).
914
- let modelObservedLogged = false;
915
- const onSubagentEvent = (event) => {
916
- if (event?.type === "tool_execution_end") {
917
- toolExecutionEvents.push({ toolName: event.toolName, result: event.result });
918
- }
919
- if (!modelObservedLogged && event?.type === "turn_end" && event.message?.model) {
920
- modelObservedLogged = true;
921
- writeDebug({
922
- kind: "model_observed",
923
- provider: event.message.provider ?? null,
924
- model: event.message.model,
925
- });
926
- }
927
- observer.onEvent(event);
928
- };
929
- // Per-phase model resolution. When config is absent or cascade bottoms
930
- // out, resolves to inherit (model: undefined) — setModel is skipped and
931
- // pi's current model is used. IL10 still holds: result.model below is
932
- // the stream-observed runtime model, not whatever we requested here.
933
- const modelResolution = resolveModelForPhase("fix-bug", phase.role, phase.personaNoun, modelRoutingConfig);
934
- writeDebug({
935
- kind: "requested_model",
936
- requested: modelResolution.model ?? null,
937
- source: modelResolution.source,
938
- persona: phase.personaNoun,
939
- });
940
- let result;
941
- try {
942
- // FORGE-BUG-040: wrap the runForgeSubagent dispatch in the phase
943
- // caller context so downstream tool calls (forge_preflight,
944
- // forge_store update-status / set-bug-summary / set-summary / emit)
945
- // can verify the caller's phase matches the phase named in the
946
- // tool's arguments. This is the single setter of phase context
947
- // for the bug pipeline; the audience-test wrap above is a
948
- // short-lived test, not the canonical dispatch context.
949
- result = await CallerContextStore.asSubagent(phase.role, () => runForgeSubagent({
950
- persona,
951
- task: bugBody,
952
- cwd,
953
- exportTag: `${bugId}__${phase.role}`,
954
- tailLog: observer.state.tailLog,
955
- // Sprint-scoped if the bug is attached to one, else bug-scoped.
956
- // Keeps every phase of this bug-fix pipeline in a single cache
957
- // namespace so the system-prompt + persona prefix stays warm
958
- // across the ~10-minute phases.
959
- cacheSessionId: typeof bugRecordBefore?.sprintId === "string"
960
- ? `forge:${bugRecordBefore.sprintId}`
961
- : `forge:bug:${bugId}`,
962
- onEvent: onSubagentEvent,
963
- requestedModel: modelResolution.model,
964
- modelRegistry: ctx.modelRegistry,
965
- signal: opts.signal,
966
- customTools: opts.forgeToolDefs ? getSubagentTools(opts.forgeToolDefs, persona.name) : undefined,
967
- }));
968
- }
969
- catch (err) {
970
- const e = err;
971
- ctx.ui.notify(`× forge:fix-bug — runForgeSubagent threw for phase ${phase.role}: ${e.message ?? "unknown"}`, "error");
972
- tree.completeNode(phaseNodeId, "failed");
973
- writeBugState(cwd, {
974
- bugId,
975
- phaseIndex: currentPhaseIndex,
976
- iterationCounts,
977
- halted: true,
978
- lastError: `runForgeSubagent threw: ${e.message ?? "unknown"}`,
979
- savedAt: new Date().toISOString(),
980
- });
981
- return {
982
- status: "failed",
983
- lastPhaseIndex: currentPhaseIndex,
984
- iterationCounts,
985
- lastError: `runForgeSubagent threw: ${e.message ?? "unknown"}`,
986
- };
987
- }
988
- // Close this dispatch's tree node with final usage/model — MUST be
989
- // called on every exit path below (failure, halt, escalation,
990
- // loopback, advance). A node left `running` keeps a live spinner
991
- // in the dashboard forever AND absorbs the next same-role
992
- // dispatch's telemetry via the observer's legacy role-prefix scan.
993
- const finishPhaseNode = (status) => {
994
- tree.setNodeUsage(phaseNodeId, {
995
- input: result.usage.input,
996
- output: result.usage.output,
997
- cacheRead: result.usage.cacheRead,
998
- });
999
- if (result.model)
1000
- tree.setNodeModel(phaseNodeId, result.model, result.provider ?? "");
1001
- tree.completeNode(phaseNodeId, status);
1002
- };
1003
- // ── Post-subagent abort detection ─────────────────────────────────
1004
- if (result.stopReason === "aborted" || opts.signal?.aborted) {
1005
- ctx.ui.notify(`⊘ forge:fix-bug — ${bugId} phase ${phase.role} cancelled.`, "info");
1006
- registry.completePhase(bugId, phase.role, "cancelled");
1007
- tree.completeNode(phaseNodeId, "cancelled");
1008
- registry.confirmCancelled(bugId);
1009
- // Bug B parity with run-task: account billed tokens of the aborted attempt.
1010
- // sprintId "bugs" = routing key for bug events (matches success path).
1011
- // The optional `type` token is omitted — verdict carries the outcome.
1012
- emitIncompletePhaseEvent({
1013
- emitCtx: {
1014
- entityType: "bug",
1015
- bugId,
1016
- sprintId: "bugs",
1017
- phase,
1018
- iteration: (iterationCounts[phase.role] ?? 0) + 1,
1019
- startMs: phaseStart,
1020
- endMs: Date.now(),
1021
- model: result.model ?? "unknown",
1022
- provider: result.provider ?? "unknown",
1023
- usage: {
1024
- input: result.usage.input,
1025
- output: result.usage.output,
1026
- cacheRead: result.usage.cacheRead,
1027
- cacheWrite: result.usage.cacheWrite,
1028
- },
1029
- judgement: undefined,
1030
- storeCli,
1031
- cwd,
1032
- },
1033
- outcome: "aborted",
1034
- notes: result.errorMessage ?? result.stopReason ?? undefined,
1035
- onDebug: writeDebug,
1036
- });
1037
- // ADR-S21-01: preserve state file so cancelled runs are resumable
1038
- writeBugState(cwd, {
1039
- bugId,
1040
- phaseIndex: currentPhaseIndex,
1041
- iterationCounts,
1042
- halted: false,
1043
- status: "cancelled",
1044
- lastError: undefined,
1045
- savedAt: new Date().toISOString(),
1046
- });
1047
- return { status: "cancelled", lastPhaseIndex: currentPhaseIndex, iterationCounts };
1048
- }
1049
- // ── Halt-on-failure ───────────────────────────────────────────
1050
- if (result.exitCode !== 0) {
1051
- ctx.ui.notify(`× forge:fix-bug — phase ${phase.role} failed (exit ${result.exitCode})` +
1052
- (result.errorMessage ? `: ${result.errorMessage}` : "") +
1053
- (result.stopReason ? ` [${result.stopReason}]` : ""), "error");
1054
- finishPhaseNode("failed");
1055
- // Bug B parity with run-task: account billed tokens of the failed attempt.
1056
- emitIncompletePhaseEvent({
1057
- emitCtx: {
1058
- entityType: "bug",
1059
- bugId,
1060
- sprintId: "bugs",
1061
- phase,
1062
- iteration: (iterationCounts[phase.role] ?? 0) + 1,
1063
- startMs: phaseStart,
1064
- endMs: Date.now(),
1065
- model: result.model ?? "unknown",
1066
- provider: result.provider ?? "unknown",
1067
- usage: {
1068
- input: result.usage.input,
1069
- output: result.usage.output,
1070
- cacheRead: result.usage.cacheRead,
1071
- cacheWrite: result.usage.cacheWrite,
1072
- },
1073
- judgement: undefined,
1074
- storeCli,
1075
- cwd,
1076
- },
1077
- outcome: "failed",
1078
- notes: result.errorMessage ?? result.stopReason ?? undefined,
1079
- onDebug: writeDebug,
1080
- });
1081
- writeBugState(cwd, {
1082
- bugId,
1083
- phaseIndex: currentPhaseIndex,
1084
- iterationCounts,
1085
- halted: true,
1086
- lastError: result.errorMessage ?? result.stopReason ?? "subagent exit non-zero",
1087
- savedAt: new Date().toISOString(),
1088
- });
1089
- return {
1090
- status: "failed",
1091
- lastPhaseIndex: currentPhaseIndex,
1092
- iterationCounts,
1093
- lastError: result.errorMessage ?? result.stopReason ?? "subagent exit non-zero",
1094
- };
1095
- }
1096
- // Capture model/provider from subagent result.
1097
- if (result.model)
1098
- lastModel = result.model;
1099
- if (result.provider)
1100
- lastProvider = result.provider;
1101
- // ── BugId capture after triage phase (Finding #1, #2) ──────────
1102
- // For new bugs, the triage subagent creates the bug record via store-cli.
1103
- // We capture the bugId by scanning tool_execution_end events.
1104
- if (phase.role === "triage" && isNewBug && bugId.startsWith("PENDING-")) {
1105
- const capturedBugId = extractBugIdFromEvents(toolExecutionEvents, loadGovernorProjectConfig(cwd).prefix);
1106
- if (capturedBugId) {
1107
- ctx.ui.notify(`forge:fix-bug — captured bug ID: ${capturedBugId}`, "info");
1108
- bugId = capturedBugId;
1109
- }
1110
- else {
1111
- // Fallback: list bugs and find the most recent one created after pipeline start.
1112
- const listResult = spawnSync("node", [storeCli, "list", "bug", "--json"], { cwd, encoding: "utf8" });
1113
- if (listResult.status === 0 && listResult.stdout) {
1114
- try {
1115
- const bugs = JSON.parse(listResult.stdout);
1116
- if (Array.isArray(bugs)) {
1117
- // Find most recent bug whose reportedAt is after the pipeline start
1118
- const pipelineStartIso = new Date(parseInt(bugId.replace("PENDING-", ""))).toISOString();
1119
- const recent = bugs
1120
- .filter((b) => b.reportedAt && b.reportedAt >= pipelineStartIso)
1121
- .sort((a, b) => String(b.reportedAt).localeCompare(String(a.reportedAt)))[0];
1122
- if (recent &&
1123
- recent.bugId &&
1124
- typeof recent.bugId === "string" &&
1125
- recent.bugId.startsWith("FORGE-BUG-")) {
1126
- bugId = recent.bugId;
1127
- ctx.ui.notify(`forge:fix-bug — captured bug ID via store fallback: ${bugId}`, "info");
1128
- }
1129
- }
1130
- }
1131
- catch {
1132
- /* parse failure — fall through to assertion */
1133
- }
1134
- }
1135
- }
1136
- // Defensive guard: if bugId is still PENDING after triage, pipeline cannot proceed.
1137
- if (bugId.startsWith("PENDING-")) {
1138
- ctx.ui.notify("× forge:fix-bug — failed to capture real bug ID after triage. Cannot proceed with PENDING placeholder.", "error");
1139
- return {
1140
- status: "failed",
1141
- lastPhaseIndex: currentPhaseIndex,
1142
- iterationCounts,
1143
- lastError: "bugId still PENDING after triage",
1144
- };
1145
- }
1146
- // Re-initialize debug log now that real bugId is available.
1147
- if (!debugLogDisabled) {
1148
- debugLogPath = path.join(cwd, ".forge", "cache", `fix-bug-debug-${bugId}.jsonl`);
1149
- const savedWriteDebug = writeDebug;
1150
- writeDebug = (rec) => {
1151
- try {
1152
- fs.mkdirSync(path.dirname(debugLogPath), { recursive: true });
1153
- try {
1154
- const st = fs.statSync(debugLogPath);
1155
- if (st.size > 10 * 1024 * 1024) {
1156
- const all = fs.readFileSync(debugLogPath, "utf8");
1157
- const lines = all.split("\n");
1158
- const keep = Math.floor(lines.length * 0.8);
1159
- fs.writeFileSync(debugLogPath, lines.slice(-keep).join("\n"), "utf8");
1160
- }
1161
- }
1162
- catch {
1163
- /* file may not exist yet */
1164
- }
1165
- fs.appendFileSync(debugLogPath, `${JSON.stringify({ ts: new Date().toISOString(), phase: phase.role, ...rec })}\n`, "utf8");
1166
- }
1167
- catch {
1168
- // non-fatal
1169
- }
1170
- };
1171
- writeDebug({ kind: "bugid_captured", bugId });
1172
- }
1173
- }
1174
- {
1175
- const elapsed = Math.floor((Date.now() - phaseStart) / 1000);
1176
- const { turn, toolCount, errCount, cumUsage, cumCompression } = observer.state;
1177
- ctx.ui.notify(`✓ ${phase.role}: ${turn} turn${turn === 1 ? "" : "s"} · ${toolCount} tool call${toolCount === 1 ? "" : "s"}${errCount ? ` · ${errCount} err` : ""} · ${elapsed}s`, "info");
1178
- orchTranscript.record({
1179
- kind: "phase-end",
1180
- ts: new Date().toISOString(),
1181
- phase: phase.role,
1182
- phaseIndex: currentPhaseIndex,
1183
- attempt: (iterationCounts[phase.role] ?? 0) + 1,
1184
- verdict: "n/a",
1185
- elapsedMs: Date.now() - phaseStart,
1186
- turns: turn,
1187
- toolCount,
1188
- errCount,
1189
- subagentTranscriptPath: result.subagentTranscriptPath,
1190
- });
1191
- registry.appendTail(bugId, phase.role, fmtPhaseSummary({
1192
- role: phase.role,
1193
- turns: turn,
1194
- tools: toolCount,
1195
- errors: errCount,
1196
- wallSeconds: elapsed,
1197
- usage: cumUsage,
1198
- model: result.model,
1199
- provider: result.provider,
1200
- compression: cumCompression.tokensSaved > 0 ? cumCompression : undefined,
1201
- }));
1202
- }
1203
- // ── Slice-2: orchestrator emits phase event ──────────────────
1204
- // sprintId for bug event emission is the literal "bugs" (routing key),
1205
- // matching the convention in .forge/workflows/fix_bug.md.
1206
- const phaseEndMs = Date.now();
1207
- const bugRecord = readBugRecord(bugId, storeCli, cwd);
1208
- const sprintId = "bugs"; // routing key for bug events — not a sprint reference
1209
- const phaseIteration = (iterationCounts[phase.role] ?? 0) + 1;
1210
- // Read summary judgement for review phases (using bug summary key map)
1211
- const judgement = phase.isReview
1212
- ? judgementFromSummary(bugRecord ?? null, phase.role, BUG_SUMMARY_KEY_BY_ROLE)
1213
- : undefined;
1214
- const emitCtx = {
1215
- entityType: "bug",
1216
- bugId,
1217
- sprintId, // routing key "bugs" — not a sprint reference
1218
- phase,
1219
- iteration: phaseIteration,
1220
- startMs: phaseStart,
1221
- endMs: phaseEndMs,
1222
- model: result.model ?? "unknown",
1223
- provider: result.provider ?? "unknown",
1224
- usage: {
1225
- input: result.usage.input,
1226
- output: result.usage.output,
1227
- cacheRead: result.usage.cacheRead,
1228
- cacheWrite: result.usage.cacheWrite,
1229
- },
1230
- judgement,
1231
- storeCli,
1232
- cwd,
1233
- };
1234
- const phaseEvent = buildPhaseEvent(emitCtx);
1235
- // Set bug event type based on BUG_TYPE_TOKENS mapping.
1236
- const typeTokenEntry = BUG_TYPE_TOKENS[phase.role];
1237
- if (typeTokenEntry) {
1238
- if (phase.isReview && judgement?.verdict === "revision") {
1239
- phaseEvent.type = typeTokenEntry.fail;
1240
- }
1241
- else {
1242
- phaseEvent.type = typeTokenEntry.pass;
1243
- }
1244
- }
1245
- const emitResult = emitEvent(storeCli, cwd, sprintId, phaseEvent);
1246
- if (!emitResult.ok) {
1247
- ctx.ui.notify(`⚠ forge:fix-bug — phase event emit failed for ${phase.role}: ${emitResult.stderr.trim()}`, "warning");
1248
- writeDebug({ kind: "emit_failed", stderr: emitResult.stderr });
1249
- }
1250
- else {
1251
- writeDebug({ kind: "emit_ok", eventId: phaseEvent.eventId });
1252
- }
1253
- // Drain friction file for this phase.
1254
- const frictionPath = path.join(cwd, ".forge", "cache", `FRICTION-${phase.role}.jsonl`);
1255
- const drain = drainFrictionFile(frictionPath, emitCtx);
1256
- if (drain.emitted + drain.failed > 0) {
1257
- writeDebug({ kind: "friction_drain", ...drain });
1258
- if (drain.failed > 0) {
1259
- ctx.ui.notify(`⚠ forge:fix-bug — friction drain for ${phase.role}: ${drain.emitted} ok, ${drain.failed} failed`, "warning");
1260
- }
1261
- }
1262
- // ── AC §C.16: Bug FSM canonical-enum assertion ────────────────
1263
- // After each phase that could transition bug status, validate the new
1264
- // status via store-cli (single source of truth). Surface a warning (not halt) if invalid.
1265
- const currentBugRecordForAssert = readBugRecord(bugId, storeCli, cwd);
1266
- if (currentBugRecordForAssert && currentBugRecordForAssert.status) {
1267
- // Defer to store-cli's isLegalTransition as authoritative guard.
1268
- // Only warn on statuses store-cli itself would reject.
1269
- const validateResult = spawnSync("node", [storeCli, "validate", "bug", JSON.stringify(currentBugRecordForAssert)], { cwd, encoding: "utf8" });
1270
- if (validateResult.status !== 0) {
1271
- const detail = typeof validateResult.stderr === "string" ? validateResult.stderr.trim() : "unknown";
1272
- ctx.ui.notify(`⚠ forge:fix-bug — bug ${bugId} validation warning: ${detail}`, "warning");
1273
- writeDebug({ kind: "fsm_assertion_warning", bugId, status: currentBugRecordForAssert.status, detail });
1274
- }
1275
- }
1276
- // ── 6b. Verdict check (review phases only) ────────────────────
1277
- if (phase.isReview) {
1278
- // Re-read bug record for latest status after subagent ran
1279
- const updatedBugRecord = readBugRecord(bugId, storeCli, cwd);
1280
- const verdict = readBugVerdict(updatedBugRecord, phase.role, BUG_SUMMARY_KEY_BY_ROLE);
1281
- if (verdict === "missing") {
1282
- ctx.ui.notify(`× forge:fix-bug — verdict missing for phase ${phase.role} after subagent completed. Halting for advisory.`, "error");
1283
- finishPhaseNode("failed");
1284
- writeBugState(cwd, {
1285
- bugId,
1286
- phaseIndex: currentPhaseIndex,
1287
- iterationCounts,
1288
- halted: true,
1289
- lastError: `verdict missing for ${phase.role}`,
1290
- savedAt: new Date().toISOString(),
1291
- });
1292
- // A missing verdict IS a postflight-outputs failure: the canonical
1293
- // phase summary the subagent must write (e.g. summaries.code_review,
1294
- // linked via set-bug-summary) was never recorded, so there is no
1295
- // verdict to route on. Route it through the halt-recovery advisor
1296
- // (FORGE-S26-T18) — the same hand-off the preflight/postflight gate
1297
- // failures use — instead of a bare escalation. Best-effort, non-fatal.
1298
- const advisorModel = resolveAdvisorModel(modelRoutingConfig, ctx.model);
1299
- void runHaltAdvisor({
1300
- gateFailure: {
1301
- phase: phase.role,
1302
- reasonCode: "verdict-missing",
1303
- detail: `Phase '${phase.role}' completed but no verdict was found in the store. ` +
1304
- "The canonical phase summary was not written, so the orchestrator has no verdict to route on.",
1305
- remediation: "Re-run the phase and ensure the subagent's forge_store set-bug-summary call " +
1306
- 'uses args:["<bugId>", "<phaseKey>"] with the literal phase key as args[1] ' +
1307
- "(e.g. code_review), and that the call exits zero before the subagent returns.",
1308
- },
1309
- advisorModel,
1310
- taskId: bugId,
1311
- cwd,
1312
- ctx: { ui: ctx.ui },
1313
- forgeRoot,
1314
- });
1315
- return {
1316
- status: "halted",
1317
- lastPhaseIndex: currentPhaseIndex,
1318
- iterationCounts,
1319
- lastError: `verdict missing for ${phase.role}`,
1320
- };
1321
- }
1322
- if (verdict === "revision") {
1323
- iterationCounts[phase.role] = (iterationCounts[phase.role] ?? 0) + 1;
1324
- if (iterationCounts[phase.role] >= phase.maxIterations) {
1325
- ctx.ui.notify(`× forge:fix-bug — revision cap reached for phase ${phase.role} ` +
1326
- `(${iterationCounts[phase.role]}/${phase.maxIterations} iterations). Escalating.`, "error");
1327
- finishPhaseNode("escalated");
1328
- writeBugState(cwd, {
1329
- bugId,
1330
- phaseIndex: currentPhaseIndex,
1331
- iterationCounts,
1332
- halted: true,
1333
- lastError: `revision cap reached for ${phase.role}`,
1334
- savedAt: new Date().toISOString(),
1335
- });
1336
- return {
1337
- status: "escalated",
1338
- lastPhaseIndex: currentPhaseIndex,
1339
- iterationCounts,
1340
- lastError: `revision cap reached for ${phase.role}`,
1341
- };
1342
- }
1343
- // Transition bug back to in-progress before re-dispatching implement.
1344
- // This is required for review-code → implement and approve → implement loops.
1345
- const currentBugStatus = updatedBugRecord?.status;
1346
- if (currentBugStatus === "fixed" || currentBugStatus === "approved") {
1347
- const transitionResult = spawnSync("node", [storeCli, "update-status", "bug", bugId, "status", "in-progress"], { cwd, encoding: "utf8" });
1348
- if (transitionResult.status !== 0) {
1349
- ctx.ui.notify(`⚠ forge:fix-bug — failed to transition bug ${bugId} from ${currentBugStatus} to in-progress: ${transitionResult.stderr ?? "unknown"}`, "warning");
1350
- }
1351
- else {
1352
- ctx.ui.notify(`⟳ forge:fix-bug — transitioned bug ${bugId}: ${currentBugStatus} → in-progress`, "info");
1353
- }
1354
- }
1355
- // The review subagent itself finished cleanly — `revision`
1356
- // is its verdict, not a failure — so its node closes as
1357
- // completed before the predecessor re-dispatches under a
1358
- // fresh node ID.
1359
- finishPhaseNode("completed");
1360
- const predIndex = findPredecessorIndex(BUG_PHASES, currentPhaseIndex);
1361
- ctx.ui.notify(`⟳ forge:fix-bug — ${phase.role} returned revision; looping to ${BUG_PHASES[predIndex]?.role ?? predIndex} ` +
1362
- `(attempt ${iterationCounts[phase.role]}/${phase.maxIterations})`, "info");
1363
- orchTranscript.record({
1364
- kind: "phase-loopback",
1365
- ts: new Date().toISOString(),
1366
- fromPhase: phase.role,
1367
- toPhase: BUG_PHASES[predIndex]?.role ?? String(predIndex),
1368
- fromPhaseIndex: currentPhaseIndex,
1369
- toPhaseIndex: predIndex,
1370
- reason: `${phase.role} returned revision (attempt ${iterationCounts[phase.role]}/${phase.maxIterations})`,
1371
- });
1372
- writeBugState(cwd, {
1373
- bugId,
1374
- phaseIndex: predIndex,
1375
- iterationCounts,
1376
- halted: false,
1377
- savedAt: new Date().toISOString(),
1378
- });
1379
- currentPhaseIndex = predIndex;
1380
- continue;
1381
- }
1382
- // verdict === "approved": fall through to advance
1383
- }
1384
- // ── Advance to next phase ─────────────────────────────────────
1385
- registry.completePhase(bugId, phase.role, "completed");
1386
- finishPhaseNode("completed");
1387
- writeBugState(cwd, {
1388
- bugId,
1389
- phaseIndex: currentPhaseIndex,
1390
- iterationCounts,
1391
- halted: false,
1392
- savedAt: new Date().toISOString(),
1393
- });
1394
- // ── 6c. Path A / Path B branch (post-triage) ──────────────────
1395
- // Per meta-fix-bug.md § Triage Judgement (forge v0.44.0+), the
1396
- // triage subagent records the route decision in
1397
- // bug.summaries.triage.route. The orchestrator reads it after
1398
- // triage returns and selects the downstream phase list:
1399
- // Path A (short-circuit): skip plan-fix + review-plan
1400
- // Path B (default, full loop): run all phases
1401
- //
1402
- // If route is missing or malformed, default to Path B (the safe
1403
- // choice — running extra phases never produces an unsafe outcome).
1404
- // The PHASE_SKIP_STATES heuristic at section 6a remains as
1405
- // defense-in-depth for cases where the field is missing but the
1406
- // bug status proves the work happened.
1407
- if (phase.role === "triage") {
1408
- const bugAfterTriage = readBugRecord(bugId, storeCli, cwd);
1409
- // Orchestrator-owned post-triage transitions (meta-fix-bug.md
1410
- // step 2). Two sequential writes through store-cli (the FSM
1411
- // authority); failure warns but does not halt — the commit
1412
- // phase's status guard is the backstop.
1413
- for (const target of postTriageTransitions(bugAfterTriage?.status)) {
1414
- const upd = spawnSync("node", [storeCli, "update-status", "bug", bugId, "status", target], { cwd, encoding: "utf8" });
1415
- if (upd.status !== 0) {
1416
- ctx.ui.notify(`⚠ forge:fix-bug — post-triage transition to '${target}' failed: ${(upd.stderr ?? "").toString().trim()}`, "warning");
1417
- break;
1418
- }
1419
- }
1420
- const triageSummary = bugAfterTriage?.summaries?.triage;
1421
- const route = triageSummary?.route;
1422
- if (route === "A") {
1423
- const skipUntilIndex = BUG_PHASES.findIndex((p) => p.role === "implement");
1424
- if (skipUntilIndex > currentPhaseIndex + 1) {
1425
- ctx.ui.notify(`⊘ forge:fix-bug — Path A selected by triage; skipping plan-fix and review-plan.`, "info");
1426
- currentPhaseIndex = skipUntilIndex;
1427
- continue;
1428
- }
1429
- }
1430
- // route === "B", missing, or any other value → fall through to standard advance
1431
- }
1432
- currentPhaseIndex++;
1433
- }
1434
- // ── All phases complete ───────────────────────────────────────────
1435
- deleteBugState(cwd, bugId);
1436
- orchTranscript.record({
1437
- kind: "pipeline-end",
1438
- ts: new Date().toISOString(),
1439
- outcome: "complete",
1440
- elapsedMs: Date.now() - pipelineStartMs,
1441
- });
1442
- return {
1443
- status: "completed",
1444
- lastPhaseIndex: BUG_PHASES.length - 1,
1445
- iterationCounts,
1446
- model: lastModel,
1447
- provider: lastProvider,
1448
- };
1449
- }
1450
- finally {
1451
- ctx.ui.notify = __origNotify;
1452
- }
1453
- }
1454
- export function registerFixBug(pi, options = {}) {
1455
- pi.registerCommand("forge:fix-bug", {
1456
- description: "Run the full bug-fix pipeline (triage → plan-fix → review-plan → implement → review-code → approve → commit). " +
1457
- "Usage: /forge:fix-bug <BUG_ID_OR_SUMMARY>. " +
1458
- "Orchestrator archetype: each phase is an isolated subagent session (IL10).",
1459
- async handler(args, ctx) {
1460
- const cwd = options.cwd ?? process.cwd();
1461
- const rawArg = args.trim();
1462
- if (!rawArg) {
1463
- ctx.ui.notify("× forge:fix-bug — bug ID or summary required. Usage: /forge:fix-bug <BUG_ID_OR_SUMMARY>", "error");
1464
- return;
1465
- }
1466
- ctx.ui.setStatus?.(STATUS_KEY, `fix-bug: initializing…`);
1467
- // ── Discover forge config ────────────────────────────────────────
1468
- const forgeConfig = discoverForgeConfigCached(cwd);
1469
- if (!forgeConfig) {
1470
- ctx.ui.notify("× forge:fix-bug — no Forge project found at cwd. Run /forge:init first.", "error");
1471
- ctx.ui.setStatus?.(STATUS_KEY, undefined);
1472
- ctx.ui.setStatus?.(MESSAGE_KEY, undefined);
1473
- return;
1474
- }
1475
- const forgeRoot = forgeConfig.forgeRoot;
1476
- // Best-effort transcript-archive sweep: adopt any project-local runs
1477
- // not yet in the central index (crash recovery + pre-existing
1478
- // history). Runs BEFORE this pipeline creates its own transcript
1479
- // writer, so the in-flight run is never swept half-written.
1480
- sweepProjectTranscripts(cwd);
1481
- // Tool paths
1482
- const storeCli = path.join(forgeRoot, "tools", "store-cli.cjs");
1483
- const preflightGate = path.join(forgeRoot, "tools", "preflight-gate.cjs");
1484
- // ── Determine bugId ────────────────────────────────────────────
1485
- let bugId;
1486
- let isNewBug = false;
1487
- // Check if arg looks like it could be a bug ID (prefixed or unprefixed).
1488
- // Covers: FORGE-BUG-042, BUG-042, B042.
1489
- const looksLikeBugId = /^(?:[A-Z0-9]+-)?(?:BUG-?\d+|B\d+)$/i.test(rawArg) || /^BUG-\d+$/i.test(rawArg);
1490
- if (/^[A-Z][A-Z0-9]*-BUG-\d+$/.test(rawArg)) {
1491
- // Canonical prefixed bug ID (any project prefix, e.g. FORGE-BUG-042,
1492
- // CART-BUG-001) — verify it exists. Previously hardcoded to FORGE-,
1493
- // which pushed other-prefix canonical IDs through the resolver.
1494
- bugId = rawArg;
1495
- const bugRecord = readBugRecord(bugId, storeCli, cwd);
1496
- if (!bugRecord) {
1497
- ctx.ui.notify(`× forge:fix-bug — bug ${bugId} not found in store.`, "error");
1498
- ctx.ui.setStatus?.(STATUS_KEY, undefined);
1499
- return;
1500
- }
1501
- // Check if bug is already in a terminal state
1502
- if (BUG_TERMINAL_STATES.has(bugRecord.status ?? "")) {
1503
- ctx.ui.notify(`× forge:fix-bug — bug ${bugId} is already in terminal state '${bugRecord.status}'. No further processing.`, "error");
1504
- ctx.ui.setStatus?.(STATUS_KEY, undefined);
1505
- return;
1506
- }
1507
- }
1508
- else if (looksLikeBugId) {
1509
- // Unprefixed bug ID — resolve through the store cascade.
1510
- // Issue #20: unprefixed entity IDs silently poisoned substitutions.
1511
- const toolDir = resolveToolDir(forgeRoot);
1512
- const resolvedBugId = await resolveToCanonicalId(rawArg, toolDir, cwd, "bug", {
1513
- ctx,
1514
- commandLabel: "forge:fix-bug",
1515
- });
1516
- if (!resolvedBugId) {
1517
- // Error already emitted by resolver
1518
- ctx.ui.setStatus?.(STATUS_KEY, undefined);
1519
- ctx.ui.setStatus?.(MESSAGE_KEY, undefined);
1520
- return;
1521
- }
1522
- bugId = resolvedBugId;
1523
- // Re-verify the resolved bug exists
1524
- const bugRecord = readBugRecord(bugId, storeCli, cwd);
1525
- if (!bugRecord) {
1526
- ctx.ui.notify(`× forge:fix-bug — bug ${bugId} not found in store.`, "error");
1527
- ctx.ui.setStatus?.(STATUS_KEY, undefined);
1528
- return;
1529
- }
1530
- if (BUG_TERMINAL_STATES.has(bugRecord.status ?? "")) {
1531
- ctx.ui.notify(`× forge:fix-bug — bug ${bugId} is already in terminal state '${bugRecord.status}'. No further processing.`, "error");
1532
- ctx.ui.setStatus?.(STATUS_KEY, undefined);
1533
- return;
1534
- }
1535
- }
1536
- else {
1537
- // @file or free-form text. If an @file report references a canonical
1538
- // <PREFIX>-BUG-NNN that already exists in the store, fix THAT record —
1539
- // minting a new bug here duplicated CART-BUG-001 as a phantom in the
1540
- // CART incident (the report header carried the real ID all along).
1541
- let reportBugId = null;
1542
- if (rawArg.startsWith("@")) {
1543
- const rel = rawArg.slice(1).trim();
1544
- const reportPath = path.isAbsolute(rel) ? rel : path.join(cwd, rel);
1545
- try {
1546
- // 256 KB cap — bug reports are small; never slurp arbitrary files.
1547
- if (fs.existsSync(reportPath) && fs.statSync(reportPath).size <= 262144) {
1548
- const projectPrefix = loadGovernorProjectConfig(cwd).prefix;
1549
- reportBugId = extractBugIdFromReportText(fs.readFileSync(reportPath, "utf8"), projectPrefix);
1550
- }
1551
- }
1552
- catch {
1553
- /* unreadable report — fall through to new-bug intake */
1554
- }
1555
- }
1556
- const reportRecord = reportBugId ? readBugRecord(reportBugId, storeCli, cwd) : null;
1557
- if (reportBugId && reportRecord) {
1558
- if (BUG_TERMINAL_STATES.has(reportRecord.status ?? "")) {
1559
- ctx.ui.notify(`× forge:fix-bug — ${rawArg} references ${reportBugId}, which is already in terminal state ` +
1560
- `'${reportRecord.status}'. No further processing.`, "error");
1561
- ctx.ui.setStatus?.(STATUS_KEY, undefined);
1562
- return;
1563
- }
1564
- bugId = reportBugId;
1565
- ctx.ui.notify(`forge:fix-bug — ${rawArg} references existing bug ${bugId}; fixing it (no new record minted).`, "info");
1566
- }
1567
- else {
1568
- // Free-form text (or report with no resolvable ID) — defer bug
1569
- // creation to the triage-phase subagent via a PENDING placeholder.
1570
- bugId = `PENDING-${Date.now()}`;
1571
- isNewBug = true;
1572
- }
1573
- }
1574
- // ── Pre-flight confirm ───────────────────────────────────────────
1575
- if (!isNonInteractive()) {
1576
- const confirmMsg = isNewBug
1577
- ? `Fix bug: "${rawArg.slice(0, 80)}"? A bug record will be created during triage.`
1578
- : `Fix bug ${bugId}?`;
1579
- const proceed = await ctx.ui.confirm(`Fix bug?`, confirmMsg);
1580
- if (!proceed) {
1581
- ctx.ui.notify("forge:fix-bug — cancelled.", "info");
1582
- ctx.ui.setStatus?.(STATUS_KEY, undefined);
1583
- return;
1584
- }
1585
- }
1586
- // ── Resume detection ─────────────────────────────────────────────
1587
- const registry = getSessionRegistry();
1588
- const existing = isNewBug ? null : readBugState(cwd, bugId);
1589
- let resumeFromState;
1590
- if (existing) {
1591
- if (isBugStateStale(existing)) {
1592
- ctx.ui.notify(`⚠ forge:fix-bug — cached state for ${bugId} is stale (>7 days old, saved at ${formatLocalTime(existing.savedAt)}). Offering purge.`, "warning");
1593
- if (!isNonInteractive()) {
1594
- const purge = await ctx.ui.confirm(`Purge stale state for ${bugId}?`, "The cached state is older than 7 days. Purge and restart from the beginning?");
1595
- if (purge) {
1596
- deleteBugState(cwd, bugId);
1597
- }
1598
- else {
1599
- ctx.ui.notify("forge:fix-bug — stale state kept; aborting.", "info");
1600
- ctx.ui.setStatus?.(STATUS_KEY, undefined);
1601
- ctx.ui.setStatus?.(MESSAGE_KEY, undefined);
1602
- return;
1603
- }
1604
- }
1605
- else {
1606
- ctx.ui.notify("forge:fix-bug — stale state; non-interactive mode auto-aborting.", "info");
1607
- ctx.ui.setStatus?.(STATUS_KEY, undefined);
1608
- ctx.ui.setStatus?.(MESSAGE_KEY, undefined);
1609
- return;
1610
- }
1611
- }
1612
- else {
1613
- // ADR-S21-01: offer resume for ALL non-stale states — halted=true
1614
- // (explicit failure), halted=false (cancelled/interrupted), and
1615
- // any state with existing.status set.
1616
- const stateStatus = existing.status ?? (existing.halted ? "halted" : "interrupted");
1617
- const statusLabel = stateStatus === "cancelled" ? "cancelled" : stateStatus === "halted" ? "halted" : "interrupted";
1618
- const phaseRole = BUG_PHASES[existing.phaseIndex]?.role ?? existing.phaseIndex;
1619
- if (!isNonInteractive()) {
1620
- const resume = await ctx.ui.confirm(`Resume ${bugId}?`, `Cached state — phase ${existing.phaseIndex} (${phaseRole}), ${statusLabel}, ` +
1621
- `saved at ${formatLocalTime(existing.savedAt)}. Resume from here?`);
1622
- if (resume) {
1623
- resumeFromState = existing;
1624
- ctx.ui.notify(`forge:fix-bug — resuming ${bugId} from phase ${phaseRole} (${statusLabel})`, "info");
1625
- }
1626
- else {
1627
- deleteBugState(cwd, bugId);
1628
- }
1629
- }
1630
- else {
1631
- // Non-interactive: auto-resume from state (no confirmation).
1632
- // Cancelled/interrupted states are valid resume points.
1633
- resumeFromState = existing;
1634
- ctx.ui.notify(`forge:fix-bug — resuming ${bugId} from phase ${phaseRole} (${statusLabel})`, "info");
1635
- }
1636
- }
1637
- }
1638
- // For new bugs, triage phase will create the bug record.
1639
- // After triage, we need to capture the bugId from the subagent events.
1640
- // This is handled inside runBugPipeline via onEvent interception.
1641
- // For now, we pass the temporary bugId; runBugPipeline will update it.
1642
- // ── Materialization check (top-level workflow) ──────────────────
1643
- const workflowPath = path.join(cwd, ".forge", "workflows", "fix_bug.md");
1644
- if (fs.existsSync(workflowPath)) {
1645
- try {
1646
- const loaded = loadWorkflow(workflowPath);
1647
- // AC#12: Top-level audience check for the fix_bug.md workflow.
1648
- // The orchestrator ITSELF runs fix_bug.md (not a subagent), so check
1649
- // from orchestrator context. Using asSubagent would falsely reject
1650
- // orchestrator-only workflows called by the orchestrator.
1651
- const topAudienceOk = CallerContextStore.asOrchestrator(() => assertAudience({ workflowName: "fix_bug", audience: loaded.audience }, ctx));
1652
- if (!topAudienceOk) {
1653
- ctx.ui.notify("× forge:fix-bug — audience check failed for top-level fix_bug workflow.", "error");
1654
- ctx.ui.setStatus?.(STATUS_KEY, undefined);
1655
- ctx.ui.setStatus?.(MESSAGE_KEY, undefined);
1656
- return;
1657
- }
1658
- // Note: no materialization-marker check here. fix_bug.md is the
1659
- // orchestrator workflow (prose algorithm), not a sub-workflow that
1660
- // subagents run directly. Per-phase sub-workflows (architect_approve,
1661
- // review_code, etc.) each get their own materialization check inside
1662
- // runBugPipeline at line ~481, which is the correct guard layer.
1663
- }
1664
- catch {
1665
- // Workflow file exists but couldn't be read — non-fatal, continue
1666
- }
1667
- }
1668
- // ── Pre-assign real bug ID for new bugs ────────────────────────
1669
- // Previously this was done inside runBugPipeline, but the session registry
1670
- // needs the real ID before startSession is called.
1671
- if (isNewBug && bugId.startsWith("PENDING-")) {
1672
- const realBugId = assignNextBugId(storeCli, cwd, loadGovernorProjectConfig(cwd).prefix);
1673
- const title = rawArg && !rawArg.startsWith("@") ? rawArg.slice(0, 120) : "New bug (pending triage)";
1674
- if (preCreateBug(realBugId, title, storeCli, cwd)) {
1675
- ctx.ui.notify(`forge:fix-bug — pre-assigned bug ID: ${realBugId}`, "info");
1676
- bugId = realBugId;
1677
- }
1678
- else {
1679
- ctx.ui.notify("× forge:fix-bug — failed to pre-create bug record. Falling back to PENDING capture.", "error");
1680
- }
1681
- }
1682
- // Register session
1683
- registry.startSession(bugId);
1684
- // Bridge: register bug in OrchestratorTree.
1685
- const tree = getOrchestratorTree();
1686
- tree.startNode(bugId, { label: `fix-bug ${bugId}`, kind: "orchestrator" });
1687
- // ── Delegate to pipeline ─────────────────────────────────────────
1688
- // ── Delegate to pipeline ─────────────────────────────────────────
1689
- const signal = registry.getAbortSignal(bugId);
1690
- const pipelineResult = await runBugPipeline({
1691
- bugId,
1692
- originalArg: isNewBug ? rawArg : undefined,
1693
- isNewBug,
1694
- cwd,
1695
- ctx,
1696
- forgeRoot,
1697
- storeCli,
1698
- preflightGate,
1699
- registry,
1700
- resumeFromState,
1701
- signal,
1702
- forgeToolDefs: options.forgeToolDefs,
1703
- });
1704
- // ── Handle result ────────────────────────────────────────────────
1705
- if (pipelineResult.status === "completed") {
1706
- registry.completeSession(bugId, "completed");
1707
- tree.completeNode(bugId, "completed");
1708
- ctx.ui.notify(`〇 forge:fix-bug — ${bugId} pipeline complete (${BUG_PHASES.length} phases).`, "info");
1709
- }
1710
- else if (pipelineResult.status === "cancelled") {
1711
- registry.completeSession(bugId, "cancelled");
1712
- tree.completeNode(bugId, "cancelled");
1713
- }
1714
- else {
1715
- registry.completeSession(bugId, "failed");
1716
- tree.completeNode(bugId, "failed");
1717
- }
1718
- // Mirror this run into the central transcript archive (best-effort —
1719
- // archiveRun never throws).
1720
- if (pipelineResult.orchestratorTranscriptPath) {
1721
- archiveRun({
1722
- cwd,
1723
- orchestratorJsonlPath: pipelineResult.orchestratorTranscriptPath,
1724
- });
1725
- }
1726
- ctx.ui.setStatus?.(STATUS_KEY, undefined);
1727
- ctx.ui.setStatus?.(MESSAGE_KEY, undefined);
1728
- },
1729
- });
1730
- }
22
+ // ── State persistence ─────────────────────────────────────────────────────
23
+ export { deleteBugState, isBugStateStale, readBugState, writeBugState, } from "./bug/bug-state.js";
24
+ // ── Bug record + ID minting / capture helpers ──────────────────────────────
25
+ export { assignNextBugId, computeNextBugId, extractBugIdFromEvents, extractBugIdFromReportText, preCreateBug, readBugRecord, } from "./bug/bug-id.js";
26
+ // ── Verdict read ───────────────────────────────────────────────────────────
27
+ export { readBugVerdict } from "./bug/bug-verdict.js";
28
+ // ── Bug body composition ───────────────────────────────────────────────────
29
+ export { composeBugBody } from "./bug/bug-body.js";
30
+ // ── Pipeline + command handler ─────────────────────────────────────────────
31
+ export { runBugPipeline } from "./bug/run-bug-pipeline.js";
32
+ export { registerFixBug } from "./bug/run-bug-command.js";
1731
33
  //# sourceMappingURL=fix-bug.js.map