karajan-code 4.70.4 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (380) 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/retired-commands.js +49 -0
  68. package/src/cli.js +18 -5
  69. package/src/commands/agent-run.js +40 -26
  70. package/src/commands/audit.js +127 -124
  71. package/src/commands/board.js +5 -9
  72. package/src/commands/check.js +29 -16
  73. package/src/commands/doctor.js +2 -0
  74. package/src/commands/env.js +176 -150
  75. package/src/commands/go.js +71 -53
  76. package/src/commands/harden.js +90 -81
  77. package/src/commands/hu-start.js +52 -0
  78. package/src/commands/hu.js +288 -221
  79. package/src/commands/identity.js +111 -78
  80. package/src/commands/init-atril.js +47 -0
  81. package/src/commands/init.js +11 -40
  82. package/src/commands/install-tools.js +70 -85
  83. package/src/commands/main-guard.js +78 -0
  84. package/src/commands/policy.js +226 -184
  85. package/src/commands/qmd.js +27 -21
  86. package/src/commands/rag.js +75 -48
  87. package/src/commands/review-gate.js +421 -341
  88. package/src/commands/start.js +4 -5
  89. package/src/commands/steward.js +96 -47
  90. package/src/commands/tournament.js +46 -37
  91. package/src/commands/worktree.js +94 -25
  92. package/src/config/loader.js +34 -17
  93. package/src/config/secrets.js +36 -0
  94. package/src/config.js +1 -0
  95. package/src/environment/adr.js +16 -2
  96. package/src/git/karajan-label.js +20 -8
  97. package/src/guards/main-guard.js +54 -0
  98. package/src/harden/hook-templates.js +16 -1
  99. package/src/harden/sentinel/posttooluse.mjs +18 -6
  100. package/src/harden/sentinel/pretooluse-sentinel.mjs +64 -36
  101. package/src/harden/sentinel/sentinel-lib.mjs +129 -26
  102. package/src/harden/sentinel/sentinel-shell.mjs +5 -16
  103. package/src/harden/sentinel/sessionstart.mjs +3 -2
  104. package/src/harden/sentinel/stop.mjs +5 -4
  105. package/src/harden/workflow-templates.js +1 -1
  106. package/src/hu/board-lookup.js +29 -0
  107. package/src/hu/gh-identity.js +23 -0
  108. package/src/hu/github-board.js +4 -2
  109. package/src/hu/lane-prune.js +15 -7
  110. package/src/hu/lane-setup.js +65 -0
  111. package/src/hu/lanes.js +66 -0
  112. package/src/hu/team-board-mirror.js +3 -2
  113. package/src/infrastructure/command-runner.js +1 -1
  114. package/src/infrastructure/file-system-service.js +1 -1
  115. package/src/mcp/handlers/direct-handlers.js +2 -292
  116. package/src/mcp/handlers/hu-handlers.js +1 -35
  117. package/src/mcp/handlers/management-handlers.js +2 -42
  118. package/src/mcp/server-handlers.js +16 -22
  119. package/src/mcp/tools.js +1 -196
  120. package/src/plan/lane-plans.js +54 -0
  121. package/src/plan/plan-store.js +7 -4
  122. package/src/policy/add.js +26 -12
  123. package/src/policy/adr-number-check.js +31 -0
  124. package/src/prompts/audit.js +299 -339
  125. package/src/prompts/card-context.js +10 -5
  126. package/src/prompts/reviewer.js +5 -2
  127. package/src/rag/auto-update.js +56 -43
  128. package/src/rag/bootstrap-index.js +28 -54
  129. package/src/rag/migrate.js +1 -1
  130. package/src/rag/onnx-fallback.js +2 -2
  131. package/src/rag/pid-lease.js +70 -0
  132. package/src/rag/project-store.js +2 -12
  133. package/src/review/card-first.js +57 -46
  134. package/src/review/gate-gitignore.js +23 -4
  135. package/src/review/gate-hook.js +12 -4
  136. package/src/review/one-shot-review.js +45 -35
  137. package/src/review/publish-pr.js +14 -2
  138. package/src/review/repo-docs.js +73 -0
  139. package/src/review/repo-stack.js +59 -0
  140. package/src/review/reviewer-fallback.js +19 -4
  141. package/src/review/seal-commit.js +29 -0
  142. package/src/review/sonar-pregate.js +59 -44
  143. package/src/review/third-party-review.js +90 -16
  144. package/src/review/third-party-size.js +43 -0
  145. package/src/review/third-party-skills.js +54 -0
  146. package/src/review/verify-pr.js +1 -24
  147. package/src/roles/audit-role.js +106 -125
  148. package/src/skills/atril-skills.js +5 -4
  149. package/src/sonar/scanner.js +116 -101
  150. package/src/steward/invariants.js +5 -4
  151. package/src/steward/phantom-sweep.js +66 -0
  152. package/src/tournament/judge.js +60 -53
  153. package/src/utils/gh-account.js +37 -0
  154. package/src/utils/git.js +0 -13
  155. package/src/utils/home-migration.js +7 -1
  156. package/src/utils/main-checkout.js +53 -0
  157. package/src/utils/qmd-collection.js +3 -1
  158. package/src/utils/welcome.js +3 -3
  159. package/templates/{skills → atril/commands}/kj-architect.md +5 -0
  160. package/templates/{skills → atril/commands}/kj-audit.md +5 -0
  161. package/templates/{skills → atril/commands}/kj-board.md +5 -0
  162. package/templates/{skills → atril/commands}/kj-code.md +5 -0
  163. package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
  164. package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
  165. package/templates/{skills → atril/commands}/kj-discover.md +5 -0
  166. package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
  167. package/templates/{skills → atril/commands}/kj-review.md +5 -0
  168. package/templates/{skills → atril/commands}/kj-security.md +5 -0
  169. package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
  170. package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
  171. package/templates/{skills → atril/commands}/kj-test.md +5 -0
  172. package/packages/hu-board/public/utils/run-launcher.js +0 -169
  173. package/packages/hu-board/src/run-tracker.js +0 -121
  174. package/src/activity-log.js +0 -59
  175. package/src/agents/host-agent.js +0 -38
  176. package/src/agents/model-registry.js +0 -142
  177. package/src/audit/plan-adherence.js +0 -224
  178. package/src/autonomy/arbiter-role.js +0 -71
  179. package/src/autonomy/arbiter.js +0 -22
  180. package/src/autonomy/decider.js +0 -18
  181. package/src/autonomy/decision-resolver.js +0 -58
  182. package/src/autonomy/outcome-report.js +0 -55
  183. package/src/autonomy/stage-ask.js +0 -30
  184. package/src/brain/decision-tracker.js +0 -125
  185. package/src/brain/decisor.js +0 -243
  186. package/src/brain/solomon-consult.js +0 -171
  187. package/src/brain/standby-scheduler.js +0 -8
  188. package/src/budget/comparison.js +0 -96
  189. package/src/canvas/parse-md.js +0 -238
  190. package/src/canvas/schema.js +0 -101
  191. package/src/canvas/to-flat.js +0 -85
  192. package/src/canvas/validate.js +0 -163
  193. package/src/ci/dispatch.js +0 -99
  194. package/src/ci/index.js +0 -3
  195. package/src/ci/pr-diff.js +0 -26
  196. package/src/cli/register-standby.js +0 -26
  197. package/src/commands/architect.js +0 -111
  198. package/src/commands/autorun.js +0 -49
  199. package/src/commands/researcher.js +0 -54
  200. package/src/commands/resume.js +0 -72
  201. package/src/commands/review.js +0 -89
  202. package/src/commands/run.js +0 -185
  203. package/src/commands/standby.js +0 -117
  204. package/src/commands/status.js +0 -36
  205. package/src/commands/triage.js +0 -78
  206. package/src/commands/undo.js +0 -62
  207. package/src/domains/domain-loader.js +0 -245
  208. package/src/domains/domain-registry.js +0 -140
  209. package/src/domains/domain-synthesizer.js +0 -129
  210. package/src/git/automation.js +0 -433
  211. package/src/git/coder-commit-guard.js +0 -40
  212. package/src/git/hu-automation.js +0 -243
  213. package/src/git/hu-snapshot.js +0 -10
  214. package/src/git/symlink-targets.js +0 -57
  215. package/src/golden/asserter.js +0 -92
  216. package/src/golden/loader.js +0 -52
  217. package/src/golden/runner.js +0 -92
  218. package/src/golden/schema.js +0 -41
  219. package/src/guards/duplicate-members.js +0 -86
  220. package/src/guards/intent-guard.js +0 -161
  221. package/src/guards/perf-guard.js +0 -126
  222. package/src/guards/policy-guard.js +0 -90
  223. package/src/guards/policy-resolver.js +0 -85
  224. package/src/guards/secret-redactor.js +0 -71
  225. package/src/hu/acceptance-runner.js +0 -138
  226. package/src/hu/lazy-planner.js +0 -109
  227. package/src/hu/parallel-executor.js +0 -94
  228. package/src/hu/routing-metrics.js +0 -17
  229. package/src/hu/split-validator.js +0 -143
  230. package/src/hu/splitting-detector.js +0 -158
  231. package/src/hu/splitting-generator.js +0 -175
  232. package/src/hu/store.js +0 -394
  233. package/src/infrastructure/mocks.js +0 -182
  234. package/src/mcp/handlers/run-handler.js +0 -318
  235. package/src/mcp/sovereignty-guard.js +0 -113
  236. package/src/mcp/tool-arg-normalizers.js +0 -16
  237. package/src/mutate/reviewer-signal.js +0 -76
  238. package/src/orchestrator/agent-fallback.js +0 -81
  239. package/src/orchestrator/brain-coordinator.js +0 -186
  240. package/src/orchestrator/ci-integration.js +0 -139
  241. package/src/orchestrator/config-init.js +0 -426
  242. package/src/orchestrator/direct-actions.js +0 -217
  243. package/src/orchestrator/drivers/error-recovery.js +0 -287
  244. package/src/orchestrator/drivers/init-context.js +0 -238
  245. package/src/orchestrator/drivers/iteration-loop.js +0 -382
  246. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
  247. package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
  248. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
  249. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
  250. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
  251. package/src/orchestrator/drivers/post-loop.js +0 -404
  252. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
  253. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
  254. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
  255. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
  256. package/src/orchestrator/drivers/pre-loop.js +0 -524
  257. package/src/orchestrator/drivers/run-hu-batch.js +0 -468
  258. package/src/orchestrator/feedback-enrichment.js +0 -140
  259. package/src/orchestrator/feedback-queue.js +0 -151
  260. package/src/orchestrator/flow-control.js +0 -170
  261. package/src/orchestrator/flow-runner.js +0 -365
  262. package/src/orchestrator/fs-leak-detector.js +0 -228
  263. package/src/orchestrator/hu-scheduler.js +0 -114
  264. package/src/orchestrator/hu-sub-pipeline.js +0 -642
  265. package/src/orchestrator/iteration-gate.js +0 -73
  266. package/src/orchestrator/iteration-stages.js +0 -9
  267. package/src/orchestrator/parallel-limiter.js +0 -74
  268. package/src/orchestrator/pipeline-context.js +0 -72
  269. package/src/orchestrator/post-loop-stages.js +0 -468
  270. package/src/orchestrator/pre-loop-stages.js +0 -11
  271. package/src/orchestrator/preflight-checks.js +0 -487
  272. package/src/orchestrator/repair/repair-detector.js +0 -117
  273. package/src/orchestrator/repair/repair-runner.js +0 -142
  274. package/src/orchestrator/reviewer-fallback.js +0 -45
  275. package/src/orchestrator/role-output-compressor.js +0 -189
  276. package/src/orchestrator/session-journal.js +0 -308
  277. package/src/orchestrator/solomon-escalation.js +0 -331
  278. package/src/orchestrator/solomon-rules.js +0 -212
  279. package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
  280. package/src/orchestrator/stages/architect-stage.js +0 -172
  281. package/src/orchestrator/stages/coder-stage.js +0 -506
  282. package/src/orchestrator/stages/domain-curator-stage.js +0 -82
  283. package/src/orchestrator/stages/format-stage.js +0 -61
  284. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
  285. package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
  286. package/src/orchestrator/stages/perf-stage.js +0 -140
  287. package/src/orchestrator/stages/planner-stage.js +0 -85
  288. package/src/orchestrator/stages/rag-context-stage.js +0 -80
  289. package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
  290. package/src/orchestrator/stages/research-stage.js +0 -69
  291. package/src/orchestrator/stages/reviewer-stage.js +0 -431
  292. package/src/orchestrator/stages/sonar-stage.js +0 -439
  293. package/src/orchestrator/stages/stage-classes.js +0 -84
  294. package/src/orchestrator/stages/stage-executor.js +0 -155
  295. package/src/orchestrator/stages/suite-green-stage.js +0 -62
  296. package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
  297. package/src/orchestrator/stages/tool-judge-stage.js +0 -53
  298. package/src/orchestrator/stages/triage-stage.js +0 -172
  299. package/src/orchestrator/standby.js +0 -71
  300. package/src/orchestrator/verification-gate.js +0 -183
  301. package/src/orchestrator.js +0 -22
  302. package/src/plan/adr-generator.js +0 -170
  303. package/src/plan/adr-loader.js +0 -60
  304. package/src/plan/plan-executor.js +0 -100
  305. package/src/plan/plan-validation.js +0 -4
  306. package/src/prompts/acceptance-coverage.js +0 -32
  307. package/src/prompts/arbiter.js +0 -38
  308. package/src/prompts/architect.js +0 -103
  309. package/src/prompts/hu-reviewer.js +0 -244
  310. package/src/prompts/tool-correctness-judge.js +0 -31
  311. package/src/prompts/triage.js +0 -57
  312. package/src/rag/embedder.js +0 -2
  313. package/src/rag/embedders/_cloud-base.js +0 -2
  314. package/src/rag/embedders/cohere.js +0 -2
  315. package/src/rag/embedders/mistral.js +0 -2
  316. package/src/rag/embedders/onnx.js +0 -2
  317. package/src/rag/embedders/openai.js +0 -2
  318. package/src/rag/embedders/voyage.js +0 -2
  319. package/src/rag/rerank.js +0 -4
  320. package/src/rag/where-parser.js +0 -4
  321. package/src/repeat-detector.js +0 -88
  322. package/src/review/diff-generator.js +0 -112
  323. package/src/review/git-stash-helper.js +0 -104
  324. package/src/review/schema.js +0 -31
  325. package/src/review/scope-filter.js +0 -152
  326. package/src/review/snapshot-diff.js +0 -122
  327. package/src/review/tdd-discipline.js +0 -90
  328. package/src/review/tdd-policy.js +0 -69
  329. package/src/roles/acceptance-coverage-role.js +0 -78
  330. package/src/roles/architect-role.js +0 -77
  331. package/src/roles/coder-role.js +0 -113
  332. package/src/roles/commiter-role.js +0 -113
  333. package/src/roles/domain-curator-role.js +0 -134
  334. package/src/roles/hu-reviewer-role.js +0 -64
  335. package/src/roles/impeccable-role.js +0 -85
  336. package/src/roles/index.js +0 -18
  337. package/src/roles/karajan-brain-role.js +0 -134
  338. package/src/roles/perf-role.js +0 -122
  339. package/src/roles/planner-role.js +0 -151
  340. package/src/roles/refactorer-role.js +0 -30
  341. package/src/roles/repairer-role.js +0 -181
  342. package/src/roles/researcher-role.js +0 -70
  343. package/src/roles/reviewer-role.js +0 -115
  344. package/src/roles/security-role.js +0 -73
  345. package/src/roles/solomon-role.js +0 -271
  346. package/src/roles/sonar-role.js +0 -72
  347. package/src/roles/tester-role.js +0 -235
  348. package/src/roles/tool-correctness-judge-role.js +0 -78
  349. package/src/roles/triage-role.js +0 -99
  350. package/src/session/journal/decisions-writer.js +0 -200
  351. package/src/session/journal/iteration-logger.js +0 -194
  352. package/src/session/journal/summary-writer.js +0 -320
  353. package/src/session/journal/tree-writer.js +0 -164
  354. package/src/session/journal/write-all.js +0 -89
  355. package/src/session-cleanup.js +0 -116
  356. package/src/skills/semantic-detector.js +0 -167
  357. package/src/skills/skill-detector.js +0 -385
  358. package/src/sonar/cloud-scanner.js +0 -78
  359. package/src/tracker-bootstrap.js +0 -44
  360. package/src/types/index.js +0 -38
  361. package/src/types/policy.js +0 -43
  362. package/src/types/solomon.js +0 -87
  363. package/src/utils/async-lock.js +0 -31
  364. package/src/utils/budget.js +0 -229
  365. package/src/utils/cwd-confirm.js +0 -39
  366. package/src/utils/display/event-handlers.js +0 -403
  367. package/src/utils/display/formatters.js +0 -132
  368. package/src/utils/display/header.js +0 -47
  369. package/src/utils/display/session-summary.js +0 -118
  370. package/src/utils/display/solomon.js +0 -42
  371. package/src/utils/pricing.js +0 -29
  372. package/src/utils/quota-simulator.js +0 -56
  373. package/src/utils/role-assigner.js +0 -137
  374. package/src/utils/rtk-wrapper.js +0 -87
  375. package/src/utils/run-registry.js +0 -9
  376. package/src/utils/status-dashboard.js +0 -254
  377. package/src/utils/tool-call-extractor.js +0 -56
  378. package/src/webperf/devtools-detect.js +0 -112
  379. package/src/webperf/webperf-role-integration.js +0 -66
  380. package/templates/skills/kj-run.md +0 -73
@@ -1,235 +0,0 @@
1
- import { AgentRole } from "./agent-role.js";
2
- import { extractFirstJson } from "../utils/json-extract.js";
3
- import { detectTestFramework } from "../utils/project-detect.js";
4
- import { loadAvailableSkills, buildSkillSection } from "../skills/skill-loader.js";
5
-
6
- const SUBAGENT_PREAMBLE = [
7
- "IMPORTANT: You are running as a Karajan sub-agent.",
8
- "Do NOT ask about using Karajan, do NOT mention Karajan, do NOT suggest orchestration.",
9
- "Do NOT use any MCP tools. Focus only on evaluating test quality."
10
- ].join(" ");
11
-
12
- /**
13
- * Map test framework to the command that runs tests with coverage.
14
- */
15
- const COVERAGE_COMMANDS = {
16
- vitest: "npx vitest run --coverage --reporter=verbose 2>&1",
17
- jest: "npx jest --coverage --verbose 2>&1",
18
- mocha: "npx c8 mocha 2>&1",
19
- playwright: "npx playwright test 2>&1",
20
- pytest: "pytest --cov --tb=short 2>&1",
21
- "go-test": "go test -cover -v ./... 2>&1",
22
- "cargo-test": "cargo test --verbose 2>&1",
23
- junit: "mvn test 2>&1",
24
- rspec: "bundle exec rspec 2>&1",
25
- phpunit: "vendor/bin/phpunit --coverage-text 2>&1",
26
- "dotnet-test": "dotnet test --collect:\"XPlat Code Coverage\" 2>&1",
27
- "dart-test": "dart test 2>&1",
28
- // INF-B: the basic suite ALWAYS runs first; checkov is the additive deep
29
- // scan. If checkov is missing this chain fails and the brief falls back to
30
- // the basic suite alone (declared degradation, never green-by-omission).
31
- "terraform-validate": "terraform init -backend=false -input=false 2>&1 && terraform validate 2>&1 && checkov -d . --compact --quiet 2>&1",
32
- "helm-lint": "helm lint . 2>&1 && checkov -d . --compact --quiet 2>&1",
33
- "kustomize-build": "kustomize build . > /dev/null && checkov -d . --compact --quiet 2>&1",
34
- "ansible-lint": "ansible-lint 2>&1 && checkov -d . --compact --quiet 2>&1"
35
- };
36
-
37
- const TEST_COMMANDS = {
38
- vitest: "npx vitest run --reporter=verbose 2>&1",
39
- jest: "npx jest --verbose 2>&1",
40
- mocha: "npx mocha 2>&1",
41
- playwright: "npx playwright test 2>&1",
42
- pytest: "pytest --tb=short 2>&1",
43
- "go-test": "go test -v ./... 2>&1",
44
- "cargo-test": "cargo test --verbose 2>&1",
45
- junit: "mvn test 2>&1",
46
- rspec: "bundle exec rspec 2>&1",
47
- phpunit: "vendor/bin/phpunit 2>&1",
48
- "dotnet-test": "dotnet test 2>&1",
49
- "dart-test": "dart test 2>&1",
50
- // INF-B (KJC-TSK-0759): infra suites — validity IS the test floor.
51
- "terraform-validate": "terraform init -backend=false -input=false 2>&1 && terraform validate 2>&1",
52
- "helm-lint": "helm lint . 2>&1",
53
- "kustomize-build": "kustomize build . > /dev/null && echo kustomize build: OK",
54
- "ansible-lint": "ansible-lint 2>&1"
55
- };
56
-
57
- export class TesterRole extends AgentRole {
58
- constructor(opts) {
59
- super({ ...opts, name: "tester" });
60
- }
61
-
62
- extractInput(input) {
63
- if (typeof input === "string") return { task: input, diff: null, sonarIssues: null, pendingGherkinTests: null, shellTestResults: null };
64
- return {
65
- task: input?.task || this.context?.task || "",
66
- diff: input?.diff || null,
67
- sonarIssues: input?.sonarIssues || null,
68
- // Tests-first Phase 3 (v2.7.5): the tester now has two extra inputs.
69
- // - pendingGherkinTests: HU-declared Gherkin scenarios that need
70
- // translation into executable code tests in the project's
71
- // framework. The tester writes the test files and includes them
72
- // in the suite run before computing verdict.
73
- // - shellTestResults: results of running the HU's shell-type
74
- // acceptance_tests (already run upstream, passed at this point).
75
- // Passed for context so the tester doesn't re-run them redundantly.
76
- pendingGherkinTests: input?.pendingGherkinTests || null,
77
- shellTestResults: input?.shellTestResults || null,
78
- onOutput: input?.onOutput || null
79
- };
80
- }
81
-
82
- async buildPrompt({ task, diff, sonarIssues, pendingGherkinTests, shellTestResults }) {
83
- const projectDir = this.config?.projectDir || process.cwd();
84
- const detection = await detectTestFramework(projectDir);
85
-
86
- const sections = [SUBAGENT_PREAMBLE];
87
- if (this.instructions) sections.push(this.instructions);
88
-
89
- sections.push(
90
- "You are a test quality gate. Your job is to EXECUTE the test suite, measure REAL coverage, and evaluate quality.",
91
- "You MUST run the actual test command — do NOT guess or estimate results."
92
- );
93
-
94
- if (detection.hasTests && detection.framework) {
95
- const coverageCmd = COVERAGE_COMMANDS[detection.framework];
96
- const testCmd = TEST_COMMANDS[detection.framework];
97
- // INF-B: infra suites have no node_modules — the step-0 contract is
98
- // "the tool exists or you SAY which one is missing", never a fake green.
99
- const step0 = detection.language === "infra"
100
- ? "**Step 0**: Verify the SUITE tool is installed (`which terraform`/`helm`/`kustomize`/`ansible-lint`). If the suite tool is MISSING, do NOT fake a green: report it in `failures` with the exact install command and set tests_pass: false. `checkov` (the deep scan below) is OPTIONAL: if it is missing, run the basic suite as the fallback and SAY the deep scan was skipped (degraded, never green-by-omission)."
101
- : "**Step 0**: If node_modules/ does not exist, run `npm install` (or `pnpm install`) first.";
102
- sections.push(
103
- `## Detected test framework: ${detection.framework} (${detection.language})`,
104
- step0,
105
- `**Step 1**: Run the test suite with coverage:`,
106
- "```bash",
107
- coverageCmd || testCmd,
108
- "```",
109
- "If the coverage command fails (missing dependency), fall back to:",
110
- "```bash",
111
- testCmd,
112
- "```",
113
- "**Step 2**: Parse the output to extract: pass/fail count, coverage percentages, any failures.",
114
- "**Step 3**: Return a single JSON object with REAL numbers from the test output."
115
- );
116
- } else {
117
- sections.push(
118
- "## No test framework detected",
119
- "**Step 0**: If package.json exists but node_modules/ does not, run `npm install` first.",
120
- "**Step 1**: Look at package.json or project files to find test scripts.",
121
- "**Step 2**: Try running `npm test` or detect the framework from config files.",
122
- "**Step 3**: If tests exist, run them. If no tests exist, report tests_pass: false with verdict: 'fail'.",
123
- "**Step 4**: Return a JSON object with your findings."
124
- );
125
- }
126
-
127
- // Tests-first Phase 3 (v2.7.5): include the upstream shell-test results
128
- // so the tester knows which part of the contract is already green.
129
- if (Array.isArray(shellTestResults) && shellTestResults.length > 0) {
130
- const passedCount = shellTestResults.filter((r) => r.passed).length;
131
- sections.push(
132
- "## Acceptance gate — shell tests",
133
- `The HU's shell-type acceptance_tests already ran upstream: ${passedCount}/${shellTestResults.length} passed.`,
134
- "Do NOT re-run them. Use the coverage/translation steps below to decide your verdict."
135
- );
136
- }
137
-
138
- // Gherkin translation block — only when the HU carries Gherkin
139
- // scenarios. Tester must produce real test files (in the detected
140
- // framework's language) that encode Given/When/Then. This is how
141
- // behaviour-level specs become enforceable.
142
- if (Array.isArray(pendingGherkinTests) && pendingGherkinTests.length > 0) {
143
- sections.push(
144
- "## Gherkin acceptance tests to translate",
145
- "These Gherkin scenarios are the HU's behavioural contract. They are NOT yet",
146
- "executable — your job is to turn each one into a concrete test case in the",
147
- "project's test framework, WRITE the test files, and INCLUDE them in the suite",
148
- "run that determines your verdict.",
149
- "",
150
- "For each scenario below:",
151
- "1. Pick the appropriate test file (use `target` hint if present; otherwise",
152
- " put the test next to the module it exercises).",
153
- "2. Write a test that asserts the Given/When/Then as-is. Do NOT soften the",
154
- " assertion — if it's ambiguous, pick the stricter reading.",
155
- "3. Make sure the test runs as part of the normal suite (no separate invocation).",
156
- "",
157
- "If ANY translated test fails on the current code, return verdict: \"fail\" with",
158
- "a failing_scenarios array listing the scenarios that didn't pass. The coder",
159
- "will iterate.",
160
- ""
161
- );
162
- pendingGherkinTests.forEach((entry, i) => {
163
- const content = typeof entry === "string" ? entry : entry?.content || "";
164
- const target = entry && entry.file ? ` · target: \`${entry.file}\`` : "";
165
- sections.push(
166
- `### Scenario ${i + 1}${target}`,
167
- "```gherkin",
168
- content,
169
- "```",
170
- ""
171
- );
172
- });
173
- }
174
-
175
- sections.push(
176
- "",
177
- "Return ONLY a single valid JSON object:",
178
- '{"tests_pass":boolean,"coverage":{"overall":number,"services":number,"utilities":number},"missing_scenarios":[string],"quality_issues":[string],"failing_scenarios":[string],"translated_scenarios":[string],"verdict":"pass"|"fail"}',
179
- "",
180
- "- coverage.overall MUST be a real number from the test runner output, NOT an estimate",
181
- "- If coverage tool is not available, set coverage.overall to null (not 0, not a guess)",
182
- "- tests_pass must reflect whether the actual test run succeeded",
183
- "- failing_scenarios: when Gherkin was given, list the scenarios your translated tests CAUGHT as failing",
184
- "- translated_scenarios: when Gherkin was given, list the scenarios you successfully turned into passing tests",
185
- "- verdict MUST be \"fail\" if any translated scenario still fails (even if coverage is green)",
186
- `## Task\n${task}`
187
- );
188
- if (diff) sections.push(`## Git diff\n${diff}`);
189
- if (sonarIssues) sections.push(`## Sonar test issues\n${sonarIssues}`);
190
-
191
- // Inject test-relevant skills (filtered by role=tester to only include
192
- // testing patterns like pytest-patterns, vitest-patterns, etc.)
193
- const skills = await loadAvailableSkills(projectDir);
194
- const skillSection = buildSkillSection(skills, {
195
- provider: this._resolvedProvider || (typeof this.resolveProvider === "function" ? this.resolveProvider() : null),
196
- role: "tester",
197
- });
198
- if (skillSection) sections.push(skillSection);
199
-
200
- return { prompt: sections.join("\n\n") };
201
- }
202
-
203
- parseOutput(raw) { return extractFirstJson(raw); }
204
-
205
- isSuccessful(parsed) {
206
- const verdict = parsed.verdict || (parsed.tests_pass ? "pass" : "fail");
207
- return verdict === "pass";
208
- }
209
-
210
- buildSuccessResult(parsed, provider) {
211
- const verdict = parsed.verdict || (parsed.tests_pass ? "pass" : "fail");
212
- return {
213
- tests_pass: Boolean(parsed.tests_pass),
214
- coverage: parsed.coverage || {},
215
- missing_scenarios: parsed.missing_scenarios || [],
216
- quality_issues: parsed.quality_issues || [],
217
- // Tests-first Phase 3: Gherkin translation outcomes.
218
- failing_scenarios: Array.isArray(parsed.failing_scenarios) ? parsed.failing_scenarios : [],
219
- translated_scenarios: Array.isArray(parsed.translated_scenarios) ? parsed.translated_scenarios : [],
220
- verdict,
221
- provider
222
- };
223
- }
224
-
225
- buildSummary(parsed) {
226
- const verdict = parsed.verdict || (parsed.tests_pass ? "pass" : "fail");
227
- const coverage = parsed.coverage || {};
228
- const coverageStr = coverage.overall != null ? `${coverage.overall}%` : "not measured";
229
- const missingPart = parsed.missing_scenarios?.length ? `; ${parsed.missing_scenarios.length} missing scenario(s)` : "";
230
- const qualityPart = parsed.quality_issues?.length ? `; ${parsed.quality_issues.length} quality issue(s)` : "";
231
- const failingPart = parsed.failing_scenarios?.length ? `; ${parsed.failing_scenarios.length} failing scenario(s)` : "";
232
- const translatedPart = parsed.translated_scenarios?.length ? `; ${parsed.translated_scenarios.length} scenario(s) translated` : "";
233
- return `Verdict: ${verdict}; Coverage: ${coverageStr}${translatedPart}${failingPart}${missingPart}${qualityPart}`;
234
- }
235
- }
@@ -1,78 +0,0 @@
1
- // KJC-TSK-0375 — ToolCorrectnessJudgeRole.
2
- // Post-iteration LLM judge scoring how well a coder picked its tool calls.
3
- // PR1 ships role + tests; wiring lives in PR3.
4
-
5
- import { AgentRole } from "./agent-role.js";
6
- import { buildToolCorrectnessJudgePrompt } from "../prompts/tool-correctness-judge.js";
7
- import { extractFirstJson } from "../utils/json-extract.js";
8
-
9
- const DEFAULT_THRESHOLD = 70;
10
-
11
- const clampScore = (n) => Number.isFinite(n) ? Math.max(0, Math.min(100, Math.round(n))) : null;
12
-
13
- const normalizeBreakdown = (b) => Array.isArray(b) ? b.map((x) => ({
14
- tool: typeof x?.tool === "string" ? x.tool : "unknown",
15
- args: x?.args && typeof x.args === "object" ? x.args : {},
16
- correct: Boolean(x?.correct),
17
- reason: typeof x?.reason === "string" ? x.reason.trim() : "",
18
- })) : [];
19
-
20
- export class ToolCorrectnessJudgeRole extends AgentRole {
21
- constructor(opts) {
22
- super({ ...opts, name: "tool-correctness-judge" });
23
- this.threshold = opts?.threshold ?? this.config?.pipeline?.tool_judge?.threshold ?? DEFAULT_THRESHOLD;
24
- }
25
-
26
- resolveProvider() {
27
- return this.config?.roles?.["tool-correctness-judge"]?.provider
28
- || this.config?.roles?.reviewer?.provider
29
- || this.config?.roles?.coder?.provider
30
- || "claude";
31
- }
32
-
33
- extractInput(input) {
34
- return { ...super.extractInput(input), toolCalls: Array.isArray(input?.toolCalls) ? input.toolCalls : [] };
35
- }
36
-
37
- async buildPrompt({ task, toolCalls }) {
38
- return { prompt: buildToolCorrectnessJudgePrompt({ task, toolCalls, instructions: this.instructions }) };
39
- }
40
-
41
- parseOutput(raw) { return extractFirstJson(raw); }
42
-
43
- buildSuccessResult(parsed, provider) {
44
- const score = clampScore(parsed?.score);
45
- return {
46
- score,
47
- summary: typeof parsed?.summary === "string" ? parsed.summary.trim() : "",
48
- breakdown: normalizeBreakdown(parsed?.breakdown),
49
- threshold: this.threshold,
50
- lowQuality: score !== null && score < this.threshold,
51
- provider,
52
- };
53
- }
54
-
55
- buildSummary(parsed) {
56
- const score = clampScore(parsed?.score);
57
- if (score === null) return "Tool-judge: unparseable score";
58
- return `Tool-judge: ${score}/100 (${score < this.threshold ? "LOW" : "OK"})`;
59
- }
60
-
61
- // Empty tool-call list = nothing to judge. Skip the LLM and report a
62
- // neutral score so an absent transcript is never read as a failure.
63
- async execute(input) {
64
- const extracted = this.extractInput(input);
65
- if (extracted.toolCalls.length === 0) {
66
- return {
67
- ok: true,
68
- result: {
69
- score: null, summary: "no tool calls captured (skipped)", breakdown: [],
70
- threshold: this.threshold, lowQuality: false, provider: this.resolveProvider(), skipped: true,
71
- },
72
- summary: "Tool-judge: skipped (no calls)",
73
- usage: null,
74
- };
75
- }
76
- return super.execute(extracted);
77
- }
78
- }
@@ -1,99 +0,0 @@
1
- import { AgentRole } from "./agent-role.js";
2
- import { buildTriagePrompt } from "../prompts/triage.js";
3
- import { VALID_TASK_TYPES } from "../guards/policy-resolver.js";
4
- import { extractFirstJson } from "../utils/json-extract.js";
5
-
6
- const VALID_LEVELS = new Set(["trivial", "simple", "medium", "complex"]);
7
- const VALID_ROLES = new Set(["planner", "researcher", "refactorer", "reviewer", "tester", "security", "impeccable"]);
8
- const FALLBACK_TASK_TYPE = "sw";
9
- const FALLBACK_RESULT = { level: "medium", roles: ["reviewer"], taskType: FALLBACK_TASK_TYPE, shouldDecompose: false };
10
-
11
- function normalizeRoles(roles) {
12
- if (!Array.isArray(roles)) return [];
13
- return Array.from(new Set(roles.filter((role) => VALID_ROLES.has(role))));
14
- }
15
-
16
- function normalizeSubtasks(subtasks) {
17
- if (!Array.isArray(subtasks)) return [];
18
- return subtasks.map((s) => (typeof s === "string" ? s.trim() : "")).filter(Boolean).slice(0, 5);
19
- }
20
-
21
- function normalizeDomainHints(hints) {
22
- if (!Array.isArray(hints)) return [];
23
- return hints.map((h) => (typeof h === "string" ? h.trim().toLowerCase() : "")).filter(Boolean);
24
- }
25
-
26
- export class TriageRole extends AgentRole {
27
- constructor(opts) {
28
- super({ ...opts, name: "triage" });
29
- }
30
-
31
- async buildPrompt({ task }) {
32
- return { prompt: buildTriagePrompt({ task, instructions: this.instructions }) };
33
- }
34
-
35
- parseOutput(raw) { return extractFirstJson(raw); }
36
-
37
- buildSuccessResult(parsed, provider) {
38
- const level = VALID_LEVELS.has(parsed.level) ? parsed.level : "medium";
39
- const roles = normalizeRoles(parsed.roles);
40
- const reasoning = String(parsed.reasoning || "").trim() || "No reasoning provided.";
41
- const shouldDecompose = Boolean(parsed.shouldDecompose);
42
- const subtasks = normalizeSubtasks(parsed.subtasks);
43
- const taskType = VALID_TASK_TYPES.has(parsed.taskType) ? parsed.taskType : FALLBACK_TASK_TYPE;
44
- const domainHints = normalizeDomainHints(parsed.domainHints);
45
-
46
- const result = { level, roles, reasoning, taskType, domainHints, provider };
47
- if (shouldDecompose && subtasks.length > 0) {
48
- result.shouldDecompose = true;
49
- result.subtasks = subtasks;
50
- } else {
51
- result.shouldDecompose = false;
52
- }
53
- return result;
54
- }
55
-
56
- buildSummary(parsed) {
57
- const level = VALID_LEVELS.has(parsed.level) ? parsed.level : "medium";
58
- const roles = normalizeRoles(parsed.roles);
59
- const decomposeNote = parsed.shouldDecompose ? " — decomposition recommended" : "";
60
- return `Triage: ${level} (${roles.length} role${roles.length === 1 ? "" : "s"})${decomposeNote}`;
61
- }
62
-
63
- handleParseNull(agentResult, provider) {
64
- return this.degradedTriageResult({
65
- agentResult, provider,
66
- reason: "parse-null",
67
- reasoning: "Unstructured output, using safe defaults.",
68
- message: "[triage] LLM output was unstructured — falling back to defaults; the pipeline may skip optional roles.",
69
- });
70
- }
71
-
72
- handleParseError(err, agentResult, provider) {
73
- return this.degradedTriageResult({
74
- agentResult, provider,
75
- reason: "parse-error",
76
- reasoning: `Failed to parse triage output (${err?.message || "unknown error"}), using safe defaults.`,
77
- message: `[triage] Could not parse LLM output — falling back to defaults: ${err?.message || "unknown"}.`,
78
- });
79
- }
80
-
81
- // Triage degradation is what made a complex task quietly run as
82
- // `level: "medium", roles: ["reviewer"]` — researcher / architect /
83
- // security / tester silently skipped. The fallback is preserved so
84
- // the pipeline still moves, but the user (and any board / kj-tail
85
- // listener) now learns it happened. result.degraded = true lets
86
- // downstream code react if it wants.
87
- // Same shape as before so downstream cost / event accounting is
88
- // byte-identical; the only change is the loud logger.warn, which is
89
- // what makes the silent degradation visible to the user / kj-tail.
90
- degradedTriageResult({ agentResult, provider, reason: _reason, reasoning, message }) {
91
- this.logger?.warn?.(message);
92
- return {
93
- ok: true,
94
- result: { ...FALLBACK_RESULT, reasoning, provider, raw: agentResult.output },
95
- summary: "Triage complete (fallback defaults)",
96
- usage: agentResult.usage,
97
- };
98
- }
99
- }
@@ -1,200 +0,0 @@
1
- /**
2
- * Build a human-readable `decisions.md` journal file summarizing every
3
- * Solomon ruling and Brain decision made during a session.
4
- *
5
- * Acceptance criterion (KJC-TSK-0287):
6
- * - Given Solomon was invoked at least once → decisions.md is written with
7
- * timestamp, trigger, conflict context, ruling (action + reasoning),
8
- * and resulting actions for every invocation.
9
- * - Given Solomon was NOT invoked → decisions.md is NOT created.
10
- *
11
- * Brain routing decisions (from KJC-TSK-0322) are appended as a secondary
12
- * section when present, since they share the same "decisions" semantics.
13
- */
14
-
15
- import fs from "node:fs/promises";
16
- import path from "node:path";
17
- import { getSessionRoot } from "../../utils/paths.js";
18
-
19
- /**
20
- * @typedef {Object} SolomonRuling
21
- * @property {string} at - ISO timestamp
22
- * @property {string} stage - conflict stage (reviewer, brain-routing, ...)
23
- * @property {string} trigger - short reason the invocation was triggered
24
- * @property {Object} conflict - conflict context (task, reason, summary)
25
- * @property {string} ruling - approve / continue / escalate_human / create_subtask / ...
26
- * @property {string} [reasoning]
27
- * @property {string[]} [conditions]
28
- * @property {string} [alternativeAgent]
29
- * @property {string} [result] - what the orchestrator did with the ruling
30
- */
31
-
32
- /**
33
- * @typedef {Object} BrainDecisionRecord
34
- * @property {number} seq
35
- * @property {string} at
36
- * @property {string} intent
37
- * @property {string[]} rolesOn
38
- * @property {string} confidence
39
- * @property {string} rationale
40
- * @property {string[]} appliedOverrides
41
- */
42
-
43
- function esc(text) {
44
- return String(text ?? "").replaceAll(/\|/g, "\\|");
45
- }
46
-
47
- function formatConditions(conditions) {
48
- if (!Array.isArray(conditions) || conditions.length === 0) return "—";
49
- return conditions.map((c) => `- ${esc(c)}`).join("\n");
50
- }
51
-
52
- function formatConflict(conflict) {
53
- if (!conflict) return "—";
54
- const parts = [];
55
- if (conflict.task) parts.push(`**Task**: ${esc(conflict.task.slice(0, 200))}${conflict.task.length > 200 ? "…" : ""}`);
56
- if (conflict.reason) parts.push(`**Reason**: ${esc(conflict.reason)}`);
57
- if (conflict.summary) parts.push(`**Summary**: ${esc(conflict.summary)}`);
58
- return parts.length > 0 ? parts.join("\n") : "—";
59
- }
60
-
61
- /**
62
- * Render a Solomon ruling as a Markdown subsection.
63
- * @param {SolomonRuling} ruling
64
- * @param {number} index
65
- * @returns {string}
66
- */
67
- function renderRuling(ruling, index) {
68
- const at = ruling.at || "(unknown timestamp)";
69
- const header = `### ${index + 1}. Solomon @ ${at} — ${esc(ruling.ruling || "unknown")}`;
70
- const lines = [
71
- header,
72
- "",
73
- `- **Stage**: ${esc(ruling.stage || "?")}`,
74
- `- **Trigger**: ${esc(ruling.trigger || "?")}`,
75
- `- **Ruling**: \`${esc(ruling.ruling || "?")}\``,
76
- ];
77
- if (ruling.alternativeAgent) lines.push(`- **Alternative agent**: ${esc(ruling.alternativeAgent)}`);
78
- if (ruling.result) lines.push(`- **Applied result**: ${esc(ruling.result)}`);
79
- lines.push("");
80
- if (ruling.reasoning) {
81
- lines.push("**Reasoning:**", "");
82
- lines.push(ruling.reasoning.trim(), "");
83
- }
84
- if (ruling.conditions?.length) {
85
- lines.push("**Conditions:**", "");
86
- lines.push(formatConditions(ruling.conditions), "");
87
- }
88
- lines.push("**Conflict context:**", "");
89
- lines.push(formatConflict(ruling.conflict), "");
90
- return lines.join("\n");
91
- }
92
-
93
- function renderBrainDecision(decision, index) {
94
- return [
95
- `### ${index + 1}. Brain @ ${decision.at} — ${esc(decision.intent)}`,
96
- "",
97
- `- **Confidence**: ${esc(decision.confidence)}`,
98
- `- **Roles on**: ${(decision.rolesOn || []).map(esc).join(", ") || "—"}`,
99
- `- **Overrides applied**: ${(decision.appliedOverrides || []).map(esc).join(", ") || "—"}`,
100
- "",
101
- `> ${esc(decision.rationale || "")}`,
102
- "",
103
- ].join("\n");
104
- }
105
-
106
- /**
107
- * Build the Markdown content for a session. Returns `null` if no Solomon
108
- * rulings are present (per acceptance criteria — no Solomon, no file).
109
- *
110
- * @param {Object} session
111
- * @returns {string|null}
112
- */
113
- export function buildDecisionsMarkdown(session) {
114
- const rulings = Array.isArray(session?.solomonRulings) ? session.solomonRulings : [];
115
- const brainDecisions = Array.isArray(session?.brainDecisions) ? session.brainDecisions : [];
116
-
117
- // AC: no Solomon invocation → do not generate the file.
118
- if (rulings.length === 0) return null;
119
-
120
- const sessionId = session?.id || "(unknown)";
121
- const header = [
122
- `# Session decisions — ${sessionId}`,
123
- "",
124
- `_Generated at ${new Date().toISOString()}._`,
125
- "",
126
- `This journal records every decision the pipeline delegated to Solomon, plus Brain routing decisions for cross-reference.`,
127
- "",
128
- "## Summary",
129
- "",
130
- `- Solomon invocations: **${rulings.length}**`,
131
- `- Brain routing decisions: **${brainDecisions.length}**`,
132
- "",
133
- "---",
134
- "",
135
- "## Solomon rulings",
136
- "",
137
- ].join("\n");
138
-
139
- const solomonSection = rulings.map((r, i) => renderRuling(r, i)).join("\n---\n\n");
140
-
141
- const brainSection = brainDecisions.length > 0
142
- ? "\n---\n\n## Brain routing decisions\n\n" + brainDecisions.map((d, i) => renderBrainDecision(d, i)).join("\n")
143
- : "";
144
-
145
- return header + solomonSection + brainSection + "\n";
146
- }
147
-
148
- /**
149
- * Persist decisions.md to the session's journal directory. No-op when there
150
- * are no Solomon rulings (per acceptance criteria).
151
- *
152
- * Path resolution (first match wins):
153
- * 1. options.journalDir — direct path to the session journal directory
154
- * 2. options.sessionRoot + id — combines with session.id
155
- * 3. getSessionRoot() + id — default Karajan session root
156
- *
157
- * @param {Object} session
158
- * @param {{ journalDir?: string, sessionRoot?: string, logger?: Object }} [options]
159
- * @returns {Promise<{ written: boolean, path: string|null }>}
160
- */
161
- export async function writeDecisionsJournal(session, options = {}) {
162
- const content = buildDecisionsMarkdown(session);
163
- if (content === null) return { written: false, path: null };
164
-
165
- const sessionDir = options.journalDir
166
- || path.join(options.sessionRoot || getSessionRoot(), session.id);
167
- const filePath = path.join(sessionDir, "decisions.md");
168
- try {
169
- await fs.mkdir(sessionDir, { recursive: true });
170
- await fs.writeFile(filePath, content, "utf8");
171
- options.logger?.info?.(`Wrote decisions journal: ${filePath}`);
172
- return { written: true, path: filePath };
173
- } catch (err) {
174
- options.logger?.warn?.(`Failed to write decisions journal (non-blocking): ${err.message}`);
175
- return { written: false, path: null };
176
- }
177
- }
178
-
179
- /**
180
- * Append a Solomon ruling to session.solomonRulings. Creates the array if
181
- * absent. Callers (invokeSolomon) pass the structured record.
182
- *
183
- * @param {Object} session
184
- * @param {SolomonRuling} record
185
- */
186
- export function recordSolomonRuling(session, record) {
187
- if (!session) return;
188
- if (!Array.isArray(session.solomonRulings)) session.solomonRulings = [];
189
- session.solomonRulings.push({
190
- at: record.at || new Date().toISOString(),
191
- stage: record.stage || "unknown",
192
- trigger: record.trigger || "",
193
- conflict: record.conflict || {},
194
- ruling: record.ruling || "unknown",
195
- reasoning: record.reasoning || "",
196
- conditions: Array.isArray(record.conditions) ? [...record.conditions] : [],
197
- alternativeAgent: record.alternativeAgent || null,
198
- result: record.result || "",
199
- });
200
- }