@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,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt-injection findings collector for `kj audit` (KJC-TSK-0498).
|
|
3
|
+
* Scans `.karajan/specs/**`, `.karajan/domain.md` and
|
|
4
|
+
* `.karajan/onboarding/**` reusing `src/utils/injection-guard.js`.
|
|
5
|
+
* Best-effort: missing folders return `scanned: 0`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { scanForInjection } from "../utils/injection-guard.js";
|
|
11
|
+
|
|
12
|
+
const MAX_FILE_BYTES = 256 * 1024;
|
|
13
|
+
const MAX_DEPTH = 6;
|
|
14
|
+
const SEVERITY_BY_TYPE = { directive: "HIGH", unicode: "MEDIUM", comment_block: "LOW" };
|
|
15
|
+
|
|
16
|
+
const SCAN_TARGETS = [
|
|
17
|
+
{ rel: ".karajan/specs", kind: "dir" },
|
|
18
|
+
{ rel: ".karajan/onboarding", kind: "dir" },
|
|
19
|
+
{ rel: ".karajan/domain.md", kind: "file" },
|
|
20
|
+
// KJC-TSK-0695: the agent-context surface — whatever lands in the host
|
|
21
|
+
// agent's context every session is the real injection vector.
|
|
22
|
+
{ rel: "CLAUDE.md", kind: "file" },
|
|
23
|
+
{ rel: "AGENTS.md", kind: "file" },
|
|
24
|
+
{ rel: "GEMINI.md", kind: "file" },
|
|
25
|
+
{ rel: ".cursorrules", kind: "file" },
|
|
26
|
+
{ rel: path.join(".github", "copilot-instructions.md"), kind: "file" },
|
|
27
|
+
{ rel: "templates", kind: "dir" },
|
|
28
|
+
{ rel: ".rulesync", kind: "dir" },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export async function collectInjectionFindings(rootDir, logger = null) {
|
|
32
|
+
if (!rootDir) return { available: false, reason: "rootDir not provided" };
|
|
33
|
+
const findings = [];
|
|
34
|
+
let scanned = 0;
|
|
35
|
+
try {
|
|
36
|
+
for (const target of SCAN_TARGETS) {
|
|
37
|
+
const abs = path.join(rootDir, target.rel);
|
|
38
|
+
const files = target.kind === "dir"
|
|
39
|
+
? await listFiles(abs, 0)
|
|
40
|
+
: (await fileExists(abs)) ? [abs] : [];
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
scanned += 1;
|
|
43
|
+
const content = await safeRead(file);
|
|
44
|
+
if (!content) continue;
|
|
45
|
+
const result = scanForInjection(content);
|
|
46
|
+
if (result.clean) continue;
|
|
47
|
+
for (const f of result.findings) {
|
|
48
|
+
findings.push({
|
|
49
|
+
file: path.relative(rootDir, file),
|
|
50
|
+
type: f.type,
|
|
51
|
+
pattern: f.pattern,
|
|
52
|
+
snippet: f.snippet,
|
|
53
|
+
line: f.line,
|
|
54
|
+
severity: SEVERITY_BY_TYPE[f.type] || "LOW",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (logger?.warn) logger.warn(`injection-findings collector failed: ${err.message}`);
|
|
61
|
+
return { available: false, reason: `collector failed: ${err.message}` };
|
|
62
|
+
}
|
|
63
|
+
return { available: true, scanned, total: findings.length, findings };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function groupInjectionBySeverity(findings) {
|
|
67
|
+
const groups = { HIGH: [], MEDIUM: [], LOW: [] };
|
|
68
|
+
for (const f of findings || []) {
|
|
69
|
+
const sev = (f.severity || "LOW").toUpperCase();
|
|
70
|
+
if (groups[sev]) groups[sev].push(f);
|
|
71
|
+
else groups.LOW.push(f);
|
|
72
|
+
}
|
|
73
|
+
return groups;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function listFiles(dir, depth) {
|
|
77
|
+
if (depth > MAX_DEPTH) return [];
|
|
78
|
+
let entries;
|
|
79
|
+
try {
|
|
80
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
81
|
+
} catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const out = [];
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
const abs = path.join(dir, entry.name);
|
|
87
|
+
if (entry.isDirectory()) {
|
|
88
|
+
const nested = await listFiles(abs, depth + 1);
|
|
89
|
+
out.push(...nested);
|
|
90
|
+
} else if (entry.isFile()) {
|
|
91
|
+
out.push(abs);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function fileExists(file) {
|
|
98
|
+
try {
|
|
99
|
+
const s = await stat(file);
|
|
100
|
+
return s.isFile();
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function safeRead(file) {
|
|
107
|
+
try {
|
|
108
|
+
const s = await stat(file);
|
|
109
|
+
if (s.size > MAX_FILE_BYTES) return null;
|
|
110
|
+
return await readFile(file, "utf8");
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Generalised deterministic false-positive filter for kj audit collectors.
|
|
2
|
+
//
|
|
3
|
+
// Originally introduced as `src/sonar/issue-filter.js` in v2.16.0 (KJC-TSK-0416)
|
|
4
|
+
// for Sonar findings. Generalised here so every collector (sonar, knip, madge,
|
|
5
|
+
// osv, semgrep, …) can reuse the same two mechanisms:
|
|
6
|
+
//
|
|
7
|
+
// 1. Static rules: { tool, rule, filePattern, reason }. Match by tool name
|
|
8
|
+
// + rule id + path pattern. Catalogue below + extensible per project via
|
|
9
|
+
// config.audit.false_positives. Legacy `config.sonar.false_positives` is
|
|
10
|
+
// honoured for backwards compatibility.
|
|
11
|
+
// 2. Inline ignore: `// karajan-audit-ignore: <tool>:<ruleId>` on the issue
|
|
12
|
+
// line or the previous one. Legacy `// karajan-sonar-ignore: <ruleId>`
|
|
13
|
+
// keeps working when the issue's tool is "sonar".
|
|
14
|
+
//
|
|
15
|
+
// Issues filtered out are kept in the `suppressed` list with `_suppressedBy`
|
|
16
|
+
// metadata for auditability — no silent dropping.
|
|
17
|
+
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import { escapeRegExp } from "../utils/escape-regexp.js";
|
|
20
|
+
|
|
21
|
+
export const DEFAULT_FALSE_POSITIVES = [
|
|
22
|
+
{
|
|
23
|
+
tool: "sonar",
|
|
24
|
+
rule: "javascript:S2699",
|
|
25
|
+
filePattern: /tests\/architecture\//,
|
|
26
|
+
reason: "Structural tests assert via expect(offenders, msg).toEqual([]) — Sonar misses the custom-message form",
|
|
27
|
+
},
|
|
28
|
+
// Knip — v2.17 audit/dead-exports collector.
|
|
29
|
+
{
|
|
30
|
+
tool: "knip",
|
|
31
|
+
rule: "unused-files",
|
|
32
|
+
filePattern: /(?:^|\/)tests\/fixtures\//,
|
|
33
|
+
reason: "Test fixtures are loaded by file path at runtime, not statically imported — knip can't see them",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
tool: "knip",
|
|
37
|
+
rule: "unused-files",
|
|
38
|
+
filePattern: /(?:^|\/)examples\//,
|
|
39
|
+
reason: "Example projects are user-facing entry points, not consumed by the parent project's source",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
tool: "knip",
|
|
43
|
+
rule: "unused-exports",
|
|
44
|
+
filePattern: /(?:^|\/)(?:index|main)\.(?:js|ts|mjs|cjs|jsx|tsx)$/,
|
|
45
|
+
reason: "Barrel re-exports in index.{js,ts} are legitimate public API surface even when no in-tree caller uses them",
|
|
46
|
+
},
|
|
47
|
+
// Madge — v2.17 audit/circular-deps collector.
|
|
48
|
+
{
|
|
49
|
+
tool: "madge",
|
|
50
|
+
rule: "circular-import",
|
|
51
|
+
filePattern: /node_modules\//,
|
|
52
|
+
reason: "Cycles inside node_modules are upstream's problem, not ours — defensive default since madge excludes node_modules by config",
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
export function hasInlineIgnore(filePath, line, ruleId, tool = "sonar") {
|
|
57
|
+
if (!filePath || !line || !ruleId) return false;
|
|
58
|
+
if (!fs.existsSync(filePath)) return false;
|
|
59
|
+
try {
|
|
60
|
+
const lines = fs.readFileSync(filePath, "utf8").split("\n");
|
|
61
|
+
const idx = line - 1;
|
|
62
|
+
const candidates = [lines[idx], lines[idx - 1]].filter(Boolean);
|
|
63
|
+
const escapedRule = escapeRegExp(ruleId);
|
|
64
|
+
const escapedTool = escapeRegExp(tool);
|
|
65
|
+
// New generalised marker + legacy sonar-only marker.
|
|
66
|
+
const reAudit = new RegExp(`karajan-audit-ignore\\s*:\\s*${escapedTool}:${escapedRule}\\b`);
|
|
67
|
+
const reLegacy = tool === "sonar"
|
|
68
|
+
? new RegExp(`karajan-sonar-ignore\\s*:\\s*${escapedRule}\\b`)
|
|
69
|
+
: null;
|
|
70
|
+
return candidates.some((l) => reAudit.test(l) || (reLegacy && reLegacy.test(l)));
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Filter a list of normalised audit findings into kept + suppressed.
|
|
78
|
+
*
|
|
79
|
+
* @param {object[]} issues Each issue must carry { tool, rule, component|path, line? }.
|
|
80
|
+
* `tool` defaults to "sonar" for backwards compat.
|
|
81
|
+
* @param {object} [opts]
|
|
82
|
+
* @param {object[]} [opts.extraRules] Rules from config.audit.false_positives
|
|
83
|
+
* (or legacy config.sonar.false_positives).
|
|
84
|
+
* @param {string} [opts.projectRoot] Used to resolve component → absolute path.
|
|
85
|
+
* @returns {{ kept: object[], suppressed: object[] }}
|
|
86
|
+
*/
|
|
87
|
+
export function filterFalsePositives(issues, opts = {}) {
|
|
88
|
+
if (!Array.isArray(issues)) return { kept: [], suppressed: [] };
|
|
89
|
+
const rules = [...DEFAULT_FALSE_POSITIVES, ...(opts.extraRules || [])].map((r) => ({
|
|
90
|
+
tool: r.tool || "sonar",
|
|
91
|
+
rule: r.rule,
|
|
92
|
+
filePattern: r.filePattern instanceof RegExp ? r.filePattern : new RegExp(String(r.filePattern || ".*")),
|
|
93
|
+
reason: r.reason,
|
|
94
|
+
}));
|
|
95
|
+
const projectRoot = opts.projectRoot || process.cwd();
|
|
96
|
+
const kept = [];
|
|
97
|
+
const suppressed = [];
|
|
98
|
+
|
|
99
|
+
for (const issue of issues) {
|
|
100
|
+
const tool = issue.tool || "sonar";
|
|
101
|
+
const path = componentToPath(issue.component) || issue.path || "";
|
|
102
|
+
let matched = null;
|
|
103
|
+
|
|
104
|
+
// 1. Static rules.
|
|
105
|
+
for (const rule of rules) {
|
|
106
|
+
if (rule.tool === tool && issue.rule === rule.rule && rule.filePattern.test(path)) {
|
|
107
|
+
matched = { tool, rule: rule.rule, path, reason: rule.reason };
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 2. Inline ignore (only checked if no static rule matched).
|
|
113
|
+
if (!matched && issue.rule && issue.line && path) {
|
|
114
|
+
const absPath = path.startsWith("/") ? path : `${projectRoot}/${path}`;
|
|
115
|
+
if (hasInlineIgnore(absPath, issue.line, issue.rule, tool)) {
|
|
116
|
+
matched = { tool, rule: issue.rule, path, reason: "inline `// karajan-audit-ignore`" };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (matched) {
|
|
121
|
+
suppressed.push({ ...issue, _suppressedBy: matched });
|
|
122
|
+
} else {
|
|
123
|
+
kept.push(issue);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { kept, suppressed };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function componentToPath(component) {
|
|
130
|
+
if (typeof component !== "string") return "";
|
|
131
|
+
const colonIdx = component.indexOf(":");
|
|
132
|
+
return colonIdx >= 0 ? component.slice(colonIdx + 1) : component;
|
|
133
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OSV-Scanner findings collector for `kj audit` (KJC-TSK-0365).
|
|
3
|
+
*
|
|
4
|
+
* Wraps Google's `osv-scanner` CLI and feeds its results into the audit
|
|
5
|
+
* prompt as a deterministic vulnerability section. OSV.dev's database
|
|
6
|
+
* pulls from GitHub Advisory Database, GLSA, PyPI advisories, Go vuln
|
|
7
|
+
* DB and others — broader coverage than `npm audit` alone, with no
|
|
8
|
+
* account, token, or upload (the scanner runs locally and only the
|
|
9
|
+
* advisory data is fetched on demand).
|
|
10
|
+
*
|
|
11
|
+
* This module follows the same pattern as src/audit/sonar-findings.js:
|
|
12
|
+
* - Best-effort: missing binary or scan errors return
|
|
13
|
+
* {available: false, reason} and the audit continues without the
|
|
14
|
+
* section.
|
|
15
|
+
* - Severity-grouped: helps the LLM (and the deterministic summary)
|
|
16
|
+
* prioritise the worst issues without scrolling.
|
|
17
|
+
* - Capped: only the first MAX_OSV_VULNS_IN_PROMPT entries land in
|
|
18
|
+
* the prompt, with overflow markers per severity, so an
|
|
19
|
+
* out-of-control result list cannot blow the token budget.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { runGoverned } from "../utils/tool-governor.js";
|
|
23
|
+
|
|
24
|
+
const SCAN_TIMEOUT_MS = 60_000;
|
|
25
|
+
const SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "MODERATE"];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Run osv-scanner on the project and return a structured result.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} projectDir - absolute path to the project root
|
|
31
|
+
* @param {object} [logger]
|
|
32
|
+
* @returns {Promise<{available: boolean, reason?: string, total?: number, vulnerabilities?: object[]}>}
|
|
33
|
+
*/
|
|
34
|
+
export async function collectOsvFindings(projectDir, logger = null) {
|
|
35
|
+
// KJC-BUG-0122: same kill-switch as semgrep — external scanners never
|
|
36
|
+
// fire from the unit suite; e2e opts in with KJ_ALLOW_REAL_SCANS=1.
|
|
37
|
+
if (process.env.VITEST && process.env.KJ_ALLOW_REAL_SCANS !== "1") {
|
|
38
|
+
return { available: false, reason: "real scans disabled under the test runner (set KJ_ALLOW_REAL_SCANS=1 to opt in)" };
|
|
39
|
+
}
|
|
40
|
+
let stdout;
|
|
41
|
+
try {
|
|
42
|
+
// KJC-TSK-0668: governed run — single-flight across kj processes and a
|
|
43
|
+
// timeout that kills the whole process tree (osv-scanner is I/O-bound,
|
|
44
|
+
// no jobs flag to cap).
|
|
45
|
+
const { stdout: out } = await runGoverned(
|
|
46
|
+
"osv-scanner",
|
|
47
|
+
["--format=json", "--recursive", projectDir || "."],
|
|
48
|
+
{ cwd: projectDir, timeout: SCAN_TIMEOUT_MS, maxBuffer: 16 * 1024 * 1024 }
|
|
49
|
+
);
|
|
50
|
+
stdout = out;
|
|
51
|
+
} catch (err) {
|
|
52
|
+
// osv-scanner exits with non-zero when vulnerabilities are found, so
|
|
53
|
+
// err.code 1 is NORMAL — we still want the stdout. Real failures are
|
|
54
|
+
// ENOENT (binary missing) or stdout empty.
|
|
55
|
+
if (err.code === "ENOENT") {
|
|
56
|
+
if (logger?.warn) logger.warn("osv-scanner not found in PATH — install via 'go install github.com/google/osv-scanner/v2/cmd/osv-scanner@latest'");
|
|
57
|
+
return { available: false, reason: "osv-scanner not installed" };
|
|
58
|
+
}
|
|
59
|
+
if (err.killed) {
|
|
60
|
+
if (logger?.warn) logger.warn(`osv-scanner timed out after ${SCAN_TIMEOUT_MS}ms`);
|
|
61
|
+
return { available: false, reason: `osv-scanner timed out after ${SCAN_TIMEOUT_MS}ms` };
|
|
62
|
+
}
|
|
63
|
+
if (typeof err.stdout === "string" && err.stdout.trim().length > 0) {
|
|
64
|
+
stdout = err.stdout;
|
|
65
|
+
} else {
|
|
66
|
+
if (logger?.warn) logger.warn(`osv-scanner failed: ${err.message}`);
|
|
67
|
+
return { available: false, reason: `osv-scanner failed: ${err.message}` };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = JSON.parse(stdout);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (logger?.warn) logger.warn(`osv-scanner output not parseable: ${err.message}`);
|
|
76
|
+
return { available: false, reason: "osv-scanner output not parseable as JSON" };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { available: true, ...parseOsvOutput(parsed) };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Flatten the osv-scanner JSON shape into a flat list of vulnerabilities.
|
|
84
|
+
* The scanner emits results[].packages[].vulnerabilities[].
|
|
85
|
+
*
|
|
86
|
+
* @param {object} raw - parsed osv-scanner JSON
|
|
87
|
+
* @returns {{total: number, vulnerabilities: object[]}}
|
|
88
|
+
*/
|
|
89
|
+
export function parseOsvOutput(raw) {
|
|
90
|
+
const flat = [];
|
|
91
|
+
const results = Array.isArray(raw?.results) ? raw.results : [];
|
|
92
|
+
for (const result of results) {
|
|
93
|
+
const source = result.source?.path || result.source || "";
|
|
94
|
+
const packages = Array.isArray(result.packages) ? result.packages : [];
|
|
95
|
+
for (const pkg of packages) {
|
|
96
|
+
const name = pkg.package?.name || "";
|
|
97
|
+
const version = pkg.package?.version || "";
|
|
98
|
+
const ecosystem = pkg.package?.ecosystem || "";
|
|
99
|
+
const vulns = Array.isArray(pkg.vulnerabilities) ? pkg.vulnerabilities : [];
|
|
100
|
+
const groups = Array.isArray(pkg.groups) ? pkg.groups : [];
|
|
101
|
+
for (const vuln of vulns) {
|
|
102
|
+
flat.push({
|
|
103
|
+
id: vuln.id || vuln.aliases?.[0] || "UNKNOWN",
|
|
104
|
+
aliases: vuln.aliases || [],
|
|
105
|
+
severity: extractSeverity(vuln, groups),
|
|
106
|
+
summary: vuln.summary || vuln.details?.split("\n")[0] || "",
|
|
107
|
+
package: name,
|
|
108
|
+
version,
|
|
109
|
+
ecosystem,
|
|
110
|
+
source,
|
|
111
|
+
fixed: extractFixedVersion(vuln),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { total: flat.length, vulnerabilities: flat };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Resolve a vulnerability's effective severity. osv-scanner reports
|
|
121
|
+
* CVSS (per-vuln) and a separate `groups[].severity` (per scanner run).
|
|
122
|
+
* Group severity wins when present because it reflects scanner policy.
|
|
123
|
+
*/
|
|
124
|
+
function extractSeverity(vuln, groups) {
|
|
125
|
+
// Group-level severity (scanner-resolved, includes ecosystem context)
|
|
126
|
+
for (const group of groups) {
|
|
127
|
+
if (group.ids?.includes(vuln.id) && group.severity) {
|
|
128
|
+
return String(group.severity).toUpperCase();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Per-vuln CVSS — pick the highest score
|
|
132
|
+
const severities = Array.isArray(vuln.severity) ? vuln.severity : [];
|
|
133
|
+
for (const sev of severities) {
|
|
134
|
+
if (sev?.severity) return String(sev.severity).toUpperCase();
|
|
135
|
+
}
|
|
136
|
+
// database_specific severity (GHSA-style)
|
|
137
|
+
if (vuln.database_specific?.severity) {
|
|
138
|
+
return String(vuln.database_specific.severity).toUpperCase();
|
|
139
|
+
}
|
|
140
|
+
return "UNKNOWN";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function extractFixedVersion(vuln) {
|
|
144
|
+
const ranges = vuln.affected?.[0]?.ranges || [];
|
|
145
|
+
for (const range of ranges) {
|
|
146
|
+
const events = range.events || [];
|
|
147
|
+
for (const event of events) {
|
|
148
|
+
if (event.fixed) return event.fixed;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Group vulnerabilities by severity, preserving canonical ordering
|
|
156
|
+
* (CRITICAL → HIGH → MEDIUM/MODERATE → LOW → UNKNOWN).
|
|
157
|
+
*/
|
|
158
|
+
export function groupVulnerabilitiesBySeverity(vulnerabilities) {
|
|
159
|
+
const groups = Object.fromEntries(SEVERITY_ORDER.map(s => [s, []]));
|
|
160
|
+
groups.UNKNOWN = [];
|
|
161
|
+
for (const vuln of vulnerabilities || []) {
|
|
162
|
+
const sev = (vuln.severity || "UNKNOWN").toUpperCase();
|
|
163
|
+
if (groups[sev]) groups[sev].push(vuln);
|
|
164
|
+
else groups.UNKNOWN.push(vuln);
|
|
165
|
+
}
|
|
166
|
+
return groups;
|
|
167
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan adherence metric — KJC-TSK-0376 (deepeval-inspired).
|
|
3
|
+
* Pure offline 0-100 score: how faithfully did the coder follow the plan?
|
|
4
|
+
* Components (weighted): commit_attribution 40%, acceptance_tests 30%,
|
|
5
|
+
* scope_discipline 20%, dependency_order 10%. Components return null
|
|
6
|
+
* when no data; aggregator redistributes the weight.
|
|
7
|
+
* See docs/plan-adherence.md (PR-C) for the full spec.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { escapeRegExp } from "../utils/escape-regexp.js";
|
|
11
|
+
|
|
12
|
+
const WEIGHTS = {
|
|
13
|
+
commit_attribution: 40,
|
|
14
|
+
acceptance_tests: 30,
|
|
15
|
+
scope_discipline: 20,
|
|
16
|
+
dependency_order: 10,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Build a regex that matches the HU id anywhere in a commit message
|
|
21
|
+
* (case-insensitive, word-boundary friendly). The `_` and `-` characters
|
|
22
|
+
* appear inside HU ids so we anchor on whole-token rather than \b.
|
|
23
|
+
*/
|
|
24
|
+
function huIdMatcher(huId) {
|
|
25
|
+
const escaped = escapeRegExp(huId);
|
|
26
|
+
return new RegExp(`(^|[^a-z0-9])${escaped}([^a-z0-9]|$)`, "i");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Slugify an HU title for branch-name / scope matching. Mirrors the
|
|
31
|
+
* `buildHuBranchName` slug rule (lowercase, non-alnum → "-").
|
|
32
|
+
*/
|
|
33
|
+
function slugify(text) {
|
|
34
|
+
return String(text || "")
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.replaceAll(/[^a-z0-9]+/g, "-")
|
|
37
|
+
.replaceAll(/(^-+)|(-+$)/g, "");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Try to attribute a single commit to one of the plan's HUs.
|
|
42
|
+
*
|
|
43
|
+
* Order of evidence, strongest first:
|
|
44
|
+
* 1. The HU id appears in the commit message.
|
|
45
|
+
* 2. The HU id appears in the branch name where the commit lives
|
|
46
|
+
* (commit.branch is supplied by listCommitsBetween's caller when
|
|
47
|
+
* it knows the per-HU branches).
|
|
48
|
+
* 3. The HU title slug matches the commit message.
|
|
49
|
+
*
|
|
50
|
+
* Returns the matched huId, or null when none of the above fires.
|
|
51
|
+
*/
|
|
52
|
+
export function attributeCommitToHu(commit, hus) {
|
|
53
|
+
if (!commit || !Array.isArray(hus) || hus.length === 0) return null;
|
|
54
|
+
const msg = String(commit.message || "");
|
|
55
|
+
const branch = String(commit.branch || "");
|
|
56
|
+
for (const hu of hus) {
|
|
57
|
+
if (!hu?.id) continue;
|
|
58
|
+
const matcher = huIdMatcher(hu.id);
|
|
59
|
+
if (matcher.test(msg)) return hu.id;
|
|
60
|
+
if (branch && matcher.test(branch)) return hu.id;
|
|
61
|
+
}
|
|
62
|
+
// Fallback: title slug
|
|
63
|
+
for (const hu of hus) {
|
|
64
|
+
const slug = slugify(hu.title);
|
|
65
|
+
if (!slug || slug.length < 6) continue;
|
|
66
|
+
if (slugify(msg).includes(slug)) return hu.id;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Component 1: commit attribution. Returns null when the run produced
|
|
73
|
+
* no commits at all (avoids penalising analysis-only or doc-only runs).
|
|
74
|
+
*/
|
|
75
|
+
export function computeCommitAttribution(commits, plan) {
|
|
76
|
+
if (!Array.isArray(commits) || commits.length === 0) return null;
|
|
77
|
+
if (!plan?.hus || plan.hus.length === 0) return null;
|
|
78
|
+
let attributed = 0;
|
|
79
|
+
for (const commit of commits) {
|
|
80
|
+
if (attributeCommitToHu(commit, plan.hus)) attributed += 1;
|
|
81
|
+
}
|
|
82
|
+
return Math.round((attributed / commits.length) * 100);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Component 2: acceptance test coverage. Heuristic: each HU declares
|
|
87
|
+
* `acceptance_tests`. We check whether at least one commit from that
|
|
88
|
+
* HU's pool has a `test:`-prefixed message. Per-HU score (1 or 0),
|
|
89
|
+
* averaged across HUs that DO declare tests. Returns null when no HU
|
|
90
|
+
* declares acceptance tests.
|
|
91
|
+
*/
|
|
92
|
+
export function computeAcceptanceTestCoverage(commits, plan) {
|
|
93
|
+
if (!plan?.hus || plan.hus.length === 0) return null;
|
|
94
|
+
if (!Array.isArray(commits) || commits.length === 0) return null;
|
|
95
|
+
const husWithTests = plan.hus.filter(
|
|
96
|
+
(hu) => Array.isArray(hu?.acceptance_tests) && hu.acceptance_tests.length > 0,
|
|
97
|
+
);
|
|
98
|
+
if (husWithTests.length === 0) return null;
|
|
99
|
+
|
|
100
|
+
let covered = 0;
|
|
101
|
+
for (const hu of husWithTests) {
|
|
102
|
+
const huCommits = (commits || []).filter(
|
|
103
|
+
(c) => attributeCommitToHu(c, [hu]) === hu.id,
|
|
104
|
+
);
|
|
105
|
+
const hasTestCommit = huCommits.some((c) =>
|
|
106
|
+
/^test[:(]/i.test(String(c.message || "").trim()),
|
|
107
|
+
);
|
|
108
|
+
if (hasTestCommit) covered += 1;
|
|
109
|
+
}
|
|
110
|
+
return Math.round((covered / husWithTests.length) * 100);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Component 3: scope discipline. For each file changed by the run, we
|
|
115
|
+
* try to match it against at least one HU's id slug, title slug, or
|
|
116
|
+
* `scope` text. A file that matches no HU is flagged as out-of-scope.
|
|
117
|
+
* Returns null when the run changed no files.
|
|
118
|
+
*/
|
|
119
|
+
export function computeScopeDiscipline(filesChanged, plan) {
|
|
120
|
+
if (!Array.isArray(filesChanged) || filesChanged.length === 0) return null;
|
|
121
|
+
if (!plan?.hus || plan.hus.length === 0) return null;
|
|
122
|
+
const huKeywords = plan.hus.map((hu) => ({
|
|
123
|
+
id: hu.id,
|
|
124
|
+
matchers: [
|
|
125
|
+
slugify(hu.id),
|
|
126
|
+
slugify(hu.title),
|
|
127
|
+
// Pull alpha-rich words from `scope` text (length >= 4).
|
|
128
|
+
...String(hu.scope || "")
|
|
129
|
+
.toLowerCase()
|
|
130
|
+
.split(/[^a-z]+/)
|
|
131
|
+
.filter((w) => w.length >= 4),
|
|
132
|
+
].filter(Boolean),
|
|
133
|
+
}));
|
|
134
|
+
let inScope = 0;
|
|
135
|
+
for (const file of filesChanged) {
|
|
136
|
+
const slug = slugify(file);
|
|
137
|
+
const matched = huKeywords.some((hk) =>
|
|
138
|
+
hk.matchers.some((m) => slug.includes(m) || file.includes(m)),
|
|
139
|
+
);
|
|
140
|
+
if (matched) inScope += 1;
|
|
141
|
+
}
|
|
142
|
+
return Math.round((inScope / filesChanged.length) * 100);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Component 4: dependency order. For every `(parent, child)` edge
|
|
147
|
+
* declared via `child.blocked_by`, check that the EARLIEST commit
|
|
148
|
+
* attributed to `parent` is older than the EARLIEST commit attributed
|
|
149
|
+
* to `child`. Returns null when no dependency edges exist.
|
|
150
|
+
*
|
|
151
|
+
* `commits` are expected oldest-first (the shape `listCommitsBetween`
|
|
152
|
+
* returns). When that's not the case the score still makes sense — we
|
|
153
|
+
* just compare relative positions in the array.
|
|
154
|
+
*/
|
|
155
|
+
export function computeDependencyOrder(commits, plan) {
|
|
156
|
+
if (!plan?.hus || plan.hus.length === 0) return null;
|
|
157
|
+
const edges = [];
|
|
158
|
+
for (const hu of plan.hus) {
|
|
159
|
+
for (const parentId of hu.blocked_by || []) {
|
|
160
|
+
edges.push([parentId, hu.id]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (edges.length === 0) return null;
|
|
164
|
+
if (!Array.isArray(commits) || commits.length === 0) return null;
|
|
165
|
+
|
|
166
|
+
// Build huId → first commit index (oldest first attribution).
|
|
167
|
+
const firstIndex = new Map();
|
|
168
|
+
commits.forEach((commit, idx) => {
|
|
169
|
+
const huId = attributeCommitToHu(commit, plan.hus);
|
|
170
|
+
if (huId && !firstIndex.has(huId)) firstIndex.set(huId, idx);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
let respected = 0;
|
|
174
|
+
let evaluable = 0;
|
|
175
|
+
for (const [parentId, childId] of edges) {
|
|
176
|
+
const p = firstIndex.get(parentId);
|
|
177
|
+
const c = firstIndex.get(childId);
|
|
178
|
+
if (p === undefined || c === undefined) continue; // can't evaluate
|
|
179
|
+
evaluable += 1;
|
|
180
|
+
if (p < c) respected += 1;
|
|
181
|
+
}
|
|
182
|
+
if (evaluable === 0) return null;
|
|
183
|
+
return Math.round((respected / evaluable) * 100);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Aggregator: compute every component and weight them. Components
|
|
188
|
+
* that return `null` are excluded and their weight redistributed
|
|
189
|
+
* proportionally across the rest, so a run that legitimately has no
|
|
190
|
+
* acceptance tests or no dependencies isn't penalised.
|
|
191
|
+
*
|
|
192
|
+
* Returns the full structured result (see file header for shape).
|
|
193
|
+
*/
|
|
194
|
+
export function computePlanAdherenceScore({ commits, plan, filesChanged }) {
|
|
195
|
+
const breakdown = {
|
|
196
|
+
commit_attribution: computeCommitAttribution(commits, plan),
|
|
197
|
+
acceptance_tests: computeAcceptanceTestCoverage(commits, plan),
|
|
198
|
+
scope_discipline: computeScopeDiscipline(filesChanged, plan),
|
|
199
|
+
dependency_order: computeDependencyOrder(commits, plan),
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Weighted average over non-null components only.
|
|
203
|
+
let totalWeight = 0;
|
|
204
|
+
let weightedSum = 0;
|
|
205
|
+
for (const [key, weight] of Object.entries(WEIGHTS)) {
|
|
206
|
+
const value = breakdown[key];
|
|
207
|
+
if (value === null || value === undefined) continue;
|
|
208
|
+
totalWeight += weight;
|
|
209
|
+
weightedSum += value * weight;
|
|
210
|
+
}
|
|
211
|
+
const score = totalWeight === 0 ? null : Math.round(weightedSum / totalWeight);
|
|
212
|
+
|
|
213
|
+
// Per-HU detail: was the HU "touched" (attributed at least one commit)?
|
|
214
|
+
const hu_scores = (plan?.hus || []).map((hu) => {
|
|
215
|
+
const huCommits = (commits || []).filter(
|
|
216
|
+
(c) => attributeCommitToHu(c, [hu]) === hu.id,
|
|
217
|
+
);
|
|
218
|
+
const issues = [];
|
|
219
|
+
if (huCommits.length === 0) issues.push("no commit attributed");
|
|
220
|
+
return { huId: hu.id, attributed: huCommits.length > 0, issues };
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return { score, breakdown, hu_scores };
|
|
224
|
+
}
|