openlore 2.0.6 → 2.0.8

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.
Files changed (97) hide show
  1. package/README.md +140 -23
  2. package/dist/cli/commands/decisions.d.ts.map +1 -1
  3. package/dist/cli/commands/decisions.js +187 -174
  4. package/dist/cli/commands/decisions.js.map +1 -1
  5. package/dist/cli/commands/mcp.d.ts +694 -0
  6. package/dist/cli/commands/mcp.d.ts.map +1 -1
  7. package/dist/cli/commands/mcp.js +417 -203
  8. package/dist/cli/commands/mcp.js.map +1 -1
  9. package/dist/constants.d.ts +6 -0
  10. package/dist/constants.d.ts.map +1 -1
  11. package/dist/constants.js +14 -0
  12. package/dist/constants.js.map +1 -1
  13. package/dist/core/analyzer/artifact-generator.d.ts.map +1 -1
  14. package/dist/core/analyzer/artifact-generator.js +59 -4
  15. package/dist/core/analyzer/artifact-generator.js.map +1 -1
  16. package/dist/core/analyzer/call-graph.d.ts +19 -1
  17. package/dist/core/analyzer/call-graph.d.ts.map +1 -1
  18. package/dist/core/analyzer/call-graph.js +128 -28
  19. package/dist/core/analyzer/call-graph.js.map +1 -1
  20. package/dist/core/architecture/check.d.ts +63 -0
  21. package/dist/core/architecture/check.d.ts.map +1 -0
  22. package/dist/core/architecture/check.js +192 -0
  23. package/dist/core/architecture/check.js.map +1 -0
  24. package/dist/core/architecture/rules.d.ts +73 -0
  25. package/dist/core/architecture/rules.d.ts.map +1 -0
  26. package/dist/core/architecture/rules.js +201 -0
  27. package/dist/core/architecture/rules.js.map +1 -0
  28. package/dist/core/decisions/lock.d.ts +10 -0
  29. package/dist/core/decisions/lock.d.ts.map +1 -0
  30. package/dist/core/decisions/lock.js +77 -0
  31. package/dist/core/decisions/lock.js.map +1 -0
  32. package/dist/core/decisions/project.d.ts +59 -0
  33. package/dist/core/decisions/project.d.ts.map +1 -0
  34. package/dist/core/decisions/project.js +68 -0
  35. package/dist/core/decisions/project.js.map +1 -0
  36. package/dist/core/decisions/verifier.d.ts +10 -0
  37. package/dist/core/decisions/verifier.d.ts.map +1 -1
  38. package/dist/core/decisions/verifier.js +48 -5
  39. package/dist/core/decisions/verifier.js.map +1 -1
  40. package/dist/core/provenance/change-coupling.d.ts +68 -0
  41. package/dist/core/provenance/change-coupling.d.ts.map +1 -0
  42. package/dist/core/provenance/change-coupling.js +134 -0
  43. package/dist/core/provenance/change-coupling.js.map +1 -0
  44. package/dist/core/provenance/git-provenance.d.ts +67 -0
  45. package/dist/core/provenance/git-provenance.d.ts.map +1 -0
  46. package/dist/core/provenance/git-provenance.js +177 -0
  47. package/dist/core/provenance/git-provenance.js.map +1 -0
  48. package/dist/core/provenance/project.d.ts +37 -0
  49. package/dist/core/provenance/project.d.ts.map +1 -0
  50. package/dist/core/provenance/project.js +46 -0
  51. package/dist/core/provenance/project.js.map +1 -0
  52. package/dist/core/services/edge-store.d.ts +41 -0
  53. package/dist/core/services/edge-store.d.ts.map +1 -1
  54. package/dist/core/services/edge-store.js +251 -3
  55. package/dist/core/services/edge-store.js.map +1 -1
  56. package/dist/core/services/llm-service.d.ts +9 -0
  57. package/dist/core/services/llm-service.d.ts.map +1 -1
  58. package/dist/core/services/llm-service.js +15 -4
  59. package/dist/core/services/llm-service.js.map +1 -1
  60. package/dist/core/services/mcp-handlers/architecture.d.ts +19 -0
  61. package/dist/core/services/mcp-handlers/architecture.d.ts.map +1 -0
  62. package/dist/core/services/mcp-handlers/architecture.js +104 -0
  63. package/dist/core/services/mcp-handlers/architecture.js.map +1 -0
  64. package/dist/core/services/mcp-handlers/change-coupling.d.ts +16 -0
  65. package/dist/core/services/mcp-handlers/change-coupling.d.ts.map +1 -0
  66. package/dist/core/services/mcp-handlers/change-coupling.js +57 -0
  67. package/dist/core/services/mcp-handlers/change-coupling.js.map +1 -0
  68. package/dist/core/services/mcp-handlers/graph.d.ts +27 -0
  69. package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -1
  70. package/dist/core/services/mcp-handlers/graph.js +98 -16
  71. package/dist/core/services/mcp-handlers/graph.js.map +1 -1
  72. package/dist/core/services/mcp-handlers/orient.d.ts.map +1 -1
  73. package/dist/core/services/mcp-handlers/orient.js +122 -2
  74. package/dist/core/services/mcp-handlers/orient.js.map +1 -1
  75. package/dist/core/services/mcp-handlers/reachability.d.ts +30 -0
  76. package/dist/core/services/mcp-handlers/reachability.d.ts.map +1 -0
  77. package/dist/core/services/mcp-handlers/reachability.js +222 -0
  78. package/dist/core/services/mcp-handlers/reachability.js.map +1 -0
  79. package/dist/core/services/mcp-handlers/structural-diff.d.ts +31 -0
  80. package/dist/core/services/mcp-handlers/structural-diff.d.ts.map +1 -0
  81. package/dist/core/services/mcp-handlers/structural-diff.js +268 -0
  82. package/dist/core/services/mcp-handlers/structural-diff.js.map +1 -0
  83. package/dist/core/services/mcp-handlers/test-impact.d.ts +34 -0
  84. package/dist/core/services/mcp-handlers/test-impact.d.ts.map +1 -0
  85. package/dist/core/services/mcp-handlers/test-impact.js +221 -0
  86. package/dist/core/services/mcp-handlers/test-impact.js.map +1 -0
  87. package/dist/core/services/mcp-handlers/tool-guard.d.ts +45 -0
  88. package/dist/core/services/mcp-handlers/tool-guard.d.ts.map +1 -0
  89. package/dist/core/services/mcp-handlers/tool-guard.js +81 -0
  90. package/dist/core/services/mcp-handlers/tool-guard.js.map +1 -0
  91. package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -1
  92. package/dist/core/services/mcp-handlers/utils.js +15 -1
  93. package/dist/core/services/mcp-handlers/utils.js.map +1 -1
  94. package/dist/core/services/mcp-watcher.d.ts.map +1 -1
  95. package/dist/core/services/mcp-watcher.js +9 -0
  96. package/dist/core/services/mcp-watcher.js.map +1 -1
  97. package/package.json +9 -8
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Reachability & Dead-Code Analysis (spec-20) — cross-language mark-and-sweep
3
+ * over the unified call graph.
4
+ *
5
+ * "Is this reachable from any entry point?", "what is dead?", and "what becomes
6
+ * dead if I delete X?" — graph reachability questions grep can't answer (it sees
7
+ * text, not reach) and the model burns tokens guessing at. Reachability is forward
8
+ * BFS from roots; candidate-dead is the unreached remainder; "dead if I delete X"
9
+ * is the set reachable only through X.
10
+ *
11
+ * Prior art: knip / ts-prune do mark-and-sweep, but TS/JS-only. This is the
12
+ * cross-language version over the tree-sitter graph (15+ languages).
13
+ *
14
+ * HONEST LIMITS — output is *candidates*, never deletion authority. Dynamic entry
15
+ * points, framework magic (routes, DI, plugin registries), reflection, and
16
+ * externally-consumed public exports all produce false "dead" positives. Roots
17
+ * therefore include tests, imported symbols, and detected framework entries; every
18
+ * candidate carries a confidence level and a reason; nothing is ever auto-deleted.
19
+ */
20
+ export interface FindDeadCodeInput {
21
+ directory: string;
22
+ /** "What becomes dead if I delete this symbol?" — switches to delete-impact mode. */
23
+ ifDeleted?: string;
24
+ /** Limit candidate-dead results (default 100). */
25
+ maxResults?: number;
26
+ /** Only report candidates whose file path contains this substring. */
27
+ filePattern?: string;
28
+ }
29
+ export declare function handleFindDeadCode(input: FindDeadCodeInput): Promise<unknown>;
30
+ //# sourceMappingURL=reachability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reachability.d.ts","sourceRoot":"","sources":["../../../../src/core/services/mcp-handlers/reachability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAUH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAmFD,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CAsHnF"}
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Reachability & Dead-Code Analysis (spec-20) — cross-language mark-and-sweep
3
+ * over the unified call graph.
4
+ *
5
+ * "Is this reachable from any entry point?", "what is dead?", and "what becomes
6
+ * dead if I delete X?" — graph reachability questions grep can't answer (it sees
7
+ * text, not reach) and the model burns tokens guessing at. Reachability is forward
8
+ * BFS from roots; candidate-dead is the unreached remainder; "dead if I delete X"
9
+ * is the set reachable only through X.
10
+ *
11
+ * Prior art: knip / ts-prune do mark-and-sweep, but TS/JS-only. This is the
12
+ * cross-language version over the tree-sitter graph (15+ languages).
13
+ *
14
+ * HONEST LIMITS — output is *candidates*, never deletion authority. Dynamic entry
15
+ * points, framework magic (routes, DI, plugin registries), reflection, and
16
+ * externally-consumed public exports all produce false "dead" positives. Roots
17
+ * therefore include tests, imported symbols, and detected framework entries; every
18
+ * candidate carries a confidence level and a reason; nothing is ever auto-deleted.
19
+ */
20
+ import { readFile } from 'node:fs/promises';
21
+ import { join } from 'node:path';
22
+ import { validateDirectory, readCachedContext } from './utils.js';
23
+ import { buildAdjacency } from './graph.js';
24
+ import { isIacLanguage } from '../../analyzer/iac/types.js';
25
+ import { OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, ARTIFACT_DEPENDENCY_GRAPH } from '../../../constants.js';
26
+ // Languages where export is explicit and dispatch mostly static — deadness is
27
+ // more reliable. Dynamic languages (implicit exports, runtime dispatch) cap low.
28
+ const STATIC_LANGS = new Set([
29
+ 'TypeScript', 'JavaScript', 'Go', 'Rust', 'Java', 'Kotlin', 'C#', 'Swift', 'C++', 'C', 'Scala', 'Dart',
30
+ ]);
31
+ const DYNAMIC_LANGS = new Set(['Python', 'Ruby', 'PHP', 'Lua', 'Elixir', 'Bash']);
32
+ /** A code node we can reason about (not external, not infrastructure). */
33
+ function isCodeNode(n) {
34
+ return !n.isExternal && !isIacLanguage(n.language);
35
+ }
36
+ /** Load the cross-file import signals from the dependency graph. */
37
+ async function loadDepSignals(absDir) {
38
+ try {
39
+ const raw = await readFile(join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, ARTIFACT_DEPENDENCY_GRAPH), 'utf-8');
40
+ const g = JSON.parse(raw);
41
+ const names = new Set();
42
+ const idToPath = new Map((g.nodes ?? []).map(n => [n.id, n.file?.path ?? '']));
43
+ const files = new Set();
44
+ for (const e of g.edges ?? []) {
45
+ for (const n of e.importedNames ?? [])
46
+ names.add(n);
47
+ const path = e.target ? idToPath.get(e.target) : undefined;
48
+ if (path)
49
+ files.add(path);
50
+ }
51
+ return { names, files };
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ /** A candidate's module is imported somewhere → tolerant path match against the dep-graph file set. */
58
+ function fileImported(filePath, importedFiles) {
59
+ if (importedFiles.has(filePath))
60
+ return true;
61
+ const a = filePath.replace(/^\/+/, '');
62
+ for (const f of importedFiles) {
63
+ const b = f.replace(/^\/+/, '');
64
+ if (a === b || a.endsWith('/' + b) || b.endsWith('/' + a))
65
+ return true;
66
+ }
67
+ return false;
68
+ }
69
+ /**
70
+ * Compute the live (reachable) node-id set by forward BFS from seed roots.
71
+ * `excludeId` removes a node from both the seeds and the traversal (delete mode).
72
+ */
73
+ function reachableFrom(seeds, forward, excludeId) {
74
+ const live = new Set();
75
+ const queue = [];
76
+ for (const s of seeds) {
77
+ if (s === excludeId || live.has(s))
78
+ continue;
79
+ live.add(s);
80
+ queue.push(s);
81
+ }
82
+ while (queue.length) {
83
+ const id = queue.shift();
84
+ for (const next of forward.get(id) ?? []) {
85
+ if (next === excludeId || live.has(next))
86
+ continue;
87
+ live.add(next);
88
+ queue.push(next);
89
+ }
90
+ }
91
+ return live;
92
+ }
93
+ export async function handleFindDeadCode(input) {
94
+ const absDir = await validateDirectory(input.directory);
95
+ const ctx = await readCachedContext(absDir);
96
+ if (!ctx)
97
+ return { error: 'No analysis found. Run analyze_codebase first.' };
98
+ if (!ctx.callGraph)
99
+ return { error: 'Call graph not available. Re-run analyze_codebase.' };
100
+ const cg = ctx.callGraph;
101
+ const dep = await loadDepSignals(absDir);
102
+ const importedNames = dep?.names ?? null;
103
+ const importedFiles = dep?.files ?? null;
104
+ const { nodeMap, forward } = buildAdjacency(cg);
105
+ // ── Roots (liveness seeds) — conservative: prefer false-live over false-dead ──
106
+ // tests (they invoke code) · symbols imported by another file · HTTP route
107
+ // handlers · main-like entry functions.
108
+ const httpHandlerIds = new Set(cg.edges.filter(e => e.confidence === 'http_endpoint' && e.calleeId).map(e => e.calleeId));
109
+ const isMainLike = (n) => n.name === 'main' || n.name === 'default';
110
+ const isRoot = (n) => !!n.isTest ||
111
+ httpHandlerIds.has(n.id) ||
112
+ isMainLike(n) ||
113
+ (importedNames !== null && importedNames.has(n.name));
114
+ const codeNodes = cg.nodes.filter(isCodeNode);
115
+ const roots = codeNodes.filter(isRoot);
116
+ const seedIds = [...roots].map(r => r.id).sort();
117
+ const live = reachableFrom(seedIds, forward);
118
+ const exportSignal = importedNames !== null ? 'dependency-graph' : 'none';
119
+ const languages = [...new Set(codeNodes.map(n => n.language))].sort();
120
+ // ── Delete-impact mode: "what becomes dead if I delete X?" ──────────────────
121
+ if (input.ifDeleted !== undefined) {
122
+ const target = codeNodes.find(n => n.name === input.ifDeleted)
123
+ ?? codeNodes.find(n => n.name.toLowerCase() === input.ifDeleted.toLowerCase());
124
+ if (!target)
125
+ return { error: `Symbol "${input.ifDeleted}" not found in the code graph.` };
126
+ const liveWithout = reachableFrom(seedIds, forward, target.id);
127
+ const becomesDead = [...live]
128
+ .filter(id => id !== target.id && !liveWithout.has(id))
129
+ .map(id => nodeMap.get(id))
130
+ .filter((n) => !!n && isCodeNode(n) && !n.isTest)
131
+ .map(n => ({ name: n.name, file: n.filePath, language: n.language, fanIn: n.fanIn }))
132
+ .sort((a, b) => a.file.localeCompare(b.file) || a.name.localeCompare(b.name));
133
+ return {
134
+ target: target.name,
135
+ file: target.filePath,
136
+ becomesDeadIfDeleted: becomesDead,
137
+ count: becomesDead.length,
138
+ note: becomesDead.length === 0
139
+ ? 'Nothing else becomes unreachable — every other node has an independent path to a root (or is itself a root).'
140
+ : 'These nodes are reachable only through the target. Deleting it orphans them — verify before removing (dynamic callers are invisible here).',
141
+ soundness: deadCodeSoundness(exportSignal, languages),
142
+ };
143
+ }
144
+ // ── Candidate dead-code report ──────────────────────────────────────────────
145
+ let candidates = codeNodes.filter(n => !n.isTest && !live.has(n.id));
146
+ if (input.filePattern)
147
+ candidates = candidates.filter(n => n.filePath.includes(input.filePattern));
148
+ const ranked = candidates
149
+ .map(n => {
150
+ const noCaller = (n.fanIn ?? 0) === 0;
151
+ const dynamic = DYNAMIC_LANGS.has(n.language) || !STATIC_LANGS.has(n.language);
152
+ const moduleUsed = importedFiles !== null && fileImported(n.filePath, importedFiles);
153
+ const reasons = [];
154
+ if (noCaller)
155
+ reasons.push('no internal caller');
156
+ else
157
+ reasons.push('reachable only from other candidate-dead code');
158
+ if (importedNames !== null && !importedNames.has(n.name))
159
+ reasons.push('not imported by name from any other file');
160
+ if (moduleUsed)
161
+ reasons.push('but its module IS imported elsewhere (symbol-level usage unresolved — e.g. namespace/default import or external API)');
162
+ reasons.push('not a test, route handler, or main entry');
163
+ // Conservative: a symbol whose module is consumed elsewhere can't be high —
164
+ // named-import detection misses namespace/default/re-export usage.
165
+ let confidence;
166
+ if (dynamic)
167
+ confidence = 'low';
168
+ else if (moduleUsed)
169
+ confidence = 'low';
170
+ else if (exportSignal === 'none')
171
+ confidence = 'medium';
172
+ else
173
+ confidence = noCaller ? 'high' : 'medium';
174
+ return {
175
+ name: n.name, file: n.filePath, language: n.language, className: n.className ?? null,
176
+ fanIn: n.fanIn ?? 0, startLine: n.startLine ?? null,
177
+ confidence, reason: reasons.join('; '),
178
+ };
179
+ })
180
+ .sort((a, b) => ({ high: 0, medium: 1, low: 2 })[a.confidence] - ({ high: 0, medium: 1, low: 2 })[b.confidence] ||
181
+ a.file.localeCompare(b.file) || a.name.localeCompare(b.name));
182
+ const limit = Math.max(1, Math.min(input.maxResults ?? 100, 1000));
183
+ const byConfidence = {
184
+ high: ranked.filter(r => r.confidence === 'high').length,
185
+ medium: ranked.filter(r => r.confidence === 'medium').length,
186
+ low: ranked.filter(r => r.confidence === 'low').length,
187
+ };
188
+ return {
189
+ stats: {
190
+ analyzed: codeNodes.filter(n => !n.isTest).length,
191
+ roots: roots.length,
192
+ reachable: [...live].filter(id => { const n = nodeMap.get(id); return !!n && isCodeNode(n) && !n.isTest; }).length,
193
+ candidateDead: ranked.length,
194
+ },
195
+ rootKinds: {
196
+ tests: roots.filter(r => r.isTest).length,
197
+ imported: importedNames !== null ? roots.filter(r => !r.isTest && importedNames.has(r.name)).length : 0,
198
+ httpHandlers: roots.filter(r => httpHandlerIds.has(r.id)).length,
199
+ },
200
+ byConfidence,
201
+ candidateDead: ranked.slice(0, limit),
202
+ truncated: ranked.length > limit ? ranked.length - limit : 0,
203
+ coverage: { languages, exportSignal },
204
+ soundness: deadCodeSoundness(exportSignal, languages),
205
+ };
206
+ }
207
+ function deadCodeSoundness(exportSignal, languages) {
208
+ const caveats = [
209
+ 'These are CANDIDATES, not deletion authority — never auto-delete based on this.',
210
+ 'Dynamic dispatch, reflection, DI, plugin registries, and framework routing invoke code invisibly to static analysis; such symbols can be flagged dead falsely.',
211
+ 'Public API consumed OUTSIDE this repo is not visible as a root and may appear dead — treat exported library symbols with caution.',
212
+ ];
213
+ if (exportSignal === 'none') {
214
+ caveats.push('No dependency graph found — the "imported elsewhere" liveness signal is unavailable, so confidence is reduced. Run analyze_codebase to generate it.');
215
+ }
216
+ const dynamic = languages.filter(l => DYNAMIC_LANGS.has(l));
217
+ if (dynamic.length > 0) {
218
+ caveats.push(`Dynamic languages present (${dynamic.join(', ')}): implicit exports and runtime dispatch make deadness unreliable — those candidates are capped at low confidence.`);
219
+ }
220
+ return { posture: 'candidates-not-authority', caveats };
221
+ }
222
+ //# sourceMappingURL=reachability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reachability.js","sourceRoot":"","sources":["../../../../src/core/services/mcp-handlers/reachability.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Structural Change Analysis — graph diff (spec-21).
3
+ *
4
+ * Given a change (working tree vs a ref, or two refs), compute what changed
5
+ * *structurally*: which functions/edges were added or removed, which signatures
6
+ * changed, and which existing callers are now STALE because a callee's signature
7
+ * moved under them. A review/refactor agent gets a precise structural changelog
8
+ * instead of re-deriving consequences from a raw text diff.
9
+ *
10
+ * The difference between "these 40 lines changed" (git diff) and "this removed
11
+ * function C, altered the signature of D, and 5 of D's callers are now stale"
12
+ * (graph diff) — a computed consequence (Layer 3), not retrieval.
13
+ *
14
+ * Bounded: only the changed files are re-parsed (old content via `git show`, new
15
+ * via the working tree / ref), so two in-memory snapshots are cheap. The canonical
16
+ * graph is never mutated. Stale callers come from the cached graph (all callers).
17
+ *
18
+ * Honest limits: rename/move detection is heuristic — a renamed function looks like
19
+ * delete + add — so both interpretations are reported, never silently guessed.
20
+ */
21
+ export interface StructuralDiffInput {
22
+ directory: string;
23
+ /** Old state (default "HEAD"). */
24
+ baseRef?: string;
25
+ /** New state. Omit to use the working tree. */
26
+ headRef?: string;
27
+ /** Cap reported items per category (default 200). */
28
+ maxResults?: number;
29
+ }
30
+ export declare function handleStructuralDiff(input: StructuralDiffInput): Promise<unknown>;
31
+ //# sourceMappingURL=structural-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structural-diff.d.ts","sourceRoot":"","sources":["../../../../src/core/services/mcp-handlers/structural-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAcH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA2BD,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CA+KvF"}
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Structural Change Analysis — graph diff (spec-21).
3
+ *
4
+ * Given a change (working tree vs a ref, or two refs), compute what changed
5
+ * *structurally*: which functions/edges were added or removed, which signatures
6
+ * changed, and which existing callers are now STALE because a callee's signature
7
+ * moved under them. A review/refactor agent gets a precise structural changelog
8
+ * instead of re-deriving consequences from a raw text diff.
9
+ *
10
+ * The difference between "these 40 lines changed" (git diff) and "this removed
11
+ * function C, altered the signature of D, and 5 of D's callers are now stale"
12
+ * (graph diff) — a computed consequence (Layer 3), not retrieval.
13
+ *
14
+ * Bounded: only the changed files are re-parsed (old content via `git show`, new
15
+ * via the working tree / ref), so two in-memory snapshots are cheap. The canonical
16
+ * graph is never mutated. Stale callers come from the cached graph (all callers).
17
+ *
18
+ * Honest limits: rename/move detection is heuristic — a renamed function looks like
19
+ * delete + add — so both interpretations are reported, never silently guessed.
20
+ */
21
+ import { execFile } from 'node:child_process';
22
+ import { readFile } from 'node:fs/promises';
23
+ import { join } from 'node:path';
24
+ import { promisify } from 'node:util';
25
+ import { validateDirectory, readCachedContext } from './utils.js';
26
+ import { isGitRepository, resolveBaseRef, validateGitRef, getChangedFiles } from '../../drift/git-diff.js';
27
+ import { CallGraphBuilder, serializeCallGraph } from '../../analyzer/call-graph.js';
28
+ import { detectLanguage } from '../../analyzer/signature-extractor.js';
29
+ const execFileAsync = promisify(execFile);
30
+ /** Content of a file at a git ref, or '' if it didn't exist there. */
31
+ async function fileAtRef(rootPath, ref, path) {
32
+ try {
33
+ const { stdout } = await execFileAsync('git', ['show', `${ref}:${path}`], {
34
+ cwd: rootPath, maxBuffer: 32 * 1024 * 1024,
35
+ });
36
+ return stdout;
37
+ }
38
+ catch {
39
+ return '';
40
+ }
41
+ }
42
+ /** Signature shape with the identifier removed — for rename matching. */
43
+ function signatureShape(signature) {
44
+ if (!signature)
45
+ return '';
46
+ const paren = signature.indexOf('(');
47
+ return (paren >= 0 ? signature.slice(paren) : signature).replace(/\s+/g, ' ').trim();
48
+ }
49
+ function nodeRef(n) {
50
+ return { name: n.name, file: n.filePath, className: n.className ?? null, signature: n.signature ?? n.name };
51
+ }
52
+ export async function handleStructuralDiff(input) {
53
+ const absDir = await validateDirectory(input.directory);
54
+ if (!(await isGitRepository(absDir))) {
55
+ return { error: 'Not a git repository. Structural diff requires git.' };
56
+ }
57
+ const baseRaw = input.baseRef ?? 'HEAD';
58
+ try {
59
+ validateGitRef(baseRaw);
60
+ if (input.headRef)
61
+ validateGitRef(input.headRef);
62
+ }
63
+ catch (err) {
64
+ return { error: `Invalid git ref: ${err instanceof Error ? err.message : String(err)}` };
65
+ }
66
+ const resolvedBase = await resolveBaseRef(absDir, baseRaw);
67
+ // ── Changed files ───────────────────────────────────────────────────────────
68
+ let changed;
69
+ try {
70
+ if (input.headRef) {
71
+ const { stdout } = await execFileAsync('git', ['diff', '--name-status', '--diff-filter=ACDMR', `${resolvedBase}..${input.headRef}`], { cwd: absDir, maxBuffer: 16 * 1024 * 1024 });
72
+ changed = stdout.split('\n').filter(Boolean).map(line => {
73
+ const parts = line.split('\t');
74
+ const s = parts[0].charAt(0);
75
+ const status = s === 'A' ? 'added' : s === 'D' ? 'deleted' : s === 'R' ? 'renamed' : 'modified';
76
+ return status === 'renamed' && parts.length >= 3
77
+ ? { path: parts[2], status, oldPath: parts[1] }
78
+ : { path: parts[1], status };
79
+ });
80
+ }
81
+ else {
82
+ const diff = await getChangedFiles({ rootPath: absDir, baseRef: resolvedBase, includeUnstaged: true });
83
+ changed = diff.files.map(f => ({ path: f.path, status: f.status, oldPath: f.oldPath }));
84
+ // git diff excludes untracked files; a brand-new file's functions are all
85
+ // structural additions, so fold them in for the working-tree comparison.
86
+ try {
87
+ const { stdout } = await execFileAsync('git', ['ls-files', '--others', '--exclude-standard'], {
88
+ cwd: absDir, maxBuffer: 16 * 1024 * 1024,
89
+ });
90
+ const seen = new Set(changed.map(c => c.path));
91
+ for (const path of stdout.split('\n').map(s => s.trim()).filter(Boolean)) {
92
+ if (!seen.has(path))
93
+ changed.push({ path, status: 'added' });
94
+ }
95
+ }
96
+ catch { /* untracked enumeration is best-effort */ }
97
+ }
98
+ }
99
+ catch (err) {
100
+ return { error: `git diff failed: ${err instanceof Error ? err.message : String(err)}` };
101
+ }
102
+ // Code files only; logical path = the new path (so a file move doesn't explode
103
+ // into all-removed + all-added at the function level).
104
+ const codeChanged = changed.filter(c => {
105
+ const lang = detectLanguage(c.path);
106
+ return lang && lang !== 'Unknown' && lang !== 'unknown';
107
+ });
108
+ if (codeChanged.length === 0) {
109
+ return {
110
+ base: resolvedBase, head: input.headRef ?? 'working tree',
111
+ message: 'No changed code files between the two states (only non-code or no changes).',
112
+ summary: emptySummary(),
113
+ soundness: diffSoundness(true),
114
+ };
115
+ }
116
+ // ── Build old + new snapshots from just the changed files ───────────────────
117
+ const oldFiles = [];
118
+ const newFiles = [];
119
+ for (const c of codeChanged) {
120
+ const lang = detectLanguage(c.path);
121
+ const oldSrcPath = c.oldPath ?? c.path;
122
+ const oldContent = c.status === 'added' ? '' : await fileAtRef(absDir, resolvedBase, oldSrcPath);
123
+ let newContent = '';
124
+ if (c.status !== 'deleted') {
125
+ newContent = input.headRef
126
+ ? await fileAtRef(absDir, input.headRef, c.path)
127
+ : await readFile(join(absDir, c.path), 'utf-8').catch(() => '');
128
+ }
129
+ if (oldContent)
130
+ oldFiles.push({ path: c.path, content: oldContent, language: lang });
131
+ if (newContent)
132
+ newFiles.push({ path: c.path, content: newContent, language: lang });
133
+ }
134
+ const oldGraph = await safeBuild(oldFiles);
135
+ const newGraph = await safeBuild(newFiles);
136
+ const oldNodes = new Map(oldGraph.nodes.filter(isCode).map(n => [n.id, n]));
137
+ const newNodes = new Map(newGraph.nodes.filter(isCode).map(n => [n.id, n]));
138
+ // ── Node-level delta ────────────────────────────────────────────────────────
139
+ const added = [];
140
+ const removed = [];
141
+ const signatureChanged = [];
142
+ for (const [id, n] of newNodes)
143
+ if (!oldNodes.has(id))
144
+ added.push(n);
145
+ for (const [id, n] of oldNodes) {
146
+ const nu = newNodes.get(id);
147
+ if (!nu) {
148
+ removed.push(n);
149
+ continue;
150
+ }
151
+ if ((n.signature ?? '') !== (nu.signature ?? '')) {
152
+ signatureChanged.push({ node: nu, before: n.signature ?? n.name, after: nu.signature ?? n.name });
153
+ }
154
+ }
155
+ // ── Rename/move candidates — removed↔added with matching signature shape ─────
156
+ const renameCandidates = [];
157
+ for (const r of removed) {
158
+ for (const a of added) {
159
+ const sameShape = signatureShape(r.signature) && signatureShape(r.signature) === signatureShape(a.signature);
160
+ const sameFile = r.filePath === a.filePath;
161
+ if (!sameShape)
162
+ continue;
163
+ const confidence = sameFile ? 'high' : 'medium';
164
+ renameCandidates.push({
165
+ from: nodeRef(r), to: nodeRef(a), confidence,
166
+ note: `"${r.name}" may have been renamed/moved to "${a.name}" (matching signature shape${sameFile ? ', same file' : ''}). Reported as both remove+add and this rename candidate — verify.`,
167
+ });
168
+ }
169
+ }
170
+ // ── Edge delta (calls among / out of the changed files) ─────────────────────
171
+ const edgeKey = (e) => `${e.callerId}\0${e.calleeName}`;
172
+ const oldEdges = new Map(oldGraph.edges.filter(isCallEdge).map(e => [edgeKey(e), e]));
173
+ const newEdges = new Map(newGraph.edges.filter(isCallEdge).map(e => [edgeKey(e), e]));
174
+ const addedEdges = [...newEdges].filter(([k]) => !oldEdges.has(k)).map(([, e]) => edgePair(e, newGraph));
175
+ const removedEdges = [...oldEdges].filter(([k]) => !newEdges.has(k)).map(([, e]) => edgePair(e, oldGraph));
176
+ // ── Stale callers — callers (in the canonical graph) of a node whose signature
177
+ // changed or that was removed, that are NOT themselves in the changed set ──
178
+ const ctx = await readCachedContext(absDir);
179
+ const changedPaths = new Set(codeChanged.map(c => c.path));
180
+ const staleCallerNote = ctx?.edgeStore
181
+ ? undefined
182
+ : 'No cached graph — stale-caller analysis skipped. Run analyze_codebase to enable it.';
183
+ const collectStaleCallers = (nodeId) => {
184
+ if (!ctx?.edgeStore)
185
+ return [];
186
+ const out = new Map();
187
+ for (const e of ctx.edgeStore.getCallers(nodeId)) {
188
+ if (e.kind && e.kind !== 'calls')
189
+ continue;
190
+ const caller = ctx.edgeStore.getNode(e.callerId);
191
+ if (!caller || caller.isExternal)
192
+ continue;
193
+ if (changedPaths.has(caller.filePath))
194
+ continue; // updated in this change
195
+ out.set(caller.id, { name: caller.name, file: caller.filePath });
196
+ }
197
+ return [...out.values()].sort((a, b) => a.file.localeCompare(b.file) || a.name.localeCompare(b.name));
198
+ };
199
+ const limit = Math.max(1, Math.min(input.maxResults ?? 200, 1000));
200
+ const sigChangedOut = signatureChanged
201
+ .map(s => ({ ...nodeRef(s.node), before: s.before, after: s.after, staleCallers: collectStaleCallers(s.node.id) }))
202
+ .sort((a, b) => b.staleCallers.length - a.staleCallers.length || a.file.localeCompare(b.file));
203
+ const removedOut = removed
204
+ .map(n => ({ ...nodeRef(n), staleCallers: collectStaleCallers(n.id) }))
205
+ .sort((a, b) => b.staleCallers.length - a.staleCallers.length || a.file.localeCompare(b.file));
206
+ const totalStale = new Set([...sigChangedOut, ...removedOut].flatMap(x => x.staleCallers.map(c => `${c.file}::${c.name}`))).size;
207
+ return {
208
+ base: resolvedBase,
209
+ head: input.headRef ?? 'working tree',
210
+ changedFiles: codeChanged.map(c => ({ path: c.path, status: c.status, ...(c.oldPath ? { oldPath: c.oldPath } : {}) })),
211
+ summary: {
212
+ addedFunctions: added.length,
213
+ removedFunctions: removed.length,
214
+ signatureChanges: signatureChanged.length,
215
+ addedEdges: addedEdges.length,
216
+ removedEdges: removedEdges.length,
217
+ staleCallers: totalStale,
218
+ renameCandidates: renameCandidates.length,
219
+ },
220
+ added: added.map(nodeRef).sort(byFileName).slice(0, limit),
221
+ removed: removedOut.slice(0, limit),
222
+ signatureChanged: sigChangedOut.slice(0, limit),
223
+ renameCandidates: renameCandidates.slice(0, limit),
224
+ edges: { added: addedEdges.slice(0, limit), removed: removedEdges.slice(0, limit) },
225
+ ...(staleCallerNote ? { note: staleCallerNote } : {}),
226
+ soundness: diffSoundness(false),
227
+ };
228
+ }
229
+ // ── helpers ────────────────────────────────────────────────────────────────────
230
+ function isCode(n) { return !n.isExternal; }
231
+ function isCallEdge(e) {
232
+ return (!e.kind || e.kind === 'calls') && !!e.calleeId;
233
+ }
234
+ function byFileName(a, b) {
235
+ return a.file.localeCompare(b.file) || a.name.localeCompare(b.name);
236
+ }
237
+ function edgePair(e, g) {
238
+ const caller = g.nodes.find(n => n.id === e.callerId);
239
+ return { caller: caller?.name ?? e.callerId, callee: e.calleeName, file: caller?.filePath ?? '' };
240
+ }
241
+ async function safeBuild(files) {
242
+ if (files.length === 0)
243
+ return emptyGraph();
244
+ try {
245
+ return serializeCallGraph(await new CallGraphBuilder().build(files));
246
+ }
247
+ catch {
248
+ return emptyGraph();
249
+ }
250
+ }
251
+ function emptyGraph() {
252
+ return { nodes: [], edges: [], classes: [], inheritanceEdges: [], hubFunctions: [], entryPoints: [], layerViolations: [],
253
+ stats: { totalNodes: 0, totalEdges: 0, avgFanIn: 0, avgFanOut: 0 } };
254
+ }
255
+ function emptySummary() {
256
+ return { addedFunctions: 0, removedFunctions: 0, signatureChanges: 0, addedEdges: 0, removedEdges: 0, staleCallers: 0, renameCandidates: 0 };
257
+ }
258
+ function diffSoundness(empty) {
259
+ const caveats = [
260
+ 'Rename/move detection is heuristic — a renamed function appears as remove+add. Candidates are reported separately; never assume a rename without verifying.',
261
+ 'Signature-change detection is limited to what the analyzer extracts per language; cross-language signature notions differ.',
262
+ 'Edge deltas cover calls among/out of the changed files; calls into unchanged files resolve against the canonical graph for stale callers only.',
263
+ ];
264
+ if (empty)
265
+ caveats.push('No code files changed — the structural delta is empty.');
266
+ return { posture: 'structural-complement-to-git-diff', caveats };
267
+ }
268
+ //# sourceMappingURL=structural-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structural-diff.js","sourceRoot":"","sources":["../../../../src/core/services/mcp-handlers/structural-diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Deterministic Test Impact Selection (spec-19) — static, call-graph-based
3
+ * regression test selection (RTS) served to the agent at edit time.
4
+ *
5
+ * "I changed parseConfig() — which tests should I run?" is answered by walking the
6
+ * call graph *backward* from the change to every test that transitively reaches it.
7
+ * grep can't (the reach is through indirect calls); the model is slow and guesses;
8
+ * a deterministic graph does it instantly over edges we already store (`calls`,
9
+ * `tested_by`, inheritance).
10
+ *
11
+ * Soundness is stated honestly: this is an OVER-APPROXIMATE PRIORITIZER, not a
12
+ * sound replacement for the full suite. Direct/static dispatch is safely
13
+ * over-approximated; dynamic dispatch, reflection, and DI can under-select.
14
+ */
15
+ import type { SerializedCallGraph, FunctionNode } from '../../analyzer/call-graph.js';
16
+ export interface SelectTestsInput {
17
+ directory: string;
18
+ /** Explicit changed symbols (function/method names). */
19
+ changedSymbols?: string[];
20
+ /** Git ref to diff the working tree against (e.g. "HEAD", "main"). */
21
+ diffRef?: string;
22
+ /** Max backward-reachability depth (default 12, capped). */
23
+ maxDepth?: number;
24
+ }
25
+ /** Resolve changed symbols → seed production nodes (exact name preferred). */
26
+ export declare function seedsFromSymbols(cg: SerializedCallGraph, symbols: string[]): FunctionNode[];
27
+ /** Resolve changed files (from a diff) → seed production nodes. */
28
+ export declare function seedsFromFiles(cg: SerializedCallGraph, files: string[]): FunctionNode[];
29
+ /**
30
+ * Select the tests that transitively reach a set of changed symbols/files.
31
+ * Read-only, deterministic, offline. Returns `unknown` (additive-by-cast).
32
+ */
33
+ export declare function handleSelectTests(input: SelectTestsInput): Promise<unknown>;
34
+ //# sourceMappingURL=test-impact.d.ts.map