karajan-code 4.70.4 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (380) hide show
  1. package/README.md +2 -2
  2. package/package.json +6 -14
  3. package/packages/hu-board/package.json +1 -1
  4. package/packages/hu-board/public/app.js +4 -18
  5. package/packages/hu-board/public/index.html +5 -2
  6. package/packages/hu-board/public/styles.css +26 -0
  7. package/packages/hu-board/public/utils/api.js +0 -74
  8. package/packages/hu-board/public/utils/board-tour.js +62 -0
  9. package/packages/hu-board/public/utils/board-view.js +49 -144
  10. package/packages/hu-board/public/utils/command-launcher.js +8 -70
  11. package/packages/hu-board/public/utils/formatters.js +10 -0
  12. package/packages/hu-board/public/utils/governance-view.js +13 -1
  13. package/packages/hu-board/public/utils/hu-actions.js +17 -68
  14. package/packages/hu-board/public/utils/init-listeners.js +7 -8
  15. package/packages/hu-board/public/utils/log-panel.js +3 -16
  16. package/packages/hu-board/public/utils/maggle-mode.js +2 -12
  17. package/packages/hu-board/public/utils/modals.js +7 -0
  18. package/packages/hu-board/public/utils/project-picker-view.js +1 -1
  19. package/packages/hu-board/public/utils/server-push.js +3 -22
  20. package/packages/hu-board/public/utils/shared-board-notice.js +33 -0
  21. package/packages/hu-board/public/utils/story-detail-view.js +5 -19
  22. package/packages/hu-board/src/cleanup-zombies.js +0 -1
  23. package/packages/hu-board/src/command-runner.js +36 -25
  24. package/packages/hu-board/src/db-connection.js +282 -0
  25. package/packages/hu-board/src/db-paths.js +113 -0
  26. package/packages/hu-board/src/db-queries.js +188 -0
  27. package/packages/hu-board/src/db-tombstones.js +51 -0
  28. package/packages/hu-board/src/db.js +28 -619
  29. package/packages/hu-board/src/ephemeral-cleaner.js +2 -3
  30. package/packages/hu-board/src/hu-zombie-reaper.js +5 -1
  31. package/packages/hu-board/src/plan-mutations.js +3 -153
  32. package/packages/hu-board/src/plans-service.js +55 -0
  33. package/packages/hu-board/src/routes/api/commands.js +122 -0
  34. package/packages/hu-board/src/routes/api/plans.js +55 -0
  35. package/packages/hu-board/src/routes/api/projects.js +267 -0
  36. package/packages/hu-board/src/routes/api/prompts.js +135 -0
  37. package/packages/hu-board/src/routes/api/sessions.js +95 -0
  38. package/packages/hu-board/src/routes/api/stories.js +183 -0
  39. package/packages/hu-board/src/routes/api/system.js +241 -0
  40. package/packages/hu-board/src/routes/api/tombstones.js +38 -0
  41. package/packages/hu-board/src/routes/api.js +24 -1371
  42. package/packages/hu-board/src/routes/governance.js +23 -1
  43. package/packages/hu-board/src/routes/lanes.js +40 -0
  44. package/packages/hu-board/src/routes/rag.js +4 -3
  45. package/packages/hu-board/src/server.js +20 -34
  46. package/packages/hu-board/src/sync-files.js +318 -0
  47. package/packages/hu-board/src/sync-project.js +99 -0
  48. package/packages/hu-board/src/sync.js +22 -566
  49. package/packages/hu-board/src/zombie-reaper.js +1 -1
  50. package/scripts/dual-publish.mjs +19 -3
  51. package/scripts/postinstall.js +15 -2
  52. package/scripts/verify-pack-mcp.mjs +11 -7
  53. package/scripts/verify-pack.mjs +4 -4
  54. package/src/audit/audit-history.js +3 -2
  55. package/src/audit/dead-exports.js +68 -59
  56. package/src/audit/deterministic-summary.js +33 -13
  57. package/src/audit/npm-signatures.js +41 -0
  58. package/src/audit/sbom.js +35 -0
  59. package/src/checks/agent-token.js +112 -0
  60. package/src/checks/dir-setup.js +34 -1
  61. package/src/checks/native-build.js +21 -39
  62. package/src/checks/runner.js +128 -142
  63. package/src/cli/advanced-commands.js +6 -6
  64. package/src/cli/register-meta.js +42 -82
  65. package/src/cli/register-pipeline.js +8 -143
  66. package/src/cli/register-plan.js +9 -100
  67. package/src/cli/retired-commands.js +49 -0
  68. package/src/cli.js +18 -5
  69. package/src/commands/agent-run.js +40 -26
  70. package/src/commands/audit.js +127 -124
  71. package/src/commands/board.js +5 -9
  72. package/src/commands/check.js +29 -16
  73. package/src/commands/doctor.js +2 -0
  74. package/src/commands/env.js +176 -150
  75. package/src/commands/go.js +71 -53
  76. package/src/commands/harden.js +90 -81
  77. package/src/commands/hu-start.js +52 -0
  78. package/src/commands/hu.js +288 -221
  79. package/src/commands/identity.js +111 -78
  80. package/src/commands/init-atril.js +47 -0
  81. package/src/commands/init.js +11 -40
  82. package/src/commands/install-tools.js +70 -85
  83. package/src/commands/main-guard.js +78 -0
  84. package/src/commands/policy.js +226 -184
  85. package/src/commands/qmd.js +27 -21
  86. package/src/commands/rag.js +75 -48
  87. package/src/commands/review-gate.js +421 -341
  88. package/src/commands/start.js +4 -5
  89. package/src/commands/steward.js +96 -47
  90. package/src/commands/tournament.js +46 -37
  91. package/src/commands/worktree.js +94 -25
  92. package/src/config/loader.js +34 -17
  93. package/src/config/secrets.js +36 -0
  94. package/src/config.js +1 -0
  95. package/src/environment/adr.js +16 -2
  96. package/src/git/karajan-label.js +20 -8
  97. package/src/guards/main-guard.js +54 -0
  98. package/src/harden/hook-templates.js +16 -1
  99. package/src/harden/sentinel/posttooluse.mjs +18 -6
  100. package/src/harden/sentinel/pretooluse-sentinel.mjs +64 -36
  101. package/src/harden/sentinel/sentinel-lib.mjs +129 -26
  102. package/src/harden/sentinel/sentinel-shell.mjs +5 -16
  103. package/src/harden/sentinel/sessionstart.mjs +3 -2
  104. package/src/harden/sentinel/stop.mjs +5 -4
  105. package/src/harden/workflow-templates.js +1 -1
  106. package/src/hu/board-lookup.js +29 -0
  107. package/src/hu/gh-identity.js +23 -0
  108. package/src/hu/github-board.js +4 -2
  109. package/src/hu/lane-prune.js +15 -7
  110. package/src/hu/lane-setup.js +65 -0
  111. package/src/hu/lanes.js +66 -0
  112. package/src/hu/team-board-mirror.js +3 -2
  113. package/src/infrastructure/command-runner.js +1 -1
  114. package/src/infrastructure/file-system-service.js +1 -1
  115. package/src/mcp/handlers/direct-handlers.js +2 -292
  116. package/src/mcp/handlers/hu-handlers.js +1 -35
  117. package/src/mcp/handlers/management-handlers.js +2 -42
  118. package/src/mcp/server-handlers.js +16 -22
  119. package/src/mcp/tools.js +1 -196
  120. package/src/plan/lane-plans.js +54 -0
  121. package/src/plan/plan-store.js +7 -4
  122. package/src/policy/add.js +26 -12
  123. package/src/policy/adr-number-check.js +31 -0
  124. package/src/prompts/audit.js +299 -339
  125. package/src/prompts/card-context.js +10 -5
  126. package/src/prompts/reviewer.js +5 -2
  127. package/src/rag/auto-update.js +56 -43
  128. package/src/rag/bootstrap-index.js +28 -54
  129. package/src/rag/migrate.js +1 -1
  130. package/src/rag/onnx-fallback.js +2 -2
  131. package/src/rag/pid-lease.js +70 -0
  132. package/src/rag/project-store.js +2 -12
  133. package/src/review/card-first.js +57 -46
  134. package/src/review/gate-gitignore.js +23 -4
  135. package/src/review/gate-hook.js +12 -4
  136. package/src/review/one-shot-review.js +45 -35
  137. package/src/review/publish-pr.js +14 -2
  138. package/src/review/repo-docs.js +73 -0
  139. package/src/review/repo-stack.js +59 -0
  140. package/src/review/reviewer-fallback.js +19 -4
  141. package/src/review/seal-commit.js +29 -0
  142. package/src/review/sonar-pregate.js +59 -44
  143. package/src/review/third-party-review.js +90 -16
  144. package/src/review/third-party-size.js +43 -0
  145. package/src/review/third-party-skills.js +54 -0
  146. package/src/review/verify-pr.js +1 -24
  147. package/src/roles/audit-role.js +106 -125
  148. package/src/skills/atril-skills.js +5 -4
  149. package/src/sonar/scanner.js +116 -101
  150. package/src/steward/invariants.js +5 -4
  151. package/src/steward/phantom-sweep.js +66 -0
  152. package/src/tournament/judge.js +60 -53
  153. package/src/utils/gh-account.js +37 -0
  154. package/src/utils/git.js +0 -13
  155. package/src/utils/home-migration.js +7 -1
  156. package/src/utils/main-checkout.js +53 -0
  157. package/src/utils/qmd-collection.js +3 -1
  158. package/src/utils/welcome.js +3 -3
  159. package/templates/{skills → atril/commands}/kj-architect.md +5 -0
  160. package/templates/{skills → atril/commands}/kj-audit.md +5 -0
  161. package/templates/{skills → atril/commands}/kj-board.md +5 -0
  162. package/templates/{skills → atril/commands}/kj-code.md +5 -0
  163. package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
  164. package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
  165. package/templates/{skills → atril/commands}/kj-discover.md +5 -0
  166. package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
  167. package/templates/{skills → atril/commands}/kj-review.md +5 -0
  168. package/templates/{skills → atril/commands}/kj-security.md +5 -0
  169. package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
  170. package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
  171. package/templates/{skills → atril/commands}/kj-test.md +5 -0
  172. package/packages/hu-board/public/utils/run-launcher.js +0 -169
  173. package/packages/hu-board/src/run-tracker.js +0 -121
  174. package/src/activity-log.js +0 -59
  175. package/src/agents/host-agent.js +0 -38
  176. package/src/agents/model-registry.js +0 -142
  177. package/src/audit/plan-adherence.js +0 -224
  178. package/src/autonomy/arbiter-role.js +0 -71
  179. package/src/autonomy/arbiter.js +0 -22
  180. package/src/autonomy/decider.js +0 -18
  181. package/src/autonomy/decision-resolver.js +0 -58
  182. package/src/autonomy/outcome-report.js +0 -55
  183. package/src/autonomy/stage-ask.js +0 -30
  184. package/src/brain/decision-tracker.js +0 -125
  185. package/src/brain/decisor.js +0 -243
  186. package/src/brain/solomon-consult.js +0 -171
  187. package/src/brain/standby-scheduler.js +0 -8
  188. package/src/budget/comparison.js +0 -96
  189. package/src/canvas/parse-md.js +0 -238
  190. package/src/canvas/schema.js +0 -101
  191. package/src/canvas/to-flat.js +0 -85
  192. package/src/canvas/validate.js +0 -163
  193. package/src/ci/dispatch.js +0 -99
  194. package/src/ci/index.js +0 -3
  195. package/src/ci/pr-diff.js +0 -26
  196. package/src/cli/register-standby.js +0 -26
  197. package/src/commands/architect.js +0 -111
  198. package/src/commands/autorun.js +0 -49
  199. package/src/commands/researcher.js +0 -54
  200. package/src/commands/resume.js +0 -72
  201. package/src/commands/review.js +0 -89
  202. package/src/commands/run.js +0 -185
  203. package/src/commands/standby.js +0 -117
  204. package/src/commands/status.js +0 -36
  205. package/src/commands/triage.js +0 -78
  206. package/src/commands/undo.js +0 -62
  207. package/src/domains/domain-loader.js +0 -245
  208. package/src/domains/domain-registry.js +0 -140
  209. package/src/domains/domain-synthesizer.js +0 -129
  210. package/src/git/automation.js +0 -433
  211. package/src/git/coder-commit-guard.js +0 -40
  212. package/src/git/hu-automation.js +0 -243
  213. package/src/git/hu-snapshot.js +0 -10
  214. package/src/git/symlink-targets.js +0 -57
  215. package/src/golden/asserter.js +0 -92
  216. package/src/golden/loader.js +0 -52
  217. package/src/golden/runner.js +0 -92
  218. package/src/golden/schema.js +0 -41
  219. package/src/guards/duplicate-members.js +0 -86
  220. package/src/guards/intent-guard.js +0 -161
  221. package/src/guards/perf-guard.js +0 -126
  222. package/src/guards/policy-guard.js +0 -90
  223. package/src/guards/policy-resolver.js +0 -85
  224. package/src/guards/secret-redactor.js +0 -71
  225. package/src/hu/acceptance-runner.js +0 -138
  226. package/src/hu/lazy-planner.js +0 -109
  227. package/src/hu/parallel-executor.js +0 -94
  228. package/src/hu/routing-metrics.js +0 -17
  229. package/src/hu/split-validator.js +0 -143
  230. package/src/hu/splitting-detector.js +0 -158
  231. package/src/hu/splitting-generator.js +0 -175
  232. package/src/hu/store.js +0 -394
  233. package/src/infrastructure/mocks.js +0 -182
  234. package/src/mcp/handlers/run-handler.js +0 -318
  235. package/src/mcp/sovereignty-guard.js +0 -113
  236. package/src/mcp/tool-arg-normalizers.js +0 -16
  237. package/src/mutate/reviewer-signal.js +0 -76
  238. package/src/orchestrator/agent-fallback.js +0 -81
  239. package/src/orchestrator/brain-coordinator.js +0 -186
  240. package/src/orchestrator/ci-integration.js +0 -139
  241. package/src/orchestrator/config-init.js +0 -426
  242. package/src/orchestrator/direct-actions.js +0 -217
  243. package/src/orchestrator/drivers/error-recovery.js +0 -287
  244. package/src/orchestrator/drivers/init-context.js +0 -238
  245. package/src/orchestrator/drivers/iteration-loop.js +0 -382
  246. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
  247. package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
  248. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
  249. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
  250. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
  251. package/src/orchestrator/drivers/post-loop.js +0 -404
  252. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
  253. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
  254. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
  255. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
  256. package/src/orchestrator/drivers/pre-loop.js +0 -524
  257. package/src/orchestrator/drivers/run-hu-batch.js +0 -468
  258. package/src/orchestrator/feedback-enrichment.js +0 -140
  259. package/src/orchestrator/feedback-queue.js +0 -151
  260. package/src/orchestrator/flow-control.js +0 -170
  261. package/src/orchestrator/flow-runner.js +0 -365
  262. package/src/orchestrator/fs-leak-detector.js +0 -228
  263. package/src/orchestrator/hu-scheduler.js +0 -114
  264. package/src/orchestrator/hu-sub-pipeline.js +0 -642
  265. package/src/orchestrator/iteration-gate.js +0 -73
  266. package/src/orchestrator/iteration-stages.js +0 -9
  267. package/src/orchestrator/parallel-limiter.js +0 -74
  268. package/src/orchestrator/pipeline-context.js +0 -72
  269. package/src/orchestrator/post-loop-stages.js +0 -468
  270. package/src/orchestrator/pre-loop-stages.js +0 -11
  271. package/src/orchestrator/preflight-checks.js +0 -487
  272. package/src/orchestrator/repair/repair-detector.js +0 -117
  273. package/src/orchestrator/repair/repair-runner.js +0 -142
  274. package/src/orchestrator/reviewer-fallback.js +0 -45
  275. package/src/orchestrator/role-output-compressor.js +0 -189
  276. package/src/orchestrator/session-journal.js +0 -308
  277. package/src/orchestrator/solomon-escalation.js +0 -331
  278. package/src/orchestrator/solomon-rules.js +0 -212
  279. package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
  280. package/src/orchestrator/stages/architect-stage.js +0 -172
  281. package/src/orchestrator/stages/coder-stage.js +0 -506
  282. package/src/orchestrator/stages/domain-curator-stage.js +0 -82
  283. package/src/orchestrator/stages/format-stage.js +0 -61
  284. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
  285. package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
  286. package/src/orchestrator/stages/perf-stage.js +0 -140
  287. package/src/orchestrator/stages/planner-stage.js +0 -85
  288. package/src/orchestrator/stages/rag-context-stage.js +0 -80
  289. package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
  290. package/src/orchestrator/stages/research-stage.js +0 -69
  291. package/src/orchestrator/stages/reviewer-stage.js +0 -431
  292. package/src/orchestrator/stages/sonar-stage.js +0 -439
  293. package/src/orchestrator/stages/stage-classes.js +0 -84
  294. package/src/orchestrator/stages/stage-executor.js +0 -155
  295. package/src/orchestrator/stages/suite-green-stage.js +0 -62
  296. package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
  297. package/src/orchestrator/stages/tool-judge-stage.js +0 -53
  298. package/src/orchestrator/stages/triage-stage.js +0 -172
  299. package/src/orchestrator/standby.js +0 -71
  300. package/src/orchestrator/verification-gate.js +0 -183
  301. package/src/orchestrator.js +0 -22
  302. package/src/plan/adr-generator.js +0 -170
  303. package/src/plan/adr-loader.js +0 -60
  304. package/src/plan/plan-executor.js +0 -100
  305. package/src/plan/plan-validation.js +0 -4
  306. package/src/prompts/acceptance-coverage.js +0 -32
  307. package/src/prompts/arbiter.js +0 -38
  308. package/src/prompts/architect.js +0 -103
  309. package/src/prompts/hu-reviewer.js +0 -244
  310. package/src/prompts/tool-correctness-judge.js +0 -31
  311. package/src/prompts/triage.js +0 -57
  312. package/src/rag/embedder.js +0 -2
  313. package/src/rag/embedders/_cloud-base.js +0 -2
  314. package/src/rag/embedders/cohere.js +0 -2
  315. package/src/rag/embedders/mistral.js +0 -2
  316. package/src/rag/embedders/onnx.js +0 -2
  317. package/src/rag/embedders/openai.js +0 -2
  318. package/src/rag/embedders/voyage.js +0 -2
  319. package/src/rag/rerank.js +0 -4
  320. package/src/rag/where-parser.js +0 -4
  321. package/src/repeat-detector.js +0 -88
  322. package/src/review/diff-generator.js +0 -112
  323. package/src/review/git-stash-helper.js +0 -104
  324. package/src/review/schema.js +0 -31
  325. package/src/review/scope-filter.js +0 -152
  326. package/src/review/snapshot-diff.js +0 -122
  327. package/src/review/tdd-discipline.js +0 -90
  328. package/src/review/tdd-policy.js +0 -69
  329. package/src/roles/acceptance-coverage-role.js +0 -78
  330. package/src/roles/architect-role.js +0 -77
  331. package/src/roles/coder-role.js +0 -113
  332. package/src/roles/commiter-role.js +0 -113
  333. package/src/roles/domain-curator-role.js +0 -134
  334. package/src/roles/hu-reviewer-role.js +0 -64
  335. package/src/roles/impeccable-role.js +0 -85
  336. package/src/roles/index.js +0 -18
  337. package/src/roles/karajan-brain-role.js +0 -134
  338. package/src/roles/perf-role.js +0 -122
  339. package/src/roles/planner-role.js +0 -151
  340. package/src/roles/refactorer-role.js +0 -30
  341. package/src/roles/repairer-role.js +0 -181
  342. package/src/roles/researcher-role.js +0 -70
  343. package/src/roles/reviewer-role.js +0 -115
  344. package/src/roles/security-role.js +0 -73
  345. package/src/roles/solomon-role.js +0 -271
  346. package/src/roles/sonar-role.js +0 -72
  347. package/src/roles/tester-role.js +0 -235
  348. package/src/roles/tool-correctness-judge-role.js +0 -78
  349. package/src/roles/triage-role.js +0 -99
  350. package/src/session/journal/decisions-writer.js +0 -200
  351. package/src/session/journal/iteration-logger.js +0 -194
  352. package/src/session/journal/summary-writer.js +0 -320
  353. package/src/session/journal/tree-writer.js +0 -164
  354. package/src/session/journal/write-all.js +0 -89
  355. package/src/session-cleanup.js +0 -116
  356. package/src/skills/semantic-detector.js +0 -167
  357. package/src/skills/skill-detector.js +0 -385
  358. package/src/sonar/cloud-scanner.js +0 -78
  359. package/src/tracker-bootstrap.js +0 -44
  360. package/src/types/index.js +0 -38
  361. package/src/types/policy.js +0 -43
  362. package/src/types/solomon.js +0 -87
  363. package/src/utils/async-lock.js +0 -31
  364. package/src/utils/budget.js +0 -229
  365. package/src/utils/cwd-confirm.js +0 -39
  366. package/src/utils/display/event-handlers.js +0 -403
  367. package/src/utils/display/formatters.js +0 -132
  368. package/src/utils/display/header.js +0 -47
  369. package/src/utils/display/session-summary.js +0 -118
  370. package/src/utils/display/solomon.js +0 -42
  371. package/src/utils/pricing.js +0 -29
  372. package/src/utils/quota-simulator.js +0 -56
  373. package/src/utils/role-assigner.js +0 -137
  374. package/src/utils/rtk-wrapper.js +0 -87
  375. package/src/utils/run-registry.js +0 -9
  376. package/src/utils/status-dashboard.js +0 -254
  377. package/src/utils/tool-call-extractor.js +0 -56
  378. package/src/webperf/devtools-detect.js +0 -112
  379. package/src/webperf/webperf-role-integration.js +0 -66
  380. package/templates/skills/kj-run.md +0 -73
@@ -1,243 +0,0 @@
1
- /**
2
- * Per-HU git automation: branch, commit, push, PR per HU story.
3
- * Called from the HU sub-pipeline wrapper in orchestrator.js.
4
- */
5
- import { commitAll, pushBranch, createPullRequest, hasChanges } from "../utils/git.js";
6
- import { runCommand } from "../utils/process.js";
7
- import { stampStagedVerdict } from "../review/verdict-store.js";
8
-
9
- /**
10
- * Build a git-safe branch name for an HU story.
11
- * @param {string} prefix - e.g. "feat/"
12
- * @param {object} story - HU story {id, title}
13
- * @returns {string}
14
- */
15
- export function buildHuBranchName(prefix, story) {
16
- const baseSlug = String(story.title || story.id || "hu")
17
- .toLowerCase()
18
- .replaceAll(/[^a-z0-9]+/g, "-")
19
- .replaceAll(/^-+|-+$/g, "")
20
- .slice(0, 40);
21
- return `${prefix}${story.id}-${baseSlug}`;
22
- }
23
-
24
- /**
25
- * Resolve the base branch for a given HU based on its dependencies.
26
- * If it has no blocked_by, uses config.base_branch.
27
- * If it has parents, uses the last-created parent branch (assumes parents ran first).
28
- *
29
- * @param {object} story - the HU story
30
- * @param {Map<string,string>} huBranches - map of huId → branchName (already created)
31
- * @param {string} baseBranch - config.base_branch fallback
32
- * @returns {string} base branch name
33
- */
34
- export function resolveHuBase(story, huBranches, baseBranch) {
35
- const parents = story.blocked_by || [];
36
- if (parents.length === 0) return baseBranch;
37
- // Walk parents in reverse order of declaration to find the most recent one
38
- for (let i = parents.length - 1; i >= 0; i--) {
39
- const parentBranch = huBranches.get(parents[i]);
40
- if (parentBranch) return parentBranch;
41
- }
42
- return baseBranch;
43
- }
44
-
45
- /**
46
- * Resolve to an actually-existing local ref. Tries the preferred branch
47
- * first, then `main` and `master`, then plain `HEAD` as last resort.
48
- *
49
- * Why: N6 plan-flow dogfooding (2026-05-07) on a `git init -q` repo
50
- * with `init.defaultBranch=master` produced 7 identical warnings:
51
- * `failed to create branch feat/...-001 from main:
52
- * fatal: 'main' is not a commit and a branch can't be created`.
53
- * Every HU then fell back to running on the original branch, which
54
- * defeats the per-HU isolation the sub-pipeline was supposed to give.
55
- *
56
- * The configured base_branch is honoured when it exists; otherwise we
57
- * pick the most likely default. HEAD always exists once the repo has
58
- * any commit, so the fallback never throws past the gate.
59
- *
60
- * @param {string} preferred
61
- * @returns {Promise<string>}
62
- */
63
- async function resolveExistingBranchRef(preferred) {
64
- const candidates = [preferred, "main", "master", "HEAD"]
65
- .filter((c, i, arr) => c && arr.indexOf(c) === i); // unique, truthy
66
- for (const ref of candidates) {
67
- const probe = await runCommand("git", ["rev-parse", "--verify", "--quiet", ref], {});
68
- if (probe.exitCode === 0) return ref;
69
- }
70
- return preferred; // give up; caller will surface the original error
71
- }
72
-
73
- /** Whether a branch name references the card, as a whole token (BB-002 is not BB-0022). */
74
- export function branchReferencesCard(branch, cardId) {
75
- const id = String(cardId).replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
76
- return new RegExp(`(^|[^a-z0-9])${id}([^a-z0-9]|$)`, "i").test(String(branch || ""));
77
- }
78
-
79
- async function cardBranch({ cardId, story, config, logger }) {
80
- const head = await runCommand("git", ["rev-parse", "--abbrev-ref", "HEAD"], {});
81
- const current = head.exitCode === 0 ? head.stdout.trim() : "";
82
- if (branchReferencesCard(current, cardId)) {
83
- logger.info(`HU ${story.id}: on '${current}', the branch of card ${cardId}`);
84
- return current;
85
- }
86
- const branchName = buildHuBranchName(config.git?.branch_prefix || "feat/", { id: cardId, title: story.title });
87
- // A card branch left by an earlier run keeps its commits: taken, never reset.
88
- const exists = await runCommand("git", ["rev-parse", "--verify", "--quiet", branchName], {});
89
- const base = exists.exitCode === 0 ? null : await resolveExistingBranchRef(config.base_branch || "main");
90
- const how = base ? `created from '${base}'` : "taken";
91
- const res = await runCommand("git", base ? ["checkout", "-b", branchName, base] : ["checkout", branchName], {});
92
- if (res.exitCode !== 0) {
93
- logger.warn(`HU git: branch ${branchName} could not be ${how}: ${res.stderr}`);
94
- return null;
95
- }
96
- logger.info(`HU ${story.id}: branch '${branchName}' ${how} for card ${cardId}`);
97
- return branchName;
98
- }
99
-
100
- /**
101
- * Create a branch for an HU starting from its resolved base.
102
- * Returns the branch name created (or null if git automation is disabled).
103
- *
104
- * @param {object} params
105
- * @param {object} params.story
106
- * @param {Map} params.huBranches
107
- * @param {object} params.config
108
- * @param {object} params.logger
109
- * @returns {Promise<string|null>}
110
- */
111
- export async function prepareHuBranch({ story, huBranches, config, logger, cardId = null }) {
112
- if (!config.git?.auto_commit && !config.git?.auto_push && !config.git?.auto_pr) {
113
- return null;
114
- }
115
- // KJC-BUG-0249 (#1896): with a Planning Game card the run is that card's
116
- // work and lives on ONE branch named after it: the current one when it
117
- // already references the card, else feat/<CARD-ID>-<slug>, created once
118
- // and shared by every HU. HU ids name branches only when there is no card.
119
- if (cardId) {
120
- const known = huBranches.get(cardId);
121
- const branch = known ?? (await cardBranch({ cardId, story, config, logger }));
122
- if (!branch) return null;
123
- huBranches.set(cardId, branch);
124
- huBranches.set(story.id, branch);
125
- return branch;
126
- }
127
- const baseBranch = resolveHuBase(story, huBranches, config.base_branch || "main");
128
- const effectiveBase = await resolveExistingBranchRef(baseBranch);
129
- if (effectiveBase !== baseBranch) {
130
- logger.info(`HU git: configured base '${baseBranch}' not found locally — using '${effectiveBase}' instead`);
131
- }
132
- const prefix = config.git?.branch_prefix || "feat/";
133
- const branchName = buildHuBranchName(prefix, story);
134
-
135
- // Checkout from the resolved base. Use `git checkout -B` to overwrite if rerun.
136
- const res = await runCommand("git", ["checkout", "-B", branchName, effectiveBase], {});
137
- if (res.exitCode !== 0) {
138
- logger.warn(`HU git: failed to create branch ${branchName} from ${effectiveBase}: ${res.stderr}`);
139
- return null;
140
- }
141
- huBranches.set(story.id, branchName);
142
- logger.info(`HU ${story.id}: branch '${branchName}' from '${effectiveBase}'`);
143
- return branchName;
144
- }
145
-
146
- /**
147
- * KJC-BUG-0248 (#1895): the remedy for a push git refused, read from its
148
- * message. GitHub refuses a push that adds or changes a workflow file when
149
- * the token lacks the `workflow` scope; that one has an exact fix.
150
- */
151
- export function pushRemedy(message, branch) {
152
- if (/workflow/i.test(message) && /scope/i.test(message)) {
153
- return `the token lacks the workflow scope: run gh auth refresh -h github.com -s workflow, then git push origin ${branch}`;
154
- }
155
- return `push the branch yourself: git push origin ${branch}`;
156
- }
157
-
158
- /** The warnings a run reports when the push or the PR of any HU failed. */
159
- export function collectGitWarnings(results) {
160
- return results.flatMap((r) => (r.result?.git?.errors ?? []).map((e) => `${r.huId}: ${e.step} failed: ${e.message}. ${e.remedy}`));
161
- }
162
-
163
- /**
164
- * After an HU is approved, commit its changes and optionally push + create PR.
165
- * A push or PR that fails is not only a warn line: it travels in `errors`
166
- * with its remedy, so the run's result says it (KJC-BUG-0248).
167
- *
168
- * @param {object} params
169
- * @param {object} params.story
170
- * @param {string} params.branchName
171
- * @param {object} params.config
172
- * @param {object} params.logger
173
- * @returns {Promise<{committed: boolean, pushed: boolean, prUrl: string|null, branch: string|null, errors: Array<{step: string, message: string, remedy: string}>}>}
174
- */
175
- export async function finalizeHuCommit({ story, branchName, config, logger, cwd = null }) {
176
- const result = { committed: false, pushed: false, prUrl: null, branch: branchName || null, errors: [] };
177
- if (!branchName) return result;
178
-
179
- const changed = await hasChanges(cwd);
180
- if (!changed) {
181
- logger.info(`HU ${story.id}: no changes to commit`);
182
- return result;
183
- }
184
-
185
- const title = story.title || story.id;
186
- const commitMsg = `feat(${story.id}): ${title}`;
187
- if (config.git?.auto_commit) {
188
- // KJC-BUG-0323: this commit runs after the HU's reviewer approved, so it
189
- // records that verdict for the staged diff, as the other post-review commits do.
190
- const commitRes = await commitAll(commitMsg, cwd, {
191
- beforeCommit: ({ paths } = {}) => stampStagedVerdict({
192
- projectDir: cwd || config.projectDir || process.cwd(),
193
- paths,
194
- reviewer: config.reviewer || "pipeline-reviewer",
195
- summary: `kj run HU ${story.id}: reviewer approved`,
196
- }),
197
- });
198
- if (commitRes) {
199
- result.committed = true;
200
- logger.info(`HU ${story.id}: committed on '${branchName}'`);
201
- }
202
- }
203
-
204
- if (config.git?.auto_push && result.committed) {
205
- try {
206
- await pushBranch(branchName, cwd);
207
- result.pushed = true;
208
- logger.info(`HU ${story.id}: pushed '${branchName}'`);
209
- } catch (err) {
210
- const remedy = pushRemedy(err.message, branchName);
211
- result.errors.push({ step: "push", message: err.message, remedy });
212
- logger.warn(`HU ${story.id}: push failed: ${err.message} (${remedy})`);
213
- }
214
- }
215
-
216
- if (config.git?.auto_pr && result.pushed) {
217
- try {
218
- const prBody = [
219
- `## HU ${story.id}: ${title}`,
220
- "",
221
- story.certified?.text || "",
222
- "",
223
- "### Acceptance criteria",
224
- ...(story.acceptance_criteria || []).map(c => `- ${c}`)
225
- ].join("\n");
226
- const url = await createPullRequest({
227
- baseBranch: config.base_branch || "main",
228
- branch: branchName,
229
- title: commitMsg,
230
- body: prBody,
231
- cwd
232
- });
233
- result.prUrl = url;
234
- logger.info(`HU ${story.id}: PR created ${url}`);
235
- } catch (err) {
236
- const remedy = `open the PR yourself: gh pr create --base ${config.base_branch || "main"} --head ${branchName}`;
237
- result.errors.push({ step: "pr", message: err.message, remedy });
238
- logger.warn(`HU ${story.id}: PR creation failed: ${err.message} (${remedy})`);
239
- }
240
- }
241
-
242
- return result;
243
- }
@@ -1,10 +0,0 @@
1
- // Shim de compatibilidad: hu-snapshot vive ahora en karajan-core.
2
- // Mantenemos esta ruta para que callers internos del CLI (orchestrator,
3
- // runners) sigan funcionando sin tocar imports.
4
- export {
5
- snapshotRefForHu,
6
- createHuSnapshot,
7
- hasHuSnapshot,
8
- restoreHuSnapshot,
9
- removeHuSnapshot,
10
- } from "karajan-core/hu-snapshot";
@@ -1,57 +0,0 @@
1
- /**
2
- * KJC-BUG-0414 (#2606, from #2564): Solomon told the coder to drop a file from the commit as a
3
- * mirror of another, but the other was a symlink to it: the link would be committed dangling.
4
- * kj names the in-repo targets of the symlinks in a change, and a condition that takes one of
5
- * them out of the commit is dropped before it reaches the coder.
6
- */
7
- import { lstatSync, readlinkSync } from "node:fs";
8
- import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
9
-
10
- /** The in-repo target of each symlink among `paths` (relative to projectDir). */
11
- export function symlinkTargets(projectDir, paths) {
12
- const root = resolve(projectDir);
13
- return paths.flatMap((link) => {
14
- try {
15
- const abs = join(root, link);
16
- if (!lstatSync(abs).isSymbolicLink()) return [];
17
- const target = relative(root, resolve(dirname(abs), readlinkSync(abs)));
18
- const outside = target === ".." || target.startsWith(`..${sep}`) || isAbsolute(target);
19
- return target && !outside ? [{ link, target: target.split(sep).join("/") }] : [];
20
- } catch {
21
- return [];
22
- }
23
- });
24
- }
25
-
26
- // A `*` matches within one path segment, as in the shell.
27
- const segmentMatches = (pattern, name) => {
28
- const parts = pattern.split("*");
29
- if (parts.length === 1) return pattern === name;
30
- if (!name.startsWith(parts[0]) || !name.endsWith(parts.at(-1))) return false;
31
- let at = parts[0].length;
32
- for (const part of parts.slice(1, -1)) {
33
- const found = name.indexOf(part, at);
34
- if (found < 0) return false;
35
- at = found + part.length;
36
- }
37
- return at <= name.length - parts.at(-1).length;
38
- };
39
- const globMatches = (pattern, path) => {
40
- const p = pattern.split("/");
41
- const t = path.split("/");
42
- return p.length === t.length && p.every((seg, i) => segmentMatches(seg, t[i]));
43
- };
44
- const REMOVES = /\b(unstage|remove|drop|exclude|delete|restore|reset|revert|rm|untrack|quitar|sacar|eliminar|excluir|borrar)\b/i;
45
- // Quoted strings are one token (a path may hold spaces); bare tokens lose surrounding punctuation.
46
- const TOKENS = /"([^"]+)"|'([^']+)'|`([^`]+)`|(\S+)/g;
47
- const tokensOf = (text) => [...String(text).matchAll(TOKENS)]
48
- .map((m) => (m[1] ?? m[2] ?? m[3] ?? m[4]).replace(/^[([{]+/, "").replace(/[)\]}:;,]+$/, ""));
49
- // Named literally anywhere in the text (any character git allows), or by a glob token.
50
- const names = (condition, target) => String(condition).includes(target) || tokensOf(condition).some((t) => globMatches(t, target));
51
-
52
- /** Splits Solomon's conditions: the ones that would take a symlink's target out of the commit go to `dropped`. */
53
- export function keepSymlinkTargets(conditions, targets) {
54
- const takesTargetOut = (condition) => REMOVES.test(condition) && targets.some(({ target }) => names(condition, target));
55
- const dropped = targets.length ? conditions.filter(takesTargetOut) : [];
56
- return { kept: conditions.filter((c) => !dropped.includes(c)), dropped };
57
- }
@@ -1,92 +0,0 @@
1
- /**
2
- * Golden tasks asserter (KJC-TSK-0374). Parses a `summary.md` produced
3
- * by a real `kj run` and compares it against a GoldenTask's structural
4
- * expectations. Pure: no I/O, no subprocess. Subprocess driver lands
5
- * in PR-3a; the 3 fixtures + baseline + spec land in PR-3b.
6
- *
7
- * Three of the five assertion families live here (the ones derivable
8
- * from summary.md alone): expected_commits_min, expected_audit_status,
9
- * expected_plan_adherence_min. expected_test_files and
10
- * allowed_loc_range need filesystem access — PR-3a wires those.
11
- */
12
-
13
- import { escapeRegExp } from "../utils/escape-regexp.js";
14
-
15
- const COMMIT_LINE = /^- `([^`]+)`\s+(.*)$/;
16
- const SCORE_LINE = /^\*\*Score\*\*:\s*(\d+)\s*\/\s*100/m;
17
- const AUDIT_ROW = /^\|\s*`audit`\s*\|\s*([^|]+?)\s*\|/m;
18
-
19
- function extractSection(content, heading) {
20
- const re = new RegExp(`^## ${escapeRegExp(heading)}\\s*$([\\s\\S]*?)(?=^## |\\Z)`, "m");
21
- const m = content.match(re);
22
- return m ? m[1].trim() : "";
23
- }
24
-
25
- function parseCommits(content) {
26
- const section = extractSection(content, "Commits");
27
- if (!section || /No commits/.test(section)) return [];
28
- return section
29
- .split("\n")
30
- .map((line) => line.match(COMMIT_LINE))
31
- .filter(Boolean)
32
- .map(([, hash, message]) => ({ hash, message }));
33
- }
34
-
35
- function parseAuditStatus(content) {
36
- const m = content.match(AUDIT_ROW);
37
- if (!m) return null;
38
- const cell = m[1];
39
- if (/✅|pass/i.test(cell)) return "pass";
40
- if (/❌|fail/i.test(cell)) return "fail";
41
- if (/⚠|warn/i.test(cell)) return "warning";
42
- return null;
43
- }
44
-
45
- function parsePlanAdherence(content) {
46
- const section = extractSection(content, "Plan adherence");
47
- if (!section) return null;
48
- const m = section.match(SCORE_LINE);
49
- if (!m) return null;
50
- const score = Number(m[1]);
51
- return Number.isFinite(score) ? score : null;
52
- }
53
-
54
- export function parseSummary(content) {
55
- const text = String(content || "");
56
- return {
57
- commits: parseCommits(text),
58
- auditStatus: parseAuditStatus(text),
59
- planAdherenceScore: parsePlanAdherence(text),
60
- };
61
- }
62
-
63
- export function assertFromSummary(task, parsed) {
64
- const failures = [];
65
-
66
- const fail = (kind, expected, actual, message) => failures.push({ kind, expected, actual, message });
67
-
68
- if (parsed.commits.length < task.expected_commits_min) {
69
- fail("commits_min", `>= ${task.expected_commits_min}`, parsed.commits.length,
70
- `expected at least ${task.expected_commits_min} commit(s); got ${parsed.commits.length}`);
71
- }
72
-
73
- if (parsed.auditStatus !== task.expected_audit_status) {
74
- const got = parsed.auditStatus === null ? "no audit row in summary" : `"${parsed.auditStatus}"`;
75
- fail("audit_status", task.expected_audit_status, parsed.auditStatus,
76
- `expected audit "${task.expected_audit_status}"; got ${got}`);
77
- }
78
-
79
- if (typeof task.expected_plan_adherence_min === "number") {
80
- const score = parsed.planAdherenceScore;
81
- const min = task.expected_plan_adherence_min;
82
- if (score === null) {
83
- fail("plan_adherence_missing", `>= ${min}`, null,
84
- `expected plan adherence >= ${min}; summary has no Plan adherence section`);
85
- } else if (score < min) {
86
- fail("plan_adherence_below", `>= ${min}`, score,
87
- `expected plan adherence >= ${min}; got ${score}`);
88
- }
89
- }
90
-
91
- return { ok: failures.length === 0, failures };
92
- }
@@ -1,52 +0,0 @@
1
- /**
2
- * Golden tasks loader (KJC-TSK-0374). Reads + validates JSON files
3
- * under a directory; aggregates errors; rejects duplicate ids.
4
- */
5
-
6
- import fs from "node:fs/promises";
7
- import path from "node:path";
8
- import { safeParseGoldenTask } from "./schema.js";
9
-
10
- export async function loadGoldenTasks(dir) {
11
- const entries = await fs.readdir(dir, { withFileTypes: true }).catch((err) => {
12
- throw new Error(`golden tasks directory not readable: ${dir} (${err.code || err.message})`);
13
- });
14
- const jsonFiles = entries
15
- .filter((e) => e.isFile() && e.name.endsWith(".json"))
16
- .map((e) => path.join(dir, e.name))
17
- .sort();
18
-
19
- const tasks = [];
20
- const errors = [];
21
- const seenIds = new Map();
22
-
23
- for (const filePath of jsonFiles) {
24
- let raw;
25
- try {
26
- const content = await fs.readFile(filePath, "utf8");
27
- raw = JSON.parse(content);
28
- } catch (err) {
29
- errors.push(`${path.basename(filePath)}: invalid JSON (${err.message})`);
30
- continue;
31
- }
32
- const result = safeParseGoldenTask(raw);
33
- if (!result.ok) {
34
- const summary = result.issues.map((iss) => ` - ${iss.path || "(root)"}: ${iss.message}`).join("\n");
35
- errors.push(`${path.basename(filePath)}:\n${summary}`);
36
- continue;
37
- }
38
- const existing = seenIds.get(result.task.id);
39
- if (existing) {
40
- errors.push(`${path.basename(filePath)}: duplicate id "${result.task.id}" (also in ${existing})`);
41
- continue;
42
- }
43
- seenIds.set(result.task.id, path.basename(filePath));
44
- tasks.push(result.task);
45
- }
46
-
47
- if (errors.length > 0) {
48
- throw new Error(`golden tasks failed validation:\n${errors.join("\n")}`);
49
- }
50
-
51
- return { tasks };
52
- }
@@ -1,92 +0,0 @@
1
- /**
2
- * Golden tasks subprocess runner (KJC-TSK-0374). Spawns `kj run` in
3
- * a sandbox dir, reads back summary.md, evaluates the schema's
4
- * filesystem-dependent assertions (expected_test_files, allowed_loc_range),
5
- * and merges them with `assertFromSummary` into a single result.
6
- *
7
- * The kj subprocess + diff calls are injected so unit tests can drive
8
- * the runner without spawning anything (real e2e is opt-in via PR-3b).
9
- */
10
-
11
- import fs from "node:fs/promises";
12
- import path from "node:path";
13
- import { runCommand as defaultRun } from "../utils/process.js";
14
- import { parseSummary, assertFromSummary } from "./asserter.js";
15
-
16
- async function findLatestSummary(workDir) {
17
- const journalRoot = path.join(workDir, ".karajan", "sessions");
18
- const sessions = await fs.readdir(journalRoot).catch(() => []);
19
- if (sessions.length === 0) return null;
20
- const stats = await Promise.all(sessions.map(async (id) => {
21
- const file = path.join(journalRoot, id, "summary.md");
22
- const stat = await fs.stat(file).catch(() => null);
23
- return stat ? { file, mtimeMs: stat.mtimeMs } : null;
24
- }));
25
- const valid = stats.filter(Boolean).sort((a, b) => b.mtimeMs - a.mtimeMs);
26
- return valid[0]?.file ?? null;
27
- }
28
-
29
- async function findMissingTestFiles(workDir, paths) {
30
- const checks = await Promise.all(paths.map(async (rel) =>
31
- [rel, await fs.stat(path.join(workDir, rel)).then(() => true).catch(() => false)]));
32
- return checks.filter(([, ok]) => !ok).map(([rel]) => rel);
33
- }
34
-
35
- async function totalLocSince(workDir, baseSha, run) {
36
- const res = await run("git", ["diff", "--numstat", `${baseSha}..HEAD`], { cwd: workDir }).catch(() => null);
37
- if (!res || res.exitCode !== 0) return null;
38
- return String(res.stdout || "").split("\n").filter(Boolean)
39
- .map((l) => l.split("\t")).filter(([a]) => a !== "-")
40
- .reduce((sum, [a]) => sum + Number(a || 0), 0);
41
- }
42
-
43
- export async function runGoldenTask(task, opts = {}) {
44
- const run = opts.run || defaultRun;
45
- const fsApi = opts.fs || fs;
46
- const workDir = opts.workDir;
47
- if (!workDir) throw new Error("runGoldenTask requires opts.workDir");
48
-
49
- const baseSha = opts.baseSha || null;
50
- const kjResult = await run("kj", ["run", task.prompt, "-y"], {
51
- cwd: workDir,
52
- timeout: (task.timeout_minutes || 30) * 60_000,
53
- });
54
-
55
- const summaryPath = opts.summaryPath || await findLatestSummary(workDir);
56
- if (!summaryPath) {
57
- return { ok: false, kjExit: kjResult?.exitCode ?? -1, failures: [{
58
- kind: "no_summary", expected: "summary.md", actual: null,
59
- message: "kj run finished without producing a summary.md",
60
- }] };
61
- }
62
- const content = await fsApi.readFile(summaryPath, "utf8");
63
- const parsed = parseSummary(content);
64
- const summaryAssertion = assertFromSummary(task, parsed);
65
-
66
- const failures = [...summaryAssertion.failures];
67
-
68
- if (Array.isArray(task.expected_test_files) && task.expected_test_files.length > 0) {
69
- const missing = await findMissingTestFiles(workDir, task.expected_test_files);
70
- if (missing.length > 0) {
71
- failures.push({ kind: "test_files_missing", expected: task.expected_test_files, actual: missing,
72
- message: `expected test files not produced: ${missing.join(", ")}` });
73
- }
74
- }
75
-
76
- if (baseSha) {
77
- const loc = await totalLocSince(workDir, baseSha, run);
78
- const [min, max] = task.allowed_loc_range;
79
- if (loc !== null && (loc < min || loc > max)) {
80
- failures.push({ kind: "loc_out_of_range", expected: `${min}..${max}`, actual: loc,
81
- message: `LOC added (${loc}) is outside the allowed range [${min}, ${max}]` });
82
- }
83
- }
84
-
85
- return {
86
- ok: failures.length === 0,
87
- kjExit: kjResult?.exitCode ?? null,
88
- summaryPath,
89
- parsed,
90
- failures,
91
- };
92
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * Golden tasks regression suite — schema (KJC-TSK-0374). Pure data,
3
- * no I/O. See docs/golden-tasks.md (PR-3) for the full spec.
4
- */
5
-
6
- import * as v from "valibot";
7
-
8
- const NonEmptyString = v.pipe(v.string(), v.minLength(1));
9
- const Percent = v.pipe(v.number(), v.minValue(0), v.maxValue(100));
10
-
11
- const NonNegInt = v.pipe(v.number(), v.integer(), v.minValue(0));
12
- const LocRangeSchema = v.pipe(v.array(NonNegInt), v.length(2),
13
- v.check((arr) => arr[0] <= arr[1], "allowed_loc_range: min must be <= max"));
14
-
15
- const GoldenTaskSchema = v.looseObject({
16
- id: v.pipe(NonEmptyString, v.regex(/^[a-z0-9_-]+$/, "id must be lowercase a-z0-9_-")),
17
- title: NonEmptyString,
18
- prompt: NonEmptyString,
19
- description: v.optional(v.string()),
20
- timeout_minutes: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1)), 30),
21
- expected_commits_min: NonNegInt,
22
- expected_audit_status: v.picklist(["pass", "warning", "fail"]),
23
- expected_test_files: v.array(NonEmptyString),
24
- allowed_loc_range: LocRangeSchema,
25
- expected_plan_adherence_min: v.optional(Percent),
26
- });
27
-
28
- /** @typedef {import("valibot").InferOutput<typeof GoldenTaskSchema>} GoldenTask */
29
-
30
- export function parseGoldenTask(raw) {
31
- return v.parse(GoldenTaskSchema, raw);
32
- }
33
-
34
- export function safeParseGoldenTask(raw) {
35
- const result = v.safeParse(GoldenTaskSchema, raw);
36
- if (result.success) return { ok: true, task: result.output };
37
- return { ok: false, issues: result.issues.map((iss) => ({
38
- path: (iss.path || []).map((p) => p.key).join("."),
39
- message: iss.message,
40
- })) };
41
- }
@@ -1,86 +0,0 @@
1
- /**
2
- * Duplicate class members (KJC-TSK-0796) — a bug JavaScript refuses to report.
3
- *
4
- * Declaring the same member twice in a class is legal: the LAST one wins and the
5
- * first disappears in silence. No runtime error, no lint warning, no compiler
6
- * complaint. That is how a `updated()` that loaded the data was lost in GREBLA
7
- * and a tab stayed empty in production for 17 days.
8
- *
9
- * What matters is not that there is a duplicate — it is WHAT WAS LOST: the body
10
- * that will never run. That is what the finding says.
11
- *
12
- * Honest about its limits (the fourth verdict): a file this guard could not read,
13
- * or one whose member names are computed at runtime, is reported as NOT OBSERVABLE
14
- * — never as clean.
15
- */
16
- import { parse } from "@babel/parser";
17
-
18
- const plugins = (file) => [
19
- ...(/\.(ts|tsx|mts|cts)$/.test(file) ? ["typescript"] : []),
20
- ...(/\.(tsx|jsx)$/.test(file) || !/\.(ts|mts|cts)$/.test(file) ? ["jsx"] : []),
21
- "decorators",
22
- ];
23
- const isClass = (node) => node?.type === "ClassDeclaration" || node?.type === "ClassExpression";
24
-
25
- /** Walks every node of the tree; enough for a syntax-only guard (no scope analysis). */
26
- function walk(node, visit) {
27
- if (!node || typeof node.type !== "string") return;
28
- visit(node);
29
- for (const key of Object.keys(node)) {
30
- const child = node[key];
31
- if (Array.isArray(child)) child.forEach((c) => walk(c, visit));
32
- else if (child && typeof child.type === "string") walk(child, visit);
33
- }
34
- }
35
-
36
- const memberName = (member) => {
37
- if (member.computed) return null; // `[expr]() {}` — the name is not known until it runs
38
- const key = member.key ?? member.id;
39
- if (key?.type === "Identifier") return key.name;
40
- if (key?.type === "PrivateName") return `#${key.id.name}`;
41
- if (key?.type === "StringLiteral" || key?.type === "NumericLiteral") return String(key.value);
42
- return null;
43
- };
44
-
45
- /**
46
- * @param {string} source
47
- * @param {{file?: string}} [where]
48
- * @returns {{findings: Array<object>, notObservable: {file: string, reason: string}|null}}
49
- * Each finding: {file, className, member, lostLine, winnerLine, message}.
50
- */
51
- export function findDuplicateMembers(source, { file = "<source>" } = {}) {
52
- let tree;
53
- try {
54
- tree = parse(source, { sourceType: "unambiguous", allowReturnOutsideFunction: true, plugins: plugins(file) });
55
- } catch (err) {
56
- return { findings: [], notObservable: { file, reason: `could not be parsed (${err.message}) — not read as clean` } };
57
- }
58
- const findings = [];
59
- let computed = 0;
60
- walk(tree, (node) => {
61
- if (!isClass(node)) return;
62
- const className = node.id?.name ?? "(anonymous)";
63
- const seen = new Map();
64
- for (const member of node.body.body) {
65
- // get/set of the same name are legal, and a static member is a different slot from an
66
- // instance one — treating either as a duplicate would be the false positive that gets a
67
- // guard switched off (the credibility rule of KJC-PCS-0082).
68
- // A TypeScript overload signature (TSDeclareMethod) declares the same name on purpose and
69
- // carries no body: counting it would flag every overloaded method in the codebase.
70
- if (member.type === "StaticBlock" || member.type === "TSDeclareMethod" || member.type === "TSIndexSignature" || member.kind === "get" || member.kind === "set") continue;
71
- const name = memberName(member);
72
- if (name === null) { computed += 1; continue; }
73
- const key = `${member.static ? "static " : ""}${name}`;
74
- const line = member.loc.start.line;
75
- const first = seen.get(key);
76
- if (first === undefined) { seen.set(key, line); continue; }
77
- findings.push({
78
- file, className, member: key, lostLine: first, winnerLine: line,
79
- message: `${file}:${line} — ${className}: the ${key} of line ${first} never runs, the one of line ${line} replaces it`,
80
- });
81
- seen.set(key, line); // a third copy is reported against the second, not against the first
82
- }
83
- });
84
- const notObservable = computed > 0 && findings.length === 0 ? { file, reason: `${computed} member name(s) computed at runtime — this file cannot be fully checked` } : null;
85
- return { findings, notObservable };
86
- }