@aisdlc/core 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1635 -943
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -39
- package/dist/index.d.ts +165 -39
- package/dist/index.js +1610 -941
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
3
|
+
var crypto3 = require('crypto');
|
|
4
|
+
var fs8 = require('fs');
|
|
5
|
+
var path5 = require('path');
|
|
6
6
|
var yaml3 = require('js-yaml');
|
|
7
|
-
var tsMorph = require('ts-morph');
|
|
8
7
|
var child_process = require('child_process');
|
|
8
|
+
var tsMorph = require('ts-morph');
|
|
9
|
+
var Ajv2020 = require('ajv/dist/2020.js');
|
|
10
|
+
var addFormats = require('ajv-formats');
|
|
9
11
|
var module$1 = require('module');
|
|
10
12
|
|
|
11
13
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -29,11 +31,13 @@ function _interopNamespace(e) {
|
|
|
29
31
|
return Object.freeze(n);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
var
|
|
33
|
-
var
|
|
34
|
-
var
|
|
34
|
+
var crypto3__namespace = /*#__PURE__*/_interopNamespace(crypto3);
|
|
35
|
+
var fs8__namespace = /*#__PURE__*/_interopNamespace(fs8);
|
|
36
|
+
var path5__namespace = /*#__PURE__*/_interopNamespace(path5);
|
|
35
37
|
var yaml3__default = /*#__PURE__*/_interopDefault(yaml3);
|
|
36
38
|
var child_process__namespace = /*#__PURE__*/_interopNamespace(child_process);
|
|
39
|
+
var Ajv2020__default = /*#__PURE__*/_interopDefault(Ajv2020);
|
|
40
|
+
var addFormats__default = /*#__PURE__*/_interopDefault(addFormats);
|
|
37
41
|
|
|
38
42
|
// src/types/index.ts
|
|
39
43
|
var VALID_CHANGE_PROFILES = ["patch", "standard", "critical"];
|
|
@@ -43,6 +47,168 @@ var VALID_AUTONOMY_MODES = [
|
|
|
43
47
|
"AMBIGUOUS",
|
|
44
48
|
"HIGH_RISK_MANUAL"
|
|
45
49
|
];
|
|
50
|
+
function canonicalSort(a, b) {
|
|
51
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
52
|
+
}
|
|
53
|
+
function normalizePath(p) {
|
|
54
|
+
return p.normalize("NFC").replace(/\\/g, "/");
|
|
55
|
+
}
|
|
56
|
+
function walkMdFiles(dir, fileList = [], excludeDirs = [
|
|
57
|
+
"node_modules",
|
|
58
|
+
".git",
|
|
59
|
+
"dist",
|
|
60
|
+
"bin",
|
|
61
|
+
"obj",
|
|
62
|
+
"build",
|
|
63
|
+
"out",
|
|
64
|
+
"target",
|
|
65
|
+
"coverage",
|
|
66
|
+
".changeset",
|
|
67
|
+
"scratch",
|
|
68
|
+
"test-scaffold",
|
|
69
|
+
"fixtures",
|
|
70
|
+
"templates",
|
|
71
|
+
"examples",
|
|
72
|
+
".agent",
|
|
73
|
+
".agents",
|
|
74
|
+
".claude",
|
|
75
|
+
".cursor",
|
|
76
|
+
".github",
|
|
77
|
+
".vscode",
|
|
78
|
+
".vs",
|
|
79
|
+
".idea"
|
|
80
|
+
]) {
|
|
81
|
+
try {
|
|
82
|
+
if (!fs8__namespace.existsSync(dir)) return fileList;
|
|
83
|
+
const files = fs8__namespace.readdirSync(dir).sort(canonicalSort);
|
|
84
|
+
for (const file of files) {
|
|
85
|
+
const fullPath = path5__namespace.join(dir, file);
|
|
86
|
+
try {
|
|
87
|
+
if (fs8__namespace.statSync(fullPath).isDirectory()) {
|
|
88
|
+
if (!excludeDirs.includes(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
|
|
89
|
+
walkMdFiles(fullPath, fileList, excludeDirs);
|
|
90
|
+
}
|
|
91
|
+
} else if (file.endsWith(".md")) {
|
|
92
|
+
fileList.push(fullPath);
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
return fileList.sort(canonicalSort);
|
|
100
|
+
}
|
|
101
|
+
function stripVolatileReportMetadata(content, _ext = "") {
|
|
102
|
+
let normalized = content.replace(/\r\n/g, "\n");
|
|
103
|
+
normalized = normalized.replace(/(?:^|\n)>\s*\*\*(?:Generation Date|Generated at|Fecha|Audit Date|Date):\*\*.*$/gim, "");
|
|
104
|
+
normalized = normalized.replace(/Generated:\s*<span>[\s\S]*?<\/span>/gi, "Generated: <span>[STRIPPED]</span>");
|
|
105
|
+
return normalized.trim();
|
|
106
|
+
}
|
|
107
|
+
function areCycloneDxSbomsEqual(existingContent, newContent) {
|
|
108
|
+
try {
|
|
109
|
+
const a = JSON.parse(existingContent);
|
|
110
|
+
const b = JSON.parse(newContent);
|
|
111
|
+
return a.serialNumber === b.serialNumber && JSON.stringify(a.components) === JSON.stringify(b.components) && JSON.stringify(a.vulnerabilities || []) === JSON.stringify(b.vulnerabilities || []);
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function areSarifReportsEqual(existingContent, newContent) {
|
|
117
|
+
try {
|
|
118
|
+
const a = JSON.parse(existingContent);
|
|
119
|
+
const b = JSON.parse(newContent);
|
|
120
|
+
return JSON.stringify(a.runs?.[0]?.results || []) === JSON.stringify(b.runs?.[0]?.results || []);
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function isReportContentSemanticallyEqual(existingContent, newContent, filePath) {
|
|
126
|
+
if (existingContent === newContent) return true;
|
|
127
|
+
if (filePath.endsWith(".cdx.json") || filePath.endsWith("sbom.json")) {
|
|
128
|
+
if (areCycloneDxSbomsEqual(existingContent, newContent)) return true;
|
|
129
|
+
}
|
|
130
|
+
if (filePath.endsWith(".sarif")) {
|
|
131
|
+
if (areSarifReportsEqual(existingContent, newContent)) return true;
|
|
132
|
+
}
|
|
133
|
+
const ext = path5__namespace.extname(filePath).toLowerCase();
|
|
134
|
+
return stripVolatileReportMetadata(existingContent, ext) === stripVolatileReportMetadata(newContent, ext);
|
|
135
|
+
}
|
|
136
|
+
function atomicWriteFile(filePath, content) {
|
|
137
|
+
const tempPath = `${filePath}.${process.pid}.${Math.random().toString(36).substring(2)}.tmp`;
|
|
138
|
+
try {
|
|
139
|
+
fs8__namespace.writeFileSync(tempPath, content, "utf-8");
|
|
140
|
+
try {
|
|
141
|
+
if (fs8__namespace.existsSync(filePath)) {
|
|
142
|
+
fs8__namespace.rmSync(filePath, { force: true });
|
|
143
|
+
}
|
|
144
|
+
fs8__namespace.renameSync(tempPath, filePath);
|
|
145
|
+
} catch {
|
|
146
|
+
fs8__namespace.copyFileSync(tempPath, filePath);
|
|
147
|
+
fs8__namespace.unlinkSync(tempPath);
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
fs8__namespace.writeFileSync(filePath, content, "utf-8");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function writeReportFileIfChanged(filePath, content) {
|
|
154
|
+
try {
|
|
155
|
+
if (fs8__namespace.existsSync(filePath)) {
|
|
156
|
+
const existing = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
157
|
+
if (isReportContentSemanticallyEqual(existing, content, filePath)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
const dir = path5__namespace.dirname(filePath);
|
|
164
|
+
if (!fs8__namespace.existsSync(dir)) {
|
|
165
|
+
fs8__namespace.mkdirSync(dir, { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
atomicWriteFile(filePath, content);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
var UUID_V5_NAMESPACE_OID = "6ba7b812-9dad-11d1-80b4-00c04fd430c8";
|
|
171
|
+
function resolveDeterministicDate(rootDir) {
|
|
172
|
+
const epoch = process.env.SOURCE_DATE_EPOCH;
|
|
173
|
+
if (epoch) {
|
|
174
|
+
const parsed = parseInt(epoch, 10);
|
|
175
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
176
|
+
return new Date(parsed * 1e3);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const targetDir = rootDir || process.cwd();
|
|
180
|
+
if (fs8__namespace.existsSync(path5__namespace.join(targetDir, ".git"))) {
|
|
181
|
+
try {
|
|
182
|
+
const gitOut = child_process.execFileSync("git", ["log", "-1", "--format=%ct"], {
|
|
183
|
+
cwd: targetDir,
|
|
184
|
+
encoding: "utf-8",
|
|
185
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
186
|
+
}).trim();
|
|
187
|
+
const parsed = parseInt(gitOut, 10);
|
|
188
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
189
|
+
return new Date(parsed * 1e3);
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return /* @__PURE__ */ new Date();
|
|
195
|
+
}
|
|
196
|
+
function generateDeterministicUuidV5(name, namespace = UUID_V5_NAMESPACE_OID) {
|
|
197
|
+
const nsHex = namespace.replace(/-/g, "");
|
|
198
|
+
const nsBytes = Buffer.from(nsHex, "hex");
|
|
199
|
+
const nameBytes = Buffer.from(name, "utf-8");
|
|
200
|
+
const hash = crypto3__namespace.createHash("sha1").update(nsBytes).update(nameBytes).digest();
|
|
201
|
+
hash[6] = hash[6] & 15 | 80;
|
|
202
|
+
hash[8] = hash[8] & 63 | 128;
|
|
203
|
+
const hex = hash.toString("hex").substring(0, 32);
|
|
204
|
+
return [
|
|
205
|
+
hex.substring(0, 8),
|
|
206
|
+
hex.substring(8, 12),
|
|
207
|
+
hex.substring(12, 16),
|
|
208
|
+
hex.substring(16, 20),
|
|
209
|
+
hex.substring(20, 32)
|
|
210
|
+
].join("-");
|
|
211
|
+
}
|
|
46
212
|
var CYCLOMATIC_KINDS = /* @__PURE__ */ new Set([
|
|
47
213
|
tsMorph.SyntaxKind.IfStatement,
|
|
48
214
|
tsMorph.SyntaxKind.ConditionalExpression,
|
|
@@ -199,7 +365,7 @@ function buildNodeMetrics(node, filePath) {
|
|
|
199
365
|
return { functionName, filePath, loc, cyclomatic, cognitive, maintainability: mi, codeSmells, line };
|
|
200
366
|
}
|
|
201
367
|
function createTempSourceFile(project, content, filePath) {
|
|
202
|
-
const ext =
|
|
368
|
+
const ext = path5__namespace.extname(filePath).toLowerCase() || ".ts";
|
|
203
369
|
const fileName = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 7)}${ext}`;
|
|
204
370
|
return project.createSourceFile(fileName, content, { overwrite: true });
|
|
205
371
|
}
|
|
@@ -236,8 +402,8 @@ function extractFunctionsTypeScriptAst(content, filePath) {
|
|
|
236
402
|
}
|
|
237
403
|
}
|
|
238
404
|
function extractFunctionsPolyglot(content, filePath) {
|
|
239
|
-
const ext =
|
|
240
|
-
return ext === ".py" ?
|
|
405
|
+
const ext = path5__namespace.extname(filePath).toLowerCase();
|
|
406
|
+
return ext === ".py" ? extractPythonFunctionsPolyglot(content, filePath) : extractBraceFunctions(content, filePath);
|
|
241
407
|
}
|
|
242
408
|
function isDocstringToggle(trimmed) {
|
|
243
409
|
const dCount = (trimmed.match(/"""/g) || []).length;
|
|
@@ -282,7 +448,7 @@ function processPythonLine(raw, state, blocks, lineIndex) {
|
|
|
282
448
|
handlePythonBodyLine(raw, state, blocks, lineIndex);
|
|
283
449
|
}
|
|
284
450
|
}
|
|
285
|
-
function
|
|
451
|
+
function extractPythonFunctionsPolyglot(content, filePath) {
|
|
286
452
|
const lines = content.split("\n");
|
|
287
453
|
const blocks = [];
|
|
288
454
|
const state = { currentFn: null, fnLines: [], baseIndent: 0, inDocstring: false };
|
|
@@ -294,120 +460,182 @@ function extractPythonFunctions(content, filePath) {
|
|
|
294
460
|
}
|
|
295
461
|
return buildMetricsFromBlocks(blocks, content, filePath);
|
|
296
462
|
}
|
|
297
|
-
function
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
463
|
+
function tryStartBraceFunction(line, state, ext) {
|
|
464
|
+
if (state.currentFn || state.inBlockComment || state.rawTerm) return;
|
|
465
|
+
const candidate = matchFunctionHeader(line, ext);
|
|
466
|
+
if (candidate) {
|
|
301
467
|
state.currentFn = candidate;
|
|
302
468
|
state.braceCount = 0;
|
|
303
469
|
state.fnLines = [];
|
|
304
470
|
}
|
|
305
|
-
|
|
306
|
-
|
|
471
|
+
}
|
|
472
|
+
function updateBraceScanState(line, state, ext) {
|
|
473
|
+
const res = scanBracesInLine(line, state.inBlockComment, state.rawTerm, ext);
|
|
307
474
|
state.inBlockComment = res.nowInBlock;
|
|
475
|
+
state.rawTerm = res.nowRawTerm;
|
|
308
476
|
state.braceCount += res.delta;
|
|
309
|
-
|
|
477
|
+
}
|
|
478
|
+
function processBraceLine(line, state, blocks, lineIndex, ext) {
|
|
479
|
+
tryStartBraceFunction(line, state, ext);
|
|
480
|
+
if (state.currentFn) state.fnLines.push(line);
|
|
481
|
+
updateBraceScanState(line, state, ext);
|
|
482
|
+
if (state.currentFn && state.braceCount === 0 && state.fnLines.length > 1) {
|
|
310
483
|
blocks.push({ name: state.currentFn, body: state.fnLines.join("\n"), startLine: lineIndex });
|
|
311
484
|
state.currentFn = null;
|
|
312
485
|
state.fnLines = [];
|
|
313
486
|
}
|
|
314
487
|
}
|
|
315
488
|
function extractBraceFunctions(content, filePath) {
|
|
316
|
-
const ext =
|
|
489
|
+
const ext = path5__namespace.extname(filePath).toLowerCase();
|
|
317
490
|
const lines = content.split("\n");
|
|
318
491
|
const blocks = [];
|
|
319
|
-
const state = {
|
|
492
|
+
const state = {
|
|
493
|
+
currentFn: null,
|
|
494
|
+
braceCount: 0,
|
|
495
|
+
fnLines: [],
|
|
496
|
+
inBlockComment: false,
|
|
497
|
+
rawTerm: null
|
|
498
|
+
};
|
|
320
499
|
for (let i = 0; i < lines.length; i++) {
|
|
321
500
|
processBraceLine(lines[i], state, blocks, i, ext);
|
|
322
501
|
}
|
|
323
502
|
return buildMetricsFromBlocks(blocks, content, filePath);
|
|
324
503
|
}
|
|
504
|
+
var HEADER_EXCLUSIONS = /* @__PURE__ */ new Set([
|
|
505
|
+
"if",
|
|
506
|
+
"for",
|
|
507
|
+
"foreach",
|
|
508
|
+
"while",
|
|
509
|
+
"switch",
|
|
510
|
+
"catch",
|
|
511
|
+
"select",
|
|
512
|
+
"match",
|
|
513
|
+
"class",
|
|
514
|
+
"struct",
|
|
515
|
+
"interface",
|
|
516
|
+
"record",
|
|
517
|
+
"enum",
|
|
518
|
+
"namespace",
|
|
519
|
+
"using",
|
|
520
|
+
"lock",
|
|
521
|
+
"fixed",
|
|
522
|
+
"synchronized",
|
|
523
|
+
"try",
|
|
524
|
+
"else"
|
|
525
|
+
]);
|
|
325
526
|
function matchCStyleHeader(line) {
|
|
326
527
|
const match = line.match(
|
|
327
|
-
/(?:(?:public|private|protected|static|async|virtual|override|fn)\s+)*(?:function\s+([a-zA-Z0-9_$]+)|(?:[a-zA-Z0-9_<>[\]
|
|
528
|
+
/(?:(?:public|private|protected|internal|static|async|virtual|override|sealed|abstract|final|synchronized|default|fn)\s+)*(?:function\s+([a-zA-Z0-9_$]+)|(?:[a-zA-Z0-9_<>[\]?,.\s]+\s+)+([a-zA-Z0-9_$]+)\s*(?:<[^>]+>)?\s*\([^;{}]*\)\s*(?:where\s+[^{]+|throws\s+[^{]+)?\{|([a-zA-Z0-9_$]+)\s*\([^;{}]*\)\s*(?::\s*[^{]+\s*)?\{)/
|
|
328
529
|
);
|
|
329
530
|
if (!match) return null;
|
|
330
531
|
const candidate = match[1] || match[2] || match[3];
|
|
331
|
-
|
|
332
|
-
return candidate && !exclusions.includes(candidate) ? candidate : null;
|
|
532
|
+
return candidate && !HEADER_EXCLUSIONS.has(candidate) ? candidate : null;
|
|
333
533
|
}
|
|
334
534
|
function matchFunctionHeader(line, ext) {
|
|
335
535
|
const trimmed = line.trim();
|
|
336
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*"))
|
|
536
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*") || trimmed.startsWith("#")) {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
337
539
|
if (ext === ".go") {
|
|
338
|
-
const match = line.match(/func\s+(?:\([^)]*\)\s*)?([a-zA-Z0-9_$]+)\s*\(/);
|
|
540
|
+
const match = line.match(/func\s+(?:\([^)]*\)\s*)?([a-zA-Z0-9_$]+)\s*(?:\[[^\]]*\])?\s*\(/);
|
|
339
541
|
return match ? match[1] : null;
|
|
340
542
|
}
|
|
341
543
|
if (ext === ".rs") {
|
|
342
|
-
const match = line.match(/(?:pub
|
|
544
|
+
const match = line.match(/(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(?:const\s+)?fn\s+([a-zA-Z0-9_$]+)\s*(?:<[^>]*>)?\s*\(/);
|
|
343
545
|
return match ? match[1] : null;
|
|
344
546
|
}
|
|
345
547
|
return matchCStyleHeader(line);
|
|
346
548
|
}
|
|
347
|
-
function
|
|
348
|
-
if (
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
549
|
+
function detectRustOrGoRawString(sub, ext) {
|
|
550
|
+
if (ext === ".rs") {
|
|
551
|
+
const m = sub.match(/^r(#*)"/);
|
|
552
|
+
return m ? { term: `"${m[1]}`, advance: m[0].length - 1 } : null;
|
|
553
|
+
}
|
|
554
|
+
if (ext === ".go" && sub.startsWith("`")) {
|
|
555
|
+
return { term: "`", advance: 0 };
|
|
556
|
+
}
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
function detectCsJavaOrCppRawString(sub, ext) {
|
|
560
|
+
if (ext === ".cs" || ext === ".java") {
|
|
561
|
+
const m = sub.match(/^\$*("{3,})/);
|
|
562
|
+
if (m) return { term: m[1], advance: m[0].length - 1 };
|
|
563
|
+
const v = ext === ".cs" ? sub.match(/^(@\$|\$@|@)"/) : null;
|
|
564
|
+
return v ? { term: '"', advance: v[0].length - 1 } : null;
|
|
565
|
+
}
|
|
566
|
+
const cpp = sub.match(/^R"([a-zA-Z0-9_]*)\(/);
|
|
567
|
+
return cpp ? { term: `)${cpp[1]}"`, advance: cpp[0].length - 1 } : null;
|
|
568
|
+
}
|
|
569
|
+
function detectRawStringOpen(line, index, ext) {
|
|
570
|
+
const sub = line.slice(index);
|
|
571
|
+
return detectRustOrGoRawString(sub, ext) || detectCsJavaOrCppRawString(sub, ext);
|
|
572
|
+
}
|
|
573
|
+
function consumeRawOrBlock(line, index, state) {
|
|
574
|
+
if (state.inBlock) {
|
|
575
|
+
if (line[index] === "*" && line[index + 1] === "/") {
|
|
576
|
+
state.inBlock = false;
|
|
577
|
+
return { advance: 1, handled: true };
|
|
578
|
+
}
|
|
579
|
+
return { advance: 0, handled: true };
|
|
580
|
+
}
|
|
581
|
+
if (state.rawTerm) {
|
|
582
|
+
if (line.startsWith(state.rawTerm, index)) {
|
|
583
|
+
const len = state.rawTerm.length - 1;
|
|
584
|
+
state.rawTerm = null;
|
|
585
|
+
return { advance: len, handled: true };
|
|
586
|
+
}
|
|
587
|
+
return { advance: 0, handled: true };
|
|
588
|
+
}
|
|
589
|
+
return { advance: 0, handled: false };
|
|
590
|
+
}
|
|
591
|
+
function consumeNormalQuote(line, index, state) {
|
|
592
|
+
if (!state.inString) return { advance: 0, handled: false };
|
|
593
|
+
const ch = line[index];
|
|
594
|
+
if (ch === "\\") return { advance: 1, handled: true };
|
|
595
|
+
if (ch === state.inString) state.inString = null;
|
|
596
|
+
return { advance: 0, handled: true };
|
|
597
|
+
}
|
|
598
|
+
function applyQuoteOrBrace(ch, state) {
|
|
599
|
+
if (ch === '"' || ch === "'") {
|
|
600
|
+
state.inString = ch;
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (ch === "{") state.delta++;
|
|
604
|
+
if (ch === "}") state.delta--;
|
|
605
|
+
}
|
|
606
|
+
function stepActiveCodeToken(line, j, ext, state) {
|
|
607
|
+
const pair = line.slice(j, j + 2);
|
|
608
|
+
if (pair === "//") return { advance: 0, stop: true };
|
|
609
|
+
if (pair === "/*") {
|
|
383
610
|
state.inBlock = true;
|
|
384
611
|
return { advance: 1, stop: false };
|
|
385
612
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
613
|
+
const rawOpen = detectRawStringOpen(line, j, ext);
|
|
614
|
+
if (rawOpen) {
|
|
615
|
+
state.rawTerm = rawOpen.term;
|
|
616
|
+
return { advance: rawOpen.advance, stop: false };
|
|
389
617
|
}
|
|
390
|
-
state
|
|
618
|
+
applyQuoteOrBrace(line[j], state);
|
|
391
619
|
return { advance: 0, stop: false };
|
|
392
620
|
}
|
|
393
|
-
function scanBracesInLine(line, wasInBlock, ext) {
|
|
394
|
-
const state = { delta: 0, inBlock: wasInBlock, inString: null };
|
|
621
|
+
function scanBracesInLine(line, wasInBlock, wasRawTerm, ext) {
|
|
622
|
+
const state = { delta: 0, inBlock: wasInBlock, rawTerm: wasRawTerm, inString: null };
|
|
395
623
|
for (let j = 0; j < line.length; j++) {
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (skipped.handled) {
|
|
400
|
-
state.inBlock = skipped.inBlock;
|
|
401
|
-
state.inString = skipped.inString;
|
|
402
|
-
if (skipped.skip) j++;
|
|
624
|
+
const rawOrBlock = consumeRawOrBlock(line, j, state);
|
|
625
|
+
if (rawOrBlock.handled) {
|
|
626
|
+
j += rawOrBlock.advance;
|
|
403
627
|
continue;
|
|
404
628
|
}
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
629
|
+
const quoteStep = consumeNormalQuote(line, j, state);
|
|
630
|
+
if (quoteStep.handled) {
|
|
631
|
+
j += quoteStep.advance;
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const codeStep = stepActiveCodeToken(line, j, ext, state);
|
|
635
|
+
if (codeStep.stop) break;
|
|
636
|
+
j += codeStep.advance;
|
|
409
637
|
}
|
|
410
|
-
return { delta: state.delta, nowInBlock: state.inBlock };
|
|
638
|
+
return { delta: state.delta, nowInBlock: state.inBlock, nowRawTerm: state.rawTerm };
|
|
411
639
|
}
|
|
412
640
|
function buildMetricsFromBlocks(blocks, content, filePath) {
|
|
413
641
|
if (blocks.length === 0) {
|
|
@@ -431,11 +659,20 @@ function buildMetricsFromBlocks(blocks, content, filePath) {
|
|
|
431
659
|
}
|
|
432
660
|
function detectPolyglotSmells(body) {
|
|
433
661
|
const smells = [];
|
|
434
|
-
if (/#\s*noqa|#\s*type:\s*ignore|@SuppressWarnings/.test(body)) {
|
|
662
|
+
if (/#\s*noqa|#\s*type:\s*ignore|@SuppressWarnings|#pragma\s+warning\s+disable/.test(body)) {
|
|
435
663
|
smells.push("Supresi\xF3n no autorizada de linter");
|
|
436
664
|
}
|
|
437
665
|
return smells;
|
|
438
666
|
}
|
|
667
|
+
function stripStringsAndComments(body, ext) {
|
|
668
|
+
let cleaned = body;
|
|
669
|
+
if (ext === ".py") {
|
|
670
|
+
cleaned = cleaned.replace(/"""[\s\S]*?"""|'''[\s\S]*?'''/g, '""').replace(/#.*/g, "");
|
|
671
|
+
} else {
|
|
672
|
+
cleaned = cleaned.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "").replace(/r(#+)"[\s\S]*?"\1/g, '""').replace(/R"([a-zA-Z0-9_]*)\([\s\S]*?\)\1"/g, '""').replace(/\$*"{3,}[\s\S]*?"{3,}/g, '""').replace(/`[\s\S]*?`/g, '""');
|
|
673
|
+
}
|
|
674
|
+
return cleaned.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, '""');
|
|
675
|
+
}
|
|
439
676
|
function computeHalsteadMI(body, cyclomatic, loc, commentCount) {
|
|
440
677
|
const cleanText = body.replace(/\/\*[\s\S]*?\*\/|\/\/.*|#.*/g, "");
|
|
441
678
|
const tokens = cleanText.split(/[\s,;().{}[\]=+\-*/<>!&|:]+/).filter((t) => t.length > 0);
|
|
@@ -458,11 +695,13 @@ function countCommentsAndLoc(rawLines, isPy) {
|
|
|
458
695
|
return { loc, commentCount, cleanLines };
|
|
459
696
|
}
|
|
460
697
|
function calculatePolyglotMetrics(body, name, filePath, startLine) {
|
|
461
|
-
const ext =
|
|
698
|
+
const ext = path5__namespace.extname(filePath).toLowerCase();
|
|
462
699
|
const rawLines = body.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
463
|
-
const { loc, commentCount
|
|
464
|
-
const
|
|
465
|
-
const
|
|
700
|
+
const { loc, commentCount } = countCommentsAndLoc(rawLines, ext === ".py");
|
|
701
|
+
const strippedBody = stripStringsAndComments(body, ext);
|
|
702
|
+
const strippedLines = strippedBody.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
703
|
+
const cyclomatic = computePolyglotCyclomatic(strippedBody, ext);
|
|
704
|
+
const cognitive = computePolyglotCognitive(strippedLines, ext === ".py");
|
|
466
705
|
const maintainability = computeHalsteadMI(body, cyclomatic, loc, commentCount);
|
|
467
706
|
return {
|
|
468
707
|
functionName: name,
|
|
@@ -475,84 +714,183 @@ function calculatePolyglotMetrics(body, name, filePath, startLine) {
|
|
|
475
714
|
line: startLine ? startLine + 1 : 1
|
|
476
715
|
};
|
|
477
716
|
}
|
|
478
|
-
function
|
|
479
|
-
let regex;
|
|
717
|
+
function getCyclomaticPattern(ext) {
|
|
480
718
|
if (ext === ".py") {
|
|
481
|
-
|
|
482
|
-
} else if (ext === ".go") {
|
|
483
|
-
regex = /\b(if|for|case|select)\b|&&|\|\|/g;
|
|
484
|
-
} else if (ext === ".rs") {
|
|
485
|
-
regex = /\b(if|for|while|match)\b|&&|\|\||\?/g;
|
|
486
|
-
} else {
|
|
487
|
-
regex = /\b(if|else\s+if|for|while|catch|case)\b|\?|&&|\|\|/g;
|
|
719
|
+
return /\b(if|elif|for|while|except|case)\b|\b(and|or)\b/g;
|
|
488
720
|
}
|
|
489
|
-
|
|
490
|
-
|
|
721
|
+
if (ext === ".go") {
|
|
722
|
+
return /\b(if|for|case|select)\b|&&|\|\|/g;
|
|
723
|
+
}
|
|
724
|
+
if (ext === ".rs") {
|
|
725
|
+
return /\b(if|for|while|match)\b|&&|\|\||\?/g;
|
|
726
|
+
}
|
|
727
|
+
if (ext === ".cs") {
|
|
728
|
+
return /\b(if|else\s+if|for|foreach|while|catch|case|when)\b|\?\?|\?\.(?=[a-zA-Z_])|&&|\|\|/g;
|
|
729
|
+
}
|
|
730
|
+
if (ext === ".java") {
|
|
731
|
+
return /\b(if|else\s+if|for|while|catch|case|when)\b|&&|\|\|/g;
|
|
732
|
+
}
|
|
733
|
+
return /\b(if|else\s+if|for|while|catch|case)\b|\?|&&|\|\|/g;
|
|
734
|
+
}
|
|
735
|
+
function computePolyglotCyclomatic(strippedBody, ext) {
|
|
736
|
+
const regex = getCyclomaticPattern(ext);
|
|
737
|
+
const matches = strippedBody.match(regex);
|
|
491
738
|
return 1 + (matches ? matches.length : 0);
|
|
492
739
|
}
|
|
493
|
-
|
|
740
|
+
var PY_COGNITIVE_RE = /\b(if|elif|for|while|except|match)\b/;
|
|
741
|
+
var BRACE_COGNITIVE_RE = /\b(if|for|foreach|while|catch|match|select|switch)\b/;
|
|
742
|
+
function computePythonCognitive(lines) {
|
|
743
|
+
let cognitive = 0;
|
|
744
|
+
const baseIndent = lines.length > 0 ? lines[0].length - lines[0].trimStart().length : 0;
|
|
745
|
+
for (let i = 1; i < lines.length; i++) {
|
|
746
|
+
const line = lines[i];
|
|
747
|
+
if (PY_COGNITIVE_RE.test(line)) {
|
|
748
|
+
const relIndent = Math.max(0, line.length - line.trimStart().length - baseIndent - 4);
|
|
749
|
+
cognitive += 1 + Math.floor(relIndent / 4);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return cognitive;
|
|
753
|
+
}
|
|
754
|
+
function computeBraceCognitive(lines) {
|
|
494
755
|
let cognitive = 0;
|
|
495
756
|
let nesting = 0;
|
|
496
|
-
for (
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
cognitive += 1 + Math.floor(indent / 4);
|
|
501
|
-
}
|
|
502
|
-
} else {
|
|
503
|
-
if (line.includes("{")) nesting++;
|
|
504
|
-
if (line.includes("}")) nesting = Math.max(0, nesting - 1);
|
|
505
|
-
if (/\b(if|for|while|catch|match|select)\b/.test(line)) {
|
|
506
|
-
cognitive += 1 + nesting;
|
|
507
|
-
}
|
|
757
|
+
for (let i = 1; i < lines.length; i++) {
|
|
758
|
+
const line = lines[i];
|
|
759
|
+
if (BRACE_COGNITIVE_RE.test(line)) {
|
|
760
|
+
cognitive += 1 + nesting;
|
|
508
761
|
}
|
|
762
|
+
const opens = (line.match(/\{/g) || []).length;
|
|
763
|
+
const closes = (line.match(/\}/g) || []).length;
|
|
764
|
+
nesting = Math.max(0, nesting + opens - closes);
|
|
509
765
|
}
|
|
510
766
|
return cognitive;
|
|
511
767
|
}
|
|
512
|
-
function
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
768
|
+
function computePolyglotCognitive(lines, isPy) {
|
|
769
|
+
return isPy ? computePythonCognitive(lines) : computeBraceCognitive(lines);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// src/verifiers/ast/python-ast.ts
|
|
773
|
+
var PYTHON_AST_RUNNER_SCRIPT = `
|
|
774
|
+
import ast, json, sys
|
|
775
|
+
|
|
776
|
+
def analyze(code, filepath):
|
|
777
|
+
try:
|
|
778
|
+
tree = ast.parse(code)
|
|
779
|
+
except Exception as e:
|
|
780
|
+
return {"language": "python", "filePath": filepath, "functions": [], "error": str(e), "unsafeTypesCount": 0, "emptyStubsCount": 0}
|
|
781
|
+
|
|
782
|
+
functions = []
|
|
783
|
+
unsafe_types = 0
|
|
784
|
+
empty_stubs = 0
|
|
785
|
+
for node in ast.walk(tree):
|
|
786
|
+
if isinstance(node, ast.Name) and node.id == "Any":
|
|
787
|
+
unsafe_types += 1
|
|
788
|
+
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
789
|
+
start = node.lineno
|
|
790
|
+
end = getattr(node, 'end_lineno', start + len(node.body))
|
|
791
|
+
loc = end - start + 1
|
|
792
|
+
cc = 1
|
|
793
|
+
for child in ast.walk(node):
|
|
794
|
+
if isinstance(child, (ast.If, ast.For, ast.AsyncFor, ast.While, ast.ExceptHandler, ast.With, ast.AsyncWith)):
|
|
795
|
+
cc += 1
|
|
796
|
+
elif isinstance(child, ast.BoolOp):
|
|
797
|
+
cc += len(child.values) - 1
|
|
798
|
+
|
|
799
|
+
real_body = [n for n in node.body if not (isinstance(n, ast.Expr) and isinstance(getattr(n, 'value', None), ast.Constant))]
|
|
800
|
+
is_stub = len(real_body) == 0 or (len(real_body) == 1 and isinstance(real_body[0], ast.Pass))
|
|
801
|
+
if is_stub:
|
|
802
|
+
empty_stubs += 1
|
|
803
|
+
|
|
804
|
+
code_smells = ["Empty stub detected"] if is_stub else []
|
|
805
|
+
mi = max(0.0, round(100.0 - (cc * 2.0) - (loc * 0.2), 1))
|
|
806
|
+
functions.append({
|
|
807
|
+
"functionName": node.name,
|
|
808
|
+
"filePath": filepath,
|
|
809
|
+
"line": start,
|
|
810
|
+
"loc": loc,
|
|
811
|
+
"cyclomatic": cc,
|
|
812
|
+
"cognitive": cc,
|
|
813
|
+
"maintainability": mi,
|
|
814
|
+
"codeSmells": code_smells
|
|
815
|
+
})
|
|
816
|
+
return {
|
|
817
|
+
"language": "python",
|
|
818
|
+
"filePath": filepath,
|
|
819
|
+
"functions": functions,
|
|
820
|
+
"unsafeTypesCount": unsafe_types,
|
|
821
|
+
"emptyStubsCount": empty_stubs
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
code = sys.stdin.read()
|
|
825
|
+
filepath = sys.argv[1] if len(sys.argv) > 1 else ""
|
|
826
|
+
print(json.dumps(analyze(code, filepath)))
|
|
827
|
+
`;
|
|
828
|
+
function extractFunctionsPythonAst(content, filePath) {
|
|
537
829
|
try {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
} else if (file.endsWith(".md")) {
|
|
548
|
-
fileList.push(fullPath);
|
|
549
|
-
}
|
|
550
|
-
} catch {
|
|
551
|
-
}
|
|
830
|
+
const res = child_process.spawnSync("python", ["-c", PYTHON_AST_RUNNER_SCRIPT, filePath], {
|
|
831
|
+
input: content,
|
|
832
|
+
encoding: "utf-8",
|
|
833
|
+
timeout: 1e4
|
|
834
|
+
});
|
|
835
|
+
if (res.status === 0 && res.stdout) {
|
|
836
|
+
const parsed = JSON.parse(res.stdout);
|
|
837
|
+
return parsed;
|
|
552
838
|
}
|
|
553
839
|
} catch {
|
|
554
840
|
}
|
|
555
|
-
|
|
841
|
+
const fallbackFns = extractPythonFunctionsPolyglot(content, filePath);
|
|
842
|
+
return {
|
|
843
|
+
language: "python",
|
|
844
|
+
filePath,
|
|
845
|
+
functions: fallbackFns,
|
|
846
|
+
unsafeTypesCount: 0,
|
|
847
|
+
emptyStubsCount: 0
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// src/verifiers/ast/native-ast-orchestrator.ts
|
|
852
|
+
function isCliInstalled(bin) {
|
|
853
|
+
try {
|
|
854
|
+
const res = child_process.spawnSync(bin, ["--version"], { encoding: "utf-8", timeout: 5e3, shell: false });
|
|
855
|
+
return res.status === 0 && !res.error;
|
|
856
|
+
} catch {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
function assertRequiredStack(stack) {
|
|
861
|
+
if (!stack) return;
|
|
862
|
+
if (!isCliInstalled(stack)) {
|
|
863
|
+
throw new Error(`[AI-SDLC Fail-Closed] Required toolchain CLI '${stack}' is not installed in the environment`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
function analyzeTypeScript(content, filePath) {
|
|
867
|
+
return {
|
|
868
|
+
language: "typescript",
|
|
869
|
+
filePath,
|
|
870
|
+
functions: extractFunctionsTypeScriptAst(content, filePath),
|
|
871
|
+
unsafeTypesCount: 0,
|
|
872
|
+
emptyStubsCount: 0
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function analyzePolyglot(content, filePath, ext) {
|
|
876
|
+
return {
|
|
877
|
+
language: ext.replace(/^\./, ""),
|
|
878
|
+
filePath,
|
|
879
|
+
functions: extractFunctionsPolyglot(content, filePath),
|
|
880
|
+
unsafeTypesCount: 0,
|
|
881
|
+
emptyStubsCount: 0
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
function orchestrateNativeAstAnalysis(content, filePath, options = {}) {
|
|
885
|
+
assertRequiredStack(options.requiredStack);
|
|
886
|
+
const ext = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
|
|
887
|
+
if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext)) {
|
|
888
|
+
return analyzeTypeScript(content, filePath);
|
|
889
|
+
}
|
|
890
|
+
if (ext === ".py") {
|
|
891
|
+
return extractFunctionsPythonAst(content, filePath);
|
|
892
|
+
}
|
|
893
|
+
return analyzePolyglot(content, filePath, ext);
|
|
556
894
|
}
|
|
557
895
|
function parseMarkdownFrontmatter(content) {
|
|
558
896
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
@@ -584,41 +922,41 @@ function extractGherkinBlock(body) {
|
|
|
584
922
|
}
|
|
585
923
|
function resolveTargetFiles(rootDir, targetPath) {
|
|
586
924
|
if (targetPath) {
|
|
587
|
-
const fullTarget =
|
|
588
|
-
if (!
|
|
589
|
-
return
|
|
925
|
+
const fullTarget = path5__namespace.isAbsolute(targetPath) ? targetPath : path5__namespace.join(rootDir, targetPath);
|
|
926
|
+
if (!fs8__namespace.existsSync(fullTarget)) return [];
|
|
927
|
+
return fs8__namespace.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
|
|
590
928
|
}
|
|
591
929
|
const searchDirs = [
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
930
|
+
path5__namespace.join(rootDir, "product"),
|
|
931
|
+
path5__namespace.join(rootDir, "security"),
|
|
932
|
+
path5__namespace.join(rootDir, "privacy"),
|
|
933
|
+
path5__namespace.join(rootDir, "specs"),
|
|
934
|
+
path5__namespace.join(rootDir, "examples", "product"),
|
|
935
|
+
path5__namespace.join(rootDir, "examples", "security"),
|
|
936
|
+
path5__namespace.join(rootDir, "examples", "privacy"),
|
|
937
|
+
path5__namespace.join(rootDir, "examples", "specs")
|
|
600
938
|
];
|
|
601
|
-
return searchDirs.filter((d) =>
|
|
939
|
+
return searchDirs.filter((d) => fs8__namespace.existsSync(d)).flatMap((d) => walkMdFiles(d));
|
|
602
940
|
}
|
|
603
941
|
function resolveFeatureFilePath(rootDir, filePath, id, customTarget) {
|
|
604
942
|
if (customTarget) {
|
|
605
|
-
return
|
|
606
|
-
}
|
|
607
|
-
const baseDir =
|
|
608
|
-
const candidateSecurity =
|
|
609
|
-
const candidatePrivacy =
|
|
610
|
-
const candidateRoot =
|
|
611
|
-
if (
|
|
612
|
-
if (
|
|
613
|
-
if (
|
|
943
|
+
return path5__namespace.isAbsolute(customTarget) ? customTarget : path5__namespace.join(rootDir, customTarget);
|
|
944
|
+
}
|
|
945
|
+
const baseDir = path5__namespace.join(rootDir, "tests", "features");
|
|
946
|
+
const candidateSecurity = path5__namespace.join(baseDir, "security", `${id.toLowerCase()}.feature`);
|
|
947
|
+
const candidatePrivacy = path5__namespace.join(baseDir, "privacy", `${id.toLowerCase()}.feature`);
|
|
948
|
+
const candidateRoot = path5__namespace.join(baseDir, `${id.toLowerCase()}.feature`);
|
|
949
|
+
if (fs8__namespace.existsSync(candidateSecurity)) return candidateSecurity;
|
|
950
|
+
if (fs8__namespace.existsSync(candidatePrivacy)) return candidatePrivacy;
|
|
951
|
+
if (fs8__namespace.existsSync(candidateRoot)) return candidateRoot;
|
|
614
952
|
const subDir = filePath.includes("security") ? "security" : filePath.includes("privacy") ? "privacy" : "";
|
|
615
|
-
return
|
|
953
|
+
return path5__namespace.join(baseDir, subDir, `${id.toLowerCase()}.feature`);
|
|
616
954
|
}
|
|
617
955
|
function buildFeatureFileContent(rootDir, filePath, id, version, gherkinBlocks) {
|
|
618
956
|
const banner = [
|
|
619
957
|
`# ==============================================================================`,
|
|
620
958
|
`# AUTO-GENERADO POR AI-SDLC (Cucumber Integration)`,
|
|
621
|
-
`# Origen: ${
|
|
959
|
+
`# Origen: ${path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/")}`,
|
|
622
960
|
`# ID Requerimiento: ${id}`,
|
|
623
961
|
`# Versi\xF3n: ${version || "1.0.0"}`,
|
|
624
962
|
`# NO EDITAR MANUALMENTE: Cualquier cambio debe realizarse en el Markdown origen.`,
|
|
@@ -643,20 +981,20 @@ function extractGherkinFeatures(options = {}) {
|
|
|
643
981
|
const targetFiles = resolveTargetFiles(rootDir, options.targetPath);
|
|
644
982
|
for (const filePath of targetFiles) {
|
|
645
983
|
try {
|
|
646
|
-
const content =
|
|
984
|
+
const content = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
647
985
|
const { frontmatter, body } = parseMarkdownFrontmatter(content);
|
|
648
986
|
const gherkinBlocks = extractGherkinBlock(body);
|
|
649
987
|
if (gherkinBlocks.length === 0) continue;
|
|
650
|
-
const id = frontmatter.id ||
|
|
988
|
+
const id = frontmatter.id || path5__namespace.basename(filePath, path5__namespace.extname(filePath));
|
|
651
989
|
const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
|
|
652
|
-
|
|
990
|
+
fs8__namespace.mkdirSync(path5__namespace.dirname(targetPath), { recursive: true });
|
|
653
991
|
const fullContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
|
|
654
|
-
|
|
992
|
+
fs8__namespace.writeFileSync(targetPath, fullContent, "utf-8");
|
|
655
993
|
const scenarioCount = countScenarios(gherkinBlocks);
|
|
656
994
|
totalScenarios += scenarioCount;
|
|
657
995
|
features.push({
|
|
658
|
-
sourceFile:
|
|
659
|
-
outputFile:
|
|
996
|
+
sourceFile: path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/"),
|
|
997
|
+
outputFile: path5__namespace.relative(rootDir, targetPath).replace(/\\/g, "/"),
|
|
660
998
|
featureName: id,
|
|
661
999
|
scenarioCount
|
|
662
1000
|
});
|
|
@@ -673,20 +1011,20 @@ function checkGherkinInSync(options = {}) {
|
|
|
673
1011
|
let totalFeatures = 0;
|
|
674
1012
|
for (const filePath of targetFiles) {
|
|
675
1013
|
try {
|
|
676
|
-
const content =
|
|
1014
|
+
const content = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
677
1015
|
const { frontmatter, body } = parseMarkdownFrontmatter(content);
|
|
678
1016
|
const gherkinBlocks = extractGherkinBlock(body);
|
|
679
1017
|
if (gherkinBlocks.length === 0) continue;
|
|
680
1018
|
totalFeatures++;
|
|
681
|
-
const id = frontmatter.id ||
|
|
1019
|
+
const id = frontmatter.id || path5__namespace.basename(filePath, path5__namespace.extname(filePath));
|
|
682
1020
|
const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
|
|
683
|
-
const relTarget =
|
|
684
|
-
if (!
|
|
1021
|
+
const relTarget = path5__namespace.relative(rootDir, targetPath).replace(/\\/g, "/");
|
|
1022
|
+
if (!fs8__namespace.existsSync(targetPath)) {
|
|
685
1023
|
missingFiles.push(relTarget);
|
|
686
1024
|
continue;
|
|
687
1025
|
}
|
|
688
1026
|
const expectedContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
|
|
689
|
-
const actualContent =
|
|
1027
|
+
const actualContent = fs8__namespace.readFileSync(targetPath, "utf-8");
|
|
690
1028
|
if (actualContent.replace(/\r\n/g, "\n") !== expectedContent.replace(/\r\n/g, "\n")) {
|
|
691
1029
|
outOfSyncFiles.push(relTarget);
|
|
692
1030
|
}
|
|
@@ -785,7 +1123,7 @@ function generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, t
|
|
|
785
1123
|
const reportLines = [
|
|
786
1124
|
`# \u{1F9EA} Deterministic Test Coverage Audit for Requirements and Tasks`,
|
|
787
1125
|
``,
|
|
788
|
-
`> **Audit Date:** ${(
|
|
1126
|
+
`> **Audit Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
789
1127
|
`> **Overall Verdict:** ${verdict}`,
|
|
790
1128
|
``,
|
|
791
1129
|
`---`,
|
|
@@ -848,8 +1186,8 @@ function parseCodeTestIds(content, relTest, reqToTestsMap) {
|
|
|
848
1186
|
}
|
|
849
1187
|
function processTestFile(fullPath, rootDir, reqToTestsMap) {
|
|
850
1188
|
try {
|
|
851
|
-
const content =
|
|
852
|
-
const relTest =
|
|
1189
|
+
const content = fs8__namespace.readFileSync(fullPath, "utf-8");
|
|
1190
|
+
const relTest = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
853
1191
|
if (fullPath.endsWith(".feature")) {
|
|
854
1192
|
parseFeatureTags(content, relTest, reqToTestsMap);
|
|
855
1193
|
}
|
|
@@ -858,16 +1196,16 @@ function processTestFile(fullPath, rootDir, reqToTestsMap) {
|
|
|
858
1196
|
}
|
|
859
1197
|
}
|
|
860
1198
|
function collectTestFiles(dir, files = []) {
|
|
861
|
-
if (!
|
|
1199
|
+
if (!fs8__namespace.existsSync(dir)) return files;
|
|
862
1200
|
try {
|
|
863
|
-
const entries =
|
|
1201
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true }).sort((a, b) => canonicalSort(a.name, b.name));
|
|
864
1202
|
for (const e of entries) {
|
|
865
1203
|
if (e.isDirectory()) {
|
|
866
1204
|
if (!EXCLUDED_TEST_DIRS.has(e.name)) {
|
|
867
|
-
collectTestFiles(
|
|
1205
|
+
collectTestFiles(path5__namespace.join(dir, e.name), files);
|
|
868
1206
|
}
|
|
869
1207
|
} else if (isTestFile(e.name)) {
|
|
870
|
-
files.push(
|
|
1208
|
+
files.push(path5__namespace.join(dir, e.name));
|
|
871
1209
|
}
|
|
872
1210
|
}
|
|
873
1211
|
} catch {
|
|
@@ -1076,7 +1414,7 @@ function collectTestCorpus(rootDir, allMdFiles) {
|
|
|
1076
1414
|
const testContents = [];
|
|
1077
1415
|
for (const tf of collectTestFiles(rootDir)) {
|
|
1078
1416
|
try {
|
|
1079
|
-
const content =
|
|
1417
|
+
const content = fs8__namespace.readFileSync(tf, "utf-8");
|
|
1080
1418
|
testContents.push(content);
|
|
1081
1419
|
if (tf.endsWith(".feature")) gherkinSources.push(content);
|
|
1082
1420
|
} catch {
|
|
@@ -1084,7 +1422,7 @@ function collectTestCorpus(rootDir, allMdFiles) {
|
|
|
1084
1422
|
}
|
|
1085
1423
|
for (const md of allMdFiles) {
|
|
1086
1424
|
try {
|
|
1087
|
-
const blocks = extractGherkinBlock(
|
|
1425
|
+
const blocks = extractGherkinBlock(fs8__namespace.readFileSync(md, "utf-8"));
|
|
1088
1426
|
gherkinSources.push(...blocks);
|
|
1089
1427
|
testContents.push(...blocks);
|
|
1090
1428
|
} catch {
|
|
@@ -1092,9 +1430,54 @@ function collectTestCorpus(rootDir, allMdFiles) {
|
|
|
1092
1430
|
}
|
|
1093
1431
|
return { gherkinSources, combinedTests: testContents.join("\n") };
|
|
1094
1432
|
}
|
|
1095
|
-
function
|
|
1433
|
+
function isCucumberScenarioPassed(element) {
|
|
1434
|
+
const steps = Array.isArray(element.steps) ? element.steps : [];
|
|
1435
|
+
if (steps.length === 0) return false;
|
|
1436
|
+
return steps.every((s) => {
|
|
1437
|
+
const res = s.result;
|
|
1438
|
+
return res && res.status === "passed";
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
function countCucumberScenarios(raw) {
|
|
1442
|
+
let passed = 0;
|
|
1443
|
+
let failed = 0;
|
|
1444
|
+
for (const feat of raw) {
|
|
1445
|
+
const elements = Array.isArray(feat.elements) ? feat.elements : [];
|
|
1446
|
+
for (const el of elements) {
|
|
1447
|
+
if (isCucumberScenarioPassed(el)) passed++;
|
|
1448
|
+
else failed++;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
return { passed, failed };
|
|
1452
|
+
}
|
|
1453
|
+
function parseCucumberReportFile(reportPath) {
|
|
1454
|
+
if (!fs8__namespace.existsSync(reportPath)) return void 0;
|
|
1455
|
+
try {
|
|
1456
|
+
const raw = JSON.parse(fs8__namespace.readFileSync(reportPath, "utf-8"));
|
|
1457
|
+
if (!Array.isArray(raw)) return void 0;
|
|
1458
|
+
const { passed, failed } = countCucumberScenarios(raw);
|
|
1459
|
+
return {
|
|
1460
|
+
evaluatedFeatures: raw.length,
|
|
1461
|
+
passedScenarios: passed,
|
|
1462
|
+
failedScenarios: failed,
|
|
1463
|
+
passed: passed + failed > 0 && failed === 0
|
|
1464
|
+
};
|
|
1465
|
+
} catch {
|
|
1466
|
+
return void 0;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
function resolveCucumberReport(rootDir, options) {
|
|
1470
|
+
const candidate = options.cucumberReportPath ? path5__namespace.isAbsolute(options.cucumberReportPath) ? options.cucumberReportPath : path5__namespace.join(rootDir, options.cucumberReportPath) : path5__namespace.join(rootDir, "reports", "cucumber-report.json");
|
|
1471
|
+
const parsed = parseCucumberReportFile(candidate);
|
|
1472
|
+
if (parsed) return parsed;
|
|
1473
|
+
if (options.enforceCucumberExecution) {
|
|
1474
|
+
return { evaluatedFeatures: 0, passedScenarios: 0, failedScenarios: 1, passed: false };
|
|
1475
|
+
}
|
|
1476
|
+
return void 0;
|
|
1477
|
+
}
|
|
1478
|
+
function assembleCoverageResult(reqAudit, taskAudit, useCaseFlows, reqErrors, taskErrors, ucErrors, rootDir, options) {
|
|
1096
1479
|
return {
|
|
1097
|
-
success: reqErrors === 0 && taskErrors === 0 && ucErrors === 0,
|
|
1480
|
+
success: reqErrors === 0 && taskErrors === 0 && ucErrors === 0 && (!resolveCucumberReport(rootDir, options) || resolveCucumberReport(rootDir, options).passed),
|
|
1098
1481
|
totalRequirements: reqAudit.length,
|
|
1099
1482
|
passedRequirements: reqAudit.length - reqErrors,
|
|
1100
1483
|
failedRequirements: reqErrors,
|
|
@@ -1105,6 +1488,7 @@ function assembleCoverageResult(reqAudit, taskAudit, useCaseFlows, reqErrors, ta
|
|
|
1105
1488
|
tasks: taskAudit,
|
|
1106
1489
|
useCaseFlows,
|
|
1107
1490
|
failedUseCaseFlows: ucErrors,
|
|
1491
|
+
...resolveCucumberReport(rootDir, options) ? { cucumberExecution: resolveCucumberReport(rootDir, options) } : {},
|
|
1108
1492
|
reportMarkdown: generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, taskErrors)
|
|
1109
1493
|
};
|
|
1110
1494
|
}
|
|
@@ -1117,8 +1501,8 @@ function recordUseCaseAuditIfPresent(frontmatter, id, relPath, isTemplate, combi
|
|
|
1117
1501
|
if (flowAudit.status === "FALLO_SIN_PRUEBA") state.ucErrors++;
|
|
1118
1502
|
}
|
|
1119
1503
|
function processMdFileForCoverage(file, rootDir, reqToTestsMap, corpus, options, state) {
|
|
1120
|
-
const { frontmatter } = parseGenericDoc(
|
|
1121
|
-
const relPath =
|
|
1504
|
+
const { frontmatter } = parseGenericDoc(fs8__namespace.readFileSync(file, "utf-8"));
|
|
1505
|
+
const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
1122
1506
|
const isTemplate = relPath.includes("templates/");
|
|
1123
1507
|
const id = frontmatter.id || "";
|
|
1124
1508
|
if (isRequirementArtifact(frontmatter, id)) {
|
|
@@ -1156,7 +1540,9 @@ function verifyTestingCoverage(options = {}) {
|
|
|
1156
1540
|
state.useCaseFlows,
|
|
1157
1541
|
state.reqErrors,
|
|
1158
1542
|
state.taskErrors,
|
|
1159
|
-
state.ucErrors
|
|
1543
|
+
state.ucErrors,
|
|
1544
|
+
rootDir,
|
|
1545
|
+
options
|
|
1160
1546
|
);
|
|
1161
1547
|
}
|
|
1162
1548
|
|
|
@@ -1190,7 +1576,6 @@ var DEFAULT_EXCLUDES = [
|
|
|
1190
1576
|
"templates",
|
|
1191
1577
|
"fixtures"
|
|
1192
1578
|
];
|
|
1193
|
-
var TS_JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1194
1579
|
function createDefaultQualityPolicy() {
|
|
1195
1580
|
return {
|
|
1196
1581
|
max_cyclomatic: 10,
|
|
@@ -1271,27 +1656,38 @@ function applyOptionOverrides(policy, overrides) {
|
|
|
1271
1656
|
if (overrides.max_function_lines !== void 0) policy.max_function_lines = Number(overrides.max_function_lines);
|
|
1272
1657
|
if (overrides.enforce_mode !== void 0) policy.enforce_mode = overrides.enforce_mode;
|
|
1273
1658
|
}
|
|
1659
|
+
function parsePolicyYaml(content) {
|
|
1660
|
+
let doc;
|
|
1661
|
+
try {
|
|
1662
|
+
doc = yaml3__default.default.load(content);
|
|
1663
|
+
} catch (err) {
|
|
1664
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in quality-policy: ${err instanceof Error ? err.message : String(err)}`);
|
|
1665
|
+
}
|
|
1666
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
1667
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid quality policy structure: root must be a YAML mapping`);
|
|
1668
|
+
}
|
|
1669
|
+
return doc;
|
|
1670
|
+
}
|
|
1274
1671
|
function parseQualityPolicy(content, overrides) {
|
|
1275
1672
|
const policy = createDefaultQualityPolicy();
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
applyDocScope(policy, record);
|
|
1281
|
-
applyDocThresholds(policy, record);
|
|
1282
|
-
if (record.max_per_function !== void 0) policy.max_cyclomatic = Number(record.max_per_function);
|
|
1283
|
-
if (record.min_acceptable_score !== void 0) policy.min_maintainability = Number(record.min_acceptable_score);
|
|
1284
|
-
if (record.max_function_lines !== void 0) policy.max_function_lines = Number(record.max_function_lines);
|
|
1285
|
-
}
|
|
1286
|
-
} catch {
|
|
1673
|
+
if (!content || !content.trim()) {
|
|
1674
|
+
applyEnvThresholds(policy);
|
|
1675
|
+
applyOptionOverrides(policy, overrides);
|
|
1676
|
+
return policy;
|
|
1287
1677
|
}
|
|
1678
|
+
const record = parsePolicyYaml(content);
|
|
1679
|
+
applyDocScope(policy, record);
|
|
1680
|
+
applyDocThresholds(policy, record);
|
|
1681
|
+
if (record.max_per_function !== void 0) policy.max_cyclomatic = Number(record.max_per_function);
|
|
1682
|
+
if (record.min_acceptable_score !== void 0) policy.min_maintainability = Number(record.min_acceptable_score);
|
|
1683
|
+
if (record.max_function_lines !== void 0) policy.max_function_lines = Number(record.max_function_lines);
|
|
1288
1684
|
applyEnvThresholds(policy);
|
|
1289
1685
|
applyOptionOverrides(policy, overrides);
|
|
1290
1686
|
return policy;
|
|
1291
1687
|
}
|
|
1292
1688
|
function extractFunctions(content, filePath) {
|
|
1293
|
-
const
|
|
1294
|
-
return
|
|
1689
|
+
const report = orchestrateNativeAstAnalysis(content, filePath);
|
|
1690
|
+
return report.functions;
|
|
1295
1691
|
}
|
|
1296
1692
|
function extractFunctionsBraceLanguages(content, filePath) {
|
|
1297
1693
|
return extractFunctions(content, filePath);
|
|
@@ -1321,55 +1717,55 @@ function isExcludedFile(file, normalized, excludePatterns) {
|
|
|
1321
1717
|
);
|
|
1322
1718
|
}
|
|
1323
1719
|
function walkDir(dir, extensions = DEFAULT_EXTENSIONS, excludePatterns = DEFAULT_EXCLUDES) {
|
|
1324
|
-
if (!
|
|
1720
|
+
if (!fs8__namespace.existsSync(dir)) return [];
|
|
1325
1721
|
const results = [];
|
|
1326
|
-
for (const file of
|
|
1327
|
-
const fullPath =
|
|
1722
|
+
for (const file of fs8__namespace.readdirSync(dir).sort(canonicalSort)) {
|
|
1723
|
+
const fullPath = path5__namespace.join(dir, file);
|
|
1328
1724
|
const normalized = fullPath.replace(/\\/g, "/");
|
|
1329
1725
|
if (isExcludedFile(file, normalized, excludePatterns)) continue;
|
|
1330
|
-
const stat =
|
|
1726
|
+
const stat = fs8__namespace.statSync(fullPath);
|
|
1331
1727
|
if (stat.isDirectory()) {
|
|
1332
1728
|
results.push(...walkDir(fullPath, extensions, excludePatterns));
|
|
1333
|
-
} else if (extensions.includes(
|
|
1729
|
+
} else if (extensions.includes(path5__namespace.extname(file).toLowerCase())) {
|
|
1334
1730
|
results.push(fullPath);
|
|
1335
1731
|
}
|
|
1336
1732
|
}
|
|
1337
1733
|
return results;
|
|
1338
1734
|
}
|
|
1339
1735
|
function resolvePolicy(rootDir, options) {
|
|
1340
|
-
const policyFile = options.policyPath ||
|
|
1736
|
+
const policyFile = options.policyPath || path5__namespace.join(rootDir, "quality-policy.yaml");
|
|
1341
1737
|
if (options.policy) {
|
|
1342
1738
|
const policy = { ...options.policy };
|
|
1343
1739
|
applyOptionOverrides(policy, options.thresholds);
|
|
1344
1740
|
return policy;
|
|
1345
1741
|
}
|
|
1346
|
-
if (
|
|
1347
|
-
return parseQualityPolicy(
|
|
1742
|
+
if (fs8__namespace.existsSync(policyFile)) {
|
|
1743
|
+
return parseQualityPolicy(fs8__namespace.readFileSync(policyFile, "utf-8"), options.thresholds);
|
|
1348
1744
|
}
|
|
1349
1745
|
return parseQualityPolicy("", options.thresholds);
|
|
1350
1746
|
}
|
|
1351
1747
|
function isPathExcluded(targetPath, rootDir, excludePatterns) {
|
|
1352
|
-
const rel =
|
|
1353
|
-
const base =
|
|
1748
|
+
const rel = path5__namespace.relative(rootDir, targetPath).replace(/\\/g, "/");
|
|
1749
|
+
const base = path5__namespace.basename(targetPath);
|
|
1354
1750
|
return excludePatterns.some(
|
|
1355
1751
|
(pat) => base === pat || rel === pat || rel.startsWith(pat + "/") || rel.includes("/" + pat + "/") || rel.endsWith("/" + pat) || rel.includes(pat)
|
|
1356
1752
|
);
|
|
1357
1753
|
}
|
|
1358
1754
|
function scanContainerSubdir(containerDir, subdirName, targetDir, rootDir, policy, files) {
|
|
1359
1755
|
if (policy.exclude_patterns.includes(subdirName)) return;
|
|
1360
|
-
const subTarget =
|
|
1361
|
-
if (!
|
|
1756
|
+
const subTarget = path5__namespace.join(containerDir, subdirName, targetDir);
|
|
1757
|
+
if (!fs8__namespace.existsSync(subTarget) || !fs8__namespace.statSync(subTarget).isDirectory()) return;
|
|
1362
1758
|
if (isPathExcluded(subTarget, rootDir, policy.exclude_patterns)) return;
|
|
1363
1759
|
for (const f of walkDir(subTarget, policy.supported_extensions, policy.exclude_patterns)) {
|
|
1364
1760
|
files.add(f);
|
|
1365
1761
|
}
|
|
1366
1762
|
}
|
|
1367
1763
|
function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
|
|
1368
|
-
const containerDir =
|
|
1369
|
-
if (!
|
|
1764
|
+
const containerDir = path5__namespace.join(rootDir, container);
|
|
1765
|
+
if (!fs8__namespace.existsSync(containerDir) || !fs8__namespace.statSync(containerDir).isDirectory()) return;
|
|
1370
1766
|
if (isPathExcluded(containerDir, rootDir, policy.exclude_patterns)) return;
|
|
1371
1767
|
try {
|
|
1372
|
-
for (const subdir of
|
|
1768
|
+
for (const subdir of fs8__namespace.readdirSync(containerDir, { withFileTypes: true })) {
|
|
1373
1769
|
if (subdir.isDirectory()) {
|
|
1374
1770
|
scanContainerSubdir(containerDir, subdir.name, targetDir, rootDir, policy, files);
|
|
1375
1771
|
}
|
|
@@ -1378,13 +1774,14 @@ function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
|
|
|
1378
1774
|
}
|
|
1379
1775
|
}
|
|
1380
1776
|
function scanDirectTarget(rootDir, targetDir, policy, files) {
|
|
1381
|
-
const fullDir =
|
|
1382
|
-
if (
|
|
1777
|
+
const fullDir = path5__namespace.join(rootDir, targetDir);
|
|
1778
|
+
if (fs8__namespace.existsSync(fullDir) && !isPathExcluded(fullDir, rootDir, policy.exclude_patterns)) {
|
|
1383
1779
|
for (const f of walkDir(fullDir, policy.supported_extensions, policy.exclude_patterns)) {
|
|
1384
1780
|
files.add(f);
|
|
1385
1781
|
}
|
|
1386
1782
|
}
|
|
1387
1783
|
}
|
|
1784
|
+
var UNIT_TEST_SUFFIXES = [".spec.ts", ".test.ts", ".spec.js", ".test.js", "_test.py", "_test.go"];
|
|
1388
1785
|
function collectFilesToScan(rootDir, policy) {
|
|
1389
1786
|
const files = /* @__PURE__ */ new Set();
|
|
1390
1787
|
const monorepoContainers = ["packages", "apps", "libs", "modules", "services"];
|
|
@@ -1394,7 +1791,7 @@ function collectFilesToScan(rootDir, policy) {
|
|
|
1394
1791
|
scanMonorepoContainer(rootDir, container, targetDir, policy, files);
|
|
1395
1792
|
}
|
|
1396
1793
|
}
|
|
1397
|
-
return Array.from(files);
|
|
1794
|
+
return Array.from(files).filter((f) => !UNIT_TEST_SUFFIXES.some((s) => f.endsWith(s)));
|
|
1398
1795
|
}
|
|
1399
1796
|
function evaluateViolations(fn, policy) {
|
|
1400
1797
|
const violations = [];
|
|
@@ -1416,38 +1813,67 @@ function evaluateViolations(fn, policy) {
|
|
|
1416
1813
|
return violations;
|
|
1417
1814
|
}
|
|
1418
1815
|
function evaluateFileQuality(file, rootDir, policy) {
|
|
1419
|
-
const content =
|
|
1816
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
1420
1817
|
const fns = extractFunctions(content, file);
|
|
1421
1818
|
return fns.map((fn) => {
|
|
1422
1819
|
const violations = evaluateViolations(fn, policy);
|
|
1423
1820
|
return {
|
|
1424
1821
|
...fn,
|
|
1425
|
-
relPath:
|
|
1822
|
+
relPath: path5__namespace.relative(rootDir, file).replace(/\\/g, "/"),
|
|
1426
1823
|
status: violations.length === 0 ? "PASS" : "FAIL",
|
|
1427
1824
|
violations
|
|
1428
1825
|
};
|
|
1429
1826
|
});
|
|
1430
1827
|
}
|
|
1431
|
-
var UNIT_TEST_SUFFIXES = [".spec.ts", ".test.ts", ".spec.js", ".test.js", "_test.py", "_test.go"];
|
|
1432
1828
|
function collectUnitTestCorpus(rootDir) {
|
|
1433
1829
|
const allCodeFiles = walkDir(rootDir, DEFAULT_EXTENSIONS, DEFAULT_EXCLUDES);
|
|
1434
1830
|
const chunks = [];
|
|
1435
1831
|
for (const tf of allCodeFiles) {
|
|
1436
1832
|
if (!UNIT_TEST_SUFFIXES.some((s) => tf.endsWith(s))) continue;
|
|
1437
1833
|
try {
|
|
1438
|
-
chunks.push(
|
|
1834
|
+
chunks.push(fs8__namespace.readFileSync(tf, "utf-8"));
|
|
1439
1835
|
} catch {
|
|
1440
1836
|
}
|
|
1441
1837
|
}
|
|
1442
1838
|
return chunks.join("\n");
|
|
1443
1839
|
}
|
|
1840
|
+
function sanitizeTestCorpus(corpus, fnName) {
|
|
1841
|
+
let cleaned = corpus.replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
1842
|
+
cleaned = cleaned.replace(/(?:\/\/|#)[^\r\n]*/g, " ");
|
|
1843
|
+
cleaned = cleaned.replace(/(['"`])((?:\\.|(?!\1)[^\\])*)\1/g, (match, quote, inner) => {
|
|
1844
|
+
const trimmed = inner.trim();
|
|
1845
|
+
if (trimmed === fnName || trimmed === `#${fnName}` || trimmed === `.${fnName}` || trimmed === `${fnName}()`) {
|
|
1846
|
+
return match;
|
|
1847
|
+
}
|
|
1848
|
+
return `${quote}${quote}`;
|
|
1849
|
+
});
|
|
1850
|
+
return cleaned;
|
|
1851
|
+
}
|
|
1852
|
+
function buildTestReferencePatterns(escaped) {
|
|
1853
|
+
return [
|
|
1854
|
+
new RegExp(`(?:\\.|\\b)${escaped}\\s*\\(`, "m"),
|
|
1855
|
+
new RegExp(`\\b(?:import|require)\\b[^;\\n]*\\b${escaped}\\b`, "m"),
|
|
1856
|
+
new RegExp(`(?:\\bexpect|\\bassert|\\bshould)\\s*\\([^;\\n]*\\b${escaped}\\b`, "m"),
|
|
1857
|
+
new RegExp("(?:\\bspyOn|\\bmock)\\s*\\([^;\\n]*['\"`]" + escaped + "['\"`]", "m"),
|
|
1858
|
+
new RegExp(`\\b${escaped}\\s*\\.`, "m"),
|
|
1859
|
+
new RegExp("\\b(?:describe|it|test|context)\\s*\\(\\s*['\"`](?:#|\\.)?" + escaped + "['\"`]", "m"),
|
|
1860
|
+
new RegExp(`\\btest_?${escaped}\\b`, "im")
|
|
1861
|
+
];
|
|
1862
|
+
}
|
|
1863
|
+
function isFunctionReferencedInTests(fnName, rawCorpus) {
|
|
1864
|
+
if (!fnName || fnName === "anonymous" || fnName === "constructor") return true;
|
|
1865
|
+
const corpus = sanitizeTestCorpus(rawCorpus, fnName);
|
|
1866
|
+
const escaped = fnName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1867
|
+
const patterns = buildTestReferencePatterns(escaped);
|
|
1868
|
+
return patterns.some((p) => p.test(corpus));
|
|
1869
|
+
}
|
|
1444
1870
|
function auditUnitTestsPerFunction(results, rootDir) {
|
|
1445
1871
|
const corpus = collectUnitTestCorpus(rootDir);
|
|
1446
1872
|
const untested = [];
|
|
1447
1873
|
for (const r of results) {
|
|
1448
1874
|
const fnName = r.functionName.includes(".") ? r.functionName.split(".").pop() : r.functionName;
|
|
1449
1875
|
if (!fnName || fnName === "anonymous" || fnName === "constructor") continue;
|
|
1450
|
-
if (!
|
|
1876
|
+
if (!isFunctionReferencedInTests(fnName, corpus)) {
|
|
1451
1877
|
untested.push({ file: r.relPath, functionName: r.functionName });
|
|
1452
1878
|
r.violations.push(`Missing unit test for function '${r.functionName}'`);
|
|
1453
1879
|
r.status = "FAIL";
|
|
@@ -1457,11 +1883,11 @@ function auditUnitTestsPerFunction(results, rootDir) {
|
|
|
1457
1883
|
}
|
|
1458
1884
|
function isVitestMidFlightCoverage(rel, fullPath) {
|
|
1459
1885
|
if (!process.env.VITEST || rel !== "coverage/coverage-summary.json") return false;
|
|
1460
|
-
return
|
|
1886
|
+
return path5__namespace.resolve(fullPath) === path5__namespace.resolve(process.cwd(), rel);
|
|
1461
1887
|
}
|
|
1462
1888
|
function parseCoverageSummaryFile(filePath, source) {
|
|
1463
1889
|
try {
|
|
1464
|
-
const raw = JSON.parse(
|
|
1890
|
+
const raw = JSON.parse(fs8__namespace.readFileSync(filePath, "utf-8"));
|
|
1465
1891
|
const total = raw.total;
|
|
1466
1892
|
if (!total) return null;
|
|
1467
1893
|
return {
|
|
@@ -1476,8 +1902,8 @@ function parseCoverageSummaryFile(filePath, source) {
|
|
|
1476
1902
|
}
|
|
1477
1903
|
function readSummaryCoverageJson(rootDir) {
|
|
1478
1904
|
for (const rel of ["coverage/coverage-summary.json", "reports/coverage-summary.json"]) {
|
|
1479
|
-
const full =
|
|
1480
|
-
if (!
|
|
1905
|
+
const full = path5__namespace.join(rootDir, rel);
|
|
1906
|
+
if (!fs8__namespace.existsSync(full) || isVitestMidFlightCoverage(rel, full)) continue;
|
|
1481
1907
|
const summary = parseCoverageSummaryFile(full, rel);
|
|
1482
1908
|
if (summary) return summary;
|
|
1483
1909
|
}
|
|
@@ -1501,7 +1927,7 @@ function evaluateCoverageThresholds(rootDir, policy, requireReport) {
|
|
|
1501
1927
|
}
|
|
1502
1928
|
function computeViolationSignature(relPath, fnName, metric) {
|
|
1503
1929
|
const normPath = relPath.replace(/\\/g, "/");
|
|
1504
|
-
return
|
|
1930
|
+
return crypto3__namespace.createHash("sha256").update(normPath + ":" + fnName + ":" + metric).digest("hex");
|
|
1505
1931
|
}
|
|
1506
1932
|
function classifyMetricFromMessage(msg, fn) {
|
|
1507
1933
|
if (msg.startsWith("Cyclomatic Complexity")) {
|
|
@@ -1530,8 +1956,8 @@ function isMetricWorsened(baselineEntry, current) {
|
|
|
1530
1956
|
}
|
|
1531
1957
|
function loadQualityBaseline(baselineFile) {
|
|
1532
1958
|
try {
|
|
1533
|
-
if (!
|
|
1534
|
-
const content =
|
|
1959
|
+
if (!fs8__namespace.existsSync(baselineFile)) return null;
|
|
1960
|
+
const content = fs8__namespace.readFileSync(baselineFile, "utf-8");
|
|
1535
1961
|
return JSON.parse(content);
|
|
1536
1962
|
} catch {
|
|
1537
1963
|
return null;
|
|
@@ -1540,11 +1966,11 @@ function loadQualityBaseline(baselineFile) {
|
|
|
1540
1966
|
function writeQualityBaseline(baselineFile, entries) {
|
|
1541
1967
|
const baseline = {
|
|
1542
1968
|
version: "1.0",
|
|
1543
|
-
generatedAt: (
|
|
1969
|
+
generatedAt: resolveDeterministicDate(path5__namespace.dirname(baselineFile)).toISOString(),
|
|
1544
1970
|
totalViolations: entries.length,
|
|
1545
1971
|
violations: entries
|
|
1546
1972
|
};
|
|
1547
|
-
|
|
1973
|
+
fs8__namespace.writeFileSync(baselineFile, JSON.stringify(baseline, null, 2) + "\n", "utf-8");
|
|
1548
1974
|
return baseline;
|
|
1549
1975
|
}
|
|
1550
1976
|
function extractBaselineEntriesFromResults(results) {
|
|
@@ -1603,7 +2029,7 @@ function applyRatchetMode(results, baseline) {
|
|
|
1603
2029
|
function resolveBaselineFilePath(rootDir, options) {
|
|
1604
2030
|
if (options.baselinePath) return options.baselinePath;
|
|
1605
2031
|
if (typeof options.baseline === "string") return options.baseline;
|
|
1606
|
-
return
|
|
2032
|
+
return path5__namespace.join(rootDir, "quality-baseline.json");
|
|
1607
2033
|
}
|
|
1608
2034
|
function isRatchetModeActive(policy, options, baselineExists) {
|
|
1609
2035
|
if (options.ratchet !== void 0) return options.ratchet;
|
|
@@ -1611,7 +2037,7 @@ function isRatchetModeActive(policy, options, baselineExists) {
|
|
|
1611
2037
|
return Boolean(policy.ratchet_mode || options.baseline && baselineExists);
|
|
1612
2038
|
}
|
|
1613
2039
|
function evaluateRatchetIfActive(results, baselineFile, isActive) {
|
|
1614
|
-
if (!isActive || !
|
|
2040
|
+
if (!isActive || !fs8__namespace.existsSync(baselineFile)) {
|
|
1615
2041
|
return { toleratedViolationsCount: 0, newViolationsCount: 0, baselineViolationsCount: 0 };
|
|
1616
2042
|
}
|
|
1617
2043
|
const baseline = loadQualityBaseline(baselineFile);
|
|
@@ -1670,7 +2096,7 @@ function verifyQualityGate(options = {}) {
|
|
|
1670
2096
|
baselineViolationsCount: entries.length
|
|
1671
2097
|
});
|
|
1672
2098
|
}
|
|
1673
|
-
const baselineExists =
|
|
2099
|
+
const baselineExists = fs8__namespace.existsSync(baselineFile);
|
|
1674
2100
|
const isRatchet = isRatchetModeActive(policy, options, baselineExists);
|
|
1675
2101
|
const ratchetEval = evaluateRatchetIfActive(results, baselineFile, isRatchet);
|
|
1676
2102
|
return buildQualityGateResult(results, allFiles.length, policy, coverageViolations, untestedFunctions, {
|
|
@@ -1688,9 +2114,9 @@ function normalizeHandoffPayload(handoff, changeId) {
|
|
|
1688
2114
|
};
|
|
1689
2115
|
}
|
|
1690
2116
|
function parseChangeProfile(specFile) {
|
|
1691
|
-
if (!
|
|
2117
|
+
if (!fs8__namespace.existsSync(specFile)) return "standard";
|
|
1692
2118
|
try {
|
|
1693
|
-
const rawSpec =
|
|
2119
|
+
const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
|
|
1694
2120
|
const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1695
2121
|
if (match) {
|
|
1696
2122
|
const parsedFm = yaml3__default.default.load(match[1]);
|
|
@@ -1712,12 +2138,12 @@ function checkSpecGherkinFormat(parsedFm, rawSpec, changeWorkspaceDir, errors) {
|
|
|
1712
2138
|
}
|
|
1713
2139
|
}
|
|
1714
2140
|
function validateSpecFileContent(specFile, changeWorkspaceDir, errors) {
|
|
1715
|
-
if (!
|
|
2141
|
+
if (!fs8__namespace.existsSync(specFile)) {
|
|
1716
2142
|
errors.push(`Falta el archivo obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
|
|
1717
2143
|
return;
|
|
1718
2144
|
}
|
|
1719
2145
|
try {
|
|
1720
|
-
const rawSpec =
|
|
2146
|
+
const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
|
|
1721
2147
|
const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1722
2148
|
if (match) {
|
|
1723
2149
|
const parsedFm = yaml3__default.default.load(match[1]);
|
|
@@ -1736,19 +2162,19 @@ function validateHandoffSidecar(handoff, changeWorkspaceDir, errors) {
|
|
|
1736
2162
|
if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
|
|
1737
2163
|
errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
|
|
1738
2164
|
}
|
|
1739
|
-
return
|
|
2165
|
+
return path5__namespace.join(changeWorkspaceDir, "handoff.yaml");
|
|
1740
2166
|
}
|
|
1741
2167
|
function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
|
|
1742
|
-
const proposalFile =
|
|
1743
|
-
const tasksFile =
|
|
2168
|
+
const proposalFile = path5__namespace.join(changeWorkspaceDir, "proposal.md");
|
|
2169
|
+
const tasksFile = path5__namespace.join(changeWorkspaceDir, "tasks.md");
|
|
1744
2170
|
if (profile === "patch") {
|
|
1745
2171
|
const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
|
|
1746
|
-
return { handoff: handoff2, sidecarFile: handoff2 ?
|
|
2172
|
+
return { handoff: handoff2, sidecarFile: handoff2 ? path5__namespace.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
|
|
1747
2173
|
}
|
|
1748
|
-
if (!
|
|
2174
|
+
if (!fs8__namespace.existsSync(proposalFile)) {
|
|
1749
2175
|
warnings.push(`Falta 'proposal.md' en el espacio de trabajo OpenSpec.`);
|
|
1750
2176
|
}
|
|
1751
|
-
if (!
|
|
2177
|
+
if (!fs8__namespace.existsSync(tasksFile)) {
|
|
1752
2178
|
errors.push(`Falta el archivo obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
|
|
1753
2179
|
}
|
|
1754
2180
|
const handoff = adapter.loadSidecar(changeWorkspaceDir);
|
|
@@ -1756,20 +2182,20 @@ function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors,
|
|
|
1756
2182
|
return { handoff, sidecarFile };
|
|
1757
2183
|
}
|
|
1758
2184
|
function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
|
|
1759
|
-
const changeId =
|
|
1760
|
-
const proposalFile =
|
|
1761
|
-
const specFile =
|
|
1762
|
-
const designFile =
|
|
1763
|
-
const tasksFile =
|
|
2185
|
+
const changeId = path5__namespace.basename(changeWorkspaceDir);
|
|
2186
|
+
const proposalFile = path5__namespace.join(changeWorkspaceDir, "proposal.md");
|
|
2187
|
+
const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
|
|
2188
|
+
const designFile = path5__namespace.join(changeWorkspaceDir, "design.md");
|
|
2189
|
+
const tasksFile = path5__namespace.join(changeWorkspaceDir, "tasks.md");
|
|
1764
2190
|
return {
|
|
1765
2191
|
framework: "openspec",
|
|
1766
2192
|
changeId,
|
|
1767
2193
|
changeDir: changeWorkspaceDir,
|
|
1768
2194
|
profile,
|
|
1769
|
-
proposalFile:
|
|
1770
|
-
specFile:
|
|
1771
|
-
designFile:
|
|
1772
|
-
tasksFile:
|
|
2195
|
+
proposalFile: fs8__namespace.existsSync(proposalFile) ? proposalFile : void 0,
|
|
2196
|
+
specFile: fs8__namespace.existsSync(specFile) ? specFile : void 0,
|
|
2197
|
+
designFile: fs8__namespace.existsSync(designFile) ? designFile : void 0,
|
|
2198
|
+
tasksFile: fs8__namespace.existsSync(tasksFile) ? tasksFile : void 0,
|
|
1773
2199
|
sidecarFile,
|
|
1774
2200
|
handoff: handoff || void 0
|
|
1775
2201
|
};
|
|
@@ -1777,28 +2203,28 @@ function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sideca
|
|
|
1777
2203
|
var OpenSpecAdapter = class {
|
|
1778
2204
|
framework = "openspec";
|
|
1779
2205
|
getChangeWorkspaceDir(rootDir, changeId) {
|
|
1780
|
-
const openspecDir =
|
|
1781
|
-
if (
|
|
1782
|
-
const activeChangeDir =
|
|
1783
|
-
if (
|
|
1784
|
-
const completedChangeDir =
|
|
1785
|
-
if (
|
|
2206
|
+
const openspecDir = path5__namespace.join(rootDir, "openspec", "changes", changeId);
|
|
2207
|
+
if (fs8__namespace.existsSync(openspecDir)) return openspecDir;
|
|
2208
|
+
const activeChangeDir = path5__namespace.join(rootDir, "specs", "changes", "active", changeId);
|
|
2209
|
+
if (fs8__namespace.existsSync(activeChangeDir)) return activeChangeDir;
|
|
2210
|
+
const completedChangeDir = path5__namespace.join(rootDir, "specs", "changes", "completed", changeId);
|
|
2211
|
+
if (fs8__namespace.existsSync(completedChangeDir)) return completedChangeDir;
|
|
1786
2212
|
return activeChangeDir;
|
|
1787
2213
|
}
|
|
1788
2214
|
depositSidecar(options) {
|
|
1789
2215
|
const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
|
|
1790
|
-
if (!
|
|
1791
|
-
|
|
2216
|
+
if (!fs8__namespace.existsSync(changeDir)) {
|
|
2217
|
+
fs8__namespace.mkdirSync(changeDir, { recursive: true });
|
|
1792
2218
|
}
|
|
1793
2219
|
const handoff = normalizeHandoffPayload(options.handoff, options.changeId);
|
|
1794
2220
|
const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
|
|
1795
|
-
const filePath =
|
|
2221
|
+
const filePath = path5__namespace.join(changeDir, filename);
|
|
1796
2222
|
const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3__default.default.dump(handoff, { indent: 2, lineWidth: -1 });
|
|
1797
|
-
|
|
2223
|
+
fs8__namespace.writeFileSync(filePath, content, "utf-8");
|
|
1798
2224
|
return filePath;
|
|
1799
2225
|
}
|
|
1800
2226
|
loadSidecar(changeWorkspaceDir) {
|
|
1801
|
-
if (!
|
|
2227
|
+
if (!fs8__namespace.existsSync(changeWorkspaceDir)) return null;
|
|
1802
2228
|
const candidateFiles = [
|
|
1803
2229
|
"handoff.yaml",
|
|
1804
2230
|
"handoff.yml",
|
|
@@ -1809,10 +2235,10 @@ var OpenSpecAdapter = class {
|
|
|
1809
2235
|
"product-handoff.json"
|
|
1810
2236
|
];
|
|
1811
2237
|
for (const file of candidateFiles) {
|
|
1812
|
-
const fullPath =
|
|
1813
|
-
if (
|
|
2238
|
+
const fullPath = path5__namespace.join(changeWorkspaceDir, file);
|
|
2239
|
+
if (fs8__namespace.existsSync(fullPath)) {
|
|
1814
2240
|
try {
|
|
1815
|
-
const raw =
|
|
2241
|
+
const raw = fs8__namespace.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
|
|
1816
2242
|
const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3__default.default.load(raw);
|
|
1817
2243
|
if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
|
|
1818
2244
|
return parsed;
|
|
@@ -1824,7 +2250,7 @@ var OpenSpecAdapter = class {
|
|
|
1824
2250
|
return null;
|
|
1825
2251
|
}
|
|
1826
2252
|
validateWorkspace(changeWorkspaceDir) {
|
|
1827
|
-
if (!
|
|
2253
|
+
if (!fs8__namespace.existsSync(changeWorkspaceDir)) {
|
|
1828
2254
|
return {
|
|
1829
2255
|
valid: false,
|
|
1830
2256
|
errors: [`El directorio del cambio OpenSpec no existe: ${changeWorkspaceDir}`],
|
|
@@ -1833,7 +2259,7 @@ var OpenSpecAdapter = class {
|
|
|
1833
2259
|
}
|
|
1834
2260
|
const errors = [];
|
|
1835
2261
|
const warnings = [];
|
|
1836
|
-
const specFile =
|
|
2262
|
+
const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
|
|
1837
2263
|
const profile = parseChangeProfile(specFile);
|
|
1838
2264
|
validateSpecFileContent(specFile, changeWorkspaceDir, errors);
|
|
1839
2265
|
const { handoff, sidecarFile } = validateOpenSpecStructure(profile, changeWorkspaceDir, this, errors, warnings);
|
|
@@ -1847,9 +2273,9 @@ var OpenSpecAdapter = class {
|
|
|
1847
2273
|
}
|
|
1848
2274
|
};
|
|
1849
2275
|
function parseChangeProfile2(specFile) {
|
|
1850
|
-
if (!
|
|
2276
|
+
if (!fs8__namespace.existsSync(specFile)) return "standard";
|
|
1851
2277
|
try {
|
|
1852
|
-
const rawSpec =
|
|
2278
|
+
const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
|
|
1853
2279
|
const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1854
2280
|
if (match) {
|
|
1855
2281
|
const parsedFm = yaml3__default.default.load(match[1]);
|
|
@@ -1871,12 +2297,12 @@ function checkSpecGherkinFormat2(parsedFm, rawSpec, changeWorkspaceDir, errors)
|
|
|
1871
2297
|
}
|
|
1872
2298
|
}
|
|
1873
2299
|
function validateSpecFileContent2(specFile, changeWorkspaceDir, errors) {
|
|
1874
|
-
if (!
|
|
2300
|
+
if (!fs8__namespace.existsSync(specFile)) {
|
|
1875
2301
|
errors.push(`Falta el archivo de especificaci\xF3n obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
|
|
1876
2302
|
return;
|
|
1877
2303
|
}
|
|
1878
2304
|
try {
|
|
1879
|
-
const rawSpec =
|
|
2305
|
+
const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
|
|
1880
2306
|
const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1881
2307
|
if (match) {
|
|
1882
2308
|
const parsedFm = yaml3__default.default.load(match[1]);
|
|
@@ -1895,19 +2321,19 @@ function validateHandoffSidecar2(handoff, changeWorkspaceDir, errors) {
|
|
|
1895
2321
|
if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
|
|
1896
2322
|
errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
|
|
1897
2323
|
}
|
|
1898
|
-
return
|
|
2324
|
+
return path5__namespace.join(changeWorkspaceDir, "handoff.yaml");
|
|
1899
2325
|
}
|
|
1900
2326
|
function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
|
|
1901
|
-
const planFile =
|
|
1902
|
-
const tasksFile =
|
|
2327
|
+
const planFile = path5__namespace.join(changeWorkspaceDir, "plan.md");
|
|
2328
|
+
const tasksFile = path5__namespace.join(changeWorkspaceDir, "tasks.md");
|
|
1903
2329
|
if (profile === "patch") {
|
|
1904
2330
|
const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
|
|
1905
|
-
return { handoff: handoff2, sidecarFile: handoff2 ?
|
|
2331
|
+
return { handoff: handoff2, sidecarFile: handoff2 ? path5__namespace.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
|
|
1906
2332
|
}
|
|
1907
|
-
if (!
|
|
2333
|
+
if (!fs8__namespace.existsSync(tasksFile)) {
|
|
1908
2334
|
errors.push(`Falta el archivo de tareas obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
|
|
1909
2335
|
}
|
|
1910
|
-
if (!
|
|
2336
|
+
if (!fs8__namespace.existsSync(planFile)) {
|
|
1911
2337
|
warnings.push(`Se recomienda incluir 'plan.md' en el espacio de trabajo Spec Kit.`);
|
|
1912
2338
|
}
|
|
1913
2339
|
const handoff = adapter.loadSidecar(changeWorkspaceDir);
|
|
@@ -1915,18 +2341,18 @@ function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors,
|
|
|
1915
2341
|
return { handoff, sidecarFile };
|
|
1916
2342
|
}
|
|
1917
2343
|
function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
|
|
1918
|
-
const changeId =
|
|
1919
|
-
const specFile =
|
|
1920
|
-
const planFile =
|
|
1921
|
-
const tasksFile =
|
|
2344
|
+
const changeId = path5__namespace.basename(changeWorkspaceDir);
|
|
2345
|
+
const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
|
|
2346
|
+
const planFile = path5__namespace.join(changeWorkspaceDir, "plan.md");
|
|
2347
|
+
const tasksFile = path5__namespace.join(changeWorkspaceDir, "tasks.md");
|
|
1922
2348
|
return {
|
|
1923
2349
|
framework: "speckit",
|
|
1924
2350
|
changeId,
|
|
1925
2351
|
changeDir: changeWorkspaceDir,
|
|
1926
2352
|
profile,
|
|
1927
|
-
specFile:
|
|
1928
|
-
designFile:
|
|
1929
|
-
tasksFile:
|
|
2353
|
+
specFile: fs8__namespace.existsSync(specFile) ? specFile : void 0,
|
|
2354
|
+
designFile: fs8__namespace.existsSync(planFile) ? planFile : void 0,
|
|
2355
|
+
tasksFile: fs8__namespace.existsSync(tasksFile) ? tasksFile : void 0,
|
|
1930
2356
|
sidecarFile,
|
|
1931
2357
|
handoff: handoff || void 0
|
|
1932
2358
|
};
|
|
@@ -1934,18 +2360,18 @@ function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecar
|
|
|
1934
2360
|
var SpecKitAdapter = class {
|
|
1935
2361
|
framework = "speckit";
|
|
1936
2362
|
getChangeWorkspaceDir(rootDir, changeId) {
|
|
1937
|
-
const standardDir =
|
|
1938
|
-
if (
|
|
1939
|
-
const activeChangeDir =
|
|
1940
|
-
if (
|
|
1941
|
-
const completedChangeDir =
|
|
1942
|
-
if (
|
|
2363
|
+
const standardDir = path5__namespace.join(rootDir, "specs", changeId);
|
|
2364
|
+
if (fs8__namespace.existsSync(standardDir)) return standardDir;
|
|
2365
|
+
const activeChangeDir = path5__namespace.join(rootDir, "specs", "changes", "active", changeId);
|
|
2366
|
+
if (fs8__namespace.existsSync(activeChangeDir)) return activeChangeDir;
|
|
2367
|
+
const completedChangeDir = path5__namespace.join(rootDir, "specs", "changes", "completed", changeId);
|
|
2368
|
+
if (fs8__namespace.existsSync(completedChangeDir)) return completedChangeDir;
|
|
1943
2369
|
return standardDir;
|
|
1944
2370
|
}
|
|
1945
2371
|
depositSidecar(options) {
|
|
1946
2372
|
const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
|
|
1947
|
-
if (!
|
|
1948
|
-
|
|
2373
|
+
if (!fs8__namespace.existsSync(changeDir)) {
|
|
2374
|
+
fs8__namespace.mkdirSync(changeDir, { recursive: true });
|
|
1949
2375
|
}
|
|
1950
2376
|
const handoff = { ...options.handoff };
|
|
1951
2377
|
if (!handoff.id.startsWith("HOF-")) {
|
|
@@ -1958,20 +2384,20 @@ var SpecKitAdapter = class {
|
|
|
1958
2384
|
handoff.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1959
2385
|
}
|
|
1960
2386
|
const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
|
|
1961
|
-
const filePath =
|
|
2387
|
+
const filePath = path5__namespace.join(changeDir, filename);
|
|
1962
2388
|
const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3__default.default.dump(handoff, { indent: 2, lineWidth: -1 });
|
|
1963
|
-
|
|
1964
|
-
const specifyMemoryDir =
|
|
1965
|
-
if (
|
|
1966
|
-
if (!
|
|
1967
|
-
|
|
2389
|
+
fs8__namespace.writeFileSync(filePath, content, "utf-8");
|
|
2390
|
+
const specifyMemoryDir = path5__namespace.join(options.rootDir, ".specify", "memory");
|
|
2391
|
+
if (fs8__namespace.existsSync(path5__namespace.join(options.rootDir, ".specify"))) {
|
|
2392
|
+
if (!fs8__namespace.existsSync(specifyMemoryDir)) {
|
|
2393
|
+
fs8__namespace.mkdirSync(specifyMemoryDir, { recursive: true });
|
|
1968
2394
|
}
|
|
1969
|
-
|
|
2395
|
+
fs8__namespace.writeFileSync(path5__namespace.join(specifyMemoryDir, `${options.changeId}.handoff.yaml`), content, "utf-8");
|
|
1970
2396
|
}
|
|
1971
2397
|
return filePath;
|
|
1972
2398
|
}
|
|
1973
2399
|
loadSidecar(changeWorkspaceDir) {
|
|
1974
|
-
if (!
|
|
2400
|
+
if (!fs8__namespace.existsSync(changeWorkspaceDir)) return null;
|
|
1975
2401
|
const candidateFiles = [
|
|
1976
2402
|
"handoff.yaml",
|
|
1977
2403
|
"handoff.yml",
|
|
@@ -1981,10 +2407,10 @@ var SpecKitAdapter = class {
|
|
|
1981
2407
|
"handoff.json"
|
|
1982
2408
|
];
|
|
1983
2409
|
for (const file of candidateFiles) {
|
|
1984
|
-
const fullPath =
|
|
1985
|
-
if (
|
|
2410
|
+
const fullPath = path5__namespace.join(changeWorkspaceDir, file);
|
|
2411
|
+
if (fs8__namespace.existsSync(fullPath)) {
|
|
1986
2412
|
try {
|
|
1987
|
-
const raw =
|
|
2413
|
+
const raw = fs8__namespace.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
|
|
1988
2414
|
const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3__default.default.load(raw);
|
|
1989
2415
|
if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
|
|
1990
2416
|
return parsed;
|
|
@@ -1996,7 +2422,7 @@ var SpecKitAdapter = class {
|
|
|
1996
2422
|
return null;
|
|
1997
2423
|
}
|
|
1998
2424
|
validateWorkspace(changeWorkspaceDir) {
|
|
1999
|
-
if (!
|
|
2425
|
+
if (!fs8__namespace.existsSync(changeWorkspaceDir)) {
|
|
2000
2426
|
return {
|
|
2001
2427
|
valid: false,
|
|
2002
2428
|
errors: [`El directorio del cambio Spec Kit no existe: ${changeWorkspaceDir}`],
|
|
@@ -2005,7 +2431,7 @@ var SpecKitAdapter = class {
|
|
|
2005
2431
|
}
|
|
2006
2432
|
const errors = [];
|
|
2007
2433
|
const warnings = [];
|
|
2008
|
-
const specFile =
|
|
2434
|
+
const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
|
|
2009
2435
|
const profile = parseChangeProfile2(specFile);
|
|
2010
2436
|
validateSpecFileContent2(specFile, changeWorkspaceDir, errors);
|
|
2011
2437
|
const { handoff, sidecarFile } = validateSpecKitStructure(profile, changeWorkspaceDir, this, errors, warnings);
|
|
@@ -2177,27 +2603,27 @@ ${frontmatter}---
|
|
|
2177
2603
|
${body}`;
|
|
2178
2604
|
}
|
|
2179
2605
|
function materializeCanonicalArtifact(rootDir, item, changeId, author, date) {
|
|
2180
|
-
const targetPath =
|
|
2181
|
-
if (
|
|
2182
|
-
|
|
2606
|
+
const targetPath = path5__namespace.join(rootDir, item.targetDir, `${item.id}.md`);
|
|
2607
|
+
if (fs8__namespace.existsSync(targetPath)) return targetPath;
|
|
2608
|
+
fs8__namespace.mkdirSync(path5__namespace.dirname(targetPath), { recursive: true });
|
|
2183
2609
|
const initialContent = buildCanonicalInitialContent(item, changeId, author, date);
|
|
2184
|
-
|
|
2610
|
+
fs8__namespace.writeFileSync(targetPath, initialContent, "utf-8");
|
|
2185
2611
|
return targetPath;
|
|
2186
2612
|
}
|
|
2187
2613
|
function findChangeWorkspaceDir(rootDir, changeId) {
|
|
2188
|
-
const activeDir =
|
|
2189
|
-
if (
|
|
2190
|
-
const completedDir =
|
|
2191
|
-
if (
|
|
2192
|
-
const openspecActive =
|
|
2193
|
-
if (
|
|
2614
|
+
const activeDir = path5__namespace.join(rootDir, "specs", "changes", "active", changeId);
|
|
2615
|
+
if (fs8__namespace.existsSync(activeDir)) return activeDir;
|
|
2616
|
+
const completedDir = path5__namespace.join(rootDir, "specs", "changes", "completed", changeId);
|
|
2617
|
+
if (fs8__namespace.existsSync(completedDir)) return completedDir;
|
|
2618
|
+
const openspecActive = path5__namespace.join(rootDir, "openspec", "changes", changeId);
|
|
2619
|
+
if (fs8__namespace.existsSync(openspecActive)) return openspecActive;
|
|
2194
2620
|
return null;
|
|
2195
2621
|
}
|
|
2196
2622
|
function checkTasksExecution(changeDir, changeId) {
|
|
2197
|
-
const tasksFile =
|
|
2623
|
+
const tasksFile = path5__namespace.join(changeDir, "tasks.md");
|
|
2198
2624
|
const allTasks = [];
|
|
2199
|
-
if (
|
|
2200
|
-
allTasks.push(...parseTasksFromContent(
|
|
2625
|
+
if (fs8__namespace.existsSync(tasksFile)) {
|
|
2626
|
+
allTasks.push(...parseTasksFromContent(fs8__namespace.readFileSync(tasksFile, "utf-8")));
|
|
2201
2627
|
}
|
|
2202
2628
|
const completedTasks = allTasks.filter((t) => t.status === "COMPLETED").map((t) => t.id);
|
|
2203
2629
|
const pendingTasks = allTasks.filter((t) => t.status !== "COMPLETED").map((t) => t.id);
|
|
@@ -2229,9 +2655,9 @@ function buildSubgraphFromProposalIds(ids) {
|
|
|
2229
2655
|
function resolveChangeHandoff(changeDir, changeId) {
|
|
2230
2656
|
const sidecar = loadProductHandoffSidecar(changeDir);
|
|
2231
2657
|
if (sidecar) return sidecar;
|
|
2232
|
-
const proposalFile =
|
|
2233
|
-
if (!
|
|
2234
|
-
const propContent =
|
|
2658
|
+
const proposalFile = path5__namespace.join(changeDir, "proposal.md");
|
|
2659
|
+
if (!fs8__namespace.existsSync(proposalFile)) return null;
|
|
2660
|
+
const propContent = fs8__namespace.readFileSync(proposalFile, "utf-8");
|
|
2235
2661
|
const { frontmatter } = parseFrontmatter(propContent);
|
|
2236
2662
|
const citations = Array.isArray(frontmatter.citations) ? frontmatter.citations : [];
|
|
2237
2663
|
const ids = citations.map((c) => c.id || "").filter(Boolean);
|
|
@@ -2245,9 +2671,9 @@ function resolveChangeHandoff(changeDir, changeId) {
|
|
|
2245
2671
|
}
|
|
2246
2672
|
function materializeNewCatalogArtifacts(changeDir, rootDir, changeId, author, date, errors) {
|
|
2247
2673
|
const created = [];
|
|
2248
|
-
const specFile =
|
|
2249
|
-
if (!
|
|
2250
|
-
const specContent =
|
|
2674
|
+
const specFile = path5__namespace.join(changeDir, "spec.md");
|
|
2675
|
+
if (!fs8__namespace.existsSync(specFile)) return created;
|
|
2676
|
+
const specContent = fs8__namespace.readFileSync(specFile, "utf-8");
|
|
2251
2677
|
const { newItems } = parseSpecCatalogScope(specContent);
|
|
2252
2678
|
for (const item of newItems) {
|
|
2253
2679
|
try {
|
|
@@ -2264,10 +2690,10 @@ function loadCanonicalArtifacts(rootDir) {
|
|
|
2264
2690
|
const allMdFiles = walkMdFiles(rootDir);
|
|
2265
2691
|
const canonicalArtifacts = /* @__PURE__ */ new Map();
|
|
2266
2692
|
for (const file of allMdFiles) {
|
|
2267
|
-
const relFile =
|
|
2693
|
+
const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
2268
2694
|
if (relFile.includes("specs/changes/") || relFile.includes("templates/")) continue;
|
|
2269
2695
|
try {
|
|
2270
|
-
const content =
|
|
2696
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
2271
2697
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
2272
2698
|
if (frontmatter.id) canonicalArtifacts.set(String(frontmatter.id), { file, frontmatter, body });
|
|
2273
2699
|
} catch {
|
|
@@ -2280,7 +2706,7 @@ function writeUpdatedArtifact(file, frontmatter, body) {
|
|
|
2280
2706
|
${yaml3__default.default.dump(frontmatter, { indent: 2, lineWidth: -1 }).trim()}
|
|
2281
2707
|
---
|
|
2282
2708
|
`;
|
|
2283
|
-
|
|
2709
|
+
fs8__namespace.writeFileSync(file, yamlHeader + body, "utf-8");
|
|
2284
2710
|
}
|
|
2285
2711
|
function updateSingleCanonicalRequirement(canonical, completedTasks, changeId, date, author) {
|
|
2286
2712
|
const fm = canonical.frontmatter;
|
|
@@ -2396,28 +2822,28 @@ function updateArchitectureComponents(citedComponents, canonicalArtifacts, targe
|
|
|
2396
2822
|
return updated;
|
|
2397
2823
|
}
|
|
2398
2824
|
function moveToCompletedFolder(changeDir, changeId, rootDir) {
|
|
2399
|
-
const completedParent =
|
|
2400
|
-
if (!
|
|
2401
|
-
const destDir =
|
|
2402
|
-
if (
|
|
2403
|
-
|
|
2404
|
-
const destProposal =
|
|
2405
|
-
if (
|
|
2406
|
-
const { frontmatter, body } = parseFrontmatter(
|
|
2825
|
+
const completedParent = path5__namespace.join(rootDir, "specs", "changes", "completed");
|
|
2826
|
+
if (!fs8__namespace.existsSync(completedParent)) fs8__namespace.mkdirSync(completedParent, { recursive: true });
|
|
2827
|
+
const destDir = path5__namespace.join(completedParent, changeId);
|
|
2828
|
+
if (fs8__namespace.existsSync(destDir)) fs8__namespace.rmSync(destDir, { recursive: true, force: true });
|
|
2829
|
+
fs8__namespace.renameSync(changeDir, destDir);
|
|
2830
|
+
const destProposal = path5__namespace.join(destDir, "proposal.md");
|
|
2831
|
+
if (fs8__namespace.existsSync(destProposal)) {
|
|
2832
|
+
const { frontmatter, body } = parseFrontmatter(fs8__namespace.readFileSync(destProposal, "utf-8"));
|
|
2407
2833
|
frontmatter.status = "applied";
|
|
2408
2834
|
writeUpdatedArtifact(destProposal, frontmatter, body);
|
|
2409
2835
|
}
|
|
2410
2836
|
return destDir;
|
|
2411
2837
|
}
|
|
2412
2838
|
function archiveChangeWorkspace(changeDir, changeId, rootDir, autoArchive, completedAll) {
|
|
2413
|
-
const isActiveFolder = changeDir.includes("specs" +
|
|
2839
|
+
const isActiveFolder = changeDir.includes("specs" + path5__namespace.sep + "changes" + path5__namespace.sep + "active");
|
|
2414
2840
|
if (completedAll && autoArchive && isActiveFolder) {
|
|
2415
2841
|
const destDir = moveToCompletedFolder(changeDir, changeId, rootDir);
|
|
2416
2842
|
return { archived: true, archivedPath: destDir };
|
|
2417
2843
|
}
|
|
2418
|
-
const proposalFile =
|
|
2419
|
-
if (
|
|
2420
|
-
const { frontmatter, body } = parseFrontmatter(
|
|
2844
|
+
const proposalFile = path5__namespace.join(changeDir, "proposal.md");
|
|
2845
|
+
if (fs8__namespace.existsSync(proposalFile)) {
|
|
2846
|
+
const { frontmatter, body } = parseFrontmatter(fs8__namespace.readFileSync(proposalFile, "utf-8"));
|
|
2421
2847
|
if (completedAll) frontmatter.status = "completed";
|
|
2422
2848
|
writeUpdatedArtifact(proposalFile, frontmatter, body);
|
|
2423
2849
|
}
|
|
@@ -2482,9 +2908,9 @@ function collectSubgraphPromotableIds(subgraph) {
|
|
|
2482
2908
|
return Array.from(new Set(combined));
|
|
2483
2909
|
}
|
|
2484
2910
|
function collectSpecAffectedNonArchIds(changeDir) {
|
|
2485
|
-
const specFile =
|
|
2486
|
-
if (!
|
|
2487
|
-
const { affectedItems } = parseSpecCatalogScope(
|
|
2911
|
+
const specFile = path5__namespace.join(changeDir, "spec.md");
|
|
2912
|
+
if (!fs8__namespace.existsSync(specFile)) return [];
|
|
2913
|
+
const { affectedItems } = parseSpecCatalogScope(fs8__namespace.readFileSync(specFile, "utf-8"));
|
|
2488
2914
|
return affectedItems.map((item) => item.id).filter((id) => !isArchitectureComponentId(id));
|
|
2489
2915
|
}
|
|
2490
2916
|
function resolveIntegrationOptions(options) {
|
|
@@ -2558,8 +2984,8 @@ function resolveAuditStatus(integratedCount, targetCount, gapsCount) {
|
|
|
2558
2984
|
function auditChangeEntry(changeDir, changeName, canonicalMap) {
|
|
2559
2985
|
const handoff = loadProductHandoffSidecar(changeDir);
|
|
2560
2986
|
if (!handoff) return null;
|
|
2561
|
-
const tasksFile =
|
|
2562
|
-
const tasks =
|
|
2987
|
+
const tasksFile = path5__namespace.join(changeDir, "tasks.md");
|
|
2988
|
+
const tasks = fs8__namespace.existsSync(tasksFile) ? parseTasksFromContent(fs8__namespace.readFileSync(tasksFile, "utf-8")) : [];
|
|
2563
2989
|
const completed = tasks.filter((t) => t.status === "COMPLETED");
|
|
2564
2990
|
const targetReqs = collectSubgraphPromotableIds(handoff.subgraph);
|
|
2565
2991
|
const { integrated, missing, gaps } = auditChangeRequirements(targetReqs, canonicalMap);
|
|
@@ -2575,11 +3001,11 @@ function auditChangeEntry(changeDir, changeName, canonicalMap) {
|
|
|
2575
3001
|
};
|
|
2576
3002
|
}
|
|
2577
3003
|
function collectParentDirAudits(parent, canonicalMap, audits, errors) {
|
|
2578
|
-
if (!
|
|
2579
|
-
const entries =
|
|
3004
|
+
if (!fs8__namespace.existsSync(parent)) return;
|
|
3005
|
+
const entries = fs8__namespace.readdirSync(parent, { withFileTypes: true });
|
|
2580
3006
|
for (const e of entries) {
|
|
2581
3007
|
if (!e.isDirectory()) continue;
|
|
2582
|
-
const audit = auditChangeEntry(
|
|
3008
|
+
const audit = auditChangeEntry(path5__namespace.join(parent, e.name), e.name, canonicalMap);
|
|
2583
3009
|
if (audit) {
|
|
2584
3010
|
if (audit.gaps.length > 0) errors.push(`[${e.name}] Desalineaci\xF3n: ${audit.gaps.join("; ")}`);
|
|
2585
3011
|
audits.push(audit);
|
|
@@ -2591,8 +3017,8 @@ function verifySddIntegration(options = {}) {
|
|
|
2591
3017
|
const audits = [];
|
|
2592
3018
|
const errors = [];
|
|
2593
3019
|
const searchDirs = [
|
|
2594
|
-
|
|
2595
|
-
|
|
3020
|
+
path5__namespace.join(rootDir, "specs", "changes", "completed"),
|
|
3021
|
+
path5__namespace.join(rootDir, "specs", "changes", "active")
|
|
2596
3022
|
];
|
|
2597
3023
|
const canonicalMap = loadCanonicalArtifacts(rootDir);
|
|
2598
3024
|
for (const parent of searchDirs) {
|
|
@@ -2640,10 +3066,10 @@ function resolveMatchFromMetadata(activeFolders, options) {
|
|
|
2640
3066
|
return {};
|
|
2641
3067
|
}
|
|
2642
3068
|
function validateActiveDirectory(activeParent) {
|
|
2643
|
-
if (!
|
|
3069
|
+
if (!fs8__namespace.existsSync(activeParent)) {
|
|
2644
3070
|
return { ok: false, folders: [], error: `No existe el directorio de cambios activos en '${activeParent}'.` };
|
|
2645
3071
|
}
|
|
2646
|
-
const entries =
|
|
3072
|
+
const entries = fs8__namespace.readdirSync(activeParent, { withFileTypes: true });
|
|
2647
3073
|
const activeFolders = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2648
3074
|
if (activeFolders.length === 0) {
|
|
2649
3075
|
return { ok: false, folders: [], error: "No se encontraron carpetas de cambios activos en specs/changes/active/." };
|
|
@@ -2675,7 +3101,7 @@ function evaluateTasksProgress(changeId, tasks) {
|
|
|
2675
3101
|
}
|
|
2676
3102
|
function detectActiveChangeForIntegration(options = {}) {
|
|
2677
3103
|
const rootDir = options.rootDir || process.cwd();
|
|
2678
|
-
const activeParent =
|
|
3104
|
+
const activeParent = path5__namespace.join(rootDir, "specs", "changes", "active");
|
|
2679
3105
|
const dirCheck = validateActiveDirectory(activeParent);
|
|
2680
3106
|
if (!dirCheck.ok) {
|
|
2681
3107
|
return {
|
|
@@ -2690,9 +3116,9 @@ function detectActiveChangeForIntegration(options = {}) {
|
|
|
2690
3116
|
if (!match.changeId) {
|
|
2691
3117
|
return buildUnmatchedActiveResult(dirCheck.folders, options);
|
|
2692
3118
|
}
|
|
2693
|
-
const changeDir =
|
|
2694
|
-
const tasksFile =
|
|
2695
|
-
const tasks =
|
|
3119
|
+
const changeDir = path5__namespace.join(activeParent, match.changeId);
|
|
3120
|
+
const tasksFile = path5__namespace.join(changeDir, "tasks.md");
|
|
3121
|
+
const tasks = fs8__namespace.existsSync(tasksFile) ? parseTasksFromContent(fs8__namespace.readFileSync(tasksFile, "utf-8")) : [];
|
|
2696
3122
|
const progress = evaluateTasksProgress(match.changeId, tasks);
|
|
2697
3123
|
const reasons = match.reason ? [match.reason] : [];
|
|
2698
3124
|
if (progress.error) reasons.push(progress.error);
|
|
@@ -2707,9 +3133,46 @@ function detectActiveChangeForIntegration(options = {}) {
|
|
|
2707
3133
|
reasons
|
|
2708
3134
|
};
|
|
2709
3135
|
}
|
|
2710
|
-
function
|
|
3136
|
+
function extractSemanticContent(content) {
|
|
3137
|
+
let normalized = content.replace(/\r\n/g, "\n");
|
|
3138
|
+
normalized = normalized.replace(/(?:\n|^)##?\s+.*(?:Historial de Revisiones|Revision History)(?:[\s\S]*?(?=(?:\n##?\s+[^\n]+)|\s*$))/i, "");
|
|
3139
|
+
normalized = normalized.replace(/(?:\n|^)\|\s*\*?Versi[oó]n\*?\s*\|\s*Fecha\s*\|\s*Autor[\s\S]*?(?=(?:\n##?\s+[^\n]+)|\s*$)/i, "");
|
|
3140
|
+
return normalized.trimEnd() + "\n";
|
|
3141
|
+
}
|
|
3142
|
+
function slugify(text) {
|
|
3143
|
+
return text.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-");
|
|
3144
|
+
}
|
|
3145
|
+
function matchesSlug(line, clean) {
|
|
3146
|
+
const headingText = line.replace(/^#+\s*/, "").trim();
|
|
3147
|
+
const slug = slugify(headingText);
|
|
3148
|
+
if (slug === clean || slug.includes(clean) || clean.includes(slug)) return true;
|
|
3149
|
+
const slugNoNums = slugify(headingText.replace(/^\d+[\.\)]\s*/, "").trim());
|
|
3150
|
+
return slugNoNums === clean || clean === slugNoNums;
|
|
3151
|
+
}
|
|
3152
|
+
function matchesHeading(content, clean) {
|
|
3153
|
+
for (const line of content.split(/\r?\n/)) {
|
|
3154
|
+
const trimmed = line.trim();
|
|
3155
|
+
if (trimmed.startsWith("#") && matchesSlug(trimmed, clean)) {
|
|
3156
|
+
return true;
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
return false;
|
|
3160
|
+
}
|
|
3161
|
+
function validateAnchorExistence(targetContent, anchor) {
|
|
3162
|
+
if (!anchor) return true;
|
|
3163
|
+
const clean = anchor.replace(/^#/, "").trim().toLowerCase();
|
|
3164
|
+
if (!clean || targetContent.toLowerCase().includes(clean)) return true;
|
|
3165
|
+
if (matchesHeading(targetContent, clean)) return true;
|
|
3166
|
+
const anchorRegex = new RegExp(`(?:id|name)=["']${clean}["']|\\{#${clean}\\}`, "i");
|
|
3167
|
+
return anchorRegex.test(targetContent);
|
|
3168
|
+
}
|
|
3169
|
+
function computeRawSha256(content) {
|
|
2711
3170
|
const normalized = content.replace(/\r\n/g, "\n");
|
|
2712
|
-
return
|
|
3171
|
+
return crypto3__namespace.createHash("sha256").update(normalized, "utf-8").digest("hex");
|
|
3172
|
+
}
|
|
3173
|
+
function computeCanonicalSha256(content) {
|
|
3174
|
+
const semantic = extractSemanticContent(content);
|
|
3175
|
+
return crypto3__namespace.createHash("sha256").update(semantic, "utf-8").digest("hex");
|
|
2713
3176
|
}
|
|
2714
3177
|
function parseFrontmatterId(content) {
|
|
2715
3178
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
@@ -2799,7 +3262,7 @@ function buildPdacReportHeader(isOk, driftsCount, nodesCount) {
|
|
|
2799
3262
|
return [
|
|
2800
3263
|
`# \u{1F512} PDaC Cryptographic Matrix and Drift Detection (Anti-Drift)`,
|
|
2801
3264
|
``,
|
|
2802
|
-
`> **Evaluation Date:** ${(
|
|
3265
|
+
`> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
2803
3266
|
`> **Verdict:** ${verdict}`,
|
|
2804
3267
|
``,
|
|
2805
3268
|
`---`,
|
|
@@ -2810,8 +3273,8 @@ function buildPdacReportHeader(isOk, driftsCount, nodesCount) {
|
|
|
2810
3273
|
`| :--- | :--- | :--- | :--- |`
|
|
2811
3274
|
];
|
|
2812
3275
|
}
|
|
2813
|
-
function generatePdacReportMarkdown(nodes, drifts, unresolvedCount) {
|
|
2814
|
-
const isOk = drifts.length === 0 && unresolvedCount === 0;
|
|
3276
|
+
function generatePdacReportMarkdown(nodes, drifts, unresolvedCount, invalidAnchors = []) {
|
|
3277
|
+
const isOk = drifts.length === 0 && unresolvedCount === 0 && invalidAnchors.length === 0;
|
|
2815
3278
|
const header = buildPdacReportHeader(isOk, drifts.length, nodes.length);
|
|
2816
3279
|
const nodeRows = nodes.map(
|
|
2817
3280
|
(n) => `| **\`${n.id}\`** | \`${n.type}\` | ${n.title || "Untitled"} | \`sha256:${n.digest.substring(0, 16)}...\` |`
|
|
@@ -2823,9 +3286,9 @@ function isPdacDeliveryDoc(relFile) {
|
|
|
2823
3286
|
}
|
|
2824
3287
|
function processPdacFile(file, rootDir, baselineMap, allCitations) {
|
|
2825
3288
|
try {
|
|
2826
|
-
const content =
|
|
3289
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
2827
3290
|
const { id, type, title } = parseFrontmatterId(content);
|
|
2828
|
-
const relFile =
|
|
3291
|
+
const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
2829
3292
|
if (id) {
|
|
2830
3293
|
const citations = extractCitations(content, relFile);
|
|
2831
3294
|
baselineMap.set(id, {
|
|
@@ -2834,6 +3297,7 @@ function processPdacFile(file, rootDir, baselineMap, allCitations) {
|
|
|
2834
3297
|
filePath: relFile,
|
|
2835
3298
|
title,
|
|
2836
3299
|
digest: computeCanonicalSha256(content),
|
|
3300
|
+
rawDigest: computeRawSha256(content),
|
|
2837
3301
|
rawContent: content,
|
|
2838
3302
|
citations: citations.map((c) => ({
|
|
2839
3303
|
targetId: c.targetId,
|
|
@@ -2857,38 +3321,65 @@ function scanPdacWorkspace(rootDir) {
|
|
|
2857
3321
|
}
|
|
2858
3322
|
return { baselineMap, allCitations };
|
|
2859
3323
|
}
|
|
3324
|
+
function checkAnchor(citation, targetNode, invalidAnchors) {
|
|
3325
|
+
if (citation.anchor && targetNode.rawContent) {
|
|
3326
|
+
if (!validateAnchorExistence(targetNode.rawContent, citation.anchor)) {
|
|
3327
|
+
invalidAnchors.push({
|
|
3328
|
+
sourceFile: citation.sourceFile,
|
|
3329
|
+
targetId: citation.targetId,
|
|
3330
|
+
anchor: citation.anchor,
|
|
3331
|
+
reason: `Anchor '${citation.anchor}' not found in target artifact '${citation.targetId}' (${targetNode.filePath})`
|
|
3332
|
+
});
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
function checkDigest(citation, targetNode, drifts) {
|
|
3337
|
+
if (!citation.expectedDigest) return;
|
|
3338
|
+
const matches = citation.expectedDigest === targetNode.digest || targetNode.rawDigest !== void 0 && citation.expectedDigest === targetNode.rawDigest;
|
|
3339
|
+
if (!matches) {
|
|
3340
|
+
drifts.push({
|
|
3341
|
+
sourceId: citation.sourceFile,
|
|
3342
|
+
targetId: citation.targetId,
|
|
3343
|
+
sourceFile: citation.sourceFile,
|
|
3344
|
+
expectedDigest: citation.expectedDigest,
|
|
3345
|
+
actualDigest: targetNode.digest
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
2860
3349
|
function computePdacDrifts(allCitations, baselineMap) {
|
|
2861
3350
|
const drifts = [];
|
|
3351
|
+
const unresolvedCitations = [];
|
|
3352
|
+
const invalidAnchors = [];
|
|
2862
3353
|
let unresolvedCount = 0;
|
|
2863
3354
|
for (const citation of allCitations) {
|
|
2864
3355
|
const targetNode = baselineMap.get(citation.targetId);
|
|
2865
3356
|
if (!targetNode) {
|
|
2866
3357
|
unresolvedCount++;
|
|
2867
|
-
|
|
2868
|
-
}
|
|
2869
|
-
if (citation.expectedDigest && citation.expectedDigest !== targetNode.digest) {
|
|
2870
|
-
drifts.push({
|
|
2871
|
-
sourceId: citation.sourceFile,
|
|
2872
|
-
targetId: citation.targetId,
|
|
3358
|
+
unresolvedCitations.push({
|
|
2873
3359
|
sourceFile: citation.sourceFile,
|
|
2874
|
-
|
|
2875
|
-
actualDigest: targetNode.digest
|
|
3360
|
+
targetId: citation.targetId
|
|
2876
3361
|
});
|
|
3362
|
+
continue;
|
|
2877
3363
|
}
|
|
3364
|
+
checkAnchor(citation, targetNode, invalidAnchors);
|
|
3365
|
+
checkDigest(citation, targetNode, drifts);
|
|
2878
3366
|
}
|
|
2879
|
-
return { drifts, unresolvedCount };
|
|
3367
|
+
return { drifts, unresolvedCount, unresolvedCitations, invalidAnchors };
|
|
2880
3368
|
}
|
|
2881
3369
|
function verifyPdacGraph(options = {}) {
|
|
2882
3370
|
const rootDir = options.rootDir || process.cwd();
|
|
2883
3371
|
const { baselineMap, allCitations } = scanPdacWorkspace(rootDir);
|
|
2884
|
-
const { drifts, unresolvedCount } = computePdacDrifts(allCitations, baselineMap);
|
|
3372
|
+
const { drifts, unresolvedCount, unresolvedCitations, invalidAnchors } = computePdacDrifts(allCitations, baselineMap);
|
|
2885
3373
|
const nodes = Array.from(baselineMap.values());
|
|
2886
|
-
const reportMarkdown = generatePdacReportMarkdown(nodes, drifts, unresolvedCount);
|
|
3374
|
+
const reportMarkdown = generatePdacReportMarkdown(nodes, drifts, unresolvedCount, invalidAnchors);
|
|
2887
3375
|
return {
|
|
2888
|
-
success: drifts.length === 0,
|
|
3376
|
+
success: drifts.length === 0 && unresolvedCount === 0 && invalidAnchors.length === 0,
|
|
2889
3377
|
totalNodes: nodes.length,
|
|
2890
3378
|
totalEdges: allCitations.length,
|
|
2891
3379
|
drifts,
|
|
3380
|
+
unresolvedCount,
|
|
3381
|
+
unresolvedCitations,
|
|
3382
|
+
invalidAnchors,
|
|
2892
3383
|
reportMarkdown
|
|
2893
3384
|
};
|
|
2894
3385
|
}
|
|
@@ -2953,22 +3444,22 @@ function updateCitationsInContent(content, baselineMap, sourceFile, details) {
|
|
|
2953
3444
|
}
|
|
2954
3445
|
function collectCandidateFilesForSync(rootDir, targetPath) {
|
|
2955
3446
|
if (targetPath) {
|
|
2956
|
-
const fullTarget =
|
|
2957
|
-
if (!
|
|
2958
|
-
return
|
|
3447
|
+
const fullTarget = path5__namespace.isAbsolute(targetPath) ? targetPath : path5__namespace.join(rootDir, targetPath);
|
|
3448
|
+
if (!fs8__namespace.existsSync(fullTarget)) return [];
|
|
3449
|
+
return fs8__namespace.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
|
|
2959
3450
|
}
|
|
2960
3451
|
return walkMdFiles(rootDir).filter((f) => {
|
|
2961
|
-
const relFile =
|
|
3452
|
+
const relFile = path5__namespace.relative(rootDir, f).replace(/\\/g, "/");
|
|
2962
3453
|
return isPdacDeliveryDoc(relFile);
|
|
2963
3454
|
});
|
|
2964
3455
|
}
|
|
2965
3456
|
function syncSingleFile(file, rootDir, baselineMap, details) {
|
|
2966
3457
|
try {
|
|
2967
|
-
const content =
|
|
2968
|
-
const relFile =
|
|
3458
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
3459
|
+
const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
2969
3460
|
const { updatedContent, changed } = updateCitationsInContent(content, baselineMap, relFile, details);
|
|
2970
3461
|
if (changed) {
|
|
2971
|
-
|
|
3462
|
+
fs8__namespace.writeFileSync(file, updatedContent, "utf-8");
|
|
2972
3463
|
return relFile;
|
|
2973
3464
|
}
|
|
2974
3465
|
} catch {
|
|
@@ -3171,6 +3662,36 @@ var STARTER_SCHEMAS = Object.entries(STARTER_SCHEMAS_MAP).map(([relPath, schema]
|
|
|
3171
3662
|
}));
|
|
3172
3663
|
|
|
3173
3664
|
// src/verifiers/schemas.ts
|
|
3665
|
+
var ajvInstance = new Ajv2020__default.default({
|
|
3666
|
+
allErrors: true,
|
|
3667
|
+
strict: false,
|
|
3668
|
+
validateFormats: true,
|
|
3669
|
+
verbose: true
|
|
3670
|
+
});
|
|
3671
|
+
addFormats__default.default(ajvInstance);
|
|
3672
|
+
var compiledSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
3673
|
+
function formatFieldPath(err, propPath) {
|
|
3674
|
+
const field = err.instancePath ? err.instancePath.replace(/^\//, "").replace(/\//g, ".") : propPath || "root";
|
|
3675
|
+
return propPath && field !== "root" && field ? `${propPath}.${field}` : field || "root";
|
|
3676
|
+
}
|
|
3677
|
+
var KEYWORD_FORMATTERS = {
|
|
3678
|
+
required: (p, _err, fullPath) => `'${fullPath}' missing required field '${String(p?.missingProperty)}'`,
|
|
3679
|
+
additionalProperties: (p, _err, fullPath) => `'${fullPath}' contains disallowed extra property '${String(p?.additionalProperty)}'`,
|
|
3680
|
+
minLength: (p, _err, fullPath) => `'${fullPath}' length must be >= ${String(p?.limit)}`,
|
|
3681
|
+
maxLength: (p, _err, fullPath) => `'${fullPath}' length must be <= ${String(p?.limit)}`,
|
|
3682
|
+
enum: (p, err, fullPath) => `'${fullPath}' must be one of [${Array.isArray(p?.allowedValues) ? p.allowedValues.join(", ") : ""}], received '${String(err.data)}'`,
|
|
3683
|
+
uniqueItems: (_p, _err, fullPath) => `'${fullPath}' contains duplicate items (must have unique items)`,
|
|
3684
|
+
pattern: (p, err, fullPath) => `'${fullPath}' value '${String(err.data)}' does not match pattern ${String(p?.pattern)}`
|
|
3685
|
+
};
|
|
3686
|
+
function formatKeywordMessage(err, fullPath) {
|
|
3687
|
+
const p = err.params;
|
|
3688
|
+
const formatter = KEYWORD_FORMATTERS[err.keyword];
|
|
3689
|
+
return formatter ? formatter(p, err, fullPath) : `'${fullPath}' ${err.message || "validation failed"}`;
|
|
3690
|
+
}
|
|
3691
|
+
function formatAjvError(err, propPath) {
|
|
3692
|
+
const fullPath = formatFieldPath(err, propPath);
|
|
3693
|
+
return formatKeywordMessage(err, fullPath);
|
|
3694
|
+
}
|
|
3174
3695
|
var TYPE_TO_SCHEMA_MAP = {
|
|
3175
3696
|
// Product
|
|
3176
3697
|
actor: "schemas/product/actor.schema.json",
|
|
@@ -3315,10 +3836,10 @@ var EXCLUDED_SCHEMA_DIRS = /* @__PURE__ */ new Set([
|
|
|
3315
3836
|
]);
|
|
3316
3837
|
function walkHandoffSidecars(dir, fileList = []) {
|
|
3317
3838
|
try {
|
|
3318
|
-
if (!
|
|
3319
|
-
for (const file of
|
|
3320
|
-
const fullPath =
|
|
3321
|
-
if (
|
|
3839
|
+
if (!fs8__namespace.existsSync(dir)) return fileList;
|
|
3840
|
+
for (const file of fs8__namespace.readdirSync(dir)) {
|
|
3841
|
+
const fullPath = path5__namespace.join(dir, file);
|
|
3842
|
+
if (fs8__namespace.statSync(fullPath).isDirectory()) {
|
|
3322
3843
|
if (!EXCLUDED_SCHEMA_DIRS.has(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
|
|
3323
3844
|
walkHandoffSidecars(fullPath, fileList);
|
|
3324
3845
|
}
|
|
@@ -3341,157 +3862,41 @@ function extractArtifactData(filePath, content) {
|
|
|
3341
3862
|
}
|
|
3342
3863
|
return extractFrontmatter(content);
|
|
3343
3864
|
}
|
|
3344
|
-
function
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
function validateEnum(value, schema, currentProp) {
|
|
3351
|
-
if (schema.enum !== void 0 && !schema.enum.includes(value)) {
|
|
3352
|
-
return [`'${currentProp}' must be one of [${schema.enum.join(", ")}], received '${String(value)}'`];
|
|
3353
|
-
}
|
|
3354
|
-
return [];
|
|
3355
|
-
}
|
|
3356
|
-
function validateCombinators(value, schema, propPath, currentProp) {
|
|
3357
|
-
if (schema.oneOf) {
|
|
3358
|
-
const matchCount = schema.oneOf.filter((sub) => validateAgainstSchema(value, sub, propPath).length === 0).length;
|
|
3359
|
-
if (matchCount !== 1) {
|
|
3360
|
-
return [`'${currentProp}' must match exactly one sub-schema in oneOf`];
|
|
3361
|
-
}
|
|
3362
|
-
}
|
|
3363
|
-
if (schema.anyOf) {
|
|
3364
|
-
const anyMatched = schema.anyOf.some((sub) => validateAgainstSchema(value, sub, propPath).length === 0);
|
|
3365
|
-
if (!anyMatched) {
|
|
3366
|
-
return [`'${currentProp}' did not match any allowed alternative in anyOf`];
|
|
3367
|
-
}
|
|
3368
|
-
}
|
|
3369
|
-
return [];
|
|
3370
|
-
}
|
|
3371
|
-
function getActualJsonType(value) {
|
|
3372
|
-
if (value === null) return "null";
|
|
3373
|
-
if (Array.isArray(value)) return "array";
|
|
3374
|
-
const t = typeof value;
|
|
3375
|
-
if (t === "number" && Number.isInteger(value)) return "integer";
|
|
3376
|
-
return t;
|
|
3377
|
-
}
|
|
3378
|
-
function validateType(value, schema, currentProp) {
|
|
3379
|
-
if (schema.type === void 0) return [];
|
|
3380
|
-
const allowed = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
3381
|
-
const actual = getActualJsonType(value);
|
|
3382
|
-
const matches = allowed.some((t) => t === actual || t === "number" && actual === "integer");
|
|
3383
|
-
if (!matches) {
|
|
3384
|
-
return [`'${currentProp}' expected type [${allowed.join("|")}], received '${actual}'`];
|
|
3865
|
+
function getOrCompileSchema(schema) {
|
|
3866
|
+
const schemaObj = schema;
|
|
3867
|
+
if (typeof schemaObj.$id === "string") {
|
|
3868
|
+
const existing = ajvInstance.getSchema(schemaObj.$id);
|
|
3869
|
+
if (existing) return existing;
|
|
3870
|
+
return ajvInstance.compile(schema);
|
|
3385
3871
|
}
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
errors.push(`'${currentProp}' length must be >= ${schema.minLength}`);
|
|
3392
|
-
}
|
|
3393
|
-
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
3394
|
-
errors.push(`'${currentProp}' length must be <= ${schema.maxLength}`);
|
|
3395
|
-
}
|
|
3396
|
-
if (schema.pattern !== void 0 && !new RegExp(schema.pattern).test(value)) {
|
|
3397
|
-
errors.push(`'${currentProp}' value '${value}' does not match pattern ${schema.pattern}`);
|
|
3398
|
-
}
|
|
3399
|
-
if (schema.format === "date") {
|
|
3400
|
-
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
|
3401
|
-
if (!dateRegex.test(value) || isNaN(Date.parse(value))) {
|
|
3402
|
-
errors.push(`'${currentProp}' must be a valid ISO-8601 date string (YYYY-MM-DD)`);
|
|
3403
|
-
}
|
|
3404
|
-
}
|
|
3405
|
-
return errors;
|
|
3406
|
-
}
|
|
3407
|
-
function validateNumber(value, schema, currentProp) {
|
|
3408
|
-
const errors = [];
|
|
3409
|
-
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
3410
|
-
errors.push(`'${currentProp}' must be >= ${schema.minimum}`);
|
|
3411
|
-
}
|
|
3412
|
-
if (schema.maximum !== void 0 && value > schema.maximum) {
|
|
3413
|
-
errors.push(`'${currentProp}' must be <= ${schema.maximum}`);
|
|
3414
|
-
}
|
|
3415
|
-
return errors;
|
|
3416
|
-
}
|
|
3417
|
-
function validateArray(value, schema, currentProp) {
|
|
3418
|
-
const errors = [];
|
|
3419
|
-
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
3420
|
-
errors.push(`'${currentProp}' items count must be >= ${schema.minItems}`);
|
|
3421
|
-
}
|
|
3422
|
-
if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
|
|
3423
|
-
errors.push(`'${currentProp}' items count must be <= ${schema.maxItems}`);
|
|
3424
|
-
}
|
|
3425
|
-
if (schema.items) {
|
|
3426
|
-
value.forEach((item, idx) => {
|
|
3427
|
-
errors.push(...validateAgainstSchema(item, schema.items, `${currentProp}[${idx}]`));
|
|
3428
|
-
});
|
|
3429
|
-
}
|
|
3430
|
-
return errors;
|
|
3431
|
-
}
|
|
3432
|
-
function checkRequiredProperties(record, required, currentProp) {
|
|
3433
|
-
if (!required) return [];
|
|
3434
|
-
const errors = [];
|
|
3435
|
-
for (const reqKey of required) {
|
|
3436
|
-
if (!(reqKey in record) || record[reqKey] === void 0) {
|
|
3437
|
-
errors.push(`'${currentProp}' missing required field '${reqKey}'`);
|
|
3438
|
-
}
|
|
3439
|
-
}
|
|
3440
|
-
return errors;
|
|
3441
|
-
}
|
|
3442
|
-
function checkObjectProperties(record, schema, propPath, currentProp) {
|
|
3443
|
-
const errors = [];
|
|
3444
|
-
const properties = schema.properties || {};
|
|
3445
|
-
for (const [key, propVal] of Object.entries(record)) {
|
|
3446
|
-
const subPath = propPath ? `${propPath}.${key}` : key;
|
|
3447
|
-
if (key in properties) {
|
|
3448
|
-
errors.push(...validateAgainstSchema(propVal, properties[key], subPath));
|
|
3449
|
-
} else if (schema.additionalProperties === false) {
|
|
3450
|
-
errors.push(`'${currentProp}' contains disallowed extra property '${key}'`);
|
|
3451
|
-
} else if (typeof schema.additionalProperties === "object") {
|
|
3452
|
-
errors.push(...validateAgainstSchema(propVal, schema.additionalProperties, subPath));
|
|
3453
|
-
}
|
|
3454
|
-
}
|
|
3455
|
-
return errors;
|
|
3456
|
-
}
|
|
3457
|
-
function validateObject(record, schema, propPath, currentProp) {
|
|
3458
|
-
return [
|
|
3459
|
-
...checkRequiredProperties(record, schema.required, currentProp),
|
|
3460
|
-
...checkObjectProperties(record, schema, propPath, currentProp)
|
|
3461
|
-
];
|
|
3462
|
-
}
|
|
3463
|
-
function validateTypeSpecificRules(value, schema, propPath, currentProp) {
|
|
3464
|
-
if (typeof value === "string") return validateString(value, schema, currentProp);
|
|
3465
|
-
if (typeof value === "number") return validateNumber(value, schema, currentProp);
|
|
3466
|
-
if (Array.isArray(value)) return validateArray(value, schema, currentProp);
|
|
3467
|
-
if (typeof value === "object" && value !== null) {
|
|
3468
|
-
return validateObject(value, schema, propPath, currentProp);
|
|
3469
|
-
}
|
|
3470
|
-
return [];
|
|
3872
|
+
const cached = compiledSchemaCache.get(schema);
|
|
3873
|
+
if (cached) return cached;
|
|
3874
|
+
const compiled = ajvInstance.compile(schema);
|
|
3875
|
+
compiledSchemaCache.set(schema, compiled);
|
|
3876
|
+
return compiled;
|
|
3471
3877
|
}
|
|
3472
3878
|
function validateAgainstSchema(value, schema, propPath = "") {
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
return validateTypeSpecificRules(value, schema, propPath, currentProp);
|
|
3879
|
+
if (!schema || typeof schema !== "object") return [];
|
|
3880
|
+
try {
|
|
3881
|
+
const validate = getOrCompileSchema(schema);
|
|
3882
|
+
const valid = validate(value);
|
|
3883
|
+
if (valid || !validate.errors) return [];
|
|
3884
|
+
return validate.errors.map((err) => formatAjvError(err, propPath));
|
|
3885
|
+
} catch (err) {
|
|
3886
|
+
return [`Schema error: ${err instanceof Error ? err.message : String(err)}`];
|
|
3887
|
+
}
|
|
3483
3888
|
}
|
|
3484
3889
|
function findSchemaFile(relSchemaPath, rootDir) {
|
|
3485
|
-
const fullPath =
|
|
3486
|
-
if (
|
|
3487
|
-
const fallbackPath =
|
|
3488
|
-
if (
|
|
3890
|
+
const fullPath = path5__namespace.join(rootDir, relSchemaPath);
|
|
3891
|
+
if (fs8__namespace.existsSync(fullPath)) return fullPath;
|
|
3892
|
+
const fallbackPath = path5__namespace.join(process.cwd(), relSchemaPath);
|
|
3893
|
+
if (fs8__namespace.existsSync(fallbackPath)) return fallbackPath;
|
|
3489
3894
|
return null;
|
|
3490
3895
|
}
|
|
3491
3896
|
function loadJsonSchema(fullPath, relSchemaPath) {
|
|
3492
|
-
if (fullPath &&
|
|
3897
|
+
if (fullPath && fs8__namespace.existsSync(fullPath)) {
|
|
3493
3898
|
try {
|
|
3494
|
-
return JSON.parse(
|
|
3899
|
+
return JSON.parse(fs8__namespace.readFileSync(fullPath, "utf-8"));
|
|
3495
3900
|
} catch {
|
|
3496
3901
|
}
|
|
3497
3902
|
}
|
|
@@ -3506,7 +3911,7 @@ function resolveSchemaForArtifact(frontmatter, rootDir) {
|
|
|
3506
3911
|
}
|
|
3507
3912
|
function loadSchemaFromPathOrType(schemaOrType, rootDir) {
|
|
3508
3913
|
if (schemaOrType.endsWith(".json")) {
|
|
3509
|
-
const fullPath =
|
|
3914
|
+
const fullPath = path5__namespace.isAbsolute(schemaOrType) ? schemaOrType : findSchemaFile(schemaOrType, rootDir);
|
|
3510
3915
|
const relNormalized = schemaOrType.replace(/\\/g, "/");
|
|
3511
3916
|
const schema = loadJsonSchema(fullPath, relNormalized);
|
|
3512
3917
|
return { schema, schemaId: schemaOrType };
|
|
@@ -3551,9 +3956,9 @@ function collectCandidateSchemaFiles(options, rootDir) {
|
|
|
3551
3956
|
if (!options.targetPath) {
|
|
3552
3957
|
return [...walkMdFiles(rootDir), ...walkHandoffSidecars(rootDir)];
|
|
3553
3958
|
}
|
|
3554
|
-
const target =
|
|
3555
|
-
if (!
|
|
3556
|
-
if (
|
|
3959
|
+
const target = path5__namespace.isAbsolute(options.targetPath) ? options.targetPath : path5__namespace.join(rootDir, options.targetPath);
|
|
3960
|
+
if (!fs8__namespace.existsSync(target)) return [];
|
|
3961
|
+
if (fs8__namespace.statSync(target).isDirectory()) {
|
|
3557
3962
|
return [...walkMdFiles(target), ...walkHandoffSidecars(target)];
|
|
3558
3963
|
}
|
|
3559
3964
|
if (target.endsWith(".md") || target.endsWith(".yaml") || target.endsWith(".yml")) {
|
|
@@ -3604,7 +4009,7 @@ function buildEvaluationFailure(relPath, err) {
|
|
|
3604
4009
|
}
|
|
3605
4010
|
function evaluateArtifactFile(file, relPath, rootDir) {
|
|
3606
4011
|
try {
|
|
3607
|
-
const content =
|
|
4012
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
3608
4013
|
const frontmatter = extractArtifactData(file, content);
|
|
3609
4014
|
if (!frontmatter || !frontmatter.id) return null;
|
|
3610
4015
|
const schemaInfo = resolveSchemaForArtifact(frontmatter, rootDir);
|
|
@@ -3639,7 +4044,7 @@ function verifyArtifactsSchemas(options = {}) {
|
|
|
3639
4044
|
let validCount = 0;
|
|
3640
4045
|
let totalEvaluated = 0;
|
|
3641
4046
|
for (const file of filesToScan) {
|
|
3642
|
-
const relPath =
|
|
4047
|
+
const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
3643
4048
|
if (isIgnoredArtifactPath(relPath, Boolean(options.targetPath))) continue;
|
|
3644
4049
|
const evaluation = evaluateArtifactFile(file, relPath, rootDir);
|
|
3645
4050
|
if (!evaluation) continue;
|
|
@@ -3654,14 +4059,14 @@ function verifyArtifactsSchemas(options = {}) {
|
|
|
3654
4059
|
}
|
|
3655
4060
|
|
|
3656
4061
|
// src/adapters/sdd/scaffold.ts
|
|
3657
|
-
function
|
|
4062
|
+
function slugify2(text) {
|
|
3658
4063
|
return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3659
4064
|
}
|
|
3660
4065
|
function extractDirMaxNum(dir) {
|
|
3661
|
-
if (!
|
|
4066
|
+
if (!fs8__namespace.existsSync(dir)) return 0;
|
|
3662
4067
|
let max = 0;
|
|
3663
4068
|
try {
|
|
3664
|
-
const entries =
|
|
4069
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
|
|
3665
4070
|
for (const e of entries) {
|
|
3666
4071
|
if (!e.isDirectory()) continue;
|
|
3667
4072
|
const match = e.name.match(/^(?:chg|CHG|patch|PATCH)-(\d+)/i);
|
|
@@ -3678,10 +4083,10 @@ function extractDirMaxNum(dir) {
|
|
|
3678
4083
|
}
|
|
3679
4084
|
function getNextCorrelativeNumber(rootDir) {
|
|
3680
4085
|
const candidateDirs = [
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
4086
|
+
path5__namespace.join(rootDir, "specs", "changes", "active"),
|
|
4087
|
+
path5__namespace.join(rootDir, "specs", "changes", "completed"),
|
|
4088
|
+
path5__namespace.join(rootDir, "specs"),
|
|
4089
|
+
path5__namespace.join(rootDir, "openspec", "changes")
|
|
3685
4090
|
];
|
|
3686
4091
|
let maxNum = 0;
|
|
3687
4092
|
for (const dir of candidateDirs) {
|
|
@@ -3694,7 +4099,7 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
|
|
|
3694
4099
|
const targetChangeId = options.changeId || options.id;
|
|
3695
4100
|
if (targetChangeId) {
|
|
3696
4101
|
const rawId = targetChangeId.trim();
|
|
3697
|
-
const slug =
|
|
4102
|
+
const slug = slugify2(rawId);
|
|
3698
4103
|
const canonicalId = rawId.toUpperCase();
|
|
3699
4104
|
const match = rawId.match(/^(?:chg|CHG|patch|PATCH)-(\d+)/i);
|
|
3700
4105
|
const padNum2 = match ? match[1] : "001";
|
|
@@ -3702,7 +4107,7 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
|
|
|
3702
4107
|
}
|
|
3703
4108
|
const nextNum = getNextCorrelativeNumber(rootDir);
|
|
3704
4109
|
const padNum = String(nextNum).padStart(3, "0");
|
|
3705
|
-
const nameSlug =
|
|
4110
|
+
const nameSlug = slugify2(name);
|
|
3706
4111
|
const prefix = profile === "patch" ? "patch" : "chg";
|
|
3707
4112
|
return {
|
|
3708
4113
|
slug: `${prefix}-${padNum}-${nameSlug}`,
|
|
@@ -3711,13 +4116,13 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
|
|
|
3711
4116
|
};
|
|
3712
4117
|
}
|
|
3713
4118
|
function ensureEmptyChangeDir(changeDir) {
|
|
3714
|
-
if (
|
|
3715
|
-
const existing =
|
|
4119
|
+
if (fs8__namespace.existsSync(changeDir)) {
|
|
4120
|
+
const existing = fs8__namespace.readdirSync(changeDir);
|
|
3716
4121
|
if (existing.length > 0) {
|
|
3717
4122
|
return `El directorio del cambio '${changeDir}' ya existe y no est\xE1 vac\xEDo.`;
|
|
3718
4123
|
}
|
|
3719
4124
|
} else {
|
|
3720
|
-
|
|
4125
|
+
fs8__namespace.mkdirSync(changeDir, { recursive: true });
|
|
3721
4126
|
}
|
|
3722
4127
|
return null;
|
|
3723
4128
|
}
|
|
@@ -3726,7 +4131,7 @@ function scanCanonicalArtifacts(rootDir) {
|
|
|
3726
4131
|
const artifactMap = /* @__PURE__ */ new Map();
|
|
3727
4132
|
for (const file of allMdFiles) {
|
|
3728
4133
|
try {
|
|
3729
|
-
const content =
|
|
4134
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
3730
4135
|
const { frontmatter } = parseFrontmatter(content);
|
|
3731
4136
|
if (frontmatter?.id) {
|
|
3732
4137
|
const id = String(frontmatter.id);
|
|
@@ -4105,7 +4510,7 @@ function buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors)
|
|
|
4105
4510
|
changeId: slug,
|
|
4106
4511
|
canonicalId,
|
|
4107
4512
|
profile: "patch",
|
|
4108
|
-
changeDir:
|
|
4513
|
+
changeDir: path5__namespace.dirname(specPath),
|
|
4109
4514
|
createdFiles: [specPath],
|
|
4110
4515
|
citedArtifacts,
|
|
4111
4516
|
errors
|
|
@@ -4116,10 +4521,10 @@ function scaffoldPatchChange(rootDir, changeDir, slug, canonicalId, name, inputF
|
|
|
4116
4521
|
const citationsSection = citedArtifacts.length > 0 ? `citations:
|
|
4117
4522
|
${renderCitationsYamlBlock(citedArtifacts)}
|
|
4118
4523
|
` : "";
|
|
4119
|
-
const specPath =
|
|
4524
|
+
const specPath = path5__namespace.join(changeDir, "spec.md");
|
|
4120
4525
|
const specFmObj = buildPatchFmObj(canonicalId);
|
|
4121
4526
|
const specContent = buildPatchSpecContent(canonicalId, name, citationsSection);
|
|
4122
|
-
|
|
4527
|
+
fs8__namespace.writeFileSync(specPath, specContent, "utf-8");
|
|
4123
4528
|
const errors = validatePatchArtifacts(rootDir, changeDir, specContent, specFmObj, adapter);
|
|
4124
4529
|
return buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors);
|
|
4125
4530
|
}
|
|
@@ -4130,46 +4535,46 @@ function buildGreenfieldContent(reqId, ucId, name, canonicalId, author, dateStr)
|
|
|
4130
4535
|
${body}`;
|
|
4131
4536
|
}
|
|
4132
4537
|
function createGreenfieldRequirement(rootDir, name, padNum, canonicalId, author, dateStr, createdFiles) {
|
|
4133
|
-
const nameSlugUpper =
|
|
4538
|
+
const nameSlugUpper = slugify2(name).toUpperCase();
|
|
4134
4539
|
const reqId = `FR-${padNum}-${nameSlugUpper}-001`;
|
|
4135
4540
|
const ucId = `UC-${padNum}-${nameSlugUpper}`;
|
|
4136
|
-
const productDir =
|
|
4137
|
-
if (!
|
|
4138
|
-
const reqFilePath =
|
|
4139
|
-
if (
|
|
4541
|
+
const productDir = path5__namespace.join(rootDir, "specs", "product");
|
|
4542
|
+
if (!fs8__namespace.existsSync(productDir)) fs8__namespace.mkdirSync(productDir, { recursive: true });
|
|
4543
|
+
const reqFilePath = path5__namespace.join(productDir, `${reqId}.md`);
|
|
4544
|
+
if (fs8__namespace.existsSync(reqFilePath)) return null;
|
|
4140
4545
|
const reqContent = buildGreenfieldContent(reqId, ucId, name, canonicalId, author, dateStr);
|
|
4141
|
-
|
|
4546
|
+
fs8__namespace.writeFileSync(reqFilePath, reqContent, "utf-8");
|
|
4142
4547
|
createdFiles.push(reqFilePath);
|
|
4143
4548
|
const digest = computeCanonicalSha256(reqContent);
|
|
4144
4549
|
return { reqId, ucId, reqFilePath, digest };
|
|
4145
4550
|
}
|
|
4146
4551
|
function resolveMissingDraftDestination(rootDir, id) {
|
|
4147
4552
|
if (id.startsWith("PRIV-REQ-")) {
|
|
4148
|
-
return { dir:
|
|
4553
|
+
return { dir: path5__namespace.join(rootDir, "specs", "privacy", "requirements"), type: "privacy-requirement" };
|
|
4149
4554
|
}
|
|
4150
4555
|
if (id.startsWith("DATA-ASSET-")) {
|
|
4151
|
-
return { dir:
|
|
4556
|
+
return { dir: path5__namespace.join(rootDir, "specs", "privacy", "assets"), type: "data-asset" };
|
|
4152
4557
|
}
|
|
4153
4558
|
if (id.startsWith("PRIV-ROPA-")) {
|
|
4154
|
-
return { dir:
|
|
4559
|
+
return { dir: path5__namespace.join(rootDir, "specs", "privacy", "ropa"), type: "privacy-ropa" };
|
|
4155
4560
|
}
|
|
4156
4561
|
if (id.startsWith("UC-")) {
|
|
4157
|
-
return { dir:
|
|
4562
|
+
return { dir: path5__namespace.join(rootDir, "specs", "product"), type: "use-case" };
|
|
4158
4563
|
}
|
|
4159
4564
|
if (id.startsWith("SEC-REQ-") || id.startsWith("SAF-REQ-")) {
|
|
4160
|
-
return { dir:
|
|
4565
|
+
return { dir: path5__namespace.join(rootDir, "specs", "product"), type: "security-requirement" };
|
|
4161
4566
|
}
|
|
4162
|
-
return { dir:
|
|
4567
|
+
return { dir: path5__namespace.join(rootDir, "specs", "product"), type: "requirement" };
|
|
4163
4568
|
}
|
|
4164
4569
|
function createMissingDraftRequirement(rootDir, name, id, canonicalId, createdFiles) {
|
|
4165
4570
|
const { dir, type: reqType } = resolveMissingDraftDestination(rootDir, id);
|
|
4166
|
-
if (!
|
|
4167
|
-
const reqFilePath =
|
|
4571
|
+
if (!fs8__namespace.existsSync(dir)) fs8__namespace.mkdirSync(dir, { recursive: true });
|
|
4572
|
+
const reqFilePath = path5__namespace.join(dir, `${id}.md`);
|
|
4168
4573
|
const header = MISSING_DRAFT_HEADER.replaceAll("{{ID}}", id).replaceAll("{{TYPE}}", reqType).replaceAll("{{NAME}}", name);
|
|
4169
4574
|
const body = MISSING_DRAFT_BODY.replaceAll("{{ID}}", id).replaceAll("{{NAME}}", name).replaceAll("{{CANONICAL_ID}}", canonicalId);
|
|
4170
4575
|
const reqContent = `${header}
|
|
4171
4576
|
${body}`;
|
|
4172
|
-
|
|
4577
|
+
fs8__namespace.writeFileSync(reqFilePath, reqContent, "utf-8");
|
|
4173
4578
|
createdFiles.push(reqFilePath);
|
|
4174
4579
|
const digest = computeCanonicalSha256(reqContent);
|
|
4175
4580
|
return { reqFilePath, digest };
|
|
@@ -4284,7 +4689,7 @@ function initEmptyCitations() {
|
|
|
4284
4689
|
}
|
|
4285
4690
|
function ensureFallbackRequirement(col, padNum, name) {
|
|
4286
4691
|
if (col.subRequirements.length === 0) {
|
|
4287
|
-
col.subRequirements.push(`FR-${padNum}-${
|
|
4692
|
+
col.subRequirements.push(`FR-${padNum}-${slugify2(name).toUpperCase()}-001`);
|
|
4288
4693
|
}
|
|
4289
4694
|
}
|
|
4290
4695
|
function collectChangeCitations(rootDir, name, padNum, canonicalId, author, dateStr, inputFromIds, artifactMap, createdFiles) {
|
|
@@ -4298,7 +4703,7 @@ function collectChangeCitations(rootDir, name, padNum, canonicalId, author, date
|
|
|
4298
4703
|
return col;
|
|
4299
4704
|
}
|
|
4300
4705
|
function renderProposalFile(changeDir, canonicalId, name, author, citationsYamlBlock) {
|
|
4301
|
-
const proposalPath =
|
|
4706
|
+
const proposalPath = path5__namespace.join(changeDir, "proposal.md");
|
|
4302
4707
|
const citationsSection = citationsYamlBlock ? `citations:
|
|
4303
4708
|
${citationsYamlBlock}
|
|
4304
4709
|
` : "";
|
|
@@ -4318,11 +4723,11 @@ ${name}
|
|
|
4318
4723
|
## 2. Dependencias Externas Evaluadas
|
|
4319
4724
|
- Lista de dependencias nuevas a incorporar y verificaci\xF3n de licencia seg\xFAn \`license-policy.yaml\`.
|
|
4320
4725
|
`;
|
|
4321
|
-
|
|
4726
|
+
fs8__namespace.writeFileSync(proposalPath, proposalContent, "utf-8");
|
|
4322
4727
|
return proposalPath;
|
|
4323
4728
|
}
|
|
4324
4729
|
function renderSpecFile(changeDir, canonicalId, name, profile) {
|
|
4325
|
-
const specPath =
|
|
4730
|
+
const specPath = path5__namespace.join(changeDir, "spec.md");
|
|
4326
4731
|
const specFmObj = {
|
|
4327
4732
|
id: `SPEC-${canonicalId}`,
|
|
4328
4733
|
type: "delivery-spec",
|
|
@@ -4343,12 +4748,12 @@ cucumber-tags:
|
|
|
4343
4748
|
---
|
|
4344
4749
|
`;
|
|
4345
4750
|
const body = SPEC_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name);
|
|
4346
|
-
|
|
4751
|
+
fs8__namespace.writeFileSync(specPath, `${header}
|
|
4347
4752
|
${body}`, "utf-8");
|
|
4348
4753
|
return { specPath, specFmObj };
|
|
4349
4754
|
}
|
|
4350
4755
|
function renderDesignFile(changeDir, canonicalId, name, author, dateStr, citationsYamlBlock) {
|
|
4351
|
-
const designPath =
|
|
4756
|
+
const designPath = path5__namespace.join(changeDir, "design.md");
|
|
4352
4757
|
const citationsSection = citationsYamlBlock ? `citations:
|
|
4353
4758
|
${citationsYamlBlock}
|
|
4354
4759
|
` : "";
|
|
@@ -4367,7 +4772,7 @@ contracts: []
|
|
|
4367
4772
|
${citationsSection}---
|
|
4368
4773
|
`;
|
|
4369
4774
|
const body = DESIGN_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
|
|
4370
|
-
|
|
4775
|
+
fs8__namespace.writeFileSync(designPath, `${header}
|
|
4371
4776
|
${body}`, "utf-8");
|
|
4372
4777
|
return designPath;
|
|
4373
4778
|
}
|
|
@@ -4392,14 +4797,14 @@ function buildTasksFrontmatter(canonicalId, name, reqIds) {
|
|
|
4392
4797
|
};
|
|
4393
4798
|
}
|
|
4394
4799
|
function renderTasksFile(changeDir, canonicalId, name, author, dateStr, reqIds) {
|
|
4395
|
-
const tasksPath =
|
|
4800
|
+
const tasksPath = path5__namespace.join(changeDir, "tasks.md");
|
|
4396
4801
|
const tasksFm = buildTasksFrontmatter(canonicalId, name, reqIds);
|
|
4397
4802
|
const header = `---
|
|
4398
4803
|
${yaml3__default.default.dump(tasksFm, { indent: 2, lineWidth: -1 }).trim()}
|
|
4399
4804
|
---
|
|
4400
4805
|
`;
|
|
4401
4806
|
const body = TASKS_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
|
|
4402
|
-
|
|
4807
|
+
fs8__namespace.writeFileSync(tasksPath, `${header}
|
|
4403
4808
|
${body}`, "utf-8");
|
|
4404
4809
|
return { tasksPath, tasksFm };
|
|
4405
4810
|
}
|
|
@@ -4578,7 +4983,7 @@ function resolveCiEnvironment(env = process.env) {
|
|
|
4578
4983
|
};
|
|
4579
4984
|
}
|
|
4580
4985
|
function updateExistingEnvFile(filePath, envString, uppercaseKey) {
|
|
4581
|
-
const content =
|
|
4986
|
+
const content = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
4582
4987
|
const lines = content.split("\n");
|
|
4583
4988
|
let found = false;
|
|
4584
4989
|
const newLines = lines.map((line) => {
|
|
@@ -4589,15 +4994,15 @@ function updateExistingEnvFile(filePath, envString, uppercaseKey) {
|
|
|
4589
4994
|
return line;
|
|
4590
4995
|
});
|
|
4591
4996
|
if (!found) newLines.push(envString);
|
|
4592
|
-
|
|
4997
|
+
fs8__namespace.writeFileSync(filePath, newLines.filter((l) => l.trim() !== "").join("\n") + "\n", "utf-8");
|
|
4593
4998
|
}
|
|
4594
4999
|
function writeEnvOutputFile(envOutputFile, key, value) {
|
|
4595
5000
|
const uppercaseKey = key.toUpperCase();
|
|
4596
5001
|
const envString = `${uppercaseKey}=${value}`;
|
|
4597
|
-
if (
|
|
5002
|
+
if (fs8__namespace.existsSync(envOutputFile)) {
|
|
4598
5003
|
updateExistingEnvFile(envOutputFile, envString, uppercaseKey);
|
|
4599
5004
|
} else {
|
|
4600
|
-
|
|
5005
|
+
fs8__namespace.writeFileSync(envOutputFile, envString + "\n", "utf-8");
|
|
4601
5006
|
}
|
|
4602
5007
|
}
|
|
4603
5008
|
function resolveEnvOutputFilePath(env) {
|
|
@@ -4607,8 +5012,8 @@ function resolveEnvOutputFilePath(env) {
|
|
|
4607
5012
|
return void 0;
|
|
4608
5013
|
}
|
|
4609
5014
|
function emitCiOutput(key, value, env = process.env) {
|
|
4610
|
-
if (env.GITHUB_OUTPUT &&
|
|
4611
|
-
|
|
5015
|
+
if (env.GITHUB_OUTPUT && fs8__namespace.existsSync(env.GITHUB_OUTPUT)) {
|
|
5016
|
+
fs8__namespace.appendFileSync(env.GITHUB_OUTPUT, `${key}=${value}
|
|
4612
5017
|
`, "utf-8");
|
|
4613
5018
|
}
|
|
4614
5019
|
if (env.TF_BUILD === "true") {
|
|
@@ -4650,11 +5055,11 @@ function isIgnoredEntry(name) {
|
|
|
4650
5055
|
return name === "changes" || name === "active" || name === "node_modules";
|
|
4651
5056
|
}
|
|
4652
5057
|
function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
|
|
4653
|
-
if (!
|
|
4654
|
-
const entries =
|
|
5058
|
+
if (!fs8__namespace.existsSync(parentDir)) return;
|
|
5059
|
+
const entries = fs8__namespace.readdirSync(parentDir, { withFileTypes: true });
|
|
4655
5060
|
for (const entry of entries) {
|
|
4656
5061
|
if (!entry.isDirectory() || isIgnoredEntry(entry.name)) continue;
|
|
4657
|
-
const changeDir =
|
|
5062
|
+
const changeDir = path5__namespace.join(parentDir, entry.name);
|
|
4658
5063
|
if (handooffsHasDir(handoffsMap, changeDir)) continue;
|
|
4659
5064
|
const handoff = loadProductHandoffSidecar(changeDir);
|
|
4660
5065
|
if (handoff?.id) {
|
|
@@ -4665,10 +5070,10 @@ function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
|
|
|
4665
5070
|
function scanAllProductHandoffs(rootDir) {
|
|
4666
5071
|
const handoffsMap = /* @__PURE__ */ new Map();
|
|
4667
5072
|
const candidateParentDirs = [
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
5073
|
+
path5__namespace.join(rootDir, "specs", "changes", "active"),
|
|
5074
|
+
path5__namespace.join(rootDir, "specs", "changes", "completed"),
|
|
5075
|
+
path5__namespace.join(rootDir, "specs"),
|
|
5076
|
+
path5__namespace.join(rootDir, "openspec", "changes")
|
|
4672
5077
|
];
|
|
4673
5078
|
for (const parentDir of candidateParentDirs) {
|
|
4674
5079
|
scanParentDirectoryHandoffs(parentDir, handoffsMap);
|
|
@@ -4676,9 +5081,9 @@ function scanAllProductHandoffs(rootDir) {
|
|
|
4676
5081
|
return handoffsMap;
|
|
4677
5082
|
}
|
|
4678
5083
|
function handooffsHasDir(map, dir) {
|
|
4679
|
-
const norm =
|
|
5084
|
+
const norm = path5__namespace.resolve(dir);
|
|
4680
5085
|
for (const key of map.keys()) {
|
|
4681
|
-
if (
|
|
5086
|
+
if (path5__namespace.resolve(key) === norm) return true;
|
|
4682
5087
|
}
|
|
4683
5088
|
return false;
|
|
4684
5089
|
}
|
|
@@ -4713,7 +5118,7 @@ function buildTraceabilityHeader(isOk, errors) {
|
|
|
4713
5118
|
return [
|
|
4714
5119
|
"# 360\xB0 Requirements Traceability Matrix (RTM)",
|
|
4715
5120
|
"",
|
|
4716
|
-
`*Verification Date: ${(
|
|
5121
|
+
`*Verification Date: ${resolveDeterministicDate().toISOString()}*`,
|
|
4717
5122
|
`*Overall Status: ${isOk ? "100% TRACEABLE (PASSED)" : `ERRORS DETECTED (${errors} gaps)`}*`,
|
|
4718
5123
|
"",
|
|
4719
5124
|
"## 1. End-to-End Coverage (PDaC Handoff \u2794 arc42/NAF v4 \u2794 BDD/Gherkin)",
|
|
@@ -4775,9 +5180,9 @@ function isCodeTestFile(name) {
|
|
|
4775
5180
|
}
|
|
4776
5181
|
function indexFeatureFile(fullPath, rootDir, map) {
|
|
4777
5182
|
try {
|
|
4778
|
-
const content =
|
|
5183
|
+
const content = fs8__namespace.readFileSync(fullPath, "utf-8");
|
|
4779
5184
|
const tags = extractFeatureTags(content);
|
|
4780
|
-
const relFeature =
|
|
5185
|
+
const relFeature = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
4781
5186
|
for (const tag of tags) {
|
|
4782
5187
|
const existing = map.get(tag) || [];
|
|
4783
5188
|
if (!existing.includes(relFeature)) existing.push(relFeature);
|
|
@@ -4787,11 +5192,11 @@ function indexFeatureFile(fullPath, rootDir, map) {
|
|
|
4787
5192
|
}
|
|
4788
5193
|
}
|
|
4789
5194
|
function scanFeatureFiles(dir, rootDir, map) {
|
|
4790
|
-
if (!
|
|
5195
|
+
if (!fs8__namespace.existsSync(dir)) return;
|
|
4791
5196
|
try {
|
|
4792
|
-
const entries =
|
|
5197
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
|
|
4793
5198
|
for (const e of entries) {
|
|
4794
|
-
const fullPath =
|
|
5199
|
+
const fullPath = path5__namespace.join(dir, e.name);
|
|
4795
5200
|
if (e.isDirectory() && !shouldSkipDir(e.name)) {
|
|
4796
5201
|
scanFeatureFiles(fullPath, rootDir, map);
|
|
4797
5202
|
} else if (e.name.endsWith(".feature")) {
|
|
@@ -4803,8 +5208,8 @@ function scanFeatureFiles(dir, rootDir, map) {
|
|
|
4803
5208
|
}
|
|
4804
5209
|
function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
|
|
4805
5210
|
try {
|
|
4806
|
-
const content =
|
|
4807
|
-
const relTest =
|
|
5211
|
+
const content = fs8__namespace.readFileSync(fullPath, "utf-8");
|
|
5212
|
+
const relTest = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
4808
5213
|
for (const artId of reqIds) {
|
|
4809
5214
|
if (content.includes(artId)) {
|
|
4810
5215
|
const existing = map.get(artId) || [];
|
|
@@ -4816,11 +5221,11 @@ function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
|
|
|
4816
5221
|
}
|
|
4817
5222
|
}
|
|
4818
5223
|
function scanCodeTestFiles(dir, rootDir, reqIds, map) {
|
|
4819
|
-
if (!
|
|
5224
|
+
if (!fs8__namespace.existsSync(dir)) return;
|
|
4820
5225
|
try {
|
|
4821
|
-
const entries =
|
|
5226
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
|
|
4822
5227
|
for (const e of entries) {
|
|
4823
|
-
const fullPath =
|
|
5228
|
+
const fullPath = path5__namespace.join(dir, e.name);
|
|
4824
5229
|
if (e.isDirectory() && !shouldSkipDir(e.name)) {
|
|
4825
5230
|
scanCodeTestFiles(fullPath, rootDir, reqIds, map);
|
|
4826
5231
|
} else if (isCodeTestFile(e.name)) {
|
|
@@ -4854,9 +5259,9 @@ function isArchitectureViewFile(relFile, frontmatter) {
|
|
|
4854
5259
|
}
|
|
4855
5260
|
function processSingleMdFile(file, rootDir, artifactMap, componentsList, architectureViews) {
|
|
4856
5261
|
try {
|
|
4857
|
-
const content =
|
|
5262
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
4858
5263
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
4859
|
-
const relFile =
|
|
5264
|
+
const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
4860
5265
|
if (frontmatter.id) {
|
|
4861
5266
|
artifactMap.set(frontmatter.id, {
|
|
4862
5267
|
file: relFile,
|
|
@@ -5021,7 +5426,7 @@ function collectArchitectureViewTraces(reqId, architectureViews) {
|
|
|
5021
5426
|
const traces = [];
|
|
5022
5427
|
for (const view of architectureViews) {
|
|
5023
5428
|
if (view.content.includes(reqId)) {
|
|
5024
|
-
const viewIdentifier = isEntityWithId(view) && view.id ? view.id :
|
|
5429
|
+
const viewIdentifier = isEntityWithId(view) && view.id ? view.id : path5__namespace.basename(view.file);
|
|
5025
5430
|
traces.push(viewIdentifier);
|
|
5026
5431
|
}
|
|
5027
5432
|
}
|
|
@@ -5109,16 +5514,16 @@ function isProductionSourceFile(name) {
|
|
|
5109
5514
|
return SOURCE_CODE_EXTS.some((ext) => name.endsWith(ext));
|
|
5110
5515
|
}
|
|
5111
5516
|
function collectCodeAndTestCorpora(dir, prodChunks = [], testChunks = []) {
|
|
5112
|
-
if (!
|
|
5517
|
+
if (!fs8__namespace.existsSync(dir)) return { prodCorpus: "", testCorpus: "" };
|
|
5113
5518
|
try {
|
|
5114
|
-
for (const e of
|
|
5115
|
-
const fullPath =
|
|
5519
|
+
for (const e of fs8__namespace.readdirSync(dir, { withFileTypes: true })) {
|
|
5520
|
+
const fullPath = path5__namespace.join(dir, e.name);
|
|
5116
5521
|
if (e.isDirectory() && !shouldSkipDir(e.name)) {
|
|
5117
5522
|
collectCodeAndTestCorpora(fullPath, prodChunks, testChunks);
|
|
5118
5523
|
} else if (isCodeTestFile(e.name) || e.name.endsWith(".feature")) {
|
|
5119
|
-
testChunks.push(
|
|
5524
|
+
testChunks.push(fs8__namespace.readFileSync(fullPath, "utf-8"));
|
|
5120
5525
|
} else if (isProductionSourceFile(e.name)) {
|
|
5121
|
-
prodChunks.push(
|
|
5526
|
+
prodChunks.push(fs8__namespace.readFileSync(fullPath, "utf-8"));
|
|
5122
5527
|
}
|
|
5123
5528
|
}
|
|
5124
5529
|
} catch {
|
|
@@ -5272,12 +5677,12 @@ function walkTaskFiles(dir, fileList = [], excludeDirs = [
|
|
|
5272
5677
|
"examples"
|
|
5273
5678
|
]) {
|
|
5274
5679
|
try {
|
|
5275
|
-
if (!
|
|
5276
|
-
const files =
|
|
5680
|
+
if (!fs8__namespace.existsSync(dir)) return fileList;
|
|
5681
|
+
const files = fs8__namespace.readdirSync(dir).sort(canonicalSort);
|
|
5277
5682
|
for (const file of files) {
|
|
5278
|
-
const fullPath =
|
|
5683
|
+
const fullPath = path5__namespace.join(dir, file);
|
|
5279
5684
|
try {
|
|
5280
|
-
if (
|
|
5685
|
+
if (fs8__namespace.statSync(fullPath).isDirectory()) {
|
|
5281
5686
|
if (!excludeDirs.includes(file)) {
|
|
5282
5687
|
walkTaskFiles(fullPath, fileList, excludeDirs);
|
|
5283
5688
|
}
|
|
@@ -5329,7 +5734,7 @@ function generateGovernanceReportMarkdown(summaries, violationsCount, modeCounts
|
|
|
5329
5734
|
const sections = [
|
|
5330
5735
|
`# \u{1F4CB} Task Governance and Human Autonomy Classification Report
|
|
5331
5736
|
|
|
5332
|
-
> **Audit Date:** ${(
|
|
5737
|
+
> **Audit Date:** ${resolveDeterministicDate().toISOString()}
|
|
5333
5738
|
> **Overall Verdict:** ${verdict}
|
|
5334
5739
|
|
|
5335
5740
|
---`,
|
|
@@ -5390,9 +5795,9 @@ function updateGovernanceCounts(t, modeCounts, riskCounts) {
|
|
|
5390
5795
|
}
|
|
5391
5796
|
}
|
|
5392
5797
|
function parseTasksFromFile(file) {
|
|
5393
|
-
if (!
|
|
5798
|
+
if (!fs8__namespace.existsSync(file)) return [];
|
|
5394
5799
|
try {
|
|
5395
|
-
const content =
|
|
5800
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
5396
5801
|
const { frontmatter } = parseTasksDoc(content);
|
|
5397
5802
|
return frontmatter.tasks || [];
|
|
5398
5803
|
} catch {
|
|
@@ -5400,11 +5805,11 @@ function parseTasksFromFile(file) {
|
|
|
5400
5805
|
}
|
|
5401
5806
|
}
|
|
5402
5807
|
function extractDesignExpectedScope(changeDir) {
|
|
5403
|
-
const designFile =
|
|
5404
|
-
if (!
|
|
5808
|
+
const designFile = path5__namespace.join(changeDir, "design.md");
|
|
5809
|
+
if (!fs8__namespace.existsSync(designFile)) return [];
|
|
5405
5810
|
const expected = [];
|
|
5406
5811
|
try {
|
|
5407
|
-
const { frontmatter } = parseTasksDoc(
|
|
5812
|
+
const { frontmatter } = parseTasksDoc(fs8__namespace.readFileSync(designFile, "utf-8"));
|
|
5408
5813
|
if (typeof frontmatter["architecture-component"] === "string") {
|
|
5409
5814
|
expected.push(frontmatter["architecture-component"]);
|
|
5410
5815
|
}
|
|
@@ -5444,7 +5849,7 @@ function hasExplicitScopeBindings(tasks) {
|
|
|
5444
5849
|
);
|
|
5445
5850
|
}
|
|
5446
5851
|
function auditDesignScopeForFile(file, tasks, options, unassignedList, violationsList) {
|
|
5447
|
-
const changeDir =
|
|
5852
|
+
const changeDir = path5__namespace.dirname(file);
|
|
5448
5853
|
const expectedItems = [
|
|
5449
5854
|
...extractDesignExpectedScope(changeDir),
|
|
5450
5855
|
...extractHandoffExpectedScope(changeDir, options.maxUseCasesPerChange, violationsList)
|
|
@@ -5455,13 +5860,13 @@ function auditDesignScopeForFile(file, tasks, options, unassignedList, violation
|
|
|
5455
5860
|
for (const item of new Set(expectedItems)) {
|
|
5456
5861
|
if (!covered.has(item)) {
|
|
5457
5862
|
unassignedList.push(item);
|
|
5458
|
-
violationsList.push(`[${
|
|
5863
|
+
violationsList.push(`[${path5__namespace.basename(changeDir)}] Unassigned design/handoff scope in tasks.md: missing task coverage for ${item}`);
|
|
5459
5864
|
}
|
|
5460
5865
|
}
|
|
5461
5866
|
}
|
|
5462
5867
|
function processTasksFile(file, rootDir, options, acc) {
|
|
5463
5868
|
const tasks = parseTasksFromFile(file);
|
|
5464
|
-
const relPath =
|
|
5869
|
+
const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
5465
5870
|
let unverifiedDelta = 0;
|
|
5466
5871
|
for (const t of tasks) {
|
|
5467
5872
|
const { summary, violation, unverified } = evaluateTaskItem(t, relPath);
|
|
@@ -5532,6 +5937,277 @@ function verifyTasksGovernance(options = {}) {
|
|
|
5532
5937
|
);
|
|
5533
5938
|
return assembleGovernanceResult(data, reportMarkdown);
|
|
5534
5939
|
}
|
|
5940
|
+
var BddStepRegistry = class {
|
|
5941
|
+
definitions = [];
|
|
5942
|
+
register(pattern, handler) {
|
|
5943
|
+
this.definitions.push({ pattern, handler });
|
|
5944
|
+
return this;
|
|
5945
|
+
}
|
|
5946
|
+
given(pattern, handler) {
|
|
5947
|
+
return this.register(pattern, handler);
|
|
5948
|
+
}
|
|
5949
|
+
when(pattern, handler) {
|
|
5950
|
+
return this.register(pattern, handler);
|
|
5951
|
+
}
|
|
5952
|
+
then(pattern, handler) {
|
|
5953
|
+
return this.register(pattern, handler);
|
|
5954
|
+
}
|
|
5955
|
+
getDefinitions() {
|
|
5956
|
+
return this.definitions;
|
|
5957
|
+
}
|
|
5958
|
+
};
|
|
5959
|
+
var STEP_PREFIX_RE = /^(Given|When|Then|And|But|Dado|Cuando|Entonces|Y|Pero)\s+(.+)$/i;
|
|
5960
|
+
var SCENARIO_HEADER_RE = /^(Scenario|Escenario|Scenario Outline|Esquema del escenario):\s*(.+)$/i;
|
|
5961
|
+
function parseTableRowCells(line) {
|
|
5962
|
+
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
|
|
5963
|
+
}
|
|
5964
|
+
function attachStepTableRow(lastStep, cells) {
|
|
5965
|
+
if (!lastStep || cells.length < 2) return;
|
|
5966
|
+
if (!lastStep.table) lastStep.table = {};
|
|
5967
|
+
lastStep.table[cells[0]] = cells[1];
|
|
5968
|
+
}
|
|
5969
|
+
function attachExampleRow(scenario, headers, cells) {
|
|
5970
|
+
if (!scenario) return headers;
|
|
5971
|
+
if (headers.length === 0) return cells;
|
|
5972
|
+
const row = {};
|
|
5973
|
+
for (let i = 0; i < headers.length; i++) {
|
|
5974
|
+
row[headers[i]] = cells[i] ?? "";
|
|
5975
|
+
}
|
|
5976
|
+
scenario.examples.push(row);
|
|
5977
|
+
return headers;
|
|
5978
|
+
}
|
|
5979
|
+
function createInitialParseState() {
|
|
5980
|
+
return {
|
|
5981
|
+
featureName: "Unnamed Feature",
|
|
5982
|
+
featureTags: [],
|
|
5983
|
+
pendingTags: [],
|
|
5984
|
+
backgroundSteps: [],
|
|
5985
|
+
scenarios: [],
|
|
5986
|
+
currentScenario: null,
|
|
5987
|
+
inBackground: false,
|
|
5988
|
+
inExamples: false,
|
|
5989
|
+
exampleHeaders: []
|
|
5990
|
+
};
|
|
5991
|
+
}
|
|
5992
|
+
function handleStructuralHeaderLine(line, state) {
|
|
5993
|
+
if (/^(Feature|Caracter[ií]stica):/i.test(line)) {
|
|
5994
|
+
state.featureName = line.replace(/^(Feature|Caracter[ií]stica):\s*/i, "").trim();
|
|
5995
|
+
state.featureTags.push(...state.pendingTags);
|
|
5996
|
+
state.pendingTags = [];
|
|
5997
|
+
return true;
|
|
5998
|
+
}
|
|
5999
|
+
if (/^(Background|Antecedentes):/i.test(line)) {
|
|
6000
|
+
state.inBackground = true;
|
|
6001
|
+
state.inExamples = false;
|
|
6002
|
+
state.currentScenario = null;
|
|
6003
|
+
return true;
|
|
6004
|
+
}
|
|
6005
|
+
if (/^(Examples|Ejemplos):/i.test(line)) {
|
|
6006
|
+
state.inExamples = true;
|
|
6007
|
+
state.exampleHeaders = [];
|
|
6008
|
+
return true;
|
|
6009
|
+
}
|
|
6010
|
+
return false;
|
|
6011
|
+
}
|
|
6012
|
+
function startScenarioBlock(scenMatch, state) {
|
|
6013
|
+
state.inBackground = false;
|
|
6014
|
+
state.inExamples = false;
|
|
6015
|
+
const block = {
|
|
6016
|
+
name: scenMatch[2].trim(),
|
|
6017
|
+
tags: [...state.featureTags, ...state.pendingTags],
|
|
6018
|
+
isOutline: /outline|esquema/i.test(scenMatch[1]),
|
|
6019
|
+
steps: [],
|
|
6020
|
+
examples: []
|
|
6021
|
+
};
|
|
6022
|
+
state.pendingTags = [];
|
|
6023
|
+
state.scenarios.push(block);
|
|
6024
|
+
state.currentScenario = block;
|
|
6025
|
+
}
|
|
6026
|
+
function appendParsedStep(stepMatch, state) {
|
|
6027
|
+
const step = { keyword: stepMatch[1], text: stepMatch[2].trim() };
|
|
6028
|
+
if (state.inBackground) {
|
|
6029
|
+
state.backgroundSteps.push(step);
|
|
6030
|
+
} else if (state.currentScenario) {
|
|
6031
|
+
state.currentScenario.steps.push(step);
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
function handleScenarioOrStepLine(line, state) {
|
|
6035
|
+
const scenMatch = line.match(SCENARIO_HEADER_RE);
|
|
6036
|
+
if (scenMatch) {
|
|
6037
|
+
startScenarioBlock(scenMatch, state);
|
|
6038
|
+
return;
|
|
6039
|
+
}
|
|
6040
|
+
const stepMatch = line.match(STEP_PREFIX_RE);
|
|
6041
|
+
if (stepMatch) {
|
|
6042
|
+
appendParsedStep(stepMatch, state);
|
|
6043
|
+
}
|
|
6044
|
+
}
|
|
6045
|
+
function processFeatureLine(rawLine, state) {
|
|
6046
|
+
const line = rawLine.trim();
|
|
6047
|
+
if (!line || line.startsWith("#")) return;
|
|
6048
|
+
if (line.startsWith("@")) {
|
|
6049
|
+
const tags = line.split(/\s+/).filter((t) => t.startsWith("@"));
|
|
6050
|
+
state.pendingTags.push(...tags);
|
|
6051
|
+
return;
|
|
6052
|
+
}
|
|
6053
|
+
if (line.startsWith("|")) {
|
|
6054
|
+
const cells = parseTableRowCells(line);
|
|
6055
|
+
if (state.inExamples) {
|
|
6056
|
+
state.exampleHeaders = attachExampleRow(state.currentScenario, state.exampleHeaders, cells);
|
|
6057
|
+
} else {
|
|
6058
|
+
const targetList = state.inBackground ? state.backgroundSteps : state.currentScenario?.steps || [];
|
|
6059
|
+
attachStepTableRow(targetList[targetList.length - 1], cells);
|
|
6060
|
+
}
|
|
6061
|
+
return;
|
|
6062
|
+
}
|
|
6063
|
+
if (!handleStructuralHeaderLine(line, state)) {
|
|
6064
|
+
handleScenarioOrStepLine(line, state);
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
function parseFeatureDocument(content) {
|
|
6068
|
+
const state = createInitialParseState();
|
|
6069
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
6070
|
+
processFeatureLine(rawLine, state);
|
|
6071
|
+
}
|
|
6072
|
+
return {
|
|
6073
|
+
featureName: state.featureName,
|
|
6074
|
+
tags: state.featureTags,
|
|
6075
|
+
backgroundSteps: state.backgroundSteps,
|
|
6076
|
+
scenarios: state.scenarios
|
|
6077
|
+
};
|
|
6078
|
+
}
|
|
6079
|
+
function interpolatePlaceholders(text, row) {
|
|
6080
|
+
let out = text;
|
|
6081
|
+
for (const [k, v] of Object.entries(row)) {
|
|
6082
|
+
out = out.replace(new RegExp(`<${k}>`, "g"), v);
|
|
6083
|
+
}
|
|
6084
|
+
return out;
|
|
6085
|
+
}
|
|
6086
|
+
function expandScenarioOutlines(scenarios) {
|
|
6087
|
+
const expanded = [];
|
|
6088
|
+
for (const sc of scenarios) {
|
|
6089
|
+
if (!sc.isOutline || sc.examples.length === 0) {
|
|
6090
|
+
expanded.push(sc);
|
|
6091
|
+
continue;
|
|
6092
|
+
}
|
|
6093
|
+
sc.examples.forEach((row, idx) => {
|
|
6094
|
+
expanded.push({
|
|
6095
|
+
name: `${sc.name} (Example #${idx + 1})`,
|
|
6096
|
+
tags: sc.tags,
|
|
6097
|
+
isOutline: false,
|
|
6098
|
+
steps: sc.steps.map((st) => ({
|
|
6099
|
+
keyword: st.keyword,
|
|
6100
|
+
text: interpolatePlaceholders(st.text, row),
|
|
6101
|
+
table: st.table
|
|
6102
|
+
})),
|
|
6103
|
+
examples: []
|
|
6104
|
+
});
|
|
6105
|
+
});
|
|
6106
|
+
}
|
|
6107
|
+
return expanded;
|
|
6108
|
+
}
|
|
6109
|
+
function matchStepDefinition(text, definitions) {
|
|
6110
|
+
for (const entry of definitions) {
|
|
6111
|
+
if (typeof entry.pattern === "string") {
|
|
6112
|
+
if (entry.pattern === text) return { entry, args: [] };
|
|
6113
|
+
} else {
|
|
6114
|
+
const m = text.match(entry.pattern);
|
|
6115
|
+
if (m) return { entry, args: m.slice(1) };
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
6118
|
+
return null;
|
|
6119
|
+
}
|
|
6120
|
+
async function executeSingleStep(step, definitions) {
|
|
6121
|
+
const matched = matchStepDefinition(step.text, definitions);
|
|
6122
|
+
if (!matched) {
|
|
6123
|
+
return { keyword: step.keyword, text: step.text, status: "undefined", durationMs: 0 };
|
|
6124
|
+
}
|
|
6125
|
+
const start = Date.now();
|
|
6126
|
+
try {
|
|
6127
|
+
await matched.entry.handler(matched.args, step.table);
|
|
6128
|
+
return { keyword: step.keyword, text: step.text, status: "passed", durationMs: Date.now() - start };
|
|
6129
|
+
} catch (err) {
|
|
6130
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6131
|
+
return {
|
|
6132
|
+
keyword: step.keyword,
|
|
6133
|
+
text: step.text,
|
|
6134
|
+
status: "failed",
|
|
6135
|
+
durationMs: Date.now() - start,
|
|
6136
|
+
errorMessage: msg
|
|
6137
|
+
};
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
async function executeScenarioSteps(allSteps, definitions) {
|
|
6141
|
+
const stepResults = [];
|
|
6142
|
+
let status = "passed";
|
|
6143
|
+
for (const step of allSteps) {
|
|
6144
|
+
if (status !== "passed") {
|
|
6145
|
+
stepResults.push({ keyword: step.keyword, text: step.text, status: "skipped", durationMs: 0 });
|
|
6146
|
+
continue;
|
|
6147
|
+
}
|
|
6148
|
+
const res = await executeSingleStep(step, definitions);
|
|
6149
|
+
stepResults.push(res);
|
|
6150
|
+
if (res.status === "failed" || res.status === "undefined") {
|
|
6151
|
+
status = res.status;
|
|
6152
|
+
}
|
|
6153
|
+
}
|
|
6154
|
+
return { stepResults, status };
|
|
6155
|
+
}
|
|
6156
|
+
async function runAllExpandedScenarios(parsed, definitions) {
|
|
6157
|
+
const expanded = expandScenarioOutlines(parsed.scenarios);
|
|
6158
|
+
const out = [];
|
|
6159
|
+
for (const sc of expanded) {
|
|
6160
|
+
const combinedSteps = [...parsed.backgroundSteps, ...sc.steps];
|
|
6161
|
+
const { stepResults, status } = await executeScenarioSteps(combinedSteps, definitions);
|
|
6162
|
+
out.push({ name: sc.name, tags: sc.tags, status, steps: stepResults });
|
|
6163
|
+
}
|
|
6164
|
+
return out;
|
|
6165
|
+
}
|
|
6166
|
+
async function executeBddFeature(featurePath, registry, options = {}) {
|
|
6167
|
+
const rootDir = options.rootDir || process.cwd();
|
|
6168
|
+
const fullPath = path5__namespace.isAbsolute(featurePath) ? featurePath : path5__namespace.join(rootDir, featurePath);
|
|
6169
|
+
const parsed = parseFeatureDocument(fs8__namespace.readFileSync(fullPath, "utf-8"));
|
|
6170
|
+
const scenarios = await runAllExpandedScenarios(parsed, registry.getDefinitions());
|
|
6171
|
+
const report = {
|
|
6172
|
+
uri: path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/"),
|
|
6173
|
+
featureName: parsed.featureName,
|
|
6174
|
+
tags: parsed.tags,
|
|
6175
|
+
scenarios,
|
|
6176
|
+
passed: scenarios.length > 0 && scenarios.every((s) => s.status === "passed")
|
|
6177
|
+
};
|
|
6178
|
+
if (options.writeReportPath) writeCucumberJsonReport([report], options.writeReportPath);
|
|
6179
|
+
return report;
|
|
6180
|
+
}
|
|
6181
|
+
function mapStepToCucumberJson(st) {
|
|
6182
|
+
return {
|
|
6183
|
+
keyword: `${st.keyword} `,
|
|
6184
|
+
name: st.text,
|
|
6185
|
+
result: {
|
|
6186
|
+
status: st.status,
|
|
6187
|
+
duration: st.durationMs * 1e6,
|
|
6188
|
+
...st.errorMessage ? { error_message: st.errorMessage } : {}
|
|
6189
|
+
}
|
|
6190
|
+
};
|
|
6191
|
+
}
|
|
6192
|
+
function mapFeatureToCucumberJson(r) {
|
|
6193
|
+
return {
|
|
6194
|
+
uri: r.uri,
|
|
6195
|
+
name: r.featureName,
|
|
6196
|
+
tags: r.tags.map((t) => ({ name: t })),
|
|
6197
|
+
elements: r.scenarios.map((sc) => ({
|
|
6198
|
+
type: "scenario",
|
|
6199
|
+
name: sc.name,
|
|
6200
|
+
tags: sc.tags.map((t) => ({ name: t })),
|
|
6201
|
+
steps: sc.steps.map(mapStepToCucumberJson)
|
|
6202
|
+
}))
|
|
6203
|
+
};
|
|
6204
|
+
}
|
|
6205
|
+
function writeCucumberJsonReport(reports, outputPath) {
|
|
6206
|
+
const dir = path5__namespace.dirname(outputPath);
|
|
6207
|
+
if (!fs8__namespace.existsSync(dir)) fs8__namespace.mkdirSync(dir, { recursive: true });
|
|
6208
|
+
const payload = JSON.stringify(reports.map(mapFeatureToCucumberJson), null, 2);
|
|
6209
|
+
fs8__namespace.writeFileSync(outputPath, payload, "utf-8");
|
|
6210
|
+
}
|
|
5535
6211
|
function isToolAvailable(toolName) {
|
|
5536
6212
|
try {
|
|
5537
6213
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
@@ -5576,8 +6252,8 @@ function findLicenseFile(pkgDir) {
|
|
|
5576
6252
|
"license.txt"
|
|
5577
6253
|
];
|
|
5578
6254
|
for (const c of candidates) {
|
|
5579
|
-
const full =
|
|
5580
|
-
if (
|
|
6255
|
+
const full = path5__namespace.join(pkgDir, c);
|
|
6256
|
+
if (fs8__namespace.existsSync(full)) {
|
|
5581
6257
|
return full;
|
|
5582
6258
|
}
|
|
5583
6259
|
}
|
|
@@ -5594,9 +6270,9 @@ var LICENSE_FILE_PATTERNS = [
|
|
|
5594
6270
|
[(t) => t.includes("Microsoft Reciprocal License") || t.includes("MS-RL"), "MS-RL"]
|
|
5595
6271
|
];
|
|
5596
6272
|
function resolveLicenseFromFile(licenseFilePath) {
|
|
5597
|
-
if (!licenseFilePath || !
|
|
6273
|
+
if (!licenseFilePath || !fs8__namespace.existsSync(licenseFilePath)) return void 0;
|
|
5598
6274
|
try {
|
|
5599
|
-
const text =
|
|
6275
|
+
const text = fs8__namespace.readFileSync(licenseFilePath, "utf-8");
|
|
5600
6276
|
for (const [matches, spdx] of LICENSE_FILE_PATTERNS) {
|
|
5601
6277
|
if (matches(text)) return spdx;
|
|
5602
6278
|
}
|
|
@@ -5605,16 +6281,16 @@ function resolveLicenseFromFile(licenseFilePath) {
|
|
|
5605
6281
|
return void 0;
|
|
5606
6282
|
}
|
|
5607
6283
|
function readPackageJson(filePath) {
|
|
5608
|
-
if (!
|
|
6284
|
+
if (!fs8__namespace.existsSync(filePath)) return null;
|
|
5609
6285
|
try {
|
|
5610
|
-
return JSON.parse(
|
|
6286
|
+
return JSON.parse(fs8__namespace.readFileSync(filePath, "utf-8"));
|
|
5611
6287
|
} catch {
|
|
5612
6288
|
return null;
|
|
5613
6289
|
}
|
|
5614
6290
|
}
|
|
5615
6291
|
function extractDirectDepNames(rootDir, productionOnly) {
|
|
5616
6292
|
const names = /* @__PURE__ */ new Set();
|
|
5617
|
-
const pkgPath =
|
|
6293
|
+
const pkgPath = path5__namespace.join(rootDir, "package.json");
|
|
5618
6294
|
const pkg = readPackageJson(pkgPath);
|
|
5619
6295
|
if (!pkg) return names;
|
|
5620
6296
|
if (pkg.dependencies && typeof pkg.dependencies === "object") {
|
|
@@ -5634,7 +6310,7 @@ function resolveSpdxForPackage(pkg, licFile) {
|
|
|
5634
6310
|
return spdx;
|
|
5635
6311
|
}
|
|
5636
6312
|
function processPackageDir(pkgDir, directNames, seen) {
|
|
5637
|
-
const pkg = readPackageJson(
|
|
6313
|
+
const pkg = readPackageJson(path5__namespace.join(pkgDir, "package.json"));
|
|
5638
6314
|
if (!pkg?.name || !pkg.version) return;
|
|
5639
6315
|
const key = `${pkg.name}@${pkg.version}`;
|
|
5640
6316
|
if (seen.has(key)) return;
|
|
@@ -5655,40 +6331,40 @@ function processPackageDir(pkgDir, directNames, seen) {
|
|
|
5655
6331
|
});
|
|
5656
6332
|
}
|
|
5657
6333
|
function scanScopeSubdirectory(scopeDir, directNames, seen) {
|
|
5658
|
-
const scopedEntries =
|
|
6334
|
+
const scopedEntries = fs8__namespace.readdirSync(scopeDir, { withFileTypes: true });
|
|
5659
6335
|
for (const scoped of scopedEntries) {
|
|
5660
6336
|
if (scoped.isDirectory()) {
|
|
5661
|
-
processPackageDir(
|
|
6337
|
+
processPackageDir(path5__namespace.join(scopeDir, scoped.name), directNames, seen);
|
|
5662
6338
|
}
|
|
5663
6339
|
}
|
|
5664
6340
|
}
|
|
5665
6341
|
function scanSubdirectories(baseDir, directNames, seen) {
|
|
5666
|
-
if (!
|
|
5667
|
-
const entries =
|
|
6342
|
+
if (!fs8__namespace.existsSync(baseDir)) return;
|
|
6343
|
+
const entries = fs8__namespace.readdirSync(baseDir, { withFileTypes: true });
|
|
5668
6344
|
for (const entry of entries) {
|
|
5669
6345
|
if (!entry.isDirectory()) continue;
|
|
5670
6346
|
if (entry.name.startsWith("@")) {
|
|
5671
|
-
scanScopeSubdirectory(
|
|
6347
|
+
scanScopeSubdirectory(path5__namespace.join(baseDir, entry.name), directNames, seen);
|
|
5672
6348
|
} else if (entry.name !== ".bin" && entry.name !== ".pnpm") {
|
|
5673
|
-
processPackageDir(
|
|
6349
|
+
processPackageDir(path5__namespace.join(baseDir, entry.name), directNames, seen);
|
|
5674
6350
|
}
|
|
5675
6351
|
}
|
|
5676
6352
|
}
|
|
5677
6353
|
function scanPnpmStore(pnpmDir, directNames, seen) {
|
|
5678
|
-
if (!
|
|
5679
|
-
const entries =
|
|
6354
|
+
if (!fs8__namespace.existsSync(pnpmDir)) return;
|
|
6355
|
+
const entries = fs8__namespace.readdirSync(pnpmDir, { withFileTypes: true });
|
|
5680
6356
|
for (const entry of entries) {
|
|
5681
6357
|
if (!entry.isDirectory()) continue;
|
|
5682
|
-
const candidateModules =
|
|
5683
|
-
if (
|
|
6358
|
+
const candidateModules = path5__namespace.join(pnpmDir, entry.name, "node_modules");
|
|
6359
|
+
if (fs8__namespace.existsSync(candidateModules)) {
|
|
5684
6360
|
scanSubdirectories(candidateModules, directNames, seen);
|
|
5685
6361
|
}
|
|
5686
6362
|
}
|
|
5687
6363
|
}
|
|
5688
6364
|
function readTextIfExists(filePath) {
|
|
5689
|
-
if (!
|
|
6365
|
+
if (!fs8__namespace.existsSync(filePath)) return null;
|
|
5690
6366
|
try {
|
|
5691
|
-
return
|
|
6367
|
+
return fs8__namespace.readFileSync(filePath, "utf-8");
|
|
5692
6368
|
} catch {
|
|
5693
6369
|
return null;
|
|
5694
6370
|
}
|
|
@@ -5748,14 +6424,14 @@ function parsePipfileSection(pkgs, isDirect, lockPath, seen) {
|
|
|
5748
6424
|
}
|
|
5749
6425
|
}
|
|
5750
6426
|
function scanPythonDependencies(rootDir, seen) {
|
|
5751
|
-
const pyproject = readTextIfExists(
|
|
6427
|
+
const pyproject = readTextIfExists(path5__namespace.join(rootDir, "pyproject.toml")) || "";
|
|
5752
6428
|
const directNames = extractPyprojectDirectDeps(pyproject);
|
|
5753
6429
|
for (const lockName of ["poetry.lock", "uv.lock"]) {
|
|
5754
|
-
const lockPath =
|
|
6430
|
+
const lockPath = path5__namespace.join(rootDir, lockName);
|
|
5755
6431
|
const content = readTextIfExists(lockPath);
|
|
5756
6432
|
if (content) parseTomlPackageBlocks(content, lockPath, directNames, seen);
|
|
5757
6433
|
}
|
|
5758
|
-
const pipfileLockPath =
|
|
6434
|
+
const pipfileLockPath = path5__namespace.join(rootDir, "Pipfile.lock");
|
|
5759
6435
|
const pipfileRaw = readTextIfExists(pipfileLockPath);
|
|
5760
6436
|
if (!pipfileRaw) return;
|
|
5761
6437
|
try {
|
|
@@ -5798,7 +6474,7 @@ function extractGoCandidateLines(content) {
|
|
|
5798
6474
|
return out;
|
|
5799
6475
|
}
|
|
5800
6476
|
function scanGoDependencies(rootDir, seen) {
|
|
5801
|
-
const goModPath =
|
|
6477
|
+
const goModPath = path5__namespace.join(rootDir, "go.mod");
|
|
5802
6478
|
const content = readTextIfExists(goModPath);
|
|
5803
6479
|
if (!content) return;
|
|
5804
6480
|
for (const line of extractGoCandidateLines(content)) {
|
|
@@ -5841,10 +6517,10 @@ function parseCargoLockBlock(block, rootName, directMeta, cargoLockPath) {
|
|
|
5841
6517
|
};
|
|
5842
6518
|
}
|
|
5843
6519
|
function scanRustDependencies(rootDir, seen) {
|
|
5844
|
-
const cargoLockPath =
|
|
6520
|
+
const cargoLockPath = path5__namespace.join(rootDir, "Cargo.lock");
|
|
5845
6521
|
const lockContent = readTextIfExists(cargoLockPath);
|
|
5846
6522
|
if (!lockContent) return;
|
|
5847
|
-
const cargoToml = readTextIfExists(
|
|
6523
|
+
const cargoToml = readTextIfExists(path5__namespace.join(rootDir, "Cargo.toml")) || "";
|
|
5848
6524
|
const directMeta = extractCargoDirectDeps(cargoToml);
|
|
5849
6525
|
const rootName = extractTomlField(cargoToml, "name");
|
|
5850
6526
|
for (const block of lockContent.split(/\[\[package\]\]/g).slice(1)) {
|
|
@@ -5880,7 +6556,7 @@ function parseMavenDepBlock(block, defaultLic, pomPath) {
|
|
|
5880
6556
|
};
|
|
5881
6557
|
}
|
|
5882
6558
|
function scanMavenDependencies(rootDir, seen) {
|
|
5883
|
-
const pomPath =
|
|
6559
|
+
const pomPath = path5__namespace.join(rootDir, "pom.xml");
|
|
5884
6560
|
const content = readTextIfExists(pomPath);
|
|
5885
6561
|
if (!content) return;
|
|
5886
6562
|
const projectLicBlock = content.match(/<licenses>([\s\S]*?)<\/licenses>/)?.[1] || "";
|
|
@@ -5896,11 +6572,11 @@ function scanMavenDependencies(rootDir, seen) {
|
|
|
5896
6572
|
function scanWithNativeFs(rootDir, options = {}) {
|
|
5897
6573
|
const directNames = extractDirectDepNames(rootDir, options.productionOnly);
|
|
5898
6574
|
const seen = /* @__PURE__ */ new Map();
|
|
5899
|
-
const nmDir =
|
|
5900
|
-
if (
|
|
6575
|
+
const nmDir = path5__namespace.join(rootDir, "node_modules");
|
|
6576
|
+
if (fs8__namespace.existsSync(nmDir)) {
|
|
5901
6577
|
scanSubdirectories(nmDir, directNames, seen);
|
|
5902
6578
|
if (options.depth !== "direct") {
|
|
5903
|
-
scanPnpmStore(
|
|
6579
|
+
scanPnpmStore(path5__namespace.join(nmDir, ".pnpm"), directNames, seen);
|
|
5904
6580
|
}
|
|
5905
6581
|
}
|
|
5906
6582
|
scanPythonDependencies(rootDir, seen);
|
|
@@ -6159,20 +6835,20 @@ function resolveProjectIdentity(options) {
|
|
|
6159
6835
|
let name = options.projectName || "ai-sdlc-project";
|
|
6160
6836
|
let version = options.projectVersion || "1.0.0";
|
|
6161
6837
|
if (!options.rootDir) return { name, version };
|
|
6162
|
-
const pkgPath =
|
|
6163
|
-
if (!
|
|
6838
|
+
const pkgPath = path5__namespace.join(options.rootDir, "package.json");
|
|
6839
|
+
if (!fs8__namespace.existsSync(pkgPath)) return { name, version };
|
|
6164
6840
|
try {
|
|
6165
|
-
const pkg = JSON.parse(
|
|
6841
|
+
const pkg = JSON.parse(fs8__namespace.readFileSync(pkgPath, "utf-8"));
|
|
6166
6842
|
if (pkg.name) name = pkg.name;
|
|
6167
6843
|
if (pkg.version) version = pkg.version;
|
|
6168
6844
|
} catch {
|
|
6169
6845
|
}
|
|
6170
6846
|
return { name, version };
|
|
6171
6847
|
}
|
|
6172
|
-
function buildBomMetadata(name, version) {
|
|
6848
|
+
function buildBomMetadata(name, version, rootDir) {
|
|
6173
6849
|
return {
|
|
6174
|
-
timestamp: (
|
|
6175
|
-
tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.
|
|
6850
|
+
timestamp: resolveDeterministicDate(rootDir).toISOString(),
|
|
6851
|
+
tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.5.0" }],
|
|
6176
6852
|
component: { type: "application", name, version }
|
|
6177
6853
|
};
|
|
6178
6854
|
}
|
|
@@ -6183,52 +6859,21 @@ function generateCycloneDxSbom(dependencies, options = {}) {
|
|
|
6183
6859
|
bomFormat: "CycloneDX",
|
|
6184
6860
|
specVersion: "1.5",
|
|
6185
6861
|
version: 1,
|
|
6186
|
-
serialNumber: `urn:uuid:${
|
|
6187
|
-
metadata: buildBomMetadata(name, version),
|
|
6862
|
+
serialNumber: `urn:uuid:${generateDeterministicUuidV5(`${name}@${version}|${dependencies.map((d) => `${d.name}@${d.version}:${d.purl || ""}`).sort(canonicalSort).join(";")}`)}`,
|
|
6863
|
+
metadata: buildBomMetadata(name, version, options.rootDir),
|
|
6188
6864
|
components: dependencies.map(buildCycloneDxComponent),
|
|
6189
6865
|
...cdxVulns.length > 0 ? { vulnerabilities: cdxVulns } : {}
|
|
6190
6866
|
};
|
|
6191
6867
|
}
|
|
6192
|
-
function tryAtomicWrite(filePath, content) {
|
|
6193
|
-
const tempPath = `${filePath}.${process.pid}.${Math.random().toString(36).substring(2)}.tmp`;
|
|
6194
|
-
try {
|
|
6195
|
-
fs7__namespace.writeFileSync(tempPath, content, "utf-8");
|
|
6196
|
-
try {
|
|
6197
|
-
if (fs7__namespace.existsSync(filePath)) {
|
|
6198
|
-
fs7__namespace.rmSync(filePath, { force: true });
|
|
6199
|
-
}
|
|
6200
|
-
fs7__namespace.renameSync(tempPath, filePath);
|
|
6201
|
-
} catch {
|
|
6202
|
-
fs7__namespace.copyFileSync(tempPath, filePath);
|
|
6203
|
-
fs7__namespace.unlinkSync(tempPath);
|
|
6204
|
-
}
|
|
6205
|
-
return true;
|
|
6206
|
-
} catch {
|
|
6207
|
-
return false;
|
|
6208
|
-
}
|
|
6209
|
-
}
|
|
6210
|
-
function safeWriteFileSync(filePath, content) {
|
|
6211
|
-
const dir = path4__namespace.dirname(filePath);
|
|
6212
|
-
if (!fs7__namespace.existsSync(dir)) {
|
|
6213
|
-
fs7__namespace.mkdirSync(dir, { recursive: true });
|
|
6214
|
-
}
|
|
6215
|
-
for (let attempt = 0; attempt < 10; attempt++) {
|
|
6216
|
-
if (tryAtomicWrite(filePath, content)) return;
|
|
6217
|
-
}
|
|
6218
|
-
try {
|
|
6219
|
-
fs7__namespace.writeFileSync(filePath, content, "utf-8");
|
|
6220
|
-
} catch {
|
|
6221
|
-
}
|
|
6222
|
-
}
|
|
6223
6868
|
function writeCycloneDxSbom(sbom, outputPath) {
|
|
6224
|
-
|
|
6869
|
+
writeReportFileIfChanged(outputPath, JSON.stringify(sbom, null, 2));
|
|
6225
6870
|
}
|
|
6226
6871
|
function buildThirdPartyHeader(projectName, count) {
|
|
6227
6872
|
return [
|
|
6228
6873
|
`# \u{1F4DC} Third-Party Software Notices and Information`,
|
|
6229
6874
|
``,
|
|
6230
6875
|
`> **Proyecto:** ${projectName}`,
|
|
6231
|
-
`> **Fecha:** ${(
|
|
6876
|
+
`> **Fecha:** ${resolveDeterministicDate().toISOString().split("T")[0]}`,
|
|
6232
6877
|
`> **Total Componentes:** ${count}`,
|
|
6233
6878
|
``,
|
|
6234
6879
|
`Este documento contiene los avisos de copyright y t\xE9rminos de licencia aplicables a los componentes de terceros utilizados en este proyecto.`,
|
|
@@ -6242,9 +6887,9 @@ function buildThirdPartyHeader(projectName, count) {
|
|
|
6242
6887
|
];
|
|
6243
6888
|
}
|
|
6244
6889
|
function readLicenseTextPreview(licenseFile) {
|
|
6245
|
-
if (!licenseFile || !
|
|
6890
|
+
if (!licenseFile || !fs8__namespace.existsSync(licenseFile)) return null;
|
|
6246
6891
|
try {
|
|
6247
|
-
const text =
|
|
6892
|
+
const text = fs8__namespace.readFileSync(licenseFile, "utf-8").trim();
|
|
6248
6893
|
return text.slice(0, 1e3) + (text.length > 1e3 ? "\n... (texto truncado)" : "");
|
|
6249
6894
|
} catch {
|
|
6250
6895
|
return null;
|
|
@@ -6276,13 +6921,13 @@ function generateThirdPartyNotices(dependencies, projectName = "AI-SDLC") {
|
|
|
6276
6921
|
"",
|
|
6277
6922
|
"---",
|
|
6278
6923
|
"",
|
|
6279
|
-
"##
|
|
6924
|
+
"## 2. Atribuciones y T\xE9rminos de Licencia Individuales",
|
|
6280
6925
|
"",
|
|
6281
6926
|
...attributions
|
|
6282
6927
|
].join("\n");
|
|
6283
6928
|
}
|
|
6284
|
-
function writeThirdPartyNotices(
|
|
6285
|
-
|
|
6929
|
+
function writeThirdPartyNotices(noticesContent, outputPath) {
|
|
6930
|
+
writeReportFileIfChanged(outputPath, noticesContent);
|
|
6286
6931
|
}
|
|
6287
6932
|
|
|
6288
6933
|
// src/verifiers/licenses.ts
|
|
@@ -6359,22 +7004,20 @@ function parseVexStatements(doc) {
|
|
|
6359
7004
|
if (!Array.isArray(rawList)) return [];
|
|
6360
7005
|
return rawList.filter((item) => Boolean(item && typeof item.id === "string" && typeof item.state === "string"));
|
|
6361
7006
|
}
|
|
6362
|
-
function
|
|
7007
|
+
function parseLicenseDoc(content) {
|
|
7008
|
+
let doc;
|
|
6363
7009
|
try {
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
rawPolicy: doc
|
|
6376
|
-
};
|
|
6377
|
-
} catch {
|
|
7010
|
+
doc = yaml3__default.default.load(content);
|
|
7011
|
+
} catch (err) {
|
|
7012
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in license-policy: ${err instanceof Error ? err.message : String(err)}`);
|
|
7013
|
+
}
|
|
7014
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
7015
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid license policy structure: root must be a YAML mapping`);
|
|
7016
|
+
}
|
|
7017
|
+
return doc;
|
|
7018
|
+
}
|
|
7019
|
+
function parseLicensePolicy(content) {
|
|
7020
|
+
if (!content || !content.trim()) {
|
|
6378
7021
|
return {
|
|
6379
7022
|
version: "1.0",
|
|
6380
7023
|
permitted: DEFAULT_PERMITTED,
|
|
@@ -6384,6 +7027,17 @@ function parseLicensePolicy(content) {
|
|
|
6384
7027
|
vex_statements: []
|
|
6385
7028
|
};
|
|
6386
7029
|
}
|
|
7030
|
+
const doc = parseLicenseDoc(content);
|
|
7031
|
+
const { permitted, restricted, blocked } = classifyCategories(doc?.categories);
|
|
7032
|
+
return {
|
|
7033
|
+
version: doc?.version || "1.0",
|
|
7034
|
+
permitted: permitted.length > 0 ? permitted : DEFAULT_PERMITTED,
|
|
7035
|
+
restricted: restricted.length > 0 ? restricted : DEFAULT_RESTRICTED,
|
|
7036
|
+
blocked: blocked.length > 0 ? blocked : DEFAULT_BLOCKED,
|
|
7037
|
+
exception_packages: parseExceptions(doc?.exceptions),
|
|
7038
|
+
vex_statements: parseVexStatements(doc),
|
|
7039
|
+
rawPolicy: doc
|
|
7040
|
+
};
|
|
6387
7041
|
}
|
|
6388
7042
|
function evaluateCompoundLicense(lic, policy) {
|
|
6389
7043
|
const cleanLic = lic.replace(/[()]/g, "").trim();
|
|
@@ -6475,7 +7129,7 @@ function buildReportHeader(isOk, depCount, violationCount, sbomPath) {
|
|
|
6475
7129
|
return [
|
|
6476
7130
|
`# \u{1F4DC} Open Source License Compliance Audit (Dynamic SCA)`,
|
|
6477
7131
|
``,
|
|
6478
|
-
`> **Verification Date:** ${(
|
|
7132
|
+
`> **Verification Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
6479
7133
|
`> **Status:** ${statusStr}`,
|
|
6480
7134
|
`> **Total Audited Dependencies:** ${depCount}`,
|
|
6481
7135
|
sbomPath ? `> **Generated CycloneDX SBOM:** \`${sbomPath}\`` : "",
|
|
@@ -6500,9 +7154,9 @@ function generateLicenseReportMarkdown(dependencies, violations, policy, sbomPat
|
|
|
6500
7154
|
return lines.filter(Boolean).join("\n");
|
|
6501
7155
|
}
|
|
6502
7156
|
function parseManifestItems(manifestFile) {
|
|
6503
|
-
if (!
|
|
7157
|
+
if (!fs8__namespace.existsSync(manifestFile)) return [];
|
|
6504
7158
|
try {
|
|
6505
|
-
const doc = yaml3__default.default.load(
|
|
7159
|
+
const doc = yaml3__default.default.load(fs8__namespace.readFileSync(manifestFile, "utf-8"));
|
|
6506
7160
|
return Array.isArray(doc?.evaluated_dependencies) ? doc.evaluated_dependencies : [];
|
|
6507
7161
|
} catch {
|
|
6508
7162
|
return [];
|
|
@@ -6534,10 +7188,10 @@ function verifyStaticManifest(manifestFile, policy) {
|
|
|
6534
7188
|
function resolveManifestPath(rootDir, customPath) {
|
|
6535
7189
|
if (customPath) return customPath;
|
|
6536
7190
|
const candidates = [
|
|
6537
|
-
|
|
6538
|
-
|
|
7191
|
+
path5__namespace.join(rootDir, "compliance", "license-manifest.yaml"),
|
|
7192
|
+
path5__namespace.join(rootDir, "license-manifest.yaml")
|
|
6539
7193
|
];
|
|
6540
|
-
return candidates.find((p) =>
|
|
7194
|
+
return candidates.find((p) => fs8__namespace.existsSync(p)) || candidates[0];
|
|
6541
7195
|
}
|
|
6542
7196
|
function handleStaticManifestVerification(options, rootDir, policy) {
|
|
6543
7197
|
const manifestFile = resolveManifestPath(rootDir, options.manifestPath);
|
|
@@ -6558,7 +7212,7 @@ function shouldGenerateSbom(options, rawPolicy) {
|
|
|
6558
7212
|
}
|
|
6559
7213
|
function maybeGenerateSbom(options, rootDir, rawPolicy, dependencies, vulnerabilities, vexStatements) {
|
|
6560
7214
|
if (!shouldGenerateSbom(options, rawPolicy)) return void 0;
|
|
6561
|
-
const sbomPath = options.sbomPath ||
|
|
7215
|
+
const sbomPath = options.sbomPath || path5__namespace.join(rootDir, "reports", "sbom.cdx.json");
|
|
6562
7216
|
const sbom = generateCycloneDxSbom(dependencies, { rootDir, vulnerabilities, vexStatements });
|
|
6563
7217
|
writeCycloneDxSbom(sbom, sbomPath);
|
|
6564
7218
|
return sbomPath;
|
|
@@ -6567,7 +7221,7 @@ function maybeGenerateNotices(options, rootDir, rawPolicy, dependencies) {
|
|
|
6567
7221
|
const shouldGen = options.generateNotices === true || Boolean(options.noticesPath || rawPolicy?.ci_gates?.output_notices_file);
|
|
6568
7222
|
if (!shouldGen) return void 0;
|
|
6569
7223
|
const noticesFileName = rawPolicy?.ci_gates?.output_notices_file || "THIRD_PARTY_NOTICES.md";
|
|
6570
|
-
const noticesPath = options.noticesPath ||
|
|
7224
|
+
const noticesPath = options.noticesPath || path5__namespace.join(rootDir, noticesFileName);
|
|
6571
7225
|
const noticesContent = generateThirdPartyNotices(dependencies);
|
|
6572
7226
|
writeThirdPartyNotices(noticesContent, noticesPath);
|
|
6573
7227
|
return noticesPath;
|
|
@@ -6579,8 +7233,8 @@ function handleDynamicScaArtifacts(options, rootDir, rawPolicy, dependencies, vu
|
|
|
6579
7233
|
};
|
|
6580
7234
|
}
|
|
6581
7235
|
function loadLicensePolicy(rootDir, customPath) {
|
|
6582
|
-
const policyFile = customPath ||
|
|
6583
|
-
return
|
|
7236
|
+
const policyFile = customPath || path5__namespace.join(rootDir, "license-policy.yaml");
|
|
7237
|
+
return fs8__namespace.existsSync(policyFile) ? parseLicensePolicy(fs8__namespace.readFileSync(policyFile, "utf-8")) : parseLicensePolicy("");
|
|
6584
7238
|
}
|
|
6585
7239
|
function evaluateDependencyList(dependencies, policy) {
|
|
6586
7240
|
const violations = [];
|
|
@@ -6684,11 +7338,11 @@ var PROTECTED_BYPASS_PATTERNS = [
|
|
|
6684
7338
|
/migrations?[\\/]/i
|
|
6685
7339
|
];
|
|
6686
7340
|
function detectProfileFromChange(changeDir) {
|
|
6687
|
-
if (!
|
|
6688
|
-
const specFile =
|
|
6689
|
-
if (
|
|
7341
|
+
if (!fs8__namespace.existsSync(changeDir)) return "standard";
|
|
7342
|
+
const specFile = path5__namespace.join(changeDir, "spec.md");
|
|
7343
|
+
if (fs8__namespace.existsSync(specFile)) {
|
|
6690
7344
|
try {
|
|
6691
|
-
const rawSpec =
|
|
7345
|
+
const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
|
|
6692
7346
|
const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
6693
7347
|
if (match) {
|
|
6694
7348
|
const parsedFm = yaml3__default.default.load(match[1]);
|
|
@@ -6706,24 +7360,29 @@ function isPathProtectedFromPatch(filePath) {
|
|
|
6706
7360
|
return PROTECTED_BYPASS_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
6707
7361
|
}
|
|
6708
7362
|
function detectBranchChangeMatch(rootDir, dirs) {
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
7363
|
+
let branch = "";
|
|
7364
|
+
if (fs8__namespace.existsSync(path5__namespace.join(rootDir, ".git"))) {
|
|
7365
|
+
try {
|
|
7366
|
+
branch = child_process.execFileSync("git", ["branch", "--show-current"], {
|
|
7367
|
+
cwd: rootDir,
|
|
7368
|
+
encoding: "utf-8",
|
|
7369
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
7370
|
+
}).trim().toLowerCase();
|
|
7371
|
+
} catch {
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
if (!branch) {
|
|
7375
|
+
const ciBranch = (process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_REF_NAME || process.env.BRANCH_NAME || "").toLowerCase();
|
|
7376
|
+
branch = ciBranch;
|
|
6720
7377
|
}
|
|
7378
|
+
if (!branch) return void 0;
|
|
7379
|
+
return dirs.find((d) => branch.includes(path5__namespace.basename(d).toLowerCase()));
|
|
6721
7380
|
}
|
|
6722
7381
|
function resolveActiveChangeDir(rootDir) {
|
|
6723
|
-
const activeRoot =
|
|
6724
|
-
if (!
|
|
7382
|
+
const activeRoot = path5__namespace.join(rootDir, "specs", "changes", "active");
|
|
7383
|
+
if (!fs8__namespace.existsSync(activeRoot)) return void 0;
|
|
6725
7384
|
try {
|
|
6726
|
-
const dirs =
|
|
7385
|
+
const dirs = fs8__namespace.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path5__namespace.join(activeRoot, d.name)).sort((a, b) => canonicalSort(a, b));
|
|
6727
7386
|
const branchMatch = detectBranchChangeMatch(rootDir, dirs);
|
|
6728
7387
|
if (branchMatch) return branchMatch;
|
|
6729
7388
|
return dirs.find((d) => detectProfileFromChange(d) === "patch") || dirs[0];
|
|
@@ -6731,8 +7390,12 @@ function resolveActiveChangeDir(rootDir) {
|
|
|
6731
7390
|
return void 0;
|
|
6732
7391
|
}
|
|
6733
7392
|
}
|
|
7393
|
+
function isEphemeralOrIgnored(file) {
|
|
7394
|
+
const norm = file.replace(/\\/g, "/");
|
|
7395
|
+
return norm.startsWith("scratch/") || norm.startsWith("reports/") || norm.startsWith("coverage/") || norm.startsWith("dist/") || norm.startsWith("temp/") || norm.startsWith("tmp/");
|
|
7396
|
+
}
|
|
6734
7397
|
function detectGitDiffFiles(rootDir) {
|
|
6735
|
-
if (!
|
|
7398
|
+
if (!fs8__namespace.existsSync(path5__namespace.join(rootDir, ".git"))) return [];
|
|
6736
7399
|
try {
|
|
6737
7400
|
const tracked = child_process.execFileSync("git", ["diff", "--name-only", "HEAD"], {
|
|
6738
7401
|
cwd: rootDir,
|
|
@@ -6745,7 +7408,7 @@ function detectGitDiffFiles(rootDir) {
|
|
|
6745
7408
|
stdio: ["pipe", "pipe", "ignore"]
|
|
6746
7409
|
});
|
|
6747
7410
|
const combined = `${tracked}
|
|
6748
|
-
${untracked}`.split(/\r?\n/).map((f) => f.trim()).filter(Boolean);
|
|
7411
|
+
${untracked}`.split(/\r?\n/).map((f) => f.trim()).filter((f) => Boolean(f) && !isEphemeralOrIgnored(f));
|
|
6749
7412
|
return Array.from(new Set(combined));
|
|
6750
7413
|
} catch {
|
|
6751
7414
|
return [];
|
|
@@ -6755,19 +7418,19 @@ function resolveChangeDirectory(rootDir, options) {
|
|
|
6755
7418
|
if (options.changeDir) return options.changeDir;
|
|
6756
7419
|
if (options.changeId) {
|
|
6757
7420
|
const candidates = [
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
7421
|
+
path5__namespace.join(rootDir, "specs", options.changeId),
|
|
7422
|
+
path5__namespace.join(rootDir, "specs", "changes", "active", options.changeId),
|
|
7423
|
+
path5__namespace.join(rootDir, "examples", "specs", options.changeId),
|
|
7424
|
+
path5__namespace.join(rootDir, "specs", "changes", "completed", options.changeId)
|
|
6762
7425
|
];
|
|
6763
|
-
return candidates.find((c) =>
|
|
7426
|
+
return candidates.find((c) => fs8__namespace.existsSync(c));
|
|
6764
7427
|
}
|
|
6765
7428
|
return resolveActiveChangeDir(rootDir);
|
|
6766
7429
|
}
|
|
6767
7430
|
function collectPatchViolations(rootDir, modifiedFiles) {
|
|
6768
7431
|
const violations = [];
|
|
6769
7432
|
for (const rawFile of modifiedFiles) {
|
|
6770
|
-
const relPath =
|
|
7433
|
+
const relPath = path5__namespace.isAbsolute(rawFile) ? path5__namespace.relative(rootDir, rawFile) : rawFile;
|
|
6771
7434
|
if (isPathProtectedFromPatch(relPath)) {
|
|
6772
7435
|
violations.push(
|
|
6773
7436
|
`[ANTI-PATCH-BYPASS] File '${relPath}' is a protected path (security, schemas, architectural decisions, or master policies). Changes with 'patch' profile are strictly prohibited from modifying these paths. Must be reclassified to 'standard' or 'critical'.`
|
|
@@ -6780,7 +7443,7 @@ function buildFrictionHeader(profile, success, fileCount) {
|
|
|
6780
7443
|
return [
|
|
6781
7444
|
`# \u{1F6E1}\uFE0F Progressive Friction and Anti-Bypass Verification Report`,
|
|
6782
7445
|
``,
|
|
6783
|
-
`> **Date:** ${(
|
|
7446
|
+
`> **Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
6784
7447
|
`> **Evaluated Profile:** \`${profile.toUpperCase()}\``,
|
|
6785
7448
|
`> **Verdict:** ${success ? "COMPLIANT \u2705" : "BLOCKED \u274C"}`,
|
|
6786
7449
|
``,
|
|
@@ -6873,13 +7536,13 @@ function extractManualMetadata(fm, id, manualType, relPath, body) {
|
|
|
6873
7536
|
}
|
|
6874
7537
|
function buildManualItem(rootDir, filePath) {
|
|
6875
7538
|
try {
|
|
6876
|
-
const raw =
|
|
7539
|
+
const raw = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
6877
7540
|
const { fm, body } = parseMdFrontmatter(raw);
|
|
6878
7541
|
const id = typeof fm.id === "string" ? fm.id.trim() : "";
|
|
6879
7542
|
const rawType = typeof fm.type === "string" ? fm.type.trim() : "";
|
|
6880
7543
|
const manualType = resolveManualType(id, rawType);
|
|
6881
7544
|
if (!id || !manualType) return null;
|
|
6882
|
-
const relPath =
|
|
7545
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
6883
7546
|
return extractManualMetadata(fm, id, manualType, relPath, body);
|
|
6884
7547
|
} catch {
|
|
6885
7548
|
return null;
|
|
@@ -6890,8 +7553,8 @@ function scanManualsInWorkspace(rootDir) {
|
|
|
6890
7553
|
const seenIds = /* @__PURE__ */ new Set();
|
|
6891
7554
|
const items = [];
|
|
6892
7555
|
for (const relDir of manualDirs) {
|
|
6893
|
-
const fullDir =
|
|
6894
|
-
if (!
|
|
7556
|
+
const fullDir = path5__namespace.join(rootDir, relDir);
|
|
7557
|
+
if (!fs8__namespace.existsSync(fullDir)) continue;
|
|
6895
7558
|
for (const file of walkMdFiles(fullDir)) {
|
|
6896
7559
|
const item = buildManualItem(rootDir, file);
|
|
6897
7560
|
if (item && !seenIds.has(item.id)) {
|
|
@@ -6914,10 +7577,10 @@ function classifyBaselineId(id, type, buckets) {
|
|
|
6914
7577
|
}
|
|
6915
7578
|
}
|
|
6916
7579
|
function processBaselineFile(file, rootDir, buckets) {
|
|
6917
|
-
const rel =
|
|
7580
|
+
const rel = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
6918
7581
|
if (rel.includes("/changes/") || rel.startsWith("changes/")) return;
|
|
6919
7582
|
try {
|
|
6920
|
-
const { fm } = parseMdFrontmatter(
|
|
7583
|
+
const { fm } = parseMdFrontmatter(fs8__namespace.readFileSync(file, "utf-8"));
|
|
6921
7584
|
const id = typeof fm.id === "string" ? fm.id.trim() : "";
|
|
6922
7585
|
const status = typeof fm.status === "string" ? fm.status.trim().toLowerCase() : "active";
|
|
6923
7586
|
if (!id || status === "retired" || status === "deprecated") return;
|
|
@@ -6938,8 +7601,8 @@ function collectBaselineTargets(rootDir) {
|
|
|
6938
7601
|
enclaves: /* @__PURE__ */ new Set()
|
|
6939
7602
|
};
|
|
6940
7603
|
for (const dir of ["product", "architecture", "security", "specs"]) {
|
|
6941
|
-
const fullDir =
|
|
6942
|
-
if (
|
|
7604
|
+
const fullDir = path5__namespace.join(rootDir, dir);
|
|
7605
|
+
if (fs8__namespace.existsSync(fullDir)) {
|
|
6943
7606
|
scanDirectoryForBaseline(rootDir, fullDir, buckets);
|
|
6944
7607
|
}
|
|
6945
7608
|
}
|
|
@@ -6996,10 +7659,10 @@ function detectCurrentGitBranch(rootDir) {
|
|
|
6996
7659
|
}
|
|
6997
7660
|
}
|
|
6998
7661
|
function resolveActiveChangeProfile(rootDir) {
|
|
6999
|
-
const activeRoot =
|
|
7000
|
-
if (!
|
|
7662
|
+
const activeRoot = path5__namespace.join(rootDir, "specs", "changes", "active");
|
|
7663
|
+
if (!fs8__namespace.existsSync(activeRoot)) return "standard";
|
|
7001
7664
|
try {
|
|
7002
|
-
const dirs =
|
|
7665
|
+
const dirs = fs8__namespace.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path5__namespace.join(activeRoot, d.name));
|
|
7003
7666
|
const profiles = dirs.map((d) => detectProfileFromChange(d));
|
|
7004
7667
|
if (profiles.includes("critical")) return "critical";
|
|
7005
7668
|
if (profiles.includes("patch") && profiles.length === 1) return "patch";
|
|
@@ -7136,7 +7799,7 @@ var REQ_TYPES = /* @__PURE__ */ new Set(["requirement", "security-requirement",
|
|
|
7136
7799
|
var REQ_CATEGORIES = /* @__PURE__ */ new Set(["functional", "quality", "constraint", "security", "safety"]);
|
|
7137
7800
|
function computeSha256(content) {
|
|
7138
7801
|
const normalized = content.replace(/\r\n/g, "\n");
|
|
7139
|
-
return
|
|
7802
|
+
return crypto3__namespace.createHash("sha256").update(normalized, "utf-8").digest("hex");
|
|
7140
7803
|
}
|
|
7141
7804
|
function tokenizeTitle(title) {
|
|
7142
7805
|
const words = title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOP_WORDS.has(w));
|
|
@@ -7212,11 +7875,11 @@ function buildRequirementObject(content, fm, body, filePath, relFile) {
|
|
|
7212
7875
|
}
|
|
7213
7876
|
function parseRequirementFile(filePath, rootDir) {
|
|
7214
7877
|
try {
|
|
7215
|
-
const content =
|
|
7878
|
+
const content = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
7216
7879
|
const extracted = extractParsedFrontmatter(content);
|
|
7217
7880
|
if (!extracted) return null;
|
|
7218
7881
|
const body = content.substring(extracted.matchLen);
|
|
7219
|
-
const relFile =
|
|
7882
|
+
const relFile = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
7220
7883
|
return buildRequirementObject(content, extracted.fm, body, filePath, relFile);
|
|
7221
7884
|
} catch {
|
|
7222
7885
|
return null;
|
|
@@ -7228,7 +7891,7 @@ function isIgnoredScanPath(relFile) {
|
|
|
7228
7891
|
function collectRequirements(rootDir) {
|
|
7229
7892
|
const reqs = [];
|
|
7230
7893
|
for (const file of walkMdFiles(rootDir)) {
|
|
7231
|
-
const relFile =
|
|
7894
|
+
const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
7232
7895
|
if (isIgnoredScanPath(relFile)) continue;
|
|
7233
7896
|
const req = parseRequirementFile(file, rootDir);
|
|
7234
7897
|
if (req) reqs.push(req);
|
|
@@ -7362,7 +8025,7 @@ function buildDuplicatesHeader(result) {
|
|
|
7362
8025
|
return [
|
|
7363
8026
|
`# \u{1F6E1}\uFE0F Duplicate Requirements and Redundancy Audit (Shift-Left Pre-Flight)`,
|
|
7364
8027
|
``,
|
|
7365
|
-
`> **Evaluation Date:** ${(
|
|
8028
|
+
`> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
7366
8029
|
`> **Verdict:** ${isOk ? "\u2705 COMPLIANT (No blocking duplicates)" : `\u274C BLOCKED (${result.errorCount} duplicate errors detected)`}`,
|
|
7367
8030
|
`> **Audited Requirements:** ${result.totalRequirements} | **Errors:** ${result.errorCount} | **Warnings:** ${result.warningCount}`,
|
|
7368
8031
|
``,
|
|
@@ -7485,7 +8148,7 @@ function buildAuditHeader(total, active, expired, invalid) {
|
|
|
7485
8148
|
return [
|
|
7486
8149
|
"# \u{1F4CB} Security Suppressions and Audit Exceptions Inventory",
|
|
7487
8150
|
"",
|
|
7488
|
-
`> **Generated at:** ${(
|
|
8151
|
+
`> **Generated at:** ${resolveDeterministicDate().toISOString()}`,
|
|
7489
8152
|
`> **Total Audited Suppressions:** ${total} | **Active:** ${active} | **Expired:** ${expired} | **Invalid:** ${invalid}`,
|
|
7490
8153
|
"",
|
|
7491
8154
|
"## 1. Executive Summary",
|
|
@@ -7716,10 +8379,10 @@ function isPathExcluded2(relPath, patterns) {
|
|
|
7716
8379
|
function walkProjectFiles(dir, rootDir, excludedPatterns = DEFAULT_EXCLUDED_PATTERNS) {
|
|
7717
8380
|
const results = [];
|
|
7718
8381
|
try {
|
|
7719
|
-
const entries =
|
|
8382
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true }).sort((a, b) => canonicalSort(a.name, b.name));
|
|
7720
8383
|
for (const entry of entries) {
|
|
7721
|
-
const fullPath =
|
|
7722
|
-
const relPath =
|
|
8384
|
+
const fullPath = path5__namespace.join(dir, entry.name);
|
|
8385
|
+
const relPath = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
7723
8386
|
if (isPathExcluded2(relPath, excludedPatterns)) continue;
|
|
7724
8387
|
if (entry.isDirectory()) {
|
|
7725
8388
|
results.push(...walkProjectFiles(fullPath, rootDir, excludedPatterns));
|
|
@@ -7748,8 +8411,8 @@ function pickFirstString(...vals) {
|
|
|
7748
8411
|
}
|
|
7749
8412
|
function parseGitleaksItem(item, rootDir) {
|
|
7750
8413
|
const rawFile = pickFirstString(item.File, item.file);
|
|
7751
|
-
const filePath =
|
|
7752
|
-
const relPath =
|
|
8414
|
+
const filePath = path5__namespace.resolve(rootDir, rawFile);
|
|
8415
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
7753
8416
|
const ruleId = pickFirstString(item.RuleID, item.ruleID) || "GITLEAKS-MATCH";
|
|
7754
8417
|
const rawSecret = pickFirstString(item.Secret, item.secret, item.Match);
|
|
7755
8418
|
const line = item.StartLine ?? item.line ?? 1;
|
|
@@ -7822,7 +8485,7 @@ function scanLineForSecrets(line, lineNumber, filePath, relPath, entropyThreshol
|
|
|
7822
8485
|
function scanContentForSecrets(content, filePath, rootDir, entropyThreshold = 4.3, collectedSuppressions) {
|
|
7823
8486
|
const violations = [];
|
|
7824
8487
|
const lines = content.split(/\r?\n/);
|
|
7825
|
-
const relPath =
|
|
8488
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
7826
8489
|
for (let i = 0; i < lines.length; i++) {
|
|
7827
8490
|
violations.push(...scanLineForSecrets(lines[i], i + 1, filePath, relPath, entropyThreshold, collectedSuppressions));
|
|
7828
8491
|
}
|
|
@@ -7843,7 +8506,7 @@ function scanDiffItems(diffLines, rootDir, excludePatterns, entropyThreshold) {
|
|
|
7843
8506
|
const suppressions = [];
|
|
7844
8507
|
for (const [relFile, items] of fileGroups) {
|
|
7845
8508
|
if (isPathExcluded2(relFile, excludePatterns)) continue;
|
|
7846
|
-
const absPath =
|
|
8509
|
+
const absPath = path5__namespace.resolve(rootDir, relFile);
|
|
7847
8510
|
for (const item of items) {
|
|
7848
8511
|
const lineViolations = scanLineForSecrets(item.content, item.lineNumber, absPath, relFile, entropyThreshold, suppressions);
|
|
7849
8512
|
for (const v of lineViolations) {
|
|
@@ -7859,7 +8522,7 @@ function scanAllProjectFiles(rootDir, excludePatterns, entropyThreshold) {
|
|
|
7859
8522
|
const suppressions = [];
|
|
7860
8523
|
for (const file of allFiles) {
|
|
7861
8524
|
try {
|
|
7862
|
-
const content =
|
|
8525
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
7863
8526
|
findings.push(...scanContentForSecrets(content, file, rootDir, entropyThreshold, suppressions));
|
|
7864
8527
|
} catch {
|
|
7865
8528
|
}
|
|
@@ -7916,7 +8579,7 @@ function buildReportHeader2(result) {
|
|
|
7916
8579
|
return [
|
|
7917
8580
|
`# \u{1F510} Deterministic Secrets and Credentials Audit (Gitleaks Gate)`,
|
|
7918
8581
|
``,
|
|
7919
|
-
`> **Evaluation Date:** ${(
|
|
8582
|
+
`> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
7920
8583
|
`> **Verdict:** ${isOk ? "\u2705 COMPLIANT (No exposed secrets or credentials)" : `\u274C BLOCKED (${result.findingsCount} exposed credentials detected)`}`,
|
|
7921
8584
|
`> **Audited Files:** ${result.totalFilesScanned} | **Security Violations:** ${result.findingsCount}`,
|
|
7922
8585
|
`> **Native Gitleaks Engine:** ${result.scannedWithGitleaks ? "\u{1F7E2} Active / Integrated" : "\u26AA Deterministic Native Scanner (@aisdlc/core)"}`,
|
|
@@ -8085,8 +8748,8 @@ function runSemgrepCli(rootDir) {
|
|
|
8085
8748
|
const parsed = JSON.parse(output);
|
|
8086
8749
|
if (Array.isArray(parsed.results)) {
|
|
8087
8750
|
for (const item of parsed.results) {
|
|
8088
|
-
const filePath =
|
|
8089
|
-
const relPath =
|
|
8751
|
+
const filePath = path5__namespace.resolve(rootDir, item.path);
|
|
8752
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
8090
8753
|
violations.push({
|
|
8091
8754
|
filePath,
|
|
8092
8755
|
relPath,
|
|
@@ -8113,15 +8776,15 @@ function isSastExcluded(relPath, excludes) {
|
|
|
8113
8776
|
function walkSourceFiles(dir, rootDir, excludes = DEFAULT_SAST_EXCLUDES) {
|
|
8114
8777
|
const files = [];
|
|
8115
8778
|
try {
|
|
8116
|
-
const entries =
|
|
8779
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true }).sort((a, b) => canonicalSort(a.name, b.name));
|
|
8117
8780
|
for (const entry of entries) {
|
|
8118
|
-
const fullPath =
|
|
8119
|
-
const relPath =
|
|
8781
|
+
const fullPath = path5__namespace.join(dir, entry.name);
|
|
8782
|
+
const relPath = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
8120
8783
|
if (isSastExcluded(relPath, excludes)) continue;
|
|
8121
8784
|
if (entry.isDirectory()) {
|
|
8122
8785
|
files.push(...walkSourceFiles(fullPath, rootDir, excludes));
|
|
8123
8786
|
} else if (entry.isFile()) {
|
|
8124
|
-
const ext =
|
|
8787
|
+
const ext = path5__namespace.extname(entry.name).toLowerCase();
|
|
8125
8788
|
if (GENERAL_SOURCE_EXTENSIONS.includes(ext)) {
|
|
8126
8789
|
files.push(fullPath);
|
|
8127
8790
|
}
|
|
@@ -8146,11 +8809,11 @@ function checkRuleMatch(rule, line, ext, minSeverity) {
|
|
|
8146
8809
|
}
|
|
8147
8810
|
function scanFileForSast(filePath, rootDir, minSeverity = "HIGH", collectedSuppressions) {
|
|
8148
8811
|
const violations = [];
|
|
8149
|
-
const ext =
|
|
8812
|
+
const ext = path5__namespace.extname(filePath).toLowerCase();
|
|
8150
8813
|
try {
|
|
8151
|
-
const content =
|
|
8814
|
+
const content = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
8152
8815
|
const lines = content.split(/\r?\n/);
|
|
8153
|
-
const relPath =
|
|
8816
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
8154
8817
|
for (let i = 0; i < lines.length; i++) {
|
|
8155
8818
|
const line = lines[i];
|
|
8156
8819
|
if (checkLineSuppression(line, relPath, i + 1, collectedSuppressions)) continue;
|
|
@@ -8176,8 +8839,8 @@ function scanFileForSast(filePath, rootDir, minSeverity = "HIGH", collectedSuppr
|
|
|
8176
8839
|
function collectSastFiles(rootDir, targetDirs) {
|
|
8177
8840
|
const files = [];
|
|
8178
8841
|
for (const tDir of targetDirs) {
|
|
8179
|
-
const fullDir =
|
|
8180
|
-
if (
|
|
8842
|
+
const fullDir = path5__namespace.join(rootDir, tDir);
|
|
8843
|
+
if (fs8__namespace.existsSync(fullDir)) {
|
|
8181
8844
|
files.push(...walkSourceFiles(fullDir, rootDir));
|
|
8182
8845
|
}
|
|
8183
8846
|
}
|
|
@@ -8223,7 +8886,7 @@ function buildSastHeader(isOk, violationsCount, totalFilesScanned) {
|
|
|
8223
8886
|
return [
|
|
8224
8887
|
`# \u{1F6E1}\uFE0F Shift-Left SAST Audit (Static Application Security Testing)`,
|
|
8225
8888
|
``,
|
|
8226
|
-
`> **Evaluation Date:** ${(
|
|
8889
|
+
`> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
8227
8890
|
`> **Verdict:** ${verdict}`,
|
|
8228
8891
|
`> **Audited Code Files:** ${totalFilesScanned} | **SAST Violations:** ${violationsCount}`,
|
|
8229
8892
|
``,
|
|
@@ -8412,22 +9075,31 @@ var PII_SAST_RULES = [
|
|
|
8412
9075
|
pattern: /\b(?:ssn|dni|nif|tax_?id|national_?id)\s*[:=]\s*['"](?:\d{3}-\d{2}-\d{4}|\d{8}[A-HJ-NP-TV-Z])['"]/i
|
|
8413
9076
|
}
|
|
8414
9077
|
];
|
|
8415
|
-
function
|
|
8416
|
-
|
|
8417
|
-
if (!fs7__namespace.existsSync(candidate)) return DEFAULT_PRIVACY_POLICY;
|
|
9078
|
+
function parsePrivacyDoc(raw, candidate) {
|
|
9079
|
+
let parsed;
|
|
8418
9080
|
try {
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
agent_guardrails: { ...DEFAULT_PRIVACY_POLICY.agent_guardrails, ...parsed.agent_guardrails || {} },
|
|
8426
|
-
ci_gates: { ...DEFAULT_PRIVACY_POLICY.ci_gates, ...parsed.ci_gates || {} }
|
|
8427
|
-
};
|
|
8428
|
-
} catch {
|
|
8429
|
-
return DEFAULT_PRIVACY_POLICY;
|
|
9081
|
+
parsed = yaml3__default.default.load(raw);
|
|
9082
|
+
} catch (err) {
|
|
9083
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in privacy-policy '${candidate}': ${err instanceof Error ? err.message : String(err)}`);
|
|
9084
|
+
}
|
|
9085
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9086
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid privacy policy structure in '${candidate}': root must be a YAML mapping`);
|
|
8430
9087
|
}
|
|
9088
|
+
return parsed;
|
|
9089
|
+
}
|
|
9090
|
+
function loadPrivacyPolicy(rootDir = process.cwd(), policyPath) {
|
|
9091
|
+
const candidate = policyPath ? path5__namespace.isAbsolute(policyPath) ? policyPath : path5__namespace.join(rootDir, policyPath) : path5__namespace.join(rootDir, "privacy-policy.yaml");
|
|
9092
|
+
if (!fs8__namespace.existsSync(candidate)) return DEFAULT_PRIVACY_POLICY;
|
|
9093
|
+
const raw = fs8__namespace.readFileSync(candidate, "utf-8");
|
|
9094
|
+
if (!raw || !raw.trim()) return DEFAULT_PRIVACY_POLICY;
|
|
9095
|
+
const parsed = parsePrivacyDoc(raw, candidate);
|
|
9096
|
+
return {
|
|
9097
|
+
...DEFAULT_PRIVACY_POLICY,
|
|
9098
|
+
...parsed,
|
|
9099
|
+
classifications: { ...DEFAULT_PRIVACY_POLICY.classifications, ...parsed.classifications || {} },
|
|
9100
|
+
agent_guardrails: { ...DEFAULT_PRIVACY_POLICY.agent_guardrails, ...parsed.agent_guardrails || {} },
|
|
9101
|
+
ci_gates: { ...DEFAULT_PRIVACY_POLICY.ci_gates, ...parsed.ci_gates || {} }
|
|
9102
|
+
};
|
|
8431
9103
|
}
|
|
8432
9104
|
function isLineExemptFromPrivacyScan(line) {
|
|
8433
9105
|
return line.includes("ai-sdlc:allow-pii") || line.includes("ai-sdlc:allow-sast") || line.includes("nosec");
|
|
@@ -8444,9 +9116,9 @@ function shouldApplyPiiRule(rule, policy) {
|
|
|
8444
9116
|
function scanFileForPrivacyViolations(filePath, rootDir, policy = DEFAULT_PRIVACY_POLICY) {
|
|
8445
9117
|
const violations = [];
|
|
8446
9118
|
try {
|
|
8447
|
-
const content =
|
|
9119
|
+
const content = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
8448
9120
|
const lines = content.split(/\r?\n/);
|
|
8449
|
-
const relPath =
|
|
9121
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
8450
9122
|
for (let i = 0; i < lines.length; i++) {
|
|
8451
9123
|
const line = lines[i];
|
|
8452
9124
|
if (isLineExemptFromPrivacyScan(line)) continue;
|
|
@@ -8555,10 +9227,10 @@ function discoverPrivacyArtifacts(rootDir) {
|
|
|
8555
9227
|
dpiaStatuses: /* @__PURE__ */ new Map()
|
|
8556
9228
|
};
|
|
8557
9229
|
for (const file of walkMdFiles(rootDir)) {
|
|
8558
|
-
const relPath =
|
|
9230
|
+
const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
8559
9231
|
if (isExcludedPrivacyMd(relPath)) continue;
|
|
8560
9232
|
try {
|
|
8561
|
-
const fm = extractFrontmatter(
|
|
9233
|
+
const fm = extractFrontmatter(fs8__namespace.readFileSync(file, "utf-8"));
|
|
8562
9234
|
if (fm && fm.id) classifyPrivacyMarkdown(fm, relPath, acc);
|
|
8563
9235
|
} catch {
|
|
8564
9236
|
}
|
|
@@ -8603,7 +9275,7 @@ function checkAssetRopaRegistration(asset, ropaRecords, policy, fullPath) {
|
|
|
8603
9275
|
}
|
|
8604
9276
|
function auditDataAssetGovernance(asset, ropaRecords, policy, rootDir) {
|
|
8605
9277
|
if (!isSensitiveClassification(asset.classification)) return [];
|
|
8606
|
-
const fullPath =
|
|
9278
|
+
const fullPath = path5__namespace.join(rootDir, asset.filePath);
|
|
8607
9279
|
const v1 = checkAssetRetention(asset, policy, fullPath);
|
|
8608
9280
|
const v2 = checkAssetRopaRegistration(asset, ropaRecords, policy, fullPath);
|
|
8609
9281
|
return [v1, v2].filter((v) => v !== null);
|
|
@@ -8675,8 +9347,8 @@ function checkRopaDpiaApproval(ropa, dataAssets, dpiaStatuses, fullPath) {
|
|
|
8675
9347
|
function collectSourceFilesForPrivacy(rootDir, targetDirs) {
|
|
8676
9348
|
const files = [];
|
|
8677
9349
|
for (const dir of targetDirs) {
|
|
8678
|
-
const fullDir =
|
|
8679
|
-
if (
|
|
9350
|
+
const fullDir = path5__namespace.join(rootDir, dir);
|
|
9351
|
+
if (fs8__namespace.existsSync(fullDir)) {
|
|
8680
9352
|
files.push(...walkSourceFiles(fullDir, rootDir));
|
|
8681
9353
|
}
|
|
8682
9354
|
}
|
|
@@ -8766,15 +9438,13 @@ function generatePrivacyReportMarkdown(result, policy = DEFAULT_PRIVACY_POLICY)
|
|
|
8766
9438
|
function writePrivacyOutputsIfNeeded(result, options, policy, rootDir) {
|
|
8767
9439
|
if (options.generateRopa) {
|
|
8768
9440
|
const relRopa = options.ropaOutputPath || policy.ci_gates.output_ropa_file || "reports/ROPA_INVENTORY.md";
|
|
8769
|
-
const fullRopa =
|
|
8770
|
-
|
|
8771
|
-
fs7__namespace.writeFileSync(fullRopa, result.ropaMarkdown, "utf-8");
|
|
9441
|
+
const fullRopa = path5__namespace.isAbsolute(relRopa) ? relRopa : path5__namespace.join(rootDir, relRopa);
|
|
9442
|
+
writeReportFileIfChanged(fullRopa, result.ropaMarkdown);
|
|
8772
9443
|
result.ropaOutputPath = fullRopa;
|
|
8773
9444
|
}
|
|
8774
9445
|
if (options.reportOutputPath) {
|
|
8775
|
-
const fullReport =
|
|
8776
|
-
|
|
8777
|
-
fs7__namespace.writeFileSync(fullReport, result.reportMarkdown, "utf-8");
|
|
9446
|
+
const fullReport = path5__namespace.isAbsolute(options.reportOutputPath) ? options.reportOutputPath : path5__namespace.join(rootDir, options.reportOutputPath);
|
|
9447
|
+
writeReportFileIfChanged(fullReport, result.reportMarkdown);
|
|
8778
9448
|
result.reportOutputPath = fullReport;
|
|
8779
9449
|
}
|
|
8780
9450
|
}
|
|
@@ -8786,7 +9456,7 @@ function evaluateDiscoveredPrivacyGovernance(discovered, policy, rootDir) {
|
|
|
8786
9456
|
}
|
|
8787
9457
|
let dpiaRequiredCount = 0;
|
|
8788
9458
|
for (const ropa of discovered.ropaRecords) {
|
|
8789
|
-
const fullPath =
|
|
9459
|
+
const fullPath = path5__namespace.join(rootDir, ropa.filePath);
|
|
8790
9460
|
violations.push(...checkRopaControlsAndSubprocessors(ropa, governedRopaIds, policy, fullPath));
|
|
8791
9461
|
const dpiaCheck = checkRopaDpiaApproval(ropa, discovered.dataAssets, discovered.dpiaStatuses, fullPath);
|
|
8792
9462
|
if (dpiaCheck.dpiaRequired) dpiaRequiredCount++;
|
|
@@ -8832,7 +9502,7 @@ function calculateRating(mi, cc) {
|
|
|
8832
9502
|
return "F";
|
|
8833
9503
|
}
|
|
8834
9504
|
function detectLanguage(filePath) {
|
|
8835
|
-
const ext =
|
|
9505
|
+
const ext = path5__namespace.extname(filePath).toLowerCase();
|
|
8836
9506
|
switch (ext) {
|
|
8837
9507
|
case ".ts":
|
|
8838
9508
|
return "TypeScript";
|
|
@@ -8857,7 +9527,7 @@ function detectLanguage(filePath) {
|
|
|
8857
9527
|
}
|
|
8858
9528
|
function parseJsonSummaryFile(filePath, relSource) {
|
|
8859
9529
|
try {
|
|
8860
|
-
const raw = JSON.parse(
|
|
9530
|
+
const raw = JSON.parse(fs8__namespace.readFileSync(filePath, "utf-8"));
|
|
8861
9531
|
const total = raw.total;
|
|
8862
9532
|
if (!total || typeof total !== "object") return null;
|
|
8863
9533
|
const linesPct = Number(total.lines?.pct);
|
|
@@ -8880,7 +9550,7 @@ function updateLcovCounts(line, counts) {
|
|
|
8880
9550
|
}
|
|
8881
9551
|
function parseLcovFile(filePath, relSource) {
|
|
8882
9552
|
try {
|
|
8883
|
-
const lines =
|
|
9553
|
+
const lines = fs8__namespace.readFileSync(filePath, "utf-8").split(/\r?\n/);
|
|
8884
9554
|
const counts = { LF: 0, LH: 0, BRF: 0, BRH: 0, FNF: 0, FNH: 0 };
|
|
8885
9555
|
for (const line of lines) {
|
|
8886
9556
|
updateLcovCounts(line, counts);
|
|
@@ -8900,16 +9570,16 @@ function parseLcovFile(filePath, relSource) {
|
|
|
8900
9570
|
function readCodeCoverageSummary(rootDir = process.cwd()) {
|
|
8901
9571
|
const jsonCandidates = ["coverage/coverage-summary.json", "reports/coverage-summary.json"];
|
|
8902
9572
|
for (const rel of jsonCandidates) {
|
|
8903
|
-
const full =
|
|
8904
|
-
if (
|
|
9573
|
+
const full = path5__namespace.join(rootDir, rel);
|
|
9574
|
+
if (fs8__namespace.existsSync(full)) {
|
|
8905
9575
|
const parsed = parseJsonSummaryFile(full, rel);
|
|
8906
9576
|
if (parsed) return parsed;
|
|
8907
9577
|
}
|
|
8908
9578
|
}
|
|
8909
9579
|
const lcovCandidates = ["coverage/lcov.info", "lcov.info", "reports/lcov.info"];
|
|
8910
9580
|
for (const rel of lcovCandidates) {
|
|
8911
|
-
const full =
|
|
8912
|
-
if (
|
|
9581
|
+
const full = path5__namespace.join(rootDir, rel);
|
|
9582
|
+
if (fs8__namespace.existsSync(full)) {
|
|
8913
9583
|
const parsed = parseLcovFile(full, rel);
|
|
8914
9584
|
if (parsed) return parsed;
|
|
8915
9585
|
}
|
|
@@ -8994,7 +9664,7 @@ function buildReportHeader3(verdict, rating, mi, cc) {
|
|
|
8994
9664
|
return [
|
|
8995
9665
|
`# \u{1F4CA} Formal Quality and Release Gate Report (AI-SDLC)`,
|
|
8996
9666
|
"",
|
|
8997
|
-
`> **Generation Date:** ${(
|
|
9667
|
+
`> **Generation Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
8998
9668
|
`> **Release Gate Verdict:** ${status}`,
|
|
8999
9669
|
`> **Global Rating:** **\`${rating}\`** (MI Index: ${mi}/100, Average CC: ${cc})`
|
|
9000
9670
|
];
|
|
@@ -9045,9 +9715,8 @@ function renderQualityReportMarkdown(gateResult, coverage, accumulated, averages
|
|
|
9045
9715
|
return [...header, "", "---", "", ...table, "", "---", "", ...polyglot, ...violationsSection, "", EVALUATION_STANDARDS_MD].join("\n");
|
|
9046
9716
|
}
|
|
9047
9717
|
function writeQualityReportFile(rootDir, markdown) {
|
|
9048
|
-
const
|
|
9049
|
-
|
|
9050
|
-
fs7__namespace.writeFileSync(path4__namespace.join(reportsDir, "QUALITY_REPORT.md"), markdown, "utf-8");
|
|
9718
|
+
const targetPath = path5__namespace.join(rootDir, "reports", "QUALITY_REPORT.md");
|
|
9719
|
+
return writeReportFileIfChanged(targetPath, markdown);
|
|
9051
9720
|
}
|
|
9052
9721
|
function generateQualityReport(options = {}) {
|
|
9053
9722
|
const rootDir = options.rootDir || process.cwd();
|
|
@@ -9189,9 +9858,9 @@ Flujo de Integraci\xF3n (Merges):`,
|
|
|
9189
9858
|
};
|
|
9190
9859
|
}
|
|
9191
9860
|
function resolveVersionFromProposal(changeDir) {
|
|
9192
|
-
const proposalFile =
|
|
9193
|
-
if (!
|
|
9194
|
-
const propContent =
|
|
9861
|
+
const proposalFile = path5__namespace.join(changeDir, "proposal.md");
|
|
9862
|
+
if (!fs8__namespace.existsSync(proposalFile)) return "1.0.0";
|
|
9863
|
+
const propContent = fs8__namespace.readFileSync(proposalFile, "utf-8");
|
|
9195
9864
|
const propFm = propContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
9196
9865
|
if (!propFm) return "1.0.0";
|
|
9197
9866
|
const parsedProp = yaml3__default.default.load(propFm[1]);
|
|
@@ -9208,9 +9877,9 @@ function isTaskMatchingId(currentId, cleanTaskId) {
|
|
|
9208
9877
|
return currentLower === targetLower || currentLower.startsWith(`${targetLower}-`) || targetLower.startsWith(`${currentLower}-`);
|
|
9209
9878
|
}
|
|
9210
9879
|
function parseTasksFrontmatter(tasksFile) {
|
|
9211
|
-
if (!
|
|
9880
|
+
if (!fs8__namespace.existsSync(tasksFile)) return null;
|
|
9212
9881
|
try {
|
|
9213
|
-
const content =
|
|
9882
|
+
const content = fs8__namespace.readFileSync(tasksFile, "utf-8");
|
|
9214
9883
|
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
9215
9884
|
if (!fmMatch) return null;
|
|
9216
9885
|
const fm = yaml3__default.default.load(fmMatch[1]);
|
|
@@ -9232,7 +9901,7 @@ function buildTaskMatch(currentId, currentTitle, rawChangeId, changeDir, version
|
|
|
9232
9901
|
};
|
|
9233
9902
|
}
|
|
9234
9903
|
function inspectChangeTasks(changeDir, entryName, cleanTaskId, availableTasks) {
|
|
9235
|
-
const fm = parseTasksFrontmatter(
|
|
9904
|
+
const fm = parseTasksFrontmatter(path5__namespace.join(changeDir, "tasks.md"));
|
|
9236
9905
|
if (!fm || !fm.tasks) return void 0;
|
|
9237
9906
|
const rawChangeId = resolveRawChangeId(fm, entryName);
|
|
9238
9907
|
const versionStr = resolveChangeVersion(fm, changeDir);
|
|
@@ -9253,16 +9922,16 @@ function locateTaskInActiveChanges(taskId, rootDir = process.cwd()) {
|
|
|
9253
9922
|
if (!cleanTaskId) {
|
|
9254
9923
|
return { found: false, error: "Debe especificar un identificador de tarea v\xE1lido (ej. TSK-001)." };
|
|
9255
9924
|
}
|
|
9256
|
-
const activeChangesDir =
|
|
9257
|
-
if (!
|
|
9925
|
+
const activeChangesDir = path5__namespace.join(rootDir, "specs", "changes", "active");
|
|
9926
|
+
if (!fs8__namespace.existsSync(activeChangesDir)) {
|
|
9258
9927
|
return { found: false, error: `No se encontr\xF3 el directorio de cambios activos en '${activeChangesDir}'.` };
|
|
9259
9928
|
}
|
|
9260
9929
|
const availableTasks = [];
|
|
9261
|
-
const candidateDirs =
|
|
9930
|
+
const candidateDirs = fs8__namespace.readdirSync(activeChangesDir, { withFileTypes: true });
|
|
9262
9931
|
let matchedTask;
|
|
9263
9932
|
for (const entry of candidateDirs) {
|
|
9264
9933
|
if (!entry.isDirectory()) continue;
|
|
9265
|
-
const changeDir =
|
|
9934
|
+
const changeDir = path5__namespace.join(activeChangesDir, entry.name);
|
|
9266
9935
|
const m = inspectChangeTasks(changeDir, entry.name, cleanTaskId, availableTasks);
|
|
9267
9936
|
if (m && !matchedTask) {
|
|
9268
9937
|
matchedTask = m;
|
|
@@ -9299,14 +9968,14 @@ function detectExistingFeatureBranch(branches, dirName, changeId, changeSlug) {
|
|
|
9299
9968
|
(b) => b.startsWith("feat/") && (b.toLowerCase().includes(dirName.toLowerCase()) || b.toLowerCase().includes(changeId.toLowerCase())) && classifyBranch(b).valid
|
|
9300
9969
|
);
|
|
9301
9970
|
if (existing) return existing;
|
|
9302
|
-
const normalizedName =
|
|
9971
|
+
const normalizedName = slugify2(changeSlug).replace(/^(chg|bug|feat)-(\d+)/i, (_, p, n) => `${p.toUpperCase()}-${n}`);
|
|
9303
9972
|
return `feat/${normalizedName}`;
|
|
9304
9973
|
}
|
|
9305
9974
|
function buildDefaultTaskSuffix(taskId, taskTitle) {
|
|
9306
9975
|
let taskSuffix = taskId.toLowerCase();
|
|
9307
9976
|
const hasSlug = taskSuffix.includes("-") && taskSuffix.split("-").length > 2;
|
|
9308
9977
|
if (!hasSlug && taskTitle) {
|
|
9309
|
-
const titleSlug =
|
|
9978
|
+
const titleSlug = slugify2(taskTitle).split("-").slice(0, 4).join("-");
|
|
9310
9979
|
if (titleSlug) {
|
|
9311
9980
|
taskSuffix = `${taskSuffix}-${titleSlug}`;
|
|
9312
9981
|
}
|
|
@@ -9326,7 +9995,7 @@ function resolveBranchHierarchy(match, rootDir = process.cwd()) {
|
|
|
9326
9995
|
const branches = getGitBranchList(rootDir);
|
|
9327
9996
|
const cleanVersion = (match.version || "1.0.0").replace(/^v/, "");
|
|
9328
9997
|
const releaseBranch = detectExistingReleaseBranch(branches, cleanVersion);
|
|
9329
|
-
const dirName =
|
|
9998
|
+
const dirName = path5__namespace.basename(match.changeDir);
|
|
9330
9999
|
const changeSlug = dirName.replace(/^(chg|CHG)-/i, (m) => m.toUpperCase());
|
|
9331
10000
|
const featureBranch = detectExistingFeatureBranch(branches, dirName, match.changeId, changeSlug);
|
|
9332
10001
|
const parentIdMatch = featureBranch.replace(/^(feat|feature)\//i, "").match(/^[a-zA-Z0-9]+-[a-zA-Z0-9]+/);
|
|
@@ -9633,13 +10302,13 @@ function getGitConfigValue(key, cwd) {
|
|
|
9633
10302
|
}
|
|
9634
10303
|
function readSessionTelemetry(cwd) {
|
|
9635
10304
|
const possiblePaths = [
|
|
9636
|
-
|
|
9637
|
-
|
|
10305
|
+
path5__namespace.join(cwd, ".ai-sdlc", "session-telemetry.json"),
|
|
10306
|
+
path5__namespace.join(cwd, ".ai-sdlc", "agent-session.json")
|
|
9638
10307
|
];
|
|
9639
10308
|
for (const p of possiblePaths) {
|
|
9640
|
-
if (
|
|
10309
|
+
if (fs8__namespace.existsSync(p)) {
|
|
9641
10310
|
try {
|
|
9642
|
-
const raw =
|
|
10311
|
+
const raw = fs8__namespace.readFileSync(p, "utf-8");
|
|
9643
10312
|
return JSON.parse(raw);
|
|
9644
10313
|
} catch {
|
|
9645
10314
|
}
|
|
@@ -9859,20 +10528,20 @@ if [ -f "$TELEMETRY_FILE" ]; then
|
|
|
9859
10528
|
fi
|
|
9860
10529
|
`;
|
|
9861
10530
|
function installGitHooks(rootDir = process.cwd()) {
|
|
9862
|
-
const gitDir =
|
|
9863
|
-
if (!
|
|
10531
|
+
const gitDir = path5__namespace.join(rootDir, ".git");
|
|
10532
|
+
if (!fs8__namespace.existsSync(gitDir)) {
|
|
9864
10533
|
return {
|
|
9865
10534
|
success: false,
|
|
9866
10535
|
error: `No se encontr\xF3 el directorio Git en '${gitDir}'.`
|
|
9867
10536
|
};
|
|
9868
10537
|
}
|
|
9869
|
-
const hooksDir =
|
|
9870
|
-
if (!
|
|
9871
|
-
|
|
10538
|
+
const hooksDir = path5__namespace.join(gitDir, "hooks");
|
|
10539
|
+
if (!fs8__namespace.existsSync(hooksDir)) {
|
|
10540
|
+
fs8__namespace.mkdirSync(hooksDir, { recursive: true });
|
|
9872
10541
|
}
|
|
9873
|
-
const hookPath =
|
|
10542
|
+
const hookPath = path5__namespace.join(hooksDir, "prepare-commit-msg");
|
|
9874
10543
|
try {
|
|
9875
|
-
|
|
10544
|
+
fs8__namespace.writeFileSync(hookPath, PREPARE_COMMIT_MSG_SCRIPT, { encoding: "utf-8", mode: 493 });
|
|
9876
10545
|
return {
|
|
9877
10546
|
success: true,
|
|
9878
10547
|
hookPath
|
|
@@ -10213,16 +10882,16 @@ function formatReleaseKpiMarkdown(report) {
|
|
|
10213
10882
|
].join("\n");
|
|
10214
10883
|
}
|
|
10215
10884
|
function writeReleaseKpiReport(report, options = {}) {
|
|
10216
|
-
const root = options.outputDir || (options.rootDir ?
|
|
10217
|
-
if (!
|
|
10218
|
-
|
|
10885
|
+
const root = options.outputDir || (options.rootDir ? path5__namespace.join(options.rootDir, "reports", "releases") : path5__namespace.join(process.cwd(), "reports", "releases"));
|
|
10886
|
+
if (!fs8__namespace.existsSync(root)) {
|
|
10887
|
+
fs8__namespace.mkdirSync(root, { recursive: true });
|
|
10219
10888
|
}
|
|
10220
10889
|
const cleanBranchName = report.releaseBranch.replace(/[^a-zA-Z0-9.-]/g, "_");
|
|
10221
|
-
const mdPath =
|
|
10222
|
-
const jsonPath =
|
|
10890
|
+
const mdPath = path5__namespace.join(root, `RELEASE_KPIS_${cleanBranchName}.md`);
|
|
10891
|
+
const jsonPath = path5__namespace.join(root, `release_${cleanBranchName}.kpis.json`);
|
|
10223
10892
|
const mdContent = formatReleaseKpiMarkdown(report);
|
|
10224
|
-
|
|
10225
|
-
|
|
10893
|
+
fs8__namespace.writeFileSync(mdPath, mdContent, "utf-8");
|
|
10894
|
+
fs8__namespace.writeFileSync(jsonPath, JSON.stringify(report, null, 2), "utf-8");
|
|
10226
10895
|
return { markdownPath: mdPath, jsonPath };
|
|
10227
10896
|
}
|
|
10228
10897
|
var cachedScript = null;
|
|
@@ -10231,19 +10900,19 @@ function resolveCytoscapePath() {
|
|
|
10231
10900
|
try {
|
|
10232
10901
|
const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
10233
10902
|
const entryPath = req.resolve("cytoscape");
|
|
10234
|
-
const dir =
|
|
10235
|
-
const minPath =
|
|
10236
|
-
if (
|
|
10903
|
+
const dir = path5__namespace.dirname(entryPath);
|
|
10904
|
+
const minPath = path5__namespace.join(dir, "cytoscape.min.js");
|
|
10905
|
+
if (fs8__namespace.existsSync(minPath)) {
|
|
10237
10906
|
return minPath;
|
|
10238
10907
|
}
|
|
10239
10908
|
} catch {
|
|
10240
10909
|
}
|
|
10241
10910
|
const searchDirs = [
|
|
10242
|
-
|
|
10243
|
-
|
|
10911
|
+
path5__namespace.join(process.cwd(), "node_modules", "cytoscape", "dist", "cytoscape.min.js"),
|
|
10912
|
+
path5__namespace.join(process.cwd(), "packages", "core", "node_modules", "cytoscape", "dist", "cytoscape.min.js")
|
|
10244
10913
|
];
|
|
10245
10914
|
for (const candidate of searchDirs) {
|
|
10246
|
-
if (
|
|
10915
|
+
if (fs8__namespace.existsSync(candidate)) {
|
|
10247
10916
|
return candidate;
|
|
10248
10917
|
}
|
|
10249
10918
|
}
|
|
@@ -10256,7 +10925,7 @@ function getCytoscapeScript() {
|
|
|
10256
10925
|
const scriptPath = resolveCytoscapePath();
|
|
10257
10926
|
if (scriptPath) {
|
|
10258
10927
|
try {
|
|
10259
|
-
cachedScript =
|
|
10928
|
+
cachedScript = fs8__namespace.readFileSync(scriptPath, "utf-8");
|
|
10260
10929
|
return cachedScript;
|
|
10261
10930
|
} catch {
|
|
10262
10931
|
}
|
|
@@ -10356,7 +11025,7 @@ function extractFrontmatter2(fileContent) {
|
|
|
10356
11025
|
}
|
|
10357
11026
|
}
|
|
10358
11027
|
function toScannedArtifact(filePath, relPath) {
|
|
10359
|
-
const fileContent =
|
|
11028
|
+
const fileContent = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
10360
11029
|
const fm = extractFrontmatter2(fileContent);
|
|
10361
11030
|
const id = typeof fm?.id === "string" ? fm.id.trim() : "";
|
|
10362
11031
|
if (!id) return null;
|
|
@@ -10368,12 +11037,12 @@ function toScannedArtifact(filePath, relPath) {
|
|
|
10368
11037
|
return { id, type: classified.type, title, layer: classified.layer, filePath: relPath, content, frontmatter: fm };
|
|
10369
11038
|
}
|
|
10370
11039
|
function parseAndCatalogArtifactFile(rootDir, filePath, scannedMap, archViewByBasename) {
|
|
10371
|
-
const relPath =
|
|
11040
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
10372
11041
|
if (relPath.includes("/changes/") || relPath.startsWith("changes/")) return;
|
|
10373
11042
|
const scannedArt = toScannedArtifact(filePath, relPath);
|
|
10374
11043
|
if (!scannedArt) return;
|
|
10375
11044
|
if (scannedArt.layer === "architecture" && scannedArt.type === "architecture") {
|
|
10376
|
-
archViewByBasename.set(
|
|
11045
|
+
archViewByBasename.set(path5__namespace.basename(relPath), scannedArt);
|
|
10377
11046
|
} else {
|
|
10378
11047
|
scannedMap.set(scannedArt.id, scannedArt);
|
|
10379
11048
|
}
|
|
@@ -10383,8 +11052,8 @@ function scanWorkspaceArtifacts(rootDir) {
|
|
|
10383
11052
|
const archViewByBasename = /* @__PURE__ */ new Map();
|
|
10384
11053
|
const artifactDirs = ["product", "architecture", "specs", "security", "privacy"];
|
|
10385
11054
|
for (const dirName of artifactDirs) {
|
|
10386
|
-
const fullDirPath =
|
|
10387
|
-
if (
|
|
11055
|
+
const fullDirPath = path5__namespace.join(rootDir, dirName);
|
|
11056
|
+
if (fs8__namespace.existsSync(fullDirPath)) {
|
|
10388
11057
|
for (const f of walkMdFiles(fullDirPath)) {
|
|
10389
11058
|
parseAndCatalogArtifactFile(rootDir, f, scannedArtifactsMap, archViewByBasename);
|
|
10390
11059
|
}
|
|
@@ -10444,9 +11113,9 @@ function addTraceArchEdges(r, status, archViewByBasename, nodeMap, addEdge) {
|
|
|
10444
11113
|
}
|
|
10445
11114
|
}
|
|
10446
11115
|
function loadTestContentPreview(filePath) {
|
|
10447
|
-
if (!
|
|
11116
|
+
if (!fs8__namespace.existsSync(filePath)) return void 0;
|
|
10448
11117
|
try {
|
|
10449
|
-
const testContent =
|
|
11118
|
+
const testContent = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
10450
11119
|
const lang = filePath.endsWith(".feature") ? "gherkin" : "typescript";
|
|
10451
11120
|
const preview = testContent.substring(0, 1500) + (testContent.length > 1500 ? "\n... (truncated)" : "");
|
|
10452
11121
|
return "```" + lang + "\n" + preview + "\n```";
|
|
@@ -10456,10 +11125,10 @@ function loadTestContentPreview(filePath) {
|
|
|
10456
11125
|
}
|
|
10457
11126
|
function ensureTestNode(t, testId, rootDir, nodeMap) {
|
|
10458
11127
|
if (nodeMap.has(testId)) return;
|
|
10459
|
-
const fullTestPath =
|
|
11128
|
+
const fullTestPath = path5__namespace.isAbsolute(t) ? t : path5__namespace.join(rootDir, t);
|
|
10460
11129
|
nodeMap.set(testId, {
|
|
10461
11130
|
id: testId,
|
|
10462
|
-
label:
|
|
11131
|
+
label: path5__namespace.basename(t),
|
|
10463
11132
|
title: t,
|
|
10464
11133
|
type: t.endsWith(".feature") ? "bdd-feature" : "code-test",
|
|
10465
11134
|
layer: "test",
|
|
@@ -12604,9 +13273,10 @@ ${manualsTab}
|
|
|
12604
13273
|
${kpisTab}
|
|
12605
13274
|
</main>`;
|
|
12606
13275
|
}
|
|
12607
|
-
function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard Interactivo de Trazabilidad y Calidad") {
|
|
13276
|
+
function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard Interactivo de Trazabilidad y Calidad", generationDate) {
|
|
12608
13277
|
const head = renderDashboardHead(title);
|
|
12609
|
-
const
|
|
13278
|
+
const effectiveDate = generationDate || resolveDeterministicDate().toISOString();
|
|
13279
|
+
const header = renderHeaderHtml(graphData.issueCount, effectiveDate);
|
|
12610
13280
|
const nav = renderNavTabsHtml();
|
|
12611
13281
|
const main = renderDashboardMain(metricsData);
|
|
12612
13282
|
const sidebar = renderTaskSidebarHtml();
|
|
@@ -12636,9 +13306,9 @@ var IGNORED_SCAN_DIRS = /* @__PURE__ */ new Set([
|
|
|
12636
13306
|
".agent"
|
|
12637
13307
|
]);
|
|
12638
13308
|
function readKpiFilesInDir(dir) {
|
|
12639
|
-
if (!
|
|
13309
|
+
if (!fs8__namespace.existsSync(dir)) return [];
|
|
12640
13310
|
try {
|
|
12641
|
-
return
|
|
13311
|
+
return fs8__namespace.readdirSync(dir).filter((f) => f.endsWith(".kpis.json")).map((f) => path5__namespace.join(dir, f));
|
|
12642
13312
|
} catch {
|
|
12643
13313
|
return [];
|
|
12644
13314
|
}
|
|
@@ -12646,11 +13316,11 @@ function readKpiFilesInDir(dir) {
|
|
|
12646
13316
|
function scanSubdirKpiFiles(dir, depth, acc) {
|
|
12647
13317
|
if (depth > 4) return;
|
|
12648
13318
|
try {
|
|
12649
|
-
const entries =
|
|
13319
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
|
|
12650
13320
|
for (const entry of entries) {
|
|
12651
13321
|
if (!entry.isDirectory() || IGNORED_SCAN_DIRS.has(entry.name)) continue;
|
|
12652
|
-
const sub =
|
|
12653
|
-
if (entry.name === "releases" &&
|
|
13322
|
+
const sub = path5__namespace.join(dir, entry.name);
|
|
13323
|
+
if (entry.name === "releases" && path5__namespace.basename(dir) === "reports") {
|
|
12654
13324
|
acc.push(...readKpiFilesInDir(sub));
|
|
12655
13325
|
} else {
|
|
12656
13326
|
scanSubdirKpiFiles(sub, depth + 1, acc);
|
|
@@ -12660,7 +13330,7 @@ function scanSubdirKpiFiles(dir, depth, acc) {
|
|
|
12660
13330
|
}
|
|
12661
13331
|
}
|
|
12662
13332
|
function findKpiJsonFiles(rootDir) {
|
|
12663
|
-
const primaryFiles = readKpiFilesInDir(
|
|
13333
|
+
const primaryFiles = readKpiFilesInDir(path5__namespace.join(rootDir, "reports", "releases"));
|
|
12664
13334
|
if (primaryFiles.length > 0) return primaryFiles;
|
|
12665
13335
|
const found = [];
|
|
12666
13336
|
scanSubdirKpiFiles(rootDir, 0, found);
|
|
@@ -12671,7 +13341,7 @@ function collectHistoricalKpis(rootDir) {
|
|
|
12671
13341
|
const kpiFiles = findKpiJsonFiles(rootDir);
|
|
12672
13342
|
for (const fullPath of kpiFiles) {
|
|
12673
13343
|
try {
|
|
12674
|
-
const content =
|
|
13344
|
+
const content = fs8__namespace.readFileSync(fullPath, "utf-8");
|
|
12675
13345
|
const report = JSON.parse(content);
|
|
12676
13346
|
if (!items.some((it) => it.id === report.releaseBranch && it.timestamp === report.generatedAt)) {
|
|
12677
13347
|
items.push({
|
|
@@ -12877,15 +13547,11 @@ function computeGateAverages(gateResults) {
|
|
|
12877
13547
|
}
|
|
12878
13548
|
function generateDashboardReport(options = {}) {
|
|
12879
13549
|
const rootDir = options.rootDir || process.cwd();
|
|
12880
|
-
const outputPath = options.outputPath ||
|
|
13550
|
+
const outputPath = options.outputPath || path5__namespace.join(rootDir, "reports", "dashboard.html");
|
|
12881
13551
|
const graphData = buildGraphElements(rootDir);
|
|
12882
13552
|
const metricsData = collectDashboardMetrics(rootDir);
|
|
12883
|
-
const html = renderDashboardHtml(graphData, metricsData, options.title);
|
|
12884
|
-
|
|
12885
|
-
if (!fs7__namespace.existsSync(outDir)) {
|
|
12886
|
-
fs7__namespace.mkdirSync(outDir, { recursive: true });
|
|
12887
|
-
}
|
|
12888
|
-
fs7__namespace.writeFileSync(outputPath, html, "utf-8");
|
|
13553
|
+
const html = renderDashboardHtml(graphData, metricsData, options.title, resolveDeterministicDate(rootDir).toISOString());
|
|
13554
|
+
writeReportFileIfChanged(outputPath, html);
|
|
12889
13555
|
const { bddCoverageRatio, traceabilityRatio } = computeSummaryRatios(metricsData);
|
|
12890
13556
|
const { avgMi, avgCc } = computeGateAverages(metricsData.gate.results);
|
|
12891
13557
|
return {
|
|
@@ -12911,6 +13577,8 @@ function generateDashboardReport(options = {}) {
|
|
|
12911
13577
|
html
|
|
12912
13578
|
};
|
|
12913
13579
|
}
|
|
13580
|
+
|
|
13581
|
+
// src/reporters/sarif.ts
|
|
12914
13582
|
var SARIF_SCHEMA_2_1_0 = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json";
|
|
12915
13583
|
var SARIF_VERSION = "2.1.0";
|
|
12916
13584
|
function normalizeUri(filePath) {
|
|
@@ -13058,11 +13726,7 @@ function serializeSarifDocument(doc, pretty = true) {
|
|
|
13058
13726
|
return JSON.stringify(doc, null, pretty ? 2 : 0);
|
|
13059
13727
|
}
|
|
13060
13728
|
function writeSecuritySarifReport(outputPath, doc, pretty = true) {
|
|
13061
|
-
|
|
13062
|
-
if (!fs7__namespace.existsSync(dir)) {
|
|
13063
|
-
fs7__namespace.mkdirSync(dir, { recursive: true });
|
|
13064
|
-
}
|
|
13065
|
-
fs7__namespace.writeFileSync(outputPath, serializeSarifDocument(doc, pretty), "utf-8");
|
|
13729
|
+
writeReportFileIfChanged(outputPath, serializeSarifDocument(doc, pretty));
|
|
13066
13730
|
}
|
|
13067
13731
|
|
|
13068
13732
|
// src/init/agent-templates.ts
|
|
@@ -15983,14 +16647,14 @@ function collectAgentFiles(targets) {
|
|
|
15983
16647
|
function writeProjectFiles(destDir, files, dryRun) {
|
|
15984
16648
|
const filesCreated = [];
|
|
15985
16649
|
for (const f of files) {
|
|
15986
|
-
const fullFilePath =
|
|
15987
|
-
const parentDir =
|
|
16650
|
+
const fullFilePath = path5__namespace.join(destDir, f.relPath);
|
|
16651
|
+
const parentDir = path5__namespace.dirname(fullFilePath);
|
|
15988
16652
|
if (!dryRun) {
|
|
15989
|
-
if (!
|
|
15990
|
-
|
|
16653
|
+
if (!fs8__namespace.existsSync(parentDir)) {
|
|
16654
|
+
fs8__namespace.mkdirSync(parentDir, { recursive: true });
|
|
15991
16655
|
}
|
|
15992
|
-
if (!
|
|
15993
|
-
|
|
16656
|
+
if (!fs8__namespace.existsSync(fullFilePath)) {
|
|
16657
|
+
fs8__namespace.writeFileSync(fullFilePath, f.content, "utf-8");
|
|
15994
16658
|
filesCreated.push(f.relPath);
|
|
15995
16659
|
}
|
|
15996
16660
|
} else {
|
|
@@ -16074,7 +16738,7 @@ function getCiWorkflowFiles(ciProvider) {
|
|
|
16074
16738
|
function createDirectoriesOnDisk(destDir, directories, dryRun) {
|
|
16075
16739
|
const created = [];
|
|
16076
16740
|
for (const d of directories) {
|
|
16077
|
-
if (!dryRun)
|
|
16741
|
+
if (!dryRun) fs8__namespace.mkdirSync(path5__namespace.join(destDir, d), { recursive: true });
|
|
16078
16742
|
created.push(d);
|
|
16079
16743
|
}
|
|
16080
16744
|
return created;
|
|
@@ -16089,7 +16753,7 @@ function collectStarterFiles(ciProvider, agentFiles = [], archGranularity = "min
|
|
|
16089
16753
|
...getCiWorkflowFiles(ciProvider),
|
|
16090
16754
|
...agentFiles,
|
|
16091
16755
|
...getArchitectureTemplates(archGranularity).map((t) => ({
|
|
16092
|
-
relPath:
|
|
16756
|
+
relPath: path5__namespace.join("templates", "architecture", t.filename).replace(/\\/g, "/"),
|
|
16093
16757
|
content: t.content
|
|
16094
16758
|
}))
|
|
16095
16759
|
];
|
|
@@ -16120,15 +16784,15 @@ function injectMissingScripts(scripts) {
|
|
|
16120
16784
|
return modified;
|
|
16121
16785
|
}
|
|
16122
16786
|
function updateConsumerPackageJson(destDir, dryRun) {
|
|
16123
|
-
const pkgPath =
|
|
16124
|
-
if (!
|
|
16787
|
+
const pkgPath = path5__namespace.join(destDir, "package.json");
|
|
16788
|
+
if (!fs8__namespace.existsSync(pkgPath)) return false;
|
|
16125
16789
|
try {
|
|
16126
|
-
const pkg = JSON.parse(
|
|
16790
|
+
const pkg = JSON.parse(fs8__namespace.readFileSync(pkgPath, "utf-8"));
|
|
16127
16791
|
if (!pkg || typeof pkg !== "object") return false;
|
|
16128
16792
|
pkg.scripts = pkg.scripts || {};
|
|
16129
16793
|
const modified = injectMissingScripts(pkg.scripts);
|
|
16130
16794
|
if (modified && !dryRun) {
|
|
16131
|
-
|
|
16795
|
+
fs8__namespace.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
16132
16796
|
}
|
|
16133
16797
|
return modified;
|
|
16134
16798
|
} catch {
|
|
@@ -16136,7 +16800,7 @@ function updateConsumerPackageJson(destDir, dryRun) {
|
|
|
16136
16800
|
}
|
|
16137
16801
|
}
|
|
16138
16802
|
function tryInstallGitHook(destDir, dryRun) {
|
|
16139
|
-
if (!dryRun &&
|
|
16803
|
+
if (!dryRun && fs8__namespace.existsSync(path5__namespace.join(destDir, ".git"))) {
|
|
16140
16804
|
return installGitHooks(destDir).success;
|
|
16141
16805
|
}
|
|
16142
16806
|
return false;
|
|
@@ -16185,7 +16849,7 @@ function validateInitOptions(options) {
|
|
|
16185
16849
|
}
|
|
16186
16850
|
function initProject(options = {}) {
|
|
16187
16851
|
const baseDir = options.rootDir || process.cwd();
|
|
16188
|
-
const destDir =
|
|
16852
|
+
const destDir = path5__namespace.resolve(baseDir, options.targetDir || ".");
|
|
16189
16853
|
const validated = validateInitOptions(options);
|
|
16190
16854
|
if (!validated.valid) return buildInitError(destDir, validated.error);
|
|
16191
16855
|
const agentTargets = resolveAgentTargets(options.agents);
|
|
@@ -16269,33 +16933,40 @@ function normalizeParsedRule(raw, idx) {
|
|
|
16269
16933
|
enabled: raw.enabled !== false
|
|
16270
16934
|
};
|
|
16271
16935
|
}
|
|
16936
|
+
function parseDlpDoc(yamlContent) {
|
|
16937
|
+
let doc;
|
|
16938
|
+
try {
|
|
16939
|
+
doc = yaml3__default.default.load(yamlContent);
|
|
16940
|
+
} catch (err) {
|
|
16941
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in dlp-policy: ${err instanceof Error ? err.message : String(err)}`);
|
|
16942
|
+
}
|
|
16943
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
16944
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid dlp-policy structure: root must be a YAML mapping`);
|
|
16945
|
+
}
|
|
16946
|
+
const record = doc;
|
|
16947
|
+
if (record.rules !== void 0 && !Array.isArray(record.rules)) {
|
|
16948
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid dlp-policy: rules must be an array`);
|
|
16949
|
+
}
|
|
16950
|
+
return record;
|
|
16951
|
+
}
|
|
16272
16952
|
function parseDlpPolicy(yamlContent) {
|
|
16273
16953
|
const fallback = createDefaultDlpPolicy();
|
|
16274
16954
|
if (!yamlContent || !yamlContent.trim()) return fallback;
|
|
16275
|
-
|
|
16276
|
-
|
|
16277
|
-
|
|
16278
|
-
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
|
|
16284
|
-
rules: parsedRules.length > 0 ? parsedRules : fallback.rules
|
|
16285
|
-
};
|
|
16286
|
-
} catch {
|
|
16287
|
-
return fallback;
|
|
16288
|
-
}
|
|
16955
|
+
const record = parseDlpDoc(yamlContent);
|
|
16956
|
+
const rawRules = Array.isArray(record.rules) ? record.rules : [];
|
|
16957
|
+
const parsedRules = rawRules.map((r, i) => normalizeParsedRule(r, i)).filter((r) => r !== null);
|
|
16958
|
+
return {
|
|
16959
|
+
version: typeof record.version === "string" ? record.version : "1.0",
|
|
16960
|
+
enabled: record.enabled !== false,
|
|
16961
|
+
zero_data_retention_required: Boolean(record.zero_data_retention_required ?? true),
|
|
16962
|
+
rules: parsedRules.length > 0 ? parsedRules : fallback.rules
|
|
16963
|
+
};
|
|
16289
16964
|
}
|
|
16290
16965
|
function loadDlpPolicy(rootDir, customPolicyPath) {
|
|
16291
16966
|
const base = rootDir || process.cwd();
|
|
16292
|
-
const filePath = customPolicyPath ?
|
|
16293
|
-
if (!
|
|
16294
|
-
|
|
16295
|
-
return parseDlpPolicy(fs7__namespace.readFileSync(filePath, "utf-8"));
|
|
16296
|
-
} catch {
|
|
16297
|
-
return createDefaultDlpPolicy();
|
|
16298
|
-
}
|
|
16967
|
+
const filePath = customPolicyPath ? path5__namespace.isAbsolute(customPolicyPath) ? customPolicyPath : path5__namespace.join(base, customPolicyPath) : path5__namespace.join(base, "dlp-policy.yaml");
|
|
16968
|
+
if (!fs8__namespace.existsSync(filePath)) return createDefaultDlpPolicy();
|
|
16969
|
+
return parseDlpPolicy(fs8__namespace.readFileSync(filePath, "utf-8"));
|
|
16299
16970
|
}
|
|
16300
16971
|
function applySingleDlpRule(text, rule) {
|
|
16301
16972
|
if (rule.enabled === false) return { output: text, matches: 0 };
|
|
@@ -16348,6 +17019,7 @@ function sanitizeDlpPayload(payload, policy = createDefaultDlpPolicy()) {
|
|
|
16348
17019
|
|
|
16349
17020
|
exports.ALL_ARCHITECTURE_TEMPLATES = ALL_ARCHITECTURE_TEMPLATES;
|
|
16350
17021
|
exports.BRANCH_PATTERNS = BRANCH_PATTERNS;
|
|
17022
|
+
exports.BddStepRegistry = BddStepRegistry;
|
|
16351
17023
|
exports.DEFAULT_DLP_RULES = DEFAULT_DLP_RULES;
|
|
16352
17024
|
exports.DEFAULT_EXCLUDED_PATTERNS = DEFAULT_EXCLUDED_PATTERNS;
|
|
16353
17025
|
exports.DEFAULT_EXCLUDES = DEFAULT_EXCLUDES;
|
|
@@ -16482,6 +17154,7 @@ exports.STARTER_WORKFLOW_HANDOFF_TEMPLATE = STARTER_WORKFLOW_HANDOFF_TEMPLATE;
|
|
|
16482
17154
|
exports.STARTER__GITHUB_PULL_REQUEST_TEMPLATE_MD = STARTER__GITHUB_PULL_REQUEST_TEMPLATE_MD;
|
|
16483
17155
|
exports.SpecKitAdapter = SpecKitAdapter;
|
|
16484
17156
|
exports.TYPE_TO_SCHEMA_MAP = TYPE_TO_SCHEMA_MAP;
|
|
17157
|
+
exports.UNIT_TEST_SUFFIXES = UNIT_TEST_SUFFIXES;
|
|
16485
17158
|
exports.VALID_AGENT_TARGETS = VALID_AGENT_TARGETS;
|
|
16486
17159
|
exports.VALID_ARCHITECTURE_GRANULARITIES = VALID_ARCHITECTURE_GRANULARITIES;
|
|
16487
17160
|
exports.VALID_AUTONOMY_MODES = VALID_AUTONOMY_MODES;
|
|
@@ -16501,6 +17174,7 @@ exports.calculateJaccardSimilarity = calculateJaccardSimilarity;
|
|
|
16501
17174
|
exports.calculateMetrics = calculateMetrics;
|
|
16502
17175
|
exports.calculateRating = calculateRating;
|
|
16503
17176
|
exports.calculateShannonEntropy = calculateShannonEntropy;
|
|
17177
|
+
exports.canonicalSort = canonicalSort;
|
|
16504
17178
|
exports.checkGherkinInSync = checkGherkinInSync;
|
|
16505
17179
|
exports.checkoutTaskBranch = checkoutTaskBranch;
|
|
16506
17180
|
exports.classifyBranch = classifyBranch;
|
|
@@ -16509,6 +17183,7 @@ exports.collectAgentFiles = collectAgentFiles;
|
|
|
16509
17183
|
exports.collectDashboardMetrics = collectDashboardMetrics;
|
|
16510
17184
|
exports.collectHistoricalKpis = collectHistoricalKpis;
|
|
16511
17185
|
exports.computeCanonicalSha256 = computeCanonicalSha256;
|
|
17186
|
+
exports.computeRawSha256 = computeRawSha256;
|
|
16512
17187
|
exports.computeSha256 = computeSha256;
|
|
16513
17188
|
exports.computeViolationSignature = computeViolationSignature;
|
|
16514
17189
|
exports.createDefaultDlpPolicy = createDefaultDlpPolicy;
|
|
@@ -16523,6 +17198,7 @@ exports.emitCiOutput = emitCiOutput;
|
|
|
16523
17198
|
exports.evaluateCompoundLicense = evaluateCompoundLicense;
|
|
16524
17199
|
exports.evaluateSingleDependency = evaluateSingleDependency;
|
|
16525
17200
|
exports.evaluateVulnerabilityThresholds = evaluateVulnerabilityThresholds;
|
|
17201
|
+
exports.executeBddFeature = executeBddFeature;
|
|
16526
17202
|
exports.extractBaselineEntriesFromResults = extractBaselineEntriesFromResults;
|
|
16527
17203
|
exports.extractCitations = extractCitations;
|
|
16528
17204
|
exports.extractCommitKpisFromRange = extractCommitKpisFromRange;
|
|
@@ -16533,10 +17209,13 @@ exports.extractFunctions = extractFunctions;
|
|
|
16533
17209
|
exports.extractFunctionsBraceLanguages = extractFunctionsBraceLanguages;
|
|
16534
17210
|
exports.extractFunctionsPolyglot = extractFunctionsPolyglot;
|
|
16535
17211
|
exports.extractFunctionsPython = extractFunctionsPython;
|
|
17212
|
+
exports.extractFunctionsPythonAst = extractFunctionsPythonAst;
|
|
16536
17213
|
exports.extractFunctionsTypeScriptAst = extractFunctionsTypeScriptAst;
|
|
16537
17214
|
exports.extractGherkinBlock = extractGherkinBlock;
|
|
16538
17215
|
exports.extractGherkinFeatures = extractGherkinFeatures;
|
|
16539
17216
|
exports.extractNormativeText = extractNormativeText;
|
|
17217
|
+
exports.extractPythonFunctionsPolyglot = extractPythonFunctionsPolyglot;
|
|
17218
|
+
exports.extractSemanticContent = extractSemanticContent;
|
|
16540
17219
|
exports.findChangeWorkspaceDir = findChangeWorkspaceDir;
|
|
16541
17220
|
exports.findLicenseFile = findLicenseFile;
|
|
16542
17221
|
exports.formatCommitTrailers = formatCommitTrailers;
|
|
@@ -16545,6 +17224,7 @@ exports.formatReleaseKpiMarkdown = formatReleaseKpiMarkdown;
|
|
|
16545
17224
|
exports.generateBranchHierarchyPlan = generateBranchHierarchyPlan;
|
|
16546
17225
|
exports.generateCycloneDxSbom = generateCycloneDxSbom;
|
|
16547
17226
|
exports.generateDashboardReport = generateDashboardReport;
|
|
17227
|
+
exports.generateDeterministicUuidV5 = generateDeterministicUuidV5;
|
|
16548
17228
|
exports.generateDuplicatesReportMarkdown = generateDuplicatesReportMarkdown;
|
|
16549
17229
|
exports.generateGovernanceReportMarkdown = generateGovernanceReportMarkdown;
|
|
16550
17230
|
exports.generateLicenseReportMarkdown = generateLicenseReportMarkdown;
|
|
@@ -16570,10 +17250,13 @@ exports.initProject = initProject;
|
|
|
16570
17250
|
exports.injectKpisIntoPrBody = injectKpisIntoPrBody;
|
|
16571
17251
|
exports.installGitHooks = installGitHooks;
|
|
16572
17252
|
exports.integrateSddChange = integrateSddChange;
|
|
17253
|
+
exports.isCliInstalled = isCliInstalled;
|
|
17254
|
+
exports.isFunctionReferencedInTests = isFunctionReferencedInTests;
|
|
16573
17255
|
exports.isGitleaksInstalled = isGitleaksInstalled;
|
|
16574
17256
|
exports.isMetricWorsened = isMetricWorsened;
|
|
16575
17257
|
exports.isPathProtectedFromPatch = isPathProtectedFromPatch;
|
|
16576
17258
|
exports.isPlaceholderOrIgnored = isPlaceholderOrIgnored;
|
|
17259
|
+
exports.isReportContentSemanticallyEqual = isReportContentSemanticallyEqual;
|
|
16577
17260
|
exports.isRequirementArtifact = isRequirementArtifact2;
|
|
16578
17261
|
exports.isSemgrepInstalled = isSemgrepInstalled;
|
|
16579
17262
|
exports.isToolAvailable = isToolAvailable;
|
|
@@ -16584,10 +17267,13 @@ exports.loadQualityBaseline = loadQualityBaseline;
|
|
|
16584
17267
|
exports.locateTaskInActiveChanges = locateTaskInActiveChanges;
|
|
16585
17268
|
exports.mapSastSeverityToSarifLevel = mapSastSeverityToSarifLevel;
|
|
16586
17269
|
exports.maskSecret = maskSecret;
|
|
17270
|
+
exports.normalizePath = normalizePath;
|
|
16587
17271
|
exports.normalizeSpdxLicense = normalizeSpdxLicense;
|
|
16588
17272
|
exports.normalizeUri = normalizeUri;
|
|
17273
|
+
exports.orchestrateNativeAstAnalysis = orchestrateNativeAstAnalysis;
|
|
16589
17274
|
exports.parseCommitTrailers = parseCommitTrailers;
|
|
16590
17275
|
exports.parseDlpPolicy = parseDlpPolicy;
|
|
17276
|
+
exports.parseFeatureDocument = parseFeatureDocument;
|
|
16591
17277
|
exports.parseFrontmatter = parseFrontmatter;
|
|
16592
17278
|
exports.parseFrontmatterId = parseFrontmatterId;
|
|
16593
17279
|
exports.parseGenericDoc = parseGenericDoc;
|
|
@@ -16619,6 +17305,7 @@ exports.resolveBranchHierarchy = resolveBranchHierarchy;
|
|
|
16619
17305
|
exports.resolveCiEnvironment = resolveCiEnvironment;
|
|
16620
17306
|
exports.resolveCytoscapePath = resolveCytoscapePath;
|
|
16621
17307
|
exports.resolveDefaultBranchBase = resolveDefaultBranchBase;
|
|
17308
|
+
exports.resolveDeterministicDate = resolveDeterministicDate;
|
|
16622
17309
|
exports.resolveFeatureFilePath = resolveFeatureFilePath;
|
|
16623
17310
|
exports.resolveGitRef = resolveGitRef;
|
|
16624
17311
|
exports.resolveLicenseFromFile = resolveLicenseFromFile;
|
|
@@ -16631,6 +17318,7 @@ exports.runGitleaksCli = runGitleaksCli;
|
|
|
16631
17318
|
exports.runSemgrepCli = runSemgrepCli;
|
|
16632
17319
|
exports.sanitizeDlpPayload = sanitizeDlpPayload;
|
|
16633
17320
|
exports.sanitizeDlpText = sanitizeDlpText;
|
|
17321
|
+
exports.sanitizeTestCorpus = sanitizeTestCorpus;
|
|
16634
17322
|
exports.scaffoldSddChange = scaffoldSddChange;
|
|
16635
17323
|
exports.scanAllProductHandoffs = scanAllProductHandoffs;
|
|
16636
17324
|
exports.scanAllTestArtifacts = scanAllTestArtifacts;
|
|
@@ -16649,11 +17337,13 @@ exports.scanWithNativeFs = scanWithNativeFs;
|
|
|
16649
17337
|
exports.scanWithSyft = scanWithSyft;
|
|
16650
17338
|
exports.scanWithTrivy = scanWithTrivy;
|
|
16651
17339
|
exports.serializeSarifDocument = serializeSarifDocument;
|
|
16652
|
-
exports.slugify =
|
|
17340
|
+
exports.slugify = slugify2;
|
|
17341
|
+
exports.stripVolatileReportMetadata = stripVolatileReportMetadata;
|
|
16653
17342
|
exports.syncPdacDigests = syncPdacDigests;
|
|
16654
17343
|
exports.tokenizeTitle = tokenizeTitle;
|
|
16655
17344
|
exports.updateCitationsInContent = updateCitationsInContent;
|
|
16656
17345
|
exports.validateAgainstSchema = validateAgainstSchema;
|
|
17346
|
+
exports.validateAnchorExistence = validateAnchorExistence;
|
|
16657
17347
|
exports.validateArtifactSchema = validateArtifactSchema;
|
|
16658
17348
|
exports.verifyArtifactDuplicates = verifyArtifactDuplicates;
|
|
16659
17349
|
exports.verifyArtifactsSchemas = verifyArtifactsSchemas;
|
|
@@ -16674,9 +17364,11 @@ exports.walkMdFiles = walkMdFiles;
|
|
|
16674
17364
|
exports.walkProjectFiles = walkProjectFiles;
|
|
16675
17365
|
exports.walkSourceFiles = walkSourceFiles;
|
|
16676
17366
|
exports.walkTaskFiles = walkTaskFiles;
|
|
17367
|
+
exports.writeCucumberJsonReport = writeCucumberJsonReport;
|
|
16677
17368
|
exports.writeCycloneDxSbom = writeCycloneDxSbom;
|
|
16678
17369
|
exports.writeQualityBaseline = writeQualityBaseline;
|
|
16679
17370
|
exports.writeReleaseKpiReport = writeReleaseKpiReport;
|
|
17371
|
+
exports.writeReportFileIfChanged = writeReportFileIfChanged;
|
|
16680
17372
|
exports.writeSecuritySarifReport = writeSecuritySarifReport;
|
|
16681
17373
|
exports.writeThirdPartyNotices = writeThirdPartyNotices;
|
|
16682
17374
|
//# sourceMappingURL=index.cjs.map
|