karajan-code 4.70.3 → 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 (388) 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/ai-surface.js +9 -7
  61. package/src/checks/config-files.js +8 -7
  62. package/src/checks/dir-setup.js +34 -1
  63. package/src/checks/native-build.js +21 -39
  64. package/src/checks/runner.js +128 -142
  65. package/src/cli/advanced-commands.js +6 -6
  66. package/src/cli/register-meta.js +42 -82
  67. package/src/cli/register-pipeline.js +8 -140
  68. package/src/cli/register-plan.js +9 -100
  69. package/src/cli/retired-commands.js +49 -0
  70. package/src/cli.js +18 -5
  71. package/src/commands/agent-run.js +40 -26
  72. package/src/commands/audit.js +127 -124
  73. package/src/commands/board.js +5 -9
  74. package/src/commands/check.js +29 -16
  75. package/src/commands/doctor.js +2 -0
  76. package/src/commands/env.js +176 -148
  77. package/src/commands/go.js +76 -55
  78. package/src/commands/harden.js +105 -86
  79. package/src/commands/hu-start.js +52 -0
  80. package/src/commands/hu.js +288 -221
  81. package/src/commands/identity.js +111 -78
  82. package/src/commands/init-atril.js +47 -0
  83. package/src/commands/init.js +11 -40
  84. package/src/commands/install-tools.js +70 -85
  85. package/src/commands/main-guard.js +78 -0
  86. package/src/commands/policy.js +226 -184
  87. package/src/commands/qmd.js +27 -21
  88. package/src/commands/rag.js +75 -48
  89. package/src/commands/review-gate.js +422 -339
  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 +94 -25
  94. package/src/config/loader.js +34 -17
  95. package/src/config/overrides.js +2 -1
  96. package/src/config/secrets.js +36 -0
  97. package/src/config.js +1 -0
  98. package/src/environment/adr.js +16 -2
  99. package/src/environment/board-access.js +11 -9
  100. package/src/git/karajan-label.js +20 -8
  101. package/src/guards/main-guard.js +54 -0
  102. package/src/harden/branch-protection.js +36 -3
  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 +81 -35
  106. package/src/harden/sentinel/sentinel-lib.mjs +149 -23
  107. package/src/harden/sentinel/sentinel-shell.mjs +64 -0
  108. package/src/harden/sentinel/sessionstart.mjs +3 -2
  109. package/src/harden/sentinel/stop.mjs +5 -4
  110. package/src/harden/workflow-engine.js +12 -1
  111. package/src/harden/workflow-templates.js +1 -1
  112. package/src/hu/board-lookup.js +29 -0
  113. package/src/hu/gh-identity.js +23 -0
  114. package/src/hu/github-board.js +4 -2
  115. package/src/hu/lane-prune.js +15 -7
  116. package/src/hu/lane-setup.js +65 -0
  117. package/src/hu/lanes.js +66 -0
  118. package/src/hu/team-board-mirror.js +3 -2
  119. package/src/infrastructure/command-runner.js +1 -1
  120. package/src/infrastructure/file-system-service.js +1 -1
  121. package/src/mcp/handlers/direct-handlers.js +2 -292
  122. package/src/mcp/handlers/hu-handlers.js +44 -55
  123. package/src/mcp/handlers/management-handlers.js +2 -42
  124. package/src/mcp/server-handlers.js +16 -22
  125. package/src/mcp/tools.js +5 -200
  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 +56 -43
  134. package/src/rag/bootstrap-index.js +28 -54
  135. package/src/rag/migrate.js +1 -1
  136. package/src/rag/onnx-fallback.js +2 -2
  137. package/src/rag/pid-lease.js +70 -0
  138. package/src/rag/project-store.js +2 -12
  139. package/src/review/card-first.js +57 -46
  140. package/src/review/dual-review.js +5 -4
  141. package/src/review/gate-gitignore.js +23 -4
  142. package/src/review/gate-hook.js +47 -0
  143. package/src/review/one-shot-review.js +51 -37
  144. package/src/review/publish-pr.js +18 -3
  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 +34 -3
  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 +2 -26
  154. package/src/roles/audit-role.js +106 -125
  155. package/src/skills/atril-skills.js +5 -4
  156. package/src/sonar/scanner.js +116 -101
  157. package/src/steward/invariants.js +5 -4
  158. package/src/steward/phantom-sweep.js +66 -0
  159. package/src/tournament/judge.js +60 -53
  160. package/src/utils/agent-home.js +33 -0
  161. package/src/utils/gh-account.js +37 -0
  162. package/src/utils/git.js +0 -13
  163. package/src/utils/home-migration.js +7 -1
  164. package/src/utils/main-checkout.js +53 -0
  165. package/src/utils/qmd-collection.js +3 -1
  166. package/src/utils/welcome.js +3 -3
  167. package/templates/{skills → atril/commands}/kj-architect.md +5 -0
  168. package/templates/{skills → atril/commands}/kj-audit.md +5 -0
  169. package/templates/{skills → atril/commands}/kj-board.md +5 -0
  170. package/templates/{skills → atril/commands}/kj-code.md +5 -0
  171. package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
  172. package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
  173. package/templates/{skills → atril/commands}/kj-discover.md +5 -0
  174. package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
  175. package/templates/{skills → atril/commands}/kj-review.md +5 -0
  176. package/templates/{skills → atril/commands}/kj-security.md +5 -0
  177. package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
  178. package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
  179. package/templates/{skills → atril/commands}/kj-test.md +5 -0
  180. package/packages/hu-board/public/utils/run-launcher.js +0 -169
  181. package/packages/hu-board/src/run-tracker.js +0 -121
  182. package/src/activity-log.js +0 -59
  183. package/src/agents/host-agent.js +0 -38
  184. package/src/agents/model-registry.js +0 -142
  185. package/src/audit/plan-adherence.js +0 -224
  186. package/src/autonomy/arbiter-role.js +0 -71
  187. package/src/autonomy/arbiter.js +0 -22
  188. package/src/autonomy/decider.js +0 -18
  189. package/src/autonomy/decision-resolver.js +0 -58
  190. package/src/autonomy/outcome-report.js +0 -55
  191. package/src/autonomy/stage-ask.js +0 -30
  192. package/src/brain/decision-tracker.js +0 -125
  193. package/src/brain/decisor.js +0 -243
  194. package/src/brain/solomon-consult.js +0 -171
  195. package/src/brain/standby-scheduler.js +0 -8
  196. package/src/budget/comparison.js +0 -96
  197. package/src/canvas/parse-md.js +0 -238
  198. package/src/canvas/schema.js +0 -101
  199. package/src/canvas/to-flat.js +0 -85
  200. package/src/canvas/validate.js +0 -163
  201. package/src/ci/dispatch.js +0 -99
  202. package/src/ci/index.js +0 -3
  203. package/src/ci/pr-diff.js +0 -26
  204. package/src/cli/register-standby.js +0 -26
  205. package/src/commands/architect.js +0 -111
  206. package/src/commands/autorun.js +0 -49
  207. package/src/commands/researcher.js +0 -54
  208. package/src/commands/resume.js +0 -72
  209. package/src/commands/review.js +0 -89
  210. package/src/commands/run.js +0 -185
  211. package/src/commands/standby.js +0 -117
  212. package/src/commands/status.js +0 -36
  213. package/src/commands/triage.js +0 -78
  214. package/src/commands/undo.js +0 -62
  215. package/src/domains/domain-loader.js +0 -245
  216. package/src/domains/domain-registry.js +0 -140
  217. package/src/domains/domain-synthesizer.js +0 -129
  218. package/src/git/automation.js +0 -433
  219. package/src/git/coder-commit-guard.js +0 -40
  220. package/src/git/hu-automation.js +0 -243
  221. package/src/git/hu-snapshot.js +0 -10
  222. package/src/git/symlink-targets.js +0 -57
  223. package/src/golden/asserter.js +0 -92
  224. package/src/golden/loader.js +0 -52
  225. package/src/golden/runner.js +0 -92
  226. package/src/golden/schema.js +0 -41
  227. package/src/guards/duplicate-members.js +0 -86
  228. package/src/guards/intent-guard.js +0 -161
  229. package/src/guards/perf-guard.js +0 -126
  230. package/src/guards/policy-guard.js +0 -90
  231. package/src/guards/policy-resolver.js +0 -85
  232. package/src/guards/secret-redactor.js +0 -71
  233. package/src/hu/acceptance-runner.js +0 -138
  234. package/src/hu/lazy-planner.js +0 -109
  235. package/src/hu/parallel-executor.js +0 -94
  236. package/src/hu/routing-metrics.js +0 -17
  237. package/src/hu/split-validator.js +0 -143
  238. package/src/hu/splitting-detector.js +0 -158
  239. package/src/hu/splitting-generator.js +0 -175
  240. package/src/hu/store.js +0 -394
  241. package/src/infrastructure/mocks.js +0 -182
  242. package/src/mcp/handlers/run-handler.js +0 -318
  243. package/src/mcp/sovereignty-guard.js +0 -113
  244. package/src/mcp/tool-arg-normalizers.js +0 -16
  245. package/src/mutate/reviewer-signal.js +0 -76
  246. package/src/orchestrator/agent-fallback.js +0 -81
  247. package/src/orchestrator/brain-coordinator.js +0 -186
  248. package/src/orchestrator/ci-integration.js +0 -139
  249. package/src/orchestrator/config-init.js +0 -426
  250. package/src/orchestrator/direct-actions.js +0 -217
  251. package/src/orchestrator/drivers/error-recovery.js +0 -287
  252. package/src/orchestrator/drivers/init-context.js +0 -238
  253. package/src/orchestrator/drivers/iteration-loop.js +0 -382
  254. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
  255. package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
  256. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
  257. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
  258. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
  259. package/src/orchestrator/drivers/post-loop.js +0 -404
  260. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
  261. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
  262. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
  263. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
  264. package/src/orchestrator/drivers/pre-loop.js +0 -524
  265. package/src/orchestrator/drivers/run-hu-batch.js +0 -468
  266. package/src/orchestrator/feedback-enrichment.js +0 -140
  267. package/src/orchestrator/feedback-queue.js +0 -151
  268. package/src/orchestrator/flow-control.js +0 -170
  269. package/src/orchestrator/flow-runner.js +0 -365
  270. package/src/orchestrator/fs-leak-detector.js +0 -228
  271. package/src/orchestrator/hu-scheduler.js +0 -114
  272. package/src/orchestrator/hu-sub-pipeline.js +0 -642
  273. package/src/orchestrator/iteration-gate.js +0 -73
  274. package/src/orchestrator/iteration-stages.js +0 -9
  275. package/src/orchestrator/parallel-limiter.js +0 -74
  276. package/src/orchestrator/pipeline-context.js +0 -72
  277. package/src/orchestrator/post-loop-stages.js +0 -468
  278. package/src/orchestrator/pre-loop-stages.js +0 -11
  279. package/src/orchestrator/preflight-checks.js +0 -487
  280. package/src/orchestrator/repair/repair-detector.js +0 -117
  281. package/src/orchestrator/repair/repair-runner.js +0 -142
  282. package/src/orchestrator/reviewer-fallback.js +0 -45
  283. package/src/orchestrator/role-output-compressor.js +0 -189
  284. package/src/orchestrator/session-journal.js +0 -308
  285. package/src/orchestrator/solomon-escalation.js +0 -331
  286. package/src/orchestrator/solomon-rules.js +0 -212
  287. package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
  288. package/src/orchestrator/stages/architect-stage.js +0 -172
  289. package/src/orchestrator/stages/coder-stage.js +0 -506
  290. package/src/orchestrator/stages/domain-curator-stage.js +0 -82
  291. package/src/orchestrator/stages/format-stage.js +0 -61
  292. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
  293. package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
  294. package/src/orchestrator/stages/perf-stage.js +0 -140
  295. package/src/orchestrator/stages/planner-stage.js +0 -85
  296. package/src/orchestrator/stages/rag-context-stage.js +0 -80
  297. package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
  298. package/src/orchestrator/stages/research-stage.js +0 -69
  299. package/src/orchestrator/stages/reviewer-stage.js +0 -431
  300. package/src/orchestrator/stages/sonar-stage.js +0 -439
  301. package/src/orchestrator/stages/stage-classes.js +0 -84
  302. package/src/orchestrator/stages/stage-executor.js +0 -155
  303. package/src/orchestrator/stages/suite-green-stage.js +0 -62
  304. package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
  305. package/src/orchestrator/stages/tool-judge-stage.js +0 -53
  306. package/src/orchestrator/stages/triage-stage.js +0 -172
  307. package/src/orchestrator/standby.js +0 -71
  308. package/src/orchestrator/verification-gate.js +0 -183
  309. package/src/orchestrator.js +0 -22
  310. package/src/plan/adr-generator.js +0 -170
  311. package/src/plan/adr-loader.js +0 -60
  312. package/src/plan/plan-executor.js +0 -100
  313. package/src/plan/plan-validation.js +0 -4
  314. package/src/prompts/acceptance-coverage.js +0 -32
  315. package/src/prompts/arbiter.js +0 -38
  316. package/src/prompts/architect.js +0 -103
  317. package/src/prompts/hu-reviewer.js +0 -244
  318. package/src/prompts/tool-correctness-judge.js +0 -31
  319. package/src/prompts/triage.js +0 -57
  320. package/src/rag/embedder.js +0 -2
  321. package/src/rag/embedders/_cloud-base.js +0 -2
  322. package/src/rag/embedders/cohere.js +0 -2
  323. package/src/rag/embedders/mistral.js +0 -2
  324. package/src/rag/embedders/onnx.js +0 -2
  325. package/src/rag/embedders/openai.js +0 -2
  326. package/src/rag/embedders/voyage.js +0 -2
  327. package/src/rag/rerank.js +0 -4
  328. package/src/rag/where-parser.js +0 -4
  329. package/src/repeat-detector.js +0 -88
  330. package/src/review/diff-generator.js +0 -112
  331. package/src/review/git-stash-helper.js +0 -104
  332. package/src/review/schema.js +0 -31
  333. package/src/review/scope-filter.js +0 -152
  334. package/src/review/snapshot-diff.js +0 -122
  335. package/src/review/tdd-discipline.js +0 -90
  336. package/src/review/tdd-policy.js +0 -69
  337. package/src/roles/acceptance-coverage-role.js +0 -78
  338. package/src/roles/architect-role.js +0 -77
  339. package/src/roles/coder-role.js +0 -113
  340. package/src/roles/commiter-role.js +0 -113
  341. package/src/roles/domain-curator-role.js +0 -134
  342. package/src/roles/hu-reviewer-role.js +0 -64
  343. package/src/roles/impeccable-role.js +0 -85
  344. package/src/roles/index.js +0 -18
  345. package/src/roles/karajan-brain-role.js +0 -134
  346. package/src/roles/perf-role.js +0 -122
  347. package/src/roles/planner-role.js +0 -151
  348. package/src/roles/refactorer-role.js +0 -30
  349. package/src/roles/repairer-role.js +0 -181
  350. package/src/roles/researcher-role.js +0 -70
  351. package/src/roles/reviewer-role.js +0 -115
  352. package/src/roles/security-role.js +0 -73
  353. package/src/roles/solomon-role.js +0 -271
  354. package/src/roles/sonar-role.js +0 -72
  355. package/src/roles/tester-role.js +0 -235
  356. package/src/roles/tool-correctness-judge-role.js +0 -78
  357. package/src/roles/triage-role.js +0 -99
  358. package/src/session/journal/decisions-writer.js +0 -200
  359. package/src/session/journal/iteration-logger.js +0 -194
  360. package/src/session/journal/summary-writer.js +0 -320
  361. package/src/session/journal/tree-writer.js +0 -164
  362. package/src/session/journal/write-all.js +0 -89
  363. package/src/session-cleanup.js +0 -116
  364. package/src/skills/semantic-detector.js +0 -167
  365. package/src/skills/skill-detector.js +0 -385
  366. package/src/sonar/cloud-scanner.js +0 -78
  367. package/src/tracker-bootstrap.js +0 -44
  368. package/src/types/index.js +0 -38
  369. package/src/types/policy.js +0 -43
  370. package/src/types/solomon.js +0 -87
  371. package/src/utils/async-lock.js +0 -31
  372. package/src/utils/budget.js +0 -229
  373. package/src/utils/cwd-confirm.js +0 -39
  374. package/src/utils/display/event-handlers.js +0 -403
  375. package/src/utils/display/formatters.js +0 -132
  376. package/src/utils/display/header.js +0 -47
  377. package/src/utils/display/session-summary.js +0 -118
  378. package/src/utils/display/solomon.js +0 -42
  379. package/src/utils/pricing.js +0 -29
  380. package/src/utils/quota-simulator.js +0 -56
  381. package/src/utils/role-assigner.js +0 -137
  382. package/src/utils/rtk-wrapper.js +0 -87
  383. package/src/utils/run-registry.js +0 -9
  384. package/src/utils/status-dashboard.js +0 -254
  385. package/src/utils/tool-call-extractor.js +0 -56
  386. package/src/webperf/devtools-detect.js +0 -112
  387. package/src/webperf/webperf-role-integration.js +0 -66
  388. package/templates/skills/kj-run.md +0 -73
@@ -1,104 +0,0 @@
1
- // Surgical stash helper for the TDD discipline gate (KJC-TSK-0398 PR2).
2
- // Stashes only the source files we want to hide for the "red" run and
3
- // restores them afterwards. Tracked files use `git stash push -- <paths>`;
4
- // untracked files (not yet known to git) are moved aside to a private
5
- // `.karajan/tmp/stash-<id>/` tree because `git stash` would ignore them.
6
-
7
- import { mkdir, rename, rm } from "node:fs/promises";
8
- import { existsSync } from "node:fs";
9
- import { dirname, join, resolve } from "node:path";
10
- import { runCommand } from "../utils/process.js";
11
-
12
- function defaultRunner(cmd, args, opts) {
13
- return runCommand(cmd, args, opts);
14
- }
15
-
16
- async function isTracked(runner, projectDir, file) {
17
- const res = await runner("git", ["ls-files", "--error-unmatch", "--", file], { cwd: projectDir });
18
- return res.exitCode === 0;
19
- }
20
-
21
- async function stashRefForToken(runner, projectDir, marker) {
22
- const res = await runner("git", ["stash", "list"], { cwd: projectDir });
23
- if (res.exitCode !== 0) return null;
24
- for (const line of String(res.stdout || "").split("\n")) {
25
- if (line.includes(marker)) {
26
- const m = line.match(/^(stash@\{\d+\})/);
27
- if (m) return m[1];
28
- }
29
- }
30
- return null;
31
- }
32
-
33
- // Move `files` aside so a test run sees them as missing. Returns a token to
34
- // pass to `restoreFiles()`. Throws on failure with the partial token attached.
35
- export async function stashFilesAside(files, { projectDir = process.cwd(), runner = defaultRunner, tmpRoot } = {}) {
36
- if (!Array.isArray(files) || files.length === 0) {
37
- return { trackedStashRef: null, untrackedMoves: [], marker: null };
38
- }
39
- const tracked = [];
40
- const untracked = [];
41
- for (const f of files) {
42
- if (await isTracked(runner, projectDir, f)) tracked.push(f);
43
- else if (existsSync(resolve(projectDir, f))) untracked.push(f);
44
- }
45
-
46
- const marker = `kj-tdd-discipline-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
47
- const stashRoot = tmpRoot || join(projectDir, ".karajan", "tmp", marker);
48
- const untrackedMoves = [];
49
- let trackedStashRef = null;
50
-
51
- if (tracked.length > 0) {
52
- const args = ["stash", "push", "--keep-index", "-m", marker, "--", ...tracked];
53
- const res = await runner("git", args, { cwd: projectDir });
54
- if (res.exitCode !== 0) {
55
- throw new Error(`git stash push failed: ${res.stderr || res.stdout}`);
56
- }
57
- trackedStashRef = await stashRefForToken(runner, projectDir, marker);
58
- }
59
-
60
- for (const f of untracked) {
61
- const src = resolve(projectDir, f);
62
- const dst = join(stashRoot, f);
63
- await mkdir(dirname(dst), { recursive: true });
64
- await rename(src, dst);
65
- untrackedMoves.push({ src, dst });
66
- }
67
-
68
- return { trackedStashRef, untrackedMoves, marker, stashRoot };
69
- }
70
-
71
- // Reverse a `stashFilesAside` token. Idempotent: a restored token becomes a no-op.
72
- export async function restoreFiles(token, { projectDir = process.cwd(), runner = defaultRunner } = {}) {
73
- if (!token || (token.trackedStashRef == null && token.untrackedMoves.length === 0)) return;
74
-
75
- for (const { src, dst } of token.untrackedMoves) {
76
- if (existsSync(dst)) {
77
- await mkdir(dirname(src), { recursive: true });
78
- await rename(dst, src);
79
- }
80
- }
81
- if (token.stashRoot && existsSync(token.stashRoot)) {
82
- await rm(token.stashRoot, { recursive: true, force: true });
83
- }
84
-
85
- if (token.trackedStashRef) {
86
- const res = await runner("git", ["stash", "pop", token.trackedStashRef], { cwd: projectDir });
87
- if (res.exitCode !== 0) {
88
- throw new Error(`git stash pop failed: ${res.stderr || res.stdout}`);
89
- }
90
- }
91
- token.trackedStashRef = null;
92
- token.untrackedMoves = [];
93
- }
94
-
95
- // Run `body` with `files` stashed aside, always restoring them — even when
96
- // `body` throws. The TDD discipline stage wraps its "red" run with this.
97
- export async function withStashedFiles(files, body, opts = {}) {
98
- const token = await stashFilesAside(files, opts);
99
- try {
100
- return await body(token);
101
- } finally {
102
- await restoreFiles(token, opts).catch(() => { /* swallow: surface body error */ });
103
- }
104
- }
@@ -1,31 +0,0 @@
1
- export function validateReviewResult(reviewResult) {
2
- if (!reviewResult || typeof reviewResult !== "object") {
3
- throw new TypeError("Reviewer output must be a JSON object");
4
- }
5
-
6
- if (typeof reviewResult.approved !== "boolean") {
7
- throw new TypeError("Reviewer output missing boolean field: approved");
8
- }
9
-
10
- if (!Array.isArray(reviewResult.blocking_issues)) {
11
- throw new TypeError("Reviewer output missing array field: blocking_issues");
12
- }
13
-
14
- if (!Array.isArray(reviewResult.non_blocking_suggestions)) {
15
- reviewResult.non_blocking_suggestions = [];
16
- }
17
-
18
- if (typeof reviewResult.summary !== "string") {
19
- reviewResult.summary = "";
20
- }
21
-
22
- if (typeof reviewResult.confidence !== "number") {
23
- reviewResult.confidence = 0.5;
24
- }
25
-
26
- if (reviewResult.approved && reviewResult.blocking_issues.length > 0) {
27
- throw new Error("Invalid reviewer output: approved=true with blocking issues");
28
- }
29
-
30
- return reviewResult;
31
- }
@@ -1,152 +0,0 @@
1
- /**
2
- * Scope filter — auto-defers reviewer blocking issues that reference
3
- * files NOT present in the diff. This prevents reviewer scope drift
4
- * (flagging missing features, unchanged code, future tasks) from
5
- * stalling the pipeline.
6
- *
7
- * Deferred issues are NOT forgotten — they are tracked in the session
8
- * as technical debt that should be resolved in future iterations or
9
- * follow-up tasks. The coder and planner receive context about what
10
- * was deferred and why.
11
- */
12
-
13
- /**
14
- * Extract the set of changed file paths from a unified diff string.
15
- */
16
- export function extractDiffFiles(diff) {
17
- const files = new Set();
18
- for (const line of (diff || "").split("\n")) {
19
- // Match "+++ b/path" lines in unified diff
20
- const m = /^\+\+\+ b\/(.+)/.exec(line);
21
- if (m) files.add(m[1]);
22
- }
23
- return files;
24
- }
25
-
26
- /**
27
- * Determine whether a blocking issue is within scope of the diff.
28
- *
29
- * An issue is considered IN scope when:
30
- * - It has no `file` field (general concern about the diff)
31
- * - Its `file` matches one of the changed files (exact or suffix match)
32
- * - It references a pattern present in the diff content itself
33
- *
34
- * An issue is OUT of scope when:
35
- * - It explicitly references a file NOT in the diff
36
- */
37
- export function isIssueInScope(issue, diffFiles, diffContent) {
38
- const file = (issue.file || "").trim();
39
-
40
- // No file specified — the reviewer is commenting on the diff generally
41
- if (!file) return true;
42
-
43
- // Direct match
44
- if (diffFiles.has(file)) return true;
45
-
46
- // Suffix match (reviewer might use full path vs relative)
47
- for (const df of diffFiles) {
48
- if (df.endsWith(file) || file.endsWith(df)) return true;
49
- }
50
-
51
- // Check if the file path appears anywhere in the diff content
52
- // (covers cases where the file is referenced in imports/requires)
53
- if (diffContent?.includes(file)) return true;
54
-
55
- return false;
56
- }
57
-
58
- /**
59
- * Filter a review result, demoting out-of-scope blocking issues to
60
- * non-blocking suggestions.
61
- *
62
- * Returns { review, demoted, deferred, allDemoted } where:
63
- * - review: the filtered review (may flip approved to true)
64
- * - demoted: array of original issues that were demoted
65
- * - deferred: structured deferred issues with metadata for session tracking
66
- * - allDemoted: true if ALL blocking issues were out of scope
67
- */
68
- export function filterReviewScope(review, diff) {
69
- if (!review || review.approved) {
70
- return { review, demoted: [], deferred: [], allDemoted: false };
71
- }
72
-
73
- const diffFiles = extractDiffFiles(diff);
74
-
75
- // If we can't parse diff files, don't filter (safety)
76
- if (diffFiles.size === 0) {
77
- return { review, demoted: [], deferred: [], allDemoted: false };
78
- }
79
-
80
- const inScope = [];
81
- const demoted = [];
82
-
83
- for (const issue of review.blocking_issues || []) {
84
- if (isIssueInScope(issue, diffFiles, diff)) {
85
- inScope.push(issue);
86
- } else {
87
- demoted.push(issue);
88
- }
89
- }
90
-
91
- if (demoted.length === 0) {
92
- return { review, demoted: [], deferred: [], allDemoted: false };
93
- }
94
-
95
- const demotedSuggestions = demoted.map(
96
- (issue) => `[auto-demoted] ${issue.file || "unknown"}: ${issue.description || issue.id || "no description"}`
97
- );
98
-
99
- const filtered = {
100
- ...review,
101
- blocking_issues: inScope,
102
- non_blocking_suggestions: [
103
- ...(review.non_blocking_suggestions || []),
104
- ...demotedSuggestions
105
- ]
106
- };
107
-
108
- // If no in-scope blocking issues remain, auto-approve
109
- const allDemoted = inScope.length === 0;
110
- if (allDemoted) {
111
- filtered.approved = true;
112
- filtered.summary = `${review.summary || ""} [Auto-approved: ${demoted.length} out-of-scope issue(s) demoted to suggestions]`.trim();
113
- }
114
-
115
- // Build structured deferred issues for session tracking
116
- const deferred = demoted.map((issue) => ({
117
- id: issue.id || null,
118
- file: issue.file || null,
119
- severity: issue.severity || "medium",
120
- description: issue.description || "no description",
121
- suggested_fix: issue.suggested_fix || null,
122
- deferred_at: new Date().toISOString(),
123
- reason: "out_of_scope"
124
- }));
125
-
126
- return { review: filtered, demoted, deferred, allDemoted };
127
- }
128
-
129
- /**
130
- * Build a human-readable summary of deferred issues for injection
131
- * into coder/planner prompts so they are aware of the tech debt.
132
- */
133
- export function buildDeferredContext(deferredIssues) {
134
- if (!deferredIssues?.length) return "";
135
-
136
- const lines = [
137
- "## Deferred reviewer concerns (technical debt)",
138
- "The following issues were flagged by the reviewer but deferred because they are outside the current diff scope.",
139
- "You do NOT need to fix them now, but be aware of them:",
140
- ""
141
- ];
142
-
143
- for (const issue of deferredIssues) {
144
- const file = issue.file ? `\`${issue.file}\`` : "general";
145
- const fix = issue.suggested_fix ? ` — Suggestion: ${issue.suggested_fix}` : "";
146
- lines.push(`- [${issue.severity}] ${file}: ${issue.description}${fix}`);
147
- }
148
-
149
- lines.push("", "If your current changes naturally address any of these, great. Otherwise, they will be tracked for future resolution.");
150
-
151
- return lines.join("\n");
152
- }
@@ -1,122 +0,0 @@
1
- /**
2
- * Git-free diff generation using filesystem snapshots.
3
- *
4
- * Takes a snapshot of project files (path → content hash), then compares
5
- * a later snapshot to generate a unified diff. Works on any OS, no git needed.
6
- */
7
-
8
- import { readdir, readFile, stat } from "node:fs/promises";
9
- import { join, relative } from "node:path";
10
- import { createHash } from "node:crypto";
11
-
12
- const DEFAULT_IGNORE = new Set([
13
- "node_modules", ".git", ".kj", ".karajan", "dist", "build",
14
- ".next", ".nuxt", ".svelte-kit", "__pycache__", ".venv",
15
- "coverage", ".nyc_output", ".reviews"
16
- ]);
17
-
18
- const MAX_FILE_SIZE = 512 * 1024; // 512KB — skip large files
19
-
20
- /**
21
- * Scan a directory recursively and return a Map of relative path → content hash.
22
- * @param {string} rootDir
23
- * @returns {Promise<Map<string, string>>} path → sha256 hash
24
- */
25
- export async function takeSnapshot(rootDir) {
26
- const snapshot = new Map();
27
- await scanDir(rootDir, rootDir, snapshot);
28
- return snapshot;
29
- }
30
-
31
- async function scanDir(dir, rootDir, snapshot) {
32
- let entries;
33
- try {
34
- entries = await readdir(dir, { withFileTypes: true });
35
- } catch { return; }
36
-
37
- for (const entry of entries) {
38
- if (DEFAULT_IGNORE.has(entry.name) || entry.name.startsWith(".")) continue;
39
-
40
- const fullPath = join(dir, entry.name);
41
- if (entry.isDirectory()) {
42
- await scanDir(fullPath, rootDir, snapshot);
43
- } else if (entry.isFile()) {
44
- try {
45
- const s = await stat(fullPath);
46
- if (s.size > MAX_FILE_SIZE) continue;
47
- const content = await readFile(fullPath);
48
- const hash = createHash("sha256").update(content).digest("hex");
49
- snapshot.set(relative(rootDir, fullPath), hash);
50
- } catch { /* skip unreadable files */ }
51
- }
52
- }
53
- }
54
-
55
- /**
56
- * Compare two snapshots and generate a unified diff string.
57
- * @param {Map<string, string>} before — snapshot before coder
58
- * @param {Map<string, string>|null} after — snapshot after coder (null = read from disk)
59
- * @param {string} rootDir — project root (for reading file contents)
60
- * @returns {Promise<string>} unified diff text
61
- */
62
- export async function generateSnapshotDiff(before, after, rootDir) {
63
- if (!after) {
64
- after = await takeSnapshot(rootDir);
65
- }
66
-
67
- const lines = [];
68
-
69
- // New files (in after but not before)
70
- for (const [path, _hash] of after) {
71
- if (!before.has(path)) {
72
- const content = await safeRead(join(rootDir, path));
73
- if (content !== null) {
74
- lines.push(`diff --snapshot a/${path} b/${path}`);
75
- lines.push("new file");
76
- lines.push(`--- /dev/null`);
77
- lines.push(`+++ b/${path}`);
78
- for (const line of content.split("\n")) {
79
- lines.push(`+${line}`);
80
- }
81
- lines.push("");
82
- }
83
- }
84
- }
85
-
86
- // Modified files (in both, different hash)
87
- for (const [path, hash] of after) {
88
- if (before.has(path) && before.get(path) !== hash) {
89
- const content = await safeRead(join(rootDir, path));
90
- if (content !== null) {
91
- lines.push(`diff --snapshot a/${path} b/${path}`);
92
- lines.push("modified file");
93
- lines.push(`--- a/${path}`);
94
- lines.push(`+++ b/${path}`);
95
- // Show full new content (we don't have the old content)
96
- for (const line of content.split("\n")) {
97
- lines.push(`+${line}`);
98
- }
99
- lines.push("");
100
- }
101
- }
102
- }
103
-
104
- // Deleted files (in before but not after)
105
- for (const [path] of before) {
106
- if (!after.has(path)) {
107
- lines.push(`diff --snapshot a/${path} b/${path}`);
108
- lines.push("deleted file");
109
- lines.push(`--- a/${path}`);
110
- lines.push(`+++ /dev/null`);
111
- lines.push("");
112
- }
113
- }
114
-
115
- return lines.join("\n");
116
- }
117
-
118
- async function safeRead(filePath) {
119
- try {
120
- return await readFile(filePath, "utf-8");
121
- } catch { return null; }
122
- }
@@ -1,90 +0,0 @@
1
- // TDD discipline gate (KJC-TSK-0398 PR1).
2
- // PR1 ships the pure decision logic with injected runTests/stashFiles
3
- // helpers. PR2 ships the real surgical stash. PR3 wires the gate into
4
- // the pipeline behind `development.require_red_then_green`.
5
-
6
- const PARSERS = {
7
- vitest: parseVitestFailures,
8
- jest: parseVitestFailures,
9
- pytest: parsePytestFailures,
10
- };
11
-
12
- function parseVitestFailures(output) {
13
- const failures = new Set();
14
- for (const line of String(output || "").split("\n")) {
15
- const m = line.match(/^\s*(?:FAIL|×|✖)\s+(\S+)/);
16
- if (m) failures.add(m[1].trim());
17
- }
18
- return failures;
19
- }
20
-
21
- function parsePytestFailures(output) {
22
- const failures = new Set();
23
- for (const line of String(output || "").split("\n")) {
24
- const m = line.match(/^FAILED\s+(\S+)/);
25
- if (m) failures.add(m[1].trim());
26
- }
27
- return failures;
28
- }
29
-
30
- function anyNewTestIn(failures, testFiles) {
31
- return [...failures].some((f) => testFiles.some((t) => f.includes(t) || t.includes(f)));
32
- }
33
-
34
- export async function verifyTddDiscipline({
35
- projectDir,
36
- sourceFiles = [],
37
- testFiles = [],
38
- framework = null,
39
- runTests,
40
- stashFiles,
41
- restoreFiles,
42
- } = {}) {
43
- if (sourceFiles.length === 0) {
44
- return { ok: true, reason: "no_source_changes", sourceFiles, testFiles };
45
- }
46
- if (testFiles.length === 0) {
47
- return { ok: true, reason: "no_test_changes", sourceFiles, testFiles };
48
- }
49
-
50
- const parser = framework ? PARSERS[framework] : null;
51
- if (!parser) {
52
- return {
53
- ok: true,
54
- reason: "framework_unsupported",
55
- sourceFiles,
56
- testFiles,
57
- warning: `tdd-discipline: framework "${framework || "unknown"}" has no failure parser; gate skipped`,
58
- };
59
- }
60
-
61
- let stashToken = null;
62
- try {
63
- stashToken = await stashFiles(sourceFiles);
64
- const before = await runTests(projectDir);
65
- if (!anyNewTestIn(parser(before.output), testFiles)) {
66
- return {
67
- ok: false,
68
- reason: "tests_pass_without_impl",
69
- sourceFiles,
70
- testFiles,
71
- message:
72
- "TDD discipline violation: new tests pass without the implementation. Tests must fail first (red), then pass (green).",
73
- };
74
- }
75
- } finally {
76
- if (stashToken) await restoreFiles(stashToken);
77
- }
78
-
79
- const after = await runTests(projectDir);
80
- if (anyNewTestIn(parser(after.output), testFiles)) {
81
- return {
82
- ok: false,
83
- reason: "tests_fail_with_impl",
84
- sourceFiles,
85
- testFiles,
86
- message: "TDD discipline violation: new tests still fail with the implementation present.",
87
- };
88
- }
89
- return { ok: true, reason: "red_then_green_verified", sourceFiles, testFiles };
90
- }
@@ -1,69 +0,0 @@
1
- function extractChangedFiles(diff) {
2
- const files = new Set();
3
- const lines = String(diff || "").split("\n");
4
- for (const line of lines) {
5
- // Support both git diff and snapshot diff formats
6
- if (!line.startsWith("diff --git ") && !line.startsWith("diff --snapshot ")) continue;
7
- const parts = line.split(" ");
8
- const b = parts[3] || "";
9
- if (b.startsWith("b/")) files.add(b.slice(2));
10
- }
11
- return [...files];
12
- }
13
-
14
- function isTestFile(file, patterns = []) {
15
- const normalized = `/${file}`;
16
- return patterns.some((pattern) => normalized.includes(pattern));
17
- }
18
-
19
- function isSourceFile(file, extensions = []) {
20
- return extensions.some((ext) => file.endsWith(ext));
21
- }
22
-
23
- const SKIP_TDD_TASK_TYPES = new Set(["doc", "infra", "no-code"]);
24
-
25
- const DEFAULT_TEST_PATTERNS = ["/tests/", "/__tests__/", ".test.", ".spec.", "/src/test/", "/test/", "Test.java", "Test.kt", "test_", "_test.py", "_test.go", "Test.cs", "_spec.rb", "Test.php", "_test.dart", "Tests.swift"];
26
- const DEFAULT_SOURCE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx", ".py", ".go", ".java", ".kt", ".rb", ".php", ".cs", ".rs", ".swift", ".dart"];
27
-
28
- export function evaluateTddPolicy(diff, developmentConfig = {}, untrackedFiles = [], taskType = null) {
29
- if (taskType && SKIP_TDD_TASK_TYPES.has(taskType)) {
30
- return { ok: true, reason: "tdd_not_applicable_for_task_type", sourceFiles: [], testFiles: [] };
31
- }
32
-
33
- const requireTestChanges = developmentConfig.require_test_changes !== false;
34
- const patterns = developmentConfig.test_file_patterns || DEFAULT_TEST_PATTERNS;
35
- const extensions = developmentConfig.source_file_extensions || DEFAULT_SOURCE_EXTENSIONS;
36
-
37
- const diffFiles = extractChangedFiles(diff);
38
- const extra = Array.isArray(untrackedFiles) ? untrackedFiles : [];
39
- const files = [...new Set([...diffFiles, ...extra])];
40
- const sourceFiles = files.filter((f) => isSourceFile(f, extensions) && !isTestFile(f, patterns));
41
- const testFiles = files.filter((f) => isTestFile(f, patterns));
42
-
43
- if (!requireTestChanges) {
44
- return {
45
- ok: true,
46
- reason: "test_changes_not_required",
47
- sourceFiles,
48
- testFiles
49
- };
50
- }
51
-
52
- if (sourceFiles.length === 0) {
53
- const reason = files.length === 0 ? "no_diff_available" : "no_source_changes";
54
- return { ok: true, reason, sourceFiles, testFiles };
55
- }
56
-
57
- if (testFiles.length === 0) {
58
- return {
59
- ok: false,
60
- reason: "source_changes_without_tests",
61
- sourceFiles,
62
- testFiles,
63
- message:
64
- "TDD policy violation: source code changed without test changes. Add/adjust tests first, then implementation."
65
- };
66
- }
67
-
68
- return { ok: true, reason: "tests_present", sourceFiles, testFiles };
69
- }
@@ -1,78 +0,0 @@
1
- // KJC-TSK-0987 (issue #1894): AcceptanceCoverageRole, the judge that reads each
2
- // acceptance criterion of the card against the diff before the run is approved.
3
- // Judged by a different AI than the coder (the reviewer's provider by default).
4
- // Role + helpers ship here; the stage wires it (same PR), the loops next.
5
-
6
- import { AgentRole } from "./agent-role.js";
7
- import { buildAcceptanceCoveragePrompt } from "../prompts/acceptance-coverage.js";
8
- import { extractFirstJson } from "../utils/json-extract.js";
9
-
10
- /**
11
- * The card's acceptance criteria as plain sentences, one per criterion.
12
- * Structured Given/When/Then first; else the free text, one per line or bullet.
13
- * @param {object|null} card
14
- * @returns {string[]}
15
- */
16
- export function criteriaOfCard(card) {
17
- const structured = Array.isArray(card?.acceptanceCriteriaStructured) ? card.acceptanceCriteriaStructured : [];
18
- const fromStructured = structured
19
- .map((ac) => (ac?.given && ac?.when && ac?.then ? `Given ${ac.given}, when ${ac.when}, then ${ac.then}` : String(ac?.raw || "").trim()))
20
- .filter(Boolean);
21
- if (fromStructured.length > 0) return fromStructured;
22
- return String(card?.acceptanceCriteria || "")
23
- .split("\n")
24
- .map((line) => line.replace(/^\s*(?:[-*•]|\d+[.)])\s*/, "").trim())
25
- .filter(Boolean);
26
- }
27
-
28
- const normalizeVerdicts = (parsed, criteria) => criteria.map((text, i) => {
29
- const v = Array.isArray(parsed?.criteria) ? parsed.criteria.find((c) => Number(c?.index) === i + 1) : null;
30
- return {
31
- index: i + 1,
32
- text,
33
- // No entry for a criterion is no evidence for it: not covered, never assumed.
34
- covered: v ? Boolean(v.covered) : false,
35
- evidence: typeof v?.evidence === "string" ? v.evidence.trim() : "",
36
- };
37
- });
38
-
39
- export class AcceptanceCoverageRole extends AgentRole {
40
- constructor(opts) {
41
- super({ ...opts, name: "acceptance-coverage" });
42
- }
43
-
44
- // A different AI than the coder: the reviewer's, unless the role has its own.
45
- resolveProvider() {
46
- return this.config?.roles?.["acceptance-coverage"]?.provider
47
- || this.config?.roles?.reviewer?.provider
48
- || this.config?.reviewer
49
- || "codex";
50
- }
51
-
52
- extractInput(input) {
53
- return { ...super.extractInput(input), criteria: Array.isArray(input?.criteria) ? input.criteria : [], diff: String(input?.diff || "") };
54
- }
55
-
56
- async buildPrompt({ task, criteria, diff }) {
57
- this._criteria = criteria;
58
- return { prompt: buildAcceptanceCoveragePrompt({ criteria, diff, task, instructions: this.instructions }) };
59
- }
60
-
61
- parseOutput(raw) { return extractFirstJson(raw); }
62
-
63
- // A verdict with no `criteria` array is not a verdict: the reviewer's failure.
64
- isSuccessful(parsed) { return Array.isArray(parsed?.criteria); }
65
-
66
- buildSuccessResult(parsed, provider) {
67
- const criteria = normalizeVerdicts(parsed, this._criteria || []);
68
- const uncovered = criteria.filter((c) => !c.covered);
69
- return { criteria, uncovered, provider, error: Array.isArray(parsed?.criteria) ? undefined : "no criteria array in the verdict" };
70
- }
71
-
72
- buildSummary(parsed) {
73
- if (!Array.isArray(parsed?.criteria)) return "acceptance-coverage: unreadable verdict (no criteria array)";
74
- const total = (this._criteria || []).length;
75
- const uncovered = normalizeVerdicts(parsed, this._criteria || []).filter((c) => !c.covered).length;
76
- return uncovered === 0 ? `acceptance-coverage: ${total}/${total} criteria covered` : `acceptance-coverage: ${uncovered} of ${total} criteria NOT covered`;
77
- }
78
- }