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,487 +0,0 @@
1
- /**
2
- * Preflight environment checks for kj_run.
3
- *
4
- * Runs AFTER policy resolution (so we know which stages are active)
5
- * and BEFORE session iteration loop (so we fail fast).
6
- *
7
- * Design: SonarQube checks are BLOCKING when enabled — if SonarQube is
8
- * configured but not available, the pipeline STOPS with a clear error.
9
- * Security agent checks remain graceful (warning, auto-disable).
10
- */
11
-
12
- import { checkBinary } from "../utils/agent-detect.js";
13
- import { isSonarReachable, sonarUp } from "../sonar/manager.js";
14
- import { resolveSonarProjectKey, canResolveSonarProjectKey } from "../sonar/project-key.js";
15
- import { runCommand } from "../utils/process.js";
16
- import { emitProgress, makeEvent } from "../utils/events.js";
17
- import { msg, getLang } from "../utils/messages.js";
18
- import {
19
- resolveSonarHost,
20
- resolveSonarTokenAsync,
21
- resolveSonarCredentials,
22
- sonarTokenSources,
23
- } from "../sonar/config-resolver.js";
24
- import { saveSonarToken } from "../sonar/credentials.js";
25
- import { withDocLink } from "../utils/doc-links.js";
26
- import { runChecks as runCheckPipeline } from "../checks/runner.js";
27
- import { STATUS } from "../checks/types.js";
28
- import { getNodeChecks } from "../checks/node.js";
29
- import { getDirSetupChecks } from "../checks/dir-setup.js";
30
- import { getPortChecks } from "../checks/ports.js";
31
- import { getTokenChecks } from "../checks/tokens.js";
32
- import { getMcpHealthChecks } from "../checks/mcp-health.js";
33
- import { getSkillsChecks } from "../checks/skills.js";
34
- import { getProjectChecks } from "../checks/project-checks.js";
35
- import { resolveTestHarness } from "../config/test-harness.js";
36
- import { collectGuaranteeFacts } from "../checks/guarantee-level.js";
37
-
38
- function parseJsonSafe(text) {
39
- try {
40
- return JSON.parse(text);
41
- } catch { /* invalid JSON */
42
- return null;
43
- }
44
- }
45
-
46
- async function checkDocker() {
47
- const result = await checkBinary("docker");
48
- return {
49
- name: "docker",
50
- ok: result.ok,
51
- detail: result.ok ? `Docker ${result.version}` : "Docker not found",
52
- };
53
- }
54
-
55
- const SONAR_STARTUP_POLL_MS = 5000;
56
- const SONAR_STARTUP_MAX_WAIT_MS = 60000;
57
-
58
- async function waitForSonar(host, maxWaitMs = SONAR_STARTUP_MAX_WAIT_MS) {
59
- const deadline = Date.now() + maxWaitMs;
60
- while (Date.now() < deadline) {
61
- if (await isSonarReachable(host)) return true;
62
- await new Promise(r => setTimeout(r, SONAR_STARTUP_POLL_MS));
63
- }
64
- return false;
65
- }
66
-
67
- async function checkSonarReachable(host) {
68
- const reachable = await isSonarReachable(host);
69
- if (reachable) {
70
- return { name: "sonar-reachable", ok: true, detail: `SonarQube reachable at ${host}`, remediated: false };
71
- }
72
-
73
- // Auto-remediation: start SonarQube and wait for it to be ready
74
- try {
75
- const upResult = await sonarUp(host);
76
- if (upResult.exitCode === 0) {
77
- // SonarQube needs 20-40s to start. Poll until ready.
78
- if (await waitForSonar(host)) {
79
- return { name: "sonar-reachable", ok: true, detail: `SonarQube started and reachable at ${host}`, remediated: true };
80
- }
81
- }
82
- } catch {
83
- // sonarUp failed, fall through
84
- }
85
-
86
- return { name: "sonar-reachable", ok: false, detail: `SonarQube not reachable at ${host} (auto-start failed after ${SONAR_STARTUP_MAX_WAIT_MS / 1000}s)` };
87
- }
88
-
89
- async function checkSonarAuth(config) {
90
- const host = resolveSonarHost(config.sonarqube?.host);
91
-
92
- // Check explicit token first (including persisted tokens from credentials file)
93
- const explicitToken = await resolveSonarTokenAsync(config);
94
- if (explicitToken) {
95
- // Validate the token works
96
- const res = await runCommand("curl", [
97
- "-sS", "-o", "/dev/null", "-w", "%{http_code}",
98
- "-H", `Authorization: Bearer ${explicitToken}`,
99
- "--max-time", "5",
100
- `${host}/api/authentication/validate`
101
- ]);
102
- if (res.exitCode === 0 && res.stdout.trim().startsWith("2")) {
103
- return { name: "sonar-auth", ok: true, detail: "Sonar token valid", token: explicitToken };
104
- }
105
- }
106
-
107
- // Try admin credentials via centralized resolver
108
- const { user: adminUser, passwords } = await resolveSonarCredentials(config);
109
-
110
- if (!adminUser || passwords.length === 0) {
111
- return { name: "sonar-auth", ok: false, detail: `No Sonar token or admin credentials found. Looked in: ${sonarTokenSources()}. With a custom KARAJAN_HOME/KJ_HOME, the token must be there (or pass kjHome).` };
112
- }
113
-
114
- for (const password of passwords) {
115
- const validateRes = await runCommand("curl", [
116
- "-sS", "-u", `${adminUser}:${password}`,
117
- `${host}/api/authentication/validate`
118
- ]);
119
- if (validateRes.exitCode !== 0) continue;
120
- const parsed = parseJsonSafe(validateRes.stdout);
121
- if (!parsed?.valid) continue;
122
-
123
- // Generate a user token
124
- const tokenName = `karajan-preflight-${Date.now()}`;
125
- const tokenRes = await runCommand("curl", [
126
- "-sS", "-u", `${adminUser}:${password}`,
127
- "-X", "POST",
128
- "--data-urlencode", `name=${tokenName}`,
129
- `${host}/api/user_tokens/generate`
130
- ]);
131
- if (tokenRes.exitCode !== 0) continue;
132
- const tokenParsed = parseJsonSafe(tokenRes.stdout);
133
- if (tokenParsed?.token) {
134
- // Persist the generated token for future sessions
135
- try { await saveSonarToken(tokenParsed.token); } catch { /* non-blocking */ }
136
- return { name: "sonar-auth", ok: true, detail: "Sonar token generated and saved", token: tokenParsed.token };
137
- }
138
- }
139
-
140
- return { name: "sonar-auth", ok: false, detail: "Could not validate or generate Sonar token" };
141
- }
142
-
143
- /**
144
- * KJC-BUG-0083 — run the real project-key resolver at preflight time so a
145
- * repo Sonar cannot scan aborts BEFORE the first coder iteration instead
146
- * of dying in sonar_repeat after burning tokens. Sonar stays mandatory
147
- * for code tasks (v2.7.4 contract); this only moves the failure earlier.
148
- */
149
- async function checkSonarProjectKey(config) {
150
- // No remote at all (fresh local project): the SonarStage already skips
151
- // cleanly via canResolveSonarProjectKey, so preflight must not block.
152
- // The hard-fail case is the inconsistent one: a remote EXISTS but the
153
- // resolver cannot parse it — that's what looped into sonar_repeat.
154
- if (!(await canResolveSonarProjectKey(config))) {
155
- return { name: "sonar-project-key", ok: true, detail: "No git remote — Sonar stage will skip cleanly" };
156
- }
157
- try {
158
- const key = await resolveSonarProjectKey(config);
159
- return { name: "sonar-project-key", ok: true, detail: `Project key resolved: ${key}` };
160
- } catch (err) {
161
- return { name: "sonar-project-key", ok: false, detail: err.message };
162
- }
163
- }
164
-
165
- async function checkSecurityAgent(config) {
166
- const provider = config.roles?.security?.provider
167
- || config.roles?.coder?.provider
168
- || config.coder
169
- || "claude";
170
-
171
- const result = await checkBinary(provider);
172
- return {
173
- name: "security-agent",
174
- ok: result.ok,
175
- detail: result.ok ? `Security agent "${provider}" available (${result.version})` : `Security agent "${provider}" not found`,
176
- provider,
177
- };
178
- }
179
-
180
- /**
181
- * Run preflight environment checks.
182
- *
183
- * SonarQube checks are BLOCKING: if SonarQube is enabled but not available,
184
- * ok will be false and errors[] will contain actionable fix instructions.
185
- * Security agent checks remain graceful (auto-disable via configOverrides).
186
- *
187
- * @param {object} opts
188
- * @param {object} opts.config - Karajan config
189
- * @param {object} opts.logger - Logger instance
190
- * @param {object|null} opts.emitter - Event emitter
191
- * @param {object} opts.eventBase - Base event data
192
- * @param {object} opts.resolvedPolicies - Output from applyPolicies()
193
- * @param {boolean} opts.securityEnabled - Whether security stage is enabled
194
- * @returns {{ ok: boolean, checks: object[], remediations: string[], configOverrides: object, warnings: string[], errors: object[] }}
195
- */
196
- export async function runPreflightChecks({ config, logger, emitter, eventBase, resolvedPolicies, securityEnabled }) {
197
- // Defensive test-harness resolution for direct callers that bypass loadConfig()
198
- // (notably tests/preflight-checks.test.js and tests/sonar-token-flow.test.js
199
- // which construct raw configs). Production callers go through loadConfig
200
- // which already populates config.testHarness once via resolveTestHarness().
201
- // Idempotent — no-op when already present.
202
- if (config && !config.testHarness) {
203
- config.testHarness = resolveTestHarness(config.testHarness);
204
- }
205
- // Sonar is intrinsic to Karajan for code tasks (sw/refactor/add-tests).
206
- // Since v2.7.4 it is NOT toggleable via `config.sonarqube.enabled` —
207
- // that field is ignored (deprecation warning emitted at config load).
208
- // The taskType policy (resolved_policies.sonar from DEFAULT_POLICIES in
209
- // src/guards/policy-resolver.js) is the single source of truth: sw /
210
- // refactor / add-tests run Sonar, audit / doc / infra / analysis /
211
- // no-code skip it. Solomon may decide to skip a single iteration via
212
- // rule alerts — that's a runtime decision, not a config option.
213
- //
214
- // Test-harness escape hatch via config.testHarness.disableSonarStage
215
- // — production code reads `config?.testHarness?.disableSonarStage`,
216
- // never `globalThis.*`. The legacy `globalThis.__KJ_DISABLE_SONAR_STAGE`
217
- // shape is documented (and exclusively read) in
218
- // src/config/test-harness.js, where the loader translates it into
219
- // the typed config slot above. ESLint rule (#557) blocks any
220
- // re-introduction outside that one file.
221
- const sonarStageDisabledForTest = config?.testHarness?.disableSonarStage === true;
222
- const sonarEnabled = !sonarStageDisabledForTest && resolvedPolicies.sonar !== false;
223
- const isExternalSonar = Boolean(config.sonarqube?.external);
224
- const sonarHost = resolveSonarHost(config.sonarqube?.host);
225
-
226
- const result = {
227
- ok: true,
228
- checks: [],
229
- remediations: [],
230
- configOverrides: {},
231
- warnings: [],
232
- errors: [],
233
- };
234
-
235
- // KJC-BUG-0252 (#1899): on a Claude host without the Sentinel harness nothing
236
- // governs the host's own actions (issues by hand, commits, pushes, merges);
237
- // a run reported Tier A OFF and the agent routed around every gate. kj never
238
- // fakes that supervision: the run stops here, with the remedy.
239
- const guarantee = collectGuaranteeFacts({ projectDir: config.projectDir || process.cwd() });
240
- if (guarantee.host === "claude" && !guarantee.tierA.active) {
241
- result.ok = false;
242
- result.checks.push({ name: "sentinel-harness", ok: false, detail: guarantee.tierA.reason });
243
- result.errors.push({
244
- check: "sentinel-harness",
245
- message: "Tier A tool-time is OFF: a Claude host without the Sentinel harness, so nothing governs the host's own actions outside this run.",
246
- fix: "Run kj harden (kj init installs it) and seal it with kj harden --commit; then run again.",
247
- });
248
- logger.error("Preflight: Sentinel harness not installed on a Claude host — run kj harden");
249
- emitProgress(emitter, makeEvent("preflight:end", { ...eventBase, stage: "preflight" }, {
250
- status: "fail", message: "Preflight FAILED — Sentinel harness not installed", detail: result
251
- }));
252
- return result;
253
- }
254
-
255
- // Resolve extended preflight opt-in early so the short-circuit below can see it.
256
- // Post-v2.7.5 this no longer reads globalThis directly — config.testHarness
257
- // is populated by the loader from the global or the default.
258
- const extendedDefault = config?.testHarness?.defaultPreflightExtended ?? true;
259
- const extendedEnabled = config?.preflight?.extended ?? extendedDefault;
260
-
261
- // Preserve legacy short-circuit: if nothing at all needs checking, skip
262
- // emitting the whole preflight bracket.
263
- if (!sonarEnabled && !securityEnabled && !extendedEnabled) {
264
- logger.info("Preflight: skipped (no sonar, no security, extended disabled)");
265
- emitProgress(emitter, makeEvent("preflight:end", { ...eventBase, stage: "preflight" }, {
266
- message: "Preflight skipped (no checks needed)",
267
- detail: { ...result, executorType: "local" }
268
- }));
269
- return result;
270
- }
271
-
272
- emitProgress(emitter, makeEvent("preflight:start", { ...eventBase, stage: "preflight" }, {
273
- message: "Running preflight environment checks",
274
- detail: { sonarEnabled, securityEnabled, extendedEnabled, executorType: "local" }
275
- }));
276
-
277
- // --- 1. Docker (only if sonar enabled and not external) ---
278
- if (sonarEnabled && !isExternalSonar) {
279
- const dockerCheck = await checkDocker();
280
- result.checks.push(dockerCheck);
281
-
282
- emitProgress(emitter, makeEvent("preflight:check", { ...eventBase, stage: "preflight" }, {
283
- status: dockerCheck.ok ? "ok" : "fail",
284
- message: `Docker: ${dockerCheck.detail}`,
285
- detail: dockerCheck
286
- }));
287
-
288
- if (!dockerCheck.ok) {
289
- result.ok = false;
290
- result.errors.push({
291
- check: "docker",
292
- message: "Docker not available but SonarQube is enabled.",
293
- fix: withDocLink("Start Docker, or disable SonarQube: set sonarqube.enabled: false in kj.config.yml, or pass --no-sonar.", "sonar_docker")
294
- });
295
- logger.error("Preflight: Docker not found — SonarQube requires Docker");
296
-
297
- // Skip remaining sonar checks, continue to security
298
- if (!securityEnabled) {
299
- emitProgress(emitter, makeEvent("preflight:end", { ...eventBase, stage: "preflight" }, {
300
- status: "fail", message: "Preflight FAILED — environment not ready", detail: result
301
- }));
302
- return result;
303
- }
304
- }
305
- }
306
-
307
- // --- 2. SonarQube reachable ---
308
- if (sonarEnabled && result.ok) {
309
- const reachableCheck = await checkSonarReachable(sonarHost);
310
- result.checks.push(reachableCheck);
311
-
312
- if (reachableCheck.remediated) {
313
- result.remediations.push("SonarQube auto-started via docker compose");
314
- }
315
-
316
- emitProgress(emitter, makeEvent("preflight:check", { ...eventBase, stage: "preflight" }, {
317
- status: reachableCheck.ok ? "ok" : "fail",
318
- message: `SonarQube reachability: ${reachableCheck.detail}`,
319
- detail: reachableCheck
320
- }));
321
-
322
- if (!reachableCheck.ok) {
323
- result.ok = false;
324
- result.errors.push({
325
- check: "sonar-reachable",
326
- message: `SonarQube not reachable at ${sonarHost}.`,
327
- fix: withDocLink("Start SonarQube: 'docker start karajan-sonarqube', or disable it: set sonarqube.enabled: false in kj.config.yml, or pass --no-sonar.", "sonar_docker")
328
- });
329
- logger.error("Preflight: SonarQube not reachable after remediation attempt");
330
- }
331
- }
332
-
333
- // --- 3. SonarQube auth/token ---
334
- if (sonarEnabled && result.ok) {
335
- const authCheck = await checkSonarAuth(config);
336
- result.checks.push(authCheck);
337
-
338
- emitProgress(emitter, makeEvent("preflight:check", { ...eventBase, stage: "preflight" }, {
339
- status: authCheck.ok ? "ok" : "fail",
340
- message: `SonarQube auth: ${authCheck.detail}`,
341
- detail: { name: authCheck.name, ok: authCheck.ok, detail: authCheck.detail }
342
- }));
343
-
344
- if (authCheck.ok && authCheck.token) {
345
- process.env.KJ_SONAR_TOKEN = authCheck.token;
346
- result.remediations.push("Sonar token resolved and cached in KJ_SONAR_TOKEN");
347
- logger.info("Preflight: Sonar token resolved and cached");
348
- } else if (!authCheck.ok) {
349
- result.ok = false;
350
- result.errors.push({
351
- check: "sonar-auth",
352
- message: `SonarQube is running but no authentication token was found. Looked in: ${sonarTokenSources()}.`,
353
- fix: withDocLink("Fix: run 'kj init' to configure it, or set KJ_SONAR_TOKEN env var, or add sonarqube.token to the kj.config.yml of the active Karajan home.", "sonar_token")
354
- });
355
- logger.error("Preflight: Sonar auth failed");
356
- }
357
- }
358
-
359
- // --- 3b. Sonar project key derivable (KJC-BUG-0083) ---
360
- // The scanner derives its project key from git remote.origin.url. With
361
- // an unparseable remote (local-path bare, exotic URL) and no explicit
362
- // sonarqube.project_key, every iteration's scan threw, the run burned
363
- // coder tokens and died in sonar_repeat. Run the REAL resolver here —
364
- // canResolveSonarProjectKey() is weaker (any non-empty remote passes) —
365
- // so the run fails fast with the actionable message before iteration 1.
366
- if (sonarEnabled && result.ok) {
367
- const keyCheck = await checkSonarProjectKey(config);
368
- result.checks.push(keyCheck);
369
-
370
- emitProgress(emitter, makeEvent("preflight:check", { ...eventBase, stage: "preflight" }, {
371
- status: keyCheck.ok ? "ok" : "fail",
372
- message: `Sonar project key: ${keyCheck.detail}`,
373
- detail: keyCheck
374
- }));
375
-
376
- if (!keyCheck.ok) {
377
- result.ok = false;
378
- result.errors.push({
379
- check: "sonar-project-key",
380
- message: "Sonar cannot scan this repository: no project key can be derived.",
381
- fix: withDocLink("Set sonarqube.project_key in kj.config.yml, or point remote.origin.url at a valid SSH/HTTPS remote.", "sonar_docker")
382
- });
383
- logger.error("Preflight: Sonar project key not derivable — failing fast before burning iterations");
384
- }
385
- }
386
-
387
- // --- 4. Security agent (graceful — only warning, not blocking) ---
388
- if (securityEnabled) {
389
- const secCheck = await checkSecurityAgent(config);
390
- result.checks.push(secCheck);
391
-
392
- emitProgress(emitter, makeEvent("preflight:check", { ...eventBase, stage: "preflight" }, {
393
- status: secCheck.ok ? "ok" : "warn",
394
- message: `Security agent: ${secCheck.detail}`,
395
- detail: secCheck
396
- }));
397
-
398
- if (!secCheck.ok) {
399
- result.configOverrides.securityDisabled = true;
400
- result.warnings.push(`Security agent "${secCheck.provider}" not found — security stage auto-disabled`);
401
- logger.warn(`Preflight: Security agent "${secCheck.provider}" not found, disabling security stage`);
402
- }
403
- }
404
-
405
- // --- 5. Complementary doctor-style checks (Node version, ports, tokens, MCP
406
- // health, ~/.karajan dirs, openskills). These are auto-remediated
407
- // with yes:true because preflight runs non-interactively inside kj run.
408
- // Opt-out via `config.preflight.extended: false`. Defaults to true
409
- // in production; the test harness (tests/setup.js) defaults it off.
410
- if (extendedEnabled) {
411
- await runExtendedPreflight({ config, result, emitter, eventBase, logger });
412
- }
413
-
414
- const hasErrors = result.errors.length > 0;
415
- const hasWarnings = result.warnings.length > 0;
416
- const preflightLang = getLang(config);
417
- emitProgress(emitter, makeEvent("preflight:end", { ...eventBase, stage: "preflight" }, {
418
- status: hasErrors ? "fail" : hasWarnings ? "warn" : "ok",
419
- message: hasErrors
420
- ? `Preflight FAILED — ${result.errors.length} blocking issue(s)`
421
- : hasWarnings
422
- ? `Preflight completed with ${result.warnings.length} warning(s)`
423
- : msg("preflight_passed", preflightLang),
424
- detail: { ...result, executorType: "local" }
425
- }));
426
-
427
- return result;
428
- }
429
-
430
- /**
431
- * Run the doctor-style complementary checks and merge their outcomes into
432
- * the existing preflight result shape. Runs with yes:true (non-interactive)
433
- * and auto-remediation enabled. FAIL is appended to errors (blocking), WARN and
434
- * TIMEOUT to warnings (non-blocking: a timeout is "not checked"), FIXED status is recorded as a
435
- * remediation, and runtime overrides from auto-fixes are merged into
436
- * configOverrides.
437
- */
438
- async function runExtendedPreflight({ config, result, emitter, eventBase, logger }) {
439
- const checks = [
440
- ...getNodeChecks(),
441
- ...getDirSetupChecks(),
442
- ...getPortChecks(),
443
- ...getTokenChecks(config),
444
- ...getMcpHealthChecks(),
445
- ...getSkillsChecks(),
446
- // KJC-TSK-0393: project-aware checks (signal detection, write perms,
447
- // tools per signal, .env consistency, gh remote access). Saltables con
448
- // flag --no-project-checks en kj run.
449
- ...(config?.flags?.noProjectChecks ? [] : getProjectChecks({ projectDir: config?.projectDir || process.cwd() })),
450
- ];
451
- let report;
452
- try {
453
- report = await runCheckPipeline(checks, { config }, { mode: "fix", yes: true, timeoutMs: 3000, logger });
454
- } catch (err) {
455
- logger.warn(`Preflight: extended checks failed to run (${err.message})`);
456
- return;
457
- }
458
-
459
- for (const c of report.checks) {
460
- result.checks.push({ name: c.name, ok: c.status === STATUS.OK || c.status === STATUS.FIXED || c.status === STATUS.SKIPPED, detail: c.detail });
461
- // KJC-BUG-0287 (#1982): a timeout is "not checked", never "broken". GitHub
462
- // unreachable for the SHA-pinned actions blocked a docs-only run that needed
463
- // no network at all; the runner itself calls TIMEOUT warn-level.
464
- let eventStatus = "ok";
465
- if (c.status === STATUS.FAIL) eventStatus = "fail";
466
- else if (c.status === STATUS.WARN || c.status === STATUS.TIMEOUT) eventStatus = "warn";
467
- emitProgress(emitter, makeEvent("preflight:check", { ...eventBase, stage: "preflight" }, {
468
- status: eventStatus,
469
- message: `${c.label}: ${c.detail}`,
470
- detail: { name: c.name, status: c.status, detail: c.detail, fix: c.fix }
471
- }));
472
- if (c.status === STATUS.FIXED) {
473
- result.remediations.push(`${c.label}: ${c.detail}`);
474
- } else if (c.status === STATUS.FAIL) {
475
- result.ok = false;
476
- result.errors.push({ check: c.name, message: `${c.label}: ${c.detail}`, fix: c.fix });
477
- } else if (c.status === STATUS.TIMEOUT) {
478
- result.warnings.push(`${c.label}: not checked, it did not answer in time (${c.detail}); the run goes on`);
479
- } else if (c.status === STATUS.WARN) {
480
- result.warnings.push(`${c.label}: ${c.detail}`);
481
- }
482
- }
483
-
484
- if (report.overrides && Object.keys(report.overrides).length > 0) {
485
- Object.assign(result.configOverrides, report.overrides);
486
- }
487
- }
@@ -1,117 +0,0 @@
1
- /**
2
- * Heuristics for classifying an acceptance-test failure as "possibly
3
- * infeasible" — i.e. the test itself looks broken, not the code.
4
- *
5
- * Used by the HU sub-pipeline to decide when to invoke the Repairer
6
- * role instead of feeding the failure back to the coder for ANOTHER
7
- * iteration. Each iteration costs real money + time; classifying
8
- * misroutes is the difference between a 3-minute repair and a
9
- * 30-minute budget burn.
10
- *
11
- * The classifier is deliberately CONSERVATIVE: false negatives
12
- * (treating a broken test as just-failed) cost an extra iteration;
13
- * false positives (treating a real failure as infeasible) waste a
14
- * Repairer call AND give the coder a spec change it shouldn't
15
- * receive. We err toward false negatives.
16
- */
17
-
18
- /**
19
- * Signatures that strongly suggest the TEST is broken, not the code.
20
- * Each is a regex applied to the failing command's stderr+stdout.
21
- */
22
- const INFEASIBLE_SIGNATURES = [
23
- // jq: `length as $n | .field` after a transform — the binding doesn't
24
- // change the value flowing through, so `.field` is applied to the wrong
25
- // thing. Real-world example from 2026-04-29: "Cannot index array with
26
- // string \"filesOver300LOC\"".
27
- { kind: "jq-as-binding-misuse", re: /Cannot index array with string/i },
28
- // bash -n / shell parse errors before the command even runs.
29
- { kind: "shell-parse-error", re: /syntax error|unexpected token|unexpected EOF/i },
30
- // jq: invalid query syntax detected by jq itself.
31
- { kind: "jq-syntax", re: /jq: error \(at .*\): (?!Cannot index)/i },
32
- // command not found = test references a tool that doesn't exist in
33
- // this environment. Could be a fixable typo or a real env mismatch.
34
- { kind: "missing-tool", re: /command not found|: not found$|No such file or directory.*\.(sh|mjs|js|py)/im },
35
- // jq fails to parse the JSON input — the input file is wrong, the
36
- // path is wrong, or the assumed schema is off.
37
- { kind: "jq-parse-input", re: /jq: error: Could not (open|parse)/i },
38
- ];
39
-
40
- /**
41
- * Compute a stable signature for a single failing test outcome — the
42
- * test command + a normalized error fingerprint. Used to detect when
43
- * the SAME test is failing the SAME way across iterations; that's the
44
- * pattern that argues for a Repairer call (vs. a coder retry).
45
- *
46
- * @param {string} cmd
47
- * @param {string} errorOutput
48
- * @returns {string}
49
- */
50
- export function failureSignature(cmd, errorOutput) {
51
- const cmdNorm = String(cmd || "").trim().slice(0, 200);
52
- const errNorm = String(errorOutput || "")
53
- .replaceAll(/\s+/g, " ") // collapse whitespace
54
- .replaceAll(/\d+/g, "N") // numbers tend to drift; line numbers, counts
55
- .replaceAll(/\/[^\s]+/g, "/PATH") // absolute paths drift across runs
56
- .trim()
57
- .slice(0, 300);
58
- return `${cmdNorm}|||${errNorm}`;
59
- }
60
-
61
- /**
62
- * Classify a single test failure as `infeasible` or `regular`.
63
- *
64
- * @param {object} args
65
- * @param {string} args.cmd — the command that ran
66
- * @param {string} args.errorOutput — stdout + stderr from the failing run
67
- * @param {number} args.consecutive — how many iterations in a row this
68
- * same signature has failed
69
- * @param {number} [args.threshold=2] — min consecutive failures before
70
- * infeasibility is even considered.
71
- * Below this, we always say "regular"
72
- * (let the coder try once more).
73
- * @returns {{infeasible: boolean, kind: string|null, signature: string}}
74
- */
75
- export function classifyFailure({ cmd, errorOutput, consecutive, threshold = 2 }) {
76
- const signature = failureSignature(cmd, errorOutput);
77
- if (!Number.isFinite(consecutive) || consecutive < threshold) {
78
- return { infeasible: false, kind: null, signature };
79
- }
80
- for (const sig of INFEASIBLE_SIGNATURES) {
81
- if (sig.re.test(errorOutput || "")) {
82
- return { infeasible: true, kind: sig.kind, signature };
83
- }
84
- }
85
- return { infeasible: false, kind: null, signature };
86
- }
87
-
88
- /**
89
- * Track consecutive identical failures across iterations of the same HU.
90
- * Caller passes the same instance across iterations; the tracker
91
- * remembers the last signature per command.
92
- */
93
- export function createFailureTracker() {
94
- // Map<cmd, { signature, count }>
95
- const state = new Map();
96
- return {
97
- /**
98
- * Record a failure and return how many consecutive iterations have
99
- * produced the same signature for this cmd.
100
- * @returns {number}
101
- */
102
- record(cmd, errorOutput) {
103
- const signature = failureSignature(cmd, errorOutput);
104
- const prev = state.get(cmd);
105
- if (prev && prev.signature === signature) {
106
- prev.count += 1;
107
- return prev.count;
108
- }
109
- state.set(cmd, { signature, count: 1 });
110
- return 1;
111
- },
112
- /** Reset tracker for a cmd that just passed (stops false positives). */
113
- reset(cmd) { state.delete(cmd); },
114
- /** Diagnostic: dump current state. */
115
- snapshot() { return Object.fromEntries(state); },
116
- };
117
- }