@unerr-ai/unerr 0.2.1 → 0.2.2
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 +6 -0
- package/dist/cli.js +37236 -35793
- package/package.json +1 -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,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centrality Computation — fan-in/fan-out counts + hub node detection.
|
|
3
|
-
*
|
|
4
|
-
* Post-processing pass over resolved edges to compute:
|
|
5
|
-
* - fan_in: how many entities call this entity
|
|
6
|
-
* - fan_out: how many entities this entity calls
|
|
7
|
-
* - risk_level: "critical" (50+), "high" (20+), "medium" (10+), "normal"
|
|
8
|
-
*
|
|
9
|
-
* Hub nodes (high fan_in) are the chokepoints of the codebase — changes
|
|
10
|
-
* to them have the widest blast radius.
|
|
11
|
-
*/
|
|
12
|
-
const RISK_THRESHOLDS = {
|
|
13
|
-
critical: 50,
|
|
14
|
-
high: 20,
|
|
15
|
-
medium: 10,
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Compute fan-in and fan-out for all entities from resolved edges.
|
|
19
|
-
*/
|
|
20
|
-
export function computeCentrality(edges) {
|
|
21
|
-
const fanIn = new Map();
|
|
22
|
-
const fanOut = new Map();
|
|
23
|
-
const callEdges = edges.filter((e) => e.type === "calls");
|
|
24
|
-
for (const edge of callEdges) {
|
|
25
|
-
fanOut.set(edge.from_key, (fanOut.get(edge.from_key) ?? 0) + 1);
|
|
26
|
-
fanIn.set(edge.to_key, (fanIn.get(edge.to_key) ?? 0) + 1);
|
|
27
|
-
}
|
|
28
|
-
const hubNodes = [...fanIn.entries()]
|
|
29
|
-
.filter(([_, count]) => count >= RISK_THRESHOLDS.high)
|
|
30
|
-
.sort((a, b) => b[1] - a[1])
|
|
31
|
-
.map(([key]) => key);
|
|
32
|
-
return { fanIn, fanOut, hubNodes };
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Determine risk level from fan_in count.
|
|
36
|
-
*/
|
|
37
|
-
export function riskLevel(fanInCount) {
|
|
38
|
-
if (fanInCount >= RISK_THRESHOLDS.critical)
|
|
39
|
-
return "critical";
|
|
40
|
-
if (fanInCount >= RISK_THRESHOLDS.high)
|
|
41
|
-
return "high";
|
|
42
|
-
if (fanInCount >= RISK_THRESHOLDS.medium)
|
|
43
|
-
return "medium";
|
|
44
|
-
return "normal";
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Apply centrality metrics to entities.
|
|
48
|
-
* Returns updated entities with fan_in, fan_out, and risk_level set.
|
|
49
|
-
*/
|
|
50
|
-
export function applyCentrality(entities, edges) {
|
|
51
|
-
const { fanIn, fanOut } = computeCentrality(edges);
|
|
52
|
-
return entities.map((entity) => {
|
|
53
|
-
const fi = fanIn.get(entity.key) ?? 0;
|
|
54
|
-
const fo = fanOut.get(entity.key) ?? 0;
|
|
55
|
-
return {
|
|
56
|
-
...entity,
|
|
57
|
-
fan_in: fi,
|
|
58
|
-
fan_out: fo,
|
|
59
|
-
risk_level: riskLevel(fi),
|
|
60
|
-
};
|
|
61
|
-
});
|
|
62
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CFG Context Extractor — walks up the AST from a call site to annotate
|
|
3
|
-
* the control-flow context of each edge.
|
|
4
|
-
*
|
|
5
|
-
* Detects:
|
|
6
|
-
* - try/catch guarding: call is inside a try block
|
|
7
|
-
* - loop context: call is inside for/while/do-while/for-of/for-in
|
|
8
|
-
* - conditional: call is inside if/else/switch/ternary
|
|
9
|
-
* - error handler: call is inside a catch/finally block
|
|
10
|
-
* - async/await: call is awaited
|
|
11
|
-
*/
|
|
12
|
-
const LOOP_TYPES = new Set([
|
|
13
|
-
"for_statement",
|
|
14
|
-
"for_in_statement",
|
|
15
|
-
"while_statement",
|
|
16
|
-
"do_statement",
|
|
17
|
-
]);
|
|
18
|
-
const CONDITIONAL_TYPES = new Set([
|
|
19
|
-
"if_statement",
|
|
20
|
-
"switch_statement",
|
|
21
|
-
"ternary_expression",
|
|
22
|
-
"conditional_expression",
|
|
23
|
-
]);
|
|
24
|
-
/**
|
|
25
|
-
* Extract CFG context by walking up from a node to the function boundary.
|
|
26
|
-
*/
|
|
27
|
-
export function extractCFGContext(node) {
|
|
28
|
-
const ctx = {
|
|
29
|
-
isTryGuarded: false,
|
|
30
|
-
isLoop: false,
|
|
31
|
-
isConditional: false,
|
|
32
|
-
isErrorHandler: false,
|
|
33
|
-
isAsync: false,
|
|
34
|
-
nestingDepth: 0,
|
|
35
|
-
};
|
|
36
|
-
if (node.parent?.type === "await_expression") {
|
|
37
|
-
ctx.isAsync = true;
|
|
38
|
-
}
|
|
39
|
-
let current = node.parent;
|
|
40
|
-
while (current) {
|
|
41
|
-
if (current.type === "function_declaration" ||
|
|
42
|
-
current.type === "arrow_function" ||
|
|
43
|
-
current.type === "method_definition" ||
|
|
44
|
-
current.type === "function_expression") {
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
if (current.type === "try_statement") {
|
|
48
|
-
const tryBody = current.childForFieldName("body");
|
|
49
|
-
if (tryBody && isDescendantOf(node, tryBody)) {
|
|
50
|
-
ctx.isTryGuarded = true;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (current.type === "catch_clause" || current.type === "finally_clause") {
|
|
54
|
-
ctx.isErrorHandler = true;
|
|
55
|
-
}
|
|
56
|
-
if (LOOP_TYPES.has(current.type)) {
|
|
57
|
-
ctx.isLoop = true;
|
|
58
|
-
ctx.nestingDepth++;
|
|
59
|
-
}
|
|
60
|
-
if (CONDITIONAL_TYPES.has(current.type)) {
|
|
61
|
-
ctx.isConditional = true;
|
|
62
|
-
ctx.nestingDepth++;
|
|
63
|
-
}
|
|
64
|
-
current = current.parent;
|
|
65
|
-
}
|
|
66
|
-
return ctx;
|
|
67
|
-
}
|
|
68
|
-
function isDescendantOf(node, ancestor) {
|
|
69
|
-
let current = node;
|
|
70
|
-
while (current) {
|
|
71
|
-
if (current.id === ancestor.id)
|
|
72
|
-
return true;
|
|
73
|
-
current = current.parent;
|
|
74
|
-
}
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Flatten CFG context into edge metadata fields.
|
|
79
|
-
*/
|
|
80
|
-
export function cfgToEdgeFields(cfg) {
|
|
81
|
-
const fields = {};
|
|
82
|
-
if (cfg.isTryGuarded)
|
|
83
|
-
fields.is_try_guarded = true;
|
|
84
|
-
if (cfg.isLoop)
|
|
85
|
-
fields.is_loop = true;
|
|
86
|
-
if (cfg.isConditional)
|
|
87
|
-
fields.is_conditional = true;
|
|
88
|
-
if (cfg.isErrorHandler)
|
|
89
|
-
fields.is_error_handler = true;
|
|
90
|
-
if (cfg.isAsync)
|
|
91
|
-
fields.is_async = true;
|
|
92
|
-
if (cfg.nestingDepth > 0)
|
|
93
|
-
fields.nesting_depth = cfg.nestingDepth;
|
|
94
|
-
return fields;
|
|
95
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Confidence Labeling — tracks provenance of extracted entities and edges.
|
|
3
|
-
*
|
|
4
|
-
* Confidence levels:
|
|
5
|
-
* - "compiler-verified": From SCIP (compiler-resolved, highest accuracy)
|
|
6
|
-
* - "structural": From tree-sitter AST (syntactic structure, high accuracy)
|
|
7
|
-
* - "heuristic": From regex fallback or heuristic rules (lower accuracy)
|
|
8
|
-
*
|
|
9
|
-
* Every entity and edge carries a confidence label that downstream
|
|
10
|
-
* consumers can use to weight results.
|
|
11
|
-
*/
|
|
12
|
-
export function labelFromTier(tier) {
|
|
13
|
-
switch (tier) {
|
|
14
|
-
case 1:
|
|
15
|
-
return { level: "structural", source: "tree-sitter-tier1" };
|
|
16
|
-
case 2:
|
|
17
|
-
return { level: "structural", source: "tree-sitter-tier2" };
|
|
18
|
-
case 3:
|
|
19
|
-
return { level: "heuristic", source: "regex-fallback" };
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
export function upgradeToCompilerVerified(label) {
|
|
23
|
-
return { level: "compiler-verified", source: `scip+${label.source}` };
|
|
24
|
-
}
|
|
25
|
-
export function confidenceScore(level) {
|
|
26
|
-
switch (level) {
|
|
27
|
-
case "compiler-verified":
|
|
28
|
-
return 1.0;
|
|
29
|
-
case "structural":
|
|
30
|
-
return 0.85;
|
|
31
|
-
case "heuristic":
|
|
32
|
-
return 0.5;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-File Call Resolver — resolves import → entity → calls edges.
|
|
3
|
-
*
|
|
4
|
-
* Given per-file extraction results with unresolved call edges (to_key starts
|
|
5
|
-
* with "unresolved:"), resolves them to actual entity keys using the export map.
|
|
6
|
-
*
|
|
7
|
-
* Also handles:
|
|
8
|
-
* J.3 — Barrel file / re-export chain resolution
|
|
9
|
-
* - export { X } from "./other" → follow to original entity
|
|
10
|
-
* - export * from "./module" → resolve all namespace imports
|
|
11
|
-
*/
|
|
12
|
-
import { buildExportMap } from "./export-map.js";
|
|
13
|
-
/**
|
|
14
|
-
* Resolve all unresolved cross-file edges using the export map.
|
|
15
|
-
*
|
|
16
|
-
* For each file's edges where to_key starts with "unresolved:", look up the
|
|
17
|
-
* callee name in the file's import map, then resolve via the export map.
|
|
18
|
-
*/
|
|
19
|
-
export function resolveCrossFileEdges(fileResults) {
|
|
20
|
-
const exportMap = buildExportMap(fileResults);
|
|
21
|
-
const allResolvedEdges = [];
|
|
22
|
-
// R.3: Track file→file import relationships (deduplicated by pair)
|
|
23
|
-
const fileImportPairs = new Set();
|
|
24
|
-
let unresolvedCount = 0;
|
|
25
|
-
let resolvedCount = 0;
|
|
26
|
-
for (const [filePath, result] of fileResults) {
|
|
27
|
-
const importLookup = buildImportLookup(result.imports, filePath);
|
|
28
|
-
const localEntityNames = new Map(result.entities.map((e) => [e.name, e.key]));
|
|
29
|
-
// R.3: Emit file→file imports edges from the import declarations directly
|
|
30
|
-
for (const imp of result.imports) {
|
|
31
|
-
if (imp.source && imp.source !== filePath) {
|
|
32
|
-
const pairKey = `${filePath}\0${imp.source}`;
|
|
33
|
-
fileImportPairs.add(pairKey);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
for (const edge of result.edges) {
|
|
37
|
-
if (!edge.to_key.startsWith("unresolved:")) {
|
|
38
|
-
allResolvedEdges.push(edge);
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
const calleeName = edge.to_key.slice("unresolved:".length);
|
|
42
|
-
const localKey = localEntityNames.get(calleeName);
|
|
43
|
-
if (localKey) {
|
|
44
|
-
allResolvedEdges.push({ ...edge, to_key: localKey });
|
|
45
|
-
resolvedCount++;
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
const importedFrom = importLookup.get(calleeName);
|
|
49
|
-
if (importedFrom) {
|
|
50
|
-
const resolved = exportMap.resolveSymbol(importedFrom.source, importedFrom.originalName, filePath);
|
|
51
|
-
if (resolved) {
|
|
52
|
-
allResolvedEdges.push({ ...edge, to_key: resolved.entityKey });
|
|
53
|
-
resolvedCount++;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
unresolvedCount++;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
// R.3: Build deduplicated file→file import edges
|
|
61
|
-
const fileImportEdges = Array.from(fileImportPairs, (pair) => {
|
|
62
|
-
const [from, to] = pair.split("\0");
|
|
63
|
-
return {
|
|
64
|
-
from_key: `file:${from}`,
|
|
65
|
-
to_key: `file:${to}`,
|
|
66
|
-
type: "imports",
|
|
67
|
-
file_path: from,
|
|
68
|
-
line: 0,
|
|
69
|
-
};
|
|
70
|
-
});
|
|
71
|
-
return {
|
|
72
|
-
resolvedEdges: allResolvedEdges,
|
|
73
|
-
fileImportEdges,
|
|
74
|
-
unresolvedCount,
|
|
75
|
-
resolvedCount,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
function buildImportLookup(imports, filePath) {
|
|
79
|
-
const lookup = new Map();
|
|
80
|
-
for (const imp of imports) {
|
|
81
|
-
if (imp.isDefault && imp.localName) {
|
|
82
|
-
lookup.set(imp.localName, {
|
|
83
|
-
source: imp.source,
|
|
84
|
-
originalName: "default",
|
|
85
|
-
isDefault: true,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
if (imp.isNamespace && imp.localName) {
|
|
89
|
-
lookup.set(imp.localName, {
|
|
90
|
-
source: imp.source,
|
|
91
|
-
originalName: "*",
|
|
92
|
-
isDefault: false,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
for (const symbol of imp.symbols) {
|
|
96
|
-
lookup.set(symbol, {
|
|
97
|
-
source: imp.source,
|
|
98
|
-
originalName: symbol,
|
|
99
|
-
isDefault: false,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return lookup;
|
|
104
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-File Edge Repair — when an import target changes, re-resolve edges.
|
|
3
|
-
*
|
|
4
|
-
* After incremental reindex of a changed file, other files that import
|
|
5
|
-
* from it may have stale edge targets. This module identifies affected
|
|
6
|
-
* files and repairs their cross-file edges.
|
|
7
|
-
*/
|
|
8
|
-
import { createModuleLogger } from "../../utils/logger.js";
|
|
9
|
-
import { buildExportMap } from "./export-map.js";
|
|
10
|
-
const log = createModuleLogger("edge-repair");
|
|
11
|
-
/**
|
|
12
|
-
* Find files that import from a changed file.
|
|
13
|
-
*/
|
|
14
|
-
export function findDependentFiles(changedFile, allImports) {
|
|
15
|
-
const dependents = [];
|
|
16
|
-
const changedBase = stripExtension(changedFile);
|
|
17
|
-
for (const [filePath, imports] of allImports) {
|
|
18
|
-
if (filePath === changedFile)
|
|
19
|
-
continue;
|
|
20
|
-
for (const imp of imports) {
|
|
21
|
-
if (!imp.source.startsWith("."))
|
|
22
|
-
continue;
|
|
23
|
-
const { dirname, join, normalize } = require("node:path");
|
|
24
|
-
const importerDir = dirname(filePath);
|
|
25
|
-
let resolved = normalize(join(importerDir, imp.source)).replace(/\\/g, "/");
|
|
26
|
-
if (resolved.startsWith("./"))
|
|
27
|
-
resolved = resolved.slice(2);
|
|
28
|
-
if (stripExtension(resolved) === changedBase ||
|
|
29
|
-
resolved === changedFile) {
|
|
30
|
-
dependents.push(filePath);
|
|
31
|
-
break;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return dependents;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Repair cross-file edges for dependent files after a target file changes.
|
|
39
|
-
*/
|
|
40
|
-
export function repairCrossFileEdges(dependentFiles, fileResults) {
|
|
41
|
-
const exportMap = buildExportMap(fileResults);
|
|
42
|
-
const repairedEdges = [];
|
|
43
|
-
let count = 0;
|
|
44
|
-
for (const filePath of dependentFiles) {
|
|
45
|
-
const result = fileResults.get(filePath);
|
|
46
|
-
if (!result)
|
|
47
|
-
continue;
|
|
48
|
-
const importLookup = new Map();
|
|
49
|
-
for (const imp of result.imports) {
|
|
50
|
-
if (imp.isDefault && imp.localName) {
|
|
51
|
-
importLookup.set(imp.localName, {
|
|
52
|
-
source: imp.source,
|
|
53
|
-
originalName: "default",
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
for (const symbol of imp.symbols) {
|
|
57
|
-
importLookup.set(symbol, { source: imp.source, originalName: symbol });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
for (const edge of result.edges) {
|
|
61
|
-
if (!edge.to_key.startsWith("unresolved:")) {
|
|
62
|
-
repairedEdges.push(edge);
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
const calleeName = edge.to_key.slice("unresolved:".length);
|
|
66
|
-
const localKey = result.entities.find((e) => e.name === calleeName)?.key;
|
|
67
|
-
if (localKey) {
|
|
68
|
-
repairedEdges.push({ ...edge, to_key: localKey });
|
|
69
|
-
count++;
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
const imported = importLookup.get(calleeName);
|
|
73
|
-
if (imported) {
|
|
74
|
-
const resolved = exportMap.resolveSymbol(imported.source, imported.originalName, filePath);
|
|
75
|
-
if (resolved) {
|
|
76
|
-
repairedEdges.push({ ...edge, to_key: resolved.entityKey });
|
|
77
|
-
count++;
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
repairedEdges.push(edge);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
log.debug(`Repaired ${count} cross-file edges in ${dependentFiles.length} files`);
|
|
85
|
-
return { repairedEdges, count };
|
|
86
|
-
}
|
|
87
|
-
function stripExtension(filePath) {
|
|
88
|
-
return filePath.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
|
|
89
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic Entity Key Generation — stable across re-indexes.
|
|
3
|
-
*
|
|
4
|
-
* Keys are SHA-256 hashes of (filePath:kind:name:scope), truncated to 16 hex chars.
|
|
5
|
-
* This ensures:
|
|
6
|
-
* - Same entity always gets the same key
|
|
7
|
-
* - Different entities in different files never collide
|
|
8
|
-
* - Nested entities (methods in classes) include parent scope
|
|
9
|
-
*/
|
|
10
|
-
import { createHash } from "node:crypto";
|
|
11
|
-
export function entityKey(filePath, kind, name, scope = "") {
|
|
12
|
-
const input = `${filePath}:${kind}:${name}:${scope}`;
|
|
13
|
-
return createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
14
|
-
}
|
|
15
|
-
export function bodyHash(content) {
|
|
16
|
-
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
17
|
-
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Export Map Builder — builds a project-wide lookup of all exported entities.
|
|
3
|
-
*
|
|
4
|
-
* Given per-file extraction results, produces a map from (filePath, symbolName)
|
|
5
|
-
* to entity key. This enables cross-file call resolution: when file A imports
|
|
6
|
-
* `fn` from file B, we look up B's export map to find the entity key for `fn`.
|
|
7
|
-
*
|
|
8
|
-
* Handles:
|
|
9
|
-
* - Named exports: export function foo() {}
|
|
10
|
-
* - Default exports: export default class Bar {}
|
|
11
|
-
* - Re-exports: export { X } from "./other"
|
|
12
|
-
* - Barrel files: export * from "./module"
|
|
13
|
-
*/
|
|
14
|
-
/**
|
|
15
|
-
* Normalize an import path relative to the importer's directory.
|
|
16
|
-
* Handles: "./foo" → "src/foo", "../utils/bar" → "utils/bar"
|
|
17
|
-
*/
|
|
18
|
-
function resolveImportPath(importSource, importerFilePath) {
|
|
19
|
-
if (!importSource.startsWith("."))
|
|
20
|
-
return null;
|
|
21
|
-
// biome-ignore format: typeof import() must stay single-line for TS
|
|
22
|
-
const { dirname, join, normalize } = require("node:path");
|
|
23
|
-
const importerDir = dirname(importerFilePath);
|
|
24
|
-
let resolved = normalize(join(importerDir, importSource));
|
|
25
|
-
resolved = resolved.replace(/\\/g, "/");
|
|
26
|
-
if (resolved.startsWith("./"))
|
|
27
|
-
resolved = resolved.slice(2);
|
|
28
|
-
return resolved;
|
|
29
|
-
}
|
|
30
|
-
function stripExtension(filePath) {
|
|
31
|
-
return filePath.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Build the export map from per-file extraction results.
|
|
35
|
-
*/
|
|
36
|
-
export function buildExportMap(fileResults) {
|
|
37
|
-
const exports = new Map();
|
|
38
|
-
const reExports = new Map();
|
|
39
|
-
for (const [filePath, result] of fileResults) {
|
|
40
|
-
const fileExports = [];
|
|
41
|
-
for (const entity of result.entities) {
|
|
42
|
-
if (entity.exported) {
|
|
43
|
-
fileExports.push({
|
|
44
|
-
entityKey: entity.key,
|
|
45
|
-
name: entity.name,
|
|
46
|
-
kind: entity.kind,
|
|
47
|
-
filePath: entity.file_path,
|
|
48
|
-
isDefault: false,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
const fileReExports = [];
|
|
53
|
-
for (const imp of result.imports) {
|
|
54
|
-
if (imp.source.startsWith(".") && imp.symbols.length > 0) {
|
|
55
|
-
const isReExport = result.entities.some((e) => imp.symbols.includes(e.name) && e.exported);
|
|
56
|
-
if (!isReExport) {
|
|
57
|
-
fileReExports.push({
|
|
58
|
-
fromFile: imp.source,
|
|
59
|
-
symbols: imp.symbols,
|
|
60
|
-
isNamespace: imp.isNamespace,
|
|
61
|
-
line: imp.line,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
exports.set(filePath, fileExports);
|
|
67
|
-
if (fileReExports.length > 0) {
|
|
68
|
-
reExports.set(filePath, fileReExports);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
function getExport(filePath, symbolName) {
|
|
72
|
-
const fileExports = exports.get(filePath);
|
|
73
|
-
if (!fileExports)
|
|
74
|
-
return null;
|
|
75
|
-
return fileExports.find((e) => e.name === symbolName) ?? null;
|
|
76
|
-
}
|
|
77
|
-
function getDefaultExport(filePath) {
|
|
78
|
-
const fileExports = exports.get(filePath);
|
|
79
|
-
if (!fileExports)
|
|
80
|
-
return null;
|
|
81
|
-
return fileExports.find((e) => e.isDefault) ?? null;
|
|
82
|
-
}
|
|
83
|
-
function getAllExports(filePath) {
|
|
84
|
-
return exports.get(filePath) ?? [];
|
|
85
|
-
}
|
|
86
|
-
function getReExports(filePath) {
|
|
87
|
-
return reExports.get(filePath) ?? [];
|
|
88
|
-
}
|
|
89
|
-
function resolveSymbol(importSource, symbolName, importerPath) {
|
|
90
|
-
const resolvedPath = resolveImportPath(importSource, importerPath);
|
|
91
|
-
if (!resolvedPath)
|
|
92
|
-
return null;
|
|
93
|
-
const candidates = [
|
|
94
|
-
resolvedPath,
|
|
95
|
-
`${resolvedPath}.ts`,
|
|
96
|
-
`${resolvedPath}.tsx`,
|
|
97
|
-
`${resolvedPath}.js`,
|
|
98
|
-
`${resolvedPath}/index.ts`,
|
|
99
|
-
`${resolvedPath}/index.tsx`,
|
|
100
|
-
`${resolvedPath}/index.js`,
|
|
101
|
-
];
|
|
102
|
-
for (const candidate of candidates) {
|
|
103
|
-
const stripped = stripExtension(candidate);
|
|
104
|
-
for (const [filePath] of exports) {
|
|
105
|
-
if (stripExtension(filePath) === stripped || filePath === candidate) {
|
|
106
|
-
const found = getExport(filePath, symbolName);
|
|
107
|
-
if (found)
|
|
108
|
-
return found;
|
|
109
|
-
const fileReExports = reExports.get(filePath) ?? [];
|
|
110
|
-
for (const re of fileReExports) {
|
|
111
|
-
if (re.symbols.includes(symbolName) || re.isNamespace) {
|
|
112
|
-
const reResolved = resolveImportPath(re.fromFile, filePath);
|
|
113
|
-
if (reResolved) {
|
|
114
|
-
const deep = resolveSymbol(re.fromFile, symbolName, filePath);
|
|
115
|
-
if (deep)
|
|
116
|
-
return deep;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
return {
|
|
126
|
-
getExport,
|
|
127
|
-
getDefaultExport,
|
|
128
|
-
getAllExports,
|
|
129
|
-
getReExports,
|
|
130
|
-
resolveSymbol,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* R.4: Compute file→file co-change edges from git log.
|
|
3
|
-
*
|
|
4
|
-
* Two files that frequently change together in the same commit are likely
|
|
5
|
-
* structurally coupled (even if no import edge exists). This creates
|
|
6
|
-
* "co_changes" edges between file entities in the graph.
|
|
7
|
-
*
|
|
8
|
-
* Algorithm:
|
|
9
|
-
* 1. Read last N commits (default 100)
|
|
10
|
-
* 2. For each commit, extract the list of changed files
|
|
11
|
-
* 3. For each pair of files in the same commit, increment co-occurrence count
|
|
12
|
-
* 4. Normalize by individual file change counts (Jaccard-like)
|
|
13
|
-
* 5. Return top K pairs with highest correlation as edges
|
|
14
|
-
*/
|
|
15
|
-
import { execSync } from "node:child_process";
|
|
16
|
-
/**
|
|
17
|
-
* Compute co-change file pairs from git history.
|
|
18
|
-
*
|
|
19
|
-
* @param projectRoot - Absolute path to the git repo root
|
|
20
|
-
* @param maxCommits - Number of recent commits to analyze (default 100)
|
|
21
|
-
* @param topK - Maximum number of co-change pairs to return (default 20)
|
|
22
|
-
* @param minCoOccurrences - Minimum co-occurrences to include (default 3)
|
|
23
|
-
*/
|
|
24
|
-
export function computeCoChangeEdges(projectRoot, maxCommits = 100, topK = 20, minCoOccurrences = 3) {
|
|
25
|
-
const commits = getCommitFileLists(projectRoot, maxCommits);
|
|
26
|
-
if (commits.length === 0)
|
|
27
|
-
return [];
|
|
28
|
-
// Count individual file changes and pair co-occurrences
|
|
29
|
-
const fileChanges = new Map();
|
|
30
|
-
const pairCount = new Map();
|
|
31
|
-
for (const files of commits) {
|
|
32
|
-
// Skip very large commits (merges, bulk renames) — they add noise
|
|
33
|
-
if (files.length > 30)
|
|
34
|
-
continue;
|
|
35
|
-
for (const file of files) {
|
|
36
|
-
fileChanges.set(file, (fileChanges.get(file) ?? 0) + 1);
|
|
37
|
-
}
|
|
38
|
-
// Count co-occurrences for all pairs in this commit
|
|
39
|
-
for (let i = 0; i < files.length; i++) {
|
|
40
|
-
for (let j = i + 1; j < files.length; j++) {
|
|
41
|
-
const key = pairKey(files[i], files[j]);
|
|
42
|
-
pairCount.set(key, (pairCount.get(key) ?? 0) + 1);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
// Build edges for pairs that meet the threshold
|
|
47
|
-
const edges = [];
|
|
48
|
-
for (const [key, count] of pairCount) {
|
|
49
|
-
if (count < minCoOccurrences)
|
|
50
|
-
continue;
|
|
51
|
-
const [a, b] = key.split("\0");
|
|
52
|
-
const changesA = fileChanges.get(a) ?? 1;
|
|
53
|
-
const changesB = fileChanges.get(b) ?? 1;
|
|
54
|
-
const correlation = count / Math.min(changesA, changesB);
|
|
55
|
-
edges.push({
|
|
56
|
-
from_file: a,
|
|
57
|
-
to_file: b,
|
|
58
|
-
co_occurrences: count,
|
|
59
|
-
correlation,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
// Sort by correlation descending, take top K
|
|
63
|
-
edges.sort((a, b) => b.correlation - a.correlation);
|
|
64
|
-
return edges.slice(0, topK);
|
|
65
|
-
}
|
|
66
|
-
/** Canonical pair key (sorted to ensure A-B == B-A). */
|
|
67
|
-
function pairKey(a, b) {
|
|
68
|
-
return a < b ? `${a}\0${b}` : `${b}\0${a}`;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Get file lists per commit from git log.
|
|
72
|
-
* Returns array of arrays, each inner array is the list of changed files in one commit.
|
|
73
|
-
*/
|
|
74
|
-
function getCommitFileLists(projectRoot, maxCommits) {
|
|
75
|
-
let output;
|
|
76
|
-
try {
|
|
77
|
-
output = execSync(`git log --name-only --pretty=format:"" -n ${maxCommits}`, { cwd: projectRoot, encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 });
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return [];
|
|
81
|
-
}
|
|
82
|
-
// Split by double newlines (commit boundaries) then filter
|
|
83
|
-
const commits = [];
|
|
84
|
-
let currentFiles = [];
|
|
85
|
-
for (const line of output.split("\n")) {
|
|
86
|
-
if (line === "") {
|
|
87
|
-
if (currentFiles.length > 0) {
|
|
88
|
-
commits.push(currentFiles);
|
|
89
|
-
currentFiles = [];
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
// Only include source files (skip binary, lockfiles, etc.)
|
|
94
|
-
if (isSourceFile(line)) {
|
|
95
|
-
currentFiles.push(line);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (currentFiles.length > 0) {
|
|
100
|
-
commits.push(currentFiles);
|
|
101
|
-
}
|
|
102
|
-
return commits;
|
|
103
|
-
}
|
|
104
|
-
const SOURCE_EXTENSIONS = new Set([
|
|
105
|
-
".ts",
|
|
106
|
-
".tsx",
|
|
107
|
-
".js",
|
|
108
|
-
".jsx",
|
|
109
|
-
".mjs",
|
|
110
|
-
".cjs",
|
|
111
|
-
".py",
|
|
112
|
-
".go",
|
|
113
|
-
".rs",
|
|
114
|
-
".java",
|
|
115
|
-
".kt",
|
|
116
|
-
".rb",
|
|
117
|
-
".vue",
|
|
118
|
-
".svelte",
|
|
119
|
-
".css",
|
|
120
|
-
".scss",
|
|
121
|
-
".html",
|
|
122
|
-
]);
|
|
123
|
-
function isSourceFile(path) {
|
|
124
|
-
const dotIdx = path.lastIndexOf(".");
|
|
125
|
-
if (dotIdx < 0)
|
|
126
|
-
return false;
|
|
127
|
-
return SOURCE_EXTENSIONS.has(path.slice(dotIdx));
|
|
128
|
-
}
|