karajan-code 4.70.4 → 5.0.1

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 (390) hide show
  1. package/README.md +2 -2
  2. package/package.json +6 -14
  3. package/packages/hu-board/package.json +1 -1
  4. package/packages/hu-board/public/app.js +4 -18
  5. package/packages/hu-board/public/index.html +5 -2
  6. package/packages/hu-board/public/styles.css +26 -0
  7. package/packages/hu-board/public/utils/api.js +0 -74
  8. package/packages/hu-board/public/utils/board-tour.js +62 -0
  9. package/packages/hu-board/public/utils/board-view.js +49 -144
  10. package/packages/hu-board/public/utils/command-launcher.js +8 -70
  11. package/packages/hu-board/public/utils/formatters.js +10 -0
  12. package/packages/hu-board/public/utils/governance-view.js +13 -1
  13. package/packages/hu-board/public/utils/hu-actions.js +17 -68
  14. package/packages/hu-board/public/utils/init-listeners.js +7 -8
  15. package/packages/hu-board/public/utils/log-panel.js +3 -16
  16. package/packages/hu-board/public/utils/maggle-mode.js +2 -12
  17. package/packages/hu-board/public/utils/modals.js +7 -0
  18. package/packages/hu-board/public/utils/project-picker-view.js +1 -1
  19. package/packages/hu-board/public/utils/server-push.js +3 -22
  20. package/packages/hu-board/public/utils/shared-board-notice.js +33 -0
  21. package/packages/hu-board/public/utils/story-detail-view.js +5 -19
  22. package/packages/hu-board/src/cleanup-zombies.js +0 -1
  23. package/packages/hu-board/src/command-runner.js +36 -25
  24. package/packages/hu-board/src/db-connection.js +282 -0
  25. package/packages/hu-board/src/db-paths.js +113 -0
  26. package/packages/hu-board/src/db-queries.js +188 -0
  27. package/packages/hu-board/src/db-tombstones.js +51 -0
  28. package/packages/hu-board/src/db.js +28 -619
  29. package/packages/hu-board/src/ephemeral-cleaner.js +2 -3
  30. package/packages/hu-board/src/hu-zombie-reaper.js +5 -1
  31. package/packages/hu-board/src/plan-mutations.js +3 -153
  32. package/packages/hu-board/src/plans-service.js +55 -0
  33. package/packages/hu-board/src/routes/api/commands.js +122 -0
  34. package/packages/hu-board/src/routes/api/plans.js +55 -0
  35. package/packages/hu-board/src/routes/api/projects.js +267 -0
  36. package/packages/hu-board/src/routes/api/prompts.js +135 -0
  37. package/packages/hu-board/src/routes/api/sessions.js +95 -0
  38. package/packages/hu-board/src/routes/api/stories.js +183 -0
  39. package/packages/hu-board/src/routes/api/system.js +241 -0
  40. package/packages/hu-board/src/routes/api/tombstones.js +38 -0
  41. package/packages/hu-board/src/routes/api.js +24 -1371
  42. package/packages/hu-board/src/routes/governance.js +23 -1
  43. package/packages/hu-board/src/routes/lanes.js +40 -0
  44. package/packages/hu-board/src/routes/rag.js +4 -3
  45. package/packages/hu-board/src/server.js +20 -34
  46. package/packages/hu-board/src/sync-files.js +318 -0
  47. package/packages/hu-board/src/sync-project.js +99 -0
  48. package/packages/hu-board/src/sync.js +22 -566
  49. package/packages/hu-board/src/zombie-reaper.js +1 -1
  50. package/scripts/dual-publish.mjs +19 -3
  51. package/scripts/postinstall.js +15 -2
  52. package/scripts/verify-pack-mcp.mjs +11 -7
  53. package/scripts/verify-pack.mjs +4 -4
  54. package/src/audit/audit-history.js +3 -2
  55. package/src/audit/dead-exports.js +68 -59
  56. package/src/audit/deterministic-summary.js +33 -13
  57. package/src/audit/npm-signatures.js +41 -0
  58. package/src/audit/sbom.js +35 -0
  59. package/src/checks/agent-token.js +112 -0
  60. package/src/checks/dir-setup.js +34 -1
  61. package/src/checks/native-build.js +21 -39
  62. package/src/checks/runner.js +128 -142
  63. package/src/cli/advanced-commands.js +6 -6
  64. package/src/cli/register-meta.js +42 -82
  65. package/src/cli/register-pipeline.js +8 -143
  66. package/src/cli/register-plan.js +9 -100
  67. package/src/cli/register-roles-skills.js +1 -1
  68. package/src/cli/retired-commands.js +49 -0
  69. package/src/cli.js +18 -5
  70. package/src/commands/agent-run.js +40 -26
  71. package/src/commands/audit.js +127 -124
  72. package/src/commands/board.js +5 -9
  73. package/src/commands/check.js +29 -16
  74. package/src/commands/doctor.js +2 -0
  75. package/src/commands/env.js +176 -150
  76. package/src/commands/go.js +71 -53
  77. package/src/commands/harden.js +90 -81
  78. package/src/commands/hu-start.js +52 -0
  79. package/src/commands/hu.js +288 -221
  80. package/src/commands/identity.js +111 -78
  81. package/src/commands/init-atril.js +47 -0
  82. package/src/commands/init.js +11 -40
  83. package/src/commands/install-tools.js +70 -85
  84. package/src/commands/main-guard.js +78 -0
  85. package/src/commands/policy.js +226 -184
  86. package/src/commands/qmd.js +27 -21
  87. package/src/commands/rag.js +121 -55
  88. package/src/commands/review-gate.js +421 -341
  89. package/src/commands/skills.js +4 -1
  90. package/src/commands/start.js +4 -5
  91. package/src/commands/steward.js +96 -47
  92. package/src/commands/tournament.js +46 -37
  93. package/src/commands/worktree.js +107 -26
  94. package/src/config/defaults.js +1 -0
  95. package/src/config/loader.js +34 -17
  96. package/src/config/role-resolver.js +1 -0
  97. package/src/config/schema.js +1 -0
  98. package/src/config/secrets.js +36 -0
  99. package/src/config.js +1 -0
  100. package/src/environment/adr.js +16 -2
  101. package/src/git/karajan-label.js +20 -8
  102. package/src/guards/main-guard.js +54 -0
  103. package/src/harden/hook-templates.js +16 -1
  104. package/src/harden/sentinel/posttooluse.mjs +18 -6
  105. package/src/harden/sentinel/pretooluse-sentinel.mjs +64 -36
  106. package/src/harden/sentinel/sentinel-lib.mjs +129 -26
  107. package/src/harden/sentinel/sentinel-shell.mjs +5 -16
  108. package/src/harden/sentinel/sessionstart.mjs +3 -2
  109. package/src/harden/sentinel/stop.mjs +5 -4
  110. package/src/harden/workflow-templates.js +1 -1
  111. package/src/hu/board-lookup.js +29 -0
  112. package/src/hu/gh-identity.js +23 -0
  113. package/src/hu/github-board.js +4 -2
  114. package/src/hu/lane-prune.js +83 -13
  115. package/src/hu/lane-setup.js +65 -0
  116. package/src/hu/lanes.js +66 -0
  117. package/src/hu/team-board-mirror.js +3 -2
  118. package/src/infrastructure/command-runner.js +1 -1
  119. package/src/infrastructure/file-system-service.js +1 -1
  120. package/src/mcp/handlers/direct-handlers.js +2 -292
  121. package/src/mcp/handlers/hu-handlers.js +1 -35
  122. package/src/mcp/handlers/management-handlers.js +2 -42
  123. package/src/mcp/handlers/rag-handler.js +9 -1
  124. package/src/mcp/server-handlers.js +16 -22
  125. package/src/mcp/tools.js +1 -196
  126. package/src/plan/lane-plans.js +54 -0
  127. package/src/plan/plan-store.js +7 -4
  128. package/src/policy/add.js +26 -12
  129. package/src/policy/adr-number-check.js +31 -0
  130. package/src/prompts/audit.js +299 -339
  131. package/src/prompts/card-context.js +10 -5
  132. package/src/prompts/reviewer.js +5 -2
  133. package/src/rag/auto-update.js +71 -44
  134. package/src/rag/bootstrap-index.js +46 -54
  135. package/src/rag/indexer.js +9 -4
  136. package/src/rag/migrate.js +1 -1
  137. package/src/rag/onnx-fallback.js +2 -2
  138. package/src/rag/pid-lease.js +181 -0
  139. package/src/rag/project-store.js +2 -12
  140. package/src/review/card-first.js +57 -46
  141. package/src/review/gate-gitignore.js +23 -4
  142. package/src/review/gate-hook.js +12 -4
  143. package/src/review/one-shot-review.js +45 -35
  144. package/src/review/publish-pr.js +14 -2
  145. package/src/review/repo-docs.js +73 -0
  146. package/src/review/repo-stack.js +59 -0
  147. package/src/review/reviewer-fallback.js +19 -4
  148. package/src/review/seal-commit.js +29 -0
  149. package/src/review/sonar-pregate.js +59 -44
  150. package/src/review/third-party-review.js +90 -16
  151. package/src/review/third-party-size.js +43 -0
  152. package/src/review/third-party-skills.js +54 -0
  153. package/src/review/verify-pr.js +1 -24
  154. package/src/roles/audit-role.js +106 -125
  155. package/src/skills/atril-cli.js +19 -1
  156. package/src/skills/atril-skills.js +5 -4
  157. package/src/skills/skills-search.js +72 -0
  158. package/src/skills/translate.js +62 -0
  159. package/src/sonar/scanner.js +116 -101
  160. package/src/steward/invariants.js +5 -4
  161. package/src/steward/phantom-sweep.js +66 -0
  162. package/src/tournament/judge.js +60 -53
  163. package/src/utils/gh-account.js +37 -0
  164. package/src/utils/git.js +0 -13
  165. package/src/utils/home-migration.js +7 -1
  166. package/src/utils/main-checkout.js +53 -0
  167. package/src/utils/qmd-collection.js +3 -1
  168. package/src/utils/welcome.js +3 -3
  169. package/templates/{skills → atril/commands}/kj-architect.md +5 -0
  170. package/templates/{skills → atril/commands}/kj-audit.md +5 -0
  171. package/templates/{skills → atril/commands}/kj-board.md +5 -0
  172. package/templates/{skills → atril/commands}/kj-code.md +5 -0
  173. package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
  174. package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
  175. package/templates/{skills → atril/commands}/kj-discover.md +5 -0
  176. package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
  177. package/templates/{skills → atril/commands}/kj-review.md +5 -0
  178. package/templates/{skills → atril/commands}/kj-security.md +5 -0
  179. package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
  180. package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
  181. package/templates/{skills → atril/commands}/kj-test.md +5 -0
  182. package/packages/hu-board/public/utils/run-launcher.js +0 -169
  183. package/packages/hu-board/src/run-tracker.js +0 -121
  184. package/src/activity-log.js +0 -59
  185. package/src/agents/host-agent.js +0 -38
  186. package/src/agents/model-registry.js +0 -142
  187. package/src/audit/plan-adherence.js +0 -224
  188. package/src/autonomy/arbiter-role.js +0 -71
  189. package/src/autonomy/arbiter.js +0 -22
  190. package/src/autonomy/decider.js +0 -18
  191. package/src/autonomy/decision-resolver.js +0 -58
  192. package/src/autonomy/outcome-report.js +0 -55
  193. package/src/autonomy/stage-ask.js +0 -30
  194. package/src/brain/decision-tracker.js +0 -125
  195. package/src/brain/decisor.js +0 -243
  196. package/src/brain/solomon-consult.js +0 -171
  197. package/src/brain/standby-scheduler.js +0 -8
  198. package/src/budget/comparison.js +0 -96
  199. package/src/canvas/parse-md.js +0 -238
  200. package/src/canvas/schema.js +0 -101
  201. package/src/canvas/to-flat.js +0 -85
  202. package/src/canvas/validate.js +0 -163
  203. package/src/ci/dispatch.js +0 -99
  204. package/src/ci/index.js +0 -3
  205. package/src/ci/pr-diff.js +0 -26
  206. package/src/cli/register-standby.js +0 -26
  207. package/src/commands/architect.js +0 -111
  208. package/src/commands/autorun.js +0 -49
  209. package/src/commands/researcher.js +0 -54
  210. package/src/commands/resume.js +0 -72
  211. package/src/commands/review.js +0 -89
  212. package/src/commands/run.js +0 -185
  213. package/src/commands/standby.js +0 -117
  214. package/src/commands/status.js +0 -36
  215. package/src/commands/triage.js +0 -78
  216. package/src/commands/undo.js +0 -62
  217. package/src/domains/domain-loader.js +0 -245
  218. package/src/domains/domain-registry.js +0 -140
  219. package/src/domains/domain-synthesizer.js +0 -129
  220. package/src/git/automation.js +0 -433
  221. package/src/git/coder-commit-guard.js +0 -40
  222. package/src/git/hu-automation.js +0 -243
  223. package/src/git/hu-snapshot.js +0 -10
  224. package/src/git/symlink-targets.js +0 -57
  225. package/src/golden/asserter.js +0 -92
  226. package/src/golden/loader.js +0 -52
  227. package/src/golden/runner.js +0 -92
  228. package/src/golden/schema.js +0 -41
  229. package/src/guards/duplicate-members.js +0 -86
  230. package/src/guards/intent-guard.js +0 -161
  231. package/src/guards/perf-guard.js +0 -126
  232. package/src/guards/policy-guard.js +0 -90
  233. package/src/guards/policy-resolver.js +0 -85
  234. package/src/guards/secret-redactor.js +0 -71
  235. package/src/hu/acceptance-runner.js +0 -138
  236. package/src/hu/lazy-planner.js +0 -109
  237. package/src/hu/parallel-executor.js +0 -94
  238. package/src/hu/routing-metrics.js +0 -17
  239. package/src/hu/split-validator.js +0 -143
  240. package/src/hu/splitting-detector.js +0 -158
  241. package/src/hu/splitting-generator.js +0 -175
  242. package/src/hu/store.js +0 -394
  243. package/src/infrastructure/mocks.js +0 -182
  244. package/src/mcp/handlers/run-handler.js +0 -318
  245. package/src/mcp/sovereignty-guard.js +0 -113
  246. package/src/mcp/tool-arg-normalizers.js +0 -16
  247. package/src/mutate/reviewer-signal.js +0 -76
  248. package/src/orchestrator/agent-fallback.js +0 -81
  249. package/src/orchestrator/brain-coordinator.js +0 -186
  250. package/src/orchestrator/ci-integration.js +0 -139
  251. package/src/orchestrator/config-init.js +0 -426
  252. package/src/orchestrator/direct-actions.js +0 -217
  253. package/src/orchestrator/drivers/error-recovery.js +0 -287
  254. package/src/orchestrator/drivers/init-context.js +0 -238
  255. package/src/orchestrator/drivers/iteration-loop.js +0 -382
  256. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
  257. package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
  258. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
  259. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
  260. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
  261. package/src/orchestrator/drivers/post-loop.js +0 -404
  262. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
  263. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
  264. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
  265. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
  266. package/src/orchestrator/drivers/pre-loop.js +0 -524
  267. package/src/orchestrator/drivers/run-hu-batch.js +0 -468
  268. package/src/orchestrator/feedback-enrichment.js +0 -140
  269. package/src/orchestrator/feedback-queue.js +0 -151
  270. package/src/orchestrator/flow-control.js +0 -170
  271. package/src/orchestrator/flow-runner.js +0 -365
  272. package/src/orchestrator/fs-leak-detector.js +0 -228
  273. package/src/orchestrator/hu-scheduler.js +0 -114
  274. package/src/orchestrator/hu-sub-pipeline.js +0 -642
  275. package/src/orchestrator/iteration-gate.js +0 -73
  276. package/src/orchestrator/iteration-stages.js +0 -9
  277. package/src/orchestrator/parallel-limiter.js +0 -74
  278. package/src/orchestrator/pipeline-context.js +0 -72
  279. package/src/orchestrator/post-loop-stages.js +0 -468
  280. package/src/orchestrator/pre-loop-stages.js +0 -11
  281. package/src/orchestrator/preflight-checks.js +0 -487
  282. package/src/orchestrator/repair/repair-detector.js +0 -117
  283. package/src/orchestrator/repair/repair-runner.js +0 -142
  284. package/src/orchestrator/reviewer-fallback.js +0 -45
  285. package/src/orchestrator/role-output-compressor.js +0 -189
  286. package/src/orchestrator/session-journal.js +0 -308
  287. package/src/orchestrator/solomon-escalation.js +0 -331
  288. package/src/orchestrator/solomon-rules.js +0 -212
  289. package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
  290. package/src/orchestrator/stages/architect-stage.js +0 -172
  291. package/src/orchestrator/stages/coder-stage.js +0 -506
  292. package/src/orchestrator/stages/domain-curator-stage.js +0 -82
  293. package/src/orchestrator/stages/format-stage.js +0 -61
  294. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
  295. package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
  296. package/src/orchestrator/stages/perf-stage.js +0 -140
  297. package/src/orchestrator/stages/planner-stage.js +0 -85
  298. package/src/orchestrator/stages/rag-context-stage.js +0 -80
  299. package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
  300. package/src/orchestrator/stages/research-stage.js +0 -69
  301. package/src/orchestrator/stages/reviewer-stage.js +0 -431
  302. package/src/orchestrator/stages/sonar-stage.js +0 -439
  303. package/src/orchestrator/stages/stage-classes.js +0 -84
  304. package/src/orchestrator/stages/stage-executor.js +0 -155
  305. package/src/orchestrator/stages/suite-green-stage.js +0 -62
  306. package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
  307. package/src/orchestrator/stages/tool-judge-stage.js +0 -53
  308. package/src/orchestrator/stages/triage-stage.js +0 -172
  309. package/src/orchestrator/standby.js +0 -71
  310. package/src/orchestrator/verification-gate.js +0 -183
  311. package/src/orchestrator.js +0 -22
  312. package/src/plan/adr-generator.js +0 -170
  313. package/src/plan/adr-loader.js +0 -60
  314. package/src/plan/plan-executor.js +0 -100
  315. package/src/plan/plan-validation.js +0 -4
  316. package/src/prompts/acceptance-coverage.js +0 -32
  317. package/src/prompts/arbiter.js +0 -38
  318. package/src/prompts/architect.js +0 -103
  319. package/src/prompts/hu-reviewer.js +0 -244
  320. package/src/prompts/tool-correctness-judge.js +0 -31
  321. package/src/prompts/triage.js +0 -57
  322. package/src/rag/embedder.js +0 -2
  323. package/src/rag/embedders/_cloud-base.js +0 -2
  324. package/src/rag/embedders/cohere.js +0 -2
  325. package/src/rag/embedders/mistral.js +0 -2
  326. package/src/rag/embedders/onnx.js +0 -2
  327. package/src/rag/embedders/openai.js +0 -2
  328. package/src/rag/embedders/voyage.js +0 -2
  329. package/src/rag/rerank.js +0 -4
  330. package/src/rag/where-parser.js +0 -4
  331. package/src/repeat-detector.js +0 -88
  332. package/src/review/diff-generator.js +0 -112
  333. package/src/review/git-stash-helper.js +0 -104
  334. package/src/review/schema.js +0 -31
  335. package/src/review/scope-filter.js +0 -152
  336. package/src/review/snapshot-diff.js +0 -122
  337. package/src/review/tdd-discipline.js +0 -90
  338. package/src/review/tdd-policy.js +0 -69
  339. package/src/roles/acceptance-coverage-role.js +0 -78
  340. package/src/roles/architect-role.js +0 -77
  341. package/src/roles/coder-role.js +0 -113
  342. package/src/roles/commiter-role.js +0 -113
  343. package/src/roles/domain-curator-role.js +0 -134
  344. package/src/roles/hu-reviewer-role.js +0 -64
  345. package/src/roles/impeccable-role.js +0 -85
  346. package/src/roles/index.js +0 -18
  347. package/src/roles/karajan-brain-role.js +0 -134
  348. package/src/roles/perf-role.js +0 -122
  349. package/src/roles/planner-role.js +0 -151
  350. package/src/roles/refactorer-role.js +0 -30
  351. package/src/roles/repairer-role.js +0 -181
  352. package/src/roles/researcher-role.js +0 -70
  353. package/src/roles/reviewer-role.js +0 -115
  354. package/src/roles/security-role.js +0 -73
  355. package/src/roles/solomon-role.js +0 -271
  356. package/src/roles/sonar-role.js +0 -72
  357. package/src/roles/tester-role.js +0 -235
  358. package/src/roles/tool-correctness-judge-role.js +0 -78
  359. package/src/roles/triage-role.js +0 -99
  360. package/src/session/journal/decisions-writer.js +0 -200
  361. package/src/session/journal/iteration-logger.js +0 -194
  362. package/src/session/journal/summary-writer.js +0 -320
  363. package/src/session/journal/tree-writer.js +0 -164
  364. package/src/session/journal/write-all.js +0 -89
  365. package/src/session-cleanup.js +0 -116
  366. package/src/skills/semantic-detector.js +0 -167
  367. package/src/skills/skill-detector.js +0 -385
  368. package/src/sonar/cloud-scanner.js +0 -78
  369. package/src/tracker-bootstrap.js +0 -44
  370. package/src/types/index.js +0 -38
  371. package/src/types/policy.js +0 -43
  372. package/src/types/solomon.js +0 -87
  373. package/src/utils/async-lock.js +0 -31
  374. package/src/utils/budget.js +0 -229
  375. package/src/utils/cwd-confirm.js +0 -39
  376. package/src/utils/display/event-handlers.js +0 -403
  377. package/src/utils/display/formatters.js +0 -132
  378. package/src/utils/display/header.js +0 -47
  379. package/src/utils/display/session-summary.js +0 -118
  380. package/src/utils/display/solomon.js +0 -42
  381. package/src/utils/pricing.js +0 -29
  382. package/src/utils/quota-simulator.js +0 -56
  383. package/src/utils/role-assigner.js +0 -137
  384. package/src/utils/rtk-wrapper.js +0 -87
  385. package/src/utils/run-registry.js +0 -9
  386. package/src/utils/status-dashboard.js +0 -254
  387. package/src/utils/tool-call-extractor.js +0 -56
  388. package/src/webperf/devtools-detect.js +0 -112
  389. package/src/webperf/webperf-role-integration.js +0 -66
  390. package/templates/skills/kj-run.md +0 -73
@@ -1,331 +0,0 @@
1
- import { SolomonRole } from "../roles/solomon-role.js";
2
- import { addCheckpoint, pauseSession, saveSession } from "../session/store.js";
3
- import { setSuggestions } from "../session/mutators.js";
4
- import { emitProgress, makeEvent } from "../utils/events.js";
5
- import { msg, getLang } from "../utils/messages.js";
6
- import { listPendingPaths } from "../utils/git.js";
7
- import { keepSymlinkTargets, symlinkTargets } from "../git/symlink-targets.js";
8
-
9
- /**
10
- * @typedef {import("../types/solomon.js").Conflict} Conflict
11
- * @typedef {import("../types/solomon.js").SolomonResult} SolomonResult
12
- */
13
-
14
- // KJC-BUG-0414 (#2606): the symlinks among the pending paths, so Solomon never advises taking a
15
- // link's target out of the commit. Unlistable is said, and Solomon goes on without the fact.
16
- async function changeSymlinks(config, logger) {
17
- const projectDir = config.projectDir || process.cwd();
18
- try {
19
- return symlinkTargets(projectDir, await listPendingPaths(projectDir));
20
- } catch (err) {
21
- logger.warn(`Solomon: the change's symlinks could not be listed (${err.message})`);
22
- return [];
23
- }
24
- }
25
-
26
- function dropSymlinkTargetConditions(ruling, symlinks, logger) {
27
- const conditions = ruling?.result?.conditions;
28
- if (!Array.isArray(conditions) || symlinks.length === 0) return;
29
- const { kept, dropped } = keepSymlinkTargets(conditions, symlinks);
30
- if (dropped.length === 0) return;
31
- ruling.result.conditions = kept;
32
- logger.warn(`Solomon: dropped ${dropped.length} condition(s) that took a symlink's target out of the commit: ${dropped.join(" | ")}`);
33
- }
34
-
35
- /**
36
- * Architectural invariant (DO NOT BREAK):
37
- * `invokeSolomon` MUST NOT be called directly from stages, rules or agent
38
- * code. The only callers should be:
39
- * - Karajan Brain (via `src/brain/solomon-consult.js`)
40
- * - The orchestrator's escalation path (`src/orchestrator/flow-runner.js`)
41
- * on behalf of Brain.
42
- * The v2.3.0 audit fixed 21 violations of this rule. Keeping Solomon behind
43
- * Brain is what the "Brain-as-gateway, Solomon-as-judge" architecture
44
- * depends on — see docs/ARCHITECTURE.md §Brain/Solomon.
45
- */
46
-
47
- /**
48
- * Build a human-readable escalation message from raw Solomon conflict data.
49
- * Replaces the raw JSON dump with structured, plain-text output.
50
- *
51
- * @param {object} solomonResult - The conflict object passed to escalateToHuman
52
- * @param {object} [session] - Current session (optional, used for extra context)
53
- * @returns {string} Formatted message suitable for display to the user
54
- */
55
- export function formatEscalationMessage(solomonResult, session) {
56
- const stage = solomonResult?.stage || "unknown";
57
- const solomonReason = solomonResult?.solomonReason || null;
58
- const history = solomonResult?.history || [];
59
- const lang = getLang(session?.config_snapshot);
60
-
61
- const lines = [];
62
- lines.push(msg("solomon_conflict", lang, { stage }));
63
- lines.push("");
64
-
65
- // Extract reviewer/agent feedback from history
66
- const feedbackEntries = history.filter(entry => entry && typeof entry === "object");
67
- if (feedbackEntries.length > 0) {
68
- lines.push(msg("solomon_feedback", lang));
69
- for (const entry of feedbackEntries) {
70
- const agent = entry.agent || "unknown";
71
- const feedback = entry.feedback || "No feedback provided";
72
- // Split multi-line feedback (e.g. "R-1: ...\nR-2: ...") into bullet points
73
- const feedbackLines = String(feedback).split("\n").filter(l => l.trim());
74
- if (feedbackLines.length === 1) {
75
- lines.push(` [${agent}] ${feedbackLines[0]}`);
76
- } else {
77
- lines.push(` [${agent}]`);
78
- for (const fl of feedbackLines) {
79
- lines.push(` - ${fl}`);
80
- }
81
- }
82
- }
83
- lines.push("");
84
- }
85
-
86
- // Solomon's reason (if it tried and failed)
87
- if (solomonReason) {
88
- lines.push(`${msg("solomon_reason", lang)} ${solomonReason}`);
89
- lines.push("");
90
- }
91
-
92
- // Iteration context
93
- const iterationCount = solomonResult?.iterationCount;
94
- const maxIterations = solomonResult?.maxIterations;
95
- if (iterationCount !== undefined && maxIterations !== undefined) {
96
- lines.push(msg("pipeline_iteration", lang, { current: iterationCount, max: maxIterations }));
97
- lines.push("");
98
- }
99
-
100
- lines.push(msg("solomon_options", lang));
101
- lines.push("");
102
- lines.push(msg("solomon_proceed", lang));
103
-
104
- return lines.join("\n");
105
- }
106
-
107
- /**
108
- * Extract Solomon's previous rulings from session checkpoints.
109
- * Gives Solomon memory of its own decisions to avoid repeating failed strategies.
110
- */
111
- function extractSolomonHistory(session) {
112
- const checkpoints = session?.checkpoints || [];
113
- return checkpoints
114
- .filter(cp => cp.stage === "solomon")
115
- .map(cp => ({
116
- conflictStage: cp.conflictStage,
117
- ruling: cp.ruling,
118
- alternativeAgent: cp.alternativeAgent,
119
- waitUntil: cp.waitUntil,
120
- conditions: cp.conditions,
121
- error: cp.error || null
122
- }));
123
- }
124
-
125
- /**
126
- * Consult Solomon (or fall back to human escalation) for a given conflict.
127
- * See the Conflict typedef for the shape of the `conflict` argument and
128
- * the invariant above for who is allowed to call this.
129
- *
130
- * @param {Object} params
131
- * @param {Object} params.config
132
- * @param {Object} params.logger
133
- * @param {Object} [params.emitter]
134
- * @param {Object} [params.eventBase]
135
- * @param {string} params.stage
136
- * @param {Conflict} params.conflict
137
- * @param {Function} [params.askQuestion]
138
- * @param {Object} [params.session]
139
- * @param {number} [params.iteration]
140
- * @returns {Promise<SolomonResult>}
141
- */
142
- export async function invokeSolomon({ config, logger, emitter, eventBase, stage, conflict, askQuestion, session, iteration }) {
143
- const solomonEnabled = Boolean(config.pipeline?.solomon?.enabled);
144
-
145
- if (!solomonEnabled) {
146
- return escalateToHuman({ askQuestion, session, emitter, eventBase, stage, conflict, iteration });
147
- }
148
-
149
- // KJC-BUG-0113: the fallback used to be "gemini" — Google retired the
150
- // Gemini Code Assist CLI for individuals (IneligibleTierError), so an
151
- // unconfigured Solomon died on every escalation. Claude matches the
152
- // brain.provider default.
153
- const solomonProvider = config?.roles?.solomon?.provider || "claude";
154
- emitProgress(
155
- emitter,
156
- makeEvent("solomon:start", { ...eventBase, stage: "solomon" }, {
157
- message: `Solomon arbitrating ${stage} conflict`,
158
- detail: { conflictStage: stage, provider: solomonProvider, executorType: "agent" }
159
- })
160
- );
161
-
162
- // Collect pending suggestions from the session for Solomon's context
163
- const pendingSuggestions = Array.isArray(session.suggestions) && session.suggestions.length > 0
164
- ? session.suggestions.slice()
165
- : null;
166
-
167
- const solomon = new SolomonRole({ config, logger, emitter });
168
- await solomon.init({ task: conflict.task, iteration });
169
- let ruling;
170
- try {
171
- // Inject Solomon's own history so it doesn't repeat failed strategies
172
- const solomonHistory = extractSolomonHistory(session);
173
- const symlinks = await changeSymlinks(config, logger);
174
- const enrichedConflict = {
175
- ...conflict,
176
- ...(pendingSuggestions && { hostSuggestions: pendingSuggestions }),
177
- ...(solomonHistory.length > 0 && { previousSolomonRulings: solomonHistory }),
178
- ...(symlinks.length > 0 && { symlinkTargets: symlinks })
179
- };
180
- ruling = await solomon.run({ conflict: enrichedConflict });
181
- dropSymlinkTargetConditions(ruling, symlinks, logger);
182
- } catch (err) {
183
- logger.warn(`Solomon threw: ${err.message}`);
184
- return escalateToHuman({
185
- askQuestion, session, emitter, eventBase, stage, iteration,
186
- conflict: { ...conflict, solomonReason: `Solomon error: ${err.message}` }
187
- });
188
- }
189
-
190
- // Clear processed suggestions from session
191
- if (pendingSuggestions) {
192
- setSuggestions(session, []);
193
- try { await saveSession(session); } catch { /* best-effort */ }
194
- }
195
-
196
- const solomonError = ruling.result?.error;
197
- if (!ruling.ok && solomonError) {
198
- logger.warn(`Solomon execution failed: ${solomonError}`);
199
- }
200
-
201
- emitProgress(
202
- emitter,
203
- makeEvent("solomon:end", { ...eventBase, stage: "solomon" }, {
204
- message: ruling.ok
205
- ? `Solomon ruling: ${ruling.result?.ruling || "unknown"}`
206
- : `Solomon failed: ${(solomonError || ruling.summary || "unknown error").slice(0, 200)}`,
207
- detail: { ...ruling.result, provider: solomonProvider, executorType: "agent" }
208
- })
209
- );
210
-
211
- await addCheckpoint(session, {
212
- stage: "solomon",
213
- iteration,
214
- conflictStage: conflict?.stage || stage,
215
- ruling: ruling.result?.ruling,
216
- alternativeAgent: ruling.result?.alternativeAgent || null,
217
- waitUntil: ruling.result?.waitUntil || null,
218
- conditions: ruling.result?.conditions || [],
219
- escalate: ruling.result?.escalate,
220
- error: solomonError ? solomonError.slice(0, 500) : undefined,
221
- subtask: ruling.result?.subtask?.title || null
222
- });
223
-
224
- // Record the ruling for the session journal (decisions.md). Lazy-import so
225
- // this module stays free of journal-specific deps for callers that don't
226
- // need them.
227
- try {
228
- const { recordSolomonRuling } = await import("../session/journal/decisions-writer.js");
229
- recordSolomonRuling(session, {
230
- stage: conflict?.stage || stage,
231
- trigger: conflict?.reason || conflict?.summary || "solomon invoked",
232
- conflict: {
233
- task: conflict?.task,
234
- reason: conflict?.reason,
235
- summary: conflict?.summary,
236
- iterationCount: conflict?.iterationCount,
237
- },
238
- ruling: ruling.result?.ruling || (ruling.ok ? "continue" : "escalate_human"),
239
- reasoning: ruling.result?.reasoning || ruling.summary || "",
240
- conditions: ruling.result?.conditions || [],
241
- alternativeAgent: ruling.result?.alternativeAgent || null,
242
- });
243
- } catch { /* best-effort, don't break the pipeline on a journal hiccup */ }
244
-
245
- if (!ruling.ok) {
246
- const reason = ruling.result?.escalate_reason || solomonError || ruling.summary;
247
- return escalateToHuman({
248
- askQuestion, session, emitter, eventBase, stage, iteration,
249
- conflict: { ...conflict, solomonReason: reason }
250
- });
251
- }
252
-
253
- const r = ruling.result?.ruling;
254
- if (r === "approve") {
255
- return { action: "approve", conditions: [], ruling };
256
- }
257
- if (r === "approve_with_conditions") {
258
- return {
259
- action: "continue",
260
- conditions: ruling.result?.conditions || [],
261
- extraIterations: ruling.result?.extraIterations || null,
262
- alternativeAgent: ruling.result?.alternativeAgent || null,
263
- waitUntil: ruling.result?.waitUntil || null,
264
- ruling
265
- };
266
- }
267
-
268
- if (r === "escalate_human") {
269
- return escalateToHuman({
270
- askQuestion, session, emitter, eventBase, stage, iteration,
271
- conflict: { ...conflict, solomonReason: ruling.result?.escalate_reason || "Solomon escalated to human" }
272
- });
273
- }
274
-
275
- if (r === "create_subtask") {
276
- return { action: "subtask", subtask: ruling.result?.subtask, ruling };
277
- }
278
-
279
- return { action: "continue", conditions: [], ruling };
280
- }
281
-
282
- export function parseEscalationAnswer(answer) {
283
- if (!answer) return null;
284
- const trimmed = answer.trim().toLowerCase();
285
- // Option 1: Accept coder's work as-is
286
- if (trimmed === "1" || trimmed === "accept" || trimmed === "yes" || trimmed === "sí" || trimmed === "si") {
287
- return { action: "continue", humanGuidance: "Accept coder's work as-is" };
288
- }
289
- // Option 2: Retry with reviewer's feedback
290
- if (trimmed === "2" || trimmed === "retry") {
291
- return { action: "continue", humanGuidance: "Retry with reviewer feedback" };
292
- }
293
- // Option 3: Stop the session
294
- if (trimmed === "3" || trimmed.startsWith("stop") || trimmed.startsWith("pause")) {
295
- return { action: "stop" };
296
- }
297
- // Free-text: treat as human guidance (backward compat)
298
- return { action: "continue", humanGuidance: answer.trim() };
299
- }
300
-
301
- export async function escalateToHuman({ askQuestion, session, emitter, eventBase, stage, conflict, iteration }) {
302
- const question = formatEscalationMessage({ ...conflict, stage }, session);
303
-
304
- if (askQuestion) {
305
- const answer = await askQuestion(question, { iteration, stage });
306
- const parsed = parseEscalationAnswer(answer);
307
- if (parsed && parsed.action === "continue") {
308
- return { action: "continue", humanGuidance: parsed.humanGuidance };
309
- }
310
- if (parsed && parsed.action === "stop") {
311
- // Fall through to pause session
312
- } else if (answer) {
313
- return { action: "continue", humanGuidance: answer };
314
- }
315
- }
316
-
317
- await pauseSession(session, {
318
- question,
319
- context: { iteration, stage, conflict }
320
- });
321
- emitProgress(
322
- emitter,
323
- makeEvent("question", { ...eventBase, stage }, {
324
- status: "paused",
325
- message: question,
326
- detail: { question, sessionId: session.id }
327
- })
328
- );
329
-
330
- return { action: "pause", question };
331
- }
@@ -1,212 +0,0 @@
1
- /**
2
- * Solomon rules engine — detects anomalies during session execution.
3
- * Each rule returns { triggered: boolean, severity: "warn"|"critical", message, detail }
4
- */
5
-
6
- const DEFAULT_RULES = {
7
- max_stale_iterations: 3,
8
- no_new_dependencies_without_task: true,
9
- scope_guard: true,
10
- reviewer_overreach: true,
11
- reviewer_style_block: true
12
- };
13
-
14
- export function evaluateRules(context, rulesConfig = {}) {
15
- const rules = { ...DEFAULT_RULES, ...rulesConfig };
16
- const alerts = [];
17
-
18
- // Rule 1: Stale iterations (no progress)
19
- if (rules.max_stale_iterations && context.staleIterations >= rules.max_stale_iterations) {
20
- alerts.push({
21
- rule: "max_stale_iterations",
22
- severity: "critical",
23
- message: `${context.staleIterations} iterations without progress. Same errors repeating.`,
24
- detail: { staleIterations: context.staleIterations, limit: rules.max_stale_iterations }
25
- });
26
- }
27
-
28
- // Rule 3: New dependencies not in task (only flag suspicious runtime deps, not dev/test tooling)
29
- if (rules.no_new_dependencies_without_task && context.newDependencies?.length > 0) {
30
- const testTooling = /vitest|jest|mocha|coverage|eslint|prettier|typescript|@types\//i;
31
- const buildTooling = /webpack|vite|rollup|esbuild|tsup|unbuild/i;
32
- const depsNotInTask = context.newDependencies.filter(dep => {
33
- if (testTooling.test(dep) || buildTooling.test(dep)) return false;
34
- if (dep.startsWith("dev:") || dep.startsWith("@vitest/") || dep.startsWith("@types/")) return false;
35
- return !context.task?.toLowerCase().includes(dep.toLowerCase());
36
- });
37
- if (depsNotInTask.length > 0) {
38
- alerts.push({
39
- rule: "no_new_dependencies_without_task",
40
- severity: "warn",
41
- message: `New dependencies added not mentioned in task: ${depsNotInTask.join(", ")}`,
42
- detail: { dependencies: depsNotInTask }
43
- });
44
- }
45
- }
46
-
47
- // Rule 4: Scope guard — files outside expected scope
48
- if (rules.scope_guard && context.outOfScopeFiles?.length > 0) {
49
- alerts.push({
50
- rule: "scope_guard",
51
- severity: "warn",
52
- message: `Files modified outside expected scope: ${context.outOfScopeFiles.join(", ")}`,
53
- detail: { files: context.outOfScopeFiles }
54
- });
55
- }
56
-
57
- // Rule 5: Reviewer overreach — reviewer consistently flags out-of-scope issues
58
- if (rules.reviewer_overreach && context.reviewerDemotedCount > 0) {
59
- const severity = context.reviewerDemotedCount >= 3 ? "critical" : "warn";
60
- alerts.push({
61
- rule: "reviewer_overreach",
62
- severity,
63
- message: `Reviewer flagged ${context.reviewerDemotedCount} out-of-scope issue(s) that were auto-demoted by scope filter.`,
64
- detail: { demotedCount: context.reviewerDemotedCount, autoApproved: context.reviewerAutoApproved || false }
65
- });
66
- }
67
-
68
- // Rule 6: Reviewer style-only block — all blocking issues are style/naming/formatting, not security/correctness
69
- if (rules.reviewer_style_block && context.blockingIssues?.length > 0) {
70
- const styleKeywords = /\b(naming|name|rename|style|format|formatting|indent|spacing|camelCase|snake_case|convention|cosmetic|readability|comment|jsdoc|documentation|whitespace|semicolon|quotes|trailing)\b/i;
71
- // Anti-style: if any of these match, the issue is NOT style — it's a real correctness/security concern.
72
- // Fixes KJC-BUG-0026 (Solomon approving legitimate security blockers misclassified as "style").
73
- // Decomposed by concern (S5843, complexity 71 → 7 named groups).
74
- // The union of tokens is byte-identical to the original alternation.
75
- const SECURITY_KEYWORD_GROUPS = [
76
- /\b(sql\s*injection|xss|csrf|ssrf|rce|injection|vulnerab|exploit)\b/i,
77
- /\b(sanitiz|escape\b|encod\b|cors|traversal|prototype[-_\s]*pollution|deserializ)\b/i,
78
- /\b(auth|authn|authz|password|secret|credential|token|api[-_\s]*key|private[-_\s]*key)\b/i,
79
- /\b(hash|cipher|crypto|encrypt|decrypt|tls|ssl|certificate)\b/i,
80
- /\b(leak|sensitive|pii|privacy|insecure|unsafe)\b/i,
81
- /\b(race[-_\s]*condition|deadlock|memory[-_\s]*leak|null[-_\s]*deref|uaf|overflow)\b/i,
82
- /\b(eval\b|exec\b|spawn|shell|command[-_\s]*injection)\b/i,
83
- ];
84
- const securityKeywords = { test: (text) => SECURITY_KEYWORD_GROUPS.some((re) => re.test(text)) };
85
- const securityCategories = new Set(["security", "correctness", "bug", "vulnerability"]);
86
- const styleSeverities = new Set(["low", "minor"]);
87
- const blockingSeverities = new Set(["critical", "high", "blocker", "major"]);
88
- const isSecurityIssue = issue => {
89
- const desc = issue.description || "";
90
- const sev = (issue.severity || "").toLowerCase();
91
- const cat = (issue.category || issue.type || "").toLowerCase();
92
- const tags = Array.isArray(issue.tags) ? issue.tags.map(t => String(t).toLowerCase()) : [];
93
- if (blockingSeverities.has(sev)) return true;
94
- if (securityCategories.has(cat)) return true;
95
- if (tags.some(t => securityCategories.has(t))) return true;
96
- return securityKeywords.test(desc);
97
- };
98
- const allStyle = context.blockingIssues.every(issue => {
99
- if (isSecurityIssue(issue)) return false;
100
- const desc = issue.description || "";
101
- const sev = (issue.severity || "").toLowerCase();
102
- return styleSeverities.has(sev) || styleKeywords.test(desc);
103
- });
104
- if (allStyle) {
105
- alerts.push({
106
- rule: "reviewer_style_block",
107
- severity: "critical",
108
- message: `Reviewer blocked on ${context.blockingIssues.length} style-only issue(s). Style preferences should not block approval — escalating to Solomon mediation.`,
109
- detail: { issueCount: context.blockingIssues.length, issues: context.blockingIssues.map(i => i.description) }
110
- });
111
- }
112
- }
113
-
114
- return {
115
- alerts,
116
- hasCritical: alerts.some(a => a.severity === "critical"),
117
- hasWarnings: alerts.some(a => a.severity === "warn")
118
- };
119
- }
120
-
121
- /**
122
- * Build context for rules evaluation from git diff and session state.
123
- */
124
- export async function buildRulesContext({ session, task, iteration, blockingIssues }) {
125
- const context = {
126
- task,
127
- iteration,
128
- filesChanged: 0,
129
- staleIterations: 0,
130
- newDependencies: [],
131
- outOfScopeFiles: [],
132
- reviewerDemotedCount: 0,
133
- reviewerAutoApproved: false,
134
- blockingIssues: blockingIssues || []
135
- };
136
-
137
- // Count reviewer scope-filter demotions from session checkpoints
138
- const scopeFilterCheckpoints = (session.checkpoints || [])
139
- .filter(cp => cp.stage === "reviewer-scope-filter");
140
- if (scopeFilterCheckpoints.length > 0) {
141
- const latest = scopeFilterCheckpoints.at(-1);
142
- context.reviewerDemotedCount = latest.demoted_count || 0;
143
- context.reviewerAutoApproved = latest.auto_approved || false;
144
- }
145
-
146
- // Count files changed via git
147
- try {
148
- // Audit follow-up: was using execaCommand with template-string
149
- // interpolation of `baseRef`. baseRef comes from session state, not
150
- // user input, but the shape is a known shell-injection vector.
151
- // Switched to execa with arg arrays so no shell expansion happens.
152
- const { execa } = await import("execa");
153
- const baseRef = session.session_start_sha || "HEAD~1";
154
-
155
- // Files changed
156
- const diffResult = await execa("git", ["diff", "--name-only", String(baseRef)], { reject: false });
157
- if (diffResult.stdout) {
158
- const files = diffResult.stdout.split("\n").filter(Boolean);
159
- context.filesChanged = files.length;
160
-
161
- // Detect scope: config files, CI/CD, etc. that are often out of scope
162
- // Exclude .env.example (legitimate for new projects) and files within projectDir
163
- const scopePatterns = [".github/", ".gitlab-ci", "docker-compose", "firebase.json", "firestore.rules"];
164
- const projectDir = session?.config_snapshot?.projectDir || null;
165
- context.outOfScopeFiles = files.filter(f => {
166
- // Files inside projectDir are always in scope
167
- if (projectDir && f.startsWith(projectDir.replace(/^\.\//, ""))) return false;
168
- // .env (but not .env.example) is out of scope
169
- if (f.endsWith(".env") && !f.endsWith(".env.example")) return true;
170
- return scopePatterns.some(pattern => f.includes(pattern));
171
- });
172
-
173
- // Detect new dependencies
174
- if (files.includes("package.json")) {
175
- try {
176
- const pkgDiff = await execa("git", ["diff", String(baseRef), "--", "package.json"], { reject: false });
177
- const addedDeps = (pkgDiff.stdout || "").split("\n")
178
- .filter(line => line.startsWith("+") && line.includes('"') && !line.startsWith("+++"))
179
- .map(line => {
180
- const match = /"([^"]+)":\s*"/.exec(line);
181
- return match ? match[1] : null;
182
- })
183
- .filter(Boolean)
184
- .filter(name => !new Set(["name", "version", "description", "main", "scripts", "type", "license", "author"]).has(name));
185
- context.newDependencies = addedDeps;
186
- } catch { /* ignore */ }
187
- }
188
- }
189
- } catch { /* git not available */ }
190
-
191
- // Count stale iterations from session checkpoints
192
- // "Stale" means TRULY no progress: same exact feedback AND zero files changed
193
- const checkpoints = session.checkpoints || [];
194
- const reviewerCps = checkpoints.filter(cp => cp.stage === "reviewer").slice(-3);
195
- const coderCps = checkpoints.filter(cp => cp.stage === "coder").slice(-3);
196
-
197
- if (reviewerCps.length >= 3) {
198
- const feedbacks = reviewerCps.map(cp => cp.note || "");
199
- const uniqueFeedbacks = new Set(feedbacks);
200
- const filesChanged = coderCps.some(cp => (cp.filesChanged || 0) > 0);
201
-
202
- // Only stale if feedback is identical AND no files changed
203
- if (uniqueFeedbacks.size === 1 && !filesChanged) {
204
- context.staleIterations = reviewerCps.length;
205
- }
206
- // If issues are changing between iterations, there IS progress
207
- }
208
-
209
- return context;
210
- }
211
-
212
- export { DEFAULT_RULES };
@@ -1,47 +0,0 @@
1
- // KJC-TSK-0987 (issue #1894): the acceptance-coverage stage. With a card whose
2
- // criteria the run was meant to implement, a different AI reads each criterion
3
- // against the diff before the run is approved. No card or no criteria: the
4
- // stage does not run and costs nothing. An unreadable verdict is the judge's
5
- // failure, never an approval.
6
- import { AcceptanceCoverageRole } from "../../roles/acceptance-coverage-role.js";
7
- import { emitProgress, makeEvent } from "../../utils/events.js";
8
-
9
- const STAGE = "acceptance-coverage";
10
-
11
- /** The feedback the coder gets: the criteria with no evidence in the diff. */
12
- export const uncoveredFeedback = (uncovered) => {
13
- const lines = uncovered.map((c) => "- [" + c.index + "] " + c.text + (c.evidence ? " (judge: " + c.evidence + ")" : ""));
14
- return "Acceptance criteria NOT covered by the diff (implement them, with tests that exercise them):\n" + lines.join("\n");
15
- };
16
-
17
- /**
18
- * @returns {Promise<{action: "skip"|"ok"|"retry"|"fail", stageResult?: object, feedback?: string}>}
19
- */
20
- export async function runAcceptanceCoverageStage({ config, logger, emitter, eventBase, trackBudget, iteration, task, criteria, diff, RoleClass = AcceptanceCoverageRole }) {
21
- if (!Array.isArray(criteria) || criteria.length === 0) return { action: "skip" };
22
- logger.setContext?.({ iteration, stage: STAGE });
23
- emitProgress(emitter, makeEvent(`${STAGE}:start`, { ...eventBase, stage: STAGE }, {
24
- message: `Acceptance coverage: ${criteria.length} criteria against the diff`,
25
- detail: { criteria: criteria.length, executorType: "agent" },
26
- }));
27
- const role = new RoleClass({ config, logger, emitter });
28
- await role.init({ task, iteration });
29
- const started = Date.now();
30
- const output = await role.run({ task, criteria, diff });
31
- const provider = output.result?.provider || role.resolveProvider();
32
- trackBudget?.({ role: STAGE, provider, model: config?.roles?.[STAGE]?.model || null, result: output, duration_ms: Date.now() - started });
33
-
34
- if (!output.ok) {
35
- const stageResult = { ok: false, summary: output.summary || "acceptance-coverage: the judge gave no verdict", provider, error: output.result?.error || null };
36
- emitProgress(emitter, makeEvent(`${STAGE}:end`, { ...eventBase, stage: STAGE }, { status: "fail", message: stageResult.summary, detail: stageResult }));
37
- return { action: "fail", stageResult };
38
- }
39
- const { criteria: verdicts, uncovered } = output.result;
40
- const stageResult = { ok: uncovered.length === 0, summary: output.summary, provider, criteria: verdicts, uncovered };
41
- emitProgress(emitter, makeEvent(`${STAGE}:end`, { ...eventBase, stage: STAGE }, {
42
- status: stageResult.ok ? "ok" : "fail", message: output.summary, detail: stageResult,
43
- }));
44
- if (stageResult.ok) return { action: "ok", stageResult };
45
- logger.warn(`${output.summary} — sending them back to the coder`);
46
- return { action: "retry", stageResult, feedback: uncoveredFeedback(uncovered) };
47
- }