karajan-code 4.70.3 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (388) hide show
  1. package/README.md +2 -2
  2. package/package.json +6 -14
  3. package/packages/hu-board/package.json +1 -1
  4. package/packages/hu-board/public/app.js +4 -18
  5. package/packages/hu-board/public/index.html +5 -2
  6. package/packages/hu-board/public/styles.css +26 -0
  7. package/packages/hu-board/public/utils/api.js +0 -74
  8. package/packages/hu-board/public/utils/board-tour.js +62 -0
  9. package/packages/hu-board/public/utils/board-view.js +49 -144
  10. package/packages/hu-board/public/utils/command-launcher.js +8 -70
  11. package/packages/hu-board/public/utils/formatters.js +10 -0
  12. package/packages/hu-board/public/utils/governance-view.js +13 -1
  13. package/packages/hu-board/public/utils/hu-actions.js +17 -68
  14. package/packages/hu-board/public/utils/init-listeners.js +7 -8
  15. package/packages/hu-board/public/utils/log-panel.js +3 -16
  16. package/packages/hu-board/public/utils/maggle-mode.js +2 -12
  17. package/packages/hu-board/public/utils/modals.js +7 -0
  18. package/packages/hu-board/public/utils/project-picker-view.js +1 -1
  19. package/packages/hu-board/public/utils/server-push.js +3 -22
  20. package/packages/hu-board/public/utils/shared-board-notice.js +33 -0
  21. package/packages/hu-board/public/utils/story-detail-view.js +5 -19
  22. package/packages/hu-board/src/cleanup-zombies.js +0 -1
  23. package/packages/hu-board/src/command-runner.js +36 -25
  24. package/packages/hu-board/src/db-connection.js +282 -0
  25. package/packages/hu-board/src/db-paths.js +113 -0
  26. package/packages/hu-board/src/db-queries.js +188 -0
  27. package/packages/hu-board/src/db-tombstones.js +51 -0
  28. package/packages/hu-board/src/db.js +28 -619
  29. package/packages/hu-board/src/ephemeral-cleaner.js +2 -3
  30. package/packages/hu-board/src/hu-zombie-reaper.js +5 -1
  31. package/packages/hu-board/src/plan-mutations.js +3 -153
  32. package/packages/hu-board/src/plans-service.js +55 -0
  33. package/packages/hu-board/src/routes/api/commands.js +122 -0
  34. package/packages/hu-board/src/routes/api/plans.js +55 -0
  35. package/packages/hu-board/src/routes/api/projects.js +267 -0
  36. package/packages/hu-board/src/routes/api/prompts.js +135 -0
  37. package/packages/hu-board/src/routes/api/sessions.js +95 -0
  38. package/packages/hu-board/src/routes/api/stories.js +183 -0
  39. package/packages/hu-board/src/routes/api/system.js +241 -0
  40. package/packages/hu-board/src/routes/api/tombstones.js +38 -0
  41. package/packages/hu-board/src/routes/api.js +24 -1371
  42. package/packages/hu-board/src/routes/governance.js +23 -1
  43. package/packages/hu-board/src/routes/lanes.js +40 -0
  44. package/packages/hu-board/src/routes/rag.js +4 -3
  45. package/packages/hu-board/src/server.js +20 -34
  46. package/packages/hu-board/src/sync-files.js +318 -0
  47. package/packages/hu-board/src/sync-project.js +99 -0
  48. package/packages/hu-board/src/sync.js +22 -566
  49. package/packages/hu-board/src/zombie-reaper.js +1 -1
  50. package/scripts/dual-publish.mjs +19 -3
  51. package/scripts/postinstall.js +15 -2
  52. package/scripts/verify-pack-mcp.mjs +11 -7
  53. package/scripts/verify-pack.mjs +4 -4
  54. package/src/audit/audit-history.js +3 -2
  55. package/src/audit/dead-exports.js +68 -59
  56. package/src/audit/deterministic-summary.js +33 -13
  57. package/src/audit/npm-signatures.js +41 -0
  58. package/src/audit/sbom.js +35 -0
  59. package/src/checks/agent-token.js +112 -0
  60. package/src/checks/ai-surface.js +9 -7
  61. package/src/checks/config-files.js +8 -7
  62. package/src/checks/dir-setup.js +34 -1
  63. package/src/checks/native-build.js +21 -39
  64. package/src/checks/runner.js +128 -142
  65. package/src/cli/advanced-commands.js +6 -6
  66. package/src/cli/register-meta.js +42 -82
  67. package/src/cli/register-pipeline.js +8 -140
  68. package/src/cli/register-plan.js +9 -100
  69. package/src/cli/retired-commands.js +49 -0
  70. package/src/cli.js +18 -5
  71. package/src/commands/agent-run.js +40 -26
  72. package/src/commands/audit.js +127 -124
  73. package/src/commands/board.js +5 -9
  74. package/src/commands/check.js +29 -16
  75. package/src/commands/doctor.js +2 -0
  76. package/src/commands/env.js +176 -148
  77. package/src/commands/go.js +76 -55
  78. package/src/commands/harden.js +105 -86
  79. package/src/commands/hu-start.js +52 -0
  80. package/src/commands/hu.js +288 -221
  81. package/src/commands/identity.js +111 -78
  82. package/src/commands/init-atril.js +47 -0
  83. package/src/commands/init.js +11 -40
  84. package/src/commands/install-tools.js +70 -85
  85. package/src/commands/main-guard.js +78 -0
  86. package/src/commands/policy.js +226 -184
  87. package/src/commands/qmd.js +27 -21
  88. package/src/commands/rag.js +75 -48
  89. package/src/commands/review-gate.js +422 -339
  90. package/src/commands/start.js +4 -5
  91. package/src/commands/steward.js +96 -47
  92. package/src/commands/tournament.js +46 -37
  93. package/src/commands/worktree.js +94 -25
  94. package/src/config/loader.js +34 -17
  95. package/src/config/overrides.js +2 -1
  96. package/src/config/secrets.js +36 -0
  97. package/src/config.js +1 -0
  98. package/src/environment/adr.js +16 -2
  99. package/src/environment/board-access.js +11 -9
  100. package/src/git/karajan-label.js +20 -8
  101. package/src/guards/main-guard.js +54 -0
  102. package/src/harden/branch-protection.js +36 -3
  103. package/src/harden/hook-templates.js +16 -1
  104. package/src/harden/sentinel/posttooluse.mjs +18 -6
  105. package/src/harden/sentinel/pretooluse-sentinel.mjs +81 -35
  106. package/src/harden/sentinel/sentinel-lib.mjs +149 -23
  107. package/src/harden/sentinel/sentinel-shell.mjs +64 -0
  108. package/src/harden/sentinel/sessionstart.mjs +3 -2
  109. package/src/harden/sentinel/stop.mjs +5 -4
  110. package/src/harden/workflow-engine.js +12 -1
  111. package/src/harden/workflow-templates.js +1 -1
  112. package/src/hu/board-lookup.js +29 -0
  113. package/src/hu/gh-identity.js +23 -0
  114. package/src/hu/github-board.js +4 -2
  115. package/src/hu/lane-prune.js +15 -7
  116. package/src/hu/lane-setup.js +65 -0
  117. package/src/hu/lanes.js +66 -0
  118. package/src/hu/team-board-mirror.js +3 -2
  119. package/src/infrastructure/command-runner.js +1 -1
  120. package/src/infrastructure/file-system-service.js +1 -1
  121. package/src/mcp/handlers/direct-handlers.js +2 -292
  122. package/src/mcp/handlers/hu-handlers.js +44 -55
  123. package/src/mcp/handlers/management-handlers.js +2 -42
  124. package/src/mcp/server-handlers.js +16 -22
  125. package/src/mcp/tools.js +5 -200
  126. package/src/plan/lane-plans.js +54 -0
  127. package/src/plan/plan-store.js +7 -4
  128. package/src/policy/add.js +26 -12
  129. package/src/policy/adr-number-check.js +31 -0
  130. package/src/prompts/audit.js +299 -339
  131. package/src/prompts/card-context.js +10 -5
  132. package/src/prompts/reviewer.js +5 -2
  133. package/src/rag/auto-update.js +56 -43
  134. package/src/rag/bootstrap-index.js +28 -54
  135. package/src/rag/migrate.js +1 -1
  136. package/src/rag/onnx-fallback.js +2 -2
  137. package/src/rag/pid-lease.js +70 -0
  138. package/src/rag/project-store.js +2 -12
  139. package/src/review/card-first.js +57 -46
  140. package/src/review/dual-review.js +5 -4
  141. package/src/review/gate-gitignore.js +23 -4
  142. package/src/review/gate-hook.js +47 -0
  143. package/src/review/one-shot-review.js +51 -37
  144. package/src/review/publish-pr.js +18 -3
  145. package/src/review/repo-docs.js +73 -0
  146. package/src/review/repo-stack.js +59 -0
  147. package/src/review/reviewer-fallback.js +34 -3
  148. package/src/review/seal-commit.js +29 -0
  149. package/src/review/sonar-pregate.js +59 -44
  150. package/src/review/third-party-review.js +90 -16
  151. package/src/review/third-party-size.js +43 -0
  152. package/src/review/third-party-skills.js +54 -0
  153. package/src/review/verify-pr.js +2 -26
  154. package/src/roles/audit-role.js +106 -125
  155. package/src/skills/atril-skills.js +5 -4
  156. package/src/sonar/scanner.js +116 -101
  157. package/src/steward/invariants.js +5 -4
  158. package/src/steward/phantom-sweep.js +66 -0
  159. package/src/tournament/judge.js +60 -53
  160. package/src/utils/agent-home.js +33 -0
  161. package/src/utils/gh-account.js +37 -0
  162. package/src/utils/git.js +0 -13
  163. package/src/utils/home-migration.js +7 -1
  164. package/src/utils/main-checkout.js +53 -0
  165. package/src/utils/qmd-collection.js +3 -1
  166. package/src/utils/welcome.js +3 -3
  167. package/templates/{skills → atril/commands}/kj-architect.md +5 -0
  168. package/templates/{skills → atril/commands}/kj-audit.md +5 -0
  169. package/templates/{skills → atril/commands}/kj-board.md +5 -0
  170. package/templates/{skills → atril/commands}/kj-code.md +5 -0
  171. package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
  172. package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
  173. package/templates/{skills → atril/commands}/kj-discover.md +5 -0
  174. package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
  175. package/templates/{skills → atril/commands}/kj-review.md +5 -0
  176. package/templates/{skills → atril/commands}/kj-security.md +5 -0
  177. package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
  178. package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
  179. package/templates/{skills → atril/commands}/kj-test.md +5 -0
  180. package/packages/hu-board/public/utils/run-launcher.js +0 -169
  181. package/packages/hu-board/src/run-tracker.js +0 -121
  182. package/src/activity-log.js +0 -59
  183. package/src/agents/host-agent.js +0 -38
  184. package/src/agents/model-registry.js +0 -142
  185. package/src/audit/plan-adherence.js +0 -224
  186. package/src/autonomy/arbiter-role.js +0 -71
  187. package/src/autonomy/arbiter.js +0 -22
  188. package/src/autonomy/decider.js +0 -18
  189. package/src/autonomy/decision-resolver.js +0 -58
  190. package/src/autonomy/outcome-report.js +0 -55
  191. package/src/autonomy/stage-ask.js +0 -30
  192. package/src/brain/decision-tracker.js +0 -125
  193. package/src/brain/decisor.js +0 -243
  194. package/src/brain/solomon-consult.js +0 -171
  195. package/src/brain/standby-scheduler.js +0 -8
  196. package/src/budget/comparison.js +0 -96
  197. package/src/canvas/parse-md.js +0 -238
  198. package/src/canvas/schema.js +0 -101
  199. package/src/canvas/to-flat.js +0 -85
  200. package/src/canvas/validate.js +0 -163
  201. package/src/ci/dispatch.js +0 -99
  202. package/src/ci/index.js +0 -3
  203. package/src/ci/pr-diff.js +0 -26
  204. package/src/cli/register-standby.js +0 -26
  205. package/src/commands/architect.js +0 -111
  206. package/src/commands/autorun.js +0 -49
  207. package/src/commands/researcher.js +0 -54
  208. package/src/commands/resume.js +0 -72
  209. package/src/commands/review.js +0 -89
  210. package/src/commands/run.js +0 -185
  211. package/src/commands/standby.js +0 -117
  212. package/src/commands/status.js +0 -36
  213. package/src/commands/triage.js +0 -78
  214. package/src/commands/undo.js +0 -62
  215. package/src/domains/domain-loader.js +0 -245
  216. package/src/domains/domain-registry.js +0 -140
  217. package/src/domains/domain-synthesizer.js +0 -129
  218. package/src/git/automation.js +0 -433
  219. package/src/git/coder-commit-guard.js +0 -40
  220. package/src/git/hu-automation.js +0 -243
  221. package/src/git/hu-snapshot.js +0 -10
  222. package/src/git/symlink-targets.js +0 -57
  223. package/src/golden/asserter.js +0 -92
  224. package/src/golden/loader.js +0 -52
  225. package/src/golden/runner.js +0 -92
  226. package/src/golden/schema.js +0 -41
  227. package/src/guards/duplicate-members.js +0 -86
  228. package/src/guards/intent-guard.js +0 -161
  229. package/src/guards/perf-guard.js +0 -126
  230. package/src/guards/policy-guard.js +0 -90
  231. package/src/guards/policy-resolver.js +0 -85
  232. package/src/guards/secret-redactor.js +0 -71
  233. package/src/hu/acceptance-runner.js +0 -138
  234. package/src/hu/lazy-planner.js +0 -109
  235. package/src/hu/parallel-executor.js +0 -94
  236. package/src/hu/routing-metrics.js +0 -17
  237. package/src/hu/split-validator.js +0 -143
  238. package/src/hu/splitting-detector.js +0 -158
  239. package/src/hu/splitting-generator.js +0 -175
  240. package/src/hu/store.js +0 -394
  241. package/src/infrastructure/mocks.js +0 -182
  242. package/src/mcp/handlers/run-handler.js +0 -318
  243. package/src/mcp/sovereignty-guard.js +0 -113
  244. package/src/mcp/tool-arg-normalizers.js +0 -16
  245. package/src/mutate/reviewer-signal.js +0 -76
  246. package/src/orchestrator/agent-fallback.js +0 -81
  247. package/src/orchestrator/brain-coordinator.js +0 -186
  248. package/src/orchestrator/ci-integration.js +0 -139
  249. package/src/orchestrator/config-init.js +0 -426
  250. package/src/orchestrator/direct-actions.js +0 -217
  251. package/src/orchestrator/drivers/error-recovery.js +0 -287
  252. package/src/orchestrator/drivers/init-context.js +0 -238
  253. package/src/orchestrator/drivers/iteration-loop.js +0 -382
  254. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
  255. package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
  256. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
  257. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
  258. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
  259. package/src/orchestrator/drivers/post-loop.js +0 -404
  260. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
  261. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
  262. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
  263. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
  264. package/src/orchestrator/drivers/pre-loop.js +0 -524
  265. package/src/orchestrator/drivers/run-hu-batch.js +0 -468
  266. package/src/orchestrator/feedback-enrichment.js +0 -140
  267. package/src/orchestrator/feedback-queue.js +0 -151
  268. package/src/orchestrator/flow-control.js +0 -170
  269. package/src/orchestrator/flow-runner.js +0 -365
  270. package/src/orchestrator/fs-leak-detector.js +0 -228
  271. package/src/orchestrator/hu-scheduler.js +0 -114
  272. package/src/orchestrator/hu-sub-pipeline.js +0 -642
  273. package/src/orchestrator/iteration-gate.js +0 -73
  274. package/src/orchestrator/iteration-stages.js +0 -9
  275. package/src/orchestrator/parallel-limiter.js +0 -74
  276. package/src/orchestrator/pipeline-context.js +0 -72
  277. package/src/orchestrator/post-loop-stages.js +0 -468
  278. package/src/orchestrator/pre-loop-stages.js +0 -11
  279. package/src/orchestrator/preflight-checks.js +0 -487
  280. package/src/orchestrator/repair/repair-detector.js +0 -117
  281. package/src/orchestrator/repair/repair-runner.js +0 -142
  282. package/src/orchestrator/reviewer-fallback.js +0 -45
  283. package/src/orchestrator/role-output-compressor.js +0 -189
  284. package/src/orchestrator/session-journal.js +0 -308
  285. package/src/orchestrator/solomon-escalation.js +0 -331
  286. package/src/orchestrator/solomon-rules.js +0 -212
  287. package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
  288. package/src/orchestrator/stages/architect-stage.js +0 -172
  289. package/src/orchestrator/stages/coder-stage.js +0 -506
  290. package/src/orchestrator/stages/domain-curator-stage.js +0 -82
  291. package/src/orchestrator/stages/format-stage.js +0 -61
  292. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
  293. package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
  294. package/src/orchestrator/stages/perf-stage.js +0 -140
  295. package/src/orchestrator/stages/planner-stage.js +0 -85
  296. package/src/orchestrator/stages/rag-context-stage.js +0 -80
  297. package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
  298. package/src/orchestrator/stages/research-stage.js +0 -69
  299. package/src/orchestrator/stages/reviewer-stage.js +0 -431
  300. package/src/orchestrator/stages/sonar-stage.js +0 -439
  301. package/src/orchestrator/stages/stage-classes.js +0 -84
  302. package/src/orchestrator/stages/stage-executor.js +0 -155
  303. package/src/orchestrator/stages/suite-green-stage.js +0 -62
  304. package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
  305. package/src/orchestrator/stages/tool-judge-stage.js +0 -53
  306. package/src/orchestrator/stages/triage-stage.js +0 -172
  307. package/src/orchestrator/standby.js +0 -71
  308. package/src/orchestrator/verification-gate.js +0 -183
  309. package/src/orchestrator.js +0 -22
  310. package/src/plan/adr-generator.js +0 -170
  311. package/src/plan/adr-loader.js +0 -60
  312. package/src/plan/plan-executor.js +0 -100
  313. package/src/plan/plan-validation.js +0 -4
  314. package/src/prompts/acceptance-coverage.js +0 -32
  315. package/src/prompts/arbiter.js +0 -38
  316. package/src/prompts/architect.js +0 -103
  317. package/src/prompts/hu-reviewer.js +0 -244
  318. package/src/prompts/tool-correctness-judge.js +0 -31
  319. package/src/prompts/triage.js +0 -57
  320. package/src/rag/embedder.js +0 -2
  321. package/src/rag/embedders/_cloud-base.js +0 -2
  322. package/src/rag/embedders/cohere.js +0 -2
  323. package/src/rag/embedders/mistral.js +0 -2
  324. package/src/rag/embedders/onnx.js +0 -2
  325. package/src/rag/embedders/openai.js +0 -2
  326. package/src/rag/embedders/voyage.js +0 -2
  327. package/src/rag/rerank.js +0 -4
  328. package/src/rag/where-parser.js +0 -4
  329. package/src/repeat-detector.js +0 -88
  330. package/src/review/diff-generator.js +0 -112
  331. package/src/review/git-stash-helper.js +0 -104
  332. package/src/review/schema.js +0 -31
  333. package/src/review/scope-filter.js +0 -152
  334. package/src/review/snapshot-diff.js +0 -122
  335. package/src/review/tdd-discipline.js +0 -90
  336. package/src/review/tdd-policy.js +0 -69
  337. package/src/roles/acceptance-coverage-role.js +0 -78
  338. package/src/roles/architect-role.js +0 -77
  339. package/src/roles/coder-role.js +0 -113
  340. package/src/roles/commiter-role.js +0 -113
  341. package/src/roles/domain-curator-role.js +0 -134
  342. package/src/roles/hu-reviewer-role.js +0 -64
  343. package/src/roles/impeccable-role.js +0 -85
  344. package/src/roles/index.js +0 -18
  345. package/src/roles/karajan-brain-role.js +0 -134
  346. package/src/roles/perf-role.js +0 -122
  347. package/src/roles/planner-role.js +0 -151
  348. package/src/roles/refactorer-role.js +0 -30
  349. package/src/roles/repairer-role.js +0 -181
  350. package/src/roles/researcher-role.js +0 -70
  351. package/src/roles/reviewer-role.js +0 -115
  352. package/src/roles/security-role.js +0 -73
  353. package/src/roles/solomon-role.js +0 -271
  354. package/src/roles/sonar-role.js +0 -72
  355. package/src/roles/tester-role.js +0 -235
  356. package/src/roles/tool-correctness-judge-role.js +0 -78
  357. package/src/roles/triage-role.js +0 -99
  358. package/src/session/journal/decisions-writer.js +0 -200
  359. package/src/session/journal/iteration-logger.js +0 -194
  360. package/src/session/journal/summary-writer.js +0 -320
  361. package/src/session/journal/tree-writer.js +0 -164
  362. package/src/session/journal/write-all.js +0 -89
  363. package/src/session-cleanup.js +0 -116
  364. package/src/skills/semantic-detector.js +0 -167
  365. package/src/skills/skill-detector.js +0 -385
  366. package/src/sonar/cloud-scanner.js +0 -78
  367. package/src/tracker-bootstrap.js +0 -44
  368. package/src/types/index.js +0 -38
  369. package/src/types/policy.js +0 -43
  370. package/src/types/solomon.js +0 -87
  371. package/src/utils/async-lock.js +0 -31
  372. package/src/utils/budget.js +0 -229
  373. package/src/utils/cwd-confirm.js +0 -39
  374. package/src/utils/display/event-handlers.js +0 -403
  375. package/src/utils/display/formatters.js +0 -132
  376. package/src/utils/display/header.js +0 -47
  377. package/src/utils/display/session-summary.js +0 -118
  378. package/src/utils/display/solomon.js +0 -42
  379. package/src/utils/pricing.js +0 -29
  380. package/src/utils/quota-simulator.js +0 -56
  381. package/src/utils/role-assigner.js +0 -137
  382. package/src/utils/rtk-wrapper.js +0 -87
  383. package/src/utils/run-registry.js +0 -9
  384. package/src/utils/status-dashboard.js +0 -254
  385. package/src/utils/tool-call-extractor.js +0 -56
  386. package/src/webperf/devtools-detect.js +0 -112
  387. package/src/webperf/webperf-role-integration.js +0 -66
  388. package/templates/skills/kj-run.md +0 -73
@@ -1,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
- }