@codragraph/cli 2.1.6 → 2.2.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/README.md +7 -7
- package/dist/cli/graphpack.d.ts +48 -0
- package/dist/cli/graphpack.js +217 -0
- package/dist/cli/index.js +72 -0
- package/dist/cli/tool.d.ts +1 -0
- package/dist/cli/tool.js +111 -2
- package/dist/core/graphpack/index.d.ts +14 -0
- package/dist/core/graphpack/index.js +474 -0
- package/dist/core/graphpack/types.d.ts +129 -0
- package/dist/core/graphpack/types.js +4 -0
- package/dist/core/semantic/relationships.d.ts +36 -0
- package/dist/core/semantic/relationships.js +261 -0
- package/dist/mcp/local/local-backend.js +42 -0
- package/dist/mcp/resources.js +125 -0
- package/dist/mcp/tools.js +105 -0
- package/dist/server/api.js +112 -0
- package/dist/web/assets/agent-CQNZQ-hg.js +1139 -0
- package/dist/web/assets/{architectureDiagram-UL44E2DR-DFSpa3Hb.js → architectureDiagram-UL44E2DR-B5_goS_i.js} +1 -1
- package/dist/web/assets/{blockDiagram-7IZFK4PR-DlFaxH1b.js → blockDiagram-7IZFK4PR-D7ZAlDyv.js} +1 -1
- package/dist/web/assets/{c4Diagram-Y2BXMSZH-BjJ_Yrim.js → c4Diagram-Y2BXMSZH-Djcgm_54.js} +1 -1
- package/dist/web/assets/{chunk-3SSMPTDK-KGZSzG3Y.js → chunk-3SSMPTDK-Cv2Zy2FO.js} +1 -1
- package/dist/web/assets/{chunk-6764PJDD-p1sGJgVm.js → chunk-6764PJDD-Cppb-jH-.js} +1 -1
- package/dist/web/assets/{chunk-AZZRMDJM-DIDkQA4V.js → chunk-AZZRMDJM-BHlLC7p3.js} +1 -1
- package/dist/web/assets/{chunk-JQRUD6KW-DAwg-yCU.js → chunk-JQRUD6KW-3F8Zg-1N.js} +1 -1
- package/dist/web/assets/{chunk-KRXBNO2N-ChVO_XdS.js → chunk-KRXBNO2N-C0mbN9a7.js} +1 -1
- package/dist/web/assets/{chunk-LCXTWHL2-DGYdb_Eh.js → chunk-LCXTWHL2-BoiuJpIF.js} +1 -1
- package/dist/web/assets/{chunk-LII3EMHJ-Bzh9SNgD.js → chunk-LII3EMHJ-Dqq0Qguw.js} +1 -1
- package/dist/web/assets/{chunk-RG4AUYOV-Bcl7U_IV.js → chunk-RG4AUYOV-Bl5F_gDs.js} +1 -1
- package/dist/web/assets/{chunk-T5OCTHI4-CZYMg5sc.js → chunk-T5OCTHI4-B2tIcggA.js} +1 -1
- package/dist/web/assets/{chunk-W44A43WB-REOI67PN.js → chunk-W44A43WB-BHe37iN7.js} +1 -1
- package/dist/web/assets/{chunk-ZXARS5L4-BfFdV1tf.js → chunk-ZXARS5L4-wcrIaQvY.js} +1 -1
- package/dist/web/assets/classDiagram-KGZ6W3CR-IbI6v_24.js +1 -0
- package/dist/web/assets/classDiagram-v2-72OJOZXJ-IbI6v_24.js +1 -0
- package/dist/web/assets/{cose-bilkent-UX7MHV2Q-D6vANJGG.js → cose-bilkent-UX7MHV2Q-BWr7v0Wr.js} +1 -1
- package/dist/web/assets/{dagre-ND4H6XIP-BiHe5Lal.js → dagre-ND4H6XIP-De5LIh1B.js} +1 -1
- package/dist/web/assets/{diagram-3NCE3AQN-CEutBCOW.js → diagram-3NCE3AQN-Dd22FSHy.js} +1 -1
- package/dist/web/assets/{diagram-GF46GFSD-CZns6HPQ.js → diagram-GF46GFSD-Cev3THY8.js} +1 -1
- package/dist/web/assets/{diagram-HNR7UZ2L-Vz8fE5of.js → diagram-HNR7UZ2L-D8Z8RQGs.js} +1 -1
- package/dist/web/assets/{diagram-QXG6HAR7-D60HKZ_y.js → diagram-QXG6HAR7-B8VOJOiE.js} +1 -1
- package/dist/web/assets/{diagram-WEQXMOUZ-vGAf1p3E.js → diagram-WEQXMOUZ-va1bLoMD.js} +1 -1
- package/dist/web/assets/{erDiagram-L5TCEMPS-DZaplJA6.js → erDiagram-L5TCEMPS-B3_9uAoP.js} +1 -1
- package/dist/web/assets/{flowDiagram-H6V6AXG4-BqUqeAsI.js → flowDiagram-H6V6AXG4-98m6maI1.js} +1 -1
- package/dist/web/assets/{ganttDiagram-JCBTUEKG-XEB6H-0G.js → ganttDiagram-JCBTUEKG-vE2nzETb.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-S2ZK5IYY-7G50u1Cd.js → gitGraphDiagram-S2ZK5IYY-DKc8uUg_.js} +1 -1
- package/dist/web/assets/index-BAhe1HSk.css +1 -0
- package/dist/web/assets/{index-B5WxtMpv.js → index-VTKdaklA.js} +230 -230
- package/dist/web/assets/{infoDiagram-3YFTVSEB-Cut_rzaf.js → infoDiagram-3YFTVSEB-DYP-Srzx.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-BNXS4ZKH-B4DGfGi3.js → ishikawaDiagram-BNXS4ZKH-QZnkpmmb.js} +1 -1
- package/dist/web/assets/{journeyDiagram-M6C3CM3L-BBFhsL3E.js → journeyDiagram-M6C3CM3L-B5ojIuqu.js} +1 -1
- package/dist/web/assets/{kanban-definition-75IXJCU3-DarGRyn3.js → kanban-definition-75IXJCU3-BJA8liRR.js} +1 -1
- package/dist/web/assets/{katex-K3KEBU37-W5XTYMhr.js → katex-K3KEBU37-DUqZiCRL.js} +1 -1
- package/dist/web/assets/{mindmap-definition-2TDM6QVE-BgeczIJM.js → mindmap-definition-2TDM6QVE-BQj5yylD.js} +1 -1
- package/dist/web/assets/{pieDiagram-CU6KROY3-Kkoo-Noq.js → pieDiagram-CU6KROY3-4eSrPiQz.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-VICAPDV7-CDQFeRWN.js → quadrantDiagram-VICAPDV7-PzxN8j55.js} +1 -1
- package/dist/web/assets/{requirementDiagram-JXO7QTGE-Cz9-XnkA.js → requirementDiagram-JXO7QTGE-CtplTc5y.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-URQDO5SZ-CU26z0n7.js → sankeyDiagram-URQDO5SZ-CoSgvkxv.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-VS2MUI6T-OGK1FLOt.js → sequenceDiagram-VS2MUI6T-D7ygyXvJ.js} +1 -1
- package/dist/web/assets/{stateDiagram-7D4R322I-DJ9brq0U.js → stateDiagram-7D4R322I-v01gvwji.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-36443NZ5-DFD2b8_x.js +1 -0
- package/dist/web/assets/{timeline-definition-O6YCAMPW-XZvnjqTT.js → timeline-definition-O6YCAMPW-CTI3M65J.js} +1 -1
- package/dist/web/assets/{vennDiagram-MWXL3ELB-CJUssEjA.js → vennDiagram-MWXL3ELB-RnB0XMP7.js} +1 -1
- package/dist/web/assets/{wardley-L42UT6IY-5TKZOOLJ-DZr11zBG.js → wardley-L42UT6IY-5TKZOOLJ-C-ZcgEBb.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-CUQ6CDDI-C276iqrN.js → wardleyDiagram-CUQ6CDDI-EwRi4kwo.js} +1 -1
- package/dist/web/assets/{xychartDiagram-N2JHSOCM-B9-uCZyP.js → xychartDiagram-N2JHSOCM-DA38II6y.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/dist/web/assets/__vite-browser-external-BIHI7g3E.js +0 -1
- package/dist/web/assets/agent-DcdaQnmu.js +0 -1104
- package/dist/web/assets/classDiagram-KGZ6W3CR-B-qkKMYi.js +0 -1
- package/dist/web/assets/classDiagram-v2-72OJOZXJ-B-qkKMYi.js +0 -1
- package/dist/web/assets/index-CT0GtFLZ.css +0 -1
- package/dist/web/assets/stateDiagram-v2-36443NZ5-DhJ4Ky-7.js +0 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { FsCAS, getJson, parseObjectId, readCommit, resolveHeadCommit, } from '@codragraph/graphstore';
|
|
5
|
+
import { GRAPHSTORE_SUBDIR } from '../graphstore/index.js';
|
|
6
|
+
export const SEMANTIC_RELATIONSHIP_VERSION = 'semantic-extractor-v1';
|
|
7
|
+
export const analyzeSemanticRelationships = async (opts) => {
|
|
8
|
+
const graphstoreRoot = path.join(opts.storagePath, GRAPHSTORE_SUBDIR);
|
|
9
|
+
const cas = new FsCAS({ root: graphstoreRoot });
|
|
10
|
+
const head = await resolveHeadCommit({ root: graphstoreRoot });
|
|
11
|
+
if (head === null) {
|
|
12
|
+
throw new Error('semantic analyze requires a graphstore HEAD. Run `codragraph analyze` first.');
|
|
13
|
+
}
|
|
14
|
+
const commit = await readCommit(cas, head);
|
|
15
|
+
const snapshot = await getJson(cas, commit.snapshot);
|
|
16
|
+
const manifest = await getJson(cas, parseObjectId(snapshot.manifestId));
|
|
17
|
+
const nodes = await loadNodes(cas, manifest);
|
|
18
|
+
const relationships = await inferRelationships({
|
|
19
|
+
cas,
|
|
20
|
+
manifest,
|
|
21
|
+
nodes,
|
|
22
|
+
limit: opts.limit ?? 1000,
|
|
23
|
+
});
|
|
24
|
+
const report = {
|
|
25
|
+
snapshotId: commit.snapshot,
|
|
26
|
+
extractorVersion: SEMANTIC_RELATIONSHIP_VERSION,
|
|
27
|
+
llmEnabled: opts.llm === true,
|
|
28
|
+
relationships,
|
|
29
|
+
summary: summarize(relationships),
|
|
30
|
+
};
|
|
31
|
+
if (opts.write) {
|
|
32
|
+
await fs.writeFile(path.join(opts.storagePath, 'semantic-relationships.json'), `${JSON.stringify(report, null, 2)}\n`, 'utf-8');
|
|
33
|
+
}
|
|
34
|
+
return report;
|
|
35
|
+
};
|
|
36
|
+
const loadNodes = async (cas, manifest) => {
|
|
37
|
+
const nodes = new Map();
|
|
38
|
+
for (const [table, tableManifest] of Object.entries(manifest.nodeTables)) {
|
|
39
|
+
for (const [id, objectId] of Object.entries(tableManifest.rows)) {
|
|
40
|
+
const row = await getJson(cas, parseObjectId(objectId));
|
|
41
|
+
nodes.set(id, {
|
|
42
|
+
id,
|
|
43
|
+
table,
|
|
44
|
+
name: stringProp(row, 'name'),
|
|
45
|
+
filePath: stringProp(row, 'filePath'),
|
|
46
|
+
startLine: numberProp(row, 'startLine'),
|
|
47
|
+
endLine: numberProp(row, 'endLine'),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return nodes;
|
|
52
|
+
};
|
|
53
|
+
const inferRelationships = async (opts) => {
|
|
54
|
+
const highSignal = [];
|
|
55
|
+
const fallback = [];
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
const edgeEntries = Object.entries(opts.manifest.edges.rows);
|
|
58
|
+
for (const [edgeKey, objectId] of edgeEntries) {
|
|
59
|
+
let edge;
|
|
60
|
+
const parsed = parseEdgeKey(edgeKey);
|
|
61
|
+
if (!parsed)
|
|
62
|
+
edge = await getJson(opts.cas, parseObjectId(objectId));
|
|
63
|
+
const from = parsed?.from ?? stringProp(edge, 'from');
|
|
64
|
+
const to = parsed?.to ?? stringProp(edge, 'to');
|
|
65
|
+
const rawType = parsed?.rawType ?? stringProp(edge, 'type');
|
|
66
|
+
if (!from || !to || !rawType)
|
|
67
|
+
continue;
|
|
68
|
+
if (!SEMANTIC_RAW_EDGE_TYPES.has(rawType))
|
|
69
|
+
continue;
|
|
70
|
+
const source = opts.nodes.get(from);
|
|
71
|
+
const target = opts.nodes.get(to);
|
|
72
|
+
const classified = classifySemanticEdge({ rawType, source, target });
|
|
73
|
+
if (!classified)
|
|
74
|
+
continue;
|
|
75
|
+
const key = `${from}|${classified.family}|${to}`;
|
|
76
|
+
if (seen.has(key))
|
|
77
|
+
continue;
|
|
78
|
+
seen.add(key);
|
|
79
|
+
const relationship = {
|
|
80
|
+
id: `semantic:${hashKey(key).slice(0, 32)}`,
|
|
81
|
+
family: classified.family,
|
|
82
|
+
sourceId: from,
|
|
83
|
+
...(source?.name ? { sourceName: source.name } : {}),
|
|
84
|
+
targetId: to,
|
|
85
|
+
...(target?.name ? { targetName: target.name } : {}),
|
|
86
|
+
confidence: classified.confidence,
|
|
87
|
+
provenance: rawType === classified.family ? 'extracted' : 'inferred',
|
|
88
|
+
extractorVersion: SEMANTIC_RELATIONSHIP_VERSION,
|
|
89
|
+
evidence: {
|
|
90
|
+
filePath: source?.filePath ?? target?.filePath,
|
|
91
|
+
startLine: source?.startLine ?? target?.startLine,
|
|
92
|
+
endLine: source?.endLine ?? target?.endLine,
|
|
93
|
+
rawEdgeType: rawType,
|
|
94
|
+
rawEdgeConfidence: edge ? numberProp(edge, 'confidence') : undefined,
|
|
95
|
+
reason: classified.reason,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
const bucket = classified.family === 'COMPOSES' ? fallback : highSignal;
|
|
99
|
+
bucket.push(relationship);
|
|
100
|
+
if (highSignal.length >= opts.limit)
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
return [
|
|
104
|
+
...highSignal.sort(compareSemanticRelationships),
|
|
105
|
+
...fallback.sort(compareSemanticRelationships),
|
|
106
|
+
].slice(0, opts.limit);
|
|
107
|
+
};
|
|
108
|
+
const SEMANTIC_RAW_EDGE_TYPES = new Set([
|
|
109
|
+
'CALLS',
|
|
110
|
+
'IMPORTS',
|
|
111
|
+
'EXTENDS',
|
|
112
|
+
'IMPLEMENTS',
|
|
113
|
+
'HAS_METHOD',
|
|
114
|
+
'HAS_PROPERTY',
|
|
115
|
+
'ACCESSES',
|
|
116
|
+
'METHOD_OVERRIDES',
|
|
117
|
+
'METHOD_IMPLEMENTS',
|
|
118
|
+
'WRAPS',
|
|
119
|
+
'FETCHES',
|
|
120
|
+
'HANDLES_ROUTE',
|
|
121
|
+
'HANDLES_TOOL',
|
|
122
|
+
'QUERIES',
|
|
123
|
+
]);
|
|
124
|
+
const parseEdgeKey = (edgeKey) => {
|
|
125
|
+
const parts = edgeKey.split('|');
|
|
126
|
+
if (parts.length < 3)
|
|
127
|
+
return null;
|
|
128
|
+
const [from, rawType, ...toParts] = parts;
|
|
129
|
+
if (!from || !rawType || toParts.length === 0)
|
|
130
|
+
return null;
|
|
131
|
+
return { from, rawType, to: toParts.join('|') };
|
|
132
|
+
};
|
|
133
|
+
const classifySemanticEdge = (input) => {
|
|
134
|
+
const sourceName = input.source?.name ?? input.source?.id ?? '';
|
|
135
|
+
const targetName = input.target?.name ?? input.target?.id ?? '';
|
|
136
|
+
const source = sourceName.toLowerCase();
|
|
137
|
+
const target = targetName.toLowerCase();
|
|
138
|
+
const rawType = input.rawType;
|
|
139
|
+
if (rawType === 'WRAPS' || hasAny(source, ['wrapper', 'middleware', 'decorator'])) {
|
|
140
|
+
return {
|
|
141
|
+
family: 'WRAPS',
|
|
142
|
+
confidence: rawType === 'WRAPS' ? 0.95 : 0.78,
|
|
143
|
+
reason: 'wrapper/decorator naming or raw WRAPS edge',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (rawType === 'IMPLEMENTS' && hasAny(source, ['adapter', 'adaptor'])) {
|
|
147
|
+
return { family: 'ADAPTS', confidence: 0.86, reason: 'adapter implementation edge' };
|
|
148
|
+
}
|
|
149
|
+
if (hasAny(source, ['adapter', 'adaptor']) && ['CALLS', 'IMPORTS'].includes(rawType)) {
|
|
150
|
+
return { family: 'ADAPTS', confidence: 0.72, reason: 'adapter symbol delegates to target' };
|
|
151
|
+
}
|
|
152
|
+
if (hasAny(source, ['proxy', 'client']) && ['CALLS', 'FETCHES', 'IMPORTS'].includes(rawType)) {
|
|
153
|
+
return {
|
|
154
|
+
family: 'PROXIES_TO',
|
|
155
|
+
confidence: 0.74,
|
|
156
|
+
reason: 'proxy/client symbol forwards to target',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (hasAny(source, ['factory', 'builder']) || /^(create|make|build)[A-Z_]/.test(sourceName)) {
|
|
160
|
+
if (['CALLS', 'HAS_METHOD', 'IMPORTS'].includes(rawType)) {
|
|
161
|
+
return {
|
|
162
|
+
family: 'FACTORY_CREATES',
|
|
163
|
+
confidence: 0.75,
|
|
164
|
+
reason: 'factory/create/build symbol reaches constructed target',
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (hasAny(source, ['config', 'configure', 'setup', 'options']) &&
|
|
169
|
+
['CALLS', 'ACCESSES', 'IMPORTS'].includes(rawType)) {
|
|
170
|
+
return {
|
|
171
|
+
family: 'CONFIGURES',
|
|
172
|
+
confidence: 0.7,
|
|
173
|
+
reason: 'configuration symbol controls target behavior',
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (hasAny(source, ['orchestrator', 'coordinator', 'workflow', 'pipeline', 'runner']) &&
|
|
177
|
+
rawType === 'CALLS') {
|
|
178
|
+
return {
|
|
179
|
+
family: 'ORCHESTRATES',
|
|
180
|
+
confidence: 0.76,
|
|
181
|
+
reason: 'orchestrator/coordinator call edge',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (hasAny(source, ['mapper', 'map', 'dto', 'transformer', 'serializer']) ||
|
|
185
|
+
hasAny(target, ['dto', 'schema', 'model'])) {
|
|
186
|
+
if (['CALLS', 'IMPORTS', 'ACCESSES'].includes(rawType)) {
|
|
187
|
+
return { family: 'MAPS_TO', confidence: 0.7, reason: 'mapper/DTO transformation signal' };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (rawType === 'HAS_PROPERTY' || rawType === 'ACCESSES') {
|
|
191
|
+
return {
|
|
192
|
+
family: 'COMPOSES',
|
|
193
|
+
confidence: 0.68,
|
|
194
|
+
reason: 'field/property ownership or access edge',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (rawType === 'CALLS' && /^(handle|run|execute|process|dispatch)/i.test(sourceName)) {
|
|
198
|
+
return {
|
|
199
|
+
family: 'DELEGATES_TO',
|
|
200
|
+
confidence: 0.66,
|
|
201
|
+
reason: 'handler/executor delegates to callee',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
};
|
|
206
|
+
const summarize = (relationships) => {
|
|
207
|
+
const summary = {
|
|
208
|
+
COMPOSES: 0,
|
|
209
|
+
ADAPTS: 0,
|
|
210
|
+
DELEGATES_TO: 0,
|
|
211
|
+
WRAPS: 0,
|
|
212
|
+
CONFIGURES: 0,
|
|
213
|
+
FACTORY_CREATES: 0,
|
|
214
|
+
ORCHESTRATES: 0,
|
|
215
|
+
PROXIES_TO: 0,
|
|
216
|
+
MAPS_TO: 0,
|
|
217
|
+
};
|
|
218
|
+
for (const rel of relationships)
|
|
219
|
+
summary[rel.family]++;
|
|
220
|
+
return summary;
|
|
221
|
+
};
|
|
222
|
+
const compareSemanticRelationships = (a, b) => {
|
|
223
|
+
const byFamily = familyPriority(a.family) - familyPriority(b.family);
|
|
224
|
+
if (byFamily !== 0)
|
|
225
|
+
return byFamily;
|
|
226
|
+
return b.confidence - a.confidence;
|
|
227
|
+
};
|
|
228
|
+
const familyPriority = (family) => {
|
|
229
|
+
switch (family) {
|
|
230
|
+
case 'ADAPTS':
|
|
231
|
+
return 0;
|
|
232
|
+
case 'WRAPS':
|
|
233
|
+
return 1;
|
|
234
|
+
case 'PROXIES_TO':
|
|
235
|
+
return 2;
|
|
236
|
+
case 'FACTORY_CREATES':
|
|
237
|
+
return 3;
|
|
238
|
+
case 'ORCHESTRATES':
|
|
239
|
+
return 4;
|
|
240
|
+
case 'CONFIGURES':
|
|
241
|
+
return 5;
|
|
242
|
+
case 'MAPS_TO':
|
|
243
|
+
return 6;
|
|
244
|
+
case 'DELEGATES_TO':
|
|
245
|
+
return 7;
|
|
246
|
+
case 'COMPOSES':
|
|
247
|
+
return 8;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const hasAny = (value, needles) => needles.some((needle) => value.includes(needle));
|
|
251
|
+
const stringProp = (row, key) => {
|
|
252
|
+
const value = row[key];
|
|
253
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
254
|
+
};
|
|
255
|
+
const numberProp = (row, key) => {
|
|
256
|
+
const value = row[key];
|
|
257
|
+
return typeof value === 'number' ? value : undefined;
|
|
258
|
+
};
|
|
259
|
+
const hashKey = (key) => {
|
|
260
|
+
return crypto.createHash('sha256').update(key).digest('hex');
|
|
261
|
+
};
|
|
@@ -768,6 +768,48 @@ export class LocalBackend {
|
|
|
768
768
|
params?.id ??
|
|
769
769
|
''), params?.repo, params?.direction ??
|
|
770
770
|
'upstream', clampNumber(params?.limit, 1, 500, 100));
|
|
771
|
+
case 'graphpack_status': {
|
|
772
|
+
const { getGraphpackStatus } = await import('../../core/graphpack/index.js');
|
|
773
|
+
return getGraphpackStatus({
|
|
774
|
+
repoPath: repo.repoPath,
|
|
775
|
+
storagePath: repo.storagePath,
|
|
776
|
+
strict: params?.strict === true,
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
case 'graphpack_publish': {
|
|
780
|
+
const { publishGraphpack } = await import('../../core/graphpack/index.js');
|
|
781
|
+
const args = params ?? {};
|
|
782
|
+
return publishGraphpack({
|
|
783
|
+
repoPath: repo.repoPath,
|
|
784
|
+
storagePath: repo.storagePath,
|
|
785
|
+
repoName: typeof args['repo'] === 'string' ? args['repo'] : repo.name,
|
|
786
|
+
analyzerVersion: 'mcp',
|
|
787
|
+
target: args['target'] === 'pr' ? 'pr' : 'main',
|
|
788
|
+
artifactDir: typeof args['artifact_dir'] === 'string' ? args['artifact_dir'] : undefined,
|
|
789
|
+
artifactUrl: typeof args['artifact_url'] === 'string' ? args['artifact_url'] : undefined,
|
|
790
|
+
baseSnapshotId: typeof args['base_snapshot_id'] === 'string' ? args['base_snapshot_id'] : undefined,
|
|
791
|
+
headSnapshotId: typeof args['head_snapshot_id'] === 'string' ? args['head_snapshot_id'] : undefined,
|
|
792
|
+
pullRequest: typeof args['pull_request'] === 'string' ? args['pull_request'] : undefined,
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
case 'graphpack_pull': {
|
|
796
|
+
const { pullGraphpack } = await import('../../core/graphpack/index.js');
|
|
797
|
+
const args = params ?? {};
|
|
798
|
+
return pullGraphpack({
|
|
799
|
+
repoPath: repo.repoPath,
|
|
800
|
+
storagePath: repo.storagePath,
|
|
801
|
+
artifactDir: typeof args['artifact_dir'] === 'string' ? args['artifact_dir'] : undefined,
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
case 'semantic_relationships': {
|
|
805
|
+
const { analyzeSemanticRelationships } = await import('../../core/semantic/relationships.js');
|
|
806
|
+
return analyzeSemanticRelationships({
|
|
807
|
+
storagePath: repo.storagePath,
|
|
808
|
+
llm: params?.llm === true,
|
|
809
|
+
limit: clampNumber(params?.limit, 1, 10000, 1000),
|
|
810
|
+
write: false,
|
|
811
|
+
});
|
|
812
|
+
}
|
|
771
813
|
case 'harness_swarm_run': {
|
|
772
814
|
// Same lazy-import dance as harness_run (see comments below) — keeps
|
|
773
815
|
// codragraph-harness optional and avoids a circular build-time dep.
|
package/dist/mcp/resources.js
CHANGED
|
@@ -96,6 +96,24 @@ export function getResourceTemplates() {
|
|
|
96
96
|
description: "Phase 4: the indexed repo's current branch + head commit, plus the snapshot it points at. Pairs with graphstore_log/diff/blame.",
|
|
97
97
|
mimeType: 'text/yaml',
|
|
98
98
|
},
|
|
99
|
+
{
|
|
100
|
+
uriTemplate: 'codragraph://repo/{name}/graphpack/status',
|
|
101
|
+
name: 'Team Graphpack Status',
|
|
102
|
+
description: 'Team graphpack provenance: canonical main graph, PR overlay, local graph, chunk health, and compatibility.',
|
|
103
|
+
mimeType: 'text/yaml',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
uriTemplate: 'codragraph://repo/{name}/graphpack/lock',
|
|
107
|
+
name: 'Team Graphpack Lock',
|
|
108
|
+
description: 'Committed .codragraph/index.lock.json content for shared team graph bootstrapping.',
|
|
109
|
+
mimeType: 'application/json',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
uriTemplate: 'codragraph://repo/{name}/semantic-relationships',
|
|
113
|
+
name: 'Semantic Relationships',
|
|
114
|
+
description: 'Developer-intent relationship families with confidence, evidence, provenance, and extractor version.',
|
|
115
|
+
mimeType: 'text/yaml',
|
|
116
|
+
},
|
|
99
117
|
{
|
|
100
118
|
uriTemplate: 'codragraph://repo/{name}/recipes',
|
|
101
119
|
name: 'Harness Recipes',
|
|
@@ -267,6 +285,12 @@ export async function readResource(uri, backend) {
|
|
|
267
285
|
return getGraphstoreBranchesResource(backend, repoName);
|
|
268
286
|
case 'graphstore/head':
|
|
269
287
|
return getGraphstoreHeadResource(backend, repoName);
|
|
288
|
+
case 'graphpack/status':
|
|
289
|
+
return getGraphpackStatusResource(backend, repoName);
|
|
290
|
+
case 'graphpack/lock':
|
|
291
|
+
return getGraphpackLockResource(backend, repoName);
|
|
292
|
+
case 'semantic-relationships':
|
|
293
|
+
return getSemanticRelationshipsResource(backend, repoName);
|
|
270
294
|
case 'recipes':
|
|
271
295
|
return getRecipesResource(backend, repoName);
|
|
272
296
|
default:
|
|
@@ -340,6 +364,8 @@ async function getContextResource(backend, repoName) {
|
|
|
340
364
|
lines.push(' - impact: Blast radius analysis (what breaks if you change a symbol)');
|
|
341
365
|
lines.push(' - detect_changes: Git-diff impact analysis (what do your changes affect)');
|
|
342
366
|
lines.push(' - rename: Multi-file coordinated rename with confidence tags');
|
|
367
|
+
lines.push(' - graphpack_status: Team graph provenance and artifact health');
|
|
368
|
+
lines.push(' - semantic_relationships: Developer-intent semantic edge families');
|
|
343
369
|
lines.push(' - cypher: Raw graph queries');
|
|
344
370
|
lines.push(' - list_repos: Discover all indexed repositories');
|
|
345
371
|
lines.push('');
|
|
@@ -350,6 +376,8 @@ async function getContextResource(backend, repoName) {
|
|
|
350
376
|
lines.push(` - codragraph://repo/${context.projectName}/clusters: All functional areas`);
|
|
351
377
|
lines.push(` - codragraph://repo/${context.projectName}/feature-clusters: Human-facing feature areas`);
|
|
352
378
|
lines.push(` - codragraph://repo/${context.projectName}/processes: All execution flows`);
|
|
379
|
+
lines.push(` - codragraph://repo/${context.projectName}/graphpack/status: Team graphpack status`);
|
|
380
|
+
lines.push(` - codragraph://repo/${context.projectName}/semantic-relationships: Developer-intent relationships`);
|
|
353
381
|
lines.push(` - codragraph://repo/${context.projectName}/cluster/{name}: Module details`);
|
|
354
382
|
lines.push(` - codragraph://repo/${context.projectName}/feature/{name}: Feature context pack`);
|
|
355
383
|
lines.push(` - codragraph://repo/${context.projectName}/process/{name}: Process trace`);
|
|
@@ -759,6 +787,100 @@ async function getGraphstoreBranchesResource(backend, repoName) {
|
|
|
759
787
|
}
|
|
760
788
|
return lines.join('\n');
|
|
761
789
|
}
|
|
790
|
+
async function getGraphpackStatusResource(backend, repoName) {
|
|
791
|
+
const repo = await backend.resolveRepo(repoName);
|
|
792
|
+
const { getGraphpackStatus } = await import('../core/graphpack/index.js');
|
|
793
|
+
try {
|
|
794
|
+
const status = await getGraphpackStatus({
|
|
795
|
+
repoPath: repo.repoPath,
|
|
796
|
+
storagePath: repo.storagePath,
|
|
797
|
+
});
|
|
798
|
+
const lines = [
|
|
799
|
+
`repo: "${repo.name}"`,
|
|
800
|
+
`source: "${status.source}"`,
|
|
801
|
+
`lockPresent: ${status.lockPresent}`,
|
|
802
|
+
`compatible: ${status.compatibility.ok}`,
|
|
803
|
+
`localGraphstore: ${status.local.graphstorePresent}`,
|
|
804
|
+
`verifiedChunks: ${status.chunks.verified}`,
|
|
805
|
+
`expectedChunks: ${status.chunks.expected}`,
|
|
806
|
+
];
|
|
807
|
+
if (status.lock?.graphpack.id)
|
|
808
|
+
lines.push(`graphpack: "${status.lock.graphpack.id}"`);
|
|
809
|
+
if (status.lock?.graphpack.graphstoreSnapshot) {
|
|
810
|
+
lines.push(`snapshot: "${status.lock.graphpack.graphstoreSnapshot}"`);
|
|
811
|
+
}
|
|
812
|
+
if (status.local.headCommit)
|
|
813
|
+
lines.push(`localHead: "${status.local.headCommit}"`);
|
|
814
|
+
if (status.compatibility.reasons.length > 0) {
|
|
815
|
+
lines.push('reasons:');
|
|
816
|
+
for (const reason of status.compatibility.reasons)
|
|
817
|
+
lines.push(` - ${JSON.stringify(reason)}`);
|
|
818
|
+
}
|
|
819
|
+
if (status.chunks.missing.length > 0) {
|
|
820
|
+
lines.push('missingChunks:');
|
|
821
|
+
for (const chunk of status.chunks.missing)
|
|
822
|
+
lines.push(` - "${chunk}"`);
|
|
823
|
+
}
|
|
824
|
+
if (status.chunks.mismatched.length > 0) {
|
|
825
|
+
lines.push('mismatchedChunks:');
|
|
826
|
+
for (const chunk of status.chunks.mismatched)
|
|
827
|
+
lines.push(` - "${chunk}"`);
|
|
828
|
+
}
|
|
829
|
+
return lines.join('\n');
|
|
830
|
+
}
|
|
831
|
+
catch (err) {
|
|
832
|
+
return `error: ${err instanceof Error ? err.message : String(err)}`;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
async function getGraphpackLockResource(backend, repoName) {
|
|
836
|
+
const repo = await backend.resolveRepo(repoName);
|
|
837
|
+
const { defaultLockPath, readGraphpackLock } = await import('../core/graphpack/index.js');
|
|
838
|
+
const lock = await readGraphpackLock(defaultLockPath(repo.repoPath));
|
|
839
|
+
if (!lock) {
|
|
840
|
+
return JSON.stringify({ error: 'No .codragraph/index.lock.json found', repo: repo.name });
|
|
841
|
+
}
|
|
842
|
+
return JSON.stringify(lock, null, 2);
|
|
843
|
+
}
|
|
844
|
+
async function getSemanticRelationshipsResource(backend, repoName) {
|
|
845
|
+
const repo = await backend.resolveRepo(repoName);
|
|
846
|
+
const { analyzeSemanticRelationships } = await import('../core/semantic/relationships.js');
|
|
847
|
+
try {
|
|
848
|
+
const report = await analyzeSemanticRelationships({
|
|
849
|
+
storagePath: repo.storagePath,
|
|
850
|
+
limit: 200,
|
|
851
|
+
write: false,
|
|
852
|
+
});
|
|
853
|
+
const lines = [
|
|
854
|
+
`repo: "${repo.name}"`,
|
|
855
|
+
`snapshot: "${report.snapshotId ?? 'unknown'}"`,
|
|
856
|
+
`extractor: "${report.extractorVersion}"`,
|
|
857
|
+
`relationships: ${report.relationships.length}`,
|
|
858
|
+
'summary:',
|
|
859
|
+
];
|
|
860
|
+
for (const [family, count] of Object.entries(report.summary)) {
|
|
861
|
+
lines.push(` ${family}: ${count}`);
|
|
862
|
+
}
|
|
863
|
+
lines.push('edges:');
|
|
864
|
+
for (const rel of report.relationships.slice(0, 50)) {
|
|
865
|
+
lines.push(` - family: "${rel.family}"`);
|
|
866
|
+
lines.push(` source: ${JSON.stringify(rel.sourceName ?? rel.sourceId)}`);
|
|
867
|
+
lines.push(` target: ${JSON.stringify(rel.targetName ?? rel.targetId)}`);
|
|
868
|
+
lines.push(` confidence: ${rel.confidence}`);
|
|
869
|
+
lines.push(` provenance: "${rel.provenance}"`);
|
|
870
|
+
lines.push(` extractor: "${rel.extractorVersion}"`);
|
|
871
|
+
if (rel.evidence.filePath)
|
|
872
|
+
lines.push(` file: "${rel.evidence.filePath}"`);
|
|
873
|
+
if (rel.evidence.startLine !== undefined)
|
|
874
|
+
lines.push(` startLine: ${rel.evidence.startLine}`);
|
|
875
|
+
lines.push(` reason: ${JSON.stringify(rel.evidence.reason)}`);
|
|
876
|
+
lines.push(` rawEdgeType: "${rel.evidence.rawEdgeType}"`);
|
|
877
|
+
}
|
|
878
|
+
return lines.join('\n');
|
|
879
|
+
}
|
|
880
|
+
catch (err) {
|
|
881
|
+
return `error: ${err instanceof Error ? err.message : String(err)}`;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
762
884
|
async function getRecipesResource(backend, repoName, taskFamily) {
|
|
763
885
|
const repo = await backend.resolveRepo(repoName);
|
|
764
886
|
let result;
|
|
@@ -794,6 +916,9 @@ async function getRecipesResource(backend, repoName, taskFamily) {
|
|
|
794
916
|
lines.push(` - id: "${r.id}"`);
|
|
795
917
|
lines.push(` taskFamily: "${r.taskFamily}"`);
|
|
796
918
|
lines.push(` snapshotId: "${r.snapshotId}"`);
|
|
919
|
+
if (r.requiredSubgraphSignature) {
|
|
920
|
+
lines.push(` requiredSubgraphSignature: "${r.requiredSubgraphSignature}"`);
|
|
921
|
+
}
|
|
797
922
|
lines.push(` accuracy: ${r.accuracy}`);
|
|
798
923
|
lines.push(` tokens: ${r.tokens}`);
|
|
799
924
|
lines.push(` latencyMs: ${r.latencyMs}`);
|
package/dist/mcp/tools.js
CHANGED
|
@@ -642,6 +642,99 @@ WHEN TO USE: After changing group.yaml or re-indexing member repos.`,
|
|
|
642
642
|
required: ['name'],
|
|
643
643
|
},
|
|
644
644
|
},
|
|
645
|
+
{
|
|
646
|
+
name: 'graphpack_status',
|
|
647
|
+
description: `Show the team graphpack state for a repo.
|
|
648
|
+
|
|
649
|
+
WHEN TO USE: before answering team-shared-context questions. Reports whether the answer should be considered canonical main graph, PR overlay, local graph, or missing/fallback.`,
|
|
650
|
+
inputSchema: {
|
|
651
|
+
type: 'object',
|
|
652
|
+
properties: {
|
|
653
|
+
repo: {
|
|
654
|
+
type: 'string',
|
|
655
|
+
description: 'Repository name or path. Omit if only one repo is indexed.',
|
|
656
|
+
},
|
|
657
|
+
strict: {
|
|
658
|
+
type: 'boolean',
|
|
659
|
+
description: 'Recompute graphstore CAS digest instead of trusting local presence.',
|
|
660
|
+
default: false,
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
required: [],
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
name: 'graphpack_publish',
|
|
668
|
+
description: `Create a thin .codragraph/index.lock.json plus graphpack manifest for GitHub-native artifact publishing.
|
|
669
|
+
|
|
670
|
+
WHEN TO USE: CI on main or PR branches after analyze. Commits the lock only; heavy graphstore chunks remain artifact storage.`,
|
|
671
|
+
inputSchema: {
|
|
672
|
+
type: 'object',
|
|
673
|
+
properties: {
|
|
674
|
+
repo: {
|
|
675
|
+
type: 'string',
|
|
676
|
+
description: 'Repository name or path. Omit if only one repo is indexed.',
|
|
677
|
+
},
|
|
678
|
+
target: {
|
|
679
|
+
type: 'string',
|
|
680
|
+
enum: ['main', 'pr'],
|
|
681
|
+
description: 'Graphpack target.',
|
|
682
|
+
default: 'main',
|
|
683
|
+
},
|
|
684
|
+
artifact_dir: { type: 'string', description: 'Local artifact staging directory.' },
|
|
685
|
+
artifact_url: { type: 'string', description: 'Remote artifact URL to record in the lock.' },
|
|
686
|
+
base_snapshot_id: { type: 'string', description: 'PR overlay base snapshot id.' },
|
|
687
|
+
head_snapshot_id: { type: 'string', description: 'PR overlay head snapshot id.' },
|
|
688
|
+
pull_request: { type: 'string', description: 'Pull request number or URL.' },
|
|
689
|
+
},
|
|
690
|
+
required: [],
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
name: 'graphpack_pull',
|
|
695
|
+
description: `Validate/pull a graphpack from a lock and report whether local materialization can proceed.
|
|
696
|
+
|
|
697
|
+
WHEN TO USE: developer bootstrap after clone/checkout. If unavailable or incompatible, the result includes a safe local analyze fallback reason.`,
|
|
698
|
+
inputSchema: {
|
|
699
|
+
type: 'object',
|
|
700
|
+
properties: {
|
|
701
|
+
repo: {
|
|
702
|
+
type: 'string',
|
|
703
|
+
description: 'Repository name or path. Omit if only one repo is indexed.',
|
|
704
|
+
},
|
|
705
|
+
artifact_dir: { type: 'string', description: 'Local graphpack artifact directory.' },
|
|
706
|
+
},
|
|
707
|
+
required: [],
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
name: 'semantic_relationships',
|
|
712
|
+
description: `Return developer-intent semantic relationships above raw graph edges.
|
|
713
|
+
|
|
714
|
+
Families include COMPOSES, ADAPTS, DELEGATES_TO, WRAPS, CONFIGURES, FACTORY_CREATES, ORCHESTRATES, PROXIES_TO, and MAPS_TO. Every edge includes evidence, confidence, extractor version, and provenance.`,
|
|
715
|
+
inputSchema: {
|
|
716
|
+
type: 'object',
|
|
717
|
+
properties: {
|
|
718
|
+
repo: {
|
|
719
|
+
type: 'string',
|
|
720
|
+
description: 'Repository name or path. Omit if only one repo is indexed.',
|
|
721
|
+
},
|
|
722
|
+
limit: {
|
|
723
|
+
type: 'number',
|
|
724
|
+
description: 'Maximum relationships to return.',
|
|
725
|
+
default: 1000,
|
|
726
|
+
minimum: 1,
|
|
727
|
+
maximum: 10000,
|
|
728
|
+
},
|
|
729
|
+
llm: {
|
|
730
|
+
type: 'boolean',
|
|
731
|
+
description: 'Allow optional LLM-inferred edges when provider support is configured.',
|
|
732
|
+
default: false,
|
|
733
|
+
},
|
|
734
|
+
},
|
|
735
|
+
required: [],
|
|
736
|
+
},
|
|
737
|
+
},
|
|
645
738
|
{
|
|
646
739
|
name: 'harness_swarm_run',
|
|
647
740
|
description: `Phase 3 swarm: Explorer + Exploiter (subprocess) + Critic (in-process), with hybrid termination.
|
|
@@ -727,6 +820,10 @@ Returns the Pareto frontier plus per-role attribution stats (which role contribu
|
|
|
727
820
|
type: 'string',
|
|
728
821
|
description: 'Phase 4 moat: codragraph-graphstore snapshot id (sha256:...). Required to enable recipe caching.',
|
|
729
822
|
},
|
|
823
|
+
required_subgraph_signature: {
|
|
824
|
+
type: 'string',
|
|
825
|
+
description: 'Phase 4 moat: stable signature of the task-required subgraph. Exact cache reuse keys on (snapshot_id, task_family, required_subgraph_signature).',
|
|
826
|
+
},
|
|
730
827
|
use_cache: {
|
|
731
828
|
type: 'boolean',
|
|
732
829
|
description: 'Phase 4 moat: when true and an exact (snapshot_id, task_family) recipe exists, return it instead of running the swarm.',
|
|
@@ -757,6 +854,10 @@ This is the Phase 4 × Phase 3 moat — versioned recipe memory. Recipes auto-in
|
|
|
757
854
|
properties: {
|
|
758
855
|
task_family: { type: 'string', description: 'Filter by task family.' },
|
|
759
856
|
snapshot_id: { type: 'string', description: 'Filter by snapshot id (sha256:...).' },
|
|
857
|
+
required_subgraph_signature: {
|
|
858
|
+
type: 'string',
|
|
859
|
+
description: 'Filter by required subgraph signature.',
|
|
860
|
+
},
|
|
760
861
|
recipe_store: {
|
|
761
862
|
type: 'string',
|
|
762
863
|
description: 'Recipe store root. Defaults to <cwd>/.codragraph/recipes.',
|
|
@@ -780,6 +881,10 @@ WHEN TO USE: before kicking off a swarm. If exact matches exist, you can skip th
|
|
|
780
881
|
properties: {
|
|
781
882
|
task_family: { type: 'string', description: 'Task family identifier.' },
|
|
782
883
|
snapshot_id: { type: 'string', description: 'Current codragraph-graphstore snapshot id.' },
|
|
884
|
+
required_subgraph_signature: {
|
|
885
|
+
type: 'string',
|
|
886
|
+
description: 'Stable signature of the subgraph this task needs. Exact reuse requires matching snapshot, family, and signature.',
|
|
887
|
+
},
|
|
783
888
|
recipe_store: {
|
|
784
889
|
type: 'string',
|
|
785
890
|
description: 'Recipe store root. Defaults to <cwd>/.codragraph/recipes.',
|