@karajan-family/code 4.21.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 (746) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +98 -0
  3. package/bin/karajan-mcp.js +2 -0
  4. package/bin/kj-rag-mcp.js +75 -0
  5. package/bin/kj-tail +361 -0
  6. package/bin/kj.js +2 -0
  7. package/docs/README.es.md +80 -0
  8. package/docs/karajan-code-logo-small.png +0 -0
  9. package/library/logical-clocks-lamport.md +40 -0
  10. package/package.json +140 -0
  11. package/packages/ai-trash/README.md +37 -0
  12. package/packages/ai-trash/bin/kj-trash.js +10 -0
  13. package/packages/ai-trash/package.json +43 -0
  14. package/packages/ai-trash/src/cli.js +200 -0
  15. package/packages/ai-trash/src/destructive-parser.js +74 -0
  16. package/packages/ai-trash/src/git-snapshot.js +128 -0
  17. package/packages/ai-trash/src/hook.js +137 -0
  18. package/packages/ai-trash/src/index.js +14 -0
  19. package/packages/ai-trash/src/install.js +73 -0
  20. package/packages/ai-trash/src/logger.js +60 -0
  21. package/packages/ai-trash/src/manifest.js +91 -0
  22. package/packages/ai-trash/src/permissions.js +26 -0
  23. package/packages/ai-trash/src/snapshot.js +66 -0
  24. package/packages/ai-trash/src/ulid.js +66 -0
  25. package/packages/hu-board/README.md +69 -0
  26. package/packages/hu-board/package.json +28 -0
  27. package/packages/hu-board/public/app.js +259 -0
  28. package/packages/hu-board/public/index.html +77 -0
  29. package/packages/hu-board/public/pipeline.css +132 -0
  30. package/packages/hu-board/public/pipeline.html +48 -0
  31. package/packages/hu-board/public/pipeline.js +200 -0
  32. package/packages/hu-board/public/rag.html +21 -0
  33. package/packages/hu-board/public/rag.js +39 -0
  34. package/packages/hu-board/public/styles.css +973 -0
  35. package/packages/hu-board/public/utils/api.js +139 -0
  36. package/packages/hu-board/public/utils/board-view.js +473 -0
  37. package/packages/hu-board/public/utils/command-launcher.js +213 -0
  38. package/packages/hu-board/public/utils/config-editor.js +181 -0
  39. package/packages/hu-board/public/utils/dashboard-view.js +103 -0
  40. package/packages/hu-board/public/utils/formatters.js +300 -0
  41. package/packages/hu-board/public/utils/graph-view.js +216 -0
  42. package/packages/hu-board/public/utils/hu-actions.js +224 -0
  43. package/packages/hu-board/public/utils/init-listeners.js +180 -0
  44. package/packages/hu-board/public/utils/log-panel.js +222 -0
  45. package/packages/hu-board/public/utils/modals.js +309 -0
  46. package/packages/hu-board/public/utils/plan-rollup-view.js +53 -0
  47. package/packages/hu-board/public/utils/preflight-view.js +89 -0
  48. package/packages/hu-board/public/utils/project-actions.js +143 -0
  49. package/packages/hu-board/public/utils/project-picker-view.js +139 -0
  50. package/packages/hu-board/public/utils/run-launcher.js +169 -0
  51. package/packages/hu-board/public/utils/server-push.js +252 -0
  52. package/packages/hu-board/public/utils/sessions-view.js +199 -0
  53. package/packages/hu-board/public/utils/story-detail-view.js +324 -0
  54. package/packages/hu-board/public/utils/story-edit-form.js +271 -0
  55. package/packages/hu-board/public/wiki.html +32 -0
  56. package/packages/hu-board/public/wiki.js +53 -0
  57. package/packages/hu-board/src/auth.js +92 -0
  58. package/packages/hu-board/src/boot-info.js +16 -0
  59. package/packages/hu-board/src/cleanup-zombies.js +145 -0
  60. package/packages/hu-board/src/command-runner.js +133 -0
  61. package/packages/hu-board/src/config-yaml.js +531 -0
  62. package/packages/hu-board/src/db.js +963 -0
  63. package/packages/hu-board/src/ephemeral-cleaner.js +196 -0
  64. package/packages/hu-board/src/event-bus.js +58 -0
  65. package/packages/hu-board/src/format.js +207 -0
  66. package/packages/hu-board/src/hu-zombie-reaper.js +103 -0
  67. package/packages/hu-board/src/journal-parser.js +180 -0
  68. package/packages/hu-board/src/plan-mutations.js +424 -0
  69. package/packages/hu-board/src/preflight.js +286 -0
  70. package/packages/hu-board/src/routes/api.js +1374 -0
  71. package/packages/hu-board/src/routes/pipeline.js +45 -0
  72. package/packages/hu-board/src/routes/rag.js +42 -0
  73. package/packages/hu-board/src/routes/wiki.js +43 -0
  74. package/packages/hu-board/src/run-tracker.js +121 -0
  75. package/packages/hu-board/src/server.js +438 -0
  76. package/packages/hu-board/src/sync.js +732 -0
  77. package/packages/hu-board/src/token-store.js +56 -0
  78. package/packages/hu-board/src/zombie-reaper.js +206 -0
  79. package/scripts/ai-attribution-guard.yml +84 -0
  80. package/scripts/audit-test-diet.mjs +151 -0
  81. package/scripts/build-sea.mjs +150 -0
  82. package/scripts/diet-baseline.mjs +170 -0
  83. package/scripts/dual-publish.mjs +67 -0
  84. package/scripts/esbuild-sea.config.mjs +273 -0
  85. package/scripts/git-hooks/post-merge +18 -0
  86. package/scripts/install-binary.ps1 +174 -0
  87. package/scripts/install-binary.sh +159 -0
  88. package/scripts/install-guard-all-repos.sh +85 -0
  89. package/scripts/install-kj.sh +193 -0
  90. package/scripts/install.js +901 -0
  91. package/scripts/install.sh +7 -0
  92. package/scripts/postinstall.js +117 -0
  93. package/scripts/protect-all-repos.sh +88 -0
  94. package/scripts/quickstart-gate.mjs +97 -0
  95. package/scripts/regen-arch-stats.sh +71 -0
  96. package/scripts/setup-multi-instance.sh +150 -0
  97. package/scripts/verify-pack.mjs +278 -0
  98. package/src/activity-log.js +59 -0
  99. package/src/agents/agy-agent.js +62 -0
  100. package/src/agents/aider-agent.js +78 -0
  101. package/src/agents/availability.js +52 -0
  102. package/src/agents/base-agent.js +134 -0
  103. package/src/agents/claude-agent.js +413 -0
  104. package/src/agents/codex-agent.js +112 -0
  105. package/src/agents/copilot-agent.js +65 -0
  106. package/src/agents/gemini-agent.js +85 -0
  107. package/src/agents/host-agent.js +38 -0
  108. package/src/agents/index.js +59 -0
  109. package/src/agents/kimi-agent.js +45 -0
  110. package/src/agents/model-registry.js +137 -0
  111. package/src/agents/opencode-agent.js +72 -0
  112. package/src/agents/qwen-agent.js +20 -0
  113. package/src/agents/resolve-bin.js +73 -0
  114. package/src/audit/agent-readiness.js +259 -0
  115. package/src/audit/ai-slop-findings.js +102 -0
  116. package/src/audit/audit-fallback.js +66 -0
  117. package/src/audit/audit-history-display.js +61 -0
  118. package/src/audit/audit-history.js +102 -0
  119. package/src/audit/basal-cost.js +281 -0
  120. package/src/audit/circular-deps.js +144 -0
  121. package/src/audit/dead-exports.js +186 -0
  122. package/src/audit/deterministic-summary.js +327 -0
  123. package/src/audit/harness-scorecard.js +100 -0
  124. package/src/audit/harness-section.js +81 -0
  125. package/src/audit/infra-findings.js +100 -0
  126. package/src/audit/injection-findings.js +114 -0
  127. package/src/audit/issue-filter.js +133 -0
  128. package/src/audit/osv-findings.js +167 -0
  129. package/src/audit/plan-adherence.js +224 -0
  130. package/src/audit/semgrep-findings.js +129 -0
  131. package/src/audit/sonar-findings.js +93 -0
  132. package/src/audit/webperf-input.js +133 -0
  133. package/src/autonomy/arbiter-role.js +71 -0
  134. package/src/autonomy/arbiter.js +22 -0
  135. package/src/autonomy/decider.js +18 -0
  136. package/src/autonomy/decision-resolver.js +58 -0
  137. package/src/autonomy/outcome-report.js +55 -0
  138. package/src/autonomy/policy.js +41 -0
  139. package/src/autonomy/stage-ask.js +30 -0
  140. package/src/bootstrap.js +276 -0
  141. package/src/brain/agent-error-classifier.js +116 -0
  142. package/src/brain/decision-tracker.js +125 -0
  143. package/src/brain/decisor.js +243 -0
  144. package/src/brain/solomon-consult.js +171 -0
  145. package/src/brain/standby-scheduler.js +8 -0
  146. package/src/brain/standby-store.js +12 -0
  147. package/src/brain/with-brain-recovery.js +235 -0
  148. package/src/budget/comparison.js +96 -0
  149. package/src/canvas/parse-md.js +238 -0
  150. package/src/canvas/schema.js +101 -0
  151. package/src/canvas/to-flat.js +85 -0
  152. package/src/canvas/validate.js +163 -0
  153. package/src/checks/ai-surface.js +79 -0
  154. package/src/checks/ai-trash.js +34 -0
  155. package/src/checks/binaries.js +207 -0
  156. package/src/checks/ci.js +105 -0
  157. package/src/checks/config-files.js +217 -0
  158. package/src/checks/dir-setup.js +103 -0
  159. package/src/checks/guarantee-level.js +38 -0
  160. package/src/checks/hardware.js +150 -0
  161. package/src/checks/harness-scorecard.js +41 -0
  162. package/src/checks/mcp-health.js +169 -0
  163. package/src/checks/method.js +73 -0
  164. package/src/checks/native-build.js +67 -0
  165. package/src/checks/node.js +70 -0
  166. package/src/checks/ollama.js +41 -0
  167. package/src/checks/orphans.js +77 -0
  168. package/src/checks/ports.js +188 -0
  169. package/src/checks/project-checks.js +245 -0
  170. package/src/checks/qmd.js +41 -0
  171. package/src/checks/rag-hooks.js +54 -0
  172. package/src/checks/release-check.js +102 -0
  173. package/src/checks/rtk.js +36 -0
  174. package/src/checks/runner.js +326 -0
  175. package/src/checks/skills.js +53 -0
  176. package/src/checks/sonar.js +67 -0
  177. package/src/checks/squeezr.js +36 -0
  178. package/src/checks/system.js +53 -0
  179. package/src/checks/tokens.js +172 -0
  180. package/src/checks/types.js +103 -0
  181. package/src/ci/dispatch.js +99 -0
  182. package/src/ci/index.js +3 -0
  183. package/src/ci/pr-diff.js +26 -0
  184. package/src/ci/repo.js +45 -0
  185. package/src/cli/_shared.js +30 -0
  186. package/src/cli/advanced-commands.js +61 -0
  187. package/src/cli/register-meta.js +772 -0
  188. package/src/cli/register-pipeline.js +335 -0
  189. package/src/cli/register-plan.js +224 -0
  190. package/src/cli/register-roles-skills.js +54 -0
  191. package/src/cli/register-sonar.js +28 -0
  192. package/src/cli/register-standby.js +26 -0
  193. package/src/cli.js +155 -0
  194. package/src/commands/advanced.js +31 -0
  195. package/src/commands/agent-run.js +63 -0
  196. package/src/commands/agents.js +105 -0
  197. package/src/commands/architect.js +123 -0
  198. package/src/commands/audit.js +430 -0
  199. package/src/commands/autorun.js +49 -0
  200. package/src/commands/board.js +458 -0
  201. package/src/commands/check.js +40 -0
  202. package/src/commands/clean.js +145 -0
  203. package/src/commands/code.js +45 -0
  204. package/src/commands/config.js +74 -0
  205. package/src/commands/discover.js +105 -0
  206. package/src/commands/doctor.js +251 -0
  207. package/src/commands/env.js +207 -0
  208. package/src/commands/harden.js +203 -0
  209. package/src/commands/hu.js +210 -0
  210. package/src/commands/identity.js +60 -0
  211. package/src/commands/init.js +950 -0
  212. package/src/commands/install-tools.js +432 -0
  213. package/src/commands/mutate.js +94 -0
  214. package/src/commands/ollama.js +44 -0
  215. package/src/commands/onboard.js +45 -0
  216. package/src/commands/plan/_shared.js +55 -0
  217. package/src/commands/plan/add-hu.js +23 -0
  218. package/src/commands/plan/delete.js +9 -0
  219. package/src/commands/plan/fix.js +99 -0
  220. package/src/commands/plan/generate.js +532 -0
  221. package/src/commands/plan/index.js +13 -0
  222. package/src/commands/plan/list.js +21 -0
  223. package/src/commands/plan/migrate-result.js +41 -0
  224. package/src/commands/plan/ready.js +46 -0
  225. package/src/commands/plan/remove-hu.js +17 -0
  226. package/src/commands/plan/share.js +85 -0
  227. package/src/commands/plan/show.js +81 -0
  228. package/src/commands/plan/unshare.js +41 -0
  229. package/src/commands/plan/validate.js +20 -0
  230. package/src/commands/plan.js +17 -0
  231. package/src/commands/policy.js +164 -0
  232. package/src/commands/privacy.js +45 -0
  233. package/src/commands/qmd.js +86 -0
  234. package/src/commands/rag-mcp.js +18 -0
  235. package/src/commands/rag.js +173 -0
  236. package/src/commands/report-issue.js +77 -0
  237. package/src/commands/report.js +469 -0
  238. package/src/commands/researcher.js +54 -0
  239. package/src/commands/resume.js +68 -0
  240. package/src/commands/review-gate.js +355 -0
  241. package/src/commands/review.js +89 -0
  242. package/src/commands/roles.js +116 -0
  243. package/src/commands/run.js +185 -0
  244. package/src/commands/scan.js +18 -0
  245. package/src/commands/skills.js +86 -0
  246. package/src/commands/sonar.js +53 -0
  247. package/src/commands/standby.js +117 -0
  248. package/src/commands/start.js +121 -0
  249. package/src/commands/status.js +36 -0
  250. package/src/commands/sync.js +103 -0
  251. package/src/commands/telemetry.js +70 -0
  252. package/src/commands/tournament.js +85 -0
  253. package/src/commands/triage.js +78 -0
  254. package/src/commands/undo.js +62 -0
  255. package/src/commands/watch.js +46 -0
  256. package/src/commands/webperf.js +162 -0
  257. package/src/commands/worktree.js +76 -0
  258. package/src/config/defaults.js +270 -0
  259. package/src/config/loader.js +204 -0
  260. package/src/config/overrides.js +220 -0
  261. package/src/config/role-resolver.js +171 -0
  262. package/src/config/schema.js +299 -0
  263. package/src/config/test-harness.js +78 -0
  264. package/src/config.js +34 -0
  265. package/src/domains/domain-loader.js +245 -0
  266. package/src/domains/domain-registry.js +140 -0
  267. package/src/domains/domain-synthesizer.js +129 -0
  268. package/src/environment/adr.js +45 -0
  269. package/src/environment/board-access.js +97 -0
  270. package/src/environment/board-select.js +59 -0
  271. package/src/environment/briefs.js +129 -0
  272. package/src/environment/issue-report.js +72 -0
  273. package/src/environment/mcp-wiring.js +44 -0
  274. package/src/environment/playbook.js +127 -0
  275. package/src/git/automation.js +334 -0
  276. package/src/git/hu-automation.js +171 -0
  277. package/src/git/hu-snapshot.js +10 -0
  278. package/src/golden/asserter.js +92 -0
  279. package/src/golden/loader.js +52 -0
  280. package/src/golden/runner.js +92 -0
  281. package/src/golden/schema.js +41 -0
  282. package/src/guards/intent-guard.js +161 -0
  283. package/src/guards/output-guard.js +149 -0
  284. package/src/guards/perf-guard.js +126 -0
  285. package/src/guards/policy-guard.js +90 -0
  286. package/src/guards/policy-resolver.js +85 -0
  287. package/src/guards/secret-redactor.js +71 -0
  288. package/src/harden/advisory.js +203 -0
  289. package/src/harden/alternatives.js +50 -0
  290. package/src/harden/check.js +81 -0
  291. package/src/harden/config-engine.js +128 -0
  292. package/src/harden/config-templates.js +122 -0
  293. package/src/harden/guidelines-engine.js +48 -0
  294. package/src/harden/guidelines-templates.js +36 -0
  295. package/src/harden/harden-engine.js +80 -0
  296. package/src/harden/harness-hooks.js +107 -0
  297. package/src/harden/hook-commands.js +25 -0
  298. package/src/harden/hook-templates.js +183 -0
  299. package/src/harden/interactive.js +76 -0
  300. package/src/harden/sentinel-hooks.js +721 -0
  301. package/src/harden/stack-roots.js +105 -0
  302. package/src/harden/test-on-push.js +38 -0
  303. package/src/harden/workflow-engine.js +116 -0
  304. package/src/harden/workflow-templates.js +285 -0
  305. package/src/hu/acceptance-runner.js +138 -0
  306. package/src/hu/auto-generator.js +358 -0
  307. package/src/hu/graph.js +71 -0
  308. package/src/hu/lazy-planner.js +109 -0
  309. package/src/hu/model-router.js +108 -0
  310. package/src/hu/parallel-executor.js +94 -0
  311. package/src/hu/split-validator.js +143 -0
  312. package/src/hu/splitting-detector.js +158 -0
  313. package/src/hu/splitting-generator.js +175 -0
  314. package/src/hu/store.js +392 -0
  315. package/src/hu/worktree-bootstrap.js +59 -0
  316. package/src/identity/bootstrap.js +48 -0
  317. package/src/identity/compare.js +38 -0
  318. package/src/identity/detect.js +51 -0
  319. package/src/identity/store.js +66 -0
  320. package/src/infrastructure/command-runner.js +44 -0
  321. package/src/infrastructure/environment.js +38 -0
  322. package/src/infrastructure/file-system-service.js +63 -0
  323. package/src/infrastructure/mocks.js +182 -0
  324. package/src/lang/chunk-go.js +94 -0
  325. package/src/lang/chunk-java.js +90 -0
  326. package/src/lang/chunk-python.js +88 -0
  327. package/src/lang/chunk-rust.js +102 -0
  328. package/src/lang/go.js +14 -0
  329. package/src/lang/java.js +16 -0
  330. package/src/lang/js.js +8 -0
  331. package/src/lang/python.js +14 -0
  332. package/src/lang/registry.js +79 -0
  333. package/src/lang/rust.js +14 -0
  334. package/src/lang/tree-sitter-loader.js +45 -0
  335. package/src/mcp/direct-role-runner.js +112 -0
  336. package/src/mcp/error-classifiers.js +66 -0
  337. package/src/mcp/handlers/direct-handlers.js +486 -0
  338. package/src/mcp/handlers/hu-handlers.js +93 -0
  339. package/src/mcp/handlers/management-handlers.js +265 -0
  340. package/src/mcp/handlers/rag-handler.js +52 -0
  341. package/src/mcp/handlers/run-handler.js +313 -0
  342. package/src/mcp/orphan-guard.js +131 -0
  343. package/src/mcp/preflight.js +47 -0
  344. package/src/mcp/progress.js +149 -0
  345. package/src/mcp/response-compressor.js +81 -0
  346. package/src/mcp/run-kj.js +135 -0
  347. package/src/mcp/server-handlers.js +79 -0
  348. package/src/mcp/server.js +66 -0
  349. package/src/mcp/shared-helpers.js +297 -0
  350. package/src/mcp/sovereignty-guard.js +112 -0
  351. package/src/mcp/suggest-handler.js +92 -0
  352. package/src/mcp/tool-arg-normalizers.js +16 -0
  353. package/src/mcp/tools.js +432 -0
  354. package/src/mutate/diff-scope.js +105 -0
  355. package/src/mutate/reviewer-signal.js +76 -0
  356. package/src/mutate/runner.js +72 -0
  357. package/src/mutate/tool-registry.js +90 -0
  358. package/src/onboarder/cache.js +24 -0
  359. package/src/onboarder/collectors/framework-multilang.js +64 -0
  360. package/src/onboarder/collectors/index.js +136 -0
  361. package/src/orchestrator/agent-fallback.js +81 -0
  362. package/src/orchestrator/brain-coordinator.js +186 -0
  363. package/src/orchestrator/ci-integration.js +139 -0
  364. package/src/orchestrator/config-init.js +510 -0
  365. package/src/orchestrator/direct-actions.js +217 -0
  366. package/src/orchestrator/drivers/error-recovery.js +287 -0
  367. package/src/orchestrator/drivers/init-context.js +227 -0
  368. package/src/orchestrator/drivers/iteration-loop.js +380 -0
  369. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +69 -0
  370. package/src/orchestrator/drivers/iteration-phases/guards.js +84 -0
  371. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +29 -0
  372. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +114 -0
  373. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +60 -0
  374. package/src/orchestrator/drivers/post-loop.js +383 -0
  375. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +136 -0
  376. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +47 -0
  377. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +84 -0
  378. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +391 -0
  379. package/src/orchestrator/drivers/pre-loop.js +507 -0
  380. package/src/orchestrator/drivers/run-hu-batch.js +427 -0
  381. package/src/orchestrator/feedback-enrichment.js +140 -0
  382. package/src/orchestrator/feedback-queue.js +151 -0
  383. package/src/orchestrator/flow-control.js +170 -0
  384. package/src/orchestrator/flow-runner.js +364 -0
  385. package/src/orchestrator/fs-leak-detector.js +210 -0
  386. package/src/orchestrator/hu-scheduler.js +114 -0
  387. package/src/orchestrator/hu-sub-pipeline.js +616 -0
  388. package/src/orchestrator/integrations.js +81 -0
  389. package/src/orchestrator/iteration-gate.js +73 -0
  390. package/src/orchestrator/iteration-stages.js +9 -0
  391. package/src/orchestrator/parallel-limiter.js +74 -0
  392. package/src/orchestrator/pipeline-context.js +72 -0
  393. package/src/orchestrator/post-loop-stages.js +468 -0
  394. package/src/orchestrator/pre-loop-stages.js +11 -0
  395. package/src/orchestrator/preflight-checks.js +462 -0
  396. package/src/orchestrator/repair/repair-detector.js +117 -0
  397. package/src/orchestrator/repair/repair-runner.js +142 -0
  398. package/src/orchestrator/reviewer-fallback.js +45 -0
  399. package/src/orchestrator/role-output-compressor.js +189 -0
  400. package/src/orchestrator/session-journal.js +308 -0
  401. package/src/orchestrator/solomon-escalation.js +305 -0
  402. package/src/orchestrator/solomon-rules.js +212 -0
  403. package/src/orchestrator/stages/architect-stage.js +172 -0
  404. package/src/orchestrator/stages/coder-stage.js +480 -0
  405. package/src/orchestrator/stages/domain-curator-stage.js +82 -0
  406. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +127 -0
  407. package/src/orchestrator/stages/hu-reviewer-stage.js +394 -0
  408. package/src/orchestrator/stages/perf-stage.js +140 -0
  409. package/src/orchestrator/stages/planner-stage.js +85 -0
  410. package/src/orchestrator/stages/rag-context-stage.js +77 -0
  411. package/src/orchestrator/stages/rag-preload-decisor.js +58 -0
  412. package/src/orchestrator/stages/research-stage.js +69 -0
  413. package/src/orchestrator/stages/reviewer-stage.js +422 -0
  414. package/src/orchestrator/stages/sonar-stage.js +421 -0
  415. package/src/orchestrator/stages/stage-classes.js +84 -0
  416. package/src/orchestrator/stages/stage-executor.js +155 -0
  417. package/src/orchestrator/stages/tdd-discipline-stage.js +56 -0
  418. package/src/orchestrator/stages/tool-judge-stage.js +53 -0
  419. package/src/orchestrator/stages/triage-stage.js +172 -0
  420. package/src/orchestrator/standby.js +71 -0
  421. package/src/orchestrator/verification-gate.js +183 -0
  422. package/src/orchestrator.js +22 -0
  423. package/src/plan/adr-generator.js +170 -0
  424. package/src/plan/adr-loader.js +60 -0
  425. package/src/plan/plan-executor.js +100 -0
  426. package/src/plan/plan-fixer.js +192 -0
  427. package/src/plan/plan-hu-ops.js +16 -0
  428. package/src/plan/plan-id.js +4 -0
  429. package/src/plan/plan-migrate-result.js +24 -0
  430. package/src/plan/plan-reviewer.js +236 -0
  431. package/src/plan/plan-schema.js +334 -0
  432. package/src/plan/plan-store.js +260 -0
  433. package/src/plan/plan-structural-pass.js +122 -0
  434. package/src/plan/plan-validation.js +4 -0
  435. package/src/plan/preflight-hu.js +105 -0
  436. package/src/plan/spec-detector.js +61 -0
  437. package/src/plan/tests-synthesizer.js +131 -0
  438. package/src/planning-game/adapter.js +108 -0
  439. package/src/planning-game/architect-adrs.js +45 -0
  440. package/src/planning-game/client.js +108 -0
  441. package/src/planning-game/decomposition.js +87 -0
  442. package/src/planning-game/pipeline-adapter.js +251 -0
  443. package/src/plugins/loader.js +67 -0
  444. package/src/policy/add.js +111 -0
  445. package/src/policy/decisions.js +42 -0
  446. package/src/policy/engine.js +82 -0
  447. package/src/policy/exceptions.js +60 -0
  448. package/src/privacy/onboarding.js +35 -0
  449. package/src/privacy/scan.js +120 -0
  450. package/src/prompts/arbiter.js +38 -0
  451. package/src/prompts/architect.js +103 -0
  452. package/src/prompts/audit.js +474 -0
  453. package/src/prompts/coder.js +287 -0
  454. package/src/prompts/diff-clip.js +27 -0
  455. package/src/prompts/discover.js +222 -0
  456. package/src/prompts/hu-reviewer.js +219 -0
  457. package/src/prompts/planner.js +312 -0
  458. package/src/prompts/prompt-layout.js +80 -0
  459. package/src/prompts/prompt-resolver.js +120 -0
  460. package/src/prompts/reviewer.js +75 -0
  461. package/src/prompts/rtk-snippet.js +25 -0
  462. package/src/prompts/spec-reviewer.js +66 -0
  463. package/src/prompts/split-signal.js +82 -0
  464. package/src/prompts/start-decision.js +52 -0
  465. package/src/prompts/tool-correctness-judge.js +31 -0
  466. package/src/prompts/triage.js +57 -0
  467. package/src/rag/auto-update.js +72 -0
  468. package/src/rag/chunker-ast.js +56 -0
  469. package/src/rag/chunker.js +132 -0
  470. package/src/rag/embedder.js +2 -0
  471. package/src/rag/embedders/_cloud-base.js +2 -0
  472. package/src/rag/embedders/cohere.js +2 -0
  473. package/src/rag/embedders/factory.js +2 -0
  474. package/src/rag/embedders/mistral.js +2 -0
  475. package/src/rag/embedders/onnx.js +2 -0
  476. package/src/rag/embedders/openai.js +2 -0
  477. package/src/rag/embedders/voyage.js +2 -0
  478. package/src/rag/eval.js +61 -0
  479. package/src/rag/governed-embedder.js +39 -0
  480. package/src/rag/indexer.js +199 -0
  481. package/src/rag/library.js +62 -0
  482. package/src/rag/ollama-capability.js +59 -0
  483. package/src/rag/ollama-manager.js +98 -0
  484. package/src/rag/onnx-fallback.js +69 -0
  485. package/src/rag/rerank.js +4 -0
  486. package/src/rag/retriever.js +2 -0
  487. package/src/rag/vec-store.js +17 -0
  488. package/src/rag/watcher.js +62 -0
  489. package/src/rag/where-parser.js +4 -0
  490. package/src/repeat-detector.js +88 -0
  491. package/src/review/card-first.js +99 -0
  492. package/src/review/card-verify.js +83 -0
  493. package/src/review/diff-generator.js +112 -0
  494. package/src/review/gate-gitignore.js +77 -0
  495. package/src/review/git-stash-helper.js +104 -0
  496. package/src/review/mutation-pregate.js +48 -0
  497. package/src/review/one-shot-review.js +122 -0
  498. package/src/review/parser.js +80 -0
  499. package/src/review/policy-gate.js +26 -0
  500. package/src/review/profiles.js +66 -0
  501. package/src/review/reviewer-fallback.js +125 -0
  502. package/src/review/schema.js +31 -0
  503. package/src/review/scope-filter.js +152 -0
  504. package/src/review/snapshot-diff.js +122 -0
  505. package/src/review/solomon-arbitration.js +113 -0
  506. package/src/review/sonar-pregate.js +63 -0
  507. package/src/review/tdd-discipline.js +90 -0
  508. package/src/review/tdd-policy.js +69 -0
  509. package/src/review/tests-with-code.js +33 -0
  510. package/src/review/verdict-store.js +87 -0
  511. package/src/review/workspace.js +21 -0
  512. package/src/roles/agent-role.js +194 -0
  513. package/src/roles/architect-role.js +77 -0
  514. package/src/roles/audit-role.js +258 -0
  515. package/src/roles/base-role.js +165 -0
  516. package/src/roles/coder-role.js +107 -0
  517. package/src/roles/commiter-role.js +110 -0
  518. package/src/roles/discover-role.js +81 -0
  519. package/src/roles/domain-curator-role.js +141 -0
  520. package/src/roles/hu-reviewer-role.js +64 -0
  521. package/src/roles/impeccable-role.js +85 -0
  522. package/src/roles/index.js +18 -0
  523. package/src/roles/karajan-brain-role.js +134 -0
  524. package/src/roles/onboarder-role.js +61 -0
  525. package/src/roles/perf-role.js +122 -0
  526. package/src/roles/planner-role.js +151 -0
  527. package/src/roles/refactorer-role.js +30 -0
  528. package/src/roles/repairer-role.js +181 -0
  529. package/src/roles/researcher-role.js +70 -0
  530. package/src/roles/reviewer-role.js +104 -0
  531. package/src/roles/security-role.js +73 -0
  532. package/src/roles/solomon-role.js +265 -0
  533. package/src/roles/sonar-role.js +70 -0
  534. package/src/roles/spec-reviewer-role.js +106 -0
  535. package/src/roles/tester-role.js +235 -0
  536. package/src/roles/tool-correctness-judge-role.js +78 -0
  537. package/src/roles/triage-role.js +99 -0
  538. package/src/session/journal/decisions-writer.js +200 -0
  539. package/src/session/journal/iteration-logger.js +194 -0
  540. package/src/session/journal/summary-writer.js +320 -0
  541. package/src/session/journal/tree-writer.js +164 -0
  542. package/src/session/journal/write-all.js +89 -0
  543. package/src/session/mutators.js +339 -0
  544. package/src/session/runtime-overrides.js +59 -0
  545. package/src/session/store.js +106 -0
  546. package/src/session-cleanup.js +116 -0
  547. package/src/skills/addyosmani-catalog.js +315 -0
  548. package/src/skills/addyosmani-role-map.js +139 -0
  549. package/src/skills/openskills-client.js +157 -0
  550. package/src/skills/semantic-detector.js +167 -0
  551. package/src/skills/skill-detector.js +385 -0
  552. package/src/skills/skill-loader.js +219 -0
  553. package/src/skills/skills-cache.js +128 -0
  554. package/src/sonar/api.js +128 -0
  555. package/src/sonar/cloud-scanner.js +78 -0
  556. package/src/sonar/config-resolver.js +88 -0
  557. package/src/sonar/credentials.js +60 -0
  558. package/src/sonar/discovery.js +124 -0
  559. package/src/sonar/enforcer.js +19 -0
  560. package/src/sonar/issue-filter.js +12 -0
  561. package/src/sonar/manager.js +181 -0
  562. package/src/sonar/project-key.js +120 -0
  563. package/src/sonar/scanner.js +307 -0
  564. package/src/sonar/token-bootstrap.js +205 -0
  565. package/src/sonar/token-recovery.js +67 -0
  566. package/src/spec-review/refine-loop.js +58 -0
  567. package/src/spec-review/run-spec-review.js +119 -0
  568. package/src/start/assessment.js +74 -0
  569. package/src/start/maturity.js +73 -0
  570. package/src/start/start-decider-role.js +79 -0
  571. package/src/start/sweep.js +157 -0
  572. package/src/sync/apply-canvas-patch.js +55 -0
  573. package/src/sync/detect-drift.js +113 -0
  574. package/src/tournament/crown.js +88 -0
  575. package/src/tournament/judge.js +150 -0
  576. package/src/tournament/run.js +142 -0
  577. package/src/tournament/scoreboard.js +124 -0
  578. package/src/tracker-bootstrap.js +44 -0
  579. package/src/types/agent.js +55 -0
  580. package/src/types/config.js +114 -0
  581. package/src/types/hu.js +47 -0
  582. package/src/types/index.js +38 -0
  583. package/src/types/policy.js +43 -0
  584. package/src/types/session.js +75 -0
  585. package/src/types/solomon.js +87 -0
  586. package/src/types/stage.js +68 -0
  587. package/src/utils/agent-detect.js +93 -0
  588. package/src/utils/ai-trash.js +48 -0
  589. package/src/utils/async-lock.js +31 -0
  590. package/src/utils/atomic-write.js +6 -0
  591. package/src/utils/banner.js +66 -0
  592. package/src/utils/binary-sources.js +70 -0
  593. package/src/utils/board-prompt-bridge.js +108 -0
  594. package/src/utils/budget.js +229 -0
  595. package/src/utils/cli-ask-question.js +93 -0
  596. package/src/utils/cli-progress.js +220 -0
  597. package/src/utils/cli-run-log.js +83 -0
  598. package/src/utils/cwd-confirm.js +39 -0
  599. package/src/utils/derive-project-name-from-cwd.js +119 -0
  600. package/src/utils/display/event-handlers.js +403 -0
  601. package/src/utils/display/formatters.js +132 -0
  602. package/src/utils/display/header.js +47 -0
  603. package/src/utils/display/resume-hint.js +41 -0
  604. package/src/utils/display/session-summary.js +118 -0
  605. package/src/utils/display/solomon.js +42 -0
  606. package/src/utils/display/spec-findings.js +44 -0
  607. package/src/utils/doc-links.js +29 -0
  608. package/src/utils/docker-install.js +34 -0
  609. package/src/utils/escape-regexp.js +15 -0
  610. package/src/utils/events.js +35 -0
  611. package/src/utils/fs.js +19 -0
  612. package/src/utils/garbage-collector.js +481 -0
  613. package/src/utils/git.js +279 -0
  614. package/src/utils/home-migration.js +87 -0
  615. package/src/utils/injection-guard.js +186 -0
  616. package/src/utils/install-hints.js +180 -0
  617. package/src/utils/json-extract.js +64 -0
  618. package/src/utils/locale.js +59 -0
  619. package/src/utils/logger.js +96 -0
  620. package/src/utils/managed-markers.js +93 -0
  621. package/src/utils/manifest-sniff.js +82 -0
  622. package/src/utils/messages.js +61 -0
  623. package/src/utils/model-selector.js +107 -0
  624. package/src/utils/os-detect.js +79 -0
  625. package/src/utils/paths.js +13 -0
  626. package/src/utils/pending-user-action.js +95 -0
  627. package/src/utils/port-check.js +6 -0
  628. package/src/utils/port-occupant.js +96 -0
  629. package/src/utils/pricing.js +29 -0
  630. package/src/utils/process.js +4 -0
  631. package/src/utils/project-detect.js +160 -0
  632. package/src/utils/prompt-project-name.js +35 -0
  633. package/src/utils/provider-env.js +116 -0
  634. package/src/utils/qmd-collection.js +72 -0
  635. package/src/utils/qmd-detect.js +18 -0
  636. package/src/utils/qmd-install.js +37 -0
  637. package/src/utils/quota-simulator.js +56 -0
  638. package/src/utils/rate-limit-detector.js +181 -0
  639. package/src/utils/repo-cleaner.js +79 -0
  640. package/src/utils/retry.js +88 -0
  641. package/src/utils/role-assigner.js +137 -0
  642. package/src/utils/role-env.js +58 -0
  643. package/src/utils/rtk-detect.js +18 -0
  644. package/src/utils/rtk-install.js +38 -0
  645. package/src/utils/rtk-wrapper.js +87 -0
  646. package/src/utils/run-context.js +75 -0
  647. package/src/utils/run-log.js +217 -0
  648. package/src/utils/run-registry.js +9 -0
  649. package/src/utils/shared-paths.js +12 -0
  650. package/src/utils/squeezr-detect.js +21 -0
  651. package/src/utils/squeezr-install.js +37 -0
  652. package/src/utils/stack-detect.js +135 -0
  653. package/src/utils/stall-detector.js +159 -0
  654. package/src/utils/status-dashboard.js +254 -0
  655. package/src/utils/task-file.js +109 -0
  656. package/src/utils/telemetry.js +124 -0
  657. package/src/utils/templates-root.js +61 -0
  658. package/src/utils/tool-call-extractor.js +56 -0
  659. package/src/utils/tool-governor.js +116 -0
  660. package/src/utils/tool-installer.js +93 -0
  661. package/src/utils/update-check.js +280 -0
  662. package/src/utils/vector-store-orphans.js +89 -0
  663. package/src/utils/welcome.js +47 -0
  664. package/src/utils/with-timeout.js +42 -0
  665. package/src/utils/wizard.js +47 -0
  666. package/src/webperf/cwv-gate.js +94 -0
  667. package/src/webperf/devtools-detect.js +112 -0
  668. package/src/webperf/scanner.js +209 -0
  669. package/src/webperf/webperf-role-integration.js +66 -0
  670. package/templates/coder-rules.md +45 -0
  671. package/templates/docker-compose.sonar.yml +60 -0
  672. package/templates/domains/example-dental.md +57 -0
  673. package/templates/kj.config.yml +128 -0
  674. package/templates/review-rules.md +17 -0
  675. package/templates/roles/architect/anthropic.md +74 -0
  676. package/templates/roles/architect/default.md +72 -0
  677. package/templates/roles/architect/google.md +61 -0
  678. package/templates/roles/architect/openai.md +60 -0
  679. package/templates/roles/architect/opencode.md +59 -0
  680. package/templates/roles/architect.md +76 -0
  681. package/templates/roles/audit.md +68 -0
  682. package/templates/roles/coder/anthropic.md +81 -0
  683. package/templates/roles/coder/default.md +78 -0
  684. package/templates/roles/coder/google.md +87 -0
  685. package/templates/roles/coder/openai.md +68 -0
  686. package/templates/roles/coder/opencode.md +58 -0
  687. package/templates/roles/coder.md +152 -0
  688. package/templates/roles/commiter.md +44 -0
  689. package/templates/roles/discover.md +167 -0
  690. package/templates/roles/domain-curator.md +52 -0
  691. package/templates/roles/hu-reviewer.md +236 -0
  692. package/templates/roles/impeccable-design.md +87 -0
  693. package/templates/roles/impeccable.md +125 -0
  694. package/templates/roles/karajan-brain.md +95 -0
  695. package/templates/roles/onboarder.md +45 -0
  696. package/templates/roles/planner/anthropic.md +59 -0
  697. package/templates/roles/planner/default.md +57 -0
  698. package/templates/roles/planner/google.md +75 -0
  699. package/templates/roles/planner/openai.md +55 -0
  700. package/templates/roles/planner/opencode.md +62 -0
  701. package/templates/roles/planner.md +61 -0
  702. package/templates/roles/refactorer.md +48 -0
  703. package/templates/roles/repairer.md +103 -0
  704. package/templates/roles/researcher.md +41 -0
  705. package/templates/roles/reviewer/anthropic.md +61 -0
  706. package/templates/roles/reviewer/default.md +62 -0
  707. package/templates/roles/reviewer/google.md +63 -0
  708. package/templates/roles/reviewer/openai.md +57 -0
  709. package/templates/roles/reviewer/opencode.md +42 -0
  710. package/templates/roles/reviewer-paranoid.md +38 -0
  711. package/templates/roles/reviewer-relaxed.md +34 -0
  712. package/templates/roles/reviewer-strict.md +37 -0
  713. package/templates/roles/reviewer.md +62 -0
  714. package/templates/roles/security.md +54 -0
  715. package/templates/roles/solomon/anthropic.md +71 -0
  716. package/templates/roles/solomon/default.md +83 -0
  717. package/templates/roles/solomon/google.md +61 -0
  718. package/templates/roles/solomon/openai.md +53 -0
  719. package/templates/roles/solomon/opencode.md +55 -0
  720. package/templates/roles/solomon.md +83 -0
  721. package/templates/roles/sonar.md +49 -0
  722. package/templates/roles/spec-reviewer.md +47 -0
  723. package/templates/roles/tester.md +50 -0
  724. package/templates/roles/triage.md +69 -0
  725. package/templates/skills/kj-architect.md +45 -0
  726. package/templates/skills/kj-audit.md +63 -0
  727. package/templates/skills/kj-board.md +16 -0
  728. package/templates/skills/kj-code.md +51 -0
  729. package/templates/skills/kj-csv-transform.md +40 -0
  730. package/templates/skills/kj-data-report.md +44 -0
  731. package/templates/skills/kj-discover.md +24 -0
  732. package/templates/skills/kj-rag-query.md +50 -0
  733. package/templates/skills/kj-review.md +47 -0
  734. package/templates/skills/kj-run.md +73 -0
  735. package/templates/skills/kj-security.md +49 -0
  736. package/templates/skills/kj-sonar.md +41 -0
  737. package/templates/skills/kj-sql-analysis.md +40 -0
  738. package/templates/skills/kj-test.md +40 -0
  739. package/templates/workflows/automerge.yml +30 -0
  740. package/templates/workflows/houston-override.yml +46 -0
  741. package/templates/workflows/kj-ci-gateway.yml +58 -0
  742. package/vendor/tree-sitter-grammars/README.md +21 -0
  743. package/vendor/tree-sitter-grammars/go.wasm +0 -0
  744. package/vendor/tree-sitter-grammars/java.wasm +0 -0
  745. package/vendor/tree-sitter-grammars/python.wasm +0 -0
  746. package/vendor/tree-sitter-grammars/rust.wasm +0 -0
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Token persistence for HU Board (KJC-TSK-0355).
3
+ *
4
+ * Stored at `~/.karajan/hu-board/token` (or `$KJ_HOME/hu-board/token`)
5
+ * with mode 0600 — single-line, hex-encoded, 32 bytes of randomness.
6
+ * Idempotent: read existing token if file exists, otherwise generate
7
+ * + persist a new one.
8
+ *
9
+ * The token is ONLY enforced when the server binds to a non-loopback
10
+ * interface. See `auth.js` for the full threat model.
11
+ */
12
+
13
+ import { randomBytes } from "node:crypto";
14
+ import { mkdirSync, readFileSync, writeFileSync, chmodSync, existsSync } from "node:fs";
15
+ import { dirname, join } from "node:path";
16
+
17
+ const TOKEN_BYTES = 32;
18
+
19
+ /**
20
+ * Compute the on-disk token path. Delegates to db.js::getKjHome which
21
+ * (post-KJC-TSK-0420) honours KARAJAN_HOME > KJ_HOME > VITEST > default.
22
+ * @returns {string}
23
+ */
24
+ import { getKjHome } from "./db.js";
25
+ export function getTokenPath() {
26
+ return join(getKjHome(), "hu-board", "token");
27
+ }
28
+
29
+ /**
30
+ * Read the persisted token if it exists; otherwise generate a fresh
31
+ * one, write it (mode 0600), and return it. Returns `null` only if
32
+ * the filesystem operation itself fails (caller decides whether that
33
+ * disables auth or aborts startup).
34
+ *
35
+ * @returns {string|null}
36
+ */
37
+ export function getOrCreateToken() {
38
+ const tokenPath = getTokenPath();
39
+ try {
40
+ if (existsSync(tokenPath)) {
41
+ const raw = readFileSync(tokenPath, "utf8").trim();
42
+ if (raw.length >= 16) return raw;
43
+ // Corrupt or empty file — overwrite below.
44
+ }
45
+ const token = randomBytes(TOKEN_BYTES).toString("hex");
46
+ mkdirSync(dirname(tokenPath), { recursive: true });
47
+ writeFileSync(tokenPath, token, { mode: 0o600 });
48
+ try { chmodSync(tokenPath, 0o600); } catch { /* best-effort on Windows */ }
49
+ return token;
50
+ } catch (err) {
51
+ // Don't crash the board over this — just disable auth and let the
52
+ // caller log a warning.
53
+ console.warn(`[token-store] failed to manage token file at ${tokenPath}: ${err.message}`);
54
+ return null;
55
+ }
56
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Zombie session reaper for the HU Board (board-polish #2,
3
+ * KJC dogfooding 2026-05-07).
4
+ *
5
+ * Why this exists
6
+ * ===============
7
+ *
8
+ * Sessions in the SQLite db can stay "in flight" forever when the
9
+ * orchestrator process dies between iterations: their `status` stays
10
+ * `coding` / `stalled` / `paused`, the UI keeps surfacing them as
11
+ * actionable, and `kj board start` weeks later still pops a
12
+ * "Karajan needs an answer" modal for a checkpoint nobody owns
13
+ * anymore.
14
+ *
15
+ * Concretely, on 2026-05-07 the user opened the board on a fresh
16
+ * `kj board start` and saw the popup of an HU from 2026-04-27 — 9
17
+ * days dead, status still `paused`. The fix per his explicit
18
+ * mandate:
19
+ *
20
+ * "No puede quedarse nada zombie. Debe haber algun sistema que lo
21
+ * compruebe al arrancar y mate esos zombies."
22
+ *
23
+ * Thresholds (per his decision in the same session):
24
+ * - status in (coding | stalled) and >= 6h since updated_at → reap
25
+ * - status === paused and >= 24h since updated_at → reap
26
+ * - everything else → skip
27
+ *
28
+ * What "reap" means: write `status: "failed"` BOTH to the DB row
29
+ * AND to the underlying `~/.karajan/sessions/<id>/session.json`,
30
+ * append a structured checkpoint of the form
31
+ * { stage: 'zombie-reap', reason: <reason>, at: <iso> }
32
+ * so the audit trail survives. The session row stays in place
33
+ * (we don't DELETE — the user can still inspect what happened).
34
+ *
35
+ * Why update the JSON on disk too
36
+ * -------------------------------
37
+ *
38
+ * The board's `fullScan()` reads every session.json on startup and
39
+ * upserts the row, OVERWRITING any DB-only mutation. If the reaper
40
+ * only updated the DB, the next reboot would reset the status back
41
+ * to `paused`/`stalled` because the JSON still says so. Persisting
42
+ * to disk makes the reap permanent on the first reboot — the user
43
+ * sees the cleanup once, and never again.
44
+ */
45
+
46
+ import fs from "node:fs";
47
+ import path from "node:path";
48
+
49
+ const DEFAULT_CODING_HOURS = 6;
50
+ const DEFAULT_PAUSED_HOURS = 24;
51
+ const CODING_LIKE_STATUSES = new Set(["coding", "stalled", "running", "in_progress"]);
52
+ const PAUSED_STATUSES = new Set(["paused", "checkpoint", "awaiting_answer"]);
53
+
54
+ /**
55
+ * Decide whether a single session row qualifies as zombie at `now`.
56
+ * Pure function — no I/O. Used by both the reaper itself and the
57
+ * unit tests.
58
+ *
59
+ * @param {{ id: string, status: string, updated_at?: string|null,
60
+ * created_at?: string|null }} session
61
+ * @param {{ now: number, codingHours: number, pausedHours: number }} ctx
62
+ * @returns {{ zombie: boolean, reason?: string }}
63
+ */
64
+ export function classifyZombie(session, ctx) {
65
+ if (!session || !session.status) return { zombie: false };
66
+ const status = String(session.status).toLowerCase();
67
+ // Reference timestamp: prefer updated_at (the daemon writes that on
68
+ // every checkpoint sync), fall back to created_at (in case the row
69
+ // was inserted but never touched again).
70
+ const refIso = session.updated_at || session.created_at;
71
+ if (!refIso) return { zombie: false };
72
+ const refTs = new Date(refIso).getTime();
73
+ if (Number.isNaN(refTs)) return { zombie: false };
74
+ const ageHours = (ctx.now - refTs) / 3_600_000;
75
+ if (ageHours <= 0) return { zombie: false };
76
+ if (CODING_LIKE_STATUSES.has(status) && ageHours >= ctx.codingHours) {
77
+ return { zombie: true, reason: `${status} ${ageHours.toFixed(1)}h inactive (>= ${ctx.codingHours}h)` };
78
+ }
79
+ if (PAUSED_STATUSES.has(status) && ageHours >= ctx.pausedHours) {
80
+ return { zombie: true, reason: `${status} ${ageHours.toFixed(1)}h inactive (>= ${ctx.pausedHours}h)` };
81
+ }
82
+ return { zombie: false };
83
+ }
84
+
85
+ /**
86
+ * Filter a list of session rows down to the zombie ones. Pure.
87
+ *
88
+ * @param {Array<object>} sessions
89
+ * @param {{ now?: number, codingHours?: number, pausedHours?: number }} [opts]
90
+ * @returns {Array<{ session: object, reason: string }>}
91
+ */
92
+ export function findZombieSessions(sessions, opts = {}) {
93
+ const ctx = {
94
+ now: opts.now ?? Date.now(),
95
+ codingHours: opts.codingHours ?? DEFAULT_CODING_HOURS,
96
+ pausedHours: opts.pausedHours ?? DEFAULT_PAUSED_HOURS,
97
+ };
98
+ const out = [];
99
+ for (const session of sessions || []) {
100
+ const verdict = classifyZombie(session, ctx);
101
+ if (verdict.zombie) out.push({ session, reason: verdict.reason });
102
+ }
103
+ return out;
104
+ }
105
+
106
+ /**
107
+ * Append a `zombie-reap` checkpoint to a JSON-encoded checkpoints
108
+ * blob (the on-disk format). Returns the new JSON string.
109
+ *
110
+ * Defensive about malformed input: if the existing blob isn't a
111
+ * JSON array, replaces it with a fresh array containing the new
112
+ * entry — never throws.
113
+ */
114
+ export function appendZombieCheckpoint(existingJson, reason, isoTimestamp) {
115
+ let arr = [];
116
+ if (existingJson) {
117
+ try {
118
+ const parsed = JSON.parse(existingJson);
119
+ if (Array.isArray(parsed)) arr = parsed;
120
+ } catch { /* fall through with empty arr */ }
121
+ }
122
+ arr.push({ stage: "zombie-reap", reason, at: isoTimestamp });
123
+ return JSON.stringify(arr);
124
+ }
125
+
126
+ /**
127
+ * Default location of the on-disk session JSON files. Delegates to
128
+ * db.js::getKjHome (KJC-TSK-0420): KARAJAN_HOME > KJ_HOME > VITEST > default.
129
+ * @returns {string}
130
+ */
131
+ import { getKjHome } from "./db.js";
132
+ function getSessionsDir() {
133
+ return path.join(getKjHome(), "sessions");
134
+ }
135
+
136
+ /**
137
+ * Persist `status: "failed"` and the new zombie-reap checkpoint to
138
+ * the on-disk session.json. This stops the next `fullScan()` from
139
+ * resurrecting the zombi.
140
+ *
141
+ * Safe-by-default: if the file is missing, malformed, or the write
142
+ * fails, this returns `false` and the caller logs a warning. We
143
+ * never throw and we never block the daemon's startup.
144
+ *
145
+ * @param {string} sessionId
146
+ * @param {string} reason
147
+ * @param {string} reapedAtIso
148
+ * @param {{ sessionsDir?: string }} [opts]
149
+ * @returns {boolean} true when the file was rewritten successfully
150
+ */
151
+ export function markSessionFailedOnDisk(sessionId, reason, reapedAtIso, opts = {}) {
152
+ const dir = opts.sessionsDir || getSessionsDir();
153
+ const file = path.join(dir, sessionId, "session.json");
154
+ let raw;
155
+ try { raw = fs.readFileSync(file, "utf8"); } catch { return false; }
156
+ let parsed;
157
+ try { parsed = JSON.parse(raw); } catch { return false; }
158
+ if (!parsed || typeof parsed !== "object") return false;
159
+ parsed.status = "failed";
160
+ parsed.updated_at = reapedAtIso;
161
+ parsed.checkpoints = Array.isArray(parsed.checkpoints) ? parsed.checkpoints : [];
162
+ parsed.checkpoints.push({ stage: "zombie-reap", reason, at: reapedAtIso });
163
+ try {
164
+ fs.writeFileSync(file, JSON.stringify(parsed, null, 2));
165
+ return true;
166
+ } catch { return false; }
167
+ }
168
+
169
+ /**
170
+ * Reap every zombie session in the DB AND in the underlying
171
+ * session.json files. Side-effecting. Returns the list of reaped
172
+ * sessions so the server can log them.
173
+ *
174
+ * @param {object} deps
175
+ * @param {object} deps.db better-sqlite3 Database handle
176
+ * @param {object} [deps.opts] threshold + sessionsDir overrides
177
+ * @param {Function} [deps.now] () => number, for tests
178
+ * @returns {Array<{ id: string, status_before: string, reason: string, disk_persisted: boolean }>}
179
+ */
180
+ export function reapZombieSessions({ db, opts = {}, now = () => Date.now() }) {
181
+ if (!db) return [];
182
+ const allSessions = db.prepare(
183
+ "SELECT id, status, updated_at, created_at, checkpoints FROM sessions WHERE status NOT IN ('failed', 'approved', 'cancelled', 'done')",
184
+ ).all();
185
+ const zombies = findZombieSessions(allSessions, { ...opts, now: now() });
186
+ if (zombies.length === 0) return [];
187
+ const update = db.prepare(
188
+ "UPDATE sessions SET status = 'failed', checkpoints = ?, updated_at = ? WHERE id = ?",
189
+ );
190
+ const reapedAt = new Date(now()).toISOString();
191
+ const reaped = [];
192
+ for (const { session, reason } of zombies) {
193
+ const newCheckpoints = appendZombieCheckpoint(session.checkpoints, reason, reapedAt);
194
+ update.run(newCheckpoints, reapedAt, session.id);
195
+ const diskPersisted = markSessionFailedOnDisk(session.id, reason, reapedAt, {
196
+ sessionsDir: opts.sessionsDir,
197
+ });
198
+ reaped.push({
199
+ id: session.id,
200
+ status_before: session.status,
201
+ reason,
202
+ disk_persisted: diskPersisted,
203
+ });
204
+ }
205
+ return reaped;
206
+ }
@@ -0,0 +1,84 @@
1
+ # AI Attribution Guard — GitHub Action
2
+ #
3
+ # Copy this file to .github/workflows/ai-attribution-guard.yml in any repo.
4
+ # Blocks PRs that contain AI attribution comments or Co-Authored-By lines.
5
+ # No dependencies, no Node.js required. Pure bash + grep.
6
+
7
+ name: AI Attribution Guard
8
+
9
+ on:
10
+ pull_request:
11
+ types: [opened, synchronize, reopened]
12
+
13
+ jobs:
14
+ scan:
15
+ name: Scan for AI attribution
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 0
21
+
22
+ - name: Check PR diff for AI attribution
23
+ # KJC-BUG-0072 — never interpolate ${{ github.* }} directly inside a
24
+ # `run:` block on pull_request workflows. Pin to env vars instead.
25
+ env:
26
+ BASE_REF: ${{ github.base_ref }}
27
+ run: |
28
+ DIFF=$(git diff "origin/${BASE_REF}...HEAD" \
29
+ -- ':!*.png' ':!*.jpg' ':!*.svg' ':!*.ico' \
30
+ ':!*.lock' ':!package-lock.json' ':!yarn.lock' \
31
+ ':!*.min.js' ':!*.min.css' \
32
+ ':!.githooks/*' \
33
+ | grep '^+' \
34
+ | grep -v '^+++' \
35
+ || true)
36
+
37
+ [ -z "$DIFF" ] && echo "No diff to scan" && exit 0
38
+
39
+ # Attribution patterns (not functional references like provider: "claude")
40
+ ATTR='(Generated|Created|Written|Made|Built|Authored|Assisted|Powered|Produced) (by|with|using|via) .*(Claude|Anthropic|OpenAI|ChatGPT|GPT-[0-9]|Copilot|Gemini|Cursor|Windsurf|Codeium)'
41
+ COAUTH='Co-Authored-By:.*\b(Claude|Anthropic|OpenAI|ChatGPT|Copilot|Gemini|Cursor|Windsurf|Codeium)\b'
42
+ COMMENT='(//|#|/\*|\*|<!--|--)\s*.*(Claude|Anthropic|OpenAI|ChatGPT|Copilot|Gemini).*(generated|assisted|helped|created|wrote|built|made)'
43
+
44
+ FOUND=""
45
+ for PAT in "$ATTR" "$COAUTH" "$COMMENT"; do
46
+ MATCH=$(echo "$DIFF" | grep -i -E "$PAT" || true)
47
+ [ -n "$MATCH" ] && FOUND="${FOUND}${MATCH}\n"
48
+ done
49
+
50
+ if [ -n "$FOUND" ]; then
51
+ echo "::error::AI attribution detected in PR diff"
52
+ echo -e "$FOUND"
53
+ echo ""
54
+ echo "Remove AI attribution comments/lines and push again."
55
+ exit 1
56
+ fi
57
+
58
+ echo "AI attribution guard: clean"
59
+
60
+ - name: Check commit messages
61
+ # KJC-BUG-0072 — same hardening as the diff scan: env-pin the ref.
62
+ env:
63
+ BASE_REF: ${{ github.base_ref }}
64
+ run: |
65
+ COMMITS=$(git log --format='%s%n%b' "origin/${BASE_REF}...HEAD")
66
+
67
+ COAUTH='Co-Authored-By:.*\b(Claude|Anthropic|OpenAI|ChatGPT|Copilot|Gemini|Cursor|Windsurf|Codeium)\b'
68
+ ATTR='(Generated|Created|Written|Built|Authored) (by|with|using) .*(Claude|Anthropic|OpenAI|ChatGPT|Copilot|Gemini|Cursor|Windsurf|Codeium)'
69
+
70
+ FOUND=""
71
+ for PAT in "$COAUTH" "$ATTR"; do
72
+ MATCH=$(echo "$COMMITS" | grep -i -E "$PAT" || true)
73
+ [ -n "$MATCH" ] && FOUND="${FOUND}${MATCH}\n"
74
+ done
75
+
76
+ if [ -n "$FOUND" ]; then
77
+ echo "::error::AI attribution detected in commit messages"
78
+ echo -e "$FOUND"
79
+ echo ""
80
+ echo "Amend commits to remove AI attribution and force-push."
81
+ exit 1
82
+ fi
83
+
84
+ echo "Commit messages: clean"
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scripts/audit-test-diet.mjs — semantic loss-of-meaning auditor (KJC-TSK-0345).
4
+ * Categories: empty-no-expect, skipped-pending, imports-orphan, deprecated-export,
5
+ * subsumed-candidate. Each finding carries confidence (high|low). Complements
6
+ * tests/_diet/audit.mjs (which classifies by LOC bucket only).
7
+ */
8
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
9
+ import { dirname, join, relative, resolve } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ const HERE = dirname(fileURLToPath(import.meta.url));
13
+ const DEFAULT_ROOT = resolve(HERE, "..");
14
+ const SKIP_DIRS = new Set(["_diet", "fixtures", "node_modules"]);
15
+
16
+ export function walkTests(rootDir, testsSubdir = "tests") {
17
+ const out = [];
18
+ const base = join(rootDir, testsSubdir);
19
+ if (!existsSync(base)) return out;
20
+ const stack = [base];
21
+ while (stack.length) {
22
+ for (const e of readdirSync(stack.pop(), { withFileTypes: true })) {
23
+ const p = join(e.parentPath ?? base, e.name);
24
+ if (e.isDirectory() && !SKIP_DIRS.has(e.name)) stack.push(p);
25
+ else if (e.isFile() && /\.test\.(m?js|ts)$/.test(e.name)) out.push(p);
26
+ }
27
+ }
28
+ return out;
29
+ }
30
+
31
+ export function parseImports(source) {
32
+ const imports = [];
33
+ const re = /^[ \t]*import\s+(?:([\w*\s{},]+?)\s+from\s+)?['"]([^'"]+)['"]/gm;
34
+ let m;
35
+ while ((m = re.exec(source)) !== null) {
36
+ const named = [];
37
+ const braced = (m[1] || "").match(/\{([^}]+)\}/);
38
+ if (braced) for (const part of braced[1].split(",")) {
39
+ const name = part.trim().split(/\s+as\s+/)[0].trim();
40
+ if (name) named.push(name);
41
+ }
42
+ imports.push({ specifier: m[2], named });
43
+ }
44
+ return imports;
45
+ }
46
+
47
+ export function resolveSrcImport(testFile, specifier, rootDir) {
48
+ if (!specifier.startsWith(".") && !specifier.startsWith("#")) return null;
49
+ const base = specifier.startsWith("#") ? resolve(rootDir, specifier.replace(/^#/, "src/")) : resolve(dirname(testFile), specifier);
50
+ for (const c of [base, `${base}.js`, `${base}.mjs`, join(base, "index.js")]) {
51
+ if (existsSync(c) && statSync(c).isFile()) return c;
52
+ }
53
+ return null;
54
+ }
55
+
56
+ export function extractItNames(source) {
57
+ const names = new Set();
58
+ for (const m of source.matchAll(/\b(?:it|test)(?:\.(?:only|skip|todo))?\s*\(\s*['"`]([^'"`]+)['"`]/g)) names.add(m[1]);
59
+ return names;
60
+ }
61
+
62
+ export function extractExports(source) {
63
+ const names = new Set();
64
+ for (const m of source.matchAll(/export\s+(?:async\s+)?(?:function|class|const|let|var)\s+([A-Za-z_$][\w$]*)/g)) names.add(m[1]);
65
+ for (const m of source.matchAll(/export\s*\{([^}]+)\}/g)) for (const part of m[1].split(",")) {
66
+ const name = part.trim().split(/\s+as\s+/).pop().trim();
67
+ if (name) names.add(name);
68
+ }
69
+ if (/export\s+default\b/.test(source)) names.add("default");
70
+ return names;
71
+ }
72
+
73
+ export function auditFile(testFile, rootDir) {
74
+ const source = readFileSync(testFile, "utf-8");
75
+ const rel = relative(rootDir, testFile);
76
+ const findings = [];
77
+ const hasIt = /\b(?:it|test)\s*\(/.test(source);
78
+ if (hasIt && !/\bexpect\s*\(/.test(source)) findings.push({ category: "empty-no-expect", confidence: "high", detail: "it()/test() blocks with no expect()" });
79
+ if (/\b(?:it|test|describe)\.skip\s*\(|\bxit\s*\(|\bxtest\s*\(|\b(?:it|test)\.todo\s*\(/.test(source)) {
80
+ findings.push({ category: "skipped-pending", confidence: "high", detail: "uses .skip / xit / xtest / .todo" });
81
+ }
82
+ const srcImports = [];
83
+ for (const imp of parseImports(source)) {
84
+ if (!imp.specifier.startsWith(".") && !imp.specifier.startsWith("#")) continue;
85
+ const resolved = resolveSrcImport(testFile, imp.specifier, rootDir);
86
+ if (!resolved) { findings.push({ category: "imports-orphan", confidence: "high", detail: `cannot resolve '${imp.specifier}'` }); continue; }
87
+ if (imp.named.length) {
88
+ const missing = imp.named.filter((n) => !extractExports(readFileSync(resolved, "utf-8")).has(n));
89
+ if (missing.length) findings.push({ category: "deprecated-export", confidence: "low", detail: `${imp.specifier} no longer exports: ${missing.join(", ")}` });
90
+ }
91
+ srcImports.push({ resolved: relative(rootDir, resolved), named: imp.named });
92
+ }
93
+ return { path: rel, srcImports, findings, itNames: extractItNames(source) };
94
+ }
95
+
96
+ export function findSubsumed(perFile) {
97
+ const bySrc = new Map();
98
+ for (const f of perFile) for (const imp of f.srcImports) {
99
+ if (!bySrc.has(imp.resolved)) bySrc.set(imp.resolved, []);
100
+ bySrc.get(imp.resolved).push({ path: f.path, named: new Set(imp.named), itNames: f.itNames });
101
+ }
102
+ const out = [];
103
+ for (const [src, consumers] of bySrc) {
104
+ if (consumers.length < 2) continue;
105
+ const sorted = [...consumers].sort((a, b) => a.named.size - b.named.size);
106
+ const narrow = sorted[0]; const broad = sorted[sorted.length - 1];
107
+ if (narrow.path === broad.path || narrow.named.size === 0 || broad.named.size === 0) continue;
108
+ if (!(narrow.named.size < broad.named.size && [...narrow.named].every((n) => broad.named.has(n)))) continue;
109
+ // Require ≥50% of narrow's it() names to also appear in broad's. Imports
110
+ // alone are a false-positive trap: two tests can share helpers and assert
111
+ // on disjoint behavior (e.g. tests/budget.test.js vs tests/utils/budget.test.js).
112
+ if (narrow.itNames.size === 0) continue;
113
+ const overlap = [...narrow.itNames].filter((n) => broad.itNames.has(n)).length;
114
+ if (overlap / narrow.itNames.size < 0.5) continue;
115
+ out.push({ category: "subsumed-candidate", confidence: "low", path: narrow.path, detail: `${overlap}/${narrow.itNames.size} it() names also in ${broad.path}; subset of imports from ${src}` });
116
+ }
117
+ return out;
118
+ }
119
+
120
+ export function auditTestDiet(rootDir = DEFAULT_ROOT) {
121
+ const files = walkTests(rootDir);
122
+ const perFile = files.map((f) => auditFile(f, rootDir));
123
+ const findings = [];
124
+ for (const f of perFile) for (const x of f.findings) findings.push({ path: f.path, ...x });
125
+ for (const s of findSubsumed(perFile)) findings.push(s);
126
+ return { generatedAt: new Date().toISOString(), rootDir, totals: { filesAudited: files.length, findings: findings.length }, findings };
127
+ }
128
+
129
+ export function renderMarkdown(report) {
130
+ const byCat = new Map();
131
+ for (const f of report.findings) {
132
+ if (!byCat.has(f.category)) byCat.set(f.category, []);
133
+ byCat.get(f.category).push(f);
134
+ }
135
+ const lines = [`# Semantic test-diet audit`, ``, `Generated: ${report.generatedAt}`, `Files audited: ${report.totals.filesAudited}`, `Findings: ${report.totals.findings}`, ``];
136
+ for (const [cat, items] of byCat) {
137
+ lines.push(`## ${cat} (${items.length})`, ``);
138
+ for (const it of items) lines.push(`- \`${it.path}\` — ${it.detail} (confidence: ${it.confidence})`);
139
+ lines.push(``);
140
+ }
141
+ return lines.join("\n");
142
+ }
143
+
144
+ if (import.meta.url === `file://${process.argv[1]}`) {
145
+ const report = auditTestDiet(DEFAULT_ROOT);
146
+ const outDir = join(DEFAULT_ROOT, "audit");
147
+ if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
148
+ writeFileSync(join(outDir, "test-diet-semantic.json"), JSON.stringify(report, null, 2));
149
+ writeFileSync(join(outDir, "test-diet-semantic.md"), renderMarkdown(report));
150
+ console.log(`audit: ${report.totals.filesAudited} files, ${report.totals.findings} findings -> audit/test-diet-semantic.{json,md}`);
151
+ }
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * build-sea.mjs — Build a Node.js Single Executable Application (SEA) binary.
5
+ *
6
+ * Steps:
7
+ * 1. esbuild bundles src/cli.js -> dist/kj-bundle.cjs (CJS, all deps inlined)
8
+ * 2. Generate SEA preparation blob via node --experimental-sea-config
9
+ * 3. Copy the current node binary to dist/kj (or dist/kj.exe on Windows)
10
+ * 4. Inject the blob into the binary with postject
11
+ * 5. (macOS only) Re-sign the binary with ad-hoc signature
12
+ *
13
+ * Requirements (installed as devDependencies — run `npm ci` first):
14
+ * - esbuild
15
+ * - postject
16
+ *
17
+ * Usage:
18
+ * node scripts/build-sea.mjs
19
+ */
20
+
21
+ import { execSync } from "node:child_process";
22
+ import { cpSync, mkdirSync, writeFileSync, statSync, readFileSync } from "node:fs";
23
+ import path from "node:path";
24
+ import { fileURLToPath } from "node:url";
25
+
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = path.dirname(__filename);
28
+ const ROOT = path.resolve(__dirname, "..");
29
+ const DIST = path.join(ROOT, "dist");
30
+
31
+ const isWindows = process.platform === "win32";
32
+ const isMac = process.platform === "darwin";
33
+ const binaryName = isWindows ? "kj.exe" : "kj";
34
+ const binaryPath = path.join(DIST, binaryName);
35
+
36
+ function run(cmd, opts = {}) {
37
+ console.log(` $ ${cmd}`);
38
+ execSync(cmd, { cwd: ROOT, stdio: "inherit", ...opts });
39
+ }
40
+
41
+ function formatBytes(bytes) {
42
+ const mb = bytes / (1024 * 1024);
43
+ return `${mb.toFixed(1)} MB`;
44
+ }
45
+
46
+ async function main() {
47
+ try {
48
+ console.log("\n=== Karajan Code — SEA Build ===\n");
49
+
50
+ // ── Step 0: Prepare dist directory ──────────────────────────────
51
+ mkdirSync(DIST, { recursive: true });
52
+
53
+ // ── Step 1: Bundle with esbuild ─────────────────────────────────
54
+ console.log("[1/5] Bundling with esbuild...");
55
+ const { seaBuildOptions } = await import("./esbuild-sea.config.mjs");
56
+ const esbuild = await import("esbuild");
57
+ await esbuild.build(seaBuildOptions);
58
+ console.log(" -> dist/kj-bundle.cjs created.\n");
59
+
60
+ // ── Step 2: Generate SEA blob ───────────────────────────────────
61
+ console.log("[2/5] Generating SEA preparation blob...");
62
+ // Templates travel as SEA assets (KJC-BUG-0104): the binary ships no
63
+ // templates/ directory, so kj init / role prompts / onboard resolved
64
+ // paths like $HOME/templates/skills and hit ENOENT. The runtime
65
+ // (src/utils/templates-root.js) reads the index asset and extracts
66
+ // the files to ~/.karajan/embedded-templates/<version>/ on first use.
67
+ const { collectTemplateFiles } = await import("./esbuild-sea.config.mjs");
68
+ const pkgVersion = JSON.parse(
69
+ readFileSync(path.join(ROOT, "package.json"), "utf8"),
70
+ ).version;
71
+ const templateFiles = collectTemplateFiles();
72
+ writeFileSync(
73
+ path.join(DIST, "templates-index.json"),
74
+ JSON.stringify({ version: pkgVersion, files: templateFiles }),
75
+ );
76
+ // Absolute paths: node resolves asset paths against the CWD (not the
77
+ // config file), so anything relative is ambiguous.
78
+ const assets = { "templates-index.json": path.join(DIST, "templates-index.json") };
79
+ for (const rel of templateFiles) {
80
+ assets[`templates/${rel}`] = path.join(ROOT, "templates", rel);
81
+ }
82
+ console.log(` -> ${templateFiles.length} template assets embedded.`);
83
+ const seaConfig = {
84
+ main: "dist/kj-bundle.cjs",
85
+ output: "dist/sea-prep.blob",
86
+ disableExperimentalSEAWarning: true,
87
+ assets,
88
+ };
89
+ writeFileSync(
90
+ path.join(DIST, "sea-config.json"),
91
+ JSON.stringify(seaConfig, null, 2),
92
+ );
93
+ run("node --experimental-sea-config dist/sea-config.json");
94
+ console.log(" -> dist/sea-prep.blob created.\n");
95
+
96
+ // ── Step 3: Copy node binary ────────────────────────────────────
97
+ console.log("[3/5] Copying node binary...");
98
+ cpSync(process.execPath, binaryPath);
99
+ console.log(` -> ${binaryName} copied.\n`);
100
+
101
+ // ── Step 3.5: Remove signature on macOS (required before postject)
102
+ if (isMac) {
103
+ console.log("[3.5] Removing macOS code signature...");
104
+ run(`codesign --remove-signature ${binaryPath}`);
105
+ console.log(" -> Signature removed.\n");
106
+ }
107
+
108
+ // ── Step 4: Inject blob with postject ───────────────────────────
109
+ console.log("[4/5] Injecting SEA blob with postject...");
110
+ // On macOS the blob MUST land as section NODE_SEA_BLOB inside a segment
111
+ // named NODE_SEA. Without --macho-segment-name postject writes it to a
112
+ // default segment the SEA loader never inspects, so the binary segfaults
113
+ // on startup (KJC-BUG-0096, nodejs/postject#76). The flag is Mach-O only —
114
+ // ELF (Linux) and PE (Windows) don't take it — so add it only on macOS.
115
+ const postjectCmd = [
116
+ `npx postject ${binaryPath}`,
117
+ "NODE_SEA_BLOB dist/sea-prep.blob",
118
+ "--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2",
119
+ ...(isMac ? ["--macho-segment-name NODE_SEA"] : []),
120
+ ].join(" ");
121
+ run(postjectCmd);
122
+ console.log(" -> Blob injected.\n");
123
+
124
+ // ── Step 4.5: Re-sign on macOS ──────────────────────────────────
125
+ if (isMac) {
126
+ console.log("[4.5] Re-signing binary (ad-hoc)...");
127
+ run(`codesign --sign - ${binaryPath}`);
128
+ console.log(" -> Binary signed.\n");
129
+ }
130
+
131
+ // ── Done ────────────────────────────────────────────────────────
132
+ const stat = statSync(binaryPath);
133
+ console.log("[5/5] Done!");
134
+ console.log(` Binary: ${binaryPath}`);
135
+ console.log(` Size: ${formatBytes(stat.size)}`);
136
+ console.log("");
137
+ } catch (err) {
138
+ console.error("\n=== SEA Build FAILED ===\n");
139
+ console.error(err.message || err);
140
+ console.error(
141
+ "\nNote: esbuild may fail to bundle certain ESM-only dependencies",
142
+ );
143
+ console.error(
144
+ "or native modules. Check the error above for details.\n",
145
+ );
146
+ process.exit(1);
147
+ }
148
+ }
149
+
150
+ main();