@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,85 @@
1
+ /**
2
+ * Policy resolution: map task type + config toggles to concrete pipeline
3
+ * flags (TDD, Sonar, reviewer retries, tests required, coder required, ...).
4
+ *
5
+ * @typedef {import("../types/policy.js").ResolvedPolicies} ResolvedPolicies
6
+ * @typedef {import("../types/config.js").KarajanConfig} KarajanConfig
7
+ */
8
+
9
+ // KJC-TSK-0400: spike y research añadidos como tipos no-code. Comparten
10
+ // las defaults restrictivas (tdd/sonar/testsRequired off) porque NO
11
+ // producen código que medir: un SPIKE es un .md de decisión, un research
12
+ // es una nota de investigación. Forzar Sonar les hace fallar siempre.
13
+ export const VALID_TASK_TYPES = new Set([
14
+ "sw", "infra", "doc", "add-tests", "refactor",
15
+ "audit", "analysis", "no-code", "spike", "research",
16
+ ]);
17
+
18
+ export const DEFAULT_POLICIES = {
19
+ sw: { tdd: true, sonar: true, reviewer: true, testsRequired: true },
20
+ infra: { tdd: false, sonar: false, reviewer: false, testsRequired: false },
21
+ doc: { tdd: false, sonar: false, reviewer: true, testsRequired: false },
22
+ "add-tests": { tdd: false, sonar: true, reviewer: true, testsRequired: true },
23
+ refactor: { tdd: true, sonar: true, reviewer: true, testsRequired: false },
24
+ audit: { tdd: false, sonar: false, reviewer: false, testsRequired: false, coderRequired: false },
25
+ analysis: { tdd: false, sonar: false, reviewer: false, testsRequired: false, coderRequired: false },
26
+ "no-code": { tdd: false, sonar: false, reviewer: true, testsRequired: false, coderRequired: true },
27
+ spike: { tdd: false, sonar: false, reviewer: true, testsRequired: false, coderRequired: true },
28
+ research: { tdd: false, sonar: false, reviewer: true, testsRequired: false, coderRequired: true },
29
+ };
30
+
31
+ // KJC-TSK-0400: si el title arranca con [SPIKE] / [DOC] / [RESEARCH] /
32
+ // [NOCODE], inferimos el task_type del prefijo. Pensado para casos donde
33
+ // el planner emite task_type='sw' por defecto pero el title declara
34
+ // claramente que la HU no produce código. Devuelve null si no hay match.
35
+ const TITLE_PREFIX_TO_TYPE = {
36
+ SPIKE: "spike",
37
+ RESEARCH: "research",
38
+ DOC: "doc",
39
+ NOCODE: "no-code",
40
+ "NO-CODE": "no-code",
41
+ };
42
+
43
+ export function inferTaskTypeFromTitle(title) {
44
+ if (typeof title !== "string") return null;
45
+ const m = title.match(/^\s*\[([A-Z][A-Z-]+)\]/);
46
+ if (!m) return null;
47
+ return TITLE_PREFIX_TO_TYPE[m[1].toUpperCase()] || null;
48
+ }
49
+
50
+ // KJC-TSK-0400: dado un HU del plan, devuelve el task_type efectivo:
51
+ // explícito en story.task_type si es válido, si no inferido del title.
52
+ // Fallback 'sw' (conservador, mantiene gates activos cuando hay duda).
53
+ export function effectiveTaskType(story) {
54
+ if (!story || typeof story !== "object") return "sw";
55
+ if (VALID_TASK_TYPES.has(story.task_type)) return story.task_type;
56
+ return inferTaskTypeFromTitle(story.title) || "sw";
57
+ }
58
+
59
+ const FALLBACK_TYPE = "sw";
60
+
61
+ /**
62
+ * Resolve pipeline policies for a given taskType.
63
+ * Unknown / null / undefined taskType falls back to "sw" (conservative).
64
+ * configOverrides optionally merges over defaults per taskType.
65
+ */
66
+ export function resolvePolicies(taskType, configOverrides) {
67
+ const resolvedType = VALID_TASK_TYPES.has(taskType) ? taskType : FALLBACK_TYPE;
68
+ const base = { ...DEFAULT_POLICIES[resolvedType] };
69
+ const overrides = configOverrides?.[resolvedType];
70
+ if (overrides && typeof overrides === "object") {
71
+ Object.assign(base, overrides);
72
+ }
73
+ return base;
74
+ }
75
+
76
+ /**
77
+ * Resolve policies for a taskType and return a flat object with the resolved
78
+ * taskType plus all policy flags. This is the main entry point for the
79
+ * orchestrator to determine which pipeline stages to enable/disable.
80
+ */
81
+ export function applyPolicies({ taskType, policies } = {}) {
82
+ const resolvedType = VALID_TASK_TYPES.has(taskType) ? taskType : FALLBACK_TYPE;
83
+ const resolved = resolvePolicies(taskType, policies);
84
+ return { taskType: resolvedType, ...resolved };
85
+ }
@@ -0,0 +1,71 @@
1
+ // KJC-TSK-0583 — Inbound secret redaction for the review boundary.
2
+ //
3
+ // The outbound boundary (output-guard.js) already blocks committing secrets.
4
+ // This closes the *inbound* gap: a hardcoded credential in the working tree
5
+ // would otherwise reach the (possibly cloud) reviewer model verbatim inside the
6
+ // diff. `redactSecrets` masks each credential before the model sees it, reusing
7
+ // the SAME catalog as output-guard — no new patterns, no AI cost, deterministic.
8
+ import { CREDENTIAL_PATTERNS } from "./output-guard.js";
9
+
10
+ // Families whose match begins with a fixed, identifiable literal prefix. The
11
+ // placeholder keeps that prefix so the reviewer can still tell WHAT kind of
12
+ // secret was redacted (and say "move it to .env") without seeing the value.
13
+ const STABLE_PREFIX_BY_ID = {
14
+ "aws-key": "AKIA",
15
+ "npm-token": "npm_",
16
+ "openai-key": "sk-",
17
+ "anthropic-key": "sk-ant-",
18
+ "stripe-key": "sk_live_",
19
+ "stripe-test": "sk_test_",
20
+ "google-api-key": "AIza",
21
+ };
22
+
23
+ // Families with a dynamic prefix: keep the real match up to and including the
24
+ // first separator (ghp_/gho_/…, xoxb-/xoxp-/…), then mask the rest.
25
+ const DYNAMIC_SEPARATOR_BY_ID = {
26
+ "github-token": "_",
27
+ "slack-token": "-",
28
+ };
29
+
30
+ const GENERIC = "***REDACTED-SECRET***";
31
+ const SUFFIX = "***REDACTED***";
32
+
33
+ function placeholderFor(id, match) {
34
+ const stable = STABLE_PREFIX_BY_ID[id];
35
+ if (stable) return `${stable}${SUFFIX}`;
36
+ const sep = DYNAMIC_SEPARATOR_BY_ID[id];
37
+ if (sep) {
38
+ const idx = match.indexOf(sep);
39
+ return idx >= 0 ? `${match.slice(0, idx + 1)}${SUFFIX}` : GENERIC;
40
+ }
41
+ return GENERIC;
42
+ }
43
+
44
+ // Apply the most specific prefixes first so `sk-ant-` wins over `sk-`; families
45
+ // without a stable prefix keep their catalog order (stable sort) and run last.
46
+ function orderedPatterns() {
47
+ return [...CREDENTIAL_PATTERNS].sort(
48
+ (a, b) => (STABLE_PREFIX_BY_ID[b.id]?.length ?? 0) - (STABLE_PREFIX_BY_ID[a.id]?.length ?? 0)
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Mask every credential in `text`, preserving line structure. Pure and
54
+ * deterministic: clean text is returned untouched (no-op, no cost).
55
+ * @param {string} text
56
+ * @returns {string}
57
+ */
58
+ export function redactSecrets(text) {
59
+ if (typeof text !== "string" || text.length === 0) return text;
60
+ let out = text;
61
+ for (const { id, pattern } of orderedPatterns()) {
62
+ const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
63
+ out = out.replace(new RegExp(pattern.source, flags), (match) =>
64
+ // Idempotent: a broad pattern (generic-secret, hardcoded-key-assignment)
65
+ // can span an already-masked value; leave the more specific placeholder
66
+ // in place instead of clobbering it with the generic one.
67
+ match.includes("REDACTED") ? match : placeholderFor(id, match)
68
+ );
69
+ }
70
+ return out;
71
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * advisory — per-artifact comparison engine for `kj harden` advisory mode
3
+ * (KJC-TSK-0565, épica KJC-PCS-0060). For each artifact harden manages it
4
+ * classifies the repo state: MISSING (no file nor equivalent), KJ_MANAGED (a
5
+ * kj:managed block, up to date or outdated vN) or USER_OWNED (the user's own,
6
+ * possibly in another format). Read-only; never writes or prints. The result
7
+ * feeds `--report` (Advisory B) / `--interactive` (Advisory C). Per-type
8
+ * improvement heuristics for USER_OWNED land in the follow-up slice (PR2).
9
+ */
10
+
11
+ import { existsSync, readFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+
14
+ import { findManagedBlock } from "../utils/managed-markers.js";
15
+ import { findAlternative } from "./alternatives.js";
16
+ import { CONFIGS_BY_LANGUAGE, UNIVERSAL_CONFIGS } from "./config-templates.js";
17
+ import { detectStackRoots } from "./stack-roots.js";
18
+
19
+ const BLOCK_VERSION = 1; // current version every markered config ships at
20
+
21
+ // Other filenames/formats for the same artifact, so kj never reports a false
22
+ // "missing". `pkgKey` is a package.json field that can hold inline config.
23
+ const EQUIVALENTS = {
24
+ commitlint: { files: [".commitlintrc", ".commitlintrc.js", ".commitlintrc.cjs", ".commitlintrc.mjs", ".commitlintrc.json", ".commitlintrc.yaml", ".commitlintrc.yml", "commitlint.config.mjs", "commitlint.config.cjs", "commitlint.config.ts"], pkgKey: "commitlint" },
25
+ eslint: { files: ["eslint.config.mjs", "eslint.config.cjs", "eslint.config.ts", "eslint.config.mts", "eslint.config.cts", ".eslintrc.js", ".eslintrc.cjs", ".eslintrc.json", ".eslintrc.yaml", ".eslintrc.yml"], pkgKey: "eslintConfig" },
26
+ prettier: { files: [".prettierrc", ".prettierrc.js", ".prettierrc.yaml", ".prettierrc.yml", ".prettierrc.json5", ".prettierrc.toml", "prettier.config.js", "prettier.config.mjs", "prettier.config.cjs", "prettier.config.ts"], pkgKey: "prettier" },
27
+ };
28
+
29
+ /**
30
+ * KJC-BUG-0119 — same-tool filename variant for `artifactId` in any of
31
+ * `searchDirs` (file or inline package.json config); null when none exists.
32
+ * Seeding kj's default filename NEXT TO a variant eclipses the user's real
33
+ * config (eslint resolves eslint.config.js before .mjs), so config-engine
34
+ * must consult this before treating an artifact as absent.
35
+ */
36
+ export function findEquivalentIn(searchDirs, artifactId) {
37
+ const spec = EQUIVALENTS[artifactId];
38
+ if (!spec) return null;
39
+ for (const dir of searchDirs) {
40
+ for (const rel of spec.files) {
41
+ if (existsSync(join(dir, rel))) return rel;
42
+ }
43
+ if (spec.pkgKey && existsSync(join(dir, "package.json"))) {
44
+ try {
45
+ if (JSON.parse(readFileSync(join(dir, "package.json"), "utf8"))[spec.pkgKey] != null) {
46
+ return `package.json#${spec.pkgKey}`;
47
+ }
48
+ } catch {
49
+ /* unreadable package.json → not a match */
50
+ }
51
+ }
52
+ }
53
+ return null;
54
+ }
55
+
56
+ // Concrete gains kj's standard would add to a USER_OWNED config, detected by
57
+ // substring probes (no AST parse): each probe absent from the user's content
58
+ // becomes one improvement. Heuristic by design — conservative, never a false
59
+ // "you're missing this" when the signal is clearly present.
60
+ const IMPROVEMENTS = {
61
+ eslint: [
62
+ { needle: "no-var", id: "no-var", detail: "bans var (ES2025: const/let)" },
63
+ { needle: "no-restricted-globals", id: "no-legacy-globals", detail: "bans alert/confirm/prompt, escape/unescape" },
64
+ { needle: "no-restricted-properties", id: "no-legacy-apis", detail: "bans document.write, substr" },
65
+ ],
66
+ commitlint: [
67
+ { needle: "header-max-length", id: "header-max-length", detail: "caps the commit header at 100 chars" },
68
+ { needle: "subject-case", id: "subject-case", detail: "enforces a lowercase subject" },
69
+ ],
70
+ prettier: [{ needle: "printWidth", id: "print-width", detail: "pins printWidth (consistent wrapping)" }],
71
+ editorconfig: [
72
+ { needle: "insert_final_newline", id: "final-newline", detail: "enforces a final newline" },
73
+ { needle: "charset", id: "charset", detail: "pins charset = utf-8" },
74
+ { needle: "trim_trailing_whitespace", id: "trim-trailing", detail: "trims trailing whitespace" },
75
+ ],
76
+ };
77
+
78
+ /** Improvements kj would add to the user's own config for `artifactId`. */
79
+ function improvementsFor(artifactId, content) {
80
+ return (IMPROVEMENTS[artifactId] ?? [])
81
+ .filter((probe) => !content.includes(probe.needle))
82
+ .map(({ id, detail }) => ({ id, detail }));
83
+ }
84
+
85
+ /** Build an artifact descriptor from a config-template entry. */
86
+ function toArtifact(cfg) {
87
+ const id = cfg.blockId ?? "prettier"; // the only marker-less JSON config is prettier
88
+ return {
89
+ id,
90
+ file: cfg.file,
91
+ blockId: cfg.blockId ?? null,
92
+ currentVersion: cfg.blockId ? BLOCK_VERSION : null,
93
+ equivalents: EQUIVALENTS[id]?.files ?? [],
94
+ pkgKey: EQUIVALENTS[id]?.pkgKey ?? null,
95
+ };
96
+ }
97
+
98
+ /** Locate the user's file for an artifact under `searchDir`; null if absent. */
99
+ function findArtifactFile(searchDir, artifact) {
100
+ if (existsSync(join(searchDir, artifact.file))) return artifact.file;
101
+ return findEquivalentIn([searchDir], artifact.id);
102
+ }
103
+
104
+ /** Read the artifact body, or the JSON of an inline package.json key. */
105
+ function readArtifactContent(searchDir, foundAt) {
106
+ const [file, key] = foundAt.split("#");
107
+ const raw = readFileSync(join(searchDir, file), "utf8");
108
+ if (!key) return raw;
109
+ try {
110
+ return JSON.stringify(JSON.parse(raw)[key] ?? null);
111
+ } catch {
112
+ return raw;
113
+ }
114
+ }
115
+
116
+ /** Map a classified state to a recommendation + human rationale. */
117
+ function recommend(state) {
118
+ if (state.status === "SATISFIED_BY_ALTERNATIVE") {
119
+ return {
120
+ recommendation: "keep",
121
+ rationale: `covered by ${state.foundAt} (${state.alternativeTool}) — kj won't add a second linter/formatter`,
122
+ mergeable: false,
123
+ };
124
+ }
125
+ if (state.status === "MISSING") return { recommendation: "install", rationale: `kj would add ${state.file}`, mergeable: true };
126
+ if (state.status === "USER_OWNED") {
127
+ const n = state.improvements?.length ?? 0;
128
+ return n === 0
129
+ ? { recommendation: "keep", rationale: "your own config — already covers the kj standard", mergeable: false }
130
+ : { recommendation: "review", rationale: `your own config — kj could add ${n} improvement(s)`, mergeable: true };
131
+ }
132
+ if (state.upToDate) return { recommendation: "keep", rationale: `managed by kj (v${state.managedVersion}), up to date`, mergeable: false };
133
+ return { recommendation: "update", rationale: `kj v${state.currentVersion} available (you have v${state.managedVersion})`, mergeable: true };
134
+ }
135
+
136
+ /**
137
+ * Classify a single artifact found (or not) under `searchDir`. `altDirs`
138
+ * widens the cross-tool alternative lookup (a root-level biome.json covers a
139
+ * monorepo's language roots); the user's OWN config of the same tool still
140
+ * wins over an alternative.
141
+ */
142
+ export function classifyArtifact(searchDir, artifact, altDirs = [searchDir]) {
143
+ const foundAt = findArtifactFile(searchDir, artifact);
144
+ const base = { foundAt, managedVersion: null, upToDate: null, improvements: [] };
145
+ let state = { ...base, status: "MISSING" };
146
+ if (!foundAt) {
147
+ const alt = findAlternative(altDirs, artifact.id);
148
+ if (alt) state = { ...base, status: "SATISFIED_BY_ALTERNATIVE", foundAt: alt.foundAt, alternativeTool: alt.tool };
149
+ }
150
+ if (foundAt) {
151
+ const content = readArtifactContent(searchDir, foundAt);
152
+ if (artifact.blockId && content.includes(`kj:managed:${artifact.blockId}`)) {
153
+ const { version } = findManagedBlock(content, artifact.blockId);
154
+ state = { ...base, status: "KJ_MANAGED", managedVersion: version, upToDate: version != null && version >= artifact.currentVersion };
155
+ } else {
156
+ state = { ...base, status: "USER_OWNED", improvements: improvementsFor(artifact.id, content) };
157
+ }
158
+ }
159
+ const head = { id: artifact.id, file: artifact.file, currentVersion: artifact.currentVersion };
160
+ return { ...head, ...state, ...recommend({ ...state, ...head }) };
161
+ }
162
+
163
+ /**
164
+ * Compare the whole repo against the kj standard, artifact by artifact:
165
+ * universal configs at the root, then each language's lint config inside its
166
+ * own root (monorepo-aware, mirroring installConfigsForRoots). `minimal`
167
+ * profile ships no config, so there is nothing to compare.
168
+ */
169
+ export function compareHarden({ projectDir = process.cwd(), profile = "standard", only = [], exclude = [] } = {}) {
170
+ if (profile === "minimal") return { artifacts: [] };
171
+ const artifacts = UNIVERSAL_CONFIGS.map((cfg) => ({ dir: ".", ...classifyArtifact(projectDir, toArtifact(cfg)) }));
172
+ for (const { dir, language } of detectStackRoots(projectDir, { only, exclude })) {
173
+ const searchDir = dir === "." ? projectDir : join(projectDir, dir);
174
+ for (const cfg of CONFIGS_BY_LANGUAGE[language] ?? []) {
175
+ const res = classifyArtifact(searchDir, toArtifact(cfg), [searchDir, projectDir]);
176
+ // Alternative matches may live at the repo root — keep their path unprefixed.
177
+ const prefix = res.foundAt && dir !== "." && res.status !== "SATISFIED_BY_ALTERNATIVE";
178
+ artifacts.push({ dir, ...res, foundAt: prefix ? join(dir, res.foundAt) : res.foundAt });
179
+ }
180
+ }
181
+ return { artifacts };
182
+ }
183
+
184
+ const MARK = { install: "+", update: "↑", review: "~", keep: "✓" };
185
+
186
+ /**
187
+ * Render a compareHarden() result as human-readable lines (Advisory B). Pure:
188
+ * returns an array of strings, prints nothing. One line per artifact, indented
189
+ * bullets for each improvement, then a one-line tally.
190
+ */
191
+ export function formatAdvisoryReport({ artifacts = [] } = {}) {
192
+ if (artifacts.length === 0) return ["kj harden — nothing to compare (minimal profile or empty repo)."];
193
+ const lines = ["kj harden — advisory report (read-only)", ""];
194
+ const tally = { install: 0, update: 0, review: 0, keep: 0 };
195
+ for (const a of artifacts) {
196
+ tally[a.recommendation] = (tally[a.recommendation] ?? 0) + 1;
197
+ const path = a.foundAt ?? (a.dir === "." ? a.file : join(a.dir, a.file));
198
+ lines.push(` ${MARK[a.recommendation] ?? "·"} ${a.recommendation.padEnd(7)} ${path} — ${a.rationale}`);
199
+ for (const imp of a.improvements ?? []) lines.push(` • ${imp.detail}`);
200
+ }
201
+ lines.push("", `${artifacts.length} artifact(s) · ${tally.install} missing · ${tally.review} to review · ${tally.update} outdated · ${tally.keep} ok`);
202
+ return lines;
203
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Cross-tool alternatives for `kj harden` (KJC-TSK-0614).
3
+ *
4
+ * A user's Biome config replaces BOTH eslint and prettier — seeding kj's
5
+ * configs next to biome.json installs a second, conflicting linter/formatter
6
+ * that fights the user's own hooks. The advisory's EQUIVALENTS map only
7
+ * covers same-tool filename variants; this map covers whole-tool
8
+ * substitutions, and the three surfaces (advisory, config-engine, check)
9
+ * consult it before treating an artifact as missing.
10
+ *
11
+ * Detection is config-file based on purpose: deterministic, and a false
12
+ * negative just means kj proposes its default (the pre-0614 behaviour),
13
+ * never that it overwrites the user's tool.
14
+ */
15
+
16
+ import { existsSync } from "node:fs";
17
+ import { join } from "node:path";
18
+
19
+ const ALTERNATIVES = {
20
+ eslint: [{ tool: "biome", files: ["biome.json", "biome.jsonc"] }],
21
+ prettier: [{ tool: "biome", files: ["biome.json", "biome.jsonc"] }],
22
+ };
23
+
24
+ /**
25
+ * First alternative tool that satisfies `artifactId`, searching each dir in
26
+ * order (a language root first, then the repo root — a root-level biome.json
27
+ * covers the whole monorepo). Null when none applies.
28
+ *
29
+ * @param {string[]} searchDirs
30
+ * @param {string} artifactId - "eslint" | "prettier" | ...
31
+ * @returns {{ tool: string, foundAt: string }|null}
32
+ */
33
+ export function findAlternative(searchDirs, artifactId) {
34
+ for (const alt of ALTERNATIVES[artifactId] ?? []) {
35
+ for (const dir of searchDirs) {
36
+ for (const rel of alt.files) {
37
+ if (existsSync(join(dir, rel))) return { tool: alt.tool, foundAt: rel };
38
+ }
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+
44
+ /**
45
+ * Artifact id for a config-template entry — same convention as the
46
+ * advisory's toArtifact: prettier is the only marker-less JSON config.
47
+ */
48
+ export function artifactIdForConfig(cfg) {
49
+ return cfg.blockId ?? "prettier";
50
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * checkHarden — verify the quality harness installed by `kj harden`
3
+ * (KJC-TSK-0558). Detects drift: a deleted hook, a hook that lost its kj
4
+ * marker or its executable bit, or a core.hooksPath that no longer points at
5
+ * `.karajan/hooks`. Returns a per-check report; the CLI maps it to exit 0/≠0.
6
+ *
7
+ * Config, workflow and language-advisory checks land in the next slice.
8
+ */
9
+
10
+ import { existsSync, readFileSync, statSync } from "node:fs";
11
+ import { join } from "node:path";
12
+
13
+ import { runCommand } from "../utils/process.js";
14
+ import { artifactIdForConfig, findAlternative } from "./alternatives.js";
15
+ import { CONFIGS_BY_LANGUAGE, UNIVERSAL_CONFIGS } from "./config-templates.js";
16
+ import { PROFILE_HOOKS } from "./hook-templates.js";
17
+ import { detectStackRoots } from "./stack-roots.js";
18
+ import { qualityWorkflowFor, WORKFLOWS } from "./workflow-templates.js";
19
+
20
+ const HOOKS_DIR = join(".karajan", "hooks");
21
+ const WORKFLOWS_DIR = join(".github", "workflows");
22
+
23
+ /** A config/workflow file just needs to be present (it may be user-owned). */
24
+ function presence(projectDir, rel, id, miss) {
25
+ const ok = existsSync(join(projectDir, rel));
26
+ return { id, ok, detail: ok ? "ok" : miss };
27
+ }
28
+
29
+ export async function checkHarden({ projectDir = process.cwd(), profile = "standard" } = {}) {
30
+ const checks = [];
31
+
32
+ const res = await runCommand("git", ["config", "--local", "core.hooksPath"], { cwd: projectDir });
33
+ const hooksPath = (res.stdout ?? "").trim();
34
+ checks.push({
35
+ id: "core.hooksPath",
36
+ ok: hooksPath === HOOKS_DIR,
37
+ detail: hooksPath === HOOKS_DIR ? "ok" : hooksPath || "(unset) — run kj harden",
38
+ });
39
+
40
+ for (const hook of PROFILE_HOOKS[profile] ?? []) {
41
+ const target = join(projectDir, HOOKS_DIR, hook);
42
+ const exists = existsSync(target);
43
+ const executable = exists && (statSync(target).mode & 0o100) !== 0;
44
+ const marked = exists && readFileSync(target, "utf8").includes(`kj:managed:hook:${hook}`);
45
+ const detail = !exists ? "missing" : !executable ? "not executable" : !marked ? "no kj marker" : "ok";
46
+ checks.push({ id: `hook:${hook}`, ok: exists && executable && marked, detail });
47
+ }
48
+
49
+ // Config + workflows ship with standard+ — and surface the greenfield gap:
50
+ // a language now present but never hardened ⇒ its config shows as missing.
51
+ if (profile !== "minimal") {
52
+ const roots = detectStackRoots(projectDir);
53
+ for (const cfg of UNIVERSAL_CONFIGS) {
54
+ checks.push(presence(projectDir, cfg.file, `config:${cfg.file}`, "missing — run kj harden"));
55
+ }
56
+ for (const { dir, language } of roots) {
57
+ const rootDir = dir === "." ? projectDir : join(projectDir, dir);
58
+ for (const cfg of CONFIGS_BY_LANGUAGE[language] ?? []) {
59
+ const rel = dir === "." ? cfg.file : join(dir, cfg.file);
60
+ // A config covered by the user's own tool (biome.json ⇒ eslint+prettier)
61
+ // is not drift — kj must not demand a second linter/formatter (KJC-TSK-0614).
62
+ if (!existsSync(join(projectDir, rel))) {
63
+ const alt = findAlternative([rootDir, projectDir], artifactIdForConfig(cfg));
64
+ if (alt) {
65
+ checks.push({ id: `config:${rel}`, ok: true, detail: `covered by ${alt.foundAt} (${alt.tool})` });
66
+ continue;
67
+ }
68
+ }
69
+ checks.push(presence(projectDir, rel, `config:${rel}`, `missing for ${language} — run kj harden`));
70
+ }
71
+ }
72
+ const expectedWf = [...WORKFLOWS];
73
+ const quality = qualityWorkflowFor(roots[0]?.language ?? null);
74
+ if (quality) expectedWf.push(quality);
75
+ for (const wf of expectedWf) {
76
+ checks.push(presence(projectDir, join(WORKFLOWS_DIR, wf.file), `workflow:${wf.file}`, "missing — run kj harden"));
77
+ }
78
+ }
79
+
80
+ return { ok: checks.every((c) => c.ok), checks };
81
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * config-engine — seeds/refreshes lint/format/commit config for `kj harden`
3
+ * (KJC-TSK-0556). Seed-if-absent: a config the user already authored (no
4
+ * kj:managed marker) is left untouched and reported as "skipped". Markered
5
+ * files refresh in place; JSON files (no comment syntax) are seed-only.
6
+ */
7
+
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
+ import { dirname, join } from "node:path";
10
+
11
+ import { upsertManagedBlock } from "../utils/managed-markers.js";
12
+ import { findEquivalentIn } from "./advisory.js";
13
+ import { artifactIdForConfig, findAlternative } from "./alternatives.js";
14
+ import { CONFIGS_BY_LANGUAGE, UNIVERSAL_CONFIGS } from "./config-templates.js";
15
+
16
+ const BLOCK_VERSION = 1;
17
+
18
+ // KJC-BUG-0137 (issue #1357): is the tool a config demands actually installed?
19
+ // Declared dependency or a resolvable bin both count; an unreadable
20
+ // package.json is no evidence of the tool.
21
+ function hasTool(dirs, tool) {
22
+ for (const d of dirs) {
23
+ if (existsSync(join(d, "node_modules", ".bin", tool))) return true;
24
+ const pkgPath = join(d, "package.json");
25
+ if (!existsSync(pkgPath)) continue;
26
+ try {
27
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
28
+ if (pkg.devDependencies?.[tool] || pkg.dependencies?.[tool]) return true;
29
+ } catch {
30
+ /* unreadable ⇒ keep looking */
31
+ }
32
+ }
33
+ return false;
34
+ }
35
+
36
+ function writeFile(target, content) {
37
+ mkdirSync(dirname(target), { recursive: true });
38
+ writeFileSync(target, content);
39
+ }
40
+
41
+ /**
42
+ * Seed a list of config entries into `targetDir`, labelling each by `prefix`.
43
+ * `altDirs` widens the cross-tool alternative lookup (KJC-TSK-0614): a config
44
+ * covered by the user's own tool (biome.json ⇒ eslint+prettier) is never
45
+ * seeded — kj must not install a second, conflicting linter/formatter.
46
+ */
47
+ function seedInto(targetDir, configs, dryRun, prefix, results, altDirs = [targetDir]) {
48
+ for (const cfg of configs) {
49
+ const target = join(targetDir, cfg.file);
50
+ const exists = existsSync(target);
51
+ const file = prefix === "." ? cfg.file : join(prefix, cfg.file);
52
+
53
+ if (!exists) {
54
+ const alt = findAlternative(altDirs, artifactIdForConfig(cfg));
55
+ if (alt) {
56
+ results.push({ file, action: "covered", by: alt.foundAt });
57
+ continue;
58
+ }
59
+ // KJC-BUG-0119: a same-tool variant (eslint.config.mjs, .eslintrc.json,
60
+ // package.json#eslintConfig…) means the config already EXISTS — seeding
61
+ // kj's default filename next to it would silently ECLIPSE the user's
62
+ // real config (eslint resolves eslint.config.js before .mjs).
63
+ const variant = findEquivalentIn(altDirs, artifactIdForConfig(cfg));
64
+ if (variant) {
65
+ results.push({ file, action: "covered", by: variant });
66
+ continue;
67
+ }
68
+ // KJC-BUG-0137: no tool ⇒ no config — report the actionable gap instead.
69
+ if (cfg.requires && !hasTool(altDirs, cfg.requires)) {
70
+ results.push({
71
+ file,
72
+ action: "omitted",
73
+ note: `${cfg.requires} is not installed — npm i -D ${cfg.requires}, then re-run kj harden`,
74
+ });
75
+ continue;
76
+ }
77
+ }
78
+
79
+ if (cfg.json) {
80
+ const action = exists ? "skipped" : "inserted";
81
+ if (!dryRun && !exists) writeFile(target, `${cfg.body}\n`);
82
+ results.push({ file, action });
83
+ continue;
84
+ }
85
+
86
+ const source = exists ? readFileSync(target, "utf8") : "";
87
+ if (exists && !source.includes(`kj:managed:${cfg.blockId}`)) {
88
+ results.push({ file, action: "skipped" });
89
+ continue;
90
+ }
91
+ const { content, action } = upsertManagedBlock({
92
+ source,
93
+ blockId: cfg.blockId,
94
+ version: BLOCK_VERSION,
95
+ body: cfg.body,
96
+ style: cfg.style,
97
+ });
98
+ if (!dryRun && action !== "unchanged") writeFile(target, content);
99
+ results.push({ file, action });
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Single-directory seed: universal (.editorconfig, commitlint) plus the
105
+ * language's lint/format config, all at `projectDir`. Unknown language ⇒
106
+ * universal only. `dryRun` computes actions without writing.
107
+ */
108
+ export function installConfigs({ projectDir = process.cwd(), language = "javascript", dryRun = false } = {}) {
109
+ const results = [];
110
+ seedInto(projectDir, [...UNIVERSAL_CONFIGS, ...(CONFIGS_BY_LANGUAGE[language] ?? [])], dryRun, ".", results);
111
+ return { dryRun, configs: results };
112
+ }
113
+
114
+ /**
115
+ * Monorepo seed: universal config once at the root, then each language's
116
+ * lint/format config inside its own root (from detectStackRoots). A fullstack
117
+ * repo gets eslint in frontend/, ruff in backend/, etc.
118
+ */
119
+ export function installConfigsForRoots({ projectDir = process.cwd(), roots = [], dryRun = false } = {}) {
120
+ const results = [];
121
+ seedInto(projectDir, UNIVERSAL_CONFIGS, dryRun, ".", results);
122
+ for (const { dir, language } of roots) {
123
+ const rootDir = join(projectDir, dir);
124
+ // A root-level biome.json covers every language root of the monorepo.
125
+ seedInto(rootDir, CONFIGS_BY_LANGUAGE[language] ?? [], dryRun, dir, results, [rootDir, projectDir]);
126
+ }
127
+ return { dryRun, configs: results };
128
+ }