@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,271 @@
|
|
|
1
|
+
// KJC-TSK-0501 step 8c/8 — Story edit form (inline editor for the HU modal).
|
|
2
|
+
//
|
|
3
|
+
// Classic script (no exports). Loaded by index.html before app.js so the
|
|
4
|
+
// top-level `function` declarations are hoisted into the shared realm and
|
|
5
|
+
// `showStoryDetail` (story-detail-view.js) can call `renderStoryEditForm`
|
|
6
|
+
// when the user hits ✎ on a story.
|
|
7
|
+
//
|
|
8
|
+
// Globals consumed from earlier scripts / app.js:
|
|
9
|
+
// - esc() (app.js)
|
|
10
|
+
// - shortStoryId() (utils/formatters.js)
|
|
11
|
+
// - projectInitialsCache (mutable in app.js — assignment crosses
|
|
12
|
+
// scripts transparently)
|
|
13
|
+
// - showStoryDetail() (utils/story-detail-view.js)
|
|
14
|
+
// - showError() (utils/modals.js)
|
|
15
|
+
// - closeModal() (app.js → window.closeModal, used by
|
|
16
|
+
// inline onclick on the modal close button)
|
|
17
|
+
// - renderBoard() (app.js)
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Swap the story modal into an inline edit form. Accepts the full story
|
|
21
|
+
* record that `showStoryDetail` just rendered so we don't re-fetch —
|
|
22
|
+
* the user's intent is "edit what I'm looking at".
|
|
23
|
+
*
|
|
24
|
+
* Cancel restores the read-only view via `showStoryDetail`; Save posts a
|
|
25
|
+
* PATCH and, on success, re-opens in read-only with the refreshed row.
|
|
26
|
+
*
|
|
27
|
+
* @param {object} story
|
|
28
|
+
*/
|
|
29
|
+
function renderStoryEditForm(story) {
|
|
30
|
+
const content = document.getElementById('modal-content');
|
|
31
|
+
const initials = projectInitialsCache[story.project_id] || 'kj';
|
|
32
|
+
const shortId = shortStoryId(story, initials);
|
|
33
|
+
|
|
34
|
+
const rawAc = story.acceptance_criteria ? JSON.parse(story.acceptance_criteria) : [];
|
|
35
|
+
// Represent AC as text: plain strings go verbatim, Gherkin objects
|
|
36
|
+
// collapse to "Given … | When … | Then …". On save we parse back —
|
|
37
|
+
// if the line matches the pattern we treat it as Gherkin, otherwise
|
|
38
|
+
// a free-form string.
|
|
39
|
+
const acInitial = rawAc
|
|
40
|
+
.map((c) => typeof c === 'string' ? c : (c.given ? `Given ${c.given} | When ${c.when} | Then ${c.then}` : JSON.stringify(c)))
|
|
41
|
+
.join('\n');
|
|
42
|
+
|
|
43
|
+
// Tests-first editor: one block per test with a type selector
|
|
44
|
+
// (shell | gherkin), the content textarea, optional file path, and
|
|
45
|
+
// a remove button. Save collects them into the structured v2.7.5
|
|
46
|
+
// array shape. Plain strings in the existing data are treated as
|
|
47
|
+
// legacy shell tests and auto-upgraded to the structured form.
|
|
48
|
+
const rawTests = story.acceptance_tests ? JSON.parse(story.acceptance_tests) : [];
|
|
49
|
+
const testsInitial = rawTests.map((t) => {
|
|
50
|
+
if (typeof t === 'string') return { type: 'shell', content: t, file: '' };
|
|
51
|
+
if (t && typeof t === 'object') {
|
|
52
|
+
return {
|
|
53
|
+
type: t.type === 'gherkin' ? 'gherkin' : 'shell',
|
|
54
|
+
content: typeof t.content === 'string' ? t.content : JSON.stringify(t),
|
|
55
|
+
file: typeof t.file === 'string' ? t.file : '',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { type: 'shell', content: String(t ?? ''), file: '' };
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const scopeInitial = story.original_text || story.certified_want || '';
|
|
62
|
+
const TASK_TYPES = ['sw', 'infra', 'doc', 'add-tests', 'refactor'];
|
|
63
|
+
|
|
64
|
+
content.innerHTML = `
|
|
65
|
+
<div class="modal__header">
|
|
66
|
+
<div>
|
|
67
|
+
<div class="modal__title" title="${esc(story.id)}">${esc(shortId)} <span style="font-size:0.75rem;color:var(--text-muted);font-weight:normal">(editing)</span></div>
|
|
68
|
+
<div class="modal__subtitle" style="font-size:0.75rem;color:var(--text-muted);font-family:monospace;margin-top:2px">${esc(story.id)}</div>
|
|
69
|
+
</div>
|
|
70
|
+
<button class="modal__close" onclick="closeModal()">×</button>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<form id="hu-edit-form" onsubmit="return false" style="display:flex;flex-direction:column;gap:14px;padding:8px 0">
|
|
74
|
+
<label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem">
|
|
75
|
+
<span style="color:var(--text-muted)">Title</span>
|
|
76
|
+
<input type="text" id="edit-title" value="${esc(story.title || '')}"
|
|
77
|
+
style="padding:8px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text);border-radius:var(--radius-sm);font-size:0.95rem"
|
|
78
|
+
maxlength="200" required>
|
|
79
|
+
</label>
|
|
80
|
+
|
|
81
|
+
<label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem">
|
|
82
|
+
<span style="color:var(--text-muted)">Scope</span>
|
|
83
|
+
<textarea id="edit-scope" rows="4"
|
|
84
|
+
style="padding:8px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text);border-radius:var(--radius-sm);font-family:inherit;font-size:0.9rem;line-height:1.5;resize:vertical">${esc(scopeInitial)}</textarea>
|
|
85
|
+
</label>
|
|
86
|
+
|
|
87
|
+
<label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem">
|
|
88
|
+
<span style="color:var(--text-muted)">Task type</span>
|
|
89
|
+
<select id="edit-task-type"
|
|
90
|
+
style="padding:8px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text);border-radius:var(--radius-sm);font-size:0.9rem">
|
|
91
|
+
${TASK_TYPES.map((t) => `<option value="${t}">${t}</option>`).join('')}
|
|
92
|
+
</select>
|
|
93
|
+
</label>
|
|
94
|
+
|
|
95
|
+
<label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem">
|
|
96
|
+
<span style="color:var(--text-muted)">
|
|
97
|
+
Acceptance criteria — one per line.
|
|
98
|
+
Gherkin: <code>Given X | When Y | Then Z</code>
|
|
99
|
+
</span>
|
|
100
|
+
<textarea id="edit-ac" rows="6"
|
|
101
|
+
style="padding:8px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text);border-radius:var(--radius-sm);font-family:var(--font-mono, monospace);font-size:0.85rem;line-height:1.5;resize:vertical">${esc(acInitial)}</textarea>
|
|
102
|
+
</label>
|
|
103
|
+
|
|
104
|
+
<fieldset style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 12px;margin:0">
|
|
105
|
+
<legend style="color:var(--text-muted);font-size:0.85rem;padding:0 6px">
|
|
106
|
+
Acceptance tests — the contract the coder must satisfy
|
|
107
|
+
</legend>
|
|
108
|
+
<div id="edit-tests-list" style="display:flex;flex-direction:column;gap:8px"></div>
|
|
109
|
+
<button type="button" id="edit-tests-add"
|
|
110
|
+
style="margin-top:8px;padding:4px 10px;font-size:0.8rem;background:var(--bg-primary);color:var(--text);border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer">
|
|
111
|
+
+ Add test
|
|
112
|
+
</button>
|
|
113
|
+
</fieldset>
|
|
114
|
+
|
|
115
|
+
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px;padding-top:12px;border-top:1px solid var(--border)">
|
|
116
|
+
<button type="button" id="edit-cancel" class="control-btn"
|
|
117
|
+
style="padding:6px 14px;border:1px solid var(--border);background:var(--bg-primary);color:var(--text);border-radius:var(--radius-sm);cursor:pointer">
|
|
118
|
+
Cancel
|
|
119
|
+
</button>
|
|
120
|
+
<button type="button" id="edit-save" class="control-btn"
|
|
121
|
+
style="padding:6px 14px;border:none;background:var(--color-green);color:#fff;border-radius:var(--radius-sm);cursor:pointer;font-weight:600">
|
|
122
|
+
Save
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
</form>
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
// Tests editor state + renderers. Kept on the function scope (not
|
|
129
|
+
// on window) so reopening the form gives a fresh copy.
|
|
130
|
+
let testRows = [...testsInitial];
|
|
131
|
+
const listEl = document.getElementById('edit-tests-list');
|
|
132
|
+
|
|
133
|
+
function renderTestsEditor() {
|
|
134
|
+
listEl.innerHTML = testRows.map((t, i) => `
|
|
135
|
+
<div class="edit-test-row" data-idx="${i}"
|
|
136
|
+
style="display:grid;grid-template-columns:auto 1fr auto;gap:6px;align-items:start;padding:8px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-primary)">
|
|
137
|
+
<select data-field="type"
|
|
138
|
+
style="padding:4px 6px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text);border-radius:var(--radius-sm);font-size:0.8rem">
|
|
139
|
+
<option value="shell"${t.type === 'shell' ? ' selected' : ''}>shell</option>
|
|
140
|
+
<option value="gherkin"${t.type === 'gherkin' ? ' selected' : ''}>gherkin</option>
|
|
141
|
+
</select>
|
|
142
|
+
<div style="display:flex;flex-direction:column;gap:4px">
|
|
143
|
+
<textarea data-field="content" rows="${t.type === 'gherkin' ? 3 : 2}"
|
|
144
|
+
placeholder="${t.type === 'gherkin' ? 'Given …\\nWhen …\\nThen …' : 'npx vitest run test/foo.test.js'}"
|
|
145
|
+
style="padding:6px 8px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text);border-radius:var(--radius-sm);font-family:var(--font-mono, monospace);font-size:0.82rem;line-height:1.4;resize:vertical">${esc(t.content)}</textarea>
|
|
146
|
+
<input type="text" data-field="file" placeholder="Optional: file path (e.g. tests/login.test.ts)" value="${esc(t.file || '')}"
|
|
147
|
+
style="padding:4px 8px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text);border-radius:var(--radius-sm);font-family:var(--font-mono, monospace);font-size:0.75rem">
|
|
148
|
+
</div>
|
|
149
|
+
<button type="button" data-field="remove" title="Remove this test"
|
|
150
|
+
style="align-self:start;padding:4px 8px;background:transparent;border:1px solid var(--border);color:var(--text-muted);border-radius:var(--radius-sm);cursor:pointer;font-size:0.8rem">✕</button>
|
|
151
|
+
</div>
|
|
152
|
+
`).join('');
|
|
153
|
+
|
|
154
|
+
// Bind the edit events. We re-bind on every render — cheap, and
|
|
155
|
+
// keeps the state → DOM mapping simple.
|
|
156
|
+
listEl.querySelectorAll('.edit-test-row').forEach((row) => {
|
|
157
|
+
const idx = Number(row.dataset.idx);
|
|
158
|
+
row.querySelector('[data-field="type"]').addEventListener('change', (e) => {
|
|
159
|
+
testRows[idx].type = e.target.value;
|
|
160
|
+
renderTestsEditor(); // re-render so placeholder updates
|
|
161
|
+
});
|
|
162
|
+
row.querySelector('[data-field="content"]').addEventListener('input', (e) => {
|
|
163
|
+
testRows[idx].content = e.target.value;
|
|
164
|
+
});
|
|
165
|
+
row.querySelector('[data-field="file"]').addEventListener('input', (e) => {
|
|
166
|
+
testRows[idx].file = e.target.value;
|
|
167
|
+
});
|
|
168
|
+
row.querySelector('[data-field="remove"]').addEventListener('click', () => {
|
|
169
|
+
testRows.splice(idx, 1);
|
|
170
|
+
renderTestsEditor();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
renderTestsEditor();
|
|
176
|
+
|
|
177
|
+
document.getElementById('edit-tests-add').addEventListener('click', () => {
|
|
178
|
+
testRows.push({ type: 'shell', content: '', file: '' });
|
|
179
|
+
renderTestsEditor();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
document.getElementById('edit-cancel').addEventListener('click', () => showStoryDetail(story.id));
|
|
183
|
+
document.getElementById('edit-save').addEventListener('click', () => saveStoryEdits(story, () => testRows));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Collect the form values, compute the diff against the story as it
|
|
188
|
+
* was on modal open, and PATCH only the changed fields. Empty diff is
|
|
189
|
+
* a no-op (close out of edit mode without an API call).
|
|
190
|
+
* @param {object} story
|
|
191
|
+
*/
|
|
192
|
+
async function saveStoryEdits(story, getTestRows) {
|
|
193
|
+
const title = document.getElementById('edit-title').value.trim();
|
|
194
|
+
const scope = document.getElementById('edit-scope').value;
|
|
195
|
+
const taskType = document.getElementById('edit-task-type').value;
|
|
196
|
+
const acRaw = document.getElementById('edit-ac').value;
|
|
197
|
+
|
|
198
|
+
if (!title) {
|
|
199
|
+
await showError('Title cannot be empty.', { title: 'Invalid input' });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Parse AC: one per line; a line with "Given X | When Y | Then Z"
|
|
204
|
+
// (case-insensitive) becomes Gherkin; everything else stays a string.
|
|
205
|
+
// Blank lines are dropped.
|
|
206
|
+
const acceptance_criteria = acRaw.split('\n')
|
|
207
|
+
.map((line) => line.trim())
|
|
208
|
+
.filter(Boolean)
|
|
209
|
+
.map((line) => {
|
|
210
|
+
const m = /^Given\s+(.*?)\s*\|\s*When\s+(.*?)\s*\|\s*Then\s+(.*)$/i.exec(line);
|
|
211
|
+
if (m) return { given: m[1].trim(), when: m[2].trim(), then: m[3].trim() };
|
|
212
|
+
return line;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Collect the structured acceptance_tests from the editor state,
|
|
216
|
+
// dropping rows the user left empty.
|
|
217
|
+
const testRows = typeof getTestRows === 'function' ? getTestRows() : [];
|
|
218
|
+
const acceptance_tests = testRows
|
|
219
|
+
.map((t) => ({
|
|
220
|
+
type: t.type === 'gherkin' ? 'gherkin' : 'shell',
|
|
221
|
+
content: (t.content || '').trim(),
|
|
222
|
+
file: (t.file || '').trim(),
|
|
223
|
+
}))
|
|
224
|
+
.filter((t) => t.content.length > 0)
|
|
225
|
+
.map((t) => (t.file ? t : { type: t.type, content: t.content }));
|
|
226
|
+
|
|
227
|
+
// Diff against the original so we don't send untouched fields and
|
|
228
|
+
// let the server's COALESCE keep existing values.
|
|
229
|
+
const patch = {};
|
|
230
|
+
const originalScope = story.original_text || story.certified_want || '';
|
|
231
|
+
if (title !== (story.title || '')) patch.title = title;
|
|
232
|
+
if (scope !== originalScope) patch.scope = scope;
|
|
233
|
+
if (taskType && taskType !== 'sw') patch.task_type = taskType;
|
|
234
|
+
const prevAcStr = story.acceptance_criteria || '[]';
|
|
235
|
+
const nextAcStr = JSON.stringify(acceptance_criteria);
|
|
236
|
+
if (nextAcStr !== prevAcStr) patch.acceptance_criteria = acceptance_criteria;
|
|
237
|
+
const prevTestsStr = story.acceptance_tests || '[]';
|
|
238
|
+
const nextTestsStr = JSON.stringify(acceptance_tests);
|
|
239
|
+
if (nextTestsStr !== prevTestsStr) patch.acceptance_tests = acceptance_tests;
|
|
240
|
+
|
|
241
|
+
if (Object.keys(patch).length === 0) {
|
|
242
|
+
showStoryDetail(story.id);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
const res = await fetch(`/api/stories/${encodeURIComponent(story.id)}`, {
|
|
248
|
+
method: 'PATCH',
|
|
249
|
+
headers: { 'Content-Type': 'application/json' },
|
|
250
|
+
body: JSON.stringify(patch),
|
|
251
|
+
});
|
|
252
|
+
if (!res.ok) {
|
|
253
|
+
if (res.status === 404) {
|
|
254
|
+
await showError(
|
|
255
|
+
'The board server does not recognise the extended PATCH endpoint.\n\n'
|
|
256
|
+
+ 'Restart it to pick up v2.7.5+:\n\n'
|
|
257
|
+
+ ' kj board stop && kj board start',
|
|
258
|
+
{ title: 'Board out of date' }
|
|
259
|
+
);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const msg = (await res.json().catch(() => ({}))).error || `HTTP ${res.status}`;
|
|
263
|
+
await showError(msg, { title: 'Could not save HU' });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
await renderBoard(); // card shows new title/counts
|
|
267
|
+
await showStoryDetail(story.id); // reopen modal with fresh data
|
|
268
|
+
} catch (err) {
|
|
269
|
+
await showError(err.message, { title: 'Could not save HU' });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Karajan Wiki · QMD semantic search</title>
|
|
7
|
+
<link rel="stylesheet" href="/styles.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header class="header">
|
|
11
|
+
<div class="header__logo"><div><div class="header__title">Karajan Wiki</div><div class="header__subtitle">QMD semantic search</div></div></div>
|
|
12
|
+
<nav class="header__nav">
|
|
13
|
+
<a class="nav-btn" href="/">← Board</a>
|
|
14
|
+
<a class="nav-btn" href="/rag.html">RAG</a>
|
|
15
|
+
<button class="nav-btn active">Wiki</button>
|
|
16
|
+
</nav>
|
|
17
|
+
</header>
|
|
18
|
+
<main class="main" id="wiki-app">
|
|
19
|
+
<section class="wiki-search">
|
|
20
|
+
<form id="wiki-form" class="wiki-form" autocomplete="off">
|
|
21
|
+
<input id="wiki-q" type="search" placeholder="Search the project wiki…" aria-label="Query" required>
|
|
22
|
+
<input id="wiki-collection" type="text" placeholder="collection (optional)" aria-label="Collection">
|
|
23
|
+
<label class="wiki-fast"><input id="wiki-fast" type="checkbox"> fast (no rerank)</label>
|
|
24
|
+
<button type="submit" class="control-btn" id="wiki-go">Search</button>
|
|
25
|
+
</form>
|
|
26
|
+
<p class="wiki-hint">QMD runs locally — 0 tokens, ~60s with rerank, ~5s in fast mode.</p>
|
|
27
|
+
</section>
|
|
28
|
+
<section id="wiki-results" class="wiki-results"></section>
|
|
29
|
+
</main>
|
|
30
|
+
<script src="/wiki.js"></script>
|
|
31
|
+
</body>
|
|
32
|
+
</html>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// KJC-TSK-0508 — HU Board Wiki panel frontend. Calls POST /api/wiki/query
|
|
2
|
+
// (server-side spawns `qmd query --format json`) and renders the hits.
|
|
3
|
+
// A 503 with `installable:true` triggers an "Install QMD" CTA instead of
|
|
4
|
+
// a generic error so the user can act on the missing binary.
|
|
5
|
+
const TOKEN = new URLSearchParams(location.search).get("token") || localStorage.getItem("kj_board_token") || "";
|
|
6
|
+
const HEADERS = { "Content-Type": "application/json", ...(TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {}) };
|
|
7
|
+
const ESC = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
|
8
|
+
|
|
9
|
+
function renderInstallCTA(root) {
|
|
10
|
+
root.innerHTML = `<div class="rag-card rag-card--error">
|
|
11
|
+
<h3>QMD not installed</h3>
|
|
12
|
+
<p>The semantic wiki engine is not available on this host. Install it once:</p>
|
|
13
|
+
<pre class="wiki-code">npm install -g @tobilu/qmd</pre>
|
|
14
|
+
<p class="rag-stat-sub">Then re-run <code>kj init</code> in this project to register the wiki collection.</p>
|
|
15
|
+
</div>`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function renderHits(root, results) {
|
|
19
|
+
if (!Array.isArray(results) || results.length === 0) {
|
|
20
|
+
root.innerHTML = '<p class="rag-empty">no hits — try a broader query or run <code>qmd update</code>.</p>';
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
root.innerHTML = results.map((r) => {
|
|
24
|
+
const src = r.source || r.path || r.file || "(unknown source)";
|
|
25
|
+
const snip = r.snippet || r.text || r.content || "";
|
|
26
|
+
const score = typeof r.score === "number" ? r.score.toFixed(3) : "";
|
|
27
|
+
return `<article class="wiki-hit">
|
|
28
|
+
<header class="wiki-hit__head"><span class="wiki-hit__src">${ESC(src)}</span>${score ? `<span class="wiki-hit__score">${ESC(score)}</span>` : ""}</header>
|
|
29
|
+
<pre class="wiki-hit__snip">${ESC(snip)}</pre>
|
|
30
|
+
</article>`;
|
|
31
|
+
}).join("");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function runSearch(ev) {
|
|
35
|
+
ev?.preventDefault();
|
|
36
|
+
const root = document.getElementById("wiki-results");
|
|
37
|
+
const query = document.getElementById("wiki-q").value.trim();
|
|
38
|
+
if (!query) return;
|
|
39
|
+
const collection = document.getElementById("wiki-collection").value.trim() || undefined;
|
|
40
|
+
const fast = document.getElementById("wiki-fast").checked;
|
|
41
|
+
root.innerHTML = '<div class="loading"><div class="loading__spinner"></div><p>Searching the wiki…</p></div>';
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch("/api/wiki/query", { method: "POST", headers: HEADERS, body: JSON.stringify({ query, collection, fast }) });
|
|
44
|
+
const data = await res.json().catch(() => ({}));
|
|
45
|
+
if (res.status === 503 && data.installable) { renderInstallCTA(root); return; }
|
|
46
|
+
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
|
47
|
+
renderHits(root, data.results);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
root.innerHTML = `<div class="rag-card rag-card--error"><strong>Error:</strong> ${ESC(err.message || String(err))}</div>`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
document.getElementById("wiki-form").addEventListener("submit", runSearch);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bearer-token authentication middleware for HU Board.
|
|
3
|
+
*
|
|
4
|
+
* Threat model — KJC-TSK-0355:
|
|
5
|
+
* The board is a localhost dev tool. The "shared dev machine" risk
|
|
6
|
+
* only kicks in when the server binds beyond loopback (e.g.
|
|
7
|
+
* `--bind 0.0.0.0` to access from another laptop on LAN). For pure
|
|
8
|
+
* loopback traffic, the OS already enforces "only the local user
|
|
9
|
+
* can connect", so a token would be UX friction with zero benefit
|
|
10
|
+
* (the browser opens `http://localhost:4000` and would stop working
|
|
11
|
+
* without manual `?token=...` injection on every link).
|
|
12
|
+
*
|
|
13
|
+
* Behaviour:
|
|
14
|
+
* - When `HU_BOARD_TOKEN` is set AND the request is from a non-
|
|
15
|
+
* loopback address → require the token in `Authorization: Bearer`
|
|
16
|
+
* header, `?token=` query param, or `kj_board_token` cookie.
|
|
17
|
+
* - When the request comes from loopback (127.0.0.1 / ::1 / ::ffff:127.0.0.1)
|
|
18
|
+
* → skip auth. The browser running on the same machine just works.
|
|
19
|
+
* - When `HU_BOARD_TOKEN` is unset → skip auth entirely. Tests and
|
|
20
|
+
* legacy callers keep working.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Loopback addresses we trust without auth, regardless of token state. */
|
|
24
|
+
const LOOPBACK_ADDRESSES = new Set([
|
|
25
|
+
"127.0.0.1",
|
|
26
|
+
"::1",
|
|
27
|
+
"::ffff:127.0.0.1",
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns an Express middleware that enforces token auth for non-
|
|
32
|
+
* loopback requests when HU_BOARD_TOKEN is defined.
|
|
33
|
+
*
|
|
34
|
+
* @returns {import('express').RequestHandler}
|
|
35
|
+
*/
|
|
36
|
+
export function authMiddleware() {
|
|
37
|
+
return (req, res, next) => {
|
|
38
|
+
const expected = process.env.HU_BOARD_TOKEN;
|
|
39
|
+
if (!expected) return next();
|
|
40
|
+
|
|
41
|
+
if (isLoopback(req)) return next();
|
|
42
|
+
|
|
43
|
+
const token = extractToken(req);
|
|
44
|
+
if (token === expected) return next();
|
|
45
|
+
|
|
46
|
+
return res.status(401).json({
|
|
47
|
+
error: "Unauthorized",
|
|
48
|
+
message:
|
|
49
|
+
"HU Board is bound to a non-loopback interface. Pass the token from " +
|
|
50
|
+
"~/.karajan/hu-board/token via Authorization: Bearer <token>, " +
|
|
51
|
+
"?token=<token>, or the kj_board_token cookie.",
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {import('express').Request} req
|
|
58
|
+
* @returns {boolean}
|
|
59
|
+
*/
|
|
60
|
+
function isLoopback(req) {
|
|
61
|
+
// Express populates req.ip from req.socket.remoteAddress unless
|
|
62
|
+
// `trust proxy` is set (we don't, by design — the board never sits
|
|
63
|
+
// behind a proxy in its supported deployment). req.ip is therefore
|
|
64
|
+
// the actual peer address.
|
|
65
|
+
const ip = req.ip || req.socket?.remoteAddress || "";
|
|
66
|
+
return LOOPBACK_ADDRESSES.has(ip);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {import('express').Request} req
|
|
71
|
+
* @returns {string|undefined}
|
|
72
|
+
*/
|
|
73
|
+
function extractToken(req) {
|
|
74
|
+
const authHeader = req.headers.authorization;
|
|
75
|
+
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
76
|
+
return authHeader.slice(7);
|
|
77
|
+
}
|
|
78
|
+
if (req.query && typeof req.query.token === "string") {
|
|
79
|
+
return req.query.token;
|
|
80
|
+
}
|
|
81
|
+
// Cookie parsing: we don't pull cookie-parser as a dep just for one
|
|
82
|
+
// header. Express stores the raw header in req.headers.cookie; the
|
|
83
|
+
// grammar is `name=value; name2=value2`. Good enough for one key.
|
|
84
|
+
const cookieHeader = req.headers.cookie;
|
|
85
|
+
if (cookieHeader) {
|
|
86
|
+
for (const part of cookieHeader.split(";")) {
|
|
87
|
+
const [name, ...rest] = part.trim().split("=");
|
|
88
|
+
if (name === "kj_board_token") return rest.join("=");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-scoped boot info (KJC-TSK-0379).
|
|
3
|
+
*
|
|
4
|
+
* BOOT_TIME is captured at module load and exposed via /api/version so
|
|
5
|
+
* the client can detect server restarts and trigger a reload. Lives in
|
|
6
|
+
* its own module to avoid the server.js ↔ routes/api.js import cycle.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
|
|
11
|
+
export const BOOT_TIME = new Date().toISOString();
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
export const PKG_VERSION = (() => {
|
|
15
|
+
try { return require('../package.json').version; } catch { return 'unknown'; }
|
|
16
|
+
})();
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cleanup zombi resources of the HU Board (KJC-TSK-0380 PR C).
|
|
3
|
+
*
|
|
4
|
+
* Detects and removes orphan / abandoned entries that survive across
|
|
5
|
+
* dogfooding sessions and would otherwise reappear on every fullScan:
|
|
6
|
+
*
|
|
7
|
+
* 1. Ephemeral projects (`tmp_*`, `test_*`, `demo_*`, `kj-test-*`,
|
|
8
|
+
* `s_*`, `plan-*`) older than `staleProjectDays` (default 7).
|
|
9
|
+
* 2. Stale prompts (no `.answer.json` and file mtime older than
|
|
10
|
+
* `stalePromptHours`, default 24).
|
|
11
|
+
* 3. Orphan session directories (`~/.karajan/sessions/<id>/`) older
|
|
12
|
+
* than `staleProjectDays` with no matching DB row.
|
|
13
|
+
*
|
|
14
|
+
* Each removal goes through the same path the API endpoints use:
|
|
15
|
+
* tombstone + fs cleanup. So nothing resurrects on the next sync.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* import { cleanupZombies } from './cleanup-zombies.js';
|
|
19
|
+
* const report = cleanupZombies({ dryRun: false });
|
|
20
|
+
*
|
|
21
|
+
* Pure module; no HTTP. Safe to call with the server down.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import {
|
|
27
|
+
getDb,
|
|
28
|
+
getKjHome,
|
|
29
|
+
addTombstone,
|
|
30
|
+
isTombstoned,
|
|
31
|
+
deleteProject,
|
|
32
|
+
deleteSession,
|
|
33
|
+
initDb,
|
|
34
|
+
closeDb,
|
|
35
|
+
} from './db.js';
|
|
36
|
+
|
|
37
|
+
// Re-export the db lifecycle helpers so callers (e.g. `kj board cleanup`)
|
|
38
|
+
// can do a single `await import('cleanup-zombies.js')` and stay within
|
|
39
|
+
// the project's dynamic-import budget. The script otherwise needs both
|
|
40
|
+
// initDb/closeDb (lifecycle) and cleanupZombies (logic).
|
|
41
|
+
export { initDb, closeDb };
|
|
42
|
+
|
|
43
|
+
const EPHEMERAL_PREFIXES = ['tmp_', 'test_', 'demo_', 'kj-test-', 's_', 'plan-'];
|
|
44
|
+
|
|
45
|
+
function isEphemeralId(id) {
|
|
46
|
+
return EPHEMERAL_PREFIXES.some((p) => id.startsWith(p));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// KJC-BUG-0059 (v2.19.3): cleanup walks all known plan roots,
|
|
50
|
+
// canonical first then legacy. Pre-fix hard-coded `~/.kj/plans/`
|
|
51
|
+
// missed every plan post-v2.19.0 → zombies never garbage-collected.
|
|
52
|
+
import { getHuBoardPlansDirs } from './db.js';
|
|
53
|
+
function plansRoots() {
|
|
54
|
+
return getHuBoardPlansDirs();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function olderThanDays(isoStr, days) {
|
|
58
|
+
if (!isoStr) return true;
|
|
59
|
+
const t = Date.parse(isoStr);
|
|
60
|
+
if (Number.isNaN(t)) return true;
|
|
61
|
+
return (Date.now() - t) > days * 24 * 60 * 60 * 1000;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function olderThanHours(mtimeMs, hours) {
|
|
65
|
+
return (Date.now() - mtimeMs) > hours * 60 * 60 * 1000;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {object} opts
|
|
70
|
+
* @param {boolean} [opts.dryRun=false] - Compute the report without writing.
|
|
71
|
+
* @param {number} [opts.staleProjectDays=7]
|
|
72
|
+
* @param {number} [opts.stalePromptHours=24]
|
|
73
|
+
* @returns {{ projects: string[], prompts: string[], sessions: string[], dryRun: boolean }}
|
|
74
|
+
*/
|
|
75
|
+
export function cleanupZombies(opts = {}) {
|
|
76
|
+
const dryRun = !!opts.dryRun;
|
|
77
|
+
const staleProjectDays = opts.staleProjectDays ?? 7;
|
|
78
|
+
const stalePromptHours = opts.stalePromptHours ?? 24;
|
|
79
|
+
|
|
80
|
+
const report = { projects: [], prompts: [], sessions: [], dryRun };
|
|
81
|
+
const db = getDb();
|
|
82
|
+
|
|
83
|
+
// (1) Ephemeral projects -------------------------------------------------
|
|
84
|
+
const allProjects = db.prepare('SELECT id, last_activity FROM projects').all();
|
|
85
|
+
for (const proj of allProjects) {
|
|
86
|
+
if (!isEphemeralId(proj.id)) continue;
|
|
87
|
+
if (!olderThanDays(proj.last_activity, staleProjectDays)) continue;
|
|
88
|
+
report.projects.push(proj.id);
|
|
89
|
+
if (dryRun) continue;
|
|
90
|
+
|
|
91
|
+
const storyIds = db.prepare('SELECT id FROM stories WHERE project_id = ?').all(proj.id).map((r) => r.id);
|
|
92
|
+
const sessionIds = db.prepare('SELECT id FROM sessions WHERE project_id = ?').all(proj.id).map((r) => r.id);
|
|
93
|
+
const fsPaths = [
|
|
94
|
+
...storyIds.map((sid) => path.join(getKjHome(), 'hu-stories', sid)),
|
|
95
|
+
...sessionIds.map((sid) => path.join(getKjHome(), 'sessions', sid)),
|
|
96
|
+
...plansRoots().map((root) => path.join(root, proj.id)),
|
|
97
|
+
];
|
|
98
|
+
deleteProject(proj.id);
|
|
99
|
+
addTombstone('project', proj.id, { source: 'auto-cleanup', fsPaths });
|
|
100
|
+
for (const sid of storyIds) addTombstone('story', sid, { source: 'auto-cleanup' });
|
|
101
|
+
for (const sid of sessionIds) addTombstone('session', sid, { source: 'auto-cleanup' });
|
|
102
|
+
for (const p of fsPaths) { try { fs.rmSync(p, { recursive: true, force: true }); } catch { /* */ } }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// (2) Stale prompts ------------------------------------------------------
|
|
106
|
+
const promptsDir = path.join(getKjHome(), 'prompts');
|
|
107
|
+
if (fs.existsSync(promptsDir)) {
|
|
108
|
+
for (const name of fs.readdirSync(promptsDir)) {
|
|
109
|
+
if (!name.endsWith('.json') || name.endsWith('.answer.json')) continue;
|
|
110
|
+
const promptId = name.replace(/\.json$/, '');
|
|
111
|
+
if (isTombstoned('prompt', promptId)) continue;
|
|
112
|
+
const fullPath = path.join(promptsDir, name);
|
|
113
|
+
const answerPath = path.join(promptsDir, `${promptId}.answer.json`);
|
|
114
|
+
if (fs.existsSync(answerPath)) continue; // already resolved
|
|
115
|
+
const stat = fs.statSync(fullPath);
|
|
116
|
+
if (!olderThanHours(stat.mtimeMs, stalePromptHours)) continue;
|
|
117
|
+
report.prompts.push(promptId);
|
|
118
|
+
if (dryRun) continue;
|
|
119
|
+
addTombstone('prompt', promptId, { source: 'auto-cleanup', fsPaths: [fullPath, answerPath] });
|
|
120
|
+
try { fs.unlinkSync(fullPath); } catch { /* */ }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// (3) Orphan session directories ----------------------------------------
|
|
125
|
+
const sessionsDir = path.join(getKjHome(), 'sessions');
|
|
126
|
+
if (fs.existsSync(sessionsDir)) {
|
|
127
|
+
for (const dir of fs.readdirSync(sessionsDir)) {
|
|
128
|
+
if (isTombstoned('session', dir)) continue;
|
|
129
|
+
const fullPath = path.join(sessionsDir, dir);
|
|
130
|
+
const stat = fs.statSync(fullPath);
|
|
131
|
+
if (!stat.isDirectory()) continue;
|
|
132
|
+
const inDb = db.prepare('SELECT id FROM sessions WHERE id = ?').get(dir);
|
|
133
|
+
if (inDb) continue; // active session, not orphan
|
|
134
|
+
const mtimeIso = new Date(stat.mtimeMs).toISOString();
|
|
135
|
+
if (!olderThanDays(mtimeIso, staleProjectDays)) continue;
|
|
136
|
+
report.sessions.push(dir);
|
|
137
|
+
if (dryRun) continue;
|
|
138
|
+
deleteSession(dir);
|
|
139
|
+
addTombstone('session', dir, { source: 'auto-cleanup', fsPaths: [fullPath] });
|
|
140
|
+
try { fs.rmSync(fullPath, { recursive: true, force: true }); } catch { /* */ }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return report;
|
|
145
|
+
}
|