@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,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Karajan-CLI launcher used by the board's "Commands" panel.
|
|
3
|
+
* Wraps the same spawn-detached-with-log pattern that `runPlan`
|
|
4
|
+
* already uses (see plan-mutations.js), but covers the broader set
|
|
5
|
+
* of commands the user wants to trigger from the UI: plan,
|
|
6
|
+
* architect, clean, etc.
|
|
7
|
+
*
|
|
8
|
+
* Security: the command name is matched against an explicit
|
|
9
|
+
* whitelist. Free-form arg arrays are NOT allowed — each command
|
|
10
|
+
* builds its argv from a typed schema. This keeps a curious browser
|
|
11
|
+
* client from doing `node cli.js -e "rm -rf /"` via a crafted POST.
|
|
12
|
+
*
|
|
13
|
+
* Output: each command writes to `~/.karajan/hu-board-runs/
|
|
14
|
+
* cmd-<commandId>.log`, which the existing `/api/plans/:planId/log`
|
|
15
|
+
* endpoint can tail (we generalise it to take a log id, not just a
|
|
16
|
+
* planId, in routes/api.js).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { mkdirSync, openSync } from "node:fs";
|
|
20
|
+
import { join, dirname } from "node:path";
|
|
21
|
+
import { spawn } from "node:child_process";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { randomUUID } from "node:crypto";
|
|
24
|
+
|
|
25
|
+
// Repo root → so we can spawn `node <repo>/src/cli.js …` regardless
|
|
26
|
+
// of whether `kj` is on $PATH for the board process.
|
|
27
|
+
const REPO_ROOT = (() => {
|
|
28
|
+
const here = dirname(fileURLToPath(import.meta.url)); // packages/hu-board/src
|
|
29
|
+
return join(here, "..", "..", ".."); // → repo root
|
|
30
|
+
})();
|
|
31
|
+
const KJ_CLI = join(REPO_ROOT, "src", "cli.js");
|
|
32
|
+
|
|
33
|
+
// Path now lives in db.js::getHuBoardRunsDir() — KJC-TSK-0421
|
|
34
|
+
// unification. The local helper is gone; callers import directly.
|
|
35
|
+
import { getHuBoardRunsDir } from "./db.js";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Whitelist + argv builders. Each entry maps a logical command name
|
|
39
|
+
* (`plan`, `architect`, `clean`) to a function that turns the
|
|
40
|
+
* board-side input into a CLI argv. Adding a new command means: add
|
|
41
|
+
* an entry here and an entry in the board UI's `COMMANDS` table.
|
|
42
|
+
*
|
|
43
|
+
* Why typed builders instead of a generic args array: the browser
|
|
44
|
+
* client can't smuggle arbitrary flags. Anything that's not in the
|
|
45
|
+
* whitelist is rejected with 400.
|
|
46
|
+
*
|
|
47
|
+
* Each builder returns `{ args, projectDir? }`. `projectDir` is the
|
|
48
|
+
* cwd the spawn should use (defaults to the board's cwd).
|
|
49
|
+
*/
|
|
50
|
+
const COMMAND_BUILDERS = {
|
|
51
|
+
plan: (input) => {
|
|
52
|
+
const args = ["plan"];
|
|
53
|
+
if (input.taskFile) args.push("--task-file", String(input.taskFile));
|
|
54
|
+
if (input.context) args.push("--context", String(input.context));
|
|
55
|
+
if (input.quick) args.push("--quick");
|
|
56
|
+
if (input.noTestsSynth) args.push("--no-tests-synth");
|
|
57
|
+
if (input.noPlanReview) args.push("--no-plan-review");
|
|
58
|
+
// Positional task goes last when no --task-file was given.
|
|
59
|
+
if (!input.taskFile && input.task) args.push(String(input.task));
|
|
60
|
+
return { args, projectDir: input.projectDir };
|
|
61
|
+
},
|
|
62
|
+
architect: (input) => {
|
|
63
|
+
const args = ["architect"];
|
|
64
|
+
if (input.taskFile) args.push("--task-file", String(input.taskFile));
|
|
65
|
+
if (input.context) args.push("--context", String(input.context));
|
|
66
|
+
if (!input.taskFile && input.task) args.push(String(input.task));
|
|
67
|
+
return { args, projectDir: input.projectDir };
|
|
68
|
+
},
|
|
69
|
+
clean: (input) => {
|
|
70
|
+
const args = ["clean", "--yes"]; // never prompt; UI confirms ahead
|
|
71
|
+
if (input.nuke) args.push("--nuke");
|
|
72
|
+
if (input.dryRun) args.push("--dry-run");
|
|
73
|
+
return { args };
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Spawn the requested CLI command as a detached child. Returns
|
|
79
|
+
* `{ ok, commandId, pid, logPath, argv }` so the caller can offer
|
|
80
|
+
* the log to the user via the existing log viewer.
|
|
81
|
+
*
|
|
82
|
+
* Test seam: `KJ_RUN_SPAWN_MODE=echo` short-circuits the spawn so
|
|
83
|
+
* vitest doesn't actually boot the orchestrator.
|
|
84
|
+
*
|
|
85
|
+
* @param {object} args
|
|
86
|
+
* @param {string} args.command - one of the COMMAND_BUILDERS keys
|
|
87
|
+
* @param {object} [args.input]
|
|
88
|
+
* @returns {{ ok: boolean, commandId?: string, pid?: number,
|
|
89
|
+
* logPath?: string, argv?: string[], error?: string }}
|
|
90
|
+
*/
|
|
91
|
+
export function runKjCommand({ command, input = {} } = {}) {
|
|
92
|
+
const build = COMMAND_BUILDERS[command];
|
|
93
|
+
if (!build) {
|
|
94
|
+
return { ok: false, error: `Unsupported command: ${command}. Allowed: ${Object.keys(COMMAND_BUILDERS).join(", ")}` };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let built;
|
|
98
|
+
try {
|
|
99
|
+
built = build(input);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
return { ok: false, error: `Bad input for "${command}": ${err.message}` };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const dir = getHuBoardRunsDir();
|
|
105
|
+
mkdirSync(dir, { recursive: true });
|
|
106
|
+
const commandId = `cmd-${command}-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
107
|
+
const logPath = join(dir, `${commandId}.log`);
|
|
108
|
+
const argv = [KJ_CLI, ...built.args];
|
|
109
|
+
|
|
110
|
+
if (process.env.KJ_RUN_SPAWN_MODE === "echo") {
|
|
111
|
+
return { ok: true, commandId, pid: 0, logPath, argv: [process.execPath, ...argv] };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const out = openSync(logPath, "a");
|
|
115
|
+
const err = openSync(logPath, "a");
|
|
116
|
+
const child = spawn(process.execPath, argv, {
|
|
117
|
+
cwd: built.projectDir || process.cwd(),
|
|
118
|
+
detached: true,
|
|
119
|
+
stdio: ["ignore", out, err],
|
|
120
|
+
// KJ_INSIDE_BOARD: lets `kj clean --nuke` skip SIGTERMing the
|
|
121
|
+
// board (it'd kill the page the user is looking at). Other
|
|
122
|
+
// future "is this run hosted by the board" branches can read
|
|
123
|
+
// the same flag.
|
|
124
|
+
env: { ...process.env, CLAUDECODE: undefined, KJ_INSIDE_BOARD: "1" },
|
|
125
|
+
});
|
|
126
|
+
child.unref();
|
|
127
|
+
return { ok: true, commandId, pid: child.pid ?? 0, logPath };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Test helper: list of command names exposed. */
|
|
131
|
+
export function listSupportedCommands() {
|
|
132
|
+
return Object.keys(COMMAND_BUILDERS);
|
|
133
|
+
}
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read / write the global Karajan config (~/.karajan/kj.config.yml)
|
|
3
|
+
* from the board.
|
|
4
|
+
*
|
|
5
|
+
* The board is a Node Express server that the user already has
|
|
6
|
+
* running, so it's the natural place to expose a "settings" page —
|
|
7
|
+
* no extra terminal trip, no risk of malformed YAML, plain Spanish
|
|
8
|
+
* labels for non-technical users.
|
|
9
|
+
*
|
|
10
|
+
* Design notes:
|
|
11
|
+
*
|
|
12
|
+
* - The yml may contain dozens of fields (legacy, advanced, future
|
|
13
|
+
* experiments). The board only EXPOSES + EDITS a small whitelist
|
|
14
|
+
* that maps to the labels the UI shows. Everything else is read,
|
|
15
|
+
* preserved verbatim, and written back unchanged. So a power
|
|
16
|
+
* user editing the file by hand never loses keys when they later
|
|
17
|
+
* touch the modal.
|
|
18
|
+
*
|
|
19
|
+
* - Writes go through a tmp-file + atomic rename so a partial
|
|
20
|
+
* write never leaves the user with a corrupt yml.
|
|
21
|
+
*
|
|
22
|
+
* - A `.bak` copy is kept of the previous content so the user can
|
|
23
|
+
* recover from a botched save outside the modal (rare but
|
|
24
|
+
* irrecoverable otherwise).
|
|
25
|
+
*
|
|
26
|
+
* - VITEST aware: when KJ_HOME is set we honour it (every test
|
|
27
|
+
* harness uses tmp dirs already). When not set we use
|
|
28
|
+
* `~/.karajan/kj.config.yml` like the rest of Karajan does.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, renameSync } from 'node:fs';
|
|
32
|
+
import { join, dirname } from 'node:path';
|
|
33
|
+
import yaml from 'js-yaml';
|
|
34
|
+
import { getKjHome } from './db.js';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* v2.30.0 PR4 — scope resolver.
|
|
38
|
+
*
|
|
39
|
+
* 'global' → ~/.karajan/kj.config.yml (mismo path que el resto de
|
|
40
|
+
* Karajan; afecta a todos los proyectos).
|
|
41
|
+
* 'project' → <projectDir>/.karajan/kj.config.yml (override por
|
|
42
|
+
* proyecto; cuando existe, el loader del padre lo prefiere
|
|
43
|
+
* sobre el global).
|
|
44
|
+
*
|
|
45
|
+
* projectDir se resuelve desde `KJ_PROJECT_DIR` (ya usado por
|
|
46
|
+
* journal-parser.js) con fallback a `process.cwd()` — el board se
|
|
47
|
+
* arranca desde el repo del usuario, así que cwd es el proyecto.
|
|
48
|
+
*/
|
|
49
|
+
export const SCOPES = ['global', 'project'];
|
|
50
|
+
|
|
51
|
+
function getProjectDir() {
|
|
52
|
+
return process.env.KJ_PROJECT_DIR || process.cwd();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function configPath(scope = 'global') {
|
|
56
|
+
if (scope === 'project') {
|
|
57
|
+
return join(getProjectDir(), '.karajan', 'kj.config.yml');
|
|
58
|
+
}
|
|
59
|
+
// Same path the parent uses (src/utils/paths.js::getKarajanHome).
|
|
60
|
+
// KJC-TSK-0420: delegate to db.js::getKjHome — gives us KARAJAN_HOME
|
|
61
|
+
// priority + KJ_HOME fallback + VITEST guard from one place, instead
|
|
62
|
+
// of duplicating the precedence here.
|
|
63
|
+
return join(getKjHome(), 'kj.config.yml');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function backupPath(scope = 'global') { return `${configPath(scope)}.bak`; }
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* v2.30.0 — Categorías para agrupar los campos en el modal. El backend
|
|
70
|
+
* sólo declara la metadata (id, label, icon, order). El front (app.js)
|
|
71
|
+
* itera sobre EDITABLE_FIELDS, agrupa por `f.category` y dibuja un
|
|
72
|
+
* <h3> por sección. Si un campo nuevo no declara categoría, cae en
|
|
73
|
+
* "Otros" (defensivo: no se pierde aunque se nos olvide etiquetarlo).
|
|
74
|
+
*/
|
|
75
|
+
export const CATEGORIES = [
|
|
76
|
+
{ id: 'agents', label: 'Agentes y modelos', icon: '🤖', order: 1 },
|
|
77
|
+
{ id: 'pipeline', label: 'Roles del pipeline', icon: '🔌', order: 2 },
|
|
78
|
+
{ id: 'rag', label: 'RAG (búsqueda de contexto)', icon: '📚', order: 3 },
|
|
79
|
+
{ id: 'session', label: 'Tiempos de sesión', icon: '⏱', order: 4 },
|
|
80
|
+
{ id: 'quality', label: 'Calidad', icon: '✅', order: 5 },
|
|
81
|
+
// KJC-PRP-0002 PR5: team-shared HU board controls. Today only carries
|
|
82
|
+
// the conflict-resolution policy when a planId lives in both
|
|
83
|
+
// ~/.karajan/plans/ (per-user) and <projectDir>/.karajan-shared/plans/
|
|
84
|
+
// (team-committed). Will grow as more board-level multi-user knobs land.
|
|
85
|
+
{ id: 'huBoard', label: 'HU Board (equipo)', icon: '🔗', order: 6 },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Whitelist of fields the board's modal exposes. Everything else
|
|
90
|
+
* stays untouched in the yml. Each entry says where in the yml the
|
|
91
|
+
* field lives (`path`) and how to render it for the UI (`type`,
|
|
92
|
+
* `label`, `options` for selects, `category` for grouping).
|
|
93
|
+
*
|
|
94
|
+
* The path is a dotted notation we resolve manually (so we never
|
|
95
|
+
* need to take a runtime dep on lodash for `_.get`/`_.set`).
|
|
96
|
+
*/
|
|
97
|
+
export const EDITABLE_FIELDS = [
|
|
98
|
+
{
|
|
99
|
+
key: 'coder',
|
|
100
|
+
path: 'coder',
|
|
101
|
+
category: 'agents',
|
|
102
|
+
type: 'select',
|
|
103
|
+
label: 'Agente para el rol "coder"',
|
|
104
|
+
help: 'Quién escribe el código. Claude es el más fiable, Codex es más rápido.',
|
|
105
|
+
options: ['claude', 'codex', 'gemini', 'aider'],
|
|
106
|
+
default: 'claude',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: 'reviewer',
|
|
110
|
+
path: 'reviewer',
|
|
111
|
+
category: 'agents',
|
|
112
|
+
type: 'select',
|
|
113
|
+
label: 'Agente para el rol "reviewer"',
|
|
114
|
+
help: 'Quién revisa el código del coder. Conviene que sea un agente distinto al coder para detectar errores.',
|
|
115
|
+
options: ['claude', 'codex', 'gemini', 'aider'],
|
|
116
|
+
default: 'codex',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
key: 'modelMode',
|
|
120
|
+
path: '_modelMode_virtual',
|
|
121
|
+
category: 'agents',
|
|
122
|
+
type: 'select',
|
|
123
|
+
label: 'Estrategia de modelos',
|
|
124
|
+
help: 'Auto = Karajan elige el modelo según la complejidad (haiku/sonnet/opus). Sonnet siempre = barato, ideal para pruebas. Opus siempre = caro, máxima calidad.',
|
|
125
|
+
options: ['auto', 'sonnet', 'opus'],
|
|
126
|
+
default: 'auto',
|
|
127
|
+
// Virtual field: doesn't map 1:1 to a yml key. Translate when
|
|
128
|
+
// reading + writing (see readConfig + writeConfigPatch).
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
key: 'maxIterationMinutes',
|
|
132
|
+
path: 'session.max_iteration_minutes',
|
|
133
|
+
category: 'session',
|
|
134
|
+
type: 'number',
|
|
135
|
+
label: 'Tope de minutos por iteración',
|
|
136
|
+
help: 'Si una iteración (coder + reviewer) supera este tiempo, Karajan la corta y Solomon decide qué hacer.',
|
|
137
|
+
min: 1,
|
|
138
|
+
max: 120,
|
|
139
|
+
default: 30,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
key: 'maxTotalMinutes',
|
|
143
|
+
path: 'session.max_total_minutes',
|
|
144
|
+
category: 'session',
|
|
145
|
+
type: 'number',
|
|
146
|
+
label: 'Tope de minutos total por sesión',
|
|
147
|
+
help: 'Tope absoluto del wall-clock de la sesión. Si se llega aquí, Karajan para aunque queden HUs.',
|
|
148
|
+
min: 5,
|
|
149
|
+
max: 480,
|
|
150
|
+
default: 120,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
key: 'sonarEnabled',
|
|
154
|
+
path: 'sonarqube.enabled',
|
|
155
|
+
category: 'quality',
|
|
156
|
+
type: 'boolean',
|
|
157
|
+
label: 'Activar análisis SonarQube',
|
|
158
|
+
help: 'Pasa el código por SonarQube y bloquea si el quality gate falla. Requiere SonarQube corriendo.',
|
|
159
|
+
default: false,
|
|
160
|
+
},
|
|
161
|
+
// v2.30.0 — pipeline role toggles. Cada uno controla si una etapa
|
|
162
|
+
// del pipeline se ejecuta. Brain está OFF por defecto porque sigue
|
|
163
|
+
// siendo opt-in (v2 feature); el resto reflejan los defaults de
|
|
164
|
+
// src/config/defaults.js.
|
|
165
|
+
{
|
|
166
|
+
key: 'plannerEnabled',
|
|
167
|
+
path: 'pipeline.planner.enabled',
|
|
168
|
+
category: 'pipeline',
|
|
169
|
+
type: 'boolean',
|
|
170
|
+
label: 'Activar rol planner',
|
|
171
|
+
help: 'Genera un plan de implementación antes de codificar. Útil en tareas con >2 archivos o devPoints ≥ 3.',
|
|
172
|
+
default: true,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
key: 'researcherEnabled',
|
|
176
|
+
path: 'pipeline.researcher.enabled',
|
|
177
|
+
category: 'pipeline',
|
|
178
|
+
type: 'boolean',
|
|
179
|
+
label: 'Activar rol researcher',
|
|
180
|
+
help: 'Investiga la codebase antes del coder (lectura de archivos clave, patrones existentes).',
|
|
181
|
+
default: true,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
key: 'architectEnabled',
|
|
185
|
+
path: 'pipeline.architect.enabled',
|
|
186
|
+
category: 'pipeline',
|
|
187
|
+
type: 'boolean',
|
|
188
|
+
label: 'Activar rol architect',
|
|
189
|
+
help: 'Diseña la arquitectura (capas, patrones, contratos) antes de codificar. Recomendado en cambios estructurales.',
|
|
190
|
+
default: true,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
key: 'testerEnabled',
|
|
194
|
+
path: 'pipeline.tester.enabled',
|
|
195
|
+
category: 'pipeline',
|
|
196
|
+
type: 'boolean',
|
|
197
|
+
label: 'Activar rol tester',
|
|
198
|
+
help: 'Genera tests automáticos durante o después del coder. Refuerza la calidad.',
|
|
199
|
+
default: true,
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
key: 'securityEnabled',
|
|
203
|
+
path: 'pipeline.security.enabled',
|
|
204
|
+
category: 'pipeline',
|
|
205
|
+
type: 'boolean',
|
|
206
|
+
label: 'Activar rol security',
|
|
207
|
+
help: 'Audita el código contra OWASP-style issues, secret leaks, dependencias vulnerables.',
|
|
208
|
+
default: true,
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
key: 'refactorerEnabled',
|
|
212
|
+
path: 'pipeline.refactorer.enabled',
|
|
213
|
+
category: 'pipeline',
|
|
214
|
+
type: 'boolean',
|
|
215
|
+
label: 'Activar rol refactorer',
|
|
216
|
+
help: 'Pasa una iteración de limpieza tras el coder (extract function, rename, etc.).',
|
|
217
|
+
default: false,
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
key: 'impeccableEnabled',
|
|
221
|
+
path: 'pipeline.impeccable.enabled',
|
|
222
|
+
category: 'pipeline',
|
|
223
|
+
type: 'boolean',
|
|
224
|
+
label: 'Activar rol impeccable',
|
|
225
|
+
help: 'Auditoría UI/UX automática (Lighthouse, a11y, contraste). Solo útil en frontend.',
|
|
226
|
+
default: false,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
key: 'brainEnabled',
|
|
230
|
+
path: 'pipeline.brain.enabled',
|
|
231
|
+
category: 'pipeline',
|
|
232
|
+
type: 'boolean',
|
|
233
|
+
label: 'Activar Karajan Brain (v2)',
|
|
234
|
+
help: 'Orquestador IA central: enriquece feedback, comprime outputs y verifica cambios reales. Opt-in.',
|
|
235
|
+
default: false,
|
|
236
|
+
},
|
|
237
|
+
// v2.30.0 — RAG controls. Cubren los tres ejes que afectan al
|
|
238
|
+
// retrieval en tiempo de pipeline: pre-carga (rag.preload.*),
|
|
239
|
+
// proveedor de embeddings (rag.embedder.provider) y parámetros de
|
|
240
|
+
// búsqueda (rag.search.*, cableados al stage en KJC-TSK-0463).
|
|
241
|
+
{
|
|
242
|
+
key: 'ragPreloadEnabled',
|
|
243
|
+
path: 'rag.preload.enabled',
|
|
244
|
+
category: 'rag',
|
|
245
|
+
type: 'boolean',
|
|
246
|
+
label: 'Activar pre-carga RAG en el pipeline',
|
|
247
|
+
help: 'Si está activo, antes de cada iteración se inyecta contexto del vector store en el prompt del coder.',
|
|
248
|
+
default: false,
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
key: 'ragPreloadTopK',
|
|
252
|
+
path: 'rag.preload.topK',
|
|
253
|
+
category: 'rag',
|
|
254
|
+
type: 'number',
|
|
255
|
+
label: 'Nº de chunks a pre-cargar (topK)',
|
|
256
|
+
help: 'Cuántos fragmentos del vector store se inyectan como contexto. 3-5 funciona bien; subir gasta tokens.',
|
|
257
|
+
min: 1,
|
|
258
|
+
max: 20,
|
|
259
|
+
default: 5,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
key: 'ragPreloadScope',
|
|
263
|
+
path: 'rag.preload.scope',
|
|
264
|
+
category: 'rag',
|
|
265
|
+
type: 'select',
|
|
266
|
+
label: 'Scope de la pre-carga RAG',
|
|
267
|
+
help: '"all" mezcla todo. "code" solo busca en el código indexado, "plans" en planes previos, "onboarding" en el brief del proyecto.',
|
|
268
|
+
options: ['all', 'code', 'plans', 'onboarding'],
|
|
269
|
+
default: 'all',
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
key: 'ragEmbedderProvider',
|
|
273
|
+
path: 'rag.embedder.provider',
|
|
274
|
+
category: 'rag',
|
|
275
|
+
type: 'select',
|
|
276
|
+
label: 'Proveedor de embeddings RAG',
|
|
277
|
+
help: 'Ollama local es gratis. OpenAI/Voyage/Cohere/Mistral son cloud (requieren API key). ONNX corre 100% local con @huggingface/transformers.',
|
|
278
|
+
options: ['ollama', 'openai', 'voyage', 'cohere', 'mistral', 'onnx'],
|
|
279
|
+
default: 'ollama',
|
|
280
|
+
},
|
|
281
|
+
// KJC-TSK-0463 — search-time knobs ahora cableados al stage.
|
|
282
|
+
// mode/alpha controlan el fuse semantic vs keyword; rerank activa el
|
|
283
|
+
// cross-encoder opcional (más calidad, más latencia).
|
|
284
|
+
{
|
|
285
|
+
key: 'ragSearchMode',
|
|
286
|
+
path: 'rag.search.mode',
|
|
287
|
+
category: 'rag',
|
|
288
|
+
type: 'select',
|
|
289
|
+
label: 'Modo de búsqueda RAG',
|
|
290
|
+
help: '"hybrid" mezcla semántico + BM25 (recomendado). "semantic" sólo embeddings. "keyword" sólo BM25 (útil si los embeddings están desafinados).',
|
|
291
|
+
options: ['hybrid', 'semantic', 'keyword'],
|
|
292
|
+
default: 'hybrid',
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
key: 'ragSearchAlpha',
|
|
296
|
+
path: 'rag.search.alpha',
|
|
297
|
+
category: 'rag',
|
|
298
|
+
type: 'number',
|
|
299
|
+
label: 'Alpha del modo hybrid (0-1)',
|
|
300
|
+
help: 'Peso del scoring semántico vs keyword en modo hybrid. 1.0 = sólo semántico, 0.0 = sólo BM25, 0.6 (default) = inclinado a semántico.',
|
|
301
|
+
min: 0,
|
|
302
|
+
max: 1,
|
|
303
|
+
// KJC-BUG-0069 — number input defaults to step=1 (HTML5), so any
|
|
304
|
+
// non-integer default fails validity. Allow any fractional value here.
|
|
305
|
+
step: 'any',
|
|
306
|
+
default: 0.6,
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
key: 'ragSearchRerank',
|
|
310
|
+
path: 'rag.search.rerank',
|
|
311
|
+
category: 'rag',
|
|
312
|
+
type: 'boolean',
|
|
313
|
+
label: 'Activar rerank cross-encoder',
|
|
314
|
+
help: 'Re-puntúa los topK supervivientes con un cross-encoder. Mejora calidad pero añade latencia y requiere modelo extra. Opt-in.',
|
|
315
|
+
default: false,
|
|
316
|
+
},
|
|
317
|
+
// KJC-PRP-0002 PR5: when the board scans plans, it visits both
|
|
318
|
+
// ~/.karajan/plans/<slug>/ (per-user cache) and .karajan-shared/plans/
|
|
319
|
+
// (team copy). If a planId lives in both, the second pass overwrites
|
|
320
|
+
// the first. Before this knob the order was hard-coded shared-wins
|
|
321
|
+
// (per-user → shared) — fine in a team but unsafe for a solo dev who
|
|
322
|
+
// forgets a stale `.karajan-shared/` exists in the repo.
|
|
323
|
+
{
|
|
324
|
+
key: 'huBoardSharedConflictPolicy',
|
|
325
|
+
path: 'huBoard.sharedConflictPolicy',
|
|
326
|
+
category: 'huBoard',
|
|
327
|
+
type: 'select',
|
|
328
|
+
label: 'Política ante plan compartido y local',
|
|
329
|
+
help: '"shared-wins" (recomendado en equipo): la copia en .karajan-shared/ pisa la local. "local-wins" (solo dev individual): tu copia en ~/.karajan/plans/ pisa la compartida — útil si todavía estás iterando antes de publicarla.',
|
|
330
|
+
options: ['shared-wins', 'local-wins'],
|
|
331
|
+
default: 'shared-wins',
|
|
332
|
+
},
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
const ROLES_FOR_MODEL_MODE = [
|
|
336
|
+
'planner', 'architect', 'coder', 'reviewer', 'triage',
|
|
337
|
+
'researcher', 'tester', 'security', 'refactorer', 'impeccable'
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
function getDeep(obj, path) {
|
|
341
|
+
if (!obj || !path) return undefined;
|
|
342
|
+
const parts = path.split('.');
|
|
343
|
+
let cur = obj;
|
|
344
|
+
for (const p of parts) {
|
|
345
|
+
if (cur == null || typeof cur !== 'object') return undefined;
|
|
346
|
+
cur = cur[p];
|
|
347
|
+
}
|
|
348
|
+
return cur;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// KJC-BUG-0076: never walk into Object.prototype via crafted YAML keys
|
|
352
|
+
// (`__proto__.x`, `constructor.prototype.y`, `prototype.z`). Silent skip
|
|
353
|
+
// over a poisoned segment — the rest of the path is untouched.
|
|
354
|
+
const UNSAFE_KEY_RE = /^(?:__proto__|constructor|prototype)$/;
|
|
355
|
+
|
|
356
|
+
function setDeep(obj, path, value) {
|
|
357
|
+
if (typeof path !== 'string') return;
|
|
358
|
+
const parts = path.split('.');
|
|
359
|
+
const last = parts.pop();
|
|
360
|
+
if (UNSAFE_KEY_RE.test(last)) return;
|
|
361
|
+
let cur = obj;
|
|
362
|
+
for (const p of parts) {
|
|
363
|
+
if (UNSAFE_KEY_RE.test(p)) return;
|
|
364
|
+
if (cur[p] == null || typeof cur[p] !== 'object') cur[p] = {};
|
|
365
|
+
cur = cur[p];
|
|
366
|
+
}
|
|
367
|
+
cur[last] = value;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function deleteDeep(obj, path) {
|
|
371
|
+
if (typeof path !== 'string') return;
|
|
372
|
+
const parts = path.split('.');
|
|
373
|
+
const last = parts.pop();
|
|
374
|
+
if (UNSAFE_KEY_RE.test(last)) return;
|
|
375
|
+
let cur = obj;
|
|
376
|
+
for (const p of parts) {
|
|
377
|
+
if (UNSAFE_KEY_RE.test(p)) return;
|
|
378
|
+
if (cur == null || typeof cur !== 'object') return;
|
|
379
|
+
cur = cur[p];
|
|
380
|
+
}
|
|
381
|
+
if (cur && typeof cur === 'object') delete cur[last];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Derive the virtual `modelMode` from the actual yml shape. Looks at
|
|
386
|
+
* roles.<role>.model: if every editable role pins the same model,
|
|
387
|
+
* that's the mode. Otherwise "auto" (the default — Karajan's
|
|
388
|
+
* smart-select picks per role).
|
|
389
|
+
*/
|
|
390
|
+
function deriveModelMode(parsed) {
|
|
391
|
+
const roles = parsed?.roles;
|
|
392
|
+
if (!roles || typeof roles !== 'object') return 'auto';
|
|
393
|
+
const seen = new Set();
|
|
394
|
+
for (const r of ROLES_FOR_MODEL_MODE) {
|
|
395
|
+
const m = roles[r]?.model;
|
|
396
|
+
if (m) seen.add(String(m).toLowerCase());
|
|
397
|
+
}
|
|
398
|
+
if (seen.size === 1) {
|
|
399
|
+
const only = [...seen][0];
|
|
400
|
+
if (only === 'sonnet' || only === 'opus') return only;
|
|
401
|
+
}
|
|
402
|
+
return 'auto';
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Apply the virtual `modelMode` back to the yml: 'auto' deletes the
|
|
407
|
+
* roles.<r>.model overrides; 'sonnet' / 'opus' set them on every
|
|
408
|
+
* editable role. Other yml keys are left alone.
|
|
409
|
+
*/
|
|
410
|
+
function applyModelMode(parsed, mode) {
|
|
411
|
+
if (!parsed.roles) parsed.roles = {};
|
|
412
|
+
if (mode === 'auto') {
|
|
413
|
+
for (const r of ROLES_FOR_MODEL_MODE) {
|
|
414
|
+
if (parsed.roles[r]?.model) delete parsed.roles[r].model;
|
|
415
|
+
if (parsed.roles[r] && Object.keys(parsed.roles[r]).length === 0) delete parsed.roles[r];
|
|
416
|
+
}
|
|
417
|
+
if (Object.keys(parsed.roles).length === 0) delete parsed.roles;
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
for (const r of ROLES_FOR_MODEL_MODE) {
|
|
421
|
+
if (!parsed.roles[r] || typeof parsed.roles[r] !== 'object') parsed.roles[r] = {};
|
|
422
|
+
parsed.roles[r].model = mode;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Read the current config + flatten it into the shape the UI wants:
|
|
428
|
+
* {
|
|
429
|
+
* path: "/abs/path",
|
|
430
|
+
* fields: [{ key, label, help, type, options?, value, default }],
|
|
431
|
+
* raw: <full parsed yml> // power-user inspection
|
|
432
|
+
* }
|
|
433
|
+
*
|
|
434
|
+
* Returns sane defaults when the file doesn't exist yet.
|
|
435
|
+
*/
|
|
436
|
+
export function readConfig({ scope = 'global' } = {}) {
|
|
437
|
+
// PR4 — accept scope. Default 'global' preserves the previous behavior;
|
|
438
|
+
// 'project' reads <projectDir>/.karajan/kj.config.yml.
|
|
439
|
+
if (!SCOPES.includes(scope)) {
|
|
440
|
+
throw new Error(`Scope inválido: ${scope}. Permitidos: ${SCOPES.join(', ')}`);
|
|
441
|
+
}
|
|
442
|
+
const p = configPath(scope);
|
|
443
|
+
let parsed = {};
|
|
444
|
+
let exists = false;
|
|
445
|
+
if (existsSync(p)) {
|
|
446
|
+
try {
|
|
447
|
+
parsed = yaml.load(readFileSync(p, 'utf8'), { json: true }) || {};
|
|
448
|
+
exists = true;
|
|
449
|
+
} catch (err) {
|
|
450
|
+
throw new Error(`No se pudo parsear ${p}: ${err.message}`, { cause: err });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const fields = EDITABLE_FIELDS.map((f) => {
|
|
454
|
+
let value;
|
|
455
|
+
if (f.key === 'modelMode') value = deriveModelMode(parsed);
|
|
456
|
+
else value = getDeep(parsed, f.path);
|
|
457
|
+
if (value === undefined) value = f.default;
|
|
458
|
+
return { ...f, value };
|
|
459
|
+
});
|
|
460
|
+
// v2.30.0 — also expose CATEGORIES so the modal can render grouped
|
|
461
|
+
// sections without re-importing the constant client-side. Ordered
|
|
462
|
+
// by `order` for deterministic UI.
|
|
463
|
+
const categories = [...CATEGORIES].sort((a, b) => (a.order ?? 999) - (b.order ?? 999));
|
|
464
|
+
return { path: p, scope, exists, fields, categories, raw: parsed };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Apply a patch (subset of EDITABLE_FIELDS keys → new values) to the
|
|
469
|
+
* yml. Validates each field; refuses unknown keys. Writes via tmp +
|
|
470
|
+
* rename; keeps a .bak.
|
|
471
|
+
*
|
|
472
|
+
* @returns {{ path: string, written: boolean, applied: object[], errors: string[] }}
|
|
473
|
+
*/
|
|
474
|
+
export function writeConfigPatch(patch, { scope = 'global' } = {}) {
|
|
475
|
+
// PR4 — same shape as readConfig: scope decides target file.
|
|
476
|
+
if (!SCOPES.includes(scope)) {
|
|
477
|
+
return { path: '', scope, written: false, applied: [], errors: [`Scope inválido: ${scope}. Permitidos: ${SCOPES.join(', ')}`] };
|
|
478
|
+
}
|
|
479
|
+
if (!patch || typeof patch !== 'object') {
|
|
480
|
+
return { path: configPath(scope), scope, written: false, applied: [], errors: ['patch debe ser un objeto'] };
|
|
481
|
+
}
|
|
482
|
+
// Read current state (or empty if file doesn't exist yet).
|
|
483
|
+
const p = configPath(scope);
|
|
484
|
+
let parsed = {};
|
|
485
|
+
if (existsSync(p)) {
|
|
486
|
+
try { parsed = yaml.load(readFileSync(p, 'utf8'), { json: true }) || {}; }
|
|
487
|
+
catch (err) { return { path: p, scope, written: false, applied: [], errors: [`No se pudo parsear el yml actual: ${err.message}`] }; }
|
|
488
|
+
}
|
|
489
|
+
const errors = [];
|
|
490
|
+
const applied = [];
|
|
491
|
+
for (const [key, val] of Object.entries(patch)) {
|
|
492
|
+
const field = EDITABLE_FIELDS.find((f) => f.key === key);
|
|
493
|
+
if (!field) { errors.push(`Campo desconocido: ${key}`); continue; }
|
|
494
|
+
|
|
495
|
+
if (field.type === 'select' && !field.options.includes(val)) {
|
|
496
|
+
errors.push(`${key}: valor inválido "${val}". Permitidos: ${field.options.join(', ')}`);
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (field.type === 'number') {
|
|
500
|
+
const n = Number(val);
|
|
501
|
+
if (!Number.isFinite(n)) { errors.push(`${key}: debe ser un número`); continue; }
|
|
502
|
+
if (field.min != null && n < field.min) { errors.push(`${key}: mínimo ${field.min}`); continue; }
|
|
503
|
+
if (field.max != null && n > field.max) { errors.push(`${key}: máximo ${field.max}`); continue; }
|
|
504
|
+
}
|
|
505
|
+
if (field.type === 'boolean' && typeof val !== 'boolean') {
|
|
506
|
+
errors.push(`${key}: debe ser true o false`);
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (field.key === 'modelMode') applyModelMode(parsed, val);
|
|
511
|
+
else setDeep(parsed, field.path, field.type === 'number' ? Number(val) : val);
|
|
512
|
+
applied.push({ key, value: val });
|
|
513
|
+
}
|
|
514
|
+
if (errors.length > 0) return { path: p, scope, written: false, applied, errors };
|
|
515
|
+
|
|
516
|
+
// Write atomically: serialize → write to .tmp → rename. Backup the
|
|
517
|
+
// existing file first so the user can recover.
|
|
518
|
+
const dir = dirname(p);
|
|
519
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
520
|
+
if (existsSync(p)) { try { copyFileSync(p, backupPath(scope)); } catch { /* best-effort */ } }
|
|
521
|
+
const dump = yaml.dump(parsed, { lineWidth: 120 });
|
|
522
|
+
// mkstemp would be nicer but we want to stay in the same dir for
|
|
523
|
+
// a guaranteed-atomic rename across the same filesystem.
|
|
524
|
+
const tmpFile = join(dir, `.kj.config.yml.${process.pid}.${Date.now()}.tmp`);
|
|
525
|
+
writeFileSync(tmpFile, dump, 'utf8');
|
|
526
|
+
renameSync(tmpFile, p);
|
|
527
|
+
return { path: p, scope, written: true, applied, errors: [] };
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Exposed for prototype-pollution regression tests (KJC-BUG-0076).
|
|
531
|
+
export const __pollutionInternals = { setDeep, deleteDeep };
|