@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,721 @@
1
+ /**
2
+ * sentinel-hooks — SEN-A (KJC-TSK-0713, epic KJC-PCS-0071 Karajan Sentinel).
3
+ * v3 had authority without intelligence; v4 intelligence without authority.
4
+ * The Sentinel separates them: a deterministic PROGRAM (zero LLM) supervises
5
+ * the agent through the harness's synchronous hooks — PostToolUse records
6
+ * method facts per session, Stop BLOCKS ending the turn while violations are
7
+ * open. Claude Code only: it is the one harness with synchronous blocking
8
+ * hooks, which is why the guaranteed level requires Claude as host (ADR).
9
+ */
10
+
11
+ import { readFileSync } from "node:fs";
12
+ import { execFileSync } from "node:child_process";
13
+ import { join } from "node:path";
14
+ import { CARD_REF_RE } from "../review/card-first.js";
15
+ import { mergeClaudeHooks, writeHarnessScript } from "./harness-hooks.js";
16
+
17
+ /** The harness lives at the PROJECT root — resolve it even from a subdir. */
18
+ export function resolveSentinelRoot(dir = process.cwd()) {
19
+ try {
20
+ return (
21
+ execFileSync("git", ["rev-parse", "--show-toplevel"], { cwd: dir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || dir
22
+ );
23
+ } catch {
24
+ return dir;
25
+ }
26
+ }
27
+
28
+ const LIB_BODY = `// kj sentinel shared lib (KJC-TSK-0714) — managed by \`kj harden\`.
29
+ // Single source for every sentinel script: state, branch, classification,
30
+ // violations, and escape recording.
31
+ import { readFileSync, writeFileSync } from "node:fs";
32
+ import { execSync } from "node:child_process";
33
+ import { dirname, join } from "node:path";
34
+ import { fileURLToPath } from "node:url";
35
+ const here = dirname(fileURLToPath(import.meta.url));
36
+ export const STATE = join(here, "sentinel-state.json");
37
+ export const ROOT = join(here, "..", "..");
38
+ // INF-A (KJC-TSK-0758): infra IS source — terraform/k8s/shell/Docker edits
39
+ // engage the method like app code; *_test.* files (terratest) are tests.
40
+ export const TESTS = /(^|\\/)(tests?|__tests__|spec)\\/|\\.(test|spec)\\.[a-z]+$|_test\\.[a-z]+$/;
41
+ export const CODE = /\\.(m?[jt]sx?|c[jt]s|py|go|rs|java|rb|php|cs|swift|kt|astro|svelte|vue|c|h|cc|cpp|hpp|tf|hcl|ya?ml|sh)$|(^|\\/)(Dockerfile|Makefile|Vagrantfile)$/;
42
+ export const CARD = new RegExp(${JSON.stringify(CARD_REF_RE.source)}, "i");
43
+ export const BASE_BRANCHES = new Set(["main", "master"]);
44
+ export const load = () => { try { return JSON.parse(readFileSync(STATE, "utf8")); } catch { return {}; } };
45
+ export const save = (state) => writeFileSync(STATE, JSON.stringify(state, null, 2));
46
+ export const session = (state, sid) => {
47
+ state.sessions ||= {};
48
+ return (state.sessions[sid] ||= { edited_sources: [], edited_tests: [], escapes: [], errors: [], blocks: 0 });
49
+ };
50
+ export const branchOf = () => {
51
+ try { return execSync("git rev-parse --abbrev-ref HEAD", { cwd: ROOT, stdio: ["ignore", "pipe", "ignore"] }).toString().trim(); }
52
+ catch { return null; }
53
+ };
54
+ const _git = (args, cwd) => {
55
+ try { return execSync("git " + args, { cwd, stdio: ["ignore", "pipe", "ignore"] }).toString().trim(); } catch { return ""; }
56
+ };
57
+ let _home;
58
+ // MONO-0 (ADR 0002): lane identity = worktree toplevel; repo identity =
59
+ // git-common-dir. Same repo but another toplevel than OUR root => foreign
60
+ // lane (returns its toplevel); anything else => null.
61
+ export const foreignLane = (targetDir) => {
62
+ const top = _git("rev-parse --show-toplevel", targetDir);
63
+ if (!top) return null;
64
+ _home ||= { top: _git("rev-parse --show-toplevel", ROOT), common: _git("rev-parse --path-format=absolute --git-common-dir", ROOT) };
65
+ if (!_home.top || top === _home.top) return null;
66
+ const common = _git("rev-parse --path-format=absolute --git-common-dir", targetDir);
67
+ return common && common === _home.common ? top : null;
68
+ };
69
+ // KJC-TSK-0765 board-sync: cards merged in this session but not yet moved in
70
+ // the tracker. The method does not advance until the board is true.
71
+ export const pendingMoves = (s) => (s && Array.isArray(s.pending_moves) ? s.pending_moves : []);
72
+ export const pendingText = (p) => (p.card ? p.card : "card sin identificar") + " (PR #" + p.pr + ") mergeada sin mover en el tracker — muevela a To Validate / Fixed con sus commits (update_card del PG o kj hu move)";
73
+ export const violations = (s, branch) => {
74
+ const v = pendingMoves(s).map(pendingText);
75
+ if (!s || !(s.edited_sources || []).length) return v;
76
+ if (BASE_BRANCHES.has(branch)) v.push("Fuentes editadas en la rama base '" + branch + "' — crea una rama: git checkout -b feat/<CARD-ID>-descripcion");
77
+ else if (branch && !CARD.test(branch)) v.push("La rama '" + branch + "' no referencia ninguna card — usa feat/<CARD-ID>-descripcion (y una card VIVA en el board)");
78
+ if (!(s.edited_tests || []).length) v.push("Fuentes editadas sin tocar un solo test (" + s.edited_sources.join(", ") + ") — escribe o actualiza el test que prueba el cambio");
79
+ return v;
80
+ };
81
+ export const recordEscape = (sid, escape, tool) => {
82
+ const state = load();
83
+ const s = session(state, sid);
84
+ s.at = Date.now();
85
+ if (!s.escapes.includes(escape)) s.escapes.push(escape);
86
+ (state.escape_events ||= []).push({ escape, tool, sid, ts: Date.now() });
87
+ save(state);
88
+ };
89
+ `;
90
+
91
+ const POST_BODY = `#!/usr/bin/env node
92
+ // kj sentinel state writer (KJC-TSK-0713) — managed by \`kj harden\`.
93
+ // Records deterministic method facts per session; never blocks, never fails
94
+ // a tool call (PostToolUse, always exit 0).
95
+ import { relative } from "node:path";
96
+ import { spawnSync } from "node:child_process";
97
+ import { CODE, TESTS, ROOT, CARD, branchOf, load, save, session } from "./sentinel-lib.mjs";
98
+ const ESCAPES = ["KJ_ALLOW_WRITE", "KJ_ALLOW_REWRITE", "KJ_ALLOW_NO_CARD", "KJ_ALLOW_NO_TESTS", "KJ_ALLOW_PII", "KJ_ALLOW_POLICY", "KJ_ALLOW_IDENTITY", "KJ_ALLOW_BOARD"];
99
+ let raw = "";
100
+ process.stdin.on("data", (d) => { raw += d; });
101
+ process.stdin.on("end", () => {
102
+ try {
103
+ const { session_id: sid = "default", tool_name: tool, tool_input: input = {}, tool_response: response = {} } = JSON.parse(raw);
104
+ // KJC-TSK-0765 board-sync: a merge that gh CONFIRMS leaves its card pending
105
+ // in the tracker (the PreToolUse gate then refuses to advance until it is
106
+ // moved). Recorded here, after the fact, so a failed merge never blocks.
107
+ const text = typeof response === "string" ? response : JSON.stringify(response);
108
+ // Closing states that count as "registered in the tracker" (PG statuses and
109
+ // HU Board moves). The pending entry is cleared by the REAL tool call that
110
+ // moved the card — never by a promise in the agent's prose.
111
+ const CLOSING = ["to validate", "to-validate", "fixed", "verified", "closed", "done", "done&validated", "validated"];
112
+ const clearPending = (cardId) => {
113
+ const state = load();
114
+ const s = session(state, sid);
115
+ const before = (s.pending_moves || []).length;
116
+ s.pending_moves = (s.pending_moves || []).filter((p) => p.card !== null && p.card !== cardId);
117
+ if (s.pending_moves.length !== before) save(state);
118
+ };
119
+ if (/__update_card$/.test(String(tool)) && /^mcp__/.test(String(tool))) {
120
+ // The MCP response reaches the hook as {content:[{text:"<json>"}]}: once
121
+ // stringified, the inner quotes are ESCAPED — match both forms (found live:
122
+ // the first real update_card did not clear its pending entry).
123
+ const cid = /\\\\?"cardId\\\\?"\\s*:\\s*\\\\?"([A-Za-z0-9-]+)\\\\?"/.exec(text);
124
+ const st = /\\\\?"status\\\\?"\\s*:\\s*\\\\?"([^"\\\\]+)\\\\?"/.exec(text);
125
+ if (cid && st && CLOSING.includes(st[1].toLowerCase())) clearPending(cid[1].toUpperCase());
126
+ process.exit(0);
127
+ }
128
+ if (tool === "Bash") {
129
+ const cmdText = String(input.command || "");
130
+ // gh prints "merged pull request #N" only on a TTY: under a tool call the
131
+ // success line is ABSENT (found live on the first dogfood merge). The
132
+ // authoritative signal is the PR state itself: one gh pr view per merge.
133
+ const mergeCmd = /\\bgh\\s+pr\\s+merge(\\s+(\\d+))?/.exec(cmdText);
134
+ const said = /merged pull request #(\\d+)/i.exec(text);
135
+ let mergedPr = said ? Number(said[1]) : null;
136
+ if (mergedPr === null && mergeCmd) {
137
+ const args = ["pr", "view", ...(mergeCmd[2] ? [mergeCmd[2]] : []), "--json", "number,state"];
138
+ const r = spawnSync("gh", args, { cwd: ROOT, encoding: "utf8" });
139
+ try { const v = JSON.parse(r.status === 0 ? r.stdout : "null"); if (v && v.state === "MERGED") mergedPr = Number(v.number); } catch { /* unknown = not confirmed */ }
140
+ }
141
+ if (mergedPr !== null) {
142
+ const state = load();
143
+ const s = session(state, sid);
144
+ const ref = CARD.exec(branchOf() || "");
145
+ (s.pending_moves ||= []).push({ card: ref ? ref[0].toUpperCase() : null, pr: mergedPr, at: Date.now() });
146
+ save(state);
147
+ }
148
+ const moved = /kj\\s+hu\\s+move\\s+([A-Za-z0-9-]+)\\s+([a-z&-]+)/i.exec(String(input.command || ""));
149
+ if (moved && CLOSING.includes(moved[2].toLowerCase()) && !/error|fail/i.test(text)) clearPending(moved[1].toUpperCase());
150
+ process.exit(0);
151
+ }
152
+ const file = input.file_path || input.notebook_path;
153
+ if (!["Write", "Edit", "MultiEdit", "NotebookEdit"].includes(tool) || !file) process.exit(0);
154
+ const state = load();
155
+ const s = session(state, sid);
156
+ s.at = Date.now();
157
+ const rel = relative(ROOT, file).replaceAll("\\\\", "/");
158
+ const bucket = TESTS.test(rel) ? s.edited_tests : CODE.test(rel) ? s.edited_sources : null;
159
+ if (bucket && !bucket.includes(rel)) bucket.push(rel);
160
+ for (const e of ESCAPES)
161
+ if (process.env[e] === "1" && !s.escapes.includes(e)) {
162
+ s.escapes.push(e);
163
+ (state.escape_events ||= []).push({ escape: e, tool, sid, ts: Date.now() });
164
+ }
165
+ const ids = Object.keys(state.sessions);
166
+ if (ids.length > 5) delete state.sessions[ids.sort((a, b) => (state.sessions[a].at || 0) - (state.sessions[b].at || 0))[0]];
167
+ save(state);
168
+ } catch { /* fail open — the sentinel never breaks a tool call */ }
169
+ process.exit(0);
170
+ });
171
+ `;
172
+
173
+ const STOP_BODY = `#!/usr/bin/env node
174
+ // kj sentinel stop gate (KJC-TSK-0713) — managed by \`kj harden\`. Exit 2
175
+ // BLOCKS ending the turn while method violations are open (stderr lists each
176
+ // one with its remediation). Fails OPEN on corrupt state, git errors, or
177
+ // after 3 unresolved blocks — a sentinel bug never bricks the session — and
178
+ // the fail-open is recorded in the state. \`--status\` prints, never blocks.
179
+ import { spawnSync } from "node:child_process";
180
+ import { load, save, branchOf, violations, ROOT } from "./sentinel-lib.mjs";
181
+ if (process.argv.includes("--status")) {
182
+ const st = load();
183
+ const branch = branchOf();
184
+ const sessions = Object.entries(st.sessions || {});
185
+ if (!sessions.length) console.log("sentinel: sin actividad registrada en esta sesion");
186
+ for (const [sid, s] of sessions) {
187
+ console.log("session " + sid + ": sources=[" + (s.edited_sources || []).join(", ") + "] tests=[" + (s.edited_tests || []).join(", ") + "] escapes=[" + (s.escapes || []).join(", ") + "] blocks=" + (s.blocks || 0) + ((s.errors || []).length ? " errors=" + s.errors.length : ""));
188
+ for (const x of violations(s, branch)) console.log(" ROJO: " + x);
189
+ }
190
+ process.exit(0);
191
+ }
192
+ let raw = "";
193
+ process.stdin.on("data", (d) => { raw += d; });
194
+ process.stdin.on("end", () => {
195
+ try {
196
+ if (process.env.KJ_SENTINEL_OFF === "1") process.exit(0);
197
+ const { session_id: sid = "default" } = JSON.parse(raw);
198
+ const state = load();
199
+ // Tamper check runs regardless of session state: shell indirection leaves
200
+ // no edit-tool record, but it cannot survive \`kj sentinel verify\`, whose
201
+ // root of trust is the INSTALLED kj package (outside this project tree) —
202
+ // nothing under .karajan can vouch for itself. No kj on PATH → fail open.
203
+ const ver = spawnSync("kj", ["sentinel", "verify", "--json"], { cwd: ROOT, encoding: "utf8" });
204
+ let tampered = [];
205
+ if (!ver.error && ver.status !== null && ver.status !== 0) {
206
+ try { tampered = JSON.parse(ver.stdout).mismatched || ["unknown"]; } catch { tampered = ["unknown"]; }
207
+ }
208
+ if (tampered.length) {
209
+ state.tamper_blocks = (state.tamper_blocks || 0) + 1;
210
+ save(state);
211
+ if (state.tamper_blocks > 3) {
212
+ console.error("kj sentinel: fail-open tras 3 bloqueos por manipulacion sin restaurar — corre kj harden y avisa a tu usuario");
213
+ process.exit(0);
214
+ }
215
+ console.error("kj sentinel: scripts del supervisor modificados fuera de kj harden (" + tampered.join(", ") + ") — restaura con kj harden antes de terminar; si lo cambiaste tu (humano), reinstalar lo deja en verde.");
216
+ process.exit(2);
217
+ }
218
+ state.tamper_blocks = 0;
219
+ const s = state.sessions?.[sid];
220
+ if (!s) process.exit(0);
221
+ const v = violations(s, branchOf());
222
+ if (!v.length) {
223
+ s.blocks = 0;
224
+ save(state);
225
+ if ((s.escapes || []).length)
226
+ console.log(JSON.stringify({ systemMessage: "kj sentinel: esta sesion uso " + s.escapes.length + " escape(s): " + s.escapes.join(", ") + " — decision registrada; detalle en kj sentinel status." }));
227
+ process.exit(0);
228
+ }
229
+ s.blocks = (s.blocks || 0) + 1;
230
+ if (s.blocks > 3) {
231
+ (s.errors ||= []).push("fail-open: 3 bloqueos consecutivos sin resolver — el sentinel se aparta para no colgar la sesion");
232
+ save(state);
233
+ console.error("kj sentinel: fail-open tras 3 bloqueos sin resolver — revisa kj sentinel status con tu usuario");
234
+ process.exit(0);
235
+ }
236
+ save(state);
237
+ console.error("kj sentinel: el turno NO puede terminar con el metodo en rojo:\\n" + v.map((x) => "- " + x).join("\\n") + "\\nResuelve las violaciones (o pide a tu usuario el escape) y termina de nuevo. Estado: kj sentinel status");
238
+ process.exit(2);
239
+ } catch { /* fail open */ }
240
+ process.exit(0);
241
+ });
242
+ `;
243
+
244
+ const PRETOOL_BODY = `#!/usr/bin/env node
245
+ // kj sentinel pretooluse gate (KJC-TSK-0714) — managed by \`kj harden\`.
246
+ // Consults the session method state BEFORE the tool runs: the rule fires
247
+ // before the damage, not in the post-mortem. Exit 2 blocks (stderr says the
248
+ // remediation); read-only tools are never wired here; every honored escape
249
+ // is recorded as an auditable event. Fails OPEN on anything unexpected.
250
+ import { dirname, join, relative, resolve } from "node:path";
251
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
252
+ import { spawnSync } from "node:child_process";
253
+ import { homedir } from "node:os";
254
+ import { CODE, TESTS, ROOT, BASE_BRANCHES, CARD, branchOf, foreignLane, load, violations, recordEscape, pendingMoves, pendingText } from "./sentinel-lib.mjs";
255
+ const EDIT_TOOLS = ["Write", "Edit", "MultiEdit", "NotebookEdit"];
256
+ const PUBLISH = /\\bnpm\\s+publish\\b|\\bfirebase\\s+deploy\\b|\\bgh\\s+release\\s+create\\b/;
257
+ const PUSH = /\\bgit\\s+push\\b/;
258
+ const PROTECTED = /\\.claude\\/settings\\.json\\b|\\.karajan\\/(hooks|harness)\\//;
259
+ let raw = "";
260
+ process.stdin.on("data", (d) => { raw += d; });
261
+ process.stdin.on("end", () => {
262
+ try {
263
+ const { session_id: sid = "default", tool_name: tool, tool_input: input = {} } = JSON.parse(raw);
264
+ // Self-protection (KJC-TSK-0715) rules run BEFORE any escape, including
265
+ // KJ_SENTINEL_OFF: the sentinel is not dismantled from inside a session —
266
+ // only the human, editing outside it.
267
+ if (EDIT_TOOLS.includes(tool)) {
268
+ const target = input.file_path || input.notebook_path;
269
+ const relT = target ? relative(ROOT, String(target)).replaceAll("\\\\", "/") : "";
270
+ if (relT && PROTECTED.test(relT)) {
271
+ console.error("kj sentinel: ese fichero es parte del supervisor (" + relT + ") — solo el humano desmonta el sentinel, editalo fuera de la sesion.");
272
+ process.exit(2);
273
+ }
274
+ }
275
+ // Any Bash that NAMES the supervisor's files is denied — a write-verb
276
+ // blocklist is bypassable (cp, dd, one-liners), and reading them is what
277
+ // the Read/Grep tools are for. Shell indirection (variables, globs,
278
+ // substitution) cannot be resolved from command text: that vector is
279
+ // caught by the Stop gate's checksum verification, which blocks the turn.
280
+ if (tool === "Bash" && PROTECTED.test(String(input.command || "").replaceAll("\\\\", "/"))) {
281
+ console.error("kj sentinel: los ficheros del supervisor no se tocan desde Bash — consultalos con la tool Read/Grep; solo el humano los modifica, fuera de la sesion.");
282
+ process.exit(2);
283
+ }
284
+ if (process.env.KJ_SENTINEL_OFF === "1") process.exit(0);
285
+ // KJC-BUG-0142: cada deny anuncia su escape como prefijo del comando
286
+ // (KJ_ALLOW_X=1 cmd), pero el hook corre con el env del HOST — el
287
+ // prefijo jamas llegaba a process.env y el escape era inalcanzable
288
+ // (deadlock real del gate de release: la landing solo se pone verde
289
+ // tras el deploy y el deploy estaba bloqueado por el mismo gate). Un
290
+ // escape es una excepcion CONSCIENTE y auditada, no un boundary de
291
+ // seguridad (security sigue sin escape y la autoprotecion corre antes),
292
+ // asi que el TEXTO del comando es fuente valida: solo asignaciones
293
+ // LITERALES al inicio (tokens VAR=VAL antes del primer verbo) y solo
294
+ // sobre un comando SIMPLE — con ;|& $() backtick o salto de linea el
295
+ // prefijo shell no alcanzaria a los comandos posteriores (catch de
296
+ // codex: "X=1 true && npm publish" escaparia sin serlo), asi que el
297
+ // escape por texto se rechaza y queda la via env de siempre.
298
+ const CMD_TEXT = tool === "Bash" ? String(input.command || "") : "";
299
+ const ESC_TAB = String.fromCharCode(9);
300
+ const ESC_NL = String.fromCharCode(10);
301
+ const ESC_BT = String.fromCharCode(96);
302
+ const escOn = (name) => {
303
+ if (process.env[name] === "1") return true;
304
+ let found = false;
305
+ let i = 0;
306
+ while (i < CMD_TEXT.length) {
307
+ while (i < CMD_TEXT.length && (CMD_TEXT[i] === " " || CMD_TEXT[i] === ESC_TAB)) i += 1;
308
+ const start = i;
309
+ while (i < CMD_TEXT.length && CMD_TEXT[i] !== " " && CMD_TEXT[i] !== ESC_TAB && CMD_TEXT[i] !== ESC_NL) i += 1;
310
+ const tok = CMD_TEXT.slice(start, i);
311
+ const eq = tok.indexOf("=");
312
+ if (eq <= 0 || !/^[A-Z][A-Z0-9_]*$/.test(tok.slice(0, eq))) break;
313
+ if (tok === name + "=1") found = true;
314
+ }
315
+ if (!found) return false;
316
+ for (const c of CMD_TEXT) {
317
+ if (c === ";" || c === "|" || c === "&" || c === ESC_NL || c === ESC_BT) return false;
318
+ if (c === "$" || c === "(" || c === ")") return false;
319
+ }
320
+ return true;
321
+ };
322
+ // IDN-B (KJC-TSK-0763, ADR 0005): identity lock — gh and mutating git never
323
+ // run under an account other than the one THIS CLONE declared (the incident:
324
+ // a gh comment without switch went out as the client account). Deny BEFORE
325
+ // the damage with the literal remedy; never auto-switch; undeclared = closed.
326
+ if (tool === "Bash" && CMD_TEXT.length > 0) {
327
+ const idFile = join(ROOT, ".karajan", "identity.local.yml");
328
+ const idLine = (key) => {
329
+ if (!existsSync(idFile)) return null;
330
+ const line = readFileSync(idFile, "utf8").split(ESC_NL).find((l) => l.startsWith(key + ":"));
331
+ const v = line ? line.slice(key.length + 1).trim() : "";
332
+ return v.length > 0 ? v : null;
333
+ };
334
+ const declaredGh = idLine("gh_user");
335
+ const declaredEmail = idLine("git_email");
336
+ const ghHostsPath = join(process.env.GH_CONFIG_DIR || join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "gh"), "hosts.yml");
337
+ const activeGh = () => {
338
+ if (!existsSync(ghHostsPath)) return null;
339
+ const lines = readFileSync(ghHostsPath, "utf8").split(ESC_NL);
340
+ const start = lines.findIndex((l) => l.startsWith("github.com:"));
341
+ if (start < 0) return null;
342
+ for (let k = start + 1; k < lines.length; k += 1) {
343
+ if (lines[k].length > 0 && lines[k][0] !== " " && lines[k][0] !== ESC_TAB) break;
344
+ const t = lines[k].trim();
345
+ if (t.startsWith("user:")) { const u = t.slice(5).trim(); return u.length > 0 ? u : null; }
346
+ }
347
+ return null;
348
+ };
349
+ const words = (seg) => seg.trim().split(" ").filter((w) => w.length > 0 && w !== ESC_TAB);
350
+ const headOf = (ws) => { let k = 0; while (k < ws.length && ws[k].indexOf("=") > 0 && ws[k].indexOf("=") < ws[k].length) k += 1; return ws.slice(k); };
351
+ // Wrapper shells hide the payload in a string (review catch): scan inside.
352
+ const WRAPPERS =["sh", "bash", "zsh", "dash", "eval", "env", "xargs", "sudo", "nohup", "time", "exec"];
353
+ let switchedTo = null;
354
+ const problems = [];
355
+ // $( and backticks are separators too (review catch): their payload is its own segment.
356
+ const SEPS = [";", "&&", "||", "|", "$(", String.fromCharCode(96), "(", ")"];
357
+ const bare = (w) => w.replaceAll(String.fromCharCode(39), "").replaceAll(String.fromCharCode(34), "");
358
+ const scan = (text, depth) => { if (depth > 3) return; for (const seg of SEPS.reduce((acc, s) => acc.flatMap((l) => l.split(s)), text.split(ESC_NL))) checkSeg(headOf(words(seg)), depth, words(seg)); };
359
+ const checkSeg = (ws, depth, raw) => {
360
+ if (ws.length === 0) return;
361
+ if (WRAPPERS.includes(ws[0])) { // wrapper args have arity (sudo -u bob): scan from the first gh/git/wrapper token (review catch)
362
+ const j = ws.findIndex((w, i) => i > 0 && ["gh", "git", ...WRAPPERS].includes(bare(w)));
363
+ if (j > 0) scan(ws.slice(j).map(bare).join(" "), depth + 1);
364
+ return;
365
+ }
366
+ if (ws[0] === "gh") {
367
+ if (ws[1] === "auth") {
368
+ if (ws[2] === "switch") { const u = ws.indexOf("--user"); if (u > 0 && ws[u + 1]) switchedTo = ws[u + 1]; }
369
+ return; // session commands ARE the remedy — never blocked
370
+ }
371
+ if (!declaredGh) { problems.push("gh without a declared identity for this clone — run: kj identity set"); return; }
372
+ const eff = switchedTo || activeGh();
373
+ if (eff !== declaredGh) problems.push("gh as '" + (eff || "no session") + "' but this clone is declared as '" + declaredGh + "' — prefix the command: gh auth switch --user " + declaredGh + " && ...");
374
+ return;
375
+ }
376
+ if (ws[0] === "git") {
377
+ // Global options (-C, -c, --git-dir, --work-tree...) are NOT re-implemented: they
378
+ // are forwarded verbatim to git var, so git resolves identity exactly as the real
379
+ // command would (review catches: -C cumulative, --git-dir, --opt=value forms).
380
+ const TWO = ["-c", "-C", "--git-dir", "--work-tree", "--namespace", "--super-prefix", "--exec-path", "--config-env"];
381
+ let k = 1;
382
+ while (k < ws.length && ws[k].startsWith("-")) k += TWO.includes(ws[k]) && ws[k + 1] ? 2 : 1;
383
+ const globals = ws.slice(1, k).map(bare);
384
+ const sub = ws[k];
385
+ if (sub === "push") { // push authenticates with the gh session, not user.email (review catch)
386
+ if (!declaredGh) { problems.push("git push without a declared identity for this clone — run: kj identity set"); return; }
387
+ const eff = switchedTo || activeGh();
388
+ if (eff !== declaredGh) problems.push("git push with gh session '" + (eff || "no session") + "' but this clone is declared as '" + declaredGh + "' — prefix: gh auth switch --user " + declaredGh + " && ...");
389
+ return;
390
+ }
391
+ // IDN-B2: anything that authors or rewrites commits carries an identity (review catch).
392
+ if (!["commit", "tag", "merge", "rebase", "cherry-pick", "am", "revert"].includes(sub)) return; // am: the COMMITTER is still this user
393
+ if (!declaredEmail) { problems.push("git " + sub + " without a declared identity for this clone — run: kj identity set"); return; }
394
+ // Let GIT resolve author and committer under the command's own environment
395
+ // (review catch: user.email, -c, GIT_*_EMAIL, EMAIL, GIT_CONFIG_*, includeIf are
396
+ // its business): git var is the single source of truth.
397
+ const assigns = raw.slice(0, raw.length - ws.length).filter((w) => w.indexOf("=") > 0);
398
+ const env = Object.assign({}, process.env);
399
+ for (const w of assigns) env[w.slice(0, w.indexOf("="))] = bare(w.slice(w.indexOf("=") + 1));
400
+ const ident = (v) => {
401
+ const r = spawnSync("git", [...globals, "var", v], { cwd: ROOT, encoding: "utf8", env });
402
+ const s = r.status === 0 ? String(r.stdout) : "";
403
+ const a = s.indexOf("<");
404
+ const b = s.indexOf(">");
405
+ return a >= 0 && b > a ? s.slice(a + 1, b) : null;
406
+ };
407
+ const ai = ws.findIndex((w) => w.startsWith("--author"));
408
+ const authorRaw = ai < 0 ? null : ws[ai].includes("=") ? ws[ai].slice(ws[ai].indexOf("=") + 1) : ws.slice(ai + 1).join(" ");
409
+ const authorMail = authorRaw === null ? null : bare(authorRaw.includes("<") ? authorRaw.slice(authorRaw.indexOf("<") + 1, authorRaw.indexOf(">")) : authorRaw.trim());
410
+ const mails = [authorMail ?? ident("GIT_AUTHOR_IDENT"), ident("GIT_COMMITTER_IDENT")]; // --author overrides the author (review catch); the committer is always git's
411
+ const offender = mails.find((m) => m !== null && m !== declaredEmail) ?? (mails[0] === null ? "none" : null);
412
+ if (offender !== null) problems.push("git " + sub + " as '" + offender + "' but this clone is declared as '" + declaredEmail + "' — use: git -c user.email=" + declaredEmail + " " + sub + " ... (or git config user.email " + declaredEmail + ")");
413
+ }
414
+ };
415
+ scan(CMD_TEXT, 0);
416
+ if (problems.length > 0) {
417
+ if (escOn("KJ_ALLOW_IDENTITY")) { recordEscape(sid, "KJ_ALLOW_IDENTITY", tool); }
418
+ else {
419
+ console.error("kj sentinel: identity lock (ADR 0005) —" + problems.map((p) => ESC_NL + "- " + p).join("") + ESC_NL + "(KJ_ALLOW_IDENTITY=1 = excepcion consciente, queda registrada)");
420
+ process.exit(2);
421
+ }
422
+ }
423
+ }
424
+ // MONO-0 (KJC-TSK-0737, ADR 0002): cada sesion muta solo SU worktree;
425
+ // otro carril del MISMO repo se deniega ANTES del dano. Lectura libre.
426
+ const laneOf = (p) => {
427
+ // Sube al primer ancestro EXISTENTE (un subdir aun no creado escapaba).
428
+ let d = String(p);
429
+ if (!(existsSync(d) && statSync(d).isDirectory())) d = dirname(d);
430
+ while (d && !existsSync(d)) {
431
+ const up = dirname(d);
432
+ if (up === d) return null;
433
+ d = up;
434
+ }
435
+ // Canonicaliza (reviewer catch: un symlink local hacia otro carril
436
+ // esquivaba la clasificacion por ruta literal).
437
+ try { d = realpathSync(d); } catch { /* se clasifica tal cual */ }
438
+ return d ? foreignLane(d) : null;
439
+ };
440
+ const laneDeny = (what, lane) => {
441
+ if (escOn("KJ_ALLOW_CROSS_LANE")) { recordEscape(sid, "KJ_ALLOW_CROSS_LANE", tool); return false; }
442
+ console.error("kj sentinel: " + what + " vive en otro carril (" + lane + ") de este repo — cada sesion muta solo SU worktree (MONO-0). Cruce deliberado: KJ_ALLOW_CROSS_LANE=1, queda registrado.");
443
+ return true;
444
+ };
445
+ if (EDIT_TOOLS.includes(tool)) {
446
+ const target = input.file_path || input.notebook_path;
447
+ const lane = target ? laneOf(target) : null;
448
+ if (lane && laneDeny(String(target), lane)) process.exit(2);
449
+ }
450
+ if (tool === "Bash") {
451
+ const cmd = String(input.command || "");
452
+ // Deny-unless-known-read-only: un allowlist de verbos mutadores era
453
+ // esquivable (dd, interpretes). Solo un comando SIMPLE de lectura (sin
454
+ // encadenar ni redirigir) puede nombrar otro carril; leer carriles es
455
+ // trabajo de las tools Read/Grep, como con los ficheros del supervisor.
456
+ // Solo LITERALES: expansión/sustitución en un comando "de lectura"
457
+ // puede ejecutar cualquier cosa (reviewer catch: grep foo $(rm ...)).
458
+ // (find fuera: -delete/-exec mutan — reviewer catch; sus tokens de
459
+ // carril los caza el escaner de abajo.)
460
+ const READONLY = /^[ \\t]*(grep|rg|cat|head|tail|less|ls|wc|diff|stat|file|du|tree|git (log|show|diff|status|blame))\\b[^;|&<>$\`(){}\\n\\r]*$/;
461
+ if (!READONLY.test(cmd)) {
462
+ // cd/pushd invalida TODO razonamiento textual de rutas posteriores
463
+ // (carrera de bypasses confirmada en review: destino bare, con $,
464
+ // relativas post-cd...): en un comando NO-read-only, cambiar de
465
+ // directorio se deniega conservadoramente — el remedio es no
466
+ // necesitarlo (git -C, npm --prefix, rutas absolutas).
467
+ if (/(^|[;&|(\\s])(cd|pushd)([ \\t]|$)/.test(cmd)) {
468
+ if (escOn("KJ_ALLOW_CROSS_LANE")) { recordEscape(sid, "KJ_ALLOW_CROSS_LANE", tool); }
469
+ else {
470
+ console.error("kj sentinel: cd/pushd en un comando mutador — las rutas posteriores no son verificables por el guard de carriles (MONO-0); usa git -C / npm --prefix / rutas ABSOLUTAS sin cd (o KJ_ALLOW_CROSS_LANE=1, queda registrado).");
471
+ process.exit(2);
472
+ }
473
+ }
474
+ // Absolute AND dot-relative tokens (../lane escapaba al primer scan —
475
+ // reviewer catch); relative se resuelve contra el cwd de la sesion.
476
+ // EVERY token (un tope seria un bypass); dedup acota el coste git.
477
+ // Fallo de solomon (seguridad no arbitrable) + carrera de bypasses:
478
+ // la expansion ($, backtick) solo se tolera en segmentos que son
479
+ // asignaciones puras o runners del toolchain propio (npm/pnpm/yarn/
480
+ // vitest/jest/kj/gh/echo/printf, git sin -C/--git-dir/--work-tree) —
481
+ // sus argumentos son datos. En herramientas genericas de fichero o
482
+ // interpretes el objetivo queda oculto al texto => deny. Los paths
483
+ // LITERALES (tambien dentro de sustituciones) los escanea el bucle
484
+ // de tokens de abajo; el residuo (expansion anidada en segmentos
485
+ // runner) queda documentado como en la regla de ficheros PROTECTED.
486
+ if (/>{1,2}[ \\t]*["']?[\\$\`]/.test(cmd)) {
487
+ if (escOn("KJ_ALLOW_CROSS_LANE")) { recordEscape(sid, "KJ_ALLOW_CROSS_LANE", tool); }
488
+ else {
489
+ console.error("kj sentinel: redireccion con destino tras variable/sustitucion — el guard de carriles no puede verificarlo (MONO-0); usa una ruta LITERAL (o KJ_ALLOW_CROSS_LANE=1, queda registrado).");
490
+ process.exit(2);
491
+ }
492
+ }
493
+ // Sustitucion de comandos ($(...) o backticks) EJECUTA su contenido
494
+ // en cualquier posicion, y una ruta entrecomillada con espacios es
495
+ // invisible al tokenizador: ambas => deny conservador (endpoint
496
+ // pedido por el reviewer; friccion asumida, el remedio es literal).
497
+ // KJC-BUG-0140: pares de comillas REALES, no regex sobre el texto —
498
+ // la comilla de CIERRE de un string + un redirect literal + la de
499
+ // apertura del siguiente creaban la ilusion de ruta con espacios y
500
+ // denegaban el flujo estandar commit -m "..." > log && echo "ok".
501
+ // Se recorre alternando estado (doctrina del parseCommand del motor):
502
+ // solo el CONTENIDO realmente entrecomillado con slash y espacio
503
+ // deniega — incluidas comillas a mitad de token (scp host:"/a b/x")
504
+ // y la comilla sin cerrar (no verificable).
505
+ // ...y por TOKEN completo, no por span: spans adyacentes concatenan
506
+ // ("dir with"" spaces/file" es UNA ruta) — catch de codex.
507
+ const quotedPathWithSpaces = (s) => {
508
+ // Un backslash pegado a comilla o blanco (comilla escapada,
509
+ // espacio escapado) altera donde CIERRA el string o esconde
510
+ // espacios: opaco => deny (doctrina del motor) — catch de codex.
511
+ // Caracteres construidos con fromCharCode: CERO escaping en este
512
+ // template — fuente y artefacto identicos, nada que malinterpretar
513
+ // (el doble-escaping confundio cuatro reviews; solomon desestimo
514
+ // la primera y las demas se volvieron ilegibles igual).
515
+ // El punto escapado de un sed y similares siguen libres.
516
+ const BS = String.fromCharCode(92);
517
+ const TAB = String.fromCharCode(9);
518
+ const NL = String.fromCharCode(10);
519
+ for (let i = s.indexOf(BS); i !== -1; i = s.indexOf(BS, i + 1)) {
520
+ const n = s[i + 1] || "";
521
+ if (n === '"' || n === "'" || n === " " || n === TAB) return true;
522
+ }
523
+ let q = null;
524
+ let tok = "";
525
+ let hadQuote = false;
526
+ const flush = () => {
527
+ const bad = hadQuote && tok.includes("/") && tok.includes(" ");
528
+ tok = "";
529
+ hadQuote = false;
530
+ return bad;
531
+ };
532
+ for (const ch of s) {
533
+ if (q) {
534
+ if (ch === q) q = null;
535
+ else tok += ch;
536
+ continue;
537
+ }
538
+ if (ch === '"' || ch === "'") {
539
+ q = ch;
540
+ hadQuote = true;
541
+ continue;
542
+ }
543
+ if (ch === " " || ch === TAB || ch === NL || ch === ";" || ch === "|" || ch === "&" || ch === "<" || ch === ">" || ch === "(" || ch === ")") {
544
+ if (flush()) return true;
545
+ continue;
546
+ }
547
+ tok += ch;
548
+ }
549
+ if (q !== null) return true; // comilla sin cerrar: no verificable
550
+ return flush();
551
+ };
552
+ if (/\\$\\(|\`/.test(cmd) || quotedPathWithSpaces(cmd)) {
553
+ if (escOn("KJ_ALLOW_CROSS_LANE")) { recordEscape(sid, "KJ_ALLOW_CROSS_LANE", tool); }
554
+ else {
555
+ console.error("kj sentinel: sustitucion de comandos o ruta entrecomillada con espacios en un comando mutador — no verificable por el guard de carriles (MONO-0); usa valores/rutas LITERALES sin sustitucion (o KJ_ALLOW_CROSS_LANE=1, queda registrado).");
556
+ process.exit(2);
557
+ }
558
+ }
559
+ const SAFE_EXP_SEG = /^([A-Za-z_][A-Za-z0-9_]*=[^ \\t]*[ \\t]*)*((npm|pnpm|yarn|vitest|jest|kj|gh|echo|printf|true|test)\\b|git[ \\t](?![^\\n]*(-C[ \\t]|--git-dir|--work-tree)))[^;|&\\n]*$|^[A-Za-z_][A-Za-z0-9_]*=[^;|&\\n]*$/;
560
+ const segs = cmd.split(/&&|\\|\\||[;|\\n]/).map((s) => s.trim()).filter(Boolean);
561
+ if (!segs.every((s) => !/[\\$]/.test(s) || SAFE_EXP_SEG.test(s))) {
562
+ if (escOn("KJ_ALLOW_CROSS_LANE")) { recordEscape(sid, "KJ_ALLOW_CROSS_LANE", tool); }
563
+ else {
564
+ console.error("kj sentinel: expansion de shell en una herramienta generica de fichero/interprete — el objetivo no es verificable por el guard de carriles (MONO-0); usa rutas LITERALES o un runner del toolchain (o KJ_ALLOW_CROSS_LANE=1, queda registrado).");
565
+ process.exit(2);
566
+ }
567
+ }
568
+ // ...incluidas relativas A PELO (los carriles kj viven en
569
+ // .kj/worktrees/). Un falso token (s/a/b/) resuelve al arbol propio
570
+ // y foreignLane da null — solo deniega lo que CAE en otro carril.
571
+ const seen = new Set();
572
+ for (const m of cmd.matchAll(/(?:^|[\\s"'=;(><])((?:\\.\\.?\\/)[^\\s"'\`;|&)]*|\\/[^\\s"'\`;|&)]+|~\\/[^\\s"'\`;|&)]+|[A-Za-z0-9_.@-]+\\/[^\\s"'\`;|&)]+)/g)) {
573
+ const t = m[1].startsWith("~/") ? (process.env.HOME || "~") + m[1].slice(1) : m[1];
574
+ const abs = resolve(t);
575
+ if (seen.has(abs)) continue;
576
+ seen.add(abs);
577
+ const lane = laneOf(abs);
578
+ if (lane && laneDeny(m[1], lane)) process.exit(2);
579
+ }
580
+ }
581
+ }
582
+ // KJC-TSK-0734 (PL-B): con .karajan/policy.yml presente, la evaluacion
583
+ // la hace el MOTOR via kj policy eval --strict (exit 2 = deny, contrato
584
+ // PL-A); los defaults del supervisor viven en el motor y el check
585
+ // PROTECTED inline de arriba queda como red fail-closed si kj falta.
586
+ // Fail CLOSED por defecto (catches de codex; doctrina KJC-BUG-0095: un
587
+ // gate no se cae en silencio): solo exit 0 permite. kj inejecutable =
588
+ // deny duro (el remedio es restaurar kj); cualquier otro fallo de
589
+ // evaluacion = deny con escape humano registrable — la sesion no queda
590
+ // presa de un typo de YAML, pero abrirla es decision del usuario.
591
+ if ((EDIT_TOOLS.includes(tool) || tool === "Bash") && existsSync(resolve(ROOT, ".karajan", "policy.yml"))) {
592
+ // PL-C: el rol que ACTÚA (KJ_POLICY_ROLE, sembrado por los runners de
593
+ // kj run en los subprocesos) — el anfitrión-brain evalúa como coder.
594
+ const actorRole = process.env.KJ_POLICY_ROLE || "coder";
595
+ const pres = spawnSync("kj", ["policy", "eval", "--strict", "--role", actorRole, "--tool", tool, "--input", JSON.stringify(input)], { cwd: ROOT, encoding: "utf8" });
596
+ if (pres.error || pres.status === null) {
597
+ console.error("kj sentinel: .karajan/policy.yml declara enforcement pero kj no es ejecutable — la policy no se puede evaluar; restaura kj en el PATH (npm i -g @karajan-family/code, o el nombre legacy karajan-code) o retira la policy conscientemente.");
598
+ process.exit(2);
599
+ }
600
+ if (pres.status === 2) {
601
+ let v = null;
602
+ try { v = JSON.parse(String(pres.stdout || "").trim().split("\\n").pop()); } catch { /* mensaje generico */ }
603
+ const secure = !!(v && v.class === "security");
604
+ if (!secure && escOn("KJ_ALLOW_POLICY")) { recordEscape(sid, "KJ_ALLOW_POLICY", tool); }
605
+ else {
606
+ console.error("kj sentinel: policy deny [" + ((v && v.rule_id) || "policy") + "] " + ((v && v.reason) || "la tool call viola la policy del proyecto") + (secure ? " [security — sin escape ni arbitraje]" : " (KJ_ALLOW_POLICY=1 = excepcion consciente, queda registrada; el commit exigira ademas KJ_POLICY_REASON)"));
607
+ process.exit(2);
608
+ }
609
+ } else if (pres.status !== 0) {
610
+ if (escOn("KJ_ALLOW_POLICY")) { recordEscape(sid, "KJ_ALLOW_POLICY", tool); }
611
+ else {
612
+ console.error("kj sentinel: kj policy eval fallo (exit " + pres.status + ") — la policy declarada no se pudo evaluar, deny por defecto; diagnostica con kj policy check y corrige .karajan/policy.yml fuera de la sesion (o KJ_ALLOW_POLICY=1 = excepcion consciente, queda registrada).");
613
+ process.exit(2);
614
+ }
615
+ }
616
+ }
617
+ if (EDIT_TOOLS.includes(tool)) {
618
+ const file = input.file_path || input.notebook_path;
619
+ const rel = file ? relative(ROOT, String(file)).replaceAll("\\\\", "/") : "";
620
+ if (rel && CODE.test(rel) && !TESTS.test(rel)) {
621
+ const branch = branchOf();
622
+ const why = !branch ? null : BASE_BRANCHES.has(branch) ? "base" : !CARD.test(branch) ? "nocard" : null;
623
+ if (why) {
624
+ if (escOn("KJ_ALLOW_NO_CARD")) { recordEscape(sid, "KJ_ALLOW_NO_CARD", tool); process.exit(0); }
625
+ console.error(why === "base"
626
+ ? "kj sentinel: no se editan fuentes en la rama base '" + branch + "' — crea la card (kj hu add) y la rama: git checkout -b feat/<CARD-ID>-descripcion. (KJ_ALLOW_NO_CARD=1 = excepcion consciente, queda registrada)"
627
+ : "kj sentinel: la rama '" + branch + "' no referencia ninguna card — crea/mueve la card a running (kj hu add | kj hu move) y usa una rama feat/<CARD-ID>-descripcion. (KJ_ALLOW_NO_CARD=1 = excepcion consciente, queda registrada)");
628
+ process.exit(2);
629
+ }
630
+ }
631
+ }
632
+ if (tool === "Bash") {
633
+ const cmd = String(input.command || "");
634
+ // KJC-TSK-0765 board-sync: a merge leaves its card PENDING in the tracker
635
+ // and nothing advances (commit, new PR, another merge; push and the end of
636
+ // the turn go through violations()) until the card is moved. The board is
637
+ // true at all times or the method stops — never a rule in the agent's memory.
638
+ const MERGE = /\\bgh\\s+pr\\s+merge(\\s+(\\d+))?/;
639
+ const ADVANCE = /\\bgit\\s+commit\\b|\\bgit\\s+push\\b|\\bgh\\s+pr\\s+create\\b/; // push also falls under violations() — explicit here too (review catch)
640
+ const mergeM = cmd.match(MERGE);
641
+ const pend = pendingMoves(load().sessions?.[sid]);
642
+ if (pend.length > 0 && (mergeM || ADVANCE.test(cmd))) {
643
+ if (escOn("KJ_ALLOW_BOARD")) { recordEscape(sid, "KJ_ALLOW_BOARD", tool); }
644
+ else {
645
+ console.error("kj sentinel: board-sync — el metodo no avanza con cards mergeadas sin mover:\\n" + pend.map((p) => "- " + pendingText(p)).join("\\n") + "\\n(KJ_ALLOW_BOARD=1 = excepcion consciente, queda registrada)");
646
+ process.exit(2);
647
+ }
648
+ }
649
+ // (the pending entry itself is recorded by the PostToolUse hook, only once
650
+ // gh confirms "merged pull request #N" — a failed merge never blocks)
651
+ if (PUBLISH.test(cmd)) {
652
+ if (escOn("KJ_ALLOW_RELEASE")) { recordEscape(sid, "KJ_ALLOW_RELEASE", tool); process.exit(0); }
653
+ const res = spawnSync("kj", ["release", "check", "--json"], { cwd: ROOT, encoding: "utf8" });
654
+ if (res.error || res.status === null) process.exit(0);
655
+ if (res.status !== 0) {
656
+ let items = "";
657
+ try { items = (JSON.parse(res.stdout).checks || []).filter((c) => !c.ok).map((c) => "\\n- " + c.name + ": " + c.detail).join(""); } catch { /* raw output */ }
658
+ console.error("kj sentinel: release check en ROJO — no se publica ni despliega hasta resolverlo:" + (items || "\\n- corre kj release check para el detalle") + "\\n(KJ_ALLOW_RELEASE=1 = excepcion consciente, queda registrada)");
659
+ process.exit(2);
660
+ }
661
+ } else if (PUSH.test(cmd)) {
662
+ const v = violations(load().sessions?.[sid], branchOf());
663
+ if (v.length) {
664
+ console.error("kj sentinel: git push con el metodo en rojo:\\n" + v.map((x) => "- " + x).join("\\n") + "\\nResuelve antes de empujar. Estado: kj sentinel status");
665
+ process.exit(2);
666
+ }
667
+ }
668
+ }
669
+ } catch { /* fail open */ }
670
+ process.exit(0);
671
+ });
672
+ `;
673
+
674
+ const SCRIPT_BODIES = {
675
+ "sentinel-lib.mjs": LIB_BODY,
676
+ "posttooluse.mjs": POST_BODY,
677
+ "stop.mjs": STOP_BODY,
678
+ "pretooluse-sentinel.mjs": PRETOOL_BODY,
679
+ };
680
+
681
+ /**
682
+ * Tamper detection (KJC-TSK-0715): compare the on-disk harness scripts with
683
+ * what THIS kj install would write. The root of trust is the installed kj
684
+ * package — outside the project tree, beyond the session's tool reach —
685
+ * because nothing under .karajan can vouch for itself.
686
+ */
687
+ export function verifySentinelScripts({ projectDir } = {}) {
688
+ const dir = join(projectDir || resolveSentinelRoot(), ".karajan", "harness");
689
+ const mismatched = [];
690
+ for (const [name, body] of Object.entries(SCRIPT_BODIES)) {
691
+ try {
692
+ if (readFileSync(join(dir, name), "utf8") !== body) mismatched.push(name);
693
+ } catch {
694
+ mismatched.push(name);
695
+ }
696
+ }
697
+ return { ok: mismatched.length === 0, mismatched };
698
+ }
699
+
700
+ /** Write the sentinel scripts (shared lib + state writer + gates) and wire them. */
701
+ export function installSentinelHooks({ projectDir = process.cwd(), logger = console } = {}) {
702
+ const [lib, post, stop, pre] = Object.entries(SCRIPT_BODIES).map(([name, body]) =>
703
+ writeHarnessScript(projectDir, name, body),
704
+ );
705
+ const { wired } = mergeClaudeHooks({
706
+ projectDir,
707
+ logger,
708
+ entries: [
709
+ { event: "PreToolUse", matcher: "Write|Edit|MultiEdit|NotebookEdit", script: "pretooluse-sentinel.mjs" },
710
+ { event: "PreToolUse", matcher: "Bash", script: "pretooluse-sentinel.mjs" },
711
+ { event: "PostToolUse", matcher: "Write|Edit|MultiEdit|NotebookEdit", script: "posttooluse.mjs" },
712
+ // KJC-TSK-0765 board-sync: the merge (Bash) and the tracker move (MCP
713
+ // update_card) are observed AFTER the fact — found live: with only the
714
+ // edit matchers wired, the board-sync recorder never ran in production.
715
+ { event: "PostToolUse", matcher: "Bash", script: "posttooluse.mjs" },
716
+ { event: "PostToolUse", matcher: "mcp__.*__update_card", script: "posttooluse.mjs" },
717
+ { event: "Stop", script: "stop.mjs" },
718
+ ],
719
+ });
720
+ return { scripts: [lib, post, stop, pre], wired };
721
+ }