@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,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brain decisor — intent-driven pipeline routing.
|
|
3
|
+
*
|
|
4
|
+
* Takes the triage output + task text + config and produces a Decision that
|
|
5
|
+
* the orchestrator applies as pipelineFlags overrides. The existing stage
|
|
6
|
+
* if/else in runFlow continues to drive execution; Brain just tells it which
|
|
7
|
+
* roles are relevant for this particular task.
|
|
8
|
+
*
|
|
9
|
+
* This is an MVP that keeps the current architecture. A future iteration
|
|
10
|
+
* (Pipeline Sovereignty / KJC-PCS-0022) can promote the Decision into a
|
|
11
|
+
* true DAG with parallel stage execution.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} TriageOutput
|
|
16
|
+
* @property {"trivial"|"simple"|"medium"|"complex"} level
|
|
17
|
+
* @property {string[]} roles - roles triage suggests running
|
|
18
|
+
* @property {string} [taskType] - sw | infra | doc | add-tests | refactor | no-code | audit
|
|
19
|
+
* @property {string} [reasoning]
|
|
20
|
+
* @property {number} [confidence] - 0..1, often absent
|
|
21
|
+
* @property {string[]} [domainHints]
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Object} Decision
|
|
26
|
+
* @property {string[]} rolesOn - roles to enable in pipelineFlags
|
|
27
|
+
* @property {string[]} rolesOff - roles to explicitly disable
|
|
28
|
+
* @property {"low"|"medium"|"high"} confidence
|
|
29
|
+
* @property {string} rationale - human-readable reasoning
|
|
30
|
+
* @property {boolean} consultSolomon - true if the Brain is unsure
|
|
31
|
+
* @property {string} [solomonReason] - why Solomon is needed
|
|
32
|
+
* @property {string[]} appliedOverrides - names of manual overrides respected
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const VALID_ROLES = new Set([
|
|
36
|
+
"planner", "researcher", "architect", "coder", "reviewer", "refactorer",
|
|
37
|
+
"tester", "security", "impeccable", "triage", "discover", "hu_reviewer", "solomon",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Translate triage level → base set of roles. Conservative baseline that the
|
|
42
|
+
* decisor then adjusts via taskType and domain hints.
|
|
43
|
+
*/
|
|
44
|
+
function baselineForLevel(level) {
|
|
45
|
+
switch (level) {
|
|
46
|
+
case "trivial": return new Set(["coder"]);
|
|
47
|
+
case "simple": return new Set(["coder", "reviewer"]);
|
|
48
|
+
case "medium": return new Set(["coder", "reviewer", "tester"]);
|
|
49
|
+
case "complex": return new Set(["researcher", "architect", "planner", "coder", "reviewer", "tester", "security"]);
|
|
50
|
+
default: return new Set(["coder", "reviewer"]);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Apply taskType adjustments on top of the baseline set (mutates in place).
|
|
56
|
+
*/
|
|
57
|
+
function applyTaskTypeAdjustments(roles, taskType) {
|
|
58
|
+
switch (taskType) {
|
|
59
|
+
case "doc":
|
|
60
|
+
// Documentation tasks: skip testing and security, keep reviewer for tone/clarity.
|
|
61
|
+
roles.delete("tester");
|
|
62
|
+
roles.delete("security");
|
|
63
|
+
roles.delete("refactorer");
|
|
64
|
+
break;
|
|
65
|
+
case "add-tests":
|
|
66
|
+
// Adding tests: tester is primary, security less relevant.
|
|
67
|
+
roles.add("tester");
|
|
68
|
+
roles.delete("security");
|
|
69
|
+
break;
|
|
70
|
+
case "refactor":
|
|
71
|
+
// Refactor: no new behavior; tester verifies nothing broke; reviewer checks structure.
|
|
72
|
+
roles.add("refactorer");
|
|
73
|
+
roles.add("tester");
|
|
74
|
+
break;
|
|
75
|
+
case "infra":
|
|
76
|
+
// Infra: architect helps; security critical.
|
|
77
|
+
roles.add("architect");
|
|
78
|
+
roles.add("security");
|
|
79
|
+
break;
|
|
80
|
+
case "audit":
|
|
81
|
+
// Audit: no coder; reviewer + security.
|
|
82
|
+
roles.delete("coder");
|
|
83
|
+
roles.add("reviewer");
|
|
84
|
+
roles.add("security");
|
|
85
|
+
break;
|
|
86
|
+
case "no-code":
|
|
87
|
+
// No-code tasks (e.g. running SQL queries): drop coder+reviewer+tester.
|
|
88
|
+
roles.delete("coder");
|
|
89
|
+
roles.delete("reviewer");
|
|
90
|
+
roles.delete("tester");
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Apply explicit overrides (CLI flags / config) on top of the decision.
|
|
97
|
+
* force = add; skip = remove. Tracks what was applied.
|
|
98
|
+
*
|
|
99
|
+
* @param {Set<string>} roles
|
|
100
|
+
* @param {{ forceRoles?: string[], skipRoles?: string[] }} overrides
|
|
101
|
+
* @returns {string[]} applied override names
|
|
102
|
+
*/
|
|
103
|
+
function applyOverrides(roles, overrides) {
|
|
104
|
+
const applied = [];
|
|
105
|
+
for (const r of overrides.forceRoles || []) {
|
|
106
|
+
if (!VALID_ROLES.has(r)) continue;
|
|
107
|
+
roles.add(r);
|
|
108
|
+
applied.push(`force:${r}`);
|
|
109
|
+
}
|
|
110
|
+
for (const r of overrides.skipRoles || []) {
|
|
111
|
+
if (!VALID_ROLES.has(r)) continue;
|
|
112
|
+
roles.delete(r);
|
|
113
|
+
applied.push(`skip:${r}`);
|
|
114
|
+
}
|
|
115
|
+
return applied;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Build a routing Decision from triage output + task text + config.
|
|
120
|
+
*
|
|
121
|
+
* @param {Object} args
|
|
122
|
+
* @param {TriageOutput} args.triage
|
|
123
|
+
* @param {string} [args.task]
|
|
124
|
+
* @param {Object} [args.config] - resolved config (for rolesEnabled in pipeline)
|
|
125
|
+
* @param {{ forceRoles?: string[], skipRoles?: string[] }} [args.overrides]
|
|
126
|
+
* @returns {Decision}
|
|
127
|
+
*/
|
|
128
|
+
export function buildDecision({ triage, task: _task, config = {}, overrides = {} } = {}) {
|
|
129
|
+
const level = triage?.level || "simple";
|
|
130
|
+
const taskType = triage?.taskType || "sw";
|
|
131
|
+
|
|
132
|
+
// 1. Baseline by complexity.
|
|
133
|
+
const roles = baselineForLevel(level);
|
|
134
|
+
|
|
135
|
+
// 2. Include triage's own suggested roles (triage already factors domain hints).
|
|
136
|
+
if (Array.isArray(triage?.roles)) {
|
|
137
|
+
for (const r of triage.roles) {
|
|
138
|
+
if (VALID_ROLES.has(r)) roles.add(r);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 3. Task-type-specific adjustments.
|
|
143
|
+
applyTaskTypeAdjustments(roles, taskType);
|
|
144
|
+
|
|
145
|
+
// 4. Config pipeline.X.enabled=false forces a role off (respect user config).
|
|
146
|
+
const pipeline = config?.pipeline || {};
|
|
147
|
+
for (const r of Array.from(roles)) {
|
|
148
|
+
if (pipeline[r]?.enabled === false) {
|
|
149
|
+
roles.delete(r);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 5. Manual CLI/flag overrides (highest priority).
|
|
154
|
+
const appliedOverrides = applyOverrides(roles, overrides);
|
|
155
|
+
|
|
156
|
+
// 6. Invariants: coder is always on unless explicitly audit/no-code AND not force-added.
|
|
157
|
+
if (taskType !== "audit" && taskType !== "no-code" && !overrides.skipRoles?.includes("coder")) {
|
|
158
|
+
roles.add("coder");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 7. Confidence + Solomon consultation.
|
|
162
|
+
const explicitConfidence = typeof triage?.confidence === "number" ? triage.confidence : null;
|
|
163
|
+
let confidence = "medium";
|
|
164
|
+
let consultSolomon = false;
|
|
165
|
+
let solomonReason;
|
|
166
|
+
if (explicitConfidence !== null) {
|
|
167
|
+
if (explicitConfidence < 0.6) {
|
|
168
|
+
confidence = "low";
|
|
169
|
+
consultSolomon = true;
|
|
170
|
+
solomonReason = `triage confidence ${explicitConfidence.toFixed(2)} < 0.6`;
|
|
171
|
+
} else if (explicitConfidence >= 0.85) {
|
|
172
|
+
confidence = "high";
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Ambiguous level (absent or off-vocabulary) → low confidence regardless.
|
|
176
|
+
if (!triage?.level) {
|
|
177
|
+
confidence = "low";
|
|
178
|
+
consultSolomon = true;
|
|
179
|
+
solomonReason = "triage returned no level";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const rolesOn = Array.from(roles).sort((a, b) => a.localeCompare(b));
|
|
183
|
+
const rolesOff = Array.from(VALID_ROLES).filter((r) => !roles.has(r)).sort((a, b) => a.localeCompare(b));
|
|
184
|
+
|
|
185
|
+
const rationale = buildRationale({ level, taskType, rolesOn, appliedOverrides, triage });
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
rolesOn,
|
|
189
|
+
rolesOff,
|
|
190
|
+
confidence,
|
|
191
|
+
rationale,
|
|
192
|
+
consultSolomon,
|
|
193
|
+
solomonReason,
|
|
194
|
+
appliedOverrides,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function buildRationale({ level, taskType, rolesOn, appliedOverrides, triage }) {
|
|
199
|
+
const parts = [
|
|
200
|
+
`level=${level}`,
|
|
201
|
+
`taskType=${taskType}`,
|
|
202
|
+
`roles=[${rolesOn.join(",")}]`,
|
|
203
|
+
];
|
|
204
|
+
if (triage?.domainHints?.length) {
|
|
205
|
+
parts.push(`hints=[${triage.domainHints.slice(0, 3).join(",")}]`);
|
|
206
|
+
}
|
|
207
|
+
if (appliedOverrides.length) {
|
|
208
|
+
parts.push(`overrides=[${appliedOverrides.join(",")}]`);
|
|
209
|
+
}
|
|
210
|
+
return parts.join(" | ");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Apply a Decision onto an existing pipelineFlags object. Returns a NEW
|
|
215
|
+
* object — the input is not mutated. Setting roleEnabled flags via a
|
|
216
|
+
* consistent naming pattern: `${role}Enabled`.
|
|
217
|
+
*
|
|
218
|
+
* Unknown roles are silently ignored; the orchestrator only consumes a
|
|
219
|
+
* fixed set of *Enabled flags.
|
|
220
|
+
*
|
|
221
|
+
* @param {Decision} decision
|
|
222
|
+
* @param {Object} pipelineFlags
|
|
223
|
+
* @returns {Object} new flags object
|
|
224
|
+
*/
|
|
225
|
+
export function applyDecisionToFlags(decision, pipelineFlags = {}) {
|
|
226
|
+
const next = { ...pipelineFlags };
|
|
227
|
+
const flagFor = (role) => {
|
|
228
|
+
if (role === "hu_reviewer") return "huReviewerEnabled";
|
|
229
|
+
return `${role}Enabled`;
|
|
230
|
+
};
|
|
231
|
+
for (const role of decision.rolesOn) {
|
|
232
|
+
next[flagFor(role)] = true;
|
|
233
|
+
}
|
|
234
|
+
for (const role of decision.rolesOff) {
|
|
235
|
+
next[flagFor(role)] = false;
|
|
236
|
+
}
|
|
237
|
+
// Coder/security/reviewer aren't always toggled via flags in the current
|
|
238
|
+
// orchestrator (coder is implicit). Don't touch fields the orchestrator
|
|
239
|
+
// doesn't know; next simply carries them.
|
|
240
|
+
return next;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export { VALID_ROLES };
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brain → Solomon consultation for routing decisions.
|
|
3
|
+
*
|
|
4
|
+
* When the Brain's Decision is low confidence, ambiguous, or the triage+config
|
|
5
|
+
* combination yields conflicting signals, the Brain consults Solomon. Solomon
|
|
6
|
+
* ruling is treated as *advisory*: if it returns a concrete role adjustment
|
|
7
|
+
* (e.g. "add security"), Brain applies it; otherwise Brain keeps the baseline.
|
|
8
|
+
*
|
|
9
|
+
* This is intentionally conservative. A future iteration can make Solomon a
|
|
10
|
+
* binding arbiter for routing (like it is for pipeline-control decisions).
|
|
11
|
+
*
|
|
12
|
+
* Architectural invariant: this module is the ONLY sanctioned path from
|
|
13
|
+
* Brain to Solomon for routing concerns. Stages / rules / agents must not
|
|
14
|
+
* bypass Brain to reach Solomon directly — see the note on `invokeSolomon`
|
|
15
|
+
* in `src/orchestrator/solomon-escalation.js` and docs/ARCHITECTURE.md.
|
|
16
|
+
*
|
|
17
|
+
* @typedef {import("../types/solomon.js").Conflict} Conflict
|
|
18
|
+
* @typedef {import("../types/solomon.js").SolomonResult} SolomonResult
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Build a synthetic conflict payload suitable for invokeSolomon().
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} args
|
|
25
|
+
* @param {import("./decisor.js").Decision} args.decision
|
|
26
|
+
* @param {Object} args.triage
|
|
27
|
+
* @param {string} args.task
|
|
28
|
+
* @returns {Conflict} conflict
|
|
29
|
+
*/
|
|
30
|
+
export function buildRoutingConflict({ decision, triage, task }) {
|
|
31
|
+
return {
|
|
32
|
+
task,
|
|
33
|
+
stage: "brain-routing",
|
|
34
|
+
reason: decision.solomonReason || "low-confidence routing decision",
|
|
35
|
+
iterationCount: 0,
|
|
36
|
+
maxIterations: 1,
|
|
37
|
+
history: [],
|
|
38
|
+
summary: `Brain rationale: ${decision.rationale}. Roles on: ${decision.rolesOn.join(", ")}. Confidence: ${decision.confidence}.`,
|
|
39
|
+
// Extra data Solomon's prompt can introspect via conflict.detail
|
|
40
|
+
detail: {
|
|
41
|
+
triageLevel: triage?.level,
|
|
42
|
+
triageTaskType: triage?.taskType,
|
|
43
|
+
triageConfidence: triage?.confidence,
|
|
44
|
+
rolesOn: decision.rolesOn,
|
|
45
|
+
rolesOff: decision.rolesOff,
|
|
46
|
+
reason: decision.solomonReason,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Invoke Solomon with a routing question and translate its ruling back into
|
|
53
|
+
* adjustments to the Decision's rolesOn/rolesOff lists.
|
|
54
|
+
*
|
|
55
|
+
* If anything goes wrong (Solomon disabled, invocation throws, ruling
|
|
56
|
+
* malformed), the original decision is returned unchanged with a diagnostic
|
|
57
|
+
* appended to its rationale.
|
|
58
|
+
*
|
|
59
|
+
* @param {Object} args
|
|
60
|
+
* @param {import("./decisor.js").Decision} args.decision
|
|
61
|
+
* @param {Object} args.triage
|
|
62
|
+
* @param {string} args.task
|
|
63
|
+
* @param {Object} args.config
|
|
64
|
+
* @param {Object} args.logger
|
|
65
|
+
* @param {Object} [args.emitter]
|
|
66
|
+
* @param {Object} [args.eventBase]
|
|
67
|
+
* @param {Object} [args.session]
|
|
68
|
+
* @param {Function} [args.askQuestion]
|
|
69
|
+
* @returns {Promise<import("./decisor.js").Decision>}
|
|
70
|
+
*/
|
|
71
|
+
export async function consultSolomonForRouting({
|
|
72
|
+
decision,
|
|
73
|
+
triage,
|
|
74
|
+
task,
|
|
75
|
+
config,
|
|
76
|
+
logger,
|
|
77
|
+
emitter = null,
|
|
78
|
+
eventBase = {},
|
|
79
|
+
session = {},
|
|
80
|
+
askQuestion = null,
|
|
81
|
+
}) {
|
|
82
|
+
if (!decision?.consultSolomon) return decision;
|
|
83
|
+
// Route through the existing infrastructure.
|
|
84
|
+
let invokeSolomon;
|
|
85
|
+
try {
|
|
86
|
+
({ invokeSolomon } = await import("../orchestrator/solomon-escalation.js"));
|
|
87
|
+
} catch (err) {
|
|
88
|
+
logger?.warn?.(`brain/solomon-consult: escalation module unavailable (${err.message})`);
|
|
89
|
+
return decision;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const conflict = buildRoutingConflict({ decision, triage, task });
|
|
93
|
+
|
|
94
|
+
let ruling;
|
|
95
|
+
try {
|
|
96
|
+
ruling = await invokeSolomon({
|
|
97
|
+
config,
|
|
98
|
+
logger,
|
|
99
|
+
emitter,
|
|
100
|
+
eventBase,
|
|
101
|
+
stage: "brain-routing",
|
|
102
|
+
conflict,
|
|
103
|
+
askQuestion,
|
|
104
|
+
session,
|
|
105
|
+
iteration: 0,
|
|
106
|
+
});
|
|
107
|
+
} catch (err) {
|
|
108
|
+
logger?.warn?.(`brain/solomon-consult: invokeSolomon threw (${err.message}) — keeping baseline`);
|
|
109
|
+
return {
|
|
110
|
+
...decision,
|
|
111
|
+
rationale: `${decision.rationale} | solomon=failed(${err.message})`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!ruling || typeof ruling !== "object") {
|
|
116
|
+
return {
|
|
117
|
+
...decision,
|
|
118
|
+
rationale: `${decision.rationale} | solomon=no-ruling`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return applyRulingToDecision(decision, ruling, logger);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Apply a Solomon ruling to a decision. Known ruling fields Solomon may
|
|
127
|
+
* return that we understand in routing context:
|
|
128
|
+
* - action: "approve" / "continue" / "retry_different_approach" / "escalate"
|
|
129
|
+
* - suggestedActions: list of strings that may include "add X" / "remove X"
|
|
130
|
+
* - conditions: list of strings
|
|
131
|
+
*
|
|
132
|
+
* For routing we only look at suggestedActions (text-based) and parse simple
|
|
133
|
+
* "add X" / "skip X" / "remove X" hints against the valid role set. Everything
|
|
134
|
+
* else is logged for the rationale and ignored.
|
|
135
|
+
*/
|
|
136
|
+
export function applyRulingToDecision(decision, ruling, logger) {
|
|
137
|
+
const rolesOn = new Set(decision.rolesOn);
|
|
138
|
+
const rolesOff = new Set(decision.rolesOff);
|
|
139
|
+
const applied = [];
|
|
140
|
+
|
|
141
|
+
const suggestions = Array.isArray(ruling.suggestedActions) ? ruling.suggestedActions : [];
|
|
142
|
+
for (const raw of suggestions) {
|
|
143
|
+
const action = String(raw || "").toLowerCase();
|
|
144
|
+
const addMatch = action.match(/\b(?:add|enable|include)\s+(\w+)/);
|
|
145
|
+
const skipMatch = action.match(/\b(?:skip|disable|remove|drop)\s+(\w+)/);
|
|
146
|
+
if (addMatch) {
|
|
147
|
+
const role = addMatch[1];
|
|
148
|
+
rolesOn.add(role);
|
|
149
|
+
rolesOff.delete(role);
|
|
150
|
+
applied.push(`solomon-add:${role}`);
|
|
151
|
+
} else if (skipMatch) {
|
|
152
|
+
const role = skipMatch[1];
|
|
153
|
+
rolesOff.add(role);
|
|
154
|
+
rolesOn.delete(role);
|
|
155
|
+
applied.push(`solomon-skip:${role}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const ruleVerdict = ruling.action || ruling.verdict || "unknown";
|
|
160
|
+
logger?.info?.(`brain/solomon-consult: Solomon verdict=${ruleVerdict} applied=[${applied.join(",")}]`);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
...decision,
|
|
164
|
+
rolesOn: Array.from(rolesOn).sort((a, b) => a.localeCompare(b)),
|
|
165
|
+
rolesOff: Array.from(rolesOff).sort((a, b) => a.localeCompare(b)),
|
|
166
|
+
appliedOverrides: [...decision.appliedOverrides, ...applied],
|
|
167
|
+
confidence: applied.length > 0 ? "high" : decision.confidence,
|
|
168
|
+
consultSolomon: false,
|
|
169
|
+
rationale: `${decision.rationale} | solomon=${ruleVerdict}${applied.length ? ` applied=${applied.join(",")}` : ""}`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Shim de compatibilidad: standby-store vive ahora en karajan-core.
|
|
2
|
+
// Mantiene la ruta para callers internos del CLI.
|
|
3
|
+
export {
|
|
4
|
+
standbyDir,
|
|
5
|
+
standbyDoneDir,
|
|
6
|
+
persistStandby,
|
|
7
|
+
buildStandbyState,
|
|
8
|
+
loadStandby,
|
|
9
|
+
listPendingStandby,
|
|
10
|
+
markStandbyDone,
|
|
11
|
+
acquireStandbyLock,
|
|
12
|
+
} from "karajan-core/standby-store";
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// KJC-TSK-0412: wrapper central de recovery. Cualquier stage que invoque
|
|
2
|
+
// un agente IA pasa por aquí. Brain consume classifyAgentError (TSK-0411)
|
|
3
|
+
// y decide: standby corto, backoff exponencial, hibernar, o abortar.
|
|
4
|
+
//
|
|
5
|
+
// Hibernar al disco (acción persistente) es TSK-0414 — esta PR devuelve
|
|
6
|
+
// { ok: false, action: "hibernate", retryUntil } cuando aplica; el caller
|
|
7
|
+
// puede ignorarlo (fallback a long standby capped) hasta que llegue 0414.
|
|
8
|
+
|
|
9
|
+
import { classifyAgentError, ERROR_CLASS } from "./agent-error-classifier.js";
|
|
10
|
+
import { persistStandby, markStandbyDone } from "./standby-store.js";
|
|
11
|
+
|
|
12
|
+
const ONE_MIN = 60 * 1000;
|
|
13
|
+
|
|
14
|
+
const ONE_HOUR = 60 * 60 * 1000;
|
|
15
|
+
const DEFAULT_FALLBACK_WAIT_HOURS = 12;
|
|
16
|
+
|
|
17
|
+
export const DEFAULT_RECOVERY_POLICY = Object.freeze({
|
|
18
|
+
hibernateThresholdMs: 5 * ONE_MIN, // > 5 min de espera → hibernar
|
|
19
|
+
longStandbyCapMs: 10 * ONE_MIN, // si el caller no hiberna, espera máx 10 min
|
|
20
|
+
// KJC-TSK-0415: threshold de fallback. Si retryAfter > esto Y el role
|
|
21
|
+
// tiene fallback configurado → switch provider en vez de hibernar.
|
|
22
|
+
fallbackWaitHoursDefault: DEFAULT_FALLBACK_WAIT_HOURS,
|
|
23
|
+
// Standby-in-process: when the cooldown is short enough we keep the
|
|
24
|
+
// process alive and `await sleepFn(retryAfter)`, then retry the
|
|
25
|
+
// agent. The user sees nothing terminal — kj just pauses. A SIGINT /
|
|
26
|
+
// SIGTERM during the wait prints `kj standby resume <id>` and exits
|
|
27
|
+
// cleanly. For waits longer than this the process exits straight
|
|
28
|
+
// away (a multi-day weekly cap is not worth keeping kj running).
|
|
29
|
+
standbyWaitHoursMax: DEFAULT_FALLBACK_WAIT_HOURS,
|
|
30
|
+
classes: {
|
|
31
|
+
[ERROR_CLASS.RATE_LIMIT_SHORT]: { mode: "standby", maxRetries: 3 },
|
|
32
|
+
[ERROR_CLASS.QUOTA_EXHAUSTED_DAILY]: { mode: "hibernate", maxRetries: 1, fallbackEligible: true },
|
|
33
|
+
[ERROR_CLASS.QUOTA_EXHAUSTED_MONTHLY]: { mode: "hibernate", maxRetries: 1, fallbackEligible: true },
|
|
34
|
+
[ERROR_CLASS.API_DOWN]: { mode: "backoff", maxRetries: 3, baseMs: 5_000, factor: 3, jitterPct: 0.2 },
|
|
35
|
+
[ERROR_CLASS.NETWORK_TIMEOUT]: { mode: "backoff", maxRetries: 3, baseMs: 5_000, factor: 3, jitterPct: 0.2 },
|
|
36
|
+
[ERROR_CLASS.SILENCED]: { mode: "backoff", maxRetries: 2, baseMs: 30_000, factor: 2, jitterPct: 0.15 },
|
|
37
|
+
[ERROR_CLASS.AUTH_FAILED]: { mode: "abort", maxRetries: 0 },
|
|
38
|
+
[ERROR_CLASS.UNKNOWN_FATAL]: { mode: "abort", maxRetries: 0 },
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function backoffDelay(policyForClass, attempt) {
|
|
43
|
+
const { baseMs = 5_000, factor = 2, jitterPct = 0.1 } = policyForClass;
|
|
44
|
+
const ms = baseMs * factor ** attempt;
|
|
45
|
+
const jitter = ms * jitterPct * (Math.random() * 2 - 1);
|
|
46
|
+
return Math.max(0, Math.round(ms + jitter));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sleep(ms) {
|
|
50
|
+
// En tests (VITEST / NODE_ENV=test) saltamos sleeps reales — los tests que
|
|
51
|
+
// necesitan timing controlado inyectan sleepFn explícito. Esto evita que
|
|
52
|
+
// tests de stages downstream que llaman a withBrainRecovery se cuelguen
|
|
53
|
+
// esperando standby cuando el agente fake devuelve un error recoverable.
|
|
54
|
+
if (process.env.VITEST || process.env.NODE_ENV === "test") return Promise.resolve();
|
|
55
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function emit(emitter, type, eventBase, detail) {
|
|
59
|
+
if (!emitter || typeof emitter.emit !== "function") return;
|
|
60
|
+
try {
|
|
61
|
+
emitter.emit("progress", { ...(eventBase || {}), type, detail });
|
|
62
|
+
} catch { /* nunca bloquees un retry por un emitter mal */ }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {object} args
|
|
67
|
+
* @param {object} args.agent - Agent con runTask({...})
|
|
68
|
+
* @param {object} args.taskArgs - args pasados a agent.runTask
|
|
69
|
+
* @param {string} args.role - "planner" | "coder" | "reviewer" | ...
|
|
70
|
+
* @param {string} [args.provider] - claude|codex|gemini|opencode (default: agent.provider)
|
|
71
|
+
* @param {object} [args.policy] - override DEFAULT_RECOVERY_POLICY
|
|
72
|
+
* @param {object} [args.emitter] - bus de eventos
|
|
73
|
+
* @param {object} [args.eventBase]
|
|
74
|
+
* @param {object} [args.logger]
|
|
75
|
+
* @param {Function} [args.sleepFn] - inyectable para tests
|
|
76
|
+
* @returns {Promise<{ ok: boolean, output?: string, action?: string, recovery?: object, error?: string }>}
|
|
77
|
+
*/
|
|
78
|
+
export async function withBrainRecovery({
|
|
79
|
+
agent, taskArgs, role, provider,
|
|
80
|
+
policy = DEFAULT_RECOVERY_POLICY,
|
|
81
|
+
emitter, eventBase, logger,
|
|
82
|
+
sleepFn = sleep,
|
|
83
|
+
// TSK-0414 PR2: si presente, en hibernate persiste { sessionId, planId?, huId?,
|
|
84
|
+
// argv?, ... } a ~/.kj/standby/<sessionId>.json y devuelve standbyFile en
|
|
85
|
+
// el resultado.
|
|
86
|
+
sessionState,
|
|
87
|
+
// KJC-TSK-0415: fallback chain. Si QUOTA_EXHAUSTED_* con retryAfter
|
|
88
|
+
// > maxWaitHours (default 12h) y hay fallback, switch en vez de hibernar.
|
|
89
|
+
fallback = null,
|
|
90
|
+
}) {
|
|
91
|
+
let effectiveAgent = agent;
|
|
92
|
+
let effectiveProvider = provider || agent?.provider || "unknown";
|
|
93
|
+
let effectiveFallback = fallback;
|
|
94
|
+
const attemptsByClass = {};
|
|
95
|
+
|
|
96
|
+
while (true) {
|
|
97
|
+
const result = await effectiveAgent.runTask(taskArgs);
|
|
98
|
+
if (result?.ok) return result;
|
|
99
|
+
|
|
100
|
+
const cls = classifyAgentError({
|
|
101
|
+
provider: effectiveProvider,
|
|
102
|
+
stdout: result?.output || "",
|
|
103
|
+
stderr: result?.error || "",
|
|
104
|
+
exitCode: result?.exitCode ?? null,
|
|
105
|
+
});
|
|
106
|
+
const classPolicy = policy.classes[cls.class] || { mode: "abort", maxRetries: 0 };
|
|
107
|
+
attemptsByClass[cls.class] = (attemptsByClass[cls.class] || 0) + 1;
|
|
108
|
+
const attempt = attemptsByClass[cls.class];
|
|
109
|
+
|
|
110
|
+
emit(emitter, "brain:agent-error", eventBase, { role, class: cls.class, attempt, message: cls.message, provider: effectiveProvider });
|
|
111
|
+
logger?.warn?.(`[brain] ${role} (${effectiveProvider}) → ${cls.class} attempt ${attempt}/${classPolicy.maxRetries}: ${cls.message}`);
|
|
112
|
+
|
|
113
|
+
// ABORT: no recuperable.
|
|
114
|
+
if (classPolicy.mode === "abort" || attempt > classPolicy.maxRetries) {
|
|
115
|
+
emit(emitter, "brain:fatal", eventBase, { role, class: cls.class, message: cls.message });
|
|
116
|
+
return { ok: false, action: "abort", recovery: cls, error: `Brain aborted: ${cls.class} — ${cls.message}` };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// KJC-TSK-0415: ¿switch a fallback antes de hibernar?
|
|
120
|
+
// Aplica si:
|
|
121
|
+
// - classPolicy.fallbackEligible (QUOTA_EXHAUSTED_*)
|
|
122
|
+
// - hay fallback configurado
|
|
123
|
+
// - retryAfter excede maxWaitHours del fallback (default 12h)
|
|
124
|
+
if (classPolicy.fallbackEligible && effectiveFallback?.agent && cls.retryAfter) {
|
|
125
|
+
const maxWaitMs = (effectiveFallback.maxWaitHours ?? policy.fallbackWaitHoursDefault ?? DEFAULT_FALLBACK_WAIT_HOURS) * ONE_HOUR;
|
|
126
|
+
if (cls.retryAfter > maxWaitMs) {
|
|
127
|
+
const from = effectiveProvider;
|
|
128
|
+
const to = effectiveFallback.provider || effectiveFallback.agent.provider || "unknown";
|
|
129
|
+
emit(emitter, "brain:fallback-switched", eventBase, { role, from, to, class: cls.class, retryAfter: cls.retryAfter, maxWaitMs });
|
|
130
|
+
logger?.info?.(`[brain] ${role}: fallback ${from} → ${to} (retryAfter ${Math.round(cls.retryAfter/3600000)}h > maxWait ${maxWaitMs/3600000}h)`);
|
|
131
|
+
// Switch al fallback. Reset attempts para que el nuevo provider
|
|
132
|
+
// pueda fallar/retry independientemente. Fallback puede tener su
|
|
133
|
+
// propio fallback en cadena.
|
|
134
|
+
effectiveAgent = effectiveFallback.agent;
|
|
135
|
+
effectiveProvider = to;
|
|
136
|
+
effectiveFallback = effectiveFallback.fallback || null;
|
|
137
|
+
for (const k of Object.keys(attemptsByClass)) attemptsByClass[k] = 0;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// HIBERNATE: the cooldown is too long for a short standby. Persist
|
|
143
|
+
// the snapshot first so a SIGINT / SIGTERM during the wait leaves a
|
|
144
|
+
// recoverable session on disk. Then either:
|
|
145
|
+
// (a) wait in-process until cooldownUntil and retry (the common
|
|
146
|
+
// case — daily quotas reset in a few hours), or
|
|
147
|
+
// (b) when the wait would exceed `standbyWaitHoursMax`, return
|
|
148
|
+
// action="hibernate" so the caller exits and the user
|
|
149
|
+
// resumes later with `kj standby resume <id>` (weekly /
|
|
150
|
+
// monthly caps).
|
|
151
|
+
if (classPolicy.mode === "hibernate" && cls.retryAfter && cls.retryAfter > policy.hibernateThresholdMs) {
|
|
152
|
+
let standbyFile = null;
|
|
153
|
+
if (sessionState && sessionState.sessionId) {
|
|
154
|
+
try {
|
|
155
|
+
standbyFile = persistStandby({
|
|
156
|
+
...sessionState,
|
|
157
|
+
cooldownUntil: cls.retryUntil,
|
|
158
|
+
reason: cls.class,
|
|
159
|
+
role, provider: effectiveProvider,
|
|
160
|
+
classifierMessage: cls.message,
|
|
161
|
+
});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
logger?.warn?.(`[brain] failed to persist standby: ${err.message}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const maxWaitMs = (policy.standbyWaitHoursMax ?? DEFAULT_FALLBACK_WAIT_HOURS) * ONE_HOUR;
|
|
168
|
+
const tooLongToWait = cls.retryAfter > maxWaitMs;
|
|
169
|
+
|
|
170
|
+
if (tooLongToWait) {
|
|
171
|
+
logger?.info?.(`[brain] hibernated session ${sessionState?.sessionId ?? "(no id)"} → ${standbyFile} (resume at ${cls.retryUntil}; wait > ${Math.round(maxWaitMs/ONE_HOUR)}h, exiting)`);
|
|
172
|
+
emit(emitter, "brain:hibernate-request", eventBase, { role, class: cls.class, retryUntil: cls.retryUntil, retryAfter: cls.retryAfter, standbyFile });
|
|
173
|
+
return { ok: false, action: "hibernate", recovery: cls, standbyFile };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Standby-in-process: keep kj alive, sleep until the cooldown,
|
|
177
|
+
// then retry. A SIGINT / SIGTERM during the wait prints the
|
|
178
|
+
// resume command and exits — the standby JSON is already on
|
|
179
|
+
// disk so the user can pick it up later.
|
|
180
|
+
const sid = sessionState?.sessionId ?? null;
|
|
181
|
+
const onSignal = (signal) => {
|
|
182
|
+
// eslint-disable-next-line no-console
|
|
183
|
+
console.log(`\n⏸ Standby interrupted (${signal}). Resume with:`);
|
|
184
|
+
// eslint-disable-next-line no-console
|
|
185
|
+
if (sid) console.log(` kj standby resume ${sid}`);
|
|
186
|
+
// eslint-disable-next-line no-console
|
|
187
|
+
else console.log(` kj standby list (no session id was assigned)`);
|
|
188
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
189
|
+
};
|
|
190
|
+
const detach = () => {
|
|
191
|
+
process.removeListener("SIGINT", onSignal);
|
|
192
|
+
process.removeListener("SIGTERM", onSignal);
|
|
193
|
+
};
|
|
194
|
+
process.once("SIGINT", onSignal);
|
|
195
|
+
process.once("SIGTERM", onSignal);
|
|
196
|
+
|
|
197
|
+
const hours = Math.round((cls.retryAfter / ONE_HOUR) * 10) / 10;
|
|
198
|
+
logger?.info?.(`[brain] ${role}: standby for ~${hours}h until ${cls.retryUntil} (Ctrl+C to detach; will resume with 'kj standby resume ${sid}').`);
|
|
199
|
+
emit(emitter, "brain:standby-wait", eventBase, { role, class: cls.class, waitMs: cls.retryAfter, retryUntil: cls.retryUntil, standbyFile });
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
await sleepFn(cls.retryAfter);
|
|
203
|
+
} finally {
|
|
204
|
+
detach();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Wait finished without an interrupt — mark the standby JSON
|
|
208
|
+
// done so it does not auto-resume from disk later.
|
|
209
|
+
if (sid) {
|
|
210
|
+
try { markStandbyDone(sid); } catch { /* ignore */ }
|
|
211
|
+
}
|
|
212
|
+
emit(emitter, "brain:standby-resumed", eventBase, { role, class: cls.class });
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// STANDBY: espera hasta cooldownUntil (capado por longStandbyCapMs).
|
|
217
|
+
if (classPolicy.mode === "standby") {
|
|
218
|
+
const waitMs = Math.min(cls.retryAfter || ONE_MIN, policy.longStandbyCapMs);
|
|
219
|
+
emit(emitter, "brain:standby", eventBase, { role, class: cls.class, waitMs, retryUntil: cls.retryUntil });
|
|
220
|
+
await sleepFn(waitMs);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// BACKOFF exponencial con jitter.
|
|
225
|
+
if (classPolicy.mode === "backoff") {
|
|
226
|
+
const waitMs = backoffDelay(classPolicy, attempt - 1);
|
|
227
|
+
emit(emitter, "brain:backoff", eventBase, { role, class: cls.class, attempt, waitMs });
|
|
228
|
+
await sleepFn(waitMs);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Modo desconocido — defensive abort.
|
|
233
|
+
return { ok: false, action: "abort", recovery: cls, error: `Brain: unknown mode '${classPolicy.mode}' for class ${cls.class}` };
|
|
234
|
+
}
|
|
235
|
+
}
|