@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,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy resolution: map task type + config toggles to concrete pipeline
|
|
3
|
+
* flags (TDD, Sonar, reviewer retries, tests required, coder required, ...).
|
|
4
|
+
*
|
|
5
|
+
* @typedef {import("../types/policy.js").ResolvedPolicies} ResolvedPolicies
|
|
6
|
+
* @typedef {import("../types/config.js").KarajanConfig} KarajanConfig
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// KJC-TSK-0400: spike y research añadidos como tipos no-code. Comparten
|
|
10
|
+
// las defaults restrictivas (tdd/sonar/testsRequired off) porque NO
|
|
11
|
+
// producen código que medir: un SPIKE es un .md de decisión, un research
|
|
12
|
+
// es una nota de investigación. Forzar Sonar les hace fallar siempre.
|
|
13
|
+
export const VALID_TASK_TYPES = new Set([
|
|
14
|
+
"sw", "infra", "doc", "add-tests", "refactor",
|
|
15
|
+
"audit", "analysis", "no-code", "spike", "research",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_POLICIES = {
|
|
19
|
+
sw: { tdd: true, sonar: true, reviewer: true, testsRequired: true },
|
|
20
|
+
infra: { tdd: false, sonar: false, reviewer: false, testsRequired: false },
|
|
21
|
+
doc: { tdd: false, sonar: false, reviewer: true, testsRequired: false },
|
|
22
|
+
"add-tests": { tdd: false, sonar: true, reviewer: true, testsRequired: true },
|
|
23
|
+
refactor: { tdd: true, sonar: true, reviewer: true, testsRequired: false },
|
|
24
|
+
audit: { tdd: false, sonar: false, reviewer: false, testsRequired: false, coderRequired: false },
|
|
25
|
+
analysis: { tdd: false, sonar: false, reviewer: false, testsRequired: false, coderRequired: false },
|
|
26
|
+
"no-code": { tdd: false, sonar: false, reviewer: true, testsRequired: false, coderRequired: true },
|
|
27
|
+
spike: { tdd: false, sonar: false, reviewer: true, testsRequired: false, coderRequired: true },
|
|
28
|
+
research: { tdd: false, sonar: false, reviewer: true, testsRequired: false, coderRequired: true },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// KJC-TSK-0400: si el title arranca con [SPIKE] / [DOC] / [RESEARCH] /
|
|
32
|
+
// [NOCODE], inferimos el task_type del prefijo. Pensado para casos donde
|
|
33
|
+
// el planner emite task_type='sw' por defecto pero el title declara
|
|
34
|
+
// claramente que la HU no produce código. Devuelve null si no hay match.
|
|
35
|
+
const TITLE_PREFIX_TO_TYPE = {
|
|
36
|
+
SPIKE: "spike",
|
|
37
|
+
RESEARCH: "research",
|
|
38
|
+
DOC: "doc",
|
|
39
|
+
NOCODE: "no-code",
|
|
40
|
+
"NO-CODE": "no-code",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function inferTaskTypeFromTitle(title) {
|
|
44
|
+
if (typeof title !== "string") return null;
|
|
45
|
+
const m = title.match(/^\s*\[([A-Z][A-Z-]+)\]/);
|
|
46
|
+
if (!m) return null;
|
|
47
|
+
return TITLE_PREFIX_TO_TYPE[m[1].toUpperCase()] || null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// KJC-TSK-0400: dado un HU del plan, devuelve el task_type efectivo:
|
|
51
|
+
// explícito en story.task_type si es válido, si no inferido del title.
|
|
52
|
+
// Fallback 'sw' (conservador, mantiene gates activos cuando hay duda).
|
|
53
|
+
export function effectiveTaskType(story) {
|
|
54
|
+
if (!story || typeof story !== "object") return "sw";
|
|
55
|
+
if (VALID_TASK_TYPES.has(story.task_type)) return story.task_type;
|
|
56
|
+
return inferTaskTypeFromTitle(story.title) || "sw";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const FALLBACK_TYPE = "sw";
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolve pipeline policies for a given taskType.
|
|
63
|
+
* Unknown / null / undefined taskType falls back to "sw" (conservative).
|
|
64
|
+
* configOverrides optionally merges over defaults per taskType.
|
|
65
|
+
*/
|
|
66
|
+
export function resolvePolicies(taskType, configOverrides) {
|
|
67
|
+
const resolvedType = VALID_TASK_TYPES.has(taskType) ? taskType : FALLBACK_TYPE;
|
|
68
|
+
const base = { ...DEFAULT_POLICIES[resolvedType] };
|
|
69
|
+
const overrides = configOverrides?.[resolvedType];
|
|
70
|
+
if (overrides && typeof overrides === "object") {
|
|
71
|
+
Object.assign(base, overrides);
|
|
72
|
+
}
|
|
73
|
+
return base;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Resolve policies for a taskType and return a flat object with the resolved
|
|
78
|
+
* taskType plus all policy flags. This is the main entry point for the
|
|
79
|
+
* orchestrator to determine which pipeline stages to enable/disable.
|
|
80
|
+
*/
|
|
81
|
+
export function applyPolicies({ taskType, policies } = {}) {
|
|
82
|
+
const resolvedType = VALID_TASK_TYPES.has(taskType) ? taskType : FALLBACK_TYPE;
|
|
83
|
+
const resolved = resolvePolicies(taskType, policies);
|
|
84
|
+
return { taskType: resolvedType, ...resolved };
|
|
85
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// KJC-TSK-0583 — Inbound secret redaction for the review boundary.
|
|
2
|
+
//
|
|
3
|
+
// The outbound boundary (output-guard.js) already blocks committing secrets.
|
|
4
|
+
// This closes the *inbound* gap: a hardcoded credential in the working tree
|
|
5
|
+
// would otherwise reach the (possibly cloud) reviewer model verbatim inside the
|
|
6
|
+
// diff. `redactSecrets` masks each credential before the model sees it, reusing
|
|
7
|
+
// the SAME catalog as output-guard — no new patterns, no AI cost, deterministic.
|
|
8
|
+
import { CREDENTIAL_PATTERNS } from "./output-guard.js";
|
|
9
|
+
|
|
10
|
+
// Families whose match begins with a fixed, identifiable literal prefix. The
|
|
11
|
+
// placeholder keeps that prefix so the reviewer can still tell WHAT kind of
|
|
12
|
+
// secret was redacted (and say "move it to .env") without seeing the value.
|
|
13
|
+
const STABLE_PREFIX_BY_ID = {
|
|
14
|
+
"aws-key": "AKIA",
|
|
15
|
+
"npm-token": "npm_",
|
|
16
|
+
"openai-key": "sk-",
|
|
17
|
+
"anthropic-key": "sk-ant-",
|
|
18
|
+
"stripe-key": "sk_live_",
|
|
19
|
+
"stripe-test": "sk_test_",
|
|
20
|
+
"google-api-key": "AIza",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Families with a dynamic prefix: keep the real match up to and including the
|
|
24
|
+
// first separator (ghp_/gho_/…, xoxb-/xoxp-/…), then mask the rest.
|
|
25
|
+
const DYNAMIC_SEPARATOR_BY_ID = {
|
|
26
|
+
"github-token": "_",
|
|
27
|
+
"slack-token": "-",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const GENERIC = "***REDACTED-SECRET***";
|
|
31
|
+
const SUFFIX = "***REDACTED***";
|
|
32
|
+
|
|
33
|
+
function placeholderFor(id, match) {
|
|
34
|
+
const stable = STABLE_PREFIX_BY_ID[id];
|
|
35
|
+
if (stable) return `${stable}${SUFFIX}`;
|
|
36
|
+
const sep = DYNAMIC_SEPARATOR_BY_ID[id];
|
|
37
|
+
if (sep) {
|
|
38
|
+
const idx = match.indexOf(sep);
|
|
39
|
+
return idx >= 0 ? `${match.slice(0, idx + 1)}${SUFFIX}` : GENERIC;
|
|
40
|
+
}
|
|
41
|
+
return GENERIC;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Apply the most specific prefixes first so `sk-ant-` wins over `sk-`; families
|
|
45
|
+
// without a stable prefix keep their catalog order (stable sort) and run last.
|
|
46
|
+
function orderedPatterns() {
|
|
47
|
+
return [...CREDENTIAL_PATTERNS].sort(
|
|
48
|
+
(a, b) => (STABLE_PREFIX_BY_ID[b.id]?.length ?? 0) - (STABLE_PREFIX_BY_ID[a.id]?.length ?? 0)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Mask every credential in `text`, preserving line structure. Pure and
|
|
54
|
+
* deterministic: clean text is returned untouched (no-op, no cost).
|
|
55
|
+
* @param {string} text
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
export function redactSecrets(text) {
|
|
59
|
+
if (typeof text !== "string" || text.length === 0) return text;
|
|
60
|
+
let out = text;
|
|
61
|
+
for (const { id, pattern } of orderedPatterns()) {
|
|
62
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
63
|
+
out = out.replace(new RegExp(pattern.source, flags), (match) =>
|
|
64
|
+
// Idempotent: a broad pattern (generic-secret, hardcoded-key-assignment)
|
|
65
|
+
// can span an already-masked value; leave the more specific placeholder
|
|
66
|
+
// in place instead of clobbering it with the generic one.
|
|
67
|
+
match.includes("REDACTED") ? match : placeholderFor(id, match)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* advisory — per-artifact comparison engine for `kj harden` advisory mode
|
|
3
|
+
* (KJC-TSK-0565, épica KJC-PCS-0060). For each artifact harden manages it
|
|
4
|
+
* classifies the repo state: MISSING (no file nor equivalent), KJ_MANAGED (a
|
|
5
|
+
* kj:managed block, up to date or outdated vN) or USER_OWNED (the user's own,
|
|
6
|
+
* possibly in another format). Read-only; never writes or prints. The result
|
|
7
|
+
* feeds `--report` (Advisory B) / `--interactive` (Advisory C). Per-type
|
|
8
|
+
* improvement heuristics for USER_OWNED land in the follow-up slice (PR2).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
import { findManagedBlock } from "../utils/managed-markers.js";
|
|
15
|
+
import { findAlternative } from "./alternatives.js";
|
|
16
|
+
import { CONFIGS_BY_LANGUAGE, UNIVERSAL_CONFIGS } from "./config-templates.js";
|
|
17
|
+
import { detectStackRoots } from "./stack-roots.js";
|
|
18
|
+
|
|
19
|
+
const BLOCK_VERSION = 1; // current version every markered config ships at
|
|
20
|
+
|
|
21
|
+
// Other filenames/formats for the same artifact, so kj never reports a false
|
|
22
|
+
// "missing". `pkgKey` is a package.json field that can hold inline config.
|
|
23
|
+
const EQUIVALENTS = {
|
|
24
|
+
commitlint: { files: [".commitlintrc", ".commitlintrc.js", ".commitlintrc.cjs", ".commitlintrc.mjs", ".commitlintrc.json", ".commitlintrc.yaml", ".commitlintrc.yml", "commitlint.config.mjs", "commitlint.config.cjs", "commitlint.config.ts"], pkgKey: "commitlint" },
|
|
25
|
+
eslint: { files: ["eslint.config.mjs", "eslint.config.cjs", "eslint.config.ts", "eslint.config.mts", "eslint.config.cts", ".eslintrc.js", ".eslintrc.cjs", ".eslintrc.json", ".eslintrc.yaml", ".eslintrc.yml"], pkgKey: "eslintConfig" },
|
|
26
|
+
prettier: { files: [".prettierrc", ".prettierrc.js", ".prettierrc.yaml", ".prettierrc.yml", ".prettierrc.json5", ".prettierrc.toml", "prettier.config.js", "prettier.config.mjs", "prettier.config.cjs", "prettier.config.ts"], pkgKey: "prettier" },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* KJC-BUG-0119 — same-tool filename variant for `artifactId` in any of
|
|
31
|
+
* `searchDirs` (file or inline package.json config); null when none exists.
|
|
32
|
+
* Seeding kj's default filename NEXT TO a variant eclipses the user's real
|
|
33
|
+
* config (eslint resolves eslint.config.js before .mjs), so config-engine
|
|
34
|
+
* must consult this before treating an artifact as absent.
|
|
35
|
+
*/
|
|
36
|
+
export function findEquivalentIn(searchDirs, artifactId) {
|
|
37
|
+
const spec = EQUIVALENTS[artifactId];
|
|
38
|
+
if (!spec) return null;
|
|
39
|
+
for (const dir of searchDirs) {
|
|
40
|
+
for (const rel of spec.files) {
|
|
41
|
+
if (existsSync(join(dir, rel))) return rel;
|
|
42
|
+
}
|
|
43
|
+
if (spec.pkgKey && existsSync(join(dir, "package.json"))) {
|
|
44
|
+
try {
|
|
45
|
+
if (JSON.parse(readFileSync(join(dir, "package.json"), "utf8"))[spec.pkgKey] != null) {
|
|
46
|
+
return `package.json#${spec.pkgKey}`;
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
/* unreadable package.json → not a match */
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Concrete gains kj's standard would add to a USER_OWNED config, detected by
|
|
57
|
+
// substring probes (no AST parse): each probe absent from the user's content
|
|
58
|
+
// becomes one improvement. Heuristic by design — conservative, never a false
|
|
59
|
+
// "you're missing this" when the signal is clearly present.
|
|
60
|
+
const IMPROVEMENTS = {
|
|
61
|
+
eslint: [
|
|
62
|
+
{ needle: "no-var", id: "no-var", detail: "bans var (ES2025: const/let)" },
|
|
63
|
+
{ needle: "no-restricted-globals", id: "no-legacy-globals", detail: "bans alert/confirm/prompt, escape/unescape" },
|
|
64
|
+
{ needle: "no-restricted-properties", id: "no-legacy-apis", detail: "bans document.write, substr" },
|
|
65
|
+
],
|
|
66
|
+
commitlint: [
|
|
67
|
+
{ needle: "header-max-length", id: "header-max-length", detail: "caps the commit header at 100 chars" },
|
|
68
|
+
{ needle: "subject-case", id: "subject-case", detail: "enforces a lowercase subject" },
|
|
69
|
+
],
|
|
70
|
+
prettier: [{ needle: "printWidth", id: "print-width", detail: "pins printWidth (consistent wrapping)" }],
|
|
71
|
+
editorconfig: [
|
|
72
|
+
{ needle: "insert_final_newline", id: "final-newline", detail: "enforces a final newline" },
|
|
73
|
+
{ needle: "charset", id: "charset", detail: "pins charset = utf-8" },
|
|
74
|
+
{ needle: "trim_trailing_whitespace", id: "trim-trailing", detail: "trims trailing whitespace" },
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** Improvements kj would add to the user's own config for `artifactId`. */
|
|
79
|
+
function improvementsFor(artifactId, content) {
|
|
80
|
+
return (IMPROVEMENTS[artifactId] ?? [])
|
|
81
|
+
.filter((probe) => !content.includes(probe.needle))
|
|
82
|
+
.map(({ id, detail }) => ({ id, detail }));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Build an artifact descriptor from a config-template entry. */
|
|
86
|
+
function toArtifact(cfg) {
|
|
87
|
+
const id = cfg.blockId ?? "prettier"; // the only marker-less JSON config is prettier
|
|
88
|
+
return {
|
|
89
|
+
id,
|
|
90
|
+
file: cfg.file,
|
|
91
|
+
blockId: cfg.blockId ?? null,
|
|
92
|
+
currentVersion: cfg.blockId ? BLOCK_VERSION : null,
|
|
93
|
+
equivalents: EQUIVALENTS[id]?.files ?? [],
|
|
94
|
+
pkgKey: EQUIVALENTS[id]?.pkgKey ?? null,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Locate the user's file for an artifact under `searchDir`; null if absent. */
|
|
99
|
+
function findArtifactFile(searchDir, artifact) {
|
|
100
|
+
if (existsSync(join(searchDir, artifact.file))) return artifact.file;
|
|
101
|
+
return findEquivalentIn([searchDir], artifact.id);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Read the artifact body, or the JSON of an inline package.json key. */
|
|
105
|
+
function readArtifactContent(searchDir, foundAt) {
|
|
106
|
+
const [file, key] = foundAt.split("#");
|
|
107
|
+
const raw = readFileSync(join(searchDir, file), "utf8");
|
|
108
|
+
if (!key) return raw;
|
|
109
|
+
try {
|
|
110
|
+
return JSON.stringify(JSON.parse(raw)[key] ?? null);
|
|
111
|
+
} catch {
|
|
112
|
+
return raw;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Map a classified state to a recommendation + human rationale. */
|
|
117
|
+
function recommend(state) {
|
|
118
|
+
if (state.status === "SATISFIED_BY_ALTERNATIVE") {
|
|
119
|
+
return {
|
|
120
|
+
recommendation: "keep",
|
|
121
|
+
rationale: `covered by ${state.foundAt} (${state.alternativeTool}) — kj won't add a second linter/formatter`,
|
|
122
|
+
mergeable: false,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (state.status === "MISSING") return { recommendation: "install", rationale: `kj would add ${state.file}`, mergeable: true };
|
|
126
|
+
if (state.status === "USER_OWNED") {
|
|
127
|
+
const n = state.improvements?.length ?? 0;
|
|
128
|
+
return n === 0
|
|
129
|
+
? { recommendation: "keep", rationale: "your own config — already covers the kj standard", mergeable: false }
|
|
130
|
+
: { recommendation: "review", rationale: `your own config — kj could add ${n} improvement(s)`, mergeable: true };
|
|
131
|
+
}
|
|
132
|
+
if (state.upToDate) return { recommendation: "keep", rationale: `managed by kj (v${state.managedVersion}), up to date`, mergeable: false };
|
|
133
|
+
return { recommendation: "update", rationale: `kj v${state.currentVersion} available (you have v${state.managedVersion})`, mergeable: true };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Classify a single artifact found (or not) under `searchDir`. `altDirs`
|
|
138
|
+
* widens the cross-tool alternative lookup (a root-level biome.json covers a
|
|
139
|
+
* monorepo's language roots); the user's OWN config of the same tool still
|
|
140
|
+
* wins over an alternative.
|
|
141
|
+
*/
|
|
142
|
+
export function classifyArtifact(searchDir, artifact, altDirs = [searchDir]) {
|
|
143
|
+
const foundAt = findArtifactFile(searchDir, artifact);
|
|
144
|
+
const base = { foundAt, managedVersion: null, upToDate: null, improvements: [] };
|
|
145
|
+
let state = { ...base, status: "MISSING" };
|
|
146
|
+
if (!foundAt) {
|
|
147
|
+
const alt = findAlternative(altDirs, artifact.id);
|
|
148
|
+
if (alt) state = { ...base, status: "SATISFIED_BY_ALTERNATIVE", foundAt: alt.foundAt, alternativeTool: alt.tool };
|
|
149
|
+
}
|
|
150
|
+
if (foundAt) {
|
|
151
|
+
const content = readArtifactContent(searchDir, foundAt);
|
|
152
|
+
if (artifact.blockId && content.includes(`kj:managed:${artifact.blockId}`)) {
|
|
153
|
+
const { version } = findManagedBlock(content, artifact.blockId);
|
|
154
|
+
state = { ...base, status: "KJ_MANAGED", managedVersion: version, upToDate: version != null && version >= artifact.currentVersion };
|
|
155
|
+
} else {
|
|
156
|
+
state = { ...base, status: "USER_OWNED", improvements: improvementsFor(artifact.id, content) };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const head = { id: artifact.id, file: artifact.file, currentVersion: artifact.currentVersion };
|
|
160
|
+
return { ...head, ...state, ...recommend({ ...state, ...head }) };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Compare the whole repo against the kj standard, artifact by artifact:
|
|
165
|
+
* universal configs at the root, then each language's lint config inside its
|
|
166
|
+
* own root (monorepo-aware, mirroring installConfigsForRoots). `minimal`
|
|
167
|
+
* profile ships no config, so there is nothing to compare.
|
|
168
|
+
*/
|
|
169
|
+
export function compareHarden({ projectDir = process.cwd(), profile = "standard", only = [], exclude = [] } = {}) {
|
|
170
|
+
if (profile === "minimal") return { artifacts: [] };
|
|
171
|
+
const artifacts = UNIVERSAL_CONFIGS.map((cfg) => ({ dir: ".", ...classifyArtifact(projectDir, toArtifact(cfg)) }));
|
|
172
|
+
for (const { dir, language } of detectStackRoots(projectDir, { only, exclude })) {
|
|
173
|
+
const searchDir = dir === "." ? projectDir : join(projectDir, dir);
|
|
174
|
+
for (const cfg of CONFIGS_BY_LANGUAGE[language] ?? []) {
|
|
175
|
+
const res = classifyArtifact(searchDir, toArtifact(cfg), [searchDir, projectDir]);
|
|
176
|
+
// Alternative matches may live at the repo root — keep their path unprefixed.
|
|
177
|
+
const prefix = res.foundAt && dir !== "." && res.status !== "SATISFIED_BY_ALTERNATIVE";
|
|
178
|
+
artifacts.push({ dir, ...res, foundAt: prefix ? join(dir, res.foundAt) : res.foundAt });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { artifacts };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const MARK = { install: "+", update: "↑", review: "~", keep: "✓" };
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Render a compareHarden() result as human-readable lines (Advisory B). Pure:
|
|
188
|
+
* returns an array of strings, prints nothing. One line per artifact, indented
|
|
189
|
+
* bullets for each improvement, then a one-line tally.
|
|
190
|
+
*/
|
|
191
|
+
export function formatAdvisoryReport({ artifacts = [] } = {}) {
|
|
192
|
+
if (artifacts.length === 0) return ["kj harden — nothing to compare (minimal profile or empty repo)."];
|
|
193
|
+
const lines = ["kj harden — advisory report (read-only)", ""];
|
|
194
|
+
const tally = { install: 0, update: 0, review: 0, keep: 0 };
|
|
195
|
+
for (const a of artifacts) {
|
|
196
|
+
tally[a.recommendation] = (tally[a.recommendation] ?? 0) + 1;
|
|
197
|
+
const path = a.foundAt ?? (a.dir === "." ? a.file : join(a.dir, a.file));
|
|
198
|
+
lines.push(` ${MARK[a.recommendation] ?? "·"} ${a.recommendation.padEnd(7)} ${path} — ${a.rationale}`);
|
|
199
|
+
for (const imp of a.improvements ?? []) lines.push(` • ${imp.detail}`);
|
|
200
|
+
}
|
|
201
|
+
lines.push("", `${artifacts.length} artifact(s) · ${tally.install} missing · ${tally.review} to review · ${tally.update} outdated · ${tally.keep} ok`);
|
|
202
|
+
return lines;
|
|
203
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-tool alternatives for `kj harden` (KJC-TSK-0614).
|
|
3
|
+
*
|
|
4
|
+
* A user's Biome config replaces BOTH eslint and prettier — seeding kj's
|
|
5
|
+
* configs next to biome.json installs a second, conflicting linter/formatter
|
|
6
|
+
* that fights the user's own hooks. The advisory's EQUIVALENTS map only
|
|
7
|
+
* covers same-tool filename variants; this map covers whole-tool
|
|
8
|
+
* substitutions, and the three surfaces (advisory, config-engine, check)
|
|
9
|
+
* consult it before treating an artifact as missing.
|
|
10
|
+
*
|
|
11
|
+
* Detection is config-file based on purpose: deterministic, and a false
|
|
12
|
+
* negative just means kj proposes its default (the pre-0614 behaviour),
|
|
13
|
+
* never that it overwrites the user's tool.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
|
|
19
|
+
const ALTERNATIVES = {
|
|
20
|
+
eslint: [{ tool: "biome", files: ["biome.json", "biome.jsonc"] }],
|
|
21
|
+
prettier: [{ tool: "biome", files: ["biome.json", "biome.jsonc"] }],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* First alternative tool that satisfies `artifactId`, searching each dir in
|
|
26
|
+
* order (a language root first, then the repo root — a root-level biome.json
|
|
27
|
+
* covers the whole monorepo). Null when none applies.
|
|
28
|
+
*
|
|
29
|
+
* @param {string[]} searchDirs
|
|
30
|
+
* @param {string} artifactId - "eslint" | "prettier" | ...
|
|
31
|
+
* @returns {{ tool: string, foundAt: string }|null}
|
|
32
|
+
*/
|
|
33
|
+
export function findAlternative(searchDirs, artifactId) {
|
|
34
|
+
for (const alt of ALTERNATIVES[artifactId] ?? []) {
|
|
35
|
+
for (const dir of searchDirs) {
|
|
36
|
+
for (const rel of alt.files) {
|
|
37
|
+
if (existsSync(join(dir, rel))) return { tool: alt.tool, foundAt: rel };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Artifact id for a config-template entry — same convention as the
|
|
46
|
+
* advisory's toArtifact: prettier is the only marker-less JSON config.
|
|
47
|
+
*/
|
|
48
|
+
export function artifactIdForConfig(cfg) {
|
|
49
|
+
return cfg.blockId ?? "prettier";
|
|
50
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* checkHarden — verify the quality harness installed by `kj harden`
|
|
3
|
+
* (KJC-TSK-0558). Detects drift: a deleted hook, a hook that lost its kj
|
|
4
|
+
* marker or its executable bit, or a core.hooksPath that no longer points at
|
|
5
|
+
* `.karajan/hooks`. Returns a per-check report; the CLI maps it to exit 0/≠0.
|
|
6
|
+
*
|
|
7
|
+
* Config, workflow and language-advisory checks land in the next slice.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
import { runCommand } from "../utils/process.js";
|
|
14
|
+
import { artifactIdForConfig, findAlternative } from "./alternatives.js";
|
|
15
|
+
import { CONFIGS_BY_LANGUAGE, UNIVERSAL_CONFIGS } from "./config-templates.js";
|
|
16
|
+
import { PROFILE_HOOKS } from "./hook-templates.js";
|
|
17
|
+
import { detectStackRoots } from "./stack-roots.js";
|
|
18
|
+
import { qualityWorkflowFor, WORKFLOWS } from "./workflow-templates.js";
|
|
19
|
+
|
|
20
|
+
const HOOKS_DIR = join(".karajan", "hooks");
|
|
21
|
+
const WORKFLOWS_DIR = join(".github", "workflows");
|
|
22
|
+
|
|
23
|
+
/** A config/workflow file just needs to be present (it may be user-owned). */
|
|
24
|
+
function presence(projectDir, rel, id, miss) {
|
|
25
|
+
const ok = existsSync(join(projectDir, rel));
|
|
26
|
+
return { id, ok, detail: ok ? "ok" : miss };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function checkHarden({ projectDir = process.cwd(), profile = "standard" } = {}) {
|
|
30
|
+
const checks = [];
|
|
31
|
+
|
|
32
|
+
const res = await runCommand("git", ["config", "--local", "core.hooksPath"], { cwd: projectDir });
|
|
33
|
+
const hooksPath = (res.stdout ?? "").trim();
|
|
34
|
+
checks.push({
|
|
35
|
+
id: "core.hooksPath",
|
|
36
|
+
ok: hooksPath === HOOKS_DIR,
|
|
37
|
+
detail: hooksPath === HOOKS_DIR ? "ok" : hooksPath || "(unset) — run kj harden",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
for (const hook of PROFILE_HOOKS[profile] ?? []) {
|
|
41
|
+
const target = join(projectDir, HOOKS_DIR, hook);
|
|
42
|
+
const exists = existsSync(target);
|
|
43
|
+
const executable = exists && (statSync(target).mode & 0o100) !== 0;
|
|
44
|
+
const marked = exists && readFileSync(target, "utf8").includes(`kj:managed:hook:${hook}`);
|
|
45
|
+
const detail = !exists ? "missing" : !executable ? "not executable" : !marked ? "no kj marker" : "ok";
|
|
46
|
+
checks.push({ id: `hook:${hook}`, ok: exists && executable && marked, detail });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Config + workflows ship with standard+ — and surface the greenfield gap:
|
|
50
|
+
// a language now present but never hardened ⇒ its config shows as missing.
|
|
51
|
+
if (profile !== "minimal") {
|
|
52
|
+
const roots = detectStackRoots(projectDir);
|
|
53
|
+
for (const cfg of UNIVERSAL_CONFIGS) {
|
|
54
|
+
checks.push(presence(projectDir, cfg.file, `config:${cfg.file}`, "missing — run kj harden"));
|
|
55
|
+
}
|
|
56
|
+
for (const { dir, language } of roots) {
|
|
57
|
+
const rootDir = dir === "." ? projectDir : join(projectDir, dir);
|
|
58
|
+
for (const cfg of CONFIGS_BY_LANGUAGE[language] ?? []) {
|
|
59
|
+
const rel = dir === "." ? cfg.file : join(dir, cfg.file);
|
|
60
|
+
// A config covered by the user's own tool (biome.json ⇒ eslint+prettier)
|
|
61
|
+
// is not drift — kj must not demand a second linter/formatter (KJC-TSK-0614).
|
|
62
|
+
if (!existsSync(join(projectDir, rel))) {
|
|
63
|
+
const alt = findAlternative([rootDir, projectDir], artifactIdForConfig(cfg));
|
|
64
|
+
if (alt) {
|
|
65
|
+
checks.push({ id: `config:${rel}`, ok: true, detail: `covered by ${alt.foundAt} (${alt.tool})` });
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
checks.push(presence(projectDir, rel, `config:${rel}`, `missing for ${language} — run kj harden`));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const expectedWf = [...WORKFLOWS];
|
|
73
|
+
const quality = qualityWorkflowFor(roots[0]?.language ?? null);
|
|
74
|
+
if (quality) expectedWf.push(quality);
|
|
75
|
+
for (const wf of expectedWf) {
|
|
76
|
+
checks.push(presence(projectDir, join(WORKFLOWS_DIR, wf.file), `workflow:${wf.file}`, "missing — run kj harden"));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
81
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config-engine — seeds/refreshes lint/format/commit config for `kj harden`
|
|
3
|
+
* (KJC-TSK-0556). Seed-if-absent: a config the user already authored (no
|
|
4
|
+
* kj:managed marker) is left untouched and reported as "skipped". Markered
|
|
5
|
+
* files refresh in place; JSON files (no comment syntax) are seed-only.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
|
|
11
|
+
import { upsertManagedBlock } from "../utils/managed-markers.js";
|
|
12
|
+
import { findEquivalentIn } from "./advisory.js";
|
|
13
|
+
import { artifactIdForConfig, findAlternative } from "./alternatives.js";
|
|
14
|
+
import { CONFIGS_BY_LANGUAGE, UNIVERSAL_CONFIGS } from "./config-templates.js";
|
|
15
|
+
|
|
16
|
+
const BLOCK_VERSION = 1;
|
|
17
|
+
|
|
18
|
+
// KJC-BUG-0137 (issue #1357): is the tool a config demands actually installed?
|
|
19
|
+
// Declared dependency or a resolvable bin both count; an unreadable
|
|
20
|
+
// package.json is no evidence of the tool.
|
|
21
|
+
function hasTool(dirs, tool) {
|
|
22
|
+
for (const d of dirs) {
|
|
23
|
+
if (existsSync(join(d, "node_modules", ".bin", tool))) return true;
|
|
24
|
+
const pkgPath = join(d, "package.json");
|
|
25
|
+
if (!existsSync(pkgPath)) continue;
|
|
26
|
+
try {
|
|
27
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
28
|
+
if (pkg.devDependencies?.[tool] || pkg.dependencies?.[tool]) return true;
|
|
29
|
+
} catch {
|
|
30
|
+
/* unreadable ⇒ keep looking */
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function writeFile(target, content) {
|
|
37
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
38
|
+
writeFileSync(target, content);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Seed a list of config entries into `targetDir`, labelling each by `prefix`.
|
|
43
|
+
* `altDirs` widens the cross-tool alternative lookup (KJC-TSK-0614): a config
|
|
44
|
+
* covered by the user's own tool (biome.json ⇒ eslint+prettier) is never
|
|
45
|
+
* seeded — kj must not install a second, conflicting linter/formatter.
|
|
46
|
+
*/
|
|
47
|
+
function seedInto(targetDir, configs, dryRun, prefix, results, altDirs = [targetDir]) {
|
|
48
|
+
for (const cfg of configs) {
|
|
49
|
+
const target = join(targetDir, cfg.file);
|
|
50
|
+
const exists = existsSync(target);
|
|
51
|
+
const file = prefix === "." ? cfg.file : join(prefix, cfg.file);
|
|
52
|
+
|
|
53
|
+
if (!exists) {
|
|
54
|
+
const alt = findAlternative(altDirs, artifactIdForConfig(cfg));
|
|
55
|
+
if (alt) {
|
|
56
|
+
results.push({ file, action: "covered", by: alt.foundAt });
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// KJC-BUG-0119: a same-tool variant (eslint.config.mjs, .eslintrc.json,
|
|
60
|
+
// package.json#eslintConfig…) means the config already EXISTS — seeding
|
|
61
|
+
// kj's default filename next to it would silently ECLIPSE the user's
|
|
62
|
+
// real config (eslint resolves eslint.config.js before .mjs).
|
|
63
|
+
const variant = findEquivalentIn(altDirs, artifactIdForConfig(cfg));
|
|
64
|
+
if (variant) {
|
|
65
|
+
results.push({ file, action: "covered", by: variant });
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// KJC-BUG-0137: no tool ⇒ no config — report the actionable gap instead.
|
|
69
|
+
if (cfg.requires && !hasTool(altDirs, cfg.requires)) {
|
|
70
|
+
results.push({
|
|
71
|
+
file,
|
|
72
|
+
action: "omitted",
|
|
73
|
+
note: `${cfg.requires} is not installed — npm i -D ${cfg.requires}, then re-run kj harden`,
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (cfg.json) {
|
|
80
|
+
const action = exists ? "skipped" : "inserted";
|
|
81
|
+
if (!dryRun && !exists) writeFile(target, `${cfg.body}\n`);
|
|
82
|
+
results.push({ file, action });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const source = exists ? readFileSync(target, "utf8") : "";
|
|
87
|
+
if (exists && !source.includes(`kj:managed:${cfg.blockId}`)) {
|
|
88
|
+
results.push({ file, action: "skipped" });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const { content, action } = upsertManagedBlock({
|
|
92
|
+
source,
|
|
93
|
+
blockId: cfg.blockId,
|
|
94
|
+
version: BLOCK_VERSION,
|
|
95
|
+
body: cfg.body,
|
|
96
|
+
style: cfg.style,
|
|
97
|
+
});
|
|
98
|
+
if (!dryRun && action !== "unchanged") writeFile(target, content);
|
|
99
|
+
results.push({ file, action });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Single-directory seed: universal (.editorconfig, commitlint) plus the
|
|
105
|
+
* language's lint/format config, all at `projectDir`. Unknown language ⇒
|
|
106
|
+
* universal only. `dryRun` computes actions without writing.
|
|
107
|
+
*/
|
|
108
|
+
export function installConfigs({ projectDir = process.cwd(), language = "javascript", dryRun = false } = {}) {
|
|
109
|
+
const results = [];
|
|
110
|
+
seedInto(projectDir, [...UNIVERSAL_CONFIGS, ...(CONFIGS_BY_LANGUAGE[language] ?? [])], dryRun, ".", results);
|
|
111
|
+
return { dryRun, configs: results };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Monorepo seed: universal config once at the root, then each language's
|
|
116
|
+
* lint/format config inside its own root (from detectStackRoots). A fullstack
|
|
117
|
+
* repo gets eslint in frontend/, ruff in backend/, etc.
|
|
118
|
+
*/
|
|
119
|
+
export function installConfigsForRoots({ projectDir = process.cwd(), roots = [], dryRun = false } = {}) {
|
|
120
|
+
const results = [];
|
|
121
|
+
seedInto(projectDir, UNIVERSAL_CONFIGS, dryRun, ".", results);
|
|
122
|
+
for (const { dir, language } of roots) {
|
|
123
|
+
const rootDir = join(projectDir, dir);
|
|
124
|
+
// A root-level biome.json covers every language root of the monorepo.
|
|
125
|
+
seedInto(rootDir, CONFIGS_BY_LANGUAGE[language] ?? [], dryRun, dir, results, [rootDir, projectDir]);
|
|
126
|
+
}
|
|
127
|
+
return { dryRun, configs: results };
|
|
128
|
+
}
|