@aisdlc/core 1.2.0 → 1.3.1
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.cjs +197 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +197 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -148,17 +148,31 @@ function calculateLocAndHalstead(fullText, cyclomatic) {
|
|
|
148
148
|
const normalizedMI = Math.max(0, Math.min(100, rawMI * 100 / 171));
|
|
149
149
|
return { loc, mi: Math.round(normalizedMI * 10) / 10 };
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
var TEST_FILE_PATTERN = /(?:^|\/)(?:tests|__tests__|fixtures)\/|\.(?:spec|test)\.[cm]?[jt]sx?$/i;
|
|
152
|
+
var EMPTY_UI_HANDLER_PATTERN = /\bon[A-Z]\w*\s*=\s*\{\s*(?:\([^)]*\)\s*=>\s*\{\s*\}|function\s*\([^)]*\)\s*\{\s*\})\s*\}/;
|
|
153
|
+
var FAKE_WIRING_STUB_PATTERN = /(?:\/\/\s*T[O]DO:\s*(?:connect|wire|integrate)\b|\b(?:m[o]ckData|F[A]KE_DATA)\b)/i;
|
|
154
|
+
function isProductionFile(filePath) {
|
|
155
|
+
return !TEST_FILE_PATTERN.test(filePath.replace(/\\/g, "/"));
|
|
156
|
+
}
|
|
157
|
+
function detectAstCodeSmells(node, loc, fullText, filePath) {
|
|
152
158
|
const smells = [];
|
|
153
159
|
if (node.getDescendantsOfKind(tsMorph.SyntaxKind.AnyKeyword).length > 0) {
|
|
154
160
|
smells.push('Uso prohibido de "any"');
|
|
155
161
|
}
|
|
156
|
-
if (/\/\/\s*@ts-ignore|\/\/\s*eslint-disable/.test(
|
|
162
|
+
if (/\/\/\s*@ts-ignore|\/\/\s*eslint-disable/.test(fullText)) {
|
|
157
163
|
smells.push("Supresi\xF3n no autorizada de linter");
|
|
158
164
|
}
|
|
159
165
|
if (loc > 40) {
|
|
160
166
|
smells.push(`Funci\xF3n extensa (${loc} l\xEDneas > l\xEDmite 40)`);
|
|
161
167
|
}
|
|
168
|
+
if (isProductionFile(filePath)) {
|
|
169
|
+
if (EMPTY_UI_HANDLER_PATTERN.test(fullText)) {
|
|
170
|
+
smells.push("Handler de UI vac\xEDo (fake-wiring detectado)");
|
|
171
|
+
}
|
|
172
|
+
if (FAKE_WIRING_STUB_PATTERN.test(fullText)) {
|
|
173
|
+
smells.push("Datos simulados o conexi\xF3n pendiente en producci\xF3n (fake-wiring)");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
162
176
|
return smells;
|
|
163
177
|
}
|
|
164
178
|
function isDeclarationFunction(node) {
|
|
@@ -182,9 +196,8 @@ function buildNodeMetrics(node, filePath) {
|
|
|
182
196
|
const cyclomatic = computeCyclomaticFromAst(node);
|
|
183
197
|
const cognitive = computeCognitiveFromAst(node);
|
|
184
198
|
const fullText = node.getFullText();
|
|
185
|
-
const nodeText = node.getText();
|
|
186
199
|
const { loc, mi } = calculateLocAndHalstead(fullText, cyclomatic);
|
|
187
|
-
const codeSmells = detectAstCodeSmells(node, loc,
|
|
200
|
+
const codeSmells = detectAstCodeSmells(node, loc, fullText, filePath);
|
|
188
201
|
return { functionName, filePath, loc, cyclomatic, cognitive, maintainability: mi, codeSmells };
|
|
189
202
|
}
|
|
190
203
|
function buildModuleFallback(content, filePath) {
|
|
@@ -737,6 +750,12 @@ function walkMdFiles(dir, fileList = [], excludeDirs = [
|
|
|
737
750
|
"node_modules",
|
|
738
751
|
".git",
|
|
739
752
|
"dist",
|
|
753
|
+
"bin",
|
|
754
|
+
"obj",
|
|
755
|
+
"build",
|
|
756
|
+
"out",
|
|
757
|
+
"target",
|
|
758
|
+
"coverage",
|
|
740
759
|
".changeset",
|
|
741
760
|
"scratch",
|
|
742
761
|
"test-scaffold",
|
|
@@ -748,7 +767,9 @@ function walkMdFiles(dir, fileList = [], excludeDirs = [
|
|
|
748
767
|
".claude",
|
|
749
768
|
".cursor",
|
|
750
769
|
".github",
|
|
751
|
-
".vscode"
|
|
770
|
+
".vscode",
|
|
771
|
+
".vs",
|
|
772
|
+
".idea"
|
|
752
773
|
]) {
|
|
753
774
|
try {
|
|
754
775
|
if (!fs6__namespace.existsSync(dir)) return fileList;
|
|
@@ -3299,10 +3320,18 @@ var SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
|
3299
3320
|
"node_modules",
|
|
3300
3321
|
".git",
|
|
3301
3322
|
"dist",
|
|
3323
|
+
"bin",
|
|
3324
|
+
"obj",
|
|
3325
|
+
"build",
|
|
3326
|
+
"out",
|
|
3327
|
+
"target",
|
|
3328
|
+
"coverage",
|
|
3302
3329
|
"scratch",
|
|
3303
3330
|
"fixtures",
|
|
3304
3331
|
"templates",
|
|
3305
|
-
"examples"
|
|
3332
|
+
"examples",
|
|
3333
|
+
".vs",
|
|
3334
|
+
".idea"
|
|
3306
3335
|
]);
|
|
3307
3336
|
function shouldSkipDir(name) {
|
|
3308
3337
|
return SKIPPED_DIR_NAMES.has(name) || name.startsWith("temp-");
|
|
@@ -3405,7 +3434,12 @@ function verifyTraceability(options = {}) {
|
|
|
3405
3434
|
}
|
|
3406
3435
|
}
|
|
3407
3436
|
if (relFile.includes("architecture/") || relFile.includes("06_runtime_view") || relFile.includes("08_security_concept") || relFile.includes("09_decisions")) {
|
|
3408
|
-
architectureViews.push({
|
|
3437
|
+
architectureViews.push({
|
|
3438
|
+
file: relFile,
|
|
3439
|
+
id: frontmatter.id ? String(frontmatter.id).trim() : void 0,
|
|
3440
|
+
type: typeof frontmatter.type === "string" ? frontmatter.type.trim() : void 0,
|
|
3441
|
+
content
|
|
3442
|
+
});
|
|
3409
3443
|
}
|
|
3410
3444
|
} catch {
|
|
3411
3445
|
}
|
|
@@ -3508,8 +3542,9 @@ function verifyTraceability(options = {}) {
|
|
|
3508
3542
|
}
|
|
3509
3543
|
for (const view of architectureViews) {
|
|
3510
3544
|
if (view.content.includes(reqId)) {
|
|
3511
|
-
const
|
|
3512
|
-
|
|
3545
|
+
const isEntityWithId = view.type === "component" || view.type === "adr" || view.type === "architecture-decision-record" || view.type === "security-enclave" || view.id !== void 0 && (view.id.startsWith("CMP-") || view.id.startsWith("ADR-") || view.id.startsWith("SEC-ENC-"));
|
|
3546
|
+
const viewIdentifier = isEntityWithId && view.id ? view.id : path8__namespace.basename(view.file);
|
|
3547
|
+
if (!archTraces.includes(viewIdentifier)) archTraces.push(viewIdentifier);
|
|
3513
3548
|
}
|
|
3514
3549
|
}
|
|
3515
3550
|
const uniqueArchTraces = Array.from(new Set(archTraces));
|
|
@@ -3846,7 +3881,7 @@ function scanAllTestArtifacts(rootDir) {
|
|
|
3846
3881
|
for (const e of entries) {
|
|
3847
3882
|
const fullPath = path8__namespace.join(dir, e.name);
|
|
3848
3883
|
if (e.isDirectory()) {
|
|
3849
|
-
if (!["node_modules", ".git", "dist", ".changeset", "scratch", "fixtures", "templates", "examples"].includes(e.name)) {
|
|
3884
|
+
if (!["node_modules", ".git", "dist", "bin", "obj", "build", "out", "target", "coverage", ".changeset", "scratch", "fixtures", "templates", "examples", ".vs", ".idea"].includes(e.name)) {
|
|
3850
3885
|
walk(fullPath);
|
|
3851
3886
|
}
|
|
3852
3887
|
} else {
|
|
@@ -7151,7 +7186,31 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7151
7186
|
for (const n of pdac.drifts) {
|
|
7152
7187
|
driftTargets.add(n.targetId);
|
|
7153
7188
|
}
|
|
7189
|
+
const NON_GRAPH_TYPES = /* @__PURE__ */ new Set([
|
|
7190
|
+
"term",
|
|
7191
|
+
"handoff",
|
|
7192
|
+
"agent-handoff",
|
|
7193
|
+
"tasks",
|
|
7194
|
+
"task-plan",
|
|
7195
|
+
"spec-change-proposal",
|
|
7196
|
+
"proposal",
|
|
7197
|
+
"spec-design",
|
|
7198
|
+
"spec-change-design",
|
|
7199
|
+
"design",
|
|
7200
|
+
"delivery-spec",
|
|
7201
|
+
"spec",
|
|
7202
|
+
"pre-merge-audit-report",
|
|
7203
|
+
"user-design-feedback",
|
|
7204
|
+
"release-kpis",
|
|
7205
|
+
"session-telemetry",
|
|
7206
|
+
"license-manifest",
|
|
7207
|
+
"mcp-config",
|
|
7208
|
+
"user-manual",
|
|
7209
|
+
"production-manual",
|
|
7210
|
+
"architecture-glossary"
|
|
7211
|
+
]);
|
|
7154
7212
|
const scannedArtifactsMap = /* @__PURE__ */ new Map();
|
|
7213
|
+
const archViewByBasename = /* @__PURE__ */ new Map();
|
|
7155
7214
|
const artifactDirs = ["product", "architecture", "specs", "security"];
|
|
7156
7215
|
for (const dirName of artifactDirs) {
|
|
7157
7216
|
const fullDirPath = path8__namespace.join(rootDir, dirName);
|
|
@@ -7161,6 +7220,7 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7161
7220
|
try {
|
|
7162
7221
|
const fileContent = fs6__namespace.readFileSync(f, "utf-8");
|
|
7163
7222
|
const relPath = path8__namespace.relative(rootDir, f).replace(/\\/g, "/");
|
|
7223
|
+
if (relPath.includes("/changes/") || relPath.startsWith("changes/")) continue;
|
|
7164
7224
|
const match = fileContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
7165
7225
|
if (!match) continue;
|
|
7166
7226
|
let fm = {};
|
|
@@ -7172,6 +7232,9 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7172
7232
|
const id = typeof fm.id === "string" ? fm.id.trim() : void 0;
|
|
7173
7233
|
if (!id) continue;
|
|
7174
7234
|
let type = typeof fm.type === "string" ? fm.type.trim() : "";
|
|
7235
|
+
if (NON_GRAPH_TYPES.has(type) || id.startsWith("TERM-") || id.startsWith("HOF-") || id.startsWith("AHOF-") || id.startsWith("CHG-") || id.startsWith("DSG-") || id.startsWith("SPEC-") || id.startsWith("TSK-") || id.startsWith("UDF-") || id.startsWith("AUDIT-")) {
|
|
7236
|
+
continue;
|
|
7237
|
+
}
|
|
7175
7238
|
let layer = "product";
|
|
7176
7239
|
if (id.startsWith("JRN-") || type === "journey") {
|
|
7177
7240
|
type = "journey";
|
|
@@ -7206,16 +7269,19 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7206
7269
|
} else if (id.startsWith("SEC-REQ-") || type === "security-requirement") {
|
|
7207
7270
|
type = "security-requirement";
|
|
7208
7271
|
layer = "requirement";
|
|
7209
|
-
} else if (id.startsWith("FR-") || id.startsWith("QR-") || id.startsWith("CON-") || type === "requirement") {
|
|
7272
|
+
} else if (id.startsWith("FR-") || id.startsWith("QR-") || id.startsWith("CON-") || type === "requirement" || type === "functional-requirement" || type === "quality-requirement") {
|
|
7210
7273
|
type = "requirement";
|
|
7211
7274
|
layer = "requirement";
|
|
7275
|
+
} else if (id.startsWith("ARCH-") || relPath.includes("architecture/")) {
|
|
7276
|
+
type = "architecture";
|
|
7277
|
+
layer = "architecture";
|
|
7212
7278
|
} else {
|
|
7213
7279
|
type = type || "product";
|
|
7214
7280
|
layer = "product";
|
|
7215
7281
|
}
|
|
7216
7282
|
const title = typeof fm.title === "string" ? fm.title.trim() : id;
|
|
7217
7283
|
const content = fileContent.replace(/^---\r?\n[\s\S]*?\r?\n---/, "").trim();
|
|
7218
|
-
|
|
7284
|
+
const scannedArt = {
|
|
7219
7285
|
id,
|
|
7220
7286
|
type,
|
|
7221
7287
|
title,
|
|
@@ -7223,7 +7289,12 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7223
7289
|
filePath: relPath,
|
|
7224
7290
|
content,
|
|
7225
7291
|
frontmatter: fm
|
|
7226
|
-
}
|
|
7292
|
+
};
|
|
7293
|
+
if (layer === "architecture" && type === "architecture") {
|
|
7294
|
+
archViewByBasename.set(path8__namespace.basename(relPath), scannedArt);
|
|
7295
|
+
} else {
|
|
7296
|
+
scannedArtifactsMap.set(id, scannedArt);
|
|
7297
|
+
}
|
|
7227
7298
|
} catch {
|
|
7228
7299
|
}
|
|
7229
7300
|
}
|
|
@@ -7232,7 +7303,7 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7232
7303
|
for (const r of trace.rows) {
|
|
7233
7304
|
const isOk = (r.productStatus === "COMPLIANT" || r.productStatus === "CONFORME") && (r.archStatus === "COMPLIANT" || r.archStatus === "CONFORME") && (r.testStatus === "COMPLIANT" || r.testStatus === "CONFORME");
|
|
7234
7305
|
const status = isOk ? "COMPLIANT" : "ORPHAN";
|
|
7235
|
-
const reqType = r.id.startsWith("SEC-REQ-") ? "security-requirement" :
|
|
7306
|
+
const reqType = r.id.startsWith("SEC-REQ-") ? "security-requirement" : "requirement";
|
|
7236
7307
|
nodeMap.set(r.id, {
|
|
7237
7308
|
id: r.id,
|
|
7238
7309
|
label: r.id,
|
|
@@ -7292,13 +7363,16 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7292
7363
|
});
|
|
7293
7364
|
if (!nodeMap.has(a)) {
|
|
7294
7365
|
const aType = a.startsWith("CMP-") ? "component" : a.startsWith("ADR-") ? "adr" : a.startsWith("SEC-ENC-") ? "security-enclave" : "architecture";
|
|
7366
|
+
const viewArt = archViewByBasename.get(a);
|
|
7295
7367
|
nodeMap.set(a, {
|
|
7296
7368
|
id: a,
|
|
7297
7369
|
label: a,
|
|
7298
|
-
title: a,
|
|
7370
|
+
title: viewArt?.title || a,
|
|
7299
7371
|
type: aType,
|
|
7300
7372
|
layer: "architecture",
|
|
7301
|
-
status: "COMPLIANT"
|
|
7373
|
+
status: "COMPLIANT",
|
|
7374
|
+
filePath: viewArt?.filePath,
|
|
7375
|
+
content: viewArt?.content
|
|
7302
7376
|
});
|
|
7303
7377
|
}
|
|
7304
7378
|
}
|
|
@@ -7430,7 +7504,31 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7430
7504
|
status: "COMPLIANT"
|
|
7431
7505
|
});
|
|
7432
7506
|
}
|
|
7433
|
-
const
|
|
7507
|
+
const supportingActors = Array.isArray(fm["supporting-actors"]) ? fm["supporting-actors"] : Array.isArray(fm["secondary-actors"]) ? fm["secondary-actors"] : [];
|
|
7508
|
+
for (const actorId of supportingActors) {
|
|
7509
|
+
if (typeof actorId === "string" && actorId.trim()) {
|
|
7510
|
+
const a = actorId.trim();
|
|
7511
|
+
if (!nodeMap.has(a)) {
|
|
7512
|
+
nodeMap.set(a, {
|
|
7513
|
+
id: a,
|
|
7514
|
+
label: a,
|
|
7515
|
+
title: a,
|
|
7516
|
+
type: "actor",
|
|
7517
|
+
layer: "product",
|
|
7518
|
+
status: "COMPLIANT"
|
|
7519
|
+
});
|
|
7520
|
+
}
|
|
7521
|
+
addEdge({
|
|
7522
|
+
id: `edge-${a}-${art.id}`,
|
|
7523
|
+
source: a,
|
|
7524
|
+
target: art.id,
|
|
7525
|
+
label: "initiates",
|
|
7526
|
+
relation: "initiates",
|
|
7527
|
+
status: "COMPLIANT"
|
|
7528
|
+
});
|
|
7529
|
+
}
|
|
7530
|
+
}
|
|
7531
|
+
const governedBy = Array.isArray(fm["governed-by"]) ? fm["governed-by"] : Array.isArray(fm["business-rules"]) ? fm["business-rules"] : [];
|
|
7434
7532
|
for (const ruleId of governedBy) {
|
|
7435
7533
|
if (typeof ruleId === "string" && ruleId.trim()) {
|
|
7436
7534
|
const r = ruleId.trim();
|
|
@@ -7491,7 +7589,31 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7491
7589
|
status: "COMPLIANT"
|
|
7492
7590
|
});
|
|
7493
7591
|
}
|
|
7494
|
-
const
|
|
7592
|
+
const secondaryThreatActors = Array.isArray(fm["secondary-actors"]) ? fm["secondary-actors"] : Array.isArray(fm["supporting-actors"]) ? fm["supporting-actors"] : [];
|
|
7593
|
+
for (const secActor of secondaryThreatActors) {
|
|
7594
|
+
if (typeof secActor === "string" && secActor.trim()) {
|
|
7595
|
+
const sa = secActor.trim();
|
|
7596
|
+
if (!nodeMap.has(sa)) {
|
|
7597
|
+
nodeMap.set(sa, {
|
|
7598
|
+
id: sa,
|
|
7599
|
+
label: sa,
|
|
7600
|
+
title: sa,
|
|
7601
|
+
type: sa.startsWith("ACT-THREAT-") ? "threat-actor" : "actor",
|
|
7602
|
+
layer: "product",
|
|
7603
|
+
status: "COMPLIANT"
|
|
7604
|
+
});
|
|
7605
|
+
}
|
|
7606
|
+
addEdge({
|
|
7607
|
+
id: `edge-${sa}-${art.id}`,
|
|
7608
|
+
source: sa,
|
|
7609
|
+
target: art.id,
|
|
7610
|
+
label: "threatens",
|
|
7611
|
+
relation: "threatens",
|
|
7612
|
+
status: "COMPLIANT"
|
|
7613
|
+
});
|
|
7614
|
+
}
|
|
7615
|
+
}
|
|
7616
|
+
const rawTargets = Array.isArray(fm["targets-use-case"]) ? fm["targets-use-case"] : typeof fm["targets-use-case"] === "string" ? [fm["targets-use-case"]] : Array.isArray(fm["targets-use-cases"]) ? fm["targets-use-cases"] : Array.isArray(fm["targets"]) ? fm["targets"] : [];
|
|
7495
7617
|
for (const tgt of rawTargets) {
|
|
7496
7618
|
if (typeof tgt === "string" && tgt.trim()) {
|
|
7497
7619
|
const t = tgt.trim();
|
|
@@ -7556,6 +7678,40 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7556
7678
|
});
|
|
7557
7679
|
}
|
|
7558
7680
|
}
|
|
7681
|
+
} else if (art.type === "adr") {
|
|
7682
|
+
const affectedComponents = Array.isArray(fm["affects-components"]) ? fm["affects-components"] : typeof fm["affects-components"] === "string" ? [fm["affects-components"]] : [];
|
|
7683
|
+
for (const cmpId of affectedComponents) {
|
|
7684
|
+
if (typeof cmpId === "string" && cmpId.trim()) {
|
|
7685
|
+
const c = cmpId.trim();
|
|
7686
|
+
if (nodeMap.has(c)) {
|
|
7687
|
+
addEdge({
|
|
7688
|
+
id: `edge-${art.id}-${c}`,
|
|
7689
|
+
source: art.id,
|
|
7690
|
+
target: c,
|
|
7691
|
+
label: "governs",
|
|
7692
|
+
relation: "governs",
|
|
7693
|
+
status: "COMPLIANT"
|
|
7694
|
+
});
|
|
7695
|
+
}
|
|
7696
|
+
}
|
|
7697
|
+
}
|
|
7698
|
+
} else if (art.type === "security-enclave") {
|
|
7699
|
+
const enforcedBy = Array.isArray(fm["enforced-by"]) ? fm["enforced-by"] : typeof fm["enforced-by"] === "string" ? [fm["enforced-by"]] : [];
|
|
7700
|
+
for (const secReq of enforcedBy) {
|
|
7701
|
+
if (typeof secReq === "string" && secReq.trim()) {
|
|
7702
|
+
const s = secReq.trim();
|
|
7703
|
+
if (nodeMap.has(s)) {
|
|
7704
|
+
addEdge({
|
|
7705
|
+
id: `edge-${s}-${art.id}`,
|
|
7706
|
+
source: s,
|
|
7707
|
+
target: art.id,
|
|
7708
|
+
label: "satisfies",
|
|
7709
|
+
relation: "satisfies",
|
|
7710
|
+
status: "COMPLIANT"
|
|
7711
|
+
});
|
|
7712
|
+
}
|
|
7713
|
+
}
|
|
7714
|
+
}
|
|
7559
7715
|
}
|
|
7560
7716
|
}
|
|
7561
7717
|
for (const [id, node] of nodeMap.entries()) {
|
|
@@ -7705,6 +7861,9 @@ function collectDashboardMetrics(rootDir) {
|
|
|
7705
7861
|
function escapeHtml(str) {
|
|
7706
7862
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
7707
7863
|
}
|
|
7864
|
+
function safeJsonForScript(data) {
|
|
7865
|
+
return JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
7866
|
+
}
|
|
7708
7867
|
function renderAutonomyModeSection(mode, statusIcon, count, roleDescription, allTasks = []) {
|
|
7709
7868
|
const modeTasks = (allTasks || []).filter(
|
|
7710
7869
|
(t) => (t.task?.autonomyMode || "").toUpperCase() === mode
|
|
@@ -7816,14 +7975,14 @@ function renderAutonomyModeSection(mode, statusIcon, count, roleDescription, all
|
|
|
7816
7975
|
}
|
|
7817
7976
|
function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard Interactivo de Trazabilidad y Calidad") {
|
|
7818
7977
|
const cytoscapeScript = getCytoscapeScript();
|
|
7819
|
-
const elementsJson =
|
|
7978
|
+
const elementsJson = safeJsonForScript(graphData.elements);
|
|
7820
7979
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7821
7980
|
return `<!DOCTYPE html>
|
|
7822
7981
|
<html lang="es">
|
|
7823
7982
|
<head>
|
|
7824
7983
|
<meta charset="UTF-8">
|
|
7825
7984
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7826
|
-
<title>${title}</title>
|
|
7985
|
+
<title>${escapeHtml(title)}</title>
|
|
7827
7986
|
<style>
|
|
7828
7987
|
:root {
|
|
7829
7988
|
--bg-dark: #0f172a;
|
|
@@ -8198,12 +8357,12 @@ function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard
|
|
|
8198
8357
|
${metricsData.trace.rows.map((r) => {
|
|
8199
8358
|
const isRowOk = (r.productStatus === "COMPLIANT" || r.productStatus === "CONFORME") && (r.archStatus === "COMPLIANT" || r.archStatus === "CONFORME") && (r.testStatus === "COMPLIANT" || r.testStatus === "CONFORME");
|
|
8200
8359
|
return `
|
|
8201
|
-
<tr onclick="focusNode('${r.id}')" onmouseenter="startRowTooltip(event, '${r.id}')" onmouseleave="cancelRowTooltip()" onmousemove="updateRowTooltipPos(event)" style="cursor: pointer;">
|
|
8202
|
-
<td><strong><code>${r.id}</code></strong></td>
|
|
8203
|
-
<td>${r.title}</td>
|
|
8204
|
-
<td><code>${r.productTraces}</code></td>
|
|
8205
|
-
<td><code>${r.archTraces}</code></td>
|
|
8206
|
-
<td><code>${r.testTraces}</code></td>
|
|
8360
|
+
<tr onclick="focusNode('${escapeHtml(String(r.id || ""))}')" onmouseenter="startRowTooltip(event, '${escapeHtml(String(r.id || ""))}')" onmouseleave="cancelRowTooltip()" onmousemove="updateRowTooltipPos(event)" style="cursor: pointer;">
|
|
8361
|
+
<td><strong><code>${escapeHtml(String(r.id || ""))}</code></strong></td>
|
|
8362
|
+
<td>${escapeHtml(String(r.title || ""))}</td>
|
|
8363
|
+
<td><code>${escapeHtml(String(r.productTraces || ""))}</code></td>
|
|
8364
|
+
<td><code>${escapeHtml(String(r.archTraces || ""))}</code></td>
|
|
8365
|
+
<td><code>${escapeHtml(String(r.testTraces || ""))}</code></td>
|
|
8207
8366
|
<td><span class="badge ${isRowOk ? "badge-green" : "badge-amber"}">${isRowOk ? "COMPLIANT" : "ORPHAN"}</span></td>
|
|
8208
8367
|
</tr>
|
|
8209
8368
|
`;
|
|
@@ -8416,7 +8575,7 @@ function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard
|
|
|
8416
8575
|
</script>
|
|
8417
8576
|
<script>
|
|
8418
8577
|
var elements = ${elementsJson};
|
|
8419
|
-
var govTasks = ${
|
|
8578
|
+
var govTasks = ${safeJsonForScript(metricsData.gov.tasks || [])};
|
|
8420
8579
|
var cy = null;
|
|
8421
8580
|
|
|
8422
8581
|
var reqHoverTimer = null;
|
|
@@ -9049,9 +9208,10 @@ function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard
|
|
|
9049
9208
|
|
|
9050
9209
|
function switchTab(name) {
|
|
9051
9210
|
hideTooltip();
|
|
9052
|
-
document.querySelectorAll('.tab-btn').forEach(function(b) {
|
|
9211
|
+
document.querySelectorAll('.tab-btn').forEach(function(b) {
|
|
9212
|
+
b.classList.toggle('active', b.getAttribute('onclick') === "switchTab('" + name + "')");
|
|
9213
|
+
});
|
|
9053
9214
|
document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); });
|
|
9054
|
-
event.target?.classList?.add('active');
|
|
9055
9215
|
var p = document.getElementById('tab-' + name);
|
|
9056
9216
|
if (p) p.classList.add('active');
|
|
9057
9217
|
if (name === 'graph' && cy) {
|
|
@@ -9313,6 +9473,7 @@ var SPECIALIZED_AGENT_TEMPLATES = [
|
|
|
9313
9473
|
|
|
9314
9474
|
## Operational Directives & Guardrails
|
|
9315
9475
|
- Decompose the system into building blocks \`CMP-*\` ensuring each component declares which use cases \`UC-*\` it implements and which requirements (\`FR-*\`, \`QR-*\`, \`SEC-REQ-*\`) it fulfills under \`satisfies-requirements\`.
|
|
9476
|
+
- **Contract-First Full-Stack Integration**: Whenever a feature or change spans Frontend (UI) and Backend (API/services), define an explicit shared typed contract (Zod schemas, shared TypeScript DTOs, or OpenAPI specification) in \`design.md\` before implementation so both layers compile against a single source of truth.
|
|
9316
9477
|
- Generate interaction and sequence diagrams in native Mermaid syntax.
|
|
9317
9478
|
- Document critical technological decisions using immutable \`ADR-*\` records conforming to \`schemas/architecture/adr.schema.json\`.
|
|
9318
9479
|
- Validate that architectural choices comply with legal constraints in \`license-policy.yaml\`.
|
|
@@ -9335,6 +9496,7 @@ var SPECIALIZED_AGENT_TEMPLATES = [
|
|
|
9335
9496
|
2. **Boundary Cases** (\`Scenario Outline\` + \`Examples\` table): values at the extremes of the accepted range.
|
|
9336
9497
|
3. **Out-of-Range / Invalid Cases** (\`Scenario Outline\` + \`Examples\` table): null, empty, wrong types, values exceeding bounds.
|
|
9337
9498
|
- Apply conditional categories when applicable: Security (\`@security @mitigation\`), Performance (\`@performance\`), Idempotency (\`@idempotence\`), Postconditions, and Interface Contracts.
|
|
9499
|
+
- **Zero-Mock Integration Rule**: When an \`FR-*\` spans UI and Backend, unit tests mocking \`fetch\`/HTTP in isolation are insufficient; include integration/contract or E2E test scenarios verifying real request/response wiring and UI state transitions (\`Loading\`, \`Empty\`, \`Error\`, \`Nominal\`).
|
|
9338
9500
|
- Tag all scenarios with \`@<FR-ID> @automated @regression\`.
|
|
9339
9501
|
- **STRICTLY FORBIDDEN**: Including production code, suggesting implementation, or anticipating technical solutions.
|
|
9340
9502
|
- Run \`pnpm run verify:testing\` before emitting handoff.
|
|
@@ -9353,6 +9515,9 @@ var SPECIALIZED_AGENT_TEMPLATES = [
|
|
|
9353
9515
|
- Respect the autonomy mode assigned to each task in \`tasks.md\` (\`AUTONOMOUS\`, \`HUMAN_REVIEW_PLAN\`, \`AMBIGUOUS\`, \`HIGH_RISK_MANUAL\`).
|
|
9354
9516
|
- Apply Test-Driven Development (TDD): make failing tests delivered by \`agent-qa-engineer\` pass green without altering tests to accommodate buggy code.
|
|
9355
9517
|
- Cite implemented \`FR-*\` or \`SEC-REQ-*\` IDs in test header comments to maintain 360\xB0 reverse traceability.
|
|
9518
|
+
- **Zero Fake-Wiring Guardrail**: Strictly forbidden from marking a task \`COMPLETED\` with hardcoded \`mockData\` arrays in production views, \`setTimeout\` simulating network calls, or empty UI event handlers (\`onClick={() => {}}\`, \`onSubmit={() => {}}\`). Frontend components must wire directly to the shared contract and real Backend endpoints.
|
|
9519
|
+
- **Mandatory 4 UI States Matrix**: Every data-driven UI component or screen must explicitly implement all 4 usability states: \`Loading\` (spinner/skeleton + disabled submit), \`Empty\` (clear message + CTA), \`Error\` (visible error recovery banner, never silent failure), and \`Nominal/Success\` (accessible semantic DOM + keyboard navigation).
|
|
9520
|
+
- **Live Runtime Smoke Verification**: For tasks touching UI or UI-to-Backend integration, unit tests alone are insufficient. Before emitting handoff, boot the real Backend and Frontend servers and verify (via Playwright E2E or Chrome DevTools MCP) that browser \`console.error\` and \`pageerror\` counts are \`0\` and API calls return real \`2xx\` responses without CORS or schema mismatches.
|
|
9356
9521
|
- Before adding any external dependency, verify its SPDX license is permitted in \`license-policy.yaml\`.
|
|
9357
9522
|
- Respect non-negotiable \`quality-policy.yaml\` thresholds: Cyclomatic Complexity $\\le 10$, Cognitive Complexity $\\le 15$, Maintainability Index $\\ge 50$, Function Length $\\le 40$ lines.
|
|
9358
9523
|
- Run unified pre-flight with auto-fix (\`pnpm run check:fix\`) and full verification (\`pnpm run verify:all\`).
|
|
@@ -9401,9 +9566,9 @@ var SPECIALIZED_AGENT_TEMPLATES = [
|
|
|
9401
9566
|
## Operational Directives & Guardrails
|
|
9402
9567
|
- Operate under bimodal discipline depending on the lifecycle phase:
|
|
9403
9568
|
1. **Design Mode (Upstream)**: Adopt the primary actor persona under real-world field conditions (stress, latency, constrained viewports). Separate strict Minimum Viable Product (MVP) core from future roadmap ideas using \`templates/product/user-design-feedback.template.md\`.
|
|
9404
|
-
2. **Functional Validation Mode (Downstream / Pre-PR / Post-Development Functional Validation)**: Inspect \`agent-developer\` deliverables once unit tests pass green. Contrast actual UI, CLI, and API behavior against approved \`UC-*\` and \`FR-*\` criteria.
|
|
9569
|
+
2. **Functional Validation Mode (Downstream / Pre-PR / Post-Development Functional Validation)**: Inspect \`agent-developer\` deliverables once unit tests pass green by executing the real application (via Playwright or Chrome DevTools MCP when UI is present). Contrast actual UI, CLI, and API behavior against approved \`UC-*\` and \`FR-*\` criteria, verifying real Backend connectivity, the 4 UI usability states (\`Loading\`, \`Empty\`, \`Error\`, \`Nominal\`), keyboard/ARIA accessibility, and \`0\` browser \`console.error\` / network failures.
|
|
9405
9570
|
- Formulate decisive questions under \`open-questions\` for the human Product Owner.
|
|
9406
|
-
- Emit handoff block suggesting the Pre-Merge Audit Triad (\`agent-code-reviewer\`, \`agent-security-auditor\`, \`agent-compliance-checker\`) if compliant, or return to \`agent-developer\` on functional drift.`
|
|
9571
|
+
- Emit handoff block suggesting the Pre-Merge Audit Triad (\`agent-code-reviewer\`, \`agent-security-auditor\`, \`agent-compliance-checker\`) if compliant, or return to \`agent-developer\` on functional drift or fake-wiring.`
|
|
9407
9572
|
},
|
|
9408
9573
|
{
|
|
9409
9574
|
id: "agent-code-reviewer",
|
|
@@ -9418,6 +9583,7 @@ var SPECIALIZED_AGENT_TEMPLATES = [
|
|
|
9418
9583
|
- Analyze code diffs against \`quality-policy.yaml\` standards: Cyclomatic Complexity $\\le 10$, Cognitive Complexity $\\le 15$, Maintainability Index $\\ge 50$, Lines per Function $\\le 40$.
|
|
9419
9584
|
- Detect improper coupling, code smells, magic numbers, ambiguous identifiers, and encapsulation breaches.
|
|
9420
9585
|
- Flag premature abstractions and speculative code violating YAGNI.
|
|
9586
|
+
- **Block Fake-Wiring & Disconnected UI**: Flag as \`[BLOCKING]\` any production UI component containing hardcoded mock data (\`mockData\`), empty event handlers (\`onClick={() => {}}\`), missing error/loading state handling, or API calls bypassing shared contracts.
|
|
9421
9587
|
- Coordinate Pre-Merge Audit Triad with \`agent-security-auditor\` and \`agent-compliance-checker\`.
|
|
9422
9588
|
- Categorize findings into: \`[BLOCKING]\` (quality violation or broken pattern), \`[CLEAN_CODE_SUGGESTION]\` (non-blocking improvement), and \`[COMPLIANT]\`.
|
|
9423
9589
|
- If autonomy is $\\ge$ \`HUMAN_REVIEW_PLAN\`, conclude by emitting the Workflow Handoff block (\`templates/workflow/agent-handoff.template.md\`) to the human Tech Lead for final approval and merge, opening the Human Action Window. In \`AUTONOMOUS\` mode, omit interactive handoff.`
|