trace-mcp 1.9.0 → 1.10.0
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/dist/cli.js +612 -486
- package/dist/cli.js.map +1 -1
- package/dist/index.js +264 -469
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3650,7 +3650,7 @@ function captureGraphSnapshots(store, cwd) {
|
|
|
3650
3650
|
|
|
3651
3651
|
// src/db/schema.ts
|
|
3652
3652
|
import Database from "better-sqlite3";
|
|
3653
|
-
var SCHEMA_VERSION =
|
|
3653
|
+
var SCHEMA_VERSION = 18;
|
|
3654
3654
|
var DDL = `
|
|
3655
3655
|
-- ============================================================
|
|
3656
3656
|
-- UNIFIED ADDRESS SPACE
|
|
@@ -4830,57 +4830,41 @@ var FilePersister = class {
|
|
|
4830
4830
|
if (ext.gitignored) {
|
|
4831
4831
|
store.updateFileGitignored(fileId, true);
|
|
4832
4832
|
}
|
|
4833
|
-
|
|
4834
|
-
const insertedIds = store.insertSymbols(fileId, ext.symbols);
|
|
4835
|
-
const trigramBySymbolId = /* @__PURE__ */ new Map();
|
|
4836
|
-
for (let i = 0; i < ext.symbols.length; i++) {
|
|
4837
|
-
trigramBySymbolId.set(ext.symbols[i].symbolId, {
|
|
4838
|
-
id: insertedIds[i],
|
|
4839
|
-
name: ext.symbols[i].name,
|
|
4840
|
-
fqn: ext.symbols[i].fqn ?? null
|
|
4841
|
-
});
|
|
4842
|
-
}
|
|
4843
|
-
indexTrigramsBatch(store.db, [...trigramBySymbolId.values()]);
|
|
4844
|
-
}
|
|
4845
|
-
if (ext.otherEdges.length > 0) this.storeRawEdges(ext.otherEdges);
|
|
4833
|
+
this.persistSymbolsAndEntities(fileId, ext.symbols, ext.otherEdges, ext);
|
|
4846
4834
|
if (ext.importEdges.length > 0) {
|
|
4847
4835
|
this.state.pendingImports.set(fileId, ext.importEdges);
|
|
4848
4836
|
}
|
|
4849
|
-
for (const r of ext.routes) store.insertRoute(r, fileId);
|
|
4850
|
-
for (const c of ext.components) store.insertComponent(c, fileId);
|
|
4851
|
-
for (const m of ext.migrations) store.insertMigration(m, fileId);
|
|
4852
|
-
if (ext.ormModels.length > 0) {
|
|
4853
|
-
this.storeOrmResults(ext.ormModels, ext.ormAssociations, fileId);
|
|
4854
|
-
}
|
|
4855
|
-
for (const s of ext.rnScreens) store.insertRnScreen(s, fileId);
|
|
4856
4837
|
for (const fwResult of ext.frameworkExtracts) {
|
|
4857
|
-
|
|
4858
|
-
const fwIds = store.insertSymbols(fileId, fwResult.symbols);
|
|
4859
|
-
const fwTrigramBySymbolId = /* @__PURE__ */ new Map();
|
|
4860
|
-
for (let i = 0; i < fwResult.symbols.length; i++) {
|
|
4861
|
-
fwTrigramBySymbolId.set(fwResult.symbols[i].symbolId, {
|
|
4862
|
-
id: fwIds[i],
|
|
4863
|
-
name: fwResult.symbols[i].name,
|
|
4864
|
-
fqn: fwResult.symbols[i].fqn ?? null
|
|
4865
|
-
});
|
|
4866
|
-
}
|
|
4867
|
-
indexTrigramsBatch(store.db, [...fwTrigramBySymbolId.values()]);
|
|
4868
|
-
}
|
|
4869
|
-
if (fwResult.edges?.length) {
|
|
4870
|
-
this.storeRawEdges(fwResult.edges);
|
|
4871
|
-
}
|
|
4872
|
-
for (const r of fwResult.routes ?? []) store.insertRoute(r, fileId);
|
|
4873
|
-
for (const c of fwResult.components ?? []) store.insertComponent(c, fileId);
|
|
4874
|
-
for (const m of fwResult.migrations ?? []) store.insertMigration(m, fileId);
|
|
4875
|
-
if (fwResult.ormModels?.length) {
|
|
4876
|
-
this.storeOrmResults(fwResult.ormModels, fwResult.ormAssociations ?? [], fileId);
|
|
4877
|
-
}
|
|
4878
|
-
for (const s of fwResult.rnScreens ?? []) store.insertRnScreen(s, fileId);
|
|
4838
|
+
this.persistSymbolsAndEntities(fileId, fwResult.symbols, fwResult.edges ?? [], fwResult);
|
|
4879
4839
|
if (fwResult.frameworkRole) {
|
|
4880
4840
|
store.updateFileStatus(fileId, fwResult.status, fwResult.frameworkRole);
|
|
4881
4841
|
}
|
|
4882
4842
|
}
|
|
4883
4843
|
}
|
|
4844
|
+
/** Insert symbols+trigrams, edges, and entities (routes/components/migrations/ORM/screens). */
|
|
4845
|
+
persistSymbolsAndEntities(fileId, symbols, edges, entities) {
|
|
4846
|
+
const store = this.state.store;
|
|
4847
|
+
if (symbols.length > 0) {
|
|
4848
|
+
const insertedIds = store.insertSymbols(fileId, symbols);
|
|
4849
|
+
const trigramBySymbolId = /* @__PURE__ */ new Map();
|
|
4850
|
+
for (let i = 0; i < symbols.length; i++) {
|
|
4851
|
+
trigramBySymbolId.set(symbols[i].symbolId, {
|
|
4852
|
+
id: insertedIds[i],
|
|
4853
|
+
name: symbols[i].name,
|
|
4854
|
+
fqn: symbols[i].fqn ?? null
|
|
4855
|
+
});
|
|
4856
|
+
}
|
|
4857
|
+
indexTrigramsBatch(store.db, [...trigramBySymbolId.values()]);
|
|
4858
|
+
}
|
|
4859
|
+
if (edges.length > 0) this.storeRawEdges(edges);
|
|
4860
|
+
for (const r of entities.routes ?? []) store.insertRoute(r, fileId);
|
|
4861
|
+
for (const c of entities.components ?? []) store.insertComponent(c, fileId);
|
|
4862
|
+
for (const m of entities.migrations ?? []) store.insertMigration(m, fileId);
|
|
4863
|
+
if (entities.ormModels?.length) {
|
|
4864
|
+
this.storeOrmResults(entities.ormModels, entities.ormAssociations ?? [], fileId);
|
|
4865
|
+
}
|
|
4866
|
+
for (const s of entities.rnScreens ?? []) store.insertRnScreen(s, fileId);
|
|
4867
|
+
}
|
|
4884
4868
|
/**
|
|
4885
4869
|
* Fast path for incremental re-indexing: if the set of symbols is structurally
|
|
4886
4870
|
* identical (same symbolIds, names, kinds, fqns, signatures), only update byte
|
|
@@ -6040,58 +6024,10 @@ var IndexingPipeline = class _IndexingPipeline {
|
|
|
6040
6024
|
this._gitignore = new GitignoreMatcher(this.rootPath);
|
|
6041
6025
|
this._traceignore = new TraceignoreMatcher(this.rootPath, this.config.ignore);
|
|
6042
6026
|
this.registerFrameworkEdgeTypes();
|
|
6043
|
-
const extractor = new FileExtractor({
|
|
6044
|
-
store: this.store,
|
|
6045
|
-
registry: this.registry,
|
|
6046
|
-
rootPath: this.rootPath,
|
|
6047
|
-
workspaces: this.workspaces,
|
|
6048
|
-
gitignore: this._gitignore,
|
|
6049
|
-
fileContentCache: this._fileContentCache,
|
|
6050
|
-
buildProjectContext: () => this.buildProjectContext()
|
|
6051
|
-
});
|
|
6052
6027
|
try {
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
for (let i = 0; i < relPaths.length; i += BATCH_SIZE3) {
|
|
6057
|
-
const batch = relPaths.slice(i, i + BATCH_SIZE3);
|
|
6058
|
-
const extractions = [];
|
|
6059
|
-
for (let c = 0; c < batch.length; c += CONCURRENCY) {
|
|
6060
|
-
const chunk = batch.slice(c, c + CONCURRENCY);
|
|
6061
|
-
const results = await Promise.all(
|
|
6062
|
-
chunk.map((relPath) => extractor.extract(relPath, force))
|
|
6063
|
-
);
|
|
6064
|
-
for (const ext of results) {
|
|
6065
|
-
if (ext === "skipped") {
|
|
6066
|
-
result.skipped++;
|
|
6067
|
-
continue;
|
|
6068
|
-
}
|
|
6069
|
-
if (ext === "error") {
|
|
6070
|
-
result.errors++;
|
|
6071
|
-
continue;
|
|
6072
|
-
}
|
|
6073
|
-
extractions.push(ext);
|
|
6074
|
-
}
|
|
6075
|
-
}
|
|
6076
|
-
if (extractions.length > 0) {
|
|
6077
|
-
const state = this.getPipelineState();
|
|
6078
|
-
const persistEdgeResolver = new EdgeResolver(state);
|
|
6079
|
-
const persister = new FilePersister(state, (edges) => persistEdgeResolver.storeRawEdges(edges));
|
|
6080
|
-
persister.persistBatch(extractions);
|
|
6081
|
-
result.indexed += extractions.length;
|
|
6082
|
-
}
|
|
6083
|
-
const processed = result.indexed + result.skipped + result.errors;
|
|
6084
|
-
this.progress?.update("indexing", { processed });
|
|
6085
|
-
}
|
|
6086
|
-
enableFts5Triggers(this.store.db);
|
|
6087
|
-
const edgeResolver = new EdgeResolver(this.getPipelineState());
|
|
6088
|
-
await edgeResolver.resolveEdges(this.buildProjectContext(), this.buildResolveContext());
|
|
6089
|
-
edgeResolver.resolveOrmAssociationEdges();
|
|
6090
|
-
edgeResolver.resolveTypeScriptHeritageEdges();
|
|
6091
|
-
edgeResolver.resolveEsmImportEdges();
|
|
6092
|
-
edgeResolver.resolveTestCoversEdges();
|
|
6093
|
-
const envIndexer = new EnvIndexer(this.store, this.config, this.rootPath, this._traceignore);
|
|
6094
|
-
await envIndexer.indexEnvFiles(force);
|
|
6028
|
+
await this.extractAndPersist(relPaths, force, result);
|
|
6029
|
+
await this.resolveAllEdges();
|
|
6030
|
+
await this.indexEnvFiles(force);
|
|
6095
6031
|
} finally {
|
|
6096
6032
|
this._fileContentCache.clear();
|
|
6097
6033
|
this._pendingImports.clear();
|
|
@@ -6115,6 +6051,66 @@ var IndexingPipeline = class _IndexingPipeline {
|
|
|
6115
6051
|
logger.info(result, "Indexing pipeline completed");
|
|
6116
6052
|
return result;
|
|
6117
6053
|
}
|
|
6054
|
+
/** Pass 1: extract symbols from files and persist in batched transactions. */
|
|
6055
|
+
async extractAndPersist(relPaths, force, result) {
|
|
6056
|
+
const extractor = new FileExtractor({
|
|
6057
|
+
store: this.store,
|
|
6058
|
+
registry: this.registry,
|
|
6059
|
+
rootPath: this.rootPath,
|
|
6060
|
+
workspaces: this.workspaces,
|
|
6061
|
+
gitignore: this._gitignore,
|
|
6062
|
+
fileContentCache: this._fileContentCache,
|
|
6063
|
+
buildProjectContext: () => this.buildProjectContext()
|
|
6064
|
+
});
|
|
6065
|
+
disableFts5Triggers(this.store.db);
|
|
6066
|
+
const BATCH_SIZE3 = Math.min(500, Math.max(100, Math.ceil(relPaths.length / 20)));
|
|
6067
|
+
const CONCURRENCY = Math.min(8, cpus().length);
|
|
6068
|
+
for (let i = 0; i < relPaths.length; i += BATCH_SIZE3) {
|
|
6069
|
+
const batch = relPaths.slice(i, i + BATCH_SIZE3);
|
|
6070
|
+
const extractions = [];
|
|
6071
|
+
for (let c = 0; c < batch.length; c += CONCURRENCY) {
|
|
6072
|
+
const chunk = batch.slice(c, c + CONCURRENCY);
|
|
6073
|
+
const results = await Promise.all(
|
|
6074
|
+
chunk.map((relPath) => extractor.extract(relPath, force))
|
|
6075
|
+
);
|
|
6076
|
+
for (const ext of results) {
|
|
6077
|
+
if (ext === "skipped") {
|
|
6078
|
+
result.skipped++;
|
|
6079
|
+
continue;
|
|
6080
|
+
}
|
|
6081
|
+
if (ext === "error") {
|
|
6082
|
+
result.errors++;
|
|
6083
|
+
continue;
|
|
6084
|
+
}
|
|
6085
|
+
extractions.push(ext);
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
if (extractions.length > 0) {
|
|
6089
|
+
const state = this.getPipelineState();
|
|
6090
|
+
const persistEdgeResolver = new EdgeResolver(state);
|
|
6091
|
+
const persister = new FilePersister(state, (edges) => persistEdgeResolver.storeRawEdges(edges));
|
|
6092
|
+
persister.persistBatch(extractions);
|
|
6093
|
+
result.indexed += extractions.length;
|
|
6094
|
+
}
|
|
6095
|
+
const processed = result.indexed + result.skipped + result.errors;
|
|
6096
|
+
this.progress?.update("indexing", { processed });
|
|
6097
|
+
}
|
|
6098
|
+
enableFts5Triggers(this.store.db);
|
|
6099
|
+
}
|
|
6100
|
+
/** Pass 2: resolve all edge types (imports, heritage, ORM, tests). */
|
|
6101
|
+
async resolveAllEdges() {
|
|
6102
|
+
const edgeResolver = new EdgeResolver(this.getPipelineState());
|
|
6103
|
+
await edgeResolver.resolveEdges(this.buildProjectContext(), this.buildResolveContext());
|
|
6104
|
+
edgeResolver.resolveOrmAssociationEdges();
|
|
6105
|
+
edgeResolver.resolveTypeScriptHeritageEdges();
|
|
6106
|
+
edgeResolver.resolveEsmImportEdges();
|
|
6107
|
+
edgeResolver.resolveTestCoversEdges();
|
|
6108
|
+
}
|
|
6109
|
+
/** Pass 3: index .env files for environment variable tracking. */
|
|
6110
|
+
async indexEnvFiles(force) {
|
|
6111
|
+
const envIndexer = new EnvIndexer(this.store, this.config, this.rootPath, this._traceignore);
|
|
6112
|
+
await envIndexer.indexEnvFiles(force);
|
|
6113
|
+
}
|
|
6118
6114
|
buildProjectContext() {
|
|
6119
6115
|
if (!this._projectContext) {
|
|
6120
6116
|
this._projectContext = buildProjectContext(this.rootPath);
|
|
@@ -8589,186 +8585,6 @@ function getProjectMap(store, registry, summaryOnly, projectContext) {
|
|
|
8589
8585
|
}
|
|
8590
8586
|
|
|
8591
8587
|
// src/tools/analysis/duplication.ts
|
|
8592
|
-
var CHECKABLE_KINDS = /* @__PURE__ */ new Set([
|
|
8593
|
-
"function",
|
|
8594
|
-
"class",
|
|
8595
|
-
"method",
|
|
8596
|
-
"interface",
|
|
8597
|
-
"type_alias",
|
|
8598
|
-
"enum"
|
|
8599
|
-
]);
|
|
8600
|
-
var TRIVIAL_NAMES = /* @__PURE__ */ new Set([
|
|
8601
|
-
"constructor",
|
|
8602
|
-
"toString",
|
|
8603
|
-
"toJSON",
|
|
8604
|
-
"valueOf",
|
|
8605
|
-
"render",
|
|
8606
|
-
"setup",
|
|
8607
|
-
"main",
|
|
8608
|
-
"index",
|
|
8609
|
-
"default",
|
|
8610
|
-
"init",
|
|
8611
|
-
"create",
|
|
8612
|
-
"get",
|
|
8613
|
-
"set",
|
|
8614
|
-
"delete",
|
|
8615
|
-
"update",
|
|
8616
|
-
"handle",
|
|
8617
|
-
"process",
|
|
8618
|
-
"run",
|
|
8619
|
-
"start",
|
|
8620
|
-
"stop",
|
|
8621
|
-
"reset",
|
|
8622
|
-
"configure",
|
|
8623
|
-
"register",
|
|
8624
|
-
"execute",
|
|
8625
|
-
"validate",
|
|
8626
|
-
"transform",
|
|
8627
|
-
"apply",
|
|
8628
|
-
"call",
|
|
8629
|
-
"bind",
|
|
8630
|
-
"map",
|
|
8631
|
-
"filter",
|
|
8632
|
-
"reduce",
|
|
8633
|
-
"forEach",
|
|
8634
|
-
"connect",
|
|
8635
|
-
"disconnect",
|
|
8636
|
-
"open",
|
|
8637
|
-
"close",
|
|
8638
|
-
"build",
|
|
8639
|
-
"destroy",
|
|
8640
|
-
"mount",
|
|
8641
|
-
"unmount",
|
|
8642
|
-
"dispose",
|
|
8643
|
-
"serialize",
|
|
8644
|
-
"deserialize"
|
|
8645
|
-
]);
|
|
8646
|
-
var TEST_PATH_RE2 = /(?:^|[/\\])(?:tests?|__tests__|spec)[/\\]|\.(?:test|spec)\.[jt]sx?$/i;
|
|
8647
|
-
var MIN_NAME_LENGTH = 4;
|
|
8648
|
-
var MAX_SYMBOLS_PER_FILE2 = 30;
|
|
8649
|
-
var W_NAME = 0.45;
|
|
8650
|
-
var W_KIND = 0.15;
|
|
8651
|
-
var W_SIGNATURE = 0.25;
|
|
8652
|
-
var W_TOKEN = 0.15;
|
|
8653
|
-
function tokenizeName(name) {
|
|
8654
|
-
const parts = name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().split(/[_\-./:]+/).filter((p) => p.length > 1);
|
|
8655
|
-
return new Set(parts);
|
|
8656
|
-
}
|
|
8657
|
-
function jaccard(a, b) {
|
|
8658
|
-
if (a.size === 0 && b.size === 0) return 0;
|
|
8659
|
-
let intersection = 0;
|
|
8660
|
-
for (const t of a) {
|
|
8661
|
-
if (b.has(t)) intersection++;
|
|
8662
|
-
}
|
|
8663
|
-
const union = a.size + b.size - intersection;
|
|
8664
|
-
return union === 0 ? 0 : intersection / union;
|
|
8665
|
-
}
|
|
8666
|
-
function signatureSimilarity(srcParamCount, candParamCount) {
|
|
8667
|
-
if (srcParamCount == null && candParamCount == null) return 0.5;
|
|
8668
|
-
if (srcParamCount == null || candParamCount == null) return 0.3;
|
|
8669
|
-
const max = Math.max(srcParamCount, candParamCount, 1);
|
|
8670
|
-
return 1 - Math.abs(srcParamCount - candParamCount) / max;
|
|
8671
|
-
}
|
|
8672
|
-
function getHeritageNames(metadata) {
|
|
8673
|
-
if (!metadata) return /* @__PURE__ */ new Set();
|
|
8674
|
-
try {
|
|
8675
|
-
const parsed = JSON.parse(metadata);
|
|
8676
|
-
const names = /* @__PURE__ */ new Set();
|
|
8677
|
-
if (typeof parsed.extends === "string") names.add(parsed.extends);
|
|
8678
|
-
if (Array.isArray(parsed.extends)) {
|
|
8679
|
-
for (const e of parsed.extends) if (typeof e === "string") names.add(e);
|
|
8680
|
-
}
|
|
8681
|
-
if (typeof parsed.implements === "string") names.add(parsed.implements);
|
|
8682
|
-
if (Array.isArray(parsed.implements)) {
|
|
8683
|
-
for (const i of parsed.implements) if (typeof i === "string") names.add(i);
|
|
8684
|
-
}
|
|
8685
|
-
return names;
|
|
8686
|
-
} catch {
|
|
8687
|
-
return /* @__PURE__ */ new Set();
|
|
8688
|
-
}
|
|
8689
|
-
}
|
|
8690
|
-
function findDuplicateSymbols(store, db, sourceSymbols, sourceFileId, sourceFilePath, options) {
|
|
8691
|
-
const threshold = options?.threshold ?? 0.7;
|
|
8692
|
-
const maxResults = options?.maxResults ?? 10;
|
|
8693
|
-
const sourceIsTest = TEST_PATH_RE2.test(sourceFilePath);
|
|
8694
|
-
const heritageNames = /* @__PURE__ */ new Set();
|
|
8695
|
-
for (const sym of sourceSymbols) {
|
|
8696
|
-
const h = getHeritageNames(sym.metadata);
|
|
8697
|
-
for (const n of h) heritageNames.add(n);
|
|
8698
|
-
}
|
|
8699
|
-
const checkable = sourceSymbols.filter(
|
|
8700
|
-
(s) => CHECKABLE_KINDS.has(s.kind) && s.name.length >= MIN_NAME_LENGTH && !TRIVIAL_NAMES.has(s.name)
|
|
8701
|
-
).slice(0, MAX_SYMBOLS_PER_FILE2);
|
|
8702
|
-
const allWarnings = [];
|
|
8703
|
-
const paramCountStmt = db.prepare(
|
|
8704
|
-
"SELECT param_count FROM symbols WHERE id = ?"
|
|
8705
|
-
);
|
|
8706
|
-
for (const src of checkable) {
|
|
8707
|
-
const srcParamCount = src.param_count ?? paramCountStmt.get(src.id)?.param_count ?? null;
|
|
8708
|
-
const srcTokens = tokenizeName(src.fqn ?? src.name);
|
|
8709
|
-
const candidates = fuzzySearch(db, src.name, {
|
|
8710
|
-
threshold: 0.25,
|
|
8711
|
-
limit: 30,
|
|
8712
|
-
kind: src.kind
|
|
8713
|
-
});
|
|
8714
|
-
for (const cand of candidates) {
|
|
8715
|
-
if (cand.fileId === sourceFileId) continue;
|
|
8716
|
-
if (cand.symbolIdStr === src.symbol_id) continue;
|
|
8717
|
-
const candFile = store.getFileById(cand.fileId);
|
|
8718
|
-
if (!candFile) continue;
|
|
8719
|
-
const candIsTest = TEST_PATH_RE2.test(candFile.path);
|
|
8720
|
-
if (sourceIsTest !== candIsTest) continue;
|
|
8721
|
-
if (heritageNames.has(cand.name)) continue;
|
|
8722
|
-
const candSymbol = getCandidateSymbol(db, cand.symbolId);
|
|
8723
|
-
if (candSymbol) {
|
|
8724
|
-
const candHeritage = getHeritageNames(candSymbol.metadata);
|
|
8725
|
-
for (const h of candHeritage) {
|
|
8726
|
-
if (heritageNames.has(h)) continue;
|
|
8727
|
-
}
|
|
8728
|
-
}
|
|
8729
|
-
const nameSim = cand.similarity;
|
|
8730
|
-
const kindMatch = cand.kind === src.kind ? 1 : 0;
|
|
8731
|
-
const candParamCount = candSymbol?.param_count ?? paramCountStmt.get(cand.symbolId)?.param_count ?? null;
|
|
8732
|
-
const sigSim = signatureSimilarity(srcParamCount, candParamCount);
|
|
8733
|
-
const candTokens = tokenizeName(cand.fqn ?? cand.name);
|
|
8734
|
-
const tokenOvl = jaccard(srcTokens, candTokens);
|
|
8735
|
-
const score = W_NAME * nameSim + W_KIND * kindMatch + W_SIGNATURE * sigSim + W_TOKEN * tokenOvl;
|
|
8736
|
-
if (score >= threshold) {
|
|
8737
|
-
allWarnings.push({
|
|
8738
|
-
source_symbol_id: src.symbol_id,
|
|
8739
|
-
source_name: src.name,
|
|
8740
|
-
source_file: sourceFilePath,
|
|
8741
|
-
duplicate_symbol_id: cand.symbolIdStr,
|
|
8742
|
-
duplicate_name: cand.name,
|
|
8743
|
-
duplicate_file: candFile.path,
|
|
8744
|
-
duplicate_line: candSymbol?.line_start ?? null,
|
|
8745
|
-
score: Math.round(score * 1e3) / 1e3,
|
|
8746
|
-
signals: {
|
|
8747
|
-
name_similarity: Math.round(nameSim * 1e3) / 1e3,
|
|
8748
|
-
kind_match: kindMatch,
|
|
8749
|
-
signature_similarity: Math.round(sigSim * 1e3) / 1e3,
|
|
8750
|
-
token_overlap: Math.round(tokenOvl * 1e3) / 1e3
|
|
8751
|
-
}
|
|
8752
|
-
});
|
|
8753
|
-
}
|
|
8754
|
-
}
|
|
8755
|
-
}
|
|
8756
|
-
allWarnings.sort((a, b) => b.score - a.score);
|
|
8757
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8758
|
-
const deduped = [];
|
|
8759
|
-
for (const w of allWarnings) {
|
|
8760
|
-
const key = `${w.source_symbol_id}::${w.duplicate_symbol_id}`;
|
|
8761
|
-
if (seen.has(key)) continue;
|
|
8762
|
-
seen.add(key);
|
|
8763
|
-
deduped.push(w);
|
|
8764
|
-
if (deduped.length >= maxResults) break;
|
|
8765
|
-
}
|
|
8766
|
-
return {
|
|
8767
|
-
warnings: deduped,
|
|
8768
|
-
symbols_checked: checkable.length,
|
|
8769
|
-
threshold
|
|
8770
|
-
};
|
|
8771
|
-
}
|
|
8772
8588
|
function checkFileForDuplicates(store, db, filePath, options) {
|
|
8773
8589
|
const file = store.getFile(filePath);
|
|
8774
8590
|
if (!file) {
|
|
@@ -8811,11 +8627,6 @@ function checkSymbolForDuplicates(store, db, query, options) {
|
|
|
8811
8627
|
}
|
|
8812
8628
|
return { warnings: [], symbols_checked: 0, threshold };
|
|
8813
8629
|
}
|
|
8814
|
-
function getCandidateSymbol(db, symbolId) {
|
|
8815
|
-
return db.prepare(
|
|
8816
|
-
"SELECT * FROM symbols WHERE id = ?"
|
|
8817
|
-
).get(symbolId);
|
|
8818
|
-
}
|
|
8819
8630
|
|
|
8820
8631
|
// src/tools/register/core.ts
|
|
8821
8632
|
function registerCoreTools(server, ctx) {
|
|
@@ -10657,11 +10468,11 @@ function suggestQueries(store) {
|
|
|
10657
10468
|
|
|
10658
10469
|
// src/tools/navigation/related.ts
|
|
10659
10470
|
import { ok as ok4, err as err5 } from "neverthrow";
|
|
10660
|
-
function
|
|
10471
|
+
function tokenizeName(name) {
|
|
10661
10472
|
const parts = name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().split(/[_\-.]/).filter((p) => p.length > 1);
|
|
10662
10473
|
return new Set(parts);
|
|
10663
10474
|
}
|
|
10664
|
-
function
|
|
10475
|
+
function jaccard(a, b) {
|
|
10665
10476
|
if (a.size === 0 && b.size === 0) return 0;
|
|
10666
10477
|
let intersection = 0;
|
|
10667
10478
|
for (const t of a) {
|
|
@@ -10750,13 +10561,13 @@ function getRelatedSymbols(store, opts) {
|
|
|
10750
10561
|
}
|
|
10751
10562
|
}
|
|
10752
10563
|
}
|
|
10753
|
-
const targetTokens =
|
|
10564
|
+
const targetTokens = tokenizeName(target.name);
|
|
10754
10565
|
const candidateIds = [...scores.keys()];
|
|
10755
10566
|
const candidateSymbols = store.getSymbolsByIds(candidateIds);
|
|
10756
10567
|
const candidateFileIds = [...new Set([...candidateSymbols.values()].map((s) => s.file_id))];
|
|
10757
10568
|
const fileMap = store.getFilesByIds(candidateFileIds);
|
|
10758
10569
|
for (const [symId, sym] of candidateSymbols) {
|
|
10759
|
-
const nameScore =
|
|
10570
|
+
const nameScore = jaccard(targetTokens, tokenizeName(sym.name));
|
|
10760
10571
|
if (nameScore > 0) {
|
|
10761
10572
|
ensureEntry(symId).name_overlap = nameScore;
|
|
10762
10573
|
}
|
|
@@ -11853,63 +11664,9 @@ var FederationManager = class {
|
|
|
11853
11664
|
detectionSource: svc.detectionSource,
|
|
11854
11665
|
metadata: svc.metadata
|
|
11855
11666
|
});
|
|
11856
|
-
|
|
11857
|
-
if (opts?.contractPaths) {
|
|
11858
|
-
for (const cp of opts.contractPaths) {
|
|
11859
|
-
const absContract = path29.resolve(absRoot, cp);
|
|
11860
|
-
if (fs23.existsSync(absContract)) {
|
|
11861
|
-
const additionalContracts = parseContracts(path29.dirname(absContract));
|
|
11862
|
-
contracts.push(...additionalContracts.filter(
|
|
11863
|
-
(c) => path29.resolve(absRoot, c.specPath) === absContract
|
|
11864
|
-
));
|
|
11865
|
-
}
|
|
11866
|
-
}
|
|
11867
|
-
}
|
|
11868
|
-
for (const contract of contracts) {
|
|
11869
|
-
const contractId = this.topoStore.insertContract(serviceId, {
|
|
11870
|
-
contractType: contract.type,
|
|
11871
|
-
specPath: contract.specPath,
|
|
11872
|
-
version: contract.version,
|
|
11873
|
-
parsedSpec: JSON.stringify({ endpoints: contract.endpoints, events: contract.events })
|
|
11874
|
-
});
|
|
11875
|
-
this.topoStore.insertEndpoints(
|
|
11876
|
-
contractId,
|
|
11877
|
-
serviceId,
|
|
11878
|
-
contract.endpoints.map((e) => ({
|
|
11879
|
-
method: e.method ?? void 0,
|
|
11880
|
-
path: e.path,
|
|
11881
|
-
operationId: e.operationId,
|
|
11882
|
-
requestSchema: e.requestSchema ? JSON.stringify(e.requestSchema) : void 0,
|
|
11883
|
-
responseSchema: e.responseSchema ? JSON.stringify(e.responseSchema) : void 0
|
|
11884
|
-
}))
|
|
11885
|
-
);
|
|
11886
|
-
if (contract.events.length > 0) {
|
|
11887
|
-
this.topoStore.insertEventChannels(
|
|
11888
|
-
contractId,
|
|
11889
|
-
serviceId,
|
|
11890
|
-
contract.events.map((e) => ({
|
|
11891
|
-
channelName: e.channelName,
|
|
11892
|
-
direction: e.direction
|
|
11893
|
-
}))
|
|
11894
|
-
);
|
|
11895
|
-
}
|
|
11896
|
-
}
|
|
11667
|
+
this.registerContracts(serviceId, svc.repoRoot, absRoot, opts?.contractPaths);
|
|
11897
11668
|
}
|
|
11898
|
-
this.
|
|
11899
|
-
const clientCalls = scanClientCalls(absRoot);
|
|
11900
|
-
if (clientCalls.length > 0) {
|
|
11901
|
-
this.topoStore.insertClientCalls(clientCalls.map((c) => ({
|
|
11902
|
-
sourceRepoId: repoId,
|
|
11903
|
-
filePath: c.filePath,
|
|
11904
|
-
line: c.line,
|
|
11905
|
-
callType: c.callType,
|
|
11906
|
-
method: c.method,
|
|
11907
|
-
urlPattern: c.urlPattern,
|
|
11908
|
-
confidence: c.confidence
|
|
11909
|
-
})));
|
|
11910
|
-
}
|
|
11911
|
-
const linkedCount = this.topoStore.linkClientCallsToEndpoints();
|
|
11912
|
-
this.buildCrossServiceEdges();
|
|
11669
|
+
const clientCalls = this.scanAndLinkClientCalls(repoId, absRoot);
|
|
11913
11670
|
this.topoStore.updateFederatedRepoSyncTime(repoId);
|
|
11914
11671
|
const stats = this.topoStore.getTopologyStats();
|
|
11915
11672
|
return {
|
|
@@ -11918,8 +11675,8 @@ var FederationManager = class {
|
|
|
11918
11675
|
services: detected.length,
|
|
11919
11676
|
contracts: stats.contracts,
|
|
11920
11677
|
endpoints: stats.endpoints,
|
|
11921
|
-
clientCalls: clientCalls.
|
|
11922
|
-
linkedCalls:
|
|
11678
|
+
clientCalls: clientCalls.scanned,
|
|
11679
|
+
linkedCalls: clientCalls.linked
|
|
11923
11680
|
};
|
|
11924
11681
|
}
|
|
11925
11682
|
/**
|
|
@@ -12023,75 +11780,26 @@ var FederationManager = class {
|
|
|
12023
11780
|
detectionSource: svc.detectionSource,
|
|
12024
11781
|
metadata: svc.metadata
|
|
12025
11782
|
});
|
|
12026
|
-
|
|
12027
|
-
for (const ec of existingContracts) {
|
|
12028
|
-
this.topoStore.insertContractSnapshot(ec.id, serviceId, {
|
|
12029
|
-
version: ec.version,
|
|
12030
|
-
specPath: ec.spec_path,
|
|
12031
|
-
contentHash: ec.content_hash ?? "",
|
|
12032
|
-
endpointsJson: ec.parsed_spec,
|
|
12033
|
-
eventsJson: "[]"
|
|
12034
|
-
});
|
|
12035
|
-
}
|
|
11783
|
+
this.snapshotContracts(serviceId);
|
|
12036
11784
|
this.topoStore.deleteContractsByService(serviceId);
|
|
12037
11785
|
const contracts = parseContracts(svc.repoRoot);
|
|
12038
11786
|
contractsUpdated += contracts.length;
|
|
12039
11787
|
for (const contract of contracts) {
|
|
12040
|
-
const contractId = this.topoStore.insertContract(serviceId, {
|
|
12041
|
-
contractType: contract.type,
|
|
12042
|
-
specPath: contract.specPath,
|
|
12043
|
-
version: contract.version,
|
|
12044
|
-
parsedSpec: JSON.stringify({ endpoints: contract.endpoints, events: contract.events })
|
|
12045
|
-
});
|
|
12046
|
-
this.topoStore.insertEndpoints(
|
|
12047
|
-
contractId,
|
|
12048
|
-
serviceId,
|
|
12049
|
-
contract.endpoints.map((e) => ({
|
|
12050
|
-
method: e.method ?? void 0,
|
|
12051
|
-
path: e.path,
|
|
12052
|
-
operationId: e.operationId,
|
|
12053
|
-
requestSchema: e.requestSchema ? JSON.stringify(e.requestSchema) : void 0,
|
|
12054
|
-
responseSchema: e.responseSchema ? JSON.stringify(e.responseSchema) : void 0
|
|
12055
|
-
}))
|
|
12056
|
-
);
|
|
12057
11788
|
endpointsUpdated += contract.endpoints.length;
|
|
12058
|
-
if (contract.events.length > 0) {
|
|
12059
|
-
this.topoStore.insertEventChannels(
|
|
12060
|
-
contractId,
|
|
12061
|
-
serviceId,
|
|
12062
|
-
contract.events.map((e) => ({
|
|
12063
|
-
channelName: e.channelName,
|
|
12064
|
-
direction: e.direction
|
|
12065
|
-
}))
|
|
12066
|
-
);
|
|
12067
|
-
}
|
|
12068
11789
|
}
|
|
11790
|
+
this.registerContracts(serviceId, svc.repoRoot);
|
|
12069
11791
|
}
|
|
12070
|
-
this.
|
|
12071
|
-
|
|
12072
|
-
clientCallsScanned += calls.length;
|
|
12073
|
-
if (calls.length > 0) {
|
|
12074
|
-
this.topoStore.insertClientCalls(calls.map((c) => ({
|
|
12075
|
-
sourceRepoId: repo.id,
|
|
12076
|
-
filePath: c.filePath,
|
|
12077
|
-
line: c.line,
|
|
12078
|
-
callType: c.callType,
|
|
12079
|
-
method: c.method,
|
|
12080
|
-
urlPattern: c.urlPattern,
|
|
12081
|
-
confidence: c.confidence
|
|
12082
|
-
})));
|
|
12083
|
-
}
|
|
11792
|
+
const calls = this.scanAndLinkClientCalls(repo.id, repo.repo_root);
|
|
11793
|
+
clientCallsScanned += calls.scanned;
|
|
12084
11794
|
this.topoStore.updateFederatedRepoSyncTime(repo.id);
|
|
12085
11795
|
}
|
|
12086
|
-
const newlyLinked = this.topoStore.linkClientCallsToEndpoints();
|
|
12087
|
-
this.buildCrossServiceEdges();
|
|
12088
11796
|
return {
|
|
12089
11797
|
repos: repos.length,
|
|
12090
11798
|
servicesUpdated,
|
|
12091
11799
|
contractsUpdated,
|
|
12092
11800
|
endpointsUpdated,
|
|
12093
11801
|
clientCallsScanned,
|
|
12094
|
-
newlyLinked,
|
|
11802
|
+
newlyLinked: this.topoStore.linkClientCallsToEndpoints(),
|
|
12095
11803
|
crossRepoEdges: this.topoStore.getTopologyStats().crossEdges
|
|
12096
11804
|
};
|
|
12097
11805
|
}
|
|
@@ -12101,82 +11809,18 @@ var FederationManager = class {
|
|
|
12101
11809
|
* If per-repo DBs exist, resolves down to symbol level.
|
|
12102
11810
|
*/
|
|
12103
11811
|
getImpact(opts) {
|
|
11812
|
+
const matchingEndpoints = this.filterEndpoints(opts);
|
|
12104
11813
|
const results = [];
|
|
12105
|
-
const allEndpoints = this.topoStore.getAllEndpoints();
|
|
12106
|
-
let matchingEndpoints = allEndpoints;
|
|
12107
|
-
if (opts.endpoint) {
|
|
12108
|
-
const normalized = opts.endpoint.toLowerCase();
|
|
12109
|
-
matchingEndpoints = allEndpoints.filter(
|
|
12110
|
-
(ep) => ep.path.toLowerCase().includes(normalized)
|
|
12111
|
-
);
|
|
12112
|
-
}
|
|
12113
|
-
if (opts.method) {
|
|
12114
|
-
matchingEndpoints = matchingEndpoints.filter(
|
|
12115
|
-
(ep) => ep.method?.toUpperCase() === opts.method.toUpperCase()
|
|
12116
|
-
);
|
|
12117
|
-
}
|
|
12118
|
-
if (opts.service) {
|
|
12119
|
-
matchingEndpoints = matchingEndpoints.filter(
|
|
12120
|
-
(ep) => ep.service_name.toLowerCase() === opts.service.toLowerCase()
|
|
12121
|
-
);
|
|
12122
|
-
}
|
|
12123
11814
|
for (const ep of matchingEndpoints) {
|
|
12124
11815
|
const clientCalls = this.topoStore.getClientCallsByEndpoint(ep.id);
|
|
12125
11816
|
if (clientCalls.length === 0) continue;
|
|
12126
|
-
const
|
|
12127
|
-
for (const call of clientCalls) {
|
|
12128
|
-
const repo2 = call.source_repo_name;
|
|
12129
|
-
if (!byRepo.has(repo2)) byRepo.set(repo2, []);
|
|
12130
|
-
byRepo.get(repo2).push(call);
|
|
12131
|
-
}
|
|
12132
|
-
const clients = [];
|
|
12133
|
-
for (const [repoName, calls] of byRepo) {
|
|
12134
|
-
const repo2 = this.topoStore.getFederatedRepo(repoName);
|
|
12135
|
-
for (const call of calls) {
|
|
12136
|
-
const symbols = repo2?.db_path && fs23.existsSync(repo2.db_path) ? resolveSymbolsAtLocation(repo2.db_path, call.file_path, call.line) : [];
|
|
12137
|
-
clients.push({
|
|
12138
|
-
repo: repoName,
|
|
12139
|
-
filePath: call.file_path,
|
|
12140
|
-
line: call.line,
|
|
12141
|
-
callType: call.call_type,
|
|
12142
|
-
confidence: call.confidence,
|
|
12143
|
-
symbols
|
|
12144
|
-
});
|
|
12145
|
-
}
|
|
12146
|
-
}
|
|
11817
|
+
const clients = this.collectEndpointClients(clientCalls);
|
|
12147
11818
|
const uniqueRepos = new Set(clients.map((c) => c.repo));
|
|
12148
|
-
const
|
|
11819
|
+
const baseRisk = computeRiskLevel(uniqueRepos.size, clients.length);
|
|
12149
11820
|
const svc = this.topoStore.getAllServices().find((s) => s.id === ep.service_id);
|
|
12150
11821
|
const repo = svc ? this.topoStore.getFederatedRepo(svc.repo_root) : void 0;
|
|
12151
|
-
|
|
12152
|
-
const
|
|
12153
|
-
for (const contract of contracts) {
|
|
12154
|
-
const snapshot = this.topoStore.getLatestSnapshot(contract.id);
|
|
12155
|
-
if (!snapshot) continue;
|
|
12156
|
-
let oldEndpoints = [];
|
|
12157
|
-
try {
|
|
12158
|
-
const parsed = JSON.parse(snapshot.endpoints_json);
|
|
12159
|
-
oldEndpoints = (parsed.endpoints ?? []).map((e) => ({ method: e.method ?? null, path: e.path, requestSchema: e.requestSchema, responseSchema: e.responseSchema }));
|
|
12160
|
-
} catch {
|
|
12161
|
-
continue;
|
|
12162
|
-
}
|
|
12163
|
-
const currentEndpoints = this.topoStore.getEndpointsByService(ep.service_id).map((e) => ({
|
|
12164
|
-
method: e.method,
|
|
12165
|
-
path: e.path,
|
|
12166
|
-
requestSchema: e.request_schema,
|
|
12167
|
-
responseSchema: e.response_schema
|
|
12168
|
-
}));
|
|
12169
|
-
const epDiffs = diffEndpoints(oldEndpoints, currentEndpoints).filter((d) => d.endpoint.path === ep.path && (d.endpoint.method ?? "*") === (ep.method ?? "*"));
|
|
12170
|
-
if (epDiffs.length > 0 && epDiffs.some((d) => d.breaking)) {
|
|
12171
|
-
breakingChanges = epDiffs;
|
|
12172
|
-
}
|
|
12173
|
-
}
|
|
12174
|
-
let finalRiskLevel = riskLevel3;
|
|
12175
|
-
if (breakingChanges?.some((d) => d.breaking)) {
|
|
12176
|
-
const riskLevels = ["low", "medium", "high", "critical"];
|
|
12177
|
-
const idx = riskLevels.indexOf(riskLevel3);
|
|
12178
|
-
if (idx < riskLevels.length - 1) finalRiskLevel = riskLevels[idx + 1];
|
|
12179
|
-
}
|
|
11822
|
+
const breakingChanges = this.detectBreakingChanges(ep);
|
|
11823
|
+
const riskLevel3 = upgradeRiskIfBreaking(baseRisk, breakingChanges);
|
|
12180
11824
|
results.push({
|
|
12181
11825
|
endpoint: {
|
|
12182
11826
|
method: ep.method,
|
|
@@ -12185,13 +11829,152 @@ var FederationManager = class {
|
|
|
12185
11829
|
repo: repo?.name ?? svc?.repo_root ?? "unknown"
|
|
12186
11830
|
},
|
|
12187
11831
|
clients,
|
|
12188
|
-
riskLevel:
|
|
11832
|
+
riskLevel: riskLevel3,
|
|
12189
11833
|
summary: `${ep.method ?? "*"} ${ep.path} is called by ${clients.length} client(s) in ${uniqueRepos.size} repo(s)${breakingChanges ? " \u26A0 BREAKING SCHEMA CHANGES" : ""}`,
|
|
12190
11834
|
breakingChanges
|
|
12191
11835
|
});
|
|
12192
11836
|
}
|
|
12193
11837
|
return results;
|
|
12194
11838
|
}
|
|
11839
|
+
/** Register contracts for a service, including explicitly provided paths. */
|
|
11840
|
+
registerContracts(serviceId, serviceRoot, repoRoot, explicitPaths) {
|
|
11841
|
+
const contracts = parseContracts(serviceRoot);
|
|
11842
|
+
if (explicitPaths && repoRoot) {
|
|
11843
|
+
for (const cp of explicitPaths) {
|
|
11844
|
+
const absContract = path29.resolve(repoRoot, cp);
|
|
11845
|
+
if (fs23.existsSync(absContract)) {
|
|
11846
|
+
const additional = parseContracts(path29.dirname(absContract));
|
|
11847
|
+
contracts.push(...additional.filter(
|
|
11848
|
+
(c) => path29.resolve(repoRoot, c.specPath) === absContract
|
|
11849
|
+
));
|
|
11850
|
+
}
|
|
11851
|
+
}
|
|
11852
|
+
}
|
|
11853
|
+
for (const contract of contracts) {
|
|
11854
|
+
const contractId = this.topoStore.insertContract(serviceId, {
|
|
11855
|
+
contractType: contract.type,
|
|
11856
|
+
specPath: contract.specPath,
|
|
11857
|
+
version: contract.version,
|
|
11858
|
+
parsedSpec: JSON.stringify({ endpoints: contract.endpoints, events: contract.events })
|
|
11859
|
+
});
|
|
11860
|
+
this.topoStore.insertEndpoints(
|
|
11861
|
+
contractId,
|
|
11862
|
+
serviceId,
|
|
11863
|
+
contract.endpoints.map((e) => ({
|
|
11864
|
+
method: e.method ?? void 0,
|
|
11865
|
+
path: e.path,
|
|
11866
|
+
operationId: e.operationId,
|
|
11867
|
+
requestSchema: e.requestSchema ? JSON.stringify(e.requestSchema) : void 0,
|
|
11868
|
+
responseSchema: e.responseSchema ? JSON.stringify(e.responseSchema) : void 0
|
|
11869
|
+
}))
|
|
11870
|
+
);
|
|
11871
|
+
if (contract.events.length > 0) {
|
|
11872
|
+
this.topoStore.insertEventChannels(
|
|
11873
|
+
contractId,
|
|
11874
|
+
serviceId,
|
|
11875
|
+
contract.events.map((e) => ({
|
|
11876
|
+
channelName: e.channelName,
|
|
11877
|
+
direction: e.direction
|
|
11878
|
+
}))
|
|
11879
|
+
);
|
|
11880
|
+
}
|
|
11881
|
+
}
|
|
11882
|
+
}
|
|
11883
|
+
/** Snapshot existing contracts before replacing them (for drift detection). */
|
|
11884
|
+
snapshotContracts(serviceId) {
|
|
11885
|
+
const existing = this.topoStore.getContractsByService(serviceId);
|
|
11886
|
+
for (const ec of existing) {
|
|
11887
|
+
this.topoStore.insertContractSnapshot(ec.id, serviceId, {
|
|
11888
|
+
version: ec.version,
|
|
11889
|
+
specPath: ec.spec_path,
|
|
11890
|
+
contentHash: ec.content_hash ?? "",
|
|
11891
|
+
endpointsJson: ec.parsed_spec,
|
|
11892
|
+
eventsJson: "[]"
|
|
11893
|
+
});
|
|
11894
|
+
}
|
|
11895
|
+
}
|
|
11896
|
+
/** Scan repo for client calls, insert them, link to endpoints, and build edges. */
|
|
11897
|
+
scanAndLinkClientCalls(repoId, repoRoot) {
|
|
11898
|
+
this.topoStore.deleteClientCallsByRepo(repoId);
|
|
11899
|
+
const clientCalls = scanClientCalls(repoRoot);
|
|
11900
|
+
if (clientCalls.length > 0) {
|
|
11901
|
+
this.topoStore.insertClientCalls(clientCalls.map((c) => ({
|
|
11902
|
+
sourceRepoId: repoId,
|
|
11903
|
+
filePath: c.filePath,
|
|
11904
|
+
line: c.line,
|
|
11905
|
+
callType: c.callType,
|
|
11906
|
+
method: c.method,
|
|
11907
|
+
urlPattern: c.urlPattern,
|
|
11908
|
+
confidence: c.confidence
|
|
11909
|
+
})));
|
|
11910
|
+
}
|
|
11911
|
+
const linked = this.topoStore.linkClientCallsToEndpoints();
|
|
11912
|
+
this.buildCrossServiceEdges();
|
|
11913
|
+
return { scanned: clientCalls.length, linked };
|
|
11914
|
+
}
|
|
11915
|
+
filterEndpoints(opts) {
|
|
11916
|
+
let endpoints = this.topoStore.getAllEndpoints();
|
|
11917
|
+
if (opts.endpoint) {
|
|
11918
|
+
const normalized = opts.endpoint.toLowerCase();
|
|
11919
|
+
endpoints = endpoints.filter((ep) => ep.path.toLowerCase().includes(normalized));
|
|
11920
|
+
}
|
|
11921
|
+
if (opts.method) {
|
|
11922
|
+
endpoints = endpoints.filter((ep) => ep.method?.toUpperCase() === opts.method.toUpperCase());
|
|
11923
|
+
}
|
|
11924
|
+
if (opts.service) {
|
|
11925
|
+
endpoints = endpoints.filter((ep) => ep.service_name.toLowerCase() === opts.service.toLowerCase());
|
|
11926
|
+
}
|
|
11927
|
+
return endpoints;
|
|
11928
|
+
}
|
|
11929
|
+
collectEndpointClients(clientCalls) {
|
|
11930
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
11931
|
+
for (const call of clientCalls) {
|
|
11932
|
+
const repo = call.source_repo_name;
|
|
11933
|
+
if (!byRepo.has(repo)) byRepo.set(repo, []);
|
|
11934
|
+
byRepo.get(repo).push(call);
|
|
11935
|
+
}
|
|
11936
|
+
const clients = [];
|
|
11937
|
+
for (const [repoName, calls] of byRepo) {
|
|
11938
|
+
const repo = this.topoStore.getFederatedRepo(repoName);
|
|
11939
|
+
for (const call of calls) {
|
|
11940
|
+
const symbols = repo?.db_path && fs23.existsSync(repo.db_path) ? resolveSymbolsAtLocation(repo.db_path, call.file_path, call.line) : [];
|
|
11941
|
+
clients.push({
|
|
11942
|
+
repo: repoName,
|
|
11943
|
+
filePath: call.file_path,
|
|
11944
|
+
line: call.line,
|
|
11945
|
+
callType: call.call_type,
|
|
11946
|
+
confidence: call.confidence,
|
|
11947
|
+
symbols
|
|
11948
|
+
});
|
|
11949
|
+
}
|
|
11950
|
+
}
|
|
11951
|
+
return clients;
|
|
11952
|
+
}
|
|
11953
|
+
detectBreakingChanges(ep) {
|
|
11954
|
+
const contracts = this.topoStore.getContractsByService(ep.service_id);
|
|
11955
|
+
for (const contract of contracts) {
|
|
11956
|
+
const snapshot = this.topoStore.getLatestSnapshot(contract.id);
|
|
11957
|
+
if (!snapshot) continue;
|
|
11958
|
+
let oldEndpoints = [];
|
|
11959
|
+
try {
|
|
11960
|
+
const parsed = JSON.parse(snapshot.endpoints_json);
|
|
11961
|
+
oldEndpoints = (parsed.endpoints ?? []).map((e) => ({ method: e.method ?? null, path: e.path, requestSchema: e.requestSchema, responseSchema: e.responseSchema }));
|
|
11962
|
+
} catch {
|
|
11963
|
+
continue;
|
|
11964
|
+
}
|
|
11965
|
+
const currentEndpoints = this.topoStore.getEndpointsByService(ep.service_id).map((e) => ({
|
|
11966
|
+
method: e.method,
|
|
11967
|
+
path: e.path,
|
|
11968
|
+
requestSchema: e.request_schema,
|
|
11969
|
+
responseSchema: e.response_schema
|
|
11970
|
+
}));
|
|
11971
|
+
const epDiffs = diffEndpoints(oldEndpoints, currentEndpoints).filter((d) => d.endpoint.path === ep.path && (d.endpoint.method ?? "*") === (ep.method ?? "*"));
|
|
11972
|
+
if (epDiffs.length > 0 && epDiffs.some((d) => d.breaking)) {
|
|
11973
|
+
return epDiffs;
|
|
11974
|
+
}
|
|
11975
|
+
}
|
|
11976
|
+
return void 0;
|
|
11977
|
+
}
|
|
12195
11978
|
/**
|
|
12196
11979
|
* Search across all federated repos. Opens each per-repo DB readonly,
|
|
12197
11980
|
* runs FTS search, normalizes scores, merges results.
|
|
@@ -12277,6 +12060,18 @@ var FederationManager = class {
|
|
|
12277
12060
|
}
|
|
12278
12061
|
}
|
|
12279
12062
|
};
|
|
12063
|
+
function computeRiskLevel(uniqueRepoCount, clientCount) {
|
|
12064
|
+
if (uniqueRepoCount >= 3) return "critical";
|
|
12065
|
+
if (uniqueRepoCount >= 2) return "high";
|
|
12066
|
+
if (clientCount >= 3) return "medium";
|
|
12067
|
+
return "low";
|
|
12068
|
+
}
|
|
12069
|
+
function upgradeRiskIfBreaking(risk, breakingChanges) {
|
|
12070
|
+
if (!breakingChanges?.some((d) => d.breaking)) return risk;
|
|
12071
|
+
const levels = ["low", "medium", "high", "critical"];
|
|
12072
|
+
const idx = levels.indexOf(risk);
|
|
12073
|
+
return idx < levels.length - 1 ? levels[idx + 1] : risk;
|
|
12074
|
+
}
|
|
12280
12075
|
function resolveSymbolsAtLocation(dbPath, filePath, line) {
|
|
12281
12076
|
if (!line) return [];
|
|
12282
12077
|
try {
|
|
@@ -22465,13 +22260,13 @@ function detectDrift(store, cwd, options = {}) {
|
|
|
22465
22260
|
const commitsB = fileCommitCount.get(fileB) ?? 0;
|
|
22466
22261
|
const denominator = commitsA + commitsB - count;
|
|
22467
22262
|
if (denominator <= 0) continue;
|
|
22468
|
-
const
|
|
22469
|
-
if (
|
|
22263
|
+
const jaccard2 = count / denominator;
|
|
22264
|
+
if (jaccard2 < minConfidence) continue;
|
|
22470
22265
|
anomalies.push({
|
|
22471
22266
|
file_a: fileA,
|
|
22472
22267
|
file_b: fileB,
|
|
22473
22268
|
co_change_count: count,
|
|
22474
|
-
confidence: round2(
|
|
22269
|
+
confidence: round2(jaccard2),
|
|
22475
22270
|
module_a: moduleA,
|
|
22476
22271
|
module_b: moduleB
|
|
22477
22272
|
});
|
|
@@ -28797,7 +28592,7 @@ var TopologyStore = class {
|
|
|
28797
28592
|
};
|
|
28798
28593
|
|
|
28799
28594
|
// src/server/server.ts
|
|
28800
|
-
var PKG_VERSION = true ? "1.
|
|
28595
|
+
var PKG_VERSION = true ? "1.10.0" : "0.0.0-dev";
|
|
28801
28596
|
function j2(value) {
|
|
28802
28597
|
return JSON.stringify(value, (_key, val) => val === null || val === void 0 ? void 0 : val);
|
|
28803
28598
|
}
|