@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,204 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exploration Cost Estimator — counterfactual token cost analysis.
|
|
3
|
-
*
|
|
4
|
-
* Estimates what an AI agent WOULD have spent in tokens to gather the same
|
|
5
|
-
* information without the graph intelligence layer. This quantifies the
|
|
6
|
-
* value of graph-backed tools vs naive file exploration.
|
|
7
|
-
*
|
|
8
|
-
* Counterfactual baselines (tokens the agent would consume without graph):
|
|
9
|
-
* - blast_radius: 500 tokens/file × caller count (read each caller file)
|
|
10
|
-
* - find_callers: grep output (~200 tokens) + read each file (~500 tokens)
|
|
11
|
-
* - show_community: ls (~50 tokens) + read 15 files (~500 tokens each)
|
|
12
|
-
* - search_entities: grep output (~200 tokens) + read 10 files (~500 tokens)
|
|
13
|
-
* - get_entity: read file (~500 tokens) + parse mentally (~100 tokens)
|
|
14
|
-
* - health_grade: read 20 files + compute manually
|
|
15
|
-
*
|
|
16
|
-
* All estimates are conservative (lower bound of actual cost).
|
|
17
|
-
*/
|
|
18
|
-
import { createModuleLogger } from "../utils/logger.js";
|
|
19
|
-
const log = createModuleLogger("exploration-cost");
|
|
20
|
-
const COUNTERFACTUAL_RULES = {
|
|
21
|
-
blast_radius: {
|
|
22
|
-
tokensPerFile: 500,
|
|
23
|
-
baseTokens: 100,
|
|
24
|
-
fileMultiplier: (resultSize) => Math.max(resultSize, 1),
|
|
25
|
-
method: "read each caller file + parse for references",
|
|
26
|
-
explain: (resultSize) => `Without graph: grep for symbol → read ${resultSize} caller files (~500 tok each) + parse references`,
|
|
27
|
-
},
|
|
28
|
-
find_callers: {
|
|
29
|
-
tokensPerFile: 500,
|
|
30
|
-
baseTokens: 200,
|
|
31
|
-
fileMultiplier: (resultSize) => Math.max(resultSize, 1),
|
|
32
|
-
method: "grep + read each matching file",
|
|
33
|
-
explain: (resultSize) => `Without graph: grep across codebase (~200 tok) → read ${resultSize} matching files (~500 tok each)`,
|
|
34
|
-
},
|
|
35
|
-
show_community: {
|
|
36
|
-
tokensPerFile: 500,
|
|
37
|
-
baseTokens: 50,
|
|
38
|
-
fileMultiplier: (_resultSize, entityCount) => Math.max(entityCount ?? 15, 5),
|
|
39
|
-
method: "ls directory + read community files",
|
|
40
|
-
explain: (_resultSize, entityCount) => `Without graph: ls directory (~50 tok) → read ${entityCount ?? 15} related files (~500 tok each)`,
|
|
41
|
-
},
|
|
42
|
-
search_entities: {
|
|
43
|
-
tokensPerFile: 500,
|
|
44
|
-
baseTokens: 200,
|
|
45
|
-
fileMultiplier: (resultSize) => Math.min(Math.max(resultSize, 1), 20),
|
|
46
|
-
method: "grep + read matching files",
|
|
47
|
-
explain: (resultSize) => `Without graph: grep codebase (~200 tok) → read ${Math.min(resultSize, 20)} result files (~500 tok each)`,
|
|
48
|
-
},
|
|
49
|
-
get_entity: {
|
|
50
|
-
tokensPerFile: 500,
|
|
51
|
-
baseTokens: 100,
|
|
52
|
-
fileMultiplier: () => 1,
|
|
53
|
-
method: "find file + read + parse",
|
|
54
|
-
explain: () => "Without graph: find file location (~100 tok) → read entire file (~500 tok)",
|
|
55
|
-
},
|
|
56
|
-
health_grade: {
|
|
57
|
-
tokensPerFile: 400,
|
|
58
|
-
baseTokens: 300,
|
|
59
|
-
fileMultiplier: (_resultSize, entityCount) => Math.max(entityCount ?? 20, 5),
|
|
60
|
-
method: "read multiple files + manual analysis",
|
|
61
|
-
explain: (_resultSize, entityCount) => `Without graph: read ${entityCount ?? 20} files (~400 tok each) + manual complexity analysis (~300 tok)`,
|
|
62
|
-
},
|
|
63
|
-
show_conventions: {
|
|
64
|
-
tokensPerFile: 300,
|
|
65
|
-
baseTokens: 500,
|
|
66
|
-
fileMultiplier: (_resultSize, entityCount) => Math.max(entityCount ?? 10, 3),
|
|
67
|
-
method: "read sample files + infer patterns",
|
|
68
|
-
explain: (_resultSize, entityCount) => `Without graph: read ${entityCount ?? 10} sample files (~300 tok each) + infer conventions (~500 tok)`,
|
|
69
|
-
},
|
|
70
|
-
risk_assessment: {
|
|
71
|
-
tokensPerFile: 500,
|
|
72
|
-
baseTokens: 200,
|
|
73
|
-
fileMultiplier: (resultSize) => Math.max(resultSize, 3),
|
|
74
|
-
method: "read callers + analyze dependencies manually",
|
|
75
|
-
explain: (resultSize) => `Without graph: read ${Math.max(resultSize, 3)} dependency files (~500 tok each) + manual risk analysis`,
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
const DEFAULT_RULE = {
|
|
79
|
-
tokensPerFile: 400,
|
|
80
|
-
baseTokens: 150,
|
|
81
|
-
fileMultiplier: (resultSize) => Math.max(Math.ceil(resultSize / 2), 1),
|
|
82
|
-
method: "generic file exploration",
|
|
83
|
-
explain: (resultSize) => `Without graph: explore ~${Math.max(Math.ceil(resultSize / 2), 1)} files (~400 tok each)`,
|
|
84
|
-
};
|
|
85
|
-
/**
|
|
86
|
-
* Maps actual MCP tool names to the underlying counterfactual rule key.
|
|
87
|
-
* Without this layer, every modern tool name (`get_references`, `search_code`,
|
|
88
|
-
* etc.) falls through to DEFAULT_RULE and the specialized cost models above
|
|
89
|
-
* become dead code.
|
|
90
|
-
*/
|
|
91
|
-
const TOOL_ALIAS = {
|
|
92
|
-
// Reference tools — same profile as find_callers (grep + read each match)
|
|
93
|
-
get_references: "find_callers",
|
|
94
|
-
get_callers: "find_callers",
|
|
95
|
-
get_callees: "find_callers",
|
|
96
|
-
get_imports: "find_callers",
|
|
97
|
-
get_test_coverage: "find_callers",
|
|
98
|
-
// Search — same profile as search_entities (grep across project + read top hits)
|
|
99
|
-
search_code: "search_entities",
|
|
100
|
-
// Single-entity reads — same profile as get_entity (find file + read it)
|
|
101
|
-
get_function: "get_entity",
|
|
102
|
-
get_class: "get_entity",
|
|
103
|
-
get_file: "get_entity",
|
|
104
|
-
// file_outline/file_read NOT aliased: get_entity assumes ONE returned entity,
|
|
105
|
-
// but file_outline returns N entities and file_read returns a body of content.
|
|
106
|
-
// Inheriting get_entity's cost curve produces negative savings (saved=-650 for
|
|
107
|
-
// a typical 15-entity outline) which suppresses the token-flow event.
|
|
108
|
-
// Letting them fall through to DEFAULT_RULE applies fileMultiplier=ceil(resultSize/2)
|
|
109
|
-
// which yields positive savings.
|
|
110
|
-
// Conventions
|
|
111
|
-
get_conventions: "show_conventions",
|
|
112
|
-
// Risk / structural — read deps + analyze manually
|
|
113
|
-
get_critical_nodes: "risk_assessment",
|
|
114
|
-
get_cross_boundary_links: "risk_assessment",
|
|
115
|
-
file_connections: "risk_assessment",
|
|
116
|
-
// Project-wide stats — counterfactual is reading many files to compute
|
|
117
|
-
get_project_stats: "health_grade",
|
|
118
|
-
};
|
|
119
|
-
function resolveQueryType(queryType) {
|
|
120
|
-
return TOOL_ALIAS[queryType] ?? queryType;
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Estimates the counterfactual token cost for a single query.
|
|
124
|
-
*
|
|
125
|
-
* @param queryType - the tool/query name (e.g. "blast_radius", "find_callers",
|
|
126
|
-
* or an actual MCP tool name like "get_references" — aliases are resolved
|
|
127
|
-
* to the matching rule)
|
|
128
|
-
* @param resultSize - number of results returned (entities, callers, files, etc.)
|
|
129
|
-
* @param entityCount - optional total entities involved (for community/health queries)
|
|
130
|
-
*/
|
|
131
|
-
export function estimateExplorationCost(queryType, resultSize, entityCount) {
|
|
132
|
-
const ruleKey = resolveQueryType(queryType);
|
|
133
|
-
const rule = COUNTERFACTUAL_RULES[ruleKey] ?? DEFAULT_RULE;
|
|
134
|
-
const fileCount = rule.fileMultiplier(resultSize, entityCount);
|
|
135
|
-
const tokensWithout = rule.baseTokens + fileCount * rule.tokensPerFile;
|
|
136
|
-
const tokensUsed = estimateGraphQueryTokens(ruleKey, resultSize);
|
|
137
|
-
return {
|
|
138
|
-
queryType,
|
|
139
|
-
tokensUsed,
|
|
140
|
-
tokensWithout,
|
|
141
|
-
counterfactualMethod: rule.method,
|
|
142
|
-
explanation: rule.explain(resultSize, entityCount),
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Creates a session-scoped accumulator that tracks cumulative exploration savings.
|
|
147
|
-
*/
|
|
148
|
-
export function createExplorationAccumulator() {
|
|
149
|
-
const history = [];
|
|
150
|
-
function record(estimate) {
|
|
151
|
-
history.push(estimate);
|
|
152
|
-
}
|
|
153
|
-
function getTotal() {
|
|
154
|
-
let totalUsed = 0;
|
|
155
|
-
let totalWithout = 0;
|
|
156
|
-
for (const est of history) {
|
|
157
|
-
totalUsed += est.tokensUsed;
|
|
158
|
-
totalWithout += est.tokensWithout;
|
|
159
|
-
}
|
|
160
|
-
const saved = totalWithout - totalUsed;
|
|
161
|
-
const ratio = totalWithout > 0 ? totalUsed / totalWithout : 1;
|
|
162
|
-
return {
|
|
163
|
-
saved,
|
|
164
|
-
without: totalWithout,
|
|
165
|
-
ratio: Math.round(ratio * 1000) / 1000,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
function getHistory() {
|
|
169
|
-
return [...history];
|
|
170
|
-
}
|
|
171
|
-
function getBreakdown() {
|
|
172
|
-
const breakdown = new Map();
|
|
173
|
-
for (const est of history) {
|
|
174
|
-
const existing = breakdown.get(est.queryType) ?? {
|
|
175
|
-
saved: 0,
|
|
176
|
-
count: 0,
|
|
177
|
-
};
|
|
178
|
-
existing.saved += est.tokensWithout - est.tokensUsed;
|
|
179
|
-
existing.count += 1;
|
|
180
|
-
breakdown.set(est.queryType, existing);
|
|
181
|
-
}
|
|
182
|
-
return breakdown;
|
|
183
|
-
}
|
|
184
|
-
return { record, getTotal, getHistory, getBreakdown };
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Estimates tokens consumed by the graph-backed query itself.
|
|
188
|
-
* Graph responses are compact structured JSON — much smaller than raw file reads.
|
|
189
|
-
*/
|
|
190
|
-
function estimateGraphQueryTokens(queryType, resultSize) {
|
|
191
|
-
const perResultTokens = {
|
|
192
|
-
blast_radius: 30,
|
|
193
|
-
find_callers: 25,
|
|
194
|
-
show_community: 20,
|
|
195
|
-
search_entities: 35,
|
|
196
|
-
get_entity: 80,
|
|
197
|
-
health_grade: 50,
|
|
198
|
-
show_conventions: 40,
|
|
199
|
-
risk_assessment: 45,
|
|
200
|
-
};
|
|
201
|
-
const baseTokens = 50;
|
|
202
|
-
const perResult = perResultTokens[queryType] ?? 30;
|
|
203
|
-
return baseTokens + resultSize * perResult;
|
|
204
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session Context Enhancement — tracks exploration patterns and predicts next queries.
|
|
3
|
-
*
|
|
4
|
-
* S.5: Observes which files/entities the agent queries and uses the pattern
|
|
5
|
-
* to predict what it will need next. Enables pre-warming the subgraph cache.
|
|
6
|
-
*/
|
|
7
|
-
export function createSessionContext() {
|
|
8
|
-
const directories = new Map();
|
|
9
|
-
const entities = new Map();
|
|
10
|
-
const recentFiles = [];
|
|
11
|
-
let queryCount = 0;
|
|
12
|
-
function recordQuery(filePath, entityKey) {
|
|
13
|
-
queryCount++;
|
|
14
|
-
recentFiles.push(filePath);
|
|
15
|
-
if (recentFiles.length > 50)
|
|
16
|
-
recentFiles.shift();
|
|
17
|
-
const dir = filePath.split("/").slice(0, -1).join("/");
|
|
18
|
-
if (dir)
|
|
19
|
-
directories.set(dir, (directories.get(dir) ?? 0) + 1);
|
|
20
|
-
if (entityKey)
|
|
21
|
-
entities.set(entityKey, (entities.get(entityKey) ?? 0) + 1);
|
|
22
|
-
}
|
|
23
|
-
function getPattern() {
|
|
24
|
-
return { directories, entities, recentFiles: [...recentFiles], queryCount };
|
|
25
|
-
}
|
|
26
|
-
function predictNextFiles(limit = 5) {
|
|
27
|
-
const activeDir = getActiveDirectory();
|
|
28
|
-
if (!activeDir)
|
|
29
|
-
return [];
|
|
30
|
-
const dirFiles = recentFiles.filter((f) => f.startsWith(activeDir));
|
|
31
|
-
const unique = [...new Set(dirFiles)];
|
|
32
|
-
return unique.slice(-limit);
|
|
33
|
-
}
|
|
34
|
-
function getActiveDirectory() {
|
|
35
|
-
if (directories.size === 0)
|
|
36
|
-
return null;
|
|
37
|
-
const recent = recentFiles.slice(-5);
|
|
38
|
-
const recentDirs = new Map();
|
|
39
|
-
for (const f of recent) {
|
|
40
|
-
const dir = f.split("/").slice(0, -1).join("/");
|
|
41
|
-
if (dir)
|
|
42
|
-
recentDirs.set(dir, (recentDirs.get(dir) ?? 0) + 1);
|
|
43
|
-
}
|
|
44
|
-
if (recentDirs.size === 0)
|
|
45
|
-
return null;
|
|
46
|
-
return [...recentDirs.entries()].sort((a, b) => b[1] - a[1])[0][0];
|
|
47
|
-
}
|
|
48
|
-
function reset() {
|
|
49
|
-
directories.clear();
|
|
50
|
-
entities.clear();
|
|
51
|
-
recentFiles.length = 0;
|
|
52
|
-
queryCount = 0;
|
|
53
|
-
}
|
|
54
|
-
return {
|
|
55
|
-
recordQuery,
|
|
56
|
-
getPattern,
|
|
57
|
-
predictNextFiles,
|
|
58
|
-
getActiveDirectory,
|
|
59
|
-
reset,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fact Generation Pipeline — automatically generates temporal facts from 4 algorithmic sources.
|
|
3
|
-
*
|
|
4
|
-
* Layer 9 PI-5: Runs in daemon mode when:
|
|
5
|
-
* 1. A session summary arrives in .unerr/sessions/
|
|
6
|
-
* 2. A full reindex completes (convention detection)
|
|
7
|
-
* 3. A revert/rewind is detected (negative knowledge)
|
|
8
|
-
* 4. A 24h survival window closes (causal bridge)
|
|
9
|
-
*
|
|
10
|
-
* All sources are purely algorithmic — zero LLM dependency.
|
|
11
|
-
* Source #1 (Agent Explicit) is handled by the `record_fact` tool in PI-2.
|
|
12
|
-
*
|
|
13
|
-
* Pipeline flow:
|
|
14
|
-
* Source Event → Candidate Fact → Dedup Check → Existing?
|
|
15
|
-
* YES → Reinforce (bump count, update timestamp)
|
|
16
|
-
* NO → Check contradictions → Create with base_confidence
|
|
17
|
-
*/
|
|
18
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
19
|
-
import { join } from "node:path";
|
|
20
|
-
// ── Pipeline Orchestrator ────────────────────────────────────────────
|
|
21
|
-
/**
|
|
22
|
-
* Run the full fact generation pipeline.
|
|
23
|
-
* Called by the daemon periodically or on specific triggers.
|
|
24
|
-
*/
|
|
25
|
-
export async function runFactGenerationPipeline(factStore, unerrDir) {
|
|
26
|
-
const results = [];
|
|
27
|
-
try {
|
|
28
|
-
const sessionResult = await generateFromSessionAnalysis(factStore, unerrDir);
|
|
29
|
-
if (sessionResult)
|
|
30
|
-
results.push(sessionResult);
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
// Non-critical — session analysis failure doesn't block other sources
|
|
34
|
-
}
|
|
35
|
-
return results;
|
|
36
|
-
}
|
|
37
|
-
// ── Source 2: Convention Detector → Semantic Facts ────────────────────
|
|
38
|
-
/**
|
|
39
|
-
* Generate semantic facts from detected conventions.
|
|
40
|
-
* Trigger: after full reindex completes.
|
|
41
|
-
*
|
|
42
|
-
* Conventions with >70% adherence rate become facts.
|
|
43
|
-
* Already-existing facts get reinforced; new ones created.
|
|
44
|
-
*/
|
|
45
|
-
export async function generateFromConventions(factStore, conventions) {
|
|
46
|
-
let created = 0;
|
|
47
|
-
let reinforced = 0;
|
|
48
|
-
const details = [];
|
|
49
|
-
const MIN_CONFIDENCE = 0.7;
|
|
50
|
-
const MIN_FREQUENCY = 5;
|
|
51
|
-
for (const conv of conventions) {
|
|
52
|
-
if (conv.confidence < MIN_CONFIDENCE)
|
|
53
|
-
continue;
|
|
54
|
-
if (conv.frequency < MIN_FREQUENCY)
|
|
55
|
-
continue;
|
|
56
|
-
const content = formatConventionFact(conv);
|
|
57
|
-
if (content.length > 280)
|
|
58
|
-
continue;
|
|
59
|
-
const input = {
|
|
60
|
-
fact_type: "semantic",
|
|
61
|
-
scope: inferScopeFromConvention(conv),
|
|
62
|
-
subject: conv.name,
|
|
63
|
-
content,
|
|
64
|
-
source: "convention_detector",
|
|
65
|
-
base_confidence: Math.min(0.9, conv.confidence),
|
|
66
|
-
};
|
|
67
|
-
const factId = await factStore.createFact(input);
|
|
68
|
-
if (factId) {
|
|
69
|
-
const isNew = !details.some((d) => d.includes(conv.name));
|
|
70
|
-
if (isNew) {
|
|
71
|
-
created++;
|
|
72
|
-
details.push(`[convention] ${conv.name}: ${content.slice(0, 80)}`);
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
reinforced++;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return { created, reinforced, source: "convention_detector", details };
|
|
80
|
-
}
|
|
81
|
-
// ── Source 3: Negative Knowledge → Negative Facts ────────────────────
|
|
82
|
-
/**
|
|
83
|
-
* Generate negative facts from revert/rewind events.
|
|
84
|
-
* Trigger: revert detected by causal bridge or ledger analysis.
|
|
85
|
-
*
|
|
86
|
-
* Each correction entry becomes a "don't do X" fact with high confidence.
|
|
87
|
-
*/
|
|
88
|
-
export async function generateFromNegativeKnowledge(factStore, corrections) {
|
|
89
|
-
let created = 0;
|
|
90
|
-
const reinforced = 0;
|
|
91
|
-
const details = [];
|
|
92
|
-
for (const correction of corrections) {
|
|
93
|
-
const content = correction.reason.slice(0, 280);
|
|
94
|
-
const input = {
|
|
95
|
-
fact_type: "negative",
|
|
96
|
-
scope: correction.entityKey,
|
|
97
|
-
subject: correction.entityKey,
|
|
98
|
-
content,
|
|
99
|
-
source: "negative_knowledge",
|
|
100
|
-
base_confidence: 0.9,
|
|
101
|
-
};
|
|
102
|
-
const factId = await factStore.createFact(input);
|
|
103
|
-
if (factId) {
|
|
104
|
-
created++;
|
|
105
|
-
details.push(`[negative] ${correction.entityKey}: ${content.slice(0, 60)}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return { created, reinforced, source: "negative_knowledge", details };
|
|
109
|
-
}
|
|
110
|
-
// ── Source 4: Causal Bridge → Episodic Facts ─────────────────────────
|
|
111
|
-
/**
|
|
112
|
-
* Generate episodic facts from 24h survival analysis.
|
|
113
|
-
* Trigger: 24h after a session ends, check if changes survived.
|
|
114
|
-
*
|
|
115
|
-
* Survived changes → episodic "survived" fact (confidence 1.0, never decays).
|
|
116
|
-
* Reverted changes → triggers negative knowledge pipeline.
|
|
117
|
-
*/
|
|
118
|
-
export async function generateFromCausalBridge(factStore, events) {
|
|
119
|
-
let created = 0;
|
|
120
|
-
const reinforced = 0;
|
|
121
|
-
const details = [];
|
|
122
|
-
for (const event of events) {
|
|
123
|
-
if (event.action === "survived") {
|
|
124
|
-
const content = `Change to ${event.entity_key} on ${new Date(event.timestamp).toISOString().split("T")[0]} survived — shipped to ${event.branch}`;
|
|
125
|
-
const input = {
|
|
126
|
-
fact_type: "episodic",
|
|
127
|
-
scope: event.entity_key,
|
|
128
|
-
subject: event.entity_key,
|
|
129
|
-
content: content.slice(0, 280),
|
|
130
|
-
source: "causal_bridge",
|
|
131
|
-
base_confidence: 1.0,
|
|
132
|
-
};
|
|
133
|
-
const factId = await factStore.createFact(input);
|
|
134
|
-
if (factId) {
|
|
135
|
-
created++;
|
|
136
|
-
details.push(`[episodic:survived] ${event.entity_key}`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
else if (event.action === "reverted") {
|
|
140
|
-
const content = `Change to ${event.entity_key} was reverted within 24h — approach was incorrect`;
|
|
141
|
-
const input = {
|
|
142
|
-
fact_type: "negative",
|
|
143
|
-
scope: event.entity_key,
|
|
144
|
-
subject: event.entity_key,
|
|
145
|
-
content: content.slice(0, 280),
|
|
146
|
-
source: "causal_bridge",
|
|
147
|
-
base_confidence: 0.85,
|
|
148
|
-
};
|
|
149
|
-
const factId = await factStore.createFact(input);
|
|
150
|
-
if (factId) {
|
|
151
|
-
created++;
|
|
152
|
-
details.push(`[negative:reverted] ${event.entity_key}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return { created, reinforced, source: "causal_bridge", details };
|
|
157
|
-
}
|
|
158
|
-
// ── Source 5: Session Analysis → Procedural Facts ────────────────────
|
|
159
|
-
/**
|
|
160
|
-
* Generate procedural facts from session summary aggregation.
|
|
161
|
-
* Trigger: new session summary received.
|
|
162
|
-
*
|
|
163
|
-
* Detects patterns across sessions:
|
|
164
|
-
* - Hot files (read in >50% of sessions)
|
|
165
|
-
* - Common tool sequences
|
|
166
|
-
* - High-revert files (files that frequently get reverted)
|
|
167
|
-
*/
|
|
168
|
-
export async function generateFromSessionAnalysis(factStore, unerrDir) {
|
|
169
|
-
let created = 0;
|
|
170
|
-
const reinforced = 0;
|
|
171
|
-
const details = [];
|
|
172
|
-
const summaries = loadRecentSessions(unerrDir, 20);
|
|
173
|
-
if (summaries.length < 3) {
|
|
174
|
-
return {
|
|
175
|
-
created: 0,
|
|
176
|
-
reinforced: 0,
|
|
177
|
-
source: "session_analysis",
|
|
178
|
-
details: [],
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
const hotFiles = detectHotFiles(summaries);
|
|
182
|
-
for (const [file, frequency] of hotFiles) {
|
|
183
|
-
const pct = Math.round(frequency * 100);
|
|
184
|
-
const content = `${file} is accessed in ${pct}% of sessions (hot file)`;
|
|
185
|
-
const confidence = Math.min(0.85, 0.4 + frequency * 0.5);
|
|
186
|
-
const input = {
|
|
187
|
-
fact_type: "procedural",
|
|
188
|
-
scope: file,
|
|
189
|
-
subject: "hot-file",
|
|
190
|
-
content,
|
|
191
|
-
source: "session_analysis",
|
|
192
|
-
base_confidence: confidence,
|
|
193
|
-
};
|
|
194
|
-
const factId = await factStore.createFact(input);
|
|
195
|
-
if (factId) {
|
|
196
|
-
created++;
|
|
197
|
-
details.push(`[procedural:hot] ${file} (${pct}%)`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
const highRevertFiles = detectHighRevertFiles(summaries);
|
|
201
|
-
for (const [file, revertRate] of highRevertFiles) {
|
|
202
|
-
const pct = Math.round(revertRate * 100);
|
|
203
|
-
const content = `${file} has a ${pct}% revert rate across sessions — changes here are fragile`;
|
|
204
|
-
const input = {
|
|
205
|
-
fact_type: "negative",
|
|
206
|
-
scope: file,
|
|
207
|
-
subject: file,
|
|
208
|
-
content: content.slice(0, 280),
|
|
209
|
-
source: "session_analysis",
|
|
210
|
-
base_confidence: Math.min(0.85, 0.5 + revertRate * 0.4),
|
|
211
|
-
};
|
|
212
|
-
const factId = await factStore.createFact(input);
|
|
213
|
-
if (factId) {
|
|
214
|
-
created++;
|
|
215
|
-
details.push(`[negative:fragile] ${file} (${pct}% revert rate)`);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return { created, reinforced, source: "session_analysis", details };
|
|
219
|
-
}
|
|
220
|
-
// ── Internal Helpers ─────────────────────────────────────────────────
|
|
221
|
-
function loadRecentSessions(unerrDir, limit) {
|
|
222
|
-
const sessionsDir = join(unerrDir, "sessions");
|
|
223
|
-
if (!existsSync(sessionsDir))
|
|
224
|
-
return [];
|
|
225
|
-
try {
|
|
226
|
-
const files = readdirSync(sessionsDir)
|
|
227
|
-
.filter((f) => f.endsWith(".jsonl"))
|
|
228
|
-
.sort()
|
|
229
|
-
.slice(-limit);
|
|
230
|
-
const summaries = [];
|
|
231
|
-
for (const file of files) {
|
|
232
|
-
try {
|
|
233
|
-
const content = readFileSync(join(sessionsDir, file), "utf-8");
|
|
234
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
235
|
-
const lastLine = lines[lines.length - 1];
|
|
236
|
-
if (lastLine) {
|
|
237
|
-
summaries.push(JSON.parse(lastLine));
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
241
|
-
// Skip unreadable session files
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return summaries;
|
|
245
|
-
}
|
|
246
|
-
catch {
|
|
247
|
-
return [];
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Detect hot files — files that appear in >50% of sessions.
|
|
252
|
-
* Returns [file, frequency] pairs sorted by frequency descending.
|
|
253
|
-
*/
|
|
254
|
-
function detectHotFiles(summaries) {
|
|
255
|
-
const fileCounts = new Map();
|
|
256
|
-
const total = summaries.length;
|
|
257
|
-
for (const session of summaries) {
|
|
258
|
-
const seen = new Set();
|
|
259
|
-
for (const file of session.files_modified) {
|
|
260
|
-
if (!seen.has(file)) {
|
|
261
|
-
seen.add(file);
|
|
262
|
-
fileCounts.set(file, (fileCounts.get(file) ?? 0) + 1);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
const HOT_THRESHOLD = 0.5;
|
|
267
|
-
return [...fileCounts.entries()]
|
|
268
|
-
.map(([file, count]) => [file, count / total])
|
|
269
|
-
.filter(([_, freq]) => freq >= HOT_THRESHOLD)
|
|
270
|
-
.sort((a, b) => b[1] - a[1])
|
|
271
|
-
.slice(0, 10);
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Detect files with high revert rates across sessions.
|
|
275
|
-
* A file modified in session with revert_count > 0 = potential fragile file.
|
|
276
|
-
*/
|
|
277
|
-
function detectHighRevertFiles(summaries) {
|
|
278
|
-
const fileModifiedCount = new Map();
|
|
279
|
-
const fileRevertCount = new Map();
|
|
280
|
-
for (const session of summaries) {
|
|
281
|
-
for (const file of session.files_modified) {
|
|
282
|
-
fileModifiedCount.set(file, (fileModifiedCount.get(file) ?? 0) + 1);
|
|
283
|
-
if (session.revert_count > 0) {
|
|
284
|
-
fileRevertCount.set(file, (fileRevertCount.get(file) ?? 0) + 1);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
const HIGH_REVERT_THRESHOLD = 0.4;
|
|
289
|
-
const MIN_MODIFICATIONS = 3;
|
|
290
|
-
return [...fileModifiedCount.entries()]
|
|
291
|
-
.filter(([_, count]) => count >= MIN_MODIFICATIONS)
|
|
292
|
-
.map(([file, count]) => {
|
|
293
|
-
const reverts = fileRevertCount.get(file) ?? 0;
|
|
294
|
-
return [file, reverts / count];
|
|
295
|
-
})
|
|
296
|
-
.filter(([_, rate]) => rate >= HIGH_REVERT_THRESHOLD)
|
|
297
|
-
.sort((a, b) => b[1] - a[1])
|
|
298
|
-
.slice(0, 5);
|
|
299
|
-
}
|
|
300
|
-
function formatConventionFact(conv) {
|
|
301
|
-
const pct = Math.round(conv.confidence * 100);
|
|
302
|
-
switch (conv.kind) {
|
|
303
|
-
case "naming":
|
|
304
|
-
return `Naming convention: ${conv.name} (${pct}% confidence, ${conv.frequency} entities)`;
|
|
305
|
-
case "structure":
|
|
306
|
-
return `Structure pattern: ${conv.name} (${pct}% confidence)`;
|
|
307
|
-
case "import_direction":
|
|
308
|
-
return `Import convention: ${conv.name} (${pct}% confidence)`;
|
|
309
|
-
default:
|
|
310
|
-
return `Convention: ${conv.name} (${pct}% confidence)`;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
function inferScopeFromConvention(conv) {
|
|
314
|
-
if (conv.exemplarKeys.length > 0) {
|
|
315
|
-
const first = conv.exemplarKeys[0];
|
|
316
|
-
const parts = first.split("/");
|
|
317
|
-
if (parts.length >= 2) {
|
|
318
|
-
return parts.slice(0, 2).join("/");
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return "project";
|
|
322
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CozoDB Schema for facts.db — Temporal Intelligence (Layer 9).
|
|
3
|
-
*
|
|
4
|
-
* SEPARATE from graph.db (cozo-schema.ts). This schema manages:
|
|
5
|
-
* - facts: Temporal knowledge with decay model (procedural, semantic, negative, episodic)
|
|
6
|
-
* - entity_interactions: Cross-session entity access history
|
|
7
|
-
*
|
|
8
|
-
* Storage: `.unerr/facts.db` (SQLite-backed CozoDB, parallel to graph.db)
|
|
9
|
-
*
|
|
10
|
-
* Dual-mode access:
|
|
11
|
-
* - Daemon (unerr): read-write — generates facts, reinforces, prunes
|
|
12
|
-
* - Headless (--mcp): read-only + single atomic write via record_fact tool
|
|
13
|
-
*/
|
|
14
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
-
const FACTS_DB_FILENAME = "facts.db";
|
|
17
|
-
/**
|
|
18
|
-
* Open or create the facts.db CozoDB instance at `{projectRoot}/.unerr/facts.db`.
|
|
19
|
-
*/
|
|
20
|
-
export async function openFactsDb(projectRoot) {
|
|
21
|
-
const unerrDir = join(projectRoot, ".unerr");
|
|
22
|
-
mkdirSync(unerrDir, { recursive: true });
|
|
23
|
-
const dbPath = join(unerrDir, FACTS_DB_FILENAME);
|
|
24
|
-
const isNew = !existsSync(dbPath);
|
|
25
|
-
const cozoModule = await import("cozo-node");
|
|
26
|
-
const CozoDbConstructor = cozoModule.default
|
|
27
|
-
? cozoModule.default.CozoDb
|
|
28
|
-
: cozoModule.CozoDb;
|
|
29
|
-
const db = new CozoDbConstructor("sqlite", dbPath);
|
|
30
|
-
return { db, isNew, dbPath };
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Initialize facts.db schema. Creates only missing relations.
|
|
34
|
-
* Safe to call on both fresh and existing databases.
|
|
35
|
-
*/
|
|
36
|
-
export async function initFactsSchema(db) {
|
|
37
|
-
const existing = await getExistingRelations(db);
|
|
38
|
-
if (!existing.has("facts")) {
|
|
39
|
-
await db.run(`
|
|
40
|
-
:create facts {
|
|
41
|
-
fact_id: String
|
|
42
|
-
=>
|
|
43
|
-
fact_type: String,
|
|
44
|
-
scope: String,
|
|
45
|
-
subject: String,
|
|
46
|
-
content: String,
|
|
47
|
-
base_confidence: Float,
|
|
48
|
-
reinforcement_count: Int,
|
|
49
|
-
created_at: Float,
|
|
50
|
-
last_reinforced_at: Float,
|
|
51
|
-
last_contradicted_at: Float,
|
|
52
|
-
source: String,
|
|
53
|
-
evidence: String
|
|
54
|
-
}
|
|
55
|
-
`);
|
|
56
|
-
}
|
|
57
|
-
if (!existing.has("entity_interactions")) {
|
|
58
|
-
await db.run(`
|
|
59
|
-
:create entity_interactions {
|
|
60
|
-
entity_key: String,
|
|
61
|
-
session_id: String,
|
|
62
|
-
interaction_type: String,
|
|
63
|
-
timestamp: Float
|
|
64
|
-
=>
|
|
65
|
-
tool_name: String,
|
|
66
|
-
outcome: String
|
|
67
|
-
}
|
|
68
|
-
`);
|
|
69
|
-
}
|
|
70
|
-
if (!existing.has("signal_shows")) {
|
|
71
|
-
// Rotation persistence: how many times each signal/fact has been surfaced.
|
|
72
|
-
// KEY (signal_id, session_id) — each session writes only its own row, so
|
|
73
|
-
// parallel `unerr --mcp` instances in the same repo never contend on writes.
|
|
74
|
-
// Aggregate count = SUM(count) over all sessions; recency = MAX(last_shown_ms).
|
|
75
|
-
await db.run(`
|
|
76
|
-
:create signal_shows {
|
|
77
|
-
signal_id: String,
|
|
78
|
-
session_id: String
|
|
79
|
-
=>
|
|
80
|
-
scope: String,
|
|
81
|
-
count: Int,
|
|
82
|
-
last_shown_ms: Float
|
|
83
|
-
}
|
|
84
|
-
`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
async function getExistingRelations(db) {
|
|
88
|
-
const result = await db.run("::relations");
|
|
89
|
-
return new Set(result.rows.map((row) => row[0]));
|
|
90
|
-
}
|