@monoes/monograph 1.4.0 → 1.5.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/src/analysis/benchmark.js.map +1 -1
- package/dist/src/analysis/changed-files.d.ts.map +1 -1
- package/dist/src/analysis/changed-files.js +11 -2
- package/dist/src/analysis/changed-files.js.map +1 -1
- package/dist/src/analysis/changed-workspaces.d.ts.map +1 -1
- package/dist/src/analysis/changed-workspaces.js +11 -3
- package/dist/src/analysis/changed-workspaces.js.map +1 -1
- package/dist/src/analysis/churn.d.ts +1 -1
- package/dist/src/analysis/churn.d.ts.map +1 -1
- package/dist/src/analysis/churn.js +56 -10
- package/dist/src/analysis/churn.js.map +1 -1
- package/dist/src/analysis/feature-flags.d.ts.map +1 -1
- package/dist/src/analysis/feature-flags.js +11 -5
- package/dist/src/analysis/feature-flags.js.map +1 -1
- package/dist/src/analysis/progress.d.ts.map +1 -1
- package/dist/src/analysis/project-state.d.ts.map +1 -1
- package/dist/src/analysis/shape-extractor.js.map +1 -1
- package/dist/src/analysis/suppression-context.d.ts.map +1 -1
- package/dist/src/analysis/surprise-scorer.js.map +1 -1
- package/dist/src/analysis/targets.d.ts.map +1 -1
- package/dist/src/analysis/worker-pool.d.ts.map +1 -1
- package/dist/src/analysis/worker-pool.js.map +1 -1
- package/dist/src/analyze/unused-deps.js.map +1 -1
- package/dist/src/analyze/unused-exports.js.map +1 -1
- package/dist/src/analyze/unused-files.js.map +1 -1
- package/dist/src/cache/augment-cache.js.map +1 -1
- package/dist/src/cache/extraction-cache.d.ts +21 -0
- package/dist/src/cache/extraction-cache.d.ts.map +1 -1
- package/dist/src/cache/extraction-cache.js +62 -6
- package/dist/src/cache/extraction-cache.js.map +1 -1
- package/dist/src/check/index.js.map +1 -1
- package/dist/src/cli/check-update.js.map +1 -1
- package/dist/src/cli/doctor.d.ts.map +1 -1
- package/dist/src/cli/doctor.js +6 -10
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/config/boundary-config.js.map +1 -1
- package/dist/src/config/config-parsing.d.ts +1 -1
- package/dist/src/config/config-parsing.d.ts.map +1 -1
- package/dist/src/config/migration-types.d.ts +2 -2
- package/dist/src/config/migration-types.d.ts.map +1 -1
- package/dist/src/config/migration-types.js.map +1 -1
- package/dist/src/config/used-class-members.js.map +1 -1
- package/dist/src/config/workspace.js.map +1 -1
- package/dist/src/coverage/upload-source-maps.js.map +1 -1
- package/dist/src/duplicates/detect/filtering.d.ts.map +1 -1
- package/dist/src/duplicates/shingle-filter.js.map +1 -1
- package/dist/src/error/fallow-error.d.ts.map +1 -1
- package/dist/src/error/fallow-error.js.map +1 -1
- package/dist/src/error.d.ts.map +1 -1
- package/dist/src/error.js.map +1 -1
- package/dist/src/export/analysis-json.js.map +1 -1
- package/dist/src/export/badge.js.map +1 -1
- package/dist/src/export/dot.js.map +1 -1
- package/dist/src/export/sarif.js.map +1 -1
- package/dist/src/fix/orchestrator.js.map +1 -1
- package/dist/src/graph/cycle-detection.js.map +1 -1
- package/dist/src/graph/dead-code.d.ts +7 -2
- package/dist/src/graph/dead-code.d.ts.map +1 -1
- package/dist/src/graph/dead-code.js +19 -16
- package/dist/src/graph/dead-code.js.map +1 -1
- package/dist/src/graph/dfs.js.map +1 -1
- package/dist/src/graph/edge-filter.js.map +1 -1
- package/dist/src/graph/hotspots.js.map +1 -1
- package/dist/src/graph/import-chain.js.map +1 -1
- package/dist/src/graph/merge.js.map +1 -1
- package/dist/src/graph/node-search.js.map +1 -1
- package/dist/src/graph/node-types.d.ts.map +1 -1
- package/dist/src/graph/pagerank.d.ts.map +1 -1
- package/dist/src/graph/pagerank.js +10 -1
- package/dist/src/graph/pagerank.js.map +1 -1
- package/dist/src/graph/regex-search.js.map +1 -1
- package/dist/src/graph/regression.d.ts.map +1 -1
- package/dist/src/graph/resolve/discover-types.js.map +1 -1
- package/dist/src/graph/resolve/types.d.ts +3 -3
- package/dist/src/graph/resolve/types.d.ts.map +1 -1
- package/dist/src/groups/group-search.d.ts.map +1 -1
- package/dist/src/groups/group-search.js +9 -18
- package/dist/src/groups/group-search.js.map +1 -1
- package/dist/src/health/complexity-findings.js.map +1 -1
- package/dist/src/health/grouping.js.map +1 -1
- package/dist/src/health/health-report-types.js.map +1 -1
- package/dist/src/health/health-report.js.map +1 -1
- package/dist/src/health/targets.js.map +1 -1
- package/dist/src/ingest/transcribe.js.map +1 -1
- package/dist/src/ingest/url-ingest.js.map +1 -1
- package/dist/src/license/manager.d.ts.map +1 -1
- package/dist/src/license/manager.js.map +1 -1
- package/dist/src/lsp/diagnostics-push.js.map +1 -1
- package/dist/src/mcp-tools/impact.d.ts.map +1 -1
- package/dist/src/mcp-tools/impact.js +7 -1
- package/dist/src/mcp-tools/impact.js.map +1 -1
- package/dist/src/mcp-tools/list-repos.d.ts +1 -1
- package/dist/src/mcp-tools/list-repos.d.ts.map +1 -1
- package/dist/src/mcp-tools/query.d.ts +1 -1
- package/dist/src/mcp-tools/query.d.ts.map +1 -1
- package/dist/src/parsers/c.js.map +1 -1
- package/dist/src/parsers/cpp.js.map +1 -1
- package/dist/src/parsers/csharp.js.map +1 -1
- package/dist/src/parsers/dart.js.map +1 -1
- package/dist/src/parsers/go.js.map +1 -1
- package/dist/src/parsers/java.js.map +1 -1
- package/dist/src/parsers/kotlin.js.map +1 -1
- package/dist/src/parsers/loader.js.map +1 -1
- package/dist/src/parsers/php.js.map +1 -1
- package/dist/src/parsers/python.js.map +1 -1
- package/dist/src/parsers/ruby.js.map +1 -1
- package/dist/src/parsers/rust.js.map +1 -1
- package/dist/src/parsers/swift.js.map +1 -1
- package/dist/src/parsers/typescript.js.map +1 -1
- package/dist/src/parsers/vue.js.map +1 -1
- package/dist/src/pipeline/effort.js.map +1 -1
- package/dist/src/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/src/pipeline/orchestrator.js +48 -24
- package/dist/src/pipeline/orchestrator.js.map +1 -1
- package/dist/src/pipeline/phases/bridge-adapters/electron-ipc.d.ts +11 -0
- package/dist/src/pipeline/phases/bridge-adapters/electron-ipc.d.ts.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/electron-ipc.js +96 -0
- package/dist/src/pipeline/phases/bridge-adapters/electron-ipc.js.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/registry.d.ts +3 -0
- package/dist/src/pipeline/phases/bridge-adapters/registry.d.ts.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/registry.js +13 -0
- package/dist/src/pipeline/phases/bridge-adapters/registry.js.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/tauri.d.ts +10 -0
- package/dist/src/pipeline/phases/bridge-adapters/tauri.d.ts.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/tauri.js +104 -0
- package/dist/src/pipeline/phases/bridge-adapters/tauri.js.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/types.d.ts +30 -0
- package/dist/src/pipeline/phases/bridge-adapters/types.d.ts.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/types.js +2 -0
- package/dist/src/pipeline/phases/bridge-adapters/types.js.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/wails.d.ts +10 -0
- package/dist/src/pipeline/phases/bridge-adapters/wails.d.ts.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/wails.js +27 -0
- package/dist/src/pipeline/phases/bridge-adapters/wails.js.map +1 -0
- package/dist/src/pipeline/phases/bridge-resolver.d.ts +23 -0
- package/dist/src/pipeline/phases/bridge-resolver.d.ts.map +1 -0
- package/dist/src/pipeline/phases/bridge-resolver.js +70 -0
- package/dist/src/pipeline/phases/bridge-resolver.js.map +1 -0
- package/dist/src/pipeline/phases/cross-file.d.ts.map +1 -1
- package/dist/src/pipeline/phases/cross-file.js +70 -27
- package/dist/src/pipeline/phases/cross-file.js.map +1 -1
- package/dist/src/pipeline/phases/leiden.js.map +1 -1
- package/dist/src/pipeline/phases/parse.d.ts.map +1 -1
- package/dist/src/pipeline/phases/parse.js +58 -17
- package/dist/src/pipeline/phases/parse.js.map +1 -1
- package/dist/src/pipeline/phases/scan.d.ts.map +1 -1
- package/dist/src/pipeline/phases/scan.js +12 -5
- package/dist/src/pipeline/phases/scan.js.map +1 -1
- package/dist/src/pipeline/phases/scope-resolution.d.ts +13 -0
- package/dist/src/pipeline/phases/scope-resolution.d.ts.map +1 -1
- package/dist/src/pipeline/phases/scope-resolution.js +18 -4
- package/dist/src/pipeline/phases/scope-resolution.js.map +1 -1
- package/dist/src/pipeline/progress.d.ts.map +1 -1
- package/dist/src/pipeline/runner.d.ts.map +1 -1
- package/dist/src/pipeline/runner.js.map +1 -1
- package/dist/src/pipeline/types.d.ts +2 -0
- package/dist/src/pipeline/types.d.ts.map +1 -1
- package/dist/src/pipeline/types.js +1 -0
- package/dist/src/pipeline/types.js.map +1 -1
- package/dist/src/regression/baseline.js.map +1 -1
- package/dist/src/regression/outcome.js.map +1 -1
- package/dist/src/report/badge-svg.js.map +1 -1
- package/dist/src/report/duplication-grouping.js.map +1 -1
- package/dist/src/report/human-check.js.map +1 -1
- package/dist/src/report/human-dupes.js.map +1 -1
- package/dist/src/report/human-health.js.map +1 -1
- package/dist/src/report/json-builders.js.map +1 -1
- package/dist/src/report/number-format.js.map +1 -1
- package/dist/src/reporting/graph-report.js.map +1 -1
- package/dist/src/scope-resolution/evidence-weights.d.ts.map +1 -1
- package/dist/src/search/hybrid-query.js.map +1 -1
- package/dist/src/security/safe-fetch.js.map +1 -1
- package/dist/src/storage/db.d.ts +13 -1
- package/dist/src/storage/db.d.ts.map +1 -1
- package/dist/src/storage/db.js +17 -1
- package/dist/src/storage/db.js.map +1 -1
- package/dist/src/storage/edge-store.d.ts +6 -0
- package/dist/src/storage/edge-store.d.ts.map +1 -1
- package/dist/src/storage/edge-store.js +8 -1
- package/dist/src/storage/edge-store.js.map +1 -1
- package/dist/src/storage/fts-store.d.ts +7 -0
- package/dist/src/storage/fts-store.d.ts.map +1 -1
- package/dist/src/storage/fts-store.js +153 -10
- package/dist/src/storage/fts-store.js.map +1 -1
- package/dist/src/storage/node-store.js.map +1 -1
- package/dist/src/storage/unlinked-refs.js.map +1 -1
- package/dist/src/types.d.ts +2 -2
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/watch/watcher.d.ts.map +1 -1
- package/dist/src/watch/watcher.js +12 -2
- package/dist/src/watch/watcher.js.map +1 -1
- package/dist/src/web/analyze-api.js.map +1 -1
- package/dist/src/web/api.js.map +1 -1
- package/dist/src/wiki/providers.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -2
- package/src/__tests__/graph/dead-code.test.ts +27 -7
- package/src/__tests__/pipeline/phases/bridge-adapters/electron-ipc.test.ts +87 -0
- package/src/__tests__/pipeline/phases/bridge-adapters/tauri.test.ts +116 -0
- package/src/__tests__/pipeline/phases/bridge-adapters/wails.test.ts +81 -0
- package/src/__tests__/pipeline/phases/bridge-resolver.test.ts +110 -0
- package/src/analysis/changed-files.ts +14 -2
- package/src/analysis/changed-workspaces.ts +13 -4
- package/src/analysis/churn.ts +57 -10
- package/src/analysis/feature-flags.ts +10 -5
- package/src/cache/extraction-cache.ts +59 -6
- package/src/cli/doctor.ts +6 -10
- package/src/graph/dead-code.ts +19 -16
- package/src/graph/pagerank.ts +10 -1
- package/src/groups/group-search.ts +9 -27
- package/src/mcp-tools/impact.ts +7 -1
- package/src/pipeline/orchestrator.ts +50 -26
- package/src/pipeline/phases/bridge-adapters/electron-ipc.ts +91 -0
- package/src/pipeline/phases/bridge-adapters/registry.ts +14 -0
- package/src/pipeline/phases/bridge-adapters/tauri.ts +103 -0
- package/src/pipeline/phases/bridge-adapters/types.ts +31 -0
- package/src/pipeline/phases/bridge-adapters/wails.ts +33 -0
- package/src/pipeline/phases/bridge-resolver.ts +77 -0
- package/src/pipeline/phases/cross-file.ts +75 -27
- package/src/pipeline/phases/parse.ts +60 -17
- package/src/pipeline/phases/scan.ts +11 -5
- package/src/pipeline/phases/scope-resolution.ts +19 -4
- package/src/pipeline/types.ts +3 -0
- package/src/storage/db.ts +27 -1
- package/src/storage/edge-store.ts +8 -1
- package/src/storage/fts-store.ts +158 -10
- package/src/watch/watcher.ts +11 -2
- package/tsconfig.json +2 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync, readdirSync,
|
|
1
|
+
import { readFileSync, readdirSync, lstatSync } from 'fs';
|
|
2
2
|
import { join, extname } from 'path';
|
|
3
3
|
|
|
4
4
|
export type FlagKind = 'EnvironmentVariable' | 'SdkCall' | 'ConfigObject';
|
|
@@ -170,13 +170,18 @@ export function crossReferenceWithDeadCode(
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
function walkDir(dir: string, fn: (filePath: string) => void): void {
|
|
173
|
-
let
|
|
174
|
-
try {
|
|
175
|
-
for (const
|
|
173
|
+
let dirents: import('fs').Dirent[];
|
|
174
|
+
try { dirents = readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
175
|
+
for (const dirent of dirents) {
|
|
176
|
+
const entry = dirent.name;
|
|
176
177
|
if (entry === 'node_modules' || entry === '.git' || entry === 'dist' || entry === 'build') continue;
|
|
178
|
+
// Never follow symlinks — avoids infinite recursion on a symlink pointing to
|
|
179
|
+
// an ancestor directory, and avoids indexing a symlinked external tree as if
|
|
180
|
+
// it were part of the repo. Matches scan.ts's walk().
|
|
181
|
+
if (dirent.isSymbolicLink()) continue;
|
|
177
182
|
const full = join(dir, entry);
|
|
178
183
|
let st;
|
|
179
|
-
try { st =
|
|
184
|
+
try { st = lstatSync(full); } catch { continue; }
|
|
180
185
|
if (st.isDirectory()) walkDir(full, fn);
|
|
181
186
|
else if (st.isFile()) fn(full);
|
|
182
187
|
}
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, statSync } from 'fs';
|
|
2
|
+
import { readFileSync, writeFileSync, renameSync, unlinkSync, readdirSync, mkdirSync, existsSync, statSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import type { MonographNode, MonographEdge } from '../types.js';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Bump this whenever the parser/extractor output format changes in a way that
|
|
8
|
+
* would make previously-cached nodes/edges stale or wrong (e.g. a parser bugfix,
|
|
9
|
+
* a change to what symbols get extracted, a change to node/edge shape). Any
|
|
10
|
+
* cached entry written under an older version is treated as a miss on read, so
|
|
11
|
+
* bumping this effectively invalidates the entire on-disk extraction cache.
|
|
12
|
+
*/
|
|
13
|
+
export const EXTRACTION_CACHE_VERSION = 1;
|
|
14
|
+
|
|
6
15
|
export interface CacheEntry {
|
|
7
16
|
fileHash: string;
|
|
8
17
|
mtimeMs?: number;
|
|
9
18
|
size?: number;
|
|
10
19
|
nodes: MonographNode[];
|
|
11
20
|
edges: MonographEdge[];
|
|
21
|
+
/** Extraction format version this entry was written under — see EXTRACTION_CACHE_VERSION. */
|
|
22
|
+
cacheVersion?: number;
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
export class ExtractionCache {
|
|
@@ -32,6 +43,46 @@ export class ExtractionCache {
|
|
|
32
43
|
return join(this.dir, `${key}.json`);
|
|
33
44
|
}
|
|
34
45
|
|
|
46
|
+
/** tmp+rename write — cache corruption is low-stakes but a torn write from a
|
|
47
|
+
* killed process still shouldn't poison the next read. */
|
|
48
|
+
private writeAtomic(p: string, data: string): void {
|
|
49
|
+
const tmp = `${p}.${process.pid}.tmp`;
|
|
50
|
+
try {
|
|
51
|
+
writeFileSync(tmp, data);
|
|
52
|
+
renameSync(tmp, p);
|
|
53
|
+
} catch {
|
|
54
|
+
try { unlinkSync(tmp); } catch { /* ignore */ }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Delete cache entries whose mtime exceeds maxAgeMs (default 30 days).
|
|
60
|
+
* Covers files that were deleted/renamed (their cache entry is now orphaned
|
|
61
|
+
* and would otherwise sit on disk forever) as well as entries simply not
|
|
62
|
+
* touched in a long time. Single readdir + stat pass over the cache dir.
|
|
63
|
+
* Returns the number of entries removed.
|
|
64
|
+
*/
|
|
65
|
+
prune(maxAgeMs = 30 * 24 * 60 * 60 * 1000): number {
|
|
66
|
+
let removed = 0;
|
|
67
|
+
let entries: string[];
|
|
68
|
+
try {
|
|
69
|
+
entries = readdirSync(this.dir).filter((f) => f.endsWith('.json') && !f.startsWith('._'));
|
|
70
|
+
} catch {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
74
|
+
for (const f of entries) {
|
|
75
|
+
const p = join(this.dir, f);
|
|
76
|
+
try {
|
|
77
|
+
if (statSync(p).mtimeMs < cutoff) {
|
|
78
|
+
unlinkSync(p);
|
|
79
|
+
removed++;
|
|
80
|
+
}
|
|
81
|
+
} catch { /* ignore */ }
|
|
82
|
+
}
|
|
83
|
+
return removed;
|
|
84
|
+
}
|
|
85
|
+
|
|
35
86
|
/**
|
|
36
87
|
* Fast-path: check mtime+size before falling back to content hash.
|
|
37
88
|
* Returns cached entry if file hasn't changed, null on miss.
|
|
@@ -41,6 +92,7 @@ export class ExtractionCache {
|
|
|
41
92
|
if (!existsSync(p)) return null;
|
|
42
93
|
try {
|
|
43
94
|
const entry: CacheEntry = JSON.parse(readFileSync(p, 'utf-8'));
|
|
95
|
+
if (entry.cacheVersion !== EXTRACTION_CACHE_VERSION) return null;
|
|
44
96
|
const st = statSync(filePath);
|
|
45
97
|
if (entry.mtimeMs === st.mtimeMs && entry.size === st.size) return entry;
|
|
46
98
|
// mtime/size differ or missing — recheck content hash
|
|
@@ -49,7 +101,7 @@ export class ExtractionCache {
|
|
|
49
101
|
// Hash matches — update entry with current mtime+size for next run
|
|
50
102
|
entry.mtimeMs = st.mtimeMs;
|
|
51
103
|
entry.size = st.size;
|
|
52
|
-
try {
|
|
104
|
+
try { this.writeAtomic(p, JSON.stringify(entry)); } catch { /* non-fatal */ }
|
|
53
105
|
return entry;
|
|
54
106
|
} catch { return null; }
|
|
55
107
|
}
|
|
@@ -59,6 +111,7 @@ export class ExtractionCache {
|
|
|
59
111
|
if (!existsSync(p)) return null;
|
|
60
112
|
try {
|
|
61
113
|
const entry: CacheEntry = JSON.parse(readFileSync(p, 'utf-8'));
|
|
114
|
+
if (entry.cacheVersion !== EXTRACTION_CACHE_VERSION) return null;
|
|
62
115
|
return entry.fileHash === fileHash ? entry : null;
|
|
63
116
|
} catch { return null; }
|
|
64
117
|
}
|
|
@@ -67,21 +120,21 @@ export class ExtractionCache {
|
|
|
67
120
|
let mtimeMs: number | undefined;
|
|
68
121
|
let size: number | undefined;
|
|
69
122
|
try { const st = statSync(filePath); mtimeMs = st.mtimeMs; size = st.size; } catch { /* ignore */ }
|
|
70
|
-
const entry: CacheEntry = { fileHash, mtimeMs, size, nodes, edges };
|
|
71
|
-
|
|
123
|
+
const entry: CacheEntry = { fileHash, mtimeMs, size, nodes, edges, cacheVersion: EXTRACTION_CACHE_VERSION };
|
|
124
|
+
this.writeAtomic(this.entryPath(filePath), JSON.stringify(entry));
|
|
72
125
|
}
|
|
73
126
|
|
|
74
127
|
setDeferred(filePath: string, fileHash: string, nodes: MonographNode[], edges: MonographEdge[]): void {
|
|
75
128
|
let mtimeMs: number | undefined;
|
|
76
129
|
let size: number | undefined;
|
|
77
130
|
try { const st = statSync(filePath); mtimeMs = st.mtimeMs; size = st.size; } catch { /* ignore */ }
|
|
78
|
-
const entry: CacheEntry = { fileHash, mtimeMs, size, nodes, edges };
|
|
131
|
+
const entry: CacheEntry = { fileHash, mtimeMs, size, nodes, edges, cacheVersion: EXTRACTION_CACHE_VERSION };
|
|
79
132
|
this.pending.push({ path: this.entryPath(filePath), data: JSON.stringify(entry) });
|
|
80
133
|
}
|
|
81
134
|
|
|
82
135
|
flush(): void {
|
|
83
136
|
for (const { path, data } of this.pending) {
|
|
84
|
-
try {
|
|
137
|
+
try { this.writeAtomic(path, data); } catch { /* non-fatal */ }
|
|
85
138
|
}
|
|
86
139
|
this.pending = [];
|
|
87
140
|
}
|
package/src/cli/doctor.ts
CHANGED
|
@@ -51,12 +51,9 @@ function checkDbExists(dbPath: string): DoctorCheck {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function checkDbReadable(dbPath: string): DoctorCheck {
|
|
54
|
+
async function checkDbReadable(dbPath: string): Promise<DoctorCheck> {
|
|
55
55
|
try {
|
|
56
|
-
|
|
57
|
-
// crash the entire doctor invocation.
|
|
58
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
59
|
-
const Database = require('better-sqlite3') as typeof import('better-sqlite3');
|
|
56
|
+
const Database = (await import('better-sqlite3')).default;
|
|
60
57
|
const db = new Database(dbPath, { readonly: true });
|
|
61
58
|
db.prepare('SELECT 1').get();
|
|
62
59
|
db.close();
|
|
@@ -67,10 +64,9 @@ function checkDbReadable(dbPath: string): DoctorCheck {
|
|
|
67
64
|
}
|
|
68
65
|
}
|
|
69
66
|
|
|
70
|
-
function checkNodeCount(dbPath: string): DoctorCheck {
|
|
67
|
+
async function checkNodeCount(dbPath: string): Promise<DoctorCheck> {
|
|
71
68
|
try {
|
|
72
|
-
|
|
73
|
-
const Database = require('better-sqlite3') as typeof import('better-sqlite3');
|
|
69
|
+
const Database = (await import('better-sqlite3')).default;
|
|
74
70
|
const db = new Database(dbPath, { readonly: true });
|
|
75
71
|
const row = db.prepare('SELECT COUNT(*) AS n FROM nodes').get() as { n: number } | undefined;
|
|
76
72
|
db.close();
|
|
@@ -159,8 +155,8 @@ export async function runDoctor(repoPath: string): Promise<DoctorResult> {
|
|
|
159
155
|
|
|
160
156
|
// 3. DB readable (only if DB exists)
|
|
161
157
|
if (dbExistsCheck.status !== 'error') {
|
|
162
|
-
checks.push(checkDbReadable(dbPath));
|
|
163
|
-
checks.push(checkNodeCount(dbPath));
|
|
158
|
+
checks.push(await checkDbReadable(dbPath));
|
|
159
|
+
checks.push(await checkNodeCount(dbPath));
|
|
164
160
|
} else {
|
|
165
161
|
checks.push({
|
|
166
162
|
name: 'SQLite DB readable',
|
package/src/graph/dead-code.ts
CHANGED
|
@@ -11,8 +11,13 @@ import type { MonographDb } from '../storage/db.js';
|
|
|
11
11
|
* - Only exported — private locals are internal by design
|
|
12
12
|
* - Skips entry-point, test, dist, and node_modules paths
|
|
13
13
|
* - Requires zero inbound CALLS, IMPORTS, REFERENCES, and RE_EXPORTS edges
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
14
|
+
* - Not re-exported through any index.ts barrel file, via an actual RE_EXPORTS
|
|
15
|
+
* edge from that barrel's File node (not merely sharing a name with something
|
|
16
|
+
* an index.ts happens to also define)
|
|
17
|
+
*
|
|
18
|
+
* Known gap: files consumed only via a dynamic `import()` call won't have a static
|
|
19
|
+
* edge in the graph, so they can still surface as false positives here — this
|
|
20
|
+
* detector only tracks statically-resolved edges.
|
|
16
21
|
*/
|
|
17
22
|
export function detectDeadCode(db: MonographDb): string[] {
|
|
18
23
|
return detectDeadCodeNodes(db).map(n => n.id);
|
|
@@ -58,21 +63,19 @@ export function detectDeadCodeNodes(db: MonographDb): DeadCodeNode[] {
|
|
|
58
63
|
WHERE e.target_id = n.id
|
|
59
64
|
AND e.relation IN ('CALLS', 'IMPORTS', 'REFERENCES', 'RE_EXPORTS')
|
|
60
65
|
)
|
|
61
|
-
--
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
AND o.file_path IS NOT NULL
|
|
66
|
-
AND o.file_path NOT LIKE '%/dist/%'
|
|
67
|
-
AND o.file_path NOT LIKE '%node_modules%'
|
|
68
|
-
)
|
|
69
|
-
-- not re-exported through a barrel (index.ts/index.js)
|
|
66
|
+
-- not re-exported through a barrel (index.ts/index.js) — checked via an
|
|
67
|
+
-- ACTUAL RE_EXPORTS edge from the barrel's File node to this candidate's
|
|
68
|
+
-- File node, not a same-NAME coincidence with an unrelated symbol that
|
|
69
|
+
-- happens to also live in some index.ts elsewhere in the repo.
|
|
70
70
|
AND NOT EXISTS (
|
|
71
|
-
SELECT 1 FROM
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
AND
|
|
71
|
+
SELECT 1 FROM edges e
|
|
72
|
+
JOIN nodes fn ON fn.id = e.target_id AND fn.label = 'File'
|
|
73
|
+
JOIN nodes bn ON bn.id = e.source_id AND bn.label = 'File'
|
|
74
|
+
WHERE e.relation = 'RE_EXPORTS'
|
|
75
|
+
AND fn.file_path = n.file_path
|
|
76
|
+
AND bn.file_path LIKE '%/index.%'
|
|
77
|
+
AND bn.file_path NOT LIKE '%/dist/%'
|
|
78
|
+
AND bn.file_path NOT LIKE '%node_modules%'
|
|
76
79
|
)
|
|
77
80
|
ORDER BY n.file_path, n.start_line`,
|
|
78
81
|
)
|
package/src/graph/pagerank.ts
CHANGED
|
@@ -24,6 +24,10 @@ function getStmts(db: MonographDb): StmtCache {
|
|
|
24
24
|
const key = (db as unknown as { name: string }).name ?? '__default__';
|
|
25
25
|
let cache = _stmtCache.get(key);
|
|
26
26
|
if (!cache) {
|
|
27
|
+
if (_stmtCache.size >= MAX_STMT_CACHE) {
|
|
28
|
+
const oldest = _stmtCache.keys().next().value!;
|
|
29
|
+
_stmtCache.delete(oldest);
|
|
30
|
+
}
|
|
27
31
|
cache = {
|
|
28
32
|
selectNodes: db.prepare('SELECT id FROM nodes'),
|
|
29
33
|
selectEdges: db.prepare('SELECT source_id, target_id FROM edges'),
|
|
@@ -38,6 +42,8 @@ function getStmts(db: MonographDb): StmtCache {
|
|
|
38
42
|
// changed. Keyed by (dbName, nodeCount, edgeCount) with a 5-second TTL.
|
|
39
43
|
// ---------------------------------------------------------------------------
|
|
40
44
|
const PAGERANK_CACHE_TTL_MS = 5_000;
|
|
45
|
+
const MAX_RESULT_CACHE = 50;
|
|
46
|
+
const MAX_STMT_CACHE = 20;
|
|
41
47
|
|
|
42
48
|
interface PageRankCacheEntry {
|
|
43
49
|
result: Map<string, number>;
|
|
@@ -149,7 +155,10 @@ export function pageRank(db: MonographDb, options: PageRankOptions = {}): Map<st
|
|
|
149
155
|
result.set(nodes[i], scores[i]);
|
|
150
156
|
}
|
|
151
157
|
|
|
152
|
-
|
|
158
|
+
if (_resultCache.size >= MAX_RESULT_CACHE) {
|
|
159
|
+
const oldest = _resultCache.keys().next().value!;
|
|
160
|
+
_resultCache.delete(oldest);
|
|
161
|
+
}
|
|
153
162
|
_resultCache.set(cacheKey, { result, expiresAt: now + PAGERANK_CACHE_TTL_MS });
|
|
154
163
|
|
|
155
164
|
return result;
|
|
@@ -8,6 +8,7 @@ import { join } from 'path';
|
|
|
8
8
|
import { existsSync } from 'fs';
|
|
9
9
|
import Database from 'better-sqlite3';
|
|
10
10
|
import { type RankedResult } from '../search/rrf.js';
|
|
11
|
+
import { ftsSearch } from '../storage/fts-store.js';
|
|
11
12
|
import type { GroupConfig } from './group-config.js';
|
|
12
13
|
|
|
13
14
|
export interface GroupResult {
|
|
@@ -19,13 +20,6 @@ export interface GroupResult {
|
|
|
19
20
|
score: number;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
interface RawFtsRow {
|
|
23
|
-
id: string;
|
|
24
|
-
name: string;
|
|
25
|
-
label: string;
|
|
26
|
-
file_path: string | null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
23
|
/**
|
|
30
24
|
* Determine the monograph DB path for a given repo root.
|
|
31
25
|
* Mirrors the convention used in monograph-tools.ts: <repoPath>/.monomind/monograph.db
|
|
@@ -53,31 +47,19 @@ function searchRepo(
|
|
|
53
47
|
try {
|
|
54
48
|
db = new Database(dbPath, { readonly: true });
|
|
55
49
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
if
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
.join(' ');
|
|
64
|
-
|
|
65
|
-
const sql = `
|
|
66
|
-
SELECT n.id, n.name, n.label, n.file_path
|
|
67
|
-
FROM nodes_fts
|
|
68
|
-
JOIN nodes n ON n.rowid = nodes_fts.rowid
|
|
69
|
-
WHERE nodes_fts MATCH ?
|
|
70
|
-
ORDER BY nodes_fts.rank
|
|
71
|
-
LIMIT ?
|
|
72
|
-
`;
|
|
73
|
-
|
|
74
|
-
const rows = db.prepare(sql).all(ftsPrefixQuery, perRepoLimit) as RawFtsRow[];
|
|
50
|
+
// Reuse the SAME ftsSearch() the single-repo `monograph_query` tool uses —
|
|
51
|
+
// its trigram-aware sanitization and MATCH/LIKE fallback (P2-15) — instead of
|
|
52
|
+
// a divergent inline query that appended `*` as if this were a prefix-match
|
|
53
|
+
// tokenizer. The trigram index needs no prefix wildcard and errors on terms
|
|
54
|
+
// shorter than 3 chars when one is appended, which silently produced empty
|
|
55
|
+
// results here before.
|
|
56
|
+
const rows = ftsSearch(db, query, perRepoLimit);
|
|
75
57
|
|
|
76
58
|
return rows.map((r, idx) => ({
|
|
77
59
|
id: `${repoName}::${r.id}`,
|
|
78
60
|
name: r.name,
|
|
79
61
|
label: r.label,
|
|
80
|
-
filePath: r.
|
|
62
|
+
filePath: r.filePath,
|
|
81
63
|
repo: repoName,
|
|
82
64
|
// Use rank position as initial score so RRF can compute correctly
|
|
83
65
|
score: 1 / (60 + idx + 1),
|
package/src/mcp-tools/impact.ts
CHANGED
|
@@ -60,7 +60,13 @@ function reverseBfs(
|
|
|
60
60
|
const visited = new Map<string, number>([[startNodeId, 0]]);
|
|
61
61
|
const queue: Array<{ id: string; depth: number }> = [{ id: startNodeId, depth: 0 }];
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
// Default to a broad set of relation types, not just CALLS — a class extended
|
|
64
|
+
// in 20 files, or a function only ever referenced via import/re-export (never
|
|
65
|
+
// directly called), should still show up in the blast radius. A narrow default
|
|
66
|
+
// reports "0 symbols affected, risk LOW" for changes that are anything but low
|
|
67
|
+
// risk. Callers that need the old narrow/fast behavior can still pass
|
|
68
|
+
// relationTypes: ['CALLS'] explicitly.
|
|
69
|
+
const relations = options.relationTypes ?? ['CALLS', 'IMPORTS', 'REFERENCES', 'EXTENDS', 'RE_EXPORTS'];
|
|
64
70
|
const placeholders = relations.map(() => '?').join(',');
|
|
65
71
|
const baseQuery = `SELECT source_id FROM edges WHERE target_id = ? AND relation IN (${placeholders})`;
|
|
66
72
|
const query = options.minConfidenceScore !== undefined
|
|
@@ -11,7 +11,7 @@ import { routesPhase } from './phases/routes.js';
|
|
|
11
11
|
import { toolsPhase } from './phases/tools.js';
|
|
12
12
|
import { ormPhase } from './phases/orm.js';
|
|
13
13
|
import { crossFilePhase } from './phases/cross-file.js';
|
|
14
|
-
import { scopeResolutionPhase } from './phases/scope-resolution.js';
|
|
14
|
+
import { scopeResolutionPhase, clearWorkspacePackageMapCache } from './phases/scope-resolution.js';
|
|
15
15
|
import { mroPhase } from './phases/mro.js';
|
|
16
16
|
import { communitiesPhase } from './phases/communities.js';
|
|
17
17
|
import { processesPhase } from './phases/processes.js';
|
|
@@ -22,11 +22,13 @@ import { variablesPhase } from './phases/variables-phase.js';
|
|
|
22
22
|
import { wildcardSynthesisPhase } from './phases/wildcard-phase.js';
|
|
23
23
|
import { frameworkDetectPhase } from './phases/framework-detect.js';
|
|
24
24
|
import { importResolverPhase } from './phases/import-resolver.js';
|
|
25
|
+
import { bridgeResolverPhase } from './phases/bridge-resolver.js';
|
|
25
26
|
import type { PipelineOptions, PipelineContext } from './types.js';
|
|
26
27
|
import { DEFAULT_OPTIONS } from './types.js';
|
|
27
28
|
import type { PipelineProgress, SuggestedQuestion } from '../types.js';
|
|
28
29
|
import { generateGraphReport } from '../reporting/graph-report.js';
|
|
29
30
|
import { analyzeChurn } from '../analysis/churn.js';
|
|
31
|
+
import { ExtractionCache } from '../cache/extraction-cache.js';
|
|
30
32
|
|
|
31
33
|
function getCurrentCommitHash(repoPath: string): string | null {
|
|
32
34
|
try {
|
|
@@ -74,6 +76,7 @@ async function acquireBuildLock(dbPath: string): Promise<(() => void) | null> {
|
|
|
74
76
|
export async function buildAsync(repoPath: string, options: BuildOptions = {}): Promise<void> {
|
|
75
77
|
const dbPath = resolve(join(repoPath, '.monomind', 'monograph.db'));
|
|
76
78
|
const fullOptions: PipelineOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
79
|
+
clearWorkspacePackageMapCache(repoPath);
|
|
77
80
|
|
|
78
81
|
const releaseLock = await acquireBuildLock(dbPath);
|
|
79
82
|
if (!releaseLock) {
|
|
@@ -113,6 +116,20 @@ async function buildAsyncLocked(
|
|
|
113
116
|
}
|
|
114
117
|
}
|
|
115
118
|
|
|
119
|
+
// Parse-cache eviction — runs once at the start of a full (non-incremental)
|
|
120
|
+
// build. The cache only ever grows via writes (parse.ts's ExtractionCache);
|
|
121
|
+
// nothing else prunes entries for files that were deleted/renamed or
|
|
122
|
+
// haven't been touched in a long time, so it accumulates forever otherwise.
|
|
123
|
+
if (!options.incremental) {
|
|
124
|
+
try {
|
|
125
|
+
const parseCache = new ExtractionCache(resolve(join(repoPath, '.monomind', 'parse-cache')));
|
|
126
|
+
const removed = parseCache.prune();
|
|
127
|
+
if (removed > 0) {
|
|
128
|
+
options.onProgress?.({ phase: 'prune', message: `Parse cache: pruned ${removed} stale entries` });
|
|
129
|
+
}
|
|
130
|
+
} catch { /* non-fatal — cache pruning must never block a build */ }
|
|
131
|
+
}
|
|
132
|
+
|
|
116
133
|
const db = openDb(dbPath);
|
|
117
134
|
|
|
118
135
|
try {
|
|
@@ -128,38 +145,45 @@ async function buildAsyncLocked(
|
|
|
128
145
|
scanPhase, frameworkDetectPhase, structurePhase, parsePhase, variablesPhase,
|
|
129
146
|
markdownPhase, routesPhase, toolsPhase, ormPhase,
|
|
130
147
|
crossFilePhase, wildcardSynthesisPhase, importResolverPhase, scopeResolutionPhase,
|
|
148
|
+
bridgeResolverPhase,
|
|
131
149
|
mroPhase, communitiesPhase, processesPhase, godNodesPhase, surprisesPhase, suggestPhase,
|
|
132
150
|
]);
|
|
133
151
|
|
|
134
152
|
const outputs = await runner.run(ctx);
|
|
135
153
|
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
154
|
+
// Sweep orphaned rows for files that were renamed/deleted since the last build.
|
|
155
|
+
// This MUST run unconditionally — even when every remaining file cache-hit
|
|
156
|
+
// (ctx.allFilesCached === true), a file may have been deleted from disk between
|
|
157
|
+
// builds, which produces zero cache misses but still leaves ghost rows in the DB
|
|
158
|
+
// unless we compare the DB's known file set against the current on-disk set.
|
|
159
|
+
const scanOut = outputs.get('scan') as { filePaths: string[] } | undefined;
|
|
160
|
+
if (scanOut) {
|
|
161
|
+
const liveFiles = new Set(scanOut.filePaths.map((f) => resolve(f)));
|
|
162
|
+
const staleFiles = (
|
|
163
|
+
db.prepare('SELECT DISTINCT file_path FROM nodes WHERE file_path IS NOT NULL').all() as { file_path: string }[]
|
|
164
|
+
)
|
|
165
|
+
.map((r) => r.file_path)
|
|
166
|
+
.filter((f) => !liveFiles.has(resolve(ctx.repoPath, f)));
|
|
167
|
+
if (staleFiles.length > 0) {
|
|
168
|
+
const deleteStale = db.transaction((files: string[]) => {
|
|
169
|
+
const deleteEdges = db.prepare(`
|
|
170
|
+
DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file_path = ?)
|
|
171
|
+
OR target_id IN (SELECT id FROM nodes WHERE file_path = ?)
|
|
172
|
+
`);
|
|
173
|
+
const deleteNodesStmt = db.prepare('DELETE FROM nodes WHERE file_path = ?');
|
|
174
|
+
for (const f of files) {
|
|
175
|
+
deleteEdges.run(f, f);
|
|
176
|
+
deleteNodesStmt.run(f);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
deleteStale(staleFiles);
|
|
161
180
|
}
|
|
181
|
+
}
|
|
162
182
|
|
|
183
|
+
// Skip expensive report regeneration when all files were cached (nothing changed) —
|
|
184
|
+
// this is purely a performance optimization, not correctness-critical, so it stays
|
|
185
|
+
// gated behind allFilesCached.
|
|
186
|
+
if (!ctx.allFilesCached) {
|
|
163
187
|
const suggestOut = outputs.get('suggest') as { questions: SuggestedQuestion[] } | undefined;
|
|
164
188
|
const questions = suggestOut?.questions ?? [];
|
|
165
189
|
await generateGraphReport(resolve(repoPath), undefined, dbPath, questions);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { statSync, readFileSync } from 'fs';
|
|
2
|
+
import { join, extname } from 'path';
|
|
3
|
+
import type { PipelineContext } from '../../types.js';
|
|
4
|
+
import type { BridgeAdapter, BridgeEndpoint } from './types.js';
|
|
5
|
+
|
|
6
|
+
const JS_TS_EXT = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']);
|
|
7
|
+
|
|
8
|
+
const IPC_MAIN_RE = /\bipcMain\.(?:handle|on)\(\s*['"]([^'"]+)['"]/g;
|
|
9
|
+
const IPC_RENDERER_RE = /\bipcRenderer\.(?:invoke|send)\(\s*['"]([^'"]+)['"]/g;
|
|
10
|
+
|
|
11
|
+
function safeReadSource(absPath: string, maxBytes: number): string | undefined {
|
|
12
|
+
try {
|
|
13
|
+
const stat = statSync(absPath);
|
|
14
|
+
if (stat.size > maxBytes) return undefined;
|
|
15
|
+
return readFileSync(absPath, 'utf-8');
|
|
16
|
+
} catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function extractChannels(re: RegExp, source: string): string[] {
|
|
22
|
+
re.lastIndex = 0;
|
|
23
|
+
const names: string[] = [];
|
|
24
|
+
let m: RegExpExecArray | null;
|
|
25
|
+
while ((m = re.exec(source)) !== null) names.push(m[1]!);
|
|
26
|
+
return names;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Electron IPC (main process JS <-> renderer/preload JS): both sides are the
|
|
31
|
+
* same language, so nothing in the existing same-file import resolution
|
|
32
|
+
* connects them — the only link is the shared channel-name string literal
|
|
33
|
+
* passed to `ipcMain.handle/on` (definition) and `ipcRenderer.invoke/send`
|
|
34
|
+
* (call site). Neither side has call-expression-level node granularity from
|
|
35
|
+
* a plain regex scan, so both attach to their containing File node.
|
|
36
|
+
*/
|
|
37
|
+
export const electronIpcAdapter: BridgeAdapter = {
|
|
38
|
+
name: 'electron-ipc',
|
|
39
|
+
|
|
40
|
+
detect(ctx, filePaths) {
|
|
41
|
+
let hasMain = false;
|
|
42
|
+
let hasRenderer = false;
|
|
43
|
+
for (const relPath of filePaths) {
|
|
44
|
+
if (!JS_TS_EXT.has(extname(relPath))) continue;
|
|
45
|
+
const source = safeReadSource(join(ctx.repoPath, relPath), ctx.options.maxFileSizeBytes);
|
|
46
|
+
if (!source) continue;
|
|
47
|
+
if (!hasMain && /\bipcMain\.(?:handle|on)\(/.test(source)) hasMain = true;
|
|
48
|
+
if (!hasRenderer && /\bipcRenderer\.(?:invoke|send)\(/.test(source)) hasRenderer = true;
|
|
49
|
+
if (hasMain && hasRenderer) return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
findDefinitions(ctx, filePaths) {
|
|
55
|
+
const endpoints: BridgeEndpoint[] = [];
|
|
56
|
+
for (const relPath of filePaths) {
|
|
57
|
+
if (!JS_TS_EXT.has(extname(relPath))) continue;
|
|
58
|
+
const source = safeReadSource(join(ctx.repoPath, relPath), ctx.options.maxFileSizeBytes);
|
|
59
|
+
if (!source) continue;
|
|
60
|
+
const channels = extractChannels(IPC_MAIN_RE, source);
|
|
61
|
+
if (channels.length === 0) continue;
|
|
62
|
+
|
|
63
|
+
const fileRow = ctx.db
|
|
64
|
+
.prepare(`SELECT id FROM nodes WHERE label = 'File' AND file_path = ?`)
|
|
65
|
+
.get(relPath) as { id: string } | undefined;
|
|
66
|
+
if (!fileRow) continue;
|
|
67
|
+
|
|
68
|
+
for (const channel of channels) endpoints.push({ key: channel, nodeId: fileRow.id, language: 'javascript' });
|
|
69
|
+
}
|
|
70
|
+
return endpoints;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
findCallSites(ctx, filePaths) {
|
|
74
|
+
const endpoints: BridgeEndpoint[] = [];
|
|
75
|
+
for (const relPath of filePaths) {
|
|
76
|
+
if (!JS_TS_EXT.has(extname(relPath))) continue;
|
|
77
|
+
const source = safeReadSource(join(ctx.repoPath, relPath), ctx.options.maxFileSizeBytes);
|
|
78
|
+
if (!source) continue;
|
|
79
|
+
const channels = extractChannels(IPC_RENDERER_RE, source);
|
|
80
|
+
if (channels.length === 0) continue;
|
|
81
|
+
|
|
82
|
+
const fileRow = ctx.db
|
|
83
|
+
.prepare(`SELECT id FROM nodes WHERE label = 'File' AND file_path = ?`)
|
|
84
|
+
.get(relPath) as { id: string } | undefined;
|
|
85
|
+
if (!fileRow) continue;
|
|
86
|
+
|
|
87
|
+
for (const channel of channels) endpoints.push({ key: channel, nodeId: fileRow.id, language: 'javascript' });
|
|
88
|
+
}
|
|
89
|
+
return endpoints;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BridgeAdapter } from './types.js';
|
|
2
|
+
import { wailsAdapter } from './wails.js';
|
|
3
|
+
import { tauriAdapter } from './tauri.js';
|
|
4
|
+
import { electronIpcAdapter } from './electron-ipc.js';
|
|
5
|
+
|
|
6
|
+
// Every adapter here has an unambiguous detect() signal (a generated binding
|
|
7
|
+
// path, or a framework-specific call shape that only appears when its
|
|
8
|
+
// framework is actually in use) — so all run unconditionally; detect() is
|
|
9
|
+
// what keeps the no-op cost near zero for repos that don't use any of them.
|
|
10
|
+
export const BUILTIN_BRIDGE_ADAPTERS: BridgeAdapter[] = [
|
|
11
|
+
wailsAdapter,
|
|
12
|
+
tauriAdapter,
|
|
13
|
+
electronIpcAdapter,
|
|
14
|
+
];
|