karajan-code 4.70.4 → 5.0.0

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