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,129 +0,0 @@
1
- /**
2
- * Domain context synthesizer.
3
- * Filters relevant sections from domain files based on task + hints,
4
- * and compacts the output to stay within a token budget.
5
- */
6
-
7
- const DEFAULT_MAX_TOKENS = 4000;
8
- const CHARS_PER_TOKEN = 4;
9
-
10
- /**
11
- * Synthesize a domain context string from selected domains,
12
- * filtered by relevance to the task and domain hints.
13
- *
14
- * @param {Object} params
15
- * @param {string} params.task — task description
16
- * @param {string[]} params.domainHints — keywords from triage
17
- * @param {import('./domain-loader.js').DomainFile[]|null} params.selectedDomains
18
- * @param {number} [params.maxTokens=4000] — token budget (0 = unlimited)
19
- * @returns {string} synthesized markdown context or empty string
20
- */
21
- export function synthesizeDomainContext({ task, domainHints = [], selectedDomains, maxTokens = DEFAULT_MAX_TOKENS }) {
22
- if (!selectedDomains?.length) return "";
23
-
24
- const keywords = buildKeywords(task, domainHints);
25
- const domainBlocks = [];
26
-
27
- for (const domain of selectedDomains) {
28
- const sections = domain.sections || [];
29
- if (!sections.length) continue;
30
-
31
- // Score all sections but include them all (domain was explicitly selected).
32
- // Sort by relevance so highest-scoring sections survive token truncation.
33
- const scored = sections
34
- .map(section => ({
35
- section,
36
- score: scoreSection(section, keywords)
37
- }))
38
- .sort((a, b) => b.score - a.score);
39
-
40
- const selected = scored.map(s => s.section);
41
-
42
- const sectionMd = selected
43
- .map(s => `#### ${s.heading}\n${s.content}`)
44
- .join("\n\n");
45
-
46
- domainBlocks.push(`### ${domain.name}\n\n${sectionMd}`);
47
- }
48
-
49
- if (!domainBlocks.length) return "";
50
-
51
- let result = domainBlocks.join("\n\n---\n\n");
52
-
53
- // Truncate by token budget if needed
54
- if (maxTokens > 0) {
55
- const maxChars = maxTokens * CHARS_PER_TOKEN;
56
- if (result.length > maxChars) {
57
- result = truncateBySection(result, maxChars);
58
- }
59
- }
60
-
61
- return result;
62
- }
63
-
64
- /**
65
- * Build a set of lowercase keywords from task text and domain hints.
66
- * @param {string} task
67
- * @param {string[]} hints
68
- * @returns {string[]}
69
- */
70
- function buildKeywords(task, hints) {
71
- const words = new Set();
72
-
73
- // Add domain hints
74
- for (const h of hints) {
75
- words.add(h.toLowerCase());
76
- }
77
-
78
- // Extract meaningful words from task (>3 chars, skip stop words)
79
- const stopWords = new Set(["the", "this", "that", "with", "from", "have", "will", "should", "could", "would", "been", "being", "their", "there", "where", "when", "what", "which", "about", "into", "some", "than", "then", "them", "they", "also", "each", "more", "most", "other"]);
80
-
81
- const taskWords = task
82
- .toLowerCase()
83
- .replaceAll(/[^a-z0-9\s-]/g, " ")
84
- .split(/\s+/)
85
- .filter(w => w.length > 3 && !stopWords.has(w));
86
-
87
- for (const w of taskWords) {
88
- words.add(w);
89
- }
90
-
91
- return Array.from(words);
92
- }
93
-
94
- /**
95
- * Score a section by keyword overlap.
96
- * @param {import('./domain-loader.js').DomainSection} section
97
- * @param {string[]} keywords
98
- * @returns {number}
99
- */
100
- function scoreSection(section, keywords) {
101
- const text = `${section.heading} ${section.content}`.toLowerCase();
102
- let score = 0;
103
- for (const kw of keywords) {
104
- if (text.includes(kw)) score++;
105
- }
106
- return score;
107
- }
108
-
109
- /**
110
- * Truncate result by dropping trailing sections to fit within maxChars.
111
- * Preserves complete sections rather than cutting mid-sentence.
112
- * @param {string} text
113
- * @param {number} maxChars
114
- * @returns {string}
115
- */
116
- function truncateBySection(text, maxChars) {
117
- // Split by section markers (#### or ###)
118
- const parts = text.split(/(?=^#{3,4}\s)/m);
119
- let result = "";
120
-
121
- for (const part of parts) {
122
- if (result.length + part.length > maxChars && result.length > 0) {
123
- break;
124
- }
125
- result += part;
126
- }
127
-
128
- return result.trim();
129
- }
@@ -1,433 +0,0 @@
1
- /**
2
- * Git automation helpers for the pipeline.
3
- * Extracted from orchestrator.js for testability and reuse.
4
- */
5
-
6
- import { addCheckpoint } from "../session/store.js";
7
- import { setGitPreexisting } from "../session/mutators.js";
8
- import { pipelineSonarBlock, stampStagedVerdict } from "../review/verdict-store.js";
9
- import { branchSize } from "../commands/pr-size.js";
10
- import { pushRemedy } from "./hu-automation.js";
11
- import { resolveRunIdentity } from "./run-identity.js";
12
- import { getRunContext } from "../utils/run-context.js";
13
- import {
14
- ensureGitRepo,
15
- currentBranch,
16
- hasCommits,
17
- fetchBase,
18
- syncBaseBranch,
19
- ensureBranchUpToDateWithBase,
20
- createBranch,
21
- checkoutBranch,
22
- buildBranchName,
23
- commitAll,
24
- pushBranch,
25
- createPullRequest,
26
- listPendingPaths,
27
- hasRemote
28
- } from "../utils/git.js";
29
-
30
- /**
31
- * Karajan writes / updates several internal directories during a pipeline
32
- * run (the workspace scaffold + the session journal). When they're the
33
- * ONLY thing pending at finalization, the post-loop commit shouldn't
34
- * inherit the user task's `feat:` message — it should be a clear
35
- * `chore: scaffold karajan workspace` so `git log --oneline` reads:
36
- *
37
- * 2d770aa docs: add JSDoc comment to index.js
38
- * abc1234 chore: scaffold karajan workspace
39
- * 8a95ef0 initial
40
- *
41
- * A path counts as scaffolding when it lives at one of these prefixes
42
- * OR is the project's root `.gitignore` (we extend it to include
43
- * stack-specific entries during triage).
44
- */
45
- const KARAJAN_SCAFFOLD_PREFIXES = [
46
- ".karajan/",
47
- ".reviews/",
48
- ".kj/",
49
- ".agent/",
50
- ];
51
-
52
- function isKarajanScaffoldPath(p) {
53
- if (p === ".gitignore") return true;
54
- return KARAJAN_SCAFFOLD_PREFIXES.some((prefix) => p.startsWith(prefix));
55
- }
56
-
57
- /**
58
- * @param {string[]} paths
59
- * @returns {boolean} true when every pending path is a Karajan internal
60
- */
61
- export function pendingPathsAreOnlyKarajanScaffold(paths) {
62
- if (!Array.isArray(paths) || paths.length === 0) return false;
63
- return paths.every(isKarajanScaffoldPath);
64
- }
65
-
66
- /**
67
- * Map the taskType (as classified by triage) to the matching
68
- * Conventional Commits prefix. When triage hasn't run or the type is
69
- * unknown we default to `feat:` (the pre-fix behaviour).
70
- */
71
- const TASK_TYPE_TO_PREFIX = {
72
- doc: "docs",
73
- "add-tests": "test",
74
- refactor: "refactor",
75
- infra: "chore",
76
- sw: "feat",
77
- };
78
-
79
- function prefixForTaskType(taskType) {
80
- return TASK_TYPE_TO_PREFIX[taskType] || "feat";
81
- }
82
-
83
- export function commitMessageFromTask(task, taskType = null) {
84
- // KJC-BUG-0413 (#2565): the header comes from the first paragraph, without
85
- // its leading Markdown marks; the whole task made "feat: # Roadmap ... Context: ...".
86
- const firstParagraph = String(task || "").trim().split(/\n[ \t]*\r?\n/)[0];
87
- const clean = firstParagraph
88
- .replace(/^[#>*\-\s]+/, "")
89
- .replaceAll(/\s+/g, " ")
90
- .trim();
91
- const prefix = prefixForTaskType(taskType);
92
- // KJC-BUG-0294 (#1993): cut on a word, never mid-word (a title with no blank keeps the hard cut).
93
- const cut = clean.length > 72 ? clean.slice(0, 72).replace(/\s+\S*$/, "") : clean;
94
- return `${prefix}: ${cut || "karajan update"}`;
95
- }
96
-
97
- /**
98
- * Build the message used by `finalizeGitAutomation` for the post-loop
99
- * commit. Two paths:
100
- * - When the only pending changes are Karajan scaffold files
101
- * (.gitignore, .karajan/, .reviews/, ...), use a fixed
102
- * `chore: scaffold karajan workspace` message. This avoids the
103
- * "duplicate feat:" git log seen when triage extends .gitignore
104
- * for a freshly detected stack and the coder already committed
105
- * the user-visible change.
106
- * - Otherwise, fall back to `commitMessageFromTask(task, taskType)`,
107
- * respecting the type for the prefix.
108
- */
109
- export function buildFinalizeCommitMessage({ task, taskType, pendingPaths }) {
110
- if (pendingPathsAreOnlyKarajanScaffold(pendingPaths)) {
111
- return "chore: scaffold karajan workspace";
112
- }
113
- return commitMessageFromTask(task, taskType);
114
- }
115
-
116
- // KJC-BUG-0249 (#1896): a branch named after the card is the same on a second
117
- // run of that card, so one already there is taken, not recreated.
118
- async function checkoutRunBranch(created) {
119
- try {
120
- await createBranch(created);
121
- } catch (err) {
122
- if (!/already exists/i.test(err.message)) throw err;
123
- await checkoutBranch(created);
124
- }
125
- }
126
-
127
- export async function prepareGitAutomation({ config, task, logger, session, preexisting: resumedBaseline = null }) {
128
- const enabled = config.git.auto_commit || config.git.auto_push || config.git.auto_pr;
129
- if (!enabled) {
130
- // KJC-TSK-1237: the format stage needs the baseline also when kj does not
131
- // commit; outside git there is none, and that stage says so.
132
- let baseline = resumedBaseline;
133
- if (!baseline) {
134
- try {
135
- baseline = await listPendingPaths();
136
- } catch {
137
- baseline = null;
138
- }
139
- }
140
- if (session && Array.isArray(baseline)) setGitPreexisting(session, baseline);
141
- return { enabled: false };
142
- }
143
-
144
- if (!(await ensureGitRepo())) {
145
- throw new Error("Git automation requested but current directory is not a git repository");
146
- }
147
-
148
- // KJC-BUG-0246 (#1893): one identity for every commit of the run, resolved
149
- // once: the clone's declaration, else the repository's config, else the
150
- // global one. From the global config it is said, because another session
151
- // may switch it mid-run. Nothing resolvable: git refuses the commit loudly.
152
- const identity = resolveRunIdentity({ projectDir: config.projectDir || process.cwd() });
153
- const runContext = getRunContext();
154
- if (identity && runContext) runContext.gitIdentity = identity;
155
- if (identity) {
156
- logger.info(`Git identity for this run: ${identity.name} <${identity.email}> (${identity.source})`);
157
- if (identity.source === "global") logger.warn("The identity comes from the global git config, which another session may switch mid-run: declare this clone's with kj identity set, or set user.email in this repository");
158
- } else {
159
- logger.warn("No git identity resolvable (kj identity set, or git config user.name and user.email): the first commit will fail");
160
- }
161
-
162
- const baseBranch = config.base_branch;
163
- const autoRebase = config.git.auto_rebase !== false;
164
- // KJC-BUG-0294 (#1993): what is dirty or untracked NOW is not the run's; the
165
- // pipeline commits only what it changes. Both names of a pending rename are
166
- // kept. Fails closed: with no answer from git the run stops here rather than
167
- // commit the person's pending changes later.
168
- // KJC-BUG-0412 (#2564): a resumed run keeps the baseline of the session it
169
- // resumes; the tree now also holds that session's own work.
170
- const preexisting = resumedBaseline ?? await listPendingPaths();
171
- if (session) setGitPreexisting(session, preexisting);
172
- const repoHasCommits = await hasCommits();
173
-
174
- // New repo without commits: create branch directly (no fetch/sync possible)
175
- if (!repoHasCommits) {
176
- const created = buildBranchName(config.git.branch_prefix || "feat/", task, session?.pg_task_id || null);
177
- // git checkout -b works even without commits (creates orphan-like branch on first commit)
178
- await createBranch(created);
179
- logger.info(`New repo — created working branch: ${created}`);
180
- await addCheckpoint(session, { stage: "git-prep", branch: created, created: true, newRepo: true });
181
- return { enabled: true, branch: created, baseBranch, autoRebase, preexisting };
182
- }
183
-
184
- await fetchBase(baseBranch).catch(() => {
185
- // No remote — skip fetch (new project without push)
186
- logger.info("No remote configured — skipping fetch");
187
- });
188
-
189
- let branch;
190
- try {
191
- branch = await currentBranch();
192
- } catch {
193
- // HEAD exists but branch detection failed — unusual, treat as main
194
- branch = baseBranch;
195
- }
196
-
197
- if (branch === baseBranch) {
198
- await syncBaseBranch({ baseBranch, autoRebase }).catch(() => {
199
- // No remote tracking — skip sync for new projects
200
- });
201
- const created = buildBranchName(config.git.branch_prefix || "feat/", task, session?.pg_task_id || null);
202
- await checkoutRunBranch(created);
203
- branch = created;
204
- logger.info(`Created working branch: ${branch}`);
205
- await addCheckpoint(session, { stage: "git-prep", branch, created: true });
206
- } else {
207
- await ensureBranchUpToDateWithBase({ branch, baseBranch, autoRebase }).catch(() => {
208
- // No remote tracking — skip rebase for new projects
209
- logger.info("No remote tracking — skipping rebase check");
210
- });
211
- await addCheckpoint(session, { stage: "git-prep", branch, created: false });
212
- }
213
-
214
- return { enabled: true, branch, baseBranch, autoRebase, preexisting };
215
- }
216
-
217
- export function buildPrBody({ task: _task, stageResults }) {
218
- const sections = ["Created by Karajan Code."];
219
-
220
- const approach = stageResults?.planner?.approach;
221
- if (approach) {
222
- sections.push("", "## Approach", approach);
223
- }
224
-
225
- const steps = stageResults?.planner?.steps;
226
- if (steps?.length) {
227
- sections.push("", "## Steps");
228
- for (let i = 0; i < steps.length; i++) {
229
- sections.push(`${i + 1}. ${steps[i]}`);
230
- }
231
- }
232
-
233
- const triageSubtasks = stageResults?.triage?.subtasks;
234
- const shouldDecompose = stageResults?.triage?.shouldDecompose;
235
- const pendingSubtasks = shouldDecompose && triageSubtasks?.length > 1 ? triageSubtasks.slice(1) : [];
236
- if (pendingSubtasks.length > 0) {
237
- sections.push("", "## Pending subtasks", "This PR addresses part of a larger task. The following subtasks were identified but not included:");
238
- for (const subtask of pendingSubtasks) {
239
- sections.push(`- [ ] ${subtask}`);
240
- }
241
- }
242
-
243
- return sections.join("\n");
244
- }
245
-
246
- /**
247
- * Create an early PR after the first coder iteration (Karajan CI flow).
248
- * Commits, pushes, and creates a PR before the reviewer runs.
249
- * Returns { prNumber, prUrl, commits } or null if nothing to commit.
250
- */
251
- export async function earlyPrCreation({ gitCtx, task, logger, session, stageResults = null }) {
252
- if (!gitCtx?.enabled) return null;
253
-
254
- const commitMsg = commitMessageFromTask(task);
255
- const commitResult = await commitAll(commitMsg, null, { exclude: gitCtx.preexisting || [] });
256
- if (!commitResult.committed) {
257
- logger.info("earlyPrCreation: no changes to commit");
258
- return null;
259
- }
260
-
261
- const commits = commitResult.commit ? [commitResult.commit] : [];
262
- await addCheckpoint(session, { stage: "ci-commit", committed: true });
263
-
264
- await pushBranch(gitCtx.branch);
265
- await addCheckpoint(session, { stage: "ci-push", branch: gitCtx.branch });
266
- logger.info(`Pushed branch for early PR: ${gitCtx.branch}`);
267
-
268
- const body = buildPrBody({ task, stageResults });
269
- const prUrl = await createPullRequest({
270
- baseBranch: gitCtx.baseBranch,
271
- branch: gitCtx.branch,
272
- title: commitMessageFromTask(task),
273
- body
274
- });
275
- await addCheckpoint(session, { stage: "ci-pr", branch: gitCtx.branch, pr: prUrl });
276
- logger.info(`Early PR created: ${prUrl}`);
277
-
278
- // Extract PR number from URL (e.g. https://github.com/owner/repo/pull/42)
279
- const prNumber = Number.parseInt(prUrl.split("/").pop(), 10) || null;
280
- return { prNumber, prUrl, commits };
281
- }
282
-
283
- /**
284
- * Incremental push after each coder iteration (Karajan CI flow).
285
- * Commits and pushes without creating a new PR.
286
- */
287
- export async function incrementalPush({ gitCtx, task, logger, session }) {
288
- if (!gitCtx?.enabled) return null;
289
-
290
- const commitMsg = commitMessageFromTask(task);
291
- const commitResult = await commitAll(commitMsg, null, { exclude: gitCtx.preexisting || [] });
292
- if (!commitResult.committed) {
293
- logger.info("incrementalPush: no changes to commit");
294
- return null;
295
- }
296
-
297
- const commits = commitResult.commit ? [commitResult.commit] : [];
298
- await addCheckpoint(session, { stage: "ci-incremental-commit", committed: true });
299
-
300
- await pushBranch(gitCtx.branch);
301
- await addCheckpoint(session, { stage: "ci-incremental-push", branch: gitCtx.branch });
302
- logger.info(`Incremental push: ${gitCtx.branch}`);
303
-
304
- return { commits };
305
- }
306
-
307
- const DEFAULT_PR_SIZE_LIMIT = 200;
308
-
309
- export async function finalizeGitAutomation({ config, gitCtx, task, logger, session, stageResults = null, review = null, measureSize = branchSize }) {
310
- if (!gitCtx?.enabled) return { git: "disabled", commits: [] };
311
- // KJC-BUG-0297 (#1993): the pipeline commits only what a reviewer approved. An
312
- // approval that reviewed nothing (reviewer disabled) stamps no verdict and
313
- // commits nothing: the changes stay in the tree, and the log says why.
314
- if (review && review.reviewed === false) {
315
- logger.warn("No review, no commit: the reviewer was disabled, so the pipeline commits nothing; the changes stay in the tree for a review (kj review --staged)");
316
- await addCheckpoint(session, { stage: "git-commit", committed: false, skipped: "unreviewed" });
317
- return { committed: false, branch: gitCtx.branch, prUrl: null, pr: null, commits: [], unreviewed: true };
318
- }
319
-
320
- // Take a snapshot of pending paths BEFORE staging so we can decide
321
- // whether what's about to be committed is just Karajan scaffolding
322
- // (in which case we use `chore: scaffold karajan workspace`) or a
323
- // real, user-visible change (in which case we honour the task's
324
- // taskType for the Conventional Commits prefix).
325
- const taskType = stageResults?.triage?.taskType ?? null;
326
- let pendingPaths = [];
327
- if (config.git.auto_commit) {
328
- try {
329
- pendingPaths = await listPendingPaths();
330
- } catch { /* best-effort — fall back to default message */ }
331
- }
332
-
333
- const commitMsg =
334
- config.git.commit_message
335
- || buildFinalizeCommitMessage({ task, taskType, pendingPaths });
336
-
337
- let committed = false;
338
- const commits = [];
339
- // KJC-BUG-0299 (#1993): the PR budget is applied BEFORE the commit, with the
340
- // CI gate's own count (kj pr-size), over what this commit will contain. A
341
- // step planned at 150 lines once committed 960. Where the branch cannot be
342
- // measured (a fresh repo with no base yet) kj says so and goes on.
343
- if (config.git.auto_commit) {
344
- const limit = Number(config.git.pr_size_limit) > 0 ? Number(config.git.pr_size_limit) : DEFAULT_PR_SIZE_LIMIT;
345
- let size = null;
346
- try {
347
- size = await measureSize({ projectDir: config.projectDir || process.cwd(), base: gitCtx.baseBranch, exclude: gitCtx.preexisting || [] });
348
- } catch (err) {
349
- logger.warn(`pr-size: the branch could not be measured (${err.message}); the budget is not checked for this commit`);
350
- }
351
- // KJC-BUG-0434: net, as the CI gate counts it.
352
- if (size && size.net > limit) {
353
- logger.warn(`pr-size: ${size.net} net line(s) against ${gitCtx.baseBranch}, over the budget of ${limit}: nothing committed. Split the step (several commits, several HUs); the changes stay in the tree`);
354
- await addCheckpoint(session, { stage: "git-commit", committed: false, skipped: "over-budget", added: size.added, net: size.net, limit });
355
- return { committed: false, branch: gitCtx.branch, prUrl: null, pr: null, commits: [], overBudget: { added: size.added, net: size.net, limit } };
356
- }
357
- // ENV-F1: this path only runs after the pipeline's reviewer approved,
358
- // so stamp that verdict for the staged diff — the v4 pre-commit gate
359
- // (when the repo opted in) accepts the pipeline's own commit.
360
- const commitResult = await commitAll(commitMsg, null, {
361
- exclude: gitCtx.preexisting || [],
362
- beforeCommit: ({ paths } = {}) => stampStagedVerdict({
363
- projectDir: config?.projectDir || process.cwd(),
364
- paths,
365
- reviewer: config?.reviewer || "pipeline-reviewer",
366
- summary: `kj run session ${session?.id || ""}: reviewer approved`.trim(),
367
- // KJC-TSK-0838: the sonar stage result travels with the stamp.
368
- sonar: pipelineSonarBlock(stageResults?.sonar),
369
- }),
370
- });
371
- committed = commitResult.committed;
372
- if (commitResult.commit) {
373
- commits.push(commitResult.commit);
374
- }
375
- // KJC-BUG-0294: what was pending before the run stays out of the commit and is named;
376
- // if the run also touched one of those files, its edit is in the tree, not in the commit.
377
- if (commitResult.leftOut?.length) {
378
- logger.warn(`Left out of the commit, pending before the run (review and commit them yourself): ${commitResult.leftOut.join(", ")}`);
379
- }
380
- await addCheckpoint(session, { stage: "git-commit", committed, leftOut: commitResult.leftOut || [] });
381
- logger.info(committed ? "Committed changes" : "No changes to commit");
382
- }
383
-
384
- // KJC-BUG-0112: without an `origin` remote (the quickstart scenario:
385
- // fresh folder + git init) push/PR automation has nothing to talk to —
386
- // fetchBase used to throw here and escalate to Solomon on EVERY
387
- // iteration, burning the whole budget on an expected condition.
388
- const remoteAvailable = (config.git.auto_push || config.git.auto_pr)
389
- ? await hasRemote()
390
- : false;
391
- if ((config.git.auto_push || config.git.auto_pr) && !remoteAvailable) {
392
- logger.info("No remote configured — skipping push/PR automation");
393
- await addCheckpoint(session, { stage: "git-push", skipped: "no-remote" });
394
- }
395
-
396
- if (remoteAvailable && (config.git.auto_push || config.git.auto_pr)) {
397
- await fetchBase(gitCtx.baseBranch);
398
- await ensureBranchUpToDateWithBase({
399
- branch: gitCtx.branch,
400
- baseBranch: gitCtx.baseBranch,
401
- autoRebase: gitCtx.autoRebase
402
- });
403
- await addCheckpoint(session, { stage: "git-rebase-check", branch: gitCtx.branch });
404
- }
405
-
406
- if (remoteAvailable && (config.git.auto_push || config.git.auto_pr)) {
407
- // KJC-BUG-0248 (#1895): a refused push stops the run loudly, with its remedy.
408
- try {
409
- await pushBranch(gitCtx.branch);
410
- } catch (err) {
411
- throw new Error(`push of ${gitCtx.branch} failed: ${err.message}. ${pushRemedy(err.message, gitCtx.branch)}`, { cause: err });
412
- }
413
- await addCheckpoint(session, { stage: "git-push", branch: gitCtx.branch });
414
- logger.info(`Pushed branch: ${gitCtx.branch}`);
415
- }
416
-
417
- let prUrl = session.ci_pr_url || null;
418
- if (remoteAvailable && config.git.auto_pr && !prUrl) {
419
- const body = buildPrBody({ task, stageResults });
420
- prUrl = await createPullRequest({
421
- baseBranch: gitCtx.baseBranch,
422
- branch: gitCtx.branch,
423
- title: commitMessageFromTask(task),
424
- body
425
- });
426
- await addCheckpoint(session, { stage: "git-pr", branch: gitCtx.branch, pr: prUrl });
427
- logger.info("Pull request created");
428
- } else if (prUrl) {
429
- logger.info(`PR already exists (CI flow): ${prUrl}`);
430
- }
431
-
432
- return { committed, branch: gitCtx.branch, prUrl, pr: prUrl, commits };
433
- }
@@ -1,40 +0,0 @@
1
- /**
2
- * KJC-BUG-0285 (#1982): the coder commits nothing; the pipeline does, once the
3
- * review approves. With --auto-commit the coder ran git commit inside its
4
- * iteration, before the reviewer, and the rejected commit stayed on the branch.
5
- * The stage reads HEAD before the coder runs and, when HEAD moved on top of it,
6
- * undoes those commits SOFTLY: the changes stay in the tree and the index for
7
- * the review. A rewritten branch (HEAD no longer descends from the start) is
8
- * left alone. Plain git, no injected runner: it never throws, it says.
9
- */
10
- import { spawnSync } from "node:child_process";
11
-
12
- const git = (args, cwd) => spawnSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
13
-
14
- /** @returns {string|null} the sha HEAD points at, or null outside a repo with commits */
15
- export function headSha(cwd = process.cwd()) {
16
- try {
17
- const res = git(["rev-parse", "HEAD"], cwd);
18
- return res.status === 0 ? res.stdout.trim() : null;
19
- } catch {
20
- return null;
21
- }
22
- }
23
-
24
- /** @returns {{undone: number, reason?: string}} */
25
- export function undoCommitsSince(sha, cwd = process.cwd()) {
26
- if (!sha) return { undone: 0, reason: "no starting commit" };
27
- try {
28
- const head = headSha(cwd);
29
- if (!head || head === sha) return { undone: 0 };
30
- if (git(["merge-base", "--is-ancestor", sha, "HEAD"], cwd).status !== 0) {
31
- return { undone: 0, reason: "HEAD no longer descends from the iteration's start" };
32
- }
33
- const undone = Number(git(["rev-list", "--count", `${sha}..HEAD`], cwd).stdout.trim());
34
- const reset = git(["reset", "--soft", sha], cwd);
35
- if (reset.status !== 0) return { undone: 0, reason: `git reset --soft failed: ${reset.stderr.trim()}` };
36
- return { undone };
37
- } catch (err) {
38
- return { undone: 0, reason: err.message };
39
- }
40
- }