@unerr-ai/unerr 0.2.1 → 0.2.3
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/README.md +36 -45
- package/dist/cli.js +37443 -36022
- package/package.json +2 -1
- package/dist/behaviors/agent-llm-bridge.js +0 -166
- package/dist/behaviors/architecture-guard.js +0 -256
- package/dist/behaviors/auto-doc.js +0 -247
- package/dist/behaviors/cascade-guard.js +0 -289
- package/dist/behaviors/change-narrative.js +0 -270
- package/dist/behaviors/convention-drift.js +0 -290
- package/dist/behaviors/framework.js +0 -235
- package/dist/behaviors/guard-formatter.js +0 -44
- package/dist/behaviors/incomplete-work.js +0 -270
- package/dist/behaviors/loop-breaker.js +0 -300
- package/dist/behaviors/session-continuity.js +0 -208
- package/dist/commands/branches.js +0 -97
- package/dist/commands/check-commit.js +0 -225
- package/dist/commands/compress-output.js +0 -64
- package/dist/commands/config-verify.js +0 -243
- package/dist/commands/daemon.js +0 -905
- package/dist/commands/dashboard.js +0 -52
- package/dist/commands/debug.js +0 -200
- package/dist/commands/enrich.js +0 -184
- package/dist/commands/exec.js +0 -233
- package/dist/commands/gain.js +0 -156
- package/dist/commands/hook.js +0 -88
- package/dist/commands/index.js +0 -88
- package/dist/commands/init.js +0 -74
- package/dist/commands/install.js +0 -505
- package/dist/commands/learn.js +0 -116
- package/dist/commands/manifest.js +0 -193
- package/dist/commands/rewind.js +0 -103
- package/dist/commands/serve.js +0 -19
- package/dist/commands/setup-wizard.js +0 -414
- package/dist/commands/skills.js +0 -64
- package/dist/commands/stats.js +0 -20
- package/dist/commands/status.js +0 -654
- package/dist/commands/timeline.js +0 -139
- package/dist/commands/uninstall.js +0 -230
- package/dist/components/App.js +0 -109
- package/dist/components/Banner.js +0 -12
- package/dist/components/ConfirmPrompt.js +0 -25
- package/dist/components/DriftSummary.js +0 -23
- package/dist/components/GradeBadge.js +0 -15
- package/dist/components/HealthCard.js +0 -18
- package/dist/components/InkSpinner.js +0 -22
- package/dist/components/InputBox.js +0 -17
- package/dist/components/KeyValue.js +0 -13
- package/dist/components/MessageList.js +0 -14
- package/dist/components/ProgressBar.js +0 -26
- package/dist/components/Section.js +0 -16
- package/dist/components/SessionSummaryCard.js +0 -73
- package/dist/components/StartupDisplay.js +0 -24
- package/dist/components/StatusDashboard.js +0 -57
- package/dist/components/StatusLine.js +0 -8
- package/dist/components/StepLine.js +0 -22
- package/dist/components/Theme.js +0 -20
- package/dist/components/ToolProgress.js +0 -8
- package/dist/components/ViolationList.js +0 -21
- package/dist/components/render.js +0 -13
- package/dist/config/agent-registry.js +0 -237
- package/dist/config/claude-settings-hooks.js +0 -304
- package/dist/config/hook-installer.js +0 -65
- package/dist/config/instruction-writer.js +0 -388
- package/dist/config/mcp-config-writer.js +0 -266
- package/dist/config/settings.js +0 -174
- package/dist/config/tool-detector.js +0 -42
- package/dist/config/value-surfacing.js +0 -119
- package/dist/core/context-assembly.js +0 -108
- package/dist/core/conversation.js +0 -33
- package/dist/core/local-chat-provider.js +0 -475
- package/dist/core/provider-factory.js +0 -55
- package/dist/core/providers.js +0 -90
- package/dist/core/query-engine.js +0 -174
- package/dist/daemon/api.js +0 -312
- package/dist/daemon/autostart.js +0 -119
- package/dist/daemon/bootstrap.js +0 -39
- package/dist/daemon/client.js +0 -164
- package/dist/daemon/detect-ci.js +0 -81
- package/dist/daemon/platform-linux.js +0 -146
- package/dist/daemon/platform-macos.js +0 -134
- package/dist/daemon/platform-windows.js +0 -116
- package/dist/daemon/process-manager.js +0 -299
- package/dist/daemon/protocol.js +0 -23
- package/dist/daemon/registry.js +0 -270
- package/dist/daemon/settings-schema.js +0 -72
- package/dist/daemon/system-health.js +0 -134
- package/dist/daemon/version-checker.js +0 -262
- package/dist/daemon/warm-start.js +0 -223
- package/dist/entrypoints/cli.js +0 -1043
- package/dist/entrypoints/daemon.js +0 -380
- package/dist/entrypoints/repl.js +0 -147
- package/dist/hooks/adapters/claude-code.js +0 -90
- package/dist/hooks/adapters/cline.js +0 -100
- package/dist/hooks/adapters/cursor.js +0 -98
- package/dist/hooks/hook-dedup.js +0 -79
- package/dist/hooks/hook-runner.js +0 -113
- package/dist/hooks/navigation-hooks.js +0 -175
- package/dist/hooks/prompt-hooks.js +0 -63
- package/dist/hooks/shell-hooks.js +0 -47
- package/dist/ignore.js +0 -111
- package/dist/intelligence/approach-suggester.js +0 -61
- package/dist/intelligence/ast-extractor.js +0 -2615
- package/dist/intelligence/ast-worker.js +0 -34
- package/dist/intelligence/background-indexer.js +0 -121
- package/dist/intelligence/blast-radius.js +0 -200
- package/dist/intelligence/community-detection.js +0 -691
- package/dist/intelligence/community-detector.js +0 -184
- package/dist/intelligence/computation-scheduler.js +0 -75
- package/dist/intelligence/confidence-propagation.js +0 -47
- package/dist/intelligence/convention-detector.js +0 -242
- package/dist/intelligence/convention-learner.js +0 -205
- package/dist/intelligence/convention-matcher.js +0 -205
- package/dist/intelligence/cozo-schema.js +0 -376
- package/dist/intelligence/decision-point-detector.js +0 -90
- package/dist/intelligence/deep-dive-tools.js +0 -586
- package/dist/intelligence/durability-scorer.js +0 -84
- package/dist/intelligence/exploration-cost.js +0 -204
- package/dist/intelligence/exploration-pattern-tracker.js +0 -61
- package/dist/intelligence/fact-generator.js +0 -322
- package/dist/intelligence/facts-schema.js +0 -90
- package/dist/intelligence/file-intelligence.js +0 -59
- package/dist/intelligence/graph-holder.js +0 -220
- package/dist/intelligence/graph-temporal-joiner.js +0 -238
- package/dist/intelligence/health-grade.js +0 -423
- package/dist/intelligence/health-grader.js +0 -200
- package/dist/intelligence/health-map-data.js +0 -259
- package/dist/intelligence/import-symbols.js +0 -136
- package/dist/intelligence/incremental-indexer.js +0 -658
- package/dist/intelligence/indexer/centrality.js +0 -62
- package/dist/intelligence/indexer/cfg-context.js +0 -95
- package/dist/intelligence/indexer/confidence.js +0 -34
- package/dist/intelligence/indexer/cross-file-resolver.js +0 -104
- package/dist/intelligence/indexer/edge-repair.js +0 -89
- package/dist/intelligence/indexer/entity-key.js +0 -17
- package/dist/intelligence/indexer/export-map.js +0 -132
- package/dist/intelligence/indexer/git-cochange.js +0 -128
- package/dist/intelligence/indexer/graph-patch.js +0 -147
- package/dist/intelligence/indexer/incremental.js +0 -78
- package/dist/intelligence/indexer/ingest.js +0 -160
- package/dist/intelligence/indexer/language-detect.js +0 -226
- package/dist/intelligence/indexer/metadata.js +0 -63
- package/dist/intelligence/indexer/mutation-tracker.js +0 -79
- package/dist/intelligence/indexer/orchestrator.js +0 -155
- package/dist/intelligence/indexer/plugin-interface.js +0 -31
- package/dist/intelligence/indexer/plugins/csharp.js +0 -440
- package/dist/intelligence/indexer/plugins/go.js +0 -335
- package/dist/intelligence/indexer/plugins/java.js +0 -370
- package/dist/intelligence/indexer/plugins/python.js +0 -358
- package/dist/intelligence/indexer/plugins/regex-fallback.js +0 -82
- package/dist/intelligence/indexer/plugins/ruby.js +0 -290
- package/dist/intelligence/indexer/plugins/rust.js +0 -484
- package/dist/intelligence/indexer/plugins/tier2-generic.js +0 -310
- package/dist/intelligence/indexer/plugins/typescript.js +0 -456
- package/dist/intelligence/indexer/resource-monitor.js +0 -93
- package/dist/intelligence/indexer/scip/decoder.js +0 -253
- package/dist/intelligence/indexer/scip/detector.js +0 -232
- package/dist/intelligence/indexer/scip/downloader.js +0 -427
- package/dist/intelligence/indexer/scip/fallback.js +0 -34
- package/dist/intelligence/indexer/scip/merger.js +0 -109
- package/dist/intelligence/indexer/scip/orchestrator.js +0 -433
- package/dist/intelligence/indexer/scip/runner.js +0 -98
- package/dist/intelligence/indexer/snapshot.js +0 -66
- package/dist/intelligence/indexer/test-detector.js +0 -196
- package/dist/intelligence/indexer/watch-integration.js +0 -61
- package/dist/intelligence/indexer/worker.js +0 -85
- package/dist/intelligence/local-convention-detector.js +0 -437
- package/dist/intelligence/local-embeddings.js +0 -190
- package/dist/intelligence/local-graph.js +0 -1946
- package/dist/intelligence/local-indexer.js +0 -1575
- package/dist/intelligence/local-llm.js +0 -163
- package/dist/intelligence/local-rule-generator.js +0 -154
- package/dist/intelligence/local-snapshot.js +0 -213
- package/dist/intelligence/negative-knowledge.js +0 -103
- package/dist/intelligence/persistent-db.js +0 -85
- package/dist/intelligence/query-router.js +0 -2556
- package/dist/intelligence/risk-classifier.js +0 -116
- package/dist/intelligence/rule-evaluator.js +0 -380
- package/dist/intelligence/rule-generator.js +0 -49
- package/dist/intelligence/search-index.js +0 -173
- package/dist/intelligence/semantic/docstring-extractor.js +0 -67
- package/dist/intelligence/semantic/embedding-store.js +0 -52
- package/dist/intelligence/semantic/enrichment-orchestrator.js +0 -48
- package/dist/intelligence/semantic/git-message-miner.js +0 -114
- package/dist/intelligence/semantic/identifier-tokenizer.js +0 -51
- package/dist/intelligence/semantic/node2vec-embeddings.js +0 -71
- package/dist/intelligence/semantic/node2vec-walks.js +0 -103
- package/dist/intelligence/semantic/path-domain-inference.js +0 -112
- package/dist/intelligence/semantic/similarity-engine.js +0 -60
- package/dist/intelligence/semantic/tfidf-vectors.js +0 -88
- package/dist/intelligence/session-brief-builder.js +0 -159
- package/dist/intelligence/session-context.js +0 -221
- package/dist/intelligence/session-health-monitor.js +0 -211
- package/dist/intelligence/session-narrative.js +0 -197
- package/dist/intelligence/session-pattern-analyzer.js +0 -218
- package/dist/intelligence/signal-scorer.js +0 -390
- package/dist/intelligence/signal-show-store.js +0 -182
- package/dist/intelligence/smart-truncate.js +0 -158
- package/dist/intelligence/subgraph-cache.js +0 -88
- package/dist/intelligence/temporal-facts.js +0 -494
- package/dist/intelligence/token-estimator.js +0 -100
- package/dist/intelligence/tool-injector.js +0 -87
- package/dist/intelligence/tree-sitter-loader.js +0 -71
- package/dist/intelligence/worker-pool.js +0 -116
- package/dist/proxy/arg-validator.js +0 -79
- package/dist/proxy/auto-bootstrap.js +0 -167
- package/dist/proxy/bridge.js +0 -147
- package/dist/proxy/budget-enforcer.js +0 -70
- package/dist/proxy/compression-quality-monitor.js +0 -160
- package/dist/proxy/compression-stats.js +0 -51
- package/dist/proxy/context-rot-detector.js +0 -137
- package/dist/proxy/drift-detector.js +0 -139
- package/dist/proxy/efficiency-tracker.js +0 -79
- package/dist/proxy/fact-ranking.js +0 -154
- package/dist/proxy/format-encoder.js +0 -266
- package/dist/proxy/http-transport.js +0 -90
- package/dist/proxy/lifecycle-actor.js +0 -55
- package/dist/proxy/lifecycle-machine.js +0 -187
- package/dist/proxy/log-tailer.js +0 -265
- package/dist/proxy/model-pricing.js +0 -98
- package/dist/proxy/network-firewall.js +0 -141
- package/dist/proxy/nudge-state.js +0 -93
- package/dist/proxy/output-compressor.js +0 -185
- package/dist/proxy/pid-lock.js +0 -291
- package/dist/proxy/proxy-context.js +0 -11
- package/dist/proxy/proxy.js +0 -2633
- package/dist/proxy/response-enrichment.js +0 -32
- package/dist/proxy/response-envelope.js +0 -313
- package/dist/proxy/session-dedup.js +0 -82
- package/dist/proxy/session-legend.js +0 -30
- package/dist/proxy/session-persistence.js +0 -210
- package/dist/proxy/session-resume.js +0 -94
- package/dist/proxy/session-stats.js +0 -513
- package/dist/proxy/shell-classifier.js +0 -1346
- package/dist/proxy/shell-compression-log.js +0 -93
- package/dist/proxy/shell-compressor.js +0 -390
- package/dist/proxy/shell-graph-boost.js +0 -202
- package/dist/proxy/shell-monitor-map.js +0 -18
- package/dist/proxy/shell-stats.js +0 -54
- package/dist/proxy/shell-strategies/cloud.js +0 -215
- package/dist/proxy/shell-strategies/diff.js +0 -159
- package/dist/proxy/shell-strategies/error-diagnostic.js +0 -796
- package/dist/proxy/shell-strategies/filter-dsl.js +0 -358
- package/dist/proxy/shell-strategies/git-status.js +0 -177
- package/dist/proxy/shell-strategies/key-value.js +0 -193
- package/dist/proxy/shell-strategies/log-text.js +0 -154
- package/dist/proxy/shell-strategies/omni.js +0 -188
- package/dist/proxy/shell-strategies/progress.js +0 -55
- package/dist/proxy/shell-strategies/redact.js +0 -76
- package/dist/proxy/shell-strategies/structured.js +0 -241
- package/dist/proxy/shell-strategies/tabular.js +0 -243
- package/dist/proxy/shell-strategies/test-results-types.js +0 -13
- package/dist/proxy/shell-strategies/test-results.js +0 -784
- package/dist/proxy/shell-strategies/tree-paths.js +0 -144
- package/dist/proxy/shell-strategies/yaml.js +0 -182
- package/dist/proxy/shell-tee.js +0 -111
- package/dist/proxy/signal-dedup.js +0 -171
- package/dist/proxy/startup-renderer.js +0 -158
- package/dist/proxy/task-token-display.js +0 -38
- package/dist/proxy/token-counter.js +0 -61
- package/dist/proxy/tool-clusters.js +0 -273
- package/dist/proxy/tool-definitions.js +0 -525
- package/dist/proxy/transport-mux.js +0 -229
- package/dist/proxy/wire-cap.js +0 -268
- package/dist/rules/developer.mozilla.org.json +0 -9
- package/dist/rules/github.com.json +0 -21
- package/dist/schemas/api/skills.js +0 -19
- package/dist/schemas/common/errors.js +0 -7
- package/dist/schemas/common/headers.js +0 -5
- package/dist/schemas/entities/edge.js +0 -25
- package/dist/schemas/entities/entity.js +0 -22
- package/dist/schemas/entities/rule.js +0 -18
- package/dist/schemas/index.js +0 -14
- package/dist/server/event-bus.js +0 -59
- package/dist/server/http.js +0 -156
- package/dist/server/middleware.js +0 -70
- package/dist/server/routes/drift.js +0 -97
- package/dist/server/routes/intelligence.js +0 -1217
- package/dist/server/routes/reasoning-quality.js +0 -444
- package/dist/server/routes/session.js +0 -86
- package/dist/server/routes/stream.js +0 -120
- package/dist/server/routes/system.js +0 -73
- package/dist/server/routes/temporal.js +0 -170
- package/dist/server/routes/timeline.js +0 -232
- package/dist/server/routes/token-flow.js +0 -403
- package/dist/skills/effectiveness-tracker.js +0 -93
- package/dist/skills/local-pack.js +0 -380
- package/dist/skills/resolver.js +0 -495
- package/dist/state-detector.js +0 -83
- package/dist/timeline/intent-detector.js +0 -263
- package/dist/timeline/loop-miner.js +0 -140
- package/dist/timeline/open-threads.js +0 -49
- package/dist/timeline/signal-reinforcer.js +0 -62
- package/dist/timeline/timeline-bootstrap.js +0 -151
- package/dist/timeline/timeline-store.js +0 -618
- package/dist/tools/coding/bash.js +0 -49
- package/dist/tools/coding/file-edit.js +0 -72
- package/dist/tools/coding/file-outline.js +0 -227
- package/dist/tools/coding/file-read-protocol.js +0 -425
- package/dist/tools/coding/file-read.js +0 -35
- package/dist/tools/coding/file-write.js +0 -43
- package/dist/tools/coding/glob-tool.js +0 -109
- package/dist/tools/coding/grep.js +0 -162
- package/dist/tools/coding/index.js +0 -27
- package/dist/tools/intelligence/index.js +0 -269
- package/dist/tools/intelligence/record-fact.js +0 -48
- package/dist/tools/intelligence/timeline-markers.js +0 -130
- package/dist/tools/registry.js +0 -47
- package/dist/tools/types.js +0 -8
- package/dist/tracking/auto-snapshot-triggers.js +0 -246
- package/dist/tracking/branch-context.js +0 -115
- package/dist/tracking/branch-snapshot.js +0 -217
- package/dist/tracking/causal-bridge.js +0 -317
- package/dist/tracking/circuit-breaker.js +0 -147
- package/dist/tracking/commit-watcher.js +0 -114
- package/dist/tracking/context-ledger.js +0 -119
- package/dist/tracking/correction-detector.js +0 -324
- package/dist/tracking/drift-tracker.js +0 -874
- package/dist/tracking/durability-tracker.js +0 -94
- package/dist/tracking/entity-rewind.js +0 -200
- package/dist/tracking/file-hash-state.js +0 -114
- package/dist/tracking/git-attribution.js +0 -132
- package/dist/tracking/git-trailers.js +0 -171
- package/dist/tracking/intelligence-counter.js +0 -46
- package/dist/tracking/intent-correlator.js +0 -202
- package/dist/tracking/intent-encoder.js +0 -52
- package/dist/tracking/intent-token-tracker.js +0 -159
- package/dist/tracking/ledger-archiver.js +0 -94
- package/dist/tracking/ledger-chains.js +0 -245
- package/dist/tracking/metrics-store.js +0 -361
- package/dist/tracking/native-watcher.js +0 -131
- package/dist/tracking/offline-rewind.js +0 -295
- package/dist/tracking/pending-violations.js +0 -74
- package/dist/tracking/persistence-effectiveness.js +0 -167
- package/dist/tracking/prompt-durability.js +0 -202
- package/dist/tracking/quality-signals.js +0 -213
- package/dist/tracking/redactor.js +0 -73
- package/dist/tracking/rewind-engine.js +0 -161
- package/dist/tracking/session-history.js +0 -128
- package/dist/tracking/session-receipt.js +0 -88
- package/dist/tracking/session-summary-writer.js +0 -157
- package/dist/tracking/shadow-ledger.js +0 -321
- package/dist/tracking/stash-manager.js +0 -258
- package/dist/tracking/timeline-fork.js +0 -213
- package/dist/tracking/timeline.js +0 -69
- package/dist/tracking/token-flow.js +0 -276
- package/dist/tracking/turn-segmenter.js +0 -122
- package/dist/tracking/weekly-accumulator.js +0 -179
- package/dist/tracking/working-snapshots.js +0 -188
- package/dist/tracking/workspace-manifest.js +0 -176
- package/dist/transport/http.js +0 -102
- package/dist/utils/counterfactual.js +0 -65
- package/dist/utils/deep-link.js +0 -34
- package/dist/utils/detect.js +0 -193
- package/dist/utils/exec.js +0 -73
- package/dist/utils/file-logger.js +0 -87
- package/dist/utils/format-error.js +0 -29
- package/dist/utils/git.js +0 -181
- package/dist/utils/log.js +0 -57
- package/dist/utils/logger.js +0 -35
- package/dist/utils/mcp-content-json.js +0 -8
- package/dist/utils/session-logger.js +0 -154
- package/dist/utils/startup-log.js +0 -512
- package/dist/utils/ui.js +0 -56
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auto-Snapshot Triggers — detects when to auto-create working snapshots.
|
|
3
|
-
*
|
|
4
|
-
* Three trigger types:
|
|
5
|
-
* 1. test_pass: tool call result contains a test command + exit code 0
|
|
6
|
-
* 2. session_end: graceful shutdown detected
|
|
7
|
-
* 3. pre_critical_change: before modifying entity with fan_in above threshold
|
|
8
|
-
*
|
|
9
|
-
* Stateless per-call: each function evaluates the trigger condition independently
|
|
10
|
-
* and returns a trigger descriptor or null.
|
|
11
|
-
*/
|
|
12
|
-
import { createModuleLogger } from "../utils/logger.js";
|
|
13
|
-
const log = createModuleLogger("auto-snapshot");
|
|
14
|
-
const TEST_COMMANDS = [
|
|
15
|
-
"test",
|
|
16
|
-
"vitest",
|
|
17
|
-
"jest",
|
|
18
|
-
"mocha",
|
|
19
|
-
"pytest",
|
|
20
|
-
"cargo test",
|
|
21
|
-
"go test",
|
|
22
|
-
"npm test",
|
|
23
|
-
"pnpm test",
|
|
24
|
-
"yarn test",
|
|
25
|
-
"npx vitest",
|
|
26
|
-
"npx jest",
|
|
27
|
-
"pnpm run test",
|
|
28
|
-
"npm run test",
|
|
29
|
-
"pnpm exec vitest",
|
|
30
|
-
"pnpm exec jest",
|
|
31
|
-
"make test",
|
|
32
|
-
"rake test",
|
|
33
|
-
"rspec",
|
|
34
|
-
"phpunit",
|
|
35
|
-
"dotnet test",
|
|
36
|
-
"mvn test",
|
|
37
|
-
"gradle test",
|
|
38
|
-
];
|
|
39
|
-
const TEST_PATTERNS = [
|
|
40
|
-
/\bvitest\b/,
|
|
41
|
-
/\bjest\b/,
|
|
42
|
-
/\bmocha\b/,
|
|
43
|
-
/\bpytest\b/,
|
|
44
|
-
/\bcargo\s+test\b/,
|
|
45
|
-
/\bgo\s+test\b/,
|
|
46
|
-
/\bnpm\s+(?:run\s+)?test\b/,
|
|
47
|
-
/\bpnpm\s+(?:run\s+|exec\s+)?(?:vitest|jest|test)\b/,
|
|
48
|
-
/\byarn\s+(?:run\s+)?test\b/,
|
|
49
|
-
/\bnpx\s+(?:vitest|jest)\b/,
|
|
50
|
-
/\brspec\b/,
|
|
51
|
-
/\bphpunit\b/,
|
|
52
|
-
/\bdotnet\s+test\b/,
|
|
53
|
-
/\bmvn\s+test\b/,
|
|
54
|
-
/\bgradle\s+test\b/,
|
|
55
|
-
/\bmake\s+test\b/,
|
|
56
|
-
/\brake\s+test\b/,
|
|
57
|
-
];
|
|
58
|
-
const DEFAULT_FAN_IN_THRESHOLD = 50;
|
|
59
|
-
/**
|
|
60
|
-
* Determines if a string is a test command based on known patterns.
|
|
61
|
-
*/
|
|
62
|
-
export function isTestCommand(command) {
|
|
63
|
-
const trimmed = command.trim().toLowerCase();
|
|
64
|
-
for (const known of TEST_COMMANDS) {
|
|
65
|
-
if (trimmed === known || trimmed.startsWith(`${known} `)) {
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
for (const pattern of TEST_PATTERNS) {
|
|
70
|
-
if (pattern.test(trimmed)) {
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (/\btest[_\-.]?(?:suite|run|all|unit|int|e2e|integration)\b/i.test(trimmed)) {
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Evaluates whether the given tool call + result should trigger an auto-snapshot.
|
|
81
|
-
*
|
|
82
|
-
* Returns a trigger descriptor if a snapshot should be taken, null otherwise.
|
|
83
|
-
*/
|
|
84
|
-
export function shouldAutoSnapshot(toolName, args, result, fanInThreshold) {
|
|
85
|
-
const testTrigger = checkTestPass(toolName, args, result);
|
|
86
|
-
if (testTrigger)
|
|
87
|
-
return testTrigger;
|
|
88
|
-
const criticalTrigger = checkPreCriticalChange(toolName, args, result, fanInThreshold ?? DEFAULT_FAN_IN_THRESHOLD);
|
|
89
|
-
if (criticalTrigger)
|
|
90
|
-
return criticalTrigger;
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Creates a session-end trigger. Called during graceful shutdown.
|
|
95
|
-
*/
|
|
96
|
-
export function createSessionEndTrigger() {
|
|
97
|
-
return { type: "session_end" };
|
|
98
|
-
}
|
|
99
|
-
function checkTestPass(toolName, args, result) {
|
|
100
|
-
const command = extractCommand(toolName, args);
|
|
101
|
-
if (!command)
|
|
102
|
-
return null;
|
|
103
|
-
if (!isTestCommand(command))
|
|
104
|
-
return null;
|
|
105
|
-
const exitCode = extractExitCode(result);
|
|
106
|
-
if (exitCode !== 0)
|
|
107
|
-
return null;
|
|
108
|
-
log.info(`Test pass detected: ${command}`);
|
|
109
|
-
return {
|
|
110
|
-
type: "test_pass",
|
|
111
|
-
triggerTool: toolName,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
function checkPreCriticalChange(toolName, args, result, threshold) {
|
|
115
|
-
const modifyTools = [
|
|
116
|
-
"sync_local_diff",
|
|
117
|
-
"unerr_revert_entity",
|
|
118
|
-
"unerr_revert_to_working_state",
|
|
119
|
-
];
|
|
120
|
-
if (!modifyTools.includes(toolName))
|
|
121
|
-
return null;
|
|
122
|
-
const entityKey = extractTargetEntity(args);
|
|
123
|
-
if (!entityKey)
|
|
124
|
-
return null;
|
|
125
|
-
const fanIn = extractFanIn(args, result);
|
|
126
|
-
if (fanIn === null || fanIn < threshold)
|
|
127
|
-
return null;
|
|
128
|
-
log.info(`Pre-critical-change trigger: ${entityKey} (fan_in=${fanIn}, threshold=${threshold})`);
|
|
129
|
-
return {
|
|
130
|
-
type: "pre_critical_change",
|
|
131
|
-
triggerTool: toolName,
|
|
132
|
-
entityKey,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
function extractCommand(toolName, args) {
|
|
136
|
-
if (typeof args.command === "string")
|
|
137
|
-
return args.command;
|
|
138
|
-
if (toolName === "run_terminal_command" || toolName === "execute_command") {
|
|
139
|
-
if (typeof args.cmd === "string")
|
|
140
|
-
return args.cmd;
|
|
141
|
-
if (typeof args.shell_command === "string")
|
|
142
|
-
return args.shell_command;
|
|
143
|
-
}
|
|
144
|
-
if (typeof args.script === "string")
|
|
145
|
-
return args.script;
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
function extractExitCode(result) {
|
|
149
|
-
if (result === null || result === undefined)
|
|
150
|
-
return null;
|
|
151
|
-
if (typeof result === "object") {
|
|
152
|
-
const obj = result;
|
|
153
|
-
if (typeof obj.exitCode === "number")
|
|
154
|
-
return obj.exitCode;
|
|
155
|
-
if (typeof obj.exit_code === "number")
|
|
156
|
-
return obj.exit_code;
|
|
157
|
-
if (typeof obj.code === "number")
|
|
158
|
-
return obj.code;
|
|
159
|
-
if (typeof obj.content === "string") {
|
|
160
|
-
return parseExitCodeFromText(obj.content);
|
|
161
|
-
}
|
|
162
|
-
if (Array.isArray(obj.content)) {
|
|
163
|
-
for (const item of obj.content) {
|
|
164
|
-
if (typeof item === "object" && item !== null) {
|
|
165
|
-
const textItem = item;
|
|
166
|
-
if (typeof textItem.text === "string") {
|
|
167
|
-
const code = parseExitCodeFromText(textItem.text);
|
|
168
|
-
if (code !== null)
|
|
169
|
-
return code;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (typeof result === "string") {
|
|
176
|
-
return parseExitCodeFromText(result);
|
|
177
|
-
}
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
function parseExitCodeFromText(text) {
|
|
181
|
-
const passPatterns = [
|
|
182
|
-
/all\s+(?:\d+\s+)?tests?\s+passed/i,
|
|
183
|
-
/\d+\s+passing/i,
|
|
184
|
-
/tests?\s+passed/i,
|
|
185
|
-
/exit\s+code[:\s]+0/i,
|
|
186
|
-
/exited\s+with\s+0/i,
|
|
187
|
-
/✓\s+\d+\s+tests?/i,
|
|
188
|
-
/Tests:\s+\d+\s+passed,\s+\d+\s+total/i,
|
|
189
|
-
];
|
|
190
|
-
for (const pattern of passPatterns) {
|
|
191
|
-
if (pattern.test(text))
|
|
192
|
-
return 0;
|
|
193
|
-
}
|
|
194
|
-
const failPatterns = [
|
|
195
|
-
/\d+\s+failing/i,
|
|
196
|
-
/tests?\s+failed/i,
|
|
197
|
-
/exit\s+code[:\s]+([1-9]\d*)/i,
|
|
198
|
-
/FAIL/,
|
|
199
|
-
];
|
|
200
|
-
for (const pattern of failPatterns) {
|
|
201
|
-
const match = pattern.exec(text);
|
|
202
|
-
if (match) {
|
|
203
|
-
if (match[1])
|
|
204
|
-
return Number.parseInt(match[1], 10);
|
|
205
|
-
return 1;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
function extractTargetEntity(args) {
|
|
211
|
-
if (typeof args.entity_key === "string")
|
|
212
|
-
return args.entity_key;
|
|
213
|
-
if (typeof args.entityKey === "string")
|
|
214
|
-
return args.entityKey;
|
|
215
|
-
if (typeof args.file === "string")
|
|
216
|
-
return args.file;
|
|
217
|
-
if (typeof args.path === "string")
|
|
218
|
-
return args.path;
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
function extractFanIn(args, result) {
|
|
222
|
-
if (typeof args.fan_in === "number")
|
|
223
|
-
return args.fan_in;
|
|
224
|
-
if (typeof args.fanIn === "number")
|
|
225
|
-
return args.fanIn;
|
|
226
|
-
if (result !== null && typeof result === "object") {
|
|
227
|
-
const obj = result;
|
|
228
|
-
if (typeof obj.fan_in === "number")
|
|
229
|
-
return obj.fan_in;
|
|
230
|
-
if (typeof obj.fanIn === "number")
|
|
231
|
-
return obj.fanIn;
|
|
232
|
-
if (typeof obj.entity === "object" && obj.entity !== null) {
|
|
233
|
-
const entity = obj.entity;
|
|
234
|
-
if (typeof entity.fan_in === "number")
|
|
235
|
-
return entity.fan_in;
|
|
236
|
-
if (typeof entity.fanIn === "number")
|
|
237
|
-
return entity.fanIn;
|
|
238
|
-
}
|
|
239
|
-
if (typeof obj.blast_radius === "object" && obj.blast_radius !== null) {
|
|
240
|
-
const br = obj.blast_radius;
|
|
241
|
-
if (typeof br.direct_callers === "number")
|
|
242
|
-
return br.direct_callers;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Branch Context Tracker — computes and maintains branch state.
|
|
3
|
-
*
|
|
4
|
-
* Tracks: current branch, base branch, merge base commit, commits ahead/behind.
|
|
5
|
-
* Updated on proxy startup and on branch switch (detected via .git/HEAD polling).
|
|
6
|
-
*
|
|
7
|
-
* All git operations use the centralized git utility — no stdout pollution.
|
|
8
|
-
*/
|
|
9
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
import { gitQuery } from "../utils/exec.js";
|
|
12
|
-
/**
|
|
13
|
-
* Compute branch context from git state.
|
|
14
|
-
* Never throws — returns safe defaults on any git failure.
|
|
15
|
-
*/
|
|
16
|
-
export async function computeBranchContext(cwd) {
|
|
17
|
-
const dir = cwd ?? process.cwd();
|
|
18
|
-
const currentBranch = (await gitQuery(["branch", "--show-current"], dir)) || "HEAD";
|
|
19
|
-
const headSha = (await gitQuery(["rev-parse", "HEAD"], dir)) || "";
|
|
20
|
-
let baseBranch = "main";
|
|
21
|
-
if (currentBranch && currentBranch !== "HEAD") {
|
|
22
|
-
const configured = await gitQuery(["config", "--get", `branch.${currentBranch}.merge`], dir);
|
|
23
|
-
if (configured) {
|
|
24
|
-
baseBranch = configured.replace(/^refs\/heads\//, "");
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
const mainExists = await gitQuery(["rev-parse", "--verify", "origin/main"], dir);
|
|
28
|
-
if (!mainExists) {
|
|
29
|
-
const masterExists = await gitQuery(["rev-parse", "--verify", "origin/master"], dir);
|
|
30
|
-
if (masterExists)
|
|
31
|
-
baseBranch = "master";
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
let baseCommit = null;
|
|
36
|
-
let commitsAhead = 0;
|
|
37
|
-
let commitsBehind = null;
|
|
38
|
-
const remoteBranch = `origin/${baseBranch}`;
|
|
39
|
-
baseCommit = await gitQuery(["merge-base", "HEAD", remoteBranch], dir);
|
|
40
|
-
if (baseCommit) {
|
|
41
|
-
const ahead = await gitQuery(["rev-list", "--count", `${baseCommit}..HEAD`], dir);
|
|
42
|
-
commitsAhead = ahead ? Number.parseInt(ahead, 10) : 0;
|
|
43
|
-
const behind = await gitQuery(["rev-list", "--count", `HEAD..${remoteBranch}`], dir);
|
|
44
|
-
commitsBehind = behind ? Number.parseInt(behind, 10) : null;
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
currentBranch,
|
|
48
|
-
baseBranch,
|
|
49
|
-
baseCommit,
|
|
50
|
-
commitsAhead,
|
|
51
|
-
commitsBehind,
|
|
52
|
-
headSha,
|
|
53
|
-
computedAt: new Date().toISOString(),
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Alias retained for call-site compatibility — both versions are now async.
|
|
58
|
-
*/
|
|
59
|
-
export const computeBranchContextAsync = computeBranchContext;
|
|
60
|
-
/**
|
|
61
|
-
* Detect if a branch switch occurred by comparing .git/HEAD content.
|
|
62
|
-
* Returns the new branch name if switched, null otherwise.
|
|
63
|
-
*/
|
|
64
|
-
export function detectBranchSwitch(previousBranch, cwd) {
|
|
65
|
-
const current = getCurrentBranch(cwd);
|
|
66
|
-
if (current && current !== previousBranch) {
|
|
67
|
-
return current;
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Get the current branch name by reading .git/HEAD directly (fast, no process spawn).
|
|
73
|
-
*/
|
|
74
|
-
export function getCurrentBranch(cwd) {
|
|
75
|
-
const gitDir = cwd ? join(cwd, ".git") : join(process.cwd(), ".git");
|
|
76
|
-
const headPath = join(gitDir, "HEAD");
|
|
77
|
-
if (!existsSync(headPath))
|
|
78
|
-
return null;
|
|
79
|
-
try {
|
|
80
|
-
const content = readFileSync(headPath, "utf-8").trim();
|
|
81
|
-
// ref: refs/heads/feature/auth → feature/auth
|
|
82
|
-
if (content.startsWith("ref: refs/heads/")) {
|
|
83
|
-
return content.slice("ref: refs/heads/".length);
|
|
84
|
-
}
|
|
85
|
-
// Detached HEAD — return short SHA
|
|
86
|
-
return content.slice(0, 8);
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Get the current HEAD SHA via git rev-parse.
|
|
94
|
-
*/
|
|
95
|
-
export async function getHeadSha(cwd) {
|
|
96
|
-
return (await gitQuery(["rev-parse", "HEAD"], cwd ?? process.cwd())) || "";
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Start a branch switch poller. Calls onSwitch when branch changes.
|
|
100
|
-
* Returns a dispose function to stop polling.
|
|
101
|
-
*/
|
|
102
|
-
export function startBranchPoller(onSwitch, intervalMs = 5000, cwd) {
|
|
103
|
-
let currentBranch = getCurrentBranch(cwd) ?? "unknown";
|
|
104
|
-
const timer = setInterval(async () => {
|
|
105
|
-
const switched = detectBranchSwitch(currentBranch, cwd);
|
|
106
|
-
if (switched) {
|
|
107
|
-
currentBranch = switched;
|
|
108
|
-
const context = await computeBranchContext(cwd);
|
|
109
|
-
onSwitch(switched, context);
|
|
110
|
-
}
|
|
111
|
-
}, intervalMs);
|
|
112
|
-
// Don't prevent process exit
|
|
113
|
-
timer.unref();
|
|
114
|
-
return () => clearInterval(timer);
|
|
115
|
-
}
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint 6.2: Per-Branch Overlay Snapshots — save/restore drift state on branch switch.
|
|
3
|
-
*
|
|
4
|
-
* On branch switch from A → B:
|
|
5
|
-
* 1. SAVE: Serialize drift_overlay + file hashes → .unerr/drift/branches/{A}/
|
|
6
|
-
* 2. CLEAR CozoDB drift_overlay + file hashes + mtime cache
|
|
7
|
-
* 3. RESTORE: If branches/{B}/ snapshot exists, bulk insert back (<10ms)
|
|
8
|
-
* Else compute from scratch (first visit)
|
|
9
|
-
*
|
|
10
|
-
* Max 20 branch snapshots stored (LRU eviction).
|
|
11
|
-
* Branches deleted from git are garbage-collected.
|
|
12
|
-
*/
|
|
13
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
-
import { listBranches } from "../utils/git.js";
|
|
16
|
-
import { createModuleLogger } from "../utils/logger.js";
|
|
17
|
-
/** Maximum number of branch snapshots to retain (LRU). */
|
|
18
|
-
const MAX_BRANCH_SNAPSHOTS = 20;
|
|
19
|
-
const OVERLAY_FILE = "overlay_snapshot.json";
|
|
20
|
-
const HASHES_FILE = "file_hashes.json";
|
|
21
|
-
const log = createModuleLogger("branch");
|
|
22
|
-
/**
|
|
23
|
-
* Sanitize branch name for use as a directory name.
|
|
24
|
-
* Replaces `/` with `__` and strips unsafe chars.
|
|
25
|
-
*/
|
|
26
|
-
function sanitizeBranchName(branch) {
|
|
27
|
-
return branch.replace(/\//g, "__").replace(/[^a-zA-Z0-9_.\-]/g, "_");
|
|
28
|
-
}
|
|
29
|
-
export class BranchSnapshotManager {
|
|
30
|
-
branchDir;
|
|
31
|
-
projectRoot;
|
|
32
|
-
constructor(unerrDir, projectRoot) {
|
|
33
|
-
this.branchDir = join(unerrDir, "drift", "branches");
|
|
34
|
-
this.projectRoot = projectRoot;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Save the current drift overlay + file hashes for a branch.
|
|
38
|
-
* Called before clearing overlay on branch switch (save outgoing branch).
|
|
39
|
-
*/
|
|
40
|
-
async saveSnapshot(branch, localGraph, fileHashState) {
|
|
41
|
-
const entities = await localGraph.getAllDriftEntities();
|
|
42
|
-
const edges = await localGraph.getAllDriftEdges();
|
|
43
|
-
if (entities.length === 0 && edges.length === 0) {
|
|
44
|
-
log.info(`No drift entities/edges to snapshot for branch ${branch}`);
|
|
45
|
-
// Still save empty snapshot so we know we visited this branch
|
|
46
|
-
// (avoids recompute on return if there truly was no drift)
|
|
47
|
-
}
|
|
48
|
-
const dirName = sanitizeBranchName(branch);
|
|
49
|
-
const snapshotDir = join(this.branchDir, dirName);
|
|
50
|
-
if (!existsSync(snapshotDir)) {
|
|
51
|
-
mkdirSync(snapshotDir, { recursive: true });
|
|
52
|
-
}
|
|
53
|
-
const snapshot = {
|
|
54
|
-
branch,
|
|
55
|
-
entities,
|
|
56
|
-
edges,
|
|
57
|
-
fileHashes: fileHashState,
|
|
58
|
-
savedAt: new Date().toISOString(),
|
|
59
|
-
};
|
|
60
|
-
writeFileSync(join(snapshotDir, OVERLAY_FILE), JSON.stringify(snapshot, null, 2), "utf-8");
|
|
61
|
-
writeFileSync(join(snapshotDir, HASHES_FILE), JSON.stringify(fileHashState, null, 2), "utf-8");
|
|
62
|
-
// Enforce LRU cap
|
|
63
|
-
this.enforceLruCap();
|
|
64
|
-
log.info(`Saved branch snapshot: ${branch} (${entities.length} entities, ${edges.length} edges)`);
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Restore drift overlay from a branch snapshot.
|
|
69
|
-
* Returns the snapshot if found, null if this is a first visit.
|
|
70
|
-
*/
|
|
71
|
-
async restoreSnapshot(branch, localGraph) {
|
|
72
|
-
const dirName = sanitizeBranchName(branch);
|
|
73
|
-
const snapshotDir = join(this.branchDir, dirName);
|
|
74
|
-
const overlayPath = join(snapshotDir, OVERLAY_FILE);
|
|
75
|
-
if (!existsSync(overlayPath)) {
|
|
76
|
-
log.info(`No snapshot for branch ${branch} — first visit`);
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
const raw = readFileSync(overlayPath, "utf-8");
|
|
81
|
-
const snapshot = JSON.parse(raw);
|
|
82
|
-
// Bulk insert entities back into drift overlay
|
|
83
|
-
for (const entity of snapshot.entities) {
|
|
84
|
-
await localGraph.upsertDriftEntity(entity);
|
|
85
|
-
}
|
|
86
|
-
// Bulk insert edges back into drift_edges (Task 6.4)
|
|
87
|
-
if (snapshot.edges) {
|
|
88
|
-
for (const edge of snapshot.edges) {
|
|
89
|
-
await localGraph.upsertDriftEdge(edge);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
// Touch the snapshot dir to update LRU ordering
|
|
93
|
-
const now = new Date();
|
|
94
|
-
writeFileSync(join(snapshotDir, ".last_access"), now.toISOString(), "utf-8");
|
|
95
|
-
log.info(`Restored branch snapshot: ${branch} (${snapshot.entities.length} entities, ${snapshot.edges?.length ?? 0} edges)`);
|
|
96
|
-
return snapshot;
|
|
97
|
-
}
|
|
98
|
-
catch (err) {
|
|
99
|
-
log.warn(`Failed to restore snapshot for ${branch}: ${err instanceof Error ? err.message : String(err)}`);
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Check if a snapshot exists for a given branch.
|
|
105
|
-
*/
|
|
106
|
-
hasSnapshot(branch) {
|
|
107
|
-
const dirName = sanitizeBranchName(branch);
|
|
108
|
-
return existsSync(join(this.branchDir, dirName, OVERLAY_FILE));
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Get the file hash state from a branch snapshot.
|
|
112
|
-
*/
|
|
113
|
-
getSnapshotFileHashes(branch) {
|
|
114
|
-
const dirName = sanitizeBranchName(branch);
|
|
115
|
-
const hashesPath = join(this.branchDir, dirName, HASHES_FILE);
|
|
116
|
-
if (!existsSync(hashesPath))
|
|
117
|
-
return null;
|
|
118
|
-
try {
|
|
119
|
-
const raw = readFileSync(hashesPath, "utf-8");
|
|
120
|
-
return JSON.parse(raw);
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Delete snapshot for a specific branch.
|
|
128
|
-
*/
|
|
129
|
-
deleteSnapshot(branch) {
|
|
130
|
-
const dirName = sanitizeBranchName(branch);
|
|
131
|
-
const snapshotDir = join(this.branchDir, dirName);
|
|
132
|
-
if (!existsSync(snapshotDir))
|
|
133
|
-
return false;
|
|
134
|
-
rmSync(snapshotDir, { recursive: true, force: true });
|
|
135
|
-
log.info(`Deleted branch snapshot: ${branch}`);
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Garbage-collect snapshots for branches that no longer exist in git.
|
|
140
|
-
*/
|
|
141
|
-
async garbageCollect() {
|
|
142
|
-
if (!existsSync(this.branchDir))
|
|
143
|
-
return 0;
|
|
144
|
-
const gitBranches = new Set(await listBranches(this.projectRoot));
|
|
145
|
-
if (gitBranches.size === 0)
|
|
146
|
-
return 0;
|
|
147
|
-
const snapshots = this.listSnapshots();
|
|
148
|
-
let removed = 0;
|
|
149
|
-
for (const snapshot of snapshots) {
|
|
150
|
-
if (!gitBranches.has(snapshot.branch)) {
|
|
151
|
-
const snapshotDir = join(this.branchDir, snapshot.id);
|
|
152
|
-
rmSync(snapshotDir, { recursive: true, force: true });
|
|
153
|
-
log.info(`GC removed snapshot for deleted branch: ${snapshot.branch}`);
|
|
154
|
-
removed++;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return removed;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* List all branch snapshots, sorted by most recently accessed first.
|
|
161
|
-
*/
|
|
162
|
-
listSnapshots() {
|
|
163
|
-
if (!existsSync(this.branchDir))
|
|
164
|
-
return [];
|
|
165
|
-
try {
|
|
166
|
-
const entries = readdirSync(this.branchDir, { withFileTypes: true });
|
|
167
|
-
const snapshots = [];
|
|
168
|
-
for (const entry of entries) {
|
|
169
|
-
if (!entry.isDirectory())
|
|
170
|
-
continue;
|
|
171
|
-
const overlayPath = join(this.branchDir, entry.name, OVERLAY_FILE);
|
|
172
|
-
if (!existsSync(overlayPath))
|
|
173
|
-
continue;
|
|
174
|
-
try {
|
|
175
|
-
const raw = readFileSync(overlayPath, "utf-8");
|
|
176
|
-
const snapshot = JSON.parse(raw);
|
|
177
|
-
// Use .last_access if available, else overlay file mtime
|
|
178
|
-
const accessPath = join(this.branchDir, entry.name, ".last_access");
|
|
179
|
-
let accessedAt;
|
|
180
|
-
if (existsSync(accessPath)) {
|
|
181
|
-
accessedAt = statSync(accessPath).mtime;
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
accessedAt = statSync(overlayPath).mtime;
|
|
185
|
-
}
|
|
186
|
-
snapshots.push({
|
|
187
|
-
id: entry.name,
|
|
188
|
-
branch: snapshot.branch,
|
|
189
|
-
accessedAt,
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
catch {
|
|
193
|
-
// Skip corrupt snapshots
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
snapshots.sort((a, b) => b.accessedAt.getTime() - a.accessedAt.getTime());
|
|
197
|
-
return snapshots;
|
|
198
|
-
}
|
|
199
|
-
catch {
|
|
200
|
-
return [];
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Enforce LRU cap — remove oldest snapshots beyond MAX_BRANCH_SNAPSHOTS.
|
|
205
|
-
*/
|
|
206
|
-
enforceLruCap() {
|
|
207
|
-
const snapshots = this.listSnapshots();
|
|
208
|
-
if (snapshots.length <= MAX_BRANCH_SNAPSHOTS)
|
|
209
|
-
return;
|
|
210
|
-
const toRemove = snapshots.slice(MAX_BRANCH_SNAPSHOTS);
|
|
211
|
-
for (const snapshot of toRemove) {
|
|
212
|
-
const dir = join(this.branchDir, snapshot.id);
|
|
213
|
-
rmSync(dir, { recursive: true, force: true });
|
|
214
|
-
log.info(`LRU evicted branch snapshot: ${snapshot.branch}`);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|