@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,119 @@
|
|
|
1
|
+
// Pre-pipeline orchestrator for the spec-reviewer (KJC-PCS-0048 PR 2).
|
|
2
|
+
// Called from src/commands/run.js and src/commands/plan/generate.js
|
|
3
|
+
// BEFORE the actual pipeline. Decides whether to proceed based on the
|
|
4
|
+
// role's findings + the user's answer to the interactive prompt.
|
|
5
|
+
// PR 2 only supports [c]ontinue / [x]cancel; refine loop lands in PR 3.
|
|
6
|
+
|
|
7
|
+
import { SpecReviewerRole } from "../roles/spec-reviewer-role.js";
|
|
8
|
+
import { printFindings } from "../utils/display/spec-findings.js";
|
|
9
|
+
import { runRefinePass } from "./refine-loop.js";
|
|
10
|
+
import { RISK } from "../autonomy/policy.js";
|
|
11
|
+
|
|
12
|
+
const MAX_REFINE_ITERATIONS = 5;
|
|
13
|
+
|
|
14
|
+
// KJC-BUG-0125 (issue #1275): the prompt used to carry only counts and
|
|
15
|
+
// categories — the dashboard modal showed "4 findings at severity fail"
|
|
16
|
+
// with nothing to act on. The full findings travel with the prompt now
|
|
17
|
+
// (capped: a runaway reviewer must not blow up the prompt payload).
|
|
18
|
+
const MAX_FINDINGS_IN_PROMPT = 20;
|
|
19
|
+
const findingsForPrompt = (findings) => findings.slice(0, MAX_FINDINGS_IN_PROMPT).map((f) => ({
|
|
20
|
+
id: f.id, severity: f.severity, category: f.category,
|
|
21
|
+
message: f.message, suggestion: f.suggestion || null,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Decide the gate action (`continue` | `refine`) autonomously: the Arbiter
|
|
26
|
+
* picks PROCEED (proceed with the best reading) or RETRY_DIFFERENT_APPROACH
|
|
27
|
+
* (auto-refine). It is never offered `cancel`, so an autonomous run never
|
|
28
|
+
* aborts on spec ambiguity (KJC-TSK-0572, closes gap G1).
|
|
29
|
+
*/
|
|
30
|
+
async function decideGateAutonomously(resolve, { severity, findings }) {
|
|
31
|
+
const { choice } = await resolve({
|
|
32
|
+
question: `Spec has ${findings.length} finding(s) at severity ${severity}: proceed with the best reading, or auto-refine?`,
|
|
33
|
+
options: [
|
|
34
|
+
{ value: "PROCEED", label: "proceed with the best reading", conservative: true },
|
|
35
|
+
{ value: "RETRY_DIFFERENT_APPROACH", label: "auto-refine the spec" },
|
|
36
|
+
],
|
|
37
|
+
context: { severity, findingCount: findings.length, categories: [...new Set(findings.map((f) => f.category))], findings: findingsForPrompt(findings) },
|
|
38
|
+
risk: severity === "fail" ? RISK.HIGH : RISK.LOW,
|
|
39
|
+
});
|
|
40
|
+
return choice === "RETRY_DIFFERENT_APPROACH" ? "refine" : "continue";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function runSpecReview({ spec, config, logger, askQuestion, flags = {}, resolve = null, autonomy = "interactive" }) {
|
|
44
|
+
// VITEST guard — pre-existing test suites (e.g. tests/commands/command-run.test.js)
|
|
45
|
+
// mock `runFlow` + `assertAgentsAvailable` but predate this role and have
|
|
46
|
+
// no need to exercise it. Auto-skip in vitest unless the test passes
|
|
47
|
+
// `flags.forceSpecReview: true` (the spec-review's own tests bypass via
|
|
48
|
+
// their vi.doMock of SpecReviewerRole and never reach this guard).
|
|
49
|
+
if (process.env.VITEST && !flags.forceSpecReview) return { proceed: true, skipped: true };
|
|
50
|
+
if (flags.skipSpecReview || config?.spec_reviewer?.enabled === false) {
|
|
51
|
+
return { proceed: true, skipped: true };
|
|
52
|
+
}
|
|
53
|
+
if (!spec || !String(spec).trim()) return { proceed: true, skipped: true };
|
|
54
|
+
|
|
55
|
+
const role = new SpecReviewerRole({ config, logger });
|
|
56
|
+
await role.init({ task: spec });
|
|
57
|
+
|
|
58
|
+
// Outer loop: review → prompt → optional refine → re-review (the
|
|
59
|
+
// refine path replaces `currentSpec` and we go around again).
|
|
60
|
+
let currentSpec = spec;
|
|
61
|
+
for (let iter = 0; iter < MAX_REFINE_ITERATIONS; iter += 1) {
|
|
62
|
+
const res = await role.execute({ spec: currentSpec });
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
logger?.warn?.(`[spec] reviewer failed: ${res.summary} — continuing without review`);
|
|
65
|
+
return { proceed: true, error: res.result?.error, finalSpec: currentSpec };
|
|
66
|
+
}
|
|
67
|
+
const { severity, findings } = res.result || {};
|
|
68
|
+
if (!findings || findings.length === 0 || severity === "ok") {
|
|
69
|
+
logger?.info?.(iter === 0 ? "✓ spec OK" : "✓ refined spec OK");
|
|
70
|
+
return { proceed: true, severity: "ok", findings: [], finalSpec: currentSpec, refined: iter > 0 };
|
|
71
|
+
}
|
|
72
|
+
printFindings(findings, severity);
|
|
73
|
+
|
|
74
|
+
// Autonomous/assisted: the Arbiter decides (continue|refine), never cancels.
|
|
75
|
+
// Interactive (no resolver): the human answers [c]/[r]/[x] as before.
|
|
76
|
+
let action;
|
|
77
|
+
if (resolve && autonomy !== "interactive") {
|
|
78
|
+
action = await decideGateAutonomously(resolve, { severity, findings });
|
|
79
|
+
} else {
|
|
80
|
+
if (!askQuestion) return { proceed: true, severity, findings, finalSpec: currentSpec };
|
|
81
|
+
// KJC-TSK-0674 (issue #1289): at severity FAIL the default flips to
|
|
82
|
+
// cancel — continuing past fails requires an explicit answer, both
|
|
83
|
+
// interactively (Enter cancels) and unattended (no declared default,
|
|
84
|
+
// so --yes / KJ_NON_INTERACTIVE stops with exit ≠ 0). warn/info keep
|
|
85
|
+
// the KJC-BUG-0109 contract: --yes presses Enter → continue.
|
|
86
|
+
const fallback = severity === "fail" ? "cancel" : "continue";
|
|
87
|
+
const answer = await askQuestion(
|
|
88
|
+
`Spec review: ${findings.length} finding${findings.length === 1 ? "" : "s"} at severity ${severity}. [c]ontinue / [r]efine / [x]cancel? (default: ${fallback})`,
|
|
89
|
+
{
|
|
90
|
+
detail: { severity, findingCount: findings.length, categories: [...new Set(findings.map((f) => f.category))], iteration: iter + 1, findings: findingsForPrompt(findings) },
|
|
91
|
+
defaultAnswer: severity === "fail" ? undefined : "continue",
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
if (answer === null) return { proceed: false, cancelled: true, severity, findings };
|
|
95
|
+
const n = String(answer).trim().toLowerCase() || fallback;
|
|
96
|
+
if (n.startsWith("x") || n === "cancel") action = "cancel";
|
|
97
|
+
else if (n.startsWith("r") || n === "refine") action = "refine";
|
|
98
|
+
else action = "continue";
|
|
99
|
+
}
|
|
100
|
+
if (action === "cancel") return { proceed: false, cancelled: true, severity, findings };
|
|
101
|
+
if (action === "refine") {
|
|
102
|
+
const refine = await runRefinePass({
|
|
103
|
+
spec: currentSpec, findings, role,
|
|
104
|
+
projectDir: config?.projectDir, taskFile: flags.taskFile, logger,
|
|
105
|
+
});
|
|
106
|
+
if (!refine.ok) {
|
|
107
|
+
logger?.warn?.(`[spec] refine failed: ${refine.error} — proceeding with original spec`);
|
|
108
|
+
return { proceed: true, severity, findings, finalSpec: currentSpec };
|
|
109
|
+
}
|
|
110
|
+
currentSpec = refine.refinedSpec;
|
|
111
|
+
if (refine.hashChanged) continue; // user tweaked v2 → re-review
|
|
112
|
+
logger?.info?.("✓ refined spec accepted by user (no edits)");
|
|
113
|
+
return { proceed: true, severity: "ok", findings: [], finalSpec: currentSpec, refined: true };
|
|
114
|
+
}
|
|
115
|
+
return { proceed: true, severity, findings, finalSpec: currentSpec }; // continue
|
|
116
|
+
}
|
|
117
|
+
logger?.warn?.(`[spec] max refine iterations (${MAX_REFINE_ITERATIONS}) reached — proceeding with last spec`);
|
|
118
|
+
return { proceed: true, finalSpec: currentSpec, refined: true };
|
|
119
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// KJC-TSK-0569 (Onboard B) — deterministic synthesis of the read-only sweep.
|
|
2
|
+
//
|
|
3
|
+
// Turns the sweep bundle (Onboard A, 0568) into one coherent digest: a
|
|
4
|
+
// human-readable report plus a machine summary the decider (StartDecidorRole)
|
|
5
|
+
// consumes to pick an intent. No LLM here — the costly call is the haiku
|
|
6
|
+
// decider, not a redundant strong-model narration. Reuses the harden advisory
|
|
7
|
+
// formatter so the improvements section reads exactly like `kj harden --report`.
|
|
8
|
+
import { formatAdvisoryReport } from "../harden/advisory.js";
|
|
9
|
+
|
|
10
|
+
const EMPTY_TALLY = { install: 0, update: 0, review: 0, keep: 0 };
|
|
11
|
+
|
|
12
|
+
/** Tally harden advisory artifacts by recommendation. */
|
|
13
|
+
function tallyImprovements(improvements) {
|
|
14
|
+
const tally = { ...EMPTY_TALLY };
|
|
15
|
+
for (const a of improvements?.artifacts ?? []) {
|
|
16
|
+
if (a?.recommendation in tally) tally[a.recommendation] += 1;
|
|
17
|
+
}
|
|
18
|
+
return tally;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Quality/maintenance gaps as short phrases, derived from signals + drift. */
|
|
22
|
+
function collectGaps(signals = {}, drift) {
|
|
23
|
+
const gaps = [];
|
|
24
|
+
if (!signals.hasTests) gaps.push("no tests");
|
|
25
|
+
if (!signals.hasCI) gaps.push("no CI");
|
|
26
|
+
if (typeof signals.staleDays === "number" && signals.staleDays > 365) {
|
|
27
|
+
gaps.push(`last commit ${signals.staleDays}d old`);
|
|
28
|
+
}
|
|
29
|
+
if (drift && drift.ok === false) {
|
|
30
|
+
const failed = (drift.checks ?? []).filter((c) => c?.ok === false).length;
|
|
31
|
+
if (failed > 0) gaps.push(`${failed} harden check(s) drifting`);
|
|
32
|
+
}
|
|
33
|
+
return gaps;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {object} bundle output of runReadOnlySweep (0568)
|
|
38
|
+
* @returns {{text: string, summary: object}}
|
|
39
|
+
*/
|
|
40
|
+
export function buildAssessment(bundle) {
|
|
41
|
+
if (!bundle || typeof bundle !== "object") {
|
|
42
|
+
throw new TypeError("buildAssessment: bundle must be an object");
|
|
43
|
+
}
|
|
44
|
+
const { maturity = {}, signals = {}, drift, improvements, rag, qmd } = bundle;
|
|
45
|
+
const gaps = collectGaps(signals, drift);
|
|
46
|
+
const improvementCounts = tallyImprovements(improvements);
|
|
47
|
+
|
|
48
|
+
const summary = {
|
|
49
|
+
maturity: maturity.maturity ?? "unknown",
|
|
50
|
+
declared: Boolean(maturity.declared),
|
|
51
|
+
deepHealthRecommended: Boolean(maturity.deepHealthRecommended),
|
|
52
|
+
gaps,
|
|
53
|
+
driftOk: drift ? drift.ok !== false : null,
|
|
54
|
+
improvementCounts,
|
|
55
|
+
indexed: rag ? Boolean(rag.indexed) : null,
|
|
56
|
+
qmdAvailable: qmd ? Boolean(qmd.available) : null,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const lines = [
|
|
60
|
+
`Project maturity: ${summary.maturity}${summary.declared ? " (declared)" : " (inferred)"}`,
|
|
61
|
+
...(maturity.reasons ?? []).map((r) => ` - ${r}`),
|
|
62
|
+
"",
|
|
63
|
+
`Tests: ${signals.hasTests ? "yes" : "no"} · CI: ${signals.hasCI ? "yes" : "no"} · commits: ${signals.commitCount ?? 0}`,
|
|
64
|
+
`Quality gaps: ${gaps.length ? gaps.join(", ") : "none detected"}`,
|
|
65
|
+
`Knowledge index: ${summary.indexed === null ? "n/a" : summary.indexed ? "present" : "missing"}` +
|
|
66
|
+
` · qmd: ${summary.qmdAvailable === null ? "n/a" : summary.qmdAvailable ? "available" : "absent"}`,
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
if (improvements) {
|
|
70
|
+
lines.push("", ...formatAdvisoryReport(improvements));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { text: lines.join("\n"), summary };
|
|
74
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// KJC-TSK-0568 (Onboard A) — maturity classifier for the Brain (`kj start`).
|
|
2
|
+
//
|
|
3
|
+
// Pure and deterministic: given a bundle of read-only signals it labels the
|
|
4
|
+
// project new | existing | legacy. No LLM, no I/O — the sweep (sweep.js) does
|
|
5
|
+
// the reading; this only decides. The label drives which read-only subset the
|
|
6
|
+
// orchestrator runs and, downstream (0570), whether a deep health pass is worth
|
|
7
|
+
// the cost. A user-declared maturity always wins over inference.
|
|
8
|
+
//
|
|
9
|
+
// Acceptance criteria (KJC-TSK-0568):
|
|
10
|
+
// - empty repo / scaffolding only -> new
|
|
11
|
+
// - code + config + tests/CI -> existing
|
|
12
|
+
// - code but missing tests/CI, or very stale -> legacy
|
|
13
|
+
|
|
14
|
+
/** Last-commit age (days) beyond which a repo counts as stalled/neglected. */
|
|
15
|
+
export const STALE_DAYS_THRESHOLD = 365;
|
|
16
|
+
|
|
17
|
+
const VALID = new Set(["new", "existing", "legacy"]);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {object} signals
|
|
21
|
+
* @param {("new"|"existing"|"legacy"|null)} [signals.declared] user override
|
|
22
|
+
* @param {boolean} signals.hasSourceCode real source files present
|
|
23
|
+
* @param {boolean} signals.scaffoldingOnly only generator boilerplate, no real code
|
|
24
|
+
* @param {boolean} signals.hasTests a test suite is present
|
|
25
|
+
* @param {boolean} signals.hasCI CI workflows are present
|
|
26
|
+
* @param {number} [signals.commitCount] number of commits (capped upstream)
|
|
27
|
+
* @param {number|null} [signals.staleDays] age of the last commit in days
|
|
28
|
+
* @returns {{maturity: "new"|"existing"|"legacy", reasons: string[], deepHealthRecommended: boolean, declared: boolean}}
|
|
29
|
+
*/
|
|
30
|
+
export function classifyMaturity(signals) {
|
|
31
|
+
if (!signals || typeof signals !== "object") {
|
|
32
|
+
throw new TypeError("classifyMaturity: signals must be an object");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (VALID.has(signals.declared)) {
|
|
36
|
+
const maturity = signals.declared;
|
|
37
|
+
return {
|
|
38
|
+
maturity,
|
|
39
|
+
reasons: [`maturity declared by user as "${maturity}"`],
|
|
40
|
+
deepHealthRecommended: maturity === "legacy",
|
|
41
|
+
declared: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const reasons = [];
|
|
46
|
+
let maturity;
|
|
47
|
+
|
|
48
|
+
if (!signals.hasSourceCode || signals.scaffoldingOnly) {
|
|
49
|
+
maturity = "new";
|
|
50
|
+
reasons.push(signals.scaffoldingOnly ? "only scaffolding present, no real code yet" : "no source code found");
|
|
51
|
+
} else {
|
|
52
|
+
const stalled = typeof signals.staleDays === "number" && signals.staleDays > STALE_DAYS_THRESHOLD;
|
|
53
|
+
const gaps = [];
|
|
54
|
+
if (!signals.hasTests) gaps.push("no tests");
|
|
55
|
+
if (!signals.hasCI) gaps.push("no CI");
|
|
56
|
+
if (stalled) gaps.push(`last commit ${signals.staleDays}d old (stalled)`);
|
|
57
|
+
|
|
58
|
+
if (gaps.length > 0) {
|
|
59
|
+
maturity = "legacy";
|
|
60
|
+
reasons.push(`code present but neglected: ${gaps.join(", ")}`);
|
|
61
|
+
} else {
|
|
62
|
+
maturity = "existing";
|
|
63
|
+
reasons.push("code with tests and CI, actively maintained");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
maturity,
|
|
69
|
+
reasons,
|
|
70
|
+
deepHealthRecommended: maturity === "legacy",
|
|
71
|
+
declared: false,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// KJC-TSK-0569 (Onboard B) — the StartDecidor: the only LLM in the Brain's
|
|
2
|
+
// onboarding. Reads the deterministic assessment + user goal, picks ONE intent
|
|
3
|
+
// from a closed set, and never executes (0570 maps the intent to a safe command).
|
|
4
|
+
// Provider falls back to the coder, model defaults to haiku. Unparseable/unknown
|
|
5
|
+
// output degrades to ASK_USER so `kj start` always has a sane next step.
|
|
6
|
+
import { AgentRole } from "../roles/agent-role.js";
|
|
7
|
+
import { extractFirstJson } from "../utils/json-extract.js";
|
|
8
|
+
import { buildStartDecisionPrompt, INTENTS } from "../prompts/start-decision.js";
|
|
9
|
+
|
|
10
|
+
const FALLBACK_QUESTION = "What would you like to do with this project?";
|
|
11
|
+
|
|
12
|
+
export class StartDecidorRole extends AgentRole {
|
|
13
|
+
constructor(opts) {
|
|
14
|
+
super({ ...opts, name: "start" });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
extractInput(input) {
|
|
18
|
+
const src = typeof input === "string" ? { userMessage: input } : input || {};
|
|
19
|
+
return {
|
|
20
|
+
userMessage: src.userMessage || "",
|
|
21
|
+
assessment: src.assessment || "",
|
|
22
|
+
onOutput: src.onOutput || null,
|
|
23
|
+
...src,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async buildPrompt({ userMessage, assessment }) {
|
|
28
|
+
return {
|
|
29
|
+
prompt: buildStartDecisionPrompt({ userMessage, assessment, instructions: this.instructions }),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
parseOutput(raw) {
|
|
34
|
+
return extractFirstJson(raw);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
buildSuccessResult(parsed, provider) {
|
|
38
|
+
const intent = INTENTS.has(parsed.intent) ? parsed.intent : "ASK_USER";
|
|
39
|
+
const rationale = String(parsed.rationale || "").trim();
|
|
40
|
+
const questionToAsk = String(parsed.questionToAsk || "").trim();
|
|
41
|
+
const degraded = intent !== parsed.intent;
|
|
42
|
+
return {
|
|
43
|
+
intent,
|
|
44
|
+
rationale: rationale || (degraded ? "Unrecognized intent, asking the user." : "No rationale provided."),
|
|
45
|
+
questionToAsk: intent === "ASK_USER" ? questionToAsk || FALLBACK_QUESTION : "",
|
|
46
|
+
provider,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
buildSummary(parsed) {
|
|
51
|
+
const intent = INTENTS.has(parsed.intent) ? parsed.intent : "ASK_USER";
|
|
52
|
+
return `Start decision: ${intent}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
handleParseNull(agentResult, provider) {
|
|
56
|
+
return this.degradedDecision({ agentResult, provider, reason: "no JSON found" });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
handleParseError(err, agentResult, provider) {
|
|
60
|
+
return this.degradedDecision({ agentResult, provider, reason: err?.message || "parse error" });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Never block onboarding: garbage in → ask the user one open question.
|
|
64
|
+
degradedDecision({ agentResult, provider, reason }) {
|
|
65
|
+
this.logger?.warn?.(`[start] decider output unusable (${reason}) — asking the user.`);
|
|
66
|
+
return {
|
|
67
|
+
ok: true,
|
|
68
|
+
result: {
|
|
69
|
+
intent: "ASK_USER",
|
|
70
|
+
rationale: `Could not read a decision (${reason}).`,
|
|
71
|
+
questionToAsk: FALLBACK_QUESTION,
|
|
72
|
+
provider,
|
|
73
|
+
degraded: true,
|
|
74
|
+
},
|
|
75
|
+
summary: "Start decision: ASK_USER (fallback)",
|
|
76
|
+
usage: agentResult.usage,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// KJC-TSK-0568 (Onboard A) — read-only sweep orchestrator for the Brain.
|
|
2
|
+
//
|
|
3
|
+
// Runs the READ-ONLY signals fitting the project's maturity and aggregates them
|
|
4
|
+
// into one structured bundle for synthesis (Onboard B, 0569). Writes nothing.
|
|
5
|
+
// No LLM: maturity is deterministic and the costly audit is deferred — legacy
|
|
6
|
+
// only flags `deepHealthRecommended` (0570 decides). Subset by maturity: new =
|
|
7
|
+
// brief+tests; existing/legacy = + drift, harden advisory, rag + qmd status.
|
|
8
|
+
// Collaborators are injected (opts.deps) so it stays trivially testable.
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { readdir } from "node:fs/promises";
|
|
11
|
+
import { join, relative } from "node:path";
|
|
12
|
+
import { execFileSync } from "node:child_process";
|
|
13
|
+
|
|
14
|
+
import { collectAll } from "../onboarder/collectors/index.js";
|
|
15
|
+
import { checkHarden } from "../harden/check.js";
|
|
16
|
+
import { compareHarden } from "../harden/advisory.js";
|
|
17
|
+
import { detectTestFramework } from "../utils/project-detect.js";
|
|
18
|
+
import { detectQmd } from "../utils/qmd-detect.js";
|
|
19
|
+
import {
|
|
20
|
+
dbPath, openVecStore, projectSlug, countChunks, getLastIndexedCommit,
|
|
21
|
+
} from "../rag/vec-store.js";
|
|
22
|
+
import { classifyMaturity } from "./maturity.js";
|
|
23
|
+
|
|
24
|
+
const CODE_EXT = new Set([
|
|
25
|
+
".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx", ".py", ".go",
|
|
26
|
+
".rs", ".java", ".php", ".rb", ".c", ".cc", ".cpp", ".cs",
|
|
27
|
+
]);
|
|
28
|
+
// KJC-BUG-0141 — infra projects (k8s/terraform/shell/Docker) ARE source code,
|
|
29
|
+
// and code nested deeper than the display tree's maxDepth must still count.
|
|
30
|
+
const INFRA_EXT = new Set([".yaml", ".yml", ".tf", ".hcl", ".sh"]);
|
|
31
|
+
const INFRA_NAMES = new Set(["Dockerfile", "Makefile", "Vagrantfile"]);
|
|
32
|
+
const SCAN_IGNORED = new Set([
|
|
33
|
+
"node_modules", "dist", "build", "out", "vendor", "coverage", "target", "__pycache__",
|
|
34
|
+
]);
|
|
35
|
+
const SCAN_MAX_DEPTH = 6;
|
|
36
|
+
const SCAFFOLD_MAX_FILES = 3;
|
|
37
|
+
const CI_MARKERS = [".github/workflows", ".gitlab-ci.yml"];
|
|
38
|
+
|
|
39
|
+
const safe = async (fn) => { try { return await fn(); } catch { return null; } };
|
|
40
|
+
|
|
41
|
+
const isTestPath = (p) => {
|
|
42
|
+
const posix = p.replaceAll("\\", "/");
|
|
43
|
+
return /\.(test|spec)\./.test(posix) || /(^|\/)(test|tests|__tests__)\//.test(posix);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Deep source counter with its own walk: the display tree stops at depth 2,
|
|
48
|
+
* which made backend/app/… projects look empty (KJC-BUG-0141).
|
|
49
|
+
* @returns {Promise<{code: number, infra: number}>}
|
|
50
|
+
*/
|
|
51
|
+
export async function deepSourceScan(projectDir, { maxDepth = SCAN_MAX_DEPTH } = {}) {
|
|
52
|
+
const counts = { code: 0, infra: 0 };
|
|
53
|
+
const walk = async (dir, depth) => {
|
|
54
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
55
|
+
for (const ent of entries) {
|
|
56
|
+
if (ent.name.startsWith(".") || SCAN_IGNORED.has(ent.name)) continue;
|
|
57
|
+
const full = join(dir, ent.name);
|
|
58
|
+
if (ent.isDirectory()) {
|
|
59
|
+
if (depth < maxDepth) await walk(full, depth + 1);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (!ent.isFile() || isTestPath(relative(projectDir, full))) continue;
|
|
63
|
+
const ext = ent.name.slice(ent.name.lastIndexOf("."));
|
|
64
|
+
if (CODE_EXT.has(ext)) counts.code += 1;
|
|
65
|
+
else if (INFRA_EXT.has(ext) || INFRA_NAMES.has(ent.name)) counts.infra += 1;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
await walk(projectDir, 0);
|
|
69
|
+
return counts;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Count real source files in a tree bundle, skipping tests. */
|
|
73
|
+
export function countCodeFiles(tree = []) {
|
|
74
|
+
let n = 0;
|
|
75
|
+
for (const node of tree) {
|
|
76
|
+
if (node.kind === "dir") n += countCodeFiles(node.children);
|
|
77
|
+
else if (node.kind === "file") {
|
|
78
|
+
const ext = node.path.slice(node.path.lastIndexOf("."));
|
|
79
|
+
if (CODE_EXT.has(ext) && !isTestPath(node.path)) n += 1;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return n;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Age of the last commit in days, or null on non-git / no commits. */
|
|
86
|
+
function lastCommitAgeDays(projectDir) {
|
|
87
|
+
try {
|
|
88
|
+
const out = execFileSync("git", ["log", "-1", "--format=%ct"], {
|
|
89
|
+
cwd: projectDir, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
|
|
90
|
+
}).trim();
|
|
91
|
+
if (!out) return null;
|
|
92
|
+
return Math.floor((Date.now() - Number(out) * 1000) / 86_400_000);
|
|
93
|
+
} catch { return null; }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Read-only RAG index status; null when no index file exists yet. */
|
|
97
|
+
function readRagStatus(projectDir) {
|
|
98
|
+
try {
|
|
99
|
+
if (!existsSync(dbPath())) return { indexed: false, chunks: 0, lastIndexedCommit: null };
|
|
100
|
+
const db = openVecStore();
|
|
101
|
+
return {
|
|
102
|
+
indexed: true,
|
|
103
|
+
chunks: countChunks(db, { kind: "code" }),
|
|
104
|
+
lastIndexedCommit: getLastIndexedCommit(db, projectSlug(projectDir)),
|
|
105
|
+
};
|
|
106
|
+
} catch { return null; }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {string} projectDir
|
|
111
|
+
* @param {{declared?: ("new"|"existing"|"legacy"|null), profile?: string, deps?: object}} [opts]
|
|
112
|
+
* declared = user-declared maturity; deps = injected collaborators (defaults = real collectors).
|
|
113
|
+
*/
|
|
114
|
+
export async function runReadOnlySweep(projectDir, { declared = null, profile = "standard", deps = {} } = {}) {
|
|
115
|
+
// KJC-BUG-0145: a missing projectDir must fail LOUD — every collector is
|
|
116
|
+
// wrapped in safe(), so an undefined root used to read as an empty project.
|
|
117
|
+
if (!projectDir) throw new Error("runReadOnlySweep: projectDir is required (got " + String(projectDir) + ")");
|
|
118
|
+
const d = {
|
|
119
|
+
collectAll, detectTestFramework, checkHarden, compareHarden, sourceScan: deepSourceScan,
|
|
120
|
+
detectQmd, ragStatus: readRagStatus, gitAgeDays: lastCommitAgeDays, ...deps,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const brief = await safe(() => d.collectAll(projectDir));
|
|
124
|
+
const tests = await safe(() => d.detectTestFramework(projectDir));
|
|
125
|
+
const scan = await safe(() => d.sourceScan(projectDir));
|
|
126
|
+
|
|
127
|
+
const tree = brief?.tree ?? [];
|
|
128
|
+
const present = brief?.configs?.present ?? [];
|
|
129
|
+
const codeFiles = scan ? scan.code + scan.infra : countCodeFiles(tree);
|
|
130
|
+
const commitCount = brief?.git?.commitCount ?? 0;
|
|
131
|
+
|
|
132
|
+
const signals = {
|
|
133
|
+
hasSourceCode: codeFiles > 0,
|
|
134
|
+
infraFiles: scan?.infra ?? 0,
|
|
135
|
+
scaffoldingOnly: codeFiles > 0 && codeFiles <= SCAFFOLD_MAX_FILES && commitCount <= 1,
|
|
136
|
+
hasTests: Boolean(tests?.hasTests),
|
|
137
|
+
hasCI: CI_MARKERS.some((m) => present.includes(m)),
|
|
138
|
+
commitCount,
|
|
139
|
+
staleDays: d.gitAgeDays(projectDir),
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const maturity = classifyMaturity({ declared, ...signals });
|
|
143
|
+
const deep = maturity.maturity !== "new";
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
projectDir,
|
|
147
|
+
maturity,
|
|
148
|
+
signals,
|
|
149
|
+
brief,
|
|
150
|
+
tests,
|
|
151
|
+
drift: deep ? await safe(() => d.checkHarden({ projectDir, profile })) : null,
|
|
152
|
+
improvements: deep ? await safe(() => d.compareHarden({ projectDir, profile })) : null,
|
|
153
|
+
rag: deep ? await safe(() => d.ragStatus(projectDir)) : null,
|
|
154
|
+
qmd: deep ? await safe(() => d.detectQmd()) : null,
|
|
155
|
+
collectedAt: new Date().toISOString(),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kj sync --apply — append a drift patch to the Canvas markdown.
|
|
3
|
+
*
|
|
4
|
+
* KJC-TSK-0348. Pure data + I/O: reads the Canvas at `canvasPath`, drops a
|
|
5
|
+
* `.bak` snapshot alongside it, appends an HTML-comment block describing
|
|
6
|
+
* untracked / missing / covered files, and atomically renames a `.tmp`
|
|
7
|
+
* over the target. Comments are inert for the Canvas parser, so the
|
|
8
|
+
* patched file stays structurally valid — the user converts the TODOs
|
|
9
|
+
* into Operations by hand.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { promises as fs } from "node:fs";
|
|
13
|
+
|
|
14
|
+
function tempPath(target) {
|
|
15
|
+
return `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function renderDriftComment(report) {
|
|
19
|
+
const lines = ["", `<!-- kj sync --apply (${new Date().toISOString()}) — review and convert into Operations as needed. -->`];
|
|
20
|
+
let mutations = 0;
|
|
21
|
+
if (report.untracked?.length) {
|
|
22
|
+
lines.push("<!-- Untracked files (modified, no Operation references them — add one): -->");
|
|
23
|
+
for (const u of report.untracked) { lines.push(`<!-- + ${u.path} -->`); mutations += 1; }
|
|
24
|
+
}
|
|
25
|
+
if (report.missing?.length) {
|
|
26
|
+
lines.push("<!-- Missing files (Operation file_hint points at a file that does not exist): -->");
|
|
27
|
+
for (const m of report.missing) { lines.push(`<!-- ! ${m.path} [op #${(m.operationIndex ?? 0) + 1}] -->`); mutations += 1; }
|
|
28
|
+
}
|
|
29
|
+
if (report.covered?.length) {
|
|
30
|
+
lines.push("<!-- Covered (referenced and modified — consider extending the Approach): -->");
|
|
31
|
+
for (const c of report.covered) lines.push(`<!-- ~ ${c.path} [op #${(c.operationIndex ?? 0) + 1}] -->`);
|
|
32
|
+
}
|
|
33
|
+
lines.push("<!-- /kj sync -->", "");
|
|
34
|
+
return { block: lines.join("\n"), mutations };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function applyCanvasPatch({ canvasPath, report }) {
|
|
38
|
+
if (!canvasPath || typeof canvasPath !== "string") throw new Error("applyCanvasPatch: canvasPath is required");
|
|
39
|
+
if (!report || typeof report !== "object") throw new Error("applyCanvasPatch: report is required");
|
|
40
|
+
const hasDrift = (report.untracked?.length || 0) + (report.missing?.length || 0) > 0;
|
|
41
|
+
if (!hasDrift) return { patchedPath: canvasPath, backupPath: null, mutations: 0, skipped: true };
|
|
42
|
+
|
|
43
|
+
let original;
|
|
44
|
+
try { original = await fs.readFile(canvasPath, "utf-8"); }
|
|
45
|
+
catch (err) { throw new Error(`applyCanvasPatch: cannot read canvas ${canvasPath}: ${err.message}`, { cause: err }); }
|
|
46
|
+
|
|
47
|
+
const backupPath = `${canvasPath}.bak`;
|
|
48
|
+
await fs.writeFile(backupPath, original, "utf-8");
|
|
49
|
+
const { block, mutations } = renderDriftComment(report);
|
|
50
|
+
const trimmed = original.endsWith("\n") ? original : `${original}\n`;
|
|
51
|
+
const tmp = tempPath(canvasPath);
|
|
52
|
+
await fs.writeFile(tmp, `${trimmed}${block}\n`, "utf-8");
|
|
53
|
+
await fs.rename(tmp, canvasPath);
|
|
54
|
+
return { patchedPath: canvasPath, backupPath, mutations };
|
|
55
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kj sync — drift detection between code and the Canvas / plan
|
|
3
|
+
* (issue #540, MVP).
|
|
4
|
+
*
|
|
5
|
+
* Scope of THIS module: pure data transformation. Given a plan
|
|
6
|
+
* (with Operations and their file_hints) plus a list of currently
|
|
7
|
+
* tracked source files (mtime + path), classify each file:
|
|
8
|
+
*
|
|
9
|
+
* - covered — file IS referenced by some Operation's file_hint
|
|
10
|
+
* AND was modified since the plan was created.
|
|
11
|
+
* (Expected drift; plan still authoritative.)
|
|
12
|
+
* - untracked — file exists, modified, but no Operation references
|
|
13
|
+
* it. The user added it manually after the run; the
|
|
14
|
+
* Canvas needs a new Operation to reflect it.
|
|
15
|
+
* - missing — Operation references a file that no longer exists
|
|
16
|
+
* on disk. Either the file was deleted or the plan
|
|
17
|
+
* is wrong about its location.
|
|
18
|
+
* - clean — file is referenced by an Operation AND has not
|
|
19
|
+
* been modified since the plan. No action needed.
|
|
20
|
+
*
|
|
21
|
+
* The classifier is pure: caller does the I/O (walking the tree,
|
|
22
|
+
* stating files, parsing plan JSON). Easy to unit-test.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {object} plan — plan JSON with `operations` (or `hus`) carrying
|
|
27
|
+
* optional `file_hint` strings, and `createdAt` ISO
|
|
28
|
+
* @param {Array<{path: string, mtime: Date}>} files — files in the project
|
|
29
|
+
* @returns {{ covered: object[], untracked: object[], missing: object[], clean: object[] }}
|
|
30
|
+
*/
|
|
31
|
+
export function classifyDrift(plan, files) {
|
|
32
|
+
const planMtime = new Date(plan?.createdAt || plan?.updatedAt || 0);
|
|
33
|
+
|
|
34
|
+
// Collect every file_hint declared by any operation/HU. Both keys
|
|
35
|
+
// are accepted for back-compat with v2 plans (HUs) and Canvas
|
|
36
|
+
// (Operations).
|
|
37
|
+
const ops = Array.isArray(plan?.operations) ? plan.operations
|
|
38
|
+
: Array.isArray(plan?.hus) ? plan.hus
|
|
39
|
+
: [];
|
|
40
|
+
const hinted = new Map(); // file → operation index
|
|
41
|
+
ops.forEach((op, idx) => {
|
|
42
|
+
const hint = op.file_hint || op.fileHint;
|
|
43
|
+
if (typeof hint === "string" && hint.length > 0) {
|
|
44
|
+
hinted.set(hint, idx);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const out = { covered: [], untracked: [], missing: [], clean: [] };
|
|
49
|
+
const seenInFs = new Set();
|
|
50
|
+
|
|
51
|
+
for (const f of files) {
|
|
52
|
+
if (!f?.path) continue;
|
|
53
|
+
seenInFs.add(f.path);
|
|
54
|
+
const opIdx = hinted.get(f.path);
|
|
55
|
+
const modified = f.mtime instanceof Date && f.mtime > planMtime;
|
|
56
|
+
|
|
57
|
+
if (opIdx === undefined) {
|
|
58
|
+
// Not referenced by any operation. Two sub-cases:
|
|
59
|
+
// - modified: real drift, propose new operation.
|
|
60
|
+
// - not modified: probably a pre-existing file from before the
|
|
61
|
+
// plan; ignore (no action).
|
|
62
|
+
if (modified) {
|
|
63
|
+
out.untracked.push({ path: f.path, mtime: f.mtime, suggestion: "new operation" });
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (modified) {
|
|
68
|
+
out.covered.push({ path: f.path, mtime: f.mtime, operationIndex: opIdx });
|
|
69
|
+
} else {
|
|
70
|
+
out.clean.push({ path: f.path, operationIndex: opIdx });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Missing: operation says a file should exist, but we never saw it.
|
|
75
|
+
for (const [hint, opIdx] of hinted.entries()) {
|
|
76
|
+
if (!seenInFs.has(hint)) {
|
|
77
|
+
out.missing.push({ path: hint, operationIndex: opIdx, suggestion: "remove operation OR fix the file_hint" });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Render a drift report as plain-text for the terminal.
|
|
86
|
+
*/
|
|
87
|
+
export function formatDriftReport(report, plan) {
|
|
88
|
+
const lines = [`Drift report — plan ${plan?.planId || "(unknown)"}`, ""];
|
|
89
|
+
if (report.untracked.length === 0 && report.missing.length === 0 && report.covered.length === 0) {
|
|
90
|
+
lines.push(" No drift detected.");
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
}
|
|
93
|
+
if (report.covered.length > 0) {
|
|
94
|
+
lines.push(`Covered (${report.covered.length}) — modified, an Operation references it; consider extending its Approach:`);
|
|
95
|
+
for (const c of report.covered) lines.push(` ~ ${c.path} [op #${c.operationIndex + 1}]`);
|
|
96
|
+
lines.push("");
|
|
97
|
+
}
|
|
98
|
+
if (report.untracked.length > 0) {
|
|
99
|
+
lines.push(`Untracked (${report.untracked.length}) — modified, NO Operation references it; propose new Operation:`);
|
|
100
|
+
for (const u of report.untracked) lines.push(` + ${u.path}`);
|
|
101
|
+
lines.push("");
|
|
102
|
+
}
|
|
103
|
+
if (report.missing.length > 0) {
|
|
104
|
+
lines.push(`Missing (${report.missing.length}) — Operation file_hint points at a file that doesn't exist:`);
|
|
105
|
+
for (const m of report.missing) lines.push(` ! ${m.path} [op #${m.operationIndex + 1}] — ${m.suggestion}`);
|
|
106
|
+
lines.push("");
|
|
107
|
+
}
|
|
108
|
+
if (report.clean.length > 0) {
|
|
109
|
+
lines.push(`Clean (${report.clean.length}) — covered, unchanged.`);
|
|
110
|
+
}
|
|
111
|
+
lines.push("", "Run with --json for machine-readable output. Future: --apply to propose Canvas patches (issue #540 follow-ups).");
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|