@vibekiln/cutline-mcp-cli 0.1.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/Dockerfile +11 -0
- package/README.md +248 -0
- package/dist/auth/callback.d.ts +6 -0
- package/dist/auth/callback.js +97 -0
- package/dist/auth/keychain.d.ts +3 -0
- package/dist/auth/keychain.js +16 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +309 -0
- package/dist/commands/login.d.ts +7 -0
- package/dist/commands/login.js +166 -0
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +25 -0
- package/dist/commands/serve.d.ts +1 -0
- package/dist/commands/serve.js +38 -0
- package/dist/commands/setup.d.ts +5 -0
- package/dist/commands/setup.js +278 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.js +127 -0
- package/dist/commands/upgrade.d.ts +3 -0
- package/dist/commands/upgrade.js +112 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +64 -0
- package/dist/servers/chunk-DE7R7WKY.js +331 -0
- package/dist/servers/chunk-KMUSQOTJ.js +47 -0
- package/dist/servers/chunk-OP4EO6FV.js +454 -0
- package/dist/servers/chunk-UBBAYTW3.js +946 -0
- package/dist/servers/chunk-ZVWDXO6M.js +1063 -0
- package/dist/servers/cutline-server.js +10448 -0
- package/dist/servers/data-client-FPUZBUO3.js +160 -0
- package/dist/servers/exploration-server.js +930 -0
- package/dist/servers/graph-metrics-DCNR7JZN.js +12 -0
- package/dist/servers/integrations-server.js +107 -0
- package/dist/servers/output-server.js +107 -0
- package/dist/servers/premortem-server.js +971 -0
- package/dist/servers/tools-server.js +287 -0
- package/dist/utils/config-store.d.ts +8 -0
- package/dist/utils/config-store.js +35 -0
- package/dist/utils/config.d.ts +22 -0
- package/dist/utils/config.js +48 -0
- package/mcpb/manifest.json +77 -0
- package/package.json +76 -0
- package/server.json +42 -0
- package/smithery.yaml +10 -0
|
@@ -0,0 +1,946 @@
|
|
|
1
|
+
// ../mcp/dist/mcp/src/context-graph/conflict-taxonomy.js
|
|
2
|
+
var CONFLICT_TAXONOMY = [
|
|
3
|
+
{
|
|
4
|
+
tag_a: ["encryption", "e2e", "homomorphic", "zero-trust", "zero_trust", "tls"],
|
|
5
|
+
tag_b: ["low-latency", "real-time", "realtime", "sub-second", "fast-response", "sub-10ms"],
|
|
6
|
+
tension: "Security vs Latency"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
tag_a: ["heavy-computation", "ml-inference", "ai", "llm", "deep-learning", "gpu"],
|
|
10
|
+
tag_b: ["low-cost", "free-tier", "minimal-cogs", "cheap", "budget", "cost-sensitive"],
|
|
11
|
+
tension: "Compute Cost vs AI/ML Features"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
tag_a: ["offline-first", "local-storage", "edge-compute", "local-only"],
|
|
15
|
+
tag_b: ["real-time-sync", "strong-consistency", "centralized", "cloud-first", "server-authoritative"],
|
|
16
|
+
tension: "Offline Availability vs Real-time Consistency"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
tag_a: ["privacy", "gdpr", "data-minimization", "anonymization", "pii-protection"],
|
|
20
|
+
tag_b: ["personalization", "analytics", "tracking", "recommendation", "targeting", "behavioral"],
|
|
21
|
+
tension: "Privacy vs Personalization"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
tag_a: ["simplicity", "minimal-ui", "frictionless", "one-click", "low-friction"],
|
|
25
|
+
tag_b: ["compliance", "audit", "verification", "multi-factor", "mfa", "approval-flow", "kyc"],
|
|
26
|
+
tension: "UX Simplicity vs Compliance Requirements"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
tag_a: ["backward-compatible", "legacy-support", "stable-api", "no-breaking-changes"],
|
|
30
|
+
tag_b: ["breaking-change", "greenfield", "rewrite", "v2-redesign", "clean-slate"],
|
|
31
|
+
tension: "Backward Compatibility vs Clean Architecture"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
tag_a: ["soc2", "hipaa", "audit-trail", "full-logging", "traceability", "retention"],
|
|
35
|
+
tag_b: ["data-minimization", "ephemeral", "no-logs", "privacy-by-design", "right-to-delete"],
|
|
36
|
+
tension: "Audit Compliance vs Data Minimization"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
tag_a: ["scalability", "horizontal-scale", "distributed", "microservice", "high-throughput"],
|
|
40
|
+
tag_b: ["simplicity", "monolith", "single-deploy", "minimal-infra", "low-ops"],
|
|
41
|
+
tension: "Scalability vs Operational Simplicity"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
tag_a: ["open-source", "vendor-neutral", "self-hosted", "portable"],
|
|
45
|
+
tag_b: ["managed-service", "vendor-lock-in", "saas-dependency", "proprietary"],
|
|
46
|
+
tension: "Vendor Independence vs Managed Convenience"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
tag_a: ["security", "hardened", "strict-cors", "least-privilege", "sandboxed"],
|
|
50
|
+
tag_b: ["developer-experience", "fast-iteration", "permissive", "hot-reload", "no-auth-dev"],
|
|
51
|
+
tension: "Security Posture vs Developer Velocity"
|
|
52
|
+
}
|
|
53
|
+
];
|
|
54
|
+
var ALL_TAXONOMY_TAGS = /* @__PURE__ */ new Set();
|
|
55
|
+
for (const pair of CONFLICT_TAXONOMY) {
|
|
56
|
+
for (const t of pair.tag_a)
|
|
57
|
+
ALL_TAXONOMY_TAGS.add(t);
|
|
58
|
+
for (const t of pair.tag_b)
|
|
59
|
+
ALL_TAXONOMY_TAGS.add(t);
|
|
60
|
+
}
|
|
61
|
+
function getRelevantTags(c) {
|
|
62
|
+
const tags = /* @__PURE__ */ new Set();
|
|
63
|
+
for (const k of c.keywords) {
|
|
64
|
+
const lk = k.toLowerCase();
|
|
65
|
+
if (ALL_TAXONOMY_TAGS.has(lk))
|
|
66
|
+
tags.add(lk);
|
|
67
|
+
}
|
|
68
|
+
const cat = String(c.category).toLowerCase();
|
|
69
|
+
if (ALL_TAXONOMY_TAGS.has(cat))
|
|
70
|
+
tags.add(cat);
|
|
71
|
+
const words = c.summary.toLowerCase().split(/[^a-z0-9-]+/).filter((w) => w.length > 2);
|
|
72
|
+
for (const w of words) {
|
|
73
|
+
if (ALL_TAXONOMY_TAGS.has(w))
|
|
74
|
+
tags.add(w);
|
|
75
|
+
}
|
|
76
|
+
return tags;
|
|
77
|
+
}
|
|
78
|
+
function findSemanticTagConflict(a, b) {
|
|
79
|
+
const tagsA = getRelevantTags(a);
|
|
80
|
+
const tagsB = getRelevantTags(b);
|
|
81
|
+
if (tagsA.size === 0 || tagsB.size === 0)
|
|
82
|
+
return null;
|
|
83
|
+
for (const pair of CONFLICT_TAXONOMY) {
|
|
84
|
+
const aMatchesSideA = pair.tag_a.some((t) => tagsA.has(t));
|
|
85
|
+
const bMatchesSideB = pair.tag_b.some((t) => tagsB.has(t));
|
|
86
|
+
if (aMatchesSideA && bMatchesSideB)
|
|
87
|
+
return pair;
|
|
88
|
+
const aMatchesSideB = pair.tag_b.some((t) => tagsA.has(t));
|
|
89
|
+
const bMatchesSideA = pair.tag_a.some((t) => tagsB.has(t));
|
|
90
|
+
if (aMatchesSideB && bMatchesSideA)
|
|
91
|
+
return pair;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
function thresholdsConflict(a, b) {
|
|
96
|
+
const aVal = Array.isArray(a.value) ? a.value[1] : a.value;
|
|
97
|
+
const bVal = Array.isArray(b.value) ? b.value[0] : b.value;
|
|
98
|
+
if ((a.operator === "<" || a.operator === "<=") && (b.operator === ">" || b.operator === ">=")) {
|
|
99
|
+
return aVal <= bVal;
|
|
100
|
+
}
|
|
101
|
+
if ((a.operator === ">" || a.operator === ">=") && (b.operator === "<" || b.operator === "<=")) {
|
|
102
|
+
return bVal <= aVal;
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
function detectConstraintConflicts(entityId, constraints) {
|
|
107
|
+
const conflicts = [];
|
|
108
|
+
for (let i = 0; i < constraints.length; i++) {
|
|
109
|
+
for (let j = i + 1; j < constraints.length; j++) {
|
|
110
|
+
const a = constraints[i];
|
|
111
|
+
const b = constraints[j];
|
|
112
|
+
if (a.threshold && b.threshold && a.threshold.metric === b.threshold.metric) {
|
|
113
|
+
if (thresholdsConflict(a.threshold, b.threshold)) {
|
|
114
|
+
conflicts.push({
|
|
115
|
+
constraint_a: a,
|
|
116
|
+
constraint_b: b,
|
|
117
|
+
entity_id: entityId,
|
|
118
|
+
conflict_type: "threshold",
|
|
119
|
+
description: `Conflicting thresholds on "${a.threshold.metric}": ${a.threshold.operator} ${a.threshold.value}${a.threshold.unit ? " " + a.threshold.unit : ""} vs ${b.threshold.operator} ${b.threshold.value}${b.threshold.unit ? " " + b.threshold.unit : ""}`
|
|
120
|
+
});
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (a.scope && b.scope && a.scope !== b.scope) {
|
|
125
|
+
const shared = a.keywords.filter((k) => b.keywords.includes(k));
|
|
126
|
+
if (shared.length >= 2) {
|
|
127
|
+
conflicts.push({
|
|
128
|
+
constraint_a: a,
|
|
129
|
+
constraint_b: b,
|
|
130
|
+
entity_id: entityId,
|
|
131
|
+
conflict_type: "scope",
|
|
132
|
+
description: `Scope conflict: "${a.summary.slice(0, 60)}" is ${a.scope} but "${b.summary.slice(0, 60)}" is ${b.scope}`
|
|
133
|
+
});
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const pair = findSemanticTagConflict(a, b);
|
|
138
|
+
if (pair) {
|
|
139
|
+
conflicts.push({
|
|
140
|
+
constraint_a: a,
|
|
141
|
+
constraint_b: b,
|
|
142
|
+
entity_id: entityId,
|
|
143
|
+
conflict_type: "semantic_tag",
|
|
144
|
+
tension: pair.tension,
|
|
145
|
+
description: `Constraint collision (${pair.tension}): "${a.summary.slice(0, 60)}" conflicts with "${b.summary.slice(0, 60)}"`
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return conflicts;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ../mcp/dist/mcp/src/context-graph/graph-traverser.js
|
|
154
|
+
var GraphTraverser = class {
|
|
155
|
+
entities = /* @__PURE__ */ new Map();
|
|
156
|
+
constraints = /* @__PURE__ */ new Map();
|
|
157
|
+
bindings = [];
|
|
158
|
+
// adjacency: nodeId -> outgoing edges
|
|
159
|
+
outgoing = /* @__PURE__ */ new Map();
|
|
160
|
+
// reverse adjacency: nodeId -> incoming edges
|
|
161
|
+
incoming = /* @__PURE__ */ new Map();
|
|
162
|
+
constructor(entities, edges, constraintNodes, bindings) {
|
|
163
|
+
for (const e of entities)
|
|
164
|
+
this.entities.set(e.id, e);
|
|
165
|
+
for (const c of constraintNodes)
|
|
166
|
+
this.constraints.set(c.id, c);
|
|
167
|
+
this.bindings = bindings;
|
|
168
|
+
for (const edge of edges) {
|
|
169
|
+
const out = this.outgoing.get(edge.source_id) ?? [];
|
|
170
|
+
out.push({ targetId: edge.target_id, edge });
|
|
171
|
+
this.outgoing.set(edge.source_id, out);
|
|
172
|
+
const inc = this.incoming.get(edge.target_id) ?? [];
|
|
173
|
+
inc.push({ targetId: edge.source_id, edge });
|
|
174
|
+
this.incoming.set(edge.target_id, inc);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
178
|
+
// LOOKUP
|
|
179
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
180
|
+
getEntity(id) {
|
|
181
|
+
return this.entities.get(id);
|
|
182
|
+
}
|
|
183
|
+
getConstraint(id) {
|
|
184
|
+
return this.constraints.get(id);
|
|
185
|
+
}
|
|
186
|
+
/** Find entities whose file_patterns match the given file path. */
|
|
187
|
+
findEntitiesByFilePath(filePath) {
|
|
188
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
189
|
+
const matchedEntityIds = /* @__PURE__ */ new Set();
|
|
190
|
+
for (const binding of this.bindings) {
|
|
191
|
+
for (const pattern of binding.file_patterns) {
|
|
192
|
+
if (globMatch(normalizedPath, pattern)) {
|
|
193
|
+
matchedEntityIds.add(binding.entity_id);
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return [...matchedEntityIds].map((id) => this.entities.get(id)).filter(Boolean);
|
|
199
|
+
}
|
|
200
|
+
/** Find entity by name (case-insensitive, checks aliases too). */
|
|
201
|
+
findEntityByName(name) {
|
|
202
|
+
const lower = name.toLowerCase();
|
|
203
|
+
for (const entity of this.entities.values()) {
|
|
204
|
+
if (entity.name.toLowerCase() === lower)
|
|
205
|
+
return entity;
|
|
206
|
+
if (entity.aliases.some((a) => a.toLowerCase() === lower))
|
|
207
|
+
return entity;
|
|
208
|
+
}
|
|
209
|
+
return void 0;
|
|
210
|
+
}
|
|
211
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
212
|
+
// SUBGRAPH EXTRACTION (BFS)
|
|
213
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
214
|
+
/**
|
|
215
|
+
* Extract a localized subgraph rooted at `entityId`, traversing up to
|
|
216
|
+
* `maxDepth` hops along both outgoing and incoming edges.
|
|
217
|
+
* Collects all reachable entities and their attached constraints.
|
|
218
|
+
*/
|
|
219
|
+
getSubgraph(entityId, maxDepth = 2) {
|
|
220
|
+
const rootEntity = this.entities.get(entityId);
|
|
221
|
+
if (!rootEntity)
|
|
222
|
+
return null;
|
|
223
|
+
const visitedEntities = /* @__PURE__ */ new Set();
|
|
224
|
+
const visitedConstraints = /* @__PURE__ */ new Set();
|
|
225
|
+
const collectedEdges = [];
|
|
226
|
+
const queue = [[entityId, 0]];
|
|
227
|
+
visitedEntities.add(entityId);
|
|
228
|
+
while (queue.length > 0) {
|
|
229
|
+
const [currentId, depth] = queue.shift();
|
|
230
|
+
const out = this.outgoing.get(currentId) ?? [];
|
|
231
|
+
for (const { targetId, edge } of out) {
|
|
232
|
+
collectedEdges.push(edge);
|
|
233
|
+
if (this.constraints.has(targetId)) {
|
|
234
|
+
visitedConstraints.add(targetId);
|
|
235
|
+
} else if (this.entities.has(targetId) && !visitedEntities.has(targetId)) {
|
|
236
|
+
visitedEntities.add(targetId);
|
|
237
|
+
if (depth + 1 <= maxDepth) {
|
|
238
|
+
queue.push([targetId, depth + 1]);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const inc = this.incoming.get(currentId) ?? [];
|
|
243
|
+
for (const { targetId: sourceId, edge } of inc) {
|
|
244
|
+
collectedEdges.push(edge);
|
|
245
|
+
if (this.constraints.has(sourceId)) {
|
|
246
|
+
visitedConstraints.add(sourceId);
|
|
247
|
+
} else if (this.entities.has(sourceId) && !visitedEntities.has(sourceId)) {
|
|
248
|
+
visitedEntities.add(sourceId);
|
|
249
|
+
if (depth + 1 <= maxDepth) {
|
|
250
|
+
queue.push([sourceId, depth + 1]);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const entities = [...visitedEntities].map((id) => this.entities.get(id)).filter(Boolean);
|
|
256
|
+
const constraints = [...visitedConstraints].map((id) => this.constraints.get(id)).filter(Boolean);
|
|
257
|
+
const propagated = this.propagateConstraints(entities, collectedEdges);
|
|
258
|
+
for (const c of propagated) {
|
|
259
|
+
if (!visitedConstraints.has(c.id)) {
|
|
260
|
+
constraints.push(c);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const conflicts = this.detectConflicts(entityId, constraints);
|
|
264
|
+
const uniqueEdges = deduplicateEdges(collectedEdges);
|
|
265
|
+
return {
|
|
266
|
+
target_entity: rootEntity,
|
|
267
|
+
entities,
|
|
268
|
+
constraints,
|
|
269
|
+
edges: uniqueEdges,
|
|
270
|
+
conflicts
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
274
|
+
// NFR PROPAGATION
|
|
275
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
276
|
+
/**
|
|
277
|
+
* Propagate NFRs downward through the graph.
|
|
278
|
+
*
|
|
279
|
+
* Rules:
|
|
280
|
+
* - If a DataType is GOVERNED_BY a Constraint, any Component that HANDLES
|
|
281
|
+
* that DataType inherits the constraint.
|
|
282
|
+
* - If a Feature is BOUNDED_BY a Constraint, any Component that the Feature
|
|
283
|
+
* REQUIRES inherits the constraint.
|
|
284
|
+
*
|
|
285
|
+
* Returns constraints that were inherited (not directly attached).
|
|
286
|
+
*/
|
|
287
|
+
propagateConstraints(subgraphEntities, subgraphEdges) {
|
|
288
|
+
const inherited = [];
|
|
289
|
+
const entityIds = new Set(subgraphEntities.map((e) => e.id));
|
|
290
|
+
const handleEdges = subgraphEdges.filter((e) => e.edge_type === "HANDLES");
|
|
291
|
+
const requiresEdges = subgraphEdges.filter((e) => e.edge_type === "REQUIRES");
|
|
292
|
+
const governedByEdges = subgraphEdges.filter((e) => e.edge_type === "GOVERNED_BY");
|
|
293
|
+
const boundedByEdges = subgraphEdges.filter((e) => e.edge_type === "BOUNDED_BY");
|
|
294
|
+
for (const govEdge of governedByEdges) {
|
|
295
|
+
const constraint = this.constraints.get(govEdge.target_id);
|
|
296
|
+
if (!constraint)
|
|
297
|
+
continue;
|
|
298
|
+
for (const handleEdge of handleEdges) {
|
|
299
|
+
if (handleEdge.target_id === govEdge.source_id && entityIds.has(handleEdge.source_id)) {
|
|
300
|
+
inherited.push(constraint);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
for (const boundEdge of boundedByEdges) {
|
|
305
|
+
const sourceEntity = this.entities.get(boundEdge.source_id);
|
|
306
|
+
if (!sourceEntity || sourceEntity.type !== "feature")
|
|
307
|
+
continue;
|
|
308
|
+
const constraint = this.constraints.get(boundEdge.target_id);
|
|
309
|
+
if (!constraint)
|
|
310
|
+
continue;
|
|
311
|
+
for (const reqEdge of requiresEdges) {
|
|
312
|
+
if (reqEdge.source_id === boundEdge.source_id && entityIds.has(reqEdge.target_id)) {
|
|
313
|
+
inherited.push(constraint);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const seen = /* @__PURE__ */ new Set();
|
|
318
|
+
return inherited.filter((c) => {
|
|
319
|
+
if (seen.has(c.id))
|
|
320
|
+
return false;
|
|
321
|
+
seen.add(c.id);
|
|
322
|
+
return true;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
326
|
+
// CONFLICT DETECTION
|
|
327
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
328
|
+
/**
|
|
329
|
+
* Detect conflicting constraints on the same entity.
|
|
330
|
+
* Delegates to the standalone `detectConstraintConflicts()` which checks
|
|
331
|
+
* three heuristics: threshold, scope, and semantic-tag conflicts.
|
|
332
|
+
*/
|
|
333
|
+
detectConflicts(entityId, constraints) {
|
|
334
|
+
return detectConstraintConflicts(entityId, constraints);
|
|
335
|
+
}
|
|
336
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
337
|
+
// FULL GRAPH SUMMARY
|
|
338
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
339
|
+
getSummary() {
|
|
340
|
+
return {
|
|
341
|
+
entities: [...this.entities.values()],
|
|
342
|
+
constraints: [...this.constraints.values()],
|
|
343
|
+
edges: getAllEdgesFromMaps(this.outgoing),
|
|
344
|
+
bindings: this.bindings
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
function globMatch(path, pattern) {
|
|
349
|
+
const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
350
|
+
return new RegExp(`^${regexStr}$`, "i").test(path);
|
|
351
|
+
}
|
|
352
|
+
function deduplicateEdges(edges) {
|
|
353
|
+
const seen = /* @__PURE__ */ new Set();
|
|
354
|
+
return edges.filter((e) => {
|
|
355
|
+
if (seen.has(e.id))
|
|
356
|
+
return false;
|
|
357
|
+
seen.add(e.id);
|
|
358
|
+
return true;
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function getAllEdgesFromMaps(outgoing) {
|
|
362
|
+
const edges = [];
|
|
363
|
+
const seen = /* @__PURE__ */ new Set();
|
|
364
|
+
for (const entries of outgoing.values()) {
|
|
365
|
+
for (const { edge } of entries) {
|
|
366
|
+
if (!seen.has(edge.id)) {
|
|
367
|
+
seen.add(edge.id);
|
|
368
|
+
edges.push(edge);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return edges;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ../mcp/dist/mcp/src/context-graph/graph-metrics.js
|
|
376
|
+
var CATEGORY_TO_BUCKET = {
|
|
377
|
+
security: "security",
|
|
378
|
+
performance: "performance",
|
|
379
|
+
infrastructure: "scalability",
|
|
380
|
+
scalability: "scalability",
|
|
381
|
+
architecture: "stability",
|
|
382
|
+
stability: "stability",
|
|
383
|
+
compliance: "compliance",
|
|
384
|
+
risk: "security",
|
|
385
|
+
accessibility: "compliance"
|
|
386
|
+
};
|
|
387
|
+
function toBucket(category) {
|
|
388
|
+
return CATEGORY_TO_BUCKET[category] ?? null;
|
|
389
|
+
}
|
|
390
|
+
function computeNfrCoverage(entities, edges, constraints) {
|
|
391
|
+
const featureEntities = entities.filter((e) => e.type === "feature");
|
|
392
|
+
if (featureEntities.length === 0) {
|
|
393
|
+
return { security: 0, performance: 0, scalability: 0, stability: 0, compliance: 0, overall: 0 };
|
|
394
|
+
}
|
|
395
|
+
const featureIds = new Set(featureEntities.map((e) => e.id));
|
|
396
|
+
const constraintMap = new Map(constraints.map((c) => [c.id, c]));
|
|
397
|
+
const coveredByBucket = {
|
|
398
|
+
security: /* @__PURE__ */ new Set(),
|
|
399
|
+
performance: /* @__PURE__ */ new Set(),
|
|
400
|
+
scalability: /* @__PURE__ */ new Set(),
|
|
401
|
+
stability: /* @__PURE__ */ new Set(),
|
|
402
|
+
compliance: /* @__PURE__ */ new Set()
|
|
403
|
+
};
|
|
404
|
+
for (const edge of edges) {
|
|
405
|
+
if (edge.edge_type !== "GOVERNED_BY" && edge.edge_type !== "BOUNDED_BY")
|
|
406
|
+
continue;
|
|
407
|
+
if (!featureIds.has(edge.source_id))
|
|
408
|
+
continue;
|
|
409
|
+
const constraint = constraintMap.get(edge.target_id);
|
|
410
|
+
if (!constraint)
|
|
411
|
+
continue;
|
|
412
|
+
const bucket = toBucket(constraint.category);
|
|
413
|
+
if (!bucket)
|
|
414
|
+
continue;
|
|
415
|
+
coveredByBucket[bucket].add(edge.source_id);
|
|
416
|
+
}
|
|
417
|
+
const totalFeatures = featureEntities.length;
|
|
418
|
+
const buckets = ["security", "performance", "scalability", "stability", "compliance"];
|
|
419
|
+
const scores = {};
|
|
420
|
+
for (const b of buckets) {
|
|
421
|
+
scores[b] = coveredByBucket[b].size / totalFeatures;
|
|
422
|
+
}
|
|
423
|
+
const weights = {
|
|
424
|
+
security: 1.5,
|
|
425
|
+
performance: 1,
|
|
426
|
+
scalability: 1,
|
|
427
|
+
stability: 1,
|
|
428
|
+
compliance: 1.5
|
|
429
|
+
};
|
|
430
|
+
let weightedSum = 0;
|
|
431
|
+
let weightTotal = 0;
|
|
432
|
+
for (const b of buckets) {
|
|
433
|
+
weightedSum += scores[b] * weights[b];
|
|
434
|
+
weightTotal += weights[b];
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
security: round(scores.security),
|
|
438
|
+
performance: round(scores.performance),
|
|
439
|
+
scalability: round(scores.scalability),
|
|
440
|
+
stability: round(scores.stability),
|
|
441
|
+
compliance: round(scores.compliance),
|
|
442
|
+
overall: round(weightedSum / weightTotal)
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
var BASE_HOURS = {
|
|
446
|
+
feature: 8,
|
|
447
|
+
component: 12,
|
|
448
|
+
data_type: 4
|
|
449
|
+
};
|
|
450
|
+
var NFR_OVERHEAD = {
|
|
451
|
+
critical: {
|
|
452
|
+
security: 6,
|
|
453
|
+
compliance: 5,
|
|
454
|
+
performance: 4,
|
|
455
|
+
scalability: 3,
|
|
456
|
+
stability: 3
|
|
457
|
+
},
|
|
458
|
+
warning: { _default: 2 },
|
|
459
|
+
info: { _default: 0.5 }
|
|
460
|
+
};
|
|
461
|
+
function computeTimeEstimate(entities, edges, constraints, bindings, coverage, conflictCount) {
|
|
462
|
+
if (entities.length === 0) {
|
|
463
|
+
return { unassisted_hours: 0, assisted_hours: 0, speedup_factor: 1, confidence: 0 };
|
|
464
|
+
}
|
|
465
|
+
let baseHours = 0;
|
|
466
|
+
const piiDataTypes = /* @__PURE__ */ new Set();
|
|
467
|
+
for (const entity of entities) {
|
|
468
|
+
let hours = BASE_HOURS[entity.type] ?? 8;
|
|
469
|
+
if (entity.type === "data_type") {
|
|
470
|
+
const isPii = entity.name.toLowerCase().includes("pii") || entity.description?.toLowerCase().includes("personal") || entity.description?.toLowerCase().includes("pii") || entity.name.toLowerCase().includes("financial") || entity.name.toLowerCase().includes("credential");
|
|
471
|
+
if (isPii) {
|
|
472
|
+
hours *= 1.3;
|
|
473
|
+
piiDataTypes.add(entity.id);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
baseHours += hours;
|
|
477
|
+
}
|
|
478
|
+
let nfrOverhead = 0;
|
|
479
|
+
for (const constraint of constraints) {
|
|
480
|
+
const bucket = toBucket(constraint.category);
|
|
481
|
+
const severity = constraint.severity;
|
|
482
|
+
if (severity === "critical" && bucket) {
|
|
483
|
+
nfrOverhead += NFR_OVERHEAD.critical[bucket] ?? 3;
|
|
484
|
+
} else if (severity === "warning") {
|
|
485
|
+
nfrOverhead += NFR_OVERHEAD.warning._default;
|
|
486
|
+
} else {
|
|
487
|
+
nfrOverhead += NFR_OVERHEAD.info._default;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const dependsOnEdges = edges.filter((e) => e.edge_type === "DEPENDS_ON");
|
|
491
|
+
const dependencyOverhead = Math.max(0, dependsOnEdges.length - 2) * 2;
|
|
492
|
+
const conflictOverhead = conflictCount * 4;
|
|
493
|
+
const rawHours = baseHours + nfrOverhead + dependencyOverhead + conflictOverhead;
|
|
494
|
+
const uncoveredRatio = 1 - coverage.overall;
|
|
495
|
+
const criticalNfrs = constraints.filter((c) => c.severity === "critical");
|
|
496
|
+
const hasCriticalGaps = criticalNfrs.length > 0 && coverage.security < 0.5;
|
|
497
|
+
const reworkMultiplier = hasCriticalGaps ? 1 + uncoveredRatio * 0.6 : 1 + uncoveredRatio * 0.4;
|
|
498
|
+
const unassistedHours = rawHours * reworkMultiplier;
|
|
499
|
+
const coveredReduction = coverage.overall * 0.3;
|
|
500
|
+
const uncoveredReduction = (1 - coverage.overall) * 0.1;
|
|
501
|
+
const conflictSavings = conflictOverhead * 0.6;
|
|
502
|
+
const assistedHours = Math.max(
|
|
503
|
+
rawHours * 0.5,
|
|
504
|
+
// floor: at least 50% of raw hours
|
|
505
|
+
rawHours * (1 - coveredReduction - uncoveredReduction) - conflictSavings
|
|
506
|
+
);
|
|
507
|
+
const speedup = assistedHours > 0 ? unassistedHours / assistedHours : 1;
|
|
508
|
+
const boundEntities = new Set(bindings.map((b) => b.entity_id)).size;
|
|
509
|
+
const bindingRatio = entities.length > 0 ? boundEntities / entities.length : 0;
|
|
510
|
+
const confidence = Math.min(1, bindingRatio * 0.5 + coverage.overall * 0.5);
|
|
511
|
+
return {
|
|
512
|
+
unassisted_hours: round(unassistedHours),
|
|
513
|
+
assisted_hours: round(assistedHours),
|
|
514
|
+
speedup_factor: round(speedup, 1),
|
|
515
|
+
confidence: round(confidence)
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function computeGraphMetrics(entities, edges, constraints, bindings, conflicts, rgrCompleted, genericPrior) {
|
|
519
|
+
const nfr_coverage = computeNfrCoverage(entities, edges, constraints);
|
|
520
|
+
const time_estimate = computeTimeEstimate(entities, edges, constraints, bindings, nfr_coverage, conflicts.length);
|
|
521
|
+
const criticalNfrCount = constraints.filter((c) => c.severity === "critical").length;
|
|
522
|
+
const piiCount = entities.filter((e) => {
|
|
523
|
+
if (e.type !== "data_type")
|
|
524
|
+
return false;
|
|
525
|
+
const lower = (e.name + " " + (e.description ?? "")).toLowerCase();
|
|
526
|
+
return lower.includes("pii") || lower.includes("personal") || lower.includes("financial") || lower.includes("credential");
|
|
527
|
+
}).length;
|
|
528
|
+
const dependsOnEdges = edges.filter((e) => e.edge_type === "DEPENDS_ON");
|
|
529
|
+
let maxDepth = 0;
|
|
530
|
+
if (dependsOnEdges.length > 0) {
|
|
531
|
+
const adj = /* @__PURE__ */ new Map();
|
|
532
|
+
for (const e of dependsOnEdges) {
|
|
533
|
+
const arr = adj.get(e.source_id) ?? [];
|
|
534
|
+
arr.push(e.target_id);
|
|
535
|
+
adj.set(e.source_id, arr);
|
|
536
|
+
}
|
|
537
|
+
for (const startId of adj.keys()) {
|
|
538
|
+
const visited = /* @__PURE__ */ new Set();
|
|
539
|
+
const queue = [[startId, 0]];
|
|
540
|
+
visited.add(startId);
|
|
541
|
+
while (queue.length > 0) {
|
|
542
|
+
const [id, d] = queue.shift();
|
|
543
|
+
maxDepth = Math.max(maxDepth, d);
|
|
544
|
+
for (const next of adj.get(id) ?? []) {
|
|
545
|
+
if (!visited.has(next)) {
|
|
546
|
+
visited.add(next);
|
|
547
|
+
queue.push([next, d + 1]);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const featureCount = entities.filter((e) => e.type === "feature").length;
|
|
554
|
+
const frCount = entities.filter((e) => e.type === "component").length;
|
|
555
|
+
const rgrCompletedPhases = rgrCompleted ?? [];
|
|
556
|
+
const security_readiness_pct = computeSecurityReadiness(nfr_coverage, constraints, entities, bindings, rgrCompletedPhases, piiCount);
|
|
557
|
+
const reliability_readiness_pct = computeReliabilityReadiness(nfr_coverage, constraints, entities, bindings, rgrCompletedPhases);
|
|
558
|
+
const scalability_readiness_pct = computeScalabilityReadiness(nfr_coverage, constraints, entities, bindings, rgrCompletedPhases);
|
|
559
|
+
const FRAMEWORK_MAP = {
|
|
560
|
+
soc2: "SOC 2",
|
|
561
|
+
csa_ccm: "CSA Controls Matrix",
|
|
562
|
+
pci: "PCI-DSS",
|
|
563
|
+
hipaa: "HIPAA",
|
|
564
|
+
fedramp: "FedRAMP",
|
|
565
|
+
gdpr: "GDPR/CCPA",
|
|
566
|
+
owasp: "OWASP LLM Top 10",
|
|
567
|
+
glba: "GLBA",
|
|
568
|
+
ferpa: "FERPA/COPPA"
|
|
569
|
+
};
|
|
570
|
+
const detectedFrameworks = /* @__PURE__ */ new Set();
|
|
571
|
+
for (const c of constraints) {
|
|
572
|
+
if (c.id.startsWith("constraint:blueprint:")) {
|
|
573
|
+
const suffix = c.id.replace("constraint:blueprint:", "");
|
|
574
|
+
for (const [prefix, label] of Object.entries(FRAMEWORK_MAP)) {
|
|
575
|
+
if (suffix.startsWith(prefix)) {
|
|
576
|
+
detectedFrameworks.add(label);
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const baseMetrics = {
|
|
583
|
+
nfr_coverage,
|
|
584
|
+
time_estimate,
|
|
585
|
+
complexity_factors: {
|
|
586
|
+
entity_count: entities.length,
|
|
587
|
+
nfr_count: constraints.length,
|
|
588
|
+
critical_nfr_count: criticalNfrCount,
|
|
589
|
+
pii_data_types: piiCount,
|
|
590
|
+
dependency_depth: maxDepth,
|
|
591
|
+
conflict_count: conflicts.length
|
|
592
|
+
},
|
|
593
|
+
engineering_readiness_pct: computeEngineeringReadiness(nfr_coverage, time_estimate, featureCount, constraints.length, frCount, dependsOnEdges.length, security_readiness_pct, reliability_readiness_pct, scalability_readiness_pct, rgrCompletedPhases, conflicts.length, maxDepth),
|
|
594
|
+
security_readiness_pct,
|
|
595
|
+
reliability_readiness_pct,
|
|
596
|
+
scalability_readiness_pct,
|
|
597
|
+
rgr_completed_phases: rgrCompletedPhases,
|
|
598
|
+
compliance_frameworks: detectedFrameworks.size > 0 ? [...detectedFrameworks].sort() : void 0,
|
|
599
|
+
computed_at: /* @__PURE__ */ new Date()
|
|
600
|
+
};
|
|
601
|
+
if (genericPrior) {
|
|
602
|
+
return applyGenericPrior(baseMetrics, genericPrior, featureCount);
|
|
603
|
+
}
|
|
604
|
+
return baseMetrics;
|
|
605
|
+
}
|
|
606
|
+
var SECURITY_CATEGORIES = /* @__PURE__ */ new Set(["security", "compliance", "risk", "accessibility"]);
|
|
607
|
+
function computeSecurityReadiness(coverage, constraints, entities, bindings, rgrCompletedPhases, piiCount) {
|
|
608
|
+
const secCoverageScore = Math.min(100, coverage.security * 100);
|
|
609
|
+
const secConstraints = constraints.filter((c) => typeof c.category === "string" && SECURITY_CATEGORIES.has(c.category));
|
|
610
|
+
const totalEntities = entities.length || 1;
|
|
611
|
+
const densityRatio = secConstraints.length / totalEntities;
|
|
612
|
+
const densityScore = Math.min(100, densityRatio * 50);
|
|
613
|
+
const blueprintConstraints = constraints.filter((c) => c.id.startsWith("constraint:blueprint:") || c.id.startsWith("constraint:audit:"));
|
|
614
|
+
const frameworkIds = new Set(blueprintConstraints.map((c) => c.id.replace("constraint:blueprint:", "").split("_")[0]).filter((f) => ["soc2", "csa", "pci", "hipaa", "fedramp", "gdpr", "owasp", "glba", "ferpa"].includes(f)));
|
|
615
|
+
const baseBlueprint = Math.min(60, blueprintConstraints.length * 4);
|
|
616
|
+
const frameworkBonus = frameworkIds.size * 10;
|
|
617
|
+
const blueprintScore = Math.min(100, baseBlueprint + frameworkBonus);
|
|
618
|
+
const complianceScore = Math.min(100, coverage.compliance * 100);
|
|
619
|
+
const secEntityIds = new Set(entities.filter((e) => e.name.toLowerCase().includes("security") || e.name.toLowerCase().includes("auth")).map((e) => e.id));
|
|
620
|
+
const boundSecEntities = new Set(bindings.filter((b) => secEntityIds.has(b.entity_id)).map((b) => b.entity_id));
|
|
621
|
+
const bindingScore = secEntityIds.size > 0 ? Math.min(100, boundSecEntities.size / secEntityIds.size * 100) : blueprintScore > 0 ? 40 : 0;
|
|
622
|
+
const base = secCoverageScore * 0.35 + densityScore * 0.25 + blueprintScore * 0.2 + complianceScore * 0.1 + bindingScore * 0.1;
|
|
623
|
+
const piiPenalty = piiCount > 0 ? Math.min(15, piiCount * 5 * (1 - coverage.security)) : 0;
|
|
624
|
+
let rgrBonus = 0;
|
|
625
|
+
if (rgrCompletedPhases.includes("security"))
|
|
626
|
+
rgrBonus += 15;
|
|
627
|
+
if (rgrCompletedPhases.includes("test_spec"))
|
|
628
|
+
rgrBonus += 5;
|
|
629
|
+
if (rgrCompletedPhases.includes("functional"))
|
|
630
|
+
rgrBonus += 3;
|
|
631
|
+
if (rgrCompletedPhases.includes("performance"))
|
|
632
|
+
rgrBonus += 2;
|
|
633
|
+
return round(Math.min(100, Math.max(0, base - piiPenalty + rgrBonus)));
|
|
634
|
+
}
|
|
635
|
+
var RELIABILITY_KEYWORDS = [
|
|
636
|
+
"uptime",
|
|
637
|
+
"availability",
|
|
638
|
+
"fault",
|
|
639
|
+
"failover",
|
|
640
|
+
"recovery",
|
|
641
|
+
"backup",
|
|
642
|
+
"retry",
|
|
643
|
+
"circuit breaker",
|
|
644
|
+
"redundan",
|
|
645
|
+
"99.",
|
|
646
|
+
"sla",
|
|
647
|
+
"rto",
|
|
648
|
+
"rpo",
|
|
649
|
+
"disaster",
|
|
650
|
+
"high availability",
|
|
651
|
+
"ha",
|
|
652
|
+
"replication",
|
|
653
|
+
"health check"
|
|
654
|
+
];
|
|
655
|
+
function computeReliabilityReadiness(coverage, constraints, entities, bindings, rgrCompletedPhases) {
|
|
656
|
+
const stabilityCoverageScore = Math.min(100, coverage.stability * 100);
|
|
657
|
+
const reliabilityConstraints = constraints.filter((c) => {
|
|
658
|
+
const text = `${c.summary || ""} ${c.action || ""}`.toLowerCase();
|
|
659
|
+
return RELIABILITY_KEYWORDS.some((kw) => text.includes(kw));
|
|
660
|
+
});
|
|
661
|
+
const totalEntities = entities.length || 1;
|
|
662
|
+
const densityRatio = reliabilityConstraints.length / totalEntities;
|
|
663
|
+
const densityScore = Math.min(100, densityRatio * 50);
|
|
664
|
+
const faultTolerancePatterns = ["circuit breaker", "retry", "redundan", "failover", "backup"];
|
|
665
|
+
const faultToleranceConstraints = constraints.filter((c) => {
|
|
666
|
+
const text = `${c.summary || ""} ${c.action || ""}`.toLowerCase();
|
|
667
|
+
return faultTolerancePatterns.some((pattern) => text.includes(pattern));
|
|
668
|
+
});
|
|
669
|
+
const faultToleranceScore = Math.min(100, faultToleranceConstraints.length * 10);
|
|
670
|
+
const observabilityKeywords = ["log", "metric", "monitor", "alert", "trace", "health check"];
|
|
671
|
+
const observabilityConstraints = constraints.filter((c) => {
|
|
672
|
+
const text = `${c.summary || ""} ${c.action || ""}`.toLowerCase();
|
|
673
|
+
return observabilityKeywords.some((kw) => text.includes(kw));
|
|
674
|
+
});
|
|
675
|
+
const observabilityScore = Math.min(100, observabilityConstraints.length * 8);
|
|
676
|
+
const persistenceKeywords = ["backup", "replication", "disaster recovery", "snapshot", "restore"];
|
|
677
|
+
const persistenceConstraints = constraints.filter((c) => {
|
|
678
|
+
const text = `${c.summary || ""} ${c.action || ""}`.toLowerCase();
|
|
679
|
+
return persistenceKeywords.some((kw) => text.includes(kw));
|
|
680
|
+
});
|
|
681
|
+
const persistenceScore = Math.min(100, persistenceConstraints.length * 12);
|
|
682
|
+
const base = stabilityCoverageScore * 0.35 + densityScore * 0.25 + faultToleranceScore * 0.2 + observabilityScore * 0.1 + persistenceScore * 0.1;
|
|
683
|
+
let rgrBonus = 0;
|
|
684
|
+
if (rgrCompletedPhases.includes("stability"))
|
|
685
|
+
rgrBonus += 10;
|
|
686
|
+
if (rgrCompletedPhases.includes("observability"))
|
|
687
|
+
rgrBonus += 5;
|
|
688
|
+
if (rgrCompletedPhases.includes("test_spec"))
|
|
689
|
+
rgrBonus += 3;
|
|
690
|
+
return round(Math.min(100, Math.max(0, base + rgrBonus)));
|
|
691
|
+
}
|
|
692
|
+
var SCALABILITY_KEYWORDS = [
|
|
693
|
+
"concurrent",
|
|
694
|
+
"scale",
|
|
695
|
+
"scaling",
|
|
696
|
+
"growth",
|
|
697
|
+
"horizontal",
|
|
698
|
+
"vertical",
|
|
699
|
+
"shard",
|
|
700
|
+
"partition",
|
|
701
|
+
"capacity",
|
|
702
|
+
"rate limit",
|
|
703
|
+
"load balanc",
|
|
704
|
+
"autoscal",
|
|
705
|
+
"elastic",
|
|
706
|
+
"throughput",
|
|
707
|
+
"queue",
|
|
708
|
+
"async",
|
|
709
|
+
"cache",
|
|
710
|
+
"cdn",
|
|
711
|
+
"distributed"
|
|
712
|
+
];
|
|
713
|
+
function computeScalabilityReadiness(coverage, constraints, entities, bindings, rgrCompletedPhases) {
|
|
714
|
+
const scalabilityCoverageScore = Math.min(100, coverage.scalability * 100);
|
|
715
|
+
const scalabilityConstraints = constraints.filter((c) => {
|
|
716
|
+
const text = `${c.summary || ""} ${c.action || ""}`.toLowerCase();
|
|
717
|
+
return SCALABILITY_KEYWORDS.some((kw) => text.includes(kw));
|
|
718
|
+
});
|
|
719
|
+
const totalEntities = entities.length || 1;
|
|
720
|
+
const densityRatio = scalabilityConstraints.length / totalEntities;
|
|
721
|
+
const densityScore = Math.min(100, densityRatio * 50);
|
|
722
|
+
const scalePatterns = ["horizontal", "vertical", "shard", "partition", "load balanc", "autoscal"];
|
|
723
|
+
const scalePatternConstraints = constraints.filter((c) => {
|
|
724
|
+
const text = `${c.summary || ""} ${c.action || ""}`.toLowerCase();
|
|
725
|
+
return scalePatterns.some((pattern) => text.includes(pattern));
|
|
726
|
+
});
|
|
727
|
+
const scalePatternScore = Math.min(100, scalePatternConstraints.length * 10);
|
|
728
|
+
const performanceScore = Math.min(100, coverage.performance * 100);
|
|
729
|
+
const infraKeywords = ["cache", "cdn", "queue", "async", "distributed"];
|
|
730
|
+
const infraConstraints = constraints.filter((c) => {
|
|
731
|
+
const text = `${c.summary || ""} ${c.action || ""}`.toLowerCase();
|
|
732
|
+
return infraKeywords.some((kw) => text.includes(kw));
|
|
733
|
+
});
|
|
734
|
+
const infraScore = Math.min(100, infraConstraints.length * 12);
|
|
735
|
+
const base = scalabilityCoverageScore * 0.35 + densityScore * 0.25 + scalePatternScore * 0.2 + performanceScore * 0.1 + infraScore * 0.1;
|
|
736
|
+
let rgrBonus = 0;
|
|
737
|
+
if (rgrCompletedPhases.includes("performance"))
|
|
738
|
+
rgrBonus += 10;
|
|
739
|
+
if (rgrCompletedPhases.includes("infrastructure"))
|
|
740
|
+
rgrBonus += 5;
|
|
741
|
+
if (rgrCompletedPhases.includes("scalability"))
|
|
742
|
+
rgrBonus += 8;
|
|
743
|
+
return round(Math.min(100, Math.max(0, base + rgrBonus)));
|
|
744
|
+
}
|
|
745
|
+
function computeEngineeringReadiness(coverage, time, featureCount, constraintCount, frCount, depEdgeCount, securityReadinessPct, reliabilityReadinessPct, scalabilityReadinessPct, rgrCompletedPhases, conflictCount, depDepth) {
|
|
746
|
+
const speedupScore = Math.min(100, 30 + (time.speedup_factor - 1) * 35);
|
|
747
|
+
const nfrScore = Math.min(100, coverage.overall * 100);
|
|
748
|
+
const depthScore = featureCount > 0 ? Math.min(100, constraintCount / featureCount * 25) : 0;
|
|
749
|
+
let structScore = 0;
|
|
750
|
+
if (featureCount > 0)
|
|
751
|
+
structScore += 40;
|
|
752
|
+
if (frCount > 0)
|
|
753
|
+
structScore += 30;
|
|
754
|
+
if (depEdgeCount > 0)
|
|
755
|
+
structScore += 15;
|
|
756
|
+
if (constraintCount > 0)
|
|
757
|
+
structScore += 15;
|
|
758
|
+
const base = speedupScore * 0.25 + nfrScore * 0.15 + securityReadinessPct * 0.15 + reliabilityReadinessPct * 0.1 + scalabilityReadinessPct * 0.1 + depthScore * 0.1 + structScore * 0.15;
|
|
759
|
+
const conflictPenalty = Math.min(15, conflictCount * 3);
|
|
760
|
+
const depthRiskPenalty = depDepth > 2 ? Math.min(10, (depDepth - 2) * 3 * (1 - coverage.overall)) : 0;
|
|
761
|
+
let rgrBonus = 0;
|
|
762
|
+
if (rgrCompletedPhases.includes("test_spec"))
|
|
763
|
+
rgrBonus += 5;
|
|
764
|
+
if (rgrCompletedPhases.includes("functional"))
|
|
765
|
+
rgrBonus += 5;
|
|
766
|
+
if (rgrCompletedPhases.includes("performance"))
|
|
767
|
+
rgrBonus += 3;
|
|
768
|
+
if (rgrCompletedPhases.includes("economics"))
|
|
769
|
+
rgrBonus += 2;
|
|
770
|
+
return round(Math.min(100, Math.max(0, base - conflictPenalty - depthRiskPenalty + rgrBonus)));
|
|
771
|
+
}
|
|
772
|
+
function computeMetricsFromGraph(entities, edges, constraints, bindings, rgrCompleted, genericPrior) {
|
|
773
|
+
const traverser = new GraphTraverser(entities, edges, constraints, bindings);
|
|
774
|
+
const allConflicts = [];
|
|
775
|
+
const seen = /* @__PURE__ */ new Set();
|
|
776
|
+
for (const entity of entities) {
|
|
777
|
+
const sub = traverser.getSubgraph(entity.id, 1);
|
|
778
|
+
if (sub?.conflicts) {
|
|
779
|
+
for (const c of sub.conflicts) {
|
|
780
|
+
const key = `${c.constraint_a.id}:${c.constraint_b.id}`;
|
|
781
|
+
if (!seen.has(key)) {
|
|
782
|
+
seen.add(key);
|
|
783
|
+
allConflicts.push(c);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return computeGraphMetrics(entities, edges, constraints, bindings, allConflicts, rgrCompleted, genericPrior);
|
|
789
|
+
}
|
|
790
|
+
var SEVERITY_WEIGHT = { critical: 3, warning: 2, info: 1 };
|
|
791
|
+
function computeBindingCoverage(entities, edges, constraints, bindings) {
|
|
792
|
+
if (constraints.length === 0) {
|
|
793
|
+
return { security: 0, performance: 0, scalability: 0, stability: 0, compliance: 0, overall: 0 };
|
|
794
|
+
}
|
|
795
|
+
const boundEntityIds = new Set(bindings.map((b) => b.entity_id));
|
|
796
|
+
const constraintToEntities = /* @__PURE__ */ new Map();
|
|
797
|
+
for (const edge of edges) {
|
|
798
|
+
if (edge.edge_type !== "GOVERNED_BY" && edge.edge_type !== "BOUNDED_BY")
|
|
799
|
+
continue;
|
|
800
|
+
const existing = constraintToEntities.get(edge.target_id) ?? /* @__PURE__ */ new Set();
|
|
801
|
+
existing.add(edge.source_id);
|
|
802
|
+
constraintToEntities.set(edge.target_id, existing);
|
|
803
|
+
}
|
|
804
|
+
const buckets = ["security", "performance", "scalability", "stability", "compliance"];
|
|
805
|
+
const totalWeight = { security: 0, performance: 0, scalability: 0, stability: 0, compliance: 0 };
|
|
806
|
+
const boundWeight = { security: 0, performance: 0, scalability: 0, stability: 0, compliance: 0 };
|
|
807
|
+
for (const c of constraints) {
|
|
808
|
+
const bucket = toBucket(c.category);
|
|
809
|
+
if (!bucket)
|
|
810
|
+
continue;
|
|
811
|
+
const w = SEVERITY_WEIGHT[c.severity] ?? 1;
|
|
812
|
+
totalWeight[bucket] += w;
|
|
813
|
+
const linkedEntities = constraintToEntities.get(c.id);
|
|
814
|
+
if (linkedEntities) {
|
|
815
|
+
for (const eid of linkedEntities) {
|
|
816
|
+
if (boundEntityIds.has(eid)) {
|
|
817
|
+
boundWeight[bucket] += w;
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
const scores = {};
|
|
824
|
+
for (const b of buckets) {
|
|
825
|
+
scores[b] = totalWeight[b] > 0 ? boundWeight[b] / totalWeight[b] : 0;
|
|
826
|
+
}
|
|
827
|
+
const weights = { security: 1.5, performance: 1, scalability: 1, stability: 1, compliance: 1.5 };
|
|
828
|
+
let weightedSum = 0;
|
|
829
|
+
let wTotal = 0;
|
|
830
|
+
for (const b of buckets) {
|
|
831
|
+
weightedSum += scores[b] * weights[b];
|
|
832
|
+
wTotal += weights[b];
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
security: round(scores.security),
|
|
836
|
+
performance: round(scores.performance),
|
|
837
|
+
scalability: round(scores.scalability),
|
|
838
|
+
stability: round(scores.stability),
|
|
839
|
+
compliance: round(scores.compliance),
|
|
840
|
+
overall: round(weightedSum / wTotal)
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
function computeGenericGraphMetrics(entities, edges, constraints, bindings) {
|
|
844
|
+
const nfr_coverage = computeBindingCoverage(entities, edges, constraints, bindings);
|
|
845
|
+
const conflicts = [];
|
|
846
|
+
if (entities.length > 0) {
|
|
847
|
+
const traverser = new GraphTraverser(entities, edges, constraints, bindings);
|
|
848
|
+
const seen = /* @__PURE__ */ new Set();
|
|
849
|
+
for (const entity of entities) {
|
|
850
|
+
const sub = traverser.getSubgraph(entity.id, 1);
|
|
851
|
+
if (sub?.conflicts) {
|
|
852
|
+
for (const c of sub.conflicts) {
|
|
853
|
+
const key = `${c.constraint_a.id}:${c.constraint_b.id}`;
|
|
854
|
+
if (!seen.has(key)) {
|
|
855
|
+
seen.add(key);
|
|
856
|
+
conflicts.push(c);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
const time_estimate = computeTimeEstimate(entities, edges, constraints, bindings, nfr_coverage, conflicts.length);
|
|
863
|
+
const criticalNfrCount = constraints.filter((c) => c.severity === "critical").length;
|
|
864
|
+
const piiCount = entities.filter((e) => {
|
|
865
|
+
if (e.type !== "data_type")
|
|
866
|
+
return false;
|
|
867
|
+
const lower = (e.name + " " + (e.description ?? "")).toLowerCase();
|
|
868
|
+
return lower.includes("pii") || lower.includes("personal") || lower.includes("financial") || lower.includes("credential");
|
|
869
|
+
}).length;
|
|
870
|
+
const security_readiness_pct = computeSecurityReadiness(nfr_coverage, constraints, entities, bindings, [], piiCount);
|
|
871
|
+
const reliability_readiness_pct = computeReliabilityReadiness(nfr_coverage, constraints, entities, bindings, []);
|
|
872
|
+
const scalability_readiness_pct = computeScalabilityReadiness(nfr_coverage, constraints, entities, bindings, []);
|
|
873
|
+
const FRAMEWORK_MAP = {
|
|
874
|
+
soc2: "SOC 2",
|
|
875
|
+
csa_ccm: "CSA Controls Matrix",
|
|
876
|
+
pci: "PCI-DSS",
|
|
877
|
+
hipaa: "HIPAA",
|
|
878
|
+
fedramp: "FedRAMP",
|
|
879
|
+
gdpr: "GDPR/CCPA",
|
|
880
|
+
owasp: "OWASP LLM Top 10",
|
|
881
|
+
glba: "GLBA",
|
|
882
|
+
ferpa: "FERPA/COPPA"
|
|
883
|
+
};
|
|
884
|
+
const detectedFrameworks = /* @__PURE__ */ new Set();
|
|
885
|
+
for (const c of constraints) {
|
|
886
|
+
if (c.id.startsWith("constraint:blueprint:")) {
|
|
887
|
+
const suffix = c.id.replace("constraint:blueprint:", "");
|
|
888
|
+
for (const [prefix, label] of Object.entries(FRAMEWORK_MAP)) {
|
|
889
|
+
if (suffix.startsWith(prefix)) {
|
|
890
|
+
detectedFrameworks.add(label);
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
const frCount = entities.filter((e) => e.type === "component").length;
|
|
897
|
+
const engineering_readiness_pct = computeEngineeringReadiness(nfr_coverage, time_estimate, 0, constraints.length, frCount, 0, security_readiness_pct, reliability_readiness_pct, scalability_readiness_pct, [], conflicts.length, 0);
|
|
898
|
+
return {
|
|
899
|
+
nfr_coverage,
|
|
900
|
+
time_estimate,
|
|
901
|
+
complexity_factors: {
|
|
902
|
+
entity_count: entities.length,
|
|
903
|
+
nfr_count: constraints.length,
|
|
904
|
+
critical_nfr_count: criticalNfrCount,
|
|
905
|
+
pii_data_types: piiCount,
|
|
906
|
+
dependency_depth: 0,
|
|
907
|
+
conflict_count: conflicts.length
|
|
908
|
+
},
|
|
909
|
+
engineering_readiness_pct,
|
|
910
|
+
security_readiness_pct,
|
|
911
|
+
reliability_readiness_pct,
|
|
912
|
+
scalability_readiness_pct,
|
|
913
|
+
rgr_completed_phases: [],
|
|
914
|
+
compliance_frameworks: detectedFrameworks.size > 0 ? [...detectedFrameworks].sort() : void 0,
|
|
915
|
+
computed_at: /* @__PURE__ */ new Date()
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
var PRIOR_MATURITY_THRESHOLD = 5;
|
|
919
|
+
function blendWithPrior(productScore, genericScore, featureCount) {
|
|
920
|
+
if (genericScore === void 0 || genericScore === 0)
|
|
921
|
+
return productScore;
|
|
922
|
+
const alpha = Math.min(1, featureCount / PRIOR_MATURITY_THRESHOLD);
|
|
923
|
+
return Math.round(alpha * productScore + (1 - alpha) * genericScore);
|
|
924
|
+
}
|
|
925
|
+
function applyGenericPrior(metrics, prior, featureCount) {
|
|
926
|
+
return {
|
|
927
|
+
...metrics,
|
|
928
|
+
engineering_readiness_pct: blendWithPrior(metrics.engineering_readiness_pct ?? 0, prior.engineering_readiness_pct, featureCount),
|
|
929
|
+
security_readiness_pct: blendWithPrior(metrics.security_readiness_pct ?? 0, prior.security_readiness_pct, featureCount),
|
|
930
|
+
reliability_readiness_pct: blendWithPrior(metrics.reliability_readiness_pct ?? 0, prior.reliability_readiness_pct, featureCount),
|
|
931
|
+
scalability_readiness_pct: blendWithPrior(metrics.scalability_readiness_pct ?? 0, prior.scalability_readiness_pct, featureCount)
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
function round(n, decimals = 2) {
|
|
935
|
+
const factor = 10 ** decimals;
|
|
936
|
+
return Math.round(n * factor) / factor;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
export {
|
|
940
|
+
detectConstraintConflicts,
|
|
941
|
+
GraphTraverser,
|
|
942
|
+
computeGraphMetrics,
|
|
943
|
+
computeMetricsFromGraph,
|
|
944
|
+
computeGenericGraphMetrics,
|
|
945
|
+
applyGenericPrior
|
|
946
|
+
};
|