@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,494 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Temporal Fact Store — CozoDB-backed project knowledge with decay model.
|
|
3
|
-
*
|
|
4
|
-
* Layer 9 core: stores facts (conventions, decisions, anti-patterns) that
|
|
5
|
-
* persist across sessions. Each fact decays over time unless reinforced.
|
|
6
|
-
*
|
|
7
|
-
* Decay formula (computed inline in Datalog at read time):
|
|
8
|
-
* effective_confidence = base_confidence * recency_factor * evidence_factor
|
|
9
|
-
* recency_factor = e^(-lambda * days_since_last_reinforcement)
|
|
10
|
-
* evidence_factor = min(1.0, reinforcement_count / 3.0)
|
|
11
|
-
*
|
|
12
|
-
* Decay rates (lambda) per fact type:
|
|
13
|
-
* procedural: 0.10 (fast — "how to" instructions go stale)
|
|
14
|
-
* semantic: 0.05 (medium — architectural knowledge)
|
|
15
|
-
* negative: 0.02 (slow — anti-patterns remain relevant)
|
|
16
|
-
* convention: 0.02 (slow — project standards persist like anti-patterns)
|
|
17
|
-
* episodic: 0.00 (never — immutable history)
|
|
18
|
-
*/
|
|
19
|
-
import { randomUUID } from "node:crypto";
|
|
20
|
-
import { initFactsSchema, openFactsDb } from "./facts-schema.js";
|
|
21
|
-
const DEFAULT_CONFIG = {
|
|
22
|
-
decay_rates: {
|
|
23
|
-
procedural: 0.1,
|
|
24
|
-
semantic: 0.05,
|
|
25
|
-
negative: 0.02,
|
|
26
|
-
episodic: 0.0,
|
|
27
|
-
convention: 0.02,
|
|
28
|
-
},
|
|
29
|
-
recall_threshold: 0.2,
|
|
30
|
-
max_facts_per_response: 5,
|
|
31
|
-
prune_threshold: 0.05,
|
|
32
|
-
};
|
|
33
|
-
const DEFAULT_CONFIDENCE = {
|
|
34
|
-
agent_explicit: 0.95,
|
|
35
|
-
negative_knowledge: 0.9,
|
|
36
|
-
causal_bridge: 1.0,
|
|
37
|
-
convention_detector: 0.75,
|
|
38
|
-
session_analysis: 0.6,
|
|
39
|
-
};
|
|
40
|
-
const MAX_CONTENT_LENGTH = 500;
|
|
41
|
-
/** Type-specific content limits — episodic needs space for what/why/how narratives. */
|
|
42
|
-
const TYPE_CONTENT_LIMITS = {
|
|
43
|
-
convention: 600,
|
|
44
|
-
semantic: 500,
|
|
45
|
-
episodic: 800,
|
|
46
|
-
procedural: 400,
|
|
47
|
-
negative: 400,
|
|
48
|
-
};
|
|
49
|
-
// ── TemporalFactStore ────────────────────────────────────────────────
|
|
50
|
-
export class TemporalFactStore {
|
|
51
|
-
db;
|
|
52
|
-
config;
|
|
53
|
-
constructor(db, config) {
|
|
54
|
-
this.db = db;
|
|
55
|
-
this.config = config;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Expose the underlying facts.db handle so cohabiting stores (e.g.
|
|
59
|
-
* SignalShowStore for rotation persistence) can share the connection
|
|
60
|
-
* instead of opening a second SQLite handle to the same file.
|
|
61
|
-
*/
|
|
62
|
-
getDb() {
|
|
63
|
-
return this.db;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Create and initialize a TemporalFactStore from a project root path.
|
|
67
|
-
* Opens facts.db (creating if needed) and ensures schema is current.
|
|
68
|
-
*/
|
|
69
|
-
static async create(projectRoot, config) {
|
|
70
|
-
const { db } = await openFactsDb(projectRoot);
|
|
71
|
-
await initFactsSchema(db);
|
|
72
|
-
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
73
|
-
if (config?.decay_rates) {
|
|
74
|
-
mergedConfig.decay_rates = {
|
|
75
|
-
...DEFAULT_CONFIG.decay_rates,
|
|
76
|
-
...config.decay_rates,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
return new TemporalFactStore(db, mergedConfig);
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Create a TemporalFactStore from an already-opened CozoDB instance.
|
|
83
|
-
* Used when the caller manages the database lifecycle.
|
|
84
|
-
*/
|
|
85
|
-
static fromDb(db, config) {
|
|
86
|
-
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
87
|
-
if (config?.decay_rates) {
|
|
88
|
-
mergedConfig.decay_rates = {
|
|
89
|
-
...DEFAULT_CONFIG.decay_rates,
|
|
90
|
-
...config.decay_rates,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
return new TemporalFactStore(db, mergedConfig);
|
|
94
|
-
}
|
|
95
|
-
// ── Write Operations ─────────────────────────────────────────────
|
|
96
|
-
/**
|
|
97
|
-
* Create a new fact or reinforce an existing one with matching (fact_type, scope, subject).
|
|
98
|
-
* Returns the fact_id (new or existing).
|
|
99
|
-
*/
|
|
100
|
-
async createFact(input) {
|
|
101
|
-
const limit = TYPE_CONTENT_LIMITS[input.fact_type] ?? MAX_CONTENT_LENGTH;
|
|
102
|
-
const content = input.content.slice(0, limit);
|
|
103
|
-
const now = Date.now();
|
|
104
|
-
const existing = await this.findDuplicate(input.fact_type, input.scope, input.subject);
|
|
105
|
-
if (existing) {
|
|
106
|
-
await this.reinforceFact(existing, {
|
|
107
|
-
session_id: "auto-dedup",
|
|
108
|
-
action: "reinforced",
|
|
109
|
-
timestamp: now,
|
|
110
|
-
});
|
|
111
|
-
return existing;
|
|
112
|
-
}
|
|
113
|
-
const factId = randomUUID();
|
|
114
|
-
const baseConfidence = input.base_confidence ?? DEFAULT_CONFIDENCE[input.source] ?? 0.8;
|
|
115
|
-
const evidence = [
|
|
116
|
-
{ session_id: "initial", action: "created", timestamp: now },
|
|
117
|
-
];
|
|
118
|
-
await this.db.run(`
|
|
119
|
-
?[fact_id, fact_type, scope, subject, content, base_confidence,
|
|
120
|
-
reinforcement_count, created_at, last_reinforced_at,
|
|
121
|
-
last_contradicted_at, source, evidence] <- [[
|
|
122
|
-
$fact_id, $fact_type, $scope, $subject, $content, $base_confidence,
|
|
123
|
-
1, $now, $now, 0.0, $source, $evidence
|
|
124
|
-
]]
|
|
125
|
-
:put facts {
|
|
126
|
-
fact_id => fact_type, scope, subject, content, base_confidence,
|
|
127
|
-
reinforcement_count, created_at, last_reinforced_at,
|
|
128
|
-
last_contradicted_at, source, evidence
|
|
129
|
-
}
|
|
130
|
-
`, {
|
|
131
|
-
fact_id: factId,
|
|
132
|
-
fact_type: input.fact_type,
|
|
133
|
-
scope: input.scope,
|
|
134
|
-
subject: input.subject,
|
|
135
|
-
content,
|
|
136
|
-
base_confidence: baseConfidence,
|
|
137
|
-
now,
|
|
138
|
-
source: input.source,
|
|
139
|
-
evidence: JSON.stringify(evidence),
|
|
140
|
-
});
|
|
141
|
-
return factId;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Reinforce an existing fact — increases reinforcement_count, updates timestamp.
|
|
145
|
-
*/
|
|
146
|
-
async reinforceFact(factId, evidence) {
|
|
147
|
-
const rows = await this.getRawFact(factId);
|
|
148
|
-
if (!rows)
|
|
149
|
-
return;
|
|
150
|
-
const existingEvidence = JSON.parse(rows.evidence);
|
|
151
|
-
existingEvidence.push(evidence);
|
|
152
|
-
const trimmedEvidence = existingEvidence.slice(-20);
|
|
153
|
-
await this.db.run(`
|
|
154
|
-
?[fact_id, fact_type, scope, subject, content, base_confidence,
|
|
155
|
-
reinforcement_count, created_at, last_reinforced_at,
|
|
156
|
-
last_contradicted_at, source, evidence] <- [[
|
|
157
|
-
$fact_id, $fact_type, $scope, $subject, $content, $base_confidence,
|
|
158
|
-
$reinforcement_count, $created_at, $now, $last_contradicted_at,
|
|
159
|
-
$source, $evidence
|
|
160
|
-
]]
|
|
161
|
-
:put facts {
|
|
162
|
-
fact_id => fact_type, scope, subject, content, base_confidence,
|
|
163
|
-
reinforcement_count, created_at, last_reinforced_at,
|
|
164
|
-
last_contradicted_at, source, evidence
|
|
165
|
-
}
|
|
166
|
-
`, {
|
|
167
|
-
fact_id: factId,
|
|
168
|
-
fact_type: rows.fact_type,
|
|
169
|
-
scope: rows.scope,
|
|
170
|
-
subject: rows.subject,
|
|
171
|
-
content: rows.content,
|
|
172
|
-
base_confidence: rows.base_confidence,
|
|
173
|
-
reinforcement_count: rows.reinforcement_count + 1,
|
|
174
|
-
created_at: rows.created_at,
|
|
175
|
-
now: Date.now(),
|
|
176
|
-
last_contradicted_at: rows.last_contradicted_at,
|
|
177
|
-
source: rows.source,
|
|
178
|
-
evidence: JSON.stringify(trimmedEvidence),
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Contradict a fact — halves its confidence.
|
|
183
|
-
*/
|
|
184
|
-
async contradictFact(factId, reason) {
|
|
185
|
-
const rows = await this.getRawFact(factId);
|
|
186
|
-
if (!rows)
|
|
187
|
-
return;
|
|
188
|
-
const now = Date.now();
|
|
189
|
-
const existingEvidence = JSON.parse(rows.evidence);
|
|
190
|
-
existingEvidence.push({
|
|
191
|
-
session_id: "contradiction",
|
|
192
|
-
action: "contradicted",
|
|
193
|
-
timestamp: now,
|
|
194
|
-
});
|
|
195
|
-
await this.db.run(`
|
|
196
|
-
?[fact_id, fact_type, scope, subject, content, base_confidence,
|
|
197
|
-
reinforcement_count, created_at, last_reinforced_at,
|
|
198
|
-
last_contradicted_at, source, evidence] <- [[
|
|
199
|
-
$fact_id, $fact_type, $scope, $subject, $content, $base_confidence,
|
|
200
|
-
$reinforcement_count, $created_at, $last_reinforced_at, $now,
|
|
201
|
-
$source, $evidence
|
|
202
|
-
]]
|
|
203
|
-
:put facts {
|
|
204
|
-
fact_id => fact_type, scope, subject, content, base_confidence,
|
|
205
|
-
reinforcement_count, created_at, last_reinforced_at,
|
|
206
|
-
last_contradicted_at, source, evidence
|
|
207
|
-
}
|
|
208
|
-
`, {
|
|
209
|
-
fact_id: factId,
|
|
210
|
-
fact_type: rows.fact_type,
|
|
211
|
-
scope: rows.scope,
|
|
212
|
-
subject: rows.subject,
|
|
213
|
-
content: rows.content,
|
|
214
|
-
base_confidence: rows.base_confidence * 0.5,
|
|
215
|
-
reinforcement_count: rows.reinforcement_count,
|
|
216
|
-
created_at: rows.created_at,
|
|
217
|
-
last_reinforced_at: rows.last_reinforced_at,
|
|
218
|
-
now,
|
|
219
|
-
source: rows.source,
|
|
220
|
-
evidence: JSON.stringify(existingEvidence.slice(-20)),
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
// ── Read Operations (with inline decay computation) ──────────────
|
|
224
|
-
/**
|
|
225
|
-
* Recall facts by scope with decay-adjusted confidence filtering.
|
|
226
|
-
*/
|
|
227
|
-
async recallByScope(scope, minConfidence) {
|
|
228
|
-
return this.runDecayQuery(`*facts{fact_id, fact_type, scope, subject, content,
|
|
229
|
-
base_confidence, reinforcement_count, created_at,
|
|
230
|
-
last_reinforced_at, last_contradicted_at, source, evidence},
|
|
231
|
-
scope == $filter_scope`, { filter_scope: scope }, minConfidence);
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Recall facts by subject (entity key or topic).
|
|
235
|
-
*/
|
|
236
|
-
async recallBySubject(subject, minConfidence) {
|
|
237
|
-
return this.runDecayQuery(`*facts{fact_id, fact_type, scope, subject, content,
|
|
238
|
-
base_confidence, reinforcement_count, created_at,
|
|
239
|
-
last_reinforced_at, last_contradicted_at, source, evidence},
|
|
240
|
-
subject == $filter_subject`, { filter_subject: subject }, minConfidence);
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Recall all negative facts (anti-patterns) — always relevant regardless of scope.
|
|
244
|
-
*/
|
|
245
|
-
async recallNegative(minConfidence) {
|
|
246
|
-
return this.runDecayQuery(`*facts{fact_id, fact_type, scope, subject, content,
|
|
247
|
-
base_confidence, reinforcement_count, created_at,
|
|
248
|
-
last_reinforced_at, last_contradicted_at, source, evidence},
|
|
249
|
-
fact_type == "negative"`, {}, minConfidence);
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
|
-
* Generate scope hierarchy from a file path for hierarchical matching.
|
|
253
|
-
* "src/proxy/proxy.ts" → ["src/proxy/proxy.ts", "src/proxy/", "src/", "project"]
|
|
254
|
-
*/
|
|
255
|
-
buildScopeHierarchy(filePath) {
|
|
256
|
-
const scopes = [filePath];
|
|
257
|
-
const parts = filePath.split("/");
|
|
258
|
-
// Build directory prefixes from deepest to shallowest
|
|
259
|
-
for (let i = parts.length - 1; i > 0; i--) {
|
|
260
|
-
scopes.push(`${parts.slice(0, i).join("/")}/`);
|
|
261
|
-
}
|
|
262
|
-
scopes.push("project");
|
|
263
|
-
return scopes;
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Recall facts by prefix hierarchy — facts scoped to a directory apply to all files in it.
|
|
267
|
-
* "src/proxy/proxy.ts" matches facts scoped to "src/proxy/proxy.ts", "src/proxy/", "src/", and "project".
|
|
268
|
-
*/
|
|
269
|
-
async recallByPrefix(filePath, minConfidence) {
|
|
270
|
-
const scopes = this.buildScopeHierarchy(filePath);
|
|
271
|
-
// Build CozoDB or-clause for all scope levels
|
|
272
|
-
const conditions = scopes.map((_, i) => `scope == $s${i}`).join(" or ");
|
|
273
|
-
const params = {};
|
|
274
|
-
for (let i = 0; i < scopes.length; i++) {
|
|
275
|
-
params[`s${i}`] = scopes[i];
|
|
276
|
-
}
|
|
277
|
-
return this.runDecayQuery(`*facts{fact_id, fact_type, scope, subject, content,
|
|
278
|
-
base_confidence, reinforcement_count, created_at,
|
|
279
|
-
last_reinforced_at, last_contradicted_at, source, evidence},
|
|
280
|
-
(${conditions})`, params, minConfidence);
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Recall facts relevant to a file — by hierarchical scope prefix OR entity keys within that file.
|
|
284
|
-
* This is the primary recall method used during tool responses.
|
|
285
|
-
*/
|
|
286
|
-
async recallForFile(filePath, entityKeys) {
|
|
287
|
-
const threshold = this.config.recall_threshold;
|
|
288
|
-
const maxFacts = this.config.max_facts_per_response;
|
|
289
|
-
// Use hierarchical prefix matching instead of exact scope
|
|
290
|
-
const scopeFacts = await this.recallByPrefix(filePath, threshold);
|
|
291
|
-
const entityFacts = [];
|
|
292
|
-
for (const key of entityKeys.slice(0, 10)) {
|
|
293
|
-
const facts = await this.recallBySubject(key, threshold);
|
|
294
|
-
for (const f of facts) {
|
|
295
|
-
if (!entityFacts.some((ef) => ef.fact_id === f.fact_id)) {
|
|
296
|
-
entityFacts.push(f);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
const projectNegative = await this.recallByScope("project", threshold);
|
|
301
|
-
const negFacts = projectNegative.filter((f) => f.fact_type === "negative");
|
|
302
|
-
const all = [...scopeFacts, ...entityFacts, ...negFacts];
|
|
303
|
-
const deduped = new Map();
|
|
304
|
-
for (const fact of all) {
|
|
305
|
-
const existing = deduped.get(fact.fact_id);
|
|
306
|
-
if (!existing ||
|
|
307
|
-
fact.effective_confidence > existing.effective_confidence) {
|
|
308
|
-
deduped.set(fact.fact_id, fact);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return [...deduped.values()]
|
|
312
|
-
.sort((a, b) => b.effective_confidence - a.effective_confidence)
|
|
313
|
-
.slice(0, maxFacts);
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Recall all facts regardless of scope (for dashboard listing).
|
|
317
|
-
*/
|
|
318
|
-
async recallAll(minConfidence) {
|
|
319
|
-
return this.runDecayQuery(`*facts{fact_id, fact_type, scope, subject, content,
|
|
320
|
-
base_confidence, reinforcement_count, created_at,
|
|
321
|
-
last_reinforced_at, last_contradicted_at, source, evidence}`, {}, minConfidence ?? 0);
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Recall facts with effective confidence in a given range (for decay visibility).
|
|
325
|
-
*/
|
|
326
|
-
async recallDecaying(minConf, maxConf) {
|
|
327
|
-
const all = await this.runDecayQuery(`*facts{fact_id, fact_type, scope, subject, content,
|
|
328
|
-
base_confidence, reinforcement_count, created_at,
|
|
329
|
-
last_reinforced_at, last_contradicted_at, source, evidence}`, {}, minConf);
|
|
330
|
-
return all.filter((f) => f.effective_confidence < maxConf);
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Get overall health metrics of the fact store.
|
|
334
|
-
*/
|
|
335
|
-
async getFactHealth() {
|
|
336
|
-
const allFacts = await this.runDecayQuery(`*facts{fact_id, fact_type, scope, subject, content,
|
|
337
|
-
base_confidence, reinforcement_count, created_at,
|
|
338
|
-
last_reinforced_at, last_contradicted_at, source, evidence}`, {}, 0);
|
|
339
|
-
const byType = {
|
|
340
|
-
procedural: 0,
|
|
341
|
-
semantic: 0,
|
|
342
|
-
negative: 0,
|
|
343
|
-
episodic: 0,
|
|
344
|
-
convention: 0,
|
|
345
|
-
};
|
|
346
|
-
let totalConfidence = 0;
|
|
347
|
-
let active = 0;
|
|
348
|
-
const threshold = this.config.recall_threshold;
|
|
349
|
-
for (const fact of allFacts) {
|
|
350
|
-
byType[fact.fact_type]++;
|
|
351
|
-
totalConfidence += fact.effective_confidence;
|
|
352
|
-
if (fact.effective_confidence >= threshold)
|
|
353
|
-
active++;
|
|
354
|
-
}
|
|
355
|
-
return {
|
|
356
|
-
total: allFacts.length,
|
|
357
|
-
active,
|
|
358
|
-
decayed: allFacts.length - active,
|
|
359
|
-
by_type: byType,
|
|
360
|
-
avg_confidence: allFacts.length > 0 ? totalConfidence / allFacts.length : 0,
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Remove facts whose effective confidence has dropped below the prune threshold.
|
|
365
|
-
* Returns the number of facts pruned.
|
|
366
|
-
*/
|
|
367
|
-
async pruneDecayed(threshold) {
|
|
368
|
-
const pruneThreshold = threshold ?? this.config.prune_threshold;
|
|
369
|
-
const allFacts = await this.runDecayQuery(`*facts{fact_id, fact_type, scope, subject, content,
|
|
370
|
-
base_confidence, reinforcement_count, created_at,
|
|
371
|
-
last_reinforced_at, last_contradicted_at, source, evidence}`, {}, 0);
|
|
372
|
-
const toPrune = allFacts.filter((f) => f.fact_type !== "episodic" && f.effective_confidence < pruneThreshold);
|
|
373
|
-
for (const fact of toPrune) {
|
|
374
|
-
await this.db.run(`
|
|
375
|
-
?[fact_id] <- [[$fact_id]]
|
|
376
|
-
:rm facts { fact_id }
|
|
377
|
-
`, { fact_id: fact.fact_id });
|
|
378
|
-
}
|
|
379
|
-
return toPrune.length;
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* Record an entity interaction (for cross-session history tracking).
|
|
383
|
-
*/
|
|
384
|
-
async recordInteraction(entityKey, sessionId, interactionType, toolName, outcome) {
|
|
385
|
-
await this.db.run(`
|
|
386
|
-
?[entity_key, session_id, interaction_type, timestamp, tool_name, outcome] <- [[
|
|
387
|
-
$entity_key, $session_id, $interaction_type, $timestamp, $tool_name, $outcome
|
|
388
|
-
]]
|
|
389
|
-
:put entity_interactions {
|
|
390
|
-
entity_key, session_id, interaction_type, timestamp
|
|
391
|
-
=> tool_name, outcome
|
|
392
|
-
}
|
|
393
|
-
`, {
|
|
394
|
-
entity_key: entityKey,
|
|
395
|
-
session_id: sessionId,
|
|
396
|
-
interaction_type: interactionType,
|
|
397
|
-
timestamp: Date.now(),
|
|
398
|
-
tool_name: toolName,
|
|
399
|
-
outcome,
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
// ── Internal ─────────────────────────────────────────────────────
|
|
403
|
-
async findDuplicate(factType, scope, subject) {
|
|
404
|
-
const result = await this.db.run(`
|
|
405
|
-
?[fact_id] :=
|
|
406
|
-
*facts{fact_id, fact_type, scope, subject},
|
|
407
|
-
fact_type == $filter_type,
|
|
408
|
-
scope == $filter_scope,
|
|
409
|
-
subject == $filter_subject
|
|
410
|
-
:limit 1
|
|
411
|
-
`, {
|
|
412
|
-
filter_type: factType,
|
|
413
|
-
filter_scope: scope,
|
|
414
|
-
filter_subject: subject,
|
|
415
|
-
});
|
|
416
|
-
return result.rows.length > 0 ? result.rows[0]?.[0] : null;
|
|
417
|
-
}
|
|
418
|
-
async getRawFact(factId) {
|
|
419
|
-
const result = await this.db.run(`
|
|
420
|
-
?[fact_id, fact_type, scope, subject, content, base_confidence,
|
|
421
|
-
reinforcement_count, created_at, last_reinforced_at,
|
|
422
|
-
last_contradicted_at, source, evidence] :=
|
|
423
|
-
*facts{fact_id, fact_type, scope, subject, content,
|
|
424
|
-
base_confidence, reinforcement_count, created_at,
|
|
425
|
-
last_reinforced_at, last_contradicted_at, source, evidence},
|
|
426
|
-
fact_id == $filter_fact_id
|
|
427
|
-
`, { filter_fact_id: factId });
|
|
428
|
-
if (result.rows.length === 0)
|
|
429
|
-
return null;
|
|
430
|
-
const row = result.rows[0];
|
|
431
|
-
return {
|
|
432
|
-
fact_id: row[0],
|
|
433
|
-
fact_type: row[1],
|
|
434
|
-
scope: row[2],
|
|
435
|
-
subject: row[3],
|
|
436
|
-
content: row[4],
|
|
437
|
-
base_confidence: row[5],
|
|
438
|
-
reinforcement_count: row[6],
|
|
439
|
-
created_at: row[7],
|
|
440
|
-
last_reinforced_at: row[8],
|
|
441
|
-
last_contradicted_at: row[9],
|
|
442
|
-
source: row[10],
|
|
443
|
-
evidence: row[11],
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Execute a recall query with inline decay computation.
|
|
448
|
-
* The decay math runs entirely within CozoDB Datalog — no post-processing.
|
|
449
|
-
*/
|
|
450
|
-
async runDecayQuery(sourceClause, params, minConfidence) {
|
|
451
|
-
const threshold = minConfidence ?? this.config.recall_threshold;
|
|
452
|
-
const nowMs = Date.now();
|
|
453
|
-
const query = `
|
|
454
|
-
?[fact_id, fact_type, scope, subject, content, base_confidence,
|
|
455
|
-
reinforcement_count, created_at, last_reinforced_at,
|
|
456
|
-
last_contradicted_at, source, evidence, effective_conf] :=
|
|
457
|
-
${sourceClause},
|
|
458
|
-
days = ($now_ms - last_reinforced_at) / 86400000.0,
|
|
459
|
-
lambda = if(fact_type == "procedural", $lambda_procedural,
|
|
460
|
-
if(fact_type == "semantic", $lambda_semantic,
|
|
461
|
-
if(fact_type == "negative", $lambda_negative,
|
|
462
|
-
if(fact_type == "convention", $lambda_convention, 0.0)))),
|
|
463
|
-
recency = exp(-1.0 * lambda * max(0.0, days)),
|
|
464
|
-
ev_factor = if(source == "agent_explicit", 1.0, min(1.0, reinforcement_count / 3.0)),
|
|
465
|
-
effective_conf = base_confidence * recency * ev_factor,
|
|
466
|
-
effective_conf >= $min_confidence
|
|
467
|
-
:order -effective_conf
|
|
468
|
-
:limit 50
|
|
469
|
-
`;
|
|
470
|
-
const result = await this.db.run(query, {
|
|
471
|
-
...params,
|
|
472
|
-
now_ms: nowMs,
|
|
473
|
-
lambda_procedural: this.config.decay_rates.procedural,
|
|
474
|
-
lambda_semantic: this.config.decay_rates.semantic,
|
|
475
|
-
lambda_negative: this.config.decay_rates.negative,
|
|
476
|
-
lambda_convention: this.config.decay_rates.convention,
|
|
477
|
-
min_confidence: threshold,
|
|
478
|
-
});
|
|
479
|
-
return result.rows.map((row) => ({
|
|
480
|
-
fact_id: row[0],
|
|
481
|
-
fact_type: row[1],
|
|
482
|
-
scope: row[2],
|
|
483
|
-
subject: row[3],
|
|
484
|
-
content: row[4],
|
|
485
|
-
base_confidence: row[5],
|
|
486
|
-
effective_confidence: row[12],
|
|
487
|
-
reinforcement_count: row[6],
|
|
488
|
-
created_at: row[7],
|
|
489
|
-
last_reinforced_at: row[8],
|
|
490
|
-
last_contradicted_at: row[9],
|
|
491
|
-
source: row[10],
|
|
492
|
-
}));
|
|
493
|
-
}
|
|
494
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Token Estimation Engine — fast heuristic-based token counting.
|
|
3
|
-
*
|
|
4
|
-
* Implements a 4-rule heuristic for cl100k_base (GPT-4/Claude) tokenization
|
|
5
|
-
* that achieves within 10% accuracy without WASM overhead.
|
|
6
|
-
*
|
|
7
|
-
* Rules:
|
|
8
|
-
* 1. Whitespace is cheap (~0.25 tokens per whitespace char)
|
|
9
|
-
* 2. Code tokens ≈ chars / 3.5 (identifiers split on boundaries)
|
|
10
|
-
* 3. English prose ≈ chars / 4 (natural language words)
|
|
11
|
-
* 4. JSON/structured ≈ chars / 3 (many delimiters = more tokens)
|
|
12
|
-
*
|
|
13
|
-
* Per-model cost table for all major providers.
|
|
14
|
-
*/
|
|
15
|
-
const CHARS_PER_TOKEN = {
|
|
16
|
-
code: 3.5,
|
|
17
|
-
prose: 4.0,
|
|
18
|
-
json: 3.0,
|
|
19
|
-
mixed: 3.7,
|
|
20
|
-
};
|
|
21
|
-
function detectContentType(text) {
|
|
22
|
-
if (text.length === 0)
|
|
23
|
-
return "mixed";
|
|
24
|
-
const sample = text.slice(0, 2000);
|
|
25
|
-
const lines = sample.split("\n");
|
|
26
|
-
const trimmed = sample.trim();
|
|
27
|
-
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
28
|
-
(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
29
|
-
return "json";
|
|
30
|
-
}
|
|
31
|
-
const braceCount = (sample.match(/[{}()[\];=><]/g) ?? []).length;
|
|
32
|
-
const wordCount = (sample.match(/\b\w+\b/g) ?? []).length;
|
|
33
|
-
const codeIndicators = (sample.match(/\b(function|const|let|var|class|import|export|return|if|for|while|async|await|type|interface|def|fn)\b/g) ?? []).length;
|
|
34
|
-
if (codeIndicators > 3 || braceCount > wordCount * 0.15)
|
|
35
|
-
return "code";
|
|
36
|
-
if (braceCount < 5 && lines.length < sample.length / 60)
|
|
37
|
-
return "prose";
|
|
38
|
-
return "mixed";
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Estimate token count for a string using cl100k_base heuristics.
|
|
42
|
-
* Accurate within 10% for typical code and prose inputs.
|
|
43
|
-
*/
|
|
44
|
-
export function estimateTokenCount(text) {
|
|
45
|
-
if (!text || text.length === 0)
|
|
46
|
-
return 0;
|
|
47
|
-
const contentType = detectContentType(text);
|
|
48
|
-
const ratio = CHARS_PER_TOKEN[contentType];
|
|
49
|
-
const whitespaceCount = (text.match(/\s/g) ?? []).length;
|
|
50
|
-
const nonWhitespaceCount = text.length - whitespaceCount;
|
|
51
|
-
const whitespaceTokens = whitespaceCount * 0.25;
|
|
52
|
-
const contentTokens = nonWhitespaceCount / ratio;
|
|
53
|
-
return Math.ceil(whitespaceTokens + contentTokens);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Estimate tokens for any value (string, object, array, etc.)
|
|
57
|
-
*/
|
|
58
|
-
export function estimateTokens(value) {
|
|
59
|
-
if (value === null || value === undefined)
|
|
60
|
-
return 0;
|
|
61
|
-
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
62
|
-
return estimateTokenCount(text);
|
|
63
|
-
}
|
|
64
|
-
const MODEL_COSTS = {
|
|
65
|
-
"claude-sonnet-4-20250514": { inputPerMillion: 3, outputPerMillion: 15 },
|
|
66
|
-
"claude-opus-4-20250514": { inputPerMillion: 15, outputPerMillion: 75 },
|
|
67
|
-
"claude-haiku-4-20250506": { inputPerMillion: 0.8, outputPerMillion: 4 },
|
|
68
|
-
"gpt-4o": { inputPerMillion: 2.5, outputPerMillion: 10 },
|
|
69
|
-
"gpt-4o-mini": { inputPerMillion: 0.15, outputPerMillion: 0.6 },
|
|
70
|
-
"gpt-4-turbo": { inputPerMillion: 10, outputPerMillion: 30 },
|
|
71
|
-
"gemini-2.0-flash": { inputPerMillion: 0.1, outputPerMillion: 0.4 },
|
|
72
|
-
"gemini-1.5-pro": { inputPerMillion: 1.25, outputPerMillion: 5 },
|
|
73
|
-
ollama: { inputPerMillion: 0, outputPerMillion: 0 },
|
|
74
|
-
};
|
|
75
|
-
/**
|
|
76
|
-
* Estimate the dollar cost of a token exchange.
|
|
77
|
-
*/
|
|
78
|
-
export function estimateCost(model, inputTokens, outputTokens) {
|
|
79
|
-
const rates = MODEL_COSTS[model] ?? {
|
|
80
|
-
inputPerMillion: 3,
|
|
81
|
-
outputPerMillion: 15,
|
|
82
|
-
};
|
|
83
|
-
return ((inputTokens * rates.inputPerMillion +
|
|
84
|
-
outputTokens * rates.outputPerMillion) /
|
|
85
|
-
1_000_000);
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Get the cost rate for a model. Returns default rates for unknown models.
|
|
89
|
-
*/
|
|
90
|
-
export function getModelCostRate(model) {
|
|
91
|
-
return MODEL_COSTS[model] ?? { inputPerMillion: 3, outputPerMillion: 15 };
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Estimate dollar savings from token reduction.
|
|
95
|
-
*/
|
|
96
|
-
export function estimateSavings(model, originalTokens, deliveredTokens) {
|
|
97
|
-
const savedTokens = Math.max(0, originalTokens - deliveredTokens);
|
|
98
|
-
const rates = getModelCostRate(model);
|
|
99
|
-
return (savedTokens * rates.inputPerMillion) / 1_000_000;
|
|
100
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprint 9.7: Dynamic Tool Injection — inject block-level rules into tool descriptions.
|
|
3
|
-
*
|
|
4
|
-
* At MCP initialize, reads top block-level rules from CozoDB and appends them
|
|
5
|
-
* to `sync_local_diff` and `check_rules` tool descriptions. On Butter-Sync
|
|
6
|
-
* rule changes, emits `tools/list_changed` to refresh IDE descriptions.
|
|
7
|
-
*
|
|
8
|
-
* Budget: 500 tokens (~2000 chars) for injected rule context.
|
|
9
|
-
*
|
|
10
|
-
* Design authority: Phase 6 IS-06, SKILL doc §2.5, Module 6A §6.6.
|
|
11
|
-
*/
|
|
12
|
-
/** stderr logger */
|
|
13
|
-
const _log = {
|
|
14
|
-
info: (msg) => process.stderr.write(`[unerr:tool-injector] ${msg}\n`),
|
|
15
|
-
};
|
|
16
|
-
/** Tools that receive rule injection */
|
|
17
|
-
const INJECTABLE_TOOLS = new Set(["sync_local_diff", "check_rules"]);
|
|
18
|
-
/** Max characters for injected rule context (~500 tokens) */
|
|
19
|
-
const MAX_INJECTION_CHARS = 2000;
|
|
20
|
-
/** Max number of block rules to inject */
|
|
21
|
-
const MAX_BLOCK_RULES = 10;
|
|
22
|
-
/**
|
|
23
|
-
* Get block-level rules from CozoDB for injection.
|
|
24
|
-
*/
|
|
25
|
-
export async function getBlockRules(localGraph) {
|
|
26
|
-
const allRules = await localGraph.getRules();
|
|
27
|
-
return allRules
|
|
28
|
-
.filter((r) => r.severity === "block" && r.enabled)
|
|
29
|
-
.slice(0, MAX_BLOCK_RULES);
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Format block rules into a compact injection string.
|
|
33
|
-
* Stays within 500-token budget.
|
|
34
|
-
*/
|
|
35
|
-
export function formatRuleInjection(blockRules) {
|
|
36
|
-
if (blockRules.length === 0)
|
|
37
|
-
return "";
|
|
38
|
-
const lines = [
|
|
39
|
-
"\n\n---\n**Active Block Rules (violations will be flagged):**",
|
|
40
|
-
];
|
|
41
|
-
let totalChars = lines[0]?.length ?? 0;
|
|
42
|
-
for (const rule of blockRules) {
|
|
43
|
-
const line = `\n- [${rule.key}] ${rule.name}: ${rule.message || "No description"}`;
|
|
44
|
-
if (totalChars + line.length > MAX_INJECTION_CHARS)
|
|
45
|
-
break;
|
|
46
|
-
lines.push(line);
|
|
47
|
-
totalChars += line.length;
|
|
48
|
-
}
|
|
49
|
-
return lines.join("");
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Inject block-level rule context into tool descriptions.
|
|
53
|
-
* Returns new tool schemas with updated descriptions. Does not mutate input.
|
|
54
|
-
*/
|
|
55
|
-
export async function injectRuleContext(tools, localGraph) {
|
|
56
|
-
const blockRules = await getBlockRules(localGraph);
|
|
57
|
-
if (blockRules.length === 0)
|
|
58
|
-
return tools;
|
|
59
|
-
const injection = formatRuleInjection(blockRules);
|
|
60
|
-
if (!injection)
|
|
61
|
-
return tools;
|
|
62
|
-
_log.info(`Injecting ${blockRules.length} block rules into tool descriptions`);
|
|
63
|
-
return tools.map((tool) => {
|
|
64
|
-
if (INJECTABLE_TOOLS.has(tool.name)) {
|
|
65
|
-
return {
|
|
66
|
-
...tool,
|
|
67
|
-
description: (tool.description ?? "") + injection,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
return tool;
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Check if tool descriptions need refreshing after a rule change.
|
|
75
|
-
* Compares current block rules against a cached version.
|
|
76
|
-
*/
|
|
77
|
-
export async function needsRefresh(localGraph, cachedRuleKeys) {
|
|
78
|
-
const currentBlockRules = await getBlockRules(localGraph);
|
|
79
|
-
const currentKeys = new Set(currentBlockRules.map((r) => r.key));
|
|
80
|
-
if (currentKeys.size !== cachedRuleKeys.size)
|
|
81
|
-
return true;
|
|
82
|
-
for (const key of currentKeys) {
|
|
83
|
-
if (!cachedRuleKeys.has(key))
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
return false;
|
|
87
|
-
}
|