@harness-engineering/graph 0.3.4 → 0.4.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.d.mts +79 -14
- package/dist/index.d.ts +79 -14
- package/dist/index.js +487 -93
- package/dist/index.mjs +485 -93
- package/package.json +8 -10
package/dist/index.js
CHANGED
|
@@ -59,6 +59,7 @@ __export(index_exports, {
|
|
|
59
59
|
KnowledgeIngestor: () => KnowledgeIngestor,
|
|
60
60
|
NODE_TYPES: () => NODE_TYPES,
|
|
61
61
|
OBSERVABILITY_TYPES: () => OBSERVABILITY_TYPES,
|
|
62
|
+
RequirementIngestor: () => RequirementIngestor,
|
|
62
63
|
ResponseFormatter: () => ResponseFormatter,
|
|
63
64
|
SlackConnector: () => SlackConnector,
|
|
64
65
|
SyncManager: () => SyncManager,
|
|
@@ -71,6 +72,7 @@ __export(index_exports, {
|
|
|
71
72
|
linkToCode: () => linkToCode,
|
|
72
73
|
loadGraph: () => loadGraph,
|
|
73
74
|
project: () => project,
|
|
75
|
+
queryTraceability: () => queryTraceability,
|
|
74
76
|
saveGraph: () => saveGraph
|
|
75
77
|
});
|
|
76
78
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -112,7 +114,9 @@ var NODE_TYPES = [
|
|
|
112
114
|
// Design
|
|
113
115
|
"design_token",
|
|
114
116
|
"aesthetic_intent",
|
|
115
|
-
"design_constraint"
|
|
117
|
+
"design_constraint",
|
|
118
|
+
// Traceability
|
|
119
|
+
"requirement"
|
|
116
120
|
];
|
|
117
121
|
var EDGE_TYPES = [
|
|
118
122
|
// Code relationships
|
|
@@ -141,7 +145,11 @@ var EDGE_TYPES = [
|
|
|
141
145
|
"uses_token",
|
|
142
146
|
"declares_intent",
|
|
143
147
|
"violates_design",
|
|
144
|
-
"platform_binding"
|
|
148
|
+
"platform_binding",
|
|
149
|
+
// Traceability relationships
|
|
150
|
+
"requires",
|
|
151
|
+
"verified_by",
|
|
152
|
+
"tested_by"
|
|
145
153
|
];
|
|
146
154
|
var OBSERVABILITY_TYPES = /* @__PURE__ */ new Set(["span", "metric", "log"]);
|
|
147
155
|
var CURRENT_SCHEMA_VERSION = 1;
|
|
@@ -171,9 +179,6 @@ var GraphEdgeSchema = import_zod.z.object({
|
|
|
171
179
|
metadata: import_zod.z.record(import_zod.z.unknown()).optional()
|
|
172
180
|
});
|
|
173
181
|
|
|
174
|
-
// src/store/GraphStore.ts
|
|
175
|
-
var import_lokijs = __toESM(require("lokijs"));
|
|
176
|
-
|
|
177
182
|
// src/store/Serializer.ts
|
|
178
183
|
var import_promises = require("fs/promises");
|
|
179
184
|
var import_node_path = require("path");
|
|
@@ -218,28 +223,38 @@ function safeMerge(target, source) {
|
|
|
218
223
|
}
|
|
219
224
|
}
|
|
220
225
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
});
|
|
231
|
-
this.edges = this.db.addCollection("edges", {
|
|
232
|
-
indices: ["from", "to", "type"]
|
|
233
|
-
});
|
|
226
|
+
function edgeKey(from, to, type) {
|
|
227
|
+
return `${from}\0${to}\0${type}`;
|
|
228
|
+
}
|
|
229
|
+
function addToIndex(index, key, edge) {
|
|
230
|
+
const list = index.get(key);
|
|
231
|
+
if (list) {
|
|
232
|
+
list.push(edge);
|
|
233
|
+
} else {
|
|
234
|
+
index.set(key, [edge]);
|
|
234
235
|
}
|
|
236
|
+
}
|
|
237
|
+
function removeFromIndex(index, key, edge) {
|
|
238
|
+
const list = index.get(key);
|
|
239
|
+
if (!list) return;
|
|
240
|
+
const idx = list.indexOf(edge);
|
|
241
|
+
if (idx !== -1) list.splice(idx, 1);
|
|
242
|
+
if (list.length === 0) index.delete(key);
|
|
243
|
+
}
|
|
244
|
+
var GraphStore = class {
|
|
245
|
+
nodeMap = /* @__PURE__ */ new Map();
|
|
246
|
+
edgeMap = /* @__PURE__ */ new Map();
|
|
247
|
+
// keyed by from\0to\0type
|
|
248
|
+
edgesByFrom = /* @__PURE__ */ new Map();
|
|
249
|
+
edgesByTo = /* @__PURE__ */ new Map();
|
|
250
|
+
edgesByType = /* @__PURE__ */ new Map();
|
|
235
251
|
// --- Node operations ---
|
|
236
252
|
addNode(node) {
|
|
237
|
-
const existing = this.
|
|
253
|
+
const existing = this.nodeMap.get(node.id);
|
|
238
254
|
if (existing) {
|
|
239
255
|
safeMerge(existing, node);
|
|
240
|
-
this.nodes.update(existing);
|
|
241
256
|
} else {
|
|
242
|
-
this.
|
|
257
|
+
this.nodeMap.set(node.id, { ...node });
|
|
243
258
|
}
|
|
244
259
|
}
|
|
245
260
|
batchAddNodes(nodes) {
|
|
@@ -248,44 +263,44 @@ var GraphStore = class {
|
|
|
248
263
|
}
|
|
249
264
|
}
|
|
250
265
|
getNode(id) {
|
|
251
|
-
const
|
|
252
|
-
if (!
|
|
253
|
-
return
|
|
266
|
+
const node = this.nodeMap.get(id);
|
|
267
|
+
if (!node) return null;
|
|
268
|
+
return { ...node };
|
|
254
269
|
}
|
|
255
270
|
findNodes(query) {
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
271
|
+
const results = [];
|
|
272
|
+
for (const node of this.nodeMap.values()) {
|
|
273
|
+
if (query.type !== void 0 && node.type !== query.type) continue;
|
|
274
|
+
if (query.name !== void 0 && node.name !== query.name) continue;
|
|
275
|
+
if (query.path !== void 0 && node.path !== query.path) continue;
|
|
276
|
+
results.push({ ...node });
|
|
277
|
+
}
|
|
278
|
+
return results;
|
|
261
279
|
}
|
|
262
280
|
removeNode(id) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const edgesToRemove = this.edges.find({
|
|
268
|
-
$or: [{ from: id }, { to: id }]
|
|
269
|
-
});
|
|
281
|
+
this.nodeMap.delete(id);
|
|
282
|
+
const fromEdges = this.edgesByFrom.get(id) ?? [];
|
|
283
|
+
const toEdges = this.edgesByTo.get(id) ?? [];
|
|
284
|
+
const edgesToRemove = /* @__PURE__ */ new Set([...fromEdges, ...toEdges]);
|
|
270
285
|
for (const edge of edgesToRemove) {
|
|
271
|
-
this.
|
|
286
|
+
this.removeEdgeInternal(edge);
|
|
272
287
|
}
|
|
273
288
|
}
|
|
274
289
|
// --- Edge operations ---
|
|
275
290
|
addEdge(edge) {
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
to: edge.to,
|
|
279
|
-
type: edge.type
|
|
280
|
-
});
|
|
291
|
+
const key = edgeKey(edge.from, edge.to, edge.type);
|
|
292
|
+
const existing = this.edgeMap.get(key);
|
|
281
293
|
if (existing) {
|
|
282
294
|
if (edge.metadata) {
|
|
283
295
|
safeMerge(existing, edge);
|
|
284
|
-
this.edges.update(existing);
|
|
285
296
|
}
|
|
286
297
|
return;
|
|
287
298
|
}
|
|
288
|
-
|
|
299
|
+
const copy = { ...edge };
|
|
300
|
+
this.edgeMap.set(key, copy);
|
|
301
|
+
addToIndex(this.edgesByFrom, edge.from, copy);
|
|
302
|
+
addToIndex(this.edgesByTo, edge.to, copy);
|
|
303
|
+
addToIndex(this.edgesByType, edge.type, copy);
|
|
289
304
|
}
|
|
290
305
|
batchAddEdges(edges) {
|
|
291
306
|
for (const edge of edges) {
|
|
@@ -293,22 +308,38 @@ var GraphStore = class {
|
|
|
293
308
|
}
|
|
294
309
|
}
|
|
295
310
|
getEdges(query) {
|
|
296
|
-
|
|
297
|
-
if (query.from !== void 0
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
311
|
+
let candidates;
|
|
312
|
+
if (query.from !== void 0 && query.to !== void 0 && query.type !== void 0) {
|
|
313
|
+
const edge = this.edgeMap.get(edgeKey(query.from, query.to, query.type));
|
|
314
|
+
return edge ? [{ ...edge }] : [];
|
|
315
|
+
} else if (query.from !== void 0) {
|
|
316
|
+
candidates = this.edgesByFrom.get(query.from) ?? [];
|
|
317
|
+
} else if (query.to !== void 0) {
|
|
318
|
+
candidates = this.edgesByTo.get(query.to) ?? [];
|
|
319
|
+
} else if (query.type !== void 0) {
|
|
320
|
+
candidates = this.edgesByType.get(query.type) ?? [];
|
|
321
|
+
} else {
|
|
322
|
+
candidates = this.edgeMap.values();
|
|
323
|
+
}
|
|
324
|
+
const results = [];
|
|
325
|
+
for (const edge of candidates) {
|
|
326
|
+
if (query.from !== void 0 && edge.from !== query.from) continue;
|
|
327
|
+
if (query.to !== void 0 && edge.to !== query.to) continue;
|
|
328
|
+
if (query.type !== void 0 && edge.type !== query.type) continue;
|
|
329
|
+
results.push({ ...edge });
|
|
330
|
+
}
|
|
331
|
+
return results;
|
|
301
332
|
}
|
|
302
333
|
getNeighbors(nodeId, direction = "both") {
|
|
303
334
|
const neighborIds = /* @__PURE__ */ new Set();
|
|
304
335
|
if (direction === "outbound" || direction === "both") {
|
|
305
|
-
const outEdges = this.
|
|
336
|
+
const outEdges = this.edgesByFrom.get(nodeId) ?? [];
|
|
306
337
|
for (const edge of outEdges) {
|
|
307
338
|
neighborIds.add(edge.to);
|
|
308
339
|
}
|
|
309
340
|
}
|
|
310
341
|
if (direction === "inbound" || direction === "both") {
|
|
311
|
-
const inEdges = this.
|
|
342
|
+
const inEdges = this.edgesByTo.get(nodeId) ?? [];
|
|
312
343
|
for (const edge of inEdges) {
|
|
313
344
|
neighborIds.add(edge.from);
|
|
314
345
|
}
|
|
@@ -322,20 +353,23 @@ var GraphStore = class {
|
|
|
322
353
|
}
|
|
323
354
|
// --- Counts ---
|
|
324
355
|
get nodeCount() {
|
|
325
|
-
return this.
|
|
356
|
+
return this.nodeMap.size;
|
|
326
357
|
}
|
|
327
358
|
get edgeCount() {
|
|
328
|
-
return this.
|
|
359
|
+
return this.edgeMap.size;
|
|
329
360
|
}
|
|
330
361
|
// --- Clear ---
|
|
331
362
|
clear() {
|
|
332
|
-
this.
|
|
333
|
-
this.
|
|
363
|
+
this.nodeMap.clear();
|
|
364
|
+
this.edgeMap.clear();
|
|
365
|
+
this.edgesByFrom.clear();
|
|
366
|
+
this.edgesByTo.clear();
|
|
367
|
+
this.edgesByType.clear();
|
|
334
368
|
}
|
|
335
369
|
// --- Persistence ---
|
|
336
370
|
async save(dirPath) {
|
|
337
|
-
const allNodes = this.
|
|
338
|
-
const allEdges = this.
|
|
371
|
+
const allNodes = Array.from(this.nodeMap.values()).map((n) => ({ ...n }));
|
|
372
|
+
const allEdges = Array.from(this.edgeMap.values()).map((e) => ({ ...e }));
|
|
339
373
|
await saveGraph(dirPath, allNodes, allEdges);
|
|
340
374
|
}
|
|
341
375
|
async load(dirPath) {
|
|
@@ -343,17 +377,25 @@ var GraphStore = class {
|
|
|
343
377
|
if (!data) return false;
|
|
344
378
|
this.clear();
|
|
345
379
|
for (const node of data.nodes) {
|
|
346
|
-
this.
|
|
380
|
+
this.nodeMap.set(node.id, { ...node });
|
|
347
381
|
}
|
|
348
382
|
for (const edge of data.edges) {
|
|
349
|
-
|
|
383
|
+
const copy = { ...edge };
|
|
384
|
+
const key = edgeKey(edge.from, edge.to, edge.type);
|
|
385
|
+
this.edgeMap.set(key, copy);
|
|
386
|
+
addToIndex(this.edgesByFrom, edge.from, copy);
|
|
387
|
+
addToIndex(this.edgesByTo, edge.to, copy);
|
|
388
|
+
addToIndex(this.edgesByType, edge.type, copy);
|
|
350
389
|
}
|
|
351
390
|
return true;
|
|
352
391
|
}
|
|
353
392
|
// --- Internal ---
|
|
354
|
-
|
|
355
|
-
const
|
|
356
|
-
|
|
393
|
+
removeEdgeInternal(edge) {
|
|
394
|
+
const key = edgeKey(edge.from, edge.to, edge.type);
|
|
395
|
+
this.edgeMap.delete(key);
|
|
396
|
+
removeFromIndex(this.edgesByFrom, edge.from, edge);
|
|
397
|
+
removeFromIndex(this.edgesByTo, edge.to, edge);
|
|
398
|
+
removeFromIndex(this.edgesByType, edge.type, edge);
|
|
357
399
|
}
|
|
358
400
|
};
|
|
359
401
|
|
|
@@ -434,11 +476,11 @@ var VectorStore = class _VectorStore {
|
|
|
434
476
|
};
|
|
435
477
|
|
|
436
478
|
// src/query/ContextQL.ts
|
|
437
|
-
function
|
|
479
|
+
function edgeKey2(e) {
|
|
438
480
|
return `${e.from}|${e.to}|${e.type}`;
|
|
439
481
|
}
|
|
440
482
|
function addEdge(state, edge) {
|
|
441
|
-
const key =
|
|
483
|
+
const key = edgeKey2(edge);
|
|
442
484
|
if (!state.edgeSet.has(key)) {
|
|
443
485
|
state.edgeSet.add(key);
|
|
444
486
|
state.resultEdges.push(edge);
|
|
@@ -618,6 +660,7 @@ var CodeIngestor = class {
|
|
|
618
660
|
constructor(store) {
|
|
619
661
|
this.store = store;
|
|
620
662
|
}
|
|
663
|
+
store;
|
|
621
664
|
async ingest(rootDir) {
|
|
622
665
|
const start = Date.now();
|
|
623
666
|
const errors = [];
|
|
@@ -640,6 +683,7 @@ var CodeIngestor = class {
|
|
|
640
683
|
this.store.addEdge(edge);
|
|
641
684
|
edgesAdded++;
|
|
642
685
|
}
|
|
686
|
+
edgesAdded += this.extractReqAnnotations(fileContents, rootDir);
|
|
643
687
|
return {
|
|
644
688
|
nodesAdded,
|
|
645
689
|
nodesUpdated: 0,
|
|
@@ -998,6 +1042,48 @@ var CodeIngestor = class {
|
|
|
998
1042
|
if (/\.jsx?$/.test(filePath)) return "javascript";
|
|
999
1043
|
return "unknown";
|
|
1000
1044
|
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Scan file contents for @req annotations and create verified_by edges
|
|
1047
|
+
* linking requirement nodes to the annotated files.
|
|
1048
|
+
* Format: // @req <feature-name>#<index>
|
|
1049
|
+
*/
|
|
1050
|
+
extractReqAnnotations(fileContents, rootDir) {
|
|
1051
|
+
const REQ_TAG = /\/\/\s*@req\s+([\w-]+)#(\d+)/g;
|
|
1052
|
+
const reqNodes = this.store.findNodes({ type: "requirement" });
|
|
1053
|
+
let edgesAdded = 0;
|
|
1054
|
+
for (const [filePath, content] of fileContents) {
|
|
1055
|
+
let match;
|
|
1056
|
+
REQ_TAG.lastIndex = 0;
|
|
1057
|
+
while ((match = REQ_TAG.exec(content)) !== null) {
|
|
1058
|
+
const featureName = match[1];
|
|
1059
|
+
const reqIndex = parseInt(match[2], 10);
|
|
1060
|
+
const reqNode = reqNodes.find(
|
|
1061
|
+
(n) => n.metadata.featureName === featureName && n.metadata.index === reqIndex
|
|
1062
|
+
);
|
|
1063
|
+
if (!reqNode) {
|
|
1064
|
+
console.warn(
|
|
1065
|
+
`@req annotation references non-existent requirement: ${featureName}#${reqIndex} in ${filePath}`
|
|
1066
|
+
);
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
const relPath = path.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
1070
|
+
const fileNodeId = `file:${relPath}`;
|
|
1071
|
+
this.store.addEdge({
|
|
1072
|
+
from: reqNode.id,
|
|
1073
|
+
to: fileNodeId,
|
|
1074
|
+
type: "verified_by",
|
|
1075
|
+
confidence: 1,
|
|
1076
|
+
metadata: {
|
|
1077
|
+
method: "annotation",
|
|
1078
|
+
tag: `@req ${featureName}#${reqIndex}`,
|
|
1079
|
+
confidence: 1
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
edgesAdded++;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return edgesAdded;
|
|
1086
|
+
}
|
|
1001
1087
|
};
|
|
1002
1088
|
|
|
1003
1089
|
// src/ingest/GitIngestor.ts
|
|
@@ -1009,6 +1095,8 @@ var GitIngestor = class {
|
|
|
1009
1095
|
this.store = store;
|
|
1010
1096
|
this.gitRunner = gitRunner;
|
|
1011
1097
|
}
|
|
1098
|
+
store;
|
|
1099
|
+
gitRunner;
|
|
1012
1100
|
async ingest(rootDir) {
|
|
1013
1101
|
const start = Date.now();
|
|
1014
1102
|
const errors = [];
|
|
@@ -1188,6 +1276,7 @@ var TopologicalLinker = class {
|
|
|
1188
1276
|
constructor(store) {
|
|
1189
1277
|
this.store = store;
|
|
1190
1278
|
}
|
|
1279
|
+
store;
|
|
1191
1280
|
link() {
|
|
1192
1281
|
let edgesAdded = 0;
|
|
1193
1282
|
const files = this.store.findNodes({ type: "file" });
|
|
@@ -1285,6 +1374,7 @@ var KnowledgeIngestor = class {
|
|
|
1285
1374
|
constructor(store) {
|
|
1286
1375
|
this.store = store;
|
|
1287
1376
|
}
|
|
1377
|
+
store;
|
|
1288
1378
|
async ingestADRs(adrDir) {
|
|
1289
1379
|
const start = Date.now();
|
|
1290
1380
|
const errors = [];
|
|
@@ -1470,8 +1560,218 @@ var KnowledgeIngestor = class {
|
|
|
1470
1560
|
}
|
|
1471
1561
|
};
|
|
1472
1562
|
|
|
1473
|
-
// src/ingest/
|
|
1563
|
+
// src/ingest/RequirementIngestor.ts
|
|
1564
|
+
var fs3 = __toESM(require("fs/promises"));
|
|
1565
|
+
var path4 = __toESM(require("path"));
|
|
1566
|
+
var REQUIREMENT_SECTIONS = [
|
|
1567
|
+
"Observable Truths",
|
|
1568
|
+
"Success Criteria",
|
|
1569
|
+
"Acceptance Criteria"
|
|
1570
|
+
];
|
|
1571
|
+
var SECTION_HEADING_RE = /^#{2,3}\s+(.+)$/;
|
|
1572
|
+
var NUMBERED_ITEM_RE = /^\s*(\d+)\.\s+(.+)$/;
|
|
1573
|
+
function detectEarsPattern(text) {
|
|
1574
|
+
const lower = text.toLowerCase();
|
|
1575
|
+
if (/^if\b.+\bthen\b.+\bshall not\b/.test(lower)) return "unwanted";
|
|
1576
|
+
if (/^when\b/.test(lower)) return "event-driven";
|
|
1577
|
+
if (/^while\b/.test(lower)) return "state-driven";
|
|
1578
|
+
if (/^where\b/.test(lower)) return "optional";
|
|
1579
|
+
if (/^the\s+\w+\s+shall\b/.test(lower)) return "ubiquitous";
|
|
1580
|
+
return void 0;
|
|
1581
|
+
}
|
|
1474
1582
|
var CODE_NODE_TYPES2 = ["file", "function", "class", "method", "interface", "variable"];
|
|
1583
|
+
var RequirementIngestor = class {
|
|
1584
|
+
constructor(store) {
|
|
1585
|
+
this.store = store;
|
|
1586
|
+
}
|
|
1587
|
+
store;
|
|
1588
|
+
/**
|
|
1589
|
+
* Scan a specs directory for `<feature>/proposal.md` files,
|
|
1590
|
+
* extract numbered requirements from recognized sections,
|
|
1591
|
+
* and create requirement nodes with convention-based edges.
|
|
1592
|
+
*/
|
|
1593
|
+
async ingestSpecs(specsDir) {
|
|
1594
|
+
const start = Date.now();
|
|
1595
|
+
const errors = [];
|
|
1596
|
+
let nodesAdded = 0;
|
|
1597
|
+
let edgesAdded = 0;
|
|
1598
|
+
let featureDirs;
|
|
1599
|
+
try {
|
|
1600
|
+
const entries = await fs3.readdir(specsDir, { withFileTypes: true });
|
|
1601
|
+
featureDirs = entries.filter((e) => e.isDirectory()).map((e) => path4.join(specsDir, e.name));
|
|
1602
|
+
} catch {
|
|
1603
|
+
return emptyResult(Date.now() - start);
|
|
1604
|
+
}
|
|
1605
|
+
for (const featureDir of featureDirs) {
|
|
1606
|
+
const featureName = path4.basename(featureDir);
|
|
1607
|
+
const specPath = path4.join(featureDir, "proposal.md");
|
|
1608
|
+
let content;
|
|
1609
|
+
try {
|
|
1610
|
+
content = await fs3.readFile(specPath, "utf-8");
|
|
1611
|
+
} catch {
|
|
1612
|
+
continue;
|
|
1613
|
+
}
|
|
1614
|
+
try {
|
|
1615
|
+
const specHash = hash(specPath);
|
|
1616
|
+
const specNodeId = `file:${specPath}`;
|
|
1617
|
+
this.store.addNode({
|
|
1618
|
+
id: specNodeId,
|
|
1619
|
+
type: "document",
|
|
1620
|
+
name: path4.basename(specPath),
|
|
1621
|
+
path: specPath,
|
|
1622
|
+
metadata: { featureName }
|
|
1623
|
+
});
|
|
1624
|
+
const requirements = this.extractRequirements(content, specPath, specHash, featureName);
|
|
1625
|
+
for (const req of requirements) {
|
|
1626
|
+
this.store.addNode(req.node);
|
|
1627
|
+
nodesAdded++;
|
|
1628
|
+
this.store.addEdge({
|
|
1629
|
+
from: req.node.id,
|
|
1630
|
+
to: specNodeId,
|
|
1631
|
+
type: "specifies"
|
|
1632
|
+
});
|
|
1633
|
+
edgesAdded++;
|
|
1634
|
+
edgesAdded += this.linkByPathPattern(req.node.id, featureName);
|
|
1635
|
+
edgesAdded += this.linkByKeywordOverlap(req.node.id, req.node.name);
|
|
1636
|
+
}
|
|
1637
|
+
} catch (err) {
|
|
1638
|
+
errors.push(`${specPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
return {
|
|
1642
|
+
nodesAdded,
|
|
1643
|
+
nodesUpdated: 0,
|
|
1644
|
+
edgesAdded,
|
|
1645
|
+
edgesUpdated: 0,
|
|
1646
|
+
errors,
|
|
1647
|
+
durationMs: Date.now() - start
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Parse markdown content and extract numbered items from recognized sections.
|
|
1652
|
+
*/
|
|
1653
|
+
extractRequirements(content, specPath, specHash, featureName) {
|
|
1654
|
+
const lines = content.split("\n");
|
|
1655
|
+
const results = [];
|
|
1656
|
+
let currentSection;
|
|
1657
|
+
let inRequirementSection = false;
|
|
1658
|
+
let globalIndex = 0;
|
|
1659
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1660
|
+
const line = lines[i];
|
|
1661
|
+
const headingMatch = line.match(SECTION_HEADING_RE);
|
|
1662
|
+
if (headingMatch) {
|
|
1663
|
+
const heading = headingMatch[1].trim();
|
|
1664
|
+
const isReqSection = REQUIREMENT_SECTIONS.some(
|
|
1665
|
+
(s) => heading.toLowerCase() === s.toLowerCase()
|
|
1666
|
+
);
|
|
1667
|
+
if (isReqSection) {
|
|
1668
|
+
currentSection = heading;
|
|
1669
|
+
inRequirementSection = true;
|
|
1670
|
+
} else {
|
|
1671
|
+
inRequirementSection = false;
|
|
1672
|
+
}
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
if (!inRequirementSection) continue;
|
|
1676
|
+
const itemMatch = line.match(NUMBERED_ITEM_RE);
|
|
1677
|
+
if (!itemMatch) continue;
|
|
1678
|
+
const index = parseInt(itemMatch[1], 10);
|
|
1679
|
+
const text = itemMatch[2].trim();
|
|
1680
|
+
const rawText = line.trim();
|
|
1681
|
+
const lineNumber = i + 1;
|
|
1682
|
+
globalIndex++;
|
|
1683
|
+
const nodeId = `req:${specHash}:${globalIndex}`;
|
|
1684
|
+
const earsPattern = detectEarsPattern(text);
|
|
1685
|
+
results.push({
|
|
1686
|
+
node: {
|
|
1687
|
+
id: nodeId,
|
|
1688
|
+
type: "requirement",
|
|
1689
|
+
name: text,
|
|
1690
|
+
path: specPath,
|
|
1691
|
+
location: {
|
|
1692
|
+
fileId: `file:${specPath}`,
|
|
1693
|
+
startLine: lineNumber,
|
|
1694
|
+
endLine: lineNumber
|
|
1695
|
+
},
|
|
1696
|
+
metadata: {
|
|
1697
|
+
specPath,
|
|
1698
|
+
index,
|
|
1699
|
+
section: currentSection,
|
|
1700
|
+
rawText,
|
|
1701
|
+
earsPattern,
|
|
1702
|
+
featureName
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
return results;
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Convention-based linking: match requirement to code/test files
|
|
1711
|
+
* by feature name in their path.
|
|
1712
|
+
*/
|
|
1713
|
+
linkByPathPattern(reqId, featureName) {
|
|
1714
|
+
let count = 0;
|
|
1715
|
+
const fileNodes = this.store.findNodes({ type: "file" });
|
|
1716
|
+
for (const node of fileNodes) {
|
|
1717
|
+
if (!node.path) continue;
|
|
1718
|
+
const normalizedPath = node.path.replace(/\\/g, "/");
|
|
1719
|
+
const isCodeMatch = normalizedPath.includes("packages/") && path4.basename(normalizedPath).includes(featureName);
|
|
1720
|
+
const isTestMatch = normalizedPath.includes("/tests/") && // platform-safe
|
|
1721
|
+
path4.basename(normalizedPath).includes(featureName);
|
|
1722
|
+
if (isCodeMatch && !isTestMatch) {
|
|
1723
|
+
this.store.addEdge({
|
|
1724
|
+
from: reqId,
|
|
1725
|
+
to: node.id,
|
|
1726
|
+
type: "requires",
|
|
1727
|
+
confidence: 0.5,
|
|
1728
|
+
metadata: { method: "convention", matchReason: "path-pattern" }
|
|
1729
|
+
});
|
|
1730
|
+
count++;
|
|
1731
|
+
} else if (isTestMatch) {
|
|
1732
|
+
this.store.addEdge({
|
|
1733
|
+
from: reqId,
|
|
1734
|
+
to: node.id,
|
|
1735
|
+
type: "verified_by",
|
|
1736
|
+
confidence: 0.5,
|
|
1737
|
+
metadata: { method: "convention", matchReason: "path-pattern" }
|
|
1738
|
+
});
|
|
1739
|
+
count++;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
return count;
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Convention-based linking: match requirement text to code nodes
|
|
1746
|
+
* by keyword overlap (function/class names appearing in requirement text).
|
|
1747
|
+
*/
|
|
1748
|
+
linkByKeywordOverlap(reqId, reqText) {
|
|
1749
|
+
let count = 0;
|
|
1750
|
+
for (const nodeType of CODE_NODE_TYPES2) {
|
|
1751
|
+
const codeNodes = this.store.findNodes({ type: nodeType });
|
|
1752
|
+
for (const node of codeNodes) {
|
|
1753
|
+
if (node.name.length < 3) continue;
|
|
1754
|
+
const escaped = node.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1755
|
+
const namePattern = new RegExp(`\\b${escaped}\\b`, "i");
|
|
1756
|
+
if (namePattern.test(reqText)) {
|
|
1757
|
+
const edgeType = node.path && node.path.replace(/\\/g, "/").includes("/tests/") ? "verified_by" : "requires";
|
|
1758
|
+
this.store.addEdge({
|
|
1759
|
+
from: reqId,
|
|
1760
|
+
to: node.id,
|
|
1761
|
+
type: edgeType,
|
|
1762
|
+
confidence: 0.6,
|
|
1763
|
+
metadata: { method: "convention", matchReason: "keyword-overlap" }
|
|
1764
|
+
});
|
|
1765
|
+
count++;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
return count;
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
|
|
1773
|
+
// src/ingest/connectors/ConnectorUtils.ts
|
|
1774
|
+
var CODE_NODE_TYPES3 = ["file", "function", "class", "method", "interface", "variable"];
|
|
1475
1775
|
var SANITIZE_RULES = [
|
|
1476
1776
|
// Strip XML/HTML-like instruction tags that could be interpreted as system prompts
|
|
1477
1777
|
{
|
|
@@ -1506,7 +1806,7 @@ function sanitizeExternalText(text, maxLength = 2e3) {
|
|
|
1506
1806
|
}
|
|
1507
1807
|
function linkToCode(store, content, sourceNodeId, edgeType, options) {
|
|
1508
1808
|
let edgesCreated = 0;
|
|
1509
|
-
for (const type of
|
|
1809
|
+
for (const type of CODE_NODE_TYPES3) {
|
|
1510
1810
|
const nodes = store.findNodes({ type });
|
|
1511
1811
|
for (const node of nodes) {
|
|
1512
1812
|
if (node.name.length < 3) continue;
|
|
@@ -1526,13 +1826,14 @@ function linkToCode(store, content, sourceNodeId, edgeType, options) {
|
|
|
1526
1826
|
}
|
|
1527
1827
|
|
|
1528
1828
|
// src/ingest/connectors/SyncManager.ts
|
|
1529
|
-
var
|
|
1530
|
-
var
|
|
1829
|
+
var fs4 = __toESM(require("fs/promises"));
|
|
1830
|
+
var path5 = __toESM(require("path"));
|
|
1531
1831
|
var SyncManager = class {
|
|
1532
1832
|
constructor(store, graphDir) {
|
|
1533
1833
|
this.store = store;
|
|
1534
|
-
this.metadataPath =
|
|
1834
|
+
this.metadataPath = path5.join(graphDir, "sync-metadata.json");
|
|
1535
1835
|
}
|
|
1836
|
+
store;
|
|
1536
1837
|
registrations = /* @__PURE__ */ new Map();
|
|
1537
1838
|
metadataPath;
|
|
1538
1839
|
registerConnector(connector, config) {
|
|
@@ -1585,15 +1886,15 @@ var SyncManager = class {
|
|
|
1585
1886
|
}
|
|
1586
1887
|
async loadMetadata() {
|
|
1587
1888
|
try {
|
|
1588
|
-
const raw = await
|
|
1889
|
+
const raw = await fs4.readFile(this.metadataPath, "utf-8");
|
|
1589
1890
|
return JSON.parse(raw);
|
|
1590
1891
|
} catch {
|
|
1591
1892
|
return { connectors: {} };
|
|
1592
1893
|
}
|
|
1593
1894
|
}
|
|
1594
1895
|
async saveMetadata(metadata) {
|
|
1595
|
-
await
|
|
1596
|
-
await
|
|
1896
|
+
await fs4.mkdir(path5.dirname(this.metadataPath), { recursive: true });
|
|
1897
|
+
await fs4.writeFile(this.metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
1597
1898
|
}
|
|
1598
1899
|
};
|
|
1599
1900
|
|
|
@@ -2122,11 +2423,12 @@ var FusionLayer = class {
|
|
|
2122
2423
|
};
|
|
2123
2424
|
|
|
2124
2425
|
// src/entropy/GraphEntropyAdapter.ts
|
|
2125
|
-
var
|
|
2426
|
+
var CODE_NODE_TYPES4 = ["file", "function", "class", "method", "interface", "variable"];
|
|
2126
2427
|
var GraphEntropyAdapter = class {
|
|
2127
2428
|
constructor(store) {
|
|
2128
2429
|
this.store = store;
|
|
2129
2430
|
}
|
|
2431
|
+
store;
|
|
2130
2432
|
/**
|
|
2131
2433
|
* Find all `documents` edges and classify them as stale or missing-target.
|
|
2132
2434
|
*
|
|
@@ -2188,7 +2490,7 @@ var GraphEntropyAdapter = class {
|
|
|
2188
2490
|
}
|
|
2189
2491
|
findEntryPoints() {
|
|
2190
2492
|
const entryPoints = [];
|
|
2191
|
-
for (const nodeType of
|
|
2493
|
+
for (const nodeType of CODE_NODE_TYPES4) {
|
|
2192
2494
|
const nodes = this.store.findNodes({ type: nodeType });
|
|
2193
2495
|
for (const node of nodes) {
|
|
2194
2496
|
const isIndexFile = nodeType === "file" && node.name === "index.ts";
|
|
@@ -2224,7 +2526,7 @@ var GraphEntropyAdapter = class {
|
|
|
2224
2526
|
}
|
|
2225
2527
|
collectUnreachableNodes(visited) {
|
|
2226
2528
|
const unreachableNodes = [];
|
|
2227
|
-
for (const nodeType of
|
|
2529
|
+
for (const nodeType of CODE_NODE_TYPES4) {
|
|
2228
2530
|
const nodes = this.store.findNodes({ type: nodeType });
|
|
2229
2531
|
for (const node of nodes) {
|
|
2230
2532
|
if (!visited.has(node.id)) {
|
|
@@ -2267,6 +2569,7 @@ var GraphComplexityAdapter = class {
|
|
|
2267
2569
|
constructor(store) {
|
|
2268
2570
|
this.store = store;
|
|
2269
2571
|
}
|
|
2572
|
+
store;
|
|
2270
2573
|
/**
|
|
2271
2574
|
* Compute complexity hotspots by combining cyclomatic complexity with change frequency.
|
|
2272
2575
|
*
|
|
@@ -2354,6 +2657,7 @@ var GraphCouplingAdapter = class {
|
|
|
2354
2657
|
constructor(store) {
|
|
2355
2658
|
this.store = store;
|
|
2356
2659
|
}
|
|
2660
|
+
store;
|
|
2357
2661
|
/**
|
|
2358
2662
|
* Compute coupling data for all file nodes in the graph.
|
|
2359
2663
|
*
|
|
@@ -2423,6 +2727,7 @@ var GraphAnomalyAdapter = class {
|
|
|
2423
2727
|
constructor(store) {
|
|
2424
2728
|
this.store = store;
|
|
2425
2729
|
}
|
|
2730
|
+
store;
|
|
2426
2731
|
detect(options) {
|
|
2427
2732
|
const threshold = options?.threshold != null && options.threshold > 0 ? options.threshold : DEFAULT_THRESHOLD;
|
|
2428
2733
|
const requestedMetrics = options?.metrics ?? [...DEFAULT_METRICS];
|
|
@@ -3041,9 +3346,9 @@ var EntityExtractor = class {
|
|
|
3041
3346
|
}
|
|
3042
3347
|
const pathConsumed = /* @__PURE__ */ new Set();
|
|
3043
3348
|
for (const match of trimmed.matchAll(FILE_PATH_RE)) {
|
|
3044
|
-
const
|
|
3045
|
-
add(
|
|
3046
|
-
pathConsumed.add(
|
|
3349
|
+
const path7 = match[0];
|
|
3350
|
+
add(path7);
|
|
3351
|
+
pathConsumed.add(path7);
|
|
3047
3352
|
}
|
|
3048
3353
|
const allConsumed = buildConsumedSet(quotedConsumed, casingConsumed, pathConsumed);
|
|
3049
3354
|
const words = trimmed.split(/\s+/);
|
|
@@ -3114,8 +3419,8 @@ var EntityResolver = class {
|
|
|
3114
3419
|
if (isPathLike && node.path.includes(raw)) {
|
|
3115
3420
|
return { raw, nodeId: node.id, node, confidence: 0.6, method: "path" };
|
|
3116
3421
|
}
|
|
3117
|
-
const
|
|
3118
|
-
if (
|
|
3422
|
+
const basename5 = node.path.split("/").pop() ?? "";
|
|
3423
|
+
if (basename5.includes(raw)) {
|
|
3119
3424
|
return { raw, nodeId: node.id, node, confidence: 0.6, method: "path" };
|
|
3120
3425
|
}
|
|
3121
3426
|
if (raw.length >= 4 && node.path.includes(raw)) {
|
|
@@ -3190,13 +3495,13 @@ var ResponseFormatter = class {
|
|
|
3190
3495
|
const context = Array.isArray(d?.context) ? d.context : [];
|
|
3191
3496
|
const firstEntity = entities[0];
|
|
3192
3497
|
const nodeType = firstEntity?.node.type ?? "node";
|
|
3193
|
-
const
|
|
3498
|
+
const path7 = firstEntity?.node.path ?? "unknown";
|
|
3194
3499
|
let neighborCount = 0;
|
|
3195
3500
|
const firstContext = context[0];
|
|
3196
3501
|
if (firstContext && Array.isArray(firstContext.nodes)) {
|
|
3197
3502
|
neighborCount = firstContext.nodes.length;
|
|
3198
3503
|
}
|
|
3199
|
-
return `**${entityName}** is a ${nodeType} at \`${
|
|
3504
|
+
return `**${entityName}** is a ${nodeType} at \`${path7}\`. Connected to ${neighborCount} nodes.`;
|
|
3200
3505
|
}
|
|
3201
3506
|
formatAnomaly(data) {
|
|
3202
3507
|
const d = data;
|
|
@@ -3337,7 +3642,7 @@ var PHASE_NODE_TYPES = {
|
|
|
3337
3642
|
debug: ["failure", "learning", "function", "method"],
|
|
3338
3643
|
plan: ["adr", "document", "module", "layer"]
|
|
3339
3644
|
};
|
|
3340
|
-
var
|
|
3645
|
+
var CODE_NODE_TYPES5 = /* @__PURE__ */ new Set([
|
|
3341
3646
|
"file",
|
|
3342
3647
|
"function",
|
|
3343
3648
|
"class",
|
|
@@ -3552,7 +3857,7 @@ var Assembler = class {
|
|
|
3552
3857
|
*/
|
|
3553
3858
|
checkCoverage() {
|
|
3554
3859
|
const codeNodes = [];
|
|
3555
|
-
for (const type of
|
|
3860
|
+
for (const type of CODE_NODE_TYPES5) {
|
|
3556
3861
|
codeNodes.push(...this.store.findNodes({ type }));
|
|
3557
3862
|
}
|
|
3558
3863
|
const documented = [];
|
|
@@ -3576,6 +3881,89 @@ var Assembler = class {
|
|
|
3576
3881
|
}
|
|
3577
3882
|
};
|
|
3578
3883
|
|
|
3884
|
+
// src/query/Traceability.ts
|
|
3885
|
+
function queryTraceability(store, options) {
|
|
3886
|
+
const allRequirements = store.findNodes({ type: "requirement" });
|
|
3887
|
+
const filtered = allRequirements.filter((node) => {
|
|
3888
|
+
if (options?.specPath && node.metadata?.specPath !== options.specPath) return false;
|
|
3889
|
+
if (options?.featureName && node.metadata?.featureName !== options.featureName) return false;
|
|
3890
|
+
return true;
|
|
3891
|
+
});
|
|
3892
|
+
if (filtered.length === 0) return [];
|
|
3893
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3894
|
+
for (const req of filtered) {
|
|
3895
|
+
const meta = req.metadata;
|
|
3896
|
+
const specPath = meta?.specPath ?? "";
|
|
3897
|
+
const featureName = meta?.featureName ?? "";
|
|
3898
|
+
const key = `${specPath}\0${featureName}`;
|
|
3899
|
+
const list = groups.get(key);
|
|
3900
|
+
if (list) {
|
|
3901
|
+
list.push(req);
|
|
3902
|
+
} else {
|
|
3903
|
+
groups.set(key, [req]);
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
const results = [];
|
|
3907
|
+
for (const [, reqs] of groups) {
|
|
3908
|
+
const firstReq = reqs[0];
|
|
3909
|
+
const firstMeta = firstReq.metadata;
|
|
3910
|
+
const specPath = firstMeta?.specPath ?? "";
|
|
3911
|
+
const featureName = firstMeta?.featureName ?? "";
|
|
3912
|
+
const requirements = [];
|
|
3913
|
+
for (const req of reqs) {
|
|
3914
|
+
const requiresEdges = store.getEdges({ from: req.id, type: "requires" });
|
|
3915
|
+
const codeFiles = requiresEdges.map((edge) => {
|
|
3916
|
+
const targetNode = store.getNode(edge.to);
|
|
3917
|
+
return {
|
|
3918
|
+
path: targetNode?.path ?? edge.to,
|
|
3919
|
+
confidence: edge.confidence ?? edge.metadata?.confidence ?? 0,
|
|
3920
|
+
method: edge.metadata?.method ?? "convention"
|
|
3921
|
+
};
|
|
3922
|
+
});
|
|
3923
|
+
const verifiedByEdges = store.getEdges({ from: req.id, type: "verified_by" });
|
|
3924
|
+
const testFiles = verifiedByEdges.map((edge) => {
|
|
3925
|
+
const targetNode = store.getNode(edge.to);
|
|
3926
|
+
return {
|
|
3927
|
+
path: targetNode?.path ?? edge.to,
|
|
3928
|
+
confidence: edge.confidence ?? edge.metadata?.confidence ?? 0,
|
|
3929
|
+
method: edge.metadata?.method ?? "convention"
|
|
3930
|
+
};
|
|
3931
|
+
});
|
|
3932
|
+
const hasCode = codeFiles.length > 0;
|
|
3933
|
+
const hasTests = testFiles.length > 0;
|
|
3934
|
+
const status = hasCode && hasTests ? "full" : hasCode ? "code-only" : hasTests ? "test-only" : "none";
|
|
3935
|
+
const allConfidences = [
|
|
3936
|
+
...codeFiles.map((f) => f.confidence),
|
|
3937
|
+
...testFiles.map((f) => f.confidence)
|
|
3938
|
+
];
|
|
3939
|
+
const maxConfidence = allConfidences.length > 0 ? Math.max(...allConfidences) : 0;
|
|
3940
|
+
requirements.push({
|
|
3941
|
+
requirementId: req.id,
|
|
3942
|
+
requirementName: req.name,
|
|
3943
|
+
index: req.metadata?.index ?? 0,
|
|
3944
|
+
codeFiles,
|
|
3945
|
+
testFiles,
|
|
3946
|
+
status,
|
|
3947
|
+
maxConfidence
|
|
3948
|
+
});
|
|
3949
|
+
}
|
|
3950
|
+
requirements.sort((a, b) => a.index - b.index);
|
|
3951
|
+
const total = requirements.length;
|
|
3952
|
+
const withCode = requirements.filter((r) => r.codeFiles.length > 0).length;
|
|
3953
|
+
const withTests = requirements.filter((r) => r.testFiles.length > 0).length;
|
|
3954
|
+
const fullyTraced = requirements.filter((r) => r.status === "full").length;
|
|
3955
|
+
const untraceable = requirements.filter((r) => r.status === "none").length;
|
|
3956
|
+
const coveragePercent = total > 0 ? Math.round(fullyTraced / total * 100) : 0;
|
|
3957
|
+
results.push({
|
|
3958
|
+
specPath,
|
|
3959
|
+
featureName,
|
|
3960
|
+
requirements,
|
|
3961
|
+
summary: { total, withCode, withTests, fullyTraced, untraceable, coveragePercent }
|
|
3962
|
+
});
|
|
3963
|
+
}
|
|
3964
|
+
return results;
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3579
3967
|
// src/constraints/GraphConstraintAdapter.ts
|
|
3580
3968
|
var import_minimatch = require("minimatch");
|
|
3581
3969
|
var import_node_path2 = require("path");
|
|
@@ -3583,6 +3971,7 @@ var GraphConstraintAdapter = class {
|
|
|
3583
3971
|
constructor(store) {
|
|
3584
3972
|
this.store = store;
|
|
3585
3973
|
}
|
|
3974
|
+
store;
|
|
3586
3975
|
computeDependencyGraph() {
|
|
3587
3976
|
const fileNodes = this.store.findNodes({ type: "file" });
|
|
3588
3977
|
const nodes = fileNodes.map((n) => n.path ?? n.id);
|
|
@@ -3634,14 +4023,14 @@ var GraphConstraintAdapter = class {
|
|
|
3634
4023
|
};
|
|
3635
4024
|
|
|
3636
4025
|
// src/ingest/DesignIngestor.ts
|
|
3637
|
-
var
|
|
3638
|
-
var
|
|
4026
|
+
var fs5 = __toESM(require("fs/promises"));
|
|
4027
|
+
var path6 = __toESM(require("path"));
|
|
3639
4028
|
function isDTCGToken(obj) {
|
|
3640
4029
|
return typeof obj === "object" && obj !== null && "$value" in obj && "$type" in obj;
|
|
3641
4030
|
}
|
|
3642
4031
|
async function readFileOrNull(filePath) {
|
|
3643
4032
|
try {
|
|
3644
|
-
return await
|
|
4033
|
+
return await fs5.readFile(filePath, "utf-8");
|
|
3645
4034
|
} catch {
|
|
3646
4035
|
return null;
|
|
3647
4036
|
}
|
|
@@ -3727,6 +4116,7 @@ var DesignIngestor = class {
|
|
|
3727
4116
|
constructor(store) {
|
|
3728
4117
|
this.store = store;
|
|
3729
4118
|
}
|
|
4119
|
+
store;
|
|
3730
4120
|
async ingestTokens(tokensPath) {
|
|
3731
4121
|
const start = Date.now();
|
|
3732
4122
|
const content = await readFileOrNull(tokensPath);
|
|
@@ -3786,8 +4176,8 @@ var DesignIngestor = class {
|
|
|
3786
4176
|
async ingestAll(designDir) {
|
|
3787
4177
|
const start = Date.now();
|
|
3788
4178
|
const [tokensResult, intentResult] = await Promise.all([
|
|
3789
|
-
this.ingestTokens(
|
|
3790
|
-
this.ingestDesignIntent(
|
|
4179
|
+
this.ingestTokens(path6.join(designDir, "tokens.json")),
|
|
4180
|
+
this.ingestDesignIntent(path6.join(designDir, "DESIGN.md"))
|
|
3791
4181
|
]);
|
|
3792
4182
|
const merged = mergeResults(tokensResult, intentResult);
|
|
3793
4183
|
return { ...merged, durationMs: Date.now() - start };
|
|
@@ -3799,6 +4189,7 @@ var DesignConstraintAdapter = class {
|
|
|
3799
4189
|
constructor(store) {
|
|
3800
4190
|
this.store = store;
|
|
3801
4191
|
}
|
|
4192
|
+
store;
|
|
3802
4193
|
checkForHardcodedColors(source, file, strictness) {
|
|
3803
4194
|
const severity = this.mapSeverity(strictness);
|
|
3804
4195
|
const tokenNodes = this.store.findNodes({ type: "design_token" });
|
|
@@ -3882,6 +4273,7 @@ var GraphFeedbackAdapter = class {
|
|
|
3882
4273
|
constructor(store) {
|
|
3883
4274
|
this.store = store;
|
|
3884
4275
|
}
|
|
4276
|
+
store;
|
|
3885
4277
|
computeImpactData(changedFiles) {
|
|
3886
4278
|
const affectedTests = [];
|
|
3887
4279
|
const affectedDocs = [];
|
|
@@ -4034,10 +4426,10 @@ var TaskIndependenceAnalyzer = class {
|
|
|
4034
4426
|
includeTypes: ["file"]
|
|
4035
4427
|
});
|
|
4036
4428
|
for (const n of queryResult.nodes) {
|
|
4037
|
-
const
|
|
4038
|
-
if (!fileSet.has(
|
|
4039
|
-
if (!result.has(
|
|
4040
|
-
result.set(
|
|
4429
|
+
const path7 = n.path ?? n.id.replace(/^file:/, "");
|
|
4430
|
+
if (!fileSet.has(path7)) {
|
|
4431
|
+
if (!result.has(path7)) {
|
|
4432
|
+
result.set(path7, file);
|
|
4041
4433
|
}
|
|
4042
4434
|
}
|
|
4043
4435
|
}
|
|
@@ -4422,6 +4814,7 @@ var VERSION = "0.2.0";
|
|
|
4422
4814
|
KnowledgeIngestor,
|
|
4423
4815
|
NODE_TYPES,
|
|
4424
4816
|
OBSERVABILITY_TYPES,
|
|
4817
|
+
RequirementIngestor,
|
|
4425
4818
|
ResponseFormatter,
|
|
4426
4819
|
SlackConnector,
|
|
4427
4820
|
SyncManager,
|
|
@@ -4434,5 +4827,6 @@ var VERSION = "0.2.0";
|
|
|
4434
4827
|
linkToCode,
|
|
4435
4828
|
loadGraph,
|
|
4436
4829
|
project,
|
|
4830
|
+
queryTraceability,
|
|
4437
4831
|
saveGraph
|
|
4438
4832
|
});
|