@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,390 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Signal Scorer — ranks intelligence signals by composite score.
|
|
3
|
-
*
|
|
4
|
-
* Replaces the flat 15-field _context dump with a ranked signals[] array.
|
|
5
|
-
* Each signal is scored: composite = actionability^1.5 * relevance * confidence.
|
|
6
|
-
* The actionability exponent (1.5) biases toward signals agents can act on.
|
|
7
|
-
*
|
|
8
|
-
* Part of Layer A+B of the Three-Layer Experience System.
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* Per-type rotation decay: how aggressively a signal's effective rank drops
|
|
12
|
-
* with each show. Warnings (anti-patterns, risks) keep surfacing even after
|
|
13
|
-
* the agent has seen them; history (low-actionability) fades fast.
|
|
14
|
-
*
|
|
15
|
-
* effective_score = composite_score / (1 + shows × decay)
|
|
16
|
-
*/
|
|
17
|
-
const SIGNAL_TYPE_DECAY = {
|
|
18
|
-
warning: 0.3,
|
|
19
|
-
guidance: 0.4,
|
|
20
|
-
context: 0.5,
|
|
21
|
-
history: 0.8,
|
|
22
|
-
};
|
|
23
|
-
/**
|
|
24
|
-
* Stable ID for a signal, used by the rotational-decay tracker in SessionContext.
|
|
25
|
-
* Uses content hash so the same fact surfaced twice gets the same id even if
|
|
26
|
-
* the signal envelope (action/source) varies slightly between calls.
|
|
27
|
-
*/
|
|
28
|
-
export function signalId(s) {
|
|
29
|
-
// Lightweight FNV-1a 32-bit hash, hex-encoded; 8 chars is enough collision
|
|
30
|
-
// resistance for in-session dedup (we expect <500 unique signals/session).
|
|
31
|
-
const str = `${s.type}|${s.content}`;
|
|
32
|
-
let h = 0x811c9dc5;
|
|
33
|
-
for (let i = 0; i < str.length; i++) {
|
|
34
|
-
h ^= str.charCodeAt(i);
|
|
35
|
-
h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
|
|
36
|
-
}
|
|
37
|
-
return h.toString(16).padStart(8, "0");
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Compute the composite score for a signal.
|
|
41
|
-
* Formula: actionability^1.5 * relevance * confidence
|
|
42
|
-
*/
|
|
43
|
-
function computeComposite(actionability, relevance, confidence) {
|
|
44
|
-
return actionability ** 1.5 * relevance * confidence;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Create a signal with auto-computed composite score.
|
|
48
|
-
*/
|
|
49
|
-
function createSignal(params) {
|
|
50
|
-
return {
|
|
51
|
-
...params,
|
|
52
|
-
composite_score: computeComposite(params.actionability, params.relevance, params.confidence),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
export class SignalScorer {
|
|
56
|
-
/**
|
|
57
|
-
* Convert raw gathered context fields into scored IntelligenceSignals.
|
|
58
|
-
* Each field maps to a signal type with default actionability scores.
|
|
59
|
-
*/
|
|
60
|
-
contextToSignals(raw, _toolName, _args) {
|
|
61
|
-
const signals = [];
|
|
62
|
-
// ── Warnings (highest actionability) ──
|
|
63
|
-
if (raw.blast_radius) {
|
|
64
|
-
signals.push(createSignal({
|
|
65
|
-
type: "warning",
|
|
66
|
-
content: raw.blast_radius,
|
|
67
|
-
action: "call get_references({direction:'callers'}) before editing",
|
|
68
|
-
actionability: 0.9,
|
|
69
|
-
relevance: 0.85,
|
|
70
|
-
confidence: 0.95,
|
|
71
|
-
source: "graph",
|
|
72
|
-
}));
|
|
73
|
-
}
|
|
74
|
-
if (raw.pending_violations && raw.pending_violations.length > 0) {
|
|
75
|
-
for (const v of raw.pending_violations.slice(0, 3)) {
|
|
76
|
-
signals.push(createSignal({
|
|
77
|
-
type: "warning",
|
|
78
|
-
content: `Rule violation in ${v.file}: ${v.message}`,
|
|
79
|
-
action: `Fix: ${v.rule}${v.line ? ` at line ${v.line}` : ""}`,
|
|
80
|
-
actionability: 1.0,
|
|
81
|
-
relevance: 0.95,
|
|
82
|
-
confidence: 0.9,
|
|
83
|
-
source: "graph",
|
|
84
|
-
}));
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (raw.drift_alert) {
|
|
88
|
-
signals.push(createSignal({
|
|
89
|
-
type: "warning",
|
|
90
|
-
content: raw.drift_alert,
|
|
91
|
-
action: "call get_references({direction:'callers'}); diff each caller against drift_overlay before edit",
|
|
92
|
-
actionability: 0.9,
|
|
93
|
-
relevance: 0.9,
|
|
94
|
-
confidence: 0.85,
|
|
95
|
-
source: "graph",
|
|
96
|
-
}));
|
|
97
|
-
}
|
|
98
|
-
if (raw.durability_warning) {
|
|
99
|
-
signals.push(createSignal({
|
|
100
|
-
type: "warning",
|
|
101
|
-
content: raw.durability_warning,
|
|
102
|
-
action: "mark_blocker before retrying; propose an alternative approach (high revert rate signals the prior approach won't stick)",
|
|
103
|
-
actionability: 0.85,
|
|
104
|
-
relevance: 0.8,
|
|
105
|
-
confidence: 0.75,
|
|
106
|
-
source: "temporal",
|
|
107
|
-
}));
|
|
108
|
-
}
|
|
109
|
-
if (raw.corrections) {
|
|
110
|
-
for (const c of raw.corrections.slice(0, 2)) {
|
|
111
|
-
signals.push(createSignal({
|
|
112
|
-
type: "warning",
|
|
113
|
-
content: c,
|
|
114
|
-
// Echo the corrected pattern itself so the model has a literal
|
|
115
|
-
// constraint, not a deictic 'this pattern' to resolve.
|
|
116
|
-
action: `do not reintroduce: ${c}`,
|
|
117
|
-
actionability: 0.8,
|
|
118
|
-
relevance: 0.75,
|
|
119
|
-
confidence: 0.7,
|
|
120
|
-
source: "temporal",
|
|
121
|
-
}));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (raw.anti_patterns) {
|
|
125
|
-
for (const ap of raw.anti_patterns.slice(0, 2)) {
|
|
126
|
-
signals.push(createSignal({
|
|
127
|
-
type: "warning",
|
|
128
|
-
content: ap,
|
|
129
|
-
// Imperative tail so the agent treats the anti-pattern as a
|
|
130
|
-
// constraint on next-turn output, not a passive observation.
|
|
131
|
-
action: "do not introduce in new code",
|
|
132
|
-
actionability: 0.8,
|
|
133
|
-
relevance: 0.75,
|
|
134
|
-
confidence: 0.8,
|
|
135
|
-
source: "temporal",
|
|
136
|
-
}));
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// ── Guidance (medium actionability) ──
|
|
140
|
-
if (raw.conventions) {
|
|
141
|
-
for (const conv of raw.conventions.slice(0, 3)) {
|
|
142
|
-
signals.push(createSignal({
|
|
143
|
-
type: "guidance",
|
|
144
|
-
content: conv,
|
|
145
|
-
actionability: 0.7,
|
|
146
|
-
relevance: 0.65,
|
|
147
|
-
confidence: 0.8,
|
|
148
|
-
source: "graph",
|
|
149
|
-
}));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (raw.learned_conventions) {
|
|
153
|
-
for (const lc of raw.learned_conventions.slice(0, 2)) {
|
|
154
|
-
signals.push(createSignal({
|
|
155
|
-
type: "guidance",
|
|
156
|
-
content: lc,
|
|
157
|
-
actionability: 0.65,
|
|
158
|
-
relevance: 0.6,
|
|
159
|
-
confidence: 0.7,
|
|
160
|
-
source: "temporal",
|
|
161
|
-
}));
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
if (raw.prompt_strategy) {
|
|
165
|
-
for (const ps of raw.prompt_strategy.slice(0, 2)) {
|
|
166
|
-
signals.push(createSignal({
|
|
167
|
-
type: "guidance",
|
|
168
|
-
content: ps,
|
|
169
|
-
actionability: 0.6,
|
|
170
|
-
relevance: 0.55,
|
|
171
|
-
confidence: 0.65,
|
|
172
|
-
source: "temporal",
|
|
173
|
-
}));
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
// ── Context (lower actionability) ──
|
|
177
|
-
if (raw.relevant_facts) {
|
|
178
|
-
for (const fact of raw.relevant_facts.slice(0, 3)) {
|
|
179
|
-
// Per-fact-type tuning. Anti-drift signals (negative, convention) need
|
|
180
|
-
// to outrank co-change `hnt` even at low decision level (cap=2).
|
|
181
|
-
// Without this, project-scoped conventions like
|
|
182
|
-
// "All API handlers must return _meta" get edged out of the prefix
|
|
183
|
-
// slot on file_reads — exactly the bug reported in Issue #4.
|
|
184
|
-
const isEpisodic = fact.startsWith("[episodic]");
|
|
185
|
-
const isNegative = fact.startsWith("[negative]");
|
|
186
|
-
const isConvention = fact.startsWith("[convention]");
|
|
187
|
-
const isSemantic = fact.startsWith("[semantic]");
|
|
188
|
-
let type;
|
|
189
|
-
let actionability;
|
|
190
|
-
let relevance;
|
|
191
|
-
if (isEpisodic) {
|
|
192
|
-
type = "history";
|
|
193
|
-
actionability = 0.7;
|
|
194
|
-
relevance = 0.6;
|
|
195
|
-
}
|
|
196
|
-
else if (isNegative) {
|
|
197
|
-
type = "warning";
|
|
198
|
-
actionability = 0.85; // ↑ from 0.8 — anti-pattern facts must always rank
|
|
199
|
-
relevance = 0.75;
|
|
200
|
-
}
|
|
201
|
-
else if (isConvention) {
|
|
202
|
-
type = "context";
|
|
203
|
-
actionability = 0.75; // NEW — outranks co-change hnt
|
|
204
|
-
relevance = 0.75;
|
|
205
|
-
}
|
|
206
|
-
else if (isSemantic) {
|
|
207
|
-
type = "context";
|
|
208
|
-
actionability = 0.55;
|
|
209
|
-
relevance = 0.6;
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
type = "context";
|
|
213
|
-
actionability = 0.5; // procedural / default
|
|
214
|
-
relevance = 0.5;
|
|
215
|
-
}
|
|
216
|
-
signals.push(createSignal({
|
|
217
|
-
type,
|
|
218
|
-
content: fact,
|
|
219
|
-
// Concrete next-action per fact subtype. No hedge verbs.
|
|
220
|
-
action: isEpisodic
|
|
221
|
-
? "read narrative above before editing; mark_decision if diverging from the prior intent"
|
|
222
|
-
: isConvention
|
|
223
|
-
? "apply the convention above to new code in this scope"
|
|
224
|
-
: undefined,
|
|
225
|
-
actionability,
|
|
226
|
-
relevance,
|
|
227
|
-
confidence: 0.7, // ↑ from 0.65 — facts are first-class data
|
|
228
|
-
source: "temporal",
|
|
229
|
-
}));
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
if (raw.community) {
|
|
233
|
-
signals.push(createSignal({
|
|
234
|
-
type: "context",
|
|
235
|
-
content: raw.community,
|
|
236
|
-
action: "call file_connections to enumerate cluster members",
|
|
237
|
-
actionability: 0.4,
|
|
238
|
-
relevance: 0.5,
|
|
239
|
-
confidence: 0.85,
|
|
240
|
-
source: "graph",
|
|
241
|
-
}));
|
|
242
|
-
}
|
|
243
|
-
if (raw.reminder) {
|
|
244
|
-
// Intentionally action-less: `raw.reminder` is a pre-formed advisory
|
|
245
|
-
// string with no structured next-step. Low actionability (0.3) ranks
|
|
246
|
-
// it below load-bearing signals, so it only surfaces when nothing
|
|
247
|
-
// better exists. Anti-pattern rule (CLAUDE.md): a signal without an
|
|
248
|
-
// action is noise — accepted here because the content itself is a
|
|
249
|
-
// self-contained reminder, not a deferred imperative.
|
|
250
|
-
signals.push(createSignal({
|
|
251
|
-
type: "context",
|
|
252
|
-
content: raw.reminder,
|
|
253
|
-
actionability: 0.3,
|
|
254
|
-
relevance: 0.5,
|
|
255
|
-
confidence: 0.9,
|
|
256
|
-
source: "graph",
|
|
257
|
-
}));
|
|
258
|
-
}
|
|
259
|
-
// ── Co-change prediction (graph+temporal) ──
|
|
260
|
-
if (raw.co_changes) {
|
|
261
|
-
signals.push(createSignal({
|
|
262
|
-
type: "guidance",
|
|
263
|
-
content: raw.co_changes,
|
|
264
|
-
// Avoid deictic 'this file' — the partners are already named in
|
|
265
|
-
// content; instruct the agent to open them before editing.
|
|
266
|
-
action: "open the listed files before editing either",
|
|
267
|
-
actionability: 0.6,
|
|
268
|
-
relevance: 0.7,
|
|
269
|
-
confidence: 0.7,
|
|
270
|
-
source: "graph+temporal",
|
|
271
|
-
}));
|
|
272
|
-
}
|
|
273
|
-
// ── Hidden coupling (temporal-only dependency) ──
|
|
274
|
-
if (raw.hidden_coupling) {
|
|
275
|
-
signals.push(createSignal({
|
|
276
|
-
type: "warning",
|
|
277
|
-
content: raw.hidden_coupling,
|
|
278
|
-
// Coupled entity is named in `content`; instruct a concrete read.
|
|
279
|
-
action: "read the coupled entity via get_entity before edit",
|
|
280
|
-
actionability: 0.85,
|
|
281
|
-
relevance: 0.8,
|
|
282
|
-
confidence: 0.7,
|
|
283
|
-
source: "graph+temporal",
|
|
284
|
-
}));
|
|
285
|
-
}
|
|
286
|
-
// ── History (lowest actionability) ──
|
|
287
|
-
if (raw.history) {
|
|
288
|
-
for (const h of raw.history.slice(0, 2)) {
|
|
289
|
-
signals.push(createSignal({
|
|
290
|
-
type: "history",
|
|
291
|
-
content: h,
|
|
292
|
-
actionability: 0.3,
|
|
293
|
-
relevance: 0.4,
|
|
294
|
-
confidence: 0.6,
|
|
295
|
-
source: "temporal",
|
|
296
|
-
}));
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
return signals;
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Apply burst multipliers for high decision points.
|
|
303
|
-
* At edit time, warnings and history crowd out informational signals.
|
|
304
|
-
*/
|
|
305
|
-
applyBurstMultipliers(signals, decisionLevel) {
|
|
306
|
-
if (decisionLevel !== "high")
|
|
307
|
-
return signals;
|
|
308
|
-
return signals.map((s) => {
|
|
309
|
-
let relevanceBoost = 1.0;
|
|
310
|
-
if (s.type === "warning") {
|
|
311
|
-
// Blast radius / corrections get boosted at edit time
|
|
312
|
-
relevanceBoost = 1.3;
|
|
313
|
-
}
|
|
314
|
-
else if (s.type === "history") {
|
|
315
|
-
// Episodic facts about prior modifications are most valuable pre-edit
|
|
316
|
-
relevanceBoost = 1.5;
|
|
317
|
-
}
|
|
318
|
-
else if (s.type === "guidance" && s.source === "temporal") {
|
|
319
|
-
// Learned conventions slightly boosted
|
|
320
|
-
relevanceBoost = 1.1;
|
|
321
|
-
}
|
|
322
|
-
if (relevanceBoost === 1.0)
|
|
323
|
-
return s;
|
|
324
|
-
const boostedRelevance = Math.min(1.0, s.relevance * relevanceBoost);
|
|
325
|
-
return {
|
|
326
|
-
...s,
|
|
327
|
-
relevance: boostedRelevance,
|
|
328
|
-
composite_score: computeComposite(s.actionability, boostedRelevance, s.confidence),
|
|
329
|
-
};
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Rank signals by composite score (descending) and return top N.
|
|
334
|
-
*
|
|
335
|
-
* Tier-3 rotational decay: when `getShowCount` is provided, the effective
|
|
336
|
-
* score is `composite_score / (1 + showCount * 0.5)`. After 3 shows the
|
|
337
|
-
* effective score halves; after 6 it's quartered. Higher-confidence signals
|
|
338
|
-
* still surface (their base score wins) but eventually rotate to give
|
|
339
|
-
* fresher content airtime instead of repeating the same anti-pattern fact.
|
|
340
|
-
*
|
|
341
|
-
* Wire-dedup awareness: when `wouldEmit` is provided, signals that would be
|
|
342
|
-
* suppressed at the wire boundary (already-emitted, same-content) are
|
|
343
|
-
* filtered out *before* ranking — preventing them from consuming scarce
|
|
344
|
-
* rank slots and starving genuinely fresh signals.
|
|
345
|
-
*/
|
|
346
|
-
rank(signals, maxSignals = 3, getShowCount, wouldEmit) {
|
|
347
|
-
if (signals.length === 0)
|
|
348
|
-
return [];
|
|
349
|
-
const filtered = wouldEmit ? signals.filter((s) => wouldEmit(s)) : signals;
|
|
350
|
-
if (filtered.length === 0)
|
|
351
|
-
return [];
|
|
352
|
-
const adjusted = filtered.map((s) => {
|
|
353
|
-
const id = signalId(s);
|
|
354
|
-
const shows = getShowCount ? getShowCount(id) : 0;
|
|
355
|
-
const decay = SIGNAL_TYPE_DECAY[s.type] ?? 0.5;
|
|
356
|
-
const effectiveScore = s.composite_score / (1 + shows * decay);
|
|
357
|
-
return { signal: s, id, effectiveScore };
|
|
358
|
-
});
|
|
359
|
-
return adjusted
|
|
360
|
-
.sort((a, b) => b.effectiveScore - a.effectiveScore)
|
|
361
|
-
.slice(0, maxSignals)
|
|
362
|
-
.map(({ signal }, i) => ({ ...signal, priority: i + 1 }));
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Convert raw convention to prescriptive signal.
|
|
366
|
-
* For get_entity/file_read: "Follow [pattern] for new [kind]s — 87% adherence"
|
|
367
|
-
* For get_conventions: keep descriptive (agent asked for overview)
|
|
368
|
-
*/
|
|
369
|
-
conventionToSignal(convention, toolName) {
|
|
370
|
-
const isPrescriptive = toolName !== "get_conventions";
|
|
371
|
-
return createSignal({
|
|
372
|
-
type: "guidance",
|
|
373
|
-
content: isPrescriptive
|
|
374
|
-
? `Follow "${convention.name}" for new ${convention.kind}s \u2014 ${convention.adherence_pct}% adherence in this project`
|
|
375
|
-
: `${convention.name}: ${convention.rule} (${convention.adherence_pct}% adherence)`,
|
|
376
|
-
action: isPrescriptive ? `Apply pattern: ${convention.rule}` : undefined,
|
|
377
|
-
actionability: isPrescriptive ? 0.8 : 0.5,
|
|
378
|
-
relevance: convention.adherence_pct / 100,
|
|
379
|
-
confidence: convention.adherence_pct / 100,
|
|
380
|
-
source: "graph",
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
/** Singleton instance */
|
|
385
|
-
let _scorer = null;
|
|
386
|
-
export function getSignalScorer() {
|
|
387
|
-
if (!_scorer)
|
|
388
|
-
_scorer = new SignalScorer();
|
|
389
|
-
return _scorer;
|
|
390
|
-
}
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SignalShowStore — persistent, cross-session rotation state for `ur|fct`,
|
|
3
|
-
* `ur|hnt`, `ur|wrn`, conventions, and any other deduplicated signal.
|
|
4
|
-
*
|
|
5
|
-
* Backed by `signal_shows` relation in facts.db (per-session rows, no write
|
|
6
|
-
* contention across parallel `unerr --mcp` instances). Each session writes
|
|
7
|
-
* only its own (signal_id, session_id) row; aggregate state is SUM/MAX over
|
|
8
|
-
* all rows for the signal_id.
|
|
9
|
-
*
|
|
10
|
-
* Hot path stays sync: the ranker calls `getEffectiveShowCount(id, now)` which
|
|
11
|
-
* reads only the in-memory cache (this-session counts + aggregated other-session
|
|
12
|
-
* snapshot). DB I/O happens off the hot path on the flush interval.
|
|
13
|
-
*
|
|
14
|
-
* Decay: `effective_shows = count × exp(−(now − lastShownMs)/24h)`. Yesterday's
|
|
15
|
-
* shows naturally fall out of weight without requiring explicit pruning.
|
|
16
|
-
*/
|
|
17
|
-
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
18
|
-
const DEFAULT_FLUSH_INTERVAL_MS = 10_000;
|
|
19
|
-
export class SignalShowStore {
|
|
20
|
-
db;
|
|
21
|
-
sessionId;
|
|
22
|
-
flushIntervalMs;
|
|
23
|
-
decayWindowMs;
|
|
24
|
-
/** This session's per-signal show counts; flushed to DB periodically. */
|
|
25
|
-
mineCount = new Map();
|
|
26
|
-
mineScope = new Map();
|
|
27
|
-
mineLastShown = new Map();
|
|
28
|
-
/** Dirty keys awaiting flush. */
|
|
29
|
-
dirty = new Set();
|
|
30
|
-
/**
|
|
31
|
-
* Cached snapshot of *other* sessions' aggregated counts. Refreshed every
|
|
32
|
-
* flush tick; combined with `mineCount` to produce effective counts for
|
|
33
|
-
* the ranker. Initialized empty until first hydrate().
|
|
34
|
-
*/
|
|
35
|
-
othersSnapshot = new Map();
|
|
36
|
-
flushTimer = null;
|
|
37
|
-
hydrated = false;
|
|
38
|
-
closed = false;
|
|
39
|
-
constructor(db, sessionId, opts = {}) {
|
|
40
|
-
this.db = db;
|
|
41
|
-
this.sessionId = sessionId;
|
|
42
|
-
this.flushIntervalMs = opts.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
43
|
-
this.decayWindowMs = opts.decayWindowMs ?? DAY_MS;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Load initial snapshot of other sessions' counts and start the periodic
|
|
47
|
-
* flush+refresh loop. Idempotent.
|
|
48
|
-
*/
|
|
49
|
-
async start() {
|
|
50
|
-
if (this.hydrated)
|
|
51
|
-
return;
|
|
52
|
-
await this.refreshOthersSnapshot();
|
|
53
|
-
this.hydrated = true;
|
|
54
|
-
if (this.flushTimer === null && !this.closed) {
|
|
55
|
-
this.flushTimer = setInterval(() => {
|
|
56
|
-
// Fire and forget; errors logged but never thrown to break the interval.
|
|
57
|
-
this.tick().catch(() => { });
|
|
58
|
-
}, this.flushIntervalMs);
|
|
59
|
-
// Don't keep the event loop alive solely for the flush timer.
|
|
60
|
-
this.flushTimer.unref?.();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
/** Record that the agent just emitted this signal. Sync — no DB I/O. */
|
|
64
|
-
recordShown(signalId, scope, now = Date.now()) {
|
|
65
|
-
if (!signalId)
|
|
66
|
-
return;
|
|
67
|
-
this.mineCount.set(signalId, (this.mineCount.get(signalId) ?? 0) + 1);
|
|
68
|
-
this.mineScope.set(signalId, scope || "");
|
|
69
|
-
this.mineLastShown.set(signalId, now);
|
|
70
|
-
this.dirty.add(signalId);
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Effective (time-decayed) show count for ranking. Sums this-session count
|
|
74
|
-
* and the cached snapshot of all other sessions' counts, then applies a
|
|
75
|
-
* 24h exponential decay against the most recent shown timestamp.
|
|
76
|
-
*/
|
|
77
|
-
getEffectiveShowCount(signalId, now = Date.now()) {
|
|
78
|
-
if (!signalId)
|
|
79
|
-
return 0;
|
|
80
|
-
const mine = this.mineCount.get(signalId) ?? 0;
|
|
81
|
-
const others = this.othersSnapshot.get(signalId);
|
|
82
|
-
const totalCount = mine + (others?.count ?? 0);
|
|
83
|
-
if (totalCount === 0)
|
|
84
|
-
return 0;
|
|
85
|
-
const myLast = this.mineLastShown.get(signalId) ?? 0;
|
|
86
|
-
const lastShown = Math.max(myLast, others?.lastShownMs ?? 0);
|
|
87
|
-
if (lastShown === 0)
|
|
88
|
-
return totalCount;
|
|
89
|
-
const ageMs = Math.max(0, now - lastShown);
|
|
90
|
-
return totalCount * Math.exp(-ageMs / this.decayWindowMs);
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* When was this signal last shown (in any session)? Used for cross-channel
|
|
94
|
-
* dedup window and for "stale" re-show logic on conventions.
|
|
95
|
-
*/
|
|
96
|
-
getLastShownMs(signalId) {
|
|
97
|
-
if (!signalId)
|
|
98
|
-
return 0;
|
|
99
|
-
const mine = this.mineLastShown.get(signalId) ?? 0;
|
|
100
|
-
const others = this.othersSnapshot.get(signalId)?.lastShownMs ?? 0;
|
|
101
|
-
return Math.max(mine, others);
|
|
102
|
-
}
|
|
103
|
-
/** Flush dirty rows AND refresh the other-sessions snapshot. */
|
|
104
|
-
async tick() {
|
|
105
|
-
if (this.closed)
|
|
106
|
-
return;
|
|
107
|
-
await this.flush();
|
|
108
|
-
await this.refreshOthersSnapshot();
|
|
109
|
-
}
|
|
110
|
-
/** Write this session's dirty entries to facts.db. */
|
|
111
|
-
async flush() {
|
|
112
|
-
if (this.dirty.size === 0)
|
|
113
|
-
return;
|
|
114
|
-
const ids = Array.from(this.dirty);
|
|
115
|
-
this.dirty.clear();
|
|
116
|
-
for (const signalId of ids) {
|
|
117
|
-
const count = this.mineCount.get(signalId) ?? 0;
|
|
118
|
-
const scope = this.mineScope.get(signalId) ?? "";
|
|
119
|
-
const last = this.mineLastShown.get(signalId) ?? 0;
|
|
120
|
-
try {
|
|
121
|
-
await this.db.run(`?[signal_id, session_id, scope, count, last_shown_ms] <- [[$sid, $sess, $scope, $count, $last]]
|
|
122
|
-
:put signal_shows { signal_id, session_id => scope, count, last_shown_ms }`, {
|
|
123
|
-
sid: signalId,
|
|
124
|
-
sess: this.sessionId,
|
|
125
|
-
scope,
|
|
126
|
-
count,
|
|
127
|
-
last,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
// Restore to dirty so the next tick retries; don't lose updates.
|
|
132
|
-
this.dirty.add(signalId);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
/** Stop the periodic flush; final flush. Safe to call multiple times. */
|
|
137
|
-
async close() {
|
|
138
|
-
if (this.closed)
|
|
139
|
-
return;
|
|
140
|
-
this.closed = true;
|
|
141
|
-
if (this.flushTimer !== null) {
|
|
142
|
-
clearInterval(this.flushTimer);
|
|
143
|
-
this.flushTimer = null;
|
|
144
|
-
}
|
|
145
|
-
await this.flush();
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Read aggregated counts from rows belonging to *other* sessions. Self rows
|
|
149
|
-
* are excluded so the in-memory `mineCount` remains the authoritative source
|
|
150
|
-
* for this session — otherwise we'd double-count after each flush.
|
|
151
|
-
*/
|
|
152
|
-
async refreshOthersSnapshot() {
|
|
153
|
-
try {
|
|
154
|
-
const result = await this.db.run(`?[signal_id, total_count, last] :=
|
|
155
|
-
*signal_shows{signal_id, session_id, count, last_shown_ms},
|
|
156
|
-
session_id != $sess,
|
|
157
|
-
total_count = count,
|
|
158
|
-
last = last_shown_ms`, { sess: this.sessionId });
|
|
159
|
-
// Aggregate in TS — Cozo's group-by syntax varies by version; this is
|
|
160
|
-
// simpler and the row count is small (one row per (signal_id, session_id)).
|
|
161
|
-
const agg = new Map();
|
|
162
|
-
for (const row of result.rows) {
|
|
163
|
-
const sid = row[0];
|
|
164
|
-
const c = Number(row[1] ?? 0);
|
|
165
|
-
const last = Number(row[2] ?? 0);
|
|
166
|
-
const prev = agg.get(sid);
|
|
167
|
-
if (prev) {
|
|
168
|
-
prev.count += c;
|
|
169
|
-
if (last > prev.lastShownMs)
|
|
170
|
-
prev.lastShownMs = last;
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
agg.set(sid, { count: c, lastShownMs: last });
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
this.othersSnapshot = agg;
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
// First-run / schema-missing: leave snapshot empty. Will retry next tick.
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|