@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,658 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Incremental Indexer — processes only changed files instead of full project reindex.
|
|
3
|
-
*
|
|
4
|
-
* Pipeline per changed file:
|
|
5
|
-
* 1. Handle deletes (file removed from disk)
|
|
6
|
-
* 2. Read + extract entities and edges
|
|
7
|
-
* 3. Query graph for old entities of this file
|
|
8
|
-
* 4. Diff old vs new entities
|
|
9
|
-
* 5. Apply graph patch (batched add/update/delete)
|
|
10
|
-
* 6. Resolve cross-file edges using existing graph (batched)
|
|
11
|
-
* 7. Update fan_in/fan_out for affected entities (batched)
|
|
12
|
-
* 8. Incremental search index update (changed keys only)
|
|
13
|
-
*
|
|
14
|
-
* Skipped (deferred to periodic full reindex):
|
|
15
|
-
* - Community detection (Louvain) — O(n+e), only meaningful on bulk changes
|
|
16
|
-
* - Convention detection — pattern scanning all entities
|
|
17
|
-
* - SCIP enrichment — requires full project compilation
|
|
18
|
-
* - Co-change edges — requires git history scan
|
|
19
|
-
* - L1 edge materialization — full rollup
|
|
20
|
-
* - Snapshot persistence
|
|
21
|
-
*
|
|
22
|
-
* Falls back to full reindex on any failure.
|
|
23
|
-
*/
|
|
24
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
25
|
-
import { join, relative } from "node:path";
|
|
26
|
-
import { entityKey, extractEdgesAsync, extractEntitiesAsync, } from "./ast-extractor.js";
|
|
27
|
-
import { isTestFile } from "./indexer/test-detector.js";
|
|
28
|
-
import { updateSearchIndexIncremental } from "./search-index.js";
|
|
29
|
-
// ── Main Entry ───────────────────────────────────────────────────
|
|
30
|
-
/**
|
|
31
|
-
* Incrementally index only the specified changed files.
|
|
32
|
-
* Does NOT touch any file not in changedFiles.
|
|
33
|
-
* Throws on unrecoverable failure (caller should fallback to full reindex).
|
|
34
|
-
*/
|
|
35
|
-
export async function indexFilesIncremental(projectRoot, changedFiles, graphStore, repoId) {
|
|
36
|
-
const startMs = Date.now();
|
|
37
|
-
const db = {
|
|
38
|
-
run: (q, p) => graphStore.db.run(q, p),
|
|
39
|
-
write: (q, p) => graphStore.write(q, p),
|
|
40
|
-
};
|
|
41
|
-
let filesProcessed = 0;
|
|
42
|
-
let filesDeleted = 0;
|
|
43
|
-
let totalEntitiesAdded = 0;
|
|
44
|
-
let totalEntitiesUpdated = 0;
|
|
45
|
-
let totalEntitiesDeleted = 0;
|
|
46
|
-
let totalEdgesAdded = 0;
|
|
47
|
-
let totalEdgesDeleted = 0;
|
|
48
|
-
// Collect all entity keys that had edges modified (for fan count recalc)
|
|
49
|
-
const affectedEntityKeys = new Set();
|
|
50
|
-
// Track keys for incremental search index update
|
|
51
|
-
const changedEntityKeys = new Set();
|
|
52
|
-
const deletedEntityKeys = new Set();
|
|
53
|
-
for (const filePath of changedFiles) {
|
|
54
|
-
const absPath = filePath.startsWith("/")
|
|
55
|
-
? filePath
|
|
56
|
-
: join(projectRoot, filePath);
|
|
57
|
-
const relPath = filePath.startsWith("/")
|
|
58
|
-
? relative(projectRoot, filePath)
|
|
59
|
-
: filePath;
|
|
60
|
-
// ── Step 1: Handle deleted files ─────────────────────────────
|
|
61
|
-
if (!existsSync(absPath)) {
|
|
62
|
-
const deleted = await deleteFileFromGraph(db, relPath);
|
|
63
|
-
filesDeleted++;
|
|
64
|
-
totalEntitiesDeleted += deleted.entitiesDeleted;
|
|
65
|
-
totalEdgesDeleted += deleted.edgesDeleted;
|
|
66
|
-
for (const k of deleted.affectedKeys) {
|
|
67
|
-
affectedEntityKeys.add(k);
|
|
68
|
-
deletedEntityKeys.add(k);
|
|
69
|
-
}
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
// ── Step 2: Read + extract ───────────────────────────────────
|
|
73
|
-
let content;
|
|
74
|
-
try {
|
|
75
|
-
content = readFileSync(absPath, "utf-8");
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
const newExtracted = await extractEntitiesAsync(content, relPath);
|
|
81
|
-
const newRawEdges = await extractEdgesAsync(content, relPath, newExtracted);
|
|
82
|
-
const fileIsTest = isTestFile(relPath);
|
|
83
|
-
// Build new CompactEntities
|
|
84
|
-
const newEntities = newExtracted.map((e) => ({
|
|
85
|
-
key: entityKey(repoId, relPath, e.kind, e.name, e.signature),
|
|
86
|
-
kind: e.kind,
|
|
87
|
-
name: e.name,
|
|
88
|
-
file_path: relPath,
|
|
89
|
-
start_line: e.line_start,
|
|
90
|
-
signature: e.signature,
|
|
91
|
-
body: "",
|
|
92
|
-
fan_in: 0,
|
|
93
|
-
fan_out: 0,
|
|
94
|
-
risk_level: "normal",
|
|
95
|
-
community: -1,
|
|
96
|
-
is_test: e.is_test ?? fileIsTest,
|
|
97
|
-
parent_class: e.parent_class,
|
|
98
|
-
}));
|
|
99
|
-
// ── Step 3: Query old entities from graph ────────────────────
|
|
100
|
-
const oldEntities = await getFileEntities(db, relPath);
|
|
101
|
-
const oldEdgeKeys = await getFileEdgeKeysBatched(db, relPath, oldEntities);
|
|
102
|
-
// ── Step 4: Diff ─────────────────────────────────────────────
|
|
103
|
-
const oldMap = new Map(oldEntities.map((e) => [e.key, e]));
|
|
104
|
-
const newMap = new Map(newEntities.map((e) => [e.key, e]));
|
|
105
|
-
const added = [];
|
|
106
|
-
const updated = [];
|
|
107
|
-
const deleted = [];
|
|
108
|
-
for (const [key, entity] of newMap) {
|
|
109
|
-
const old = oldMap.get(key);
|
|
110
|
-
if (!old) {
|
|
111
|
-
added.push(entity);
|
|
112
|
-
}
|
|
113
|
-
else if (old.start_line !== entity.start_line ||
|
|
114
|
-
old.signature !== entity.signature) {
|
|
115
|
-
updated.push(entity);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
for (const [key, entity] of oldMap) {
|
|
119
|
-
if (!newMap.has(key)) {
|
|
120
|
-
deleted.push(entity);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
// If nothing changed in this file, skip
|
|
124
|
-
if (added.length === 0 && updated.length === 0 && deleted.length === 0) {
|
|
125
|
-
filesProcessed++;
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
// ── Step 5: Apply graph patches (batched) ────────────────────
|
|
129
|
-
// Delete removed entities + their edges (batched)
|
|
130
|
-
if (deleted.length > 0) {
|
|
131
|
-
const deletedKeys = deleted.map((e) => e.key);
|
|
132
|
-
await removeEntitiesAndEdgesBatched(db, deletedKeys);
|
|
133
|
-
for (const entity of deleted) {
|
|
134
|
-
affectedEntityKeys.add(entity.key);
|
|
135
|
-
deletedEntityKeys.add(entity.key);
|
|
136
|
-
totalEntitiesDeleted++;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// Delete edges for updated entities (batched)
|
|
140
|
-
if (updated.length > 0) {
|
|
141
|
-
const updatedKeys = updated.map((e) => e.key);
|
|
142
|
-
await removeEdgesForKeysBatched(db, updatedKeys);
|
|
143
|
-
for (const entity of updated) {
|
|
144
|
-
affectedEntityKeys.add(entity.key);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
// Upsert added + updated entities (batched)
|
|
148
|
-
const toUpsert = [...added, ...updated];
|
|
149
|
-
if (toUpsert.length > 0) {
|
|
150
|
-
await upsertEntitiesBatched(db, toUpsert);
|
|
151
|
-
for (const entity of toUpsert) {
|
|
152
|
-
affectedEntityKeys.add(entity.key);
|
|
153
|
-
changedEntityKeys.add(entity.key);
|
|
154
|
-
}
|
|
155
|
-
totalEntitiesAdded += added.length;
|
|
156
|
-
totalEntitiesUpdated += updated.length;
|
|
157
|
-
}
|
|
158
|
-
// Remove old edges originating from this file (batched)
|
|
159
|
-
if (oldEdgeKeys.size > 0) {
|
|
160
|
-
const edgeTuples = [];
|
|
161
|
-
for (const edgeKey of oldEdgeKeys) {
|
|
162
|
-
const [fromKey, toKey, type] = edgeKey.split("::");
|
|
163
|
-
if (fromKey && toKey && type) {
|
|
164
|
-
edgeTuples.push([fromKey, toKey, type]);
|
|
165
|
-
affectedEntityKeys.add(fromKey);
|
|
166
|
-
affectedEntityKeys.add(toKey);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
if (edgeTuples.length > 0) {
|
|
170
|
-
await removeEdgesBatched(db, edgeTuples);
|
|
171
|
-
totalEdgesDeleted += edgeTuples.length;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
// ── Step 6: Resolve cross-file edges (batched) ───────────────
|
|
175
|
-
// Collect all unique to_names that need global resolution
|
|
176
|
-
const toResolve = new Set();
|
|
177
|
-
const localResolved = new Map();
|
|
178
|
-
for (const edge of newRawEdges) {
|
|
179
|
-
const fromKey = resolveLocalEntityName(edge.from_name, relPath, repoId, newExtracted);
|
|
180
|
-
if (!fromKey)
|
|
181
|
-
continue;
|
|
182
|
-
localResolved.set(edge.from_name, fromKey);
|
|
183
|
-
if (edge.to_name !== "__file__") {
|
|
184
|
-
// Check local first
|
|
185
|
-
const localKey = resolveLocalEntityName(edge.to_name, relPath, repoId, newExtracted);
|
|
186
|
-
if (localKey) {
|
|
187
|
-
localResolved.set(edge.to_name, localKey);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
toResolve.add(edge.to_name);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// Batch resolve all global names in a single query
|
|
195
|
-
const globalResolved = toResolve.size > 0
|
|
196
|
-
? await resolveEntityNamesGlobal([...toResolve], db)
|
|
197
|
-
: new Map();
|
|
198
|
-
// Now insert all edges in batch
|
|
199
|
-
const edgesToInsert = [];
|
|
200
|
-
for (const edge of newRawEdges) {
|
|
201
|
-
const fromKey = edge.from_name === "__file__"
|
|
202
|
-
? `file:${relPath}`
|
|
203
|
-
: (localResolved.get(edge.from_name) ?? null);
|
|
204
|
-
if (!fromKey)
|
|
205
|
-
continue;
|
|
206
|
-
let toKey = null;
|
|
207
|
-
if (edge.to_name === "__file__") {
|
|
208
|
-
toKey = `file:${relPath}`;
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
toKey =
|
|
212
|
-
localResolved.get(edge.to_name) ??
|
|
213
|
-
globalResolved.get(edge.to_name) ??
|
|
214
|
-
null;
|
|
215
|
-
}
|
|
216
|
-
if (!toKey)
|
|
217
|
-
continue;
|
|
218
|
-
edgesToInsert.push([fromKey, toKey, edge.type]);
|
|
219
|
-
affectedEntityKeys.add(fromKey);
|
|
220
|
-
affectedEntityKeys.add(toKey);
|
|
221
|
-
}
|
|
222
|
-
if (edgesToInsert.length > 0) {
|
|
223
|
-
const inserted = await insertEdgesBatched(db, edgesToInsert);
|
|
224
|
-
totalEdgesAdded += inserted;
|
|
225
|
-
}
|
|
226
|
-
// Update file_index + contains edges (batched)
|
|
227
|
-
if (toUpsert.length > 0) {
|
|
228
|
-
await updateFileIndexBatched(db, relPath, toUpsert);
|
|
229
|
-
}
|
|
230
|
-
filesProcessed++;
|
|
231
|
-
}
|
|
232
|
-
// ── Step 7: Update fan_in/fan_out for affected entities (batched) ──
|
|
233
|
-
await updateFanCountsBatched(db, affectedEntityKeys);
|
|
234
|
-
// ── Step 8: Incremental search index update ────────────────────
|
|
235
|
-
await updateSearchIndexIncremental(db, changedEntityKeys, deletedEntityKeys);
|
|
236
|
-
return {
|
|
237
|
-
filesProcessed,
|
|
238
|
-
filesDeleted,
|
|
239
|
-
entitiesAdded: totalEntitiesAdded,
|
|
240
|
-
entitiesUpdated: totalEntitiesUpdated,
|
|
241
|
-
entitiesDeleted: totalEntitiesDeleted,
|
|
242
|
-
edgesAdded: totalEdgesAdded,
|
|
243
|
-
edgesDeleted: totalEdgesDeleted,
|
|
244
|
-
elapsedMs: Date.now() - startMs,
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
// ── Batched Helpers ─────────────────────────────────────────────
|
|
248
|
-
async function getFileEntities(db, relPath) {
|
|
249
|
-
try {
|
|
250
|
-
const result = await db.run(`?[key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test] :=
|
|
251
|
-
*file_index{file_path: $fp, entity_key: key},
|
|
252
|
-
*entities{key, kind, name, file_path, start_line, signature, body, fan_in, fan_out, risk_level, is_test}`, { fp: relPath });
|
|
253
|
-
return result.rows.map((row) => ({
|
|
254
|
-
key: row[0],
|
|
255
|
-
kind: row[1],
|
|
256
|
-
name: row[2],
|
|
257
|
-
file_path: row[3],
|
|
258
|
-
start_line: row[4],
|
|
259
|
-
signature: row[5],
|
|
260
|
-
body: row[6],
|
|
261
|
-
fan_in: row[7],
|
|
262
|
-
fan_out: row[8],
|
|
263
|
-
risk_level: row[9],
|
|
264
|
-
community: -1,
|
|
265
|
-
is_test: row[10],
|
|
266
|
-
}));
|
|
267
|
-
}
|
|
268
|
-
catch {
|
|
269
|
-
return [];
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Get all edges originating from any entity in this file — single query.
|
|
274
|
-
* Replaces the old N+1 pattern that ran one query per entity key.
|
|
275
|
-
*/
|
|
276
|
-
async function getFileEdgeKeysBatched(db, relPath, entities) {
|
|
277
|
-
const keys = new Set();
|
|
278
|
-
if (entities.length > 0) {
|
|
279
|
-
// Single query: get all outgoing edges for all file entities at once
|
|
280
|
-
try {
|
|
281
|
-
const result = await db.run(`?[from_key, to_key, type] :=
|
|
282
|
-
*file_index{file_path: $fp, entity_key: ek},
|
|
283
|
-
*edges{from_key: ek, to_key, type}`, { fp: relPath });
|
|
284
|
-
for (const row of result.rows) {
|
|
285
|
-
keys.add(`${row[0]}::${row[1]}::${row[2]}`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
catch {
|
|
289
|
-
/* safe */
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
// Also get edges from file entity
|
|
293
|
-
try {
|
|
294
|
-
const result = await db.run("?[from_key, to_key, type] := *edges{from_key: $key, to_key, type}", { key: `file:${relPath}` });
|
|
295
|
-
for (const row of result.rows) {
|
|
296
|
-
keys.add(`${row[0]}::${row[1]}::${row[2]}`);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
catch {
|
|
300
|
-
/* safe */
|
|
301
|
-
}
|
|
302
|
-
return keys;
|
|
303
|
-
}
|
|
304
|
-
async function deleteFileFromGraph(db, relPath) {
|
|
305
|
-
const affectedKeys = new Set();
|
|
306
|
-
let entitiesDeleted = 0;
|
|
307
|
-
let edgesDeleted = 0;
|
|
308
|
-
try {
|
|
309
|
-
const result = await db.run("?[entity_key] := *file_index{file_path: $fp, entity_key}", { fp: relPath });
|
|
310
|
-
const entityKeys = result.rows.map((r) => r[0]);
|
|
311
|
-
if (entityKeys.length > 0) {
|
|
312
|
-
edgesDeleted += await removeEntitiesAndEdgesBatched(db, entityKeys);
|
|
313
|
-
entitiesDeleted += entityKeys.length;
|
|
314
|
-
for (const key of entityKeys)
|
|
315
|
-
affectedKeys.add(key);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
catch {
|
|
319
|
-
/* file not in index */
|
|
320
|
-
}
|
|
321
|
-
// Remove file_index entries
|
|
322
|
-
try {
|
|
323
|
-
await db.write("?[file_path, entity_key] := *file_index{file_path: $fp, entity_key} :rm file_index { file_path, entity_key }", { fp: relPath });
|
|
324
|
-
}
|
|
325
|
-
catch {
|
|
326
|
-
/* safe */
|
|
327
|
-
}
|
|
328
|
-
// Remove file entity itself
|
|
329
|
-
try {
|
|
330
|
-
await db.write("?[key] <- [[$key]] :rm entities { key }", {
|
|
331
|
-
key: `file:${relPath}`,
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
catch {
|
|
335
|
-
/* safe */
|
|
336
|
-
}
|
|
337
|
-
return { entitiesDeleted, edgesDeleted, affectedKeys };
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* Remove multiple entities and all their edges in batched queries.
|
|
341
|
-
* Replaces per-entity sequential removeEntityAndEdges calls.
|
|
342
|
-
*/
|
|
343
|
-
async function removeEntitiesAndEdgesBatched(db, entityKeys) {
|
|
344
|
-
let edgesRemoved = 0;
|
|
345
|
-
for (const key of entityKeys) {
|
|
346
|
-
// Remove outgoing edges
|
|
347
|
-
try {
|
|
348
|
-
const result = await db.write("?[from_key, to_key, type] := *edges{from_key: $key, to_key, type} :rm edges { from_key, to_key, type }", { key });
|
|
349
|
-
edgesRemoved += result.rows?.length ?? 0;
|
|
350
|
-
}
|
|
351
|
-
catch {
|
|
352
|
-
/* safe */
|
|
353
|
-
}
|
|
354
|
-
// Remove incoming edges
|
|
355
|
-
try {
|
|
356
|
-
const result = await db.write("?[from_key, to_key, type] := *edges{from_key, to_key: $key, type} :rm edges { from_key, to_key, type }", { key });
|
|
357
|
-
edgesRemoved += result.rows?.length ?? 0;
|
|
358
|
-
}
|
|
359
|
-
catch {
|
|
360
|
-
/* safe */
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
// Batch remove entities
|
|
364
|
-
if (entityKeys.length > 0) {
|
|
365
|
-
const rows = entityKeys
|
|
366
|
-
.map((k) => `["${k.replace(/"/g, '\\"')}"]`)
|
|
367
|
-
.join(", ");
|
|
368
|
-
try {
|
|
369
|
-
await db.write(`?[key] <- [${rows}] :rm entities { key }`);
|
|
370
|
-
}
|
|
371
|
-
catch {
|
|
372
|
-
/* safe */
|
|
373
|
-
}
|
|
374
|
-
// Batch remove file_index entries
|
|
375
|
-
for (const key of entityKeys) {
|
|
376
|
-
try {
|
|
377
|
-
await db.write("?[file_path, entity_key] := *file_index{file_path, entity_key}, entity_key = $key :rm file_index { file_path, entity_key }", { key });
|
|
378
|
-
}
|
|
379
|
-
catch {
|
|
380
|
-
/* safe */
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return edgesRemoved;
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Remove all edges (in + out) for multiple entity keys.
|
|
388
|
-
*/
|
|
389
|
-
async function removeEdgesForKeysBatched(db, entityKeys) {
|
|
390
|
-
for (const key of entityKeys) {
|
|
391
|
-
try {
|
|
392
|
-
await db.write("?[from_key, to_key, type] := *edges{from_key: $key, to_key, type} :rm edges { from_key, to_key, type }", { key });
|
|
393
|
-
}
|
|
394
|
-
catch {
|
|
395
|
-
/* safe */
|
|
396
|
-
}
|
|
397
|
-
try {
|
|
398
|
-
await db.write("?[from_key, to_key, type] := *edges{from_key, to_key: $key, type} :rm edges { from_key, to_key, type }", { key });
|
|
399
|
-
}
|
|
400
|
-
catch {
|
|
401
|
-
/* safe */
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Upsert multiple entities in a single CozoDB :put operation.
|
|
407
|
-
*/
|
|
408
|
-
async function upsertEntitiesBatched(db, entities) {
|
|
409
|
-
if (entities.length === 0)
|
|
410
|
-
return;
|
|
411
|
-
// CozoDB supports multi-row :put — build the data rows
|
|
412
|
-
const rows = entities.map((e) => [
|
|
413
|
-
e.key,
|
|
414
|
-
e.kind,
|
|
415
|
-
e.name,
|
|
416
|
-
e.file_path,
|
|
417
|
-
e.start_line ?? 0,
|
|
418
|
-
e.end_line ?? 0,
|
|
419
|
-
e.signature ?? "",
|
|
420
|
-
e.body ?? "",
|
|
421
|
-
e.fan_in ?? 0,
|
|
422
|
-
e.fan_out ?? 0,
|
|
423
|
-
e.risk_level ?? "normal",
|
|
424
|
-
e.is_test ?? false,
|
|
425
|
-
]);
|
|
426
|
-
// Build inline data: ?[...] <- [[...], [...], ...]
|
|
427
|
-
const rowStrs = rows.map((r) => {
|
|
428
|
-
const vals = r.map((v) => {
|
|
429
|
-
if (typeof v === "string")
|
|
430
|
-
return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
431
|
-
if (typeof v === "boolean")
|
|
432
|
-
return v ? "true" : "false";
|
|
433
|
-
return String(v);
|
|
434
|
-
});
|
|
435
|
-
return `[${vals.join(", ")}]`;
|
|
436
|
-
});
|
|
437
|
-
try {
|
|
438
|
-
await db.write(`?[key, kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [${rowStrs.join(", ")}]
|
|
439
|
-
:put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test }`);
|
|
440
|
-
}
|
|
441
|
-
catch {
|
|
442
|
-
// Fallback: try one-by-one if batch fails (e.g., encoding issues)
|
|
443
|
-
for (const entity of entities) {
|
|
444
|
-
try {
|
|
445
|
-
await db.write(`?[key, kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test] <- [[$key, $kind, $name, $fp, $sl, $el, $sig, $body, $fi, $fo, $rl, $is_test]]
|
|
446
|
-
:put entities { key => kind, name, file_path, start_line, end_line, signature, body, fan_in, fan_out, risk_level, is_test }`, {
|
|
447
|
-
key: entity.key,
|
|
448
|
-
kind: entity.kind,
|
|
449
|
-
name: entity.name,
|
|
450
|
-
fp: entity.file_path,
|
|
451
|
-
sl: entity.start_line ?? 0,
|
|
452
|
-
el: entity.end_line ?? 0,
|
|
453
|
-
sig: entity.signature ?? "",
|
|
454
|
-
body: entity.body ?? "",
|
|
455
|
-
fi: entity.fan_in ?? 0,
|
|
456
|
-
fo: entity.fan_out ?? 0,
|
|
457
|
-
rl: entity.risk_level ?? "normal",
|
|
458
|
-
is_test: entity.is_test ?? false,
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
catch {
|
|
462
|
-
/* safe */
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* Remove edges by exact (from_key, to_key, type) tuples — batched.
|
|
469
|
-
*/
|
|
470
|
-
async function removeEdgesBatched(db, edges) {
|
|
471
|
-
if (edges.length === 0)
|
|
472
|
-
return;
|
|
473
|
-
const rowStrs = edges.map(([fk, tk, t]) => `["${fk.replace(/"/g, '\\"')}", "${tk.replace(/"/g, '\\"')}", "${t.replace(/"/g, '\\"')}"]`);
|
|
474
|
-
try {
|
|
475
|
-
await db.write(`?[from_key, to_key, type] <- [${rowStrs.join(", ")}] :rm edges { from_key, to_key, type }`);
|
|
476
|
-
}
|
|
477
|
-
catch {
|
|
478
|
-
// Fallback one-by-one
|
|
479
|
-
for (const [fk, tk, type] of edges) {
|
|
480
|
-
try {
|
|
481
|
-
await db.write("?[from_key, to_key, type] <- [[$fk, $tk, $type]] :rm edges { from_key, to_key, type }", { fk, tk, type });
|
|
482
|
-
}
|
|
483
|
-
catch {
|
|
484
|
-
/* safe */
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* Insert edges in batch — single :put with all edge rows.
|
|
491
|
-
*/
|
|
492
|
-
async function insertEdgesBatched(db, edges) {
|
|
493
|
-
if (edges.length === 0)
|
|
494
|
-
return 0;
|
|
495
|
-
const rowStrs = edges.map(([fk, tk, t]) => {
|
|
496
|
-
const eFk = fk.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
497
|
-
const eTk = tk.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
498
|
-
const eT = t.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
499
|
-
return `["${eFk}", "${eTk}", "${eT}", -1, "", "", false, "", 0, false, false, "", ""]`;
|
|
500
|
-
});
|
|
501
|
-
try {
|
|
502
|
-
await db.write(`?[from_key, to_key, type, sequence_order, condition, branch_kind, is_loop, loop_kind, nesting_depth, is_try_guarded, is_error_handler, mutation_target, mutation_mode] <- [${rowStrs.join(", ")}]
|
|
503
|
-
:put edges { from_key, to_key, type => sequence_order, condition, branch_kind, is_loop, loop_kind, nesting_depth, is_try_guarded, is_error_handler, mutation_target, mutation_mode }`);
|
|
504
|
-
return edges.length;
|
|
505
|
-
}
|
|
506
|
-
catch {
|
|
507
|
-
// Fallback one-by-one
|
|
508
|
-
let inserted = 0;
|
|
509
|
-
for (const [fk, tk, type] of edges) {
|
|
510
|
-
try {
|
|
511
|
-
await db.write(`?[from_key, to_key, type, sequence_order, condition, branch_kind, is_loop, loop_kind, nesting_depth, is_try_guarded, is_error_handler, mutation_target, mutation_mode] <- [[$fk, $tk, $type, -1, "", "", false, "", 0, false, false, "", ""]]
|
|
512
|
-
:put edges { from_key, to_key, type => sequence_order, condition, branch_kind, is_loop, loop_kind, nesting_depth, is_try_guarded, is_error_handler, mutation_target, mutation_mode }`, { fk, tk, type });
|
|
513
|
-
inserted++;
|
|
514
|
-
}
|
|
515
|
-
catch {
|
|
516
|
-
/* edge conflict — safe */
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
return inserted;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* Update file_index and contains edges for upserted entities — batched.
|
|
524
|
-
*/
|
|
525
|
-
async function updateFileIndexBatched(db, relPath, entities) {
|
|
526
|
-
if (entities.length === 0)
|
|
527
|
-
return;
|
|
528
|
-
// Batch file_index :put
|
|
529
|
-
const indexRows = entities.map((e) => {
|
|
530
|
-
const ek = e.key.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
531
|
-
const fp = relPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
532
|
-
return `["${fp}", "${ek}"]`;
|
|
533
|
-
});
|
|
534
|
-
try {
|
|
535
|
-
await db.write(`?[file_path, entity_key] <- [${indexRows.join(", ")}] :put file_index { file_path, entity_key }`);
|
|
536
|
-
}
|
|
537
|
-
catch {
|
|
538
|
-
/* safe */
|
|
539
|
-
}
|
|
540
|
-
// Batch contains edges :put
|
|
541
|
-
const containsRows = entities.map((e) => {
|
|
542
|
-
const from = `file:${relPath}`.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
543
|
-
const to = e.key.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
544
|
-
return `["${from}", "${to}", "contains", -1, "", "", false, "", 0, false, false, "", ""]`;
|
|
545
|
-
});
|
|
546
|
-
try {
|
|
547
|
-
await db.write(`?[from_key, to_key, type, sequence_order, condition, branch_kind, is_loop, loop_kind, nesting_depth, is_try_guarded, is_error_handler, mutation_target, mutation_mode] <- [${containsRows.join(", ")}]
|
|
548
|
-
:put edges { from_key, to_key, type => sequence_order, condition, branch_kind, is_loop, loop_kind, nesting_depth, is_try_guarded, is_error_handler, mutation_target, mutation_mode }`);
|
|
549
|
-
}
|
|
550
|
-
catch {
|
|
551
|
-
/* safe */
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* Resolve multiple entity names to keys in a single query.
|
|
556
|
-
* Replaces the old per-name sequential resolveEntityNameGlobal calls.
|
|
557
|
-
*/
|
|
558
|
-
async function resolveEntityNamesGlobal(names, db) {
|
|
559
|
-
const resolved = new Map();
|
|
560
|
-
if (names.length === 0)
|
|
561
|
-
return resolved;
|
|
562
|
-
// Build inline data for name lookup
|
|
563
|
-
const nameRows = names
|
|
564
|
-
.map((n) => `["${n.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`)
|
|
565
|
-
.join(", ");
|
|
566
|
-
try {
|
|
567
|
-
const result = await db.run(`
|
|
568
|
-
lookup[n] <- [${nameRows}]
|
|
569
|
-
?[n, key] := lookup[n], *entities{key, name}, name = n
|
|
570
|
-
`);
|
|
571
|
-
for (const row of result.rows) {
|
|
572
|
-
const [name, key] = row;
|
|
573
|
-
// Only take first match per name (equivalent to old :limit 1)
|
|
574
|
-
if (!resolved.has(name)) {
|
|
575
|
-
resolved.set(name, key);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
catch {
|
|
580
|
-
/* safe — caller handles missing resolutions */
|
|
581
|
-
}
|
|
582
|
-
return resolved;
|
|
583
|
-
}
|
|
584
|
-
function resolveLocalEntityName(name, filePath, repoId, entities) {
|
|
585
|
-
if (name === "__file__")
|
|
586
|
-
return `file:${filePath}`;
|
|
587
|
-
const match = entities.find((e) => e.name === name);
|
|
588
|
-
if (match) {
|
|
589
|
-
return entityKey(repoId, filePath, match.kind, match.name, match.signature);
|
|
590
|
-
}
|
|
591
|
-
return null;
|
|
592
|
-
}
|
|
593
|
-
/**
|
|
594
|
-
* Update fan_in/fan_out for all affected entities in batched queries.
|
|
595
|
-
* Replaces the old 3-queries-per-entity pattern with 2 aggregate queries + 1 batch update.
|
|
596
|
-
*/
|
|
597
|
-
async function updateFanCountsBatched(db, affectedKeys) {
|
|
598
|
-
// Filter to real entity keys
|
|
599
|
-
const keys = [...affectedKeys].filter((k) => !k.startsWith("file:") && !k.startsWith("unresolved:"));
|
|
600
|
-
if (keys.length === 0)
|
|
601
|
-
return;
|
|
602
|
-
// Build lookup data for all affected keys
|
|
603
|
-
const keyRows = keys
|
|
604
|
-
.map((k) => `["${k.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`)
|
|
605
|
-
.join(", ");
|
|
606
|
-
const fanOutMap = new Map();
|
|
607
|
-
const fanInMap = new Map();
|
|
608
|
-
// Single query: fan_out for all affected keys
|
|
609
|
-
try {
|
|
610
|
-
const outResult = await db.run(`
|
|
611
|
-
targets[k] <- [${keyRows}]
|
|
612
|
-
?[k, count(to_key)] := targets[k], *edges{from_key: k, to_key, type}, type != "contains"
|
|
613
|
-
`);
|
|
614
|
-
for (const row of outResult.rows) {
|
|
615
|
-
fanOutMap.set(row[0], row[1]);
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
catch {
|
|
619
|
-
/* safe */
|
|
620
|
-
}
|
|
621
|
-
// Single query: fan_in for all affected keys
|
|
622
|
-
try {
|
|
623
|
-
const inResult = await db.run(`
|
|
624
|
-
targets[k] <- [${keyRows}]
|
|
625
|
-
?[k, count(from_key)] := targets[k], *edges{from_key, to_key: k, type}, type != "contains"
|
|
626
|
-
`);
|
|
627
|
-
for (const row of inResult.rows) {
|
|
628
|
-
fanInMap.set(row[0], row[1]);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
catch {
|
|
632
|
-
/* safe */
|
|
633
|
-
}
|
|
634
|
-
// Build batch update rows
|
|
635
|
-
const updateRows = keys.map((k) => {
|
|
636
|
-
const fi = fanInMap.get(k) ?? 0;
|
|
637
|
-
const fo = fanOutMap.get(k) ?? 0;
|
|
638
|
-
return `["${k.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}", ${fi}, ${fo}]`;
|
|
639
|
-
});
|
|
640
|
-
// Single batch update
|
|
641
|
-
try {
|
|
642
|
-
await db.write(`?[key, fan_in, fan_out] <- [${updateRows.join(", ")}]
|
|
643
|
-
:update entities { key => fan_in, fan_out }`);
|
|
644
|
-
}
|
|
645
|
-
catch {
|
|
646
|
-
// Fallback: some keys may no longer exist — try individually
|
|
647
|
-
for (const key of keys) {
|
|
648
|
-
const fi = fanInMap.get(key) ?? 0;
|
|
649
|
-
const fo = fanOutMap.get(key) ?? 0;
|
|
650
|
-
try {
|
|
651
|
-
await db.write("?[key, fan_in, fan_out] <- [[$key, $fi, $fo]] :update entities { key => fan_in, fan_out }", { key, fi, fo });
|
|
652
|
-
}
|
|
653
|
-
catch {
|
|
654
|
-
/* entity deleted — safe */
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|