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,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
- }