@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,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ephemeral project cleaner for the HU Board (board-polish #3,
|
|
3
|
+
* KJC-TSK-0371 — dogfooding 2026-05-07).
|
|
4
|
+
*
|
|
5
|
+
* Why this exists
|
|
6
|
+
* ===============
|
|
7
|
+
*
|
|
8
|
+
* Every `kj run` against a throwaway directory (`/tmp/kj-test-1`,
|
|
9
|
+
* `/tmp/demo-foo`, ad-hoc scratch projects under `tmp_*`) produces
|
|
10
|
+
* a board project that hangs around forever, even after the user
|
|
11
|
+
* has long since rm-rf'd the source tree. After 12 days of
|
|
12
|
+
* dogfooding the board ended up with 30+ "kj-test-N" projects
|
|
13
|
+
* the user couldn't tell apart from real ones — exactly the
|
|
14
|
+
* "noise on the demo" risk the user flagged on 2026-05-07.
|
|
15
|
+
*
|
|
16
|
+
* Heuristic (per the user's spec on the same session, "solución
|
|
17
|
+
* c — heurÃstica + flag visible"):
|
|
18
|
+
*
|
|
19
|
+
* 1. Project is ephemeral when:
|
|
20
|
+
* a) `is_test = 1` (user explicitly marked it test), OR
|
|
21
|
+
* b) project id matches /^(tmp_|test_|demo_|kj-test-)/i
|
|
22
|
+
* AND `is_test IS NULL` (no override).
|
|
23
|
+
* 2. AND last_activity is older than `ttlHours` (default 24h).
|
|
24
|
+
*
|
|
25
|
+
* 3. Override: when `is_test = 0` (user clicked "keep it"), the
|
|
26
|
+
* project is NEVER deleted, even if the heuristic would match.
|
|
27
|
+
*
|
|
28
|
+
* What "cleanup" means: DELETE the project row + cascade-delete its
|
|
29
|
+
* stories and sessions, ADD a project tombstone, and rm-rf the
|
|
30
|
+
* on-disk dirs (`hu-stories/<sid>/`, `sessions/<sid>/`,
|
|
31
|
+
* `~/.kj/plans/<projectId>/`). Without the tombstone + fs cleanup,
|
|
32
|
+
* the very next chokidar `add` event or `fullScan` would re-import
|
|
33
|
+
* those files as a brand-new project (KJC-BUG-0055).
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import fs from "node:fs";
|
|
37
|
+
import path from "node:path";
|
|
38
|
+
import { homedir } from "node:os";
|
|
39
|
+
import { addTombstone, getKjHome } from "./db.js";
|
|
40
|
+
|
|
41
|
+
const DEFAULT_TTL_HOURS = 24;
|
|
42
|
+
const EPHEMERAL_ID_PATTERNS = [
|
|
43
|
+
/^tmp_/i,
|
|
44
|
+
/^test_/i,
|
|
45
|
+
/^demo_/i,
|
|
46
|
+
/^kj-test-/i,
|
|
47
|
+
/^auto-tmp_/i, // also covers auto-batch projects derived from /tmp/
|
|
48
|
+
/^auto-test_/i,
|
|
49
|
+
/^auto-demo_/i,
|
|
50
|
+
// KJC-TSK-0377: stray session/plan-prefixed projects accumulate during
|
|
51
|
+
// dogfooding when a kj run is invoked without a proper projectDir.
|
|
52
|
+
// The HU Board's sync_*File handlers create a placeholder project row
|
|
53
|
+
// with `id = session_id` or `id = plan_id`, which after 24h of
|
|
54
|
+
// inactivity is the same kind of zombi the other patterns target.
|
|
55
|
+
/^s_/i,
|
|
56
|
+
/^plan-/i,
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Decide whether a single project row qualifies as an ephemeral
|
|
61
|
+
* candidate ready for deletion at `now`. Pure function — no I/O.
|
|
62
|
+
*
|
|
63
|
+
* The matrix:
|
|
64
|
+
* is_test = 1 → ephemeral (user marked it)
|
|
65
|
+
* is_test = 0 → not ephemeral, EVER (user override wins)
|
|
66
|
+
* is_test = NULL/und. → fall back to id-pattern heuristic
|
|
67
|
+
* too recent → not ephemeral (still in use)
|
|
68
|
+
*
|
|
69
|
+
* @param {{ id: string, last_activity?: string|null, first_seen?: string|null,
|
|
70
|
+
* is_test?: number|null }} project
|
|
71
|
+
* @param {{ now: number, ttlHours: number }} ctx
|
|
72
|
+
* @returns {{ ephemeral: boolean, reason?: string }}
|
|
73
|
+
*/
|
|
74
|
+
export function classifyEphemeral(project, ctx) {
|
|
75
|
+
if (!project || !project.id) return { ephemeral: false };
|
|
76
|
+
// Explicit user override always wins. is_test=0 means "keep it".
|
|
77
|
+
if (project.is_test === 0) return { ephemeral: false };
|
|
78
|
+
|
|
79
|
+
const idMatch = EPHEMERAL_ID_PATTERNS.some((re) => re.test(project.id));
|
|
80
|
+
const explicitTest = project.is_test === 1;
|
|
81
|
+
if (!idMatch && !explicitTest) return { ephemeral: false };
|
|
82
|
+
|
|
83
|
+
const refIso = project.last_activity || project.first_seen;
|
|
84
|
+
if (!refIso) return { ephemeral: false };
|
|
85
|
+
const refTs = new Date(refIso).getTime();
|
|
86
|
+
if (Number.isNaN(refTs)) return { ephemeral: false };
|
|
87
|
+
const ageHours = (ctx.now - refTs) / 3_600_000;
|
|
88
|
+
if (ageHours <= 0) return { ephemeral: false };
|
|
89
|
+
if (ageHours < ctx.ttlHours) return { ephemeral: false };
|
|
90
|
+
|
|
91
|
+
const why = explicitTest ? "is_test=1" : "id heuristic";
|
|
92
|
+
return {
|
|
93
|
+
ephemeral: true,
|
|
94
|
+
reason: `${why}, ${ageHours.toFixed(1)}h inactive (>= ${ctx.ttlHours}h)`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Filter a list of project rows down to the ephemeral ones. Pure.
|
|
100
|
+
*
|
|
101
|
+
* @param {Array<object>} projects
|
|
102
|
+
* @param {{ now?: number, ttlHours?: number }} [opts]
|
|
103
|
+
* @returns {Array<{ project: object, reason: string }>}
|
|
104
|
+
*/
|
|
105
|
+
export function findEphemeralProjects(projects, opts = {}) {
|
|
106
|
+
const ctx = {
|
|
107
|
+
now: opts.now ?? Date.now(),
|
|
108
|
+
ttlHours: opts.ttlHours ?? DEFAULT_TTL_HOURS,
|
|
109
|
+
};
|
|
110
|
+
const out = [];
|
|
111
|
+
for (const project of projects || []) {
|
|
112
|
+
const verdict = classifyEphemeral(project, ctx);
|
|
113
|
+
if (verdict.ephemeral) out.push({ project, reason: verdict.reason });
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Cascade-delete every ephemeral project at `now`. Side-effecting.
|
|
120
|
+
* Returns the list of cleaned projects so the server can log them.
|
|
121
|
+
*
|
|
122
|
+
* Idempotent: cleanup deletes the rows outright, so running twice
|
|
123
|
+
* has no effect on the second pass.
|
|
124
|
+
*
|
|
125
|
+
* @param {object} deps
|
|
126
|
+
* @param {object} deps.db better-sqlite3 Database handle
|
|
127
|
+
* @param {object} [deps.opts] threshold overrides
|
|
128
|
+
* @param {Function} [deps.now] () => number, for tests
|
|
129
|
+
* @returns {Array<{ id: string, reason: string, stories_deleted: number, sessions_deleted: number }>}
|
|
130
|
+
*/
|
|
131
|
+
export function cleanupEphemeralProjects({
|
|
132
|
+
db,
|
|
133
|
+
opts = {},
|
|
134
|
+
now = () => Date.now(),
|
|
135
|
+
// Deps are injectable so the legacy in-memory tests (which never
|
|
136
|
+
// call `initDb()`) can pass no-op stubs and still verify the
|
|
137
|
+
// cascade-delete logic. Production callers omit them and get the
|
|
138
|
+
// real persistence + fs side-effects.
|
|
139
|
+
tombstone = (type, id, o) => {
|
|
140
|
+
try { addTombstone(type, id, o); } catch { /* DB not initialised (test) */ }
|
|
141
|
+
},
|
|
142
|
+
kjHome = () => {
|
|
143
|
+
try { return getKjHome(); } catch { return null; }
|
|
144
|
+
},
|
|
145
|
+
} = {}) {
|
|
146
|
+
if (!db) return [];
|
|
147
|
+
const allProjects = db
|
|
148
|
+
.prepare("SELECT id, last_activity, first_seen, is_test FROM projects")
|
|
149
|
+
.all();
|
|
150
|
+
const candidates = findEphemeralProjects(allProjects, { ...opts, now: now() });
|
|
151
|
+
if (candidates.length === 0) return [];
|
|
152
|
+
|
|
153
|
+
const delStories = db.prepare("DELETE FROM stories WHERE project_id = ?");
|
|
154
|
+
const delSessions = db.prepare("DELETE FROM sessions WHERE project_id = ?");
|
|
155
|
+
const delProject = db.prepare("DELETE FROM projects WHERE id = ?");
|
|
156
|
+
|
|
157
|
+
const selStories = db.prepare("SELECT id FROM stories WHERE project_id = ?");
|
|
158
|
+
const selSessions = db.prepare("SELECT id FROM sessions WHERE project_id = ?");
|
|
159
|
+
const plansRoot = process.env.KJ_PLANS_DIR
|
|
160
|
+
|| path.join(homedir(), ".kj", "plans");
|
|
161
|
+
const home = kjHome();
|
|
162
|
+
const cleaned = [];
|
|
163
|
+
for (const { project, reason } of candidates) {
|
|
164
|
+
const storyIds = selStories.all(project.id).map((r) => r.id);
|
|
165
|
+
const sessionIds = selSessions.all(project.id).map((r) => r.id);
|
|
166
|
+
const fsPaths = home
|
|
167
|
+
? [
|
|
168
|
+
...storyIds.map((sid) => path.join(home, "hu-stories", sid)),
|
|
169
|
+
...sessionIds.map((sid) => path.join(home, "sessions", sid)),
|
|
170
|
+
path.join(plansRoot, project.id),
|
|
171
|
+
]
|
|
172
|
+
: [];
|
|
173
|
+
db.transaction(() => {
|
|
174
|
+
delStories.run(project.id);
|
|
175
|
+
delSessions.run(project.id);
|
|
176
|
+
delProject.run(project.id);
|
|
177
|
+
})();
|
|
178
|
+
// Tombstone the lot + best-effort rm-rf. If the fs op fails (perm
|
|
179
|
+
// error, file in use) we still keep the tombstone — that alone is
|
|
180
|
+
// enough to stop sync.js from re-importing on the next chokidar
|
|
181
|
+
// event, and `fullScan` will retry the rm-rf at next boot.
|
|
182
|
+
tombstone("project", project.id, { source: "ephemeral-cleaner", fsPaths });
|
|
183
|
+
for (const sid of storyIds) tombstone("story", sid, { source: "ephemeral-cleaner" });
|
|
184
|
+
for (const sid of sessionIds) tombstone("session", sid, { source: "ephemeral-cleaner" });
|
|
185
|
+
for (const p of fsPaths) {
|
|
186
|
+
try { fs.rmSync(p, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
187
|
+
}
|
|
188
|
+
cleaned.push({
|
|
189
|
+
id: project.id,
|
|
190
|
+
reason,
|
|
191
|
+
stories_deleted: storyIds.length,
|
|
192
|
+
sessions_deleted: sessionIds.length,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return cleaned;
|
|
196
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny pub/sub used to push sync-related events to connected SSE
|
|
3
|
+
* clients. The chokidar watcher calls `publish(event)` whenever a plan
|
|
4
|
+
* / session / batch file changes; the SSE endpoint subscribes each
|
|
5
|
+
* connected response and writes the JSON-encoded event as a message.
|
|
6
|
+
*
|
|
7
|
+
* No external dependency on purpose — `events.EventEmitter` would work
|
|
8
|
+
* but would make testing the endpoint awkward (we need deterministic
|
|
9
|
+
* subscribe/publish semantics for the integration tests in
|
|
10
|
+
* sse.test.js). The contract is:
|
|
11
|
+
*
|
|
12
|
+
* subscribe(fn) → returns an unsubscribe function
|
|
13
|
+
* publish(event) → calls every current subscriber once
|
|
14
|
+
* subscriberCount → how many listeners are live (tests only)
|
|
15
|
+
*
|
|
16
|
+
* Events are plain JSON objects. Callers pick the shape:
|
|
17
|
+
*
|
|
18
|
+
* { type: 'plan', planId, projectId } — a plan JSON was synced
|
|
19
|
+
* { type: 'session', sessionId, projectId }— a session JSON was synced
|
|
20
|
+
* { type: 'story', storyId, projectId } — a single HU row mutated
|
|
21
|
+
*
|
|
22
|
+
* The board's client treats these as hints: the usual response is to
|
|
23
|
+
* re-fetch the affected project's stories and patch the kanban in
|
|
24
|
+
* place, so no full page reload is ever required.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const subscribers = new Set();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {(event: object) => void} fn
|
|
31
|
+
* @returns {() => void} unsubscribe
|
|
32
|
+
*/
|
|
33
|
+
export function subscribe(fn) {
|
|
34
|
+
subscribers.add(fn);
|
|
35
|
+
return () => { subscribers.delete(fn); };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {object} event
|
|
40
|
+
*/
|
|
41
|
+
export function publish(event) {
|
|
42
|
+
for (const fn of subscribers) {
|
|
43
|
+
try { fn(event); } catch (err) {
|
|
44
|
+
// A broken subscriber must not take the bus down. Log and move on.
|
|
45
|
+
console.error('[event-bus] subscriber threw:', err.message);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @returns {number} */
|
|
51
|
+
export function subscriberCount() {
|
|
52
|
+
return subscribers.size;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Test helper — drops every subscriber. */
|
|
56
|
+
export function _resetForTests() {
|
|
57
|
+
subscribers.clear();
|
|
58
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure formatters used by the board UI to turn database rows into
|
|
3
|
+
* human-friendly labels. No DOM, no node APIs — easy to unit-test.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const TIME_RE_FALLBACK = "—";
|
|
7
|
+
const MAX_TASK_CHARS = 60;
|
|
8
|
+
const MAX_PROJECT_CHARS = 40;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Format an ISO 8601 timestamp as `HH:MM` (24h, local time of the
|
|
12
|
+
* server). Falls back to `—` when the input is missing or unparseable
|
|
13
|
+
* so the UI never renders `Invalid Date` or `null`.
|
|
14
|
+
*
|
|
15
|
+
* @param {string|null|undefined} iso
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
export function formatHHMM(iso) {
|
|
19
|
+
if (!iso) return TIME_RE_FALLBACK;
|
|
20
|
+
const d = new Date(iso);
|
|
21
|
+
if (Number.isNaN(d.getTime())) return TIME_RE_FALLBACK;
|
|
22
|
+
const hh = String(d.getHours()).padStart(2, "0");
|
|
23
|
+
const mm = String(d.getMinutes()).padStart(2, "0");
|
|
24
|
+
return `${hh}:${mm}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Truncate a task description to a max of N chars, collapsing internal
|
|
29
|
+
* whitespace, suffixed with "…" when truncated. Returns "" when the
|
|
30
|
+
* input is empty so the caller can decide whether to render the slot.
|
|
31
|
+
*
|
|
32
|
+
* @param {string|null|undefined} text
|
|
33
|
+
* @param {number} [max=MAX_TASK_CHARS]
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
export function shortTask(text, max = MAX_TASK_CHARS) {
|
|
37
|
+
if (!text) return "";
|
|
38
|
+
const collapsed = String(text).replace(/\s+/g, " ").trim();
|
|
39
|
+
if (collapsed.length <= max) return collapsed;
|
|
40
|
+
return collapsed.slice(0, max - 1).trimEnd() + "…";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build the label tuple used by `renderSessionCard` and
|
|
45
|
+
* `showSessionDetail`. The board used to print the session.id verbatim
|
|
46
|
+
* (e.g. `s_2026-05-07T08-35-58-010Z`) which is technically correct but
|
|
47
|
+
* impossible to scan at a glance — the user explicitly flagged this
|
|
48
|
+
* during pre-talk testing on 2026-05-07.
|
|
49
|
+
*
|
|
50
|
+
* Returned shape:
|
|
51
|
+
* - `title`: bold-line title for the card (project + time).
|
|
52
|
+
* Always non-empty; falls back to the session id when no project
|
|
53
|
+
* is known so the card is never blank.
|
|
54
|
+
* - `subtitle`: short task summary (≤ 60 chars). Empty when the
|
|
55
|
+
* session has no task field — caller hides the slot.
|
|
56
|
+
* - `idChip`: the cryptic session id, kept available for the user
|
|
57
|
+
* who needs it for `kj resume <id>` and as a tooltip on the title.
|
|
58
|
+
*
|
|
59
|
+
* @param {{ id: string, project_name?: string|null, task?: string|null,
|
|
60
|
+
* created_at?: string|null }} session
|
|
61
|
+
* @returns {{ title: string, subtitle: string, idChip: string }}
|
|
62
|
+
*/
|
|
63
|
+
/**
|
|
64
|
+
* Format a per-HU cost (USD) for the card badge. Cost F (KJC-TSK-0517).
|
|
65
|
+
*
|
|
66
|
+
* Two outputs in one call so the UI can render both at once:
|
|
67
|
+
* - `label`: 2-decimal "$X.XX" shown on the card (compact).
|
|
68
|
+
* - `tooltip`: 4-decimal "Estimated cost: $X.XXXX" shown on hover
|
|
69
|
+
* (matches the precision aggregated by Cost B/C/D, see
|
|
70
|
+
* `packages/core/src/cost/index.js`).
|
|
71
|
+
*
|
|
72
|
+
* Returns `null` for null/undefined/non-finite inputs so the caller
|
|
73
|
+
* skips the badge entirely (rendering "$0.00" for an unknown cost
|
|
74
|
+
* would be misleading — there is a real difference between "we know it
|
|
75
|
+
* was free" and "we haven't measured it yet").
|
|
76
|
+
*
|
|
77
|
+
* @param {number|null|undefined} costUsd
|
|
78
|
+
* @returns {{ label: string, tooltip: string } | null}
|
|
79
|
+
*/
|
|
80
|
+
export function formatCost(costUsd) {
|
|
81
|
+
if (costUsd === null || costUsd === undefined) return null;
|
|
82
|
+
const n = Number(costUsd);
|
|
83
|
+
if (!Number.isFinite(n)) return null;
|
|
84
|
+
return {
|
|
85
|
+
label: `$${n.toFixed(2)}`,
|
|
86
|
+
tooltip: `Estimated cost: $${n.toFixed(4)}`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Φ0-G (KJC-TSK-0525): per-HU and per-plan cached-tokens badge.
|
|
92
|
+
*
|
|
93
|
+
* Returns `{ label, tooltip }` for "🎯 23%" with a tooltip showing the
|
|
94
|
+
* exact numerator/denominator. Same "don't render 0% when there is no
|
|
95
|
+
* data" stance as formatCost — returns null when:
|
|
96
|
+
* - either arg is null/undefined/non-finite,
|
|
97
|
+
* - tokens_in is 0 (no input → ratio undefined, not zero),
|
|
98
|
+
* - cached is negative (would only happen from corrupted telemetry).
|
|
99
|
+
*
|
|
100
|
+
* The badge intentionally shows 1 decimal to keep narrow runs (e.g.
|
|
101
|
+
* a 73-token cache out of 1 200) readable without rounding to 0%.
|
|
102
|
+
*
|
|
103
|
+
* @param {number|null|undefined} cachedTokens
|
|
104
|
+
* @param {number|null|undefined} tokensIn
|
|
105
|
+
* @returns {{ label: string, tooltip: string } | null}
|
|
106
|
+
*/
|
|
107
|
+
export function formatCacheRatio(cachedTokens, tokensIn) {
|
|
108
|
+
// Reject null/undefined explicitly — Number(null) coerces to 0,
|
|
109
|
+
// which would otherwise render an honest-looking "🎯 0%" badge for
|
|
110
|
+
// runs where we simply have no telemetry. Same stance as formatCost.
|
|
111
|
+
if (cachedTokens === null || cachedTokens === undefined) return null;
|
|
112
|
+
if (tokensIn === null || tokensIn === undefined) return null;
|
|
113
|
+
const c = Number(cachedTokens);
|
|
114
|
+
const t = Number(tokensIn);
|
|
115
|
+
if (!Number.isFinite(c) || !Number.isFinite(t)) return null;
|
|
116
|
+
if (t <= 0 || c < 0) return null;
|
|
117
|
+
const pct = Math.round((c / t) * 1000) / 10;
|
|
118
|
+
const num = c.toLocaleString("en-US");
|
|
119
|
+
const den = t.toLocaleString("en-US");
|
|
120
|
+
return {
|
|
121
|
+
label: `🎯 ${pct}%`,
|
|
122
|
+
tooltip: `Cache hits: ${num} / ${den} input tokens (${pct}%)`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Format the aggregated project-level cost chip rendered in the board
|
|
128
|
+
* header. Cost G (KJC-TSK-0518). Consumes the shape returned by
|
|
129
|
+
* `GET /api/projects/:id/cost` (Cost E):
|
|
130
|
+
* { totalUsd, byPlan: [{planId, totalUsd, huCount}, …],
|
|
131
|
+
* unknownModelTokens: { tokensIn, tokensOut, models }, currency }
|
|
132
|
+
*
|
|
133
|
+
* Returns `{ label, tooltip }` where:
|
|
134
|
+
* - `label` is the compact "Total: $X.XX" shown in the header.
|
|
135
|
+
* - `tooltip` is a multi-line string with full precision, the
|
|
136
|
+
* per-plan breakdown, and a note when some tokens used a model we
|
|
137
|
+
* don't price (they aren't included in the total — surfacing the
|
|
138
|
+
* skip is more honest than silently swallowing it).
|
|
139
|
+
*
|
|
140
|
+
* Returns `null` when there is nothing to show (no input, or no
|
|
141
|
+
* measured cost yet and no plans). The chip is then hidden — same
|
|
142
|
+
* reasoning as `formatCost`: "$0.00" with no data is misleading.
|
|
143
|
+
*
|
|
144
|
+
* @param {{totalUsd:number, byPlan?:Array, unknownModelTokens?:object}|null|undefined} cost
|
|
145
|
+
* @returns {{ label: string, tooltip: string } | null}
|
|
146
|
+
*/
|
|
147
|
+
export function formatProjectCostSummary(cost) {
|
|
148
|
+
if (!cost || typeof cost !== "object") return null;
|
|
149
|
+
const total = Number(cost.totalUsd);
|
|
150
|
+
if (!Number.isFinite(total)) return null;
|
|
151
|
+
const byPlan = Array.isArray(cost.byPlan) ? cost.byPlan : [];
|
|
152
|
+
if (total === 0 && byPlan.length === 0) return null;
|
|
153
|
+
|
|
154
|
+
const lines = [`Total: $${total.toFixed(4)}`];
|
|
155
|
+
// Φ0-G (KJC-TSK-0525): when the API includes project-level cache
|
|
156
|
+
// counters, show the aggregate cached% line right under the total
|
|
157
|
+
// so users can see at a glance how much was served from cache.
|
|
158
|
+
const projCache = formatCacheRatio(cost.cachedTokens, cost.tokensIn);
|
|
159
|
+
if (projCache) lines.push(`Cache: ${projCache.tooltip.split(": ")[1]}`);
|
|
160
|
+
if (byPlan.length > 0) {
|
|
161
|
+
lines.push("By plan:");
|
|
162
|
+
for (const p of byPlan) {
|
|
163
|
+
const planTotal = Number(p?.totalUsd);
|
|
164
|
+
if (!Number.isFinite(planTotal)) continue;
|
|
165
|
+
const huCount = Number.isFinite(p?.huCount) ? p.huCount : 0;
|
|
166
|
+
const planLabel = p?.planId || "unassigned";
|
|
167
|
+
const cachePct = p?.cachedRatioPct;
|
|
168
|
+
const cacheSuffix = (cachePct !== null && cachePct !== undefined && Number.isFinite(Number(cachePct)))
|
|
169
|
+
? ` 🎯 ${cachePct}%`
|
|
170
|
+
: "";
|
|
171
|
+
lines.push(` ${planLabel}: $${planTotal.toFixed(2)} (${huCount} HU${huCount === 1 ? "" : "s"})${cacheSuffix}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const unk = cost.unknownModelTokens;
|
|
175
|
+
if (unk && Number.isFinite(unk.tokensIn) && Number.isFinite(unk.tokensOut)) {
|
|
176
|
+
const unkTotal = unk.tokensIn + unk.tokensOut;
|
|
177
|
+
if (unkTotal > 0) {
|
|
178
|
+
lines.push(`(${unkTotal} tokens with unknown pricing not included)`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
label: `Total: $${total.toFixed(2)}`,
|
|
183
|
+
tooltip: lines.join("\n"),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function formatSessionLabel(session) {
|
|
188
|
+
if (!session || !session.id) {
|
|
189
|
+
return { title: "(no session)", subtitle: "", idChip: "" };
|
|
190
|
+
}
|
|
191
|
+
const projectName = (session.project_name || "").trim();
|
|
192
|
+
const time = formatHHMM(session.created_at);
|
|
193
|
+
const projectPart = projectName
|
|
194
|
+
? projectName.length > MAX_PROJECT_CHARS
|
|
195
|
+
? projectName.slice(0, MAX_PROJECT_CHARS - 1) + "…"
|
|
196
|
+
: projectName
|
|
197
|
+
: session.id; // fall back so a card without a joined project never
|
|
198
|
+
// ends up blank — the cryptic id is better than empty
|
|
199
|
+
const title = time !== TIME_RE_FALLBACK
|
|
200
|
+
? `${projectPart} · ${time}`
|
|
201
|
+
: projectPart;
|
|
202
|
+
return {
|
|
203
|
+
title,
|
|
204
|
+
subtitle: shortTask(session.task, MAX_TASK_CHARS),
|
|
205
|
+
idChip: session.id,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HU-level zombie reaper (KJC-TSK-0394 step 3).
|
|
3
|
+
*
|
|
4
|
+
* Counterpart to zombie-reaper.js (que opera sobre sesiones). Detecta
|
|
5
|
+
* HUs que el orquestador dejó stuck en `coding`/`reviewing` —sÃntoma
|
|
6
|
+
* de crash mid-iteration— y las resetea a `pending`.
|
|
7
|
+
*
|
|
8
|
+
* Threshold separado de las sesiones: una sesión legÃtima puede
|
|
9
|
+
* tardar horas (loop largo), una HU individual rara vez >30 min. Por
|
|
10
|
+
* eso este reaper trabaja en minutos, no en horas.
|
|
11
|
+
*
|
|
12
|
+
* Semántica del reap: status → `pending` (NO `failed`: el orquestador
|
|
13
|
+
* perdió contacto, no podemos afirmar el outcome). El campo `result`
|
|
14
|
+
* NO se toca — se conserva como historial. La persistencia pasa por
|
|
15
|
+
* plan-mutations::setHuStatus (escribe el plan JSON + resync DB). Si
|
|
16
|
+
* el JSON write falla cae al fallback DB-only.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const DEFAULT_RUNNING_MINUTES = 30;
|
|
20
|
+
// `coding` + `reviewing` son los nombres legacy; `running` es el
|
|
21
|
+
// canónico tras step 5. Aceptamos los tres durante la transición.
|
|
22
|
+
const RUNNING_STATUSES = new Set(["coding", "reviewing", "running"]);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Pure. Decide si un HU row es zombi a `now`.
|
|
26
|
+
*/
|
|
27
|
+
export function classifyHuZombie(hu, ctx) {
|
|
28
|
+
if (!hu || !hu.status) return { zombie: false };
|
|
29
|
+
const status = String(hu.status).toLowerCase();
|
|
30
|
+
if (!RUNNING_STATUSES.has(status)) return { zombie: false };
|
|
31
|
+
const refIso = hu.updated_at;
|
|
32
|
+
if (!refIso) return { zombie: false };
|
|
33
|
+
const refTs = new Date(refIso).getTime();
|
|
34
|
+
if (Number.isNaN(refTs)) return { zombie: false };
|
|
35
|
+
const ageMinutes = (ctx.now - refTs) / 60_000;
|
|
36
|
+
if (ageMinutes < ctx.runningMinutes) return { zombie: false };
|
|
37
|
+
return {
|
|
38
|
+
zombie: true,
|
|
39
|
+
reason: `${status} ${ageMinutes.toFixed(0)}min inactive (>= ${ctx.runningMinutes}min)`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Pure. Filtra solo los zombis. */
|
|
44
|
+
export function findZombieHus(hus, opts = {}) {
|
|
45
|
+
const ctx = {
|
|
46
|
+
now: opts.now ?? Date.now(),
|
|
47
|
+
runningMinutes: opts.runningMinutes ?? DEFAULT_RUNNING_MINUTES,
|
|
48
|
+
};
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const hu of hus || []) {
|
|
51
|
+
const verdict = classifyHuZombie(hu, ctx);
|
|
52
|
+
if (verdict.zombie) out.push({ hu, reason: verdict.reason });
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resetea a `pending` cada HU zombi en la DB Y en el fichero del
|
|
59
|
+
* plan. `setHuStatus` se inyecta para que los tests no necesiten un
|
|
60
|
+
* plan-file real. Devuelve la lista de reapeados para el log.
|
|
61
|
+
*/
|
|
62
|
+
export function reapZombieHus({ db, setHuStatus, setHuFailResult, opts = {}, now = () => Date.now() }) {
|
|
63
|
+
if (!db || typeof setHuStatus !== "function") return [];
|
|
64
|
+
const rows = db.prepare(
|
|
65
|
+
"SELECT id, project_id, plan_id, status, updated_at FROM stories WHERE status IN ('coding', 'reviewing', 'running')",
|
|
66
|
+
).all();
|
|
67
|
+
const zombies = findZombieHus(rows, { ...opts, now: now() });
|
|
68
|
+
if (zombies.length === 0) return [];
|
|
69
|
+
const reaped = [];
|
|
70
|
+
for (const { hu, reason } of zombies) {
|
|
71
|
+
// El id compuesto del board es `${projectId}::${huId}`; setHuStatus
|
|
72
|
+
// quiere el huId local.
|
|
73
|
+
const localHuId = hu.id.includes("::") ? hu.id.split("::").slice(1).join("::") : hu.id;
|
|
74
|
+
let planPersisted = false;
|
|
75
|
+
if (hu.plan_id) {
|
|
76
|
+
try {
|
|
77
|
+
const result = setHuStatus({ planId: hu.plan_id, huId: localHuId, status: "pending", projectId: hu.project_id });
|
|
78
|
+
planPersisted = !!(result && result.ok);
|
|
79
|
+
} catch { /* swallow — never block startup */ }
|
|
80
|
+
// KJC-TSK-0404: además de resetear status=pending, marcar la HU con
|
|
81
|
+
// result=fail + blocker. El siguiente `kj run` ve el blocker y se
|
|
82
|
+
// lo inyecta al coder como "## PREVIOUS ATTEMPT FAILED" para que
|
|
83
|
+
// no repita el mismo error. No-op si la fn no se inyectó.
|
|
84
|
+
if (planPersisted && typeof setHuFailResult === "function") {
|
|
85
|
+
try {
|
|
86
|
+
setHuFailResult({
|
|
87
|
+
planId: hu.plan_id, huId: localHuId, projectId: hu.project_id,
|
|
88
|
+
blocker: `timeout: ${reason}`,
|
|
89
|
+
});
|
|
90
|
+
} catch { /* swallow — el status reset ya está hecho */ }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Fallback DB-only para legacy rows (plan_id null) o si el write al
|
|
94
|
+
// plan falló. setHuStatus ya resincroniza en el caso happy.
|
|
95
|
+
if (!planPersisted) {
|
|
96
|
+
try {
|
|
97
|
+
db.prepare("UPDATE stories SET status = 'pending', updated_at = ? WHERE id = ?").run(new Date(now()).toISOString(), hu.id);
|
|
98
|
+
} catch { /* swallow */ }
|
|
99
|
+
}
|
|
100
|
+
reaped.push({ id: hu.id, status_before: hu.status, reason, plan_persisted: planPersisted });
|
|
101
|
+
}
|
|
102
|
+
return reaped;
|
|
103
|
+
}
|