@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,458 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import net from "node:net";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { getKarajanHome } from "../utils/paths.js";
|
|
7
|
+
import { getPortOccupant } from "../utils/port-occupant.js";
|
|
8
|
+
|
|
9
|
+
const BOARD_DIR = path.resolve(import.meta.dirname, "../../packages/hu-board");
|
|
10
|
+
const PID_FILE = path.join(getKarajanHome(), "hu-board.pid");
|
|
11
|
+
|
|
12
|
+
function readPid() {
|
|
13
|
+
try {
|
|
14
|
+
const pid = Number.parseInt(fs.readFileSync(PID_FILE, "utf8").trim(), 10);
|
|
15
|
+
return Number.isFinite(pid) ? pid : null;
|
|
16
|
+
} catch { /* PID file does not exist */
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isProcessAlive(pid) {
|
|
22
|
+
try {
|
|
23
|
+
process.kill(pid, 0);
|
|
24
|
+
return true;
|
|
25
|
+
} catch { /* process does not exist */
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* HTTP-probe the board's /api/dashboard. Returns true when it
|
|
32
|
+
* responds with 2xx within ~750ms. Used as a fallback when the PID
|
|
33
|
+
* file is missing/stale to detect "board is up, just untracked".
|
|
34
|
+
*/
|
|
35
|
+
async function isBoardReachable(port, timeoutMs = 750) {
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
const req = http.request(
|
|
38
|
+
{ host: '127.0.0.1', port, path: '/api/dashboard', method: 'GET', timeout: timeoutMs },
|
|
39
|
+
(res) => {
|
|
40
|
+
const ok = res.statusCode >= 200 && res.statusCode < 500;
|
|
41
|
+
res.resume(); // drain
|
|
42
|
+
resolve(ok);
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
req.on('error', () => resolve(false));
|
|
46
|
+
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
47
|
+
req.end();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if a TCP port is available on localhost.
|
|
53
|
+
* Returns true when we can successfully bind (port is free).
|
|
54
|
+
*/
|
|
55
|
+
function isPortAvailable(port) {
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
const server = net.createServer();
|
|
58
|
+
server.once("error", () => resolve(false));
|
|
59
|
+
server.once("listening", () => {
|
|
60
|
+
server.close(() => resolve(true));
|
|
61
|
+
});
|
|
62
|
+
server.listen(port, "127.0.0.1");
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Starting from `desiredPort`, return the first free port within `maxTries`.
|
|
68
|
+
* Returns null if none available.
|
|
69
|
+
*/
|
|
70
|
+
async function findAvailablePort(desiredPort, maxTries = 10) {
|
|
71
|
+
for (let i = 0; i < maxTries; i++) {
|
|
72
|
+
const port = desiredPort + i;
|
|
73
|
+
if (await isPortAvailable(port)) return port;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Build the board URL. When `projectSlug` is provided, the URL points at
|
|
80
|
+
* the per-project view via the SPA hash route (`#board/<slug>`) so the
|
|
81
|
+
* caller's project shows up pre-filtered instead of "All projects". The
|
|
82
|
+
* frontend router is hash-based (see public/utils/init-listeners.js); a
|
|
83
|
+
* real `/p/<slug>` path is not a route and silently falls back to the
|
|
84
|
+
* global dashboard. KJC-BUG-0093.
|
|
85
|
+
* @param {number} port
|
|
86
|
+
* @param {string|null} [projectSlug]
|
|
87
|
+
* @returns {string}
|
|
88
|
+
*/
|
|
89
|
+
export function buildBoardUrl(port, projectSlug) {
|
|
90
|
+
const base = `http://localhost:${port}`;
|
|
91
|
+
return projectSlug ? `${base}/#board/${projectSlug}` : base;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* After spawning the board daemon, watch for an early exit. Resolves to
|
|
96
|
+
* an error string when the child dies within the window — a broken
|
|
97
|
+
* native module, a crash on boot, or the silent entry-point mismatch
|
|
98
|
+
* that made the daemon exit 0 without a trace — and to `null` when it
|
|
99
|
+
* survives. This is what stops `startBoard` from reporting `ok` for a
|
|
100
|
+
* daemon that died milliseconds after spawn. The window is overridable
|
|
101
|
+
* via KJ_BOARD_START_WINDOW_MS (tests set it low to stay fast).
|
|
102
|
+
* @param {import('node:child_process').ChildProcess} child
|
|
103
|
+
* @param {string} logPath
|
|
104
|
+
* @returns {Promise<string|null>}
|
|
105
|
+
*/
|
|
106
|
+
export function waitForEarlyExit(child, logPath) {
|
|
107
|
+
const envWindow = Number(process.env.KJ_BOARD_START_WINDOW_MS);
|
|
108
|
+
const windowMs = Number.isFinite(envWindow) && envWindow >= 0 ? envWindow : 1500;
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
let settled = false;
|
|
111
|
+
let timer = null;
|
|
112
|
+
const onExit = (code, signal) => {
|
|
113
|
+
const how = signal ? `signal ${signal}` : `exit code ${code}`;
|
|
114
|
+
finish(`HU Board daemon died on startup (${how}). See ${logPath} for the cause.`);
|
|
115
|
+
};
|
|
116
|
+
function finish(value) {
|
|
117
|
+
if (settled) return;
|
|
118
|
+
settled = true;
|
|
119
|
+
if (timer) clearTimeout(timer);
|
|
120
|
+
child.removeListener("exit", onExit);
|
|
121
|
+
resolve(value);
|
|
122
|
+
}
|
|
123
|
+
child.once("exit", onExit);
|
|
124
|
+
timer = setTimeout(() => finish(null), windowMs);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// KJC-BUG-0080: waitForEarlyExit only catches an exit, not a hung
|
|
129
|
+
// daemon that survived the window without ever bind()ing the port.
|
|
130
|
+
// Poll /api/dashboard until it answers or the readiness budget runs
|
|
131
|
+
// out, so we never announce "HU Board started" for an unreachable
|
|
132
|
+
// server. Overridable with KJ_BOARD_READY_TIMEOUT_MS (tests set it
|
|
133
|
+
// low to stay fast).
|
|
134
|
+
async function waitForBoardReachable(port, totalTimeoutMs) {
|
|
135
|
+
const envTimeout = Number(process.env.KJ_BOARD_READY_TIMEOUT_MS);
|
|
136
|
+
const budget = Number.isFinite(envTimeout) && envTimeout >= 0
|
|
137
|
+
? envTimeout
|
|
138
|
+
: (Number.isFinite(totalTimeoutMs) ? totalTimeoutMs : 5000);
|
|
139
|
+
const intervalMs = budget <= 200 ? Math.max(10, Math.floor(budget / 5)) : 200;
|
|
140
|
+
const deadline = Date.now() + budget;
|
|
141
|
+
while (Date.now() < deadline) {
|
|
142
|
+
const remaining = deadline - Date.now();
|
|
143
|
+
const probeTimeout = Math.min(750, Math.max(50, remaining));
|
|
144
|
+
if (await isBoardReachable(port, probeTimeout)) return true;
|
|
145
|
+
const wait = Math.min(intervalMs, deadline - Date.now());
|
|
146
|
+
if (wait > 0) await new Promise((r) => setTimeout(r, wait));
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function startBoard(desiredPort = 4000, opts = {}) {
|
|
152
|
+
const { projectSlug = null, bind = "127.0.0.1" } = opts;
|
|
153
|
+
const existingPid = readPid();
|
|
154
|
+
if (existingPid && isProcessAlive(existingPid)) {
|
|
155
|
+
// Trust the saved PID — port info comes from the PID file if present
|
|
156
|
+
return {
|
|
157
|
+
ok: true,
|
|
158
|
+
alreadyRunning: true,
|
|
159
|
+
pid: existingPid,
|
|
160
|
+
url: buildBoardUrl(desiredPort, projectSlug),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Belt-and-suspenders (v2.7.5 fix): even if the PID file is missing
|
|
165
|
+
// or stale, HTTP-probe the configured port before spawning. If
|
|
166
|
+
// /api/dashboard responds, a board is already there — reuse it. The
|
|
167
|
+
// pre-fix scenario: 🔁 self-restart didn't update the PID file
|
|
168
|
+
// (now fixed), so a `kj plan` afterwards saw a stale PID, spawned
|
|
169
|
+
// a duplicate board on port 4001, and confused the user.
|
|
170
|
+
if (await isBoardReachable(desiredPort)) {
|
|
171
|
+
return {
|
|
172
|
+
ok: true,
|
|
173
|
+
alreadyRunning: true,
|
|
174
|
+
pid: existingPid || null,
|
|
175
|
+
url: buildBoardUrl(desiredPort, projectSlug),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Find a free port starting from desiredPort
|
|
180
|
+
const port = await findAvailablePort(desiredPort, 10);
|
|
181
|
+
if (port === null) {
|
|
182
|
+
throw new Error(`No free port in range ${desiredPort}-${desiredPort + 9}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const serverPath = path.join(BOARD_DIR, "src/server.js");
|
|
186
|
+
const karajanHome = getKarajanHome();
|
|
187
|
+
fs.mkdirSync(karajanHome, { recursive: true });
|
|
188
|
+
|
|
189
|
+
// Open a log file for the daemon. Pre-fix the daemon was spawned
|
|
190
|
+
// with `stdio: "ignore"`, so every `console.log` from server.js
|
|
191
|
+
// (including the new [zombie-reaper] messages) went straight to
|
|
192
|
+
// /dev/null and the user had no way to see what was happening.
|
|
193
|
+
// The file is opened in append-only mode so successive starts
|
|
194
|
+
// don't truncate the history.
|
|
195
|
+
const logPath = path.join(karajanHome, "hu-board.log");
|
|
196
|
+
const logFd = fs.openSync(logPath, "a");
|
|
197
|
+
fs.writeSync(
|
|
198
|
+
logFd,
|
|
199
|
+
`\n--- kj board start at ${new Date().toISOString()} (port ${port}, bind ${bind}) ---\n`,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// Use process.execPath (absolute path to current node binary) instead of "node".
|
|
203
|
+
// Fixes ENOENT on nvm setups where `node` is not in the spawned process's PATH.
|
|
204
|
+
// KJ_BOARD_DAEMON=1 tells server.js to run main() regardless of how
|
|
205
|
+
// its entry-point path compares — the symlink/Windows/space-in-path
|
|
206
|
+
// mismatch that used to make the daemon exit 0 in total silence.
|
|
207
|
+
const child = spawn(process.execPath, [serverPath], {
|
|
208
|
+
env: { ...process.env, PORT: String(port), BIND_HOST: bind, KJ_BOARD_DAEMON: "1" },
|
|
209
|
+
detached: true,
|
|
210
|
+
stdio: ["ignore", logFd, logFd],
|
|
211
|
+
cwd: BOARD_DIR
|
|
212
|
+
});
|
|
213
|
+
// The parent doesn't need the fd anymore — the child inherited it.
|
|
214
|
+
fs.closeSync(logFd);
|
|
215
|
+
// Swallow spawn errors so an HU Board failure never crashes the pipeline.
|
|
216
|
+
child.on("error", () => { /* non-blocking — caller logs via tryAutoStartBoard catch */ });
|
|
217
|
+
if (!child.pid) {
|
|
218
|
+
throw new Error("Failed to spawn HU Board server");
|
|
219
|
+
}
|
|
220
|
+
// Don't report success blindly: a crash on boot (e.g. broken
|
|
221
|
+
// better-sqlite3, or the silent entry-point mismatch) used to be
|
|
222
|
+
// reported as ok:true, leaving the user with a dead board and a log
|
|
223
|
+
// showing only the start line. Catch the early exit here.
|
|
224
|
+
const failure = await waitForEarlyExit(child, logPath);
|
|
225
|
+
if (failure) {
|
|
226
|
+
throw new Error(failure);
|
|
227
|
+
}
|
|
228
|
+
// KJC-BUG-0080: even after a clean window, the server may have
|
|
229
|
+
// failed to bind (e.g. port race vs the parent's findAvailablePort)
|
|
230
|
+
// or be still booting. Probe /api/dashboard before announcing
|
|
231
|
+
// success — without this the CLI used to print "HU Board started
|
|
232
|
+
// at http://localhost:4000" while :4000 actually returned nothing.
|
|
233
|
+
const reachable = await waitForBoardReachable(port);
|
|
234
|
+
if (!reachable) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`HU Board daemon spawned (PID ${child.pid}) but did not answer on http://localhost:${port} in time. See ${logPath} for the cause.`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
fs.writeFileSync(PID_FILE, String(child.pid));
|
|
240
|
+
// Detach for real: `detached: true` only sets up the new session;
|
|
241
|
+
// without `unref()` the parent's event loop still holds a reference
|
|
242
|
+
// to the child handle and `kj board start` hangs at the prompt
|
|
243
|
+
// instead of returning.
|
|
244
|
+
child.unref();
|
|
245
|
+
return {
|
|
246
|
+
ok: true,
|
|
247
|
+
alreadyRunning: false,
|
|
248
|
+
pid: child.pid,
|
|
249
|
+
url: buildBoardUrl(port, projectSlug),
|
|
250
|
+
port,
|
|
251
|
+
logPath,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export async function stopBoard(opts = {}) {
|
|
256
|
+
const { port = 4000 } = opts;
|
|
257
|
+
// Path 1: live PID in the file → straight SIGTERM. Pre-v2.7.5
|
|
258
|
+
// this was the only path; if the file was missing or stale (e.g.
|
|
259
|
+
// user ran a board built before the server-writes-own-PID fix in
|
|
260
|
+
// PR #508) we'd return "wasn't running" while the ghost server
|
|
261
|
+
// happily kept serving requests. The fallbacks below close that gap.
|
|
262
|
+
const pid = readPid();
|
|
263
|
+
if (pid && isProcessAlive(pid)) {
|
|
264
|
+
try { process.kill(pid, "SIGTERM"); } catch { /* race with dying process */ }
|
|
265
|
+
fs.rmSync(PID_FILE, { force: true });
|
|
266
|
+
return { ok: true, wasRunning: true, pid, via: "pidfile" };
|
|
267
|
+
}
|
|
268
|
+
fs.rmSync(PID_FILE, { force: true });
|
|
269
|
+
|
|
270
|
+
// Path 2: HTTP-ask the server to exit cleanly. Works for any
|
|
271
|
+
// running server that has the /api/board/shutdown endpoint —
|
|
272
|
+
// even when there's no PID file, no process tree to walk, etc.
|
|
273
|
+
const shutdownUrl = `/api/board/shutdown`;
|
|
274
|
+
const shutdownOk = await postBoardShutdown(port, shutdownUrl);
|
|
275
|
+
if (shutdownOk) {
|
|
276
|
+
return { ok: true, wasRunning: true, pid: null, via: "api" };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Path 3: there's something on the port and the API didn't take it
|
|
280
|
+
// down. Find its PID via lsof / ss / netstat and SIGTERM it.
|
|
281
|
+
// Works against truly old servers that don't have the shutdown
|
|
282
|
+
// endpoint, with the cost of one extra subprocess call.
|
|
283
|
+
try {
|
|
284
|
+
const occupant = await getPortOccupant(port);
|
|
285
|
+
if (occupant?.pid) {
|
|
286
|
+
try { process.kill(occupant.pid, "SIGTERM"); } catch { /* gone */ }
|
|
287
|
+
return { ok: true, wasRunning: true, pid: occupant.pid, via: "port-occupant" };
|
|
288
|
+
}
|
|
289
|
+
} catch { /* lsof not installed etc — fall through */ }
|
|
290
|
+
|
|
291
|
+
return { ok: true, wasRunning: false };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Best-effort POST to the board's shutdown endpoint. Returns true
|
|
296
|
+
* when the server acks (and is presumably exiting); false on any
|
|
297
|
+
* error so the caller falls through to the next strategy.
|
|
298
|
+
*/
|
|
299
|
+
function postBoardShutdown(port, urlPath, timeoutMs = 750) {
|
|
300
|
+
return new Promise((resolve) => {
|
|
301
|
+
const req = http.request(
|
|
302
|
+
{ host: '127.0.0.1', port, path: urlPath, method: 'POST', timeout: timeoutMs,
|
|
303
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': '2' } },
|
|
304
|
+
(res) => {
|
|
305
|
+
const ok = res.statusCode >= 200 && res.statusCode < 400;
|
|
306
|
+
res.resume();
|
|
307
|
+
resolve(ok);
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
req.on('error', () => resolve(false));
|
|
311
|
+
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
312
|
+
req.write('{}');
|
|
313
|
+
req.end();
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export async function boardStatus(port = 4000) {
|
|
318
|
+
const pid = readPid();
|
|
319
|
+
const running = pid !== null && isProcessAlive(pid);
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
ok: true,
|
|
323
|
+
running,
|
|
324
|
+
pid: running ? pid : null,
|
|
325
|
+
url: running ? `http://localhost:${port}` : null
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Strip ANSI escapes so we can measure the actual on-screen width of a
|
|
330
|
+
// styled string. Used to size the rules around the banner content.
|
|
331
|
+
const ANSI_RE = /\[[0-9;]*m/g;
|
|
332
|
+
const visibleWidth = (s) => s.replace(ANSI_RE, "").length;
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Render a HU Board announcement framed by two horizontal rules of EQUAL
|
|
336
|
+
* length. Pre-v2.7.5 we used a fixed 50-char rounded box drawn with
|
|
337
|
+
* corners + verticals — fine when the URL fit, but a project slug
|
|
338
|
+
* pushed past the right wall and the box looked broken. The user's
|
|
339
|
+
* verdict: "looks worse than no box at all when it overflows".
|
|
340
|
+
*
|
|
341
|
+
* New rule: just an over-and-under rule sized to the longest visible
|
|
342
|
+
* content line. No left/right verticals, no fixed width. Always tidy.
|
|
343
|
+
*
|
|
344
|
+
* @param {Object} params
|
|
345
|
+
* @param {string} params.url - "http://localhost:4000"
|
|
346
|
+
* @param {string} params.status - "started" | "already running"
|
|
347
|
+
* @param {string} [params.projectName] - optional project label
|
|
348
|
+
* @returns {string} ANSI-coloured banner ready for console.log
|
|
349
|
+
*/
|
|
350
|
+
export function renderBoardBanner({ url, status, projectName }) {
|
|
351
|
+
const cyan = (s) => `[36m${s}[0m`;
|
|
352
|
+
const bold = (s) => `[1m${s}[0m`;
|
|
353
|
+
const underline = (s) => `[4m${s}[0m`;
|
|
354
|
+
|
|
355
|
+
const heading = ` ${bold("HU Board")} · ${status} → ${underline(url)}`;
|
|
356
|
+
const content = [heading];
|
|
357
|
+
if (projectName) content.push(` Project: ${bold(projectName)}`);
|
|
358
|
+
|
|
359
|
+
// Width = widest visible content line + 2-cell padding. Cap at the
|
|
360
|
+
// terminal width so a silly-long URL doesn't draw a 200-char rule.
|
|
361
|
+
const termWidth = (process.stdout.columns && process.stdout.columns > 20)
|
|
362
|
+
? process.stdout.columns
|
|
363
|
+
: 100;
|
|
364
|
+
const longest = Math.max(...content.map(visibleWidth));
|
|
365
|
+
const ruleWidth = Math.min(longest + 2, termWidth);
|
|
366
|
+
const rule = cyan("─".repeat(ruleWidth));
|
|
367
|
+
|
|
368
|
+
return ["", rule, ...content, rule, ""].join("\n");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export async function boardCommand({ action = "start", port = 4000, bind = "127.0.0.1", logger }) {
|
|
372
|
+
switch (action) {
|
|
373
|
+
case "start": {
|
|
374
|
+
let result;
|
|
375
|
+
try {
|
|
376
|
+
result = await startBoard(port, { bind });
|
|
377
|
+
} catch (err) {
|
|
378
|
+
// The daemon died on boot (broken native module, crash) or no
|
|
379
|
+
// free port was found. Surface it cleanly — the whole point of
|
|
380
|
+
// this fix is that `kj board start` never reports a phantom
|
|
381
|
+
// success while leaving a dead board behind.
|
|
382
|
+
logger.error(`HU Board failed to start: ${err.message}`);
|
|
383
|
+
process.exitCode = 1;
|
|
384
|
+
return { ok: false, error: err.message };
|
|
385
|
+
}
|
|
386
|
+
if (result.alreadyRunning) {
|
|
387
|
+
logger.info(`HU Board already running (PID ${result.pid}) at ${result.url}`);
|
|
388
|
+
} else {
|
|
389
|
+
logger.info(`HU Board started (PID ${result.pid}) at ${result.url}`);
|
|
390
|
+
if (result.logPath) {
|
|
391
|
+
logger.info(`Daemon logs: tail -f ${result.logPath}`);
|
|
392
|
+
}
|
|
393
|
+
if (bind !== "127.0.0.1" && bind !== "localhost") {
|
|
394
|
+
logger.warn(`HU Board bound to ${bind} — token auth enforced for non-loopback peers. Token: ~/.karajan/hu-board/token`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
case "stop": {
|
|
400
|
+
const result = await stopBoard({ port });
|
|
401
|
+
if (result.wasRunning) {
|
|
402
|
+
const pidPart = result.pid ? ` (PID ${result.pid})` : "";
|
|
403
|
+
const viaPart = result.via && result.via !== "pidfile" ? ` [via ${result.via}]` : "";
|
|
404
|
+
logger.info(`HU Board stopped${pidPart}${viaPart}`);
|
|
405
|
+
} else {
|
|
406
|
+
logger.info("HU Board was not running");
|
|
407
|
+
}
|
|
408
|
+
return result;
|
|
409
|
+
}
|
|
410
|
+
case "status": {
|
|
411
|
+
const result = await boardStatus(port);
|
|
412
|
+
if (result.running) {
|
|
413
|
+
logger.info(`HU Board is running (PID ${result.pid}) at ${result.url}`);
|
|
414
|
+
} else {
|
|
415
|
+
logger.info("HU Board is not running");
|
|
416
|
+
}
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
case "open": {
|
|
420
|
+
const status = await boardStatus(port);
|
|
421
|
+
if (!status.running) {
|
|
422
|
+
logger.info("HU Board is not running. Starting it first...");
|
|
423
|
+
await startBoard(port);
|
|
424
|
+
}
|
|
425
|
+
const url = `http://localhost:${port}`;
|
|
426
|
+
// eslint-disable-next-line import-x/no-unresolved -- optional peer; the .catch handles its absence
|
|
427
|
+
const { default: open } = await import("open").catch(() => ({ default: null }));
|
|
428
|
+
if (open) {
|
|
429
|
+
await open(url);
|
|
430
|
+
logger.info(`Opened ${url}`);
|
|
431
|
+
} else {
|
|
432
|
+
logger.info(`Open in browser: ${url}`);
|
|
433
|
+
}
|
|
434
|
+
return { ok: true, url };
|
|
435
|
+
}
|
|
436
|
+
case "cleanup": {
|
|
437
|
+
const { initDb, closeDb, cleanupZombies } = await import("../../packages/hu-board/src/cleanup-zombies.js");
|
|
438
|
+
initDb();
|
|
439
|
+
try {
|
|
440
|
+
const dryRun = process.argv.includes("--dry-run");
|
|
441
|
+
const report = cleanupZombies({ dryRun });
|
|
442
|
+
const total = report.projects.length + report.prompts.length + report.sessions.length;
|
|
443
|
+
const prefix = dryRun ? "[dry-run] Would clean" : "Cleaned";
|
|
444
|
+
logger.info(`${prefix}: ${report.projects.length} projects, ${report.prompts.length} prompts, ${report.sessions.length} sessions (total ${total})`);
|
|
445
|
+
if (report.projects.length) logger.info(` projects: ${report.projects.join(", ")}`);
|
|
446
|
+
if (report.prompts.length) logger.info(` prompts: ${report.prompts.join(", ")}`);
|
|
447
|
+
if (report.sessions.length) logger.info(` sessions: ${report.sessions.join(", ")}`);
|
|
448
|
+
if (total === 0) logger.info(" Board is already clean.");
|
|
449
|
+
return { ok: true, ...report };
|
|
450
|
+
} finally {
|
|
451
|
+
closeDb();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
default:
|
|
455
|
+
logger.error(`Unknown board action: ${action}. Use start|stop|status|open|cleanup`);
|
|
456
|
+
return { ok: false, error: `Unknown action: ${action}` };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `kj check` — verify the quality harness installed by `kj harden`
|
|
3
|
+
* (KJC-TSK-0558). Prints a per-category table and returns an exit code so it
|
|
4
|
+
* works both locally and as a CI drift gate. `--json` for machine output.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { checkHarden } from "../harden/check.js";
|
|
8
|
+
import { collectMethodStats, formatMethodStats } from "../checks/method.js";
|
|
9
|
+
import { checkAiSurface, formatAiSurface } from "../checks/ai-surface.js";
|
|
10
|
+
import { detectObservedAgents } from "../utils/agent-detect.js";
|
|
11
|
+
|
|
12
|
+
export async function checkCommand({ projectDir = process.cwd(), profile = "standard", json = false, logger = console } = {}) {
|
|
13
|
+
const result = await checkHarden({ projectDir, profile });
|
|
14
|
+
// KJC-TSK-0689 (MG-D): method adherence is VISIBILITY, not a gate — it
|
|
15
|
+
// rides along in check output but never affects the exit code.
|
|
16
|
+
const method = await collectMethodStats({ projectDir }).catch(() => null);
|
|
17
|
+
// KJC-TSK-0694: same deal for the MCP inventory — a nudge, never a gate.
|
|
18
|
+
// KJC-TSK-0728: observed agent CLIs ride the same snapshot as "(cli)"
|
|
19
|
+
// entries, so a newly-appeared agent binary trips the same drift question.
|
|
20
|
+
let aiSurface = null;
|
|
21
|
+
try {
|
|
22
|
+
const clis = (await detectObservedAgents().catch(() => []))
|
|
23
|
+
.filter((a) => a.available)
|
|
24
|
+
.map((a) => `${a.name} (cli)`);
|
|
25
|
+
aiSurface = checkAiSurface({ projectDir, extraSurface: clis });
|
|
26
|
+
} catch { /* inventory is best-effort */ }
|
|
27
|
+
|
|
28
|
+
if (json) {
|
|
29
|
+
logger.info?.(JSON.stringify({ ...result, method, aiSurface }));
|
|
30
|
+
return result.ok ? 0 : 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
logger.info?.(`kj check (${profile})`);
|
|
34
|
+
for (const c of result.checks) logger.info?.(` ${c.ok ? "✓" : "✗"} ${c.id}: ${c.detail}`);
|
|
35
|
+
if (method) logger.info?.(` method: ${formatMethodStats(method)}`);
|
|
36
|
+
if (aiSurface) logger.info?.(` ${formatAiSurface(aiSurface)}`);
|
|
37
|
+
if (!result.ok) logger.info?.("Harness drift detected — run `kj harden` to repair.");
|
|
38
|
+
else logger.info?.("Harness OK.");
|
|
39
|
+
return result.ok ? 0 : 1;
|
|
40
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `kj clean` — manual garbage collector.
|
|
3
|
+
*
|
|
4
|
+
* Shows or deletes accumulated state under `~/.kj/plans/` and
|
|
5
|
+
* `~/.karajan/` (sessions, HU story batches). Dry-run by default so
|
|
6
|
+
* the user can eyeball the plan before committing to it.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* kj clean # dry-run, shows what would be removed
|
|
10
|
+
* kj clean --yes # actually delete
|
|
11
|
+
* kj clean --plan-days 60 # keep finalised plans for 60 days instead of 30
|
|
12
|
+
* kj clean --draft-days 90 # keep drafts for 90 days instead of 60
|
|
13
|
+
* kj clean --session-days 14 # keep finalised sessions for 14 days instead of 7
|
|
14
|
+
* kj clean --hu-days 30 # keep HU batches for 30 days instead of 14
|
|
15
|
+
*
|
|
16
|
+
* The auto-GC that runs at the start of every `kj run` / `kj plan`
|
|
17
|
+
* uses the same retention defaults; tweaking them here lets the user
|
|
18
|
+
* sweep more or less aggressively without editing config.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { runManualGC, summarizeGC, nukeBoardDb } from "../utils/garbage-collector.js";
|
|
22
|
+
import { collectRepoCandidates } from "../utils/repo-cleaner.js";
|
|
23
|
+
import { collectVectorStoreOrphans } from "../utils/vector-store-orphans.js";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {Object} opts
|
|
27
|
+
* @param {boolean} [opts.yes=false] - when false, dry-run only
|
|
28
|
+
* @param {boolean} [opts.nuke=false] - retention=0 for every bucket + wipe board DB
|
|
29
|
+
* @param {boolean} [opts.repo=false] - also report repo-level candidates (branches, dist, *.tmp, *.bak)
|
|
30
|
+
* @param {number} [opts.repoDays=7] - age threshold for repo artifacts (default 7d)
|
|
31
|
+
* @param {string} [opts.repoBase="origin/main"]
|
|
32
|
+
* @param {boolean} [opts.vectorStores=false] - also report orphan RAG vector-store entries
|
|
33
|
+
* @param {string[]} [opts.projectRoots] - directories scanned to resolve slugs (default: ~/ws_*, ~/projects, ~/code)
|
|
34
|
+
* @param {number} [opts.planDays=30]
|
|
35
|
+
* @param {number} [opts.draftDays=60]
|
|
36
|
+
* @param {number} [opts.sessionDays=7]
|
|
37
|
+
* @param {number} [opts.huDays=14]
|
|
38
|
+
* @param {Object} [opts.logger]
|
|
39
|
+
*/
|
|
40
|
+
export async function cleanCommand(opts = {}) {
|
|
41
|
+
const dryRun = !opts.yes;
|
|
42
|
+
// --nuke forces retention=0 everywhere so drafts / sessions / plans of
|
|
43
|
+
// any age are swept. Combined with wipeBoardDb this is the "I want it
|
|
44
|
+
// all gone right now" button.
|
|
45
|
+
const retention = opts.nuke
|
|
46
|
+
? { planRetentionDays: 0, draftRetentionDays: 0, sessionRetentionDays: 0, huRetentionDays: 0 }
|
|
47
|
+
: {
|
|
48
|
+
planRetentionDays: opts.planDays,
|
|
49
|
+
draftRetentionDays: opts.draftDays,
|
|
50
|
+
sessionRetentionDays: opts.sessionDays,
|
|
51
|
+
huRetentionDays: opts.huDays,
|
|
52
|
+
};
|
|
53
|
+
const result = await runManualGC({ ...retention, dryRun });
|
|
54
|
+
|
|
55
|
+
if (opts.nuke) {
|
|
56
|
+
const boardResult = await nukeBoardDb({ dryRun });
|
|
57
|
+
result.removed.push(...boardResult.removed);
|
|
58
|
+
result.bytesFreed += boardResult.bytesFreed;
|
|
59
|
+
result.errors.push(...boardResult.errors);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!result.removed.length) {
|
|
63
|
+
console.log("[clean] nothing to clean — ~/.kj/ and ~/.karajan/ are tidy.");
|
|
64
|
+
return { ok: true, removed: 0 };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const verb = dryRun ? "would remove" : "removed";
|
|
68
|
+
console.log(`[clean] ${verb} ${result.removed.length} item(s):`);
|
|
69
|
+
for (const r of result.removed) {
|
|
70
|
+
console.log(` - ${r.kind.padEnd(10)} ${r.path}`);
|
|
71
|
+
console.log(` ${r.reason}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const kb = Math.round(result.bytesFreed / 1024);
|
|
75
|
+
if (kb) console.log(`[clean] ${verb} ${kb} KiB total`);
|
|
76
|
+
|
|
77
|
+
if (result.errors.length) {
|
|
78
|
+
console.log(`[clean] ${result.errors.length} error(s) (non-blocking):`);
|
|
79
|
+
for (const e of result.errors) console.log(` - ${e.path}: ${e.error}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (dryRun) {
|
|
83
|
+
console.log("");
|
|
84
|
+
console.log("[clean] DRY-RUN — no files touched. Re-run with --yes to actually delete.");
|
|
85
|
+
} else {
|
|
86
|
+
console.log(summarizeGC(result));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (opts.repo) await printRepoReport(opts);
|
|
90
|
+
if (opts.vectorStores) await printVectorStoreReport(opts);
|
|
91
|
+
|
|
92
|
+
return { ok: true, removed: result.removed.length, bytesFreed: result.bytesFreed };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function printRepoReport(opts) {
|
|
96
|
+
const { candidates, errors } = await collectRepoCandidates({
|
|
97
|
+
maxAgeDays: opts.repoDays ?? 7,
|
|
98
|
+
mergedTo: opts.repoBase ?? "origin/main",
|
|
99
|
+
});
|
|
100
|
+
console.log("");
|
|
101
|
+
if (!candidates.length) {
|
|
102
|
+
console.log("[clean:repo] no repo-level candidates — branches/dist/tmp are tidy.");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
console.log(`[clean:repo] ${candidates.length} candidate(s) — review and run the command to remove:`);
|
|
106
|
+
for (const c of candidates) {
|
|
107
|
+
console.log(` - ${c.kind.padEnd(11)} ${c.target}`);
|
|
108
|
+
console.log(` ${c.reason}`);
|
|
109
|
+
console.log(` $ ${c.command}`);
|
|
110
|
+
}
|
|
111
|
+
if (errors.length) {
|
|
112
|
+
console.log(`[clean:repo] ${errors.length} non-blocking error(s):`);
|
|
113
|
+
for (const e of errors) console.log(` - ${e.stage}: ${e.error}`);
|
|
114
|
+
}
|
|
115
|
+
console.log("[clean:repo] read-only — commands are printed, never executed.");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function printVectorStoreReport(opts) {
|
|
119
|
+
const { rows, orphans, errors } = await collectVectorStoreOrphans({
|
|
120
|
+
projectRoots: opts.projectRoots,
|
|
121
|
+
});
|
|
122
|
+
console.log("");
|
|
123
|
+
if (errors.length && !rows.length) {
|
|
124
|
+
console.log("[clean:rag] could not read rag.db — RAG may not be initialised yet.");
|
|
125
|
+
for (const e of errors) console.log(` - ${e.stage}: ${e.error}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
console.log(`[clean:rag] ${rows.length} project_slug(s) indexed in rag.db:`);
|
|
129
|
+
for (const r of rows) {
|
|
130
|
+
const tag = r.orphan ? " ORPHAN" : "";
|
|
131
|
+
const when = r.lastAt || "n/a";
|
|
132
|
+
console.log(` - ${r.slug.padEnd(28)} chunks=${String(r.chunkCount).padStart(5)} last=${when}${tag}`);
|
|
133
|
+
}
|
|
134
|
+
if (!orphans.length) {
|
|
135
|
+
console.log("[clean:rag] no orphans — every indexed slug resolves to a live project directory.");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
console.log("");
|
|
139
|
+
console.log(`[clean:rag] ${orphans.length} orphan(s) — review and run to purge:`);
|
|
140
|
+
for (const o of orphans) {
|
|
141
|
+
console.log(` - ${o.slug}`);
|
|
142
|
+
console.log(` $ ${o.command}`);
|
|
143
|
+
}
|
|
144
|
+
console.log("[clean:rag] read-only — commands are printed, never executed.");
|
|
145
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { createAgent } from "../agents/index.js";
|
|
3
|
+
import { assertAgentsAvailable } from "../agents/availability.js";
|
|
4
|
+
import { buildCoderPrompt } from "../prompts/coder.js";
|
|
5
|
+
import { resolveRole } from "../config.js";
|
|
6
|
+
import { withCliRunLog } from "../utils/cli-run-log.js";
|
|
7
|
+
import { createCliProgressReporter } from "../utils/cli-progress.js";
|
|
8
|
+
|
|
9
|
+
export async function codeCommand({ task, config, logger }) {
|
|
10
|
+
return withCliRunLog("code", { projectDir: config?.projectDir, logger }, async ({ runLog }) => {
|
|
11
|
+
const coderRole = resolveRole(config, "coder");
|
|
12
|
+
await assertAgentsAvailable([coderRole.provider]);
|
|
13
|
+
logger.info(`Coder (${coderRole.provider}) starting...`);
|
|
14
|
+
runLog.logText(`[coder] provider=${coderRole.provider}`);
|
|
15
|
+
const coder = createAgent(coderRole.provider, config, logger);
|
|
16
|
+
let coderRules = null;
|
|
17
|
+
if (config.coder_rules) {
|
|
18
|
+
try {
|
|
19
|
+
coderRules = await fs.readFile(config.coder_rules, "utf8");
|
|
20
|
+
} catch { /* configured coder_rules path not found — try fallback */
|
|
21
|
+
try { coderRules = await fs.readFile("coder-rules.md", "utf8"); } catch { /* no coder rules file */ }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const prompt = await buildCoderPrompt({ task, coderRules, methodology: config.development?.methodology || "tdd" });
|
|
25
|
+
const progress = createCliProgressReporter({ role: "coder" });
|
|
26
|
+
let result;
|
|
27
|
+
try {
|
|
28
|
+
result = await coder.runTask({ prompt, onOutput: progress.onOutput, role: "coder" });
|
|
29
|
+
progress.finish(result.ok ? "done" : "failed");
|
|
30
|
+
} catch (err) { progress.finish("failed"); throw err; }
|
|
31
|
+
if (!result.ok) {
|
|
32
|
+
if (result.error) logger.error(result.error);
|
|
33
|
+
throw new Error(result.error || result.output || `Coder failed (exit ${result.exitCode})`);
|
|
34
|
+
}
|
|
35
|
+
if (result.output) {
|
|
36
|
+
console.log(result.output);
|
|
37
|
+
}
|
|
38
|
+
if (result.error) {
|
|
39
|
+
logger.warn(result.error);
|
|
40
|
+
}
|
|
41
|
+
logger.info(`Coder completed (exit ${result.exitCode})`);
|
|
42
|
+
runLog.logText(`[coder] finished (exit=${result.exitCode})`);
|
|
43
|
+
return { ok: true };
|
|
44
|
+
});
|
|
45
|
+
}
|