@decantr/core 2.1.0 → 3.0.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 +15 -0
- package/dist/index.d.ts +365 -2
- package/dist/index.js +1376 -0
- package/dist/index.js.map +1 -1
- package/package.json +15 -8
- package/schema/contract-capsule.v1.json +208 -0
- package/schema/graph-diff.v1.json +52 -0
- package/schema/graph-manifest.v1.json +74 -0
- package/schema/graph-snapshot.v1.json +70 -0
- package/schema/graph.common.v1.json +186 -0
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
|
};
|