@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,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan reviewer — third pass after `kj plan` (PR D of the v2.7.5
|
|
3
|
+
* tests-first roadmap). Asks the planner LLM five concrete
|
|
4
|
+
* questions about the plan it just produced. Returns structured
|
|
5
|
+
* findings, NOT free-form prose.
|
|
6
|
+
*
|
|
7
|
+
* The questions are intentionally closed-ended:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Missing HUs** — sections of the SPEC the plan doesn't cover.
|
|
10
|
+
* (Catches "the planner forgot module X".)
|
|
11
|
+
* 2. **Missing dependencies** — pairs of HUs that should have a
|
|
12
|
+
* `blocked_by` edge but don't.
|
|
13
|
+
* (Catches "HU 4 needs the schema HU 2 produces".)
|
|
14
|
+
* 3. **Scope overlap** — pairs of HUs that touch the same scope.
|
|
15
|
+
* (Catches "the planner split one piece of work into two HUs
|
|
16
|
+
* that step on each other".)
|
|
17
|
+
* 4. **Parallelisable** — sets of HUs that have no inter-deps and
|
|
18
|
+
* could run side-by-side.
|
|
19
|
+
* (Surface for the user, not auto-applied — `kj run --plan`
|
|
20
|
+
* runs serially today.)
|
|
21
|
+
* 5. **Order issues** — HUs whose `blocked_by` chain is wrong or
|
|
22
|
+
* circular.
|
|
23
|
+
*
|
|
24
|
+
* The output is purely advisory. The reviewer NEVER edits the plan.
|
|
25
|
+
* It reports findings; the user decides whether to apply them via
|
|
26
|
+
* the board's edit modal or another `kj plan` pass.
|
|
27
|
+
*
|
|
28
|
+
* Free-form "could we add logging?" suggestions are explicitly out
|
|
29
|
+
* of scope to avoid scope-creep noise — see the prompt rules.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { extractFirstJson } from "../utils/json-extract.js";
|
|
33
|
+
import { withBrainRecovery } from "../brain/with-brain-recovery.js";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Build the focused review prompt. Exported for unit-testing the
|
|
37
|
+
* shape without an LLM in the loop.
|
|
38
|
+
*
|
|
39
|
+
* @param {object} args
|
|
40
|
+
* @param {string} args.task
|
|
41
|
+
* @param {Array<object>} args.hus
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
export function buildReviewerPrompt({ task, hus }) {
|
|
45
|
+
const huSummary = hus.map((h, i) => {
|
|
46
|
+
const deps = Array.isArray(h.blocked_by) && h.blocked_by.length > 0
|
|
47
|
+
? ` ← blocked_by: ${h.blocked_by.join(", ")}`
|
|
48
|
+
: "";
|
|
49
|
+
const spec = h.spec_section ? ` [§${h.spec_section}]` : "";
|
|
50
|
+
return `${i + 1}. ${h.id}${spec}: ${h.title || "(untitled)"}${deps}\n scope: ${h.scope || "(none)"}`;
|
|
51
|
+
}).join("\n\n");
|
|
52
|
+
|
|
53
|
+
return [
|
|
54
|
+
"You previously generated an implementation plan for the task below.",
|
|
55
|
+
"Your ONLY job in this call is to review it against five specific questions.",
|
|
56
|
+
"DO NOT propose new features, logging, monitoring, refactors, or anything",
|
|
57
|
+
"beyond what the SPEC explicitly requires. We're checking the plan for",
|
|
58
|
+
"completeness and correctness — NOT improving its scope.",
|
|
59
|
+
"",
|
|
60
|
+
"Output MUST be a JSON object with this exact shape:",
|
|
61
|
+
"",
|
|
62
|
+
"```json",
|
|
63
|
+
"{",
|
|
64
|
+
' "missing_hus": [',
|
|
65
|
+
' { "spec_section": "<section ref or short description>", "rationale": "<why it should exist>" }',
|
|
66
|
+
" ],",
|
|
67
|
+
' "missing_dependencies": [',
|
|
68
|
+
' { "from": "<huId that should depend on>", "on": "<huId>", "rationale": "<why>" }',
|
|
69
|
+
" ],",
|
|
70
|
+
' "scope_overlaps": [',
|
|
71
|
+
' { "between": ["<huId>", "<huId>"], "rationale": "<why they overlap>" }',
|
|
72
|
+
" ],",
|
|
73
|
+
' "parallelisable_groups": [',
|
|
74
|
+
' { "his": ["<huId>", "<huId>"], "rationale": "<why these can run side-by-side>" }',
|
|
75
|
+
" ],",
|
|
76
|
+
' "order_issues": [',
|
|
77
|
+
' { "hus": ["<huId>"], "issue": "<wrong order | circular | other>", "rationale": "<why>" }',
|
|
78
|
+
" ],",
|
|
79
|
+
' "summary": "<one sentence overall verdict>"',
|
|
80
|
+
"}",
|
|
81
|
+
"```",
|
|
82
|
+
"",
|
|
83
|
+
"Rules:",
|
|
84
|
+
"- Each array can be empty if there's nothing to flag for that question.",
|
|
85
|
+
"- Reference HUs by their EXACT id from the list below.",
|
|
86
|
+
"- Suggest at MOST 5 entries per array — only the most important ones.",
|
|
87
|
+
"- DO NOT propose tests / acceptance criteria — those are handled by other roles.",
|
|
88
|
+
"- DO NOT suggest renames / cosmetic changes — only structural issues.",
|
|
89
|
+
"- Return ONLY the JSON — no prose, no markdown fences around it.",
|
|
90
|
+
"",
|
|
91
|
+
"## Original Task",
|
|
92
|
+
task,
|
|
93
|
+
"",
|
|
94
|
+
"## Plan to review",
|
|
95
|
+
"",
|
|
96
|
+
huSummary,
|
|
97
|
+
"",
|
|
98
|
+
].join("\n");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Run the focused review pass.
|
|
103
|
+
*
|
|
104
|
+
* Pure-function side: parses the LLM JSON and normalises the shape
|
|
105
|
+
* (missing arrays default to `[]`, summary defaults to `""`). Junk
|
|
106
|
+
* entries (missing required fields) are dropped silently.
|
|
107
|
+
*
|
|
108
|
+
* @param {object} args
|
|
109
|
+
* @param {object} args.agent
|
|
110
|
+
* @param {string} args.task
|
|
111
|
+
* @param {Array<object>} args.hus
|
|
112
|
+
* @param {Function} [args.onOutput]
|
|
113
|
+
* @param {number} [args.silenceTimeoutMs]
|
|
114
|
+
* @param {number} [args.timeoutMs]
|
|
115
|
+
* @returns {Promise<{ ok: boolean, findings?: object, error?: string }>}
|
|
116
|
+
*/
|
|
117
|
+
export async function reviewPlan({ agent, task, hus, onOutput, silenceTimeoutMs, timeoutMs, emitter, eventBase, logger }) {
|
|
118
|
+
if (!Array.isArray(hus) || hus.length === 0) {
|
|
119
|
+
return { ok: true, findings: emptyFindings("Plan has no HUs to review.") };
|
|
120
|
+
}
|
|
121
|
+
const prompt = buildReviewerPrompt({ task, hus });
|
|
122
|
+
const runArgs = { prompt, role: "planner" };
|
|
123
|
+
if (onOutput) runArgs.onOutput = onOutput;
|
|
124
|
+
if (silenceTimeoutMs) runArgs.silenceTimeoutMs = silenceTimeoutMs;
|
|
125
|
+
if (timeoutMs) runArgs.timeoutMs = timeoutMs;
|
|
126
|
+
|
|
127
|
+
// KJC-TSK-0413: el caso real del 2026-05-16 (plan-fix iter 2 "failed
|
|
128
|
+
// 219.3s" sin clase) entraba aquí. Ahora Brain clasifica el fallo y
|
|
129
|
+
// decide retry/standby/hibernate/abort.
|
|
130
|
+
const result = await withBrainRecovery({
|
|
131
|
+
agent, taskArgs: runArgs, role: "plan-reviewer",
|
|
132
|
+
emitter, eventBase, logger,
|
|
133
|
+
});
|
|
134
|
+
if (!result.ok) {
|
|
135
|
+
return { ok: false, error: result.error || `reviewer agent failed (${result.recovery?.class || "unknown"})`, recovery: result.recovery, action: result.action };
|
|
136
|
+
}
|
|
137
|
+
const parsed = extractFirstJson(result.output || "");
|
|
138
|
+
if (!parsed || typeof parsed !== "object") {
|
|
139
|
+
return { ok: false, error: "reviewer returned no JSON object" };
|
|
140
|
+
}
|
|
141
|
+
return { ok: true, findings: normaliseFindings(parsed) };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function emptyFindings(summary) {
|
|
145
|
+
return {
|
|
146
|
+
missing_hus: [],
|
|
147
|
+
missing_dependencies: [],
|
|
148
|
+
scope_overlaps: [],
|
|
149
|
+
parallelisable_groups: [],
|
|
150
|
+
order_issues: [],
|
|
151
|
+
summary: summary || "",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Coerce the raw LLM payload into the canonical shape. Each list is
|
|
157
|
+
* filtered to keep only entries with the required fields populated.
|
|
158
|
+
* @param {object} raw
|
|
159
|
+
*/
|
|
160
|
+
function normaliseFindings(raw) {
|
|
161
|
+
const f = emptyFindings(typeof raw.summary === "string" ? raw.summary : "");
|
|
162
|
+
if (Array.isArray(raw.missing_hus)) {
|
|
163
|
+
f.missing_hus = raw.missing_hus
|
|
164
|
+
.filter((m) => m && (m.spec_section || m.section || m.title) && m.rationale)
|
|
165
|
+
.map((m) => ({
|
|
166
|
+
spec_section: String(m.spec_section || m.section || m.title),
|
|
167
|
+
rationale: String(m.rationale),
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
if (Array.isArray(raw.missing_dependencies)) {
|
|
171
|
+
f.missing_dependencies = raw.missing_dependencies
|
|
172
|
+
.filter((d) => d && d.from && d.on && d.rationale)
|
|
173
|
+
.map((d) => ({ from: String(d.from), on: String(d.on), rationale: String(d.rationale) }));
|
|
174
|
+
}
|
|
175
|
+
if (Array.isArray(raw.scope_overlaps)) {
|
|
176
|
+
f.scope_overlaps = raw.scope_overlaps
|
|
177
|
+
.filter((o) => o && Array.isArray(o.between) && o.between.length >= 2 && o.rationale)
|
|
178
|
+
.map((o) => ({ between: o.between.map(String), rationale: String(o.rationale) }));
|
|
179
|
+
}
|
|
180
|
+
if (Array.isArray(raw.parallelisable_groups)) {
|
|
181
|
+
f.parallelisable_groups = raw.parallelisable_groups
|
|
182
|
+
.filter((g) => g && Array.isArray(g.hus) && g.hus.length >= 2)
|
|
183
|
+
.map((g) => ({ hus: g.hus.map(String), rationale: g.rationale ? String(g.rationale) : "" }));
|
|
184
|
+
}
|
|
185
|
+
if (Array.isArray(raw.order_issues)) {
|
|
186
|
+
f.order_issues = raw.order_issues
|
|
187
|
+
.filter((o) => o && Array.isArray(o.hus) && o.hus.length >= 1 && o.issue)
|
|
188
|
+
.map((o) => ({ hus: o.hus.map(String), issue: String(o.issue), rationale: o.rationale ? String(o.rationale) : "" }));
|
|
189
|
+
}
|
|
190
|
+
return f;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Flatten findings into a count of actionable items (for the CLI
|
|
195
|
+
* banner: "Reviewer found 3 issues.").
|
|
196
|
+
*/
|
|
197
|
+
export function findingsCount(findings) {
|
|
198
|
+
if (!findings) return 0;
|
|
199
|
+
return (
|
|
200
|
+
(findings.missing_hus?.length || 0)
|
|
201
|
+
+ (findings.missing_dependencies?.length || 0)
|
|
202
|
+
+ (findings.scope_overlaps?.length || 0)
|
|
203
|
+
+ (findings.order_issues?.length || 0)
|
|
204
|
+
// parallelisable_groups is informational, not an issue.
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* KJC-BUG-0054: count findings categorised por prioridad, para que
|
|
210
|
+
* el self-fix convergence guard pueda distinguir "regresión genuina"
|
|
211
|
+
* de "regresión por arreglar algo importante".
|
|
212
|
+
*
|
|
213
|
+
* Categorías:
|
|
214
|
+
* - priority: ciclos (order_issues con issue=='circular') + missing_hus.
|
|
215
|
+
* Estos NO pueden ignorarse — un ciclo deja el plan no ejecutable;
|
|
216
|
+
* una HU faltante es feature missing.
|
|
217
|
+
* - secondary: missing_dependencies + scope_overlaps + order_issues
|
|
218
|
+
* no-circulares. Importantes pero recuperables.
|
|
219
|
+
*
|
|
220
|
+
* @param {object} findings
|
|
221
|
+
* @returns {{ priority: number, secondary: number, total: number, cycles: number }}
|
|
222
|
+
*/
|
|
223
|
+
export function findingsCountByType(findings) {
|
|
224
|
+
if (!findings) return { priority: 0, secondary: 0, total: 0, cycles: 0 };
|
|
225
|
+
const orderIssues = findings.order_issues || [];
|
|
226
|
+
const cycles = orderIssues.filter(
|
|
227
|
+
(i) => i && typeof i === "object" && i.issue === "circular"
|
|
228
|
+
).length;
|
|
229
|
+
const orderNonCircular = orderIssues.length - cycles;
|
|
230
|
+
const missingHus = findings.missing_hus?.length || 0;
|
|
231
|
+
const missingDeps = findings.missing_dependencies?.length || 0;
|
|
232
|
+
const overlaps = findings.scope_overlaps?.length || 0;
|
|
233
|
+
const priority = cycles + missingHus;
|
|
234
|
+
const secondary = missingDeps + overlaps + orderNonCircular;
|
|
235
|
+
return { priority, secondary, total: priority + secondary, cycles };
|
|
236
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan v2 schema: plans with embedded HUs.
|
|
3
|
+
* v1 = text-only plans (approach + steps + risks)
|
|
4
|
+
* v2 = plans + executable HUs with acceptance tests
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { generatePlanId } from "./plan-id.js";
|
|
8
|
+
|
|
9
|
+
const VALID_PLAN_STATUSES = new Set(["draft", "ready", "running", "done", "failed"]);
|
|
10
|
+
// KJC-TSK-0394 step 5: el modelo CANONICAL del lifecycle es
|
|
11
|
+
// {pending, running, done}. Los valores certified/coding/reviewing/
|
|
12
|
+
// failed/blocked/needs_context se mantienen como LEGACY (siguen
|
|
13
|
+
// aceptados por validatePlan para no romper plans existentes ni el
|
|
14
|
+
// runtime actual). El cleanup definitivo del runtime queda como
|
|
15
|
+
// follow-up; mientras tanto la UI puede usar canonicalStatus +
|
|
16
|
+
// effectiveResult para presentar el modelo de 3 estados sin tocar
|
|
17
|
+
// la persistencia.
|
|
18
|
+
//
|
|
19
|
+
// needs_context se PATCHeaba vía /api/stories/:id (dropdown libre del
|
|
20
|
+
// modal) sin que el enum lo aceptara → `kj plan validate` daba
|
|
21
|
+
// false-negative tras un set manual. Incluido aquí para alinear.
|
|
22
|
+
const VALID_HU_STATUSES = new Set([
|
|
23
|
+
// Canonical
|
|
24
|
+
"pending", "running", "done",
|
|
25
|
+
// Legacy
|
|
26
|
+
"certified", "coding", "reviewing", "failed", "blocked", "needs_context",
|
|
27
|
+
]);
|
|
28
|
+
export const CANONICAL_HU_STATUSES = new Set(["pending", "running", "done"]);
|
|
29
|
+
// KJC-TSK-0394: nuevo campo `result` ortogonal a status. Reemplaza la
|
|
30
|
+
// sobrecarga semántica del status (certified=pass, failed=fail) por una
|
|
31
|
+
// distinción explícita entre lifecycle (status) y último resultado (result).
|
|
32
|
+
// Vacío `null` significa "nunca ejecutado". Migración no-breaking: status
|
|
33
|
+
// legacy sigue válido durante el período de transición.
|
|
34
|
+
// Nota: usamos `result` y NO `outcome` porque el campo `outcome` ya existe
|
|
35
|
+
// con otra semántica (blob JSON con iterations/duration/commits del run).
|
|
36
|
+
export const VALID_HU_RESULTS = new Set([null, "pass", "fail", "partial"]);
|
|
37
|
+
const VALID_TASK_TYPES = new Set(["sw", "infra", "doc", "add-tests", "refactor", "nocode"]);
|
|
38
|
+
const VALID_TEST_TYPES = new Set(["shell", "gherkin"]);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* KJC-TSK-0394 step 4: deduce `result` de un HU legacy. Idempotente.
|
|
42
|
+
* Heurística: done→pass (si hubiera fallado estaría en failed),
|
|
43
|
+
* failed→fail, resto→null. NO toca status (eso es step 5).
|
|
44
|
+
*/
|
|
45
|
+
export function inferResultFromLegacyStatus(hu) {
|
|
46
|
+
if (hu && hu.result != null) return hu.result;
|
|
47
|
+
if (hu?.status === "done") return "pass";
|
|
48
|
+
if (hu?.status === "failed") return "fail";
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* KJC-TSK-0394 step 5: mapea cualquier status (legacy o canonical) al
|
|
54
|
+
* modelo CANONICAL {pending, running, done}. Permite que la UI y los
|
|
55
|
+
* agregados (counts, kanban columns) trabajen contra un enum pequeño
|
|
56
|
+
* sin tener que conocer todas las variantes legacy.
|
|
57
|
+
*
|
|
58
|
+
* certified | blocked | needs_context | pending → pending
|
|
59
|
+
* coding | reviewing | running → running
|
|
60
|
+
* done | failed → done
|
|
61
|
+
*
|
|
62
|
+
* `failed` mapea a `done` porque conceptualmente es lifecycle terminal
|
|
63
|
+
* — el "falló" lo lleva el campo `result` ortogonal (step 1). Lo mismo
|
|
64
|
+
* para `certified`: no es un lifecycle, es "ready to run" que en el
|
|
65
|
+
* modelo canónico vive como `pending` + acceptance_tests no vacío.
|
|
66
|
+
*/
|
|
67
|
+
export function canonicalStatus(status) {
|
|
68
|
+
switch (status) {
|
|
69
|
+
case "running":
|
|
70
|
+
case "coding":
|
|
71
|
+
case "reviewing":
|
|
72
|
+
return "running";
|
|
73
|
+
case "done":
|
|
74
|
+
case "failed":
|
|
75
|
+
return "done";
|
|
76
|
+
default:
|
|
77
|
+
return "pending";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* KJC-TSK-0394 step 5: result "efectivo" para la UI. Si el HU tiene
|
|
83
|
+
* `result` poblado (plans migrados), lo usa directamente. Si no
|
|
84
|
+
* (plans legacy pre-migración), lo deduce del status — equivalente a
|
|
85
|
+
* inferResultFromLegacyStatus pero llamable directamente sobre cada
|
|
86
|
+
* HU al renderizar.
|
|
87
|
+
*
|
|
88
|
+
* Permite a la UI mostrar badges ✓/✗ en plans antiguos sin esperar a
|
|
89
|
+
* que el usuario corra `kj plan migrate-result`.
|
|
90
|
+
*/
|
|
91
|
+
export function effectiveResult(hu) {
|
|
92
|
+
if (!hu) return null;
|
|
93
|
+
if (hu.result !== undefined && hu.result !== null) return hu.result;
|
|
94
|
+
return inferResultFromLegacyStatus(hu);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Map a legacy status to (newStatus, result) pair. Used by addHu() defaults
|
|
99
|
+
* and by the migration script in step 4. Idempotent: re-mapping a value that
|
|
100
|
+
* is already in the canonical {pending, running, done} set keeps it.
|
|
101
|
+
*
|
|
102
|
+
* @param {string|null|undefined} legacy
|
|
103
|
+
* @returns {{ status: string, result: string|null }}
|
|
104
|
+
*/
|
|
105
|
+
export function mapLegacyStatusToResult(legacy) {
|
|
106
|
+
switch (legacy) {
|
|
107
|
+
case "pending":
|
|
108
|
+
case "blocked": // blocked es lifecycle no terminal → pending
|
|
109
|
+
case "needs_context": // ídem
|
|
110
|
+
case undefined:
|
|
111
|
+
case null:
|
|
112
|
+
return { status: "pending", result: null };
|
|
113
|
+
case "coding":
|
|
114
|
+
case "reviewing":
|
|
115
|
+
case "running":
|
|
116
|
+
return { status: "running", result: null };
|
|
117
|
+
case "certified":
|
|
118
|
+
return { status: "done", result: "pass" };
|
|
119
|
+
case "failed":
|
|
120
|
+
// KJC-TSK-0403: HU fallida vuelve a Pending con result=fail
|
|
121
|
+
// (no a Done) para que el usuario pueda relanzarla con el
|
|
122
|
+
// contexto del fallo previo (KJC-TSK-0404).
|
|
123
|
+
return { status: "pending", result: "fail" };
|
|
124
|
+
case "done":
|
|
125
|
+
return { status: "done", result: null }; // sin result conocido (raro)
|
|
126
|
+
default:
|
|
127
|
+
return { status: "pending", result: null };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Structured acceptance-test shape introduced in v2.7.5 (tests-first):
|
|
133
|
+
*
|
|
134
|
+
* { type: "shell", content: "<bash command>", file?: "<optional path>" }
|
|
135
|
+
* { type: "gherkin", content: "Given …\nWhen …\nThen …", file?: "<optional path>" }
|
|
136
|
+
*
|
|
137
|
+
* Legacy entries (plain strings) are accepted by validatePlan and get
|
|
138
|
+
* normalised to `{ type: "shell", content: <string> }` at read time by
|
|
139
|
+
* `normaliseAcceptanceTest`. New plans MUST emit the structured form.
|
|
140
|
+
*
|
|
141
|
+
* Why two types: shell tests are executable as-is (a bash command that
|
|
142
|
+
* exits 0 on pass); gherkin tests are human-readable specs that the
|
|
143
|
+
* tester role translates into real code tests during `kj run` — they
|
|
144
|
+
* are the contract the coder is expected to satisfy.
|
|
145
|
+
*
|
|
146
|
+
* @param {unknown} entry
|
|
147
|
+
* @returns {{ type: string, content: string, file?: string } | null}
|
|
148
|
+
*/
|
|
149
|
+
export function normaliseAcceptanceTest(entry) {
|
|
150
|
+
if (entry == null) return null;
|
|
151
|
+
if (typeof entry === "string") {
|
|
152
|
+
const trimmed = entry.trim();
|
|
153
|
+
if (!trimmed) return null;
|
|
154
|
+
return { type: "shell", content: trimmed };
|
|
155
|
+
}
|
|
156
|
+
if (typeof entry === "object" && typeof entry.content === "string") {
|
|
157
|
+
const content = entry.content.trim();
|
|
158
|
+
if (!content) return null;
|
|
159
|
+
const type = VALID_TEST_TYPES.has(entry.type) ? entry.type : "shell";
|
|
160
|
+
const out = { type, content };
|
|
161
|
+
if (typeof entry.file === "string" && entry.file.trim()) out.file = entry.file.trim();
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Normalise an array of raw acceptance_tests, dropping empties.
|
|
169
|
+
* @param {unknown[]} list
|
|
170
|
+
* @returns {{ type: string, content: string, file?: string }[]}
|
|
171
|
+
*/
|
|
172
|
+
export function normaliseAcceptanceTests(list) {
|
|
173
|
+
if (!Array.isArray(list)) return [];
|
|
174
|
+
return list.map(normaliseAcceptanceTest).filter(Boolean);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Check if a plan uses v2 schema (has version field + hus array).
|
|
179
|
+
*/
|
|
180
|
+
export function isPlanV2(plan) {
|
|
181
|
+
return plan?.version === 2 && Array.isArray(plan.hus);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Create an empty v2 plan.
|
|
186
|
+
* @param {string} task - original task description
|
|
187
|
+
* @param {object} [context] - research/architect/triage context
|
|
188
|
+
* @returns {object} v2 plan with empty hus array
|
|
189
|
+
*/
|
|
190
|
+
export function createPlanV2(task, context = {}) {
|
|
191
|
+
const planId = generatePlanId();
|
|
192
|
+
return {
|
|
193
|
+
planId,
|
|
194
|
+
// KJC-TSK humanización IDs: alias legible (ej "greta-app"). Se
|
|
195
|
+
// asigna en savePlan a partir de plan.name. El planId sigue
|
|
196
|
+
// siendo la ID canónica interna (timestamp+random), pero la CLI
|
|
197
|
+
// y el board prefieren el alias para mostrar y aceptan ambos
|
|
198
|
+
// como referencia (--plan greta-app o --plan plan-XXX-XX).
|
|
199
|
+
alias: null,
|
|
200
|
+
version: 2,
|
|
201
|
+
task,
|
|
202
|
+
name: null, // set by caller via deriveProjectName or manually
|
|
203
|
+
status: "draft",
|
|
204
|
+
createdAt: new Date().toISOString(),
|
|
205
|
+
updatedAt: new Date().toISOString(),
|
|
206
|
+
createdBy: "planner",
|
|
207
|
+
context: {
|
|
208
|
+
researchContext: context.researchContext || null,
|
|
209
|
+
architectContext: context.architectContext || null,
|
|
210
|
+
triageLevel: context.triageLevel || null,
|
|
211
|
+
domain: context.domain || null
|
|
212
|
+
},
|
|
213
|
+
approach: null,
|
|
214
|
+
risks: [],
|
|
215
|
+
outOfScope: [],
|
|
216
|
+
hus: []
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Migrate a v1 plan to v2 schema. Preserves all existing data.
|
|
222
|
+
* v1 plans have no HUs — they become v2 with empty hus array.
|
|
223
|
+
* @param {object} v1plan
|
|
224
|
+
* @returns {object} v2 plan
|
|
225
|
+
*/
|
|
226
|
+
export function migratePlanV1toV2(v1plan) {
|
|
227
|
+
if (isPlanV2(v1plan)) return v1plan; // already v2
|
|
228
|
+
return {
|
|
229
|
+
planId: v1plan.planId,
|
|
230
|
+
version: 2,
|
|
231
|
+
task: v1plan.task || null,
|
|
232
|
+
name: null,
|
|
233
|
+
status: "draft",
|
|
234
|
+
createdAt: v1plan.createdAt || new Date().toISOString(),
|
|
235
|
+
updatedAt: new Date().toISOString(),
|
|
236
|
+
createdBy: "migrated",
|
|
237
|
+
context: {
|
|
238
|
+
researchContext: v1plan.researchContext || null,
|
|
239
|
+
architectContext: v1plan.architectContext || null,
|
|
240
|
+
triageLevel: null,
|
|
241
|
+
domain: null
|
|
242
|
+
},
|
|
243
|
+
approach: v1plan.plan?.approach || (typeof v1plan.plan === "string" ? v1plan.plan : null),
|
|
244
|
+
risks: v1plan.plan?.risks || [],
|
|
245
|
+
outOfScope: v1plan.plan?.outOfScope || [],
|
|
246
|
+
hus: [],
|
|
247
|
+
_v1: { raw: v1plan.raw, plan: v1plan.plan } // preserve original for reference
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Validate a v2 plan. Returns { valid: true } or { valid: false, errors: string[] }.
|
|
253
|
+
*/
|
|
254
|
+
export function validatePlan(plan) {
|
|
255
|
+
const errors = [];
|
|
256
|
+
|
|
257
|
+
if (!plan.planId) errors.push("Missing planId");
|
|
258
|
+
if (plan.version !== 2) errors.push(`Expected version 2, got ${plan.version}`);
|
|
259
|
+
if (!plan.task) errors.push("Missing task");
|
|
260
|
+
if (!VALID_PLAN_STATUSES.has(plan.status)) errors.push(`Invalid status: ${plan.status}`);
|
|
261
|
+
if (!Array.isArray(plan.hus)) errors.push("hus must be an array");
|
|
262
|
+
|
|
263
|
+
const huIds = new Set();
|
|
264
|
+
for (const hu of plan.hus || []) {
|
|
265
|
+
if (!hu.id) { errors.push("HU missing id"); continue; }
|
|
266
|
+
if (huIds.has(hu.id)) errors.push(`Duplicate HU id: ${hu.id}`);
|
|
267
|
+
huIds.add(hu.id);
|
|
268
|
+
if (!hu.title) errors.push(`HU ${hu.id}: missing title`);
|
|
269
|
+
if (!VALID_HU_STATUSES.has(hu.status)) errors.push(`HU ${hu.id}: invalid status ${hu.status}`);
|
|
270
|
+
// KJC-TSK-0394: result es opcional. Si está presente, debe ser pass/fail/partial.
|
|
271
|
+
if (hu.result !== undefined && hu.result !== null && !VALID_HU_RESULTS.has(hu.result)) {
|
|
272
|
+
errors.push(`HU ${hu.id}: invalid result ${hu.result} (expected pass|fail|partial|null)`);
|
|
273
|
+
}
|
|
274
|
+
if (hu.task_type && !VALID_TASK_TYPES.has(hu.task_type)) errors.push(`HU ${hu.id}: invalid task_type ${hu.task_type}`);
|
|
275
|
+
// acceptance_tests: may be absent (legacy) or an array of either
|
|
276
|
+
// plain strings (legacy shell) or { type, content, file? } objects.
|
|
277
|
+
if (hu.acceptance_tests !== undefined) {
|
|
278
|
+
if (!Array.isArray(hu.acceptance_tests)) {
|
|
279
|
+
errors.push(`HU ${hu.id}: acceptance_tests must be an array`);
|
|
280
|
+
} else {
|
|
281
|
+
hu.acceptance_tests.forEach((entry, idx) => {
|
|
282
|
+
if (typeof entry === "string") return; // legacy form — OK
|
|
283
|
+
if (!entry || typeof entry !== "object") {
|
|
284
|
+
errors.push(`HU ${hu.id}: acceptance_tests[${idx}] must be a string or { type, content } object`);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (entry.type && !VALID_TEST_TYPES.has(entry.type)) {
|
|
288
|
+
errors.push(`HU ${hu.id}: acceptance_tests[${idx}].type must be one of ${[...VALID_TEST_TYPES].join("|")}`);
|
|
289
|
+
}
|
|
290
|
+
if (typeof entry.content !== "string" || !entry.content.trim()) {
|
|
291
|
+
errors.push(`HU ${hu.id}: acceptance_tests[${idx}].content must be a non-empty string`);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// Check blocked_by references exist
|
|
297
|
+
for (const dep of hu.blocked_by || []) {
|
|
298
|
+
if (!huIds.has(dep) && !(plan.hus || []).some(h => h.id === dep)) {
|
|
299
|
+
errors.push(`HU ${hu.id}: blocked_by references unknown HU ${dep}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Check for circular dependencies
|
|
305
|
+
try {
|
|
306
|
+
topoSort(plan.hus || []);
|
|
307
|
+
} catch {
|
|
308
|
+
errors.push("Circular dependency detected in HU blocked_by graph");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return errors.length === 0 ? { valid: true } : { valid: false, errors };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Simple topological sort for cycle detection. */
|
|
315
|
+
function topoSort(hus) {
|
|
316
|
+
const visited = new Set();
|
|
317
|
+
const visiting = new Set();
|
|
318
|
+
const order = [];
|
|
319
|
+
const map = new Map(hus.map(h => [h.id, h]));
|
|
320
|
+
|
|
321
|
+
function visit(id) {
|
|
322
|
+
if (visited.has(id)) return;
|
|
323
|
+
if (visiting.has(id)) throw new Error("cycle");
|
|
324
|
+
visiting.add(id);
|
|
325
|
+
const hu = map.get(id);
|
|
326
|
+
for (const dep of hu?.blocked_by || []) visit(dep);
|
|
327
|
+
visiting.delete(id);
|
|
328
|
+
visited.add(id);
|
|
329
|
+
order.push(id);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
for (const hu of hus) visit(hu.id);
|
|
333
|
+
return order;
|
|
334
|
+
}
|