@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,355 @@
1
+ /**
2
+ * `kj review --staged | --check | --range` (ENV-B1, KJC-TSK-0637) — the v4
3
+ * cross-AI review gate. Unlike the legacy task-review mode, this reviews a
4
+ * raw git diff with an AI DIFFERENT from the host agent and records the
5
+ * verdict tied to the exact diff (verdict-store), so the pre-commit hook
6
+ * (ENV-C) can verify it. Exit code 0 = approved, 1 = rejected/stale.
7
+ */
8
+ import { existsSync } from "node:fs";
9
+ import { isAbsolute, join } from "node:path";
10
+ import { runCommand } from "../utils/process.js";
11
+ import { checkVerdict, diffHash } from "../review/verdict-store.js";
12
+ import { runOneShotReview } from "../review/one-shot-review.js";
13
+ import { runSolomonArbitration } from "../review/solomon-arbitration.js";
14
+ import { ensureGateTrackable } from "../review/gate-gitignore.js";
15
+ import { runSonarPregate, formatSonarFinding } from "../review/sonar-pregate.js";
16
+ import { runMutationPregate, formatSurvivor } from "../review/mutation-pregate.js";
17
+ import { checkCardFirst } from "../review/card-first.js";
18
+ import { checkTestsWithCode } from "../review/tests-with-code.js";
19
+ import { loadPrivacyList, scanText } from "../privacy/scan.js";
20
+ import { checkStagedDiff, loadPolicy } from "../policy/engine.js";
21
+ import { loadStandingExceptions, recordPolicyException } from "../policy/exceptions.js";
22
+ import { policyFileHash, recordGateDecision } from "../policy/decisions.js";
23
+ import { evaluatePolicyGate } from "../review/policy-gate.js";
24
+
25
+ // KJC-TSK-0686 (MG-A): card-first is a gate, not a habit. Runs on --staged
26
+ // (before spending sonar/reviewer effort) AND on --check — the pre-commit
27
+ // hook already calls --check, so existing projects gain the gate with a
28
+ // simple package update, no hook regeneration.
29
+ async function enforceCardFirst({ config, projectDir }) {
30
+ const branchRes = await runCommand("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
31
+ const branch = branchRes.stdout?.trim() || "HEAD";
32
+ const card = await checkCardFirst({ config, projectDir, branch });
33
+ if (card.mode === "warn") console.log(`⚠ card-first: ${card.reason}`);
34
+ // KJC-TSK-0732: the verification LEVEL is part of the verdict — a pass on
35
+ // branch-ref alone must never read as a tracker-verified pass.
36
+ if (card.note) console.log(`⚠ card-first: ${card.note}`);
37
+ if (card.mode === "exempt" && card.reason.includes("KJ_ALLOW_NO_CARD")) console.log(`⚠ card-first exempt: ${card.reason}`);
38
+ if (!card.ok) {
39
+ console.log(`✗ card-first gate: ${card.reason}`);
40
+ process.exitCode = 1;
41
+ }
42
+ return card;
43
+ }
44
+
45
+ // Raw git always — never a wrapped/compressing runner (KJC-BUG-0115).
46
+ async function rawDiff(range, extraArgs = []) {
47
+ const args = range ? ["diff", range, ...extraArgs] : ["diff", "--cached", ...extraArgs];
48
+ const res = await runCommand("git", args);
49
+ if (res.exitCode !== 0) {
50
+ throw new Error(res.stderr?.trim() || `git ${args.join(" ")} failed`);
51
+ }
52
+ return res.stdout;
53
+ }
54
+
55
+ function printVerdict(record) {
56
+ if (record.verdict === "approved") {
57
+ const ws = record.workspace ? ` [${record.workspace}]` : "";
58
+ console.log(`✓ APPROVED by ${record.reviewer} (diff ${record.diffHash.slice(0, 12)})${ws}`);
59
+ if (record.summary) console.log(` ${record.summary}`);
60
+ return;
61
+ }
62
+ console.log(`✗ REJECTED by ${record.reviewer} — ${record.issues.length} blocking issue(s):`);
63
+ for (const issue of record.issues) {
64
+ let where = "";
65
+ if (issue.file) {
66
+ const line = issue.line ? `:${issue.line}` : "";
67
+ where = ` [${issue.file}${line}]`;
68
+ }
69
+ console.log(` - (${issue.severity || "high"})${where} ${issue.description || issue.id}`);
70
+ if (issue.suggested_fix) console.log(` fix: ${issue.suggested_fix}`);
71
+ }
72
+ console.log("Fix the issues and run `kj review --staged` again — the verdict is tied to the exact diff.");
73
+ }
74
+
75
+ /**
76
+ * `kj solomon --position "<why>"` (AB-E, KJC-TSK-0651) — the brain asks a
77
+ * third AI to arbitrate a rejected verdict it disagrees with. Exit 0 =
78
+ * approve (gate opens), 1 = reject (obey the reviewer and fix).
79
+ */
80
+ export async function solomonCommand({ config, logger = null, flags = {} }) {
81
+ const projectDir = config?.projectDir || process.cwd();
82
+ const diff = await rawDiff(flags.range);
83
+ // KJC-TSK-0734 (PL-B): un hallazgo de policy de clase seguridad no es
84
+ // arbitrable — solomon se niega antes de gastar un token. Y una policy
85
+ // inválida cierra el arbitraje en vez de abrirlo (fail closed, catch de
86
+ // codex: un `sec` vacío por error de carga NO puede significar "adelante").
87
+ const files = (await rawDiff(flags.range, ["--name-only"])).split("\n").map((f) => f.trim()).filter(Boolean);
88
+ const pol = loadPolicy({ projectDir });
89
+ if (pol.errors.length > 0) {
90
+ for (const e of pol.errors) console.log(`✗ ${e}`);
91
+ console.log("⚖ Solomon no arbitra con policy.yml inválida — corrígela primero: una regla que miente es peor que ninguna.");
92
+ process.exitCode = 1;
93
+ return { ruling: "reject", reasoning: "invalid policy.yml — arbitration refused (fail closed)" };
94
+ }
95
+ const sec = checkStagedDiff(pol.policy, { files, netLinesAdded: 0 }).filter((v) => v.class === "security" && v.enforcement === "deny");
96
+ if (sec.length > 0) {
97
+ for (const v of sec) {
98
+ const where = v.file ? ` (${v.file})` : "";
99
+ console.log(`✗ policy [${v.rule_id}] ${v.reason}${where}`);
100
+ }
101
+ console.log("⚖ Solomon no arbitra hallazgos de clase seguridad — resuélvelos; la regla no es negociable.");
102
+ process.exitCode = 1;
103
+ return { ruling: "reject", reasoning: "security-class policy denial — not arbitrable" };
104
+ }
105
+ const res = await runSolomonArbitration({ diff, position: flags.position, config, logger, projectDir });
106
+ if (res.ruling === "approve") {
107
+ console.log(`⚖ Solomon (${res.solomon}) rules for the brain — verdict recorded, the gate is open.`);
108
+ } else {
109
+ const who = res.solomon ? ` (${res.solomon})` : "";
110
+ console.log(`⚖ Solomon${who} rules for the reviewer — obey and fix:`);
111
+ }
112
+ if (res.reasoning) console.log(` ${res.reasoning}`);
113
+ process.exitCode = res.ruling === "approve" ? 0 : 1;
114
+ return res;
115
+ }
116
+
117
+ export async function reviewGateCommand({ config, logger = null, flags = {} }) {
118
+ const projectDir = config?.projectDir || process.cwd();
119
+
120
+ if (flags.installGate) {
121
+ const fs = await import("node:fs/promises");
122
+ const marker = join(projectDir, ".karajan", "review-gate");
123
+ await fs.mkdir(join(projectDir, ".karajan"), { recursive: true });
124
+ await fs.writeFile(marker, "# Cross-AI review gate enabled (ENV-C1). Commit this file so the whole team inherits the gate.\n");
125
+ console.log("✓ review gate enabled — commits now require an approved cross-AI verdict (kj review --staged)");
126
+ // KJC-TSK-0646: a `.karajan/` dir-exclude would silently keep the
127
+ // contract out of git — rewrite it so the team actually inherits it.
128
+ const gi = await ensureGateTrackable(projectDir);
129
+ if (gi.changed) {
130
+ console.log("✓ .gitignore adjusted: the gate contract (.karajan/review-gate, hooks) is now trackable — commit it");
131
+ }
132
+ const hookProbe = await runCommand("git", ["config", "core.hooksPath"]);
133
+ if (!hookProbe.stdout?.trim()) {
134
+ console.log("⚠ no core.hooksPath configured — run `kj harden` so the pre-commit hook enforces the gate");
135
+ }
136
+ return { installed: true };
137
+ }
138
+
139
+ const diff = await rawDiff(flags.range);
140
+
141
+ const card = await enforceCardFirst({ config, projectDir });
142
+ if (!card.ok) return { verdict: "rejected", reviewer: "card-first", issues: [{ severity: "high", description: card.reason }] };
143
+
144
+ // KJC-TSK-0687 (MG-B): the verifiable half of TDD — sources without a
145
+ // single test change warn (block via method_gates.tests_with_code).
146
+ const changedFiles = (await rawDiff(flags.range, ["--name-only"])).split("\n").map((f) => f.trim()).filter(Boolean);
147
+
148
+ // KJC-TSK-0688 (MG-C) + KJC-TSK-0691 (MG-E): oversized-diff policy.
149
+ // Field case: a 522-line PR sailed past the warning with a
150
+ // rationalization — where the project wants teeth, pr_size: "block"
151
+ // rejects deterministically with a NAMED escape. Default stays warn.
152
+ // PL-C (KJC-TSK-0735), fuente única de umbrales: si la policy declara el
153
+ // invariante net_lines_added, SU max es el umbral — el mismo número que
154
+ // aplican kj policy check y el tier C de CI; method_gates queda de fallback.
155
+ const pol = loadPolicy({ projectDir });
156
+ const locInv = pol.errors.length === 0
157
+ ? (pol.policy.invariants || []).find((i) => i.kind === "diff-threshold" && i.metric === "net_lines_added")
158
+ : null;
159
+ const sizeWarn = locInv?.max ?? config?.method_gates?.pr_size_warn ?? 150;
160
+ const sizeSource = locInv ? `policy [${locInv.id}]` : "method_gates";
161
+ if (sizeWarn > 0) {
162
+ const numstat = await rawDiff(flags.range, ["--numstat"]);
163
+ const added = numstat.split("\n").reduce((acc, l) => acc + (Number(l.split("\t")[0]) || 0), 0);
164
+ if (added > sizeWarn) {
165
+ const sizePolicy = config?.method_gates?.pr_size || "warn";
166
+ if (process.env.KJ_ALLOW_LARGE_PR === "1") {
167
+ console.log(`⚠ pr-size exempt: ${added} lines added — KJ_ALLOW_LARGE_PR=1 (explicit escape hatch)`);
168
+ } else if (sizePolicy === "block") {
169
+ const reason = `${added} lines added exceeds the ${sizeWarn}-line budget (${sizeSource}; method_gates.pr_size: block) — partition the work, or get your user's explicit OK and re-run with KJ_ALLOW_LARGE_PR=1`;
170
+ console.log(`✗ pr-size gate: ${reason}`);
171
+ process.exitCode = 1;
172
+ return { verdict: "rejected", reviewer: "pr-size", issues: [{ severity: "high", description: reason }] };
173
+ } else {
174
+ console.log(`⚠ pr-size: ${added} lines added (guideline ~${sizeWarn}, source: ${sizeSource}) — an oversized warning is not an opinion: partition, or ask your user (method_gates.pr_size: block to harden)`);
175
+ }
176
+ }
177
+ }
178
+ // KJC-TSK-0705 (PV-B): the outbound privacy boundary at commit time.
179
+ // Denylist data rejects (KJ_ALLOW_PII=1 is the named escape); generic PII
180
+ // warns (privacy.generic: "block" hardens). Added lines only — deletions
181
+ // don't publish. In the SEA binary the privacy module is stubbed and
182
+ // throws: the gate degrades with a note instead of crashing.
183
+ try {
184
+ const added = diff.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).map((l) => l.slice(1)).join("\n");
185
+ const findings = scanText(added, { list: loadPrivacyList(), source: "<staged diff>" });
186
+ const blocks = findings.filter((f) => f.severity === "block");
187
+ const warns = findings.filter((f) => f.severity === "warn");
188
+ for (const f of warns) console.log(`⚠ privacy: [${f.type}] added line ${f.line} → ${f.masked} — personal data? move it out before it ships`);
189
+ const hardened = config?.privacy?.generic === "block" && warns.length > 0;
190
+ if (blocks.length > 0 || hardened) {
191
+ if (process.env.KJ_ALLOW_PII === "1") {
192
+ console.log(`⚠ privacy exempt: ${blocks.length} denylist hit(s) — KJ_ALLOW_PII=1 (explicit escape hatch)`);
193
+ } else {
194
+ for (const f of blocks) console.log(`✗ privacy: [${f.type}] on added line ${f.line} → ${f.masked}`);
195
+ const reason = `${blocks.length || warns.length} personal-data finding(s) in the staged diff — this must not reach the repo (KJ_ALLOW_PII=1 to override consciously)`;
196
+ console.log(`✗ privacy gate: ${reason}`);
197
+ process.exitCode = 1;
198
+ return { verdict: "rejected", reviewer: "privacy", issues: [{ severity: "high", description: reason }] };
199
+ }
200
+ }
201
+ } catch (err) {
202
+ // Known degradation: the SEA stub throws its install-from-npm message.
203
+ // Anything else fails CLOSED — a silent skip would defeat the guarantee.
204
+ if (/standalone binary/i.test(err?.message || "")) {
205
+ console.log("⚠ privacy gate unavailable in this build — skipping");
206
+ } else if (process.env.KJ_ALLOW_PII === "1") {
207
+ console.log(`⚠ privacy gate errored (${err.message}) — KJ_ALLOW_PII=1 (explicit escape hatch)`);
208
+ } else {
209
+ const reason = `privacy gate error: ${err.message} — refusing to pass the boundary unchecked (KJ_ALLOW_PII=1 to override consciously)`;
210
+ console.log(`✗ ${reason}`);
211
+ process.exitCode = 1;
212
+ return { verdict: "rejected", reviewer: "privacy", issues: [{ severity: "high", description: reason }] };
213
+ }
214
+ }
215
+
216
+ const tests = checkTestsWithCode({ config, stagedFiles: changedFiles });
217
+ if (tests.mode === "warn") console.log(`⚠ tests-with-code: ${tests.reason}`);
218
+ if (tests.mode === "exempt") console.log(`⚠ tests-with-code exempt: ${tests.reason}`);
219
+ if (!tests.ok) {
220
+ console.log(`✗ tests-with-code gate: ${tests.reason}`);
221
+ process.exitCode = 1;
222
+ return { verdict: "rejected", reviewer: "tests-with-code", issues: [{ severity: "high", description: tests.reason }] };
223
+ }
224
+
225
+ // KJC-TSK-0734 (PL-B): la policy declarativa como gate determinista, en
226
+ // --staged Y en --check — el pre-commit hereda los dientes sin regenerar
227
+ // hooks. enforcement=warn avisa; deny cierra salvo excepción probatoria
228
+ // (KJ_ALLOW_POLICY=1 + KJ_POLICY_REASON, registrada con identidad y hash
229
+ // del diff); class=security cierra sin escape y sin arbitraje.
230
+ let net = 0;
231
+ for (const l of (await rawDiff(flags.range, ["--numstat"])).split("\n")) {
232
+ const [a, r] = l.trim().split(/\s+/);
233
+ if (a && a !== "-") net += Number(a) || 0;
234
+ if (r && r !== "-") net -= Number(r) || 0;
235
+ }
236
+ // GOV-B (KJC-TSK-0746): las permanentes concedidas (kj policy grant)
237
+ // eximen su regla mientras viven; el descarte de líneas corruptas se dice.
238
+ const std = loadStandingExceptions(projectDir);
239
+ if (std.discarded > 0) console.log(`⚠ policy: ${std.discarded} línea(s) corruptas descartadas en policy-exceptions.jsonl — revísalo`);
240
+ const gate = evaluatePolicyGate({
241
+ policy: pol.policy, errors: pol.errors, files: changedFiles, netLinesAdded: net,
242
+ diffHashValue: diffHash(diff),
243
+ recordException: (entry) => recordPolicyException({ projectDir, entry }),
244
+ standingExceptions: std.standing,
245
+ });
246
+ const at = (v) => (v.file ? ` (${v.file})` : "");
247
+ for (const w of gate.warns) console.log(`⚠ policy [${w.rule_id}] ${w.reason}${at(w)}`);
248
+ for (const e of gate.exempted) {
249
+ console.log(e.standing
250
+ ? `⚠ policy standing [${e.rule_id}] — excepción permanente viva hasta ${e.standing.expiresAt} (${e.standing.justification || "sin justificación"})`
251
+ : `⚠ policy exempt [${e.rule_id}] — excepción registrada en .karajan/policy-exceptions.jsonl (${e.justification})`);
252
+ }
253
+ // GOV-C (KJC-TSK-0747): las decisiones de chokepoint dejan rastro
254
+ // hash-encadenado — cada deny, cada excepción, y el allow del commit.
255
+ const chokepoint = flags.check ? "commit" : "review";
256
+ const seal = (decision, extra = {}) =>
257
+ recordGateDecision(projectDir, { decision, chokepoint, policy_hash: policyFileHash(projectDir), artifact_hash: diffHash(diff), ...extra });
258
+ if (gate.exempted.length > 0) seal("exempt", { rule_ids: gate.exempted.map((e) => e.rule_id) });
259
+ if (!gate.ok) {
260
+ for (const d of gate.denials) {
261
+ const sec = d.class === "security" ? " [security — sin escape ni arbitraje]" : "";
262
+ console.log(`✗ policy [${d.rule_id}]${sec} ${d.reason}${at(d)}`);
263
+ }
264
+ seal("deny", { rule_ids: gate.denials.map((d) => d.rule_id) });
265
+ const reason = gate.invalid
266
+ ? "policy.yml inválida — una regla que miente es peor que ninguna: corrígela"
267
+ : `${gate.denials.length} violación(es) de policy con enforcement=deny — el diff no entra`;
268
+ console.log(`✗ policy gate: ${reason}`);
269
+ process.exitCode = 1;
270
+ return { verdict: "rejected", reviewer: "policy", issues: gate.denials.map((d) => ({ severity: "high", file: d.file, description: `[${d.rule_id}] ${d.reason}` })) };
271
+ }
272
+
273
+ if (flags.check) {
274
+ // KJC-BUG-0132 (issue #1344): a PURE merge commit stages no content of
275
+ // its own — everything it brings reached the parent branches already
276
+ // reviewed, and there is no diff to bind a verdict to, so demanding one
277
+ // deadlocks the merge. A merge WITH conflict resolutions stages real
278
+ // content and still requires its verdict below.
279
+ if (!flags.range && !diff.trim()) {
280
+ const gitDirRaw = (await runCommand("git", ["rev-parse", "--git-dir"])).stdout?.trim();
281
+ const gitDir = gitDirRaw && !isAbsolute(gitDirRaw) ? join(projectDir, gitDirRaw) : gitDirRaw;
282
+ if (gitDir && existsSync(join(gitDir, "MERGE_HEAD"))) {
283
+ console.log("✓ pure merge commit — empty staged diff, parents already reviewed; gate passes with trail");
284
+ process.exitCode = 0;
285
+ return { ok: true, merge: true, reason: "pure merge commit (MERGE_HEAD, empty staged diff)" };
286
+ }
287
+ }
288
+ const res = await checkVerdict(projectDir, diff);
289
+ console.log(res.ok
290
+ ? `✓ verdict ok — approved by ${res.verdict.reviewer} (diff ${res.verdict.diffHash.slice(0, 12)})`
291
+ : `✗ ${res.reason}`);
292
+ // GOV-C: el allow del chokepoint de COMMIT es evidencia — se sella.
293
+ if (res.ok) seal("allow");
294
+ process.exitCode = res.ok ? 0 : 1;
295
+ return res;
296
+ }
297
+
298
+ // KJC-TSK-0676: deterministic pre-gate — sonar findings on the changed
299
+ // files come back BEFORE any AI opinion. Blocking severities reject
300
+ // without spending reviewer tokens; the rest travel with the task so
301
+ // the cross-AI reviewer weighs them. Unavailable sonar degrades loudly.
302
+ let task = flags.task;
303
+ if (flags.sonar !== false) {
304
+ const pre = await runSonarPregate({ config, stagedFiles: changedFiles, logger });
305
+ if (!pre.available) {
306
+ console.log(`⚠ sonar pre-gate skipped: ${pre.reason}`);
307
+ } else {
308
+ const found = [...pre.blocking, ...pre.advisory];
309
+ if (found.length > 0) {
310
+ console.log(`Sonar on the changed files — ${pre.blocking.length} blocking, ${pre.advisory.length} advisory (project total: ${pre.totalProject}):`);
311
+ for (const f of found) console.log(` - ${formatSonarFinding(f)}`);
312
+ }
313
+ if (pre.blocking.length > 0) {
314
+ console.log("✗ REJECTED by sonar (deterministic) — fix the blocking findings; the cross-AI reviewer was not invoked.");
315
+ process.exitCode = 1;
316
+ return {
317
+ verdict: "rejected", reviewer: "sonar",
318
+ issues: pre.blocking.map((i) => ({ severity: i.severity, file: undefined, description: formatSonarFinding(i) })),
319
+ };
320
+ }
321
+ if (pre.advisory.length > 0) {
322
+ task = `${task || "Review the following diff for correctness, security and maintainability."}\n\n`
323
+ + `Deterministic Sonar findings on these files (fold them into your review):\n`
324
+ + pre.advisory.map((f) => `- ${formatSonarFinding(f)}`).join("\n");
325
+ }
326
+ }
327
+ }
328
+
329
+ // MUT-A (KJC-TSK-0716): mutation pre-gate — opt-in (method_gates.mutation),
330
+ // SOLO en --staged (jamás en pre-commit: cuesta minutos; y jamás en --range:
331
+ // el scope es el ÍNDICE y anotaría trabajo ajeno — catch de codex). block
332
+ // cierra ANTES de gastar reviewer; warn viaja como advisory en su task.
333
+ const mut = flags.staged && !flags.range ? await runMutationPregate({ config, projectDir }) : { enabled: false, ok: true };
334
+ if (mut.enabled) {
335
+ if (mut.available === false) {
336
+ console.log(`⚠ mutation pre-gate no disponible: ${mut.reason} — degradado avisando (method_gates.mutation)`);
337
+ } else if (mut.survived.length > 0) {
338
+ console.log(`Mutantes supervivientes en el diff — score ${mut.score ?? "n/a"}%:`);
339
+ for (const m of mut.survived) console.log(` ✗ ${formatSurvivor(m)}`);
340
+ if (!mut.ok) {
341
+ console.log("✗ mutation gate: la suite verde no PRUEBA el cambio — mata los supervivientes antes del review (method_gates.mutation: block).");
342
+ process.exitCode = 1;
343
+ return { verdict: "rejected", reviewer: "mutation", issues: mut.survived.map((m) => ({ severity: "high", file: m.file, description: formatSurvivor(m) })) };
344
+ }
345
+ task = `${task || "Review the following diff for correctness, security and maintainability."}\n\n`
346
+ + `Surviving mutants on the changed lines (weak asserts — fold into your review):\n`
347
+ + mut.survived.map((m) => `- ${formatSurvivor(m)}`).join("\n");
348
+ }
349
+ }
350
+
351
+ const record = await runOneShotReview({ diff, task, config, logger, projectDir });
352
+ printVerdict(record);
353
+ process.exitCode = record.verdict === "approved" ? 0 : 1;
354
+ return record;
355
+ }
@@ -0,0 +1,89 @@
1
+ import { createAgent } from "../agents/index.js";
2
+ import { assertAgentsAvailable } from "../agents/availability.js";
3
+ import { computeBaseRef, generateDiff } from "../review/diff-generator.js";
4
+ import { buildReviewerPrompt } from "../prompts/reviewer.js";
5
+ import { resolveRole } from "../config.js";
6
+ import { resolveReviewProfile } from "../review/profiles.js";
7
+ import { withCliRunLog } from "../utils/cli-run-log.js";
8
+ import { createCliProgressReporter } from "../utils/cli-progress.js";
9
+
10
+ export async function reviewCommand({ task, config, logger, baseRef }) {
11
+ return withCliRunLog("review", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
12
+ const reviewerRole = resolveRole(config, "reviewer");
13
+ await assertAgentsAvailable([reviewerRole.provider]);
14
+ logger.info(`Reviewer (${reviewerRole.provider}) starting...`);
15
+ runLog.logText(`[reviewer] provider=${reviewerRole.provider} baseRef=${baseRef || "(auto)"}`);
16
+ const reviewer = createAgent(reviewerRole.provider, config, logger);
17
+
18
+ let diff;
19
+ if (config.ci?.enabled) {
20
+ // CI mode: read diff from open PR
21
+ const { detectRepo, detectPrNumber } = await import("../ci/repo.js");
22
+ const { getPrDiff } = await import("../ci/pr-diff.js");
23
+ const repo = await detectRepo();
24
+ const prNumber = await detectPrNumber();
25
+ if (!prNumber) {
26
+ throw new Error("CI enabled but no open PR found for current branch. Create a PR first or disable CI.");
27
+ }
28
+ logger.info(`CI: reading PR diff #${prNumber}`);
29
+ diff = await getPrDiff(prNumber);
30
+ // Store for dispatch later
31
+ config._ci_pr = { repo, prNumber };
32
+ } else {
33
+ const resolvedBase = await computeBaseRef({ baseBranch: config.base_branch, baseRef });
34
+ diff = await generateDiff({ baseRef: resolvedBase });
35
+ }
36
+
37
+ const { rules } = await resolveReviewProfile({ mode: config.review_mode, projectDir: process.cwd() });
38
+
39
+ const prompt = await buildReviewerPrompt({ task, diff, reviewRules: rules, mode: config.review_mode });
40
+ const progress = createCliProgressReporter({ role: "reviewer" });
41
+ let result;
42
+ try {
43
+ result = await reviewer.reviewTask({ prompt, onOutput: progress.onOutput, role: "reviewer" });
44
+ progress.finish(result.ok ? "done" : "failed");
45
+ } catch (err) { progress.finish("failed"); throw err; }
46
+ if (!result.ok) {
47
+ if (result.error) logger.error(result.error);
48
+ throw new Error(result.error || result.output || `Reviewer failed (exit ${result.exitCode})`);
49
+ }
50
+ console.log(result.output);
51
+ logger.info(`Reviewer completed (exit ${result.exitCode})`);
52
+
53
+ // CI: dispatch review result
54
+ if (config.ci?.enabled && config._ci_pr) {
55
+ try {
56
+ const { dispatchReview, dispatchComment } = await import("../ci/dispatch.js");
57
+ const { repo, prNumber } = config._ci_pr;
58
+ const bc = config.ci;
59
+
60
+ // Try to parse structured review from output
61
+ let review;
62
+ try {
63
+ review = JSON.parse(result.output);
64
+ } catch { /* output is not JSON */
65
+ review = { approved: true, summary: result.output };
66
+ }
67
+
68
+ const event = review.approved ? "APPROVE" : "REQUEST_CHANGES";
69
+ await dispatchReview({
70
+ repo, prNumber, event,
71
+ body: review.summary || result.output.slice(0, 500),
72
+ agent: "Reviewer", ciConfig: bc
73
+ });
74
+
75
+ await dispatchComment({
76
+ repo, prNumber, agent: "Reviewer",
77
+ body: `Standalone review: ${event}\n\n${review.summary || result.output.slice(0, 1000)}`,
78
+ ciConfig: bc
79
+ });
80
+
81
+ logger.info(`CI: dispatched review for PR #${prNumber}`);
82
+ } catch (err) {
83
+ logger.warn(`CI dispatch failed (non-blocking): ${err.message}`);
84
+ }
85
+ }
86
+ runLog.logText(`[reviewer] finished (exit=${result.exitCode})`);
87
+ return { ok: true };
88
+ });
89
+ }
@@ -0,0 +1,116 @@
1
+ import fs from "node:fs/promises";
2
+ import { resolveRoleMdPath, loadFirstExisting } from "../roles/base-role.js";
3
+ import { resolveRole } from "../config.js";
4
+ import { exists } from "../utils/fs.js";
5
+
6
+ const PIPELINE_ROLES = [
7
+ { name: "triage", description: "Classifies task complexity and activates pipeline roles" },
8
+ { name: "researcher", description: "Investigates codebase before planning" },
9
+ { name: "planner", description: "Generates implementation plans" },
10
+ { name: "coder", description: "Writes code and tests (TDD)" },
11
+ { name: "refactorer", description: "Improves code clarity without changing behavior" },
12
+ { name: "sonar", description: "SonarQube static analysis" },
13
+ { name: "reviewer", description: "Code review with configurable strictness" },
14
+ { name: "tester", description: "Test quality gate and coverage checks" },
15
+ { name: "security", description: "OWASP security audit" },
16
+ { name: "solomon", description: "Conflict resolver between agents" },
17
+ { name: "commiter", description: "Git commit, push, and PR automation" }
18
+ ];
19
+
20
+ const REVIEW_VARIANTS = ["reviewer-strict", "reviewer-relaxed", "reviewer-paranoid"];
21
+
22
+ function isRoleEnabled(config, roleName) {
23
+ if (roleName === "coder" || roleName === "commiter" || roleName === "sonar") {
24
+ if (roleName === "sonar") return Boolean(config?.sonarqube?.enabled);
25
+ return true;
26
+ }
27
+ if (roleName === "reviewer") return config?.pipeline?.reviewer?.enabled !== false;
28
+ return Boolean(config?.pipeline?.[roleName]?.enabled);
29
+ }
30
+
31
+ export function listRoles(config) {
32
+ return PIPELINE_ROLES.map((role) => {
33
+ const resolved = resolveRole(config, role.name);
34
+ return {
35
+ name: role.name,
36
+ description: role.description,
37
+ provider: resolved.provider || "-",
38
+ model: resolved.model || "-",
39
+ enabled: isRoleEnabled(config, role.name)
40
+ };
41
+ });
42
+ }
43
+
44
+ export async function showRole(roleName, config) {
45
+ const projectDir = config?.projectDir || process.cwd();
46
+ const candidates = resolveRoleMdPath(roleName, projectDir);
47
+
48
+ const customPath = candidates[0];
49
+ const hasCustom = await exists(customPath);
50
+
51
+ const content = await loadFirstExisting(candidates);
52
+ if (!content) {
53
+ return { found: false, roleName, content: null, source: null, customPath: null };
54
+ }
55
+
56
+ let source = "built-in";
57
+ if (hasCustom) {
58
+ source = "custom";
59
+ } else if (candidates.length > 2) {
60
+ try {
61
+ await fs.readFile(candidates[1], "utf8");
62
+ source = "user";
63
+ } catch { /* user role file not found */
64
+ source = "built-in";
65
+ }
66
+ }
67
+
68
+ return {
69
+ found: true,
70
+ roleName,
71
+ content,
72
+ source,
73
+ customPath: hasCustom ? customPath : null
74
+ };
75
+ }
76
+
77
+ function printRoleList(roles) {
78
+ const nameWidth = Math.max(...roles.map((r) => r.name.length), 4);
79
+ const provWidth = Math.max(...roles.map((r) => r.provider.length), 8);
80
+
81
+ const header = `${"Role".padEnd(nameWidth)} ${"Provider".padEnd(provWidth)} Enabled Description`;
82
+ console.log(header);
83
+ console.log("-".repeat(header.length));
84
+
85
+ for (const role of roles) {
86
+ const enabled = role.enabled ? "yes" : "no ";
87
+ console.log(
88
+ `${role.name.padEnd(nameWidth)} ${role.provider.padEnd(provWidth)} ${enabled.padEnd(7)} ${role.description}`
89
+ );
90
+ }
91
+ }
92
+
93
+ export async function rolesCommand({ config, subcommand, roleName }) {
94
+ if (subcommand === "show" && roleName) {
95
+ const result = await showRole(roleName, config);
96
+ if (!result.found) {
97
+ console.log(`Role "${roleName}" not found.`);
98
+ console.log(`Available roles: ${PIPELINE_ROLES.map((r) => r.name).join(", ")}`);
99
+ console.log(`Review variants: ${REVIEW_VARIANTS.join(", ")}`);
100
+ return result;
101
+ }
102
+ if (result.source === "custom") {
103
+ console.log(`[custom override: ${result.customPath}]\n`);
104
+ }
105
+ console.log(result.content);
106
+ return result;
107
+ }
108
+
109
+ const roles = listRoles(config);
110
+ printRoleList(roles);
111
+ console.log(`\nReview variants: ${REVIEW_VARIANTS.join(", ")}`);
112
+ console.log('\nUse "kj roles show <role>" to view template instructions.');
113
+ return roles;
114
+ }
115
+
116
+ export { PIPELINE_ROLES, REVIEW_VARIANTS };