@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.
Files changed (72) hide show
  1. package/README.md +7 -7
  2. package/dist/cli/graphpack.d.ts +48 -0
  3. package/dist/cli/graphpack.js +217 -0
  4. package/dist/cli/index.js +72 -0
  5. package/dist/cli/tool.d.ts +1 -0
  6. package/dist/cli/tool.js +111 -2
  7. package/dist/core/graphpack/index.d.ts +14 -0
  8. package/dist/core/graphpack/index.js +474 -0
  9. package/dist/core/graphpack/types.d.ts +129 -0
  10. package/dist/core/graphpack/types.js +4 -0
  11. package/dist/core/semantic/relationships.d.ts +36 -0
  12. package/dist/core/semantic/relationships.js +261 -0
  13. package/dist/mcp/local/local-backend.js +42 -0
  14. package/dist/mcp/resources.js +125 -0
  15. package/dist/mcp/tools.js +105 -0
  16. package/dist/server/api.js +112 -0
  17. package/dist/web/assets/agent-CQNZQ-hg.js +1139 -0
  18. package/dist/web/assets/{architectureDiagram-UL44E2DR-DFSpa3Hb.js → architectureDiagram-UL44E2DR-B5_goS_i.js} +1 -1
  19. package/dist/web/assets/{blockDiagram-7IZFK4PR-DlFaxH1b.js → blockDiagram-7IZFK4PR-D7ZAlDyv.js} +1 -1
  20. package/dist/web/assets/{c4Diagram-Y2BXMSZH-BjJ_Yrim.js → c4Diagram-Y2BXMSZH-Djcgm_54.js} +1 -1
  21. package/dist/web/assets/{chunk-3SSMPTDK-KGZSzG3Y.js → chunk-3SSMPTDK-Cv2Zy2FO.js} +1 -1
  22. package/dist/web/assets/{chunk-6764PJDD-p1sGJgVm.js → chunk-6764PJDD-Cppb-jH-.js} +1 -1
  23. package/dist/web/assets/{chunk-AZZRMDJM-DIDkQA4V.js → chunk-AZZRMDJM-BHlLC7p3.js} +1 -1
  24. package/dist/web/assets/{chunk-JQRUD6KW-DAwg-yCU.js → chunk-JQRUD6KW-3F8Zg-1N.js} +1 -1
  25. package/dist/web/assets/{chunk-KRXBNO2N-ChVO_XdS.js → chunk-KRXBNO2N-C0mbN9a7.js} +1 -1
  26. package/dist/web/assets/{chunk-LCXTWHL2-DGYdb_Eh.js → chunk-LCXTWHL2-BoiuJpIF.js} +1 -1
  27. package/dist/web/assets/{chunk-LII3EMHJ-Bzh9SNgD.js → chunk-LII3EMHJ-Dqq0Qguw.js} +1 -1
  28. package/dist/web/assets/{chunk-RG4AUYOV-Bcl7U_IV.js → chunk-RG4AUYOV-Bl5F_gDs.js} +1 -1
  29. package/dist/web/assets/{chunk-T5OCTHI4-CZYMg5sc.js → chunk-T5OCTHI4-B2tIcggA.js} +1 -1
  30. package/dist/web/assets/{chunk-W44A43WB-REOI67PN.js → chunk-W44A43WB-BHe37iN7.js} +1 -1
  31. package/dist/web/assets/{chunk-ZXARS5L4-BfFdV1tf.js → chunk-ZXARS5L4-wcrIaQvY.js} +1 -1
  32. package/dist/web/assets/classDiagram-KGZ6W3CR-IbI6v_24.js +1 -0
  33. package/dist/web/assets/classDiagram-v2-72OJOZXJ-IbI6v_24.js +1 -0
  34. package/dist/web/assets/{cose-bilkent-UX7MHV2Q-D6vANJGG.js → cose-bilkent-UX7MHV2Q-BWr7v0Wr.js} +1 -1
  35. package/dist/web/assets/{dagre-ND4H6XIP-BiHe5Lal.js → dagre-ND4H6XIP-De5LIh1B.js} +1 -1
  36. package/dist/web/assets/{diagram-3NCE3AQN-CEutBCOW.js → diagram-3NCE3AQN-Dd22FSHy.js} +1 -1
  37. package/dist/web/assets/{diagram-GF46GFSD-CZns6HPQ.js → diagram-GF46GFSD-Cev3THY8.js} +1 -1
  38. package/dist/web/assets/{diagram-HNR7UZ2L-Vz8fE5of.js → diagram-HNR7UZ2L-D8Z8RQGs.js} +1 -1
  39. package/dist/web/assets/{diagram-QXG6HAR7-D60HKZ_y.js → diagram-QXG6HAR7-B8VOJOiE.js} +1 -1
  40. package/dist/web/assets/{diagram-WEQXMOUZ-vGAf1p3E.js → diagram-WEQXMOUZ-va1bLoMD.js} +1 -1
  41. package/dist/web/assets/{erDiagram-L5TCEMPS-DZaplJA6.js → erDiagram-L5TCEMPS-B3_9uAoP.js} +1 -1
  42. package/dist/web/assets/{flowDiagram-H6V6AXG4-BqUqeAsI.js → flowDiagram-H6V6AXG4-98m6maI1.js} +1 -1
  43. package/dist/web/assets/{ganttDiagram-JCBTUEKG-XEB6H-0G.js → ganttDiagram-JCBTUEKG-vE2nzETb.js} +1 -1
  44. package/dist/web/assets/{gitGraphDiagram-S2ZK5IYY-7G50u1Cd.js → gitGraphDiagram-S2ZK5IYY-DKc8uUg_.js} +1 -1
  45. package/dist/web/assets/index-BAhe1HSk.css +1 -0
  46. package/dist/web/assets/{index-B5WxtMpv.js → index-VTKdaklA.js} +230 -230
  47. package/dist/web/assets/{infoDiagram-3YFTVSEB-Cut_rzaf.js → infoDiagram-3YFTVSEB-DYP-Srzx.js} +1 -1
  48. package/dist/web/assets/{ishikawaDiagram-BNXS4ZKH-B4DGfGi3.js → ishikawaDiagram-BNXS4ZKH-QZnkpmmb.js} +1 -1
  49. package/dist/web/assets/{journeyDiagram-M6C3CM3L-BBFhsL3E.js → journeyDiagram-M6C3CM3L-B5ojIuqu.js} +1 -1
  50. package/dist/web/assets/{kanban-definition-75IXJCU3-DarGRyn3.js → kanban-definition-75IXJCU3-BJA8liRR.js} +1 -1
  51. package/dist/web/assets/{katex-K3KEBU37-W5XTYMhr.js → katex-K3KEBU37-DUqZiCRL.js} +1 -1
  52. package/dist/web/assets/{mindmap-definition-2TDM6QVE-BgeczIJM.js → mindmap-definition-2TDM6QVE-BQj5yylD.js} +1 -1
  53. package/dist/web/assets/{pieDiagram-CU6KROY3-Kkoo-Noq.js → pieDiagram-CU6KROY3-4eSrPiQz.js} +1 -1
  54. package/dist/web/assets/{quadrantDiagram-VICAPDV7-CDQFeRWN.js → quadrantDiagram-VICAPDV7-PzxN8j55.js} +1 -1
  55. package/dist/web/assets/{requirementDiagram-JXO7QTGE-Cz9-XnkA.js → requirementDiagram-JXO7QTGE-CtplTc5y.js} +1 -1
  56. package/dist/web/assets/{sankeyDiagram-URQDO5SZ-CU26z0n7.js → sankeyDiagram-URQDO5SZ-CoSgvkxv.js} +1 -1
  57. package/dist/web/assets/{sequenceDiagram-VS2MUI6T-OGK1FLOt.js → sequenceDiagram-VS2MUI6T-D7ygyXvJ.js} +1 -1
  58. package/dist/web/assets/{stateDiagram-7D4R322I-DJ9brq0U.js → stateDiagram-7D4R322I-v01gvwji.js} +1 -1
  59. package/dist/web/assets/stateDiagram-v2-36443NZ5-DFD2b8_x.js +1 -0
  60. package/dist/web/assets/{timeline-definition-O6YCAMPW-XZvnjqTT.js → timeline-definition-O6YCAMPW-CTI3M65J.js} +1 -1
  61. package/dist/web/assets/{vennDiagram-MWXL3ELB-CJUssEjA.js → vennDiagram-MWXL3ELB-RnB0XMP7.js} +1 -1
  62. package/dist/web/assets/{wardley-L42UT6IY-5TKZOOLJ-DZr11zBG.js → wardley-L42UT6IY-5TKZOOLJ-C-ZcgEBb.js} +1 -1
  63. package/dist/web/assets/{wardleyDiagram-CUQ6CDDI-C276iqrN.js → wardleyDiagram-CUQ6CDDI-EwRi4kwo.js} +1 -1
  64. package/dist/web/assets/{xychartDiagram-N2JHSOCM-B9-uCZyP.js → xychartDiagram-N2JHSOCM-DA38II6y.js} +1 -1
  65. package/dist/web/index.html +2 -2
  66. package/package.json +2 -2
  67. package/dist/web/assets/__vite-browser-external-BIHI7g3E.js +0 -1
  68. package/dist/web/assets/agent-DcdaQnmu.js +0 -1104
  69. package/dist/web/assets/classDiagram-KGZ6W3CR-B-qkKMYi.js +0 -1
  70. package/dist/web/assets/classDiagram-v2-72OJOZXJ-B-qkKMYi.js +0 -1
  71. package/dist/web/assets/index-CT0GtFLZ.css +0 -1
  72. 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.
@@ -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.',