@aisdlc/core 2.3.0 → 2.5.0

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