@decantr/core 2.1.0 → 3.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,1356 @@
1
+ // src/graph.ts
2
+ var GRAPH_SCHEMA_VERSION = "3.0.0-draft";
3
+ var GRAPH_COMMON_SCHEMA_URL = "https://decantr.ai/schemas/graph.common.v1.json";
4
+ var GRAPH_SNAPSHOT_SCHEMA_URL = "https://decantr.ai/schemas/graph-snapshot.v1.json";
5
+ var GRAPH_MANIFEST_SCHEMA_URL = "https://decantr.ai/schemas/graph-manifest.v1.json";
6
+ var GRAPH_DIFF_SCHEMA_URL = "https://decantr.ai/schemas/graph-diff.v1.json";
7
+ var CONTRACT_CAPSULE_SCHEMA_URL = "https://decantr.ai/schemas/contract-capsule.v1.json";
8
+ var DEFAULT_CONTRACT_CAPSULE_SOURCE_ARTIFACT_LIMIT = 200;
9
+ var GRAPH_NODE_TYPES = [
10
+ "Project",
11
+ "Section",
12
+ "Page",
13
+ "Route",
14
+ "Shell",
15
+ "Region",
16
+ "Pattern",
17
+ "Component",
18
+ "Token",
19
+ "Theme",
20
+ "Decorator",
21
+ "Feature",
22
+ "LocalRule",
23
+ "StyleBridge",
24
+ "SourceArtifact",
25
+ "Finding",
26
+ "Evidence",
27
+ "Repair",
28
+ "Test",
29
+ "AgentRun"
30
+ ];
31
+ var GRAPH_RELATIONS = [
32
+ "PROJECT_CONTAINS_SECTION",
33
+ "PROJECT_ENABLES_FEATURE",
34
+ "PROJECT_USES_THEME",
35
+ "SECTION_CONTAINS_PAGE",
36
+ "PAGE_ROUTED_AT_ROUTE",
37
+ "PAGE_USES_SHELL",
38
+ "SHELL_HAS_REGION",
39
+ "PAGE_COMPOSES_PATTERN",
40
+ "PATTERN_NEEDS_COMPONENT",
41
+ "COMPONENT_STYLED_WITH_TOKEN",
42
+ "THEME_DEFINES_TOKEN",
43
+ "THEME_DEFINES_DECORATOR",
44
+ "COMPONENT_DECORATED_WITH_DECORATOR",
45
+ "LOCAL_RULE_APPLIES_TO",
46
+ "STYLE_BRIDGE_MAPS_TO",
47
+ "NODE_DERIVED_FROM_SOURCE",
48
+ "SOURCE_IMPORTS_SOURCE",
49
+ "FINDING_VIOLATES_RULE",
50
+ "FINDING_ANCHORED_AT",
51
+ "EVIDENCE_SUPPORTS_FINDING",
52
+ "EVIDENCE_CAPTURED_FOR",
53
+ "REPAIR_FIXES_FINDING",
54
+ "TEST_COVERS_NODE",
55
+ "AGENT_RUN_CHANGED_NODE"
56
+ ];
57
+ function graphEdgeKey(edge) {
58
+ return [edge.src, edge.relation, edge.dst, String(edge.idx ?? "")].join("\0");
59
+ }
60
+ function graphNodeIdsByType(nodes, type) {
61
+ return nodes.filter((node) => node.type === type).map((node) => node.id).sort();
62
+ }
63
+ function stableJson(value) {
64
+ if (Array.isArray(value)) {
65
+ return `[${value.map((item) => stableJson(item)).join(",")}]`;
66
+ }
67
+ if (value && typeof value === "object") {
68
+ const record = value;
69
+ return `{${Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
70
+ }
71
+ return JSON.stringify(value);
72
+ }
73
+ function hashStableJson(value) {
74
+ const input = stableJson(value);
75
+ let hash = 2166136261;
76
+ for (let index = 0; index < input.length; index += 1) {
77
+ hash ^= input.charCodeAt(index);
78
+ hash = Math.imul(hash, 16777619) >>> 0;
79
+ }
80
+ return `fnv1a32:${hash.toString(16).padStart(8, "0")}`;
81
+ }
82
+ function relationMatches(edge, query) {
83
+ if (query.relations?.length) {
84
+ return query.relations.includes(edge.relation);
85
+ }
86
+ if ("relation" in query && query.relation) {
87
+ return edge.relation === query.relation;
88
+ }
89
+ return true;
90
+ }
91
+ function sortGraphNodes(nodes) {
92
+ return [...nodes].sort((a, b) => a.id.localeCompare(b.id) || a.type.localeCompare(b.type));
93
+ }
94
+ function sortGraphEdges(edges) {
95
+ return [...edges].sort(
96
+ (a, b) => a.src.localeCompare(b.src) || a.relation.localeCompare(b.relation) || a.dst.localeCompare(b.dst) || (a.idx ?? 0) - (b.idx ?? 0)
97
+ );
98
+ }
99
+ function normalizeGraphSnapshot(snapshot) {
100
+ const nodes = sortGraphNodes(snapshot.nodes);
101
+ const edges = sortGraphEdges(snapshot.edges);
102
+ return {
103
+ ...snapshot,
104
+ nodes,
105
+ edges,
106
+ summary: {
107
+ ...snapshot.summary,
108
+ nodes: nodes.length,
109
+ edges: edges.length,
110
+ findings: nodes.filter((node) => node.type === "Finding").length,
111
+ evidence: nodes.filter((node) => node.type === "Evidence").length
112
+ }
113
+ };
114
+ }
115
+ function diffGraphSnapshots(from, to) {
116
+ const normalizedTo = normalizeGraphSnapshot(to);
117
+ const normalizedFrom = from ? normalizeGraphSnapshot(from) : null;
118
+ const ops = [];
119
+ const fromNodes = new Map((normalizedFrom?.nodes ?? []).map((node) => [node.id, node]));
120
+ const toNodes = new Map(normalizedTo.nodes.map((node) => [node.id, node]));
121
+ for (const [id, node] of toNodes) {
122
+ const previous = fromNodes.get(id);
123
+ if (!previous) {
124
+ const op = node.type === "Finding" ? "finding.added" : node.type === "Evidence" ? "evidence.added" : "node.added";
125
+ ops.push({ op, id, type: node.type, after: node });
126
+ continue;
127
+ }
128
+ if (stableJson(previous) !== stableJson(node)) {
129
+ ops.push({ op: "node.changed", id, type: node.type, before: previous, after: node });
130
+ }
131
+ }
132
+ for (const [id, node] of fromNodes) {
133
+ if (!toNodes.has(id)) {
134
+ ops.push({
135
+ op: node.type === "Finding" ? "finding.resolved" : "node.removed",
136
+ id,
137
+ type: node.type,
138
+ before: node
139
+ });
140
+ }
141
+ }
142
+ const fromEdges = new Map(
143
+ (normalizedFrom?.edges ?? []).map((edge) => [graphEdgeKey(edge), edge])
144
+ );
145
+ const toEdges = new Map(normalizedTo.edges.map((edge) => [graphEdgeKey(edge), edge]));
146
+ for (const [key, edge] of toEdges) {
147
+ const previous = fromEdges.get(key);
148
+ if (!previous) {
149
+ ops.push({
150
+ op: "edge.added",
151
+ src: edge.src,
152
+ dst: edge.dst,
153
+ relation: edge.relation,
154
+ after: edge
155
+ });
156
+ continue;
157
+ }
158
+ if (stableJson(previous) !== stableJson(edge)) {
159
+ ops.push({
160
+ op: "edge.changed",
161
+ src: edge.src,
162
+ dst: edge.dst,
163
+ relation: edge.relation,
164
+ before: previous,
165
+ after: edge
166
+ });
167
+ }
168
+ }
169
+ for (const [key, edge] of fromEdges) {
170
+ if (!toEdges.has(key)) {
171
+ ops.push({
172
+ op: "edge.removed",
173
+ src: edge.src,
174
+ dst: edge.dst,
175
+ relation: edge.relation,
176
+ before: edge
177
+ });
178
+ }
179
+ }
180
+ return {
181
+ $schema: GRAPH_DIFF_SCHEMA_URL,
182
+ id: `diff:${normalizedFrom?.id ?? "empty"}:${normalizedTo.id}`,
183
+ from: normalizedFrom?.id,
184
+ to: normalizedTo.id,
185
+ ops
186
+ };
187
+ }
188
+ function summarizeGraphDiff(diff) {
189
+ const byType = Object.fromEntries(
190
+ [
191
+ "node.added",
192
+ "node.removed",
193
+ "node.changed",
194
+ "edge.added",
195
+ "edge.removed",
196
+ "edge.changed",
197
+ "finding.added",
198
+ "finding.resolved",
199
+ "evidence.added"
200
+ ].map((op) => [op, 0])
201
+ );
202
+ for (const op of diff?.ops ?? []) {
203
+ byType[op.op] = (byType[op.op] ?? 0) + 1;
204
+ }
205
+ return {
206
+ total: diff?.ops.length ?? 0,
207
+ by_type: byType,
208
+ nodes: {
209
+ added: byType["node.added"],
210
+ removed: byType["node.removed"],
211
+ changed: byType["node.changed"]
212
+ },
213
+ edges: {
214
+ added: byType["edge.added"],
215
+ removed: byType["edge.removed"],
216
+ changed: byType["edge.changed"]
217
+ },
218
+ findings: {
219
+ added: byType["finding.added"],
220
+ resolved: byType["finding.resolved"]
221
+ },
222
+ evidence: {
223
+ added: byType["evidence.added"]
224
+ }
225
+ };
226
+ }
227
+ function graphPayloadRecord(payload) {
228
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
229
+ }
230
+ function graphPayloadString(payload, key) {
231
+ const value = graphPayloadRecord(payload)[key];
232
+ return typeof value === "string" ? value : void 0;
233
+ }
234
+ function graphPayloadValueAtPath(payload, path) {
235
+ const parts = path.split(".").map((part) => part.trim()).filter(Boolean);
236
+ let current = payload;
237
+ for (const part of parts) {
238
+ if (!current || typeof current !== "object" || Array.isArray(current)) return void 0;
239
+ current = current[part];
240
+ }
241
+ return current;
242
+ }
243
+ function graphNodePayloadMatches(node, query) {
244
+ if (query.payloadKey) {
245
+ const value = graphPayloadValueAtPath(node.payload, query.payloadKey);
246
+ if (value === void 0) return false;
247
+ if (query.payloadValue !== void 0 && String(value) !== query.payloadValue) return false;
248
+ }
249
+ if (query.payloadContains) {
250
+ return JSON.stringify(node.payload).toLowerCase().includes(query.payloadContains.toLowerCase());
251
+ }
252
+ return true;
253
+ }
254
+ function nodeLabel(node) {
255
+ return graphPayloadString(node.payload, "label") ?? graphPayloadString(node.payload, "name") ?? graphPayloadString(node.payload, "id") ?? graphPayloadString(node.payload, "path");
256
+ }
257
+ function taskKeywords(text) {
258
+ if (!text) return [];
259
+ const stopWords = /* @__PURE__ */ new Set([
260
+ "a",
261
+ "an",
262
+ "and",
263
+ "are",
264
+ "as",
265
+ "at",
266
+ "be",
267
+ "by",
268
+ "for",
269
+ "from",
270
+ "in",
271
+ "into",
272
+ "is",
273
+ "it",
274
+ "of",
275
+ "on",
276
+ "or",
277
+ "page",
278
+ "route",
279
+ "section",
280
+ "that",
281
+ "the",
282
+ "this",
283
+ "to",
284
+ "with"
285
+ ]);
286
+ return [
287
+ ...new Set(
288
+ text.toLowerCase().split(/[^a-z0-9]+/g).map((term) => term.trim()).filter((term) => term.length >= 3 && !stopWords.has(term))
289
+ )
290
+ ].slice(0, 12);
291
+ }
292
+ function graphNodeSearchText(node) {
293
+ return [node.id, node.type, nodeLabel(node), JSON.stringify(node.payload ?? {})].filter(Boolean).join(" ").toLowerCase();
294
+ }
295
+ function vocabularyItems(snapshot, type) {
296
+ return sortGraphNodes(snapshot.nodes.filter((node) => node.type === type)).map((node) => ({
297
+ id: node.id,
298
+ label: nodeLabel(node),
299
+ payload: node.payload
300
+ }));
301
+ }
302
+ function contractCapsuleSourceArtifacts(snapshot) {
303
+ return sortGraphNodes(snapshot.nodes.filter((node) => node.type === "SourceArtifact")).map((node) => ({
304
+ id: node.id,
305
+ path: graphPayloadString(node.payload, "path") ?? node.id.replace(/^src:/, ""),
306
+ kind: graphPayloadString(node.payload, "kind"),
307
+ label: nodeLabel(node),
308
+ payload: node.payload
309
+ })).sort((a, b) => a.path.localeCompare(b.path) || a.id.localeCompare(b.id));
310
+ }
311
+ function contractCapsuleSourceArtifactLimit(limit) {
312
+ if (limit === void 0) return DEFAULT_CONTRACT_CAPSULE_SOURCE_ARTIFACT_LIMIT;
313
+ if (!Number.isFinite(limit)) return DEFAULT_CONTRACT_CAPSULE_SOURCE_ARTIFACT_LIMIT;
314
+ return Math.max(0, Math.floor(limit));
315
+ }
316
+ function isContractCapsuleNode(node) {
317
+ return !["Finding", "Evidence", "Repair", "Test", "AgentRun", "SourceArtifact"].includes(
318
+ node.type
319
+ );
320
+ }
321
+ function isContractCapsuleEdge(edge, nodeIds) {
322
+ if (!nodeIds.has(edge.src) || !nodeIds.has(edge.dst)) return false;
323
+ return ![
324
+ "FINDING_VIOLATES_RULE",
325
+ "FINDING_ANCHORED_AT",
326
+ "EVIDENCE_SUPPORTS_FINDING",
327
+ "EVIDENCE_CAPTURED_FOR",
328
+ "REPAIR_FIXES_FINDING",
329
+ "TEST_COVERS_NODE",
330
+ "AGENT_RUN_CHANGED_NODE"
331
+ ].includes(edge.relation);
332
+ }
333
+ function graphContractHash(snapshot) {
334
+ const normalized = normalizeGraphSnapshot(snapshot);
335
+ const nodes = sortGraphNodes(normalized.nodes.filter(isContractCapsuleNode));
336
+ const nodeIds = new Set(nodes.map((node) => node.id));
337
+ const edges = sortGraphEdges(
338
+ normalized.edges.filter((edge) => isContractCapsuleEdge(edge, nodeIds))
339
+ );
340
+ return hashStableJson({
341
+ schema_version: normalized.schema_version,
342
+ project_id: normalized.project_id,
343
+ nodes,
344
+ edges
345
+ });
346
+ }
347
+ function buildContractCapsuleFromSnapshot(snapshot, options = {}) {
348
+ const normalized = normalizeGraphSnapshot(snapshot);
349
+ const pageForRoute = /* @__PURE__ */ new Map();
350
+ const shellForPage = /* @__PURE__ */ new Map();
351
+ for (const edge of normalized.edges) {
352
+ if (edge.relation === "PAGE_ROUTED_AT_ROUTE") {
353
+ pageForRoute.set(edge.dst, edge.src);
354
+ }
355
+ if (edge.relation === "PAGE_USES_SHELL") {
356
+ shellForPage.set(edge.src, edge.dst);
357
+ }
358
+ }
359
+ const routes = sortGraphNodes(normalized.nodes.filter((node) => node.type === "Route")).map(
360
+ (node) => {
361
+ const pageId = pageForRoute.get(node.id);
362
+ return {
363
+ id: node.id,
364
+ path: graphPayloadString(node.payload, "path") ?? node.id.replace(/^rt:/, ""),
365
+ page_id: pageId,
366
+ shell_id: pageId ? shellForPage.get(pageId) : void 0
367
+ };
368
+ }
369
+ );
370
+ const openFindings = sortGraphNodes(
371
+ normalized.nodes.filter((node) => node.type === "Finding")
372
+ ).map((node) => ({
373
+ id: node.id,
374
+ code: graphPayloadString(node.payload, "code"),
375
+ severity: graphPayloadString(node.payload, "severity"),
376
+ anchored_at: graphPayloadString(node.payload, "anchored_at"),
377
+ message: graphPayloadString(node.payload, "message")
378
+ }));
379
+ const components = vocabularyItems(normalized, "Component");
380
+ const tokens = vocabularyItems(normalized, "Token");
381
+ const localRules = vocabularyItems(normalized, "LocalRule");
382
+ const styleBridge = vocabularyItems(normalized, "StyleBridge");
383
+ const allSourceArtifacts = contractCapsuleSourceArtifacts(normalized);
384
+ const sourceArtifactLimit = contractCapsuleSourceArtifactLimit(options.sourceArtifactLimit);
385
+ const sourceArtifacts = allSourceArtifacts.slice(0, sourceArtifactLimit);
386
+ const contractHash = graphContractHash(normalized);
387
+ const contractCacheKey = `decantr-contract:${contractHash}`;
388
+ return {
389
+ $schema: CONTRACT_CAPSULE_SCHEMA_URL,
390
+ schema_version: normalized.schema_version,
391
+ snapshot_id: normalized.id,
392
+ project_id: normalized.project_id,
393
+ created_at: options.createdAt ?? normalized.created_at,
394
+ source_hash: normalized.source_hash,
395
+ contract_hash: contractHash,
396
+ cache_key: options.cacheKey ?? contractCacheKey,
397
+ contract_cache_key: contractCacheKey,
398
+ summary: {
399
+ routes: routes.length,
400
+ components: components.length,
401
+ tokens: tokens.length,
402
+ local_rules: localRules.length,
403
+ style_bridge: styleBridge.length,
404
+ source_artifacts: allSourceArtifacts.length,
405
+ open_findings: openFindings.length
406
+ },
407
+ source_artifact_limit: sourceArtifactLimit,
408
+ source_artifacts_truncated: allSourceArtifacts.length > sourceArtifacts.length,
409
+ routes,
410
+ components,
411
+ tokens,
412
+ local_rules: localRules,
413
+ style_bridge: styleBridge,
414
+ source_artifacts: sourceArtifacts,
415
+ open_findings: openFindings
416
+ };
417
+ }
418
+ function rankGraphRouteContextNodes(nodes, routeNodeId, ids, keywords = []) {
419
+ const scoreForNode = (node) => {
420
+ if (node.id === routeNodeId) return { score: 1, reason: "requested_route" };
421
+ if (ids.pages.includes(node.id)) return { score: 0.95, reason: "route_page" };
422
+ if (ids.shells.includes(node.id)) return { score: 0.86, reason: "route_shell" };
423
+ if (ids.patterns.includes(node.id)) return { score: 0.8, reason: "page_pattern" };
424
+ if (ids.components.includes(node.id)) return { score: 0.72, reason: "pattern_component" };
425
+ if (ids.tokens.includes(node.id)) return { score: 0.66, reason: "route_style_token" };
426
+ if (ids.localRules.includes(node.id)) return { score: 0.68, reason: "applicable_local_rule" };
427
+ if (ids.styleBridge.includes(node.id)) return { score: 0.64, reason: "style_bridge_mapping" };
428
+ if (ids.openFindings.includes(node.id)) return { score: 0.62, reason: "open_finding" };
429
+ if (ids.evidence.includes(node.id)) return { score: 0.56, reason: "supporting_evidence" };
430
+ if (ids.sourceArtifacts.includes(node.id)) return { score: 0.32, reason: "source_provenance" };
431
+ return { score: 0.24, reason: "included_context" };
432
+ };
433
+ return nodes.map((node) => {
434
+ const ranked = scoreForNode(node);
435
+ const searchText = keywords.length > 0 ? graphNodeSearchText(node) : "";
436
+ const matchedTerms = keywords.filter((keyword) => searchText.includes(keyword));
437
+ const taskBoost = Math.min(0.12, matchedTerms.length * 0.035);
438
+ return {
439
+ id: node.id,
440
+ type: node.type,
441
+ score: Number(Math.min(1, ranked.score + taskBoost).toFixed(3)),
442
+ reason: matchedTerms.length > 0 ? `${ranked.reason}+task_match` : ranked.reason,
443
+ ...matchedTerms.length > 0 ? { matched_terms: matchedTerms } : {}
444
+ };
445
+ }).sort(
446
+ (a, b) => b.score - a.score || (b.matched_terms?.length ?? 0) - (a.matched_terms?.length ?? 0) || a.id.localeCompare(b.id)
447
+ );
448
+ }
449
+ function buildGraphRouteContext(snapshot, route, options = {}) {
450
+ if (!snapshot) return null;
451
+ const routeNode = snapshot.nodes.find(
452
+ (node) => node.type === "Route" && (graphPayloadString(node.payload, "path") === route || node.id === `rt:${route}`)
453
+ );
454
+ if (!routeNode) return null;
455
+ const includeNodeIds = /* @__PURE__ */ new Set([routeNode.id]);
456
+ const includeEdges = /* @__PURE__ */ new Map();
457
+ const includeEdge = (edge) => {
458
+ includeEdges.set(graphEdgeKey(edge), edge);
459
+ includeNodeIds.add(edge.src);
460
+ includeNodeIds.add(edge.dst);
461
+ };
462
+ for (const edge of snapshot.edges) {
463
+ if (edge.relation === "PAGE_ROUTED_AT_ROUTE" && edge.dst === routeNode.id) {
464
+ includeEdge(edge);
465
+ }
466
+ }
467
+ const pageIds = new Set(
468
+ [...includeEdges.values()].filter((edge) => edge.relation === "PAGE_ROUTED_AT_ROUTE").map((edge) => edge.src)
469
+ );
470
+ for (const edge of snapshot.edges) {
471
+ if (pageIds.has(edge.src) && ["PAGE_USES_SHELL", "PAGE_COMPOSES_PATTERN", "NODE_DERIVED_FROM_SOURCE"].includes(
472
+ edge.relation
473
+ )) {
474
+ includeEdge(edge);
475
+ }
476
+ }
477
+ const patternIds2 = new Set(
478
+ [...includeEdges.values()].filter((edge) => edge.relation === "PAGE_COMPOSES_PATTERN").map((edge) => edge.dst)
479
+ );
480
+ for (const edge of snapshot.edges) {
481
+ if (patternIds2.has(edge.src) && ["PATTERN_NEEDS_COMPONENT", "NODE_DERIVED_FROM_SOURCE"].includes(edge.relation)) {
482
+ includeEdge(edge);
483
+ }
484
+ }
485
+ for (const node of snapshot.nodes) {
486
+ if (node.type === "LocalRule" || node.type === "StyleBridge") {
487
+ includeNodeIds.add(node.id);
488
+ }
489
+ }
490
+ for (const edge of snapshot.edges) {
491
+ if (includeNodeIds.has(edge.src) && ["LOCAL_RULE_APPLIES_TO", "STYLE_BRIDGE_MAPS_TO", "NODE_DERIVED_FROM_SOURCE"].includes(
492
+ edge.relation
493
+ )) {
494
+ includeEdge(edge);
495
+ }
496
+ }
497
+ for (const edge of snapshot.edges) {
498
+ if (edge.relation === "FINDING_ANCHORED_AT" && includeNodeIds.has(edge.dst)) {
499
+ includeEdge(edge);
500
+ }
501
+ }
502
+ const findingIds = new Set(
503
+ [...includeEdges.values()].filter((edge) => edge.relation === "FINDING_ANCHORED_AT").map((edge) => edge.src)
504
+ );
505
+ for (const edge of snapshot.edges) {
506
+ if (findingIds.has(edge.src) || findingIds.has(edge.dst) && ["FINDING_VIOLATES_RULE", "EVIDENCE_SUPPORTS_FINDING", "REPAIR_FIXES_FINDING"].includes(
507
+ edge.relation
508
+ )) {
509
+ includeEdge(edge);
510
+ }
511
+ }
512
+ for (const edge of snapshot.edges) {
513
+ if (edge.relation === "EVIDENCE_CAPTURED_FOR" && includeNodeIds.has(edge.dst)) {
514
+ includeEdge(edge);
515
+ }
516
+ }
517
+ for (const edge of snapshot.edges) {
518
+ if (includeNodeIds.has(edge.src) && edge.relation === "NODE_DERIVED_FROM_SOURCE") {
519
+ includeEdge(edge);
520
+ }
521
+ }
522
+ const sourceArtifactIds = [...includeNodeIds].filter((nodeId) => {
523
+ const node = snapshot.nodes.find((candidate) => candidate.id === nodeId);
524
+ if (node?.type !== "SourceArtifact") return false;
525
+ const kind = graphPayloadString(node.payload, "kind");
526
+ return kind === "route-source" || kind === "component-source";
527
+ });
528
+ for (const edge of snapshot.edges) {
529
+ if (sourceArtifactIds.includes(edge.dst) && edge.relation === "NODE_DERIVED_FROM_SOURCE" && snapshot.nodes.find((node) => node.id === edge.src)?.type === "Component") {
530
+ includeEdge(edge);
531
+ }
532
+ }
533
+ for (const edge of snapshot.edges) {
534
+ if (edge.relation === "SOURCE_IMPORTS_SOURCE" && (sourceArtifactIds.includes(edge.src) || sourceArtifactIds.includes(edge.dst))) {
535
+ includeEdge(edge);
536
+ }
537
+ }
538
+ const nodes = sortGraphNodes(snapshot.nodes.filter((node) => includeNodeIds.has(node.id)));
539
+ const edges = sortGraphEdges([...includeEdges.values()]);
540
+ const ids = {
541
+ pages: graphNodeIdsByType(nodes, "Page"),
542
+ shells: graphNodeIdsByType(nodes, "Shell"),
543
+ patterns: graphNodeIdsByType(nodes, "Pattern"),
544
+ components: graphNodeIdsByType(nodes, "Component"),
545
+ tokens: graphNodeIdsByType(nodes, "Token"),
546
+ localRules: graphNodeIdsByType(nodes, "LocalRule"),
547
+ styleBridge: graphNodeIdsByType(nodes, "StyleBridge"),
548
+ openFindings: graphNodeIdsByType(nodes, "Finding"),
549
+ evidence: graphNodeIdsByType(nodes, "Evidence"),
550
+ sourceArtifacts: graphNodeIdsByType(nodes, "SourceArtifact")
551
+ };
552
+ const keywords = taskKeywords(options.task);
553
+ return {
554
+ snapshotId: snapshot.id,
555
+ sourceHash: snapshot.source_hash,
556
+ routeNode,
557
+ ranking: {
558
+ method: keywords.length > 0 ? "weighted_traversal_with_task_boost" : "weighted_traversal",
559
+ seed: routeNode.id,
560
+ task_keywords: keywords
561
+ },
562
+ summary: {
563
+ nodes: nodes.length,
564
+ edges: edges.length,
565
+ pages: graphNodeIdsByType(nodes, "Page").length,
566
+ shells: graphNodeIdsByType(nodes, "Shell").length,
567
+ patterns: graphNodeIdsByType(nodes, "Pattern").length,
568
+ components: graphNodeIdsByType(nodes, "Component").length,
569
+ tokens: graphNodeIdsByType(nodes, "Token").length,
570
+ localRules: graphNodeIdsByType(nodes, "LocalRule").length,
571
+ styleBridge: graphNodeIdsByType(nodes, "StyleBridge").length,
572
+ openFindings: graphNodeIdsByType(nodes, "Finding").length,
573
+ evidence: graphNodeIdsByType(nodes, "Evidence").length,
574
+ sourceArtifacts: graphNodeIdsByType(nodes, "SourceArtifact").length
575
+ },
576
+ ids,
577
+ ranked: rankGraphRouteContextNodes(nodes, routeNode.id, ids, keywords),
578
+ nodes,
579
+ edges
580
+ };
581
+ }
582
+ var IMPACT_TRAVERSAL_RELATIONS = /* @__PURE__ */ new Set([
583
+ "SECTION_CONTAINS_PAGE",
584
+ "PAGE_ROUTED_AT_ROUTE",
585
+ "PAGE_USES_SHELL",
586
+ "PAGE_COMPOSES_PATTERN",
587
+ "PATTERN_NEEDS_COMPONENT",
588
+ "COMPONENT_STYLED_WITH_TOKEN",
589
+ "THEME_DEFINES_TOKEN",
590
+ "THEME_DEFINES_DECORATOR",
591
+ "COMPONENT_DECORATED_WITH_DECORATOR",
592
+ "LOCAL_RULE_APPLIES_TO",
593
+ "STYLE_BRIDGE_MAPS_TO",
594
+ "FINDING_VIOLATES_RULE",
595
+ "FINDING_ANCHORED_AT",
596
+ "EVIDENCE_SUPPORTS_FINDING",
597
+ "EVIDENCE_CAPTURED_FOR",
598
+ "REPAIR_FIXES_FINDING",
599
+ "TEST_COVERS_NODE",
600
+ "AGENT_RUN_CHANGED_NODE",
601
+ "SOURCE_IMPORTS_SOURCE"
602
+ ]);
603
+ function impactIds(nodes) {
604
+ return {
605
+ routes: graphNodeIdsByType(nodes, "Route"),
606
+ pages: graphNodeIdsByType(nodes, "Page"),
607
+ shells: graphNodeIdsByType(nodes, "Shell"),
608
+ patterns: graphNodeIdsByType(nodes, "Pattern"),
609
+ components: graphNodeIdsByType(nodes, "Component"),
610
+ tokens: graphNodeIdsByType(nodes, "Token"),
611
+ localRules: graphNodeIdsByType(nodes, "LocalRule"),
612
+ styleBridge: graphNodeIdsByType(nodes, "StyleBridge"),
613
+ openFindings: graphNodeIdsByType(nodes, "Finding"),
614
+ evidence: graphNodeIdsByType(nodes, "Evidence"),
615
+ repairs: graphNodeIdsByType(nodes, "Repair"),
616
+ sourceArtifacts: graphNodeIdsByType(nodes, "SourceArtifact")
617
+ };
618
+ }
619
+ function rankGraphImpactContextNodes(nodes, seedIds, ids, keywords = []) {
620
+ const scoreForNode = (node) => {
621
+ if (seedIds.has(node.id)) return { score: 1, reason: "seed_node" };
622
+ if (ids.routes.includes(node.id)) return { score: 0.92, reason: "affected_route" };
623
+ if (ids.pages.includes(node.id)) return { score: 0.88, reason: "affected_page" };
624
+ if (ids.shells.includes(node.id)) return { score: 0.82, reason: "affected_shell" };
625
+ if (ids.patterns.includes(node.id)) return { score: 0.78, reason: "affected_pattern" };
626
+ if (ids.components.includes(node.id)) return { score: 0.72, reason: "affected_component" };
627
+ if (ids.tokens.includes(node.id)) return { score: 0.68, reason: "affected_token" };
628
+ if (ids.localRules.includes(node.id)) return { score: 0.64, reason: "governing_local_rule" };
629
+ if (ids.styleBridge.includes(node.id)) return { score: 0.62, reason: "style_bridge_mapping" };
630
+ if (ids.openFindings.includes(node.id)) return { score: 0.6, reason: "open_finding" };
631
+ if (ids.evidence.includes(node.id)) return { score: 0.54, reason: "supporting_evidence" };
632
+ if (ids.repairs.includes(node.id)) return { score: 0.52, reason: "available_repair" };
633
+ if (ids.sourceArtifacts.includes(node.id)) return { score: 0.34, reason: "source_provenance" };
634
+ return { score: 0.24, reason: "included_impact" };
635
+ };
636
+ return nodes.map((node) => {
637
+ const ranked = scoreForNode(node);
638
+ const searchText = keywords.length > 0 ? graphNodeSearchText(node) : "";
639
+ const matchedTerms = keywords.filter((keyword) => searchText.includes(keyword));
640
+ const taskBoost = Math.min(0.12, matchedTerms.length * 0.035);
641
+ return {
642
+ id: node.id,
643
+ type: node.type,
644
+ score: Number(Math.min(1, ranked.score + taskBoost).toFixed(3)),
645
+ reason: matchedTerms.length > 0 ? `${ranked.reason}+task_match` : ranked.reason,
646
+ ...matchedTerms.length > 0 ? { matched_terms: matchedTerms } : {}
647
+ };
648
+ }).sort(
649
+ (a, b) => b.score - a.score || (b.matched_terms?.length ?? 0) - (a.matched_terms?.length ?? 0) || a.id.localeCompare(b.id)
650
+ );
651
+ }
652
+ function shouldTraverseImpactEdge(edge, includedNodeIds, projectId) {
653
+ if (!IMPACT_TRAVERSAL_RELATIONS.has(edge.relation)) return false;
654
+ if (!includedNodeIds.has(edge.src) && !includedNodeIds.has(edge.dst)) return false;
655
+ if (edge.dst === projectId && includedNodeIds.has(projectId) && !includedNodeIds.has(edge.src) && (edge.relation === "LOCAL_RULE_APPLIES_TO" || edge.relation === "STYLE_BRIDGE_MAPS_TO")) {
656
+ return false;
657
+ }
658
+ return true;
659
+ }
660
+ function isProjectScopedPolicySeed(seedNodeIds, nodeById, edges, projectId) {
661
+ return [...seedNodeIds].some((seedId) => {
662
+ const node = nodeById.get(seedId);
663
+ if (node?.type !== "LocalRule" && node?.type !== "StyleBridge") return false;
664
+ return edges.some(
665
+ (edge) => edge.src === seedId && edge.dst === projectId && (edge.relation === "LOCAL_RULE_APPLIES_TO" || edge.relation === "STYLE_BRIDGE_MAPS_TO")
666
+ );
667
+ });
668
+ }
669
+ function buildGraphImpactContext(snapshot, seed, options = {}) {
670
+ if (!snapshot) return null;
671
+ const seedIds = [...new Set(Array.isArray(seed) ? seed : [seed])].filter(Boolean);
672
+ const nodeById = new Map(snapshot.nodes.map((node) => [node.id, node]));
673
+ const seedNodes = seedIds.map((nodeId) => nodeById.get(nodeId)).filter((node) => !!node);
674
+ const missingNodeIds = seedIds.filter((nodeId) => !nodeById.has(nodeId)).sort();
675
+ if (!seedNodes.length) return null;
676
+ const includeNodeIds = new Set(seedNodes.map((node) => node.id));
677
+ const seedNodeIds = new Set(includeNodeIds);
678
+ const seedSourceIds = new Set(
679
+ seedNodes.filter((node) => node.type === "SourceArtifact").map((node) => node.id)
680
+ );
681
+ const includeEdges = /* @__PURE__ */ new Map();
682
+ const includeEdge = (edge) => {
683
+ includeEdges.set(graphEdgeKey(edge), edge);
684
+ includeNodeIds.add(edge.src);
685
+ includeNodeIds.add(edge.dst);
686
+ };
687
+ for (let pass = 0; pass < 5; pass += 1) {
688
+ const nodeCount = includeNodeIds.size;
689
+ const edgeCount = includeEdges.size;
690
+ for (const edge of snapshot.edges) {
691
+ if (edge.relation === "NODE_DERIVED_FROM_SOURCE") {
692
+ if (includeNodeIds.has(edge.src) || seedSourceIds.has(edge.dst)) {
693
+ includeEdge(edge);
694
+ }
695
+ continue;
696
+ }
697
+ if (shouldTraverseImpactEdge(edge, includeNodeIds, snapshot.project_id)) {
698
+ includeEdge(edge);
699
+ }
700
+ }
701
+ if (includeNodeIds.size === nodeCount && includeEdges.size === edgeCount) break;
702
+ }
703
+ if (isProjectScopedPolicySeed(seedNodeIds, nodeById, snapshot.edges, snapshot.project_id)) {
704
+ for (const edge of snapshot.edges) {
705
+ if (edge.relation === "PAGE_ROUTED_AT_ROUTE") {
706
+ includeEdge(edge);
707
+ }
708
+ }
709
+ const pageIds = new Set(
710
+ [...includeEdges.values()].filter((edge) => edge.relation === "PAGE_ROUTED_AT_ROUTE").map((edge) => edge.src)
711
+ );
712
+ for (const edge of snapshot.edges) {
713
+ if (pageIds.has(edge.src) && (edge.relation === "PAGE_USES_SHELL" || edge.relation === "NODE_DERIVED_FROM_SOURCE")) {
714
+ includeEdge(edge);
715
+ }
716
+ }
717
+ }
718
+ let nodes = sortGraphNodes(snapshot.nodes.filter((node) => includeNodeIds.has(node.id)));
719
+ let edges = sortGraphEdges([...includeEdges.values()]);
720
+ const totalNodes = nodes.length;
721
+ const totalEdges = edges.length;
722
+ const keywords = taskKeywords(options.task);
723
+ const fullIds = impactIds(nodes);
724
+ const fullRanked = rankGraphImpactContextNodes(nodes, seedNodeIds, fullIds, keywords);
725
+ const boundedLimit = typeof options.limit === "number" && Number.isFinite(options.limit) && options.limit > 0 ? Math.floor(options.limit) : void 0;
726
+ let truncated = false;
727
+ if (boundedLimit && nodes.length > boundedLimit) {
728
+ const keepIds = new Set(fullRanked.slice(0, boundedLimit).map((node) => node.id));
729
+ for (const seedId of seedNodeIds) {
730
+ keepIds.add(seedId);
731
+ }
732
+ nodes = sortGraphNodes(nodes.filter((node) => keepIds.has(node.id)));
733
+ const keptNodeIds = new Set(nodes.map((node) => node.id));
734
+ edges = sortGraphEdges(
735
+ edges.filter((edge) => keptNodeIds.has(edge.src) && keptNodeIds.has(edge.dst))
736
+ );
737
+ truncated = true;
738
+ }
739
+ const ids = impactIds(nodes);
740
+ const ranked = rankGraphImpactContextNodes(nodes, seedNodeIds, ids, keywords);
741
+ return {
742
+ snapshotId: snapshot.id,
743
+ sourceHash: snapshot.source_hash,
744
+ seedNodes: sortGraphNodes(seedNodes),
745
+ missingNodeIds,
746
+ ranking: {
747
+ method: keywords.length > 0 ? "impact_traversal_with_task_boost" : "impact_traversal",
748
+ seed: [...seedNodeIds].sort(),
749
+ task_keywords: keywords
750
+ },
751
+ summary: {
752
+ nodes: nodes.length,
753
+ edges: edges.length,
754
+ totalNodes,
755
+ totalEdges,
756
+ truncated,
757
+ routes: ids.routes.length,
758
+ pages: ids.pages.length,
759
+ shells: ids.shells.length,
760
+ patterns: ids.patterns.length,
761
+ components: ids.components.length,
762
+ tokens: ids.tokens.length,
763
+ localRules: ids.localRules.length,
764
+ styleBridge: ids.styleBridge.length,
765
+ openFindings: ids.openFindings.length,
766
+ evidence: ids.evidence.length,
767
+ repairs: ids.repairs.length,
768
+ sourceArtifacts: ids.sourceArtifacts.length
769
+ },
770
+ ids,
771
+ ranked,
772
+ nodes,
773
+ edges
774
+ };
775
+ }
776
+ function graphSlug(value, fallback) {
777
+ const slug = value.trim().toLowerCase().replace(/[^a-z0-9_.:/-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
778
+ return slug || fallback;
779
+ }
780
+ function addNode(nodes, node) {
781
+ if (!nodes.has(node.id)) {
782
+ nodes.set(node.id, node);
783
+ }
784
+ }
785
+ function addEdge(edges, edge) {
786
+ edges.set(graphEdgeKey(edge), edge);
787
+ }
788
+ function walkPatternNodes(node, patterns = []) {
789
+ if (node.type === "pattern") {
790
+ patterns.push(node);
791
+ }
792
+ for (const child of node.children) {
793
+ walkPatternNodes(child, patterns);
794
+ }
795
+ return patterns;
796
+ }
797
+ function collectEssenceLayoutPatterns(item, patterns) {
798
+ if (typeof item === "string") {
799
+ patterns.push({ id: item });
800
+ return;
801
+ }
802
+ if (!item || typeof item !== "object") {
803
+ return;
804
+ }
805
+ if ("pattern" in item && typeof item.pattern === "string") {
806
+ patterns.push({
807
+ id: item.pattern,
808
+ alias: typeof item.as === "string" ? item.as : void 0,
809
+ preset: typeof item.preset === "string" ? item.preset : void 0
810
+ });
811
+ }
812
+ if ("cols" in item && Array.isArray(item.cols)) {
813
+ for (const column of item.cols) {
814
+ collectEssenceLayoutPatterns(column, patterns);
815
+ }
816
+ }
817
+ }
818
+ function essenceLayoutPatterns(layout) {
819
+ const patterns = [];
820
+ for (const item of layout) {
821
+ collectEssenceLayoutPatterns(item, patterns);
822
+ }
823
+ return patterns;
824
+ }
825
+ function buildGraphSnapshotFromEssence(essence, options = {}) {
826
+ const projectId = options.projectId ?? "proj:default";
827
+ const nodes = /* @__PURE__ */ new Map();
828
+ const edges = /* @__PURE__ */ new Map();
829
+ const sourceNodeId = options.sourceArtifact?.id;
830
+ const routeEntries = essence.blueprint.routes ?? {};
831
+ addNode(nodes, {
832
+ id: projectId,
833
+ type: "Project",
834
+ payload: {
835
+ archetype: essence.meta.archetype,
836
+ target: essence.meta.target,
837
+ platform: essence.meta.platform,
838
+ guard: essence.meta.guard,
839
+ routing: essence.meta.platform.routing,
840
+ features: essence.blueprint.features
841
+ }
842
+ });
843
+ if (options.sourceArtifact) {
844
+ addNode(nodes, {
845
+ id: options.sourceArtifact.id,
846
+ type: "SourceArtifact",
847
+ payload: options.sourceArtifact
848
+ });
849
+ addEdge(edges, {
850
+ src: projectId,
851
+ dst: options.sourceArtifact.id,
852
+ relation: "NODE_DERIVED_FROM_SOURCE"
853
+ });
854
+ }
855
+ const themeId = `theme:${graphSlug(essence.dna.theme.id, "theme")}`;
856
+ addNode(nodes, {
857
+ id: themeId,
858
+ type: "Theme",
859
+ payload: essence.dna.theme
860
+ });
861
+ addEdge(edges, {
862
+ src: projectId,
863
+ dst: themeId,
864
+ relation: "PROJECT_USES_THEME"
865
+ });
866
+ if (sourceNodeId) {
867
+ addEdge(edges, {
868
+ src: themeId,
869
+ dst: sourceNodeId,
870
+ relation: "NODE_DERIVED_FROM_SOURCE"
871
+ });
872
+ }
873
+ for (const feature of essence.blueprint.features) {
874
+ const featureId = `feat:${graphSlug(feature, "feature")}`;
875
+ addNode(nodes, {
876
+ id: featureId,
877
+ type: "Feature",
878
+ payload: {
879
+ id: feature
880
+ }
881
+ });
882
+ addEdge(edges, {
883
+ src: projectId,
884
+ dst: featureId,
885
+ relation: "PROJECT_ENABLES_FEATURE"
886
+ });
887
+ if (sourceNodeId) {
888
+ addEdge(edges, {
889
+ src: featureId,
890
+ dst: sourceNodeId,
891
+ relation: "NODE_DERIVED_FROM_SOURCE"
892
+ });
893
+ }
894
+ }
895
+ const routeForPage = /* @__PURE__ */ new Map();
896
+ for (const [path, entry] of Object.entries(routeEntries)) {
897
+ routeForPage.set(`${entry.section}:${entry.page}`, path);
898
+ const routeId = `rt:${path}`;
899
+ addNode(nodes, {
900
+ id: routeId,
901
+ type: "Route",
902
+ payload: {
903
+ path,
904
+ section: entry.section,
905
+ page: entry.page
906
+ }
907
+ });
908
+ if (sourceNodeId) {
909
+ addEdge(edges, {
910
+ src: routeId,
911
+ dst: sourceNodeId,
912
+ relation: "NODE_DERIVED_FROM_SOURCE"
913
+ });
914
+ }
915
+ }
916
+ for (const section of essence.blueprint.sections) {
917
+ const sectionId = `sec:${graphSlug(section.id, "section")}`;
918
+ const shellId = `sh:${graphSlug(String(section.shell), "shell")}`;
919
+ addNode(nodes, {
920
+ id: sectionId,
921
+ type: "Section",
922
+ payload: {
923
+ id: section.id,
924
+ role: section.role,
925
+ description: section.description,
926
+ features: section.features,
927
+ directives: section.directives
928
+ }
929
+ });
930
+ addNode(nodes, {
931
+ id: shellId,
932
+ type: "Shell",
933
+ payload: {
934
+ id: section.shell,
935
+ navigation_items: section.navigation_items
936
+ }
937
+ });
938
+ addEdge(edges, {
939
+ src: projectId,
940
+ dst: sectionId,
941
+ relation: "PROJECT_CONTAINS_SECTION"
942
+ });
943
+ if (sourceNodeId) {
944
+ addEdge(edges, {
945
+ src: sectionId,
946
+ dst: sourceNodeId,
947
+ relation: "NODE_DERIVED_FROM_SOURCE"
948
+ });
949
+ addEdge(edges, {
950
+ src: shellId,
951
+ dst: sourceNodeId,
952
+ relation: "NODE_DERIVED_FROM_SOURCE"
953
+ });
954
+ }
955
+ for (const page of section.pages) {
956
+ const pageId = `pg:${graphSlug(`${section.id}:${page.id}`, "page")}`;
957
+ const routePath3 = page.route ?? routeForPage.get(`${section.id}:${page.id}`);
958
+ const routeId = routePath3 ? `rt:${routePath3}` : null;
959
+ const pageShellId = page.shell_override ? `sh:${graphSlug(String(page.shell_override), "shell")}` : shellId;
960
+ if (page.shell_override) {
961
+ addNode(nodes, {
962
+ id: pageShellId,
963
+ type: "Shell",
964
+ payload: {
965
+ id: page.shell_override,
966
+ source: "page.shell_override"
967
+ }
968
+ });
969
+ if (sourceNodeId) {
970
+ addEdge(edges, {
971
+ src: pageShellId,
972
+ dst: sourceNodeId,
973
+ relation: "NODE_DERIVED_FROM_SOURCE"
974
+ });
975
+ }
976
+ }
977
+ addNode(nodes, {
978
+ id: pageId,
979
+ type: "Page",
980
+ payload: {
981
+ id: page.id,
982
+ section: section.id,
983
+ route: routePath3,
984
+ surface: page.surface,
985
+ shell_override: page.shell_override,
986
+ dna_overrides: page.dna_overrides,
987
+ directives: page.directives
988
+ }
989
+ });
990
+ addEdge(edges, {
991
+ src: sectionId,
992
+ dst: pageId,
993
+ relation: "SECTION_CONTAINS_PAGE"
994
+ });
995
+ addEdge(edges, {
996
+ src: pageId,
997
+ dst: pageShellId,
998
+ relation: "PAGE_USES_SHELL"
999
+ });
1000
+ if (routeId) {
1001
+ addNode(nodes, {
1002
+ id: routeId,
1003
+ type: "Route",
1004
+ payload: {
1005
+ path: routePath3,
1006
+ section: section.id,
1007
+ page: page.id
1008
+ }
1009
+ });
1010
+ addEdge(edges, {
1011
+ src: pageId,
1012
+ dst: routeId,
1013
+ relation: "PAGE_ROUTED_AT_ROUTE"
1014
+ });
1015
+ }
1016
+ if (sourceNodeId) {
1017
+ addEdge(edges, {
1018
+ src: pageId,
1019
+ dst: sourceNodeId,
1020
+ relation: "NODE_DERIVED_FROM_SOURCE"
1021
+ });
1022
+ }
1023
+ essenceLayoutPatterns(page.layout).forEach((pattern, ordinal) => {
1024
+ const patternId = `pat:${graphSlug(pattern.id, "pattern")}`;
1025
+ addNode(nodes, {
1026
+ id: patternId,
1027
+ type: "Pattern",
1028
+ payload: {
1029
+ id: pattern.id,
1030
+ alias: pattern.alias,
1031
+ preset: pattern.preset
1032
+ }
1033
+ });
1034
+ addEdge(edges, {
1035
+ src: pageId,
1036
+ dst: patternId,
1037
+ relation: "PAGE_COMPOSES_PATTERN",
1038
+ payload: {
1039
+ alias: pattern.alias,
1040
+ preset: pattern.preset,
1041
+ source: "essence.layout"
1042
+ },
1043
+ idx: ordinal
1044
+ });
1045
+ if (sourceNodeId) {
1046
+ addEdge(edges, {
1047
+ src: patternId,
1048
+ dst: sourceNodeId,
1049
+ relation: "NODE_DERIVED_FROM_SOURCE"
1050
+ });
1051
+ }
1052
+ });
1053
+ }
1054
+ }
1055
+ return normalizeGraphSnapshot({
1056
+ $schema: GRAPH_SNAPSHOT_SCHEMA_URL,
1057
+ id: options.snapshotId ?? `graph:${graphSlug(projectId, "default")}:draft`,
1058
+ schema_version: GRAPH_SCHEMA_VERSION,
1059
+ project_id: projectId,
1060
+ created_at: options.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString(),
1061
+ parent_id: options.parentId,
1062
+ source_hash: options.sourceHash ?? "unknown",
1063
+ git: options.git,
1064
+ nodes: [...nodes.values()],
1065
+ edges: [...edges.values()],
1066
+ summary: {
1067
+ nodes: 0,
1068
+ edges: 0,
1069
+ findings: 0,
1070
+ evidence: 0
1071
+ }
1072
+ });
1073
+ }
1074
+ function buildGraphSnapshotFromIR(ir, options = {}) {
1075
+ const projectId = options.projectId ?? "proj:default";
1076
+ const nodes = /* @__PURE__ */ new Map();
1077
+ const edges = /* @__PURE__ */ new Map();
1078
+ const sourceNodeId = options.sourceArtifact?.id;
1079
+ addNode(nodes, {
1080
+ id: projectId,
1081
+ type: "Project",
1082
+ payload: {
1083
+ routing: ir.routing,
1084
+ features: ir.features
1085
+ }
1086
+ });
1087
+ if (options.sourceArtifact) {
1088
+ addNode(nodes, {
1089
+ id: options.sourceArtifact.id,
1090
+ type: "SourceArtifact",
1091
+ payload: options.sourceArtifact
1092
+ });
1093
+ addEdge(edges, {
1094
+ src: projectId,
1095
+ dst: options.sourceArtifact.id,
1096
+ relation: "NODE_DERIVED_FROM_SOURCE"
1097
+ });
1098
+ }
1099
+ const shellId = `sh:${graphSlug(ir.shell.config.type || ir.shell.id, "shell")}`;
1100
+ addNode(nodes, {
1101
+ id: shellId,
1102
+ type: "Shell",
1103
+ payload: ir.shell.config
1104
+ });
1105
+ if (sourceNodeId) {
1106
+ addEdge(edges, {
1107
+ src: shellId,
1108
+ dst: sourceNodeId,
1109
+ relation: "NODE_DERIVED_FROM_SOURCE"
1110
+ });
1111
+ }
1112
+ for (const route of ir.routes) {
1113
+ const routeId = `rt:${route.path}`;
1114
+ addNode(nodes, {
1115
+ id: routeId,
1116
+ type: "Route",
1117
+ payload: route
1118
+ });
1119
+ }
1120
+ for (const page of ir.children) {
1121
+ if (page.type !== "page") continue;
1122
+ const pageNode = page;
1123
+ const sectionId = pageNode.sectionId ? `sec:${graphSlug(pageNode.sectionId, "section")}` : null;
1124
+ const pageId = `pg:${graphSlug(pageNode.id, pageNode.pageId)}`;
1125
+ const route = ir.routes.find(
1126
+ (candidate) => candidate.pageId === pageNode.pageId && (!pageNode.sectionId || candidate.sectionId === pageNode.sectionId)
1127
+ );
1128
+ const routeId = route ? `rt:${route.path}` : null;
1129
+ if (sectionId) {
1130
+ addNode(nodes, {
1131
+ id: sectionId,
1132
+ type: "Section",
1133
+ payload: {
1134
+ id: pageNode.sectionId
1135
+ }
1136
+ });
1137
+ addEdge(edges, {
1138
+ src: projectId,
1139
+ dst: sectionId,
1140
+ relation: "PROJECT_CONTAINS_SECTION"
1141
+ });
1142
+ addEdge(edges, {
1143
+ src: sectionId,
1144
+ dst: pageId,
1145
+ relation: "SECTION_CONTAINS_PAGE"
1146
+ });
1147
+ if (sourceNodeId) {
1148
+ addEdge(edges, {
1149
+ src: sectionId,
1150
+ dst: sourceNodeId,
1151
+ relation: "NODE_DERIVED_FROM_SOURCE"
1152
+ });
1153
+ }
1154
+ }
1155
+ addNode(nodes, {
1156
+ id: pageId,
1157
+ type: "Page",
1158
+ payload: {
1159
+ id: pageNode.pageId,
1160
+ sectionId: pageNode.sectionId,
1161
+ surface: pageNode.surface
1162
+ }
1163
+ });
1164
+ addEdge(edges, {
1165
+ src: pageId,
1166
+ dst: shellId,
1167
+ relation: "PAGE_USES_SHELL"
1168
+ });
1169
+ if (routeId) {
1170
+ addEdge(edges, {
1171
+ src: pageId,
1172
+ dst: routeId,
1173
+ relation: "PAGE_ROUTED_AT_ROUTE"
1174
+ });
1175
+ }
1176
+ if (sourceNodeId) {
1177
+ addEdge(edges, {
1178
+ src: pageId,
1179
+ dst: sourceNodeId,
1180
+ relation: "NODE_DERIVED_FROM_SOURCE"
1181
+ });
1182
+ }
1183
+ walkPatternNodes(pageNode).forEach((patternNode, ordinal) => {
1184
+ const patternId = `pat:${graphSlug(patternNode.pattern.patternId, "pattern")}`;
1185
+ addNode(nodes, {
1186
+ id: patternId,
1187
+ type: "Pattern",
1188
+ payload: {
1189
+ id: patternNode.pattern.patternId,
1190
+ alias: patternNode.pattern.alias,
1191
+ preset: patternNode.pattern.preset,
1192
+ layout: patternNode.pattern.layout
1193
+ }
1194
+ });
1195
+ addEdge(edges, {
1196
+ src: pageId,
1197
+ dst: patternId,
1198
+ relation: "PAGE_COMPOSES_PATTERN",
1199
+ payload: {
1200
+ alias: patternNode.pattern.alias,
1201
+ preset: patternNode.pattern.preset
1202
+ },
1203
+ idx: ordinal
1204
+ });
1205
+ for (const component of patternNode.pattern.components) {
1206
+ const componentId = `cmp:${graphSlug(component, "component")}`;
1207
+ addNode(nodes, {
1208
+ id: componentId,
1209
+ type: "Component",
1210
+ payload: {
1211
+ name: component
1212
+ }
1213
+ });
1214
+ addEdge(edges, {
1215
+ src: patternId,
1216
+ dst: componentId,
1217
+ relation: "PATTERN_NEEDS_COMPONENT"
1218
+ });
1219
+ }
1220
+ if (sourceNodeId) {
1221
+ addEdge(edges, {
1222
+ src: patternId,
1223
+ dst: sourceNodeId,
1224
+ relation: "NODE_DERIVED_FROM_SOURCE"
1225
+ });
1226
+ }
1227
+ });
1228
+ }
1229
+ return normalizeGraphSnapshot({
1230
+ $schema: GRAPH_SNAPSHOT_SCHEMA_URL,
1231
+ id: options.snapshotId ?? `graph:${graphSlug(projectId, "default")}:draft`,
1232
+ schema_version: GRAPH_SCHEMA_VERSION,
1233
+ project_id: projectId,
1234
+ created_at: options.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString(),
1235
+ parent_id: options.parentId,
1236
+ source_hash: options.sourceHash ?? "unknown",
1237
+ git: options.git,
1238
+ nodes: [...nodes.values()],
1239
+ edges: [...edges.values()],
1240
+ summary: {
1241
+ nodes: 0,
1242
+ edges: 0,
1243
+ findings: 0,
1244
+ evidence: 0
1245
+ }
1246
+ });
1247
+ }
1248
+ var MemoryGraphStore = class {
1249
+ nodes = /* @__PURE__ */ new Map();
1250
+ edges = /* @__PURE__ */ new Map();
1251
+ snapshots = /* @__PURE__ */ new Map();
1252
+ latestSnapshotId = null;
1253
+ constructor(seed = {}) {
1254
+ for (const node of seed.nodes ?? []) {
1255
+ this.nodes.set(node.id, node);
1256
+ }
1257
+ for (const edge of seed.edges ?? []) {
1258
+ this.edges.set(graphEdgeKey(edge), edge);
1259
+ }
1260
+ for (const snapshot of seed.snapshots ?? []) {
1261
+ this.snapshots.set(snapshot.id, normalizeGraphSnapshot(snapshot));
1262
+ this.latestSnapshotId = snapshot.id;
1263
+ }
1264
+ }
1265
+ async open() {
1266
+ return;
1267
+ }
1268
+ async close() {
1269
+ return;
1270
+ }
1271
+ async upsertNode(node) {
1272
+ this.nodes.set(node.id, node);
1273
+ }
1274
+ async upsertEdge(edge) {
1275
+ this.edges.set(graphEdgeKey(edge), edge);
1276
+ }
1277
+ async getNode(id) {
1278
+ return this.nodes.get(id) ?? null;
1279
+ }
1280
+ async queryNodes(query) {
1281
+ const ids = query.ids ? new Set(query.ids) : null;
1282
+ const types = query.types ?? (query.type ? [query.type] : null);
1283
+ return sortGraphNodes(
1284
+ [...this.nodes.values()].filter((node) => {
1285
+ if (ids && !ids.has(node.id)) return false;
1286
+ if (types && !types.includes(node.type)) return false;
1287
+ if (!graphNodePayloadMatches(node, query)) return false;
1288
+ return true;
1289
+ })
1290
+ );
1291
+ }
1292
+ async queryEdges(query) {
1293
+ return sortGraphEdges(
1294
+ [...this.edges.values()].filter((edge) => {
1295
+ if (query.src && edge.src !== query.src) return false;
1296
+ if (query.dst && edge.dst !== query.dst) return false;
1297
+ if (!relationMatches(edge, query)) return false;
1298
+ return true;
1299
+ })
1300
+ );
1301
+ }
1302
+ async traverse(query) {
1303
+ const direction = query.direction ?? "out";
1304
+ const depth = Math.max(0, query.depth ?? 1);
1305
+ const startIds = Array.isArray(query.from) ? query.from : [query.from];
1306
+ const seenNodeIds = new Set(startIds);
1307
+ const seenEdgeKeys = /* @__PURE__ */ new Set();
1308
+ let frontier = startIds;
1309
+ for (let level = 0; level < depth; level += 1) {
1310
+ const nextFrontier = [];
1311
+ for (const nodeId of frontier) {
1312
+ const candidates = [...this.edges.values()].filter((edge) => {
1313
+ if (!relationMatches(edge, query)) return false;
1314
+ if (direction === "out") return edge.src === nodeId;
1315
+ if (direction === "in") return edge.dst === nodeId;
1316
+ return edge.src === nodeId || edge.dst === nodeId;
1317
+ });
1318
+ for (const edge of candidates) {
1319
+ const key = graphEdgeKey(edge);
1320
+ seenEdgeKeys.add(key);
1321
+ const nextNodeId = edge.src === nodeId ? edge.dst : edge.src;
1322
+ if (!seenNodeIds.has(nextNodeId)) {
1323
+ seenNodeIds.add(nextNodeId);
1324
+ nextFrontier.push(nextNodeId);
1325
+ }
1326
+ }
1327
+ }
1328
+ frontier = nextFrontier;
1329
+ if (!frontier.length) break;
1330
+ }
1331
+ return {
1332
+ nodes: sortGraphNodes(
1333
+ [...seenNodeIds].map((id) => this.nodes.get(id)).filter((node) => !!node)
1334
+ ),
1335
+ edges: sortGraphEdges(
1336
+ [...seenEdgeKeys].map((key) => this.edges.get(key)).filter((edge) => !!edge)
1337
+ )
1338
+ };
1339
+ }
1340
+ async writeSnapshot(snapshot) {
1341
+ const normalized = normalizeGraphSnapshot(snapshot);
1342
+ this.snapshots.set(normalized.id, normalized);
1343
+ this.latestSnapshotId = normalized.id;
1344
+ }
1345
+ async readSnapshot(id) {
1346
+ const snapshotId = id ?? this.latestSnapshotId;
1347
+ return snapshotId ? this.snapshots.get(snapshotId) ?? null : null;
1348
+ }
1349
+ };
1350
+ function createMemoryGraphStore(seed) {
1351
+ return new MemoryGraphStore(seed);
1352
+ }
1353
+
1
1354
  // src/resolve.ts
2
1355
  import { computeDensity, isV4 } from "@decantr/essence-spec";
3
1356
  import { detectWirings, resolvePatternPreset } from "@decantr/registry";
@@ -1790,10 +3143,24 @@ function compileRealizationPlan(essence) {
1790
3143
  };
1791
3144
  }
1792
3145
  export {
3146
+ CONTRACT_CAPSULE_SCHEMA_URL,
3147
+ DEFAULT_CONTRACT_CAPSULE_SOURCE_ARTIFACT_LIMIT,
1793
3148
  EXECUTION_PACK_BUNDLE_SCHEMA_URL,
1794
3149
  EXECUTION_PACK_SCHEMA_URLS,
3150
+ GRAPH_COMMON_SCHEMA_URL,
3151
+ GRAPH_DIFF_SCHEMA_URL,
3152
+ GRAPH_MANIFEST_SCHEMA_URL,
3153
+ GRAPH_NODE_TYPES,
3154
+ GRAPH_RELATIONS,
3155
+ GRAPH_SCHEMA_VERSION,
3156
+ GRAPH_SNAPSHOT_SCHEMA_URL,
1795
3157
  PACK_MANIFEST_SCHEMA_URL,
1796
3158
  SELECTED_EXECUTION_PACK_SCHEMA_URL,
3159
+ buildContractCapsuleFromSnapshot,
3160
+ buildGraphImpactContext,
3161
+ buildGraphRouteContext,
3162
+ buildGraphSnapshotFromEssence,
3163
+ buildGraphSnapshotFromIR,
1797
3164
  buildMutationPack,
1798
3165
  buildPageIR,
1799
3166
  buildPagePack,
@@ -1804,9 +3171,15 @@ export {
1804
3171
  compileRealizationPlan,
1805
3172
  compileSelectedExecutionPack,
1806
3173
  countPatterns,
3174
+ createMemoryGraphStore,
3175
+ diffGraphSnapshots,
1807
3176
  findNodes,
3177
+ graphContractHash,
3178
+ graphPayloadRecord,
3179
+ graphPayloadString,
1808
3180
  listPackPages,
1809
3181
  listPackSections,
3182
+ normalizeGraphSnapshot,
1810
3183
  pascalCase,
1811
3184
  renderExecutionPackMarkdown,
1812
3185
  resolveEssence,
@@ -1814,6 +3187,9 @@ export {
1814
3187
  resolveVisualEffects,
1815
3188
  runPipeline,
1816
3189
  selectExecutionPackFromBundle,
3190
+ sortGraphEdges,
3191
+ sortGraphNodes,
3192
+ summarizeGraphDiff,
1817
3193
  validateIR,
1818
3194
  walkIR
1819
3195
  };