@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,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic-only summary formatter for `kj audit` two-phase mode
|
|
3
|
+
* (KJC-TSK-0364). Renders the data AuditRole.collectDeterministic()
|
|
4
|
+
* gathers — basalCost, growth delta, stack, Sonar findings, WebPerf —
|
|
5
|
+
* as a self-contained markdown block the user can read BEFORE deciding
|
|
6
|
+
* whether to spend tokens on the LLM analysis.
|
|
7
|
+
*
|
|
8
|
+
* Kept in src/audit/ (not src/commands/) so the same formatter can be
|
|
9
|
+
* reused by future MCP tools and other entrypoints.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { groupIssuesBySeverity } from "./sonar-findings.js";
|
|
13
|
+
import { groupVulnerabilitiesBySeverity } from "./osv-findings.js";
|
|
14
|
+
import { groupFindingsBySeverity as groupSemgrepBySeverity } from "./semgrep-findings.js";
|
|
15
|
+
import { groupCyclesBySeverity } from "./circular-deps.js";
|
|
16
|
+
import { groupDeadExportsBySeverity } from "./dead-exports.js";
|
|
17
|
+
import { groupInjectionBySeverity } from "./injection-findings.js";
|
|
18
|
+
|
|
19
|
+
const MAX_SAMPLE_DEAD_EXPORTS = 10;
|
|
20
|
+
const MAX_SAMPLE_SONAR_PER_SEVERITY = 5;
|
|
21
|
+
const MAX_SAMPLE_OSV_PER_SEVERITY = 5;
|
|
22
|
+
const MAX_SAMPLE_SEMGREP_PER_SEVERITY = 5;
|
|
23
|
+
const MAX_SAMPLE_CYCLES_PER_SEVERITY = 5;
|
|
24
|
+
const MAX_SAMPLE_KNIP_PER_SEVERITY = 5;
|
|
25
|
+
const MAX_SAMPLE_INJECTION_PER_SEVERITY = 5;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {{basalCost?: object, growthDelta?: object, stack?: object, sonarFindings?: object, webperf?: object, osvFindings?: object}} ctx
|
|
29
|
+
* @returns {string} markdown block
|
|
30
|
+
*/
|
|
31
|
+
export function formatDeterministicSummary(ctx) {
|
|
32
|
+
const lines = ["## Deterministic Findings", ""];
|
|
33
|
+
lines.push("These are the rule-based findings (no LLM). They cost zero tokens and are always factually accurate. The interactive prompt below asks if you want to extend this with an LLM analysis (which will cost tokens).");
|
|
34
|
+
lines.push("");
|
|
35
|
+
|
|
36
|
+
if (ctx.stack) lines.push(...formatStackBlock(ctx.stack));
|
|
37
|
+
if (ctx.basalCost) lines.push(...formatBasalCostBlock(ctx.basalCost, ctx.growthDelta));
|
|
38
|
+
if (ctx.sonarFindings) lines.push(...formatSonarBlock(ctx.sonarFindings));
|
|
39
|
+
if (ctx.osvFindings) lines.push(...formatOsvBlock(ctx.osvFindings));
|
|
40
|
+
if (ctx.semgrepFindings) lines.push(...formatSemgrepBlock(ctx.semgrepFindings));
|
|
41
|
+
if (ctx.infraFindings) lines.push(...formatInfraBlock(ctx.infraFindings));
|
|
42
|
+
if (ctx.circularDeps) lines.push(...formatCircularDepsBlock(ctx.circularDeps));
|
|
43
|
+
if (ctx.deadExports) lines.push(...formatDeadExportsBlock(ctx.deadExports));
|
|
44
|
+
if (ctx.injectionFindings) lines.push(...formatInjectionBlock(ctx.injectionFindings));
|
|
45
|
+
if (ctx.aiSlop) lines.push(...formatAiSlopBlock(ctx.aiSlop));
|
|
46
|
+
if (ctx.webperf) lines.push(...formatWebperfBlock(ctx.webperf));
|
|
47
|
+
|
|
48
|
+
return lines.join("\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function formatAiSlopBlock(slop) {
|
|
52
|
+
if (!slop.available) return ["### AI-slop tells (deterministic)", `- Status: not available — ${slop.reason || "scan failed"}`, ""];
|
|
53
|
+
const lines = ["### AI-slop tells (deterministic)", `- Files scanned: ${slop.filesScanned ?? 0}`, `- Score: ${slop.score}/100 (100 = clean)`, `- Findings: ${slop.total ?? 0}`];
|
|
54
|
+
if ((slop.total ?? 0) > 0) {
|
|
55
|
+
for (const [cat, n] of Object.entries(slop.byCategory || {})) {
|
|
56
|
+
if (n > 0) lines.push(` - ${cat}: ${n}`);
|
|
57
|
+
}
|
|
58
|
+
if (Array.isArray(slop.worst) && slop.worst.length > 0) {
|
|
59
|
+
lines.push(" - Worst offenders:");
|
|
60
|
+
for (const f of slop.worst) lines.push(` - ${f.path}:${f.line} [${f.category}] ${f.snippet}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
lines.push("");
|
|
64
|
+
return lines;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function formatInjectionBlock(inj) {
|
|
68
|
+
if (!inj.available) return ["### Prompt-injection scan", `- Status: not available — ${inj.reason || "scan failed"}`, ""];
|
|
69
|
+
const lines = ["### Prompt-injection scan (agent-context surface: CLAUDE/AGENTS/GEMINI.md, templates, .rulesync, .karajan)", `- Files scanned: ${inj.scanned ?? 0}`, `- Findings: ${inj.total ?? 0}`];
|
|
70
|
+
if ((inj.total ?? 0) > 0) {
|
|
71
|
+
for (const [severity, items] of Object.entries(groupInjectionBySeverity(inj.findings || []))) {
|
|
72
|
+
if (items.length === 0) continue;
|
|
73
|
+
lines.push(` - ${severity} (${items.length}):`);
|
|
74
|
+
for (const f of items.slice(0, MAX_SAMPLE_INJECTION_PER_SEVERITY)) {
|
|
75
|
+
const lineSuffix = f.line ? `:${f.line}` : "";
|
|
76
|
+
lines.push(` - ${f.file || "(unknown)"}${lineSuffix} [${f.type}] ${f.pattern}`);
|
|
77
|
+
}
|
|
78
|
+
if (items.length > MAX_SAMPLE_INJECTION_PER_SEVERITY) lines.push(` - ... and ${items.length - MAX_SAMPLE_INJECTION_PER_SEVERITY} more in ${severity}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
lines.push("");
|
|
82
|
+
return lines;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function formatCircularDepsBlock(circularDeps) {
|
|
86
|
+
if (!circularDeps.available) {
|
|
87
|
+
return ["### Circular Dependencies (architecture)", `- Status: not available — ${circularDeps.reason || "madge not available"}`, ""];
|
|
88
|
+
}
|
|
89
|
+
const lines = ["### Circular Dependencies (architecture)"];
|
|
90
|
+
const suppressedSuffix = circularDeps.suppressedCount ? ` (+${circularDeps.suppressedCount} suppressed)` : "";
|
|
91
|
+
lines.push(`- Total cycles: ${circularDeps.total ?? 0}${suppressedSuffix}`);
|
|
92
|
+
if ((circularDeps.total ?? 0) > 0) {
|
|
93
|
+
const groups = groupCyclesBySeverity(circularDeps.cycles || []);
|
|
94
|
+
for (const [severity, cycles] of Object.entries(groups)) {
|
|
95
|
+
if (cycles.length === 0) continue;
|
|
96
|
+
lines.push(` - ${severity} (${cycles.length}):`);
|
|
97
|
+
for (const c of cycles.slice(0, MAX_SAMPLE_CYCLES_PER_SEVERITY)) {
|
|
98
|
+
const head = (c.cycle || [c.path]).slice(0, 3).join(" → ");
|
|
99
|
+
const more = (c.cycle?.length || 0) > 3 ? ` → … (${c.cycle.length} files)` : "";
|
|
100
|
+
lines.push(` - ${head}${more}`);
|
|
101
|
+
}
|
|
102
|
+
if (cycles.length > MAX_SAMPLE_CYCLES_PER_SEVERITY) {
|
|
103
|
+
lines.push(` - ... and ${cycles.length - MAX_SAMPLE_CYCLES_PER_SEVERITY} more in ${severity}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (circularDeps.truncated > 0) lines.push(` - ... ${circularDeps.truncated} more cycles not shown`);
|
|
107
|
+
}
|
|
108
|
+
lines.push("");
|
|
109
|
+
return lines;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function formatSemgrepBlock(semgrepFindings) {
|
|
113
|
+
if (!semgrepFindings.available) {
|
|
114
|
+
return ["### Semgrep SAST", `- Status: not available — ${semgrepFindings.reason || "semgrep not installed"}`, ""];
|
|
115
|
+
}
|
|
116
|
+
const lines = ["### Semgrep SAST"];
|
|
117
|
+
lines.push(`- Total findings: ${semgrepFindings.total ?? 0}`);
|
|
118
|
+
if ((semgrepFindings.total ?? 0) > 0) {
|
|
119
|
+
const groups = groupSemgrepBySeverity(semgrepFindings.findings || []);
|
|
120
|
+
for (const [severity, findings] of Object.entries(groups)) {
|
|
121
|
+
if (findings.length === 0) continue;
|
|
122
|
+
lines.push(` - ${severity} (${findings.length}):`);
|
|
123
|
+
for (const f of findings.slice(0, MAX_SAMPLE_SEMGREP_PER_SEVERITY)) {
|
|
124
|
+
const lineSuffix = f.line ? `:${f.line}` : "";
|
|
125
|
+
const loc = f.file ? `${f.file}${lineSuffix}` : "";
|
|
126
|
+
lines.push(` - ${loc} [${f.rule}]`);
|
|
127
|
+
}
|
|
128
|
+
if (findings.length > MAX_SAMPLE_SEMGREP_PER_SEVERITY) {
|
|
129
|
+
lines.push(` - ... and ${findings.length - MAX_SAMPLE_SEMGREP_PER_SEVERITY} more in ${severity}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
lines.push("");
|
|
134
|
+
return lines;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// INF-C (KJC-TSK-0760): checkov misconfigs — same shape as the semgrep
|
|
138
|
+
// block; a not-applicable repo (no infra markers) declares it in one line.
|
|
139
|
+
function formatInfraBlock(infraFindings) {
|
|
140
|
+
if (!infraFindings.available) {
|
|
141
|
+
return ["### Infra misconfigurations (checkov)", `- Status: not available — ${infraFindings.reason || "checkov not installed"}`, ""];
|
|
142
|
+
}
|
|
143
|
+
const lines = ["### Infra misconfigurations (checkov)"];
|
|
144
|
+
lines.push(`- Total findings: ${infraFindings.total ?? 0}`);
|
|
145
|
+
const cap = MAX_SAMPLE_SEMGREP_PER_SEVERITY * 3;
|
|
146
|
+
for (const f of (infraFindings.findings || []).slice(0, cap)) {
|
|
147
|
+
const lineSuffix = f.line ? `:${f.line}` : "";
|
|
148
|
+
lines.push(` - [${f.severity}] ${f.file}${lineSuffix} [${f.rule}] ${f.message}${f.guideline ? ` (${f.guideline})` : ""}`);
|
|
149
|
+
}
|
|
150
|
+
const overflow = (infraFindings.total ?? 0) - cap;
|
|
151
|
+
if (overflow > 0) lines.push(` - ... and ${overflow} more`);
|
|
152
|
+
lines.push("");
|
|
153
|
+
return lines;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function formatOsvBlock(osvFindings) {
|
|
157
|
+
if (!osvFindings.available) {
|
|
158
|
+
return ["### OSV Vulnerabilities", `- Status: not available — ${osvFindings.reason || "osv-scanner not installed"}`, ""];
|
|
159
|
+
}
|
|
160
|
+
const lines = ["### OSV Vulnerabilities (Dependencies)"];
|
|
161
|
+
lines.push(`- Total open: ${osvFindings.total ?? 0}`);
|
|
162
|
+
if ((osvFindings.total ?? 0) > 0) {
|
|
163
|
+
const groups = groupVulnerabilitiesBySeverity(osvFindings.vulnerabilities || []);
|
|
164
|
+
for (const [severity, vulns] of Object.entries(groups)) {
|
|
165
|
+
if (vulns.length === 0) continue;
|
|
166
|
+
lines.push(` - ${severity} (${vulns.length}):`);
|
|
167
|
+
for (const v of vulns.slice(0, MAX_SAMPLE_OSV_PER_SEVERITY)) {
|
|
168
|
+
const where = v.package ? `${v.package}@${v.version || "?"}` : "(unknown)";
|
|
169
|
+
const fixed = v.fixed ? ` → fixed in ${v.fixed}` : "";
|
|
170
|
+
lines.push(` - ${v.id} ${where}${fixed}`);
|
|
171
|
+
}
|
|
172
|
+
if (vulns.length > MAX_SAMPLE_OSV_PER_SEVERITY) {
|
|
173
|
+
lines.push(` - ... and ${vulns.length - MAX_SAMPLE_OSV_PER_SEVERITY} more in ${severity}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
lines.push("");
|
|
178
|
+
return lines;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function formatDeadExportsBlock(deadExports) {
|
|
182
|
+
if (!deadExports.available) {
|
|
183
|
+
return ["### Dead Code (knip)", `- Status: not available — ${deadExports.reason || "knip not available"}`, ""];
|
|
184
|
+
}
|
|
185
|
+
const lines = ["### Dead Code (knip)"];
|
|
186
|
+
const exportsTotal = (deadExports.exports || []).length;
|
|
187
|
+
const filesTotal = (deadExports.files || []).length;
|
|
188
|
+
lines.push(`- Unused exports/types: ${exportsTotal}`);
|
|
189
|
+
lines.push(`- Unused files: ${filesTotal}`);
|
|
190
|
+
if (deadExports.suppressedCount) lines.push(`- Suppressed (FP filter): ${deadExports.suppressedCount}`);
|
|
191
|
+
const allItems = [...(deadExports.exports || []), ...(deadExports.files || [])];
|
|
192
|
+
if (allItems.length > 0) {
|
|
193
|
+
const groups = groupDeadExportsBySeverity(allItems);
|
|
194
|
+
for (const [severity, items] of Object.entries(groups)) {
|
|
195
|
+
if (items.length === 0) continue;
|
|
196
|
+
lines.push(` - ${severity} (${items.length}):`);
|
|
197
|
+
for (const item of items.slice(0, MAX_SAMPLE_KNIP_PER_SEVERITY)) {
|
|
198
|
+
const lineSuffix = item.line ? `:${item.line}` : "";
|
|
199
|
+
const nameSuffix = item.name ? ` \`${item.name}\`` : "";
|
|
200
|
+
lines.push(` - ${item.path}${lineSuffix} [${item.rule}]${nameSuffix}`);
|
|
201
|
+
}
|
|
202
|
+
if (items.length > MAX_SAMPLE_KNIP_PER_SEVERITY) {
|
|
203
|
+
lines.push(` - ... and ${items.length - MAX_SAMPLE_KNIP_PER_SEVERITY} more in ${severity}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
lines.push("");
|
|
208
|
+
return lines;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function formatStackBlock(stack) {
|
|
212
|
+
if (!stack || (!stack.frameworks?.length && !stack.language && !stack.isFrontend && !stack.isBackend)) return [];
|
|
213
|
+
const lines = ["### Project Stack"];
|
|
214
|
+
if (stack.language) lines.push(`- Language: ${stack.language}`);
|
|
215
|
+
if (stack.frameworks?.length) lines.push(`- Frameworks: ${stack.frameworks.join(", ")}`);
|
|
216
|
+
const tier = stack.isFullstack ? "fullstack (frontend + backend)"
|
|
217
|
+
: stack.isFrontend ? "frontend-only"
|
|
218
|
+
: stack.isBackend ? "backend-only"
|
|
219
|
+
: "unknown";
|
|
220
|
+
lines.push(`- Tier: ${tier}`);
|
|
221
|
+
lines.push("");
|
|
222
|
+
return lines;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function formatBasalCostBlock(basalCost, growthDelta) {
|
|
226
|
+
const lines = ["### Basal Cost"];
|
|
227
|
+
lines.push(`- Source: ${basalCost.totalLines} lines across ${basalCost.totalFiles} files`);
|
|
228
|
+
const deps = basalCost.dependencies || {};
|
|
229
|
+
if (deps.total) lines.push(`- Dependencies: ${deps.total} (${deps.dependencies} prod + ${deps.devDependencies} dev)`);
|
|
230
|
+
|
|
231
|
+
const unused = basalCost.unusedDependencies?.unused;
|
|
232
|
+
if (Array.isArray(unused) && unused.length > 0) {
|
|
233
|
+
lines.push(`- Unused dependencies (${unused.length}): ${unused.join(", ")}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const dead = Array.isArray(basalCost.deadExports) ? basalCost.deadExports : [];
|
|
237
|
+
if (dead.length > 0) {
|
|
238
|
+
lines.push(`- Dead exports: ${dead.length}`);
|
|
239
|
+
for (const de of dead.slice(0, MAX_SAMPLE_DEAD_EXPORTS)) {
|
|
240
|
+
lines.push(` - \`${de.name}\` in ${de.file}`);
|
|
241
|
+
}
|
|
242
|
+
if (dead.length > MAX_SAMPLE_DEAD_EXPORTS) {
|
|
243
|
+
lines.push(` - ... and ${dead.length - MAX_SAMPLE_DEAD_EXPORTS} more`);
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
lines.push("- Dead exports: 0");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (growthDelta) {
|
|
250
|
+
lines.push("");
|
|
251
|
+
lines.push("**Growth since last audit**");
|
|
252
|
+
lines.push(`- Lines: ${growthDelta.lines >= 0 ? "+" : ""}${growthDelta.lines}`);
|
|
253
|
+
lines.push(`- Files: ${growthDelta.files >= 0 ? "+" : ""}${growthDelta.files}`);
|
|
254
|
+
lines.push(`- Dependencies: ${growthDelta.deps >= 0 ? "+" : ""}${growthDelta.deps}`);
|
|
255
|
+
if (growthDelta.since) lines.push(`- Since: ${growthDelta.since}`);
|
|
256
|
+
}
|
|
257
|
+
lines.push("");
|
|
258
|
+
return lines;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function formatSonarBlock(sonarFindings) {
|
|
262
|
+
if (!sonarFindings.available) {
|
|
263
|
+
return ["### SonarQube", `- Status: not available — ${sonarFindings.reason || "host unreachable"}`, ""];
|
|
264
|
+
}
|
|
265
|
+
const lines = ["### SonarQube"];
|
|
266
|
+
if (sonarFindings.qualityGate?.status) lines.push(`- Quality gate: ${sonarFindings.qualityGate.status}`);
|
|
267
|
+
lines.push(`- Open issues: ${sonarFindings.total ?? 0}`);
|
|
268
|
+
if ((sonarFindings.total ?? 0) > 0) {
|
|
269
|
+
const groups = groupIssuesBySeverity(sonarFindings.issues || []);
|
|
270
|
+
for (const [severity, issues] of Object.entries(groups)) {
|
|
271
|
+
if (issues.length === 0) continue;
|
|
272
|
+
lines.push(` - ${severity} (${issues.length}):`);
|
|
273
|
+
for (const issue of issues.slice(0, MAX_SAMPLE_SONAR_PER_SEVERITY)) {
|
|
274
|
+
const lineSuffix = issue.line ? `:${issue.line}` : "";
|
|
275
|
+
const loc = issue.component ? `${issue.component}${lineSuffix}` : "";
|
|
276
|
+
const rule = issue.rule ? ` [${issue.rule}]` : "";
|
|
277
|
+
lines.push(` - ${loc}${rule} ${issue.message || ""}`.trim());
|
|
278
|
+
}
|
|
279
|
+
if (issues.length > MAX_SAMPLE_SONAR_PER_SEVERITY) {
|
|
280
|
+
lines.push(` - ... and ${issues.length - MAX_SAMPLE_SONAR_PER_SEVERITY} more in ${severity}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
lines.push("");
|
|
285
|
+
return lines;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function formatWebperfBlock(webperf) {
|
|
289
|
+
if (!webperf.available) {
|
|
290
|
+
return ["### WebPerf", `- Status: not collected — ${webperf.reason || "no frontend signal"}`, ""];
|
|
291
|
+
}
|
|
292
|
+
const lines = ["### WebPerf"];
|
|
293
|
+
if (webperf.mode === "cwv-result" && webperf.cwv) {
|
|
294
|
+
lines.push(`- Mode: live CWV measurement`);
|
|
295
|
+
if (webperf.url) lines.push(`- URL: ${webperf.url}`);
|
|
296
|
+
lines.push(`- Verdict: ${webperf.cwv.pass ? "PASS" : "FAIL"}`);
|
|
297
|
+
for (const [metric, score] of Object.entries(webperf.cwv.scores || {})) {
|
|
298
|
+
lines.push(` - ${metric.toUpperCase()}: ${score.value} (${score.rating})`);
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
lines.push(`- Mode: static hints only (no live CWV measurement available)`);
|
|
302
|
+
lines.push(`- LLM phase will scan source for bundle/lazy/img-opt patterns when activated`);
|
|
303
|
+
}
|
|
304
|
+
lines.push("");
|
|
305
|
+
return lines;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Detect whether the deterministic context found any signal worth showing.
|
|
310
|
+
* If everything came back empty/unavailable, the CLI can skip the
|
|
311
|
+
* "Continue with LLM?" prompt — there's nothing to compare against.
|
|
312
|
+
*/
|
|
313
|
+
export function deterministicContextHasFindings(ctx) {
|
|
314
|
+
if (!ctx) return false;
|
|
315
|
+
if (ctx.basalCost?.deadExports?.length > 0) return true;
|
|
316
|
+
if (ctx.basalCost?.unusedDependencies?.unused?.length > 0) return true;
|
|
317
|
+
if (ctx.sonarFindings?.available && (ctx.sonarFindings.total ?? 0) > 0) return true;
|
|
318
|
+
if (ctx.sonarFindings?.qualityGate?.status === "ERROR") return true;
|
|
319
|
+
if (ctx.osvFindings?.available && (ctx.osvFindings.total ?? 0) > 0) return true;
|
|
320
|
+
if (ctx.semgrepFindings?.available && (ctx.semgrepFindings.total ?? 0) > 0) return true;
|
|
321
|
+
if (ctx.infraFindings?.available && (ctx.infraFindings.total ?? 0) > 0) return true;
|
|
322
|
+
if (ctx.circularDeps?.available && (ctx.circularDeps.total ?? 0) > 0) return true;
|
|
323
|
+
if (ctx.deadExports?.available && (ctx.deadExports.total ?? 0) > 0) return true;
|
|
324
|
+
if (ctx.growthDelta && (Math.abs(ctx.growthDelta.lines || 0) > 100 || Math.abs(ctx.growthDelta.deps || 0) > 0)) return true;
|
|
325
|
+
if (ctx.aiSlop?.available && (ctx.aiSlop.total ?? 0) > 0) return true;
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// KJC-TSK-0470 — Bootstrap Docker de ai-harness-scorecard.
|
|
2
|
+
// One-shot container: detect Docker, auto-pull on first run (cache hit
|
|
3
|
+
// thereafter), execute `assess <repo>` with the project mounted read-only.
|
|
4
|
+
// No compose, no port discovery — assess is a single process that exits.
|
|
5
|
+
//
|
|
6
|
+
// KJC-BUG-0077 — classify failures (docker_missing / image_inaccessible /
|
|
7
|
+
// auth_required / network_error / unknown) and emit an actionable hint
|
|
8
|
+
// alongside the raw stderr so `kj audit` surfaces something better than
|
|
9
|
+
// the opaque "pull access denied" line the registry returns.
|
|
10
|
+
import { runCommand } from "../utils/process.js";
|
|
11
|
+
|
|
12
|
+
const D = { image: "markmishaev76/ai-harness-scorecard:latest", pullTimeoutMs: 300000, assessTimeoutMs: 180000 };
|
|
13
|
+
const pos = (v, d) => (Number(v) > 0 ? Number(v) : d);
|
|
14
|
+
|
|
15
|
+
const DOCKER_FAILURE_PATTERNS = [
|
|
16
|
+
{ reason: "image_inaccessible", re: /pull access denied|repository does not exist|manifest unknown|not found: manifest|denied: requested access/i },
|
|
17
|
+
{ reason: "auth_required", re: /unauthorized|authentication required|login required|requires authentication/i },
|
|
18
|
+
{ reason: "network_error", re: /no such host|connection refused|dial tcp|i\/o timeout|TLS handshake|net\/http|temporary failure in name resolution/i },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export function classifyDockerFailure(stderr = "") {
|
|
22
|
+
const s = String(stderr);
|
|
23
|
+
for (const { reason, re } of DOCKER_FAILURE_PATTERNS) {
|
|
24
|
+
if (re.test(s)) return reason;
|
|
25
|
+
}
|
|
26
|
+
return "unknown";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function hintForReason(reason, image) {
|
|
30
|
+
switch (reason) {
|
|
31
|
+
case "image_inaccessible":
|
|
32
|
+
return `Image \`${image}\` is not accessible from the public registry (it may have been removed, renamed, or made private). Either run \`kj audit --no-harness\` to skip this step, or override \`audit.harness.image\` with a mirror you control.`;
|
|
33
|
+
case "auth_required":
|
|
34
|
+
return `Pulling \`${image}\` requires registry authentication. Run \`docker login <registry>\` first, or override \`audit.harness.image\` with a public mirror.`;
|
|
35
|
+
case "network_error":
|
|
36
|
+
return `Network error reaching the registry while pulling \`${image}\`. Retry once connectivity is back; \`kj audit --no-harness\` skips this step in the meantime.`;
|
|
37
|
+
case "docker_missing":
|
|
38
|
+
return `Docker is not available on this machine. Install Docker (or run \`kj audit --no-harness\` to silence this).`;
|
|
39
|
+
default: return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function normalizeHarnessConfig(h = {}) {
|
|
44
|
+
return {
|
|
45
|
+
enabled: h.enabled !== false,
|
|
46
|
+
image: h.image || D.image,
|
|
47
|
+
pullTimeoutMs: pos(h.pull_timeout_ms, D.pullTimeoutMs),
|
|
48
|
+
assessTimeoutMs: pos(h.assess_timeout_ms, D.assessTimeoutMs),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function isDockerAvailable() {
|
|
53
|
+
try {
|
|
54
|
+
const r = await runCommand("docker", ["version", "--format", "{{.Server.Version}}"], { timeout: 10000 });
|
|
55
|
+
return r.exitCode === 0;
|
|
56
|
+
} catch { return false; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function isHarnessImagePresent(image) {
|
|
60
|
+
const r = await runCommand("docker", ["images", "-q", image], { timeout: 10000 });
|
|
61
|
+
return r.exitCode === 0 && r.stdout.trim().length > 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function ensureHarnessImage(harness = null) {
|
|
65
|
+
const cfg = normalizeHarnessConfig(harness || {});
|
|
66
|
+
if (await isHarnessImagePresent(cfg.image)) return { pulled: false, reused: true, image: cfg.image };
|
|
67
|
+
const r = await runCommand("docker", ["pull", cfg.image], { timeout: cfg.pullTimeoutMs });
|
|
68
|
+
if (r.exitCode !== 0) {
|
|
69
|
+
const stderr = r.stderr?.trim() || `exit ${r.exitCode}`;
|
|
70
|
+
const reason = classifyDockerFailure(stderr);
|
|
71
|
+
return { pulled: false, reused: false, image: cfg.image, error: stderr, reason };
|
|
72
|
+
}
|
|
73
|
+
return { pulled: true, reused: false, image: cfg.image };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function runHarnessAssess(repoPath, harness = null) {
|
|
77
|
+
const cfg = normalizeHarnessConfig(harness || {});
|
|
78
|
+
if (!(await isDockerAvailable())) {
|
|
79
|
+
return { ok: false, reason: "docker_missing", image: cfg.image, error: "Docker daemon is not available", hint: hintForReason("docker_missing", cfg.image) };
|
|
80
|
+
}
|
|
81
|
+
const pull = await ensureHarnessImage(cfg);
|
|
82
|
+
if (pull.error) {
|
|
83
|
+
const reason = pull.reason || "pull_failed";
|
|
84
|
+
return { ok: false, reason, image: cfg.image, error: pull.error, hint: hintForReason(reason, cfg.image) };
|
|
85
|
+
}
|
|
86
|
+
const r = await runCommand(
|
|
87
|
+
"docker",
|
|
88
|
+
["run", "--rm", "-v", `${repoPath}:/repo:ro`, cfg.image, "assess", "/repo", "--format", "json"],
|
|
89
|
+
{ timeout: cfg.assessTimeoutMs },
|
|
90
|
+
);
|
|
91
|
+
if (r.exitCode !== 0) {
|
|
92
|
+
const stderr = r.stderr?.trim() || r.stdout?.trim() || `exit ${r.exitCode}`;
|
|
93
|
+
const reason = classifyDockerFailure(stderr);
|
|
94
|
+
return { ok: false, reason, image: cfg.image, error: stderr, hint: hintForReason(reason, cfg.image) };
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(r.stdout);
|
|
98
|
+
return { ok: true, score: parsed.overall_score, grade: parsed.grade, raw: parsed };
|
|
99
|
+
} catch (e) { return { ok: false, reason: "invalid_json", error: `Invalid JSON from harness: ${e.message}` }; }
|
|
100
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// KJC-TSK-0471 — Harness Scorecard audit stage.
|
|
2
|
+
// Runs the markmishaev76/ai-harness-scorecard container after the deterministic
|
|
3
|
+
// phase, persists scorecard.json + scorecard-report.md under
|
|
4
|
+
// .karajan/audit-runs/<ts>/, and returns a summary the renderer surfaces as a
|
|
5
|
+
// "Harness Health Score" markdown block. KJC-TSK-0472 will consume the
|
|
6
|
+
// persisted artefacts to build audit-history.db.
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { normalizeHarnessConfig, runHarnessAssess } from "./harness-scorecard.js";
|
|
10
|
+
|
|
11
|
+
const CATEGORIES = [
|
|
12
|
+
["architectural_docs", "Architectural Docs", 20],
|
|
13
|
+
["mechanical_constraints", "Mechanical Constraints", 25],
|
|
14
|
+
["testing_stability", "Testing & Stability", 25],
|
|
15
|
+
["review_drift_prevention", "Review & Drift Prevention", 15],
|
|
16
|
+
["ai_specific_safeguards", "AI-Specific Safeguards", 15],
|
|
17
|
+
];
|
|
18
|
+
const labelFor = (k) => CATEGORIES.find(([key]) => key === k)?.[1] || k;
|
|
19
|
+
const ts = () => new Date().toISOString().replaceAll(/[:.]/g, "-");
|
|
20
|
+
|
|
21
|
+
export async function runHarnessSection({ projectDir, harnessConfig = null, runAssess = runHarnessAssess } = {}) {
|
|
22
|
+
const cfg = normalizeHarnessConfig(harnessConfig || {});
|
|
23
|
+
if (!cfg.enabled) return { ok: true, skipped: true, reason: "disabled" };
|
|
24
|
+
const res = await runAssess(projectDir, cfg);
|
|
25
|
+
if (!res.ok) return { ok: false, error: res.error, reason: res.reason, hint: res.hint, image: cfg.image };
|
|
26
|
+
const outDir = path.join(projectDir, ".karajan", "audit-runs", ts());
|
|
27
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
28
|
+
const scorecardPath = path.join(outDir, "scorecard.json");
|
|
29
|
+
const rawReportPath = path.join(outDir, "scorecard-report.md");
|
|
30
|
+
await fs.writeFile(scorecardPath, JSON.stringify(res.raw, null, 2), "utf8");
|
|
31
|
+
await fs.writeFile(rawReportPath, res.raw?.report_markdown || res.raw?.markdown || "", "utf8");
|
|
32
|
+
return {
|
|
33
|
+
ok: true, score: res.score, grade: res.grade,
|
|
34
|
+
categories: res.raw?.categories || {},
|
|
35
|
+
checks: Array.isArray(res.raw?.checks) ? res.raw.checks : [],
|
|
36
|
+
scorecardPath, rawReportPath, image: cfg.image,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function formatHarnessSection(summary) {
|
|
41
|
+
if (!summary || summary.skipped) return "";
|
|
42
|
+
if (!summary.ok) {
|
|
43
|
+
const reason = summary.reason ? ` (${summary.reason})` : "";
|
|
44
|
+
const hint = summary.hint ? `\n\n${summary.hint}\n` : "";
|
|
45
|
+
return `## Harness Scorecard\n\n_skipped${reason}: ${summary.error}_${hint}\n`;
|
|
46
|
+
}
|
|
47
|
+
const lines = [
|
|
48
|
+
`## Harness Health Score: ${summary.score}/100 (${summary.grade})`,
|
|
49
|
+
"",
|
|
50
|
+
"| Category | Weight | Score |",
|
|
51
|
+
"| --- | --- | --- |",
|
|
52
|
+
];
|
|
53
|
+
for (const [key, label, weight] of CATEGORIES) {
|
|
54
|
+
const score = summary.categories?.[key]?.score ?? "—";
|
|
55
|
+
lines.push(`| ${label} | ${weight}% | ${score} |`);
|
|
56
|
+
}
|
|
57
|
+
const checks = Array.isArray(summary.checks) ? summary.checks : [];
|
|
58
|
+
if (checks.length) {
|
|
59
|
+
const groups = new Map();
|
|
60
|
+
for (const c of checks) {
|
|
61
|
+
const k = c.category || "other";
|
|
62
|
+
if (!groups.has(k)) groups.set(k, []);
|
|
63
|
+
groups.get(k).push(c);
|
|
64
|
+
}
|
|
65
|
+
lines.push("", "### Checks");
|
|
66
|
+
for (const [k, list] of groups) {
|
|
67
|
+
lines.push(`- **${labelFor(k)}**`);
|
|
68
|
+
for (const c of list) lines.push(` - ${c.passed ? "✓" : "✗"} ${c.name}${c.detail ? ` — ${c.detail}` : ""}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return lines.join("\n") + "\n";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function harnessSummaryForJson(summary) {
|
|
75
|
+
if (!summary?.ok || summary.skipped) return null;
|
|
76
|
+
return {
|
|
77
|
+
score: summary.score, grade: summary.grade,
|
|
78
|
+
categories: summary.categories, checks: summary.checks,
|
|
79
|
+
rawReportPath: summary.rawReportPath, scorecardPath: summary.scorecardPath,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkov infra-misconfiguration collector for `kj audit` (INF-C,
|
|
3
|
+
* KJC-TSK-0760, epic KJC-PCS-0078).
|
|
4
|
+
*
|
|
5
|
+
* One tool covers the whole infra surface — terraform, kubernetes, helm,
|
|
6
|
+
* kustomize, ansible, dockerfiles — so checkov is THE infra scanner (tfsec
|
|
7
|
+
* and tflint stay out of scope: single dependency to learn and install).
|
|
8
|
+
* Findings enter the audit through the SAME best-effort channel as
|
|
9
|
+
* semgrep-findings (KJC-TSK-0366): no infra markers → not-applicable (zero
|
|
10
|
+
* noise, checkov never runs); missing binary → declared with the install
|
|
11
|
+
* command; non-zero exit with JSON on stdout (checkov's failed-checks
|
|
12
|
+
* behavior) rescued; unparseable output degrades SAYING so.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { runGoverned, jobsCap } from "../utils/tool-governor.js";
|
|
16
|
+
import { detectInfraMarkers } from "../utils/project-detect.js";
|
|
17
|
+
|
|
18
|
+
const SCAN_TIMEOUT_MS = 120_000;
|
|
19
|
+
const INSTALL_HINT = "install via 'pipx install checkov' or 'pip install checkov'";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Run checkov on the project and return a structured result.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} projectDir - absolute path to the project root
|
|
25
|
+
* @param {object} [logger]
|
|
26
|
+
* @returns {Promise<{available: boolean, reason?: string, total?: number, findings?: object[]}>}
|
|
27
|
+
*/
|
|
28
|
+
export async function collectInfraFindings(projectDir, logger = null) {
|
|
29
|
+
// KJC-BUG-0122 doctrine: external scanners never fire from the unit suite.
|
|
30
|
+
if (process.env.VITEST && process.env.KJ_ALLOW_REAL_SCANS !== "1") {
|
|
31
|
+
return { available: false, reason: "real scans disabled under the test runner (set KJ_ALLOW_REAL_SCANS=1 to opt in)" };
|
|
32
|
+
}
|
|
33
|
+
if (!(await detectInfraMarkers(projectDir))) {
|
|
34
|
+
return { available: false, reason: "no infra markers (terraform/helm/kustomize/ansible) — not applicable" };
|
|
35
|
+
}
|
|
36
|
+
let stdout;
|
|
37
|
+
try {
|
|
38
|
+
const { stdout: out } = await runGoverned(
|
|
39
|
+
"checkov",
|
|
40
|
+
["-d", projectDir || ".", "--output", "json", "--quiet", "--compact", `--parallel-runner-count=${jobsCap()}`],
|
|
41
|
+
{ cwd: projectDir, timeout: SCAN_TIMEOUT_MS + 5000, maxBuffer: 32 * 1024 * 1024 }
|
|
42
|
+
);
|
|
43
|
+
stdout = out;
|
|
44
|
+
} catch (err) {
|
|
45
|
+
if (err.code === "ENOENT") {
|
|
46
|
+
if (logger?.warn) logger.warn(`checkov not found in PATH — ${INSTALL_HINT}`);
|
|
47
|
+
return { available: false, reason: `checkov not installed — ${INSTALL_HINT}` };
|
|
48
|
+
}
|
|
49
|
+
if (err.killed) {
|
|
50
|
+
if (logger?.warn) logger.warn(`checkov timed out after ${SCAN_TIMEOUT_MS}ms`);
|
|
51
|
+
return { available: false, reason: `checkov timed out after ${SCAN_TIMEOUT_MS}ms` };
|
|
52
|
+
}
|
|
53
|
+
// checkov exits non-zero when checks fail, but still emits the report.
|
|
54
|
+
if (typeof err.stdout === "string" && err.stdout.trim().length > 0) {
|
|
55
|
+
stdout = err.stdout;
|
|
56
|
+
} else {
|
|
57
|
+
if (logger?.warn) logger.warn(`checkov failed: ${err.message}`);
|
|
58
|
+
return { available: false, reason: `checkov failed: ${err.message}` };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
parsed = JSON.parse(stdout);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (logger?.warn) logger.warn(`checkov output not parseable: ${err.message}`);
|
|
67
|
+
return { available: false, reason: "checkov output not parseable as JSON" };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { available: true, ...parseCheckovOutput(parsed) };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Convert checkov's JSON (object for one framework, array for several)
|
|
75
|
+
* into the flat findings list the audit prompt consumes. Checkov OSS may
|
|
76
|
+
* omit severity — WARNING is the honest default.
|
|
77
|
+
*
|
|
78
|
+
* @param {object|object[]} raw
|
|
79
|
+
* @returns {{total: number, findings: object[]}}
|
|
80
|
+
*/
|
|
81
|
+
export function parseCheckovOutput(raw) {
|
|
82
|
+
const reports = Array.isArray(raw) ? raw : [raw];
|
|
83
|
+
const findings = [];
|
|
84
|
+
for (const report of reports) {
|
|
85
|
+
const failed = report?.results?.failed_checks;
|
|
86
|
+
if (!Array.isArray(failed)) continue;
|
|
87
|
+
for (const c of failed) {
|
|
88
|
+
findings.push({
|
|
89
|
+
rule: c.check_id || "(unknown)",
|
|
90
|
+
severity: String(c.severity || "WARNING").toUpperCase(),
|
|
91
|
+
framework: report.check_type || "infra",
|
|
92
|
+
file: String(c.file_path || "").replace(/^\//, ""),
|
|
93
|
+
line: Array.isArray(c.file_line_range) ? (c.file_line_range[0] ?? 0) : 0,
|
|
94
|
+
message: c.check_name || "",
|
|
95
|
+
guideline: c.guideline || null,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { total: findings.length, findings };
|
|
100
|
+
}
|