@harness-engineering/graph 0.2.1 → 0.2.3
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 +35 -9
- package/dist/index.d.ts +35 -9
- package/dist/index.js +308 -17
- package/dist/index.mjs +306 -17
- package/package.json +15 -3
- package/dist/.tsbuildinfo +0 -1
package/dist/index.js
CHANGED
|
@@ -36,6 +36,8 @@ __export(index_exports, {
|
|
|
36
36
|
CodeIngestor: () => CodeIngestor,
|
|
37
37
|
ConfluenceConnector: () => ConfluenceConnector,
|
|
38
38
|
ContextQL: () => ContextQL,
|
|
39
|
+
DesignConstraintAdapter: () => DesignConstraintAdapter,
|
|
40
|
+
DesignIngestor: () => DesignIngestor,
|
|
39
41
|
EDGE_TYPES: () => EDGE_TYPES,
|
|
40
42
|
FusionLayer: () => FusionLayer,
|
|
41
43
|
GitIngestor: () => GitIngestor,
|
|
@@ -96,7 +98,11 @@ var NODE_TYPES = [
|
|
|
96
98
|
"layer",
|
|
97
99
|
"pattern",
|
|
98
100
|
"constraint",
|
|
99
|
-
"violation"
|
|
101
|
+
"violation",
|
|
102
|
+
// Design
|
|
103
|
+
"design_token",
|
|
104
|
+
"aesthetic_intent",
|
|
105
|
+
"design_constraint"
|
|
100
106
|
];
|
|
101
107
|
var EDGE_TYPES = [
|
|
102
108
|
// Code relationships
|
|
@@ -120,7 +126,12 @@ var EDGE_TYPES = [
|
|
|
120
126
|
"failed_in",
|
|
121
127
|
// Execution relationships (future)
|
|
122
128
|
"executed_by",
|
|
123
|
-
"measured_by"
|
|
129
|
+
"measured_by",
|
|
130
|
+
// Design relationships
|
|
131
|
+
"uses_token",
|
|
132
|
+
"declares_intent",
|
|
133
|
+
"violates_design",
|
|
134
|
+
"platform_binding"
|
|
124
135
|
];
|
|
125
136
|
var OBSERVABILITY_TYPES = /* @__PURE__ */ new Set(["span", "metric", "log"]);
|
|
126
137
|
var CURRENT_SCHEMA_VERSION = 1;
|
|
@@ -189,6 +200,14 @@ async function loadGraph(dirPath) {
|
|
|
189
200
|
}
|
|
190
201
|
|
|
191
202
|
// src/store/GraphStore.ts
|
|
203
|
+
var POISONED_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
204
|
+
function safeMerge(target, source) {
|
|
205
|
+
for (const key of Object.keys(source)) {
|
|
206
|
+
if (!POISONED_KEYS.has(key)) {
|
|
207
|
+
target[key] = source[key];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
192
211
|
var GraphStore = class {
|
|
193
212
|
db;
|
|
194
213
|
nodes;
|
|
@@ -207,7 +226,7 @@ var GraphStore = class {
|
|
|
207
226
|
addNode(node) {
|
|
208
227
|
const existing = this.nodes.by("id", node.id);
|
|
209
228
|
if (existing) {
|
|
210
|
-
|
|
229
|
+
safeMerge(existing, node);
|
|
211
230
|
this.nodes.update(existing);
|
|
212
231
|
} else {
|
|
213
232
|
this.nodes.insert({ ...node });
|
|
@@ -251,7 +270,7 @@ var GraphStore = class {
|
|
|
251
270
|
});
|
|
252
271
|
if (existing) {
|
|
253
272
|
if (edge.metadata) {
|
|
254
|
-
|
|
273
|
+
safeMerge(existing, edge);
|
|
255
274
|
this.edges.update(existing);
|
|
256
275
|
}
|
|
257
276
|
return;
|
|
@@ -531,7 +550,7 @@ var CodeIngestor = class {
|
|
|
531
550
|
const fileContents = /* @__PURE__ */ new Map();
|
|
532
551
|
for (const filePath of files) {
|
|
533
552
|
try {
|
|
534
|
-
const relativePath = path.relative(rootDir, filePath);
|
|
553
|
+
const relativePath = path.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
535
554
|
const content = await fs.readFile(filePath, "utf-8");
|
|
536
555
|
const stat2 = await fs.stat(filePath);
|
|
537
556
|
const fileId = `file:${relativePath}`;
|
|
@@ -821,7 +840,7 @@ var CodeIngestor = class {
|
|
|
821
840
|
}
|
|
822
841
|
async resolveImportPath(fromFile, importPath, rootDir) {
|
|
823
842
|
const fromDir = path.dirname(fromFile);
|
|
824
|
-
const resolved = path.normalize(path.join(fromDir, importPath));
|
|
843
|
+
const resolved = path.normalize(path.join(fromDir, importPath)).replace(/\\/g, "/");
|
|
825
844
|
const extensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
826
845
|
for (const ext of extensions) {
|
|
827
846
|
const candidate = resolved.replace(/\.js$/, "") + ext;
|
|
@@ -833,7 +852,7 @@ var CodeIngestor = class {
|
|
|
833
852
|
}
|
|
834
853
|
}
|
|
835
854
|
for (const ext of extensions) {
|
|
836
|
-
const candidate = path.join(resolved, `index${ext}`);
|
|
855
|
+
const candidate = path.join(resolved, `index${ext}`).replace(/\\/g, "/");
|
|
837
856
|
const fullPath = path.join(rootDir, candidate);
|
|
838
857
|
try {
|
|
839
858
|
await fs.access(fullPath);
|
|
@@ -1149,10 +1168,11 @@ var TopologicalLinker = class {
|
|
|
1149
1168
|
// src/ingest/KnowledgeIngestor.ts
|
|
1150
1169
|
var fs2 = __toESM(require("fs/promises"));
|
|
1151
1170
|
var path3 = __toESM(require("path"));
|
|
1171
|
+
|
|
1172
|
+
// src/ingest/ingestUtils.ts
|
|
1152
1173
|
var crypto = __toESM(require("crypto"));
|
|
1153
|
-
var CODE_NODE_TYPES = ["file", "function", "class", "method", "interface", "variable"];
|
|
1154
1174
|
function hash(text) {
|
|
1155
|
-
return crypto.createHash("
|
|
1175
|
+
return crypto.createHash("sha256").update(text).digest("hex").slice(0, 8);
|
|
1156
1176
|
}
|
|
1157
1177
|
function mergeResults(...results) {
|
|
1158
1178
|
return {
|
|
@@ -1167,6 +1187,9 @@ function mergeResults(...results) {
|
|
|
1167
1187
|
function emptyResult(durationMs = 0) {
|
|
1168
1188
|
return { nodesAdded: 0, nodesUpdated: 0, edgesAdded: 0, edgesUpdated: 0, errors: [], durationMs };
|
|
1169
1189
|
}
|
|
1190
|
+
|
|
1191
|
+
// src/ingest/KnowledgeIngestor.ts
|
|
1192
|
+
var CODE_NODE_TYPES = ["file", "function", "class", "method", "interface", "variable"];
|
|
1170
1193
|
var KnowledgeIngestor = class {
|
|
1171
1194
|
constructor(store) {
|
|
1172
1195
|
this.store = store;
|
|
@@ -1358,6 +1381,22 @@ var KnowledgeIngestor = class {
|
|
|
1358
1381
|
|
|
1359
1382
|
// src/ingest/connectors/ConnectorUtils.ts
|
|
1360
1383
|
var CODE_NODE_TYPES2 = ["file", "function", "class", "method", "interface", "variable"];
|
|
1384
|
+
function sanitizeExternalText(text, maxLength = 2e3) {
|
|
1385
|
+
let sanitized = text.replace(
|
|
1386
|
+
/<\/?(?:system|instruction|prompt|role|context|tool_call|function_call|assistant|human|user)[^>]*>/gi,
|
|
1387
|
+
""
|
|
1388
|
+
).replace(/^#{1,3}\s*(?:system|instruction|prompt)\s*[::]\s*/gim, "").replace(
|
|
1389
|
+
/(?:ignore|disregard|forget)\s+(?:all\s+)?(?:previous|prior|above)\s+(?:instructions?|prompts?|context)/gi,
|
|
1390
|
+
"[filtered]"
|
|
1391
|
+
).replace(
|
|
1392
|
+
/you\s+are\s+now\s+(?:a\s+)?(?:helpful\s+)?(?:an?\s+)?(?:assistant|system|ai|bot|agent|tool)\b/gi,
|
|
1393
|
+
"[filtered]"
|
|
1394
|
+
);
|
|
1395
|
+
if (sanitized.length > maxLength) {
|
|
1396
|
+
sanitized = sanitized.slice(0, maxLength) + "\u2026";
|
|
1397
|
+
}
|
|
1398
|
+
return sanitized;
|
|
1399
|
+
}
|
|
1361
1400
|
function linkToCode(store, content, sourceNodeId, edgeType, options) {
|
|
1362
1401
|
let edgesCreated = 0;
|
|
1363
1402
|
for (const type of CODE_NODE_TYPES2) {
|
|
@@ -1525,7 +1564,7 @@ var JiraConnector = class {
|
|
|
1525
1564
|
store.addNode({
|
|
1526
1565
|
id: nodeId,
|
|
1527
1566
|
type: "issue",
|
|
1528
|
-
name: issue.fields.summary,
|
|
1567
|
+
name: sanitizeExternalText(issue.fields.summary, 500),
|
|
1529
1568
|
metadata: {
|
|
1530
1569
|
key: issue.key,
|
|
1531
1570
|
status: issue.fields.status?.name,
|
|
@@ -1535,7 +1574,9 @@ var JiraConnector = class {
|
|
|
1535
1574
|
}
|
|
1536
1575
|
});
|
|
1537
1576
|
nodesAdded++;
|
|
1538
|
-
const searchText =
|
|
1577
|
+
const searchText = sanitizeExternalText(
|
|
1578
|
+
[issue.fields.summary, issue.fields.description ?? ""].join(" ")
|
|
1579
|
+
);
|
|
1539
1580
|
edgesAdded += linkToCode(store, searchText, nodeId, "applies_to");
|
|
1540
1581
|
}
|
|
1541
1582
|
startAt += maxResults;
|
|
@@ -1611,7 +1652,8 @@ var SlackConnector = class {
|
|
|
1611
1652
|
}
|
|
1612
1653
|
for (const message of data.messages) {
|
|
1613
1654
|
const nodeId = `conversation:slack:${channel}:${message.ts}`;
|
|
1614
|
-
const
|
|
1655
|
+
const sanitizedText = sanitizeExternalText(message.text);
|
|
1656
|
+
const snippet = sanitizedText.length > 100 ? sanitizedText.slice(0, 100) : sanitizedText;
|
|
1615
1657
|
store.addNode({
|
|
1616
1658
|
id: nodeId,
|
|
1617
1659
|
type: "conversation",
|
|
@@ -1623,7 +1665,9 @@ var SlackConnector = class {
|
|
|
1623
1665
|
}
|
|
1624
1666
|
});
|
|
1625
1667
|
nodesAdded++;
|
|
1626
|
-
edgesAdded += linkToCode(store,
|
|
1668
|
+
edgesAdded += linkToCode(store, sanitizedText, nodeId, "references", {
|
|
1669
|
+
checkPaths: true
|
|
1670
|
+
});
|
|
1627
1671
|
}
|
|
1628
1672
|
} catch (err) {
|
|
1629
1673
|
errors.push(
|
|
@@ -1686,7 +1730,7 @@ var ConfluenceConnector = class {
|
|
|
1686
1730
|
store.addNode({
|
|
1687
1731
|
id: nodeId,
|
|
1688
1732
|
type: "document",
|
|
1689
|
-
name: page.title,
|
|
1733
|
+
name: sanitizeExternalText(page.title, 500),
|
|
1690
1734
|
metadata: {
|
|
1691
1735
|
source: "confluence",
|
|
1692
1736
|
spaceKey,
|
|
@@ -1696,7 +1740,7 @@ var ConfluenceConnector = class {
|
|
|
1696
1740
|
}
|
|
1697
1741
|
});
|
|
1698
1742
|
nodesAdded++;
|
|
1699
|
-
const text = `${page.title} ${page.body?.storage?.value ?? ""}
|
|
1743
|
+
const text = sanitizeExternalText(`${page.title} ${page.body?.storage?.value ?? ""}`);
|
|
1700
1744
|
edgesAdded += linkToCode(store, text, nodeId, "documents");
|
|
1701
1745
|
}
|
|
1702
1746
|
nextUrl = data._links?.next ? `${baseUrl}${data._links.next}` : null;
|
|
@@ -1761,10 +1805,11 @@ var CIConnector = class {
|
|
|
1761
1805
|
const data = await response.json();
|
|
1762
1806
|
for (const run of data.workflow_runs) {
|
|
1763
1807
|
const buildId = `build:${run.id}`;
|
|
1808
|
+
const safeName = sanitizeExternalText(run.name, 200);
|
|
1764
1809
|
store.addNode({
|
|
1765
1810
|
id: buildId,
|
|
1766
1811
|
type: "build",
|
|
1767
|
-
name: `${
|
|
1812
|
+
name: `${safeName} #${run.id}`,
|
|
1768
1813
|
metadata: {
|
|
1769
1814
|
source: "github-actions",
|
|
1770
1815
|
status: run.status,
|
|
@@ -1786,7 +1831,7 @@ var CIConnector = class {
|
|
|
1786
1831
|
store.addNode({
|
|
1787
1832
|
id: testResultId,
|
|
1788
1833
|
type: "test_result",
|
|
1789
|
-
name: `Failed: ${
|
|
1834
|
+
name: `Failed: ${safeName} #${run.id}`,
|
|
1790
1835
|
metadata: {
|
|
1791
1836
|
source: "github-actions",
|
|
1792
1837
|
buildId: String(run.id),
|
|
@@ -2534,6 +2579,250 @@ var GraphConstraintAdapter = class {
|
|
|
2534
2579
|
}
|
|
2535
2580
|
};
|
|
2536
2581
|
|
|
2582
|
+
// src/ingest/DesignIngestor.ts
|
|
2583
|
+
var fs4 = __toESM(require("fs/promises"));
|
|
2584
|
+
var path5 = __toESM(require("path"));
|
|
2585
|
+
function isDTCGToken(obj) {
|
|
2586
|
+
return typeof obj === "object" && obj !== null && "$value" in obj && "$type" in obj;
|
|
2587
|
+
}
|
|
2588
|
+
async function readFileOrNull(filePath) {
|
|
2589
|
+
try {
|
|
2590
|
+
return await fs4.readFile(filePath, "utf-8");
|
|
2591
|
+
} catch {
|
|
2592
|
+
return null;
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
function parseJsonOrError(content, filePath) {
|
|
2596
|
+
try {
|
|
2597
|
+
return { data: JSON.parse(content) };
|
|
2598
|
+
} catch (err) {
|
|
2599
|
+
return {
|
|
2600
|
+
error: `Failed to parse ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
function walkDTCGTokens(store, obj, groupPath, topGroup, tokensPath) {
|
|
2605
|
+
let count = 0;
|
|
2606
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2607
|
+
if (key.startsWith("$")) continue;
|
|
2608
|
+
if (isDTCGToken(value)) {
|
|
2609
|
+
const tokenPath = [...groupPath, key].join(".");
|
|
2610
|
+
store.addNode({
|
|
2611
|
+
id: `design_token:${tokenPath}`,
|
|
2612
|
+
type: "design_token",
|
|
2613
|
+
name: tokenPath,
|
|
2614
|
+
path: tokensPath,
|
|
2615
|
+
metadata: {
|
|
2616
|
+
tokenType: value.$type,
|
|
2617
|
+
value: value.$value,
|
|
2618
|
+
group: topGroup || groupPath[0] || key,
|
|
2619
|
+
...value.$description ? { description: value.$description } : {}
|
|
2620
|
+
}
|
|
2621
|
+
});
|
|
2622
|
+
count++;
|
|
2623
|
+
} else if (typeof value === "object" && value !== null) {
|
|
2624
|
+
count += walkDTCGTokens(
|
|
2625
|
+
store,
|
|
2626
|
+
value,
|
|
2627
|
+
[...groupPath, key],
|
|
2628
|
+
topGroup || key,
|
|
2629
|
+
tokensPath
|
|
2630
|
+
);
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
return count;
|
|
2634
|
+
}
|
|
2635
|
+
function parseAestheticDirection(content) {
|
|
2636
|
+
const extract = (pattern) => {
|
|
2637
|
+
const m = content.match(pattern);
|
|
2638
|
+
return m ? m[1].trim() : void 0;
|
|
2639
|
+
};
|
|
2640
|
+
const result = {};
|
|
2641
|
+
const style = extract(/\*\*Style:\*\*\s*(.+)/);
|
|
2642
|
+
if (style !== void 0) result.style = style;
|
|
2643
|
+
const tone = extract(/\*\*Tone:\*\*\s*(.+)/);
|
|
2644
|
+
if (tone !== void 0) result.tone = tone;
|
|
2645
|
+
const differentiator = extract(/\*\*Differentiator:\*\*\s*(.+)/);
|
|
2646
|
+
if (differentiator !== void 0) result.differentiator = differentiator;
|
|
2647
|
+
const strictness = extract(/^level:\s*(strict|standard|permissive)\s*$/m);
|
|
2648
|
+
if (strictness !== void 0) result.strictness = strictness;
|
|
2649
|
+
return result;
|
|
2650
|
+
}
|
|
2651
|
+
function parseAntiPatterns(content) {
|
|
2652
|
+
const lines = content.split("\n");
|
|
2653
|
+
const patterns = [];
|
|
2654
|
+
let inSection = false;
|
|
2655
|
+
for (const line of lines) {
|
|
2656
|
+
if (/^##\s+Anti-Patterns/i.test(line)) {
|
|
2657
|
+
inSection = true;
|
|
2658
|
+
continue;
|
|
2659
|
+
}
|
|
2660
|
+
if (inSection && /^##\s+/.test(line)) {
|
|
2661
|
+
break;
|
|
2662
|
+
}
|
|
2663
|
+
if (inSection) {
|
|
2664
|
+
const bulletMatch = line.match(/^-\s+(.+)/);
|
|
2665
|
+
if (bulletMatch) {
|
|
2666
|
+
patterns.push(bulletMatch[1].trim());
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
return patterns;
|
|
2671
|
+
}
|
|
2672
|
+
var DesignIngestor = class {
|
|
2673
|
+
constructor(store) {
|
|
2674
|
+
this.store = store;
|
|
2675
|
+
}
|
|
2676
|
+
async ingestTokens(tokensPath) {
|
|
2677
|
+
const start = Date.now();
|
|
2678
|
+
const content = await readFileOrNull(tokensPath);
|
|
2679
|
+
if (content === null) return emptyResult(Date.now() - start);
|
|
2680
|
+
const parsed = parseJsonOrError(content, tokensPath);
|
|
2681
|
+
if ("error" in parsed) {
|
|
2682
|
+
return { ...emptyResult(Date.now() - start), errors: [parsed.error] };
|
|
2683
|
+
}
|
|
2684
|
+
const nodesAdded = walkDTCGTokens(this.store, parsed.data, [], "", tokensPath);
|
|
2685
|
+
return {
|
|
2686
|
+
nodesAdded,
|
|
2687
|
+
nodesUpdated: 0,
|
|
2688
|
+
edgesAdded: 0,
|
|
2689
|
+
edgesUpdated: 0,
|
|
2690
|
+
errors: [],
|
|
2691
|
+
durationMs: Date.now() - start
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
async ingestDesignIntent(designPath) {
|
|
2695
|
+
const start = Date.now();
|
|
2696
|
+
const content = await readFileOrNull(designPath);
|
|
2697
|
+
if (content === null) return emptyResult(Date.now() - start);
|
|
2698
|
+
let nodesAdded = 0;
|
|
2699
|
+
const direction = parseAestheticDirection(content);
|
|
2700
|
+
const metadata = {};
|
|
2701
|
+
if (direction.style) metadata.style = direction.style;
|
|
2702
|
+
if (direction.tone) metadata.tone = direction.tone;
|
|
2703
|
+
if (direction.differentiator) metadata.differentiator = direction.differentiator;
|
|
2704
|
+
if (direction.strictness) metadata.strictness = direction.strictness;
|
|
2705
|
+
this.store.addNode({
|
|
2706
|
+
id: "aesthetic_intent:project",
|
|
2707
|
+
type: "aesthetic_intent",
|
|
2708
|
+
name: "project",
|
|
2709
|
+
path: designPath,
|
|
2710
|
+
metadata
|
|
2711
|
+
});
|
|
2712
|
+
nodesAdded++;
|
|
2713
|
+
for (const text of parseAntiPatterns(content)) {
|
|
2714
|
+
this.store.addNode({
|
|
2715
|
+
id: `design_constraint:${hash(text)}`,
|
|
2716
|
+
type: "design_constraint",
|
|
2717
|
+
name: text,
|
|
2718
|
+
path: designPath,
|
|
2719
|
+
metadata: { rule: text, severity: "warn", scope: "project" }
|
|
2720
|
+
});
|
|
2721
|
+
nodesAdded++;
|
|
2722
|
+
}
|
|
2723
|
+
return {
|
|
2724
|
+
nodesAdded,
|
|
2725
|
+
nodesUpdated: 0,
|
|
2726
|
+
edgesAdded: 0,
|
|
2727
|
+
edgesUpdated: 0,
|
|
2728
|
+
errors: [],
|
|
2729
|
+
durationMs: Date.now() - start
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
async ingestAll(designDir) {
|
|
2733
|
+
const start = Date.now();
|
|
2734
|
+
const [tokensResult, intentResult] = await Promise.all([
|
|
2735
|
+
this.ingestTokens(path5.join(designDir, "tokens.json")),
|
|
2736
|
+
this.ingestDesignIntent(path5.join(designDir, "DESIGN.md"))
|
|
2737
|
+
]);
|
|
2738
|
+
const merged = mergeResults(tokensResult, intentResult);
|
|
2739
|
+
return { ...merged, durationMs: Date.now() - start };
|
|
2740
|
+
}
|
|
2741
|
+
};
|
|
2742
|
+
|
|
2743
|
+
// src/constraints/DesignConstraintAdapter.ts
|
|
2744
|
+
var DesignConstraintAdapter = class {
|
|
2745
|
+
constructor(store) {
|
|
2746
|
+
this.store = store;
|
|
2747
|
+
}
|
|
2748
|
+
checkForHardcodedColors(source, file, strictness) {
|
|
2749
|
+
const severity = this.mapSeverity(strictness);
|
|
2750
|
+
const tokenNodes = this.store.findNodes({ type: "design_token" });
|
|
2751
|
+
const colorValues = /* @__PURE__ */ new Set();
|
|
2752
|
+
for (const node of tokenNodes) {
|
|
2753
|
+
if (node.metadata.tokenType === "color" && typeof node.metadata.value === "string") {
|
|
2754
|
+
colorValues.add(node.metadata.value.toLowerCase());
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
const hexPattern = /#[0-9a-fA-F]{3,8}\b/g;
|
|
2758
|
+
const violations = [];
|
|
2759
|
+
let match;
|
|
2760
|
+
while ((match = hexPattern.exec(source)) !== null) {
|
|
2761
|
+
const hexValue = match[0];
|
|
2762
|
+
if (!colorValues.has(hexValue.toLowerCase())) {
|
|
2763
|
+
violations.push({
|
|
2764
|
+
code: "DESIGN-001",
|
|
2765
|
+
file,
|
|
2766
|
+
message: `Hardcoded color ${hexValue} is not in the design token set`,
|
|
2767
|
+
severity,
|
|
2768
|
+
value: hexValue
|
|
2769
|
+
});
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
return violations;
|
|
2773
|
+
}
|
|
2774
|
+
checkForHardcodedFonts(source, file, strictness) {
|
|
2775
|
+
const severity = this.mapSeverity(strictness);
|
|
2776
|
+
const tokenNodes = this.store.findNodes({ type: "design_token" });
|
|
2777
|
+
const fontFamilies = /* @__PURE__ */ new Set();
|
|
2778
|
+
for (const node of tokenNodes) {
|
|
2779
|
+
if (node.metadata.tokenType === "typography") {
|
|
2780
|
+
const value = node.metadata.value;
|
|
2781
|
+
if (typeof value === "object" && value !== null && "fontFamily" in value) {
|
|
2782
|
+
fontFamilies.add(value.fontFamily.toLowerCase());
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
const fontPatterns = [/fontFamily:\s*['"]([^'"]+)['"]/g, /font-family:\s*['"]([^'"]+)['"]/g];
|
|
2787
|
+
const violations = [];
|
|
2788
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2789
|
+
for (const pattern of fontPatterns) {
|
|
2790
|
+
let match;
|
|
2791
|
+
while ((match = pattern.exec(source)) !== null) {
|
|
2792
|
+
const fontName = match[1];
|
|
2793
|
+
if (seen.has(fontName.toLowerCase())) continue;
|
|
2794
|
+
seen.add(fontName.toLowerCase());
|
|
2795
|
+
if (!fontFamilies.has(fontName.toLowerCase())) {
|
|
2796
|
+
violations.push({
|
|
2797
|
+
code: "DESIGN-002",
|
|
2798
|
+
file,
|
|
2799
|
+
message: `Hardcoded font family "${fontName}" is not in the design token set`,
|
|
2800
|
+
severity,
|
|
2801
|
+
value: fontName
|
|
2802
|
+
});
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
return violations;
|
|
2807
|
+
}
|
|
2808
|
+
checkAll(source, file, strictness) {
|
|
2809
|
+
return [
|
|
2810
|
+
...this.checkForHardcodedColors(source, file, strictness),
|
|
2811
|
+
...this.checkForHardcodedFonts(source, file, strictness)
|
|
2812
|
+
];
|
|
2813
|
+
}
|
|
2814
|
+
mapSeverity(strictness = "standard") {
|
|
2815
|
+
switch (strictness) {
|
|
2816
|
+
case "permissive":
|
|
2817
|
+
return "info";
|
|
2818
|
+
case "standard":
|
|
2819
|
+
return "warn";
|
|
2820
|
+
case "strict":
|
|
2821
|
+
return "error";
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
};
|
|
2825
|
+
|
|
2537
2826
|
// src/feedback/GraphFeedbackAdapter.ts
|
|
2538
2827
|
var GraphFeedbackAdapter = class {
|
|
2539
2828
|
constructor(store) {
|
|
@@ -2615,6 +2904,8 @@ var VERSION = "0.2.0";
|
|
|
2615
2904
|
CodeIngestor,
|
|
2616
2905
|
ConfluenceConnector,
|
|
2617
2906
|
ContextQL,
|
|
2907
|
+
DesignConstraintAdapter,
|
|
2908
|
+
DesignIngestor,
|
|
2618
2909
|
EDGE_TYPES,
|
|
2619
2910
|
FusionLayer,
|
|
2620
2911
|
GitIngestor,
|