@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,37 @@
|
|
|
1
|
+
import { runCommand } from "./process.js";
|
|
2
|
+
import { getInstallCommand } from "./os-detect.js";
|
|
3
|
+
import { detectSqueezr } from "./squeezr-detect.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Install Squeezr using the OS-appropriate command.
|
|
7
|
+
* @param {object} logger - Logger with info/warn methods
|
|
8
|
+
* @returns {Promise<{ ok: boolean, version: string|null, error: string|null }>}
|
|
9
|
+
*/
|
|
10
|
+
export async function installSqueezr(logger) {
|
|
11
|
+
const command = getInstallCommand("squeezr");
|
|
12
|
+
|
|
13
|
+
logger.info("Installing Squeezr...");
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const result = await runCommand("sh", ["-c", command], { timeout: 120_000 });
|
|
17
|
+
|
|
18
|
+
if (result.exitCode !== 0) {
|
|
19
|
+
const error = (result.stderr || "").trim() || `exit code ${result.exitCode}`;
|
|
20
|
+
logger.warn(`Squeezr install failed: ${error}`);
|
|
21
|
+
return { ok: false, version: null, error };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const check = await detectSqueezr();
|
|
25
|
+
if (check.available) {
|
|
26
|
+
logger.info(`Squeezr ${check.version || ""} installed successfully.`);
|
|
27
|
+
return { ok: true, version: check.version, error: null };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
logger.warn("Squeezr install command succeeded but squeezr binary not found in PATH.");
|
|
31
|
+
return { ok: false, version: null, error: "Binary not found after install" };
|
|
32
|
+
} catch (err) {
|
|
33
|
+
const error = err.message || String(err);
|
|
34
|
+
logger.warn(`Squeezr install failed: ${error}`);
|
|
35
|
+
return { ok: false, version: null, error };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects the project technology stack by scanning project files.
|
|
3
|
+
* Used by `kj init` to auto-configure roles, skills, and settings.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from "node:fs/promises";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Frameworks detectable from package.json dependencies.
|
|
11
|
+
* Maps dependency name -> { name, type } where type is "frontend" | "backend" | "fullstack".
|
|
12
|
+
*/
|
|
13
|
+
const PKG_FRAMEWORKS = {
|
|
14
|
+
react: { name: "react", type: "frontend" },
|
|
15
|
+
"react-dom": { name: "react", type: "frontend" },
|
|
16
|
+
vue: { name: "vue", type: "frontend" },
|
|
17
|
+
svelte: { name: "svelte", type: "frontend" },
|
|
18
|
+
"@angular/core": { name: "angular", type: "frontend" },
|
|
19
|
+
astro: { name: "astro", type: "frontend" },
|
|
20
|
+
next: { name: "next", type: "fullstack" },
|
|
21
|
+
nuxt: { name: "nuxt", type: "fullstack" },
|
|
22
|
+
"solid-js": { name: "solid", type: "frontend" },
|
|
23
|
+
lit: { name: "lit", type: "frontend" },
|
|
24
|
+
express: { name: "express", type: "backend" },
|
|
25
|
+
fastify: { name: "fastify", type: "backend" },
|
|
26
|
+
"@nestjs/core": { name: "nestjs", type: "backend" },
|
|
27
|
+
hono: { name: "hono", type: "backend" },
|
|
28
|
+
koa: { name: "koa", type: "backend" },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Language markers: file presence -> { language, type }.
|
|
33
|
+
*/
|
|
34
|
+
const LANGUAGE_FILE_MARKERS = [
|
|
35
|
+
{ file: "go.mod", language: "go", type: "backend" },
|
|
36
|
+
{ file: "Cargo.toml", language: "rust", type: "backend" },
|
|
37
|
+
{ file: "pom.xml", language: "java", type: "backend" },
|
|
38
|
+
{ file: "build.gradle", language: "java", type: "backend" },
|
|
39
|
+
{ file: "build.gradle.kts", language: "kotlin", type: "backend" },
|
|
40
|
+
{ file: "pyproject.toml", language: "python", type: "backend" },
|
|
41
|
+
{ file: "setup.py", language: "python", type: "backend" },
|
|
42
|
+
{ file: "requirements.txt", language: "python", type: "backend" },
|
|
43
|
+
{ file: "Gemfile", language: "ruby", type: "backend" },
|
|
44
|
+
{ file: "composer.json", language: "php", type: "backend" },
|
|
45
|
+
{ file: "pubspec.yaml", language: "dart", type: "frontend" },
|
|
46
|
+
{ file: "Package.swift", language: "swift", type: "backend" },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Maps framework names to skill names for auto-install suggestions.
|
|
51
|
+
*/
|
|
52
|
+
const FRAMEWORK_TO_SKILL = {
|
|
53
|
+
react: "react",
|
|
54
|
+
vue: "vue",
|
|
55
|
+
svelte: "svelte",
|
|
56
|
+
angular: "angular",
|
|
57
|
+
astro: "astro",
|
|
58
|
+
next: "nextjs",
|
|
59
|
+
nuxt: "vue",
|
|
60
|
+
solid: "solid",
|
|
61
|
+
lit: "lit",
|
|
62
|
+
express: "express",
|
|
63
|
+
fastify: "fastify",
|
|
64
|
+
nestjs: "nestjs",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Detect the project technology stack.
|
|
69
|
+
* @param {string} projectDir - Absolute path to project root.
|
|
70
|
+
* @returns {Promise<{frameworks: string[], language: string|null, isFrontend: boolean, isBackend: boolean, isFullstack: boolean, suggestions: {impeccable: boolean, skills: string[]}}>}
|
|
71
|
+
*/
|
|
72
|
+
export async function detectProjectStack(projectDir) {
|
|
73
|
+
const frameworks = new Set();
|
|
74
|
+
let language = null;
|
|
75
|
+
let hasFrontend = false;
|
|
76
|
+
let hasBackend = false;
|
|
77
|
+
|
|
78
|
+
// 1. Scan package.json for known frameworks
|
|
79
|
+
try {
|
|
80
|
+
const pkgRaw = await fs.readFile(path.join(projectDir, "package.json"), "utf8");
|
|
81
|
+
const pkg = JSON.parse(pkgRaw);
|
|
82
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
83
|
+
|
|
84
|
+
if (!language) language = "javascript";
|
|
85
|
+
|
|
86
|
+
// Check for TypeScript
|
|
87
|
+
if (allDeps.typescript || allDeps["ts-node"]) {
|
|
88
|
+
language = "typescript";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const [dep, info] of Object.entries(PKG_FRAMEWORKS)) {
|
|
92
|
+
if (allDeps[dep]) {
|
|
93
|
+
frameworks.add(info.name);
|
|
94
|
+
if (info.type === "frontend" || info.type === "fullstack") hasFrontend = true;
|
|
95
|
+
if (info.type === "backend" || info.type === "fullstack") hasBackend = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch { /* no package.json or parse error */ }
|
|
99
|
+
|
|
100
|
+
// 2. Check language file markers
|
|
101
|
+
for (const marker of LANGUAGE_FILE_MARKERS) {
|
|
102
|
+
try {
|
|
103
|
+
await fs.access(path.join(projectDir, marker.file));
|
|
104
|
+
if (!language || language === "javascript") {
|
|
105
|
+
language = marker.language;
|
|
106
|
+
}
|
|
107
|
+
if (marker.type === "frontend") hasFrontend = true;
|
|
108
|
+
if (marker.type === "backend") hasBackend = true;
|
|
109
|
+
} catch { /* file not found */ }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const frameworkList = Array.from(frameworks);
|
|
113
|
+
const isFullstack = hasFrontend && hasBackend;
|
|
114
|
+
|
|
115
|
+
// Build suggestions
|
|
116
|
+
const skills = [];
|
|
117
|
+
for (const fw of frameworkList) {
|
|
118
|
+
const skill = FRAMEWORK_TO_SKILL[fw];
|
|
119
|
+
if (skill && !skills.includes(skill)) {
|
|
120
|
+
skills.push(skill);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
frameworks: frameworkList,
|
|
126
|
+
language,
|
|
127
|
+
isFrontend: hasFrontend,
|
|
128
|
+
isBackend: hasBackend,
|
|
129
|
+
isFullstack,
|
|
130
|
+
suggestions: {
|
|
131
|
+
impeccable: hasFrontend,
|
|
132
|
+
skills,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stall detector for agent execution.
|
|
3
|
+
*
|
|
4
|
+
* Wraps an onOutput callback to track activity and emit heartbeat / stall
|
|
5
|
+
* warnings when an agent stops producing output for too long.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const detector = createStallDetector({ onOutput, emitter, eventBase, stage, provider, stallTimeoutMs });
|
|
9
|
+
* // pass detector.onOutput to the agent
|
|
10
|
+
* // when done: detector.stop();
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { emitProgress, makeEvent } from "./events.js";
|
|
14
|
+
|
|
15
|
+
const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000; // heartbeat every 30s
|
|
16
|
+
const DEFAULT_STALL_TIMEOUT_MS = 120_000; // warn after 2min silence
|
|
17
|
+
const DEFAULT_CRITICAL_TIMEOUT_MS = 300_000; // critical after 5min silence
|
|
18
|
+
const DEFAULT_STALL_REPEAT_MS = 60_000; // repeat stall notices every 60s
|
|
19
|
+
|
|
20
|
+
export function createStallDetector({
|
|
21
|
+
onOutput,
|
|
22
|
+
emitter,
|
|
23
|
+
eventBase,
|
|
24
|
+
stage,
|
|
25
|
+
provider,
|
|
26
|
+
heartbeatIntervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
27
|
+
stallTimeoutMs = DEFAULT_STALL_TIMEOUT_MS,
|
|
28
|
+
criticalTimeoutMs = DEFAULT_CRITICAL_TIMEOUT_MS,
|
|
29
|
+
stallRepeatMs = DEFAULT_STALL_REPEAT_MS,
|
|
30
|
+
maxSilenceMs = null,
|
|
31
|
+
onMaxSilence = null
|
|
32
|
+
}) {
|
|
33
|
+
let lastActivityAt = Date.now();
|
|
34
|
+
let lineCount = 0;
|
|
35
|
+
let bytesReceived = 0;
|
|
36
|
+
let heartbeatTimer = null;
|
|
37
|
+
const startedAt = Date.now();
|
|
38
|
+
let lastStallWarnAt = 0;
|
|
39
|
+
let lastCriticalWarnAt = 0;
|
|
40
|
+
let maxSilenceTriggered = false;
|
|
41
|
+
|
|
42
|
+
function emitHeartbeat() {
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
const silenceMs = now - lastActivityAt;
|
|
45
|
+
const elapsedMs = now - startedAt;
|
|
46
|
+
const shouldWarn = silenceMs >= stallTimeoutMs;
|
|
47
|
+
const shouldCritical = silenceMs >= criticalTimeoutMs;
|
|
48
|
+
const repeatWindow = Math.max(1000, Number(stallRepeatMs) || DEFAULT_STALL_REPEAT_MS);
|
|
49
|
+
|
|
50
|
+
if (shouldCritical && (now - lastCriticalWarnAt >= repeatWindow)) {
|
|
51
|
+
lastCriticalWarnAt = now;
|
|
52
|
+
emitProgress(emitter, makeEvent("agent:stall", { ...eventBase, stage }, {
|
|
53
|
+
status: "critical",
|
|
54
|
+
message: `[${stage}] Agent ${provider} unresponsive for ${Math.round(silenceMs / 1000)}s — may be hung`,
|
|
55
|
+
detail: {
|
|
56
|
+
provider,
|
|
57
|
+
silenceMs,
|
|
58
|
+
elapsedMs,
|
|
59
|
+
lineCount,
|
|
60
|
+
bytesReceived,
|
|
61
|
+
severity: "critical"
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
} else if (shouldWarn && (now - lastStallWarnAt >= repeatWindow)) {
|
|
65
|
+
lastStallWarnAt = now;
|
|
66
|
+
emitProgress(emitter, makeEvent("agent:stall", { ...eventBase, stage }, {
|
|
67
|
+
status: "warning",
|
|
68
|
+
message: `[${stage}] Agent ${provider} silent for ${Math.round(silenceMs / 1000)}s — still waiting`,
|
|
69
|
+
detail: {
|
|
70
|
+
provider,
|
|
71
|
+
silenceMs,
|
|
72
|
+
elapsedMs,
|
|
73
|
+
lineCount,
|
|
74
|
+
bytesReceived,
|
|
75
|
+
severity: "warning"
|
|
76
|
+
}
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
emitProgress(emitter, makeEvent("agent:heartbeat", { ...eventBase, stage }, {
|
|
81
|
+
message: silenceMs < stallTimeoutMs
|
|
82
|
+
? `[${stage}] Agent ${provider} active — ${lineCount} lines, ${Math.round(elapsedMs / 1000)}s elapsed`
|
|
83
|
+
: `[${stage}] Agent ${provider} waiting — silent ${Math.round(silenceMs / 1000)}s, ${Math.round(elapsedMs / 1000)}s elapsed`,
|
|
84
|
+
detail: {
|
|
85
|
+
provider,
|
|
86
|
+
elapsedMs,
|
|
87
|
+
silenceMs,
|
|
88
|
+
lineCount,
|
|
89
|
+
bytesReceived,
|
|
90
|
+
status: silenceMs < stallTimeoutMs ? "active" : "waiting"
|
|
91
|
+
}
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
const hardLimit = Number(maxSilenceMs);
|
|
95
|
+
if (!maxSilenceTriggered && Number.isFinite(hardLimit) && hardLimit > 0 && silenceMs >= hardLimit) {
|
|
96
|
+
maxSilenceTriggered = true;
|
|
97
|
+
emitProgress(emitter, makeEvent("agent:stall", { ...eventBase, stage }, {
|
|
98
|
+
status: "fail",
|
|
99
|
+
message: `[${stage}] Agent ${provider} exceeded max silence (${Math.round(hardLimit / 1000)}s)`,
|
|
100
|
+
detail: {
|
|
101
|
+
provider,
|
|
102
|
+
silenceMs,
|
|
103
|
+
elapsedMs,
|
|
104
|
+
lineCount,
|
|
105
|
+
bytesReceived,
|
|
106
|
+
severity: "fatal",
|
|
107
|
+
maxSilenceMs: hardLimit
|
|
108
|
+
}
|
|
109
|
+
}));
|
|
110
|
+
if (typeof onMaxSilence === "function") {
|
|
111
|
+
onMaxSilence({
|
|
112
|
+
provider,
|
|
113
|
+
stage,
|
|
114
|
+
silenceMs,
|
|
115
|
+
elapsedMs,
|
|
116
|
+
lineCount,
|
|
117
|
+
bytesReceived,
|
|
118
|
+
maxSilenceMs: hardLimit
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Start periodic heartbeat
|
|
125
|
+
heartbeatTimer = setInterval(emitHeartbeat, heartbeatIntervalMs);
|
|
126
|
+
|
|
127
|
+
function wrappedOnOutput(data) {
|
|
128
|
+
lastActivityAt = Date.now();
|
|
129
|
+
lineCount++;
|
|
130
|
+
bytesReceived += data.line?.length || 0;
|
|
131
|
+
|
|
132
|
+
// Forward to the original callback
|
|
133
|
+
if (onOutput) {
|
|
134
|
+
onOutput(data);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function stop() {
|
|
139
|
+
if (heartbeatTimer) {
|
|
140
|
+
clearInterval(heartbeatTimer);
|
|
141
|
+
heartbeatTimer = null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function stats() {
|
|
146
|
+
return {
|
|
147
|
+
lineCount,
|
|
148
|
+
bytesReceived,
|
|
149
|
+
elapsedMs: Date.now() - startedAt,
|
|
150
|
+
lastActivityMs: Date.now() - lastActivityAt
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
onOutput: wrappedOnOutput,
|
|
156
|
+
stop,
|
|
157
|
+
stats
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status dashboard formatter.
|
|
3
|
+
* Builds a structured terminal dashboard from session data and run-log lines.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { HU_STATUS } from "../hu/store.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Status label mapping with visual indicators for terminal output.
|
|
10
|
+
* @type {Record<string, string>}
|
|
11
|
+
*/
|
|
12
|
+
const STATUS_LABELS = {
|
|
13
|
+
[HU_STATUS.PENDING]: "pending",
|
|
14
|
+
[HU_STATUS.CODING]: "coding",
|
|
15
|
+
[HU_STATUS.REVIEWING]: "reviewing",
|
|
16
|
+
[HU_STATUS.DONE]: "done",
|
|
17
|
+
[HU_STATUS.FAILED]: "failed",
|
|
18
|
+
[HU_STATUS.BLOCKED]: "blocked",
|
|
19
|
+
[HU_STATUS.CERTIFIED]: "certified",
|
|
20
|
+
[HU_STATUS.NEEDS_CONTEXT]: "needs_ctx",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Format a millisecond duration into a human-readable string (e.g. "3m 42s").
|
|
25
|
+
* @param {number} ms
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
export function formatDuration(ms) {
|
|
29
|
+
if (!ms || ms < 0) return "0s";
|
|
30
|
+
const totalSec = Math.floor(ms / 1000);
|
|
31
|
+
const hours = Math.floor(totalSec / 3600);
|
|
32
|
+
const minutes = Math.floor((totalSec % 3600) / 60);
|
|
33
|
+
const seconds = totalSec % 60;
|
|
34
|
+
|
|
35
|
+
const parts = [];
|
|
36
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
37
|
+
if (minutes > 0) parts.push(`${minutes}m`);
|
|
38
|
+
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
|
|
39
|
+
return parts.join(" ");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Parse the current pipeline stage from log lines.
|
|
44
|
+
* @param {string[]} lines - Raw log lines.
|
|
45
|
+
* @returns {{ currentStage: string|null, lastEvent: string|null }}
|
|
46
|
+
*/
|
|
47
|
+
export function parseStageFromLog(lines) {
|
|
48
|
+
let currentStage = null;
|
|
49
|
+
let lastEvent = null;
|
|
50
|
+
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
const stageStart = /\[(\w+):start\]/.exec(line);
|
|
53
|
+
if (stageStart) currentStage = stageStart[1];
|
|
54
|
+
|
|
55
|
+
const stageDone = /\[(\w+):done\]|\[(\w+)\] finished/.exec(line);
|
|
56
|
+
if (stageDone) {
|
|
57
|
+
const doneName = stageDone[1] || stageDone[2];
|
|
58
|
+
if (doneName === currentStage) currentStage = "idle";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (line.includes("[standby]") || line.includes("standby")) {
|
|
62
|
+
currentStage = "standby";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (line.trim()) lastEvent = line.trim();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { currentStage, lastEvent };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Format a single HU line for the dashboard.
|
|
73
|
+
* @param {object} story - HU story object.
|
|
74
|
+
* @param {boolean} isCurrent - Whether this is the currently active HU.
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
function formatHuLine(story, isCurrent) {
|
|
78
|
+
const id = (story.id || "???").padEnd(12);
|
|
79
|
+
const rawStatus = story.status || "pending";
|
|
80
|
+
const label = STATUS_LABELS[rawStatus] || rawStatus;
|
|
81
|
+
const statusStr = `[${label}]`.padEnd(13);
|
|
82
|
+
const title = story.title || story.original?.text || "(untitled)";
|
|
83
|
+
const truncTitle = title.length > 40 ? title.slice(0, 37) + "..." : title;
|
|
84
|
+
|
|
85
|
+
let timing = "";
|
|
86
|
+
if (story.duration_ms) {
|
|
87
|
+
timing = formatDuration(story.duration_ms);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const blockedInfo = story.blocked_by?.length
|
|
91
|
+
? ` (needs ${story.blocked_by.join(", ")})`
|
|
92
|
+
: "";
|
|
93
|
+
|
|
94
|
+
const marker = isCurrent ? " <-- current" : "";
|
|
95
|
+
const timingStr = timing ? ` ${timing}` : "";
|
|
96
|
+
|
|
97
|
+
return ` ${id} ${statusStr} ${truncTitle}${blockedInfo}${timingStr}${marker}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Build a terminal dashboard string from session data and log lines.
|
|
102
|
+
*
|
|
103
|
+
* @param {object|null} sessionData - Session JSON (from session-store) or null.
|
|
104
|
+
* @param {string[]} logLines - Raw run-log lines.
|
|
105
|
+
* @param {object} [options] - Optional overrides.
|
|
106
|
+
* @param {object[]|null} [options.stories] - HU stories array (from hu batch).
|
|
107
|
+
* @returns {string}
|
|
108
|
+
*/
|
|
109
|
+
export function buildDashboard(sessionData, logLines, options = {}) {
|
|
110
|
+
if (!sessionData) {
|
|
111
|
+
return "No active pipeline";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const lines = [];
|
|
115
|
+
const config = sessionData.config_snapshot || {};
|
|
116
|
+
const maxIter = config.max_iterations ?? "?";
|
|
117
|
+
const status = sessionData.status || "unknown";
|
|
118
|
+
|
|
119
|
+
// Parse log for current stage info
|
|
120
|
+
const logInfo = parseStageFromLog(logLines || []);
|
|
121
|
+
|
|
122
|
+
// Determine iteration from session or log
|
|
123
|
+
const iteration = sessionData.reviewer_retry_count != null
|
|
124
|
+
? sessionData.reviewer_retry_count + 1
|
|
125
|
+
: null;
|
|
126
|
+
const iterFromLog = parseIterationFromLog(logLines || []);
|
|
127
|
+
const displayIter = iterFromLog || iteration || "?";
|
|
128
|
+
|
|
129
|
+
// Pipeline header
|
|
130
|
+
const statusUpper = status.toUpperCase();
|
|
131
|
+
lines.push(`Pipeline: ${statusUpper} (iteration ${displayIter}/${maxIter})`);
|
|
132
|
+
|
|
133
|
+
// Duration
|
|
134
|
+
const duration = computeDuration(sessionData);
|
|
135
|
+
lines.push(`Duration: ${formatDuration(duration)}`);
|
|
136
|
+
|
|
137
|
+
// Agent info
|
|
138
|
+
const coderProvider = config.coder?.provider || config.roles?.coder?.provider || "?";
|
|
139
|
+
const reviewerProvider = config.reviewer?.provider || config.roles?.reviewer?.provider || "?";
|
|
140
|
+
lines.push(`Agent: ${coderProvider} (coder) / ${reviewerProvider} (reviewer)`);
|
|
141
|
+
|
|
142
|
+
// HU stories section
|
|
143
|
+
const stories = options.stories || [];
|
|
144
|
+
if (stories.length > 0) {
|
|
145
|
+
lines.push("");
|
|
146
|
+
lines.push("HUs:");
|
|
147
|
+
|
|
148
|
+
// Determine which story is currently being worked on
|
|
149
|
+
const currentStory = stories.find(s =>
|
|
150
|
+
s.status === HU_STATUS.CODING || s.status === HU_STATUS.REVIEWING
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
for (const story of stories) {
|
|
154
|
+
const isCurrent = currentStory && story.id === currentStory.id;
|
|
155
|
+
lines.push(formatHuLine(story, isCurrent));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Current stage and last event
|
|
160
|
+
lines.push("");
|
|
161
|
+
const stage = logInfo.currentStage || "idle";
|
|
162
|
+
const iterSuffix = displayIter !== "?" ? ` (iteration ${displayIter})` : "";
|
|
163
|
+
lines.push(`Current stage: ${stage}${iterSuffix}`);
|
|
164
|
+
|
|
165
|
+
if (logInfo.lastEvent) {
|
|
166
|
+
// Truncate last event for display
|
|
167
|
+
const maxLen = 120;
|
|
168
|
+
const event = logInfo.lastEvent.length > maxLen
|
|
169
|
+
? logInfo.lastEvent.slice(0, maxLen - 3) + "..."
|
|
170
|
+
: logInfo.lastEvent;
|
|
171
|
+
lines.push(`Last event: ${event}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Errors
|
|
175
|
+
if (sessionData.deferred_issues?.length) {
|
|
176
|
+
lines.push("");
|
|
177
|
+
lines.push(`Deferred issues: ${sessionData.deferred_issues.length}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return lines.join("\n");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Build a structured JSON response for MCP consumers.
|
|
185
|
+
*
|
|
186
|
+
* @param {object|null} sessionData - Session JSON or null.
|
|
187
|
+
* @param {string[]} logLines - Raw run-log lines.
|
|
188
|
+
* @param {object} [options] - Optional overrides.
|
|
189
|
+
* @param {object[]|null} [options.stories] - HU stories array.
|
|
190
|
+
* @returns {object}
|
|
191
|
+
*/
|
|
192
|
+
export function buildDashboardJson(sessionData, logLines, options = {}) {
|
|
193
|
+
if (!sessionData) {
|
|
194
|
+
return { ok: false, message: "No active pipeline" };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const config = sessionData.config_snapshot || {};
|
|
198
|
+
const logInfo = parseStageFromLog(logLines || []);
|
|
199
|
+
const iterFromLog = parseIterationFromLog(logLines || []);
|
|
200
|
+
const iteration = iterFromLog
|
|
201
|
+
|| (sessionData.reviewer_retry_count != null ? sessionData.reviewer_retry_count + 1 : null);
|
|
202
|
+
|
|
203
|
+
const stories = (options.stories || []).map(s => ({
|
|
204
|
+
id: s.id,
|
|
205
|
+
status: s.status || "pending",
|
|
206
|
+
title: s.title || s.original?.text || null,
|
|
207
|
+
blocked_by: s.blocked_by || [],
|
|
208
|
+
duration_ms: s.duration_ms || null,
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
ok: true,
|
|
213
|
+
pipeline: {
|
|
214
|
+
status: sessionData.status || "unknown",
|
|
215
|
+
iteration: iteration || null,
|
|
216
|
+
maxIterations: config.max_iterations ?? null,
|
|
217
|
+
duration_ms: computeDuration(sessionData),
|
|
218
|
+
coder: config.coder?.provider || config.roles?.coder?.provider || null,
|
|
219
|
+
reviewer: config.reviewer?.provider || config.roles?.reviewer?.provider || null,
|
|
220
|
+
},
|
|
221
|
+
hus: stories,
|
|
222
|
+
currentStage: logInfo.currentStage || "idle",
|
|
223
|
+
lastEvent: logInfo.lastEvent || null,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Parse iteration number from log lines.
|
|
229
|
+
* @param {string[]} lines
|
|
230
|
+
* @returns {number|null}
|
|
231
|
+
*/
|
|
232
|
+
function parseIterationFromLog(lines) {
|
|
233
|
+
let iteration = null;
|
|
234
|
+
for (const line of lines) {
|
|
235
|
+
const m = /[Ii]teration\s+(\d+)/.exec(line);
|
|
236
|
+
if (m) iteration = Number.parseInt(m[1], 10);
|
|
237
|
+
}
|
|
238
|
+
return iteration;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Compute pipeline duration in milliseconds from session timestamps.
|
|
243
|
+
* @param {object} sessionData
|
|
244
|
+
* @returns {number}
|
|
245
|
+
*/
|
|
246
|
+
function computeDuration(sessionData) {
|
|
247
|
+
const created = sessionData.created_at;
|
|
248
|
+
if (!created) return 0;
|
|
249
|
+
const start = new Date(created).getTime();
|
|
250
|
+
const now = sessionData.updated_at
|
|
251
|
+
? new Date(sessionData.updated_at).getTime()
|
|
252
|
+
: Date.now();
|
|
253
|
+
return Math.max(0, now - start);
|
|
254
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load a task description from a file (.md / .txt / .markdown / any plain text).
|
|
3
|
+
*
|
|
4
|
+
* Motivation: for anything beyond a one-line task, writing a `kj run "long
|
|
5
|
+
* multi-paragraph prompt ..."` becomes painful — escaping quotes, losing
|
|
6
|
+
* markdown formatting, no history. `kj run --task-file my-task.md` reads the
|
|
7
|
+
* file's contents and uses them as the task description. Same for `kj plan`,
|
|
8
|
+
* `kj audit`, `kj code`, and their MCP counterparts (via the `taskFile`
|
|
9
|
+
* argument).
|
|
10
|
+
*
|
|
11
|
+
* Behaviour:
|
|
12
|
+
* - `taskFile` may be relative (resolved against `process.cwd()` by default,
|
|
13
|
+
* or against `projectDir` if supplied) or absolute.
|
|
14
|
+
* - Accepts any text file; markdown is the expected common case but we do
|
|
15
|
+
* not require a specific extension.
|
|
16
|
+
* - Trimmed of leading/trailing whitespace so agents don't get empty lines
|
|
17
|
+
* around the task.
|
|
18
|
+
* - `maxBytes` (default 256 KiB) guards against accidentally passing a huge
|
|
19
|
+
* file (log dumps, PDFs). Agents choke on very long prompts anyway.
|
|
20
|
+
* - Precedence with the positional `task` arg is decided by the caller:
|
|
21
|
+
* this helper only loads, never merges. Convention used across commands:
|
|
22
|
+
* when BOTH are provided, the positional task wins and the file is a
|
|
23
|
+
* fallback — with a log warning so the user notices.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import fs from "node:fs/promises";
|
|
27
|
+
import path from "node:path";
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_TASK_FILE_MAX_BYTES = 256 * 1024;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} filePath - relative or absolute
|
|
33
|
+
* @param {Object} [opts]
|
|
34
|
+
* @param {string} [opts.projectDir] - base dir for relative paths; defaults to process.cwd()
|
|
35
|
+
* @param {number} [opts.maxBytes] - size cap; default 256 KiB
|
|
36
|
+
* @returns {Promise<string>} - the trimmed file contents (task text)
|
|
37
|
+
* @throws - Error with a user-readable message when the file is
|
|
38
|
+
* missing, unreadable, empty, or over the size cap.
|
|
39
|
+
*/
|
|
40
|
+
export async function readTaskFile(filePath, opts = {}) {
|
|
41
|
+
if (!filePath || typeof filePath !== "string") {
|
|
42
|
+
throw new Error("readTaskFile: path must be a non-empty string");
|
|
43
|
+
}
|
|
44
|
+
const { projectDir, maxBytes = DEFAULT_TASK_FILE_MAX_BYTES } = opts;
|
|
45
|
+
const base = projectDir || process.cwd();
|
|
46
|
+
const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(base, filePath);
|
|
47
|
+
|
|
48
|
+
let stat;
|
|
49
|
+
try {
|
|
50
|
+
stat = await fs.stat(resolved);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
throw new Error(`Task file not found: ${resolved} (${err.code || err.message})`, { cause: err });
|
|
53
|
+
}
|
|
54
|
+
if (!stat.isFile()) {
|
|
55
|
+
throw new Error(`Task file is not a regular file: ${resolved}`);
|
|
56
|
+
}
|
|
57
|
+
if (stat.size > maxBytes) {
|
|
58
|
+
const kb = Math.round(stat.size / 1024);
|
|
59
|
+
const capKb = Math.round(maxBytes / 1024);
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Task file is too large: ${resolved} (${kb} KiB > ${capKb} KiB cap). ` +
|
|
62
|
+
`Summarise the task or raise the limit with the caller's maxBytes option.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const raw = await fs.readFile(resolved, "utf8");
|
|
67
|
+
const trimmed = raw.trim();
|
|
68
|
+
if (!trimmed) {
|
|
69
|
+
throw new Error(`Task file is empty (or whitespace-only): ${resolved}`);
|
|
70
|
+
}
|
|
71
|
+
return trimmed;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolve the task text a command should use, given both positional `task`
|
|
76
|
+
* and an optional `taskFile`. Single source of truth for precedence across
|
|
77
|
+
* CLI and MCP entry points.
|
|
78
|
+
*
|
|
79
|
+
* Rules:
|
|
80
|
+
* - Neither provided → throw (caller decides the UX; typically propagates).
|
|
81
|
+
* - Only `task` → returns it, untouched.
|
|
82
|
+
* - Only `taskFile` → reads and returns it.
|
|
83
|
+
* - Both → `task` wins; emits a warning through `logger.warn` (if given).
|
|
84
|
+
*
|
|
85
|
+
* @param {Object} args
|
|
86
|
+
* @param {string|undefined} args.task
|
|
87
|
+
* @param {string|undefined} args.taskFile
|
|
88
|
+
* @param {string} [args.projectDir]
|
|
89
|
+
* @param {Object} [args.logger] - optional; uses `logger.warn(msg)` only
|
|
90
|
+
* @returns {Promise<string>}
|
|
91
|
+
*/
|
|
92
|
+
export async function resolveTaskInput({ task, taskFile, projectDir, logger } = {}) {
|
|
93
|
+
const hasTask = typeof task === "string" && task.trim().length > 0;
|
|
94
|
+
const hasFile = typeof taskFile === "string" && taskFile.trim().length > 0;
|
|
95
|
+
|
|
96
|
+
if (!hasTask && !hasFile) {
|
|
97
|
+
throw new Error("No task provided. Pass a task argument or --task-file <path>.");
|
|
98
|
+
}
|
|
99
|
+
if (hasTask && hasFile) {
|
|
100
|
+
logger?.warn?.(
|
|
101
|
+
`Both task and --task-file given; using the positional task and ignoring file: ${taskFile}`
|
|
102
|
+
);
|
|
103
|
+
return task.trim();
|
|
104
|
+
}
|
|
105
|
+
if (hasFile) {
|
|
106
|
+
return readTaskFile(taskFile, { projectDir });
|
|
107
|
+
}
|
|
108
|
+
return task.trim();
|
|
109
|
+
}
|