@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.js CHANGED
@@ -1,10 +1,12 @@
1
- import * as crypto from 'crypto';
2
- import * as fs7 from 'fs';
3
- import * as path4 from 'path';
1
+ import * as crypto3 from 'crypto';
2
+ import * as fs8 from 'fs';
3
+ import * as path5 from 'path';
4
4
  import yaml3 from 'js-yaml';
5
- import { SyntaxKind, Project, Node } from 'ts-morph';
6
5
  import * as child_process from 'child_process';
7
- import { execFileSync } from 'child_process';
6
+ import { execFileSync, spawnSync } from 'child_process';
7
+ import { SyntaxKind, Project, Node } from 'ts-morph';
8
+ import Ajv2020 from 'ajv/dist/2020.js';
9
+ import addFormats from 'ajv-formats';
8
10
  import { createRequire } from 'module';
9
11
 
10
12
  // src/types/index.ts
@@ -15,6 +17,168 @@ var VALID_AUTONOMY_MODES = [
15
17
  "AMBIGUOUS",
16
18
  "HIGH_RISK_MANUAL"
17
19
  ];
20
+ function canonicalSort(a, b) {
21
+ return a < b ? -1 : a > b ? 1 : 0;
22
+ }
23
+ function normalizePath(p) {
24
+ return p.normalize("NFC").replace(/\\/g, "/");
25
+ }
26
+ function walkMdFiles(dir, fileList = [], excludeDirs = [
27
+ "node_modules",
28
+ ".git",
29
+ "dist",
30
+ "bin",
31
+ "obj",
32
+ "build",
33
+ "out",
34
+ "target",
35
+ "coverage",
36
+ ".changeset",
37
+ "scratch",
38
+ "test-scaffold",
39
+ "fixtures",
40
+ "templates",
41
+ "examples",
42
+ ".agent",
43
+ ".agents",
44
+ ".claude",
45
+ ".cursor",
46
+ ".github",
47
+ ".vscode",
48
+ ".vs",
49
+ ".idea"
50
+ ]) {
51
+ try {
52
+ if (!fs8.existsSync(dir)) return fileList;
53
+ const files = fs8.readdirSync(dir).sort(canonicalSort);
54
+ for (const file of files) {
55
+ const fullPath = path5.join(dir, file);
56
+ try {
57
+ if (fs8.statSync(fullPath).isDirectory()) {
58
+ if (!excludeDirs.includes(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
59
+ walkMdFiles(fullPath, fileList, excludeDirs);
60
+ }
61
+ } else if (file.endsWith(".md")) {
62
+ fileList.push(fullPath);
63
+ }
64
+ } catch {
65
+ }
66
+ }
67
+ } catch {
68
+ }
69
+ return fileList.sort(canonicalSort);
70
+ }
71
+ function stripVolatileReportMetadata(content, _ext = "") {
72
+ let normalized = content.replace(/\r\n/g, "\n");
73
+ normalized = normalized.replace(/(?:^|\n)>\s*\*\*(?:Generation Date|Generated at|Fecha|Audit Date|Date):\*\*.*$/gim, "");
74
+ normalized = normalized.replace(/Generated:\s*<span>[\s\S]*?<\/span>/gi, "Generated: <span>[STRIPPED]</span>");
75
+ return normalized.trim();
76
+ }
77
+ function areCycloneDxSbomsEqual(existingContent, newContent) {
78
+ try {
79
+ const a = JSON.parse(existingContent);
80
+ const b = JSON.parse(newContent);
81
+ return a.serialNumber === b.serialNumber && JSON.stringify(a.components) === JSON.stringify(b.components) && JSON.stringify(a.vulnerabilities || []) === JSON.stringify(b.vulnerabilities || []);
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+ function areSarifReportsEqual(existingContent, newContent) {
87
+ try {
88
+ const a = JSON.parse(existingContent);
89
+ const b = JSON.parse(newContent);
90
+ return JSON.stringify(a.runs?.[0]?.results || []) === JSON.stringify(b.runs?.[0]?.results || []);
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+ function isReportContentSemanticallyEqual(existingContent, newContent, filePath) {
96
+ if (existingContent === newContent) return true;
97
+ if (filePath.endsWith(".cdx.json") || filePath.endsWith("sbom.json")) {
98
+ if (areCycloneDxSbomsEqual(existingContent, newContent)) return true;
99
+ }
100
+ if (filePath.endsWith(".sarif")) {
101
+ if (areSarifReportsEqual(existingContent, newContent)) return true;
102
+ }
103
+ const ext = path5.extname(filePath).toLowerCase();
104
+ return stripVolatileReportMetadata(existingContent, ext) === stripVolatileReportMetadata(newContent, ext);
105
+ }
106
+ function atomicWriteFile(filePath, content) {
107
+ const tempPath = `${filePath}.${process.pid}.${Math.random().toString(36).substring(2)}.tmp`;
108
+ try {
109
+ fs8.writeFileSync(tempPath, content, "utf-8");
110
+ try {
111
+ if (fs8.existsSync(filePath)) {
112
+ fs8.rmSync(filePath, { force: true });
113
+ }
114
+ fs8.renameSync(tempPath, filePath);
115
+ } catch {
116
+ fs8.copyFileSync(tempPath, filePath);
117
+ fs8.unlinkSync(tempPath);
118
+ }
119
+ } catch {
120
+ fs8.writeFileSync(filePath, content, "utf-8");
121
+ }
122
+ }
123
+ function writeReportFileIfChanged(filePath, content) {
124
+ try {
125
+ if (fs8.existsSync(filePath)) {
126
+ const existing = fs8.readFileSync(filePath, "utf-8");
127
+ if (isReportContentSemanticallyEqual(existing, content, filePath)) {
128
+ return false;
129
+ }
130
+ }
131
+ } catch {
132
+ }
133
+ const dir = path5.dirname(filePath);
134
+ if (!fs8.existsSync(dir)) {
135
+ fs8.mkdirSync(dir, { recursive: true });
136
+ }
137
+ atomicWriteFile(filePath, content);
138
+ return true;
139
+ }
140
+ var UUID_V5_NAMESPACE_OID = "6ba7b812-9dad-11d1-80b4-00c04fd430c8";
141
+ function resolveDeterministicDate(rootDir) {
142
+ const epoch = process.env.SOURCE_DATE_EPOCH;
143
+ if (epoch) {
144
+ const parsed = parseInt(epoch, 10);
145
+ if (!isNaN(parsed) && parsed > 0) {
146
+ return new Date(parsed * 1e3);
147
+ }
148
+ }
149
+ const targetDir = rootDir || process.cwd();
150
+ if (fs8.existsSync(path5.join(targetDir, ".git"))) {
151
+ try {
152
+ const gitOut = execFileSync("git", ["log", "-1", "--format=%ct"], {
153
+ cwd: targetDir,
154
+ encoding: "utf-8",
155
+ stdio: ["pipe", "pipe", "ignore"]
156
+ }).trim();
157
+ const parsed = parseInt(gitOut, 10);
158
+ if (!isNaN(parsed) && parsed > 0) {
159
+ return new Date(parsed * 1e3);
160
+ }
161
+ } catch {
162
+ }
163
+ }
164
+ return /* @__PURE__ */ new Date();
165
+ }
166
+ function generateDeterministicUuidV5(name, namespace = UUID_V5_NAMESPACE_OID) {
167
+ const nsHex = namespace.replace(/-/g, "");
168
+ const nsBytes = Buffer.from(nsHex, "hex");
169
+ const nameBytes = Buffer.from(name, "utf-8");
170
+ const hash = crypto3.createHash("sha1").update(nsBytes).update(nameBytes).digest();
171
+ hash[6] = hash[6] & 15 | 80;
172
+ hash[8] = hash[8] & 63 | 128;
173
+ const hex = hash.toString("hex").substring(0, 32);
174
+ return [
175
+ hex.substring(0, 8),
176
+ hex.substring(8, 12),
177
+ hex.substring(12, 16),
178
+ hex.substring(16, 20),
179
+ hex.substring(20, 32)
180
+ ].join("-");
181
+ }
18
182
  var CYCLOMATIC_KINDS = /* @__PURE__ */ new Set([
19
183
  SyntaxKind.IfStatement,
20
184
  SyntaxKind.ConditionalExpression,
@@ -171,7 +335,7 @@ function buildNodeMetrics(node, filePath) {
171
335
  return { functionName, filePath, loc, cyclomatic, cognitive, maintainability: mi, codeSmells, line };
172
336
  }
173
337
  function createTempSourceFile(project, content, filePath) {
174
- const ext = path4.extname(filePath).toLowerCase() || ".ts";
338
+ const ext = path5.extname(filePath).toLowerCase() || ".ts";
175
339
  const fileName = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 7)}${ext}`;
176
340
  return project.createSourceFile(fileName, content, { overwrite: true });
177
341
  }
@@ -208,8 +372,8 @@ function extractFunctionsTypeScriptAst(content, filePath) {
208
372
  }
209
373
  }
210
374
  function extractFunctionsPolyglot(content, filePath) {
211
- const ext = path4.extname(filePath).toLowerCase();
212
- return ext === ".py" ? extractPythonFunctions(content, filePath) : extractBraceFunctions(content, filePath);
375
+ const ext = path5.extname(filePath).toLowerCase();
376
+ return ext === ".py" ? extractPythonFunctionsPolyglot(content, filePath) : extractBraceFunctions(content, filePath);
213
377
  }
214
378
  function isDocstringToggle(trimmed) {
215
379
  const dCount = (trimmed.match(/"""/g) || []).length;
@@ -254,7 +418,7 @@ function processPythonLine(raw, state, blocks, lineIndex) {
254
418
  handlePythonBodyLine(raw, state, blocks, lineIndex);
255
419
  }
256
420
  }
257
- function extractPythonFunctions(content, filePath) {
421
+ function extractPythonFunctionsPolyglot(content, filePath) {
258
422
  const lines = content.split("\n");
259
423
  const blocks = [];
260
424
  const state = { currentFn: null, fnLines: [], baseIndent: 0, inDocstring: false };
@@ -266,120 +430,182 @@ function extractPythonFunctions(content, filePath) {
266
430
  }
267
431
  return buildMetricsFromBlocks(blocks, content, filePath);
268
432
  }
269
- function processBraceLine(line, state, blocks, lineIndex, ext) {
270
- if (!state.currentFn) {
271
- const candidate = matchFunctionHeader(line, ext);
272
- if (!candidate) return;
433
+ function tryStartBraceFunction(line, state, ext) {
434
+ if (state.currentFn || state.inBlockComment || state.rawTerm) return;
435
+ const candidate = matchFunctionHeader(line, ext);
436
+ if (candidate) {
273
437
  state.currentFn = candidate;
274
438
  state.braceCount = 0;
275
439
  state.fnLines = [];
276
440
  }
277
- state.fnLines.push(line);
278
- const res = scanBracesInLine(line, state.inBlockComment, ext);
441
+ }
442
+ function updateBraceScanState(line, state, ext) {
443
+ const res = scanBracesInLine(line, state.inBlockComment, state.rawTerm, ext);
279
444
  state.inBlockComment = res.nowInBlock;
445
+ state.rawTerm = res.nowRawTerm;
280
446
  state.braceCount += res.delta;
281
- if (state.braceCount === 0 && state.fnLines.length > 1) {
447
+ }
448
+ function processBraceLine(line, state, blocks, lineIndex, ext) {
449
+ tryStartBraceFunction(line, state, ext);
450
+ if (state.currentFn) state.fnLines.push(line);
451
+ updateBraceScanState(line, state, ext);
452
+ if (state.currentFn && state.braceCount === 0 && state.fnLines.length > 1) {
282
453
  blocks.push({ name: state.currentFn, body: state.fnLines.join("\n"), startLine: lineIndex });
283
454
  state.currentFn = null;
284
455
  state.fnLines = [];
285
456
  }
286
457
  }
287
458
  function extractBraceFunctions(content, filePath) {
288
- const ext = path4.extname(filePath).toLowerCase();
459
+ const ext = path5.extname(filePath).toLowerCase();
289
460
  const lines = content.split("\n");
290
461
  const blocks = [];
291
- const state = { currentFn: null, braceCount: 0, fnLines: [], inBlockComment: false };
462
+ const state = {
463
+ currentFn: null,
464
+ braceCount: 0,
465
+ fnLines: [],
466
+ inBlockComment: false,
467
+ rawTerm: null
468
+ };
292
469
  for (let i = 0; i < lines.length; i++) {
293
470
  processBraceLine(lines[i], state, blocks, i, ext);
294
471
  }
295
472
  return buildMetricsFromBlocks(blocks, content, filePath);
296
473
  }
474
+ var HEADER_EXCLUSIONS = /* @__PURE__ */ new Set([
475
+ "if",
476
+ "for",
477
+ "foreach",
478
+ "while",
479
+ "switch",
480
+ "catch",
481
+ "select",
482
+ "match",
483
+ "class",
484
+ "struct",
485
+ "interface",
486
+ "record",
487
+ "enum",
488
+ "namespace",
489
+ "using",
490
+ "lock",
491
+ "fixed",
492
+ "synchronized",
493
+ "try",
494
+ "else"
495
+ ]);
297
496
  function matchCStyleHeader(line) {
298
497
  const match = line.match(
299
- /(?:(?:public|private|protected|static|async|virtual|override|fn)\s+)*(?:function\s+([a-zA-Z0-9_$]+)|(?:[a-zA-Z0-9_<>[\]?]+\s+)+([a-zA-Z0-9_$]+)\s*\([^;{]*\)\s*(?:throws\s+[^{]+)?\{|([a-zA-Z0-9_$]+)\s*\([^;{]*\)\s*(?::\s*[^{]+\s*)?\{)/
498
+ /(?:(?: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*)?\{)/
300
499
  );
301
500
  if (!match) return null;
302
501
  const candidate = match[1] || match[2] || match[3];
303
- const exclusions = ["if", "for", "while", "switch", "catch", "select", "match", "class", "struct"];
304
- return candidate && !exclusions.includes(candidate) ? candidate : null;
502
+ return candidate && !HEADER_EXCLUSIONS.has(candidate) ? candidate : null;
305
503
  }
306
504
  function matchFunctionHeader(line, ext) {
307
505
  const trimmed = line.trim();
308
- if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) return null;
506
+ if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*") || trimmed.startsWith("#")) {
507
+ return null;
508
+ }
309
509
  if (ext === ".go") {
310
- const match = line.match(/func\s+(?:\([^)]*\)\s*)?([a-zA-Z0-9_$]+)\s*\(/);
510
+ const match = line.match(/func\s+(?:\([^)]*\)\s*)?([a-zA-Z0-9_$]+)\s*(?:\[[^\]]*\])?\s*\(/);
311
511
  return match ? match[1] : null;
312
512
  }
313
513
  if (ext === ".rs") {
314
- const match = line.match(/(?:pub\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+([a-zA-Z0-9_$]+)\s*\(/);
514
+ const match = line.match(/(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(?:const\s+)?fn\s+([a-zA-Z0-9_$]+)\s*(?:<[^>]*>)?\s*\(/);
315
515
  return match ? match[1] : null;
316
516
  }
317
517
  return matchCStyleHeader(line);
318
518
  }
319
- function handleStringChar(char, inString) {
320
- if (char === inString) return { inString: null, skip: false };
321
- return { inString, skip: char === "\\" };
322
- }
323
- function handleBlockState(char, next, inBlock) {
324
- if (inBlock && char === "*" && next === "/") return { inBlock: false, skip: true };
325
- return { inBlock, skip: false };
326
- }
327
- function checkCommentOrQuote(char, next, ext) {
328
- if (char === "/" && next === "/") return { startBlock: false, quote: null, isLineComment: true };
329
- if (char === "/" && next === "*") return { startBlock: true, quote: null, isLineComment: false };
330
- const isQuote = char === '"' || char === "'" || char === "`" && ext === ".go";
331
- return { startBlock: false, quote: isQuote ? char : null, isLineComment: false };
332
- }
333
- function stepSkippedState(char, next, inBlock, inString) {
334
- if (inBlock) {
335
- const res = handleBlockState(char, next, inBlock);
336
- return { inBlock: res.inBlock, inString, skip: res.skip, handled: true };
337
- }
338
- if (inString) {
339
- const res = handleStringChar(char, inString);
340
- return { inBlock, inString: res.inString, skip: res.skip, handled: true };
341
- }
342
- return { inBlock, inString, skip: false, handled: false };
343
- }
344
- function checkCodeChar(char, next, ext) {
345
- const trigger = checkCommentOrQuote(char, next, ext);
346
- if (trigger.isLineComment) return { delta: 0, startBlock: false, quote: null, stop: true };
347
- if (trigger.startBlock) return { delta: 0, startBlock: true, quote: null, stop: false };
348
- if (trigger.quote) return { delta: 0, startBlock: false, quote: trigger.quote, stop: false };
349
- const delta = char === "{" ? 1 : char === "}" ? -1 : 0;
350
- return { delta, startBlock: false, quote: null, stop: false };
351
- }
352
- function applyCodeChar(res, state) {
353
- if (res.stop) return { advance: 0, stop: true };
354
- if (res.startBlock) {
519
+ function detectRustOrGoRawString(sub, ext) {
520
+ if (ext === ".rs") {
521
+ const m = sub.match(/^r(#*)"/);
522
+ return m ? { term: `"${m[1]}`, advance: m[0].length - 1 } : null;
523
+ }
524
+ if (ext === ".go" && sub.startsWith("`")) {
525
+ return { term: "`", advance: 0 };
526
+ }
527
+ return null;
528
+ }
529
+ function detectCsJavaOrCppRawString(sub, ext) {
530
+ if (ext === ".cs" || ext === ".java") {
531
+ const m = sub.match(/^\$*("{3,})/);
532
+ if (m) return { term: m[1], advance: m[0].length - 1 };
533
+ const v = ext === ".cs" ? sub.match(/^(@\$|\$@|@)"/) : null;
534
+ return v ? { term: '"', advance: v[0].length - 1 } : null;
535
+ }
536
+ const cpp = sub.match(/^R"([a-zA-Z0-9_]*)\(/);
537
+ return cpp ? { term: `)${cpp[1]}"`, advance: cpp[0].length - 1 } : null;
538
+ }
539
+ function detectRawStringOpen(line, index, ext) {
540
+ const sub = line.slice(index);
541
+ return detectRustOrGoRawString(sub, ext) || detectCsJavaOrCppRawString(sub, ext);
542
+ }
543
+ function consumeRawOrBlock(line, index, state) {
544
+ if (state.inBlock) {
545
+ if (line[index] === "*" && line[index + 1] === "/") {
546
+ state.inBlock = false;
547
+ return { advance: 1, handled: true };
548
+ }
549
+ return { advance: 0, handled: true };
550
+ }
551
+ if (state.rawTerm) {
552
+ if (line.startsWith(state.rawTerm, index)) {
553
+ const len = state.rawTerm.length - 1;
554
+ state.rawTerm = null;
555
+ return { advance: len, handled: true };
556
+ }
557
+ return { advance: 0, handled: true };
558
+ }
559
+ return { advance: 0, handled: false };
560
+ }
561
+ function consumeNormalQuote(line, index, state) {
562
+ if (!state.inString) return { advance: 0, handled: false };
563
+ const ch = line[index];
564
+ if (ch === "\\") return { advance: 1, handled: true };
565
+ if (ch === state.inString) state.inString = null;
566
+ return { advance: 0, handled: true };
567
+ }
568
+ function applyQuoteOrBrace(ch, state) {
569
+ if (ch === '"' || ch === "'") {
570
+ state.inString = ch;
571
+ return;
572
+ }
573
+ if (ch === "{") state.delta++;
574
+ if (ch === "}") state.delta--;
575
+ }
576
+ function stepActiveCodeToken(line, j, ext, state) {
577
+ const pair = line.slice(j, j + 2);
578
+ if (pair === "//") return { advance: 0, stop: true };
579
+ if (pair === "/*") {
355
580
  state.inBlock = true;
356
581
  return { advance: 1, stop: false };
357
582
  }
358
- if (res.quote) {
359
- state.inString = res.quote;
360
- return { advance: 0, stop: false };
583
+ const rawOpen = detectRawStringOpen(line, j, ext);
584
+ if (rawOpen) {
585
+ state.rawTerm = rawOpen.term;
586
+ return { advance: rawOpen.advance, stop: false };
361
587
  }
362
- state.delta += res.delta;
588
+ applyQuoteOrBrace(line[j], state);
363
589
  return { advance: 0, stop: false };
364
590
  }
365
- function scanBracesInLine(line, wasInBlock, ext) {
366
- const state = { delta: 0, inBlock: wasInBlock, inString: null };
591
+ function scanBracesInLine(line, wasInBlock, wasRawTerm, ext) {
592
+ const state = { delta: 0, inBlock: wasInBlock, rawTerm: wasRawTerm, inString: null };
367
593
  for (let j = 0; j < line.length; j++) {
368
- const char = line[j];
369
- const next = j + 1 < line.length ? line[j + 1] : "";
370
- const skipped = stepSkippedState(char, next, state.inBlock, state.inString);
371
- if (skipped.handled) {
372
- state.inBlock = skipped.inBlock;
373
- state.inString = skipped.inString;
374
- if (skipped.skip) j++;
594
+ const rawOrBlock = consumeRawOrBlock(line, j, state);
595
+ if (rawOrBlock.handled) {
596
+ j += rawOrBlock.advance;
375
597
  continue;
376
598
  }
377
- const res = checkCodeChar(char, next, ext);
378
- const step = applyCodeChar(res, state);
379
- if (step.stop) break;
380
- j += step.advance;
599
+ const quoteStep = consumeNormalQuote(line, j, state);
600
+ if (quoteStep.handled) {
601
+ j += quoteStep.advance;
602
+ continue;
603
+ }
604
+ const codeStep = stepActiveCodeToken(line, j, ext, state);
605
+ if (codeStep.stop) break;
606
+ j += codeStep.advance;
381
607
  }
382
- return { delta: state.delta, nowInBlock: state.inBlock };
608
+ return { delta: state.delta, nowInBlock: state.inBlock, nowRawTerm: state.rawTerm };
383
609
  }
384
610
  function buildMetricsFromBlocks(blocks, content, filePath) {
385
611
  if (blocks.length === 0) {
@@ -403,11 +629,20 @@ function buildMetricsFromBlocks(blocks, content, filePath) {
403
629
  }
404
630
  function detectPolyglotSmells(body) {
405
631
  const smells = [];
406
- if (/#\s*noqa|#\s*type:\s*ignore|@SuppressWarnings/.test(body)) {
632
+ if (/#\s*noqa|#\s*type:\s*ignore|@SuppressWarnings|#pragma\s+warning\s+disable/.test(body)) {
407
633
  smells.push("Supresi\xF3n no autorizada de linter");
408
634
  }
409
635
  return smells;
410
636
  }
637
+ function stripStringsAndComments(body, ext) {
638
+ let cleaned = body;
639
+ if (ext === ".py") {
640
+ cleaned = cleaned.replace(/"""[\s\S]*?"""|'''[\s\S]*?'''/g, '""').replace(/#.*/g, "");
641
+ } else {
642
+ 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, '""');
643
+ }
644
+ return cleaned.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, '""');
645
+ }
411
646
  function computeHalsteadMI(body, cyclomatic, loc, commentCount) {
412
647
  const cleanText = body.replace(/\/\*[\s\S]*?\*\/|\/\/.*|#.*/g, "");
413
648
  const tokens = cleanText.split(/[\s,;().{}[\]=+\-*/<>!&|:]+/).filter((t) => t.length > 0);
@@ -430,11 +665,13 @@ function countCommentsAndLoc(rawLines, isPy) {
430
665
  return { loc, commentCount, cleanLines };
431
666
  }
432
667
  function calculatePolyglotMetrics(body, name, filePath, startLine) {
433
- const ext = path4.extname(filePath).toLowerCase();
668
+ const ext = path5.extname(filePath).toLowerCase();
434
669
  const rawLines = body.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
435
- const { loc, commentCount, cleanLines } = countCommentsAndLoc(rawLines, ext === ".py");
436
- const cyclomatic = computePolyglotCyclomatic(body, ext);
437
- const cognitive = computePolyglotCognitive(cleanLines, ext === ".py");
670
+ const { loc, commentCount } = countCommentsAndLoc(rawLines, ext === ".py");
671
+ const strippedBody = stripStringsAndComments(body, ext);
672
+ const strippedLines = strippedBody.split(/\r?\n/).filter((l) => l.trim().length > 0);
673
+ const cyclomatic = computePolyglotCyclomatic(strippedBody, ext);
674
+ const cognitive = computePolyglotCognitive(strippedLines, ext === ".py");
438
675
  const maintainability = computeHalsteadMI(body, cyclomatic, loc, commentCount);
439
676
  return {
440
677
  functionName: name,
@@ -447,84 +684,183 @@ function calculatePolyglotMetrics(body, name, filePath, startLine) {
447
684
  line: startLine ? startLine + 1 : 1
448
685
  };
449
686
  }
450
- function computePolyglotCyclomatic(body, ext) {
451
- let regex;
687
+ function getCyclomaticPattern(ext) {
452
688
  if (ext === ".py") {
453
- regex = /\b(if|elif|for|while|except)\b|\b(and|or)\b/g;
454
- } else if (ext === ".go") {
455
- regex = /\b(if|for|case|select)\b|&&|\|\|/g;
456
- } else if (ext === ".rs") {
457
- regex = /\b(if|for|while|match)\b|&&|\|\||\?/g;
458
- } else {
459
- regex = /\b(if|else\s+if|for|while|catch|case)\b|\?|&&|\|\|/g;
689
+ return /\b(if|elif|for|while|except|case)\b|\b(and|or)\b/g;
460
690
  }
461
- const cleanBody = body.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, "");
462
- const matches = cleanBody.match(regex);
691
+ if (ext === ".go") {
692
+ return /\b(if|for|case|select)\b|&&|\|\|/g;
693
+ }
694
+ if (ext === ".rs") {
695
+ return /\b(if|for|while|match)\b|&&|\|\||\?/g;
696
+ }
697
+ if (ext === ".cs") {
698
+ return /\b(if|else\s+if|for|foreach|while|catch|case|when)\b|\?\?|\?\.(?=[a-zA-Z_])|&&|\|\|/g;
699
+ }
700
+ if (ext === ".java") {
701
+ return /\b(if|else\s+if|for|while|catch|case|when)\b|&&|\|\|/g;
702
+ }
703
+ return /\b(if|else\s+if|for|while|catch|case)\b|\?|&&|\|\|/g;
704
+ }
705
+ function computePolyglotCyclomatic(strippedBody, ext) {
706
+ const regex = getCyclomaticPattern(ext);
707
+ const matches = strippedBody.match(regex);
463
708
  return 1 + (matches ? matches.length : 0);
464
709
  }
465
- function computePolyglotCognitive(lines, isPy) {
710
+ var PY_COGNITIVE_RE = /\b(if|elif|for|while|except|match)\b/;
711
+ var BRACE_COGNITIVE_RE = /\b(if|for|foreach|while|catch|match|select|switch)\b/;
712
+ function computePythonCognitive(lines) {
713
+ let cognitive = 0;
714
+ const baseIndent = lines.length > 0 ? lines[0].length - lines[0].trimStart().length : 0;
715
+ for (let i = 1; i < lines.length; i++) {
716
+ const line = lines[i];
717
+ if (PY_COGNITIVE_RE.test(line)) {
718
+ const relIndent = Math.max(0, line.length - line.trimStart().length - baseIndent - 4);
719
+ cognitive += 1 + Math.floor(relIndent / 4);
720
+ }
721
+ }
722
+ return cognitive;
723
+ }
724
+ function computeBraceCognitive(lines) {
466
725
  let cognitive = 0;
467
726
  let nesting = 0;
468
- for (const line of lines) {
469
- if (isPy) {
470
- const indent = line.length - line.trimStart().length;
471
- if (/\b(if|elif|for|while|except)\b/.test(line)) {
472
- cognitive += 1 + Math.floor(indent / 4);
473
- }
474
- } else {
475
- if (line.includes("{")) nesting++;
476
- if (line.includes("}")) nesting = Math.max(0, nesting - 1);
477
- if (/\b(if|for|while|catch|match|select)\b/.test(line)) {
478
- cognitive += 1 + nesting;
479
- }
727
+ for (let i = 1; i < lines.length; i++) {
728
+ const line = lines[i];
729
+ if (BRACE_COGNITIVE_RE.test(line)) {
730
+ cognitive += 1 + nesting;
480
731
  }
732
+ const opens = (line.match(/\{/g) || []).length;
733
+ const closes = (line.match(/\}/g) || []).length;
734
+ nesting = Math.max(0, nesting + opens - closes);
481
735
  }
482
736
  return cognitive;
483
737
  }
484
- function walkMdFiles(dir, fileList = [], excludeDirs = [
485
- "node_modules",
486
- ".git",
487
- "dist",
488
- "bin",
489
- "obj",
490
- "build",
491
- "out",
492
- "target",
493
- "coverage",
494
- ".changeset",
495
- "scratch",
496
- "test-scaffold",
497
- "fixtures",
498
- "templates",
499
- "examples",
500
- ".agent",
501
- ".agents",
502
- ".claude",
503
- ".cursor",
504
- ".github",
505
- ".vscode",
506
- ".vs",
507
- ".idea"
508
- ]) {
738
+ function computePolyglotCognitive(lines, isPy) {
739
+ return isPy ? computePythonCognitive(lines) : computeBraceCognitive(lines);
740
+ }
741
+
742
+ // src/verifiers/ast/python-ast.ts
743
+ var PYTHON_AST_RUNNER_SCRIPT = `
744
+ import ast, json, sys
745
+
746
+ def analyze(code, filepath):
747
+ try:
748
+ tree = ast.parse(code)
749
+ except Exception as e:
750
+ return {"language": "python", "filePath": filepath, "functions": [], "error": str(e), "unsafeTypesCount": 0, "emptyStubsCount": 0}
751
+
752
+ functions = []
753
+ unsafe_types = 0
754
+ empty_stubs = 0
755
+ for node in ast.walk(tree):
756
+ if isinstance(node, ast.Name) and node.id == "Any":
757
+ unsafe_types += 1
758
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
759
+ start = node.lineno
760
+ end = getattr(node, 'end_lineno', start + len(node.body))
761
+ loc = end - start + 1
762
+ cc = 1
763
+ for child in ast.walk(node):
764
+ if isinstance(child, (ast.If, ast.For, ast.AsyncFor, ast.While, ast.ExceptHandler, ast.With, ast.AsyncWith)):
765
+ cc += 1
766
+ elif isinstance(child, ast.BoolOp):
767
+ cc += len(child.values) - 1
768
+
769
+ real_body = [n for n in node.body if not (isinstance(n, ast.Expr) and isinstance(getattr(n, 'value', None), ast.Constant))]
770
+ is_stub = len(real_body) == 0 or (len(real_body) == 1 and isinstance(real_body[0], ast.Pass))
771
+ if is_stub:
772
+ empty_stubs += 1
773
+
774
+ code_smells = ["Empty stub detected"] if is_stub else []
775
+ mi = max(0.0, round(100.0 - (cc * 2.0) - (loc * 0.2), 1))
776
+ functions.append({
777
+ "functionName": node.name,
778
+ "filePath": filepath,
779
+ "line": start,
780
+ "loc": loc,
781
+ "cyclomatic": cc,
782
+ "cognitive": cc,
783
+ "maintainability": mi,
784
+ "codeSmells": code_smells
785
+ })
786
+ return {
787
+ "language": "python",
788
+ "filePath": filepath,
789
+ "functions": functions,
790
+ "unsafeTypesCount": unsafe_types,
791
+ "emptyStubsCount": empty_stubs
792
+ }
793
+
794
+ code = sys.stdin.read()
795
+ filepath = sys.argv[1] if len(sys.argv) > 1 else ""
796
+ print(json.dumps(analyze(code, filepath)))
797
+ `;
798
+ function extractFunctionsPythonAst(content, filePath) {
509
799
  try {
510
- if (!fs7.existsSync(dir)) return fileList;
511
- const files = fs7.readdirSync(dir);
512
- for (const file of files) {
513
- const fullPath = path4.join(dir, file);
514
- try {
515
- if (fs7.statSync(fullPath).isDirectory()) {
516
- if (!excludeDirs.includes(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
517
- walkMdFiles(fullPath, fileList, excludeDirs);
518
- }
519
- } else if (file.endsWith(".md")) {
520
- fileList.push(fullPath);
521
- }
522
- } catch {
523
- }
800
+ const res = spawnSync("python", ["-c", PYTHON_AST_RUNNER_SCRIPT, filePath], {
801
+ input: content,
802
+ encoding: "utf-8",
803
+ timeout: 1e4
804
+ });
805
+ if (res.status === 0 && res.stdout) {
806
+ const parsed = JSON.parse(res.stdout);
807
+ return parsed;
524
808
  }
525
809
  } catch {
526
810
  }
527
- return fileList;
811
+ const fallbackFns = extractPythonFunctionsPolyglot(content, filePath);
812
+ return {
813
+ language: "python",
814
+ filePath,
815
+ functions: fallbackFns,
816
+ unsafeTypesCount: 0,
817
+ emptyStubsCount: 0
818
+ };
819
+ }
820
+
821
+ // src/verifiers/ast/native-ast-orchestrator.ts
822
+ function isCliInstalled(bin) {
823
+ try {
824
+ const res = spawnSync(bin, ["--version"], { encoding: "utf-8", timeout: 5e3, shell: false });
825
+ return res.status === 0 && !res.error;
826
+ } catch {
827
+ return false;
828
+ }
829
+ }
830
+ function assertRequiredStack(stack) {
831
+ if (!stack) return;
832
+ if (!isCliInstalled(stack)) {
833
+ throw new Error(`[AI-SDLC Fail-Closed] Required toolchain CLI '${stack}' is not installed in the environment`);
834
+ }
835
+ }
836
+ function analyzeTypeScript(content, filePath) {
837
+ return {
838
+ language: "typescript",
839
+ filePath,
840
+ functions: extractFunctionsTypeScriptAst(content, filePath),
841
+ unsafeTypesCount: 0,
842
+ emptyStubsCount: 0
843
+ };
844
+ }
845
+ function analyzePolyglot(content, filePath, ext) {
846
+ return {
847
+ language: ext.replace(/^\./, ""),
848
+ filePath,
849
+ functions: extractFunctionsPolyglot(content, filePath),
850
+ unsafeTypesCount: 0,
851
+ emptyStubsCount: 0
852
+ };
853
+ }
854
+ function orchestrateNativeAstAnalysis(content, filePath, options = {}) {
855
+ assertRequiredStack(options.requiredStack);
856
+ const ext = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
857
+ if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext)) {
858
+ return analyzeTypeScript(content, filePath);
859
+ }
860
+ if (ext === ".py") {
861
+ return extractFunctionsPythonAst(content, filePath);
862
+ }
863
+ return analyzePolyglot(content, filePath, ext);
528
864
  }
529
865
  function parseMarkdownFrontmatter(content) {
530
866
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -556,41 +892,41 @@ function extractGherkinBlock(body) {
556
892
  }
557
893
  function resolveTargetFiles(rootDir, targetPath) {
558
894
  if (targetPath) {
559
- const fullTarget = path4.isAbsolute(targetPath) ? targetPath : path4.join(rootDir, targetPath);
560
- if (!fs7.existsSync(fullTarget)) return [];
561
- return fs7.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
895
+ const fullTarget = path5.isAbsolute(targetPath) ? targetPath : path5.join(rootDir, targetPath);
896
+ if (!fs8.existsSync(fullTarget)) return [];
897
+ return fs8.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
562
898
  }
563
899
  const searchDirs = [
564
- path4.join(rootDir, "product"),
565
- path4.join(rootDir, "security"),
566
- path4.join(rootDir, "privacy"),
567
- path4.join(rootDir, "specs"),
568
- path4.join(rootDir, "examples", "product"),
569
- path4.join(rootDir, "examples", "security"),
570
- path4.join(rootDir, "examples", "privacy"),
571
- path4.join(rootDir, "examples", "specs")
900
+ path5.join(rootDir, "product"),
901
+ path5.join(rootDir, "security"),
902
+ path5.join(rootDir, "privacy"),
903
+ path5.join(rootDir, "specs"),
904
+ path5.join(rootDir, "examples", "product"),
905
+ path5.join(rootDir, "examples", "security"),
906
+ path5.join(rootDir, "examples", "privacy"),
907
+ path5.join(rootDir, "examples", "specs")
572
908
  ];
573
- return searchDirs.filter((d) => fs7.existsSync(d)).flatMap((d) => walkMdFiles(d));
909
+ return searchDirs.filter((d) => fs8.existsSync(d)).flatMap((d) => walkMdFiles(d));
574
910
  }
575
911
  function resolveFeatureFilePath(rootDir, filePath, id, customTarget) {
576
912
  if (customTarget) {
577
- return path4.isAbsolute(customTarget) ? customTarget : path4.join(rootDir, customTarget);
578
- }
579
- const baseDir = path4.join(rootDir, "tests", "features");
580
- const candidateSecurity = path4.join(baseDir, "security", `${id.toLowerCase()}.feature`);
581
- const candidatePrivacy = path4.join(baseDir, "privacy", `${id.toLowerCase()}.feature`);
582
- const candidateRoot = path4.join(baseDir, `${id.toLowerCase()}.feature`);
583
- if (fs7.existsSync(candidateSecurity)) return candidateSecurity;
584
- if (fs7.existsSync(candidatePrivacy)) return candidatePrivacy;
585
- if (fs7.existsSync(candidateRoot)) return candidateRoot;
913
+ return path5.isAbsolute(customTarget) ? customTarget : path5.join(rootDir, customTarget);
914
+ }
915
+ const baseDir = path5.join(rootDir, "tests", "features");
916
+ const candidateSecurity = path5.join(baseDir, "security", `${id.toLowerCase()}.feature`);
917
+ const candidatePrivacy = path5.join(baseDir, "privacy", `${id.toLowerCase()}.feature`);
918
+ const candidateRoot = path5.join(baseDir, `${id.toLowerCase()}.feature`);
919
+ if (fs8.existsSync(candidateSecurity)) return candidateSecurity;
920
+ if (fs8.existsSync(candidatePrivacy)) return candidatePrivacy;
921
+ if (fs8.existsSync(candidateRoot)) return candidateRoot;
586
922
  const subDir = filePath.includes("security") ? "security" : filePath.includes("privacy") ? "privacy" : "";
587
- return path4.join(baseDir, subDir, `${id.toLowerCase()}.feature`);
923
+ return path5.join(baseDir, subDir, `${id.toLowerCase()}.feature`);
588
924
  }
589
925
  function buildFeatureFileContent(rootDir, filePath, id, version, gherkinBlocks) {
590
926
  const banner = [
591
927
  `# ==============================================================================`,
592
928
  `# AUTO-GENERADO POR AI-SDLC (Cucumber Integration)`,
593
- `# Origen: ${path4.relative(rootDir, filePath).replace(/\\/g, "/")}`,
929
+ `# Origen: ${path5.relative(rootDir, filePath).replace(/\\/g, "/")}`,
594
930
  `# ID Requerimiento: ${id}`,
595
931
  `# Versi\xF3n: ${version || "1.0.0"}`,
596
932
  `# NO EDITAR MANUALMENTE: Cualquier cambio debe realizarse en el Markdown origen.`,
@@ -615,20 +951,20 @@ function extractGherkinFeatures(options = {}) {
615
951
  const targetFiles = resolveTargetFiles(rootDir, options.targetPath);
616
952
  for (const filePath of targetFiles) {
617
953
  try {
618
- const content = fs7.readFileSync(filePath, "utf-8");
954
+ const content = fs8.readFileSync(filePath, "utf-8");
619
955
  const { frontmatter, body } = parseMarkdownFrontmatter(content);
620
956
  const gherkinBlocks = extractGherkinBlock(body);
621
957
  if (gherkinBlocks.length === 0) continue;
622
- const id = frontmatter.id || path4.basename(filePath, path4.extname(filePath));
958
+ const id = frontmatter.id || path5.basename(filePath, path5.extname(filePath));
623
959
  const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
624
- fs7.mkdirSync(path4.dirname(targetPath), { recursive: true });
960
+ fs8.mkdirSync(path5.dirname(targetPath), { recursive: true });
625
961
  const fullContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
626
- fs7.writeFileSync(targetPath, fullContent, "utf-8");
962
+ fs8.writeFileSync(targetPath, fullContent, "utf-8");
627
963
  const scenarioCount = countScenarios(gherkinBlocks);
628
964
  totalScenarios += scenarioCount;
629
965
  features.push({
630
- sourceFile: path4.relative(rootDir, filePath).replace(/\\/g, "/"),
631
- outputFile: path4.relative(rootDir, targetPath).replace(/\\/g, "/"),
966
+ sourceFile: path5.relative(rootDir, filePath).replace(/\\/g, "/"),
967
+ outputFile: path5.relative(rootDir, targetPath).replace(/\\/g, "/"),
632
968
  featureName: id,
633
969
  scenarioCount
634
970
  });
@@ -645,20 +981,20 @@ function checkGherkinInSync(options = {}) {
645
981
  let totalFeatures = 0;
646
982
  for (const filePath of targetFiles) {
647
983
  try {
648
- const content = fs7.readFileSync(filePath, "utf-8");
984
+ const content = fs8.readFileSync(filePath, "utf-8");
649
985
  const { frontmatter, body } = parseMarkdownFrontmatter(content);
650
986
  const gherkinBlocks = extractGherkinBlock(body);
651
987
  if (gherkinBlocks.length === 0) continue;
652
988
  totalFeatures++;
653
- const id = frontmatter.id || path4.basename(filePath, path4.extname(filePath));
989
+ const id = frontmatter.id || path5.basename(filePath, path5.extname(filePath));
654
990
  const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
655
- const relTarget = path4.relative(rootDir, targetPath).replace(/\\/g, "/");
656
- if (!fs7.existsSync(targetPath)) {
991
+ const relTarget = path5.relative(rootDir, targetPath).replace(/\\/g, "/");
992
+ if (!fs8.existsSync(targetPath)) {
657
993
  missingFiles.push(relTarget);
658
994
  continue;
659
995
  }
660
996
  const expectedContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
661
- const actualContent = fs7.readFileSync(targetPath, "utf-8");
997
+ const actualContent = fs8.readFileSync(targetPath, "utf-8");
662
998
  if (actualContent.replace(/\r\n/g, "\n") !== expectedContent.replace(/\r\n/g, "\n")) {
663
999
  outOfSyncFiles.push(relTarget);
664
1000
  }
@@ -757,7 +1093,7 @@ function generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, t
757
1093
  const reportLines = [
758
1094
  `# \u{1F9EA} Deterministic Test Coverage Audit for Requirements and Tasks`,
759
1095
  ``,
760
- `> **Audit Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
1096
+ `> **Audit Date:** ${resolveDeterministicDate().toISOString()}`,
761
1097
  `> **Overall Verdict:** ${verdict}`,
762
1098
  ``,
763
1099
  `---`,
@@ -820,8 +1156,8 @@ function parseCodeTestIds(content, relTest, reqToTestsMap) {
820
1156
  }
821
1157
  function processTestFile(fullPath, rootDir, reqToTestsMap) {
822
1158
  try {
823
- const content = fs7.readFileSync(fullPath, "utf-8");
824
- const relTest = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
1159
+ const content = fs8.readFileSync(fullPath, "utf-8");
1160
+ const relTest = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
825
1161
  if (fullPath.endsWith(".feature")) {
826
1162
  parseFeatureTags(content, relTest, reqToTestsMap);
827
1163
  }
@@ -830,16 +1166,16 @@ function processTestFile(fullPath, rootDir, reqToTestsMap) {
830
1166
  }
831
1167
  }
832
1168
  function collectTestFiles(dir, files = []) {
833
- if (!fs7.existsSync(dir)) return files;
1169
+ if (!fs8.existsSync(dir)) return files;
834
1170
  try {
835
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
1171
+ const entries = fs8.readdirSync(dir, { withFileTypes: true }).sort((a, b) => canonicalSort(a.name, b.name));
836
1172
  for (const e of entries) {
837
1173
  if (e.isDirectory()) {
838
1174
  if (!EXCLUDED_TEST_DIRS.has(e.name)) {
839
- collectTestFiles(path4.join(dir, e.name), files);
1175
+ collectTestFiles(path5.join(dir, e.name), files);
840
1176
  }
841
1177
  } else if (isTestFile(e.name)) {
842
- files.push(path4.join(dir, e.name));
1178
+ files.push(path5.join(dir, e.name));
843
1179
  }
844
1180
  }
845
1181
  } catch {
@@ -1048,7 +1384,7 @@ function collectTestCorpus(rootDir, allMdFiles) {
1048
1384
  const testContents = [];
1049
1385
  for (const tf of collectTestFiles(rootDir)) {
1050
1386
  try {
1051
- const content = fs7.readFileSync(tf, "utf-8");
1387
+ const content = fs8.readFileSync(tf, "utf-8");
1052
1388
  testContents.push(content);
1053
1389
  if (tf.endsWith(".feature")) gherkinSources.push(content);
1054
1390
  } catch {
@@ -1056,7 +1392,7 @@ function collectTestCorpus(rootDir, allMdFiles) {
1056
1392
  }
1057
1393
  for (const md of allMdFiles) {
1058
1394
  try {
1059
- const blocks = extractGherkinBlock(fs7.readFileSync(md, "utf-8"));
1395
+ const blocks = extractGherkinBlock(fs8.readFileSync(md, "utf-8"));
1060
1396
  gherkinSources.push(...blocks);
1061
1397
  testContents.push(...blocks);
1062
1398
  } catch {
@@ -1064,9 +1400,54 @@ function collectTestCorpus(rootDir, allMdFiles) {
1064
1400
  }
1065
1401
  return { gherkinSources, combinedTests: testContents.join("\n") };
1066
1402
  }
1067
- function assembleCoverageResult(reqAudit, taskAudit, useCaseFlows, reqErrors, taskErrors, ucErrors) {
1403
+ function isCucumberScenarioPassed(element) {
1404
+ const steps = Array.isArray(element.steps) ? element.steps : [];
1405
+ if (steps.length === 0) return false;
1406
+ return steps.every((s) => {
1407
+ const res = s.result;
1408
+ return res && res.status === "passed";
1409
+ });
1410
+ }
1411
+ function countCucumberScenarios(raw) {
1412
+ let passed = 0;
1413
+ let failed = 0;
1414
+ for (const feat of raw) {
1415
+ const elements = Array.isArray(feat.elements) ? feat.elements : [];
1416
+ for (const el of elements) {
1417
+ if (isCucumberScenarioPassed(el)) passed++;
1418
+ else failed++;
1419
+ }
1420
+ }
1421
+ return { passed, failed };
1422
+ }
1423
+ function parseCucumberReportFile(reportPath) {
1424
+ if (!fs8.existsSync(reportPath)) return void 0;
1425
+ try {
1426
+ const raw = JSON.parse(fs8.readFileSync(reportPath, "utf-8"));
1427
+ if (!Array.isArray(raw)) return void 0;
1428
+ const { passed, failed } = countCucumberScenarios(raw);
1429
+ return {
1430
+ evaluatedFeatures: raw.length,
1431
+ passedScenarios: passed,
1432
+ failedScenarios: failed,
1433
+ passed: passed + failed > 0 && failed === 0
1434
+ };
1435
+ } catch {
1436
+ return void 0;
1437
+ }
1438
+ }
1439
+ function resolveCucumberReport(rootDir, options) {
1440
+ const candidate = options.cucumberReportPath ? path5.isAbsolute(options.cucumberReportPath) ? options.cucumberReportPath : path5.join(rootDir, options.cucumberReportPath) : path5.join(rootDir, "reports", "cucumber-report.json");
1441
+ const parsed = parseCucumberReportFile(candidate);
1442
+ if (parsed) return parsed;
1443
+ if (options.enforceCucumberExecution) {
1444
+ return { evaluatedFeatures: 0, passedScenarios: 0, failedScenarios: 1, passed: false };
1445
+ }
1446
+ return void 0;
1447
+ }
1448
+ function assembleCoverageResult(reqAudit, taskAudit, useCaseFlows, reqErrors, taskErrors, ucErrors, rootDir, options) {
1068
1449
  return {
1069
- success: reqErrors === 0 && taskErrors === 0 && ucErrors === 0,
1450
+ success: reqErrors === 0 && taskErrors === 0 && ucErrors === 0 && (!resolveCucumberReport(rootDir, options) || resolveCucumberReport(rootDir, options).passed),
1070
1451
  totalRequirements: reqAudit.length,
1071
1452
  passedRequirements: reqAudit.length - reqErrors,
1072
1453
  failedRequirements: reqErrors,
@@ -1077,6 +1458,7 @@ function assembleCoverageResult(reqAudit, taskAudit, useCaseFlows, reqErrors, ta
1077
1458
  tasks: taskAudit,
1078
1459
  useCaseFlows,
1079
1460
  failedUseCaseFlows: ucErrors,
1461
+ ...resolveCucumberReport(rootDir, options) ? { cucumberExecution: resolveCucumberReport(rootDir, options) } : {},
1080
1462
  reportMarkdown: generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, taskErrors)
1081
1463
  };
1082
1464
  }
@@ -1089,8 +1471,8 @@ function recordUseCaseAuditIfPresent(frontmatter, id, relPath, isTemplate, combi
1089
1471
  if (flowAudit.status === "FALLO_SIN_PRUEBA") state.ucErrors++;
1090
1472
  }
1091
1473
  function processMdFileForCoverage(file, rootDir, reqToTestsMap, corpus, options, state) {
1092
- const { frontmatter } = parseGenericDoc(fs7.readFileSync(file, "utf-8"));
1093
- const relPath = path4.relative(rootDir, file).replace(/\\/g, "/");
1474
+ const { frontmatter } = parseGenericDoc(fs8.readFileSync(file, "utf-8"));
1475
+ const relPath = path5.relative(rootDir, file).replace(/\\/g, "/");
1094
1476
  const isTemplate = relPath.includes("templates/");
1095
1477
  const id = frontmatter.id || "";
1096
1478
  if (isRequirementArtifact(frontmatter, id)) {
@@ -1128,7 +1510,9 @@ function verifyTestingCoverage(options = {}) {
1128
1510
  state.useCaseFlows,
1129
1511
  state.reqErrors,
1130
1512
  state.taskErrors,
1131
- state.ucErrors
1513
+ state.ucErrors,
1514
+ rootDir,
1515
+ options
1132
1516
  );
1133
1517
  }
1134
1518
 
@@ -1162,7 +1546,6 @@ var DEFAULT_EXCLUDES = [
1162
1546
  "templates",
1163
1547
  "fixtures"
1164
1548
  ];
1165
- var TS_JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1166
1549
  function createDefaultQualityPolicy() {
1167
1550
  return {
1168
1551
  max_cyclomatic: 10,
@@ -1243,27 +1626,38 @@ function applyOptionOverrides(policy, overrides) {
1243
1626
  if (overrides.max_function_lines !== void 0) policy.max_function_lines = Number(overrides.max_function_lines);
1244
1627
  if (overrides.enforce_mode !== void 0) policy.enforce_mode = overrides.enforce_mode;
1245
1628
  }
1629
+ function parsePolicyYaml(content) {
1630
+ let doc;
1631
+ try {
1632
+ doc = yaml3.load(content);
1633
+ } catch (err) {
1634
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in quality-policy: ${err instanceof Error ? err.message : String(err)}`);
1635
+ }
1636
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
1637
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid quality policy structure: root must be a YAML mapping`);
1638
+ }
1639
+ return doc;
1640
+ }
1246
1641
  function parseQualityPolicy(content, overrides) {
1247
1642
  const policy = createDefaultQualityPolicy();
1248
- try {
1249
- const doc = yaml3.load(content);
1250
- if (doc && typeof doc === "object") {
1251
- const record = doc;
1252
- applyDocScope(policy, record);
1253
- applyDocThresholds(policy, record);
1254
- if (record.max_per_function !== void 0) policy.max_cyclomatic = Number(record.max_per_function);
1255
- if (record.min_acceptable_score !== void 0) policy.min_maintainability = Number(record.min_acceptable_score);
1256
- if (record.max_function_lines !== void 0) policy.max_function_lines = Number(record.max_function_lines);
1257
- }
1258
- } catch {
1643
+ if (!content || !content.trim()) {
1644
+ applyEnvThresholds(policy);
1645
+ applyOptionOverrides(policy, overrides);
1646
+ return policy;
1259
1647
  }
1648
+ const record = parsePolicyYaml(content);
1649
+ applyDocScope(policy, record);
1650
+ applyDocThresholds(policy, record);
1651
+ if (record.max_per_function !== void 0) policy.max_cyclomatic = Number(record.max_per_function);
1652
+ if (record.min_acceptable_score !== void 0) policy.min_maintainability = Number(record.min_acceptable_score);
1653
+ if (record.max_function_lines !== void 0) policy.max_function_lines = Number(record.max_function_lines);
1260
1654
  applyEnvThresholds(policy);
1261
1655
  applyOptionOverrides(policy, overrides);
1262
1656
  return policy;
1263
1657
  }
1264
1658
  function extractFunctions(content, filePath) {
1265
- const ext = path4.extname(filePath).toLowerCase();
1266
- return TS_JS_EXTENSIONS.includes(ext) ? extractFunctionsTypeScriptAst(content, filePath) : extractFunctionsPolyglot(content, filePath);
1659
+ const report = orchestrateNativeAstAnalysis(content, filePath);
1660
+ return report.functions;
1267
1661
  }
1268
1662
  function extractFunctionsBraceLanguages(content, filePath) {
1269
1663
  return extractFunctions(content, filePath);
@@ -1293,55 +1687,55 @@ function isExcludedFile(file, normalized, excludePatterns) {
1293
1687
  );
1294
1688
  }
1295
1689
  function walkDir(dir, extensions = DEFAULT_EXTENSIONS, excludePatterns = DEFAULT_EXCLUDES) {
1296
- if (!fs7.existsSync(dir)) return [];
1690
+ if (!fs8.existsSync(dir)) return [];
1297
1691
  const results = [];
1298
- for (const file of fs7.readdirSync(dir)) {
1299
- const fullPath = path4.join(dir, file);
1692
+ for (const file of fs8.readdirSync(dir).sort(canonicalSort)) {
1693
+ const fullPath = path5.join(dir, file);
1300
1694
  const normalized = fullPath.replace(/\\/g, "/");
1301
1695
  if (isExcludedFile(file, normalized, excludePatterns)) continue;
1302
- const stat = fs7.statSync(fullPath);
1696
+ const stat = fs8.statSync(fullPath);
1303
1697
  if (stat.isDirectory()) {
1304
1698
  results.push(...walkDir(fullPath, extensions, excludePatterns));
1305
- } else if (extensions.includes(path4.extname(file).toLowerCase())) {
1699
+ } else if (extensions.includes(path5.extname(file).toLowerCase())) {
1306
1700
  results.push(fullPath);
1307
1701
  }
1308
1702
  }
1309
1703
  return results;
1310
1704
  }
1311
1705
  function resolvePolicy(rootDir, options) {
1312
- const policyFile = options.policyPath || path4.join(rootDir, "quality-policy.yaml");
1706
+ const policyFile = options.policyPath || path5.join(rootDir, "quality-policy.yaml");
1313
1707
  if (options.policy) {
1314
1708
  const policy = { ...options.policy };
1315
1709
  applyOptionOverrides(policy, options.thresholds);
1316
1710
  return policy;
1317
1711
  }
1318
- if (fs7.existsSync(policyFile)) {
1319
- return parseQualityPolicy(fs7.readFileSync(policyFile, "utf-8"), options.thresholds);
1712
+ if (fs8.existsSync(policyFile)) {
1713
+ return parseQualityPolicy(fs8.readFileSync(policyFile, "utf-8"), options.thresholds);
1320
1714
  }
1321
1715
  return parseQualityPolicy("", options.thresholds);
1322
1716
  }
1323
1717
  function isPathExcluded(targetPath, rootDir, excludePatterns) {
1324
- const rel = path4.relative(rootDir, targetPath).replace(/\\/g, "/");
1325
- const base = path4.basename(targetPath);
1718
+ const rel = path5.relative(rootDir, targetPath).replace(/\\/g, "/");
1719
+ const base = path5.basename(targetPath);
1326
1720
  return excludePatterns.some(
1327
1721
  (pat) => base === pat || rel === pat || rel.startsWith(pat + "/") || rel.includes("/" + pat + "/") || rel.endsWith("/" + pat) || rel.includes(pat)
1328
1722
  );
1329
1723
  }
1330
1724
  function scanContainerSubdir(containerDir, subdirName, targetDir, rootDir, policy, files) {
1331
1725
  if (policy.exclude_patterns.includes(subdirName)) return;
1332
- const subTarget = path4.join(containerDir, subdirName, targetDir);
1333
- if (!fs7.existsSync(subTarget) || !fs7.statSync(subTarget).isDirectory()) return;
1726
+ const subTarget = path5.join(containerDir, subdirName, targetDir);
1727
+ if (!fs8.existsSync(subTarget) || !fs8.statSync(subTarget).isDirectory()) return;
1334
1728
  if (isPathExcluded(subTarget, rootDir, policy.exclude_patterns)) return;
1335
1729
  for (const f of walkDir(subTarget, policy.supported_extensions, policy.exclude_patterns)) {
1336
1730
  files.add(f);
1337
1731
  }
1338
1732
  }
1339
1733
  function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
1340
- const containerDir = path4.join(rootDir, container);
1341
- if (!fs7.existsSync(containerDir) || !fs7.statSync(containerDir).isDirectory()) return;
1734
+ const containerDir = path5.join(rootDir, container);
1735
+ if (!fs8.existsSync(containerDir) || !fs8.statSync(containerDir).isDirectory()) return;
1342
1736
  if (isPathExcluded(containerDir, rootDir, policy.exclude_patterns)) return;
1343
1737
  try {
1344
- for (const subdir of fs7.readdirSync(containerDir, { withFileTypes: true })) {
1738
+ for (const subdir of fs8.readdirSync(containerDir, { withFileTypes: true })) {
1345
1739
  if (subdir.isDirectory()) {
1346
1740
  scanContainerSubdir(containerDir, subdir.name, targetDir, rootDir, policy, files);
1347
1741
  }
@@ -1350,13 +1744,14 @@ function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
1350
1744
  }
1351
1745
  }
1352
1746
  function scanDirectTarget(rootDir, targetDir, policy, files) {
1353
- const fullDir = path4.join(rootDir, targetDir);
1354
- if (fs7.existsSync(fullDir) && !isPathExcluded(fullDir, rootDir, policy.exclude_patterns)) {
1747
+ const fullDir = path5.join(rootDir, targetDir);
1748
+ if (fs8.existsSync(fullDir) && !isPathExcluded(fullDir, rootDir, policy.exclude_patterns)) {
1355
1749
  for (const f of walkDir(fullDir, policy.supported_extensions, policy.exclude_patterns)) {
1356
1750
  files.add(f);
1357
1751
  }
1358
1752
  }
1359
1753
  }
1754
+ var UNIT_TEST_SUFFIXES = [".spec.ts", ".test.ts", ".spec.js", ".test.js", "_test.py", "_test.go"];
1360
1755
  function collectFilesToScan(rootDir, policy) {
1361
1756
  const files = /* @__PURE__ */ new Set();
1362
1757
  const monorepoContainers = ["packages", "apps", "libs", "modules", "services"];
@@ -1366,7 +1761,7 @@ function collectFilesToScan(rootDir, policy) {
1366
1761
  scanMonorepoContainer(rootDir, container, targetDir, policy, files);
1367
1762
  }
1368
1763
  }
1369
- return Array.from(files);
1764
+ return Array.from(files).filter((f) => !UNIT_TEST_SUFFIXES.some((s) => f.endsWith(s)));
1370
1765
  }
1371
1766
  function evaluateViolations(fn, policy) {
1372
1767
  const violations = [];
@@ -1388,38 +1783,67 @@ function evaluateViolations(fn, policy) {
1388
1783
  return violations;
1389
1784
  }
1390
1785
  function evaluateFileQuality(file, rootDir, policy) {
1391
- const content = fs7.readFileSync(file, "utf-8");
1786
+ const content = fs8.readFileSync(file, "utf-8");
1392
1787
  const fns = extractFunctions(content, file);
1393
1788
  return fns.map((fn) => {
1394
1789
  const violations = evaluateViolations(fn, policy);
1395
1790
  return {
1396
1791
  ...fn,
1397
- relPath: path4.relative(rootDir, file).replace(/\\/g, "/"),
1792
+ relPath: path5.relative(rootDir, file).replace(/\\/g, "/"),
1398
1793
  status: violations.length === 0 ? "PASS" : "FAIL",
1399
1794
  violations
1400
1795
  };
1401
1796
  });
1402
1797
  }
1403
- var UNIT_TEST_SUFFIXES = [".spec.ts", ".test.ts", ".spec.js", ".test.js", "_test.py", "_test.go"];
1404
1798
  function collectUnitTestCorpus(rootDir) {
1405
1799
  const allCodeFiles = walkDir(rootDir, DEFAULT_EXTENSIONS, DEFAULT_EXCLUDES);
1406
1800
  const chunks = [];
1407
1801
  for (const tf of allCodeFiles) {
1408
1802
  if (!UNIT_TEST_SUFFIXES.some((s) => tf.endsWith(s))) continue;
1409
1803
  try {
1410
- chunks.push(fs7.readFileSync(tf, "utf-8"));
1804
+ chunks.push(fs8.readFileSync(tf, "utf-8"));
1411
1805
  } catch {
1412
1806
  }
1413
1807
  }
1414
1808
  return chunks.join("\n");
1415
1809
  }
1810
+ function sanitizeTestCorpus(corpus, fnName) {
1811
+ let cleaned = corpus.replace(/\/\*[\s\S]*?\*\//g, " ");
1812
+ cleaned = cleaned.replace(/(?:\/\/|#)[^\r\n]*/g, " ");
1813
+ cleaned = cleaned.replace(/(['"`])((?:\\.|(?!\1)[^\\])*)\1/g, (match, quote, inner) => {
1814
+ const trimmed = inner.trim();
1815
+ if (trimmed === fnName || trimmed === `#${fnName}` || trimmed === `.${fnName}` || trimmed === `${fnName}()`) {
1816
+ return match;
1817
+ }
1818
+ return `${quote}${quote}`;
1819
+ });
1820
+ return cleaned;
1821
+ }
1822
+ function buildTestReferencePatterns(escaped) {
1823
+ return [
1824
+ new RegExp(`(?:\\.|\\b)${escaped}\\s*\\(`, "m"),
1825
+ new RegExp(`\\b(?:import|require)\\b[^;\\n]*\\b${escaped}\\b`, "m"),
1826
+ new RegExp(`(?:\\bexpect|\\bassert|\\bshould)\\s*\\([^;\\n]*\\b${escaped}\\b`, "m"),
1827
+ new RegExp("(?:\\bspyOn|\\bmock)\\s*\\([^;\\n]*['\"`]" + escaped + "['\"`]", "m"),
1828
+ new RegExp(`\\b${escaped}\\s*\\.`, "m"),
1829
+ new RegExp("\\b(?:describe|it|test|context)\\s*\\(\\s*['\"`](?:#|\\.)?" + escaped + "['\"`]", "m"),
1830
+ new RegExp(`\\btest_?${escaped}\\b`, "im")
1831
+ ];
1832
+ }
1833
+ function isFunctionReferencedInTests(fnName, rawCorpus) {
1834
+ if (!fnName || fnName === "anonymous" || fnName === "constructor") return true;
1835
+ const corpus = sanitizeTestCorpus(rawCorpus, fnName);
1836
+ const escaped = fnName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1837
+ const patterns = buildTestReferencePatterns(escaped);
1838
+ return patterns.some((p) => p.test(corpus));
1839
+ }
1416
1840
  function auditUnitTestsPerFunction(results, rootDir) {
1417
1841
  const corpus = collectUnitTestCorpus(rootDir);
1418
1842
  const untested = [];
1419
1843
  for (const r of results) {
1420
1844
  const fnName = r.functionName.includes(".") ? r.functionName.split(".").pop() : r.functionName;
1421
1845
  if (!fnName || fnName === "anonymous" || fnName === "constructor") continue;
1422
- if (!corpus.includes(fnName)) {
1846
+ if (!isFunctionReferencedInTests(fnName, corpus)) {
1423
1847
  untested.push({ file: r.relPath, functionName: r.functionName });
1424
1848
  r.violations.push(`Missing unit test for function '${r.functionName}'`);
1425
1849
  r.status = "FAIL";
@@ -1429,11 +1853,11 @@ function auditUnitTestsPerFunction(results, rootDir) {
1429
1853
  }
1430
1854
  function isVitestMidFlightCoverage(rel, fullPath) {
1431
1855
  if (!process.env.VITEST || rel !== "coverage/coverage-summary.json") return false;
1432
- return path4.resolve(fullPath) === path4.resolve(process.cwd(), rel);
1856
+ return path5.resolve(fullPath) === path5.resolve(process.cwd(), rel);
1433
1857
  }
1434
1858
  function parseCoverageSummaryFile(filePath, source) {
1435
1859
  try {
1436
- const raw = JSON.parse(fs7.readFileSync(filePath, "utf-8"));
1860
+ const raw = JSON.parse(fs8.readFileSync(filePath, "utf-8"));
1437
1861
  const total = raw.total;
1438
1862
  if (!total) return null;
1439
1863
  return {
@@ -1448,8 +1872,8 @@ function parseCoverageSummaryFile(filePath, source) {
1448
1872
  }
1449
1873
  function readSummaryCoverageJson(rootDir) {
1450
1874
  for (const rel of ["coverage/coverage-summary.json", "reports/coverage-summary.json"]) {
1451
- const full = path4.join(rootDir, rel);
1452
- if (!fs7.existsSync(full) || isVitestMidFlightCoverage(rel, full)) continue;
1875
+ const full = path5.join(rootDir, rel);
1876
+ if (!fs8.existsSync(full) || isVitestMidFlightCoverage(rel, full)) continue;
1453
1877
  const summary = parseCoverageSummaryFile(full, rel);
1454
1878
  if (summary) return summary;
1455
1879
  }
@@ -1473,7 +1897,7 @@ function evaluateCoverageThresholds(rootDir, policy, requireReport) {
1473
1897
  }
1474
1898
  function computeViolationSignature(relPath, fnName, metric) {
1475
1899
  const normPath = relPath.replace(/\\/g, "/");
1476
- return crypto.createHash("sha256").update(normPath + ":" + fnName + ":" + metric).digest("hex");
1900
+ return crypto3.createHash("sha256").update(normPath + ":" + fnName + ":" + metric).digest("hex");
1477
1901
  }
1478
1902
  function classifyMetricFromMessage(msg, fn) {
1479
1903
  if (msg.startsWith("Cyclomatic Complexity")) {
@@ -1502,8 +1926,8 @@ function isMetricWorsened(baselineEntry, current) {
1502
1926
  }
1503
1927
  function loadQualityBaseline(baselineFile) {
1504
1928
  try {
1505
- if (!fs7.existsSync(baselineFile)) return null;
1506
- const content = fs7.readFileSync(baselineFile, "utf-8");
1929
+ if (!fs8.existsSync(baselineFile)) return null;
1930
+ const content = fs8.readFileSync(baselineFile, "utf-8");
1507
1931
  return JSON.parse(content);
1508
1932
  } catch {
1509
1933
  return null;
@@ -1512,11 +1936,11 @@ function loadQualityBaseline(baselineFile) {
1512
1936
  function writeQualityBaseline(baselineFile, entries) {
1513
1937
  const baseline = {
1514
1938
  version: "1.0",
1515
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1939
+ generatedAt: resolveDeterministicDate(path5.dirname(baselineFile)).toISOString(),
1516
1940
  totalViolations: entries.length,
1517
1941
  violations: entries
1518
1942
  };
1519
- fs7.writeFileSync(baselineFile, JSON.stringify(baseline, null, 2) + "\n", "utf-8");
1943
+ fs8.writeFileSync(baselineFile, JSON.stringify(baseline, null, 2) + "\n", "utf-8");
1520
1944
  return baseline;
1521
1945
  }
1522
1946
  function extractBaselineEntriesFromResults(results) {
@@ -1575,7 +1999,7 @@ function applyRatchetMode(results, baseline) {
1575
1999
  function resolveBaselineFilePath(rootDir, options) {
1576
2000
  if (options.baselinePath) return options.baselinePath;
1577
2001
  if (typeof options.baseline === "string") return options.baseline;
1578
- return path4.join(rootDir, "quality-baseline.json");
2002
+ return path5.join(rootDir, "quality-baseline.json");
1579
2003
  }
1580
2004
  function isRatchetModeActive(policy, options, baselineExists) {
1581
2005
  if (options.ratchet !== void 0) return options.ratchet;
@@ -1583,7 +2007,7 @@ function isRatchetModeActive(policy, options, baselineExists) {
1583
2007
  return Boolean(policy.ratchet_mode || options.baseline && baselineExists);
1584
2008
  }
1585
2009
  function evaluateRatchetIfActive(results, baselineFile, isActive) {
1586
- if (!isActive || !fs7.existsSync(baselineFile)) {
2010
+ if (!isActive || !fs8.existsSync(baselineFile)) {
1587
2011
  return { toleratedViolationsCount: 0, newViolationsCount: 0, baselineViolationsCount: 0 };
1588
2012
  }
1589
2013
  const baseline = loadQualityBaseline(baselineFile);
@@ -1642,7 +2066,7 @@ function verifyQualityGate(options = {}) {
1642
2066
  baselineViolationsCount: entries.length
1643
2067
  });
1644
2068
  }
1645
- const baselineExists = fs7.existsSync(baselineFile);
2069
+ const baselineExists = fs8.existsSync(baselineFile);
1646
2070
  const isRatchet = isRatchetModeActive(policy, options, baselineExists);
1647
2071
  const ratchetEval = evaluateRatchetIfActive(results, baselineFile, isRatchet);
1648
2072
  return buildQualityGateResult(results, allFiles.length, policy, coverageViolations, untestedFunctions, {
@@ -1660,9 +2084,9 @@ function normalizeHandoffPayload(handoff, changeId) {
1660
2084
  };
1661
2085
  }
1662
2086
  function parseChangeProfile(specFile) {
1663
- if (!fs7.existsSync(specFile)) return "standard";
2087
+ if (!fs8.existsSync(specFile)) return "standard";
1664
2088
  try {
1665
- const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
2089
+ const rawSpec = fs8.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1666
2090
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1667
2091
  if (match) {
1668
2092
  const parsedFm = yaml3.load(match[1]);
@@ -1684,12 +2108,12 @@ function checkSpecGherkinFormat(parsedFm, rawSpec, changeWorkspaceDir, errors) {
1684
2108
  }
1685
2109
  }
1686
2110
  function validateSpecFileContent(specFile, changeWorkspaceDir, errors) {
1687
- if (!fs7.existsSync(specFile)) {
2111
+ if (!fs8.existsSync(specFile)) {
1688
2112
  errors.push(`Falta el archivo obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
1689
2113
  return;
1690
2114
  }
1691
2115
  try {
1692
- const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
2116
+ const rawSpec = fs8.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1693
2117
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1694
2118
  if (match) {
1695
2119
  const parsedFm = yaml3.load(match[1]);
@@ -1708,19 +2132,19 @@ function validateHandoffSidecar(handoff, changeWorkspaceDir, errors) {
1708
2132
  if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
1709
2133
  errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
1710
2134
  }
1711
- return path4.join(changeWorkspaceDir, "handoff.yaml");
2135
+ return path5.join(changeWorkspaceDir, "handoff.yaml");
1712
2136
  }
1713
2137
  function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
1714
- const proposalFile = path4.join(changeWorkspaceDir, "proposal.md");
1715
- const tasksFile = path4.join(changeWorkspaceDir, "tasks.md");
2138
+ const proposalFile = path5.join(changeWorkspaceDir, "proposal.md");
2139
+ const tasksFile = path5.join(changeWorkspaceDir, "tasks.md");
1716
2140
  if (profile === "patch") {
1717
2141
  const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
1718
- return { handoff: handoff2, sidecarFile: handoff2 ? path4.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
2142
+ return { handoff: handoff2, sidecarFile: handoff2 ? path5.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
1719
2143
  }
1720
- if (!fs7.existsSync(proposalFile)) {
2144
+ if (!fs8.existsSync(proposalFile)) {
1721
2145
  warnings.push(`Falta 'proposal.md' en el espacio de trabajo OpenSpec.`);
1722
2146
  }
1723
- if (!fs7.existsSync(tasksFile)) {
2147
+ if (!fs8.existsSync(tasksFile)) {
1724
2148
  errors.push(`Falta el archivo obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
1725
2149
  }
1726
2150
  const handoff = adapter.loadSidecar(changeWorkspaceDir);
@@ -1728,20 +2152,20 @@ function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors,
1728
2152
  return { handoff, sidecarFile };
1729
2153
  }
1730
2154
  function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
1731
- const changeId = path4.basename(changeWorkspaceDir);
1732
- const proposalFile = path4.join(changeWorkspaceDir, "proposal.md");
1733
- const specFile = path4.join(changeWorkspaceDir, "spec.md");
1734
- const designFile = path4.join(changeWorkspaceDir, "design.md");
1735
- const tasksFile = path4.join(changeWorkspaceDir, "tasks.md");
2155
+ const changeId = path5.basename(changeWorkspaceDir);
2156
+ const proposalFile = path5.join(changeWorkspaceDir, "proposal.md");
2157
+ const specFile = path5.join(changeWorkspaceDir, "spec.md");
2158
+ const designFile = path5.join(changeWorkspaceDir, "design.md");
2159
+ const tasksFile = path5.join(changeWorkspaceDir, "tasks.md");
1736
2160
  return {
1737
2161
  framework: "openspec",
1738
2162
  changeId,
1739
2163
  changeDir: changeWorkspaceDir,
1740
2164
  profile,
1741
- proposalFile: fs7.existsSync(proposalFile) ? proposalFile : void 0,
1742
- specFile: fs7.existsSync(specFile) ? specFile : void 0,
1743
- designFile: fs7.existsSync(designFile) ? designFile : void 0,
1744
- tasksFile: fs7.existsSync(tasksFile) ? tasksFile : void 0,
2165
+ proposalFile: fs8.existsSync(proposalFile) ? proposalFile : void 0,
2166
+ specFile: fs8.existsSync(specFile) ? specFile : void 0,
2167
+ designFile: fs8.existsSync(designFile) ? designFile : void 0,
2168
+ tasksFile: fs8.existsSync(tasksFile) ? tasksFile : void 0,
1745
2169
  sidecarFile,
1746
2170
  handoff: handoff || void 0
1747
2171
  };
@@ -1749,28 +2173,28 @@ function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sideca
1749
2173
  var OpenSpecAdapter = class {
1750
2174
  framework = "openspec";
1751
2175
  getChangeWorkspaceDir(rootDir, changeId) {
1752
- const openspecDir = path4.join(rootDir, "openspec", "changes", changeId);
1753
- if (fs7.existsSync(openspecDir)) return openspecDir;
1754
- const activeChangeDir = path4.join(rootDir, "specs", "changes", "active", changeId);
1755
- if (fs7.existsSync(activeChangeDir)) return activeChangeDir;
1756
- const completedChangeDir = path4.join(rootDir, "specs", "changes", "completed", changeId);
1757
- if (fs7.existsSync(completedChangeDir)) return completedChangeDir;
2176
+ const openspecDir = path5.join(rootDir, "openspec", "changes", changeId);
2177
+ if (fs8.existsSync(openspecDir)) return openspecDir;
2178
+ const activeChangeDir = path5.join(rootDir, "specs", "changes", "active", changeId);
2179
+ if (fs8.existsSync(activeChangeDir)) return activeChangeDir;
2180
+ const completedChangeDir = path5.join(rootDir, "specs", "changes", "completed", changeId);
2181
+ if (fs8.existsSync(completedChangeDir)) return completedChangeDir;
1758
2182
  return activeChangeDir;
1759
2183
  }
1760
2184
  depositSidecar(options) {
1761
2185
  const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
1762
- if (!fs7.existsSync(changeDir)) {
1763
- fs7.mkdirSync(changeDir, { recursive: true });
2186
+ if (!fs8.existsSync(changeDir)) {
2187
+ fs8.mkdirSync(changeDir, { recursive: true });
1764
2188
  }
1765
2189
  const handoff = normalizeHandoffPayload(options.handoff, options.changeId);
1766
2190
  const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
1767
- const filePath = path4.join(changeDir, filename);
2191
+ const filePath = path5.join(changeDir, filename);
1768
2192
  const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3.dump(handoff, { indent: 2, lineWidth: -1 });
1769
- fs7.writeFileSync(filePath, content, "utf-8");
2193
+ fs8.writeFileSync(filePath, content, "utf-8");
1770
2194
  return filePath;
1771
2195
  }
1772
2196
  loadSidecar(changeWorkspaceDir) {
1773
- if (!fs7.existsSync(changeWorkspaceDir)) return null;
2197
+ if (!fs8.existsSync(changeWorkspaceDir)) return null;
1774
2198
  const candidateFiles = [
1775
2199
  "handoff.yaml",
1776
2200
  "handoff.yml",
@@ -1781,10 +2205,10 @@ var OpenSpecAdapter = class {
1781
2205
  "product-handoff.json"
1782
2206
  ];
1783
2207
  for (const file of candidateFiles) {
1784
- const fullPath = path4.join(changeWorkspaceDir, file);
1785
- if (fs7.existsSync(fullPath)) {
2208
+ const fullPath = path5.join(changeWorkspaceDir, file);
2209
+ if (fs8.existsSync(fullPath)) {
1786
2210
  try {
1787
- const raw = fs7.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
2211
+ const raw = fs8.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
1788
2212
  const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3.load(raw);
1789
2213
  if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
1790
2214
  return parsed;
@@ -1796,7 +2220,7 @@ var OpenSpecAdapter = class {
1796
2220
  return null;
1797
2221
  }
1798
2222
  validateWorkspace(changeWorkspaceDir) {
1799
- if (!fs7.existsSync(changeWorkspaceDir)) {
2223
+ if (!fs8.existsSync(changeWorkspaceDir)) {
1800
2224
  return {
1801
2225
  valid: false,
1802
2226
  errors: [`El directorio del cambio OpenSpec no existe: ${changeWorkspaceDir}`],
@@ -1805,7 +2229,7 @@ var OpenSpecAdapter = class {
1805
2229
  }
1806
2230
  const errors = [];
1807
2231
  const warnings = [];
1808
- const specFile = path4.join(changeWorkspaceDir, "spec.md");
2232
+ const specFile = path5.join(changeWorkspaceDir, "spec.md");
1809
2233
  const profile = parseChangeProfile(specFile);
1810
2234
  validateSpecFileContent(specFile, changeWorkspaceDir, errors);
1811
2235
  const { handoff, sidecarFile } = validateOpenSpecStructure(profile, changeWorkspaceDir, this, errors, warnings);
@@ -1819,9 +2243,9 @@ var OpenSpecAdapter = class {
1819
2243
  }
1820
2244
  };
1821
2245
  function parseChangeProfile2(specFile) {
1822
- if (!fs7.existsSync(specFile)) return "standard";
2246
+ if (!fs8.existsSync(specFile)) return "standard";
1823
2247
  try {
1824
- const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
2248
+ const rawSpec = fs8.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1825
2249
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1826
2250
  if (match) {
1827
2251
  const parsedFm = yaml3.load(match[1]);
@@ -1843,12 +2267,12 @@ function checkSpecGherkinFormat2(parsedFm, rawSpec, changeWorkspaceDir, errors)
1843
2267
  }
1844
2268
  }
1845
2269
  function validateSpecFileContent2(specFile, changeWorkspaceDir, errors) {
1846
- if (!fs7.existsSync(specFile)) {
2270
+ if (!fs8.existsSync(specFile)) {
1847
2271
  errors.push(`Falta el archivo de especificaci\xF3n obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
1848
2272
  return;
1849
2273
  }
1850
2274
  try {
1851
- const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
2275
+ const rawSpec = fs8.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1852
2276
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1853
2277
  if (match) {
1854
2278
  const parsedFm = yaml3.load(match[1]);
@@ -1867,19 +2291,19 @@ function validateHandoffSidecar2(handoff, changeWorkspaceDir, errors) {
1867
2291
  if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
1868
2292
  errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
1869
2293
  }
1870
- return path4.join(changeWorkspaceDir, "handoff.yaml");
2294
+ return path5.join(changeWorkspaceDir, "handoff.yaml");
1871
2295
  }
1872
2296
  function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
1873
- const planFile = path4.join(changeWorkspaceDir, "plan.md");
1874
- const tasksFile = path4.join(changeWorkspaceDir, "tasks.md");
2297
+ const planFile = path5.join(changeWorkspaceDir, "plan.md");
2298
+ const tasksFile = path5.join(changeWorkspaceDir, "tasks.md");
1875
2299
  if (profile === "patch") {
1876
2300
  const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
1877
- return { handoff: handoff2, sidecarFile: handoff2 ? path4.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
2301
+ return { handoff: handoff2, sidecarFile: handoff2 ? path5.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
1878
2302
  }
1879
- if (!fs7.existsSync(tasksFile)) {
2303
+ if (!fs8.existsSync(tasksFile)) {
1880
2304
  errors.push(`Falta el archivo de tareas obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
1881
2305
  }
1882
- if (!fs7.existsSync(planFile)) {
2306
+ if (!fs8.existsSync(planFile)) {
1883
2307
  warnings.push(`Se recomienda incluir 'plan.md' en el espacio de trabajo Spec Kit.`);
1884
2308
  }
1885
2309
  const handoff = adapter.loadSidecar(changeWorkspaceDir);
@@ -1887,18 +2311,18 @@ function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors,
1887
2311
  return { handoff, sidecarFile };
1888
2312
  }
1889
2313
  function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
1890
- const changeId = path4.basename(changeWorkspaceDir);
1891
- const specFile = path4.join(changeWorkspaceDir, "spec.md");
1892
- const planFile = path4.join(changeWorkspaceDir, "plan.md");
1893
- const tasksFile = path4.join(changeWorkspaceDir, "tasks.md");
2314
+ const changeId = path5.basename(changeWorkspaceDir);
2315
+ const specFile = path5.join(changeWorkspaceDir, "spec.md");
2316
+ const planFile = path5.join(changeWorkspaceDir, "plan.md");
2317
+ const tasksFile = path5.join(changeWorkspaceDir, "tasks.md");
1894
2318
  return {
1895
2319
  framework: "speckit",
1896
2320
  changeId,
1897
2321
  changeDir: changeWorkspaceDir,
1898
2322
  profile,
1899
- specFile: fs7.existsSync(specFile) ? specFile : void 0,
1900
- designFile: fs7.existsSync(planFile) ? planFile : void 0,
1901
- tasksFile: fs7.existsSync(tasksFile) ? tasksFile : void 0,
2323
+ specFile: fs8.existsSync(specFile) ? specFile : void 0,
2324
+ designFile: fs8.existsSync(planFile) ? planFile : void 0,
2325
+ tasksFile: fs8.existsSync(tasksFile) ? tasksFile : void 0,
1902
2326
  sidecarFile,
1903
2327
  handoff: handoff || void 0
1904
2328
  };
@@ -1906,18 +2330,18 @@ function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecar
1906
2330
  var SpecKitAdapter = class {
1907
2331
  framework = "speckit";
1908
2332
  getChangeWorkspaceDir(rootDir, changeId) {
1909
- const standardDir = path4.join(rootDir, "specs", changeId);
1910
- if (fs7.existsSync(standardDir)) return standardDir;
1911
- const activeChangeDir = path4.join(rootDir, "specs", "changes", "active", changeId);
1912
- if (fs7.existsSync(activeChangeDir)) return activeChangeDir;
1913
- const completedChangeDir = path4.join(rootDir, "specs", "changes", "completed", changeId);
1914
- if (fs7.existsSync(completedChangeDir)) return completedChangeDir;
2333
+ const standardDir = path5.join(rootDir, "specs", changeId);
2334
+ if (fs8.existsSync(standardDir)) return standardDir;
2335
+ const activeChangeDir = path5.join(rootDir, "specs", "changes", "active", changeId);
2336
+ if (fs8.existsSync(activeChangeDir)) return activeChangeDir;
2337
+ const completedChangeDir = path5.join(rootDir, "specs", "changes", "completed", changeId);
2338
+ if (fs8.existsSync(completedChangeDir)) return completedChangeDir;
1915
2339
  return standardDir;
1916
2340
  }
1917
2341
  depositSidecar(options) {
1918
2342
  const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
1919
- if (!fs7.existsSync(changeDir)) {
1920
- fs7.mkdirSync(changeDir, { recursive: true });
2343
+ if (!fs8.existsSync(changeDir)) {
2344
+ fs8.mkdirSync(changeDir, { recursive: true });
1921
2345
  }
1922
2346
  const handoff = { ...options.handoff };
1923
2347
  if (!handoff.id.startsWith("HOF-")) {
@@ -1930,20 +2354,20 @@ var SpecKitAdapter = class {
1930
2354
  handoff.createdAt = (/* @__PURE__ */ new Date()).toISOString();
1931
2355
  }
1932
2356
  const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
1933
- const filePath = path4.join(changeDir, filename);
2357
+ const filePath = path5.join(changeDir, filename);
1934
2358
  const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3.dump(handoff, { indent: 2, lineWidth: -1 });
1935
- fs7.writeFileSync(filePath, content, "utf-8");
1936
- const specifyMemoryDir = path4.join(options.rootDir, ".specify", "memory");
1937
- if (fs7.existsSync(path4.join(options.rootDir, ".specify"))) {
1938
- if (!fs7.existsSync(specifyMemoryDir)) {
1939
- fs7.mkdirSync(specifyMemoryDir, { recursive: true });
2359
+ fs8.writeFileSync(filePath, content, "utf-8");
2360
+ const specifyMemoryDir = path5.join(options.rootDir, ".specify", "memory");
2361
+ if (fs8.existsSync(path5.join(options.rootDir, ".specify"))) {
2362
+ if (!fs8.existsSync(specifyMemoryDir)) {
2363
+ fs8.mkdirSync(specifyMemoryDir, { recursive: true });
1940
2364
  }
1941
- fs7.writeFileSync(path4.join(specifyMemoryDir, `${options.changeId}.handoff.yaml`), content, "utf-8");
2365
+ fs8.writeFileSync(path5.join(specifyMemoryDir, `${options.changeId}.handoff.yaml`), content, "utf-8");
1942
2366
  }
1943
2367
  return filePath;
1944
2368
  }
1945
2369
  loadSidecar(changeWorkspaceDir) {
1946
- if (!fs7.existsSync(changeWorkspaceDir)) return null;
2370
+ if (!fs8.existsSync(changeWorkspaceDir)) return null;
1947
2371
  const candidateFiles = [
1948
2372
  "handoff.yaml",
1949
2373
  "handoff.yml",
@@ -1953,10 +2377,10 @@ var SpecKitAdapter = class {
1953
2377
  "handoff.json"
1954
2378
  ];
1955
2379
  for (const file of candidateFiles) {
1956
- const fullPath = path4.join(changeWorkspaceDir, file);
1957
- if (fs7.existsSync(fullPath)) {
2380
+ const fullPath = path5.join(changeWorkspaceDir, file);
2381
+ if (fs8.existsSync(fullPath)) {
1958
2382
  try {
1959
- const raw = fs7.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
2383
+ const raw = fs8.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
1960
2384
  const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3.load(raw);
1961
2385
  if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
1962
2386
  return parsed;
@@ -1968,7 +2392,7 @@ var SpecKitAdapter = class {
1968
2392
  return null;
1969
2393
  }
1970
2394
  validateWorkspace(changeWorkspaceDir) {
1971
- if (!fs7.existsSync(changeWorkspaceDir)) {
2395
+ if (!fs8.existsSync(changeWorkspaceDir)) {
1972
2396
  return {
1973
2397
  valid: false,
1974
2398
  errors: [`El directorio del cambio Spec Kit no existe: ${changeWorkspaceDir}`],
@@ -1977,7 +2401,7 @@ var SpecKitAdapter = class {
1977
2401
  }
1978
2402
  const errors = [];
1979
2403
  const warnings = [];
1980
- const specFile = path4.join(changeWorkspaceDir, "spec.md");
2404
+ const specFile = path5.join(changeWorkspaceDir, "spec.md");
1981
2405
  const profile = parseChangeProfile2(specFile);
1982
2406
  validateSpecFileContent2(specFile, changeWorkspaceDir, errors);
1983
2407
  const { handoff, sidecarFile } = validateSpecKitStructure(profile, changeWorkspaceDir, this, errors, warnings);
@@ -2149,27 +2573,27 @@ ${frontmatter}---
2149
2573
  ${body}`;
2150
2574
  }
2151
2575
  function materializeCanonicalArtifact(rootDir, item, changeId, author, date) {
2152
- const targetPath = path4.join(rootDir, item.targetDir, `${item.id}.md`);
2153
- if (fs7.existsSync(targetPath)) return targetPath;
2154
- fs7.mkdirSync(path4.dirname(targetPath), { recursive: true });
2576
+ const targetPath = path5.join(rootDir, item.targetDir, `${item.id}.md`);
2577
+ if (fs8.existsSync(targetPath)) return targetPath;
2578
+ fs8.mkdirSync(path5.dirname(targetPath), { recursive: true });
2155
2579
  const initialContent = buildCanonicalInitialContent(item, changeId, author, date);
2156
- fs7.writeFileSync(targetPath, initialContent, "utf-8");
2580
+ fs8.writeFileSync(targetPath, initialContent, "utf-8");
2157
2581
  return targetPath;
2158
2582
  }
2159
2583
  function findChangeWorkspaceDir(rootDir, changeId) {
2160
- const activeDir = path4.join(rootDir, "specs", "changes", "active", changeId);
2161
- if (fs7.existsSync(activeDir)) return activeDir;
2162
- const completedDir = path4.join(rootDir, "specs", "changes", "completed", changeId);
2163
- if (fs7.existsSync(completedDir)) return completedDir;
2164
- const openspecActive = path4.join(rootDir, "openspec", "changes", changeId);
2165
- if (fs7.existsSync(openspecActive)) return openspecActive;
2584
+ const activeDir = path5.join(rootDir, "specs", "changes", "active", changeId);
2585
+ if (fs8.existsSync(activeDir)) return activeDir;
2586
+ const completedDir = path5.join(rootDir, "specs", "changes", "completed", changeId);
2587
+ if (fs8.existsSync(completedDir)) return completedDir;
2588
+ const openspecActive = path5.join(rootDir, "openspec", "changes", changeId);
2589
+ if (fs8.existsSync(openspecActive)) return openspecActive;
2166
2590
  return null;
2167
2591
  }
2168
2592
  function checkTasksExecution(changeDir, changeId) {
2169
- const tasksFile = path4.join(changeDir, "tasks.md");
2593
+ const tasksFile = path5.join(changeDir, "tasks.md");
2170
2594
  const allTasks = [];
2171
- if (fs7.existsSync(tasksFile)) {
2172
- allTasks.push(...parseTasksFromContent(fs7.readFileSync(tasksFile, "utf-8")));
2595
+ if (fs8.existsSync(tasksFile)) {
2596
+ allTasks.push(...parseTasksFromContent(fs8.readFileSync(tasksFile, "utf-8")));
2173
2597
  }
2174
2598
  const completedTasks = allTasks.filter((t) => t.status === "COMPLETED").map((t) => t.id);
2175
2599
  const pendingTasks = allTasks.filter((t) => t.status !== "COMPLETED").map((t) => t.id);
@@ -2201,9 +2625,9 @@ function buildSubgraphFromProposalIds(ids) {
2201
2625
  function resolveChangeHandoff(changeDir, changeId) {
2202
2626
  const sidecar = loadProductHandoffSidecar(changeDir);
2203
2627
  if (sidecar) return sidecar;
2204
- const proposalFile = path4.join(changeDir, "proposal.md");
2205
- if (!fs7.existsSync(proposalFile)) return null;
2206
- const propContent = fs7.readFileSync(proposalFile, "utf-8");
2628
+ const proposalFile = path5.join(changeDir, "proposal.md");
2629
+ if (!fs8.existsSync(proposalFile)) return null;
2630
+ const propContent = fs8.readFileSync(proposalFile, "utf-8");
2207
2631
  const { frontmatter } = parseFrontmatter(propContent);
2208
2632
  const citations = Array.isArray(frontmatter.citations) ? frontmatter.citations : [];
2209
2633
  const ids = citations.map((c) => c.id || "").filter(Boolean);
@@ -2217,9 +2641,9 @@ function resolveChangeHandoff(changeDir, changeId) {
2217
2641
  }
2218
2642
  function materializeNewCatalogArtifacts(changeDir, rootDir, changeId, author, date, errors) {
2219
2643
  const created = [];
2220
- const specFile = path4.join(changeDir, "spec.md");
2221
- if (!fs7.existsSync(specFile)) return created;
2222
- const specContent = fs7.readFileSync(specFile, "utf-8");
2644
+ const specFile = path5.join(changeDir, "spec.md");
2645
+ if (!fs8.existsSync(specFile)) return created;
2646
+ const specContent = fs8.readFileSync(specFile, "utf-8");
2223
2647
  const { newItems } = parseSpecCatalogScope(specContent);
2224
2648
  for (const item of newItems) {
2225
2649
  try {
@@ -2236,10 +2660,10 @@ function loadCanonicalArtifacts(rootDir) {
2236
2660
  const allMdFiles = walkMdFiles(rootDir);
2237
2661
  const canonicalArtifacts = /* @__PURE__ */ new Map();
2238
2662
  for (const file of allMdFiles) {
2239
- const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
2663
+ const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
2240
2664
  if (relFile.includes("specs/changes/") || relFile.includes("templates/")) continue;
2241
2665
  try {
2242
- const content = fs7.readFileSync(file, "utf-8");
2666
+ const content = fs8.readFileSync(file, "utf-8");
2243
2667
  const { frontmatter, body } = parseFrontmatter(content);
2244
2668
  if (frontmatter.id) canonicalArtifacts.set(String(frontmatter.id), { file, frontmatter, body });
2245
2669
  } catch {
@@ -2252,7 +2676,7 @@ function writeUpdatedArtifact(file, frontmatter, body) {
2252
2676
  ${yaml3.dump(frontmatter, { indent: 2, lineWidth: -1 }).trim()}
2253
2677
  ---
2254
2678
  `;
2255
- fs7.writeFileSync(file, yamlHeader + body, "utf-8");
2679
+ fs8.writeFileSync(file, yamlHeader + body, "utf-8");
2256
2680
  }
2257
2681
  function updateSingleCanonicalRequirement(canonical, completedTasks, changeId, date, author) {
2258
2682
  const fm = canonical.frontmatter;
@@ -2368,28 +2792,28 @@ function updateArchitectureComponents(citedComponents, canonicalArtifacts, targe
2368
2792
  return updated;
2369
2793
  }
2370
2794
  function moveToCompletedFolder(changeDir, changeId, rootDir) {
2371
- const completedParent = path4.join(rootDir, "specs", "changes", "completed");
2372
- if (!fs7.existsSync(completedParent)) fs7.mkdirSync(completedParent, { recursive: true });
2373
- const destDir = path4.join(completedParent, changeId);
2374
- if (fs7.existsSync(destDir)) fs7.rmSync(destDir, { recursive: true, force: true });
2375
- fs7.renameSync(changeDir, destDir);
2376
- const destProposal = path4.join(destDir, "proposal.md");
2377
- if (fs7.existsSync(destProposal)) {
2378
- const { frontmatter, body } = parseFrontmatter(fs7.readFileSync(destProposal, "utf-8"));
2795
+ const completedParent = path5.join(rootDir, "specs", "changes", "completed");
2796
+ if (!fs8.existsSync(completedParent)) fs8.mkdirSync(completedParent, { recursive: true });
2797
+ const destDir = path5.join(completedParent, changeId);
2798
+ if (fs8.existsSync(destDir)) fs8.rmSync(destDir, { recursive: true, force: true });
2799
+ fs8.renameSync(changeDir, destDir);
2800
+ const destProposal = path5.join(destDir, "proposal.md");
2801
+ if (fs8.existsSync(destProposal)) {
2802
+ const { frontmatter, body } = parseFrontmatter(fs8.readFileSync(destProposal, "utf-8"));
2379
2803
  frontmatter.status = "applied";
2380
2804
  writeUpdatedArtifact(destProposal, frontmatter, body);
2381
2805
  }
2382
2806
  return destDir;
2383
2807
  }
2384
2808
  function archiveChangeWorkspace(changeDir, changeId, rootDir, autoArchive, completedAll) {
2385
- const isActiveFolder = changeDir.includes("specs" + path4.sep + "changes" + path4.sep + "active");
2809
+ const isActiveFolder = changeDir.includes("specs" + path5.sep + "changes" + path5.sep + "active");
2386
2810
  if (completedAll && autoArchive && isActiveFolder) {
2387
2811
  const destDir = moveToCompletedFolder(changeDir, changeId, rootDir);
2388
2812
  return { archived: true, archivedPath: destDir };
2389
2813
  }
2390
- const proposalFile = path4.join(changeDir, "proposal.md");
2391
- if (fs7.existsSync(proposalFile)) {
2392
- const { frontmatter, body } = parseFrontmatter(fs7.readFileSync(proposalFile, "utf-8"));
2814
+ const proposalFile = path5.join(changeDir, "proposal.md");
2815
+ if (fs8.existsSync(proposalFile)) {
2816
+ const { frontmatter, body } = parseFrontmatter(fs8.readFileSync(proposalFile, "utf-8"));
2393
2817
  if (completedAll) frontmatter.status = "completed";
2394
2818
  writeUpdatedArtifact(proposalFile, frontmatter, body);
2395
2819
  }
@@ -2454,9 +2878,9 @@ function collectSubgraphPromotableIds(subgraph) {
2454
2878
  return Array.from(new Set(combined));
2455
2879
  }
2456
2880
  function collectSpecAffectedNonArchIds(changeDir) {
2457
- const specFile = path4.join(changeDir, "spec.md");
2458
- if (!fs7.existsSync(specFile)) return [];
2459
- const { affectedItems } = parseSpecCatalogScope(fs7.readFileSync(specFile, "utf-8"));
2881
+ const specFile = path5.join(changeDir, "spec.md");
2882
+ if (!fs8.existsSync(specFile)) return [];
2883
+ const { affectedItems } = parseSpecCatalogScope(fs8.readFileSync(specFile, "utf-8"));
2460
2884
  return affectedItems.map((item) => item.id).filter((id) => !isArchitectureComponentId(id));
2461
2885
  }
2462
2886
  function resolveIntegrationOptions(options) {
@@ -2530,8 +2954,8 @@ function resolveAuditStatus(integratedCount, targetCount, gapsCount) {
2530
2954
  function auditChangeEntry(changeDir, changeName, canonicalMap) {
2531
2955
  const handoff = loadProductHandoffSidecar(changeDir);
2532
2956
  if (!handoff) return null;
2533
- const tasksFile = path4.join(changeDir, "tasks.md");
2534
- const tasks = fs7.existsSync(tasksFile) ? parseTasksFromContent(fs7.readFileSync(tasksFile, "utf-8")) : [];
2957
+ const tasksFile = path5.join(changeDir, "tasks.md");
2958
+ const tasks = fs8.existsSync(tasksFile) ? parseTasksFromContent(fs8.readFileSync(tasksFile, "utf-8")) : [];
2535
2959
  const completed = tasks.filter((t) => t.status === "COMPLETED");
2536
2960
  const targetReqs = collectSubgraphPromotableIds(handoff.subgraph);
2537
2961
  const { integrated, missing, gaps } = auditChangeRequirements(targetReqs, canonicalMap);
@@ -2547,11 +2971,11 @@ function auditChangeEntry(changeDir, changeName, canonicalMap) {
2547
2971
  };
2548
2972
  }
2549
2973
  function collectParentDirAudits(parent, canonicalMap, audits, errors) {
2550
- if (!fs7.existsSync(parent)) return;
2551
- const entries = fs7.readdirSync(parent, { withFileTypes: true });
2974
+ if (!fs8.existsSync(parent)) return;
2975
+ const entries = fs8.readdirSync(parent, { withFileTypes: true });
2552
2976
  for (const e of entries) {
2553
2977
  if (!e.isDirectory()) continue;
2554
- const audit = auditChangeEntry(path4.join(parent, e.name), e.name, canonicalMap);
2978
+ const audit = auditChangeEntry(path5.join(parent, e.name), e.name, canonicalMap);
2555
2979
  if (audit) {
2556
2980
  if (audit.gaps.length > 0) errors.push(`[${e.name}] Desalineaci\xF3n: ${audit.gaps.join("; ")}`);
2557
2981
  audits.push(audit);
@@ -2563,8 +2987,8 @@ function verifySddIntegration(options = {}) {
2563
2987
  const audits = [];
2564
2988
  const errors = [];
2565
2989
  const searchDirs = [
2566
- path4.join(rootDir, "specs", "changes", "completed"),
2567
- path4.join(rootDir, "specs", "changes", "active")
2990
+ path5.join(rootDir, "specs", "changes", "completed"),
2991
+ path5.join(rootDir, "specs", "changes", "active")
2568
2992
  ];
2569
2993
  const canonicalMap = loadCanonicalArtifacts(rootDir);
2570
2994
  for (const parent of searchDirs) {
@@ -2612,10 +3036,10 @@ function resolveMatchFromMetadata(activeFolders, options) {
2612
3036
  return {};
2613
3037
  }
2614
3038
  function validateActiveDirectory(activeParent) {
2615
- if (!fs7.existsSync(activeParent)) {
3039
+ if (!fs8.existsSync(activeParent)) {
2616
3040
  return { ok: false, folders: [], error: `No existe el directorio de cambios activos en '${activeParent}'.` };
2617
3041
  }
2618
- const entries = fs7.readdirSync(activeParent, { withFileTypes: true });
3042
+ const entries = fs8.readdirSync(activeParent, { withFileTypes: true });
2619
3043
  const activeFolders = entries.filter((e) => e.isDirectory()).map((e) => e.name);
2620
3044
  if (activeFolders.length === 0) {
2621
3045
  return { ok: false, folders: [], error: "No se encontraron carpetas de cambios activos en specs/changes/active/." };
@@ -2647,7 +3071,7 @@ function evaluateTasksProgress(changeId, tasks) {
2647
3071
  }
2648
3072
  function detectActiveChangeForIntegration(options = {}) {
2649
3073
  const rootDir = options.rootDir || process.cwd();
2650
- const activeParent = path4.join(rootDir, "specs", "changes", "active");
3074
+ const activeParent = path5.join(rootDir, "specs", "changes", "active");
2651
3075
  const dirCheck = validateActiveDirectory(activeParent);
2652
3076
  if (!dirCheck.ok) {
2653
3077
  return {
@@ -2662,9 +3086,9 @@ function detectActiveChangeForIntegration(options = {}) {
2662
3086
  if (!match.changeId) {
2663
3087
  return buildUnmatchedActiveResult(dirCheck.folders, options);
2664
3088
  }
2665
- const changeDir = path4.join(activeParent, match.changeId);
2666
- const tasksFile = path4.join(changeDir, "tasks.md");
2667
- const tasks = fs7.existsSync(tasksFile) ? parseTasksFromContent(fs7.readFileSync(tasksFile, "utf-8")) : [];
3089
+ const changeDir = path5.join(activeParent, match.changeId);
3090
+ const tasksFile = path5.join(changeDir, "tasks.md");
3091
+ const tasks = fs8.existsSync(tasksFile) ? parseTasksFromContent(fs8.readFileSync(tasksFile, "utf-8")) : [];
2668
3092
  const progress = evaluateTasksProgress(match.changeId, tasks);
2669
3093
  const reasons = match.reason ? [match.reason] : [];
2670
3094
  if (progress.error) reasons.push(progress.error);
@@ -2679,9 +3103,46 @@ function detectActiveChangeForIntegration(options = {}) {
2679
3103
  reasons
2680
3104
  };
2681
3105
  }
2682
- function computeCanonicalSha256(content) {
3106
+ function extractSemanticContent(content) {
3107
+ let normalized = content.replace(/\r\n/g, "\n");
3108
+ normalized = normalized.replace(/(?:\n|^)##?\s+.*(?:Historial de Revisiones|Revision History)(?:[\s\S]*?(?=(?:\n##?\s+[^\n]+)|\s*$))/i, "");
3109
+ normalized = normalized.replace(/(?:\n|^)\|\s*\*?Versi[oó]n\*?\s*\|\s*Fecha\s*\|\s*Autor[\s\S]*?(?=(?:\n##?\s+[^\n]+)|\s*$)/i, "");
3110
+ return normalized.trimEnd() + "\n";
3111
+ }
3112
+ function slugify(text) {
3113
+ return text.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-");
3114
+ }
3115
+ function matchesSlug(line, clean) {
3116
+ const headingText = line.replace(/^#+\s*/, "").trim();
3117
+ const slug = slugify(headingText);
3118
+ if (slug === clean || slug.includes(clean) || clean.includes(slug)) return true;
3119
+ const slugNoNums = slugify(headingText.replace(/^\d+[\.\)]\s*/, "").trim());
3120
+ return slugNoNums === clean || clean === slugNoNums;
3121
+ }
3122
+ function matchesHeading(content, clean) {
3123
+ for (const line of content.split(/\r?\n/)) {
3124
+ const trimmed = line.trim();
3125
+ if (trimmed.startsWith("#") && matchesSlug(trimmed, clean)) {
3126
+ return true;
3127
+ }
3128
+ }
3129
+ return false;
3130
+ }
3131
+ function validateAnchorExistence(targetContent, anchor) {
3132
+ if (!anchor) return true;
3133
+ const clean = anchor.replace(/^#/, "").trim().toLowerCase();
3134
+ if (!clean || targetContent.toLowerCase().includes(clean)) return true;
3135
+ if (matchesHeading(targetContent, clean)) return true;
3136
+ const anchorRegex = new RegExp(`(?:id|name)=["']${clean}["']|\\{#${clean}\\}`, "i");
3137
+ return anchorRegex.test(targetContent);
3138
+ }
3139
+ function computeRawSha256(content) {
2683
3140
  const normalized = content.replace(/\r\n/g, "\n");
2684
- return crypto.createHash("sha256").update(normalized, "utf-8").digest("hex");
3141
+ return crypto3.createHash("sha256").update(normalized, "utf-8").digest("hex");
3142
+ }
3143
+ function computeCanonicalSha256(content) {
3144
+ const semantic = extractSemanticContent(content);
3145
+ return crypto3.createHash("sha256").update(semantic, "utf-8").digest("hex");
2685
3146
  }
2686
3147
  function parseFrontmatterId(content) {
2687
3148
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -2771,7 +3232,7 @@ function buildPdacReportHeader(isOk, driftsCount, nodesCount) {
2771
3232
  return [
2772
3233
  `# \u{1F512} PDaC Cryptographic Matrix and Drift Detection (Anti-Drift)`,
2773
3234
  ``,
2774
- `> **Evaluation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
3235
+ `> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
2775
3236
  `> **Verdict:** ${verdict}`,
2776
3237
  ``,
2777
3238
  `---`,
@@ -2782,8 +3243,8 @@ function buildPdacReportHeader(isOk, driftsCount, nodesCount) {
2782
3243
  `| :--- | :--- | :--- | :--- |`
2783
3244
  ];
2784
3245
  }
2785
- function generatePdacReportMarkdown(nodes, drifts, unresolvedCount) {
2786
- const isOk = drifts.length === 0 && unresolvedCount === 0;
3246
+ function generatePdacReportMarkdown(nodes, drifts, unresolvedCount, invalidAnchors = []) {
3247
+ const isOk = drifts.length === 0 && unresolvedCount === 0 && invalidAnchors.length === 0;
2787
3248
  const header = buildPdacReportHeader(isOk, drifts.length, nodes.length);
2788
3249
  const nodeRows = nodes.map(
2789
3250
  (n) => `| **\`${n.id}\`** | \`${n.type}\` | ${n.title || "Untitled"} | \`sha256:${n.digest.substring(0, 16)}...\` |`
@@ -2795,9 +3256,9 @@ function isPdacDeliveryDoc(relFile) {
2795
3256
  }
2796
3257
  function processPdacFile(file, rootDir, baselineMap, allCitations) {
2797
3258
  try {
2798
- const content = fs7.readFileSync(file, "utf-8");
3259
+ const content = fs8.readFileSync(file, "utf-8");
2799
3260
  const { id, type, title } = parseFrontmatterId(content);
2800
- const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
3261
+ const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
2801
3262
  if (id) {
2802
3263
  const citations = extractCitations(content, relFile);
2803
3264
  baselineMap.set(id, {
@@ -2806,6 +3267,7 @@ function processPdacFile(file, rootDir, baselineMap, allCitations) {
2806
3267
  filePath: relFile,
2807
3268
  title,
2808
3269
  digest: computeCanonicalSha256(content),
3270
+ rawDigest: computeRawSha256(content),
2809
3271
  rawContent: content,
2810
3272
  citations: citations.map((c) => ({
2811
3273
  targetId: c.targetId,
@@ -2829,38 +3291,65 @@ function scanPdacWorkspace(rootDir) {
2829
3291
  }
2830
3292
  return { baselineMap, allCitations };
2831
3293
  }
3294
+ function checkAnchor(citation, targetNode, invalidAnchors) {
3295
+ if (citation.anchor && targetNode.rawContent) {
3296
+ if (!validateAnchorExistence(targetNode.rawContent, citation.anchor)) {
3297
+ invalidAnchors.push({
3298
+ sourceFile: citation.sourceFile,
3299
+ targetId: citation.targetId,
3300
+ anchor: citation.anchor,
3301
+ reason: `Anchor '${citation.anchor}' not found in target artifact '${citation.targetId}' (${targetNode.filePath})`
3302
+ });
3303
+ }
3304
+ }
3305
+ }
3306
+ function checkDigest(citation, targetNode, drifts) {
3307
+ if (!citation.expectedDigest) return;
3308
+ const matches = citation.expectedDigest === targetNode.digest || targetNode.rawDigest !== void 0 && citation.expectedDigest === targetNode.rawDigest;
3309
+ if (!matches) {
3310
+ drifts.push({
3311
+ sourceId: citation.sourceFile,
3312
+ targetId: citation.targetId,
3313
+ sourceFile: citation.sourceFile,
3314
+ expectedDigest: citation.expectedDigest,
3315
+ actualDigest: targetNode.digest
3316
+ });
3317
+ }
3318
+ }
2832
3319
  function computePdacDrifts(allCitations, baselineMap) {
2833
3320
  const drifts = [];
3321
+ const unresolvedCitations = [];
3322
+ const invalidAnchors = [];
2834
3323
  let unresolvedCount = 0;
2835
3324
  for (const citation of allCitations) {
2836
3325
  const targetNode = baselineMap.get(citation.targetId);
2837
3326
  if (!targetNode) {
2838
3327
  unresolvedCount++;
2839
- continue;
2840
- }
2841
- if (citation.expectedDigest && citation.expectedDigest !== targetNode.digest) {
2842
- drifts.push({
2843
- sourceId: citation.sourceFile,
2844
- targetId: citation.targetId,
3328
+ unresolvedCitations.push({
2845
3329
  sourceFile: citation.sourceFile,
2846
- expectedDigest: citation.expectedDigest,
2847
- actualDigest: targetNode.digest
3330
+ targetId: citation.targetId
2848
3331
  });
3332
+ continue;
2849
3333
  }
3334
+ checkAnchor(citation, targetNode, invalidAnchors);
3335
+ checkDigest(citation, targetNode, drifts);
2850
3336
  }
2851
- return { drifts, unresolvedCount };
3337
+ return { drifts, unresolvedCount, unresolvedCitations, invalidAnchors };
2852
3338
  }
2853
3339
  function verifyPdacGraph(options = {}) {
2854
3340
  const rootDir = options.rootDir || process.cwd();
2855
3341
  const { baselineMap, allCitations } = scanPdacWorkspace(rootDir);
2856
- const { drifts, unresolvedCount } = computePdacDrifts(allCitations, baselineMap);
3342
+ const { drifts, unresolvedCount, unresolvedCitations, invalidAnchors } = computePdacDrifts(allCitations, baselineMap);
2857
3343
  const nodes = Array.from(baselineMap.values());
2858
- const reportMarkdown = generatePdacReportMarkdown(nodes, drifts, unresolvedCount);
3344
+ const reportMarkdown = generatePdacReportMarkdown(nodes, drifts, unresolvedCount, invalidAnchors);
2859
3345
  return {
2860
- success: drifts.length === 0,
3346
+ success: drifts.length === 0 && unresolvedCount === 0 && invalidAnchors.length === 0,
2861
3347
  totalNodes: nodes.length,
2862
3348
  totalEdges: allCitations.length,
2863
3349
  drifts,
3350
+ unresolvedCount,
3351
+ unresolvedCitations,
3352
+ invalidAnchors,
2864
3353
  reportMarkdown
2865
3354
  };
2866
3355
  }
@@ -2925,22 +3414,22 @@ function updateCitationsInContent(content, baselineMap, sourceFile, details) {
2925
3414
  }
2926
3415
  function collectCandidateFilesForSync(rootDir, targetPath) {
2927
3416
  if (targetPath) {
2928
- const fullTarget = path4.isAbsolute(targetPath) ? targetPath : path4.join(rootDir, targetPath);
2929
- if (!fs7.existsSync(fullTarget)) return [];
2930
- return fs7.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
3417
+ const fullTarget = path5.isAbsolute(targetPath) ? targetPath : path5.join(rootDir, targetPath);
3418
+ if (!fs8.existsSync(fullTarget)) return [];
3419
+ return fs8.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
2931
3420
  }
2932
3421
  return walkMdFiles(rootDir).filter((f) => {
2933
- const relFile = path4.relative(rootDir, f).replace(/\\/g, "/");
3422
+ const relFile = path5.relative(rootDir, f).replace(/\\/g, "/");
2934
3423
  return isPdacDeliveryDoc(relFile);
2935
3424
  });
2936
3425
  }
2937
3426
  function syncSingleFile(file, rootDir, baselineMap, details) {
2938
3427
  try {
2939
- const content = fs7.readFileSync(file, "utf-8");
2940
- const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
3428
+ const content = fs8.readFileSync(file, "utf-8");
3429
+ const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
2941
3430
  const { updatedContent, changed } = updateCitationsInContent(content, baselineMap, relFile, details);
2942
3431
  if (changed) {
2943
- fs7.writeFileSync(file, updatedContent, "utf-8");
3432
+ fs8.writeFileSync(file, updatedContent, "utf-8");
2944
3433
  return relFile;
2945
3434
  }
2946
3435
  } catch {
@@ -3143,6 +3632,36 @@ var STARTER_SCHEMAS = Object.entries(STARTER_SCHEMAS_MAP).map(([relPath, schema]
3143
3632
  }));
3144
3633
 
3145
3634
  // src/verifiers/schemas.ts
3635
+ var ajvInstance = new Ajv2020({
3636
+ allErrors: true,
3637
+ strict: false,
3638
+ validateFormats: true,
3639
+ verbose: true
3640
+ });
3641
+ addFormats(ajvInstance);
3642
+ var compiledSchemaCache = /* @__PURE__ */ new WeakMap();
3643
+ function formatFieldPath(err, propPath) {
3644
+ const field = err.instancePath ? err.instancePath.replace(/^\//, "").replace(/\//g, ".") : propPath || "root";
3645
+ return propPath && field !== "root" && field ? `${propPath}.${field}` : field || "root";
3646
+ }
3647
+ var KEYWORD_FORMATTERS = {
3648
+ required: (p, _err, fullPath) => `'${fullPath}' missing required field '${String(p?.missingProperty)}'`,
3649
+ additionalProperties: (p, _err, fullPath) => `'${fullPath}' contains disallowed extra property '${String(p?.additionalProperty)}'`,
3650
+ minLength: (p, _err, fullPath) => `'${fullPath}' length must be >= ${String(p?.limit)}`,
3651
+ maxLength: (p, _err, fullPath) => `'${fullPath}' length must be <= ${String(p?.limit)}`,
3652
+ enum: (p, err, fullPath) => `'${fullPath}' must be one of [${Array.isArray(p?.allowedValues) ? p.allowedValues.join(", ") : ""}], received '${String(err.data)}'`,
3653
+ uniqueItems: (_p, _err, fullPath) => `'${fullPath}' contains duplicate items (must have unique items)`,
3654
+ pattern: (p, err, fullPath) => `'${fullPath}' value '${String(err.data)}' does not match pattern ${String(p?.pattern)}`
3655
+ };
3656
+ function formatKeywordMessage(err, fullPath) {
3657
+ const p = err.params;
3658
+ const formatter = KEYWORD_FORMATTERS[err.keyword];
3659
+ return formatter ? formatter(p, err, fullPath) : `'${fullPath}' ${err.message || "validation failed"}`;
3660
+ }
3661
+ function formatAjvError(err, propPath) {
3662
+ const fullPath = formatFieldPath(err, propPath);
3663
+ return formatKeywordMessage(err, fullPath);
3664
+ }
3146
3665
  var TYPE_TO_SCHEMA_MAP = {
3147
3666
  // Product
3148
3667
  actor: "schemas/product/actor.schema.json",
@@ -3287,10 +3806,10 @@ var EXCLUDED_SCHEMA_DIRS = /* @__PURE__ */ new Set([
3287
3806
  ]);
3288
3807
  function walkHandoffSidecars(dir, fileList = []) {
3289
3808
  try {
3290
- if (!fs7.existsSync(dir)) return fileList;
3291
- for (const file of fs7.readdirSync(dir)) {
3292
- const fullPath = path4.join(dir, file);
3293
- if (fs7.statSync(fullPath).isDirectory()) {
3809
+ if (!fs8.existsSync(dir)) return fileList;
3810
+ for (const file of fs8.readdirSync(dir)) {
3811
+ const fullPath = path5.join(dir, file);
3812
+ if (fs8.statSync(fullPath).isDirectory()) {
3294
3813
  if (!EXCLUDED_SCHEMA_DIRS.has(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
3295
3814
  walkHandoffSidecars(fullPath, fileList);
3296
3815
  }
@@ -3313,157 +3832,41 @@ function extractArtifactData(filePath, content) {
3313
3832
  }
3314
3833
  return extractFrontmatter(content);
3315
3834
  }
3316
- function validateConst(value, schema, currentProp) {
3317
- if (schema.const !== void 0 && value !== schema.const) {
3318
- return [`'${currentProp}' must equal '${schema.const}', received '${String(value)}'`];
3319
- }
3320
- return [];
3321
- }
3322
- function validateEnum(value, schema, currentProp) {
3323
- if (schema.enum !== void 0 && !schema.enum.includes(value)) {
3324
- return [`'${currentProp}' must be one of [${schema.enum.join(", ")}], received '${String(value)}'`];
3325
- }
3326
- return [];
3327
- }
3328
- function validateCombinators(value, schema, propPath, currentProp) {
3329
- if (schema.oneOf) {
3330
- const matchCount = schema.oneOf.filter((sub) => validateAgainstSchema(value, sub, propPath).length === 0).length;
3331
- if (matchCount !== 1) {
3332
- return [`'${currentProp}' must match exactly one sub-schema in oneOf`];
3333
- }
3334
- }
3335
- if (schema.anyOf) {
3336
- const anyMatched = schema.anyOf.some((sub) => validateAgainstSchema(value, sub, propPath).length === 0);
3337
- if (!anyMatched) {
3338
- return [`'${currentProp}' did not match any allowed alternative in anyOf`];
3339
- }
3340
- }
3341
- return [];
3342
- }
3343
- function getActualJsonType(value) {
3344
- if (value === null) return "null";
3345
- if (Array.isArray(value)) return "array";
3346
- const t = typeof value;
3347
- if (t === "number" && Number.isInteger(value)) return "integer";
3348
- return t;
3349
- }
3350
- function validateType(value, schema, currentProp) {
3351
- if (schema.type === void 0) return [];
3352
- const allowed = Array.isArray(schema.type) ? schema.type : [schema.type];
3353
- const actual = getActualJsonType(value);
3354
- const matches = allowed.some((t) => t === actual || t === "number" && actual === "integer");
3355
- if (!matches) {
3356
- return [`'${currentProp}' expected type [${allowed.join("|")}], received '${actual}'`];
3835
+ function getOrCompileSchema(schema) {
3836
+ const schemaObj = schema;
3837
+ if (typeof schemaObj.$id === "string") {
3838
+ const existing = ajvInstance.getSchema(schemaObj.$id);
3839
+ if (existing) return existing;
3840
+ return ajvInstance.compile(schema);
3357
3841
  }
3358
- return [];
3359
- }
3360
- function validateString(value, schema, currentProp) {
3361
- const errors = [];
3362
- if (schema.minLength !== void 0 && value.length < schema.minLength) {
3363
- errors.push(`'${currentProp}' length must be >= ${schema.minLength}`);
3364
- }
3365
- if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
3366
- errors.push(`'${currentProp}' length must be <= ${schema.maxLength}`);
3367
- }
3368
- if (schema.pattern !== void 0 && !new RegExp(schema.pattern).test(value)) {
3369
- errors.push(`'${currentProp}' value '${value}' does not match pattern ${schema.pattern}`);
3370
- }
3371
- if (schema.format === "date") {
3372
- const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
3373
- if (!dateRegex.test(value) || isNaN(Date.parse(value))) {
3374
- errors.push(`'${currentProp}' must be a valid ISO-8601 date string (YYYY-MM-DD)`);
3375
- }
3376
- }
3377
- return errors;
3378
- }
3379
- function validateNumber(value, schema, currentProp) {
3380
- const errors = [];
3381
- if (schema.minimum !== void 0 && value < schema.minimum) {
3382
- errors.push(`'${currentProp}' must be >= ${schema.minimum}`);
3383
- }
3384
- if (schema.maximum !== void 0 && value > schema.maximum) {
3385
- errors.push(`'${currentProp}' must be <= ${schema.maximum}`);
3386
- }
3387
- return errors;
3388
- }
3389
- function validateArray(value, schema, currentProp) {
3390
- const errors = [];
3391
- if (schema.minItems !== void 0 && value.length < schema.minItems) {
3392
- errors.push(`'${currentProp}' items count must be >= ${schema.minItems}`);
3393
- }
3394
- if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
3395
- errors.push(`'${currentProp}' items count must be <= ${schema.maxItems}`);
3396
- }
3397
- if (schema.items) {
3398
- value.forEach((item, idx) => {
3399
- errors.push(...validateAgainstSchema(item, schema.items, `${currentProp}[${idx}]`));
3400
- });
3401
- }
3402
- return errors;
3403
- }
3404
- function checkRequiredProperties(record, required, currentProp) {
3405
- if (!required) return [];
3406
- const errors = [];
3407
- for (const reqKey of required) {
3408
- if (!(reqKey in record) || record[reqKey] === void 0) {
3409
- errors.push(`'${currentProp}' missing required field '${reqKey}'`);
3410
- }
3411
- }
3412
- return errors;
3413
- }
3414
- function checkObjectProperties(record, schema, propPath, currentProp) {
3415
- const errors = [];
3416
- const properties = schema.properties || {};
3417
- for (const [key, propVal] of Object.entries(record)) {
3418
- const subPath = propPath ? `${propPath}.${key}` : key;
3419
- if (key in properties) {
3420
- errors.push(...validateAgainstSchema(propVal, properties[key], subPath));
3421
- } else if (schema.additionalProperties === false) {
3422
- errors.push(`'${currentProp}' contains disallowed extra property '${key}'`);
3423
- } else if (typeof schema.additionalProperties === "object") {
3424
- errors.push(...validateAgainstSchema(propVal, schema.additionalProperties, subPath));
3425
- }
3426
- }
3427
- return errors;
3428
- }
3429
- function validateObject(record, schema, propPath, currentProp) {
3430
- return [
3431
- ...checkRequiredProperties(record, schema.required, currentProp),
3432
- ...checkObjectProperties(record, schema, propPath, currentProp)
3433
- ];
3434
- }
3435
- function validateTypeSpecificRules(value, schema, propPath, currentProp) {
3436
- if (typeof value === "string") return validateString(value, schema, currentProp);
3437
- if (typeof value === "number") return validateNumber(value, schema, currentProp);
3438
- if (Array.isArray(value)) return validateArray(value, schema, currentProp);
3439
- if (typeof value === "object" && value !== null) {
3440
- return validateObject(value, schema, propPath, currentProp);
3441
- }
3442
- return [];
3842
+ const cached = compiledSchemaCache.get(schema);
3843
+ if (cached) return cached;
3844
+ const compiled = ajvInstance.compile(schema);
3845
+ compiledSchemaCache.set(schema, compiled);
3846
+ return compiled;
3443
3847
  }
3444
3848
  function validateAgainstSchema(value, schema, propPath = "") {
3445
- const currentProp = propPath || "root";
3446
- const constErr = validateConst(value, schema, currentProp);
3447
- if (constErr.length > 0) return constErr;
3448
- const enumErr = validateEnum(value, schema, currentProp);
3449
- if (enumErr.length > 0) return enumErr;
3450
- const combErr = validateCombinators(value, schema, propPath, currentProp);
3451
- if (combErr.length > 0) return combErr;
3452
- const typeErr = validateType(value, schema, currentProp);
3453
- if (typeErr.length > 0) return typeErr;
3454
- return validateTypeSpecificRules(value, schema, propPath, currentProp);
3849
+ if (!schema || typeof schema !== "object") return [];
3850
+ try {
3851
+ const validate = getOrCompileSchema(schema);
3852
+ const valid = validate(value);
3853
+ if (valid || !validate.errors) return [];
3854
+ return validate.errors.map((err) => formatAjvError(err, propPath));
3855
+ } catch (err) {
3856
+ return [`Schema error: ${err instanceof Error ? err.message : String(err)}`];
3857
+ }
3455
3858
  }
3456
3859
  function findSchemaFile(relSchemaPath, rootDir) {
3457
- const fullPath = path4.join(rootDir, relSchemaPath);
3458
- if (fs7.existsSync(fullPath)) return fullPath;
3459
- const fallbackPath = path4.join(process.cwd(), relSchemaPath);
3460
- if (fs7.existsSync(fallbackPath)) return fallbackPath;
3860
+ const fullPath = path5.join(rootDir, relSchemaPath);
3861
+ if (fs8.existsSync(fullPath)) return fullPath;
3862
+ const fallbackPath = path5.join(process.cwd(), relSchemaPath);
3863
+ if (fs8.existsSync(fallbackPath)) return fallbackPath;
3461
3864
  return null;
3462
3865
  }
3463
3866
  function loadJsonSchema(fullPath, relSchemaPath) {
3464
- if (fullPath && fs7.existsSync(fullPath)) {
3867
+ if (fullPath && fs8.existsSync(fullPath)) {
3465
3868
  try {
3466
- return JSON.parse(fs7.readFileSync(fullPath, "utf-8"));
3869
+ return JSON.parse(fs8.readFileSync(fullPath, "utf-8"));
3467
3870
  } catch {
3468
3871
  }
3469
3872
  }
@@ -3478,7 +3881,7 @@ function resolveSchemaForArtifact(frontmatter, rootDir) {
3478
3881
  }
3479
3882
  function loadSchemaFromPathOrType(schemaOrType, rootDir) {
3480
3883
  if (schemaOrType.endsWith(".json")) {
3481
- const fullPath = path4.isAbsolute(schemaOrType) ? schemaOrType : findSchemaFile(schemaOrType, rootDir);
3884
+ const fullPath = path5.isAbsolute(schemaOrType) ? schemaOrType : findSchemaFile(schemaOrType, rootDir);
3482
3885
  const relNormalized = schemaOrType.replace(/\\/g, "/");
3483
3886
  const schema = loadJsonSchema(fullPath, relNormalized);
3484
3887
  return { schema, schemaId: schemaOrType };
@@ -3523,9 +3926,9 @@ function collectCandidateSchemaFiles(options, rootDir) {
3523
3926
  if (!options.targetPath) {
3524
3927
  return [...walkMdFiles(rootDir), ...walkHandoffSidecars(rootDir)];
3525
3928
  }
3526
- const target = path4.isAbsolute(options.targetPath) ? options.targetPath : path4.join(rootDir, options.targetPath);
3527
- if (!fs7.existsSync(target)) return [];
3528
- if (fs7.statSync(target).isDirectory()) {
3929
+ const target = path5.isAbsolute(options.targetPath) ? options.targetPath : path5.join(rootDir, options.targetPath);
3930
+ if (!fs8.existsSync(target)) return [];
3931
+ if (fs8.statSync(target).isDirectory()) {
3529
3932
  return [...walkMdFiles(target), ...walkHandoffSidecars(target)];
3530
3933
  }
3531
3934
  if (target.endsWith(".md") || target.endsWith(".yaml") || target.endsWith(".yml")) {
@@ -3576,7 +3979,7 @@ function buildEvaluationFailure(relPath, err) {
3576
3979
  }
3577
3980
  function evaluateArtifactFile(file, relPath, rootDir) {
3578
3981
  try {
3579
- const content = fs7.readFileSync(file, "utf-8");
3982
+ const content = fs8.readFileSync(file, "utf-8");
3580
3983
  const frontmatter = extractArtifactData(file, content);
3581
3984
  if (!frontmatter || !frontmatter.id) return null;
3582
3985
  const schemaInfo = resolveSchemaForArtifact(frontmatter, rootDir);
@@ -3611,7 +4014,7 @@ function verifyArtifactsSchemas(options = {}) {
3611
4014
  let validCount = 0;
3612
4015
  let totalEvaluated = 0;
3613
4016
  for (const file of filesToScan) {
3614
- const relPath = path4.relative(rootDir, file).replace(/\\/g, "/");
4017
+ const relPath = path5.relative(rootDir, file).replace(/\\/g, "/");
3615
4018
  if (isIgnoredArtifactPath(relPath, Boolean(options.targetPath))) continue;
3616
4019
  const evaluation = evaluateArtifactFile(file, relPath, rootDir);
3617
4020
  if (!evaluation) continue;
@@ -3626,14 +4029,14 @@ function verifyArtifactsSchemas(options = {}) {
3626
4029
  }
3627
4030
 
3628
4031
  // src/adapters/sdd/scaffold.ts
3629
- function slugify(text) {
4032
+ function slugify2(text) {
3630
4033
  return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3631
4034
  }
3632
4035
  function extractDirMaxNum(dir) {
3633
- if (!fs7.existsSync(dir)) return 0;
4036
+ if (!fs8.existsSync(dir)) return 0;
3634
4037
  let max = 0;
3635
4038
  try {
3636
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
4039
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
3637
4040
  for (const e of entries) {
3638
4041
  if (!e.isDirectory()) continue;
3639
4042
  const match = e.name.match(/^(?:chg|CHG|patch|PATCH)-(\d+)/i);
@@ -3650,10 +4053,10 @@ function extractDirMaxNum(dir) {
3650
4053
  }
3651
4054
  function getNextCorrelativeNumber(rootDir) {
3652
4055
  const candidateDirs = [
3653
- path4.join(rootDir, "specs", "changes", "active"),
3654
- path4.join(rootDir, "specs", "changes", "completed"),
3655
- path4.join(rootDir, "specs"),
3656
- path4.join(rootDir, "openspec", "changes")
4056
+ path5.join(rootDir, "specs", "changes", "active"),
4057
+ path5.join(rootDir, "specs", "changes", "completed"),
4058
+ path5.join(rootDir, "specs"),
4059
+ path5.join(rootDir, "openspec", "changes")
3657
4060
  ];
3658
4061
  let maxNum = 0;
3659
4062
  for (const dir of candidateDirs) {
@@ -3666,7 +4069,7 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
3666
4069
  const targetChangeId = options.changeId || options.id;
3667
4070
  if (targetChangeId) {
3668
4071
  const rawId = targetChangeId.trim();
3669
- const slug = slugify(rawId);
4072
+ const slug = slugify2(rawId);
3670
4073
  const canonicalId = rawId.toUpperCase();
3671
4074
  const match = rawId.match(/^(?:chg|CHG|patch|PATCH)-(\d+)/i);
3672
4075
  const padNum2 = match ? match[1] : "001";
@@ -3674,7 +4077,7 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
3674
4077
  }
3675
4078
  const nextNum = getNextCorrelativeNumber(rootDir);
3676
4079
  const padNum = String(nextNum).padStart(3, "0");
3677
- const nameSlug = slugify(name);
4080
+ const nameSlug = slugify2(name);
3678
4081
  const prefix = profile === "patch" ? "patch" : "chg";
3679
4082
  return {
3680
4083
  slug: `${prefix}-${padNum}-${nameSlug}`,
@@ -3683,13 +4086,13 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
3683
4086
  };
3684
4087
  }
3685
4088
  function ensureEmptyChangeDir(changeDir) {
3686
- if (fs7.existsSync(changeDir)) {
3687
- const existing = fs7.readdirSync(changeDir);
4089
+ if (fs8.existsSync(changeDir)) {
4090
+ const existing = fs8.readdirSync(changeDir);
3688
4091
  if (existing.length > 0) {
3689
4092
  return `El directorio del cambio '${changeDir}' ya existe y no est\xE1 vac\xEDo.`;
3690
4093
  }
3691
4094
  } else {
3692
- fs7.mkdirSync(changeDir, { recursive: true });
4095
+ fs8.mkdirSync(changeDir, { recursive: true });
3693
4096
  }
3694
4097
  return null;
3695
4098
  }
@@ -3698,7 +4101,7 @@ function scanCanonicalArtifacts(rootDir) {
3698
4101
  const artifactMap = /* @__PURE__ */ new Map();
3699
4102
  for (const file of allMdFiles) {
3700
4103
  try {
3701
- const content = fs7.readFileSync(file, "utf-8");
4104
+ const content = fs8.readFileSync(file, "utf-8");
3702
4105
  const { frontmatter } = parseFrontmatter(content);
3703
4106
  if (frontmatter?.id) {
3704
4107
  const id = String(frontmatter.id);
@@ -4077,7 +4480,7 @@ function buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors)
4077
4480
  changeId: slug,
4078
4481
  canonicalId,
4079
4482
  profile: "patch",
4080
- changeDir: path4.dirname(specPath),
4483
+ changeDir: path5.dirname(specPath),
4081
4484
  createdFiles: [specPath],
4082
4485
  citedArtifacts,
4083
4486
  errors
@@ -4088,10 +4491,10 @@ function scaffoldPatchChange(rootDir, changeDir, slug, canonicalId, name, inputF
4088
4491
  const citationsSection = citedArtifacts.length > 0 ? `citations:
4089
4492
  ${renderCitationsYamlBlock(citedArtifacts)}
4090
4493
  ` : "";
4091
- const specPath = path4.join(changeDir, "spec.md");
4494
+ const specPath = path5.join(changeDir, "spec.md");
4092
4495
  const specFmObj = buildPatchFmObj(canonicalId);
4093
4496
  const specContent = buildPatchSpecContent(canonicalId, name, citationsSection);
4094
- fs7.writeFileSync(specPath, specContent, "utf-8");
4497
+ fs8.writeFileSync(specPath, specContent, "utf-8");
4095
4498
  const errors = validatePatchArtifacts(rootDir, changeDir, specContent, specFmObj, adapter);
4096
4499
  return buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors);
4097
4500
  }
@@ -4102,46 +4505,46 @@ function buildGreenfieldContent(reqId, ucId, name, canonicalId, author, dateStr)
4102
4505
  ${body}`;
4103
4506
  }
4104
4507
  function createGreenfieldRequirement(rootDir, name, padNum, canonicalId, author, dateStr, createdFiles) {
4105
- const nameSlugUpper = slugify(name).toUpperCase();
4508
+ const nameSlugUpper = slugify2(name).toUpperCase();
4106
4509
  const reqId = `FR-${padNum}-${nameSlugUpper}-001`;
4107
4510
  const ucId = `UC-${padNum}-${nameSlugUpper}`;
4108
- const productDir = path4.join(rootDir, "specs", "product");
4109
- if (!fs7.existsSync(productDir)) fs7.mkdirSync(productDir, { recursive: true });
4110
- const reqFilePath = path4.join(productDir, `${reqId}.md`);
4111
- if (fs7.existsSync(reqFilePath)) return null;
4511
+ const productDir = path5.join(rootDir, "specs", "product");
4512
+ if (!fs8.existsSync(productDir)) fs8.mkdirSync(productDir, { recursive: true });
4513
+ const reqFilePath = path5.join(productDir, `${reqId}.md`);
4514
+ if (fs8.existsSync(reqFilePath)) return null;
4112
4515
  const reqContent = buildGreenfieldContent(reqId, ucId, name, canonicalId, author, dateStr);
4113
- fs7.writeFileSync(reqFilePath, reqContent, "utf-8");
4516
+ fs8.writeFileSync(reqFilePath, reqContent, "utf-8");
4114
4517
  createdFiles.push(reqFilePath);
4115
4518
  const digest = computeCanonicalSha256(reqContent);
4116
4519
  return { reqId, ucId, reqFilePath, digest };
4117
4520
  }
4118
4521
  function resolveMissingDraftDestination(rootDir, id) {
4119
4522
  if (id.startsWith("PRIV-REQ-")) {
4120
- return { dir: path4.join(rootDir, "specs", "privacy", "requirements"), type: "privacy-requirement" };
4523
+ return { dir: path5.join(rootDir, "specs", "privacy", "requirements"), type: "privacy-requirement" };
4121
4524
  }
4122
4525
  if (id.startsWith("DATA-ASSET-")) {
4123
- return { dir: path4.join(rootDir, "specs", "privacy", "assets"), type: "data-asset" };
4526
+ return { dir: path5.join(rootDir, "specs", "privacy", "assets"), type: "data-asset" };
4124
4527
  }
4125
4528
  if (id.startsWith("PRIV-ROPA-")) {
4126
- return { dir: path4.join(rootDir, "specs", "privacy", "ropa"), type: "privacy-ropa" };
4529
+ return { dir: path5.join(rootDir, "specs", "privacy", "ropa"), type: "privacy-ropa" };
4127
4530
  }
4128
4531
  if (id.startsWith("UC-")) {
4129
- return { dir: path4.join(rootDir, "specs", "product"), type: "use-case" };
4532
+ return { dir: path5.join(rootDir, "specs", "product"), type: "use-case" };
4130
4533
  }
4131
4534
  if (id.startsWith("SEC-REQ-") || id.startsWith("SAF-REQ-")) {
4132
- return { dir: path4.join(rootDir, "specs", "product"), type: "security-requirement" };
4535
+ return { dir: path5.join(rootDir, "specs", "product"), type: "security-requirement" };
4133
4536
  }
4134
- return { dir: path4.join(rootDir, "specs", "product"), type: "requirement" };
4537
+ return { dir: path5.join(rootDir, "specs", "product"), type: "requirement" };
4135
4538
  }
4136
4539
  function createMissingDraftRequirement(rootDir, name, id, canonicalId, createdFiles) {
4137
4540
  const { dir, type: reqType } = resolveMissingDraftDestination(rootDir, id);
4138
- if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
4139
- const reqFilePath = path4.join(dir, `${id}.md`);
4541
+ if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
4542
+ const reqFilePath = path5.join(dir, `${id}.md`);
4140
4543
  const header = MISSING_DRAFT_HEADER.replaceAll("{{ID}}", id).replaceAll("{{TYPE}}", reqType).replaceAll("{{NAME}}", name);
4141
4544
  const body = MISSING_DRAFT_BODY.replaceAll("{{ID}}", id).replaceAll("{{NAME}}", name).replaceAll("{{CANONICAL_ID}}", canonicalId);
4142
4545
  const reqContent = `${header}
4143
4546
  ${body}`;
4144
- fs7.writeFileSync(reqFilePath, reqContent, "utf-8");
4547
+ fs8.writeFileSync(reqFilePath, reqContent, "utf-8");
4145
4548
  createdFiles.push(reqFilePath);
4146
4549
  const digest = computeCanonicalSha256(reqContent);
4147
4550
  return { reqFilePath, digest };
@@ -4256,7 +4659,7 @@ function initEmptyCitations() {
4256
4659
  }
4257
4660
  function ensureFallbackRequirement(col, padNum, name) {
4258
4661
  if (col.subRequirements.length === 0) {
4259
- col.subRequirements.push(`FR-${padNum}-${slugify(name).toUpperCase()}-001`);
4662
+ col.subRequirements.push(`FR-${padNum}-${slugify2(name).toUpperCase()}-001`);
4260
4663
  }
4261
4664
  }
4262
4665
  function collectChangeCitations(rootDir, name, padNum, canonicalId, author, dateStr, inputFromIds, artifactMap, createdFiles) {
@@ -4270,7 +4673,7 @@ function collectChangeCitations(rootDir, name, padNum, canonicalId, author, date
4270
4673
  return col;
4271
4674
  }
4272
4675
  function renderProposalFile(changeDir, canonicalId, name, author, citationsYamlBlock) {
4273
- const proposalPath = path4.join(changeDir, "proposal.md");
4676
+ const proposalPath = path5.join(changeDir, "proposal.md");
4274
4677
  const citationsSection = citationsYamlBlock ? `citations:
4275
4678
  ${citationsYamlBlock}
4276
4679
  ` : "";
@@ -4290,11 +4693,11 @@ ${name}
4290
4693
  ## 2. Dependencias Externas Evaluadas
4291
4694
  - Lista de dependencias nuevas a incorporar y verificaci\xF3n de licencia seg\xFAn \`license-policy.yaml\`.
4292
4695
  `;
4293
- fs7.writeFileSync(proposalPath, proposalContent, "utf-8");
4696
+ fs8.writeFileSync(proposalPath, proposalContent, "utf-8");
4294
4697
  return proposalPath;
4295
4698
  }
4296
4699
  function renderSpecFile(changeDir, canonicalId, name, profile) {
4297
- const specPath = path4.join(changeDir, "spec.md");
4700
+ const specPath = path5.join(changeDir, "spec.md");
4298
4701
  const specFmObj = {
4299
4702
  id: `SPEC-${canonicalId}`,
4300
4703
  type: "delivery-spec",
@@ -4315,12 +4718,12 @@ cucumber-tags:
4315
4718
  ---
4316
4719
  `;
4317
4720
  const body = SPEC_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name);
4318
- fs7.writeFileSync(specPath, `${header}
4721
+ fs8.writeFileSync(specPath, `${header}
4319
4722
  ${body}`, "utf-8");
4320
4723
  return { specPath, specFmObj };
4321
4724
  }
4322
4725
  function renderDesignFile(changeDir, canonicalId, name, author, dateStr, citationsYamlBlock) {
4323
- const designPath = path4.join(changeDir, "design.md");
4726
+ const designPath = path5.join(changeDir, "design.md");
4324
4727
  const citationsSection = citationsYamlBlock ? `citations:
4325
4728
  ${citationsYamlBlock}
4326
4729
  ` : "";
@@ -4339,7 +4742,7 @@ contracts: []
4339
4742
  ${citationsSection}---
4340
4743
  `;
4341
4744
  const body = DESIGN_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
4342
- fs7.writeFileSync(designPath, `${header}
4745
+ fs8.writeFileSync(designPath, `${header}
4343
4746
  ${body}`, "utf-8");
4344
4747
  return designPath;
4345
4748
  }
@@ -4364,14 +4767,14 @@ function buildTasksFrontmatter(canonicalId, name, reqIds) {
4364
4767
  };
4365
4768
  }
4366
4769
  function renderTasksFile(changeDir, canonicalId, name, author, dateStr, reqIds) {
4367
- const tasksPath = path4.join(changeDir, "tasks.md");
4770
+ const tasksPath = path5.join(changeDir, "tasks.md");
4368
4771
  const tasksFm = buildTasksFrontmatter(canonicalId, name, reqIds);
4369
4772
  const header = `---
4370
4773
  ${yaml3.dump(tasksFm, { indent: 2, lineWidth: -1 }).trim()}
4371
4774
  ---
4372
4775
  `;
4373
4776
  const body = TASKS_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
4374
- fs7.writeFileSync(tasksPath, `${header}
4777
+ fs8.writeFileSync(tasksPath, `${header}
4375
4778
  ${body}`, "utf-8");
4376
4779
  return { tasksPath, tasksFm };
4377
4780
  }
@@ -4550,7 +4953,7 @@ function resolveCiEnvironment(env = process.env) {
4550
4953
  };
4551
4954
  }
4552
4955
  function updateExistingEnvFile(filePath, envString, uppercaseKey) {
4553
- const content = fs7.readFileSync(filePath, "utf-8");
4956
+ const content = fs8.readFileSync(filePath, "utf-8");
4554
4957
  const lines = content.split("\n");
4555
4958
  let found = false;
4556
4959
  const newLines = lines.map((line) => {
@@ -4561,15 +4964,15 @@ function updateExistingEnvFile(filePath, envString, uppercaseKey) {
4561
4964
  return line;
4562
4965
  });
4563
4966
  if (!found) newLines.push(envString);
4564
- fs7.writeFileSync(filePath, newLines.filter((l) => l.trim() !== "").join("\n") + "\n", "utf-8");
4967
+ fs8.writeFileSync(filePath, newLines.filter((l) => l.trim() !== "").join("\n") + "\n", "utf-8");
4565
4968
  }
4566
4969
  function writeEnvOutputFile(envOutputFile, key, value) {
4567
4970
  const uppercaseKey = key.toUpperCase();
4568
4971
  const envString = `${uppercaseKey}=${value}`;
4569
- if (fs7.existsSync(envOutputFile)) {
4972
+ if (fs8.existsSync(envOutputFile)) {
4570
4973
  updateExistingEnvFile(envOutputFile, envString, uppercaseKey);
4571
4974
  } else {
4572
- fs7.writeFileSync(envOutputFile, envString + "\n", "utf-8");
4975
+ fs8.writeFileSync(envOutputFile, envString + "\n", "utf-8");
4573
4976
  }
4574
4977
  }
4575
4978
  function resolveEnvOutputFilePath(env) {
@@ -4579,8 +4982,8 @@ function resolveEnvOutputFilePath(env) {
4579
4982
  return void 0;
4580
4983
  }
4581
4984
  function emitCiOutput(key, value, env = process.env) {
4582
- if (env.GITHUB_OUTPUT && fs7.existsSync(env.GITHUB_OUTPUT)) {
4583
- fs7.appendFileSync(env.GITHUB_OUTPUT, `${key}=${value}
4985
+ if (env.GITHUB_OUTPUT && fs8.existsSync(env.GITHUB_OUTPUT)) {
4986
+ fs8.appendFileSync(env.GITHUB_OUTPUT, `${key}=${value}
4584
4987
  `, "utf-8");
4585
4988
  }
4586
4989
  if (env.TF_BUILD === "true") {
@@ -4622,11 +5025,11 @@ function isIgnoredEntry(name) {
4622
5025
  return name === "changes" || name === "active" || name === "node_modules";
4623
5026
  }
4624
5027
  function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
4625
- if (!fs7.existsSync(parentDir)) return;
4626
- const entries = fs7.readdirSync(parentDir, { withFileTypes: true });
5028
+ if (!fs8.existsSync(parentDir)) return;
5029
+ const entries = fs8.readdirSync(parentDir, { withFileTypes: true });
4627
5030
  for (const entry of entries) {
4628
5031
  if (!entry.isDirectory() || isIgnoredEntry(entry.name)) continue;
4629
- const changeDir = path4.join(parentDir, entry.name);
5032
+ const changeDir = path5.join(parentDir, entry.name);
4630
5033
  if (handooffsHasDir(handoffsMap, changeDir)) continue;
4631
5034
  const handoff = loadProductHandoffSidecar(changeDir);
4632
5035
  if (handoff?.id) {
@@ -4637,10 +5040,10 @@ function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
4637
5040
  function scanAllProductHandoffs(rootDir) {
4638
5041
  const handoffsMap = /* @__PURE__ */ new Map();
4639
5042
  const candidateParentDirs = [
4640
- path4.join(rootDir, "specs", "changes", "active"),
4641
- path4.join(rootDir, "specs", "changes", "completed"),
4642
- path4.join(rootDir, "specs"),
4643
- path4.join(rootDir, "openspec", "changes")
5043
+ path5.join(rootDir, "specs", "changes", "active"),
5044
+ path5.join(rootDir, "specs", "changes", "completed"),
5045
+ path5.join(rootDir, "specs"),
5046
+ path5.join(rootDir, "openspec", "changes")
4644
5047
  ];
4645
5048
  for (const parentDir of candidateParentDirs) {
4646
5049
  scanParentDirectoryHandoffs(parentDir, handoffsMap);
@@ -4648,9 +5051,9 @@ function scanAllProductHandoffs(rootDir) {
4648
5051
  return handoffsMap;
4649
5052
  }
4650
5053
  function handooffsHasDir(map, dir) {
4651
- const norm = path4.resolve(dir);
5054
+ const norm = path5.resolve(dir);
4652
5055
  for (const key of map.keys()) {
4653
- if (path4.resolve(key) === norm) return true;
5056
+ if (path5.resolve(key) === norm) return true;
4654
5057
  }
4655
5058
  return false;
4656
5059
  }
@@ -4685,7 +5088,7 @@ function buildTraceabilityHeader(isOk, errors) {
4685
5088
  return [
4686
5089
  "# 360\xB0 Requirements Traceability Matrix (RTM)",
4687
5090
  "",
4688
- `*Verification Date: ${(/* @__PURE__ */ new Date()).toISOString()}*`,
5091
+ `*Verification Date: ${resolveDeterministicDate().toISOString()}*`,
4689
5092
  `*Overall Status: ${isOk ? "100% TRACEABLE (PASSED)" : `ERRORS DETECTED (${errors} gaps)`}*`,
4690
5093
  "",
4691
5094
  "## 1. End-to-End Coverage (PDaC Handoff \u2794 arc42/NAF v4 \u2794 BDD/Gherkin)",
@@ -4747,9 +5150,9 @@ function isCodeTestFile(name) {
4747
5150
  }
4748
5151
  function indexFeatureFile(fullPath, rootDir, map) {
4749
5152
  try {
4750
- const content = fs7.readFileSync(fullPath, "utf-8");
5153
+ const content = fs8.readFileSync(fullPath, "utf-8");
4751
5154
  const tags = extractFeatureTags(content);
4752
- const relFeature = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
5155
+ const relFeature = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
4753
5156
  for (const tag of tags) {
4754
5157
  const existing = map.get(tag) || [];
4755
5158
  if (!existing.includes(relFeature)) existing.push(relFeature);
@@ -4759,11 +5162,11 @@ function indexFeatureFile(fullPath, rootDir, map) {
4759
5162
  }
4760
5163
  }
4761
5164
  function scanFeatureFiles(dir, rootDir, map) {
4762
- if (!fs7.existsSync(dir)) return;
5165
+ if (!fs8.existsSync(dir)) return;
4763
5166
  try {
4764
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
5167
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
4765
5168
  for (const e of entries) {
4766
- const fullPath = path4.join(dir, e.name);
5169
+ const fullPath = path5.join(dir, e.name);
4767
5170
  if (e.isDirectory() && !shouldSkipDir(e.name)) {
4768
5171
  scanFeatureFiles(fullPath, rootDir, map);
4769
5172
  } else if (e.name.endsWith(".feature")) {
@@ -4775,8 +5178,8 @@ function scanFeatureFiles(dir, rootDir, map) {
4775
5178
  }
4776
5179
  function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
4777
5180
  try {
4778
- const content = fs7.readFileSync(fullPath, "utf-8");
4779
- const relTest = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
5181
+ const content = fs8.readFileSync(fullPath, "utf-8");
5182
+ const relTest = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
4780
5183
  for (const artId of reqIds) {
4781
5184
  if (content.includes(artId)) {
4782
5185
  const existing = map.get(artId) || [];
@@ -4788,11 +5191,11 @@ function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
4788
5191
  }
4789
5192
  }
4790
5193
  function scanCodeTestFiles(dir, rootDir, reqIds, map) {
4791
- if (!fs7.existsSync(dir)) return;
5194
+ if (!fs8.existsSync(dir)) return;
4792
5195
  try {
4793
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
5196
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
4794
5197
  for (const e of entries) {
4795
- const fullPath = path4.join(dir, e.name);
5198
+ const fullPath = path5.join(dir, e.name);
4796
5199
  if (e.isDirectory() && !shouldSkipDir(e.name)) {
4797
5200
  scanCodeTestFiles(fullPath, rootDir, reqIds, map);
4798
5201
  } else if (isCodeTestFile(e.name)) {
@@ -4826,9 +5229,9 @@ function isArchitectureViewFile(relFile, frontmatter) {
4826
5229
  }
4827
5230
  function processSingleMdFile(file, rootDir, artifactMap, componentsList, architectureViews) {
4828
5231
  try {
4829
- const content = fs7.readFileSync(file, "utf-8");
5232
+ const content = fs8.readFileSync(file, "utf-8");
4830
5233
  const { frontmatter, body } = parseFrontmatter(content);
4831
- const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
5234
+ const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
4832
5235
  if (frontmatter.id) {
4833
5236
  artifactMap.set(frontmatter.id, {
4834
5237
  file: relFile,
@@ -4993,7 +5396,7 @@ function collectArchitectureViewTraces(reqId, architectureViews) {
4993
5396
  const traces = [];
4994
5397
  for (const view of architectureViews) {
4995
5398
  if (view.content.includes(reqId)) {
4996
- const viewIdentifier = isEntityWithId(view) && view.id ? view.id : path4.basename(view.file);
5399
+ const viewIdentifier = isEntityWithId(view) && view.id ? view.id : path5.basename(view.file);
4997
5400
  traces.push(viewIdentifier);
4998
5401
  }
4999
5402
  }
@@ -5081,16 +5484,16 @@ function isProductionSourceFile(name) {
5081
5484
  return SOURCE_CODE_EXTS.some((ext) => name.endsWith(ext));
5082
5485
  }
5083
5486
  function collectCodeAndTestCorpora(dir, prodChunks = [], testChunks = []) {
5084
- if (!fs7.existsSync(dir)) return { prodCorpus: "", testCorpus: "" };
5487
+ if (!fs8.existsSync(dir)) return { prodCorpus: "", testCorpus: "" };
5085
5488
  try {
5086
- for (const e of fs7.readdirSync(dir, { withFileTypes: true })) {
5087
- const fullPath = path4.join(dir, e.name);
5489
+ for (const e of fs8.readdirSync(dir, { withFileTypes: true })) {
5490
+ const fullPath = path5.join(dir, e.name);
5088
5491
  if (e.isDirectory() && !shouldSkipDir(e.name)) {
5089
5492
  collectCodeAndTestCorpora(fullPath, prodChunks, testChunks);
5090
5493
  } else if (isCodeTestFile(e.name) || e.name.endsWith(".feature")) {
5091
- testChunks.push(fs7.readFileSync(fullPath, "utf-8"));
5494
+ testChunks.push(fs8.readFileSync(fullPath, "utf-8"));
5092
5495
  } else if (isProductionSourceFile(e.name)) {
5093
- prodChunks.push(fs7.readFileSync(fullPath, "utf-8"));
5496
+ prodChunks.push(fs8.readFileSync(fullPath, "utf-8"));
5094
5497
  }
5095
5498
  }
5096
5499
  } catch {
@@ -5244,12 +5647,12 @@ function walkTaskFiles(dir, fileList = [], excludeDirs = [
5244
5647
  "examples"
5245
5648
  ]) {
5246
5649
  try {
5247
- if (!fs7.existsSync(dir)) return fileList;
5248
- const files = fs7.readdirSync(dir);
5650
+ if (!fs8.existsSync(dir)) return fileList;
5651
+ const files = fs8.readdirSync(dir).sort(canonicalSort);
5249
5652
  for (const file of files) {
5250
- const fullPath = path4.join(dir, file);
5653
+ const fullPath = path5.join(dir, file);
5251
5654
  try {
5252
- if (fs7.statSync(fullPath).isDirectory()) {
5655
+ if (fs8.statSync(fullPath).isDirectory()) {
5253
5656
  if (!excludeDirs.includes(file)) {
5254
5657
  walkTaskFiles(fullPath, fileList, excludeDirs);
5255
5658
  }
@@ -5301,7 +5704,7 @@ function generateGovernanceReportMarkdown(summaries, violationsCount, modeCounts
5301
5704
  const sections = [
5302
5705
  `# \u{1F4CB} Task Governance and Human Autonomy Classification Report
5303
5706
 
5304
- > **Audit Date:** ${(/* @__PURE__ */ new Date()).toISOString()}
5707
+ > **Audit Date:** ${resolveDeterministicDate().toISOString()}
5305
5708
  > **Overall Verdict:** ${verdict}
5306
5709
 
5307
5710
  ---`,
@@ -5362,9 +5765,9 @@ function updateGovernanceCounts(t, modeCounts, riskCounts) {
5362
5765
  }
5363
5766
  }
5364
5767
  function parseTasksFromFile(file) {
5365
- if (!fs7.existsSync(file)) return [];
5768
+ if (!fs8.existsSync(file)) return [];
5366
5769
  try {
5367
- const content = fs7.readFileSync(file, "utf-8");
5770
+ const content = fs8.readFileSync(file, "utf-8");
5368
5771
  const { frontmatter } = parseTasksDoc(content);
5369
5772
  return frontmatter.tasks || [];
5370
5773
  } catch {
@@ -5372,11 +5775,11 @@ function parseTasksFromFile(file) {
5372
5775
  }
5373
5776
  }
5374
5777
  function extractDesignExpectedScope(changeDir) {
5375
- const designFile = path4.join(changeDir, "design.md");
5376
- if (!fs7.existsSync(designFile)) return [];
5778
+ const designFile = path5.join(changeDir, "design.md");
5779
+ if (!fs8.existsSync(designFile)) return [];
5377
5780
  const expected = [];
5378
5781
  try {
5379
- const { frontmatter } = parseTasksDoc(fs7.readFileSync(designFile, "utf-8"));
5782
+ const { frontmatter } = parseTasksDoc(fs8.readFileSync(designFile, "utf-8"));
5380
5783
  if (typeof frontmatter["architecture-component"] === "string") {
5381
5784
  expected.push(frontmatter["architecture-component"]);
5382
5785
  }
@@ -5416,7 +5819,7 @@ function hasExplicitScopeBindings(tasks) {
5416
5819
  );
5417
5820
  }
5418
5821
  function auditDesignScopeForFile(file, tasks, options, unassignedList, violationsList) {
5419
- const changeDir = path4.dirname(file);
5822
+ const changeDir = path5.dirname(file);
5420
5823
  const expectedItems = [
5421
5824
  ...extractDesignExpectedScope(changeDir),
5422
5825
  ...extractHandoffExpectedScope(changeDir, options.maxUseCasesPerChange, violationsList)
@@ -5427,13 +5830,13 @@ function auditDesignScopeForFile(file, tasks, options, unassignedList, violation
5427
5830
  for (const item of new Set(expectedItems)) {
5428
5831
  if (!covered.has(item)) {
5429
5832
  unassignedList.push(item);
5430
- violationsList.push(`[${path4.basename(changeDir)}] Unassigned design/handoff scope in tasks.md: missing task coverage for ${item}`);
5833
+ violationsList.push(`[${path5.basename(changeDir)}] Unassigned design/handoff scope in tasks.md: missing task coverage for ${item}`);
5431
5834
  }
5432
5835
  }
5433
5836
  }
5434
5837
  function processTasksFile(file, rootDir, options, acc) {
5435
5838
  const tasks = parseTasksFromFile(file);
5436
- const relPath = path4.relative(rootDir, file).replace(/\\/g, "/");
5839
+ const relPath = path5.relative(rootDir, file).replace(/\\/g, "/");
5437
5840
  let unverifiedDelta = 0;
5438
5841
  for (const t of tasks) {
5439
5842
  const { summary, violation, unverified } = evaluateTaskItem(t, relPath);
@@ -5504,6 +5907,277 @@ function verifyTasksGovernance(options = {}) {
5504
5907
  );
5505
5908
  return assembleGovernanceResult(data, reportMarkdown);
5506
5909
  }
5910
+ var BddStepRegistry = class {
5911
+ definitions = [];
5912
+ register(pattern, handler) {
5913
+ this.definitions.push({ pattern, handler });
5914
+ return this;
5915
+ }
5916
+ given(pattern, handler) {
5917
+ return this.register(pattern, handler);
5918
+ }
5919
+ when(pattern, handler) {
5920
+ return this.register(pattern, handler);
5921
+ }
5922
+ then(pattern, handler) {
5923
+ return this.register(pattern, handler);
5924
+ }
5925
+ getDefinitions() {
5926
+ return this.definitions;
5927
+ }
5928
+ };
5929
+ var STEP_PREFIX_RE = /^(Given|When|Then|And|But|Dado|Cuando|Entonces|Y|Pero)\s+(.+)$/i;
5930
+ var SCENARIO_HEADER_RE = /^(Scenario|Escenario|Scenario Outline|Esquema del escenario):\s*(.+)$/i;
5931
+ function parseTableRowCells(line) {
5932
+ return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
5933
+ }
5934
+ function attachStepTableRow(lastStep, cells) {
5935
+ if (!lastStep || cells.length < 2) return;
5936
+ if (!lastStep.table) lastStep.table = {};
5937
+ lastStep.table[cells[0]] = cells[1];
5938
+ }
5939
+ function attachExampleRow(scenario, headers, cells) {
5940
+ if (!scenario) return headers;
5941
+ if (headers.length === 0) return cells;
5942
+ const row = {};
5943
+ for (let i = 0; i < headers.length; i++) {
5944
+ row[headers[i]] = cells[i] ?? "";
5945
+ }
5946
+ scenario.examples.push(row);
5947
+ return headers;
5948
+ }
5949
+ function createInitialParseState() {
5950
+ return {
5951
+ featureName: "Unnamed Feature",
5952
+ featureTags: [],
5953
+ pendingTags: [],
5954
+ backgroundSteps: [],
5955
+ scenarios: [],
5956
+ currentScenario: null,
5957
+ inBackground: false,
5958
+ inExamples: false,
5959
+ exampleHeaders: []
5960
+ };
5961
+ }
5962
+ function handleStructuralHeaderLine(line, state) {
5963
+ if (/^(Feature|Caracter[ií]stica):/i.test(line)) {
5964
+ state.featureName = line.replace(/^(Feature|Caracter[ií]stica):\s*/i, "").trim();
5965
+ state.featureTags.push(...state.pendingTags);
5966
+ state.pendingTags = [];
5967
+ return true;
5968
+ }
5969
+ if (/^(Background|Antecedentes):/i.test(line)) {
5970
+ state.inBackground = true;
5971
+ state.inExamples = false;
5972
+ state.currentScenario = null;
5973
+ return true;
5974
+ }
5975
+ if (/^(Examples|Ejemplos):/i.test(line)) {
5976
+ state.inExamples = true;
5977
+ state.exampleHeaders = [];
5978
+ return true;
5979
+ }
5980
+ return false;
5981
+ }
5982
+ function startScenarioBlock(scenMatch, state) {
5983
+ state.inBackground = false;
5984
+ state.inExamples = false;
5985
+ const block = {
5986
+ name: scenMatch[2].trim(),
5987
+ tags: [...state.featureTags, ...state.pendingTags],
5988
+ isOutline: /outline|esquema/i.test(scenMatch[1]),
5989
+ steps: [],
5990
+ examples: []
5991
+ };
5992
+ state.pendingTags = [];
5993
+ state.scenarios.push(block);
5994
+ state.currentScenario = block;
5995
+ }
5996
+ function appendParsedStep(stepMatch, state) {
5997
+ const step = { keyword: stepMatch[1], text: stepMatch[2].trim() };
5998
+ if (state.inBackground) {
5999
+ state.backgroundSteps.push(step);
6000
+ } else if (state.currentScenario) {
6001
+ state.currentScenario.steps.push(step);
6002
+ }
6003
+ }
6004
+ function handleScenarioOrStepLine(line, state) {
6005
+ const scenMatch = line.match(SCENARIO_HEADER_RE);
6006
+ if (scenMatch) {
6007
+ startScenarioBlock(scenMatch, state);
6008
+ return;
6009
+ }
6010
+ const stepMatch = line.match(STEP_PREFIX_RE);
6011
+ if (stepMatch) {
6012
+ appendParsedStep(stepMatch, state);
6013
+ }
6014
+ }
6015
+ function processFeatureLine(rawLine, state) {
6016
+ const line = rawLine.trim();
6017
+ if (!line || line.startsWith("#")) return;
6018
+ if (line.startsWith("@")) {
6019
+ const tags = line.split(/\s+/).filter((t) => t.startsWith("@"));
6020
+ state.pendingTags.push(...tags);
6021
+ return;
6022
+ }
6023
+ if (line.startsWith("|")) {
6024
+ const cells = parseTableRowCells(line);
6025
+ if (state.inExamples) {
6026
+ state.exampleHeaders = attachExampleRow(state.currentScenario, state.exampleHeaders, cells);
6027
+ } else {
6028
+ const targetList = state.inBackground ? state.backgroundSteps : state.currentScenario?.steps || [];
6029
+ attachStepTableRow(targetList[targetList.length - 1], cells);
6030
+ }
6031
+ return;
6032
+ }
6033
+ if (!handleStructuralHeaderLine(line, state)) {
6034
+ handleScenarioOrStepLine(line, state);
6035
+ }
6036
+ }
6037
+ function parseFeatureDocument(content) {
6038
+ const state = createInitialParseState();
6039
+ for (const rawLine of content.split(/\r?\n/)) {
6040
+ processFeatureLine(rawLine, state);
6041
+ }
6042
+ return {
6043
+ featureName: state.featureName,
6044
+ tags: state.featureTags,
6045
+ backgroundSteps: state.backgroundSteps,
6046
+ scenarios: state.scenarios
6047
+ };
6048
+ }
6049
+ function interpolatePlaceholders(text, row) {
6050
+ let out = text;
6051
+ for (const [k, v] of Object.entries(row)) {
6052
+ out = out.replace(new RegExp(`<${k}>`, "g"), v);
6053
+ }
6054
+ return out;
6055
+ }
6056
+ function expandScenarioOutlines(scenarios) {
6057
+ const expanded = [];
6058
+ for (const sc of scenarios) {
6059
+ if (!sc.isOutline || sc.examples.length === 0) {
6060
+ expanded.push(sc);
6061
+ continue;
6062
+ }
6063
+ sc.examples.forEach((row, idx) => {
6064
+ expanded.push({
6065
+ name: `${sc.name} (Example #${idx + 1})`,
6066
+ tags: sc.tags,
6067
+ isOutline: false,
6068
+ steps: sc.steps.map((st) => ({
6069
+ keyword: st.keyword,
6070
+ text: interpolatePlaceholders(st.text, row),
6071
+ table: st.table
6072
+ })),
6073
+ examples: []
6074
+ });
6075
+ });
6076
+ }
6077
+ return expanded;
6078
+ }
6079
+ function matchStepDefinition(text, definitions) {
6080
+ for (const entry of definitions) {
6081
+ if (typeof entry.pattern === "string") {
6082
+ if (entry.pattern === text) return { entry, args: [] };
6083
+ } else {
6084
+ const m = text.match(entry.pattern);
6085
+ if (m) return { entry, args: m.slice(1) };
6086
+ }
6087
+ }
6088
+ return null;
6089
+ }
6090
+ async function executeSingleStep(step, definitions) {
6091
+ const matched = matchStepDefinition(step.text, definitions);
6092
+ if (!matched) {
6093
+ return { keyword: step.keyword, text: step.text, status: "undefined", durationMs: 0 };
6094
+ }
6095
+ const start = Date.now();
6096
+ try {
6097
+ await matched.entry.handler(matched.args, step.table);
6098
+ return { keyword: step.keyword, text: step.text, status: "passed", durationMs: Date.now() - start };
6099
+ } catch (err) {
6100
+ const msg = err instanceof Error ? err.message : String(err);
6101
+ return {
6102
+ keyword: step.keyword,
6103
+ text: step.text,
6104
+ status: "failed",
6105
+ durationMs: Date.now() - start,
6106
+ errorMessage: msg
6107
+ };
6108
+ }
6109
+ }
6110
+ async function executeScenarioSteps(allSteps, definitions) {
6111
+ const stepResults = [];
6112
+ let status = "passed";
6113
+ for (const step of allSteps) {
6114
+ if (status !== "passed") {
6115
+ stepResults.push({ keyword: step.keyword, text: step.text, status: "skipped", durationMs: 0 });
6116
+ continue;
6117
+ }
6118
+ const res = await executeSingleStep(step, definitions);
6119
+ stepResults.push(res);
6120
+ if (res.status === "failed" || res.status === "undefined") {
6121
+ status = res.status;
6122
+ }
6123
+ }
6124
+ return { stepResults, status };
6125
+ }
6126
+ async function runAllExpandedScenarios(parsed, definitions) {
6127
+ const expanded = expandScenarioOutlines(parsed.scenarios);
6128
+ const out = [];
6129
+ for (const sc of expanded) {
6130
+ const combinedSteps = [...parsed.backgroundSteps, ...sc.steps];
6131
+ const { stepResults, status } = await executeScenarioSteps(combinedSteps, definitions);
6132
+ out.push({ name: sc.name, tags: sc.tags, status, steps: stepResults });
6133
+ }
6134
+ return out;
6135
+ }
6136
+ async function executeBddFeature(featurePath, registry, options = {}) {
6137
+ const rootDir = options.rootDir || process.cwd();
6138
+ const fullPath = path5.isAbsolute(featurePath) ? featurePath : path5.join(rootDir, featurePath);
6139
+ const parsed = parseFeatureDocument(fs8.readFileSync(fullPath, "utf-8"));
6140
+ const scenarios = await runAllExpandedScenarios(parsed, registry.getDefinitions());
6141
+ const report = {
6142
+ uri: path5.relative(rootDir, fullPath).replace(/\\/g, "/"),
6143
+ featureName: parsed.featureName,
6144
+ tags: parsed.tags,
6145
+ scenarios,
6146
+ passed: scenarios.length > 0 && scenarios.every((s) => s.status === "passed")
6147
+ };
6148
+ if (options.writeReportPath) writeCucumberJsonReport([report], options.writeReportPath);
6149
+ return report;
6150
+ }
6151
+ function mapStepToCucumberJson(st) {
6152
+ return {
6153
+ keyword: `${st.keyword} `,
6154
+ name: st.text,
6155
+ result: {
6156
+ status: st.status,
6157
+ duration: st.durationMs * 1e6,
6158
+ ...st.errorMessage ? { error_message: st.errorMessage } : {}
6159
+ }
6160
+ };
6161
+ }
6162
+ function mapFeatureToCucumberJson(r) {
6163
+ return {
6164
+ uri: r.uri,
6165
+ name: r.featureName,
6166
+ tags: r.tags.map((t) => ({ name: t })),
6167
+ elements: r.scenarios.map((sc) => ({
6168
+ type: "scenario",
6169
+ name: sc.name,
6170
+ tags: sc.tags.map((t) => ({ name: t })),
6171
+ steps: sc.steps.map(mapStepToCucumberJson)
6172
+ }))
6173
+ };
6174
+ }
6175
+ function writeCucumberJsonReport(reports, outputPath) {
6176
+ const dir = path5.dirname(outputPath);
6177
+ if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
6178
+ const payload = JSON.stringify(reports.map(mapFeatureToCucumberJson), null, 2);
6179
+ fs8.writeFileSync(outputPath, payload, "utf-8");
6180
+ }
5507
6181
  function isToolAvailable(toolName) {
5508
6182
  try {
5509
6183
  const cmd = process.platform === "win32" ? "where" : "which";
@@ -5548,8 +6222,8 @@ function findLicenseFile(pkgDir) {
5548
6222
  "license.txt"
5549
6223
  ];
5550
6224
  for (const c of candidates) {
5551
- const full = path4.join(pkgDir, c);
5552
- if (fs7.existsSync(full)) {
6225
+ const full = path5.join(pkgDir, c);
6226
+ if (fs8.existsSync(full)) {
5553
6227
  return full;
5554
6228
  }
5555
6229
  }
@@ -5566,9 +6240,9 @@ var LICENSE_FILE_PATTERNS = [
5566
6240
  [(t) => t.includes("Microsoft Reciprocal License") || t.includes("MS-RL"), "MS-RL"]
5567
6241
  ];
5568
6242
  function resolveLicenseFromFile(licenseFilePath) {
5569
- if (!licenseFilePath || !fs7.existsSync(licenseFilePath)) return void 0;
6243
+ if (!licenseFilePath || !fs8.existsSync(licenseFilePath)) return void 0;
5570
6244
  try {
5571
- const text = fs7.readFileSync(licenseFilePath, "utf-8");
6245
+ const text = fs8.readFileSync(licenseFilePath, "utf-8");
5572
6246
  for (const [matches, spdx] of LICENSE_FILE_PATTERNS) {
5573
6247
  if (matches(text)) return spdx;
5574
6248
  }
@@ -5577,16 +6251,16 @@ function resolveLicenseFromFile(licenseFilePath) {
5577
6251
  return void 0;
5578
6252
  }
5579
6253
  function readPackageJson(filePath) {
5580
- if (!fs7.existsSync(filePath)) return null;
6254
+ if (!fs8.existsSync(filePath)) return null;
5581
6255
  try {
5582
- return JSON.parse(fs7.readFileSync(filePath, "utf-8"));
6256
+ return JSON.parse(fs8.readFileSync(filePath, "utf-8"));
5583
6257
  } catch {
5584
6258
  return null;
5585
6259
  }
5586
6260
  }
5587
6261
  function extractDirectDepNames(rootDir, productionOnly) {
5588
6262
  const names = /* @__PURE__ */ new Set();
5589
- const pkgPath = path4.join(rootDir, "package.json");
6263
+ const pkgPath = path5.join(rootDir, "package.json");
5590
6264
  const pkg = readPackageJson(pkgPath);
5591
6265
  if (!pkg) return names;
5592
6266
  if (pkg.dependencies && typeof pkg.dependencies === "object") {
@@ -5606,7 +6280,7 @@ function resolveSpdxForPackage(pkg, licFile) {
5606
6280
  return spdx;
5607
6281
  }
5608
6282
  function processPackageDir(pkgDir, directNames, seen) {
5609
- const pkg = readPackageJson(path4.join(pkgDir, "package.json"));
6283
+ const pkg = readPackageJson(path5.join(pkgDir, "package.json"));
5610
6284
  if (!pkg?.name || !pkg.version) return;
5611
6285
  const key = `${pkg.name}@${pkg.version}`;
5612
6286
  if (seen.has(key)) return;
@@ -5627,40 +6301,40 @@ function processPackageDir(pkgDir, directNames, seen) {
5627
6301
  });
5628
6302
  }
5629
6303
  function scanScopeSubdirectory(scopeDir, directNames, seen) {
5630
- const scopedEntries = fs7.readdirSync(scopeDir, { withFileTypes: true });
6304
+ const scopedEntries = fs8.readdirSync(scopeDir, { withFileTypes: true });
5631
6305
  for (const scoped of scopedEntries) {
5632
6306
  if (scoped.isDirectory()) {
5633
- processPackageDir(path4.join(scopeDir, scoped.name), directNames, seen);
6307
+ processPackageDir(path5.join(scopeDir, scoped.name), directNames, seen);
5634
6308
  }
5635
6309
  }
5636
6310
  }
5637
6311
  function scanSubdirectories(baseDir, directNames, seen) {
5638
- if (!fs7.existsSync(baseDir)) return;
5639
- const entries = fs7.readdirSync(baseDir, { withFileTypes: true });
6312
+ if (!fs8.existsSync(baseDir)) return;
6313
+ const entries = fs8.readdirSync(baseDir, { withFileTypes: true });
5640
6314
  for (const entry of entries) {
5641
6315
  if (!entry.isDirectory()) continue;
5642
6316
  if (entry.name.startsWith("@")) {
5643
- scanScopeSubdirectory(path4.join(baseDir, entry.name), directNames, seen);
6317
+ scanScopeSubdirectory(path5.join(baseDir, entry.name), directNames, seen);
5644
6318
  } else if (entry.name !== ".bin" && entry.name !== ".pnpm") {
5645
- processPackageDir(path4.join(baseDir, entry.name), directNames, seen);
6319
+ processPackageDir(path5.join(baseDir, entry.name), directNames, seen);
5646
6320
  }
5647
6321
  }
5648
6322
  }
5649
6323
  function scanPnpmStore(pnpmDir, directNames, seen) {
5650
- if (!fs7.existsSync(pnpmDir)) return;
5651
- const entries = fs7.readdirSync(pnpmDir, { withFileTypes: true });
6324
+ if (!fs8.existsSync(pnpmDir)) return;
6325
+ const entries = fs8.readdirSync(pnpmDir, { withFileTypes: true });
5652
6326
  for (const entry of entries) {
5653
6327
  if (!entry.isDirectory()) continue;
5654
- const candidateModules = path4.join(pnpmDir, entry.name, "node_modules");
5655
- if (fs7.existsSync(candidateModules)) {
6328
+ const candidateModules = path5.join(pnpmDir, entry.name, "node_modules");
6329
+ if (fs8.existsSync(candidateModules)) {
5656
6330
  scanSubdirectories(candidateModules, directNames, seen);
5657
6331
  }
5658
6332
  }
5659
6333
  }
5660
6334
  function readTextIfExists(filePath) {
5661
- if (!fs7.existsSync(filePath)) return null;
6335
+ if (!fs8.existsSync(filePath)) return null;
5662
6336
  try {
5663
- return fs7.readFileSync(filePath, "utf-8");
6337
+ return fs8.readFileSync(filePath, "utf-8");
5664
6338
  } catch {
5665
6339
  return null;
5666
6340
  }
@@ -5720,14 +6394,14 @@ function parsePipfileSection(pkgs, isDirect, lockPath, seen) {
5720
6394
  }
5721
6395
  }
5722
6396
  function scanPythonDependencies(rootDir, seen) {
5723
- const pyproject = readTextIfExists(path4.join(rootDir, "pyproject.toml")) || "";
6397
+ const pyproject = readTextIfExists(path5.join(rootDir, "pyproject.toml")) || "";
5724
6398
  const directNames = extractPyprojectDirectDeps(pyproject);
5725
6399
  for (const lockName of ["poetry.lock", "uv.lock"]) {
5726
- const lockPath = path4.join(rootDir, lockName);
6400
+ const lockPath = path5.join(rootDir, lockName);
5727
6401
  const content = readTextIfExists(lockPath);
5728
6402
  if (content) parseTomlPackageBlocks(content, lockPath, directNames, seen);
5729
6403
  }
5730
- const pipfileLockPath = path4.join(rootDir, "Pipfile.lock");
6404
+ const pipfileLockPath = path5.join(rootDir, "Pipfile.lock");
5731
6405
  const pipfileRaw = readTextIfExists(pipfileLockPath);
5732
6406
  if (!pipfileRaw) return;
5733
6407
  try {
@@ -5770,7 +6444,7 @@ function extractGoCandidateLines(content) {
5770
6444
  return out;
5771
6445
  }
5772
6446
  function scanGoDependencies(rootDir, seen) {
5773
- const goModPath = path4.join(rootDir, "go.mod");
6447
+ const goModPath = path5.join(rootDir, "go.mod");
5774
6448
  const content = readTextIfExists(goModPath);
5775
6449
  if (!content) return;
5776
6450
  for (const line of extractGoCandidateLines(content)) {
@@ -5813,10 +6487,10 @@ function parseCargoLockBlock(block, rootName, directMeta, cargoLockPath) {
5813
6487
  };
5814
6488
  }
5815
6489
  function scanRustDependencies(rootDir, seen) {
5816
- const cargoLockPath = path4.join(rootDir, "Cargo.lock");
6490
+ const cargoLockPath = path5.join(rootDir, "Cargo.lock");
5817
6491
  const lockContent = readTextIfExists(cargoLockPath);
5818
6492
  if (!lockContent) return;
5819
- const cargoToml = readTextIfExists(path4.join(rootDir, "Cargo.toml")) || "";
6493
+ const cargoToml = readTextIfExists(path5.join(rootDir, "Cargo.toml")) || "";
5820
6494
  const directMeta = extractCargoDirectDeps(cargoToml);
5821
6495
  const rootName = extractTomlField(cargoToml, "name");
5822
6496
  for (const block of lockContent.split(/\[\[package\]\]/g).slice(1)) {
@@ -5852,7 +6526,7 @@ function parseMavenDepBlock(block, defaultLic, pomPath) {
5852
6526
  };
5853
6527
  }
5854
6528
  function scanMavenDependencies(rootDir, seen) {
5855
- const pomPath = path4.join(rootDir, "pom.xml");
6529
+ const pomPath = path5.join(rootDir, "pom.xml");
5856
6530
  const content = readTextIfExists(pomPath);
5857
6531
  if (!content) return;
5858
6532
  const projectLicBlock = content.match(/<licenses>([\s\S]*?)<\/licenses>/)?.[1] || "";
@@ -5868,11 +6542,11 @@ function scanMavenDependencies(rootDir, seen) {
5868
6542
  function scanWithNativeFs(rootDir, options = {}) {
5869
6543
  const directNames = extractDirectDepNames(rootDir, options.productionOnly);
5870
6544
  const seen = /* @__PURE__ */ new Map();
5871
- const nmDir = path4.join(rootDir, "node_modules");
5872
- if (fs7.existsSync(nmDir)) {
6545
+ const nmDir = path5.join(rootDir, "node_modules");
6546
+ if (fs8.existsSync(nmDir)) {
5873
6547
  scanSubdirectories(nmDir, directNames, seen);
5874
6548
  if (options.depth !== "direct") {
5875
- scanPnpmStore(path4.join(nmDir, ".pnpm"), directNames, seen);
6549
+ scanPnpmStore(path5.join(nmDir, ".pnpm"), directNames, seen);
5876
6550
  }
5877
6551
  }
5878
6552
  scanPythonDependencies(rootDir, seen);
@@ -6131,20 +6805,20 @@ function resolveProjectIdentity(options) {
6131
6805
  let name = options.projectName || "ai-sdlc-project";
6132
6806
  let version = options.projectVersion || "1.0.0";
6133
6807
  if (!options.rootDir) return { name, version };
6134
- const pkgPath = path4.join(options.rootDir, "package.json");
6135
- if (!fs7.existsSync(pkgPath)) return { name, version };
6808
+ const pkgPath = path5.join(options.rootDir, "package.json");
6809
+ if (!fs8.existsSync(pkgPath)) return { name, version };
6136
6810
  try {
6137
- const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
6811
+ const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
6138
6812
  if (pkg.name) name = pkg.name;
6139
6813
  if (pkg.version) version = pkg.version;
6140
6814
  } catch {
6141
6815
  }
6142
6816
  return { name, version };
6143
6817
  }
6144
- function buildBomMetadata(name, version) {
6818
+ function buildBomMetadata(name, version, rootDir) {
6145
6819
  return {
6146
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6147
- tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.3.0" }],
6820
+ timestamp: resolveDeterministicDate(rootDir).toISOString(),
6821
+ tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.5.0" }],
6148
6822
  component: { type: "application", name, version }
6149
6823
  };
6150
6824
  }
@@ -6155,52 +6829,21 @@ function generateCycloneDxSbom(dependencies, options = {}) {
6155
6829
  bomFormat: "CycloneDX",
6156
6830
  specVersion: "1.5",
6157
6831
  version: 1,
6158
- serialNumber: `urn:uuid:${crypto.randomUUID()}`,
6159
- metadata: buildBomMetadata(name, version),
6832
+ serialNumber: `urn:uuid:${generateDeterministicUuidV5(`${name}@${version}|${dependencies.map((d) => `${d.name}@${d.version}:${d.purl || ""}`).sort(canonicalSort).join(";")}`)}`,
6833
+ metadata: buildBomMetadata(name, version, options.rootDir),
6160
6834
  components: dependencies.map(buildCycloneDxComponent),
6161
6835
  ...cdxVulns.length > 0 ? { vulnerabilities: cdxVulns } : {}
6162
6836
  };
6163
6837
  }
6164
- function tryAtomicWrite(filePath, content) {
6165
- const tempPath = `${filePath}.${process.pid}.${Math.random().toString(36).substring(2)}.tmp`;
6166
- try {
6167
- fs7.writeFileSync(tempPath, content, "utf-8");
6168
- try {
6169
- if (fs7.existsSync(filePath)) {
6170
- fs7.rmSync(filePath, { force: true });
6171
- }
6172
- fs7.renameSync(tempPath, filePath);
6173
- } catch {
6174
- fs7.copyFileSync(tempPath, filePath);
6175
- fs7.unlinkSync(tempPath);
6176
- }
6177
- return true;
6178
- } catch {
6179
- return false;
6180
- }
6181
- }
6182
- function safeWriteFileSync(filePath, content) {
6183
- const dir = path4.dirname(filePath);
6184
- if (!fs7.existsSync(dir)) {
6185
- fs7.mkdirSync(dir, { recursive: true });
6186
- }
6187
- for (let attempt = 0; attempt < 10; attempt++) {
6188
- if (tryAtomicWrite(filePath, content)) return;
6189
- }
6190
- try {
6191
- fs7.writeFileSync(filePath, content, "utf-8");
6192
- } catch {
6193
- }
6194
- }
6195
6838
  function writeCycloneDxSbom(sbom, outputPath) {
6196
- safeWriteFileSync(outputPath, JSON.stringify(sbom, null, 2));
6839
+ writeReportFileIfChanged(outputPath, JSON.stringify(sbom, null, 2));
6197
6840
  }
6198
6841
  function buildThirdPartyHeader(projectName, count) {
6199
6842
  return [
6200
6843
  `# \u{1F4DC} Third-Party Software Notices and Information`,
6201
6844
  ``,
6202
6845
  `> **Proyecto:** ${projectName}`,
6203
- `> **Fecha:** ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
6846
+ `> **Fecha:** ${resolveDeterministicDate().toISOString().split("T")[0]}`,
6204
6847
  `> **Total Componentes:** ${count}`,
6205
6848
  ``,
6206
6849
  `Este documento contiene los avisos de copyright y t\xE9rminos de licencia aplicables a los componentes de terceros utilizados en este proyecto.`,
@@ -6214,9 +6857,9 @@ function buildThirdPartyHeader(projectName, count) {
6214
6857
  ];
6215
6858
  }
6216
6859
  function readLicenseTextPreview(licenseFile) {
6217
- if (!licenseFile || !fs7.existsSync(licenseFile)) return null;
6860
+ if (!licenseFile || !fs8.existsSync(licenseFile)) return null;
6218
6861
  try {
6219
- const text = fs7.readFileSync(licenseFile, "utf-8").trim();
6862
+ const text = fs8.readFileSync(licenseFile, "utf-8").trim();
6220
6863
  return text.slice(0, 1e3) + (text.length > 1e3 ? "\n... (texto truncado)" : "");
6221
6864
  } catch {
6222
6865
  return null;
@@ -6248,13 +6891,13 @@ function generateThirdPartyNotices(dependencies, projectName = "AI-SDLC") {
6248
6891
  "",
6249
6892
  "---",
6250
6893
  "",
6251
- "## Textos de Licencia y Atribuciones",
6894
+ "## 2. Atribuciones y T\xE9rminos de Licencia Individuales",
6252
6895
  "",
6253
6896
  ...attributions
6254
6897
  ].join("\n");
6255
6898
  }
6256
- function writeThirdPartyNotices(content, outputPath) {
6257
- safeWriteFileSync(outputPath, content);
6899
+ function writeThirdPartyNotices(noticesContent, outputPath) {
6900
+ writeReportFileIfChanged(outputPath, noticesContent);
6258
6901
  }
6259
6902
 
6260
6903
  // src/verifiers/licenses.ts
@@ -6331,22 +6974,20 @@ function parseVexStatements(doc) {
6331
6974
  if (!Array.isArray(rawList)) return [];
6332
6975
  return rawList.filter((item) => Boolean(item && typeof item.id === "string" && typeof item.state === "string"));
6333
6976
  }
6334
- function parseLicensePolicy(content) {
6977
+ function parseLicenseDoc(content) {
6978
+ let doc;
6335
6979
  try {
6336
- const doc = yaml3.load(content);
6337
- const { permitted, restricted, blocked } = classifyCategories(doc?.categories);
6338
- const exception_packages = parseExceptions(doc?.exceptions);
6339
- const vex_statements = parseVexStatements(doc);
6340
- return {
6341
- version: doc?.version || "1.0",
6342
- permitted: permitted.length > 0 ? permitted : DEFAULT_PERMITTED,
6343
- restricted: restricted.length > 0 ? restricted : DEFAULT_RESTRICTED,
6344
- blocked: blocked.length > 0 ? blocked : DEFAULT_BLOCKED,
6345
- exception_packages,
6346
- vex_statements,
6347
- rawPolicy: doc
6348
- };
6349
- } catch {
6980
+ doc = yaml3.load(content);
6981
+ } catch (err) {
6982
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in license-policy: ${err instanceof Error ? err.message : String(err)}`);
6983
+ }
6984
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
6985
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid license policy structure: root must be a YAML mapping`);
6986
+ }
6987
+ return doc;
6988
+ }
6989
+ function parseLicensePolicy(content) {
6990
+ if (!content || !content.trim()) {
6350
6991
  return {
6351
6992
  version: "1.0",
6352
6993
  permitted: DEFAULT_PERMITTED,
@@ -6356,6 +6997,17 @@ function parseLicensePolicy(content) {
6356
6997
  vex_statements: []
6357
6998
  };
6358
6999
  }
7000
+ const doc = parseLicenseDoc(content);
7001
+ const { permitted, restricted, blocked } = classifyCategories(doc?.categories);
7002
+ return {
7003
+ version: doc?.version || "1.0",
7004
+ permitted: permitted.length > 0 ? permitted : DEFAULT_PERMITTED,
7005
+ restricted: restricted.length > 0 ? restricted : DEFAULT_RESTRICTED,
7006
+ blocked: blocked.length > 0 ? blocked : DEFAULT_BLOCKED,
7007
+ exception_packages: parseExceptions(doc?.exceptions),
7008
+ vex_statements: parseVexStatements(doc),
7009
+ rawPolicy: doc
7010
+ };
6359
7011
  }
6360
7012
  function evaluateCompoundLicense(lic, policy) {
6361
7013
  const cleanLic = lic.replace(/[()]/g, "").trim();
@@ -6447,7 +7099,7 @@ function buildReportHeader(isOk, depCount, violationCount, sbomPath) {
6447
7099
  return [
6448
7100
  `# \u{1F4DC} Open Source License Compliance Audit (Dynamic SCA)`,
6449
7101
  ``,
6450
- `> **Verification Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
7102
+ `> **Verification Date:** ${resolveDeterministicDate().toISOString()}`,
6451
7103
  `> **Status:** ${statusStr}`,
6452
7104
  `> **Total Audited Dependencies:** ${depCount}`,
6453
7105
  sbomPath ? `> **Generated CycloneDX SBOM:** \`${sbomPath}\`` : "",
@@ -6472,9 +7124,9 @@ function generateLicenseReportMarkdown(dependencies, violations, policy, sbomPat
6472
7124
  return lines.filter(Boolean).join("\n");
6473
7125
  }
6474
7126
  function parseManifestItems(manifestFile) {
6475
- if (!fs7.existsSync(manifestFile)) return [];
7127
+ if (!fs8.existsSync(manifestFile)) return [];
6476
7128
  try {
6477
- const doc = yaml3.load(fs7.readFileSync(manifestFile, "utf-8"));
7129
+ const doc = yaml3.load(fs8.readFileSync(manifestFile, "utf-8"));
6478
7130
  return Array.isArray(doc?.evaluated_dependencies) ? doc.evaluated_dependencies : [];
6479
7131
  } catch {
6480
7132
  return [];
@@ -6506,10 +7158,10 @@ function verifyStaticManifest(manifestFile, policy) {
6506
7158
  function resolveManifestPath(rootDir, customPath) {
6507
7159
  if (customPath) return customPath;
6508
7160
  const candidates = [
6509
- path4.join(rootDir, "compliance", "license-manifest.yaml"),
6510
- path4.join(rootDir, "license-manifest.yaml")
7161
+ path5.join(rootDir, "compliance", "license-manifest.yaml"),
7162
+ path5.join(rootDir, "license-manifest.yaml")
6511
7163
  ];
6512
- return candidates.find((p) => fs7.existsSync(p)) || candidates[0];
7164
+ return candidates.find((p) => fs8.existsSync(p)) || candidates[0];
6513
7165
  }
6514
7166
  function handleStaticManifestVerification(options, rootDir, policy) {
6515
7167
  const manifestFile = resolveManifestPath(rootDir, options.manifestPath);
@@ -6530,7 +7182,7 @@ function shouldGenerateSbom(options, rawPolicy) {
6530
7182
  }
6531
7183
  function maybeGenerateSbom(options, rootDir, rawPolicy, dependencies, vulnerabilities, vexStatements) {
6532
7184
  if (!shouldGenerateSbom(options, rawPolicy)) return void 0;
6533
- const sbomPath = options.sbomPath || path4.join(rootDir, "reports", "sbom.cdx.json");
7185
+ const sbomPath = options.sbomPath || path5.join(rootDir, "reports", "sbom.cdx.json");
6534
7186
  const sbom = generateCycloneDxSbom(dependencies, { rootDir, vulnerabilities, vexStatements });
6535
7187
  writeCycloneDxSbom(sbom, sbomPath);
6536
7188
  return sbomPath;
@@ -6539,7 +7191,7 @@ function maybeGenerateNotices(options, rootDir, rawPolicy, dependencies) {
6539
7191
  const shouldGen = options.generateNotices === true || Boolean(options.noticesPath || rawPolicy?.ci_gates?.output_notices_file);
6540
7192
  if (!shouldGen) return void 0;
6541
7193
  const noticesFileName = rawPolicy?.ci_gates?.output_notices_file || "THIRD_PARTY_NOTICES.md";
6542
- const noticesPath = options.noticesPath || path4.join(rootDir, noticesFileName);
7194
+ const noticesPath = options.noticesPath || path5.join(rootDir, noticesFileName);
6543
7195
  const noticesContent = generateThirdPartyNotices(dependencies);
6544
7196
  writeThirdPartyNotices(noticesContent, noticesPath);
6545
7197
  return noticesPath;
@@ -6551,8 +7203,8 @@ function handleDynamicScaArtifacts(options, rootDir, rawPolicy, dependencies, vu
6551
7203
  };
6552
7204
  }
6553
7205
  function loadLicensePolicy(rootDir, customPath) {
6554
- const policyFile = customPath || path4.join(rootDir, "license-policy.yaml");
6555
- return fs7.existsSync(policyFile) ? parseLicensePolicy(fs7.readFileSync(policyFile, "utf-8")) : parseLicensePolicy("");
7206
+ const policyFile = customPath || path5.join(rootDir, "license-policy.yaml");
7207
+ return fs8.existsSync(policyFile) ? parseLicensePolicy(fs8.readFileSync(policyFile, "utf-8")) : parseLicensePolicy("");
6556
7208
  }
6557
7209
  function evaluateDependencyList(dependencies, policy) {
6558
7210
  const violations = [];
@@ -6656,11 +7308,11 @@ var PROTECTED_BYPASS_PATTERNS = [
6656
7308
  /migrations?[\\/]/i
6657
7309
  ];
6658
7310
  function detectProfileFromChange(changeDir) {
6659
- if (!fs7.existsSync(changeDir)) return "standard";
6660
- const specFile = path4.join(changeDir, "spec.md");
6661
- if (fs7.existsSync(specFile)) {
7311
+ if (!fs8.existsSync(changeDir)) return "standard";
7312
+ const specFile = path5.join(changeDir, "spec.md");
7313
+ if (fs8.existsSync(specFile)) {
6662
7314
  try {
6663
- const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
7315
+ const rawSpec = fs8.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
6664
7316
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
6665
7317
  if (match) {
6666
7318
  const parsedFm = yaml3.load(match[1]);
@@ -6678,24 +7330,29 @@ function isPathProtectedFromPatch(filePath) {
6678
7330
  return PROTECTED_BYPASS_PATTERNS.some((pattern) => pattern.test(normalized));
6679
7331
  }
6680
7332
  function detectBranchChangeMatch(rootDir, dirs) {
6681
- if (!fs7.existsSync(path4.join(rootDir, ".git"))) return void 0;
6682
- try {
6683
- const branch = execFileSync("git", ["branch", "--show-current"], {
6684
- cwd: rootDir,
6685
- encoding: "utf-8",
6686
- stdio: ["pipe", "pipe", "ignore"]
6687
- }).trim().toLowerCase();
6688
- if (!branch) return void 0;
6689
- return dirs.find((d) => branch.includes(path4.basename(d).toLowerCase()));
6690
- } catch {
6691
- return void 0;
7333
+ let branch = "";
7334
+ if (fs8.existsSync(path5.join(rootDir, ".git"))) {
7335
+ try {
7336
+ branch = execFileSync("git", ["branch", "--show-current"], {
7337
+ cwd: rootDir,
7338
+ encoding: "utf-8",
7339
+ stdio: ["pipe", "pipe", "ignore"]
7340
+ }).trim().toLowerCase();
7341
+ } catch {
7342
+ }
7343
+ }
7344
+ if (!branch) {
7345
+ const ciBranch = (process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_REF_NAME || process.env.BRANCH_NAME || "").toLowerCase();
7346
+ branch = ciBranch;
6692
7347
  }
7348
+ if (!branch) return void 0;
7349
+ return dirs.find((d) => branch.includes(path5.basename(d).toLowerCase()));
6693
7350
  }
6694
7351
  function resolveActiveChangeDir(rootDir) {
6695
- const activeRoot = path4.join(rootDir, "specs", "changes", "active");
6696
- if (!fs7.existsSync(activeRoot)) return void 0;
7352
+ const activeRoot = path5.join(rootDir, "specs", "changes", "active");
7353
+ if (!fs8.existsSync(activeRoot)) return void 0;
6697
7354
  try {
6698
- const dirs = fs7.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path4.join(activeRoot, d.name));
7355
+ const dirs = fs8.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path5.join(activeRoot, d.name)).sort((a, b) => canonicalSort(a, b));
6699
7356
  const branchMatch = detectBranchChangeMatch(rootDir, dirs);
6700
7357
  if (branchMatch) return branchMatch;
6701
7358
  return dirs.find((d) => detectProfileFromChange(d) === "patch") || dirs[0];
@@ -6703,8 +7360,12 @@ function resolveActiveChangeDir(rootDir) {
6703
7360
  return void 0;
6704
7361
  }
6705
7362
  }
7363
+ function isEphemeralOrIgnored(file) {
7364
+ const norm = file.replace(/\\/g, "/");
7365
+ return norm.startsWith("scratch/") || norm.startsWith("reports/") || norm.startsWith("coverage/") || norm.startsWith("dist/") || norm.startsWith("temp/") || norm.startsWith("tmp/");
7366
+ }
6706
7367
  function detectGitDiffFiles(rootDir) {
6707
- if (!fs7.existsSync(path4.join(rootDir, ".git"))) return [];
7368
+ if (!fs8.existsSync(path5.join(rootDir, ".git"))) return [];
6708
7369
  try {
6709
7370
  const tracked = execFileSync("git", ["diff", "--name-only", "HEAD"], {
6710
7371
  cwd: rootDir,
@@ -6717,7 +7378,7 @@ function detectGitDiffFiles(rootDir) {
6717
7378
  stdio: ["pipe", "pipe", "ignore"]
6718
7379
  });
6719
7380
  const combined = `${tracked}
6720
- ${untracked}`.split(/\r?\n/).map((f) => f.trim()).filter(Boolean);
7381
+ ${untracked}`.split(/\r?\n/).map((f) => f.trim()).filter((f) => Boolean(f) && !isEphemeralOrIgnored(f));
6721
7382
  return Array.from(new Set(combined));
6722
7383
  } catch {
6723
7384
  return [];
@@ -6727,19 +7388,19 @@ function resolveChangeDirectory(rootDir, options) {
6727
7388
  if (options.changeDir) return options.changeDir;
6728
7389
  if (options.changeId) {
6729
7390
  const candidates = [
6730
- path4.join(rootDir, "specs", options.changeId),
6731
- path4.join(rootDir, "specs", "changes", "active", options.changeId),
6732
- path4.join(rootDir, "examples", "specs", options.changeId),
6733
- path4.join(rootDir, "specs", "changes", "completed", options.changeId)
7391
+ path5.join(rootDir, "specs", options.changeId),
7392
+ path5.join(rootDir, "specs", "changes", "active", options.changeId),
7393
+ path5.join(rootDir, "examples", "specs", options.changeId),
7394
+ path5.join(rootDir, "specs", "changes", "completed", options.changeId)
6734
7395
  ];
6735
- return candidates.find((c) => fs7.existsSync(c));
7396
+ return candidates.find((c) => fs8.existsSync(c));
6736
7397
  }
6737
7398
  return resolveActiveChangeDir(rootDir);
6738
7399
  }
6739
7400
  function collectPatchViolations(rootDir, modifiedFiles) {
6740
7401
  const violations = [];
6741
7402
  for (const rawFile of modifiedFiles) {
6742
- const relPath = path4.isAbsolute(rawFile) ? path4.relative(rootDir, rawFile) : rawFile;
7403
+ const relPath = path5.isAbsolute(rawFile) ? path5.relative(rootDir, rawFile) : rawFile;
6743
7404
  if (isPathProtectedFromPatch(relPath)) {
6744
7405
  violations.push(
6745
7406
  `[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'.`
@@ -6752,7 +7413,7 @@ function buildFrictionHeader(profile, success, fileCount) {
6752
7413
  return [
6753
7414
  `# \u{1F6E1}\uFE0F Progressive Friction and Anti-Bypass Verification Report`,
6754
7415
  ``,
6755
- `> **Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
7416
+ `> **Date:** ${resolveDeterministicDate().toISOString()}`,
6756
7417
  `> **Evaluated Profile:** \`${profile.toUpperCase()}\``,
6757
7418
  `> **Verdict:** ${success ? "COMPLIANT \u2705" : "BLOCKED \u274C"}`,
6758
7419
  ``,
@@ -6845,13 +7506,13 @@ function extractManualMetadata(fm, id, manualType, relPath, body) {
6845
7506
  }
6846
7507
  function buildManualItem(rootDir, filePath) {
6847
7508
  try {
6848
- const raw = fs7.readFileSync(filePath, "utf-8");
7509
+ const raw = fs8.readFileSync(filePath, "utf-8");
6849
7510
  const { fm, body } = parseMdFrontmatter(raw);
6850
7511
  const id = typeof fm.id === "string" ? fm.id.trim() : "";
6851
7512
  const rawType = typeof fm.type === "string" ? fm.type.trim() : "";
6852
7513
  const manualType = resolveManualType(id, rawType);
6853
7514
  if (!id || !manualType) return null;
6854
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
7515
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
6855
7516
  return extractManualMetadata(fm, id, manualType, relPath, body);
6856
7517
  } catch {
6857
7518
  return null;
@@ -6862,8 +7523,8 @@ function scanManualsInWorkspace(rootDir) {
6862
7523
  const seenIds = /* @__PURE__ */ new Set();
6863
7524
  const items = [];
6864
7525
  for (const relDir of manualDirs) {
6865
- const fullDir = path4.join(rootDir, relDir);
6866
- if (!fs7.existsSync(fullDir)) continue;
7526
+ const fullDir = path5.join(rootDir, relDir);
7527
+ if (!fs8.existsSync(fullDir)) continue;
6867
7528
  for (const file of walkMdFiles(fullDir)) {
6868
7529
  const item = buildManualItem(rootDir, file);
6869
7530
  if (item && !seenIds.has(item.id)) {
@@ -6886,10 +7547,10 @@ function classifyBaselineId(id, type, buckets) {
6886
7547
  }
6887
7548
  }
6888
7549
  function processBaselineFile(file, rootDir, buckets) {
6889
- const rel = path4.relative(rootDir, file).replace(/\\/g, "/");
7550
+ const rel = path5.relative(rootDir, file).replace(/\\/g, "/");
6890
7551
  if (rel.includes("/changes/") || rel.startsWith("changes/")) return;
6891
7552
  try {
6892
- const { fm } = parseMdFrontmatter(fs7.readFileSync(file, "utf-8"));
7553
+ const { fm } = parseMdFrontmatter(fs8.readFileSync(file, "utf-8"));
6893
7554
  const id = typeof fm.id === "string" ? fm.id.trim() : "";
6894
7555
  const status = typeof fm.status === "string" ? fm.status.trim().toLowerCase() : "active";
6895
7556
  if (!id || status === "retired" || status === "deprecated") return;
@@ -6910,8 +7571,8 @@ function collectBaselineTargets(rootDir) {
6910
7571
  enclaves: /* @__PURE__ */ new Set()
6911
7572
  };
6912
7573
  for (const dir of ["product", "architecture", "security", "specs"]) {
6913
- const fullDir = path4.join(rootDir, dir);
6914
- if (fs7.existsSync(fullDir)) {
7574
+ const fullDir = path5.join(rootDir, dir);
7575
+ if (fs8.existsSync(fullDir)) {
6915
7576
  scanDirectoryForBaseline(rootDir, fullDir, buckets);
6916
7577
  }
6917
7578
  }
@@ -6968,10 +7629,10 @@ function detectCurrentGitBranch(rootDir) {
6968
7629
  }
6969
7630
  }
6970
7631
  function resolveActiveChangeProfile(rootDir) {
6971
- const activeRoot = path4.join(rootDir, "specs", "changes", "active");
6972
- if (!fs7.existsSync(activeRoot)) return "standard";
7632
+ const activeRoot = path5.join(rootDir, "specs", "changes", "active");
7633
+ if (!fs8.existsSync(activeRoot)) return "standard";
6973
7634
  try {
6974
- const dirs = fs7.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path4.join(activeRoot, d.name));
7635
+ const dirs = fs8.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path5.join(activeRoot, d.name));
6975
7636
  const profiles = dirs.map((d) => detectProfileFromChange(d));
6976
7637
  if (profiles.includes("critical")) return "critical";
6977
7638
  if (profiles.includes("patch") && profiles.length === 1) return "patch";
@@ -7108,7 +7769,7 @@ var REQ_TYPES = /* @__PURE__ */ new Set(["requirement", "security-requirement",
7108
7769
  var REQ_CATEGORIES = /* @__PURE__ */ new Set(["functional", "quality", "constraint", "security", "safety"]);
7109
7770
  function computeSha256(content) {
7110
7771
  const normalized = content.replace(/\r\n/g, "\n");
7111
- return crypto.createHash("sha256").update(normalized, "utf-8").digest("hex");
7772
+ return crypto3.createHash("sha256").update(normalized, "utf-8").digest("hex");
7112
7773
  }
7113
7774
  function tokenizeTitle(title) {
7114
7775
  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));
@@ -7184,11 +7845,11 @@ function buildRequirementObject(content, fm, body, filePath, relFile) {
7184
7845
  }
7185
7846
  function parseRequirementFile(filePath, rootDir) {
7186
7847
  try {
7187
- const content = fs7.readFileSync(filePath, "utf-8");
7848
+ const content = fs8.readFileSync(filePath, "utf-8");
7188
7849
  const extracted = extractParsedFrontmatter(content);
7189
7850
  if (!extracted) return null;
7190
7851
  const body = content.substring(extracted.matchLen);
7191
- const relFile = path4.relative(rootDir, filePath).replace(/\\/g, "/");
7852
+ const relFile = path5.relative(rootDir, filePath).replace(/\\/g, "/");
7192
7853
  return buildRequirementObject(content, extracted.fm, body, filePath, relFile);
7193
7854
  } catch {
7194
7855
  return null;
@@ -7200,7 +7861,7 @@ function isIgnoredScanPath(relFile) {
7200
7861
  function collectRequirements(rootDir) {
7201
7862
  const reqs = [];
7202
7863
  for (const file of walkMdFiles(rootDir)) {
7203
- const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
7864
+ const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
7204
7865
  if (isIgnoredScanPath(relFile)) continue;
7205
7866
  const req = parseRequirementFile(file, rootDir);
7206
7867
  if (req) reqs.push(req);
@@ -7334,7 +7995,7 @@ function buildDuplicatesHeader(result) {
7334
7995
  return [
7335
7996
  `# \u{1F6E1}\uFE0F Duplicate Requirements and Redundancy Audit (Shift-Left Pre-Flight)`,
7336
7997
  ``,
7337
- `> **Evaluation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
7998
+ `> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
7338
7999
  `> **Verdict:** ${isOk ? "\u2705 COMPLIANT (No blocking duplicates)" : `\u274C BLOCKED (${result.errorCount} duplicate errors detected)`}`,
7339
8000
  `> **Audited Requirements:** ${result.totalRequirements} | **Errors:** ${result.errorCount} | **Warnings:** ${result.warningCount}`,
7340
8001
  ``,
@@ -7457,7 +8118,7 @@ function buildAuditHeader(total, active, expired, invalid) {
7457
8118
  return [
7458
8119
  "# \u{1F4CB} Security Suppressions and Audit Exceptions Inventory",
7459
8120
  "",
7460
- `> **Generated at:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
8121
+ `> **Generated at:** ${resolveDeterministicDate().toISOString()}`,
7461
8122
  `> **Total Audited Suppressions:** ${total} | **Active:** ${active} | **Expired:** ${expired} | **Invalid:** ${invalid}`,
7462
8123
  "",
7463
8124
  "## 1. Executive Summary",
@@ -7688,10 +8349,10 @@ function isPathExcluded2(relPath, patterns) {
7688
8349
  function walkProjectFiles(dir, rootDir, excludedPatterns = DEFAULT_EXCLUDED_PATTERNS) {
7689
8350
  const results = [];
7690
8351
  try {
7691
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
8352
+ const entries = fs8.readdirSync(dir, { withFileTypes: true }).sort((a, b) => canonicalSort(a.name, b.name));
7692
8353
  for (const entry of entries) {
7693
- const fullPath = path4.join(dir, entry.name);
7694
- const relPath = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
8354
+ const fullPath = path5.join(dir, entry.name);
8355
+ const relPath = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
7695
8356
  if (isPathExcluded2(relPath, excludedPatterns)) continue;
7696
8357
  if (entry.isDirectory()) {
7697
8358
  results.push(...walkProjectFiles(fullPath, rootDir, excludedPatterns));
@@ -7720,8 +8381,8 @@ function pickFirstString(...vals) {
7720
8381
  }
7721
8382
  function parseGitleaksItem(item, rootDir) {
7722
8383
  const rawFile = pickFirstString(item.File, item.file);
7723
- const filePath = path4.resolve(rootDir, rawFile);
7724
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
8384
+ const filePath = path5.resolve(rootDir, rawFile);
8385
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
7725
8386
  const ruleId = pickFirstString(item.RuleID, item.ruleID) || "GITLEAKS-MATCH";
7726
8387
  const rawSecret = pickFirstString(item.Secret, item.secret, item.Match);
7727
8388
  const line = item.StartLine ?? item.line ?? 1;
@@ -7794,7 +8455,7 @@ function scanLineForSecrets(line, lineNumber, filePath, relPath, entropyThreshol
7794
8455
  function scanContentForSecrets(content, filePath, rootDir, entropyThreshold = 4.3, collectedSuppressions) {
7795
8456
  const violations = [];
7796
8457
  const lines = content.split(/\r?\n/);
7797
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
8458
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
7798
8459
  for (let i = 0; i < lines.length; i++) {
7799
8460
  violations.push(...scanLineForSecrets(lines[i], i + 1, filePath, relPath, entropyThreshold, collectedSuppressions));
7800
8461
  }
@@ -7815,7 +8476,7 @@ function scanDiffItems(diffLines, rootDir, excludePatterns, entropyThreshold) {
7815
8476
  const suppressions = [];
7816
8477
  for (const [relFile, items] of fileGroups) {
7817
8478
  if (isPathExcluded2(relFile, excludePatterns)) continue;
7818
- const absPath = path4.resolve(rootDir, relFile);
8479
+ const absPath = path5.resolve(rootDir, relFile);
7819
8480
  for (const item of items) {
7820
8481
  const lineViolations = scanLineForSecrets(item.content, item.lineNumber, absPath, relFile, entropyThreshold, suppressions);
7821
8482
  for (const v of lineViolations) {
@@ -7831,7 +8492,7 @@ function scanAllProjectFiles(rootDir, excludePatterns, entropyThreshold) {
7831
8492
  const suppressions = [];
7832
8493
  for (const file of allFiles) {
7833
8494
  try {
7834
- const content = fs7.readFileSync(file, "utf-8");
8495
+ const content = fs8.readFileSync(file, "utf-8");
7835
8496
  findings.push(...scanContentForSecrets(content, file, rootDir, entropyThreshold, suppressions));
7836
8497
  } catch {
7837
8498
  }
@@ -7888,7 +8549,7 @@ function buildReportHeader2(result) {
7888
8549
  return [
7889
8550
  `# \u{1F510} Deterministic Secrets and Credentials Audit (Gitleaks Gate)`,
7890
8551
  ``,
7891
- `> **Evaluation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
8552
+ `> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
7892
8553
  `> **Verdict:** ${isOk ? "\u2705 COMPLIANT (No exposed secrets or credentials)" : `\u274C BLOCKED (${result.findingsCount} exposed credentials detected)`}`,
7893
8554
  `> **Audited Files:** ${result.totalFilesScanned} | **Security Violations:** ${result.findingsCount}`,
7894
8555
  `> **Native Gitleaks Engine:** ${result.scannedWithGitleaks ? "\u{1F7E2} Active / Integrated" : "\u26AA Deterministic Native Scanner (@aisdlc/core)"}`,
@@ -8057,8 +8718,8 @@ function runSemgrepCli(rootDir) {
8057
8718
  const parsed = JSON.parse(output);
8058
8719
  if (Array.isArray(parsed.results)) {
8059
8720
  for (const item of parsed.results) {
8060
- const filePath = path4.resolve(rootDir, item.path);
8061
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
8721
+ const filePath = path5.resolve(rootDir, item.path);
8722
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
8062
8723
  violations.push({
8063
8724
  filePath,
8064
8725
  relPath,
@@ -8085,15 +8746,15 @@ function isSastExcluded(relPath, excludes) {
8085
8746
  function walkSourceFiles(dir, rootDir, excludes = DEFAULT_SAST_EXCLUDES) {
8086
8747
  const files = [];
8087
8748
  try {
8088
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
8749
+ const entries = fs8.readdirSync(dir, { withFileTypes: true }).sort((a, b) => canonicalSort(a.name, b.name));
8089
8750
  for (const entry of entries) {
8090
- const fullPath = path4.join(dir, entry.name);
8091
- const relPath = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
8751
+ const fullPath = path5.join(dir, entry.name);
8752
+ const relPath = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
8092
8753
  if (isSastExcluded(relPath, excludes)) continue;
8093
8754
  if (entry.isDirectory()) {
8094
8755
  files.push(...walkSourceFiles(fullPath, rootDir, excludes));
8095
8756
  } else if (entry.isFile()) {
8096
- const ext = path4.extname(entry.name).toLowerCase();
8757
+ const ext = path5.extname(entry.name).toLowerCase();
8097
8758
  if (GENERAL_SOURCE_EXTENSIONS.includes(ext)) {
8098
8759
  files.push(fullPath);
8099
8760
  }
@@ -8118,11 +8779,11 @@ function checkRuleMatch(rule, line, ext, minSeverity) {
8118
8779
  }
8119
8780
  function scanFileForSast(filePath, rootDir, minSeverity = "HIGH", collectedSuppressions) {
8120
8781
  const violations = [];
8121
- const ext = path4.extname(filePath).toLowerCase();
8782
+ const ext = path5.extname(filePath).toLowerCase();
8122
8783
  try {
8123
- const content = fs7.readFileSync(filePath, "utf-8");
8784
+ const content = fs8.readFileSync(filePath, "utf-8");
8124
8785
  const lines = content.split(/\r?\n/);
8125
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
8786
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
8126
8787
  for (let i = 0; i < lines.length; i++) {
8127
8788
  const line = lines[i];
8128
8789
  if (checkLineSuppression(line, relPath, i + 1, collectedSuppressions)) continue;
@@ -8148,8 +8809,8 @@ function scanFileForSast(filePath, rootDir, minSeverity = "HIGH", collectedSuppr
8148
8809
  function collectSastFiles(rootDir, targetDirs) {
8149
8810
  const files = [];
8150
8811
  for (const tDir of targetDirs) {
8151
- const fullDir = path4.join(rootDir, tDir);
8152
- if (fs7.existsSync(fullDir)) {
8812
+ const fullDir = path5.join(rootDir, tDir);
8813
+ if (fs8.existsSync(fullDir)) {
8153
8814
  files.push(...walkSourceFiles(fullDir, rootDir));
8154
8815
  }
8155
8816
  }
@@ -8195,7 +8856,7 @@ function buildSastHeader(isOk, violationsCount, totalFilesScanned) {
8195
8856
  return [
8196
8857
  `# \u{1F6E1}\uFE0F Shift-Left SAST Audit (Static Application Security Testing)`,
8197
8858
  ``,
8198
- `> **Evaluation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
8859
+ `> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
8199
8860
  `> **Verdict:** ${verdict}`,
8200
8861
  `> **Audited Code Files:** ${totalFilesScanned} | **SAST Violations:** ${violationsCount}`,
8201
8862
  ``,
@@ -8384,22 +9045,31 @@ var PII_SAST_RULES = [
8384
9045
  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
8385
9046
  }
8386
9047
  ];
8387
- function loadPrivacyPolicy(rootDir, policyPath) {
8388
- const candidate = policyPath ? path4.isAbsolute(policyPath) ? policyPath : path4.join(rootDir, policyPath) : path4.join(rootDir, "privacy-policy.yaml");
8389
- if (!fs7.existsSync(candidate)) return DEFAULT_PRIVACY_POLICY;
9048
+ function parsePrivacyDoc(raw, candidate) {
9049
+ let parsed;
8390
9050
  try {
8391
- const parsed = yaml3.load(fs7.readFileSync(candidate, "utf-8"));
8392
- if (!parsed || typeof parsed !== "object") return DEFAULT_PRIVACY_POLICY;
8393
- return {
8394
- ...DEFAULT_PRIVACY_POLICY,
8395
- ...parsed,
8396
- classifications: { ...DEFAULT_PRIVACY_POLICY.classifications, ...parsed.classifications || {} },
8397
- agent_guardrails: { ...DEFAULT_PRIVACY_POLICY.agent_guardrails, ...parsed.agent_guardrails || {} },
8398
- ci_gates: { ...DEFAULT_PRIVACY_POLICY.ci_gates, ...parsed.ci_gates || {} }
8399
- };
8400
- } catch {
8401
- return DEFAULT_PRIVACY_POLICY;
9051
+ parsed = yaml3.load(raw);
9052
+ } catch (err) {
9053
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in privacy-policy '${candidate}': ${err instanceof Error ? err.message : String(err)}`);
9054
+ }
9055
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
9056
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid privacy policy structure in '${candidate}': root must be a YAML mapping`);
8402
9057
  }
9058
+ return parsed;
9059
+ }
9060
+ function loadPrivacyPolicy(rootDir = process.cwd(), policyPath) {
9061
+ const candidate = policyPath ? path5.isAbsolute(policyPath) ? policyPath : path5.join(rootDir, policyPath) : path5.join(rootDir, "privacy-policy.yaml");
9062
+ if (!fs8.existsSync(candidate)) return DEFAULT_PRIVACY_POLICY;
9063
+ const raw = fs8.readFileSync(candidate, "utf-8");
9064
+ if (!raw || !raw.trim()) return DEFAULT_PRIVACY_POLICY;
9065
+ const parsed = parsePrivacyDoc(raw, candidate);
9066
+ return {
9067
+ ...DEFAULT_PRIVACY_POLICY,
9068
+ ...parsed,
9069
+ classifications: { ...DEFAULT_PRIVACY_POLICY.classifications, ...parsed.classifications || {} },
9070
+ agent_guardrails: { ...DEFAULT_PRIVACY_POLICY.agent_guardrails, ...parsed.agent_guardrails || {} },
9071
+ ci_gates: { ...DEFAULT_PRIVACY_POLICY.ci_gates, ...parsed.ci_gates || {} }
9072
+ };
8403
9073
  }
8404
9074
  function isLineExemptFromPrivacyScan(line) {
8405
9075
  return line.includes("ai-sdlc:allow-pii") || line.includes("ai-sdlc:allow-sast") || line.includes("nosec");
@@ -8416,9 +9086,9 @@ function shouldApplyPiiRule(rule, policy) {
8416
9086
  function scanFileForPrivacyViolations(filePath, rootDir, policy = DEFAULT_PRIVACY_POLICY) {
8417
9087
  const violations = [];
8418
9088
  try {
8419
- const content = fs7.readFileSync(filePath, "utf-8");
9089
+ const content = fs8.readFileSync(filePath, "utf-8");
8420
9090
  const lines = content.split(/\r?\n/);
8421
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
9091
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
8422
9092
  for (let i = 0; i < lines.length; i++) {
8423
9093
  const line = lines[i];
8424
9094
  if (isLineExemptFromPrivacyScan(line)) continue;
@@ -8527,10 +9197,10 @@ function discoverPrivacyArtifacts(rootDir) {
8527
9197
  dpiaStatuses: /* @__PURE__ */ new Map()
8528
9198
  };
8529
9199
  for (const file of walkMdFiles(rootDir)) {
8530
- const relPath = path4.relative(rootDir, file).replace(/\\/g, "/");
9200
+ const relPath = path5.relative(rootDir, file).replace(/\\/g, "/");
8531
9201
  if (isExcludedPrivacyMd(relPath)) continue;
8532
9202
  try {
8533
- const fm = extractFrontmatter(fs7.readFileSync(file, "utf-8"));
9203
+ const fm = extractFrontmatter(fs8.readFileSync(file, "utf-8"));
8534
9204
  if (fm && fm.id) classifyPrivacyMarkdown(fm, relPath, acc);
8535
9205
  } catch {
8536
9206
  }
@@ -8575,7 +9245,7 @@ function checkAssetRopaRegistration(asset, ropaRecords, policy, fullPath) {
8575
9245
  }
8576
9246
  function auditDataAssetGovernance(asset, ropaRecords, policy, rootDir) {
8577
9247
  if (!isSensitiveClassification(asset.classification)) return [];
8578
- const fullPath = path4.join(rootDir, asset.filePath);
9248
+ const fullPath = path5.join(rootDir, asset.filePath);
8579
9249
  const v1 = checkAssetRetention(asset, policy, fullPath);
8580
9250
  const v2 = checkAssetRopaRegistration(asset, ropaRecords, policy, fullPath);
8581
9251
  return [v1, v2].filter((v) => v !== null);
@@ -8647,8 +9317,8 @@ function checkRopaDpiaApproval(ropa, dataAssets, dpiaStatuses, fullPath) {
8647
9317
  function collectSourceFilesForPrivacy(rootDir, targetDirs) {
8648
9318
  const files = [];
8649
9319
  for (const dir of targetDirs) {
8650
- const fullDir = path4.join(rootDir, dir);
8651
- if (fs7.existsSync(fullDir)) {
9320
+ const fullDir = path5.join(rootDir, dir);
9321
+ if (fs8.existsSync(fullDir)) {
8652
9322
  files.push(...walkSourceFiles(fullDir, rootDir));
8653
9323
  }
8654
9324
  }
@@ -8738,15 +9408,13 @@ function generatePrivacyReportMarkdown(result, policy = DEFAULT_PRIVACY_POLICY)
8738
9408
  function writePrivacyOutputsIfNeeded(result, options, policy, rootDir) {
8739
9409
  if (options.generateRopa) {
8740
9410
  const relRopa = options.ropaOutputPath || policy.ci_gates.output_ropa_file || "reports/ROPA_INVENTORY.md";
8741
- const fullRopa = path4.isAbsolute(relRopa) ? relRopa : path4.join(rootDir, relRopa);
8742
- fs7.mkdirSync(path4.dirname(fullRopa), { recursive: true });
8743
- fs7.writeFileSync(fullRopa, result.ropaMarkdown, "utf-8");
9411
+ const fullRopa = path5.isAbsolute(relRopa) ? relRopa : path5.join(rootDir, relRopa);
9412
+ writeReportFileIfChanged(fullRopa, result.ropaMarkdown);
8744
9413
  result.ropaOutputPath = fullRopa;
8745
9414
  }
8746
9415
  if (options.reportOutputPath) {
8747
- const fullReport = path4.isAbsolute(options.reportOutputPath) ? options.reportOutputPath : path4.join(rootDir, options.reportOutputPath);
8748
- fs7.mkdirSync(path4.dirname(fullReport), { recursive: true });
8749
- fs7.writeFileSync(fullReport, result.reportMarkdown, "utf-8");
9416
+ const fullReport = path5.isAbsolute(options.reportOutputPath) ? options.reportOutputPath : path5.join(rootDir, options.reportOutputPath);
9417
+ writeReportFileIfChanged(fullReport, result.reportMarkdown);
8750
9418
  result.reportOutputPath = fullReport;
8751
9419
  }
8752
9420
  }
@@ -8758,7 +9426,7 @@ function evaluateDiscoveredPrivacyGovernance(discovered, policy, rootDir) {
8758
9426
  }
8759
9427
  let dpiaRequiredCount = 0;
8760
9428
  for (const ropa of discovered.ropaRecords) {
8761
- const fullPath = path4.join(rootDir, ropa.filePath);
9429
+ const fullPath = path5.join(rootDir, ropa.filePath);
8762
9430
  violations.push(...checkRopaControlsAndSubprocessors(ropa, governedRopaIds, policy, fullPath));
8763
9431
  const dpiaCheck = checkRopaDpiaApproval(ropa, discovered.dataAssets, discovered.dpiaStatuses, fullPath);
8764
9432
  if (dpiaCheck.dpiaRequired) dpiaRequiredCount++;
@@ -8804,7 +9472,7 @@ function calculateRating(mi, cc) {
8804
9472
  return "F";
8805
9473
  }
8806
9474
  function detectLanguage(filePath) {
8807
- const ext = path4.extname(filePath).toLowerCase();
9475
+ const ext = path5.extname(filePath).toLowerCase();
8808
9476
  switch (ext) {
8809
9477
  case ".ts":
8810
9478
  return "TypeScript";
@@ -8829,7 +9497,7 @@ function detectLanguage(filePath) {
8829
9497
  }
8830
9498
  function parseJsonSummaryFile(filePath, relSource) {
8831
9499
  try {
8832
- const raw = JSON.parse(fs7.readFileSync(filePath, "utf-8"));
9500
+ const raw = JSON.parse(fs8.readFileSync(filePath, "utf-8"));
8833
9501
  const total = raw.total;
8834
9502
  if (!total || typeof total !== "object") return null;
8835
9503
  const linesPct = Number(total.lines?.pct);
@@ -8852,7 +9520,7 @@ function updateLcovCounts(line, counts) {
8852
9520
  }
8853
9521
  function parseLcovFile(filePath, relSource) {
8854
9522
  try {
8855
- const lines = fs7.readFileSync(filePath, "utf-8").split(/\r?\n/);
9523
+ const lines = fs8.readFileSync(filePath, "utf-8").split(/\r?\n/);
8856
9524
  const counts = { LF: 0, LH: 0, BRF: 0, BRH: 0, FNF: 0, FNH: 0 };
8857
9525
  for (const line of lines) {
8858
9526
  updateLcovCounts(line, counts);
@@ -8872,16 +9540,16 @@ function parseLcovFile(filePath, relSource) {
8872
9540
  function readCodeCoverageSummary(rootDir = process.cwd()) {
8873
9541
  const jsonCandidates = ["coverage/coverage-summary.json", "reports/coverage-summary.json"];
8874
9542
  for (const rel of jsonCandidates) {
8875
- const full = path4.join(rootDir, rel);
8876
- if (fs7.existsSync(full)) {
9543
+ const full = path5.join(rootDir, rel);
9544
+ if (fs8.existsSync(full)) {
8877
9545
  const parsed = parseJsonSummaryFile(full, rel);
8878
9546
  if (parsed) return parsed;
8879
9547
  }
8880
9548
  }
8881
9549
  const lcovCandidates = ["coverage/lcov.info", "lcov.info", "reports/lcov.info"];
8882
9550
  for (const rel of lcovCandidates) {
8883
- const full = path4.join(rootDir, rel);
8884
- if (fs7.existsSync(full)) {
9551
+ const full = path5.join(rootDir, rel);
9552
+ if (fs8.existsSync(full)) {
8885
9553
  const parsed = parseLcovFile(full, rel);
8886
9554
  if (parsed) return parsed;
8887
9555
  }
@@ -8966,7 +9634,7 @@ function buildReportHeader3(verdict, rating, mi, cc) {
8966
9634
  return [
8967
9635
  `# \u{1F4CA} Formal Quality and Release Gate Report (AI-SDLC)`,
8968
9636
  "",
8969
- `> **Generation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
9637
+ `> **Generation Date:** ${resolveDeterministicDate().toISOString()}`,
8970
9638
  `> **Release Gate Verdict:** ${status}`,
8971
9639
  `> **Global Rating:** **\`${rating}\`** (MI Index: ${mi}/100, Average CC: ${cc})`
8972
9640
  ];
@@ -9017,9 +9685,8 @@ function renderQualityReportMarkdown(gateResult, coverage, accumulated, averages
9017
9685
  return [...header, "", "---", "", ...table, "", "---", "", ...polyglot, ...violationsSection, "", EVALUATION_STANDARDS_MD].join("\n");
9018
9686
  }
9019
9687
  function writeQualityReportFile(rootDir, markdown) {
9020
- const reportsDir = path4.join(rootDir, "reports");
9021
- fs7.mkdirSync(reportsDir, { recursive: true });
9022
- fs7.writeFileSync(path4.join(reportsDir, "QUALITY_REPORT.md"), markdown, "utf-8");
9688
+ const targetPath = path5.join(rootDir, "reports", "QUALITY_REPORT.md");
9689
+ return writeReportFileIfChanged(targetPath, markdown);
9023
9690
  }
9024
9691
  function generateQualityReport(options = {}) {
9025
9692
  const rootDir = options.rootDir || process.cwd();
@@ -9161,9 +9828,9 @@ Flujo de Integraci\xF3n (Merges):`,
9161
9828
  };
9162
9829
  }
9163
9830
  function resolveVersionFromProposal(changeDir) {
9164
- const proposalFile = path4.join(changeDir, "proposal.md");
9165
- if (!fs7.existsSync(proposalFile)) return "1.0.0";
9166
- const propContent = fs7.readFileSync(proposalFile, "utf-8");
9831
+ const proposalFile = path5.join(changeDir, "proposal.md");
9832
+ if (!fs8.existsSync(proposalFile)) return "1.0.0";
9833
+ const propContent = fs8.readFileSync(proposalFile, "utf-8");
9167
9834
  const propFm = propContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
9168
9835
  if (!propFm) return "1.0.0";
9169
9836
  const parsedProp = yaml3.load(propFm[1]);
@@ -9180,9 +9847,9 @@ function isTaskMatchingId(currentId, cleanTaskId) {
9180
9847
  return currentLower === targetLower || currentLower.startsWith(`${targetLower}-`) || targetLower.startsWith(`${currentLower}-`);
9181
9848
  }
9182
9849
  function parseTasksFrontmatter(tasksFile) {
9183
- if (!fs7.existsSync(tasksFile)) return null;
9850
+ if (!fs8.existsSync(tasksFile)) return null;
9184
9851
  try {
9185
- const content = fs7.readFileSync(tasksFile, "utf-8");
9852
+ const content = fs8.readFileSync(tasksFile, "utf-8");
9186
9853
  const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
9187
9854
  if (!fmMatch) return null;
9188
9855
  const fm = yaml3.load(fmMatch[1]);
@@ -9204,7 +9871,7 @@ function buildTaskMatch(currentId, currentTitle, rawChangeId, changeDir, version
9204
9871
  };
9205
9872
  }
9206
9873
  function inspectChangeTasks(changeDir, entryName, cleanTaskId, availableTasks) {
9207
- const fm = parseTasksFrontmatter(path4.join(changeDir, "tasks.md"));
9874
+ const fm = parseTasksFrontmatter(path5.join(changeDir, "tasks.md"));
9208
9875
  if (!fm || !fm.tasks) return void 0;
9209
9876
  const rawChangeId = resolveRawChangeId(fm, entryName);
9210
9877
  const versionStr = resolveChangeVersion(fm, changeDir);
@@ -9225,16 +9892,16 @@ function locateTaskInActiveChanges(taskId, rootDir = process.cwd()) {
9225
9892
  if (!cleanTaskId) {
9226
9893
  return { found: false, error: "Debe especificar un identificador de tarea v\xE1lido (ej. TSK-001)." };
9227
9894
  }
9228
- const activeChangesDir = path4.join(rootDir, "specs", "changes", "active");
9229
- if (!fs7.existsSync(activeChangesDir)) {
9895
+ const activeChangesDir = path5.join(rootDir, "specs", "changes", "active");
9896
+ if (!fs8.existsSync(activeChangesDir)) {
9230
9897
  return { found: false, error: `No se encontr\xF3 el directorio de cambios activos en '${activeChangesDir}'.` };
9231
9898
  }
9232
9899
  const availableTasks = [];
9233
- const candidateDirs = fs7.readdirSync(activeChangesDir, { withFileTypes: true });
9900
+ const candidateDirs = fs8.readdirSync(activeChangesDir, { withFileTypes: true });
9234
9901
  let matchedTask;
9235
9902
  for (const entry of candidateDirs) {
9236
9903
  if (!entry.isDirectory()) continue;
9237
- const changeDir = path4.join(activeChangesDir, entry.name);
9904
+ const changeDir = path5.join(activeChangesDir, entry.name);
9238
9905
  const m = inspectChangeTasks(changeDir, entry.name, cleanTaskId, availableTasks);
9239
9906
  if (m && !matchedTask) {
9240
9907
  matchedTask = m;
@@ -9271,14 +9938,14 @@ function detectExistingFeatureBranch(branches, dirName, changeId, changeSlug) {
9271
9938
  (b) => b.startsWith("feat/") && (b.toLowerCase().includes(dirName.toLowerCase()) || b.toLowerCase().includes(changeId.toLowerCase())) && classifyBranch(b).valid
9272
9939
  );
9273
9940
  if (existing) return existing;
9274
- const normalizedName = slugify(changeSlug).replace(/^(chg|bug|feat)-(\d+)/i, (_, p, n) => `${p.toUpperCase()}-${n}`);
9941
+ const normalizedName = slugify2(changeSlug).replace(/^(chg|bug|feat)-(\d+)/i, (_, p, n) => `${p.toUpperCase()}-${n}`);
9275
9942
  return `feat/${normalizedName}`;
9276
9943
  }
9277
9944
  function buildDefaultTaskSuffix(taskId, taskTitle) {
9278
9945
  let taskSuffix = taskId.toLowerCase();
9279
9946
  const hasSlug = taskSuffix.includes("-") && taskSuffix.split("-").length > 2;
9280
9947
  if (!hasSlug && taskTitle) {
9281
- const titleSlug = slugify(taskTitle).split("-").slice(0, 4).join("-");
9948
+ const titleSlug = slugify2(taskTitle).split("-").slice(0, 4).join("-");
9282
9949
  if (titleSlug) {
9283
9950
  taskSuffix = `${taskSuffix}-${titleSlug}`;
9284
9951
  }
@@ -9298,7 +9965,7 @@ function resolveBranchHierarchy(match, rootDir = process.cwd()) {
9298
9965
  const branches = getGitBranchList(rootDir);
9299
9966
  const cleanVersion = (match.version || "1.0.0").replace(/^v/, "");
9300
9967
  const releaseBranch = detectExistingReleaseBranch(branches, cleanVersion);
9301
- const dirName = path4.basename(match.changeDir);
9968
+ const dirName = path5.basename(match.changeDir);
9302
9969
  const changeSlug = dirName.replace(/^(chg|CHG)-/i, (m) => m.toUpperCase());
9303
9970
  const featureBranch = detectExistingFeatureBranch(branches, dirName, match.changeId, changeSlug);
9304
9971
  const parentIdMatch = featureBranch.replace(/^(feat|feature)\//i, "").match(/^[a-zA-Z0-9]+-[a-zA-Z0-9]+/);
@@ -9605,13 +10272,13 @@ function getGitConfigValue(key, cwd) {
9605
10272
  }
9606
10273
  function readSessionTelemetry(cwd) {
9607
10274
  const possiblePaths = [
9608
- path4.join(cwd, ".ai-sdlc", "session-telemetry.json"),
9609
- path4.join(cwd, ".ai-sdlc", "agent-session.json")
10275
+ path5.join(cwd, ".ai-sdlc", "session-telemetry.json"),
10276
+ path5.join(cwd, ".ai-sdlc", "agent-session.json")
9610
10277
  ];
9611
10278
  for (const p of possiblePaths) {
9612
- if (fs7.existsSync(p)) {
10279
+ if (fs8.existsSync(p)) {
9613
10280
  try {
9614
- const raw = fs7.readFileSync(p, "utf-8");
10281
+ const raw = fs8.readFileSync(p, "utf-8");
9615
10282
  return JSON.parse(raw);
9616
10283
  } catch {
9617
10284
  }
@@ -9831,20 +10498,20 @@ if [ -f "$TELEMETRY_FILE" ]; then
9831
10498
  fi
9832
10499
  `;
9833
10500
  function installGitHooks(rootDir = process.cwd()) {
9834
- const gitDir = path4.join(rootDir, ".git");
9835
- if (!fs7.existsSync(gitDir)) {
10501
+ const gitDir = path5.join(rootDir, ".git");
10502
+ if (!fs8.existsSync(gitDir)) {
9836
10503
  return {
9837
10504
  success: false,
9838
10505
  error: `No se encontr\xF3 el directorio Git en '${gitDir}'.`
9839
10506
  };
9840
10507
  }
9841
- const hooksDir = path4.join(gitDir, "hooks");
9842
- if (!fs7.existsSync(hooksDir)) {
9843
- fs7.mkdirSync(hooksDir, { recursive: true });
10508
+ const hooksDir = path5.join(gitDir, "hooks");
10509
+ if (!fs8.existsSync(hooksDir)) {
10510
+ fs8.mkdirSync(hooksDir, { recursive: true });
9844
10511
  }
9845
- const hookPath = path4.join(hooksDir, "prepare-commit-msg");
10512
+ const hookPath = path5.join(hooksDir, "prepare-commit-msg");
9846
10513
  try {
9847
- fs7.writeFileSync(hookPath, PREPARE_COMMIT_MSG_SCRIPT, { encoding: "utf-8", mode: 493 });
10514
+ fs8.writeFileSync(hookPath, PREPARE_COMMIT_MSG_SCRIPT, { encoding: "utf-8", mode: 493 });
9848
10515
  return {
9849
10516
  success: true,
9850
10517
  hookPath
@@ -10185,16 +10852,16 @@ function formatReleaseKpiMarkdown(report) {
10185
10852
  ].join("\n");
10186
10853
  }
10187
10854
  function writeReleaseKpiReport(report, options = {}) {
10188
- const root = options.outputDir || (options.rootDir ? path4.join(options.rootDir, "reports", "releases") : path4.join(process.cwd(), "reports", "releases"));
10189
- if (!fs7.existsSync(root)) {
10190
- fs7.mkdirSync(root, { recursive: true });
10855
+ const root = options.outputDir || (options.rootDir ? path5.join(options.rootDir, "reports", "releases") : path5.join(process.cwd(), "reports", "releases"));
10856
+ if (!fs8.existsSync(root)) {
10857
+ fs8.mkdirSync(root, { recursive: true });
10191
10858
  }
10192
10859
  const cleanBranchName = report.releaseBranch.replace(/[^a-zA-Z0-9.-]/g, "_");
10193
- const mdPath = path4.join(root, `RELEASE_KPIS_${cleanBranchName}.md`);
10194
- const jsonPath = path4.join(root, `release_${cleanBranchName}.kpis.json`);
10860
+ const mdPath = path5.join(root, `RELEASE_KPIS_${cleanBranchName}.md`);
10861
+ const jsonPath = path5.join(root, `release_${cleanBranchName}.kpis.json`);
10195
10862
  const mdContent = formatReleaseKpiMarkdown(report);
10196
- fs7.writeFileSync(mdPath, mdContent, "utf-8");
10197
- fs7.writeFileSync(jsonPath, JSON.stringify(report, null, 2), "utf-8");
10863
+ fs8.writeFileSync(mdPath, mdContent, "utf-8");
10864
+ fs8.writeFileSync(jsonPath, JSON.stringify(report, null, 2), "utf-8");
10198
10865
  return { markdownPath: mdPath, jsonPath };
10199
10866
  }
10200
10867
  var cachedScript = null;
@@ -10203,19 +10870,19 @@ function resolveCytoscapePath() {
10203
10870
  try {
10204
10871
  const req = createRequire(import.meta.url);
10205
10872
  const entryPath = req.resolve("cytoscape");
10206
- const dir = path4.dirname(entryPath);
10207
- const minPath = path4.join(dir, "cytoscape.min.js");
10208
- if (fs7.existsSync(minPath)) {
10873
+ const dir = path5.dirname(entryPath);
10874
+ const minPath = path5.join(dir, "cytoscape.min.js");
10875
+ if (fs8.existsSync(minPath)) {
10209
10876
  return minPath;
10210
10877
  }
10211
10878
  } catch {
10212
10879
  }
10213
10880
  const searchDirs = [
10214
- path4.join(process.cwd(), "node_modules", "cytoscape", "dist", "cytoscape.min.js"),
10215
- path4.join(process.cwd(), "packages", "core", "node_modules", "cytoscape", "dist", "cytoscape.min.js")
10881
+ path5.join(process.cwd(), "node_modules", "cytoscape", "dist", "cytoscape.min.js"),
10882
+ path5.join(process.cwd(), "packages", "core", "node_modules", "cytoscape", "dist", "cytoscape.min.js")
10216
10883
  ];
10217
10884
  for (const candidate of searchDirs) {
10218
- if (fs7.existsSync(candidate)) {
10885
+ if (fs8.existsSync(candidate)) {
10219
10886
  return candidate;
10220
10887
  }
10221
10888
  }
@@ -10228,7 +10895,7 @@ function getCytoscapeScript() {
10228
10895
  const scriptPath = resolveCytoscapePath();
10229
10896
  if (scriptPath) {
10230
10897
  try {
10231
- cachedScript = fs7.readFileSync(scriptPath, "utf-8");
10898
+ cachedScript = fs8.readFileSync(scriptPath, "utf-8");
10232
10899
  return cachedScript;
10233
10900
  } catch {
10234
10901
  }
@@ -10328,7 +10995,7 @@ function extractFrontmatter2(fileContent) {
10328
10995
  }
10329
10996
  }
10330
10997
  function toScannedArtifact(filePath, relPath) {
10331
- const fileContent = fs7.readFileSync(filePath, "utf-8");
10998
+ const fileContent = fs8.readFileSync(filePath, "utf-8");
10332
10999
  const fm = extractFrontmatter2(fileContent);
10333
11000
  const id = typeof fm?.id === "string" ? fm.id.trim() : "";
10334
11001
  if (!id) return null;
@@ -10340,12 +11007,12 @@ function toScannedArtifact(filePath, relPath) {
10340
11007
  return { id, type: classified.type, title, layer: classified.layer, filePath: relPath, content, frontmatter: fm };
10341
11008
  }
10342
11009
  function parseAndCatalogArtifactFile(rootDir, filePath, scannedMap, archViewByBasename) {
10343
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
11010
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
10344
11011
  if (relPath.includes("/changes/") || relPath.startsWith("changes/")) return;
10345
11012
  const scannedArt = toScannedArtifact(filePath, relPath);
10346
11013
  if (!scannedArt) return;
10347
11014
  if (scannedArt.layer === "architecture" && scannedArt.type === "architecture") {
10348
- archViewByBasename.set(path4.basename(relPath), scannedArt);
11015
+ archViewByBasename.set(path5.basename(relPath), scannedArt);
10349
11016
  } else {
10350
11017
  scannedMap.set(scannedArt.id, scannedArt);
10351
11018
  }
@@ -10355,8 +11022,8 @@ function scanWorkspaceArtifacts(rootDir) {
10355
11022
  const archViewByBasename = /* @__PURE__ */ new Map();
10356
11023
  const artifactDirs = ["product", "architecture", "specs", "security", "privacy"];
10357
11024
  for (const dirName of artifactDirs) {
10358
- const fullDirPath = path4.join(rootDir, dirName);
10359
- if (fs7.existsSync(fullDirPath)) {
11025
+ const fullDirPath = path5.join(rootDir, dirName);
11026
+ if (fs8.existsSync(fullDirPath)) {
10360
11027
  for (const f of walkMdFiles(fullDirPath)) {
10361
11028
  parseAndCatalogArtifactFile(rootDir, f, scannedArtifactsMap, archViewByBasename);
10362
11029
  }
@@ -10416,9 +11083,9 @@ function addTraceArchEdges(r, status, archViewByBasename, nodeMap, addEdge) {
10416
11083
  }
10417
11084
  }
10418
11085
  function loadTestContentPreview(filePath) {
10419
- if (!fs7.existsSync(filePath)) return void 0;
11086
+ if (!fs8.existsSync(filePath)) return void 0;
10420
11087
  try {
10421
- const testContent = fs7.readFileSync(filePath, "utf-8");
11088
+ const testContent = fs8.readFileSync(filePath, "utf-8");
10422
11089
  const lang = filePath.endsWith(".feature") ? "gherkin" : "typescript";
10423
11090
  const preview = testContent.substring(0, 1500) + (testContent.length > 1500 ? "\n... (truncated)" : "");
10424
11091
  return "```" + lang + "\n" + preview + "\n```";
@@ -10428,10 +11095,10 @@ function loadTestContentPreview(filePath) {
10428
11095
  }
10429
11096
  function ensureTestNode(t, testId, rootDir, nodeMap) {
10430
11097
  if (nodeMap.has(testId)) return;
10431
- const fullTestPath = path4.isAbsolute(t) ? t : path4.join(rootDir, t);
11098
+ const fullTestPath = path5.isAbsolute(t) ? t : path5.join(rootDir, t);
10432
11099
  nodeMap.set(testId, {
10433
11100
  id: testId,
10434
- label: path4.basename(t),
11101
+ label: path5.basename(t),
10435
11102
  title: t,
10436
11103
  type: t.endsWith(".feature") ? "bdd-feature" : "code-test",
10437
11104
  layer: "test",
@@ -12576,9 +13243,10 @@ ${manualsTab}
12576
13243
  ${kpisTab}
12577
13244
  </main>`;
12578
13245
  }
12579
- function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard Interactivo de Trazabilidad y Calidad") {
13246
+ function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard Interactivo de Trazabilidad y Calidad", generationDate) {
12580
13247
  const head = renderDashboardHead(title);
12581
- const header = renderHeaderHtml(graphData.issueCount, (/* @__PURE__ */ new Date()).toISOString());
13248
+ const effectiveDate = generationDate || resolveDeterministicDate().toISOString();
13249
+ const header = renderHeaderHtml(graphData.issueCount, effectiveDate);
12582
13250
  const nav = renderNavTabsHtml();
12583
13251
  const main = renderDashboardMain(metricsData);
12584
13252
  const sidebar = renderTaskSidebarHtml();
@@ -12608,9 +13276,9 @@ var IGNORED_SCAN_DIRS = /* @__PURE__ */ new Set([
12608
13276
  ".agent"
12609
13277
  ]);
12610
13278
  function readKpiFilesInDir(dir) {
12611
- if (!fs7.existsSync(dir)) return [];
13279
+ if (!fs8.existsSync(dir)) return [];
12612
13280
  try {
12613
- return fs7.readdirSync(dir).filter((f) => f.endsWith(".kpis.json")).map((f) => path4.join(dir, f));
13281
+ return fs8.readdirSync(dir).filter((f) => f.endsWith(".kpis.json")).map((f) => path5.join(dir, f));
12614
13282
  } catch {
12615
13283
  return [];
12616
13284
  }
@@ -12618,11 +13286,11 @@ function readKpiFilesInDir(dir) {
12618
13286
  function scanSubdirKpiFiles(dir, depth, acc) {
12619
13287
  if (depth > 4) return;
12620
13288
  try {
12621
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
13289
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
12622
13290
  for (const entry of entries) {
12623
13291
  if (!entry.isDirectory() || IGNORED_SCAN_DIRS.has(entry.name)) continue;
12624
- const sub = path4.join(dir, entry.name);
12625
- if (entry.name === "releases" && path4.basename(dir) === "reports") {
13292
+ const sub = path5.join(dir, entry.name);
13293
+ if (entry.name === "releases" && path5.basename(dir) === "reports") {
12626
13294
  acc.push(...readKpiFilesInDir(sub));
12627
13295
  } else {
12628
13296
  scanSubdirKpiFiles(sub, depth + 1, acc);
@@ -12632,7 +13300,7 @@ function scanSubdirKpiFiles(dir, depth, acc) {
12632
13300
  }
12633
13301
  }
12634
13302
  function findKpiJsonFiles(rootDir) {
12635
- const primaryFiles = readKpiFilesInDir(path4.join(rootDir, "reports", "releases"));
13303
+ const primaryFiles = readKpiFilesInDir(path5.join(rootDir, "reports", "releases"));
12636
13304
  if (primaryFiles.length > 0) return primaryFiles;
12637
13305
  const found = [];
12638
13306
  scanSubdirKpiFiles(rootDir, 0, found);
@@ -12643,7 +13311,7 @@ function collectHistoricalKpis(rootDir) {
12643
13311
  const kpiFiles = findKpiJsonFiles(rootDir);
12644
13312
  for (const fullPath of kpiFiles) {
12645
13313
  try {
12646
- const content = fs7.readFileSync(fullPath, "utf-8");
13314
+ const content = fs8.readFileSync(fullPath, "utf-8");
12647
13315
  const report = JSON.parse(content);
12648
13316
  if (!items.some((it) => it.id === report.releaseBranch && it.timestamp === report.generatedAt)) {
12649
13317
  items.push({
@@ -12849,15 +13517,11 @@ function computeGateAverages(gateResults) {
12849
13517
  }
12850
13518
  function generateDashboardReport(options = {}) {
12851
13519
  const rootDir = options.rootDir || process.cwd();
12852
- const outputPath = options.outputPath || path4.join(rootDir, "reports", "dashboard.html");
13520
+ const outputPath = options.outputPath || path5.join(rootDir, "reports", "dashboard.html");
12853
13521
  const graphData = buildGraphElements(rootDir);
12854
13522
  const metricsData = collectDashboardMetrics(rootDir);
12855
- const html = renderDashboardHtml(graphData, metricsData, options.title);
12856
- const outDir = path4.dirname(outputPath);
12857
- if (!fs7.existsSync(outDir)) {
12858
- fs7.mkdirSync(outDir, { recursive: true });
12859
- }
12860
- fs7.writeFileSync(outputPath, html, "utf-8");
13523
+ const html = renderDashboardHtml(graphData, metricsData, options.title, resolveDeterministicDate(rootDir).toISOString());
13524
+ writeReportFileIfChanged(outputPath, html);
12861
13525
  const { bddCoverageRatio, traceabilityRatio } = computeSummaryRatios(metricsData);
12862
13526
  const { avgMi, avgCc } = computeGateAverages(metricsData.gate.results);
12863
13527
  return {
@@ -12883,6 +13547,8 @@ function generateDashboardReport(options = {}) {
12883
13547
  html
12884
13548
  };
12885
13549
  }
13550
+
13551
+ // src/reporters/sarif.ts
12886
13552
  var SARIF_SCHEMA_2_1_0 = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json";
12887
13553
  var SARIF_VERSION = "2.1.0";
12888
13554
  function normalizeUri(filePath) {
@@ -13030,11 +13696,7 @@ function serializeSarifDocument(doc, pretty = true) {
13030
13696
  return JSON.stringify(doc, null, pretty ? 2 : 0);
13031
13697
  }
13032
13698
  function writeSecuritySarifReport(outputPath, doc, pretty = true) {
13033
- const dir = path4.dirname(outputPath);
13034
- if (!fs7.existsSync(dir)) {
13035
- fs7.mkdirSync(dir, { recursive: true });
13036
- }
13037
- fs7.writeFileSync(outputPath, serializeSarifDocument(doc, pretty), "utf-8");
13699
+ writeReportFileIfChanged(outputPath, serializeSarifDocument(doc, pretty));
13038
13700
  }
13039
13701
 
13040
13702
  // src/init/agent-templates.ts
@@ -15955,14 +16617,14 @@ function collectAgentFiles(targets) {
15955
16617
  function writeProjectFiles(destDir, files, dryRun) {
15956
16618
  const filesCreated = [];
15957
16619
  for (const f of files) {
15958
- const fullFilePath = path4.join(destDir, f.relPath);
15959
- const parentDir = path4.dirname(fullFilePath);
16620
+ const fullFilePath = path5.join(destDir, f.relPath);
16621
+ const parentDir = path5.dirname(fullFilePath);
15960
16622
  if (!dryRun) {
15961
- if (!fs7.existsSync(parentDir)) {
15962
- fs7.mkdirSync(parentDir, { recursive: true });
16623
+ if (!fs8.existsSync(parentDir)) {
16624
+ fs8.mkdirSync(parentDir, { recursive: true });
15963
16625
  }
15964
- if (!fs7.existsSync(fullFilePath)) {
15965
- fs7.writeFileSync(fullFilePath, f.content, "utf-8");
16626
+ if (!fs8.existsSync(fullFilePath)) {
16627
+ fs8.writeFileSync(fullFilePath, f.content, "utf-8");
15966
16628
  filesCreated.push(f.relPath);
15967
16629
  }
15968
16630
  } else {
@@ -16046,7 +16708,7 @@ function getCiWorkflowFiles(ciProvider) {
16046
16708
  function createDirectoriesOnDisk(destDir, directories, dryRun) {
16047
16709
  const created = [];
16048
16710
  for (const d of directories) {
16049
- if (!dryRun) fs7.mkdirSync(path4.join(destDir, d), { recursive: true });
16711
+ if (!dryRun) fs8.mkdirSync(path5.join(destDir, d), { recursive: true });
16050
16712
  created.push(d);
16051
16713
  }
16052
16714
  return created;
@@ -16061,7 +16723,7 @@ function collectStarterFiles(ciProvider, agentFiles = [], archGranularity = "min
16061
16723
  ...getCiWorkflowFiles(ciProvider),
16062
16724
  ...agentFiles,
16063
16725
  ...getArchitectureTemplates(archGranularity).map((t) => ({
16064
- relPath: path4.join("templates", "architecture", t.filename).replace(/\\/g, "/"),
16726
+ relPath: path5.join("templates", "architecture", t.filename).replace(/\\/g, "/"),
16065
16727
  content: t.content
16066
16728
  }))
16067
16729
  ];
@@ -16092,15 +16754,15 @@ function injectMissingScripts(scripts) {
16092
16754
  return modified;
16093
16755
  }
16094
16756
  function updateConsumerPackageJson(destDir, dryRun) {
16095
- const pkgPath = path4.join(destDir, "package.json");
16096
- if (!fs7.existsSync(pkgPath)) return false;
16757
+ const pkgPath = path5.join(destDir, "package.json");
16758
+ if (!fs8.existsSync(pkgPath)) return false;
16097
16759
  try {
16098
- const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
16760
+ const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
16099
16761
  if (!pkg || typeof pkg !== "object") return false;
16100
16762
  pkg.scripts = pkg.scripts || {};
16101
16763
  const modified = injectMissingScripts(pkg.scripts);
16102
16764
  if (modified && !dryRun) {
16103
- fs7.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
16765
+ fs8.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
16104
16766
  }
16105
16767
  return modified;
16106
16768
  } catch {
@@ -16108,7 +16770,7 @@ function updateConsumerPackageJson(destDir, dryRun) {
16108
16770
  }
16109
16771
  }
16110
16772
  function tryInstallGitHook(destDir, dryRun) {
16111
- if (!dryRun && fs7.existsSync(path4.join(destDir, ".git"))) {
16773
+ if (!dryRun && fs8.existsSync(path5.join(destDir, ".git"))) {
16112
16774
  return installGitHooks(destDir).success;
16113
16775
  }
16114
16776
  return false;
@@ -16157,7 +16819,7 @@ function validateInitOptions(options) {
16157
16819
  }
16158
16820
  function initProject(options = {}) {
16159
16821
  const baseDir = options.rootDir || process.cwd();
16160
- const destDir = path4.resolve(baseDir, options.targetDir || ".");
16822
+ const destDir = path5.resolve(baseDir, options.targetDir || ".");
16161
16823
  const validated = validateInitOptions(options);
16162
16824
  if (!validated.valid) return buildInitError(destDir, validated.error);
16163
16825
  const agentTargets = resolveAgentTargets(options.agents);
@@ -16241,33 +16903,40 @@ function normalizeParsedRule(raw, idx) {
16241
16903
  enabled: raw.enabled !== false
16242
16904
  };
16243
16905
  }
16906
+ function parseDlpDoc(yamlContent) {
16907
+ let doc;
16908
+ try {
16909
+ doc = yaml3.load(yamlContent);
16910
+ } catch (err) {
16911
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in dlp-policy: ${err instanceof Error ? err.message : String(err)}`);
16912
+ }
16913
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
16914
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid dlp-policy structure: root must be a YAML mapping`);
16915
+ }
16916
+ const record = doc;
16917
+ if (record.rules !== void 0 && !Array.isArray(record.rules)) {
16918
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid dlp-policy: rules must be an array`);
16919
+ }
16920
+ return record;
16921
+ }
16244
16922
  function parseDlpPolicy(yamlContent) {
16245
16923
  const fallback = createDefaultDlpPolicy();
16246
16924
  if (!yamlContent || !yamlContent.trim()) return fallback;
16247
- try {
16248
- const doc = yaml3.load(yamlContent);
16249
- if (!doc || typeof doc !== "object") return fallback;
16250
- const rawRules = Array.isArray(doc.rules) ? doc.rules : [];
16251
- const parsedRules = rawRules.map((r, i) => normalizeParsedRule(r, i)).filter((r) => r !== null);
16252
- return {
16253
- version: typeof doc.version === "string" ? doc.version : "1.0",
16254
- enabled: doc.enabled !== false,
16255
- zero_data_retention_required: Boolean(doc.zero_data_retention_required ?? true),
16256
- rules: parsedRules.length > 0 ? parsedRules : fallback.rules
16257
- };
16258
- } catch {
16259
- return fallback;
16260
- }
16925
+ const record = parseDlpDoc(yamlContent);
16926
+ const rawRules = Array.isArray(record.rules) ? record.rules : [];
16927
+ const parsedRules = rawRules.map((r, i) => normalizeParsedRule(r, i)).filter((r) => r !== null);
16928
+ return {
16929
+ version: typeof record.version === "string" ? record.version : "1.0",
16930
+ enabled: record.enabled !== false,
16931
+ zero_data_retention_required: Boolean(record.zero_data_retention_required ?? true),
16932
+ rules: parsedRules.length > 0 ? parsedRules : fallback.rules
16933
+ };
16261
16934
  }
16262
16935
  function loadDlpPolicy(rootDir, customPolicyPath) {
16263
16936
  const base = rootDir || process.cwd();
16264
- const filePath = customPolicyPath ? path4.isAbsolute(customPolicyPath) ? customPolicyPath : path4.join(base, customPolicyPath) : path4.join(base, "dlp-policy.yaml");
16265
- if (!fs7.existsSync(filePath)) return createDefaultDlpPolicy();
16266
- try {
16267
- return parseDlpPolicy(fs7.readFileSync(filePath, "utf-8"));
16268
- } catch {
16269
- return createDefaultDlpPolicy();
16270
- }
16937
+ const filePath = customPolicyPath ? path5.isAbsolute(customPolicyPath) ? customPolicyPath : path5.join(base, customPolicyPath) : path5.join(base, "dlp-policy.yaml");
16938
+ if (!fs8.existsSync(filePath)) return createDefaultDlpPolicy();
16939
+ return parseDlpPolicy(fs8.readFileSync(filePath, "utf-8"));
16271
16940
  }
16272
16941
  function applySingleDlpRule(text, rule) {
16273
16942
  if (rule.enabled === false) return { output: text, matches: 0 };
@@ -16318,6 +16987,6 @@ function sanitizeDlpPayload(payload, policy = createDefaultDlpPolicy()) {
16318
16987
  }
16319
16988
  }
16320
16989
 
16321
- export { ALL_ARCHITECTURE_TEMPLATES, BRANCH_PATTERNS, DEFAULT_DLP_RULES, DEFAULT_EXCLUDED_PATTERNS, DEFAULT_EXCLUDES, DEFAULT_EXTENSIONS, DEFAULT_PRIVACY_POLICY, DEFAULT_SAST_EXCLUDES, GENERAL_SOURCE_EXTENSIONS, KPI_SUMMARY_END_TAG, KPI_SUMMARY_START_TAG, LIFECYCLE_SKILL_TEMPLATES, OpenSpecAdapter, PII_SAST_RULES, PREPARE_COMMIT_MSG_SCRIPT, PROMPT_INJECTION_SIGNATURES, PROTECTED_BYPASS_PATTERNS, SARIF_SCHEMA_2_1_0, SARIF_VERSION, SAST_RULES, SCHEMA_SCHEMAS_ARCHITECTURE_ADR_SCHEMA_JSON, SCHEMA_SCHEMAS_ARCHITECTURE_COMPONENT_SCHEMA_JSON, SCHEMA_SCHEMAS_ARCHITECTURE_INTRODUCTION_AND_GOALS_SCHEMA_JSON, SCHEMA_SCHEMAS_COMPLIANCE_LICENSE_MANIFEST_SCHEMA_JSON, SCHEMA_SCHEMAS_COMPLIANCE_LICENSE_POLICY_SCHEMA_JSON, SCHEMA_SCHEMAS_COMPLIANCE_QUALITY_POLICY_SCHEMA_JSON, SCHEMA_SCHEMAS_IDE_MCP_CONFIG_SCHEMA_JSON, SCHEMA_SCHEMAS_MANUALS_PRODUCTION_MANUAL_SCHEMA_JSON, SCHEMA_SCHEMAS_MANUALS_USER_MANUAL_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_DATA_ASSET_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_PRIVACY_POLICY_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_PRIVACY_REQ_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_PRIVACY_ROPA_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_ACTOR_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_BOUNDED_CONTEXT_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_BUSINESS_RULE_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_JOURNEY_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_REQUIREMENT_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_TERM_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_USE_CASE_SCHEMA_JSON, SCHEMA_SCHEMAS_SAFETY_HAZARD_SCHEMA_JSON, SCHEMA_SCHEMAS_SAFETY_SAFETY_REQ_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_DESIGN_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_HANDOFF_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_PROPOSAL_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_RELEASE_KPIS_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_SESSION_TELEMETRY_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_SPEC_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_TASKS_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_ABUSE_CASE_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_ENCLAVE_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_SECURITY_REQ_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_THREAT_ACTOR_SCHEMA_JSON, SCHEMA_SCHEMAS_WORKFLOW_AGENT_HANDOFF_SCHEMA_JSON, SECRET_RULES, SPECIALIZED_AGENT_TEMPLATES, STARTER_ANTIGRAVITY_MCP, STARTER_ANTIGRAVITY_RULES, STARTER_ARCH_ADR, STARTER_ARCH_ARCHITECTURE_CONSTRAINTS, STARTER_ARCH_COMPONENT, STARTER_ARCH_CONTEXT_AND_SCOPE, STARTER_ARCH_CROSS_CUTTING_CONCEPTS, STARTER_ARCH_DEPLOYMENT_VIEW, STARTER_ARCH_GLOSSARY, STARTER_ARCH_INTRODUCTION_AND_GOALS, STARTER_ARCH_LEVEL_1_WHITEBOX, STARTER_ARCH_QUALITY_REQUIREMENTS, STARTER_ARCH_RISKS_AND_TECHNICAL_DEBT, STARTER_ARCH_RUNTIME_VIEW, STARTER_ARCH_SOLUTION_STRATEGY, STARTER_AZURE_PIPELINES, STARTER_BASE_TEMPLATES, STARTER_BITBUCKET_PIPELINES, STARTER_CLAUDE_RULES, STARTER_COMPLIANCE_LICENSE_MANIFEST_YAML, STARTER_COPILOT_RULES, STARTER_CURSOR_CORE_RULES, STARTER_CURSOR_MCP, STARTER_CURSOR_PRODUCT_RULES, STARTER_CURSOR_QUALITY_RULES, STARTER_GITHUB_CI, STARTER_GITHUB_DEPLOY_DASHBOARD, STARTER_GITHUB_PR_KPI, STARTER_GITHUB_PULL_REQUEST_TEMPLATE, STARTER_GITHUB_SDD_INTEGRATE, STARTER_GITLAB_CI, STARTER_LICENSE_POLICY, STARTER_PRIVACY_POLICY, STARTER_PROCESS_README, STARTER_PULL_REQUEST_TEMPLATE, STARTER_QUALITY_POLICY, STARTER_REQUIREMENT_TEMPLATE, STARTER_SCHEMAS, STARTER_SCHEMAS_MAP, STARTER_TASK_TEMPLATE, STARTER_TEMPLATES_CI_AZURE_PIPELINES_YML, STARTER_TEMPLATES_CI_BITBUCKET_PIPELINES_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_CI_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_DEPLOY_DASHBOARD_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_PR_KPI_SUMMARY_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_SDD_INTEGRATE_ON_MERGE_YML, STARTER_TEMPLATES_CI__GITLAB_CI_YML, STARTER_TEMPLATES_COMPLIANCE_COMMERCIAL_ACQUISITION_REQUEST_TEMPLATE_MD, STARTER_TEMPLATES_MANUALS_PRODUCTION_MANUAL_TEMPLATE_MD, STARTER_TEMPLATES_MANUALS_USER_MANUAL_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_DATA_ASSET_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_DPIA_REQUEST_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_PRIVACY_REQ_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_PRIVACY_ROPA_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_ACTOR_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_BOUNDED_CONTEXT_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_BUSINESS_RULE_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_JOURNEY_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_REQUIREMENT_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_TERM_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_USER_DESIGN_FEEDBACK_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_USE_CASE_TEMPLATE_MD, STARTER_TEMPLATES_SAFETY_HAZARD_TEMPLATE_MD, STARTER_TEMPLATES_SAFETY_SAFETY_REQ_TEMPLATE_MD, STARTER_TEMPLATES_SDD_DESIGN_TEMPLATE_MD, STARTER_TEMPLATES_SDD_HANDOFF_TEMPLATE_YAML, STARTER_TEMPLATES_SDD_PROPOSAL_TEMPLATE_MD, STARTER_TEMPLATES_SDD_PULL_REQUEST_TEMPLATE_MD, STARTER_TEMPLATES_SDD_SPEC_TEMPLATE_MD, STARTER_TEMPLATES_SDD_TASKS_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_ABUSE_CASE_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_ENCLAVE_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_SECURITY_REQ_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_THREAT_ACTOR_TEMPLATE_MD, STARTER_TEMPLATES_WORKFLOW_AGENT_HANDOFF_TEMPLATE_MD, STARTER_VSCODE_MCP, STARTER_WORKFLOW_HANDOFF_TEMPLATE, STARTER__GITHUB_PULL_REQUEST_TEMPLATE_MD, SpecKitAdapter, TYPE_TO_SCHEMA_MAP, VALID_AGENT_TARGETS, VALID_ARCHITECTURE_GRANULARITIES, VALID_AUTONOMY_MODES, VALID_CHANGE_PROFILES, VALID_CI_PROVIDERS, aggregateCommitKpiRecords, aggregatePrKpis, aggregateReleaseKpis, analyzeBddVariantsForRequirement, appendRevisionHistoryRow, applyRatchetMode, buildCycloneDxComponent, buildCycloneDxVulnerabilities, buildFeatureFileContent, buildGraphElements, calculateJaccardSimilarity, calculateMetrics, calculateRating, calculateShannonEntropy, checkGherkinInSync, checkoutTaskBranch, classifyBranch, classifyMetricFromMessage, collectAgentFiles, collectDashboardMetrics, collectHistoricalKpis, computeCanonicalSha256, computeSha256, computeViolationSignature, createDefaultDlpPolicy, createDefaultQualityPolicy, depositProductHandoffSidecar, detectActiveChangeForIntegration, detectAuthorIdentity, detectLanguage, detectProfileFromChange, detectPromptInjection, emitCiOutput, evaluateCompoundLicense, evaluateSingleDependency, evaluateVulnerabilityThresholds, extractBaselineEntriesFromResults, extractCitations, extractCommitKpisFromRange, extractDirectDepNames, extractFeatureTags, extractFrontmatter, extractFunctions, extractFunctionsBraceLanguages, extractFunctionsPolyglot, extractFunctionsPython, extractFunctionsTypeScriptAst, extractGherkinBlock, extractGherkinFeatures, extractNormativeText, findChangeWorkspaceDir, findLicenseFile, formatCommitTrailers, formatPrKpiMarkdown, formatReleaseKpiMarkdown, generateBranchHierarchyPlan, generateCycloneDxSbom, generateDashboardReport, generateDuplicatesReportMarkdown, generateGovernanceReportMarkdown, generateLicenseReportMarkdown, generatePdacReportMarkdown, generatePrivacyReportMarkdown, generateQualityReport, generateRopaInventoryMarkdown, generateSastReportMarkdown, generateSecretsReportMarkdown, generateSecuritySarifReport, generateSuppressionsAuditReport, generateTestingCoverageReportMarkdown, generateThirdPartyNotices, generateTraceabilityReportMarkdown, getArchitectureTemplates, getCytoscapeScript, getGitDiffLines, getNextCorrelativeNumber, getSddAdapter, inferArtifactTypeFromId, inferMissingTrailers, initProject, injectKpisIntoPrBody, installGitHooks, integrateSddChange, isGitleaksInstalled, isMetricWorsened, isPathProtectedFromPatch, isPlaceholderOrIgnored, isRequirementArtifact2 as isRequirementArtifact, isSemgrepInstalled, isToolAvailable, loadDlpPolicy, loadPrivacyPolicy, loadProductHandoffSidecar, loadQualityBaseline, locateTaskInActiveChanges, mapSastSeverityToSarifLevel, maskSecret, normalizeSpdxLicense, normalizeUri, parseCommitTrailers, parseDlpPolicy, parseFrontmatter, parseFrontmatterId, parseGenericDoc, parseLicensePolicy, parseMarkdownFrontmatter, parseQualityPolicy, parseRequirementFile, parseSecuritySuppression, parseSpecCatalogScope, parseTasksDoc, parseTasksFromContent, parseTrivyVulnerabilities, processPackageDir, readCodeCoverageSummary, readPackageJson, renderAntigravityAgentFile, renderAntigravitySkillFile, renderAutonomyModeSection, renderClaudeAgentFile, renderClaudeSkillFile, renderCopilotAgentFile, renderCopilotSkillFile, renderCursorAgentFile, renderCursorSkillFile, renderDashboardHtml, renderManualsTab, resolveAgentTargets, resolveBranchHierarchy, resolveCiEnvironment, resolveCytoscapePath, resolveDefaultBranchBase, resolveFeatureFilePath, resolveGitRef, resolveLicenseFromFile, resolveReleaseBaseBranch, resolveSastCwe, resolveSastOwasp, resolveSchemaForArtifact, resolveTargetFiles, runGitleaksCli, runSemgrepCli, sanitizeDlpPayload, sanitizeDlpText, scaffoldSddChange, scanAllProductHandoffs, scanAllTestArtifacts, scanContentForSecrets, scanFileForPrivacyViolations, scanFileForSast, scanGoDependencies, scanInstalledLicenses, scanManualsInWorkspace, scanMavenDependencies, scanPnpmStore, scanPythonDependencies, scanRustDependencies, scanSubdirectories, scanWithNativeFs, scanWithSyft, scanWithTrivy, serializeSarifDocument, slugify, syncPdacDigests, tokenizeTitle, updateCitationsInContent, validateAgainstSchema, validateArtifactSchema, verifyArtifactDuplicates, verifyArtifactsSchemas, verifyLicenses, verifyManuals, verifyPdacGraph, verifyPrivacy, verifyProgressiveFriction, verifyQualityGate, verifySast, verifySddIntegration, verifySecrets, verifyTasksGovernance, verifyTestingCoverage, verifyTraceability, walkDir, walkMdFiles, walkProjectFiles, walkSourceFiles, walkTaskFiles, writeCycloneDxSbom, writeQualityBaseline, writeReleaseKpiReport, writeSecuritySarifReport, writeThirdPartyNotices };
16990
+ export { ALL_ARCHITECTURE_TEMPLATES, BRANCH_PATTERNS, BddStepRegistry, DEFAULT_DLP_RULES, DEFAULT_EXCLUDED_PATTERNS, DEFAULT_EXCLUDES, DEFAULT_EXTENSIONS, DEFAULT_PRIVACY_POLICY, DEFAULT_SAST_EXCLUDES, GENERAL_SOURCE_EXTENSIONS, KPI_SUMMARY_END_TAG, KPI_SUMMARY_START_TAG, LIFECYCLE_SKILL_TEMPLATES, OpenSpecAdapter, PII_SAST_RULES, PREPARE_COMMIT_MSG_SCRIPT, PROMPT_INJECTION_SIGNATURES, PROTECTED_BYPASS_PATTERNS, SARIF_SCHEMA_2_1_0, SARIF_VERSION, SAST_RULES, SCHEMA_SCHEMAS_ARCHITECTURE_ADR_SCHEMA_JSON, SCHEMA_SCHEMAS_ARCHITECTURE_COMPONENT_SCHEMA_JSON, SCHEMA_SCHEMAS_ARCHITECTURE_INTRODUCTION_AND_GOALS_SCHEMA_JSON, SCHEMA_SCHEMAS_COMPLIANCE_LICENSE_MANIFEST_SCHEMA_JSON, SCHEMA_SCHEMAS_COMPLIANCE_LICENSE_POLICY_SCHEMA_JSON, SCHEMA_SCHEMAS_COMPLIANCE_QUALITY_POLICY_SCHEMA_JSON, SCHEMA_SCHEMAS_IDE_MCP_CONFIG_SCHEMA_JSON, SCHEMA_SCHEMAS_MANUALS_PRODUCTION_MANUAL_SCHEMA_JSON, SCHEMA_SCHEMAS_MANUALS_USER_MANUAL_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_DATA_ASSET_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_PRIVACY_POLICY_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_PRIVACY_REQ_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_PRIVACY_ROPA_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_ACTOR_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_BOUNDED_CONTEXT_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_BUSINESS_RULE_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_JOURNEY_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_REQUIREMENT_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_TERM_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_USE_CASE_SCHEMA_JSON, SCHEMA_SCHEMAS_SAFETY_HAZARD_SCHEMA_JSON, SCHEMA_SCHEMAS_SAFETY_SAFETY_REQ_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_DESIGN_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_HANDOFF_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_PROPOSAL_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_RELEASE_KPIS_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_SESSION_TELEMETRY_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_SPEC_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_TASKS_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_ABUSE_CASE_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_ENCLAVE_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_SECURITY_REQ_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_THREAT_ACTOR_SCHEMA_JSON, SCHEMA_SCHEMAS_WORKFLOW_AGENT_HANDOFF_SCHEMA_JSON, SECRET_RULES, SPECIALIZED_AGENT_TEMPLATES, STARTER_ANTIGRAVITY_MCP, STARTER_ANTIGRAVITY_RULES, STARTER_ARCH_ADR, STARTER_ARCH_ARCHITECTURE_CONSTRAINTS, STARTER_ARCH_COMPONENT, STARTER_ARCH_CONTEXT_AND_SCOPE, STARTER_ARCH_CROSS_CUTTING_CONCEPTS, STARTER_ARCH_DEPLOYMENT_VIEW, STARTER_ARCH_GLOSSARY, STARTER_ARCH_INTRODUCTION_AND_GOALS, STARTER_ARCH_LEVEL_1_WHITEBOX, STARTER_ARCH_QUALITY_REQUIREMENTS, STARTER_ARCH_RISKS_AND_TECHNICAL_DEBT, STARTER_ARCH_RUNTIME_VIEW, STARTER_ARCH_SOLUTION_STRATEGY, STARTER_AZURE_PIPELINES, STARTER_BASE_TEMPLATES, STARTER_BITBUCKET_PIPELINES, STARTER_CLAUDE_RULES, STARTER_COMPLIANCE_LICENSE_MANIFEST_YAML, STARTER_COPILOT_RULES, STARTER_CURSOR_CORE_RULES, STARTER_CURSOR_MCP, STARTER_CURSOR_PRODUCT_RULES, STARTER_CURSOR_QUALITY_RULES, STARTER_GITHUB_CI, STARTER_GITHUB_DEPLOY_DASHBOARD, STARTER_GITHUB_PR_KPI, STARTER_GITHUB_PULL_REQUEST_TEMPLATE, STARTER_GITHUB_SDD_INTEGRATE, STARTER_GITLAB_CI, STARTER_LICENSE_POLICY, STARTER_PRIVACY_POLICY, STARTER_PROCESS_README, STARTER_PULL_REQUEST_TEMPLATE, STARTER_QUALITY_POLICY, STARTER_REQUIREMENT_TEMPLATE, STARTER_SCHEMAS, STARTER_SCHEMAS_MAP, STARTER_TASK_TEMPLATE, STARTER_TEMPLATES_CI_AZURE_PIPELINES_YML, STARTER_TEMPLATES_CI_BITBUCKET_PIPELINES_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_CI_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_DEPLOY_DASHBOARD_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_PR_KPI_SUMMARY_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_SDD_INTEGRATE_ON_MERGE_YML, STARTER_TEMPLATES_CI__GITLAB_CI_YML, STARTER_TEMPLATES_COMPLIANCE_COMMERCIAL_ACQUISITION_REQUEST_TEMPLATE_MD, STARTER_TEMPLATES_MANUALS_PRODUCTION_MANUAL_TEMPLATE_MD, STARTER_TEMPLATES_MANUALS_USER_MANUAL_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_DATA_ASSET_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_DPIA_REQUEST_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_PRIVACY_REQ_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_PRIVACY_ROPA_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_ACTOR_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_BOUNDED_CONTEXT_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_BUSINESS_RULE_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_JOURNEY_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_REQUIREMENT_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_TERM_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_USER_DESIGN_FEEDBACK_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_USE_CASE_TEMPLATE_MD, STARTER_TEMPLATES_SAFETY_HAZARD_TEMPLATE_MD, STARTER_TEMPLATES_SAFETY_SAFETY_REQ_TEMPLATE_MD, STARTER_TEMPLATES_SDD_DESIGN_TEMPLATE_MD, STARTER_TEMPLATES_SDD_HANDOFF_TEMPLATE_YAML, STARTER_TEMPLATES_SDD_PROPOSAL_TEMPLATE_MD, STARTER_TEMPLATES_SDD_PULL_REQUEST_TEMPLATE_MD, STARTER_TEMPLATES_SDD_SPEC_TEMPLATE_MD, STARTER_TEMPLATES_SDD_TASKS_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_ABUSE_CASE_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_ENCLAVE_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_SECURITY_REQ_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_THREAT_ACTOR_TEMPLATE_MD, STARTER_TEMPLATES_WORKFLOW_AGENT_HANDOFF_TEMPLATE_MD, STARTER_VSCODE_MCP, STARTER_WORKFLOW_HANDOFF_TEMPLATE, STARTER__GITHUB_PULL_REQUEST_TEMPLATE_MD, SpecKitAdapter, TYPE_TO_SCHEMA_MAP, UNIT_TEST_SUFFIXES, VALID_AGENT_TARGETS, VALID_ARCHITECTURE_GRANULARITIES, VALID_AUTONOMY_MODES, VALID_CHANGE_PROFILES, VALID_CI_PROVIDERS, aggregateCommitKpiRecords, aggregatePrKpis, aggregateReleaseKpis, analyzeBddVariantsForRequirement, appendRevisionHistoryRow, applyRatchetMode, buildCycloneDxComponent, buildCycloneDxVulnerabilities, buildFeatureFileContent, buildGraphElements, calculateJaccardSimilarity, calculateMetrics, calculateRating, calculateShannonEntropy, canonicalSort, checkGherkinInSync, checkoutTaskBranch, classifyBranch, classifyMetricFromMessage, collectAgentFiles, collectDashboardMetrics, collectHistoricalKpis, computeCanonicalSha256, computeRawSha256, computeSha256, computeViolationSignature, createDefaultDlpPolicy, createDefaultQualityPolicy, depositProductHandoffSidecar, detectActiveChangeForIntegration, detectAuthorIdentity, detectLanguage, detectProfileFromChange, detectPromptInjection, emitCiOutput, evaluateCompoundLicense, evaluateSingleDependency, evaluateVulnerabilityThresholds, executeBddFeature, extractBaselineEntriesFromResults, extractCitations, extractCommitKpisFromRange, extractDirectDepNames, extractFeatureTags, extractFrontmatter, extractFunctions, extractFunctionsBraceLanguages, extractFunctionsPolyglot, extractFunctionsPython, extractFunctionsPythonAst, extractFunctionsTypeScriptAst, extractGherkinBlock, extractGherkinFeatures, extractNormativeText, extractPythonFunctionsPolyglot, extractSemanticContent, findChangeWorkspaceDir, findLicenseFile, formatCommitTrailers, formatPrKpiMarkdown, formatReleaseKpiMarkdown, generateBranchHierarchyPlan, generateCycloneDxSbom, generateDashboardReport, generateDeterministicUuidV5, generateDuplicatesReportMarkdown, generateGovernanceReportMarkdown, generateLicenseReportMarkdown, generatePdacReportMarkdown, generatePrivacyReportMarkdown, generateQualityReport, generateRopaInventoryMarkdown, generateSastReportMarkdown, generateSecretsReportMarkdown, generateSecuritySarifReport, generateSuppressionsAuditReport, generateTestingCoverageReportMarkdown, generateThirdPartyNotices, generateTraceabilityReportMarkdown, getArchitectureTemplates, getCytoscapeScript, getGitDiffLines, getNextCorrelativeNumber, getSddAdapter, inferArtifactTypeFromId, inferMissingTrailers, initProject, injectKpisIntoPrBody, installGitHooks, integrateSddChange, isCliInstalled, isFunctionReferencedInTests, isGitleaksInstalled, isMetricWorsened, isPathProtectedFromPatch, isPlaceholderOrIgnored, isReportContentSemanticallyEqual, isRequirementArtifact2 as isRequirementArtifact, isSemgrepInstalled, isToolAvailable, loadDlpPolicy, loadPrivacyPolicy, loadProductHandoffSidecar, loadQualityBaseline, locateTaskInActiveChanges, mapSastSeverityToSarifLevel, maskSecret, normalizePath, normalizeSpdxLicense, normalizeUri, orchestrateNativeAstAnalysis, parseCommitTrailers, parseDlpPolicy, parseFeatureDocument, parseFrontmatter, parseFrontmatterId, parseGenericDoc, parseLicensePolicy, parseMarkdownFrontmatter, parseQualityPolicy, parseRequirementFile, parseSecuritySuppression, parseSpecCatalogScope, parseTasksDoc, parseTasksFromContent, parseTrivyVulnerabilities, processPackageDir, readCodeCoverageSummary, readPackageJson, renderAntigravityAgentFile, renderAntigravitySkillFile, renderAutonomyModeSection, renderClaudeAgentFile, renderClaudeSkillFile, renderCopilotAgentFile, renderCopilotSkillFile, renderCursorAgentFile, renderCursorSkillFile, renderDashboardHtml, renderManualsTab, resolveAgentTargets, resolveBranchHierarchy, resolveCiEnvironment, resolveCytoscapePath, resolveDefaultBranchBase, resolveDeterministicDate, resolveFeatureFilePath, resolveGitRef, resolveLicenseFromFile, resolveReleaseBaseBranch, resolveSastCwe, resolveSastOwasp, resolveSchemaForArtifact, resolveTargetFiles, runGitleaksCli, runSemgrepCli, sanitizeDlpPayload, sanitizeDlpText, sanitizeTestCorpus, scaffoldSddChange, scanAllProductHandoffs, scanAllTestArtifacts, scanContentForSecrets, scanFileForPrivacyViolations, scanFileForSast, scanGoDependencies, scanInstalledLicenses, scanManualsInWorkspace, scanMavenDependencies, scanPnpmStore, scanPythonDependencies, scanRustDependencies, scanSubdirectories, scanWithNativeFs, scanWithSyft, scanWithTrivy, serializeSarifDocument, slugify2 as slugify, stripVolatileReportMetadata, syncPdacDigests, tokenizeTitle, updateCitationsInContent, validateAgainstSchema, validateAnchorExistence, validateArtifactSchema, verifyArtifactDuplicates, verifyArtifactsSchemas, verifyLicenses, verifyManuals, verifyPdacGraph, verifyPrivacy, verifyProgressiveFriction, verifyQualityGate, verifySast, verifySddIntegration, verifySecrets, verifyTasksGovernance, verifyTestingCoverage, verifyTraceability, walkDir, walkMdFiles, walkProjectFiles, walkSourceFiles, walkTaskFiles, writeCucumberJsonReport, writeCycloneDxSbom, writeQualityBaseline, writeReleaseKpiReport, writeReportFileIfChanged, writeSecuritySarifReport, writeThirdPartyNotices };
16322
16991
  //# sourceMappingURL=index.js.map
16323
16992
  //# sourceMappingURL=index.js.map