@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,258 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint 7.1: Git Stash Awareness — save/restore drift overlay on stash/pop.
|
|
3
|
-
*
|
|
4
|
-
* Detects `git stash` and `git stash pop` by watching `.git/refs/stash`.
|
|
5
|
-
* On stash push: serializes drift_overlay + file hashes to `.unerr/drift/stash/{ref}/`.
|
|
6
|
-
* On stash pop: restores overlay from snapshot.
|
|
7
|
-
* Max 10 stash snapshots (LRU eviction).
|
|
8
|
-
*/
|
|
9
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
/** Maximum number of stash snapshots to retain (LRU). */
|
|
12
|
-
const MAX_STASH_SNAPSHOTS = 10;
|
|
13
|
-
/** Files written per stash snapshot. */
|
|
14
|
-
const OVERLAY_FILE = "overlay_snapshot.json";
|
|
15
|
-
const HASHES_FILE = "file_hashes.json";
|
|
16
|
-
/** stderr logger */
|
|
17
|
-
const _log = {
|
|
18
|
-
info: (msg) => process.stderr.write(`[unerr:stash] ${msg}\n`),
|
|
19
|
-
warn: (msg) => process.stderr.write(`[unerr:stash] WARN: ${msg}\n`),
|
|
20
|
-
};
|
|
21
|
-
export class StashManager {
|
|
22
|
-
unerrDir;
|
|
23
|
-
projectRoot;
|
|
24
|
-
stashDir;
|
|
25
|
-
gitDir;
|
|
26
|
-
previousStashRef = null;
|
|
27
|
-
previousStashCount = 0;
|
|
28
|
-
constructor(unerrDir, projectRoot) {
|
|
29
|
-
this.unerrDir = unerrDir;
|
|
30
|
-
this.projectRoot = projectRoot;
|
|
31
|
-
this.stashDir = join(unerrDir, "drift", "stash");
|
|
32
|
-
this.gitDir = join(projectRoot, ".git");
|
|
33
|
-
// Initialize stash state
|
|
34
|
-
this.previousStashRef = this.readStashRef();
|
|
35
|
-
this.previousStashCount = this.getStashCount();
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Check for stash changes. Call this periodically or on file-watcher trigger.
|
|
39
|
-
* Returns "push" if a stash was pushed, "pop" if popped/dropped, null if unchanged.
|
|
40
|
-
*/
|
|
41
|
-
detectStashChange() {
|
|
42
|
-
const currentRef = this.readStashRef();
|
|
43
|
-
const currentCount = this.getStashCount();
|
|
44
|
-
if (currentRef === this.previousStashRef &&
|
|
45
|
-
currentCount === this.previousStashCount) {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
let action = null;
|
|
49
|
-
if (currentCount > this.previousStashCount) {
|
|
50
|
-
action = "push";
|
|
51
|
-
}
|
|
52
|
-
else if (currentCount < this.previousStashCount) {
|
|
53
|
-
action = "pop";
|
|
54
|
-
}
|
|
55
|
-
else if (currentRef !== this.previousStashRef) {
|
|
56
|
-
// Same count but different ref — stash apply or complex operation
|
|
57
|
-
action = null;
|
|
58
|
-
}
|
|
59
|
-
this.previousStashRef = currentRef;
|
|
60
|
-
this.previousStashCount = currentCount;
|
|
61
|
-
return action;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Save the current drift overlay + file hashes as a stash snapshot.
|
|
65
|
-
*/
|
|
66
|
-
async saveSnapshot(localGraph, fileHashState) {
|
|
67
|
-
const stashRef = this.readStashRef();
|
|
68
|
-
if (!stashRef)
|
|
69
|
-
return null;
|
|
70
|
-
const entities = await localGraph.getAllDriftEntities();
|
|
71
|
-
const edges = await localGraph.getAllDriftEdges();
|
|
72
|
-
if (entities.length === 0 && edges.length === 0) {
|
|
73
|
-
_log.info("No drift entities/edges to snapshot on stash push");
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
// Use stash ref SHA (truncated) as directory name
|
|
77
|
-
const snapshotId = stashRef.slice(0, 12);
|
|
78
|
-
const snapshotDir = join(this.stashDir, snapshotId);
|
|
79
|
-
if (!existsSync(snapshotDir)) {
|
|
80
|
-
mkdirSync(snapshotDir, { recursive: true });
|
|
81
|
-
}
|
|
82
|
-
const snapshot = {
|
|
83
|
-
stashRef,
|
|
84
|
-
entities,
|
|
85
|
-
edges,
|
|
86
|
-
fileHashes: fileHashState,
|
|
87
|
-
savedAt: new Date().toISOString(),
|
|
88
|
-
};
|
|
89
|
-
writeFileSync(join(snapshotDir, OVERLAY_FILE), JSON.stringify(snapshot, null, 2), "utf-8");
|
|
90
|
-
writeFileSync(join(snapshotDir, HASHES_FILE), JSON.stringify(fileHashState, null, 2), "utf-8");
|
|
91
|
-
// Enforce LRU cap
|
|
92
|
-
this.enforceLruCap();
|
|
93
|
-
_log.info(`Saved stash snapshot: ${snapshotId} (${entities.length} entities)`);
|
|
94
|
-
return snapshotId;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Restore drift overlay from the most recent stash snapshot.
|
|
98
|
-
* Called on stash pop. Returns the number of entities restored.
|
|
99
|
-
*/
|
|
100
|
-
async restoreSnapshot(localGraph) {
|
|
101
|
-
const snapshots = this.listSnapshots();
|
|
102
|
-
if (snapshots.length === 0) {
|
|
103
|
-
_log.info("No stash snapshots to restore");
|
|
104
|
-
return 0;
|
|
105
|
-
}
|
|
106
|
-
// Restore the most recently saved snapshot
|
|
107
|
-
const latest = snapshots[0];
|
|
108
|
-
const snapshotDir = join(this.stashDir, latest.id);
|
|
109
|
-
const overlayPath = join(snapshotDir, OVERLAY_FILE);
|
|
110
|
-
if (!existsSync(overlayPath)) {
|
|
111
|
-
_log.warn(`Snapshot ${latest.id} missing overlay file`);
|
|
112
|
-
return 0;
|
|
113
|
-
}
|
|
114
|
-
try {
|
|
115
|
-
const raw = readFileSync(overlayPath, "utf-8");
|
|
116
|
-
const snapshot = JSON.parse(raw);
|
|
117
|
-
// Restore entities into drift overlay
|
|
118
|
-
for (const entity of snapshot.entities) {
|
|
119
|
-
await localGraph.upsertDriftEntity(entity);
|
|
120
|
-
}
|
|
121
|
-
// Restore drift edges (Task 6.4)
|
|
122
|
-
if (snapshot.edges) {
|
|
123
|
-
for (const edge of snapshot.edges) {
|
|
124
|
-
await localGraph.upsertDriftEdge(edge);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// Clean up the snapshot after restore
|
|
128
|
-
rmSync(snapshotDir, { recursive: true, force: true });
|
|
129
|
-
_log.info(`Restored stash snapshot: ${latest.id} (${snapshot.entities.length} entities, ${snapshot.edges?.length ?? 0} edges)`);
|
|
130
|
-
return snapshot.entities.length;
|
|
131
|
-
}
|
|
132
|
-
catch (err) {
|
|
133
|
-
_log.warn(`Failed to restore snapshot ${latest.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
134
|
-
return 0;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Get the file hash state from the most recent stash snapshot.
|
|
139
|
-
* Returns null if no snapshot exists.
|
|
140
|
-
*/
|
|
141
|
-
getSnapshotFileHashes() {
|
|
142
|
-
const snapshots = this.listSnapshots();
|
|
143
|
-
if (snapshots.length === 0)
|
|
144
|
-
return null;
|
|
145
|
-
const latest = snapshots[0];
|
|
146
|
-
const hashesPath = join(this.stashDir, latest.id, HASHES_FILE);
|
|
147
|
-
if (!existsSync(hashesPath))
|
|
148
|
-
return null;
|
|
149
|
-
try {
|
|
150
|
-
const raw = readFileSync(hashesPath, "utf-8");
|
|
151
|
-
return JSON.parse(raw);
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Remove snapshot for a specific stash ref (on stash drop).
|
|
159
|
-
*/
|
|
160
|
-
dropSnapshot(stashRef) {
|
|
161
|
-
const snapshotId = stashRef.slice(0, 12);
|
|
162
|
-
const snapshotDir = join(this.stashDir, snapshotId);
|
|
163
|
-
if (!existsSync(snapshotDir))
|
|
164
|
-
return false;
|
|
165
|
-
rmSync(snapshotDir, { recursive: true, force: true });
|
|
166
|
-
_log.info(`Dropped stash snapshot: ${snapshotId}`);
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* List all stash snapshots, sorted by most recent first.
|
|
171
|
-
*/
|
|
172
|
-
listSnapshots() {
|
|
173
|
-
if (!existsSync(this.stashDir))
|
|
174
|
-
return [];
|
|
175
|
-
try {
|
|
176
|
-
const entries = readdirSync(this.stashDir, { withFileTypes: true });
|
|
177
|
-
const snapshots = [];
|
|
178
|
-
for (const entry of entries) {
|
|
179
|
-
if (!entry.isDirectory())
|
|
180
|
-
continue;
|
|
181
|
-
const overlayPath = join(this.stashDir, entry.name, OVERLAY_FILE);
|
|
182
|
-
if (!existsSync(overlayPath))
|
|
183
|
-
continue;
|
|
184
|
-
try {
|
|
185
|
-
const stat = statSync(overlayPath);
|
|
186
|
-
snapshots.push({ id: entry.name, savedAt: stat.mtime });
|
|
187
|
-
}
|
|
188
|
-
catch { }
|
|
189
|
-
}
|
|
190
|
-
// Sort by most recent first
|
|
191
|
-
snapshots.sort((a, b) => b.savedAt.getTime() - a.savedAt.getTime());
|
|
192
|
-
return snapshots;
|
|
193
|
-
}
|
|
194
|
-
catch {
|
|
195
|
-
return [];
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Read the current stash ref SHA from `.git/refs/stash`.
|
|
200
|
-
*/
|
|
201
|
-
readStashRef() {
|
|
202
|
-
const stashPath = join(this.gitDir, "refs", "stash");
|
|
203
|
-
if (!existsSync(stashPath))
|
|
204
|
-
return null;
|
|
205
|
-
try {
|
|
206
|
-
return readFileSync(stashPath, "utf-8").trim() || null;
|
|
207
|
-
}
|
|
208
|
-
catch {
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* Count current stash entries via `.git/logs/refs/stash`.
|
|
214
|
-
*/
|
|
215
|
-
getStashCount() {
|
|
216
|
-
const logPath = join(this.gitDir, "logs", "refs", "stash");
|
|
217
|
-
if (!existsSync(logPath))
|
|
218
|
-
return 0;
|
|
219
|
-
try {
|
|
220
|
-
const content = readFileSync(logPath, "utf-8");
|
|
221
|
-
return content.split("\n").filter((line) => line.trim().length > 0)
|
|
222
|
-
.length;
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
return 0;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Enforce LRU cap — remove oldest snapshots beyond MAX_STASH_SNAPSHOTS.
|
|
230
|
-
*/
|
|
231
|
-
enforceLruCap() {
|
|
232
|
-
const snapshots = this.listSnapshots();
|
|
233
|
-
if (snapshots.length <= MAX_STASH_SNAPSHOTS)
|
|
234
|
-
return;
|
|
235
|
-
const toRemove = snapshots.slice(MAX_STASH_SNAPSHOTS);
|
|
236
|
-
for (const snapshot of toRemove) {
|
|
237
|
-
const dir = join(this.stashDir, snapshot.id);
|
|
238
|
-
rmSync(dir, { recursive: true, force: true });
|
|
239
|
-
_log.info(`LRU evicted stash snapshot: ${snapshot.id}`);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Start a stash change poller. Calls onPush/onPop when stash state changes.
|
|
245
|
-
* Returns a dispose function to stop polling.
|
|
246
|
-
*/
|
|
247
|
-
export function startStashPoller(stashManager, onPush, onPop, intervalMs = 3000) {
|
|
248
|
-
const timer = setInterval(() => {
|
|
249
|
-
const action = stashManager.detectStashChange();
|
|
250
|
-
if (action === "push")
|
|
251
|
-
onPush();
|
|
252
|
-
else if (action === "pop")
|
|
253
|
-
onPop();
|
|
254
|
-
}, intervalMs);
|
|
255
|
-
// Don't prevent process exit
|
|
256
|
-
timer.unref();
|
|
257
|
-
return () => clearInterval(timer);
|
|
258
|
-
}
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Timeline Fork — tracks divergence points when rewinds create alternate timelines.
|
|
3
|
-
*
|
|
4
|
-
* When a rewind occurs, the current timeline is "abandoned" and a new one begins.
|
|
5
|
-
* This module persists fork metadata to `.unerr/state/timeline-forks.json`,
|
|
6
|
-
* enabling the system to trace what was tried, why it failed, and what replaced it.
|
|
7
|
-
*
|
|
8
|
-
* Storage format: JSON array of TimelineFork objects, newest first.
|
|
9
|
-
* Active timeline ID: persisted alongside forks, monotonically increasing.
|
|
10
|
-
*/
|
|
11
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
import { createModuleLogger } from "../utils/logger.js";
|
|
14
|
-
const log = createModuleLogger("timeline-fork");
|
|
15
|
-
const MAX_FORK_HISTORY = 100;
|
|
16
|
-
let inMemoryTimeline = 0;
|
|
17
|
-
/**
|
|
18
|
-
* Creates a new timeline fork when a rewind occurs.
|
|
19
|
-
*
|
|
20
|
-
* When called with unerrDir, reads/writes persistent state.
|
|
21
|
-
* When called without unerrDir, uses in-memory counter (for lightweight use).
|
|
22
|
-
*/
|
|
23
|
-
export function createTimelineFork(snapshotId, abandonedEntities, prompts, reason, unerrDir) {
|
|
24
|
-
const state = unerrDir
|
|
25
|
-
? loadState(unerrDir)
|
|
26
|
-
: { activeTimeline: inMemoryTimeline, forks: [] };
|
|
27
|
-
const now = new Date().toISOString();
|
|
28
|
-
const currentTimeline = state.activeTimeline;
|
|
29
|
-
const nextTimeline = currentTimeline + 1;
|
|
30
|
-
const fork = {
|
|
31
|
-
forkPoint: snapshotId,
|
|
32
|
-
abandonedBranch: {
|
|
33
|
-
timelineId: currentTimeline,
|
|
34
|
-
entityChanges: deduplicateStrings(abandonedEntities),
|
|
35
|
-
promptsTried: deduplicateStrings(prompts),
|
|
36
|
-
...(reason ? { failureReason: reason } : {}),
|
|
37
|
-
},
|
|
38
|
-
newBranch: {
|
|
39
|
-
timelineId: nextTimeline,
|
|
40
|
-
startedAt: now,
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
state.forks.unshift(fork);
|
|
44
|
-
if (state.forks.length > MAX_FORK_HISTORY) {
|
|
45
|
-
state.forks = state.forks.slice(0, MAX_FORK_HISTORY);
|
|
46
|
-
}
|
|
47
|
-
state.activeTimeline = nextTimeline;
|
|
48
|
-
if (unerrDir) {
|
|
49
|
-
saveState(unerrDir, state);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
inMemoryTimeline = nextTimeline;
|
|
53
|
-
}
|
|
54
|
-
log.info(`Fork created: timeline ${currentTimeline} → ${nextTimeline} at snapshot ${snapshotId}`);
|
|
55
|
-
return fork;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Returns the active timeline ID for the repository.
|
|
59
|
-
*/
|
|
60
|
-
export function getActiveTimeline(unerrDir) {
|
|
61
|
-
const state = loadState(unerrDir);
|
|
62
|
-
return state.activeTimeline;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Returns the full fork history, newest first.
|
|
66
|
-
*/
|
|
67
|
-
export function getForkHistory(unerrDir) {
|
|
68
|
-
const state = loadState(unerrDir);
|
|
69
|
-
return state.forks;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Returns forks where a specific entity was abandoned.
|
|
73
|
-
*/
|
|
74
|
-
export function getEntityForkHistory(unerrDir, entityKey) {
|
|
75
|
-
const state = loadState(unerrDir);
|
|
76
|
-
return state.forks.filter((f) => f.abandonedBranch.entityChanges.includes(entityKey));
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Returns the most recent fork, or null if no forks have occurred.
|
|
80
|
-
*/
|
|
81
|
-
export function getLatestFork(unerrDir) {
|
|
82
|
-
const state = loadState(unerrDir);
|
|
83
|
-
return state.forks[0] ?? null;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Returns aggregate statistics about the fork history.
|
|
87
|
-
*/
|
|
88
|
-
export function getForkStats(unerrDir) {
|
|
89
|
-
const state = loadState(unerrDir);
|
|
90
|
-
const entitySet = new Set();
|
|
91
|
-
const promptSet = new Set();
|
|
92
|
-
const reasons = {};
|
|
93
|
-
for (const fork of state.forks) {
|
|
94
|
-
for (const entity of fork.abandonedBranch.entityChanges) {
|
|
95
|
-
entitySet.add(entity);
|
|
96
|
-
}
|
|
97
|
-
for (const prompt of fork.abandonedBranch.promptsTried) {
|
|
98
|
-
promptSet.add(prompt);
|
|
99
|
-
}
|
|
100
|
-
if (fork.abandonedBranch.failureReason) {
|
|
101
|
-
const reason = fork.abandonedBranch.failureReason;
|
|
102
|
-
reasons[reason] = (reasons[reason] ?? 0) + 1;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
totalForks: state.forks.length,
|
|
107
|
-
activeTimeline: state.activeTimeline,
|
|
108
|
-
uniqueEntitiesAbandoned: entitySet.size,
|
|
109
|
-
uniquePromptsAbandoned: promptSet.size,
|
|
110
|
-
failureReasons: reasons,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Checks if a fork involved a specific snapshot as its fork point.
|
|
115
|
-
*/
|
|
116
|
-
export function wasForkPoint(unerrDir, snapshotId) {
|
|
117
|
-
const state = loadState(unerrDir);
|
|
118
|
-
return state.forks.some((f) => f.forkPoint === snapshotId);
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Returns all prompts tried across abandoned timelines for a given entity.
|
|
122
|
-
* Useful for avoiding repeated failed approaches.
|
|
123
|
-
*/
|
|
124
|
-
export function getAbandonedPrompts(unerrDir, entityKey) {
|
|
125
|
-
const state = loadState(unerrDir);
|
|
126
|
-
const prompts = new Set();
|
|
127
|
-
for (const fork of state.forks) {
|
|
128
|
-
if (fork.abandonedBranch.entityChanges.includes(entityKey)) {
|
|
129
|
-
for (const p of fork.abandonedBranch.promptsTried) {
|
|
130
|
-
prompts.add(p);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return Array.from(prompts);
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Returns the timeline ID at a given fork point, or null if not a fork point.
|
|
138
|
-
*/
|
|
139
|
-
export function getTimelineAtFork(unerrDir, snapshotId) {
|
|
140
|
-
const state = loadState(unerrDir);
|
|
141
|
-
const fork = state.forks.find((f) => f.forkPoint === snapshotId);
|
|
142
|
-
if (!fork)
|
|
143
|
-
return null;
|
|
144
|
-
return {
|
|
145
|
-
abandoned: fork.abandonedBranch.timelineId,
|
|
146
|
-
created: fork.newBranch.timelineId,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Resets the timeline state. Primarily for testing.
|
|
151
|
-
*/
|
|
152
|
-
export function resetTimelineForks(unerrDir) {
|
|
153
|
-
const defaultState = {
|
|
154
|
-
activeTimeline: 0,
|
|
155
|
-
forks: [],
|
|
156
|
-
};
|
|
157
|
-
saveState(unerrDir, defaultState);
|
|
158
|
-
log.info("Timeline fork state reset");
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Compacts the fork history by removing forks older than the given cutoff.
|
|
162
|
-
*/
|
|
163
|
-
export function compactForkHistory(unerrDir, maxAge) {
|
|
164
|
-
const state = loadState(unerrDir);
|
|
165
|
-
const cutoff = Date.now() - maxAge;
|
|
166
|
-
const originalCount = state.forks.length;
|
|
167
|
-
state.forks = state.forks.filter((f) => {
|
|
168
|
-
const forkTs = new Date(f.newBranch.startedAt).getTime();
|
|
169
|
-
return forkTs >= cutoff;
|
|
170
|
-
});
|
|
171
|
-
const removed = originalCount - state.forks.length;
|
|
172
|
-
if (removed > 0) {
|
|
173
|
-
saveState(unerrDir, state);
|
|
174
|
-
log.info(`Compacted ${removed} old forks`);
|
|
175
|
-
}
|
|
176
|
-
return removed;
|
|
177
|
-
}
|
|
178
|
-
// ── Persistence ─────────────────────────────────────────────────────
|
|
179
|
-
function getStatePath(unerrDir) {
|
|
180
|
-
return join(unerrDir, "state", "timeline-forks.json");
|
|
181
|
-
}
|
|
182
|
-
function ensureStateDir(unerrDir) {
|
|
183
|
-
const stateDir = join(unerrDir, "state");
|
|
184
|
-
if (!existsSync(stateDir)) {
|
|
185
|
-
mkdirSync(stateDir, { recursive: true });
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
function loadState(unerrDir) {
|
|
189
|
-
const path = getStatePath(unerrDir);
|
|
190
|
-
if (!existsSync(path)) {
|
|
191
|
-
return { activeTimeline: 0, forks: [] };
|
|
192
|
-
}
|
|
193
|
-
try {
|
|
194
|
-
const raw = readFileSync(path, "utf-8");
|
|
195
|
-
const parsed = JSON.parse(raw);
|
|
196
|
-
return {
|
|
197
|
-
activeTimeline: typeof parsed.activeTimeline === "number" ? parsed.activeTimeline : 0,
|
|
198
|
-
forks: Array.isArray(parsed.forks) ? parsed.forks : [],
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
log.warn("Failed to parse timeline-forks.json, starting fresh");
|
|
203
|
-
return { activeTimeline: 0, forks: [] };
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
function saveState(unerrDir, state) {
|
|
207
|
-
ensureStateDir(unerrDir);
|
|
208
|
-
const path = getStatePath(unerrDir);
|
|
209
|
-
writeFileSync(path, JSON.stringify(state, null, 2), "utf-8");
|
|
210
|
-
}
|
|
211
|
-
function deduplicateStrings(arr) {
|
|
212
|
-
return Array.from(new Set(arr));
|
|
213
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint 10.3: Timeline View — chronological ledger entries per branch.
|
|
3
|
-
*
|
|
4
|
-
* Provides `unerr_get_timeline` MCP tool: returns ledger entries
|
|
5
|
-
* grouped by branch with timeline branch counter for rewind context.
|
|
6
|
-
*
|
|
7
|
-
* Design authority: Phase 5.5 §1.5 (Timeline View)
|
|
8
|
-
*/
|
|
9
|
-
/** stderr logger */
|
|
10
|
-
const _log = {
|
|
11
|
-
info: (msg) => process.stderr.write(`[unerr:timeline] ${msg}\n`),
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* Build a timeline view from the shadow ledger.
|
|
15
|
-
*/
|
|
16
|
-
export function getTimeline(ledger, currentBranch, timelineBranch, opts = {}) {
|
|
17
|
-
const limit = opts.limit ?? 50;
|
|
18
|
-
const branch = opts.branch ?? currentBranch;
|
|
19
|
-
// Read all entries from buffer (fast path) or file (full history)
|
|
20
|
-
let entries;
|
|
21
|
-
if (limit <= 100) {
|
|
22
|
-
entries = ledger.getRecentEntries(100);
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
entries = ledger.readAllEntries();
|
|
26
|
-
}
|
|
27
|
-
// Apply filters
|
|
28
|
-
let filtered = entries.filter((e) => e.branch === branch);
|
|
29
|
-
if (opts.tool) {
|
|
30
|
-
filtered = filtered.filter((e) => e.tool === opts.tool);
|
|
31
|
-
}
|
|
32
|
-
if (opts.rootsOnly) {
|
|
33
|
-
filtered = filtered.filter((e) => e.correlation_id === null);
|
|
34
|
-
}
|
|
35
|
-
if (opts.after) {
|
|
36
|
-
const afterMs = new Date(opts.after).getTime();
|
|
37
|
-
filtered = filtered.filter((e) => new Date(e.ts).getTime() > afterMs);
|
|
38
|
-
}
|
|
39
|
-
if (opts.before) {
|
|
40
|
-
const beforeMs = new Date(opts.before).getTime();
|
|
41
|
-
filtered = filtered.filter((e) => new Date(e.ts).getTime() < beforeMs);
|
|
42
|
-
}
|
|
43
|
-
const totalCount = filtered.length;
|
|
44
|
-
// Most recent first, then apply limit
|
|
45
|
-
filtered.sort((a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime());
|
|
46
|
-
const limited = filtered.slice(0, limit);
|
|
47
|
-
// Map to timeline entries
|
|
48
|
-
const timelineEntries = limited.map((e) => ({
|
|
49
|
-
id: e.id,
|
|
50
|
-
timestamp: e.ts,
|
|
51
|
-
tool: e.tool,
|
|
52
|
-
argsSummary: e.args_summary,
|
|
53
|
-
resultSummary: e.result_summary,
|
|
54
|
-
branch: e.branch,
|
|
55
|
-
headSha: e.head_sha,
|
|
56
|
-
correlationId: e.correlation_id,
|
|
57
|
-
commitSha: e.commit_sha,
|
|
58
|
-
planSummary: e.plan_summary,
|
|
59
|
-
changeType: e.change_type,
|
|
60
|
-
featureArea: e.feature_area,
|
|
61
|
-
}));
|
|
62
|
-
return {
|
|
63
|
-
entries: timelineEntries,
|
|
64
|
-
branch,
|
|
65
|
-
timelineBranch,
|
|
66
|
-
totalCount,
|
|
67
|
-
sessionId: ledger.getSessionId(),
|
|
68
|
-
};
|
|
69
|
-
}
|