@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.
- package/LICENSE +661 -0
- package/README.md +98 -0
- package/bin/karajan-mcp.js +2 -0
- package/bin/kj-rag-mcp.js +75 -0
- package/bin/kj-tail +361 -0
- package/bin/kj.js +2 -0
- package/docs/README.es.md +80 -0
- package/docs/karajan-code-logo-small.png +0 -0
- package/library/logical-clocks-lamport.md +40 -0
- package/package.json +140 -0
- package/packages/ai-trash/README.md +37 -0
- package/packages/ai-trash/bin/kj-trash.js +10 -0
- package/packages/ai-trash/package.json +43 -0
- package/packages/ai-trash/src/cli.js +200 -0
- package/packages/ai-trash/src/destructive-parser.js +74 -0
- package/packages/ai-trash/src/git-snapshot.js +128 -0
- package/packages/ai-trash/src/hook.js +137 -0
- package/packages/ai-trash/src/index.js +14 -0
- package/packages/ai-trash/src/install.js +73 -0
- package/packages/ai-trash/src/logger.js +60 -0
- package/packages/ai-trash/src/manifest.js +91 -0
- package/packages/ai-trash/src/permissions.js +26 -0
- package/packages/ai-trash/src/snapshot.js +66 -0
- package/packages/ai-trash/src/ulid.js +66 -0
- package/packages/hu-board/README.md +69 -0
- package/packages/hu-board/package.json +28 -0
- package/packages/hu-board/public/app.js +259 -0
- package/packages/hu-board/public/index.html +77 -0
- package/packages/hu-board/public/pipeline.css +132 -0
- package/packages/hu-board/public/pipeline.html +48 -0
- package/packages/hu-board/public/pipeline.js +200 -0
- package/packages/hu-board/public/rag.html +21 -0
- package/packages/hu-board/public/rag.js +39 -0
- package/packages/hu-board/public/styles.css +973 -0
- package/packages/hu-board/public/utils/api.js +139 -0
- package/packages/hu-board/public/utils/board-view.js +473 -0
- package/packages/hu-board/public/utils/command-launcher.js +213 -0
- package/packages/hu-board/public/utils/config-editor.js +181 -0
- package/packages/hu-board/public/utils/dashboard-view.js +103 -0
- package/packages/hu-board/public/utils/formatters.js +300 -0
- package/packages/hu-board/public/utils/graph-view.js +216 -0
- package/packages/hu-board/public/utils/hu-actions.js +224 -0
- package/packages/hu-board/public/utils/init-listeners.js +180 -0
- package/packages/hu-board/public/utils/log-panel.js +222 -0
- package/packages/hu-board/public/utils/modals.js +309 -0
- package/packages/hu-board/public/utils/plan-rollup-view.js +53 -0
- package/packages/hu-board/public/utils/preflight-view.js +89 -0
- package/packages/hu-board/public/utils/project-actions.js +143 -0
- package/packages/hu-board/public/utils/project-picker-view.js +139 -0
- package/packages/hu-board/public/utils/run-launcher.js +169 -0
- package/packages/hu-board/public/utils/server-push.js +252 -0
- package/packages/hu-board/public/utils/sessions-view.js +199 -0
- package/packages/hu-board/public/utils/story-detail-view.js +324 -0
- package/packages/hu-board/public/utils/story-edit-form.js +271 -0
- package/packages/hu-board/public/wiki.html +32 -0
- package/packages/hu-board/public/wiki.js +53 -0
- package/packages/hu-board/src/auth.js +92 -0
- package/packages/hu-board/src/boot-info.js +16 -0
- package/packages/hu-board/src/cleanup-zombies.js +145 -0
- package/packages/hu-board/src/command-runner.js +133 -0
- package/packages/hu-board/src/config-yaml.js +531 -0
- package/packages/hu-board/src/db.js +963 -0
- package/packages/hu-board/src/ephemeral-cleaner.js +196 -0
- package/packages/hu-board/src/event-bus.js +58 -0
- package/packages/hu-board/src/format.js +207 -0
- package/packages/hu-board/src/hu-zombie-reaper.js +103 -0
- package/packages/hu-board/src/journal-parser.js +180 -0
- package/packages/hu-board/src/plan-mutations.js +424 -0
- package/packages/hu-board/src/preflight.js +286 -0
- package/packages/hu-board/src/routes/api.js +1374 -0
- package/packages/hu-board/src/routes/pipeline.js +45 -0
- package/packages/hu-board/src/routes/rag.js +42 -0
- package/packages/hu-board/src/routes/wiki.js +43 -0
- package/packages/hu-board/src/run-tracker.js +121 -0
- package/packages/hu-board/src/server.js +438 -0
- package/packages/hu-board/src/sync.js +732 -0
- package/packages/hu-board/src/token-store.js +56 -0
- package/packages/hu-board/src/zombie-reaper.js +206 -0
- package/scripts/ai-attribution-guard.yml +84 -0
- package/scripts/audit-test-diet.mjs +151 -0
- package/scripts/build-sea.mjs +150 -0
- package/scripts/diet-baseline.mjs +170 -0
- package/scripts/dual-publish.mjs +67 -0
- package/scripts/esbuild-sea.config.mjs +273 -0
- package/scripts/git-hooks/post-merge +18 -0
- package/scripts/install-binary.ps1 +174 -0
- package/scripts/install-binary.sh +159 -0
- package/scripts/install-guard-all-repos.sh +85 -0
- package/scripts/install-kj.sh +193 -0
- package/scripts/install.js +901 -0
- package/scripts/install.sh +7 -0
- package/scripts/postinstall.js +117 -0
- package/scripts/protect-all-repos.sh +88 -0
- package/scripts/quickstart-gate.mjs +97 -0
- package/scripts/regen-arch-stats.sh +71 -0
- package/scripts/setup-multi-instance.sh +150 -0
- package/scripts/verify-pack.mjs +278 -0
- package/src/activity-log.js +59 -0
- package/src/agents/agy-agent.js +62 -0
- package/src/agents/aider-agent.js +78 -0
- package/src/agents/availability.js +52 -0
- package/src/agents/base-agent.js +134 -0
- package/src/agents/claude-agent.js +413 -0
- package/src/agents/codex-agent.js +112 -0
- package/src/agents/copilot-agent.js +65 -0
- package/src/agents/gemini-agent.js +85 -0
- package/src/agents/host-agent.js +38 -0
- package/src/agents/index.js +59 -0
- package/src/agents/kimi-agent.js +45 -0
- package/src/agents/model-registry.js +137 -0
- package/src/agents/opencode-agent.js +72 -0
- package/src/agents/qwen-agent.js +20 -0
- package/src/agents/resolve-bin.js +73 -0
- package/src/audit/agent-readiness.js +259 -0
- package/src/audit/ai-slop-findings.js +102 -0
- package/src/audit/audit-fallback.js +66 -0
- package/src/audit/audit-history-display.js +61 -0
- package/src/audit/audit-history.js +102 -0
- package/src/audit/basal-cost.js +281 -0
- package/src/audit/circular-deps.js +144 -0
- package/src/audit/dead-exports.js +186 -0
- package/src/audit/deterministic-summary.js +327 -0
- package/src/audit/harness-scorecard.js +100 -0
- package/src/audit/harness-section.js +81 -0
- package/src/audit/infra-findings.js +100 -0
- package/src/audit/injection-findings.js +114 -0
- package/src/audit/issue-filter.js +133 -0
- package/src/audit/osv-findings.js +167 -0
- package/src/audit/plan-adherence.js +224 -0
- package/src/audit/semgrep-findings.js +129 -0
- package/src/audit/sonar-findings.js +93 -0
- package/src/audit/webperf-input.js +133 -0
- package/src/autonomy/arbiter-role.js +71 -0
- package/src/autonomy/arbiter.js +22 -0
- package/src/autonomy/decider.js +18 -0
- package/src/autonomy/decision-resolver.js +58 -0
- package/src/autonomy/outcome-report.js +55 -0
- package/src/autonomy/policy.js +41 -0
- package/src/autonomy/stage-ask.js +30 -0
- package/src/bootstrap.js +276 -0
- package/src/brain/agent-error-classifier.js +116 -0
- package/src/brain/decision-tracker.js +125 -0
- package/src/brain/decisor.js +243 -0
- package/src/brain/solomon-consult.js +171 -0
- package/src/brain/standby-scheduler.js +8 -0
- package/src/brain/standby-store.js +12 -0
- package/src/brain/with-brain-recovery.js +235 -0
- package/src/budget/comparison.js +96 -0
- package/src/canvas/parse-md.js +238 -0
- package/src/canvas/schema.js +101 -0
- package/src/canvas/to-flat.js +85 -0
- package/src/canvas/validate.js +163 -0
- package/src/checks/ai-surface.js +79 -0
- package/src/checks/ai-trash.js +34 -0
- package/src/checks/binaries.js +207 -0
- package/src/checks/ci.js +105 -0
- package/src/checks/config-files.js +217 -0
- package/src/checks/dir-setup.js +103 -0
- package/src/checks/guarantee-level.js +38 -0
- package/src/checks/hardware.js +150 -0
- package/src/checks/harness-scorecard.js +41 -0
- package/src/checks/mcp-health.js +169 -0
- package/src/checks/method.js +73 -0
- package/src/checks/native-build.js +67 -0
- package/src/checks/node.js +70 -0
- package/src/checks/ollama.js +41 -0
- package/src/checks/orphans.js +77 -0
- package/src/checks/ports.js +188 -0
- package/src/checks/project-checks.js +245 -0
- package/src/checks/qmd.js +41 -0
- package/src/checks/rag-hooks.js +54 -0
- package/src/checks/release-check.js +102 -0
- package/src/checks/rtk.js +36 -0
- package/src/checks/runner.js +326 -0
- package/src/checks/skills.js +53 -0
- package/src/checks/sonar.js +67 -0
- package/src/checks/squeezr.js +36 -0
- package/src/checks/system.js +53 -0
- package/src/checks/tokens.js +172 -0
- package/src/checks/types.js +103 -0
- package/src/ci/dispatch.js +99 -0
- package/src/ci/index.js +3 -0
- package/src/ci/pr-diff.js +26 -0
- package/src/ci/repo.js +45 -0
- package/src/cli/_shared.js +30 -0
- package/src/cli/advanced-commands.js +61 -0
- package/src/cli/register-meta.js +772 -0
- package/src/cli/register-pipeline.js +335 -0
- package/src/cli/register-plan.js +224 -0
- package/src/cli/register-roles-skills.js +54 -0
- package/src/cli/register-sonar.js +28 -0
- package/src/cli/register-standby.js +26 -0
- package/src/cli.js +155 -0
- package/src/commands/advanced.js +31 -0
- package/src/commands/agent-run.js +63 -0
- package/src/commands/agents.js +105 -0
- package/src/commands/architect.js +123 -0
- package/src/commands/audit.js +430 -0
- package/src/commands/autorun.js +49 -0
- package/src/commands/board.js +458 -0
- package/src/commands/check.js +40 -0
- package/src/commands/clean.js +145 -0
- package/src/commands/code.js +45 -0
- package/src/commands/config.js +74 -0
- package/src/commands/discover.js +105 -0
- package/src/commands/doctor.js +251 -0
- package/src/commands/env.js +207 -0
- package/src/commands/harden.js +203 -0
- package/src/commands/hu.js +210 -0
- package/src/commands/identity.js +60 -0
- package/src/commands/init.js +950 -0
- package/src/commands/install-tools.js +432 -0
- package/src/commands/mutate.js +94 -0
- package/src/commands/ollama.js +44 -0
- package/src/commands/onboard.js +45 -0
- package/src/commands/plan/_shared.js +55 -0
- package/src/commands/plan/add-hu.js +23 -0
- package/src/commands/plan/delete.js +9 -0
- package/src/commands/plan/fix.js +99 -0
- package/src/commands/plan/generate.js +532 -0
- package/src/commands/plan/index.js +13 -0
- package/src/commands/plan/list.js +21 -0
- package/src/commands/plan/migrate-result.js +41 -0
- package/src/commands/plan/ready.js +46 -0
- package/src/commands/plan/remove-hu.js +17 -0
- package/src/commands/plan/share.js +85 -0
- package/src/commands/plan/show.js +81 -0
- package/src/commands/plan/unshare.js +41 -0
- package/src/commands/plan/validate.js +20 -0
- package/src/commands/plan.js +17 -0
- package/src/commands/policy.js +164 -0
- package/src/commands/privacy.js +45 -0
- package/src/commands/qmd.js +86 -0
- package/src/commands/rag-mcp.js +18 -0
- package/src/commands/rag.js +173 -0
- package/src/commands/report-issue.js +77 -0
- package/src/commands/report.js +469 -0
- package/src/commands/researcher.js +54 -0
- package/src/commands/resume.js +68 -0
- package/src/commands/review-gate.js +355 -0
- package/src/commands/review.js +89 -0
- package/src/commands/roles.js +116 -0
- package/src/commands/run.js +185 -0
- package/src/commands/scan.js +18 -0
- package/src/commands/skills.js +86 -0
- package/src/commands/sonar.js +53 -0
- package/src/commands/standby.js +117 -0
- package/src/commands/start.js +121 -0
- package/src/commands/status.js +36 -0
- package/src/commands/sync.js +103 -0
- package/src/commands/telemetry.js +70 -0
- package/src/commands/tournament.js +85 -0
- package/src/commands/triage.js +78 -0
- package/src/commands/undo.js +62 -0
- package/src/commands/watch.js +46 -0
- package/src/commands/webperf.js +162 -0
- package/src/commands/worktree.js +76 -0
- package/src/config/defaults.js +270 -0
- package/src/config/loader.js +204 -0
- package/src/config/overrides.js +220 -0
- package/src/config/role-resolver.js +171 -0
- package/src/config/schema.js +299 -0
- package/src/config/test-harness.js +78 -0
- package/src/config.js +34 -0
- package/src/domains/domain-loader.js +245 -0
- package/src/domains/domain-registry.js +140 -0
- package/src/domains/domain-synthesizer.js +129 -0
- package/src/environment/adr.js +45 -0
- package/src/environment/board-access.js +97 -0
- package/src/environment/board-select.js +59 -0
- package/src/environment/briefs.js +129 -0
- package/src/environment/issue-report.js +72 -0
- package/src/environment/mcp-wiring.js +44 -0
- package/src/environment/playbook.js +127 -0
- package/src/git/automation.js +334 -0
- package/src/git/hu-automation.js +171 -0
- package/src/git/hu-snapshot.js +10 -0
- package/src/golden/asserter.js +92 -0
- package/src/golden/loader.js +52 -0
- package/src/golden/runner.js +92 -0
- package/src/golden/schema.js +41 -0
- package/src/guards/intent-guard.js +161 -0
- package/src/guards/output-guard.js +149 -0
- package/src/guards/perf-guard.js +126 -0
- package/src/guards/policy-guard.js +90 -0
- package/src/guards/policy-resolver.js +85 -0
- package/src/guards/secret-redactor.js +71 -0
- package/src/harden/advisory.js +203 -0
- package/src/harden/alternatives.js +50 -0
- package/src/harden/check.js +81 -0
- package/src/harden/config-engine.js +128 -0
- package/src/harden/config-templates.js +122 -0
- package/src/harden/guidelines-engine.js +48 -0
- package/src/harden/guidelines-templates.js +36 -0
- package/src/harden/harden-engine.js +80 -0
- package/src/harden/harness-hooks.js +107 -0
- package/src/harden/hook-commands.js +25 -0
- package/src/harden/hook-templates.js +183 -0
- package/src/harden/interactive.js +76 -0
- package/src/harden/sentinel-hooks.js +721 -0
- package/src/harden/stack-roots.js +105 -0
- package/src/harden/test-on-push.js +38 -0
- package/src/harden/workflow-engine.js +116 -0
- package/src/harden/workflow-templates.js +285 -0
- package/src/hu/acceptance-runner.js +138 -0
- package/src/hu/auto-generator.js +358 -0
- package/src/hu/graph.js +71 -0
- package/src/hu/lazy-planner.js +109 -0
- package/src/hu/model-router.js +108 -0
- package/src/hu/parallel-executor.js +94 -0
- package/src/hu/split-validator.js +143 -0
- package/src/hu/splitting-detector.js +158 -0
- package/src/hu/splitting-generator.js +175 -0
- package/src/hu/store.js +392 -0
- package/src/hu/worktree-bootstrap.js +59 -0
- package/src/identity/bootstrap.js +48 -0
- package/src/identity/compare.js +38 -0
- package/src/identity/detect.js +51 -0
- package/src/identity/store.js +66 -0
- package/src/infrastructure/command-runner.js +44 -0
- package/src/infrastructure/environment.js +38 -0
- package/src/infrastructure/file-system-service.js +63 -0
- package/src/infrastructure/mocks.js +182 -0
- package/src/lang/chunk-go.js +94 -0
- package/src/lang/chunk-java.js +90 -0
- package/src/lang/chunk-python.js +88 -0
- package/src/lang/chunk-rust.js +102 -0
- package/src/lang/go.js +14 -0
- package/src/lang/java.js +16 -0
- package/src/lang/js.js +8 -0
- package/src/lang/python.js +14 -0
- package/src/lang/registry.js +79 -0
- package/src/lang/rust.js +14 -0
- package/src/lang/tree-sitter-loader.js +45 -0
- package/src/mcp/direct-role-runner.js +112 -0
- package/src/mcp/error-classifiers.js +66 -0
- package/src/mcp/handlers/direct-handlers.js +486 -0
- package/src/mcp/handlers/hu-handlers.js +93 -0
- package/src/mcp/handlers/management-handlers.js +265 -0
- package/src/mcp/handlers/rag-handler.js +52 -0
- package/src/mcp/handlers/run-handler.js +313 -0
- package/src/mcp/orphan-guard.js +131 -0
- package/src/mcp/preflight.js +47 -0
- package/src/mcp/progress.js +149 -0
- package/src/mcp/response-compressor.js +81 -0
- package/src/mcp/run-kj.js +135 -0
- package/src/mcp/server-handlers.js +79 -0
- package/src/mcp/server.js +66 -0
- package/src/mcp/shared-helpers.js +297 -0
- package/src/mcp/sovereignty-guard.js +112 -0
- package/src/mcp/suggest-handler.js +92 -0
- package/src/mcp/tool-arg-normalizers.js +16 -0
- package/src/mcp/tools.js +432 -0
- package/src/mutate/diff-scope.js +105 -0
- package/src/mutate/reviewer-signal.js +76 -0
- package/src/mutate/runner.js +72 -0
- package/src/mutate/tool-registry.js +90 -0
- package/src/onboarder/cache.js +24 -0
- package/src/onboarder/collectors/framework-multilang.js +64 -0
- package/src/onboarder/collectors/index.js +136 -0
- package/src/orchestrator/agent-fallback.js +81 -0
- package/src/orchestrator/brain-coordinator.js +186 -0
- package/src/orchestrator/ci-integration.js +139 -0
- package/src/orchestrator/config-init.js +510 -0
- package/src/orchestrator/direct-actions.js +217 -0
- package/src/orchestrator/drivers/error-recovery.js +287 -0
- package/src/orchestrator/drivers/init-context.js +227 -0
- package/src/orchestrator/drivers/iteration-loop.js +380 -0
- package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +69 -0
- package/src/orchestrator/drivers/iteration-phases/guards.js +84 -0
- package/src/orchestrator/drivers/iteration-phases/handle-approved.js +29 -0
- package/src/orchestrator/drivers/iteration-phases/quality-gates.js +114 -0
- package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +60 -0
- package/src/orchestrator/drivers/post-loop.js +383 -0
- package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +136 -0
- package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +47 -0
- package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +84 -0
- package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +391 -0
- package/src/orchestrator/drivers/pre-loop.js +507 -0
- package/src/orchestrator/drivers/run-hu-batch.js +427 -0
- package/src/orchestrator/feedback-enrichment.js +140 -0
- package/src/orchestrator/feedback-queue.js +151 -0
- package/src/orchestrator/flow-control.js +170 -0
- package/src/orchestrator/flow-runner.js +364 -0
- package/src/orchestrator/fs-leak-detector.js +210 -0
- package/src/orchestrator/hu-scheduler.js +114 -0
- package/src/orchestrator/hu-sub-pipeline.js +616 -0
- package/src/orchestrator/integrations.js +81 -0
- package/src/orchestrator/iteration-gate.js +73 -0
- package/src/orchestrator/iteration-stages.js +9 -0
- package/src/orchestrator/parallel-limiter.js +74 -0
- package/src/orchestrator/pipeline-context.js +72 -0
- package/src/orchestrator/post-loop-stages.js +468 -0
- package/src/orchestrator/pre-loop-stages.js +11 -0
- package/src/orchestrator/preflight-checks.js +462 -0
- package/src/orchestrator/repair/repair-detector.js +117 -0
- package/src/orchestrator/repair/repair-runner.js +142 -0
- package/src/orchestrator/reviewer-fallback.js +45 -0
- package/src/orchestrator/role-output-compressor.js +189 -0
- package/src/orchestrator/session-journal.js +308 -0
- package/src/orchestrator/solomon-escalation.js +305 -0
- package/src/orchestrator/solomon-rules.js +212 -0
- package/src/orchestrator/stages/architect-stage.js +172 -0
- package/src/orchestrator/stages/coder-stage.js +480 -0
- package/src/orchestrator/stages/domain-curator-stage.js +82 -0
- package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +127 -0
- package/src/orchestrator/stages/hu-reviewer-stage.js +394 -0
- package/src/orchestrator/stages/perf-stage.js +140 -0
- package/src/orchestrator/stages/planner-stage.js +85 -0
- package/src/orchestrator/stages/rag-context-stage.js +77 -0
- package/src/orchestrator/stages/rag-preload-decisor.js +58 -0
- package/src/orchestrator/stages/research-stage.js +69 -0
- package/src/orchestrator/stages/reviewer-stage.js +422 -0
- package/src/orchestrator/stages/sonar-stage.js +421 -0
- package/src/orchestrator/stages/stage-classes.js +84 -0
- package/src/orchestrator/stages/stage-executor.js +155 -0
- package/src/orchestrator/stages/tdd-discipline-stage.js +56 -0
- package/src/orchestrator/stages/tool-judge-stage.js +53 -0
- package/src/orchestrator/stages/triage-stage.js +172 -0
- package/src/orchestrator/standby.js +71 -0
- package/src/orchestrator/verification-gate.js +183 -0
- package/src/orchestrator.js +22 -0
- package/src/plan/adr-generator.js +170 -0
- package/src/plan/adr-loader.js +60 -0
- package/src/plan/plan-executor.js +100 -0
- package/src/plan/plan-fixer.js +192 -0
- package/src/plan/plan-hu-ops.js +16 -0
- package/src/plan/plan-id.js +4 -0
- package/src/plan/plan-migrate-result.js +24 -0
- package/src/plan/plan-reviewer.js +236 -0
- package/src/plan/plan-schema.js +334 -0
- package/src/plan/plan-store.js +260 -0
- package/src/plan/plan-structural-pass.js +122 -0
- package/src/plan/plan-validation.js +4 -0
- package/src/plan/preflight-hu.js +105 -0
- package/src/plan/spec-detector.js +61 -0
- package/src/plan/tests-synthesizer.js +131 -0
- package/src/planning-game/adapter.js +108 -0
- package/src/planning-game/architect-adrs.js +45 -0
- package/src/planning-game/client.js +108 -0
- package/src/planning-game/decomposition.js +87 -0
- package/src/planning-game/pipeline-adapter.js +251 -0
- package/src/plugins/loader.js +67 -0
- package/src/policy/add.js +111 -0
- package/src/policy/decisions.js +42 -0
- package/src/policy/engine.js +82 -0
- package/src/policy/exceptions.js +60 -0
- package/src/privacy/onboarding.js +35 -0
- package/src/privacy/scan.js +120 -0
- package/src/prompts/arbiter.js +38 -0
- package/src/prompts/architect.js +103 -0
- package/src/prompts/audit.js +474 -0
- package/src/prompts/coder.js +287 -0
- package/src/prompts/diff-clip.js +27 -0
- package/src/prompts/discover.js +222 -0
- package/src/prompts/hu-reviewer.js +219 -0
- package/src/prompts/planner.js +312 -0
- package/src/prompts/prompt-layout.js +80 -0
- package/src/prompts/prompt-resolver.js +120 -0
- package/src/prompts/reviewer.js +75 -0
- package/src/prompts/rtk-snippet.js +25 -0
- package/src/prompts/spec-reviewer.js +66 -0
- package/src/prompts/split-signal.js +82 -0
- package/src/prompts/start-decision.js +52 -0
- package/src/prompts/tool-correctness-judge.js +31 -0
- package/src/prompts/triage.js +57 -0
- package/src/rag/auto-update.js +72 -0
- package/src/rag/chunker-ast.js +56 -0
- package/src/rag/chunker.js +132 -0
- package/src/rag/embedder.js +2 -0
- package/src/rag/embedders/_cloud-base.js +2 -0
- package/src/rag/embedders/cohere.js +2 -0
- package/src/rag/embedders/factory.js +2 -0
- package/src/rag/embedders/mistral.js +2 -0
- package/src/rag/embedders/onnx.js +2 -0
- package/src/rag/embedders/openai.js +2 -0
- package/src/rag/embedders/voyage.js +2 -0
- package/src/rag/eval.js +61 -0
- package/src/rag/governed-embedder.js +39 -0
- package/src/rag/indexer.js +199 -0
- package/src/rag/library.js +62 -0
- package/src/rag/ollama-capability.js +59 -0
- package/src/rag/ollama-manager.js +98 -0
- package/src/rag/onnx-fallback.js +69 -0
- package/src/rag/rerank.js +4 -0
- package/src/rag/retriever.js +2 -0
- package/src/rag/vec-store.js +17 -0
- package/src/rag/watcher.js +62 -0
- package/src/rag/where-parser.js +4 -0
- package/src/repeat-detector.js +88 -0
- package/src/review/card-first.js +99 -0
- package/src/review/card-verify.js +83 -0
- package/src/review/diff-generator.js +112 -0
- package/src/review/gate-gitignore.js +77 -0
- package/src/review/git-stash-helper.js +104 -0
- package/src/review/mutation-pregate.js +48 -0
- package/src/review/one-shot-review.js +122 -0
- package/src/review/parser.js +80 -0
- package/src/review/policy-gate.js +26 -0
- package/src/review/profiles.js +66 -0
- package/src/review/reviewer-fallback.js +125 -0
- package/src/review/schema.js +31 -0
- package/src/review/scope-filter.js +152 -0
- package/src/review/snapshot-diff.js +122 -0
- package/src/review/solomon-arbitration.js +113 -0
- package/src/review/sonar-pregate.js +63 -0
- package/src/review/tdd-discipline.js +90 -0
- package/src/review/tdd-policy.js +69 -0
- package/src/review/tests-with-code.js +33 -0
- package/src/review/verdict-store.js +87 -0
- package/src/review/workspace.js +21 -0
- package/src/roles/agent-role.js +194 -0
- package/src/roles/architect-role.js +77 -0
- package/src/roles/audit-role.js +258 -0
- package/src/roles/base-role.js +165 -0
- package/src/roles/coder-role.js +107 -0
- package/src/roles/commiter-role.js +110 -0
- package/src/roles/discover-role.js +81 -0
- package/src/roles/domain-curator-role.js +141 -0
- package/src/roles/hu-reviewer-role.js +64 -0
- package/src/roles/impeccable-role.js +85 -0
- package/src/roles/index.js +18 -0
- package/src/roles/karajan-brain-role.js +134 -0
- package/src/roles/onboarder-role.js +61 -0
- package/src/roles/perf-role.js +122 -0
- package/src/roles/planner-role.js +151 -0
- package/src/roles/refactorer-role.js +30 -0
- package/src/roles/repairer-role.js +181 -0
- package/src/roles/researcher-role.js +70 -0
- package/src/roles/reviewer-role.js +104 -0
- package/src/roles/security-role.js +73 -0
- package/src/roles/solomon-role.js +265 -0
- package/src/roles/sonar-role.js +70 -0
- package/src/roles/spec-reviewer-role.js +106 -0
- package/src/roles/tester-role.js +235 -0
- package/src/roles/tool-correctness-judge-role.js +78 -0
- package/src/roles/triage-role.js +99 -0
- package/src/session/journal/decisions-writer.js +200 -0
- package/src/session/journal/iteration-logger.js +194 -0
- package/src/session/journal/summary-writer.js +320 -0
- package/src/session/journal/tree-writer.js +164 -0
- package/src/session/journal/write-all.js +89 -0
- package/src/session/mutators.js +339 -0
- package/src/session/runtime-overrides.js +59 -0
- package/src/session/store.js +106 -0
- package/src/session-cleanup.js +116 -0
- package/src/skills/addyosmani-catalog.js +315 -0
- package/src/skills/addyosmani-role-map.js +139 -0
- package/src/skills/openskills-client.js +157 -0
- package/src/skills/semantic-detector.js +167 -0
- package/src/skills/skill-detector.js +385 -0
- package/src/skills/skill-loader.js +219 -0
- package/src/skills/skills-cache.js +128 -0
- package/src/sonar/api.js +128 -0
- package/src/sonar/cloud-scanner.js +78 -0
- package/src/sonar/config-resolver.js +88 -0
- package/src/sonar/credentials.js +60 -0
- package/src/sonar/discovery.js +124 -0
- package/src/sonar/enforcer.js +19 -0
- package/src/sonar/issue-filter.js +12 -0
- package/src/sonar/manager.js +181 -0
- package/src/sonar/project-key.js +120 -0
- package/src/sonar/scanner.js +307 -0
- package/src/sonar/token-bootstrap.js +205 -0
- package/src/sonar/token-recovery.js +67 -0
- package/src/spec-review/refine-loop.js +58 -0
- package/src/spec-review/run-spec-review.js +119 -0
- package/src/start/assessment.js +74 -0
- package/src/start/maturity.js +73 -0
- package/src/start/start-decider-role.js +79 -0
- package/src/start/sweep.js +157 -0
- package/src/sync/apply-canvas-patch.js +55 -0
- package/src/sync/detect-drift.js +113 -0
- package/src/tournament/crown.js +88 -0
- package/src/tournament/judge.js +150 -0
- package/src/tournament/run.js +142 -0
- package/src/tournament/scoreboard.js +124 -0
- package/src/tracker-bootstrap.js +44 -0
- package/src/types/agent.js +55 -0
- package/src/types/config.js +114 -0
- package/src/types/hu.js +47 -0
- package/src/types/index.js +38 -0
- package/src/types/policy.js +43 -0
- package/src/types/session.js +75 -0
- package/src/types/solomon.js +87 -0
- package/src/types/stage.js +68 -0
- package/src/utils/agent-detect.js +93 -0
- package/src/utils/ai-trash.js +48 -0
- package/src/utils/async-lock.js +31 -0
- package/src/utils/atomic-write.js +6 -0
- package/src/utils/banner.js +66 -0
- package/src/utils/binary-sources.js +70 -0
- package/src/utils/board-prompt-bridge.js +108 -0
- package/src/utils/budget.js +229 -0
- package/src/utils/cli-ask-question.js +93 -0
- package/src/utils/cli-progress.js +220 -0
- package/src/utils/cli-run-log.js +83 -0
- package/src/utils/cwd-confirm.js +39 -0
- package/src/utils/derive-project-name-from-cwd.js +119 -0
- package/src/utils/display/event-handlers.js +403 -0
- package/src/utils/display/formatters.js +132 -0
- package/src/utils/display/header.js +47 -0
- package/src/utils/display/resume-hint.js +41 -0
- package/src/utils/display/session-summary.js +118 -0
- package/src/utils/display/solomon.js +42 -0
- package/src/utils/display/spec-findings.js +44 -0
- package/src/utils/doc-links.js +29 -0
- package/src/utils/docker-install.js +34 -0
- package/src/utils/escape-regexp.js +15 -0
- package/src/utils/events.js +35 -0
- package/src/utils/fs.js +19 -0
- package/src/utils/garbage-collector.js +481 -0
- package/src/utils/git.js +279 -0
- package/src/utils/home-migration.js +87 -0
- package/src/utils/injection-guard.js +186 -0
- package/src/utils/install-hints.js +180 -0
- package/src/utils/json-extract.js +64 -0
- package/src/utils/locale.js +59 -0
- package/src/utils/logger.js +96 -0
- package/src/utils/managed-markers.js +93 -0
- package/src/utils/manifest-sniff.js +82 -0
- package/src/utils/messages.js +61 -0
- package/src/utils/model-selector.js +107 -0
- package/src/utils/os-detect.js +79 -0
- package/src/utils/paths.js +13 -0
- package/src/utils/pending-user-action.js +95 -0
- package/src/utils/port-check.js +6 -0
- package/src/utils/port-occupant.js +96 -0
- package/src/utils/pricing.js +29 -0
- package/src/utils/process.js +4 -0
- package/src/utils/project-detect.js +160 -0
- package/src/utils/prompt-project-name.js +35 -0
- package/src/utils/provider-env.js +116 -0
- package/src/utils/qmd-collection.js +72 -0
- package/src/utils/qmd-detect.js +18 -0
- package/src/utils/qmd-install.js +37 -0
- package/src/utils/quota-simulator.js +56 -0
- package/src/utils/rate-limit-detector.js +181 -0
- package/src/utils/repo-cleaner.js +79 -0
- package/src/utils/retry.js +88 -0
- package/src/utils/role-assigner.js +137 -0
- package/src/utils/role-env.js +58 -0
- package/src/utils/rtk-detect.js +18 -0
- package/src/utils/rtk-install.js +38 -0
- package/src/utils/rtk-wrapper.js +87 -0
- package/src/utils/run-context.js +75 -0
- package/src/utils/run-log.js +217 -0
- package/src/utils/run-registry.js +9 -0
- package/src/utils/shared-paths.js +12 -0
- package/src/utils/squeezr-detect.js +21 -0
- package/src/utils/squeezr-install.js +37 -0
- package/src/utils/stack-detect.js +135 -0
- package/src/utils/stall-detector.js +159 -0
- package/src/utils/status-dashboard.js +254 -0
- package/src/utils/task-file.js +109 -0
- package/src/utils/telemetry.js +124 -0
- package/src/utils/templates-root.js +61 -0
- package/src/utils/tool-call-extractor.js +56 -0
- package/src/utils/tool-governor.js +116 -0
- package/src/utils/tool-installer.js +93 -0
- package/src/utils/update-check.js +280 -0
- package/src/utils/vector-store-orphans.js +89 -0
- package/src/utils/welcome.js +47 -0
- package/src/utils/with-timeout.js +42 -0
- package/src/utils/wizard.js +47 -0
- package/src/webperf/cwv-gate.js +94 -0
- package/src/webperf/devtools-detect.js +112 -0
- package/src/webperf/scanner.js +209 -0
- package/src/webperf/webperf-role-integration.js +66 -0
- package/templates/coder-rules.md +45 -0
- package/templates/docker-compose.sonar.yml +60 -0
- package/templates/domains/example-dental.md +57 -0
- package/templates/kj.config.yml +128 -0
- package/templates/review-rules.md +17 -0
- package/templates/roles/architect/anthropic.md +74 -0
- package/templates/roles/architect/default.md +72 -0
- package/templates/roles/architect/google.md +61 -0
- package/templates/roles/architect/openai.md +60 -0
- package/templates/roles/architect/opencode.md +59 -0
- package/templates/roles/architect.md +76 -0
- package/templates/roles/audit.md +68 -0
- package/templates/roles/coder/anthropic.md +81 -0
- package/templates/roles/coder/default.md +78 -0
- package/templates/roles/coder/google.md +87 -0
- package/templates/roles/coder/openai.md +68 -0
- package/templates/roles/coder/opencode.md +58 -0
- package/templates/roles/coder.md +152 -0
- package/templates/roles/commiter.md +44 -0
- package/templates/roles/discover.md +167 -0
- package/templates/roles/domain-curator.md +52 -0
- package/templates/roles/hu-reviewer.md +236 -0
- package/templates/roles/impeccable-design.md +87 -0
- package/templates/roles/impeccable.md +125 -0
- package/templates/roles/karajan-brain.md +95 -0
- package/templates/roles/onboarder.md +45 -0
- package/templates/roles/planner/anthropic.md +59 -0
- package/templates/roles/planner/default.md +57 -0
- package/templates/roles/planner/google.md +75 -0
- package/templates/roles/planner/openai.md +55 -0
- package/templates/roles/planner/opencode.md +62 -0
- package/templates/roles/planner.md +61 -0
- package/templates/roles/refactorer.md +48 -0
- package/templates/roles/repairer.md +103 -0
- package/templates/roles/researcher.md +41 -0
- package/templates/roles/reviewer/anthropic.md +61 -0
- package/templates/roles/reviewer/default.md +62 -0
- package/templates/roles/reviewer/google.md +63 -0
- package/templates/roles/reviewer/openai.md +57 -0
- package/templates/roles/reviewer/opencode.md +42 -0
- package/templates/roles/reviewer-paranoid.md +38 -0
- package/templates/roles/reviewer-relaxed.md +34 -0
- package/templates/roles/reviewer-strict.md +37 -0
- package/templates/roles/reviewer.md +62 -0
- package/templates/roles/security.md +54 -0
- package/templates/roles/solomon/anthropic.md +71 -0
- package/templates/roles/solomon/default.md +83 -0
- package/templates/roles/solomon/google.md +61 -0
- package/templates/roles/solomon/openai.md +53 -0
- package/templates/roles/solomon/opencode.md +55 -0
- package/templates/roles/solomon.md +83 -0
- package/templates/roles/sonar.md +49 -0
- package/templates/roles/spec-reviewer.md +47 -0
- package/templates/roles/tester.md +50 -0
- package/templates/roles/triage.md +69 -0
- package/templates/skills/kj-architect.md +45 -0
- package/templates/skills/kj-audit.md +63 -0
- package/templates/skills/kj-board.md +16 -0
- package/templates/skills/kj-code.md +51 -0
- package/templates/skills/kj-csv-transform.md +40 -0
- package/templates/skills/kj-data-report.md +44 -0
- package/templates/skills/kj-discover.md +24 -0
- package/templates/skills/kj-rag-query.md +50 -0
- package/templates/skills/kj-review.md +47 -0
- package/templates/skills/kj-run.md +73 -0
- package/templates/skills/kj-security.md +49 -0
- package/templates/skills/kj-sonar.md +41 -0
- package/templates/skills/kj-sql-analysis.md +40 -0
- package/templates/skills/kj-test.md +40 -0
- package/templates/workflows/automerge.yml +30 -0
- package/templates/workflows/houston-override.yml +46 -0
- package/templates/workflows/kj-ci-gateway.yml +58 -0
- package/vendor/tree-sitter-grammars/README.md +21 -0
- package/vendor/tree-sitter-grammars/go.wasm +0 -0
- package/vendor/tree-sitter-grammars/java.wasm +0 -0
- package/vendor/tree-sitter-grammars/python.wasm +0 -0
- package/vendor/tree-sitter-grammars/rust.wasm +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-harness-visible config knobs — resolved once per run, then read
|
|
3
|
+
* everywhere via `config.testHarness.*`.
|
|
4
|
+
*
|
|
5
|
+
* Pre-v2.7.5 these defaults were read from `globalThis.__KJ_*` directly
|
|
6
|
+
* in production code paths — `flow-runner.js` read
|
|
7
|
+
* `globalThis.__KJ_DEFAULT_BRAIN_DECISOR`, `preflight-checks.js` read
|
|
8
|
+
* `globalThis.__KJ_DISABLE_SONAR_STAGE`, etc. The audit flagged this as
|
|
9
|
+
* spooky-action-at-a-distance: a stray test-harness leftover could flip
|
|
10
|
+
* a production default, and tests did not exercise the same path
|
|
11
|
+
* production users would.
|
|
12
|
+
*
|
|
13
|
+
* TSK-0334 isolates the globalThis lookup to a single site (this module,
|
|
14
|
+
* run during config load) and exposes the resolved knobs under
|
|
15
|
+
* `config.testHarness`. Production code reads only from `config.*`,
|
|
16
|
+
* never from `globalThis`. Tests can set either `globalThis.__KJ_*`
|
|
17
|
+
* (back-compat for the ~10 existing test files that already do) or pass
|
|
18
|
+
* `config.testHarness = { ... }` explicitly.
|
|
19
|
+
*
|
|
20
|
+
* The `tests/architecture/no-globalThis-kj.test.js` invariant fails CI
|
|
21
|
+
* if a `globalThis.__KJ_` read reappears anywhere under `src/` outside
|
|
22
|
+
* this module.
|
|
23
|
+
*
|
|
24
|
+
* Resolution order (first defined wins):
|
|
25
|
+
* 1. `config.testHarness.<key>` (explicit test or user config)
|
|
26
|
+
* 2. `globalThis.__KJ_<MACRO>` (existing test-harness global, back-compat)
|
|
27
|
+
* 3. Documented default (second column below)
|
|
28
|
+
*
|
|
29
|
+
* | config.testHarness key | globalThis key | prod default |
|
|
30
|
+
* |--------------------------------|----------------------------------|--------------|
|
|
31
|
+
* | disableSonarStage | __KJ_DISABLE_SONAR_STAGE | false |
|
|
32
|
+
* | defaultSkillsMode | __KJ_DEFAULT_SKILLS_MODE | "auto" |
|
|
33
|
+
* | defaultPreflightExtended | __KJ_DEFAULT_PREFLIGHT_EXTENDED | true |
|
|
34
|
+
* | defaultBrainDecisor | __KJ_DEFAULT_BRAIN_DECISOR | true |
|
|
35
|
+
* | defaultAddyosmaniEnabled | __KJ_DEFAULT_ADDYOSMANI_ENABLED | true |
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const GLOBAL_KEYS = {
|
|
39
|
+
disableSonarStage: "__KJ_DISABLE_SONAR_STAGE",
|
|
40
|
+
defaultSkillsMode: "__KJ_DEFAULT_SKILLS_MODE",
|
|
41
|
+
defaultPreflightExtended: "__KJ_DEFAULT_PREFLIGHT_EXTENDED",
|
|
42
|
+
defaultBrainDecisor: "__KJ_DEFAULT_BRAIN_DECISOR",
|
|
43
|
+
defaultAddyosmaniEnabled: "__KJ_DEFAULT_ADDYOSMANI_ENABLED",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const PROD_DEFAULTS = Object.freeze({
|
|
47
|
+
disableSonarStage: false,
|
|
48
|
+
defaultSkillsMode: "auto",
|
|
49
|
+
defaultPreflightExtended: true,
|
|
50
|
+
defaultBrainDecisor: true,
|
|
51
|
+
defaultAddyosmaniEnabled: true,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the testHarness slice of config. Safe to call multiple times;
|
|
56
|
+
* each call re-reads globalThis so test-harness overrides applied after
|
|
57
|
+
* config load still take effect on the next resolution.
|
|
58
|
+
*
|
|
59
|
+
* @param {Partial<typeof PROD_DEFAULTS>} [explicit] - typically `config.testHarness` passed in
|
|
60
|
+
* @returns {Readonly<typeof PROD_DEFAULTS>}
|
|
61
|
+
*/
|
|
62
|
+
export function resolveTestHarness(explicit = {}) {
|
|
63
|
+
const out = {};
|
|
64
|
+
for (const [key, globalName] of Object.entries(GLOBAL_KEYS)) {
|
|
65
|
+
if (explicit && Object.prototype.hasOwnProperty.call(explicit, key) && explicit[key] !== undefined) {
|
|
66
|
+
out[key] = explicit[key];
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const g = globalThis[globalName];
|
|
70
|
+
if (g !== undefined) {
|
|
71
|
+
out[key] = g;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
out[key] = PROD_DEFAULTS[key];
|
|
75
|
+
}
|
|
76
|
+
return Object.freeze(out);
|
|
77
|
+
}
|
|
78
|
+
|
package/src/config.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Karajan config barrel — public re-exports for back-compat.
|
|
3
|
+
*
|
|
4
|
+
* Pre-v2.7.5 this file was 636 LOC with fan-in 28: it bundled DEFAULTS
|
|
5
|
+
* data, loadConfig/writeConfig I/O, applyRunOverrides merge logic,
|
|
6
|
+
* resolveRole lookup and validateConfig schema gate — touching any of
|
|
7
|
+
* those risked rippling to a quarter of the codebase. TSK-0332 (Oleada 2
|
|
8
|
+
* of the audit refactor) split along the natural seams:
|
|
9
|
+
*
|
|
10
|
+
* - `src/config/defaults.js` — pure data, DEFAULTS
|
|
11
|
+
* - `src/config/loader.js` — fs/yaml I/O (getConfigPath,
|
|
12
|
+
* loadConfig, writeConfig, etc.)
|
|
13
|
+
* - `src/config/overrides.js` — CLI-flag merge (applyRunOverrides)
|
|
14
|
+
* - `src/config/role-resolver.js` — provider/model/validate
|
|
15
|
+
* - `src/config/schema.js` — Valibot schema (pre-existing)
|
|
16
|
+
*
|
|
17
|
+
* This file is now a thin barrel so the 28+ consumers importing from
|
|
18
|
+
* `src/config.js` keep working unchanged. New code should import from
|
|
19
|
+
* the specific submodule.
|
|
20
|
+
*
|
|
21
|
+
* @typedef {import("./config/schema.js").KarajanConfig} KarajanConfig
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export { DEFAULTS } from "./config/defaults.js";
|
|
25
|
+
export {
|
|
26
|
+
getConfigPath,
|
|
27
|
+
getProjectConfigPath,
|
|
28
|
+
loadProjectConfig,
|
|
29
|
+
loadConfig,
|
|
30
|
+
writeConfig,
|
|
31
|
+
} from "./config/loader.js";
|
|
32
|
+
export { applyRunOverrides } from "./config/overrides.js";
|
|
33
|
+
export { resolveRole, validateConfig } from "./config/role-resolver.js";
|
|
34
|
+
export { safeParseConfig, formatConfigIssues } from "./config/schema.js";
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain Knowledge loader.
|
|
3
|
+
* Scans .karajan/domains/ (project-local) and ~/.karajan/domains/ (user-global)
|
|
4
|
+
* for DOMAIN.md files with YAML frontmatter.
|
|
5
|
+
* Project-local domains override user-global domains by directory name.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readdir, readFile, mkdir, writeFile, stat } from "node:fs/promises";
|
|
9
|
+
import { join, isAbsolute } from "node:path";
|
|
10
|
+
import yaml from "js-yaml";
|
|
11
|
+
import { getKarajanHome } from "../utils/paths.js";
|
|
12
|
+
|
|
13
|
+
const DOMAIN_FILE = "DOMAIN.md";
|
|
14
|
+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse a DOMAIN.md file into a structured DomainFile object.
|
|
18
|
+
* @param {string} filePath — absolute path to the DOMAIN.md file
|
|
19
|
+
* @returns {Promise<DomainFile|null>} parsed domain or null on error
|
|
20
|
+
*
|
|
21
|
+
* @typedef {Object} DomainSection
|
|
22
|
+
* @property {string} heading — section heading (e.g. "Core Concepts")
|
|
23
|
+
* @property {string} content — section body (markdown)
|
|
24
|
+
*
|
|
25
|
+
* @typedef {Object} DomainFile
|
|
26
|
+
* @property {string} name
|
|
27
|
+
* @property {string} description
|
|
28
|
+
* @property {string[]} tags
|
|
29
|
+
* @property {string} version
|
|
30
|
+
* @property {string} author
|
|
31
|
+
* @property {string} visibility — "private" | "public"
|
|
32
|
+
* @property {Array<{type: string, note?: string}>} sources
|
|
33
|
+
* @property {string} content — full markdown body (after frontmatter)
|
|
34
|
+
* @property {DomainSection[]} sections — parsed markdown sections
|
|
35
|
+
* @property {string} filePath — original file path
|
|
36
|
+
* @property {string} [origin] — "project" | "user" (set by loadDomains)
|
|
37
|
+
*/
|
|
38
|
+
export async function parseDomainFile(filePath) {
|
|
39
|
+
let raw;
|
|
40
|
+
try {
|
|
41
|
+
raw = await readFile(filePath, "utf-8");
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const match = raw.match(FRONTMATTER_RE);
|
|
47
|
+
if (!match) return null;
|
|
48
|
+
|
|
49
|
+
let meta;
|
|
50
|
+
try {
|
|
51
|
+
meta = yaml.load(match[1]);
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!meta || typeof meta !== "object" || !meta.name) return null;
|
|
57
|
+
|
|
58
|
+
const content = match[2].trim();
|
|
59
|
+
const sections = parseSections(content);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
name: meta.name,
|
|
63
|
+
description: meta.description || "",
|
|
64
|
+
tags: Array.isArray(meta.tags) ? meta.tags : [],
|
|
65
|
+
version: meta.version || "0.0.0",
|
|
66
|
+
author: meta.author || "",
|
|
67
|
+
visibility: meta.visibility || "private",
|
|
68
|
+
sources: Array.isArray(meta.sources) ? meta.sources : [],
|
|
69
|
+
content,
|
|
70
|
+
sections,
|
|
71
|
+
filePath
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Split markdown content into sections by ## headings.
|
|
77
|
+
* @param {string} content
|
|
78
|
+
* @returns {DomainSection[]}
|
|
79
|
+
*/
|
|
80
|
+
function parseSections(content) {
|
|
81
|
+
const sections = [];
|
|
82
|
+
const lines = content.split("\n");
|
|
83
|
+
let current = null;
|
|
84
|
+
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
const headingMatch = line.match(/^##\s+(.+)/);
|
|
87
|
+
if (headingMatch) {
|
|
88
|
+
if (current) {
|
|
89
|
+
current.content = current.content.trim();
|
|
90
|
+
sections.push(current);
|
|
91
|
+
}
|
|
92
|
+
current = { heading: headingMatch[1].trim(), content: "" };
|
|
93
|
+
} else if (current) {
|
|
94
|
+
current.content += line + "\n";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (current) {
|
|
99
|
+
current.content = current.content.trim();
|
|
100
|
+
sections.push(current);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return sections;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Auto-detect project context files (README.md, CLAUDE.md) as lightweight domains.
|
|
108
|
+
* Used as fallback when no explicit .karajan/domains/ are configured.
|
|
109
|
+
*
|
|
110
|
+
* @param {string} projectDir — absolute path to project root
|
|
111
|
+
* @returns {Promise<DomainFile[]>}
|
|
112
|
+
*/
|
|
113
|
+
export async function autoDetectDomains(projectDir) {
|
|
114
|
+
if (!projectDir) return [];
|
|
115
|
+
|
|
116
|
+
const candidates = ["README.md", "CLAUDE.md", "docs/README.md"];
|
|
117
|
+
const detected = [];
|
|
118
|
+
|
|
119
|
+
for (const rel of candidates) {
|
|
120
|
+
const filePath = join(projectDir, rel);
|
|
121
|
+
let content;
|
|
122
|
+
try {
|
|
123
|
+
content = await readFile(filePath, "utf-8");
|
|
124
|
+
} catch { continue; }
|
|
125
|
+
|
|
126
|
+
if (!content || content.trim().length < 20) continue;
|
|
127
|
+
|
|
128
|
+
// Truncate to first 4000 chars to avoid bloating context
|
|
129
|
+
const trimmed = content.length > 4000 ? content.slice(0, 4000) + "\n\n[...truncated]" : content;
|
|
130
|
+
|
|
131
|
+
detected.push({
|
|
132
|
+
name: `auto:${rel}`,
|
|
133
|
+
description: `Auto-detected from ${rel}`,
|
|
134
|
+
tags: [],
|
|
135
|
+
version: "0.0.0",
|
|
136
|
+
author: "",
|
|
137
|
+
visibility: "private",
|
|
138
|
+
sources: [{ type: "auto-detect", note: rel }],
|
|
139
|
+
content: trimmed,
|
|
140
|
+
sections: parseSections(trimmed),
|
|
141
|
+
filePath,
|
|
142
|
+
origin: "auto-detect"
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return detected;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Load all domains from project-local and user-global directories.
|
|
151
|
+
* Falls back to auto-detecting README.md/CLAUDE.md when no explicit domains found.
|
|
152
|
+
* Project-local domains override user-global domains when directory names match.
|
|
153
|
+
*
|
|
154
|
+
* @param {string|null} projectDir — absolute path to project root (null = user-global only)
|
|
155
|
+
* @returns {Promise<DomainFile[]>}
|
|
156
|
+
*/
|
|
157
|
+
export async function loadDomains(projectDir) {
|
|
158
|
+
const userDir = join(getKarajanHome(), "domains");
|
|
159
|
+
const projectDomainDir = projectDir ? join(projectDir, ".karajan", "domains") : null;
|
|
160
|
+
|
|
161
|
+
// Load user-global first, then project-local overrides
|
|
162
|
+
const userDomains = await scanDomainDir(userDir, "user");
|
|
163
|
+
const projectDomains = projectDomainDir ? await scanDomainDir(projectDomainDir, "project") : [];
|
|
164
|
+
|
|
165
|
+
// Build a map keyed by directory name for merge.
|
|
166
|
+
const merged = new Map();
|
|
167
|
+
|
|
168
|
+
for (const { dirName, domain } of userDomains) {
|
|
169
|
+
merged.set(dirName, domain);
|
|
170
|
+
}
|
|
171
|
+
for (const { dirName, domain } of projectDomains) {
|
|
172
|
+
merged.set(dirName, domain); // project overrides user
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const explicit = Array.from(merged.values());
|
|
176
|
+
|
|
177
|
+
// Fallback: auto-detect README.md, CLAUDE.md when no explicit domains found
|
|
178
|
+
if (explicit.length === 0 && projectDir) {
|
|
179
|
+
return autoDetectDomains(projectDir);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return explicit;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Scan a single domains directory for DOMAIN.md files.
|
|
187
|
+
* @param {string} dir — absolute path to a domains/ directory
|
|
188
|
+
* @param {string} origin — "project" | "user"
|
|
189
|
+
* @returns {Promise<Array<{dirName: string, domain: DomainFile}>>}
|
|
190
|
+
*/
|
|
191
|
+
async function scanDomainDir(dir, origin) {
|
|
192
|
+
let entries;
|
|
193
|
+
try {
|
|
194
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
195
|
+
} catch {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const results = [];
|
|
200
|
+
|
|
201
|
+
for (const entry of entries) {
|
|
202
|
+
if (!entry.isDirectory()) continue;
|
|
203
|
+
|
|
204
|
+
const domainPath = join(dir, entry.name, DOMAIN_FILE);
|
|
205
|
+
const domain = await parseDomainFile(domainPath);
|
|
206
|
+
|
|
207
|
+
if (domain) {
|
|
208
|
+
domain.origin = origin;
|
|
209
|
+
results.push({ dirName: entry.name, domain });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return results;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Persist an inline domain (text or file path) to .karajan/domains/inline/DOMAIN.md.
|
|
218
|
+
* If domainInput is a path to an existing .md file, reads its content.
|
|
219
|
+
* Otherwise treats it as inline text.
|
|
220
|
+
*
|
|
221
|
+
* @param {string} domainInput — inline text or absolute path to a .md file
|
|
222
|
+
* @param {string} projectDir — project root
|
|
223
|
+
*/
|
|
224
|
+
export async function persistInlineDomain(domainInput, projectDir) {
|
|
225
|
+
if (!domainInput || !projectDir) return;
|
|
226
|
+
|
|
227
|
+
let content = domainInput;
|
|
228
|
+
|
|
229
|
+
// If it looks like a file path, try to read it
|
|
230
|
+
if (isAbsolute(domainInput) && domainInput.endsWith(".md")) {
|
|
231
|
+
try {
|
|
232
|
+
const s = await stat(domainInput);
|
|
233
|
+
if (s.isFile()) {
|
|
234
|
+
content = await readFile(domainInput, "utf-8");
|
|
235
|
+
}
|
|
236
|
+
} catch { /* treat as inline text */ }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const domainDir = join(projectDir, ".karajan", "domains", "inline");
|
|
240
|
+
await mkdir(domainDir, { recursive: true });
|
|
241
|
+
|
|
242
|
+
const domainFile = join(domainDir, DOMAIN_FILE);
|
|
243
|
+
const wrapped = `---\nname: inline-domain\ndescription: Domain knowledge provided via --domain parameter\ntags: []\nversion: "0.0.0"\n---\n\n${content}\n`;
|
|
244
|
+
await writeFile(domainFile, wrapped, "utf-8");
|
|
245
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local domain registry.
|
|
3
|
+
* Tracks installed domains in ~/.karajan/domain-registry.json.
|
|
4
|
+
* Provides search and filtering by tags/hints.
|
|
5
|
+
* Interface prepared for future remote registry extension.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { getKarajanHome } from "../utils/paths.js";
|
|
11
|
+
|
|
12
|
+
const REGISTRY_FILE = "domain-registry.json";
|
|
13
|
+
const SCHEMA_VERSION = 1;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} RegistryEntry
|
|
17
|
+
* @property {string} name
|
|
18
|
+
* @property {string} [description]
|
|
19
|
+
* @property {string} version
|
|
20
|
+
* @property {string} source — "local" (future: "remote")
|
|
21
|
+
* @property {string} installedAt — ISO timestamp
|
|
22
|
+
* @property {string} filePath — absolute path to DOMAIN.md
|
|
23
|
+
* @property {string[]} tags
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export class DomainRegistry {
|
|
27
|
+
/**
|
|
28
|
+
* @param {Record<string, RegistryEntry>} domains
|
|
29
|
+
*/
|
|
30
|
+
constructor(domains = {}) {
|
|
31
|
+
this._domains = domains;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Load registry from disk. Creates empty registry if file is missing or corrupt.
|
|
36
|
+
* @returns {Promise<DomainRegistry>}
|
|
37
|
+
*/
|
|
38
|
+
static async load() {
|
|
39
|
+
const filePath = join(getKarajanHome(), REGISTRY_FILE);
|
|
40
|
+
try {
|
|
41
|
+
const raw = await readFile(filePath, "utf-8");
|
|
42
|
+
const data = JSON.parse(raw);
|
|
43
|
+
if (data?.schemaVersion === SCHEMA_VERSION && data.domains) {
|
|
44
|
+
return new DomainRegistry(data.domains);
|
|
45
|
+
}
|
|
46
|
+
} catch { /* file missing, corrupt, or wrong schema */ }
|
|
47
|
+
return new DomainRegistry();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Persist registry to disk.
|
|
52
|
+
*/
|
|
53
|
+
async save() {
|
|
54
|
+
const home = getKarajanHome();
|
|
55
|
+
await mkdir(home, { recursive: true });
|
|
56
|
+
const filePath = join(home, REGISTRY_FILE);
|
|
57
|
+
const data = {
|
|
58
|
+
schemaVersion: SCHEMA_VERSION,
|
|
59
|
+
domains: this._domains
|
|
60
|
+
};
|
|
61
|
+
await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Register or update a domain in the registry.
|
|
66
|
+
* @param {{name: string, version?: string, tags?: string[], filePath?: string, description?: string}} domainFile
|
|
67
|
+
*/
|
|
68
|
+
register(domainFile) {
|
|
69
|
+
this._domains[domainFile.name] = {
|
|
70
|
+
name: domainFile.name,
|
|
71
|
+
description: domainFile.description || "",
|
|
72
|
+
version: domainFile.version || "0.0.0",
|
|
73
|
+
source: "local",
|
|
74
|
+
installedAt: new Date().toISOString(),
|
|
75
|
+
filePath: domainFile.filePath || "",
|
|
76
|
+
tags: domainFile.tags || []
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* List domains, optionally filtered by tags and/or free-text query.
|
|
82
|
+
* @param {{tags?: string[], query?: string}} [filters]
|
|
83
|
+
* @returns {RegistryEntry[]}
|
|
84
|
+
*/
|
|
85
|
+
list(filters = {}) {
|
|
86
|
+
let entries = Object.values(this._domains);
|
|
87
|
+
|
|
88
|
+
if (filters.tags?.length) {
|
|
89
|
+
const tagSet = new Set(filters.tags.map(t => t.toLowerCase()));
|
|
90
|
+
entries = entries.filter(e =>
|
|
91
|
+
e.tags?.some(t => tagSet.has(t.toLowerCase()))
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (filters.query) {
|
|
96
|
+
const q = filters.query.toLowerCase();
|
|
97
|
+
entries = entries.filter(e =>
|
|
98
|
+
(e.name || "").toLowerCase().includes(q) ||
|
|
99
|
+
(e.description || "").toLowerCase().includes(q)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return entries;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Search domains by triage hints.
|
|
108
|
+
* Matches hints against tags, name and description.
|
|
109
|
+
* Returns results ranked by number of matching hints.
|
|
110
|
+
* @param {string[]} hints — keywords from triage domainHints
|
|
111
|
+
* @returns {RegistryEntry[]}
|
|
112
|
+
*/
|
|
113
|
+
search(hints) {
|
|
114
|
+
if (!hints?.length) return [];
|
|
115
|
+
|
|
116
|
+
const lowerHints = hints.map(h => h.toLowerCase());
|
|
117
|
+
const entries = Object.values(this._domains);
|
|
118
|
+
const scored = [];
|
|
119
|
+
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
const searchable = [
|
|
122
|
+
entry.name || "",
|
|
123
|
+
entry.description || "",
|
|
124
|
+
...(entry.tags || [])
|
|
125
|
+
].join(" ").toLowerCase();
|
|
126
|
+
|
|
127
|
+
let score = 0;
|
|
128
|
+
for (const hint of lowerHints) {
|
|
129
|
+
if (searchable.includes(hint)) score++;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (score > 0) {
|
|
133
|
+
scored.push({ entry, score });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
scored.sort((a, b) => b.score - a.score);
|
|
138
|
+
return scored.map(s => s.entry);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain context synthesizer.
|
|
3
|
+
* Filters relevant sections from domain files based on task + hints,
|
|
4
|
+
* and compacts the output to stay within a token budget.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const DEFAULT_MAX_TOKENS = 4000;
|
|
8
|
+
const CHARS_PER_TOKEN = 4;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Synthesize a domain context string from selected domains,
|
|
12
|
+
* filtered by relevance to the task and domain hints.
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} params
|
|
15
|
+
* @param {string} params.task — task description
|
|
16
|
+
* @param {string[]} params.domainHints — keywords from triage
|
|
17
|
+
* @param {import('./domain-loader.js').DomainFile[]|null} params.selectedDomains
|
|
18
|
+
* @param {number} [params.maxTokens=4000] — token budget (0 = unlimited)
|
|
19
|
+
* @returns {string} synthesized markdown context or empty string
|
|
20
|
+
*/
|
|
21
|
+
export function synthesizeDomainContext({ task, domainHints = [], selectedDomains, maxTokens = DEFAULT_MAX_TOKENS }) {
|
|
22
|
+
if (!selectedDomains?.length) return "";
|
|
23
|
+
|
|
24
|
+
const keywords = buildKeywords(task, domainHints);
|
|
25
|
+
const domainBlocks = [];
|
|
26
|
+
|
|
27
|
+
for (const domain of selectedDomains) {
|
|
28
|
+
const sections = domain.sections || [];
|
|
29
|
+
if (!sections.length) continue;
|
|
30
|
+
|
|
31
|
+
// Score all sections but include them all (domain was explicitly selected).
|
|
32
|
+
// Sort by relevance so highest-scoring sections survive token truncation.
|
|
33
|
+
const scored = sections
|
|
34
|
+
.map(section => ({
|
|
35
|
+
section,
|
|
36
|
+
score: scoreSection(section, keywords)
|
|
37
|
+
}))
|
|
38
|
+
.sort((a, b) => b.score - a.score);
|
|
39
|
+
|
|
40
|
+
const selected = scored.map(s => s.section);
|
|
41
|
+
|
|
42
|
+
const sectionMd = selected
|
|
43
|
+
.map(s => `#### ${s.heading}\n${s.content}`)
|
|
44
|
+
.join("\n\n");
|
|
45
|
+
|
|
46
|
+
domainBlocks.push(`### ${domain.name}\n\n${sectionMd}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!domainBlocks.length) return "";
|
|
50
|
+
|
|
51
|
+
let result = domainBlocks.join("\n\n---\n\n");
|
|
52
|
+
|
|
53
|
+
// Truncate by token budget if needed
|
|
54
|
+
if (maxTokens > 0) {
|
|
55
|
+
const maxChars = maxTokens * CHARS_PER_TOKEN;
|
|
56
|
+
if (result.length > maxChars) {
|
|
57
|
+
result = truncateBySection(result, maxChars);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build a set of lowercase keywords from task text and domain hints.
|
|
66
|
+
* @param {string} task
|
|
67
|
+
* @param {string[]} hints
|
|
68
|
+
* @returns {string[]}
|
|
69
|
+
*/
|
|
70
|
+
function buildKeywords(task, hints) {
|
|
71
|
+
const words = new Set();
|
|
72
|
+
|
|
73
|
+
// Add domain hints
|
|
74
|
+
for (const h of hints) {
|
|
75
|
+
words.add(h.toLowerCase());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Extract meaningful words from task (>3 chars, skip stop words)
|
|
79
|
+
const stopWords = new Set(["the", "this", "that", "with", "from", "have", "will", "should", "could", "would", "been", "being", "their", "there", "where", "when", "what", "which", "about", "into", "some", "than", "then", "them", "they", "also", "each", "more", "most", "other"]);
|
|
80
|
+
|
|
81
|
+
const taskWords = task
|
|
82
|
+
.toLowerCase()
|
|
83
|
+
.replaceAll(/[^a-z0-9\s-]/g, " ")
|
|
84
|
+
.split(/\s+/)
|
|
85
|
+
.filter(w => w.length > 3 && !stopWords.has(w));
|
|
86
|
+
|
|
87
|
+
for (const w of taskWords) {
|
|
88
|
+
words.add(w);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return Array.from(words);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Score a section by keyword overlap.
|
|
96
|
+
* @param {import('./domain-loader.js').DomainSection} section
|
|
97
|
+
* @param {string[]} keywords
|
|
98
|
+
* @returns {number}
|
|
99
|
+
*/
|
|
100
|
+
function scoreSection(section, keywords) {
|
|
101
|
+
const text = `${section.heading} ${section.content}`.toLowerCase();
|
|
102
|
+
let score = 0;
|
|
103
|
+
for (const kw of keywords) {
|
|
104
|
+
if (text.includes(kw)) score++;
|
|
105
|
+
}
|
|
106
|
+
return score;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Truncate result by dropping trailing sections to fit within maxChars.
|
|
111
|
+
* Preserves complete sections rather than cutting mid-sentence.
|
|
112
|
+
* @param {string} text
|
|
113
|
+
* @param {number} maxChars
|
|
114
|
+
* @returns {string}
|
|
115
|
+
*/
|
|
116
|
+
function truncateBySection(text, maxChars) {
|
|
117
|
+
// Split by section markers (#### or ###)
|
|
118
|
+
const parts = text.split(/(?=^#{3,4}\s)/m);
|
|
119
|
+
let result = "";
|
|
120
|
+
|
|
121
|
+
for (const part of parts) {
|
|
122
|
+
if (result.length + part.length > maxChars && result.length > 0) {
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
result += part;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return result.trim();
|
|
129
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADRs in the repo (AB-H, KJC-TSK-0658) — `kj adr add|list`. Architecture
|
|
3
|
+
* decisions live as numbered markdown under .karajan/adrs/, git-tracked so
|
|
4
|
+
* the whole team (and every brain) inherits them. The architect brief's
|
|
5
|
+
* "check the ADRs before deciding" now has a target on every backend.
|
|
6
|
+
*/
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
|
|
10
|
+
const ADR_DIR = path.join(".karajan", "adrs");
|
|
11
|
+
|
|
12
|
+
const slugify = (t) => t.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-|-$/g, "").slice(0, 60);
|
|
13
|
+
|
|
14
|
+
export async function listAdrs(projectDir) {
|
|
15
|
+
const dir = path.join(projectDir, ADR_DIR);
|
|
16
|
+
let files;
|
|
17
|
+
try { files = await fs.readdir(dir); } catch { return []; }
|
|
18
|
+
const adrs = [];
|
|
19
|
+
for (const f of files.filter((f) => /^\d{4}-.*\.md$/.test(f)).sort()) {
|
|
20
|
+
const text = await fs.readFile(path.join(dir, f), "utf8");
|
|
21
|
+
const title = text.match(/^# (.+)$/m)?.[1] || f;
|
|
22
|
+
const status = text.match(/^Status: (.+)$/m)?.[1] || "accepted";
|
|
23
|
+
adrs.push({ file: path.join(ADR_DIR, f), number: Number(f.slice(0, 4)), title, status });
|
|
24
|
+
}
|
|
25
|
+
return adrs;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function addAdr(projectDir, { title, decision, context = "", consequences = "" }) {
|
|
29
|
+
if (!title?.trim() || !decision?.trim()) {
|
|
30
|
+
throw new Error("kj adr add requires a title and --decision");
|
|
31
|
+
}
|
|
32
|
+
const existing = await listAdrs(projectDir);
|
|
33
|
+
const number = (existing.at(-1)?.number || 0) + 1;
|
|
34
|
+
const file = path.join(ADR_DIR, `${String(number).padStart(4, "0")}-${slugify(title)}.md`);
|
|
35
|
+
const body = [
|
|
36
|
+
`# ${title}`, "",
|
|
37
|
+
`Status: accepted`, `Date: ${new Date().toISOString().slice(0, 10)}`, "",
|
|
38
|
+
...(context ? ["## Context", "", context, ""] : []),
|
|
39
|
+
"## Decision", "", decision, "",
|
|
40
|
+
...(consequences ? ["## Consequences", "", consequences, ""] : []),
|
|
41
|
+
].join("\n");
|
|
42
|
+
await fs.mkdir(path.join(projectDir, ADR_DIR), { recursive: true });
|
|
43
|
+
await fs.writeFile(path.join(projectDir, file), body);
|
|
44
|
+
return { number, file, title };
|
|
45
|
+
}
|