karajan-code 4.70.4 → 5.0.1

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