karajan-code 4.70.3 → 5.0.0

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