@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,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope filter — auto-defers reviewer blocking issues that reference
|
|
3
|
+
* files NOT present in the diff. This prevents reviewer scope drift
|
|
4
|
+
* (flagging missing features, unchanged code, future tasks) from
|
|
5
|
+
* stalling the pipeline.
|
|
6
|
+
*
|
|
7
|
+
* Deferred issues are NOT forgotten — they are tracked in the session
|
|
8
|
+
* as technical debt that should be resolved in future iterations or
|
|
9
|
+
* follow-up tasks. The coder and planner receive context about what
|
|
10
|
+
* was deferred and why.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract the set of changed file paths from a unified diff string.
|
|
15
|
+
*/
|
|
16
|
+
export function extractDiffFiles(diff) {
|
|
17
|
+
const files = new Set();
|
|
18
|
+
for (const line of (diff || "").split("\n")) {
|
|
19
|
+
// Match "+++ b/path" lines in unified diff
|
|
20
|
+
const m = /^\+\+\+ b\/(.+)/.exec(line);
|
|
21
|
+
if (m) files.add(m[1]);
|
|
22
|
+
}
|
|
23
|
+
return files;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Determine whether a blocking issue is within scope of the diff.
|
|
28
|
+
*
|
|
29
|
+
* An issue is considered IN scope when:
|
|
30
|
+
* - It has no `file` field (general concern about the diff)
|
|
31
|
+
* - Its `file` matches one of the changed files (exact or suffix match)
|
|
32
|
+
* - It references a pattern present in the diff content itself
|
|
33
|
+
*
|
|
34
|
+
* An issue is OUT of scope when:
|
|
35
|
+
* - It explicitly references a file NOT in the diff
|
|
36
|
+
*/
|
|
37
|
+
export function isIssueInScope(issue, diffFiles, diffContent) {
|
|
38
|
+
const file = (issue.file || "").trim();
|
|
39
|
+
|
|
40
|
+
// No file specified — the reviewer is commenting on the diff generally
|
|
41
|
+
if (!file) return true;
|
|
42
|
+
|
|
43
|
+
// Direct match
|
|
44
|
+
if (diffFiles.has(file)) return true;
|
|
45
|
+
|
|
46
|
+
// Suffix match (reviewer might use full path vs relative)
|
|
47
|
+
for (const df of diffFiles) {
|
|
48
|
+
if (df.endsWith(file) || file.endsWith(df)) return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check if the file path appears anywhere in the diff content
|
|
52
|
+
// (covers cases where the file is referenced in imports/requires)
|
|
53
|
+
if (diffContent?.includes(file)) return true;
|
|
54
|
+
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Filter a review result, demoting out-of-scope blocking issues to
|
|
60
|
+
* non-blocking suggestions.
|
|
61
|
+
*
|
|
62
|
+
* Returns { review, demoted, deferred, allDemoted } where:
|
|
63
|
+
* - review: the filtered review (may flip approved to true)
|
|
64
|
+
* - demoted: array of original issues that were demoted
|
|
65
|
+
* - deferred: structured deferred issues with metadata for session tracking
|
|
66
|
+
* - allDemoted: true if ALL blocking issues were out of scope
|
|
67
|
+
*/
|
|
68
|
+
export function filterReviewScope(review, diff) {
|
|
69
|
+
if (!review || review.approved) {
|
|
70
|
+
return { review, demoted: [], deferred: [], allDemoted: false };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const diffFiles = extractDiffFiles(diff);
|
|
74
|
+
|
|
75
|
+
// If we can't parse diff files, don't filter (safety)
|
|
76
|
+
if (diffFiles.size === 0) {
|
|
77
|
+
return { review, demoted: [], deferred: [], allDemoted: false };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const inScope = [];
|
|
81
|
+
const demoted = [];
|
|
82
|
+
|
|
83
|
+
for (const issue of review.blocking_issues || []) {
|
|
84
|
+
if (isIssueInScope(issue, diffFiles, diff)) {
|
|
85
|
+
inScope.push(issue);
|
|
86
|
+
} else {
|
|
87
|
+
demoted.push(issue);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (demoted.length === 0) {
|
|
92
|
+
return { review, demoted: [], deferred: [], allDemoted: false };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const demotedSuggestions = demoted.map(
|
|
96
|
+
(issue) => `[auto-demoted] ${issue.file || "unknown"}: ${issue.description || issue.id || "no description"}`
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const filtered = {
|
|
100
|
+
...review,
|
|
101
|
+
blocking_issues: inScope,
|
|
102
|
+
non_blocking_suggestions: [
|
|
103
|
+
...(review.non_blocking_suggestions || []),
|
|
104
|
+
...demotedSuggestions
|
|
105
|
+
]
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// If no in-scope blocking issues remain, auto-approve
|
|
109
|
+
const allDemoted = inScope.length === 0;
|
|
110
|
+
if (allDemoted) {
|
|
111
|
+
filtered.approved = true;
|
|
112
|
+
filtered.summary = `${review.summary || ""} [Auto-approved: ${demoted.length} out-of-scope issue(s) demoted to suggestions]`.trim();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Build structured deferred issues for session tracking
|
|
116
|
+
const deferred = demoted.map((issue) => ({
|
|
117
|
+
id: issue.id || null,
|
|
118
|
+
file: issue.file || null,
|
|
119
|
+
severity: issue.severity || "medium",
|
|
120
|
+
description: issue.description || "no description",
|
|
121
|
+
suggested_fix: issue.suggested_fix || null,
|
|
122
|
+
deferred_at: new Date().toISOString(),
|
|
123
|
+
reason: "out_of_scope"
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
return { review: filtered, demoted, deferred, allDemoted };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Build a human-readable summary of deferred issues for injection
|
|
131
|
+
* into coder/planner prompts so they are aware of the tech debt.
|
|
132
|
+
*/
|
|
133
|
+
export function buildDeferredContext(deferredIssues) {
|
|
134
|
+
if (!deferredIssues?.length) return "";
|
|
135
|
+
|
|
136
|
+
const lines = [
|
|
137
|
+
"## Deferred reviewer concerns (technical debt)",
|
|
138
|
+
"The following issues were flagged by the reviewer but deferred because they are outside the current diff scope.",
|
|
139
|
+
"You do NOT need to fix them now, but be aware of them:",
|
|
140
|
+
""
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
for (const issue of deferredIssues) {
|
|
144
|
+
const file = issue.file ? `\`${issue.file}\`` : "general";
|
|
145
|
+
const fix = issue.suggested_fix ? ` — Suggestion: ${issue.suggested_fix}` : "";
|
|
146
|
+
lines.push(`- [${issue.severity}] ${file}: ${issue.description}${fix}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
lines.push("", "If your current changes naturally address any of these, great. Otherwise, they will be tracked for future resolution.");
|
|
150
|
+
|
|
151
|
+
return lines.join("\n");
|
|
152
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git-free diff generation using filesystem snapshots.
|
|
3
|
+
*
|
|
4
|
+
* Takes a snapshot of project files (path → content hash), then compares
|
|
5
|
+
* a later snapshot to generate a unified diff. Works on any OS, no git needed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
9
|
+
import { join, relative } from "node:path";
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_IGNORE = new Set([
|
|
13
|
+
"node_modules", ".git", ".kj", ".karajan", "dist", "build",
|
|
14
|
+
".next", ".nuxt", ".svelte-kit", "__pycache__", ".venv",
|
|
15
|
+
"coverage", ".nyc_output", ".reviews"
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const MAX_FILE_SIZE = 512 * 1024; // 512KB — skip large files
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Scan a directory recursively and return a Map of relative path → content hash.
|
|
22
|
+
* @param {string} rootDir
|
|
23
|
+
* @returns {Promise<Map<string, string>>} path → sha256 hash
|
|
24
|
+
*/
|
|
25
|
+
export async function takeSnapshot(rootDir) {
|
|
26
|
+
const snapshot = new Map();
|
|
27
|
+
await scanDir(rootDir, rootDir, snapshot);
|
|
28
|
+
return snapshot;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function scanDir(dir, rootDir, snapshot) {
|
|
32
|
+
let entries;
|
|
33
|
+
try {
|
|
34
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
35
|
+
} catch { return; }
|
|
36
|
+
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
if (DEFAULT_IGNORE.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
39
|
+
|
|
40
|
+
const fullPath = join(dir, entry.name);
|
|
41
|
+
if (entry.isDirectory()) {
|
|
42
|
+
await scanDir(fullPath, rootDir, snapshot);
|
|
43
|
+
} else if (entry.isFile()) {
|
|
44
|
+
try {
|
|
45
|
+
const s = await stat(fullPath);
|
|
46
|
+
if (s.size > MAX_FILE_SIZE) continue;
|
|
47
|
+
const content = await readFile(fullPath);
|
|
48
|
+
const hash = createHash("sha256").update(content).digest("hex");
|
|
49
|
+
snapshot.set(relative(rootDir, fullPath), hash);
|
|
50
|
+
} catch { /* skip unreadable files */ }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Compare two snapshots and generate a unified diff string.
|
|
57
|
+
* @param {Map<string, string>} before — snapshot before coder
|
|
58
|
+
* @param {Map<string, string>|null} after — snapshot after coder (null = read from disk)
|
|
59
|
+
* @param {string} rootDir — project root (for reading file contents)
|
|
60
|
+
* @returns {Promise<string>} unified diff text
|
|
61
|
+
*/
|
|
62
|
+
export async function generateSnapshotDiff(before, after, rootDir) {
|
|
63
|
+
if (!after) {
|
|
64
|
+
after = await takeSnapshot(rootDir);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const lines = [];
|
|
68
|
+
|
|
69
|
+
// New files (in after but not before)
|
|
70
|
+
for (const [path, _hash] of after) {
|
|
71
|
+
if (!before.has(path)) {
|
|
72
|
+
const content = await safeRead(join(rootDir, path));
|
|
73
|
+
if (content !== null) {
|
|
74
|
+
lines.push(`diff --snapshot a/${path} b/${path}`);
|
|
75
|
+
lines.push("new file");
|
|
76
|
+
lines.push(`--- /dev/null`);
|
|
77
|
+
lines.push(`+++ b/${path}`);
|
|
78
|
+
for (const line of content.split("\n")) {
|
|
79
|
+
lines.push(`+${line}`);
|
|
80
|
+
}
|
|
81
|
+
lines.push("");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Modified files (in both, different hash)
|
|
87
|
+
for (const [path, hash] of after) {
|
|
88
|
+
if (before.has(path) && before.get(path) !== hash) {
|
|
89
|
+
const content = await safeRead(join(rootDir, path));
|
|
90
|
+
if (content !== null) {
|
|
91
|
+
lines.push(`diff --snapshot a/${path} b/${path}`);
|
|
92
|
+
lines.push("modified file");
|
|
93
|
+
lines.push(`--- a/${path}`);
|
|
94
|
+
lines.push(`+++ b/${path}`);
|
|
95
|
+
// Show full new content (we don't have the old content)
|
|
96
|
+
for (const line of content.split("\n")) {
|
|
97
|
+
lines.push(`+${line}`);
|
|
98
|
+
}
|
|
99
|
+
lines.push("");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Deleted files (in before but not after)
|
|
105
|
+
for (const [path] of before) {
|
|
106
|
+
if (!after.has(path)) {
|
|
107
|
+
lines.push(`diff --snapshot a/${path} b/${path}`);
|
|
108
|
+
lines.push("deleted file");
|
|
109
|
+
lines.push(`--- a/${path}`);
|
|
110
|
+
lines.push(`+++ /dev/null`);
|
|
111
|
+
lines.push("");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return lines.join("\n");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function safeRead(filePath) {
|
|
119
|
+
try {
|
|
120
|
+
return await readFile(filePath, "utf-8");
|
|
121
|
+
} catch { return null; }
|
|
122
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solomon arbitration (AB-E, KJC-TSK-0651) — when the brain (host agent)
|
|
3
|
+
* disagrees with the reviewer's verdict, a THIRD AI arbitrates and its
|
|
4
|
+
* ruling is recorded in the verdict store, tied to the exact diff.
|
|
5
|
+
*
|
|
6
|
+
* Hard rules:
|
|
7
|
+
* - brain ≠ reviewer ≠ solomon. Two-agent machines get an actionable
|
|
8
|
+
* error — self-arbitration would void the whole point.
|
|
9
|
+
* - Security issues from the reviewer are NEVER overridable (inherited
|
|
10
|
+
* from the pipeline's Solomon): the arbiter is not even consulted.
|
|
11
|
+
* - An approve ruling writes an approved verdict (`solomon:<agent>`) for
|
|
12
|
+
* the same diff hash, so the pre-commit gate opens structurally; a
|
|
13
|
+
* reject keeps it closed. Both record the full conflict for audit.
|
|
14
|
+
*/
|
|
15
|
+
import { createAgent } from "../agents/index.js";
|
|
16
|
+
import { parseMaybeJsonString } from "./parser.js";
|
|
17
|
+
import { detectAvailableAgents, detectHostAgent } from "../utils/agent-detect.js";
|
|
18
|
+
import { diffHash, loadVerdict, saveVerdict } from "./verdict-store.js";
|
|
19
|
+
|
|
20
|
+
const SECURITY_PATTERN = /injection|xss|csrf|secret|credential|password|token leak|auth(entication|orization)?|crypt|session|sanitiz|traversal|rce\b/i;
|
|
21
|
+
|
|
22
|
+
// Structured signal first (`category: "security"` — part of the reviewer's
|
|
23
|
+
// JSON schema since AB-E); the free-text pattern stays as a fail-closed net
|
|
24
|
+
// for verdicts recorded before the schema carried categories.
|
|
25
|
+
function isSecurityIssue(issue) {
|
|
26
|
+
return issue.category === "security"
|
|
27
|
+
|| SECURITY_PATTERN.test(`${issue.id || ""} ${issue.description || ""}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function pickThirdParty({ config, hostAgent, reviewerAgent, detectAgents = detectAvailableAgents }) {
|
|
31
|
+
const excluded = new Set([hostAgent, reviewerAgent]);
|
|
32
|
+
const configured = config?.roles?.solomon?.provider;
|
|
33
|
+
const agents = await detectAgents();
|
|
34
|
+
const candidates = agents.filter((a) => a.available && !excluded.has(a.name)).map((a) => a.name);
|
|
35
|
+
if (configured && candidates.includes(configured)) return configured;
|
|
36
|
+
return candidates[0] || null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function runSolomonArbitration({
|
|
40
|
+
diff, position, config, logger, projectDir,
|
|
41
|
+
hostAgent = detectHostAgent(),
|
|
42
|
+
createAgentFn = createAgent,
|
|
43
|
+
detectAgents = detectAvailableAgents,
|
|
44
|
+
}) {
|
|
45
|
+
if (!position || !position.trim()) {
|
|
46
|
+
throw new Error("kj solomon requires --position \"<why you disagree with the reviewer>\"");
|
|
47
|
+
}
|
|
48
|
+
const verdict = await loadVerdict(projectDir, diffHash(diff));
|
|
49
|
+
if (!verdict || verdict.verdict !== "rejected") {
|
|
50
|
+
throw new Error("nothing to arbitrate — there is no rejected verdict for the current diff (run `kj review --staged` first)");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ABSOLUTE RULE: security findings are never overridable.
|
|
54
|
+
const securityIssues = (verdict.issues || []).filter(isSecurityIssue);
|
|
55
|
+
if (securityIssues.length > 0) {
|
|
56
|
+
const record = await saveVerdict(projectDir, diff, {
|
|
57
|
+
...verdict,
|
|
58
|
+
arbitration: { position, ruling: "reject", reasoning: "security issues from the reviewer are never overridable", solomon: null },
|
|
59
|
+
});
|
|
60
|
+
return { ruling: "reject", reasoning: "security issues are never overridable — fix them", solomon: null, record };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const solomon = await pickThirdParty({ config, hostAgent, reviewerAgent: verdict.reviewer, detectAgents });
|
|
64
|
+
if (!solomon) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`arbitration requires a third AI distinct from the brain (${hostAgent || "unknown"}) and the reviewer (${verdict.reviewer}) — install a third agent CLI`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const prompt = [
|
|
71
|
+
"You are Solomon, the neutral arbiter between two AIs in a coding workflow.",
|
|
72
|
+
"The reviewer REJECTED a diff; the orchestrating brain DISAGREES. Decide who is right.",
|
|
73
|
+
"Never approve to save time or effort — only if the reviewer's objections are genuinely wrong or immaterial for this diff.",
|
|
74
|
+
'Return only one JSON object: {"ruling":"approve"|"reject","reasoning":string}. "approve" = the brain is right, the diff may ship as-is. "reject" = the reviewer is right, the brain must fix the issues.',
|
|
75
|
+
"", "## Reviewer's blocking issues",
|
|
76
|
+
JSON.stringify(verdict.issues || [], null, 2),
|
|
77
|
+
"", "## Brain's position", position,
|
|
78
|
+
"", "## The diff under dispute", diff,
|
|
79
|
+
].join("\n");
|
|
80
|
+
|
|
81
|
+
logger?.info?.(`kj solomon: brain=${hostAgent || "?"} vs reviewer=${verdict.reviewer} → arbiter=${solomon}`);
|
|
82
|
+
const agent = createAgentFn(solomon, config, logger);
|
|
83
|
+
const result = await agent.reviewTask({ prompt, role: "solomon" });
|
|
84
|
+
if (!result?.ok) {
|
|
85
|
+
// KJC-BUG-0121 layer 3: a binary on PATH is not an OPERATIONAL arbiter
|
|
86
|
+
// (dead account tier, auth, trust). Fail with the way out, not just the
|
|
87
|
+
// wreckage: name the other agents that could arbitrate instead.
|
|
88
|
+
const others = (await detectAgents())
|
|
89
|
+
.filter((a) => a.available && a.name !== solomon && a.name !== hostAgent && a.name !== verdict.reviewer)
|
|
90
|
+
.map((a) => a.name);
|
|
91
|
+
const hint = others.length > 0
|
|
92
|
+
? `If ${solomon} is not operational on this machine (account tier, auth, workspace trust), set roles.solomon.provider in your kj config to another agent: ${others.join(", ")}.`
|
|
93
|
+
: `No other agent CLI on this machine can arbitrate (needs one distinct from brain "${hostAgent}" and reviewer "${verdict.reviewer}") — install a third agent CLI or fix ${solomon}.`;
|
|
94
|
+
throw new Error(`solomon ${solomon} failed: ${String(result?.error || "no output").slice(0, 400)}\n${hint}`);
|
|
95
|
+
}
|
|
96
|
+
const parsed = parseMaybeJsonString(result.output);
|
|
97
|
+
if (!parsed || !["approve", "reject"].includes(parsed.ruling)) {
|
|
98
|
+
throw new Error(`solomon ${solomon} returned no parseable ruling`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const arbitration = {
|
|
102
|
+
position, ruling: parsed.ruling, reasoning: parsed.reasoning || "", solomon,
|
|
103
|
+
originalVerdict: { reviewer: verdict.reviewer, issues: verdict.issues },
|
|
104
|
+
};
|
|
105
|
+
const record = parsed.ruling === "approve"
|
|
106
|
+
? await saveVerdict(projectDir, diff, {
|
|
107
|
+
verdict: "approved", reviewer: `solomon:${solomon}`, host: hostAgent || null,
|
|
108
|
+
issues: [], summary: `Arbitration overrode ${verdict.reviewer}'s rejection: ${parsed.reasoning || ""}`.trim(), arbitration,
|
|
109
|
+
})
|
|
110
|
+
: await saveVerdict(projectDir, diff, { ...verdict, arbitration });
|
|
111
|
+
|
|
112
|
+
return { ruling: parsed.ruling, reasoning: parsed.reasoning || "", solomon, record };
|
|
113
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sonar pre-gate for `kj review --staged` (KJC-TSK-0676, user request
|
|
3
|
+
* 2026-07-23). With the brain outside the core (v4), skipping sonar is one
|
|
4
|
+
* decision away — proven the day the brain itself shipped 3 sonar issues in
|
|
5
|
+
* new code. Hung off the review gate, it becomes a gate, not discipline:
|
|
6
|
+
* deterministic findings on the STAGED files come back before any AI
|
|
7
|
+
* verdict, and BLOCKER/CRITICAL ones reject without spending reviewer
|
|
8
|
+
* tokens. Unavailable sonar degrades loudly ({available:false, reason}) —
|
|
9
|
+
* a laptop without Docker still commits.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { runSonarScan } from "../sonar/scanner.js";
|
|
13
|
+
import { getOpenIssues } from "../sonar/api.js";
|
|
14
|
+
import { acquireToolLock } from "../utils/tool-governor.js";
|
|
15
|
+
|
|
16
|
+
const BLOCKING_SEVERITIES = new Set(["BLOCKER", "CRITICAL"]);
|
|
17
|
+
|
|
18
|
+
/** Repo-relative path of a sonar issue (component is "<projectKey>:<path>"). */
|
|
19
|
+
export function issueFile(issue) {
|
|
20
|
+
const component = String(issue?.component || "");
|
|
21
|
+
const idx = component.indexOf(":");
|
|
22
|
+
return idx === -1 ? component : component.slice(idx + 1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** One printable line per finding: (SEVERITY) [file:line] rule — message. */
|
|
26
|
+
export function formatSonarFinding(issue) {
|
|
27
|
+
const line = issue.line ? `:${issue.line}` : "";
|
|
28
|
+
return `(${issue.severity}) [${issueFile(issue)}${line}] ${issue.rule} — ${issue.message}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Scan the project (single-flight via the tool governor) and return the
|
|
33
|
+
* open issues that live on the staged files, split by blocking severity.
|
|
34
|
+
* Every failure path degrades to {available:false, reason} — the pre-gate
|
|
35
|
+
* never breaks the review, it only refuses to stay silent.
|
|
36
|
+
*/
|
|
37
|
+
export async function runSonarPregate({ config, stagedFiles = [], logger = null }) {
|
|
38
|
+
if (config?.review_gate?.sonar === false) {
|
|
39
|
+
return { available: false, reason: "disabled in config (review_gate.sonar: false)" };
|
|
40
|
+
}
|
|
41
|
+
let lock = null;
|
|
42
|
+
try {
|
|
43
|
+
lock = await acquireToolLock("sonar-scanner", { timeoutMs: 300_000 });
|
|
44
|
+
const scan = await runSonarScan(config);
|
|
45
|
+
if (!scan.ok) {
|
|
46
|
+
return { available: false, reason: (scan.stderr || scan.stdout || "sonar scan failed").trim() };
|
|
47
|
+
}
|
|
48
|
+
const res = await getOpenIssues(config, scan.projectKey);
|
|
49
|
+
const staged = new Set(stagedFiles);
|
|
50
|
+
const onStaged = (res.issues || []).filter((i) => staged.has(issueFile(i)));
|
|
51
|
+
return {
|
|
52
|
+
available: true,
|
|
53
|
+
blocking: onStaged.filter((i) => BLOCKING_SEVERITIES.has(String(i.severity).toUpperCase())),
|
|
54
|
+
advisory: onStaged.filter((i) => !BLOCKING_SEVERITIES.has(String(i.severity).toUpperCase())),
|
|
55
|
+
totalProject: res.total ?? (res.issues || []).length,
|
|
56
|
+
};
|
|
57
|
+
} catch (err) {
|
|
58
|
+
logger?.warn?.(`[sonar-pregate] ${err.message}`);
|
|
59
|
+
return { available: false, reason: err.message };
|
|
60
|
+
} finally {
|
|
61
|
+
lock?.release?.();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// TDD discipline gate (KJC-TSK-0398 PR1).
|
|
2
|
+
// PR1 ships the pure decision logic with injected runTests/stashFiles
|
|
3
|
+
// helpers. PR2 ships the real surgical stash. PR3 wires the gate into
|
|
4
|
+
// the pipeline behind `development.require_red_then_green`.
|
|
5
|
+
|
|
6
|
+
const PARSERS = {
|
|
7
|
+
vitest: parseVitestFailures,
|
|
8
|
+
jest: parseVitestFailures,
|
|
9
|
+
pytest: parsePytestFailures,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function parseVitestFailures(output) {
|
|
13
|
+
const failures = new Set();
|
|
14
|
+
for (const line of String(output || "").split("\n")) {
|
|
15
|
+
const m = line.match(/^\s*(?:FAIL|×|✖)\s+(\S+)/);
|
|
16
|
+
if (m) failures.add(m[1].trim());
|
|
17
|
+
}
|
|
18
|
+
return failures;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parsePytestFailures(output) {
|
|
22
|
+
const failures = new Set();
|
|
23
|
+
for (const line of String(output || "").split("\n")) {
|
|
24
|
+
const m = line.match(/^FAILED\s+(\S+)/);
|
|
25
|
+
if (m) failures.add(m[1].trim());
|
|
26
|
+
}
|
|
27
|
+
return failures;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function anyNewTestIn(failures, testFiles) {
|
|
31
|
+
return [...failures].some((f) => testFiles.some((t) => f.includes(t) || t.includes(f)));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function verifyTddDiscipline({
|
|
35
|
+
projectDir,
|
|
36
|
+
sourceFiles = [],
|
|
37
|
+
testFiles = [],
|
|
38
|
+
framework = null,
|
|
39
|
+
runTests,
|
|
40
|
+
stashFiles,
|
|
41
|
+
restoreFiles,
|
|
42
|
+
} = {}) {
|
|
43
|
+
if (sourceFiles.length === 0) {
|
|
44
|
+
return { ok: true, reason: "no_source_changes", sourceFiles, testFiles };
|
|
45
|
+
}
|
|
46
|
+
if (testFiles.length === 0) {
|
|
47
|
+
return { ok: true, reason: "no_test_changes", sourceFiles, testFiles };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const parser = framework ? PARSERS[framework] : null;
|
|
51
|
+
if (!parser) {
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
reason: "framework_unsupported",
|
|
55
|
+
sourceFiles,
|
|
56
|
+
testFiles,
|
|
57
|
+
warning: `tdd-discipline: framework "${framework || "unknown"}" has no failure parser; gate skipped`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let stashToken = null;
|
|
62
|
+
try {
|
|
63
|
+
stashToken = await stashFiles(sourceFiles);
|
|
64
|
+
const before = await runTests(projectDir);
|
|
65
|
+
if (!anyNewTestIn(parser(before.output), testFiles)) {
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
reason: "tests_pass_without_impl",
|
|
69
|
+
sourceFiles,
|
|
70
|
+
testFiles,
|
|
71
|
+
message:
|
|
72
|
+
"TDD discipline violation: new tests pass without the implementation. Tests must fail first (red), then pass (green).",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
} finally {
|
|
76
|
+
if (stashToken) await restoreFiles(stashToken);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const after = await runTests(projectDir);
|
|
80
|
+
if (anyNewTestIn(parser(after.output), testFiles)) {
|
|
81
|
+
return {
|
|
82
|
+
ok: false,
|
|
83
|
+
reason: "tests_fail_with_impl",
|
|
84
|
+
sourceFiles,
|
|
85
|
+
testFiles,
|
|
86
|
+
message: "TDD discipline violation: new tests still fail with the implementation present.",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return { ok: true, reason: "red_then_green_verified", sourceFiles, testFiles };
|
|
90
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
function extractChangedFiles(diff) {
|
|
2
|
+
const files = new Set();
|
|
3
|
+
const lines = String(diff || "").split("\n");
|
|
4
|
+
for (const line of lines) {
|
|
5
|
+
// Support both git diff and snapshot diff formats
|
|
6
|
+
if (!line.startsWith("diff --git ") && !line.startsWith("diff --snapshot ")) continue;
|
|
7
|
+
const parts = line.split(" ");
|
|
8
|
+
const b = parts[3] || "";
|
|
9
|
+
if (b.startsWith("b/")) files.add(b.slice(2));
|
|
10
|
+
}
|
|
11
|
+
return [...files];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isTestFile(file, patterns = []) {
|
|
15
|
+
const normalized = `/${file}`;
|
|
16
|
+
return patterns.some((pattern) => normalized.includes(pattern));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isSourceFile(file, extensions = []) {
|
|
20
|
+
return extensions.some((ext) => file.endsWith(ext));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const SKIP_TDD_TASK_TYPES = new Set(["doc", "infra", "no-code"]);
|
|
24
|
+
|
|
25
|
+
const DEFAULT_TEST_PATTERNS = ["/tests/", "/__tests__/", ".test.", ".spec.", "/src/test/", "/test/", "Test.java", "Test.kt", "test_", "_test.py", "_test.go", "Test.cs", "_spec.rb", "Test.php", "_test.dart", "Tests.swift"];
|
|
26
|
+
const DEFAULT_SOURCE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx", ".py", ".go", ".java", ".kt", ".rb", ".php", ".cs", ".rs", ".swift", ".dart"];
|
|
27
|
+
|
|
28
|
+
export function evaluateTddPolicy(diff, developmentConfig = {}, untrackedFiles = [], taskType = null) {
|
|
29
|
+
if (taskType && SKIP_TDD_TASK_TYPES.has(taskType)) {
|
|
30
|
+
return { ok: true, reason: "tdd_not_applicable_for_task_type", sourceFiles: [], testFiles: [] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const requireTestChanges = developmentConfig.require_test_changes !== false;
|
|
34
|
+
const patterns = developmentConfig.test_file_patterns || DEFAULT_TEST_PATTERNS;
|
|
35
|
+
const extensions = developmentConfig.source_file_extensions || DEFAULT_SOURCE_EXTENSIONS;
|
|
36
|
+
|
|
37
|
+
const diffFiles = extractChangedFiles(diff);
|
|
38
|
+
const extra = Array.isArray(untrackedFiles) ? untrackedFiles : [];
|
|
39
|
+
const files = [...new Set([...diffFiles, ...extra])];
|
|
40
|
+
const sourceFiles = files.filter((f) => isSourceFile(f, extensions) && !isTestFile(f, patterns));
|
|
41
|
+
const testFiles = files.filter((f) => isTestFile(f, patterns));
|
|
42
|
+
|
|
43
|
+
if (!requireTestChanges) {
|
|
44
|
+
return {
|
|
45
|
+
ok: true,
|
|
46
|
+
reason: "test_changes_not_required",
|
|
47
|
+
sourceFiles,
|
|
48
|
+
testFiles
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (sourceFiles.length === 0) {
|
|
53
|
+
const reason = files.length === 0 ? "no_diff_available" : "no_source_changes";
|
|
54
|
+
return { ok: true, reason, sourceFiles, testFiles };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (testFiles.length === 0) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
reason: "source_changes_without_tests",
|
|
61
|
+
sourceFiles,
|
|
62
|
+
testFiles,
|
|
63
|
+
message:
|
|
64
|
+
"TDD policy violation: source code changed without test changes. Add/adjust tests first, then implementation."
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { ok: true, reason: "tests_present", sourceFiles, testFiles };
|
|
69
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests-with-code gate (KJC-TSK-0687, MG-B of épica KJC-PCS-0068). The
|
|
3
|
+
* verifiable half of TDD is deterministic: a staged diff that touches
|
|
4
|
+
* source files without touching a single test warns — or blocks, via
|
|
5
|
+
* `method_gates.tests_with_code`. Test-FIRST remains the reviewer's
|
|
6
|
+
* judgment; test-PRESENT belongs to the gate. Patterns come from the
|
|
7
|
+
* project's own `development.*` config (same ones headless enforces).
|
|
8
|
+
* KJ_ALLOW_NO_TESTS=1 is the explicit escape hatch.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const DEFAULT_TEST_PATTERNS = ["/tests/", "/__tests__/", ".test.", ".spec."];
|
|
12
|
+
const DEFAULT_SOURCE_EXTS = [".js", ".jsx", ".ts", ".tsx", ".py", ".go", ".java", ".rb", ".php", ".cs"];
|
|
13
|
+
|
|
14
|
+
export function checkTestsWithCode({ config = {}, stagedFiles = [], env = process.env }) {
|
|
15
|
+
if (env.KJ_ALLOW_NO_TESTS === "1") {
|
|
16
|
+
return { ok: true, mode: "exempt", reason: "KJ_ALLOW_NO_TESTS=1 (explicit escape hatch)" };
|
|
17
|
+
}
|
|
18
|
+
const patterns = config.development?.test_file_patterns || DEFAULT_TEST_PATTERNS;
|
|
19
|
+
const exts = config.development?.source_file_extensions || DEFAULT_SOURCE_EXTS;
|
|
20
|
+
// Staged paths are repo-relative (no leading slash) — normalize so the
|
|
21
|
+
// "/tests/" style patterns also match the top-level tests directory.
|
|
22
|
+
const isTest = (f) => patterns.some((p) => `/${f}`.includes(p));
|
|
23
|
+
const sources = stagedFiles.filter((f) => !isTest(f) && exts.some((e) => f.endsWith(e)));
|
|
24
|
+
const hasTests = stagedFiles.some(isTest);
|
|
25
|
+
|
|
26
|
+
if (sources.length === 0 || hasTests) return { ok: true, mode: "pass" };
|
|
27
|
+
|
|
28
|
+
const policy = config.method_gates?.tests_with_code || "warn";
|
|
29
|
+
const reason = `source changes without any test change (${sources.slice(0, 5).join(", ")}${sources.length > 5 ? "…" : ""}) — the failing test comes first; add one or KJ_ALLOW_NO_TESTS=1 for a deliberate exception`;
|
|
30
|
+
return policy === "block"
|
|
31
|
+
? { ok: false, mode: "block", sources, reason }
|
|
32
|
+
: { ok: true, mode: "warn", sources, reason };
|
|
33
|
+
}
|