karajan-code 4.70.4 → 5.0.1

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