@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,40 @@
|
|
|
1
|
+
# Logical clocks & happened-before (Lamport, 1978)
|
|
2
|
+
|
|
3
|
+
## Problem signature
|
|
4
|
+
|
|
5
|
+
Events across processes/services need a consistent order, and wall clocks
|
|
6
|
+
disagree: "last write wins" flip-flops between replicas, audit logs interleave
|
|
7
|
+
impossibly, a consumer sees an update before the create it depends on.
|
|
8
|
+
|
|
9
|
+
## Reach for it when
|
|
10
|
+
|
|
11
|
+
- Two or more writers/emitters produce events whose CAUSAL order matters
|
|
12
|
+
(A caused B must never be observed as B before A).
|
|
13
|
+
- You are tempted to "fix it with NTP" or timestamps — clock skew makes
|
|
14
|
+
timestamp ordering a race, not an order.
|
|
15
|
+
- You need to detect concurrent (conflicting) updates, not just order them:
|
|
16
|
+
that is the vector-clock extension (one counter per node; compare
|
|
17
|
+
component-wise; incomparable = concurrent — see Dynamo's usage).
|
|
18
|
+
|
|
19
|
+
## Do NOT reach for it when
|
|
20
|
+
|
|
21
|
+
- A single writer (or a single database) already totally orders the events —
|
|
22
|
+
its sequence/autoincrement/WAL IS the clock. Most CRUD apps live here.
|
|
23
|
+
- You only need "roughly recent first" for humans (feeds, logs for reading):
|
|
24
|
+
wall-clock timestamps are fine and far simpler.
|
|
25
|
+
- You need a TOTAL order agreed by all nodes: logical clocks give a partial
|
|
26
|
+
causal order; total order needs consensus (Raft/Paxos) or a sequencer.
|
|
27
|
+
|
|
28
|
+
## Trade-offs
|
|
29
|
+
|
|
30
|
+
- Lamport clocks are one counter: tiny, but they cannot tell concurrent from
|
|
31
|
+
ordered. Vector clocks can, at O(nodes) metadata per event that must be
|
|
32
|
+
carried, stored and pruned.
|
|
33
|
+
- Causality tracking pushes conflict RESOLUTION to the reader (semantic
|
|
34
|
+
merge, CRDTs, or ask-the-user) — ordering was the easy half.
|
|
35
|
+
|
|
36
|
+
## Canonical source
|
|
37
|
+
|
|
38
|
+
Leslie Lamport, "Time, Clocks, and the Ordering of Events in a Distributed
|
|
39
|
+
System", Communications of the ACM, 1978. The happened-before relation and
|
|
40
|
+
the clock condition come verbatim from it.
|
package/package.json
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@karajan-family/code",
|
|
3
|
+
"version": "4.21.0",
|
|
4
|
+
"description": "Local multi-agent coding orchestrator with TDD, SonarQube, and code review pipeline",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "AGPL-3.0",
|
|
7
|
+
"author": "manufosela (https://github.com/manufosela)",
|
|
8
|
+
"homepage": "https://github.com/manufosela/karajan-code#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/manufosela/karajan-code.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/manufosela/karajan-code/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ai",
|
|
18
|
+
"orchestrator",
|
|
19
|
+
"code-review",
|
|
20
|
+
"tdd",
|
|
21
|
+
"sonarqube",
|
|
22
|
+
"multi-agent",
|
|
23
|
+
"mcp",
|
|
24
|
+
"cli"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22.12.0"
|
|
28
|
+
},
|
|
29
|
+
"kj": {
|
|
30
|
+
"harden": {
|
|
31
|
+
"exclude": [
|
|
32
|
+
"wrappers",
|
|
33
|
+
"packages"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"workspaces": [
|
|
38
|
+
"packages/ai-trash",
|
|
39
|
+
"packages/core",
|
|
40
|
+
"packages/hu-board",
|
|
41
|
+
"packages/governance"
|
|
42
|
+
],
|
|
43
|
+
"imports": {
|
|
44
|
+
"#utils/*": "./src/utils/*",
|
|
45
|
+
"#session/*": "./src/session/*",
|
|
46
|
+
"#hu/*": "./src/hu/*",
|
|
47
|
+
"#skills/*": "./src/skills/*"
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"src/",
|
|
51
|
+
"bin/",
|
|
52
|
+
"library/",
|
|
53
|
+
"templates/",
|
|
54
|
+
"scripts/",
|
|
55
|
+
"vendor/tree-sitter-grammars/",
|
|
56
|
+
"packages/hu-board/src/",
|
|
57
|
+
"packages/hu-board/public/",
|
|
58
|
+
"packages/hu-board/package.json",
|
|
59
|
+
"packages/ai-trash/src/",
|
|
60
|
+
"packages/ai-trash/bin/",
|
|
61
|
+
"packages/ai-trash/package.json",
|
|
62
|
+
"docs/karajan-code-logo-small.png",
|
|
63
|
+
"docs/README.es.md",
|
|
64
|
+
"LICENSE",
|
|
65
|
+
"README.md"
|
|
66
|
+
],
|
|
67
|
+
"bin": {
|
|
68
|
+
"kj": "bin/kj.js",
|
|
69
|
+
"kj-tail": "bin/kj-tail",
|
|
70
|
+
"kj-trash": "packages/ai-trash/bin/kj-trash.js",
|
|
71
|
+
"karajan-mcp": "bin/karajan-mcp.js",
|
|
72
|
+
"kj-rag-mcp": "bin/kj-rag-mcp.js"
|
|
73
|
+
},
|
|
74
|
+
"scripts": {
|
|
75
|
+
"postinstall": "node scripts/postinstall.js",
|
|
76
|
+
"setup": "node scripts/install.js",
|
|
77
|
+
"test": "vitest run",
|
|
78
|
+
"test:watch": "vitest",
|
|
79
|
+
"test:coverage": "vitest run --coverage",
|
|
80
|
+
"lint": "eslint src/ packages/",
|
|
81
|
+
"lint:fix": "eslint src/ packages/ --fix",
|
|
82
|
+
"lint:syntax": "((command -v rg >/dev/null 2>&1 && rg --files src tests | rg '\\.js$') || find src tests -type f -name '*.js') | while IFS= read -r f; do node --check \"$f\"; done",
|
|
83
|
+
"format:check": "prettier --check .",
|
|
84
|
+
"format:fix": "prettier --write .",
|
|
85
|
+
"typecheck": "npx -p typescript@5 tsc --noEmit -p tsconfig.json",
|
|
86
|
+
"validate": "npm run lint && npm run lint:syntax && npm test",
|
|
87
|
+
"mcp": "node src/mcp/server.js",
|
|
88
|
+
"audit:test-diet": "node scripts/audit-test-diet.mjs",
|
|
89
|
+
"verify-pack": "node scripts/verify-pack.mjs",
|
|
90
|
+
"prepublishOnly": "node scripts/verify-pack.mjs",
|
|
91
|
+
"verify-quickstart": "node scripts/quickstart-gate.mjs"
|
|
92
|
+
},
|
|
93
|
+
"simple-git-hooks": {
|
|
94
|
+
"pre-commit": "npx lint-staged"
|
|
95
|
+
},
|
|
96
|
+
"lint-staged": {
|
|
97
|
+
"src/**/*.js": "eslint --max-warnings=999"
|
|
98
|
+
},
|
|
99
|
+
"overrides": {
|
|
100
|
+
"fast-uri": "^3.1.2"
|
|
101
|
+
},
|
|
102
|
+
"dependencies": {
|
|
103
|
+
"@babel/parser": "^7.29.7",
|
|
104
|
+
"@karajan-family/governance": "^0.1.0",
|
|
105
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
106
|
+
"better-sqlite3": "^12.10.0",
|
|
107
|
+
"chokidar": "^5.0.0",
|
|
108
|
+
"commander": "^15.0.0",
|
|
109
|
+
"execa": "^9.6.0",
|
|
110
|
+
"express": "^5.1.0",
|
|
111
|
+
"express-rate-limit": "^8.5.0",
|
|
112
|
+
"helmet": "^8.1.0",
|
|
113
|
+
"js-yaml": "^4.2.0",
|
|
114
|
+
"karajan-core": "^1.4.0",
|
|
115
|
+
"karajan-rag": "^1.2.0",
|
|
116
|
+
"knip": "^6.15.0",
|
|
117
|
+
"madge": "^8.0.0",
|
|
118
|
+
"sqlite-vec": "^0.1.9",
|
|
119
|
+
"valibot": "^1.4.1",
|
|
120
|
+
"web-tree-sitter": "^0.26.9",
|
|
121
|
+
"yaml": "^2.9.0"
|
|
122
|
+
},
|
|
123
|
+
"devDependencies": {
|
|
124
|
+
"@eslint/js": "^10.0.1",
|
|
125
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
126
|
+
"esbuild": "^0.28.0",
|
|
127
|
+
"eslint": "^10.4.1",
|
|
128
|
+
"eslint-plugin-import-x": "^4.16.2",
|
|
129
|
+
"eslint-plugin-security": "^4.0.0",
|
|
130
|
+
"globals": "^17.6.0",
|
|
131
|
+
"lint-staged": "^17.0.7",
|
|
132
|
+
"postject": "^1.0.0-alpha.6",
|
|
133
|
+
"prettier": "^3.4.2",
|
|
134
|
+
"simple-git-hooks": "^2.13.1",
|
|
135
|
+
"vitest": "^4.1.8"
|
|
136
|
+
},
|
|
137
|
+
"publishConfig": {
|
|
138
|
+
"access": "public"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @karajan/ai-trash
|
|
2
|
+
|
|
3
|
+
AI-proof trash: intercept destructive operations from an AI-driven shell
|
|
4
|
+
(`rm -rf`, truncation via `>`, `mv` overwriting, destructive `git` ops, …)
|
|
5
|
+
and snapshot the affected paths to a bin the AI cannot reach before the
|
|
6
|
+
command runs.
|
|
7
|
+
|
|
8
|
+
Status: **skeleton** — see Karajan epic `KJC-PCS-0041` and the planning
|
|
9
|
+
documents in `docs/ai-trash-fase1-report.md` + `docs/ai-trash-fase2-plan.md`
|
|
10
|
+
of the parent repo.
|
|
11
|
+
|
|
12
|
+
The MVP is being built incrementally (KJC-TSK-0388). This package currently
|
|
13
|
+
only ships the skeleton (`package.json`, `bin/kj-trash.js`, `src/index.js`).
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
Any flow where an LLM has shell access (Claude Code hooks, Cursor, Aider,
|
|
18
|
+
OpenCode, Devin, custom LangChain/AutoGen agents …) shares one risk: the
|
|
19
|
+
agent can issue `rm -rf` on a critical path and there is no undo. `ai-trash`
|
|
20
|
+
puts a snapshot in a place the agent cannot read, write, or `empty` —
|
|
21
|
+
restore is one command away; emptying the bin requires a human factor
|
|
22
|
+
(sudo / TTY / external token).
|
|
23
|
+
|
|
24
|
+
## Roadmap
|
|
25
|
+
|
|
26
|
+
1. Manifest + trash-store (ULID, TTL, LRU quota) — KJC-TSK-0388 commit 2
|
|
27
|
+
2. Logger append-only + permissions (linux + macOS) — commit 3
|
|
28
|
+
3. File / directory snapshotter (mv + reflink fallback cp) — commit 4
|
|
29
|
+
4. CLI `list / inspect / restore` + `bin/kj-trash` — commit 5
|
|
30
|
+
5. CLI `empty / purge` with TTY guard — commit 6
|
|
31
|
+
6. Destructive git ops (`reset --hard`, `push --force`, …) — KJC-TSK-0389
|
|
32
|
+
7. Claude Code `PreToolUse` adapter — KJC-TSK-0390
|
|
33
|
+
8. Wiring into `kj doctor` — KJC-TSK-0391
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
AGPL-3.0 — same as Karajan Code.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@karajan/ai-trash",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "AI-proof trash: intercept destructive ops from an AI shell and snapshot them to an unreachable bin before execution",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "AGPL-3.0",
|
|
7
|
+
"author": "Manu Fosela",
|
|
8
|
+
"homepage": "https://github.com/manufosela/karajan-code/tree/main/packages/ai-trash#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/manufosela/karajan-code.git",
|
|
12
|
+
"directory": "packages/ai-trash"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/manufosela/karajan-code/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"ai",
|
|
19
|
+
"trash",
|
|
20
|
+
"undo",
|
|
21
|
+
"snapshot",
|
|
22
|
+
"safety",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"cursor",
|
|
25
|
+
"aider"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22.12.0"
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"kj-trash": "bin/kj-trash.js"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"bin/",
|
|
35
|
+
"src/",
|
|
36
|
+
"README.md",
|
|
37
|
+
"CHANGELOG.md"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"test": "vitest run"
|
|
41
|
+
},
|
|
42
|
+
"private": false
|
|
43
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import {
|
|
5
|
+
loadManifest,
|
|
6
|
+
saveManifest,
|
|
7
|
+
removeEntry,
|
|
8
|
+
expireBefore,
|
|
9
|
+
enforceLruQuota,
|
|
10
|
+
} from "./manifest.js";
|
|
11
|
+
import { restoreSnapshot, purgeSnapshot } from "./snapshot.js";
|
|
12
|
+
import { restoreGitBundle } from "./git-snapshot.js";
|
|
13
|
+
import { ensureSecureDir, assertOwnedByCurrentUser } from "./permissions.js";
|
|
14
|
+
import { runHook } from "./hook.js";
|
|
15
|
+
import { installClaudeCodeHook, DEFAULT_SETTINGS_PATH } from "./install.js";
|
|
16
|
+
|
|
17
|
+
export const DEFAULT_ROOT = process.env.AI_TRASH_ROOT || join(homedir(), ".ai-trash");
|
|
18
|
+
|
|
19
|
+
function parseFlags(argv) {
|
|
20
|
+
const flags = {};
|
|
21
|
+
const positional = [];
|
|
22
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
23
|
+
const tok = argv[i];
|
|
24
|
+
if (tok.startsWith("--")) {
|
|
25
|
+
const eq = tok.indexOf("=");
|
|
26
|
+
if (eq > -1) flags[tok.slice(2, eq)] = tok.slice(eq + 1);
|
|
27
|
+
else if (argv[i + 1] && !argv[i + 1].startsWith("--")) flags[tok.slice(2)] = argv[++i];
|
|
28
|
+
else flags[tok.slice(2)] = true;
|
|
29
|
+
} else positional.push(tok);
|
|
30
|
+
}
|
|
31
|
+
return { positional, flags };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function shortAge(ms) {
|
|
35
|
+
const sec = Math.max(1, Math.floor(ms / 1000));
|
|
36
|
+
if (sec < 60) return `${sec}s`;
|
|
37
|
+
if (sec < 3600) return `${Math.floor(sec / 60)}m`;
|
|
38
|
+
if (sec < 86400) return `${Math.floor(sec / 3600)}h`;
|
|
39
|
+
return `${Math.floor(sec / 86400)}d`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function ensureRoot(root) {
|
|
43
|
+
await ensureSecureDir(root);
|
|
44
|
+
await assertOwnedByCurrentUser(root);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function cmdList(root, out) {
|
|
48
|
+
await ensureRoot(root);
|
|
49
|
+
const m = await loadManifest(root);
|
|
50
|
+
if (!m.entries.length) {
|
|
51
|
+
out.write("kj-trash: (empty)\n");
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
out.write("ID AGE BYTES SOURCE\n");
|
|
56
|
+
for (const e of m.entries) {
|
|
57
|
+
out.write(
|
|
58
|
+
`${e.id} ${shortAge(now - e.createdAt).padStart(4)} ${String(e.sizeBytes).padStart(5)} ${e.sourcePath}\n`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function cmdInspect(root, id, out, err) {
|
|
65
|
+
await ensureRoot(root);
|
|
66
|
+
const m = await loadManifest(root);
|
|
67
|
+
const entry = m.entries.find((e) => e.id === id);
|
|
68
|
+
if (!entry) {
|
|
69
|
+
err.write(`kj-trash: no entry with id ${id}\n`);
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
out.write(`${JSON.stringify(entry, null, 2)}\n`);
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function cmdRestore(root, id, target, out, err) {
|
|
77
|
+
await ensureRoot(root);
|
|
78
|
+
const m = await loadManifest(root);
|
|
79
|
+
const entry = m.entries.find((e) => e.id === id);
|
|
80
|
+
if (!entry) {
|
|
81
|
+
err.write(`kj-trash: no entry with id ${id}\n`);
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
if (entry.type === "git-bundle") {
|
|
85
|
+
const res = await restoreGitBundle(root, entry, target);
|
|
86
|
+
out.write(
|
|
87
|
+
`kj-trash: fetched bundle ${id} -> ${res.repo} (${res.refCount} ref(s) at refs/remotes/restore/*)\n`
|
|
88
|
+
);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
const written = await restoreSnapshot(root, entry, target);
|
|
92
|
+
removeEntry(m, id);
|
|
93
|
+
await saveManifest(root, m);
|
|
94
|
+
out.write(`kj-trash: restored ${id} -> ${written}\n`);
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function cmdPurge(root, id, out, err) {
|
|
99
|
+
await ensureRoot(root);
|
|
100
|
+
const m = await loadManifest(root);
|
|
101
|
+
const entry = m.entries.find((e) => e.id === id);
|
|
102
|
+
if (!entry) {
|
|
103
|
+
err.write(`kj-trash: no entry with id ${id}\n`);
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
await purgeSnapshot(root, entry);
|
|
107
|
+
removeEntry(m, id);
|
|
108
|
+
await saveManifest(root, m);
|
|
109
|
+
out.write(`kj-trash: purged ${id}\n`);
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function cmdEmpty(root, flags, out) {
|
|
114
|
+
await ensureRoot(root);
|
|
115
|
+
const m = await loadManifest(root);
|
|
116
|
+
// Assigned in every branch below (the trailing else covers the default),
|
|
117
|
+
// so an initializer would never be read (no-useless-assignment).
|
|
118
|
+
let dropped;
|
|
119
|
+
if (flags["older-than-days"]) {
|
|
120
|
+
const days = Number(flags["older-than-days"]);
|
|
121
|
+
if (!Number.isFinite(days) || days < 0) throw new Error("--older-than-days must be >= 0");
|
|
122
|
+
dropped = expireBefore(m, Date.now() - days * 86400 * 1000);
|
|
123
|
+
} else if (flags["max-bytes"]) {
|
|
124
|
+
const max = Number(flags["max-bytes"]);
|
|
125
|
+
if (!Number.isFinite(max) || max < 0) throw new Error("--max-bytes must be >= 0");
|
|
126
|
+
dropped = enforceLruQuota(m, max);
|
|
127
|
+
} else {
|
|
128
|
+
dropped = m.entries.slice();
|
|
129
|
+
m.entries = [];
|
|
130
|
+
}
|
|
131
|
+
for (const e of dropped) await purgeSnapshot(root, e);
|
|
132
|
+
await saveManifest(root, m);
|
|
133
|
+
out.write(`kj-trash: emptied ${dropped.length} snapshot(s)\n`);
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const HELP =
|
|
138
|
+
"usage: kj-trash <command> [args]\n" +
|
|
139
|
+
" list list snapshots oldest-first\n" +
|
|
140
|
+
" inspect <id> print snapshot metadata as JSON\n" +
|
|
141
|
+
" restore <id> [--to PATH] restore a snapshot (default: original path)\n" +
|
|
142
|
+
" purge <id> delete a single snapshot\n" +
|
|
143
|
+
" empty [--older-than-days N | --max-bytes N]\n" +
|
|
144
|
+
" drop all (or filtered) snapshots\n" +
|
|
145
|
+
" hook Claude Code PreToolUse handler (reads JSON\n" +
|
|
146
|
+
" on stdin, writes decision JSON on stdout)\n" +
|
|
147
|
+
" install --claude-code patch ~/.claude/settings.json (idempotent)\n" +
|
|
148
|
+
"env: AI_TRASH_ROOT (default ~/.ai-trash)\n";
|
|
149
|
+
|
|
150
|
+
export async function runCli(argv, io = {}) {
|
|
151
|
+
const out = io.out ?? process.stdout;
|
|
152
|
+
const err = io.err ?? process.stderr;
|
|
153
|
+
const root = io.root ?? DEFAULT_ROOT;
|
|
154
|
+
const { positional, flags } = parseFlags(argv);
|
|
155
|
+
const [cmd, arg1] = positional;
|
|
156
|
+
switch (cmd) {
|
|
157
|
+
case "list":
|
|
158
|
+
return cmdList(root, out);
|
|
159
|
+
case "inspect":
|
|
160
|
+
if (!arg1) {
|
|
161
|
+
err.write("kj-trash: inspect requires <id>\n");
|
|
162
|
+
return 2;
|
|
163
|
+
}
|
|
164
|
+
return cmdInspect(root, arg1, out, err);
|
|
165
|
+
case "restore":
|
|
166
|
+
if (!arg1) {
|
|
167
|
+
err.write("kj-trash: restore requires <id>\n");
|
|
168
|
+
return 2;
|
|
169
|
+
}
|
|
170
|
+
return cmdRestore(root, arg1, typeof flags.to === "string" ? flags.to : undefined, out, err);
|
|
171
|
+
case "purge":
|
|
172
|
+
if (!arg1) {
|
|
173
|
+
err.write("kj-trash: purge requires <id>\n");
|
|
174
|
+
return 2;
|
|
175
|
+
}
|
|
176
|
+
return cmdPurge(root, arg1, out, err);
|
|
177
|
+
case "empty":
|
|
178
|
+
return cmdEmpty(root, flags, out);
|
|
179
|
+
case "hook":
|
|
180
|
+
return runHook({ root, stdin: io.stdin ?? process.stdin, stdout: out });
|
|
181
|
+
case "install": {
|
|
182
|
+
if (!flags["claude-code"]) {
|
|
183
|
+
err.write("kj-trash: install requires --claude-code\n");
|
|
184
|
+
return 2;
|
|
185
|
+
}
|
|
186
|
+
const path = typeof flags.settings === "string" ? flags.settings : DEFAULT_SETTINGS_PATH;
|
|
187
|
+
const res = await installClaudeCodeHook({ path });
|
|
188
|
+
out.write(`kj-trash: ${res.mutated ? "installed" : "already installed"} -> ${res.path}\n`);
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
case "help":
|
|
192
|
+
case "--help":
|
|
193
|
+
case undefined:
|
|
194
|
+
out.write(HELP);
|
|
195
|
+
return cmd ? 0 : 2;
|
|
196
|
+
default:
|
|
197
|
+
err.write(`kj-trash: unknown command '${cmd}'\n${HELP}`);
|
|
198
|
+
return 2;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Pure classifier for shell commands the PreToolUse hook receives.
|
|
2
|
+
// Recognises the destructive ops ai-trash will snapshot before they run.
|
|
3
|
+
// Conservative: anything unknown is left as safe, but every destructive
|
|
4
|
+
// shape returns kind+paths so the hook can snapshot before letting it run.
|
|
5
|
+
|
|
6
|
+
function tokenise(command) {
|
|
7
|
+
if (Array.isArray(command)) return command.filter((t) => typeof t === "string");
|
|
8
|
+
if (typeof command !== "string") return [];
|
|
9
|
+
const out = [];
|
|
10
|
+
let buf = "", quote = null;
|
|
11
|
+
for (const ch of command) {
|
|
12
|
+
if (quote) { if (ch === quote) quote = null; else buf += ch; continue; }
|
|
13
|
+
if (ch === "'" || ch === '"') { quote = ch; continue; }
|
|
14
|
+
if (ch === " " || ch === "\t" || ch === "\n") { if (buf) { out.push(buf); buf = ""; } continue; }
|
|
15
|
+
buf += ch;
|
|
16
|
+
}
|
|
17
|
+
if (buf) out.push(buf);
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function rmFlags(tokens) {
|
|
22
|
+
const flags = tokens.filter((t) => t.startsWith("-")).join("");
|
|
23
|
+
return { recursive: flags.includes("r"), force: flags.includes("f") };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function classifyRm(tokens) {
|
|
27
|
+
const positional = tokens.slice(1).filter((t) => !t.startsWith("-"));
|
|
28
|
+
const { recursive, force } = rmFlags(tokens.slice(1));
|
|
29
|
+
return { destructive: true, kind: "rm", recursive, force, paths: positional, reason: "rm removes files" };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function classifyMv(tokens) {
|
|
33
|
+
const positional = tokens.slice(1).filter((t) => !t.startsWith("-"));
|
|
34
|
+
if (positional.length < 2) return { destructive: false, kind: "mv-noop", paths: [], reason: "mv without source/target" };
|
|
35
|
+
return { destructive: true, kind: "mv-overwrite", paths: positional.slice(-1), reason: "mv may overwrite destination" };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function classifyGit(tokens) {
|
|
39
|
+
const sub = tokens[1];
|
|
40
|
+
const has = (...flags) => flags.some((f) => tokens.includes(f));
|
|
41
|
+
if (sub === "reset" && has("--hard")) return { destructive: true, kind: "git-reset-hard", paths: [], reason: "git reset --hard drops working tree" };
|
|
42
|
+
if (sub === "clean" && has("-f", "-fd", "-xdf")) return { destructive: true, kind: "git-clean", paths: [], reason: "git clean -f removes untracked files" };
|
|
43
|
+
if (sub === "branch" && has("-D")) return { destructive: true, kind: "git-branch-D", paths: [], reason: "git branch -D drops unmerged branch" };
|
|
44
|
+
if (sub === "push" && has("--force", "-f", "--force-with-lease")) return { destructive: true, kind: "git-push-force", paths: [], reason: "git push --force rewrites remote history" };
|
|
45
|
+
if (sub === "checkout" && has("--", ".")) return { destructive: true, kind: "git-checkout-discard", paths: [], reason: "git checkout -- discards local edits" };
|
|
46
|
+
return { destructive: false, kind: "git-safe", paths: [], reason: "git op without destructive flags" };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function classifyRedirect(tokens) {
|
|
50
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
51
|
+
if ((tokens[i] === ">" || tokens[i] === ">|") && tokens[i + 1]) {
|
|
52
|
+
return { destructive: true, kind: "redirect-clobber", paths: [tokens[i + 1]], reason: "> truncates the target file" };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function classifyCommand(command) {
|
|
59
|
+
const tokens = tokenise(command);
|
|
60
|
+
if (!tokens.length) return { destructive: false, kind: "empty", paths: [], reason: "no command" };
|
|
61
|
+
const redirect = classifyRedirect(tokens);
|
|
62
|
+
if (redirect) return redirect;
|
|
63
|
+
const head = tokens[0];
|
|
64
|
+
if (head === "rm") return classifyRm(tokens);
|
|
65
|
+
if (head === "truncate") {
|
|
66
|
+
const paths = tokens.slice(1).filter((t) => !t.startsWith("-") && !/^\d+$/.test(t));
|
|
67
|
+
return { destructive: true, kind: "truncate", paths, reason: "truncate rewrites file size" };
|
|
68
|
+
}
|
|
69
|
+
if (head === "mv" || head === "cp") return classifyMv(tokens);
|
|
70
|
+
if (head === "git") return classifyGit(tokens);
|
|
71
|
+
return { destructive: false, kind: "safe", paths: [], reason: `head '${head}' not in destructive list` };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { tokenise };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Git snapshot primitive (KJC-TSK-0389 commit 1). Captures the full repo
|
|
2
|
+
// state via `git bundle create --all` before a destructive git op runs so
|
|
3
|
+
// the hook can restore lost commits/refs afterwards. The bundle includes
|
|
4
|
+
// every reachable commit + every ref (branches + tags), letting
|
|
5
|
+
// `git fetch --all <bundle>` rebuild HEAD + ref tips even after a
|
|
6
|
+
// `reset --hard`, `branch -D`, or `push --force`.
|
|
7
|
+
|
|
8
|
+
import { promises as fs } from "node:fs";
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { ulid } from "./ulid.js";
|
|
12
|
+
import { ensureSecureDir, lockdownFile } from "./permissions.js";
|
|
13
|
+
import { appendLogEntry } from "./logger.js";
|
|
14
|
+
|
|
15
|
+
function runGit(args, cwd) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
18
|
+
let stdout = "";
|
|
19
|
+
let stderr = "";
|
|
20
|
+
child.stdout.on("data", (b) => (stdout += b.toString("utf8")));
|
|
21
|
+
child.stderr.on("data", (b) => (stderr += b.toString("utf8")));
|
|
22
|
+
child.on("error", reject);
|
|
23
|
+
child.on("close", (code) => resolve({ code, stdout, stderr }));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function captureRefs(repoDir) {
|
|
28
|
+
const res = await runGit(["show-ref", "--head"], repoDir);
|
|
29
|
+
if (res.code !== 0) return [];
|
|
30
|
+
return res.stdout
|
|
31
|
+
.split("\n")
|
|
32
|
+
.map((line) => line.trim())
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
.map((line) => {
|
|
35
|
+
const [sha, ref] = line.split(/\s+/, 2);
|
|
36
|
+
return { sha, ref };
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Snapshot the full state of a git repository as a `.bundle` file so the
|
|
42
|
+
* pre-op refs can be restored after a destructive op. Returns a manifest
|
|
43
|
+
* entry shaped like `snapshotFile`'s output so the manifest layer can
|
|
44
|
+
* store both file and git snapshots side by side.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} rootDir - ai-trash root (`<root>/store/<id>/repo.bundle`).
|
|
47
|
+
* @param {string} repoDir - target git repository.
|
|
48
|
+
* @param {{command?: string, origin?: string, label?: string}} [options]
|
|
49
|
+
* @returns {Promise<object>} manifest entry with type=`git-bundle`.
|
|
50
|
+
*/
|
|
51
|
+
export async function snapshotGitBundle(rootDir, repoDir, options = {}) {
|
|
52
|
+
const insideRepo = await runGit(["rev-parse", "--is-inside-work-tree"], repoDir);
|
|
53
|
+
if (insideRepo.code !== 0 || insideRepo.stdout.trim() !== "true") {
|
|
54
|
+
throw new Error(`ai-trash: not a git repo: ${repoDir}`);
|
|
55
|
+
}
|
|
56
|
+
const id = ulid();
|
|
57
|
+
const storeDir = join(rootDir, "store", id);
|
|
58
|
+
await ensureSecureDir(storeDir);
|
|
59
|
+
const bundlePath = join(storeDir, "repo.bundle");
|
|
60
|
+
const bundle = await runGit(["bundle", "create", bundlePath, "--all"], repoDir);
|
|
61
|
+
if (bundle.code !== 0) {
|
|
62
|
+
throw new Error(`ai-trash: git bundle failed: ${bundle.stderr.trim()}`);
|
|
63
|
+
}
|
|
64
|
+
await lockdownFile(bundlePath);
|
|
65
|
+
const refs = await captureRefs(repoDir);
|
|
66
|
+
const stat = await fs.stat(bundlePath);
|
|
67
|
+
const entry = {
|
|
68
|
+
id,
|
|
69
|
+
type: "git-bundle",
|
|
70
|
+
createdAt: Date.now(),
|
|
71
|
+
sourcePath: repoDir,
|
|
72
|
+
snapshotPath: bundlePath,
|
|
73
|
+
sizeBytes: stat.size,
|
|
74
|
+
refs,
|
|
75
|
+
command: options.command ?? null,
|
|
76
|
+
origin: options.origin ?? "unknown",
|
|
77
|
+
label: options.label ?? null,
|
|
78
|
+
};
|
|
79
|
+
await appendLogEntry(rootDir, "snapshot.git-bundle", {
|
|
80
|
+
id,
|
|
81
|
+
repoDir,
|
|
82
|
+
bundlePath,
|
|
83
|
+
sizeBytes: stat.size,
|
|
84
|
+
refCount: refs.length,
|
|
85
|
+
});
|
|
86
|
+
return entry;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Replay a `git-bundle` snapshot into a target repository. Runs
|
|
91
|
+
* `git fetch <bundlePath> +refs/heads/*:refs/remotes/restore/*` so the
|
|
92
|
+
* pre-op refs land under `restore/*` and can be inspected, checked out,
|
|
93
|
+
* or `reset --hard`-ed back without clobbering anything in the current
|
|
94
|
+
* working tree. Leaves the manifest entry intact so repeated fetches
|
|
95
|
+
* remain possible (the bundle is not deleted).
|
|
96
|
+
*
|
|
97
|
+
* @param {string} rootDir - ai-trash root (for audit).
|
|
98
|
+
* @param {object} entry - manifest entry with type `git-bundle`.
|
|
99
|
+
* @param {string} [targetRepo] - destination repo (default: entry.sourcePath).
|
|
100
|
+
* @returns {Promise<{ repo: string, refspec: string, refCount: number }>}
|
|
101
|
+
*/
|
|
102
|
+
export async function restoreGitBundle(rootDir, entry, targetRepo) {
|
|
103
|
+
if (entry?.type !== "git-bundle") {
|
|
104
|
+
throw new Error(`ai-trash: not a git-bundle entry: ${entry?.id ?? "<missing>"}`);
|
|
105
|
+
}
|
|
106
|
+
const repo = targetRepo ?? entry.sourcePath;
|
|
107
|
+
const insideRepo = await runGit(["rev-parse", "--is-inside-work-tree"], repo);
|
|
108
|
+
if (insideRepo.code !== 0 || insideRepo.stdout.trim() !== "true") {
|
|
109
|
+
throw new Error(`ai-trash: restore target is not a git repo: ${repo}`);
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
await fs.stat(entry.snapshotPath);
|
|
113
|
+
} catch {
|
|
114
|
+
throw new Error(`ai-trash: bundle missing on disk: ${entry.snapshotPath}`);
|
|
115
|
+
}
|
|
116
|
+
const refspec = "+refs/heads/*:refs/remotes/restore/*";
|
|
117
|
+
const fetched = await runGit(["fetch", entry.snapshotPath, refspec], repo);
|
|
118
|
+
if (fetched.code !== 0) {
|
|
119
|
+
throw new Error(`ai-trash: git fetch failed: ${fetched.stderr.trim()}`);
|
|
120
|
+
}
|
|
121
|
+
await appendLogEntry(rootDir, "snapshot.restore-bundle", {
|
|
122
|
+
id: entry.id,
|
|
123
|
+
bundlePath: entry.snapshotPath,
|
|
124
|
+
targetRepo: repo,
|
|
125
|
+
refCount: entry.refs?.length ?? 0,
|
|
126
|
+
});
|
|
127
|
+
return { repo, refspec, refCount: entry.refs?.length ?? 0 };
|
|
128
|
+
}
|