@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.js
CHANGED
|
@@ -120,17 +120,31 @@ function calculateLocAndHalstead(fullText, cyclomatic) {
|
|
|
120
120
|
const normalizedMI = Math.max(0, Math.min(100, rawMI * 100 / 171));
|
|
121
121
|
return { loc, mi: Math.round(normalizedMI * 10) / 10 };
|
|
122
122
|
}
|
|
123
|
-
|
|
123
|
+
var TEST_FILE_PATTERN = /(?:^|\/)(?:tests|__tests__|fixtures)\/|\.(?:spec|test)\.[cm]?[jt]sx?$/i;
|
|
124
|
+
var EMPTY_UI_HANDLER_PATTERN = /\bon[A-Z]\w*\s*=\s*\{\s*(?:\([^)]*\)\s*=>\s*\{\s*\}|function\s*\([^)]*\)\s*\{\s*\})\s*\}/;
|
|
125
|
+
var FAKE_WIRING_STUB_PATTERN = /(?:\/\/\s*T[O]DO:\s*(?:connect|wire|integrate)\b|\b(?:m[o]ckData|F[A]KE_DATA)\b)/i;
|
|
126
|
+
function isProductionFile(filePath) {
|
|
127
|
+
return !TEST_FILE_PATTERN.test(filePath.replace(/\\/g, "/"));
|
|
128
|
+
}
|
|
129
|
+
function detectAstCodeSmells(node, loc, fullText, filePath) {
|
|
124
130
|
const smells = [];
|
|
125
131
|
if (node.getDescendantsOfKind(SyntaxKind.AnyKeyword).length > 0) {
|
|
126
132
|
smells.push('Uso prohibido de "any"');
|
|
127
133
|
}
|
|
128
|
-
if (/\/\/\s*@ts-ignore|\/\/\s*eslint-disable/.test(
|
|
134
|
+
if (/\/\/\s*@ts-ignore|\/\/\s*eslint-disable/.test(fullText)) {
|
|
129
135
|
smells.push("Supresi\xF3n no autorizada de linter");
|
|
130
136
|
}
|
|
131
137
|
if (loc > 40) {
|
|
132
138
|
smells.push(`Funci\xF3n extensa (${loc} l\xEDneas > l\xEDmite 40)`);
|
|
133
139
|
}
|
|
140
|
+
if (isProductionFile(filePath)) {
|
|
141
|
+
if (EMPTY_UI_HANDLER_PATTERN.test(fullText)) {
|
|
142
|
+
smells.push("Handler de UI vac\xEDo (fake-wiring detectado)");
|
|
143
|
+
}
|
|
144
|
+
if (FAKE_WIRING_STUB_PATTERN.test(fullText)) {
|
|
145
|
+
smells.push("Datos simulados o conexi\xF3n pendiente en producci\xF3n (fake-wiring)");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
134
148
|
return smells;
|
|
135
149
|
}
|
|
136
150
|
function isDeclarationFunction(node) {
|
|
@@ -154,9 +168,8 @@ function buildNodeMetrics(node, filePath) {
|
|
|
154
168
|
const cyclomatic = computeCyclomaticFromAst(node);
|
|
155
169
|
const cognitive = computeCognitiveFromAst(node);
|
|
156
170
|
const fullText = node.getFullText();
|
|
157
|
-
const nodeText = node.getText();
|
|
158
171
|
const { loc, mi } = calculateLocAndHalstead(fullText, cyclomatic);
|
|
159
|
-
const codeSmells = detectAstCodeSmells(node, loc,
|
|
172
|
+
const codeSmells = detectAstCodeSmells(node, loc, fullText, filePath);
|
|
160
173
|
return { functionName, filePath, loc, cyclomatic, cognitive, maintainability: mi, codeSmells };
|
|
161
174
|
}
|
|
162
175
|
function buildModuleFallback(content, filePath) {
|
|
@@ -709,6 +722,12 @@ function walkMdFiles(dir, fileList = [], excludeDirs = [
|
|
|
709
722
|
"node_modules",
|
|
710
723
|
".git",
|
|
711
724
|
"dist",
|
|
725
|
+
"bin",
|
|
726
|
+
"obj",
|
|
727
|
+
"build",
|
|
728
|
+
"out",
|
|
729
|
+
"target",
|
|
730
|
+
"coverage",
|
|
712
731
|
".changeset",
|
|
713
732
|
"scratch",
|
|
714
733
|
"test-scaffold",
|
|
@@ -720,7 +739,9 @@ function walkMdFiles(dir, fileList = [], excludeDirs = [
|
|
|
720
739
|
".claude",
|
|
721
740
|
".cursor",
|
|
722
741
|
".github",
|
|
723
|
-
".vscode"
|
|
742
|
+
".vscode",
|
|
743
|
+
".vs",
|
|
744
|
+
".idea"
|
|
724
745
|
]) {
|
|
725
746
|
try {
|
|
726
747
|
if (!fs6.existsSync(dir)) return fileList;
|
|
@@ -3271,10 +3292,18 @@ var SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
|
3271
3292
|
"node_modules",
|
|
3272
3293
|
".git",
|
|
3273
3294
|
"dist",
|
|
3295
|
+
"bin",
|
|
3296
|
+
"obj",
|
|
3297
|
+
"build",
|
|
3298
|
+
"out",
|
|
3299
|
+
"target",
|
|
3300
|
+
"coverage",
|
|
3274
3301
|
"scratch",
|
|
3275
3302
|
"fixtures",
|
|
3276
3303
|
"templates",
|
|
3277
|
-
"examples"
|
|
3304
|
+
"examples",
|
|
3305
|
+
".vs",
|
|
3306
|
+
".idea"
|
|
3278
3307
|
]);
|
|
3279
3308
|
function shouldSkipDir(name) {
|
|
3280
3309
|
return SKIPPED_DIR_NAMES.has(name) || name.startsWith("temp-");
|
|
@@ -3377,7 +3406,12 @@ function verifyTraceability(options = {}) {
|
|
|
3377
3406
|
}
|
|
3378
3407
|
}
|
|
3379
3408
|
if (relFile.includes("architecture/") || relFile.includes("06_runtime_view") || relFile.includes("08_security_concept") || relFile.includes("09_decisions")) {
|
|
3380
|
-
architectureViews.push({
|
|
3409
|
+
architectureViews.push({
|
|
3410
|
+
file: relFile,
|
|
3411
|
+
id: frontmatter.id ? String(frontmatter.id).trim() : void 0,
|
|
3412
|
+
type: typeof frontmatter.type === "string" ? frontmatter.type.trim() : void 0,
|
|
3413
|
+
content
|
|
3414
|
+
});
|
|
3381
3415
|
}
|
|
3382
3416
|
} catch {
|
|
3383
3417
|
}
|
|
@@ -3480,8 +3514,9 @@ function verifyTraceability(options = {}) {
|
|
|
3480
3514
|
}
|
|
3481
3515
|
for (const view of architectureViews) {
|
|
3482
3516
|
if (view.content.includes(reqId)) {
|
|
3483
|
-
const
|
|
3484
|
-
|
|
3517
|
+
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-"));
|
|
3518
|
+
const viewIdentifier = isEntityWithId && view.id ? view.id : path8.basename(view.file);
|
|
3519
|
+
if (!archTraces.includes(viewIdentifier)) archTraces.push(viewIdentifier);
|
|
3485
3520
|
}
|
|
3486
3521
|
}
|
|
3487
3522
|
const uniqueArchTraces = Array.from(new Set(archTraces));
|
|
@@ -3818,7 +3853,7 @@ function scanAllTestArtifacts(rootDir) {
|
|
|
3818
3853
|
for (const e of entries) {
|
|
3819
3854
|
const fullPath = path8.join(dir, e.name);
|
|
3820
3855
|
if (e.isDirectory()) {
|
|
3821
|
-
if (!["node_modules", ".git", "dist", ".changeset", "scratch", "fixtures", "templates", "examples"].includes(e.name)) {
|
|
3856
|
+
if (!["node_modules", ".git", "dist", "bin", "obj", "build", "out", "target", "coverage", ".changeset", "scratch", "fixtures", "templates", "examples", ".vs", ".idea"].includes(e.name)) {
|
|
3822
3857
|
walk(fullPath);
|
|
3823
3858
|
}
|
|
3824
3859
|
} else {
|
|
@@ -7123,7 +7158,31 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7123
7158
|
for (const n of pdac.drifts) {
|
|
7124
7159
|
driftTargets.add(n.targetId);
|
|
7125
7160
|
}
|
|
7161
|
+
const NON_GRAPH_TYPES = /* @__PURE__ */ new Set([
|
|
7162
|
+
"term",
|
|
7163
|
+
"handoff",
|
|
7164
|
+
"agent-handoff",
|
|
7165
|
+
"tasks",
|
|
7166
|
+
"task-plan",
|
|
7167
|
+
"spec-change-proposal",
|
|
7168
|
+
"proposal",
|
|
7169
|
+
"spec-design",
|
|
7170
|
+
"spec-change-design",
|
|
7171
|
+
"design",
|
|
7172
|
+
"delivery-spec",
|
|
7173
|
+
"spec",
|
|
7174
|
+
"pre-merge-audit-report",
|
|
7175
|
+
"user-design-feedback",
|
|
7176
|
+
"release-kpis",
|
|
7177
|
+
"session-telemetry",
|
|
7178
|
+
"license-manifest",
|
|
7179
|
+
"mcp-config",
|
|
7180
|
+
"user-manual",
|
|
7181
|
+
"production-manual",
|
|
7182
|
+
"architecture-glossary"
|
|
7183
|
+
]);
|
|
7126
7184
|
const scannedArtifactsMap = /* @__PURE__ */ new Map();
|
|
7185
|
+
const archViewByBasename = /* @__PURE__ */ new Map();
|
|
7127
7186
|
const artifactDirs = ["product", "architecture", "specs", "security"];
|
|
7128
7187
|
for (const dirName of artifactDirs) {
|
|
7129
7188
|
const fullDirPath = path8.join(rootDir, dirName);
|
|
@@ -7133,6 +7192,7 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7133
7192
|
try {
|
|
7134
7193
|
const fileContent = fs6.readFileSync(f, "utf-8");
|
|
7135
7194
|
const relPath = path8.relative(rootDir, f).replace(/\\/g, "/");
|
|
7195
|
+
if (relPath.includes("/changes/") || relPath.startsWith("changes/")) continue;
|
|
7136
7196
|
const match = fileContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
7137
7197
|
if (!match) continue;
|
|
7138
7198
|
let fm = {};
|
|
@@ -7144,6 +7204,9 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7144
7204
|
const id = typeof fm.id === "string" ? fm.id.trim() : void 0;
|
|
7145
7205
|
if (!id) continue;
|
|
7146
7206
|
let type = typeof fm.type === "string" ? fm.type.trim() : "";
|
|
7207
|
+
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-")) {
|
|
7208
|
+
continue;
|
|
7209
|
+
}
|
|
7147
7210
|
let layer = "product";
|
|
7148
7211
|
if (id.startsWith("JRN-") || type === "journey") {
|
|
7149
7212
|
type = "journey";
|
|
@@ -7178,16 +7241,19 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7178
7241
|
} else if (id.startsWith("SEC-REQ-") || type === "security-requirement") {
|
|
7179
7242
|
type = "security-requirement";
|
|
7180
7243
|
layer = "requirement";
|
|
7181
|
-
} else if (id.startsWith("FR-") || id.startsWith("QR-") || id.startsWith("CON-") || type === "requirement") {
|
|
7244
|
+
} else if (id.startsWith("FR-") || id.startsWith("QR-") || id.startsWith("CON-") || type === "requirement" || type === "functional-requirement" || type === "quality-requirement") {
|
|
7182
7245
|
type = "requirement";
|
|
7183
7246
|
layer = "requirement";
|
|
7247
|
+
} else if (id.startsWith("ARCH-") || relPath.includes("architecture/")) {
|
|
7248
|
+
type = "architecture";
|
|
7249
|
+
layer = "architecture";
|
|
7184
7250
|
} else {
|
|
7185
7251
|
type = type || "product";
|
|
7186
7252
|
layer = "product";
|
|
7187
7253
|
}
|
|
7188
7254
|
const title = typeof fm.title === "string" ? fm.title.trim() : id;
|
|
7189
7255
|
const content = fileContent.replace(/^---\r?\n[\s\S]*?\r?\n---/, "").trim();
|
|
7190
|
-
|
|
7256
|
+
const scannedArt = {
|
|
7191
7257
|
id,
|
|
7192
7258
|
type,
|
|
7193
7259
|
title,
|
|
@@ -7195,7 +7261,12 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7195
7261
|
filePath: relPath,
|
|
7196
7262
|
content,
|
|
7197
7263
|
frontmatter: fm
|
|
7198
|
-
}
|
|
7264
|
+
};
|
|
7265
|
+
if (layer === "architecture" && type === "architecture") {
|
|
7266
|
+
archViewByBasename.set(path8.basename(relPath), scannedArt);
|
|
7267
|
+
} else {
|
|
7268
|
+
scannedArtifactsMap.set(id, scannedArt);
|
|
7269
|
+
}
|
|
7199
7270
|
} catch {
|
|
7200
7271
|
}
|
|
7201
7272
|
}
|
|
@@ -7204,7 +7275,7 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7204
7275
|
for (const r of trace.rows) {
|
|
7205
7276
|
const isOk = (r.productStatus === "COMPLIANT" || r.productStatus === "CONFORME") && (r.archStatus === "COMPLIANT" || r.archStatus === "CONFORME") && (r.testStatus === "COMPLIANT" || r.testStatus === "CONFORME");
|
|
7206
7277
|
const status = isOk ? "COMPLIANT" : "ORPHAN";
|
|
7207
|
-
const reqType = r.id.startsWith("SEC-REQ-") ? "security-requirement" :
|
|
7278
|
+
const reqType = r.id.startsWith("SEC-REQ-") ? "security-requirement" : "requirement";
|
|
7208
7279
|
nodeMap.set(r.id, {
|
|
7209
7280
|
id: r.id,
|
|
7210
7281
|
label: r.id,
|
|
@@ -7264,13 +7335,16 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7264
7335
|
});
|
|
7265
7336
|
if (!nodeMap.has(a)) {
|
|
7266
7337
|
const aType = a.startsWith("CMP-") ? "component" : a.startsWith("ADR-") ? "adr" : a.startsWith("SEC-ENC-") ? "security-enclave" : "architecture";
|
|
7338
|
+
const viewArt = archViewByBasename.get(a);
|
|
7267
7339
|
nodeMap.set(a, {
|
|
7268
7340
|
id: a,
|
|
7269
7341
|
label: a,
|
|
7270
|
-
title: a,
|
|
7342
|
+
title: viewArt?.title || a,
|
|
7271
7343
|
type: aType,
|
|
7272
7344
|
layer: "architecture",
|
|
7273
|
-
status: "COMPLIANT"
|
|
7345
|
+
status: "COMPLIANT",
|
|
7346
|
+
filePath: viewArt?.filePath,
|
|
7347
|
+
content: viewArt?.content
|
|
7274
7348
|
});
|
|
7275
7349
|
}
|
|
7276
7350
|
}
|
|
@@ -7402,7 +7476,31 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7402
7476
|
status: "COMPLIANT"
|
|
7403
7477
|
});
|
|
7404
7478
|
}
|
|
7405
|
-
const
|
|
7479
|
+
const supportingActors = Array.isArray(fm["supporting-actors"]) ? fm["supporting-actors"] : Array.isArray(fm["secondary-actors"]) ? fm["secondary-actors"] : [];
|
|
7480
|
+
for (const actorId of supportingActors) {
|
|
7481
|
+
if (typeof actorId === "string" && actorId.trim()) {
|
|
7482
|
+
const a = actorId.trim();
|
|
7483
|
+
if (!nodeMap.has(a)) {
|
|
7484
|
+
nodeMap.set(a, {
|
|
7485
|
+
id: a,
|
|
7486
|
+
label: a,
|
|
7487
|
+
title: a,
|
|
7488
|
+
type: "actor",
|
|
7489
|
+
layer: "product",
|
|
7490
|
+
status: "COMPLIANT"
|
|
7491
|
+
});
|
|
7492
|
+
}
|
|
7493
|
+
addEdge({
|
|
7494
|
+
id: `edge-${a}-${art.id}`,
|
|
7495
|
+
source: a,
|
|
7496
|
+
target: art.id,
|
|
7497
|
+
label: "initiates",
|
|
7498
|
+
relation: "initiates",
|
|
7499
|
+
status: "COMPLIANT"
|
|
7500
|
+
});
|
|
7501
|
+
}
|
|
7502
|
+
}
|
|
7503
|
+
const governedBy = Array.isArray(fm["governed-by"]) ? fm["governed-by"] : Array.isArray(fm["business-rules"]) ? fm["business-rules"] : [];
|
|
7406
7504
|
for (const ruleId of governedBy) {
|
|
7407
7505
|
if (typeof ruleId === "string" && ruleId.trim()) {
|
|
7408
7506
|
const r = ruleId.trim();
|
|
@@ -7463,7 +7561,31 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7463
7561
|
status: "COMPLIANT"
|
|
7464
7562
|
});
|
|
7465
7563
|
}
|
|
7466
|
-
const
|
|
7564
|
+
const secondaryThreatActors = Array.isArray(fm["secondary-actors"]) ? fm["secondary-actors"] : Array.isArray(fm["supporting-actors"]) ? fm["supporting-actors"] : [];
|
|
7565
|
+
for (const secActor of secondaryThreatActors) {
|
|
7566
|
+
if (typeof secActor === "string" && secActor.trim()) {
|
|
7567
|
+
const sa = secActor.trim();
|
|
7568
|
+
if (!nodeMap.has(sa)) {
|
|
7569
|
+
nodeMap.set(sa, {
|
|
7570
|
+
id: sa,
|
|
7571
|
+
label: sa,
|
|
7572
|
+
title: sa,
|
|
7573
|
+
type: sa.startsWith("ACT-THREAT-") ? "threat-actor" : "actor",
|
|
7574
|
+
layer: "product",
|
|
7575
|
+
status: "COMPLIANT"
|
|
7576
|
+
});
|
|
7577
|
+
}
|
|
7578
|
+
addEdge({
|
|
7579
|
+
id: `edge-${sa}-${art.id}`,
|
|
7580
|
+
source: sa,
|
|
7581
|
+
target: art.id,
|
|
7582
|
+
label: "threatens",
|
|
7583
|
+
relation: "threatens",
|
|
7584
|
+
status: "COMPLIANT"
|
|
7585
|
+
});
|
|
7586
|
+
}
|
|
7587
|
+
}
|
|
7588
|
+
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"] : [];
|
|
7467
7589
|
for (const tgt of rawTargets) {
|
|
7468
7590
|
if (typeof tgt === "string" && tgt.trim()) {
|
|
7469
7591
|
const t = tgt.trim();
|
|
@@ -7528,6 +7650,40 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7528
7650
|
});
|
|
7529
7651
|
}
|
|
7530
7652
|
}
|
|
7653
|
+
} else if (art.type === "adr") {
|
|
7654
|
+
const affectedComponents = Array.isArray(fm["affects-components"]) ? fm["affects-components"] : typeof fm["affects-components"] === "string" ? [fm["affects-components"]] : [];
|
|
7655
|
+
for (const cmpId of affectedComponents) {
|
|
7656
|
+
if (typeof cmpId === "string" && cmpId.trim()) {
|
|
7657
|
+
const c = cmpId.trim();
|
|
7658
|
+
if (nodeMap.has(c)) {
|
|
7659
|
+
addEdge({
|
|
7660
|
+
id: `edge-${art.id}-${c}`,
|
|
7661
|
+
source: art.id,
|
|
7662
|
+
target: c,
|
|
7663
|
+
label: "governs",
|
|
7664
|
+
relation: "governs",
|
|
7665
|
+
status: "COMPLIANT"
|
|
7666
|
+
});
|
|
7667
|
+
}
|
|
7668
|
+
}
|
|
7669
|
+
}
|
|
7670
|
+
} else if (art.type === "security-enclave") {
|
|
7671
|
+
const enforcedBy = Array.isArray(fm["enforced-by"]) ? fm["enforced-by"] : typeof fm["enforced-by"] === "string" ? [fm["enforced-by"]] : [];
|
|
7672
|
+
for (const secReq of enforcedBy) {
|
|
7673
|
+
if (typeof secReq === "string" && secReq.trim()) {
|
|
7674
|
+
const s = secReq.trim();
|
|
7675
|
+
if (nodeMap.has(s)) {
|
|
7676
|
+
addEdge({
|
|
7677
|
+
id: `edge-${s}-${art.id}`,
|
|
7678
|
+
source: s,
|
|
7679
|
+
target: art.id,
|
|
7680
|
+
label: "satisfies",
|
|
7681
|
+
relation: "satisfies",
|
|
7682
|
+
status: "COMPLIANT"
|
|
7683
|
+
});
|
|
7684
|
+
}
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7531
7687
|
}
|
|
7532
7688
|
}
|
|
7533
7689
|
for (const [id, node] of nodeMap.entries()) {
|
|
@@ -7677,6 +7833,9 @@ function collectDashboardMetrics(rootDir) {
|
|
|
7677
7833
|
function escapeHtml(str) {
|
|
7678
7834
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
7679
7835
|
}
|
|
7836
|
+
function safeJsonForScript(data) {
|
|
7837
|
+
return JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
7838
|
+
}
|
|
7680
7839
|
function renderAutonomyModeSection(mode, statusIcon, count, roleDescription, allTasks = []) {
|
|
7681
7840
|
const modeTasks = (allTasks || []).filter(
|
|
7682
7841
|
(t) => (t.task?.autonomyMode || "").toUpperCase() === mode
|
|
@@ -7788,14 +7947,14 @@ function renderAutonomyModeSection(mode, statusIcon, count, roleDescription, all
|
|
|
7788
7947
|
}
|
|
7789
7948
|
function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard Interactivo de Trazabilidad y Calidad") {
|
|
7790
7949
|
const cytoscapeScript = getCytoscapeScript();
|
|
7791
|
-
const elementsJson =
|
|
7950
|
+
const elementsJson = safeJsonForScript(graphData.elements);
|
|
7792
7951
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7793
7952
|
return `<!DOCTYPE html>
|
|
7794
7953
|
<html lang="es">
|
|
7795
7954
|
<head>
|
|
7796
7955
|
<meta charset="UTF-8">
|
|
7797
7956
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7798
|
-
<title>${title}</title>
|
|
7957
|
+
<title>${escapeHtml(title)}</title>
|
|
7799
7958
|
<style>
|
|
7800
7959
|
:root {
|
|
7801
7960
|
--bg-dark: #0f172a;
|
|
@@ -8170,12 +8329,12 @@ function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard
|
|
|
8170
8329
|
${metricsData.trace.rows.map((r) => {
|
|
8171
8330
|
const isRowOk = (r.productStatus === "COMPLIANT" || r.productStatus === "CONFORME") && (r.archStatus === "COMPLIANT" || r.archStatus === "CONFORME") && (r.testStatus === "COMPLIANT" || r.testStatus === "CONFORME");
|
|
8172
8331
|
return `
|
|
8173
|
-
<tr onclick="focusNode('${r.id}')" onmouseenter="startRowTooltip(event, '${r.id}')" onmouseleave="cancelRowTooltip()" onmousemove="updateRowTooltipPos(event)" style="cursor: pointer;">
|
|
8174
|
-
<td><strong><code>${r.id}</code></strong></td>
|
|
8175
|
-
<td>${r.title}</td>
|
|
8176
|
-
<td><code>${r.productTraces}</code></td>
|
|
8177
|
-
<td><code>${r.archTraces}</code></td>
|
|
8178
|
-
<td><code>${r.testTraces}</code></td>
|
|
8332
|
+
<tr onclick="focusNode('${escapeHtml(String(r.id || ""))}')" onmouseenter="startRowTooltip(event, '${escapeHtml(String(r.id || ""))}')" onmouseleave="cancelRowTooltip()" onmousemove="updateRowTooltipPos(event)" style="cursor: pointer;">
|
|
8333
|
+
<td><strong><code>${escapeHtml(String(r.id || ""))}</code></strong></td>
|
|
8334
|
+
<td>${escapeHtml(String(r.title || ""))}</td>
|
|
8335
|
+
<td><code>${escapeHtml(String(r.productTraces || ""))}</code></td>
|
|
8336
|
+
<td><code>${escapeHtml(String(r.archTraces || ""))}</code></td>
|
|
8337
|
+
<td><code>${escapeHtml(String(r.testTraces || ""))}</code></td>
|
|
8179
8338
|
<td><span class="badge ${isRowOk ? "badge-green" : "badge-amber"}">${isRowOk ? "COMPLIANT" : "ORPHAN"}</span></td>
|
|
8180
8339
|
</tr>
|
|
8181
8340
|
`;
|
|
@@ -8388,7 +8547,7 @@ function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard
|
|
|
8388
8547
|
</script>
|
|
8389
8548
|
<script>
|
|
8390
8549
|
var elements = ${elementsJson};
|
|
8391
|
-
var govTasks = ${
|
|
8550
|
+
var govTasks = ${safeJsonForScript(metricsData.gov.tasks || [])};
|
|
8392
8551
|
var cy = null;
|
|
8393
8552
|
|
|
8394
8553
|
var reqHoverTimer = null;
|
|
@@ -9021,9 +9180,10 @@ function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard
|
|
|
9021
9180
|
|
|
9022
9181
|
function switchTab(name) {
|
|
9023
9182
|
hideTooltip();
|
|
9024
|
-
document.querySelectorAll('.tab-btn').forEach(function(b) {
|
|
9183
|
+
document.querySelectorAll('.tab-btn').forEach(function(b) {
|
|
9184
|
+
b.classList.toggle('active', b.getAttribute('onclick') === "switchTab('" + name + "')");
|
|
9185
|
+
});
|
|
9025
9186
|
document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); });
|
|
9026
|
-
event.target?.classList?.add('active');
|
|
9027
9187
|
var p = document.getElementById('tab-' + name);
|
|
9028
9188
|
if (p) p.classList.add('active');
|
|
9029
9189
|
if (name === 'graph' && cy) {
|
|
@@ -9285,6 +9445,7 @@ var SPECIALIZED_AGENT_TEMPLATES = [
|
|
|
9285
9445
|
|
|
9286
9446
|
## Operational Directives & Guardrails
|
|
9287
9447
|
- 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\`.
|
|
9448
|
+
- **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.
|
|
9288
9449
|
- Generate interaction and sequence diagrams in native Mermaid syntax.
|
|
9289
9450
|
- Document critical technological decisions using immutable \`ADR-*\` records conforming to \`schemas/architecture/adr.schema.json\`.
|
|
9290
9451
|
- Validate that architectural choices comply with legal constraints in \`license-policy.yaml\`.
|
|
@@ -9307,6 +9468,7 @@ var SPECIALIZED_AGENT_TEMPLATES = [
|
|
|
9307
9468
|
2. **Boundary Cases** (\`Scenario Outline\` + \`Examples\` table): values at the extremes of the accepted range.
|
|
9308
9469
|
3. **Out-of-Range / Invalid Cases** (\`Scenario Outline\` + \`Examples\` table): null, empty, wrong types, values exceeding bounds.
|
|
9309
9470
|
- Apply conditional categories when applicable: Security (\`@security @mitigation\`), Performance (\`@performance\`), Idempotency (\`@idempotence\`), Postconditions, and Interface Contracts.
|
|
9471
|
+
- **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\`).
|
|
9310
9472
|
- Tag all scenarios with \`@<FR-ID> @automated @regression\`.
|
|
9311
9473
|
- **STRICTLY FORBIDDEN**: Including production code, suggesting implementation, or anticipating technical solutions.
|
|
9312
9474
|
- Run \`pnpm run verify:testing\` before emitting handoff.
|
|
@@ -9325,6 +9487,9 @@ var SPECIALIZED_AGENT_TEMPLATES = [
|
|
|
9325
9487
|
- Respect the autonomy mode assigned to each task in \`tasks.md\` (\`AUTONOMOUS\`, \`HUMAN_REVIEW_PLAN\`, \`AMBIGUOUS\`, \`HIGH_RISK_MANUAL\`).
|
|
9326
9488
|
- Apply Test-Driven Development (TDD): make failing tests delivered by \`agent-qa-engineer\` pass green without altering tests to accommodate buggy code.
|
|
9327
9489
|
- Cite implemented \`FR-*\` or \`SEC-REQ-*\` IDs in test header comments to maintain 360\xB0 reverse traceability.
|
|
9490
|
+
- **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.
|
|
9491
|
+
- **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).
|
|
9492
|
+
- **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.
|
|
9328
9493
|
- Before adding any external dependency, verify its SPDX license is permitted in \`license-policy.yaml\`.
|
|
9329
9494
|
- Respect non-negotiable \`quality-policy.yaml\` thresholds: Cyclomatic Complexity $\\le 10$, Cognitive Complexity $\\le 15$, Maintainability Index $\\ge 50$, Function Length $\\le 40$ lines.
|
|
9330
9495
|
- Run unified pre-flight with auto-fix (\`pnpm run check:fix\`) and full verification (\`pnpm run verify:all\`).
|
|
@@ -9373,9 +9538,9 @@ var SPECIALIZED_AGENT_TEMPLATES = [
|
|
|
9373
9538
|
## Operational Directives & Guardrails
|
|
9374
9539
|
- Operate under bimodal discipline depending on the lifecycle phase:
|
|
9375
9540
|
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\`.
|
|
9376
|
-
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.
|
|
9541
|
+
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.
|
|
9377
9542
|
- Formulate decisive questions under \`open-questions\` for the human Product Owner.
|
|
9378
|
-
- 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.`
|
|
9543
|
+
- 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.`
|
|
9379
9544
|
},
|
|
9380
9545
|
{
|
|
9381
9546
|
id: "agent-code-reviewer",
|
|
@@ -9390,6 +9555,7 @@ var SPECIALIZED_AGENT_TEMPLATES = [
|
|
|
9390
9555
|
- 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$.
|
|
9391
9556
|
- Detect improper coupling, code smells, magic numbers, ambiguous identifiers, and encapsulation breaches.
|
|
9392
9557
|
- Flag premature abstractions and speculative code violating YAGNI.
|
|
9558
|
+
- **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.
|
|
9393
9559
|
- Coordinate Pre-Merge Audit Triad with \`agent-security-auditor\` and \`agent-compliance-checker\`.
|
|
9394
9560
|
- Categorize findings into: \`[BLOCKING]\` (quality violation or broken pattern), \`[CLEAN_CODE_SUGGESTION]\` (non-blocking improvement), and \`[COMPLIANT]\`.
|
|
9395
9561
|
- 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.`
|