@aisdlc/core 2.4.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs 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 };
@@ -320,7 +486,7 @@ function processBraceLine(line, state, blocks, lineIndex, ext) {
320
486
  }
321
487
  }
322
488
  function extractBraceFunctions(content, filePath) {
323
- const ext = path4__namespace.extname(filePath).toLowerCase();
489
+ const ext = path5__namespace.extname(filePath).toLowerCase();
324
490
  const lines = content.split("\n");
325
491
  const blocks = [];
326
492
  const state = {
@@ -529,7 +695,7 @@ function countCommentsAndLoc(rawLines, isPy) {
529
695
  return { loc, commentCount, cleanLines };
530
696
  }
531
697
  function calculatePolyglotMetrics(body, name, filePath, startLine) {
532
- const ext = path4__namespace.extname(filePath).toLowerCase();
698
+ const ext = path5__namespace.extname(filePath).toLowerCase();
533
699
  const rawLines = body.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
534
700
  const { loc, commentCount } = countCommentsAndLoc(rawLines, ext === ".py");
535
701
  const strippedBody = stripStringsAndComments(body, ext);
@@ -602,50 +768,129 @@ function computeBraceCognitive(lines) {
602
768
  function computePolyglotCognitive(lines, isPy) {
603
769
  return isPy ? computePythonCognitive(lines) : computeBraceCognitive(lines);
604
770
  }
605
- function walkMdFiles(dir, fileList = [], excludeDirs = [
606
- "node_modules",
607
- ".git",
608
- "dist",
609
- "bin",
610
- "obj",
611
- "build",
612
- "out",
613
- "target",
614
- "coverage",
615
- ".changeset",
616
- "scratch",
617
- "test-scaffold",
618
- "fixtures",
619
- "templates",
620
- "examples",
621
- ".agent",
622
- ".agents",
623
- ".claude",
624
- ".cursor",
625
- ".github",
626
- ".vscode",
627
- ".vs",
628
- ".idea"
629
- ]) {
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) {
630
829
  try {
631
- if (!fs7__namespace.existsSync(dir)) return fileList;
632
- const files = fs7__namespace.readdirSync(dir);
633
- for (const file of files) {
634
- const fullPath = path4__namespace.join(dir, file);
635
- try {
636
- if (fs7__namespace.statSync(fullPath).isDirectory()) {
637
- if (!excludeDirs.includes(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
638
- walkMdFiles(fullPath, fileList, excludeDirs);
639
- }
640
- } else if (file.endsWith(".md")) {
641
- fileList.push(fullPath);
642
- }
643
- } catch {
644
- }
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;
645
838
  }
646
839
  } catch {
647
840
  }
648
- 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);
649
894
  }
650
895
  function parseMarkdownFrontmatter(content) {
651
896
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -677,41 +922,41 @@ function extractGherkinBlock(body) {
677
922
  }
678
923
  function resolveTargetFiles(rootDir, targetPath) {
679
924
  if (targetPath) {
680
- const fullTarget = path4__namespace.isAbsolute(targetPath) ? targetPath : path4__namespace.join(rootDir, targetPath);
681
- if (!fs7__namespace.existsSync(fullTarget)) return [];
682
- 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];
683
928
  }
684
929
  const searchDirs = [
685
- path4__namespace.join(rootDir, "product"),
686
- path4__namespace.join(rootDir, "security"),
687
- path4__namespace.join(rootDir, "privacy"),
688
- path4__namespace.join(rootDir, "specs"),
689
- path4__namespace.join(rootDir, "examples", "product"),
690
- path4__namespace.join(rootDir, "examples", "security"),
691
- path4__namespace.join(rootDir, "examples", "privacy"),
692
- 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")
693
938
  ];
694
- 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));
695
940
  }
696
941
  function resolveFeatureFilePath(rootDir, filePath, id, customTarget) {
697
942
  if (customTarget) {
698
- return path4__namespace.isAbsolute(customTarget) ? customTarget : path4__namespace.join(rootDir, customTarget);
699
- }
700
- const baseDir = path4__namespace.join(rootDir, "tests", "features");
701
- const candidateSecurity = path4__namespace.join(baseDir, "security", `${id.toLowerCase()}.feature`);
702
- const candidatePrivacy = path4__namespace.join(baseDir, "privacy", `${id.toLowerCase()}.feature`);
703
- const candidateRoot = path4__namespace.join(baseDir, `${id.toLowerCase()}.feature`);
704
- if (fs7__namespace.existsSync(candidateSecurity)) return candidateSecurity;
705
- if (fs7__namespace.existsSync(candidatePrivacy)) return candidatePrivacy;
706
- 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;
707
952
  const subDir = filePath.includes("security") ? "security" : filePath.includes("privacy") ? "privacy" : "";
708
- return path4__namespace.join(baseDir, subDir, `${id.toLowerCase()}.feature`);
953
+ return path5__namespace.join(baseDir, subDir, `${id.toLowerCase()}.feature`);
709
954
  }
710
955
  function buildFeatureFileContent(rootDir, filePath, id, version, gherkinBlocks) {
711
956
  const banner = [
712
957
  `# ==============================================================================`,
713
958
  `# AUTO-GENERADO POR AI-SDLC (Cucumber Integration)`,
714
- `# Origen: ${path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/")}`,
959
+ `# Origen: ${path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/")}`,
715
960
  `# ID Requerimiento: ${id}`,
716
961
  `# Versi\xF3n: ${version || "1.0.0"}`,
717
962
  `# NO EDITAR MANUALMENTE: Cualquier cambio debe realizarse en el Markdown origen.`,
@@ -736,20 +981,20 @@ function extractGherkinFeatures(options = {}) {
736
981
  const targetFiles = resolveTargetFiles(rootDir, options.targetPath);
737
982
  for (const filePath of targetFiles) {
738
983
  try {
739
- const content = fs7__namespace.readFileSync(filePath, "utf-8");
984
+ const content = fs8__namespace.readFileSync(filePath, "utf-8");
740
985
  const { frontmatter, body } = parseMarkdownFrontmatter(content);
741
986
  const gherkinBlocks = extractGherkinBlock(body);
742
987
  if (gherkinBlocks.length === 0) continue;
743
- 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));
744
989
  const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
745
- fs7__namespace.mkdirSync(path4__namespace.dirname(targetPath), { recursive: true });
990
+ fs8__namespace.mkdirSync(path5__namespace.dirname(targetPath), { recursive: true });
746
991
  const fullContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
747
- fs7__namespace.writeFileSync(targetPath, fullContent, "utf-8");
992
+ fs8__namespace.writeFileSync(targetPath, fullContent, "utf-8");
748
993
  const scenarioCount = countScenarios(gherkinBlocks);
749
994
  totalScenarios += scenarioCount;
750
995
  features.push({
751
- sourceFile: path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/"),
752
- 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, "/"),
753
998
  featureName: id,
754
999
  scenarioCount
755
1000
  });
@@ -766,20 +1011,20 @@ function checkGherkinInSync(options = {}) {
766
1011
  let totalFeatures = 0;
767
1012
  for (const filePath of targetFiles) {
768
1013
  try {
769
- const content = fs7__namespace.readFileSync(filePath, "utf-8");
1014
+ const content = fs8__namespace.readFileSync(filePath, "utf-8");
770
1015
  const { frontmatter, body } = parseMarkdownFrontmatter(content);
771
1016
  const gherkinBlocks = extractGherkinBlock(body);
772
1017
  if (gherkinBlocks.length === 0) continue;
773
1018
  totalFeatures++;
774
- 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));
775
1020
  const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
776
- const relTarget = path4__namespace.relative(rootDir, targetPath).replace(/\\/g, "/");
777
- if (!fs7__namespace.existsSync(targetPath)) {
1021
+ const relTarget = path5__namespace.relative(rootDir, targetPath).replace(/\\/g, "/");
1022
+ if (!fs8__namespace.existsSync(targetPath)) {
778
1023
  missingFiles.push(relTarget);
779
1024
  continue;
780
1025
  }
781
1026
  const expectedContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
782
- const actualContent = fs7__namespace.readFileSync(targetPath, "utf-8");
1027
+ const actualContent = fs8__namespace.readFileSync(targetPath, "utf-8");
783
1028
  if (actualContent.replace(/\r\n/g, "\n") !== expectedContent.replace(/\r\n/g, "\n")) {
784
1029
  outOfSyncFiles.push(relTarget);
785
1030
  }
@@ -878,7 +1123,7 @@ function generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, t
878
1123
  const reportLines = [
879
1124
  `# \u{1F9EA} Deterministic Test Coverage Audit for Requirements and Tasks`,
880
1125
  ``,
881
- `> **Audit Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
1126
+ `> **Audit Date:** ${resolveDeterministicDate().toISOString()}`,
882
1127
  `> **Overall Verdict:** ${verdict}`,
883
1128
  ``,
884
1129
  `---`,
@@ -941,8 +1186,8 @@ function parseCodeTestIds(content, relTest, reqToTestsMap) {
941
1186
  }
942
1187
  function processTestFile(fullPath, rootDir, reqToTestsMap) {
943
1188
  try {
944
- const content = fs7__namespace.readFileSync(fullPath, "utf-8");
945
- 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, "/");
946
1191
  if (fullPath.endsWith(".feature")) {
947
1192
  parseFeatureTags(content, relTest, reqToTestsMap);
948
1193
  }
@@ -951,16 +1196,16 @@ function processTestFile(fullPath, rootDir, reqToTestsMap) {
951
1196
  }
952
1197
  }
953
1198
  function collectTestFiles(dir, files = []) {
954
- if (!fs7__namespace.existsSync(dir)) return files;
1199
+ if (!fs8__namespace.existsSync(dir)) return files;
955
1200
  try {
956
- 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));
957
1202
  for (const e of entries) {
958
1203
  if (e.isDirectory()) {
959
1204
  if (!EXCLUDED_TEST_DIRS.has(e.name)) {
960
- collectTestFiles(path4__namespace.join(dir, e.name), files);
1205
+ collectTestFiles(path5__namespace.join(dir, e.name), files);
961
1206
  }
962
1207
  } else if (isTestFile(e.name)) {
963
- files.push(path4__namespace.join(dir, e.name));
1208
+ files.push(path5__namespace.join(dir, e.name));
964
1209
  }
965
1210
  }
966
1211
  } catch {
@@ -1169,7 +1414,7 @@ function collectTestCorpus(rootDir, allMdFiles) {
1169
1414
  const testContents = [];
1170
1415
  for (const tf of collectTestFiles(rootDir)) {
1171
1416
  try {
1172
- const content = fs7__namespace.readFileSync(tf, "utf-8");
1417
+ const content = fs8__namespace.readFileSync(tf, "utf-8");
1173
1418
  testContents.push(content);
1174
1419
  if (tf.endsWith(".feature")) gherkinSources.push(content);
1175
1420
  } catch {
@@ -1177,7 +1422,7 @@ function collectTestCorpus(rootDir, allMdFiles) {
1177
1422
  }
1178
1423
  for (const md of allMdFiles) {
1179
1424
  try {
1180
- const blocks = extractGherkinBlock(fs7__namespace.readFileSync(md, "utf-8"));
1425
+ const blocks = extractGherkinBlock(fs8__namespace.readFileSync(md, "utf-8"));
1181
1426
  gherkinSources.push(...blocks);
1182
1427
  testContents.push(...blocks);
1183
1428
  } catch {
@@ -1206,9 +1451,9 @@ function countCucumberScenarios(raw) {
1206
1451
  return { passed, failed };
1207
1452
  }
1208
1453
  function parseCucumberReportFile(reportPath) {
1209
- if (!fs7__namespace.existsSync(reportPath)) return void 0;
1454
+ if (!fs8__namespace.existsSync(reportPath)) return void 0;
1210
1455
  try {
1211
- const raw = JSON.parse(fs7__namespace.readFileSync(reportPath, "utf-8"));
1456
+ const raw = JSON.parse(fs8__namespace.readFileSync(reportPath, "utf-8"));
1212
1457
  if (!Array.isArray(raw)) return void 0;
1213
1458
  const { passed, failed } = countCucumberScenarios(raw);
1214
1459
  return {
@@ -1222,7 +1467,7 @@ function parseCucumberReportFile(reportPath) {
1222
1467
  }
1223
1468
  }
1224
1469
  function resolveCucumberReport(rootDir, options) {
1225
- const candidate = options.cucumberReportPath ? path4__namespace.isAbsolute(options.cucumberReportPath) ? options.cucumberReportPath : path4__namespace.join(rootDir, options.cucumberReportPath) : path4__namespace.join(rootDir, "reports", "cucumber-report.json");
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");
1226
1471
  const parsed = parseCucumberReportFile(candidate);
1227
1472
  if (parsed) return parsed;
1228
1473
  if (options.enforceCucumberExecution) {
@@ -1256,8 +1501,8 @@ function recordUseCaseAuditIfPresent(frontmatter, id, relPath, isTemplate, combi
1256
1501
  if (flowAudit.status === "FALLO_SIN_PRUEBA") state.ucErrors++;
1257
1502
  }
1258
1503
  function processMdFileForCoverage(file, rootDir, reqToTestsMap, corpus, options, state) {
1259
- const { frontmatter } = parseGenericDoc(fs7__namespace.readFileSync(file, "utf-8"));
1260
- 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, "/");
1261
1506
  const isTemplate = relPath.includes("templates/");
1262
1507
  const id = frontmatter.id || "";
1263
1508
  if (isRequirementArtifact(frontmatter, id)) {
@@ -1331,7 +1576,6 @@ var DEFAULT_EXCLUDES = [
1331
1576
  "templates",
1332
1577
  "fixtures"
1333
1578
  ];
1334
- var TS_JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1335
1579
  function createDefaultQualityPolicy() {
1336
1580
  return {
1337
1581
  max_cyclomatic: 10,
@@ -1412,27 +1656,38 @@ function applyOptionOverrides(policy, overrides) {
1412
1656
  if (overrides.max_function_lines !== void 0) policy.max_function_lines = Number(overrides.max_function_lines);
1413
1657
  if (overrides.enforce_mode !== void 0) policy.enforce_mode = overrides.enforce_mode;
1414
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
+ }
1415
1671
  function parseQualityPolicy(content, overrides) {
1416
1672
  const policy = createDefaultQualityPolicy();
1417
- try {
1418
- const doc = yaml3__default.default.load(content);
1419
- if (doc && typeof doc === "object") {
1420
- const record = doc;
1421
- applyDocScope(policy, record);
1422
- applyDocThresholds(policy, record);
1423
- if (record.max_per_function !== void 0) policy.max_cyclomatic = Number(record.max_per_function);
1424
- if (record.min_acceptable_score !== void 0) policy.min_maintainability = Number(record.min_acceptable_score);
1425
- if (record.max_function_lines !== void 0) policy.max_function_lines = Number(record.max_function_lines);
1426
- }
1427
- } catch {
1673
+ if (!content || !content.trim()) {
1674
+ applyEnvThresholds(policy);
1675
+ applyOptionOverrides(policy, overrides);
1676
+ return policy;
1428
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);
1429
1684
  applyEnvThresholds(policy);
1430
1685
  applyOptionOverrides(policy, overrides);
1431
1686
  return policy;
1432
1687
  }
1433
1688
  function extractFunctions(content, filePath) {
1434
- const ext = path4__namespace.extname(filePath).toLowerCase();
1435
- return TS_JS_EXTENSIONS.includes(ext) ? extractFunctionsTypeScriptAst(content, filePath) : extractFunctionsPolyglot(content, filePath);
1689
+ const report = orchestrateNativeAstAnalysis(content, filePath);
1690
+ return report.functions;
1436
1691
  }
1437
1692
  function extractFunctionsBraceLanguages(content, filePath) {
1438
1693
  return extractFunctions(content, filePath);
@@ -1462,55 +1717,55 @@ function isExcludedFile(file, normalized, excludePatterns) {
1462
1717
  );
1463
1718
  }
1464
1719
  function walkDir(dir, extensions = DEFAULT_EXTENSIONS, excludePatterns = DEFAULT_EXCLUDES) {
1465
- if (!fs7__namespace.existsSync(dir)) return [];
1720
+ if (!fs8__namespace.existsSync(dir)) return [];
1466
1721
  const results = [];
1467
- for (const file of fs7__namespace.readdirSync(dir)) {
1468
- 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);
1469
1724
  const normalized = fullPath.replace(/\\/g, "/");
1470
1725
  if (isExcludedFile(file, normalized, excludePatterns)) continue;
1471
- const stat = fs7__namespace.statSync(fullPath);
1726
+ const stat = fs8__namespace.statSync(fullPath);
1472
1727
  if (stat.isDirectory()) {
1473
1728
  results.push(...walkDir(fullPath, extensions, excludePatterns));
1474
- } else if (extensions.includes(path4__namespace.extname(file).toLowerCase())) {
1729
+ } else if (extensions.includes(path5__namespace.extname(file).toLowerCase())) {
1475
1730
  results.push(fullPath);
1476
1731
  }
1477
1732
  }
1478
1733
  return results;
1479
1734
  }
1480
1735
  function resolvePolicy(rootDir, options) {
1481
- const policyFile = options.policyPath || path4__namespace.join(rootDir, "quality-policy.yaml");
1736
+ const policyFile = options.policyPath || path5__namespace.join(rootDir, "quality-policy.yaml");
1482
1737
  if (options.policy) {
1483
1738
  const policy = { ...options.policy };
1484
1739
  applyOptionOverrides(policy, options.thresholds);
1485
1740
  return policy;
1486
1741
  }
1487
- if (fs7__namespace.existsSync(policyFile)) {
1488
- 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);
1489
1744
  }
1490
1745
  return parseQualityPolicy("", options.thresholds);
1491
1746
  }
1492
1747
  function isPathExcluded(targetPath, rootDir, excludePatterns) {
1493
- const rel = path4__namespace.relative(rootDir, targetPath).replace(/\\/g, "/");
1494
- const base = path4__namespace.basename(targetPath);
1748
+ const rel = path5__namespace.relative(rootDir, targetPath).replace(/\\/g, "/");
1749
+ const base = path5__namespace.basename(targetPath);
1495
1750
  return excludePatterns.some(
1496
1751
  (pat) => base === pat || rel === pat || rel.startsWith(pat + "/") || rel.includes("/" + pat + "/") || rel.endsWith("/" + pat) || rel.includes(pat)
1497
1752
  );
1498
1753
  }
1499
1754
  function scanContainerSubdir(containerDir, subdirName, targetDir, rootDir, policy, files) {
1500
1755
  if (policy.exclude_patterns.includes(subdirName)) return;
1501
- const subTarget = path4__namespace.join(containerDir, subdirName, targetDir);
1502
- 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;
1503
1758
  if (isPathExcluded(subTarget, rootDir, policy.exclude_patterns)) return;
1504
1759
  for (const f of walkDir(subTarget, policy.supported_extensions, policy.exclude_patterns)) {
1505
1760
  files.add(f);
1506
1761
  }
1507
1762
  }
1508
1763
  function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
1509
- const containerDir = path4__namespace.join(rootDir, container);
1510
- 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;
1511
1766
  if (isPathExcluded(containerDir, rootDir, policy.exclude_patterns)) return;
1512
1767
  try {
1513
- for (const subdir of fs7__namespace.readdirSync(containerDir, { withFileTypes: true })) {
1768
+ for (const subdir of fs8__namespace.readdirSync(containerDir, { withFileTypes: true })) {
1514
1769
  if (subdir.isDirectory()) {
1515
1770
  scanContainerSubdir(containerDir, subdir.name, targetDir, rootDir, policy, files);
1516
1771
  }
@@ -1519,13 +1774,14 @@ function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
1519
1774
  }
1520
1775
  }
1521
1776
  function scanDirectTarget(rootDir, targetDir, policy, files) {
1522
- const fullDir = path4__namespace.join(rootDir, targetDir);
1523
- 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)) {
1524
1779
  for (const f of walkDir(fullDir, policy.supported_extensions, policy.exclude_patterns)) {
1525
1780
  files.add(f);
1526
1781
  }
1527
1782
  }
1528
1783
  }
1784
+ var UNIT_TEST_SUFFIXES = [".spec.ts", ".test.ts", ".spec.js", ".test.js", "_test.py", "_test.go"];
1529
1785
  function collectFilesToScan(rootDir, policy) {
1530
1786
  const files = /* @__PURE__ */ new Set();
1531
1787
  const monorepoContainers = ["packages", "apps", "libs", "modules", "services"];
@@ -1535,7 +1791,7 @@ function collectFilesToScan(rootDir, policy) {
1535
1791
  scanMonorepoContainer(rootDir, container, targetDir, policy, files);
1536
1792
  }
1537
1793
  }
1538
- return Array.from(files);
1794
+ return Array.from(files).filter((f) => !UNIT_TEST_SUFFIXES.some((s) => f.endsWith(s)));
1539
1795
  }
1540
1796
  function evaluateViolations(fn, policy) {
1541
1797
  const violations = [];
@@ -1557,38 +1813,67 @@ function evaluateViolations(fn, policy) {
1557
1813
  return violations;
1558
1814
  }
1559
1815
  function evaluateFileQuality(file, rootDir, policy) {
1560
- const content = fs7__namespace.readFileSync(file, "utf-8");
1816
+ const content = fs8__namespace.readFileSync(file, "utf-8");
1561
1817
  const fns = extractFunctions(content, file);
1562
1818
  return fns.map((fn) => {
1563
1819
  const violations = evaluateViolations(fn, policy);
1564
1820
  return {
1565
1821
  ...fn,
1566
- relPath: path4__namespace.relative(rootDir, file).replace(/\\/g, "/"),
1822
+ relPath: path5__namespace.relative(rootDir, file).replace(/\\/g, "/"),
1567
1823
  status: violations.length === 0 ? "PASS" : "FAIL",
1568
1824
  violations
1569
1825
  };
1570
1826
  });
1571
1827
  }
1572
- var UNIT_TEST_SUFFIXES = [".spec.ts", ".test.ts", ".spec.js", ".test.js", "_test.py", "_test.go"];
1573
1828
  function collectUnitTestCorpus(rootDir) {
1574
1829
  const allCodeFiles = walkDir(rootDir, DEFAULT_EXTENSIONS, DEFAULT_EXCLUDES);
1575
1830
  const chunks = [];
1576
1831
  for (const tf of allCodeFiles) {
1577
1832
  if (!UNIT_TEST_SUFFIXES.some((s) => tf.endsWith(s))) continue;
1578
1833
  try {
1579
- chunks.push(fs7__namespace.readFileSync(tf, "utf-8"));
1834
+ chunks.push(fs8__namespace.readFileSync(tf, "utf-8"));
1580
1835
  } catch {
1581
1836
  }
1582
1837
  }
1583
1838
  return chunks.join("\n");
1584
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
+ }
1585
1870
  function auditUnitTestsPerFunction(results, rootDir) {
1586
1871
  const corpus = collectUnitTestCorpus(rootDir);
1587
1872
  const untested = [];
1588
1873
  for (const r of results) {
1589
1874
  const fnName = r.functionName.includes(".") ? r.functionName.split(".").pop() : r.functionName;
1590
1875
  if (!fnName || fnName === "anonymous" || fnName === "constructor") continue;
1591
- if (!corpus.includes(fnName)) {
1876
+ if (!isFunctionReferencedInTests(fnName, corpus)) {
1592
1877
  untested.push({ file: r.relPath, functionName: r.functionName });
1593
1878
  r.violations.push(`Missing unit test for function '${r.functionName}'`);
1594
1879
  r.status = "FAIL";
@@ -1598,11 +1883,11 @@ function auditUnitTestsPerFunction(results, rootDir) {
1598
1883
  }
1599
1884
  function isVitestMidFlightCoverage(rel, fullPath) {
1600
1885
  if (!process.env.VITEST || rel !== "coverage/coverage-summary.json") return false;
1601
- return path4__namespace.resolve(fullPath) === path4__namespace.resolve(process.cwd(), rel);
1886
+ return path5__namespace.resolve(fullPath) === path5__namespace.resolve(process.cwd(), rel);
1602
1887
  }
1603
1888
  function parseCoverageSummaryFile(filePath, source) {
1604
1889
  try {
1605
- const raw = JSON.parse(fs7__namespace.readFileSync(filePath, "utf-8"));
1890
+ const raw = JSON.parse(fs8__namespace.readFileSync(filePath, "utf-8"));
1606
1891
  const total = raw.total;
1607
1892
  if (!total) return null;
1608
1893
  return {
@@ -1617,8 +1902,8 @@ function parseCoverageSummaryFile(filePath, source) {
1617
1902
  }
1618
1903
  function readSummaryCoverageJson(rootDir) {
1619
1904
  for (const rel of ["coverage/coverage-summary.json", "reports/coverage-summary.json"]) {
1620
- const full = path4__namespace.join(rootDir, rel);
1621
- 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;
1622
1907
  const summary = parseCoverageSummaryFile(full, rel);
1623
1908
  if (summary) return summary;
1624
1909
  }
@@ -1642,7 +1927,7 @@ function evaluateCoverageThresholds(rootDir, policy, requireReport) {
1642
1927
  }
1643
1928
  function computeViolationSignature(relPath, fnName, metric) {
1644
1929
  const normPath = relPath.replace(/\\/g, "/");
1645
- return crypto__namespace.createHash("sha256").update(normPath + ":" + fnName + ":" + metric).digest("hex");
1930
+ return crypto3__namespace.createHash("sha256").update(normPath + ":" + fnName + ":" + metric).digest("hex");
1646
1931
  }
1647
1932
  function classifyMetricFromMessage(msg, fn) {
1648
1933
  if (msg.startsWith("Cyclomatic Complexity")) {
@@ -1671,8 +1956,8 @@ function isMetricWorsened(baselineEntry, current) {
1671
1956
  }
1672
1957
  function loadQualityBaseline(baselineFile) {
1673
1958
  try {
1674
- if (!fs7__namespace.existsSync(baselineFile)) return null;
1675
- 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");
1676
1961
  return JSON.parse(content);
1677
1962
  } catch {
1678
1963
  return null;
@@ -1681,11 +1966,11 @@ function loadQualityBaseline(baselineFile) {
1681
1966
  function writeQualityBaseline(baselineFile, entries) {
1682
1967
  const baseline = {
1683
1968
  version: "1.0",
1684
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1969
+ generatedAt: resolveDeterministicDate(path5__namespace.dirname(baselineFile)).toISOString(),
1685
1970
  totalViolations: entries.length,
1686
1971
  violations: entries
1687
1972
  };
1688
- 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");
1689
1974
  return baseline;
1690
1975
  }
1691
1976
  function extractBaselineEntriesFromResults(results) {
@@ -1744,7 +2029,7 @@ function applyRatchetMode(results, baseline) {
1744
2029
  function resolveBaselineFilePath(rootDir, options) {
1745
2030
  if (options.baselinePath) return options.baselinePath;
1746
2031
  if (typeof options.baseline === "string") return options.baseline;
1747
- return path4__namespace.join(rootDir, "quality-baseline.json");
2032
+ return path5__namespace.join(rootDir, "quality-baseline.json");
1748
2033
  }
1749
2034
  function isRatchetModeActive(policy, options, baselineExists) {
1750
2035
  if (options.ratchet !== void 0) return options.ratchet;
@@ -1752,7 +2037,7 @@ function isRatchetModeActive(policy, options, baselineExists) {
1752
2037
  return Boolean(policy.ratchet_mode || options.baseline && baselineExists);
1753
2038
  }
1754
2039
  function evaluateRatchetIfActive(results, baselineFile, isActive) {
1755
- if (!isActive || !fs7__namespace.existsSync(baselineFile)) {
2040
+ if (!isActive || !fs8__namespace.existsSync(baselineFile)) {
1756
2041
  return { toleratedViolationsCount: 0, newViolationsCount: 0, baselineViolationsCount: 0 };
1757
2042
  }
1758
2043
  const baseline = loadQualityBaseline(baselineFile);
@@ -1811,7 +2096,7 @@ function verifyQualityGate(options = {}) {
1811
2096
  baselineViolationsCount: entries.length
1812
2097
  });
1813
2098
  }
1814
- const baselineExists = fs7__namespace.existsSync(baselineFile);
2099
+ const baselineExists = fs8__namespace.existsSync(baselineFile);
1815
2100
  const isRatchet = isRatchetModeActive(policy, options, baselineExists);
1816
2101
  const ratchetEval = evaluateRatchetIfActive(results, baselineFile, isRatchet);
1817
2102
  return buildQualityGateResult(results, allFiles.length, policy, coverageViolations, untestedFunctions, {
@@ -1829,9 +2114,9 @@ function normalizeHandoffPayload(handoff, changeId) {
1829
2114
  };
1830
2115
  }
1831
2116
  function parseChangeProfile(specFile) {
1832
- if (!fs7__namespace.existsSync(specFile)) return "standard";
2117
+ if (!fs8__namespace.existsSync(specFile)) return "standard";
1833
2118
  try {
1834
- const rawSpec = fs7__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
2119
+ const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1835
2120
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1836
2121
  if (match) {
1837
2122
  const parsedFm = yaml3__default.default.load(match[1]);
@@ -1853,12 +2138,12 @@ function checkSpecGherkinFormat(parsedFm, rawSpec, changeWorkspaceDir, errors) {
1853
2138
  }
1854
2139
  }
1855
2140
  function validateSpecFileContent(specFile, changeWorkspaceDir, errors) {
1856
- if (!fs7__namespace.existsSync(specFile)) {
2141
+ if (!fs8__namespace.existsSync(specFile)) {
1857
2142
  errors.push(`Falta el archivo obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
1858
2143
  return;
1859
2144
  }
1860
2145
  try {
1861
- const rawSpec = fs7__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
2146
+ const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1862
2147
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1863
2148
  if (match) {
1864
2149
  const parsedFm = yaml3__default.default.load(match[1]);
@@ -1877,19 +2162,19 @@ function validateHandoffSidecar(handoff, changeWorkspaceDir, errors) {
1877
2162
  if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
1878
2163
  errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
1879
2164
  }
1880
- return path4__namespace.join(changeWorkspaceDir, "handoff.yaml");
2165
+ return path5__namespace.join(changeWorkspaceDir, "handoff.yaml");
1881
2166
  }
1882
2167
  function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
1883
- const proposalFile = path4__namespace.join(changeWorkspaceDir, "proposal.md");
1884
- 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");
1885
2170
  if (profile === "patch") {
1886
2171
  const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
1887
- 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 };
1888
2173
  }
1889
- if (!fs7__namespace.existsSync(proposalFile)) {
2174
+ if (!fs8__namespace.existsSync(proposalFile)) {
1890
2175
  warnings.push(`Falta 'proposal.md' en el espacio de trabajo OpenSpec.`);
1891
2176
  }
1892
- if (!fs7__namespace.existsSync(tasksFile)) {
2177
+ if (!fs8__namespace.existsSync(tasksFile)) {
1893
2178
  errors.push(`Falta el archivo obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
1894
2179
  }
1895
2180
  const handoff = adapter.loadSidecar(changeWorkspaceDir);
@@ -1897,20 +2182,20 @@ function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors,
1897
2182
  return { handoff, sidecarFile };
1898
2183
  }
1899
2184
  function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
1900
- const changeId = path4__namespace.basename(changeWorkspaceDir);
1901
- const proposalFile = path4__namespace.join(changeWorkspaceDir, "proposal.md");
1902
- const specFile = path4__namespace.join(changeWorkspaceDir, "spec.md");
1903
- const designFile = path4__namespace.join(changeWorkspaceDir, "design.md");
1904
- 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");
1905
2190
  return {
1906
2191
  framework: "openspec",
1907
2192
  changeId,
1908
2193
  changeDir: changeWorkspaceDir,
1909
2194
  profile,
1910
- proposalFile: fs7__namespace.existsSync(proposalFile) ? proposalFile : void 0,
1911
- specFile: fs7__namespace.existsSync(specFile) ? specFile : void 0,
1912
- designFile: fs7__namespace.existsSync(designFile) ? designFile : void 0,
1913
- 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,
1914
2199
  sidecarFile,
1915
2200
  handoff: handoff || void 0
1916
2201
  };
@@ -1918,28 +2203,28 @@ function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sideca
1918
2203
  var OpenSpecAdapter = class {
1919
2204
  framework = "openspec";
1920
2205
  getChangeWorkspaceDir(rootDir, changeId) {
1921
- const openspecDir = path4__namespace.join(rootDir, "openspec", "changes", changeId);
1922
- if (fs7__namespace.existsSync(openspecDir)) return openspecDir;
1923
- const activeChangeDir = path4__namespace.join(rootDir, "specs", "changes", "active", changeId);
1924
- if (fs7__namespace.existsSync(activeChangeDir)) return activeChangeDir;
1925
- const completedChangeDir = path4__namespace.join(rootDir, "specs", "changes", "completed", changeId);
1926
- 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;
1927
2212
  return activeChangeDir;
1928
2213
  }
1929
2214
  depositSidecar(options) {
1930
2215
  const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
1931
- if (!fs7__namespace.existsSync(changeDir)) {
1932
- fs7__namespace.mkdirSync(changeDir, { recursive: true });
2216
+ if (!fs8__namespace.existsSync(changeDir)) {
2217
+ fs8__namespace.mkdirSync(changeDir, { recursive: true });
1933
2218
  }
1934
2219
  const handoff = normalizeHandoffPayload(options.handoff, options.changeId);
1935
2220
  const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
1936
- const filePath = path4__namespace.join(changeDir, filename);
2221
+ const filePath = path5__namespace.join(changeDir, filename);
1937
2222
  const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3__default.default.dump(handoff, { indent: 2, lineWidth: -1 });
1938
- fs7__namespace.writeFileSync(filePath, content, "utf-8");
2223
+ fs8__namespace.writeFileSync(filePath, content, "utf-8");
1939
2224
  return filePath;
1940
2225
  }
1941
2226
  loadSidecar(changeWorkspaceDir) {
1942
- if (!fs7__namespace.existsSync(changeWorkspaceDir)) return null;
2227
+ if (!fs8__namespace.existsSync(changeWorkspaceDir)) return null;
1943
2228
  const candidateFiles = [
1944
2229
  "handoff.yaml",
1945
2230
  "handoff.yml",
@@ -1950,10 +2235,10 @@ var OpenSpecAdapter = class {
1950
2235
  "product-handoff.json"
1951
2236
  ];
1952
2237
  for (const file of candidateFiles) {
1953
- const fullPath = path4__namespace.join(changeWorkspaceDir, file);
1954
- if (fs7__namespace.existsSync(fullPath)) {
2238
+ const fullPath = path5__namespace.join(changeWorkspaceDir, file);
2239
+ if (fs8__namespace.existsSync(fullPath)) {
1955
2240
  try {
1956
- const raw = fs7__namespace.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
2241
+ const raw = fs8__namespace.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
1957
2242
  const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3__default.default.load(raw);
1958
2243
  if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
1959
2244
  return parsed;
@@ -1965,7 +2250,7 @@ var OpenSpecAdapter = class {
1965
2250
  return null;
1966
2251
  }
1967
2252
  validateWorkspace(changeWorkspaceDir) {
1968
- if (!fs7__namespace.existsSync(changeWorkspaceDir)) {
2253
+ if (!fs8__namespace.existsSync(changeWorkspaceDir)) {
1969
2254
  return {
1970
2255
  valid: false,
1971
2256
  errors: [`El directorio del cambio OpenSpec no existe: ${changeWorkspaceDir}`],
@@ -1974,7 +2259,7 @@ var OpenSpecAdapter = class {
1974
2259
  }
1975
2260
  const errors = [];
1976
2261
  const warnings = [];
1977
- const specFile = path4__namespace.join(changeWorkspaceDir, "spec.md");
2262
+ const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
1978
2263
  const profile = parseChangeProfile(specFile);
1979
2264
  validateSpecFileContent(specFile, changeWorkspaceDir, errors);
1980
2265
  const { handoff, sidecarFile } = validateOpenSpecStructure(profile, changeWorkspaceDir, this, errors, warnings);
@@ -1988,9 +2273,9 @@ var OpenSpecAdapter = class {
1988
2273
  }
1989
2274
  };
1990
2275
  function parseChangeProfile2(specFile) {
1991
- if (!fs7__namespace.existsSync(specFile)) return "standard";
2276
+ if (!fs8__namespace.existsSync(specFile)) return "standard";
1992
2277
  try {
1993
- const rawSpec = fs7__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
2278
+ const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1994
2279
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1995
2280
  if (match) {
1996
2281
  const parsedFm = yaml3__default.default.load(match[1]);
@@ -2012,12 +2297,12 @@ function checkSpecGherkinFormat2(parsedFm, rawSpec, changeWorkspaceDir, errors)
2012
2297
  }
2013
2298
  }
2014
2299
  function validateSpecFileContent2(specFile, changeWorkspaceDir, errors) {
2015
- if (!fs7__namespace.existsSync(specFile)) {
2300
+ if (!fs8__namespace.existsSync(specFile)) {
2016
2301
  errors.push(`Falta el archivo de especificaci\xF3n obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
2017
2302
  return;
2018
2303
  }
2019
2304
  try {
2020
- const rawSpec = fs7__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
2305
+ const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
2021
2306
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
2022
2307
  if (match) {
2023
2308
  const parsedFm = yaml3__default.default.load(match[1]);
@@ -2036,19 +2321,19 @@ function validateHandoffSidecar2(handoff, changeWorkspaceDir, errors) {
2036
2321
  if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
2037
2322
  errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
2038
2323
  }
2039
- return path4__namespace.join(changeWorkspaceDir, "handoff.yaml");
2324
+ return path5__namespace.join(changeWorkspaceDir, "handoff.yaml");
2040
2325
  }
2041
2326
  function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
2042
- const planFile = path4__namespace.join(changeWorkspaceDir, "plan.md");
2043
- 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");
2044
2329
  if (profile === "patch") {
2045
2330
  const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
2046
- 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 };
2047
2332
  }
2048
- if (!fs7__namespace.existsSync(tasksFile)) {
2333
+ if (!fs8__namespace.existsSync(tasksFile)) {
2049
2334
  errors.push(`Falta el archivo de tareas obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
2050
2335
  }
2051
- if (!fs7__namespace.existsSync(planFile)) {
2336
+ if (!fs8__namespace.existsSync(planFile)) {
2052
2337
  warnings.push(`Se recomienda incluir 'plan.md' en el espacio de trabajo Spec Kit.`);
2053
2338
  }
2054
2339
  const handoff = adapter.loadSidecar(changeWorkspaceDir);
@@ -2056,18 +2341,18 @@ function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors,
2056
2341
  return { handoff, sidecarFile };
2057
2342
  }
2058
2343
  function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
2059
- const changeId = path4__namespace.basename(changeWorkspaceDir);
2060
- const specFile = path4__namespace.join(changeWorkspaceDir, "spec.md");
2061
- const planFile = path4__namespace.join(changeWorkspaceDir, "plan.md");
2062
- 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");
2063
2348
  return {
2064
2349
  framework: "speckit",
2065
2350
  changeId,
2066
2351
  changeDir: changeWorkspaceDir,
2067
2352
  profile,
2068
- specFile: fs7__namespace.existsSync(specFile) ? specFile : void 0,
2069
- designFile: fs7__namespace.existsSync(planFile) ? planFile : void 0,
2070
- 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,
2071
2356
  sidecarFile,
2072
2357
  handoff: handoff || void 0
2073
2358
  };
@@ -2075,18 +2360,18 @@ function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecar
2075
2360
  var SpecKitAdapter = class {
2076
2361
  framework = "speckit";
2077
2362
  getChangeWorkspaceDir(rootDir, changeId) {
2078
- const standardDir = path4__namespace.join(rootDir, "specs", changeId);
2079
- if (fs7__namespace.existsSync(standardDir)) return standardDir;
2080
- const activeChangeDir = path4__namespace.join(rootDir, "specs", "changes", "active", changeId);
2081
- if (fs7__namespace.existsSync(activeChangeDir)) return activeChangeDir;
2082
- const completedChangeDir = path4__namespace.join(rootDir, "specs", "changes", "completed", changeId);
2083
- 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;
2084
2369
  return standardDir;
2085
2370
  }
2086
2371
  depositSidecar(options) {
2087
2372
  const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
2088
- if (!fs7__namespace.existsSync(changeDir)) {
2089
- fs7__namespace.mkdirSync(changeDir, { recursive: true });
2373
+ if (!fs8__namespace.existsSync(changeDir)) {
2374
+ fs8__namespace.mkdirSync(changeDir, { recursive: true });
2090
2375
  }
2091
2376
  const handoff = { ...options.handoff };
2092
2377
  if (!handoff.id.startsWith("HOF-")) {
@@ -2099,20 +2384,20 @@ var SpecKitAdapter = class {
2099
2384
  handoff.createdAt = (/* @__PURE__ */ new Date()).toISOString();
2100
2385
  }
2101
2386
  const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
2102
- const filePath = path4__namespace.join(changeDir, filename);
2387
+ const filePath = path5__namespace.join(changeDir, filename);
2103
2388
  const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3__default.default.dump(handoff, { indent: 2, lineWidth: -1 });
2104
- fs7__namespace.writeFileSync(filePath, content, "utf-8");
2105
- const specifyMemoryDir = path4__namespace.join(options.rootDir, ".specify", "memory");
2106
- if (fs7__namespace.existsSync(path4__namespace.join(options.rootDir, ".specify"))) {
2107
- if (!fs7__namespace.existsSync(specifyMemoryDir)) {
2108
- 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 });
2109
2394
  }
2110
- 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");
2111
2396
  }
2112
2397
  return filePath;
2113
2398
  }
2114
2399
  loadSidecar(changeWorkspaceDir) {
2115
- if (!fs7__namespace.existsSync(changeWorkspaceDir)) return null;
2400
+ if (!fs8__namespace.existsSync(changeWorkspaceDir)) return null;
2116
2401
  const candidateFiles = [
2117
2402
  "handoff.yaml",
2118
2403
  "handoff.yml",
@@ -2122,10 +2407,10 @@ var SpecKitAdapter = class {
2122
2407
  "handoff.json"
2123
2408
  ];
2124
2409
  for (const file of candidateFiles) {
2125
- const fullPath = path4__namespace.join(changeWorkspaceDir, file);
2126
- if (fs7__namespace.existsSync(fullPath)) {
2410
+ const fullPath = path5__namespace.join(changeWorkspaceDir, file);
2411
+ if (fs8__namespace.existsSync(fullPath)) {
2127
2412
  try {
2128
- const raw = fs7__namespace.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
2413
+ const raw = fs8__namespace.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
2129
2414
  const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3__default.default.load(raw);
2130
2415
  if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
2131
2416
  return parsed;
@@ -2137,7 +2422,7 @@ var SpecKitAdapter = class {
2137
2422
  return null;
2138
2423
  }
2139
2424
  validateWorkspace(changeWorkspaceDir) {
2140
- if (!fs7__namespace.existsSync(changeWorkspaceDir)) {
2425
+ if (!fs8__namespace.existsSync(changeWorkspaceDir)) {
2141
2426
  return {
2142
2427
  valid: false,
2143
2428
  errors: [`El directorio del cambio Spec Kit no existe: ${changeWorkspaceDir}`],
@@ -2146,7 +2431,7 @@ var SpecKitAdapter = class {
2146
2431
  }
2147
2432
  const errors = [];
2148
2433
  const warnings = [];
2149
- const specFile = path4__namespace.join(changeWorkspaceDir, "spec.md");
2434
+ const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
2150
2435
  const profile = parseChangeProfile2(specFile);
2151
2436
  validateSpecFileContent2(specFile, changeWorkspaceDir, errors);
2152
2437
  const { handoff, sidecarFile } = validateSpecKitStructure(profile, changeWorkspaceDir, this, errors, warnings);
@@ -2318,27 +2603,27 @@ ${frontmatter}---
2318
2603
  ${body}`;
2319
2604
  }
2320
2605
  function materializeCanonicalArtifact(rootDir, item, changeId, author, date) {
2321
- const targetPath = path4__namespace.join(rootDir, item.targetDir, `${item.id}.md`);
2322
- if (fs7__namespace.existsSync(targetPath)) return targetPath;
2323
- 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 });
2324
2609
  const initialContent = buildCanonicalInitialContent(item, changeId, author, date);
2325
- fs7__namespace.writeFileSync(targetPath, initialContent, "utf-8");
2610
+ fs8__namespace.writeFileSync(targetPath, initialContent, "utf-8");
2326
2611
  return targetPath;
2327
2612
  }
2328
2613
  function findChangeWorkspaceDir(rootDir, changeId) {
2329
- const activeDir = path4__namespace.join(rootDir, "specs", "changes", "active", changeId);
2330
- if (fs7__namespace.existsSync(activeDir)) return activeDir;
2331
- const completedDir = path4__namespace.join(rootDir, "specs", "changes", "completed", changeId);
2332
- if (fs7__namespace.existsSync(completedDir)) return completedDir;
2333
- const openspecActive = path4__namespace.join(rootDir, "openspec", "changes", changeId);
2334
- 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;
2335
2620
  return null;
2336
2621
  }
2337
2622
  function checkTasksExecution(changeDir, changeId) {
2338
- const tasksFile = path4__namespace.join(changeDir, "tasks.md");
2623
+ const tasksFile = path5__namespace.join(changeDir, "tasks.md");
2339
2624
  const allTasks = [];
2340
- if (fs7__namespace.existsSync(tasksFile)) {
2341
- 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")));
2342
2627
  }
2343
2628
  const completedTasks = allTasks.filter((t) => t.status === "COMPLETED").map((t) => t.id);
2344
2629
  const pendingTasks = allTasks.filter((t) => t.status !== "COMPLETED").map((t) => t.id);
@@ -2370,9 +2655,9 @@ function buildSubgraphFromProposalIds(ids) {
2370
2655
  function resolveChangeHandoff(changeDir, changeId) {
2371
2656
  const sidecar = loadProductHandoffSidecar(changeDir);
2372
2657
  if (sidecar) return sidecar;
2373
- const proposalFile = path4__namespace.join(changeDir, "proposal.md");
2374
- if (!fs7__namespace.existsSync(proposalFile)) return null;
2375
- 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");
2376
2661
  const { frontmatter } = parseFrontmatter(propContent);
2377
2662
  const citations = Array.isArray(frontmatter.citations) ? frontmatter.citations : [];
2378
2663
  const ids = citations.map((c) => c.id || "").filter(Boolean);
@@ -2386,9 +2671,9 @@ function resolveChangeHandoff(changeDir, changeId) {
2386
2671
  }
2387
2672
  function materializeNewCatalogArtifacts(changeDir, rootDir, changeId, author, date, errors) {
2388
2673
  const created = [];
2389
- const specFile = path4__namespace.join(changeDir, "spec.md");
2390
- if (!fs7__namespace.existsSync(specFile)) return created;
2391
- 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");
2392
2677
  const { newItems } = parseSpecCatalogScope(specContent);
2393
2678
  for (const item of newItems) {
2394
2679
  try {
@@ -2405,10 +2690,10 @@ function loadCanonicalArtifacts(rootDir) {
2405
2690
  const allMdFiles = walkMdFiles(rootDir);
2406
2691
  const canonicalArtifacts = /* @__PURE__ */ new Map();
2407
2692
  for (const file of allMdFiles) {
2408
- const relFile = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
2693
+ const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
2409
2694
  if (relFile.includes("specs/changes/") || relFile.includes("templates/")) continue;
2410
2695
  try {
2411
- const content = fs7__namespace.readFileSync(file, "utf-8");
2696
+ const content = fs8__namespace.readFileSync(file, "utf-8");
2412
2697
  const { frontmatter, body } = parseFrontmatter(content);
2413
2698
  if (frontmatter.id) canonicalArtifacts.set(String(frontmatter.id), { file, frontmatter, body });
2414
2699
  } catch {
@@ -2421,7 +2706,7 @@ function writeUpdatedArtifact(file, frontmatter, body) {
2421
2706
  ${yaml3__default.default.dump(frontmatter, { indent: 2, lineWidth: -1 }).trim()}
2422
2707
  ---
2423
2708
  `;
2424
- fs7__namespace.writeFileSync(file, yamlHeader + body, "utf-8");
2709
+ fs8__namespace.writeFileSync(file, yamlHeader + body, "utf-8");
2425
2710
  }
2426
2711
  function updateSingleCanonicalRequirement(canonical, completedTasks, changeId, date, author) {
2427
2712
  const fm = canonical.frontmatter;
@@ -2537,28 +2822,28 @@ function updateArchitectureComponents(citedComponents, canonicalArtifacts, targe
2537
2822
  return updated;
2538
2823
  }
2539
2824
  function moveToCompletedFolder(changeDir, changeId, rootDir) {
2540
- const completedParent = path4__namespace.join(rootDir, "specs", "changes", "completed");
2541
- if (!fs7__namespace.existsSync(completedParent)) fs7__namespace.mkdirSync(completedParent, { recursive: true });
2542
- const destDir = path4__namespace.join(completedParent, changeId);
2543
- if (fs7__namespace.existsSync(destDir)) fs7__namespace.rmSync(destDir, { recursive: true, force: true });
2544
- fs7__namespace.renameSync(changeDir, destDir);
2545
- const destProposal = path4__namespace.join(destDir, "proposal.md");
2546
- if (fs7__namespace.existsSync(destProposal)) {
2547
- 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"));
2548
2833
  frontmatter.status = "applied";
2549
2834
  writeUpdatedArtifact(destProposal, frontmatter, body);
2550
2835
  }
2551
2836
  return destDir;
2552
2837
  }
2553
2838
  function archiveChangeWorkspace(changeDir, changeId, rootDir, autoArchive, completedAll) {
2554
- 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");
2555
2840
  if (completedAll && autoArchive && isActiveFolder) {
2556
2841
  const destDir = moveToCompletedFolder(changeDir, changeId, rootDir);
2557
2842
  return { archived: true, archivedPath: destDir };
2558
2843
  }
2559
- const proposalFile = path4__namespace.join(changeDir, "proposal.md");
2560
- if (fs7__namespace.existsSync(proposalFile)) {
2561
- 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"));
2562
2847
  if (completedAll) frontmatter.status = "completed";
2563
2848
  writeUpdatedArtifact(proposalFile, frontmatter, body);
2564
2849
  }
@@ -2623,9 +2908,9 @@ function collectSubgraphPromotableIds(subgraph) {
2623
2908
  return Array.from(new Set(combined));
2624
2909
  }
2625
2910
  function collectSpecAffectedNonArchIds(changeDir) {
2626
- const specFile = path4__namespace.join(changeDir, "spec.md");
2627
- if (!fs7__namespace.existsSync(specFile)) return [];
2628
- 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"));
2629
2914
  return affectedItems.map((item) => item.id).filter((id) => !isArchitectureComponentId(id));
2630
2915
  }
2631
2916
  function resolveIntegrationOptions(options) {
@@ -2699,8 +2984,8 @@ function resolveAuditStatus(integratedCount, targetCount, gapsCount) {
2699
2984
  function auditChangeEntry(changeDir, changeName, canonicalMap) {
2700
2985
  const handoff = loadProductHandoffSidecar(changeDir);
2701
2986
  if (!handoff) return null;
2702
- const tasksFile = path4__namespace.join(changeDir, "tasks.md");
2703
- 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")) : [];
2704
2989
  const completed = tasks.filter((t) => t.status === "COMPLETED");
2705
2990
  const targetReqs = collectSubgraphPromotableIds(handoff.subgraph);
2706
2991
  const { integrated, missing, gaps } = auditChangeRequirements(targetReqs, canonicalMap);
@@ -2716,11 +3001,11 @@ function auditChangeEntry(changeDir, changeName, canonicalMap) {
2716
3001
  };
2717
3002
  }
2718
3003
  function collectParentDirAudits(parent, canonicalMap, audits, errors) {
2719
- if (!fs7__namespace.existsSync(parent)) return;
2720
- const entries = fs7__namespace.readdirSync(parent, { withFileTypes: true });
3004
+ if (!fs8__namespace.existsSync(parent)) return;
3005
+ const entries = fs8__namespace.readdirSync(parent, { withFileTypes: true });
2721
3006
  for (const e of entries) {
2722
3007
  if (!e.isDirectory()) continue;
2723
- 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);
2724
3009
  if (audit) {
2725
3010
  if (audit.gaps.length > 0) errors.push(`[${e.name}] Desalineaci\xF3n: ${audit.gaps.join("; ")}`);
2726
3011
  audits.push(audit);
@@ -2732,8 +3017,8 @@ function verifySddIntegration(options = {}) {
2732
3017
  const audits = [];
2733
3018
  const errors = [];
2734
3019
  const searchDirs = [
2735
- path4__namespace.join(rootDir, "specs", "changes", "completed"),
2736
- path4__namespace.join(rootDir, "specs", "changes", "active")
3020
+ path5__namespace.join(rootDir, "specs", "changes", "completed"),
3021
+ path5__namespace.join(rootDir, "specs", "changes", "active")
2737
3022
  ];
2738
3023
  const canonicalMap = loadCanonicalArtifacts(rootDir);
2739
3024
  for (const parent of searchDirs) {
@@ -2781,10 +3066,10 @@ function resolveMatchFromMetadata(activeFolders, options) {
2781
3066
  return {};
2782
3067
  }
2783
3068
  function validateActiveDirectory(activeParent) {
2784
- if (!fs7__namespace.existsSync(activeParent)) {
3069
+ if (!fs8__namespace.existsSync(activeParent)) {
2785
3070
  return { ok: false, folders: [], error: `No existe el directorio de cambios activos en '${activeParent}'.` };
2786
3071
  }
2787
- const entries = fs7__namespace.readdirSync(activeParent, { withFileTypes: true });
3072
+ const entries = fs8__namespace.readdirSync(activeParent, { withFileTypes: true });
2788
3073
  const activeFolders = entries.filter((e) => e.isDirectory()).map((e) => e.name);
2789
3074
  if (activeFolders.length === 0) {
2790
3075
  return { ok: false, folders: [], error: "No se encontraron carpetas de cambios activos en specs/changes/active/." };
@@ -2816,7 +3101,7 @@ function evaluateTasksProgress(changeId, tasks) {
2816
3101
  }
2817
3102
  function detectActiveChangeForIntegration(options = {}) {
2818
3103
  const rootDir = options.rootDir || process.cwd();
2819
- const activeParent = path4__namespace.join(rootDir, "specs", "changes", "active");
3104
+ const activeParent = path5__namespace.join(rootDir, "specs", "changes", "active");
2820
3105
  const dirCheck = validateActiveDirectory(activeParent);
2821
3106
  if (!dirCheck.ok) {
2822
3107
  return {
@@ -2831,9 +3116,9 @@ function detectActiveChangeForIntegration(options = {}) {
2831
3116
  if (!match.changeId) {
2832
3117
  return buildUnmatchedActiveResult(dirCheck.folders, options);
2833
3118
  }
2834
- const changeDir = path4__namespace.join(activeParent, match.changeId);
2835
- const tasksFile = path4__namespace.join(changeDir, "tasks.md");
2836
- 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")) : [];
2837
3122
  const progress = evaluateTasksProgress(match.changeId, tasks);
2838
3123
  const reasons = match.reason ? [match.reason] : [];
2839
3124
  if (progress.error) reasons.push(progress.error);
@@ -2848,9 +3133,46 @@ function detectActiveChangeForIntegration(options = {}) {
2848
3133
  reasons
2849
3134
  };
2850
3135
  }
2851
- 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) {
2852
3170
  const normalized = content.replace(/\r\n/g, "\n");
2853
- 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");
2854
3176
  }
2855
3177
  function parseFrontmatterId(content) {
2856
3178
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -2940,7 +3262,7 @@ function buildPdacReportHeader(isOk, driftsCount, nodesCount) {
2940
3262
  return [
2941
3263
  `# \u{1F512} PDaC Cryptographic Matrix and Drift Detection (Anti-Drift)`,
2942
3264
  ``,
2943
- `> **Evaluation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
3265
+ `> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
2944
3266
  `> **Verdict:** ${verdict}`,
2945
3267
  ``,
2946
3268
  `---`,
@@ -2951,8 +3273,8 @@ function buildPdacReportHeader(isOk, driftsCount, nodesCount) {
2951
3273
  `| :--- | :--- | :--- | :--- |`
2952
3274
  ];
2953
3275
  }
2954
- function generatePdacReportMarkdown(nodes, drifts, unresolvedCount) {
2955
- 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;
2956
3278
  const header = buildPdacReportHeader(isOk, drifts.length, nodes.length);
2957
3279
  const nodeRows = nodes.map(
2958
3280
  (n) => `| **\`${n.id}\`** | \`${n.type}\` | ${n.title || "Untitled"} | \`sha256:${n.digest.substring(0, 16)}...\` |`
@@ -2964,9 +3286,9 @@ function isPdacDeliveryDoc(relFile) {
2964
3286
  }
2965
3287
  function processPdacFile(file, rootDir, baselineMap, allCitations) {
2966
3288
  try {
2967
- const content = fs7__namespace.readFileSync(file, "utf-8");
3289
+ const content = fs8__namespace.readFileSync(file, "utf-8");
2968
3290
  const { id, type, title } = parseFrontmatterId(content);
2969
- const relFile = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
3291
+ const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
2970
3292
  if (id) {
2971
3293
  const citations = extractCitations(content, relFile);
2972
3294
  baselineMap.set(id, {
@@ -2975,6 +3297,7 @@ function processPdacFile(file, rootDir, baselineMap, allCitations) {
2975
3297
  filePath: relFile,
2976
3298
  title,
2977
3299
  digest: computeCanonicalSha256(content),
3300
+ rawDigest: computeRawSha256(content),
2978
3301
  rawContent: content,
2979
3302
  citations: citations.map((c) => ({
2980
3303
  targetId: c.targetId,
@@ -2998,38 +3321,65 @@ function scanPdacWorkspace(rootDir) {
2998
3321
  }
2999
3322
  return { baselineMap, allCitations };
3000
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
+ }
3001
3349
  function computePdacDrifts(allCitations, baselineMap) {
3002
3350
  const drifts = [];
3351
+ const unresolvedCitations = [];
3352
+ const invalidAnchors = [];
3003
3353
  let unresolvedCount = 0;
3004
3354
  for (const citation of allCitations) {
3005
3355
  const targetNode = baselineMap.get(citation.targetId);
3006
3356
  if (!targetNode) {
3007
3357
  unresolvedCount++;
3008
- continue;
3009
- }
3010
- if (citation.expectedDigest && citation.expectedDigest !== targetNode.digest) {
3011
- drifts.push({
3012
- sourceId: citation.sourceFile,
3013
- targetId: citation.targetId,
3358
+ unresolvedCitations.push({
3014
3359
  sourceFile: citation.sourceFile,
3015
- expectedDigest: citation.expectedDigest,
3016
- actualDigest: targetNode.digest
3360
+ targetId: citation.targetId
3017
3361
  });
3362
+ continue;
3018
3363
  }
3364
+ checkAnchor(citation, targetNode, invalidAnchors);
3365
+ checkDigest(citation, targetNode, drifts);
3019
3366
  }
3020
- return { drifts, unresolvedCount };
3367
+ return { drifts, unresolvedCount, unresolvedCitations, invalidAnchors };
3021
3368
  }
3022
3369
  function verifyPdacGraph(options = {}) {
3023
3370
  const rootDir = options.rootDir || process.cwd();
3024
3371
  const { baselineMap, allCitations } = scanPdacWorkspace(rootDir);
3025
- const { drifts, unresolvedCount } = computePdacDrifts(allCitations, baselineMap);
3372
+ const { drifts, unresolvedCount, unresolvedCitations, invalidAnchors } = computePdacDrifts(allCitations, baselineMap);
3026
3373
  const nodes = Array.from(baselineMap.values());
3027
- const reportMarkdown = generatePdacReportMarkdown(nodes, drifts, unresolvedCount);
3374
+ const reportMarkdown = generatePdacReportMarkdown(nodes, drifts, unresolvedCount, invalidAnchors);
3028
3375
  return {
3029
- success: drifts.length === 0,
3376
+ success: drifts.length === 0 && unresolvedCount === 0 && invalidAnchors.length === 0,
3030
3377
  totalNodes: nodes.length,
3031
3378
  totalEdges: allCitations.length,
3032
3379
  drifts,
3380
+ unresolvedCount,
3381
+ unresolvedCitations,
3382
+ invalidAnchors,
3033
3383
  reportMarkdown
3034
3384
  };
3035
3385
  }
@@ -3094,22 +3444,22 @@ function updateCitationsInContent(content, baselineMap, sourceFile, details) {
3094
3444
  }
3095
3445
  function collectCandidateFilesForSync(rootDir, targetPath) {
3096
3446
  if (targetPath) {
3097
- const fullTarget = path4__namespace.isAbsolute(targetPath) ? targetPath : path4__namespace.join(rootDir, targetPath);
3098
- if (!fs7__namespace.existsSync(fullTarget)) return [];
3099
- 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];
3100
3450
  }
3101
3451
  return walkMdFiles(rootDir).filter((f) => {
3102
- const relFile = path4__namespace.relative(rootDir, f).replace(/\\/g, "/");
3452
+ const relFile = path5__namespace.relative(rootDir, f).replace(/\\/g, "/");
3103
3453
  return isPdacDeliveryDoc(relFile);
3104
3454
  });
3105
3455
  }
3106
3456
  function syncSingleFile(file, rootDir, baselineMap, details) {
3107
3457
  try {
3108
- const content = fs7__namespace.readFileSync(file, "utf-8");
3109
- 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, "/");
3110
3460
  const { updatedContent, changed } = updateCitationsInContent(content, baselineMap, relFile, details);
3111
3461
  if (changed) {
3112
- fs7__namespace.writeFileSync(file, updatedContent, "utf-8");
3462
+ fs8__namespace.writeFileSync(file, updatedContent, "utf-8");
3113
3463
  return relFile;
3114
3464
  }
3115
3465
  } catch {
@@ -3312,6 +3662,36 @@ var STARTER_SCHEMAS = Object.entries(STARTER_SCHEMAS_MAP).map(([relPath, schema]
3312
3662
  }));
3313
3663
 
3314
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
+ }
3315
3695
  var TYPE_TO_SCHEMA_MAP = {
3316
3696
  // Product
3317
3697
  actor: "schemas/product/actor.schema.json",
@@ -3456,10 +3836,10 @@ var EXCLUDED_SCHEMA_DIRS = /* @__PURE__ */ new Set([
3456
3836
  ]);
3457
3837
  function walkHandoffSidecars(dir, fileList = []) {
3458
3838
  try {
3459
- if (!fs7__namespace.existsSync(dir)) return fileList;
3460
- for (const file of fs7__namespace.readdirSync(dir)) {
3461
- const fullPath = path4__namespace.join(dir, file);
3462
- 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()) {
3463
3843
  if (!EXCLUDED_SCHEMA_DIRS.has(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
3464
3844
  walkHandoffSidecars(fullPath, fileList);
3465
3845
  }
@@ -3482,157 +3862,41 @@ function extractArtifactData(filePath, content) {
3482
3862
  }
3483
3863
  return extractFrontmatter(content);
3484
3864
  }
3485
- function validateConst(value, schema, currentProp) {
3486
- if (schema.const !== void 0 && value !== schema.const) {
3487
- return [`'${currentProp}' must equal '${schema.const}', received '${String(value)}'`];
3488
- }
3489
- return [];
3490
- }
3491
- function validateEnum(value, schema, currentProp) {
3492
- if (schema.enum !== void 0 && !schema.enum.includes(value)) {
3493
- return [`'${currentProp}' must be one of [${schema.enum.join(", ")}], received '${String(value)}'`];
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);
3494
3871
  }
3495
- return [];
3496
- }
3497
- function validateCombinators(value, schema, propPath, currentProp) {
3498
- if (schema.oneOf) {
3499
- const matchCount = schema.oneOf.filter((sub) => validateAgainstSchema(value, sub, propPath).length === 0).length;
3500
- if (matchCount !== 1) {
3501
- return [`'${currentProp}' must match exactly one sub-schema in oneOf`];
3502
- }
3503
- }
3504
- if (schema.anyOf) {
3505
- const anyMatched = schema.anyOf.some((sub) => validateAgainstSchema(value, sub, propPath).length === 0);
3506
- if (!anyMatched) {
3507
- return [`'${currentProp}' did not match any allowed alternative in anyOf`];
3508
- }
3509
- }
3510
- return [];
3511
- }
3512
- function getActualJsonType(value) {
3513
- if (value === null) return "null";
3514
- if (Array.isArray(value)) return "array";
3515
- const t = typeof value;
3516
- if (t === "number" && Number.isInteger(value)) return "integer";
3517
- return t;
3518
- }
3519
- function validateType(value, schema, currentProp) {
3520
- if (schema.type === void 0) return [];
3521
- const allowed = Array.isArray(schema.type) ? schema.type : [schema.type];
3522
- const actual = getActualJsonType(value);
3523
- const matches = allowed.some((t) => t === actual || t === "number" && actual === "integer");
3524
- if (!matches) {
3525
- return [`'${currentProp}' expected type [${allowed.join("|")}], received '${actual}'`];
3526
- }
3527
- return [];
3528
- }
3529
- function validateString(value, schema, currentProp) {
3530
- const errors = [];
3531
- if (schema.minLength !== void 0 && value.length < schema.minLength) {
3532
- errors.push(`'${currentProp}' length must be >= ${schema.minLength}`);
3533
- }
3534
- if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
3535
- errors.push(`'${currentProp}' length must be <= ${schema.maxLength}`);
3536
- }
3537
- if (schema.pattern !== void 0 && !new RegExp(schema.pattern).test(value)) {
3538
- errors.push(`'${currentProp}' value '${value}' does not match pattern ${schema.pattern}`);
3539
- }
3540
- if (schema.format === "date") {
3541
- const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
3542
- if (!dateRegex.test(value) || isNaN(Date.parse(value))) {
3543
- errors.push(`'${currentProp}' must be a valid ISO-8601 date string (YYYY-MM-DD)`);
3544
- }
3545
- }
3546
- return errors;
3547
- }
3548
- function validateNumber(value, schema, currentProp) {
3549
- const errors = [];
3550
- if (schema.minimum !== void 0 && value < schema.minimum) {
3551
- errors.push(`'${currentProp}' must be >= ${schema.minimum}`);
3552
- }
3553
- if (schema.maximum !== void 0 && value > schema.maximum) {
3554
- errors.push(`'${currentProp}' must be <= ${schema.maximum}`);
3555
- }
3556
- return errors;
3557
- }
3558
- function validateArray(value, schema, currentProp) {
3559
- const errors = [];
3560
- if (schema.minItems !== void 0 && value.length < schema.minItems) {
3561
- errors.push(`'${currentProp}' items count must be >= ${schema.minItems}`);
3562
- }
3563
- if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
3564
- errors.push(`'${currentProp}' items count must be <= ${schema.maxItems}`);
3565
- }
3566
- if (schema.items) {
3567
- value.forEach((item, idx) => {
3568
- errors.push(...validateAgainstSchema(item, schema.items, `${currentProp}[${idx}]`));
3569
- });
3570
- }
3571
- return errors;
3572
- }
3573
- function checkRequiredProperties(record, required, currentProp) {
3574
- if (!required) return [];
3575
- const errors = [];
3576
- for (const reqKey of required) {
3577
- if (!(reqKey in record) || record[reqKey] === void 0) {
3578
- errors.push(`'${currentProp}' missing required field '${reqKey}'`);
3579
- }
3580
- }
3581
- return errors;
3582
- }
3583
- function checkObjectProperties(record, schema, propPath, currentProp) {
3584
- const errors = [];
3585
- const properties = schema.properties || {};
3586
- for (const [key, propVal] of Object.entries(record)) {
3587
- const subPath = propPath ? `${propPath}.${key}` : key;
3588
- if (key in properties) {
3589
- errors.push(...validateAgainstSchema(propVal, properties[key], subPath));
3590
- } else if (schema.additionalProperties === false) {
3591
- errors.push(`'${currentProp}' contains disallowed extra property '${key}'`);
3592
- } else if (typeof schema.additionalProperties === "object") {
3593
- errors.push(...validateAgainstSchema(propVal, schema.additionalProperties, subPath));
3594
- }
3595
- }
3596
- return errors;
3597
- }
3598
- function validateObject(record, schema, propPath, currentProp) {
3599
- return [
3600
- ...checkRequiredProperties(record, schema.required, currentProp),
3601
- ...checkObjectProperties(record, schema, propPath, currentProp)
3602
- ];
3603
- }
3604
- function validateTypeSpecificRules(value, schema, propPath, currentProp) {
3605
- if (typeof value === "string") return validateString(value, schema, currentProp);
3606
- if (typeof value === "number") return validateNumber(value, schema, currentProp);
3607
- if (Array.isArray(value)) return validateArray(value, schema, currentProp);
3608
- if (typeof value === "object" && value !== null) {
3609
- return validateObject(value, schema, propPath, currentProp);
3610
- }
3611
- 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;
3612
3877
  }
3613
3878
  function validateAgainstSchema(value, schema, propPath = "") {
3614
- const currentProp = propPath || "root";
3615
- const constErr = validateConst(value, schema, currentProp);
3616
- if (constErr.length > 0) return constErr;
3617
- const enumErr = validateEnum(value, schema, currentProp);
3618
- if (enumErr.length > 0) return enumErr;
3619
- const combErr = validateCombinators(value, schema, propPath, currentProp);
3620
- if (combErr.length > 0) return combErr;
3621
- const typeErr = validateType(value, schema, currentProp);
3622
- if (typeErr.length > 0) return typeErr;
3623
- 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
+ }
3624
3888
  }
3625
3889
  function findSchemaFile(relSchemaPath, rootDir) {
3626
- const fullPath = path4__namespace.join(rootDir, relSchemaPath);
3627
- if (fs7__namespace.existsSync(fullPath)) return fullPath;
3628
- const fallbackPath = path4__namespace.join(process.cwd(), relSchemaPath);
3629
- 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;
3630
3894
  return null;
3631
3895
  }
3632
3896
  function loadJsonSchema(fullPath, relSchemaPath) {
3633
- if (fullPath && fs7__namespace.existsSync(fullPath)) {
3897
+ if (fullPath && fs8__namespace.existsSync(fullPath)) {
3634
3898
  try {
3635
- return JSON.parse(fs7__namespace.readFileSync(fullPath, "utf-8"));
3899
+ return JSON.parse(fs8__namespace.readFileSync(fullPath, "utf-8"));
3636
3900
  } catch {
3637
3901
  }
3638
3902
  }
@@ -3647,7 +3911,7 @@ function resolveSchemaForArtifact(frontmatter, rootDir) {
3647
3911
  }
3648
3912
  function loadSchemaFromPathOrType(schemaOrType, rootDir) {
3649
3913
  if (schemaOrType.endsWith(".json")) {
3650
- const fullPath = path4__namespace.isAbsolute(schemaOrType) ? schemaOrType : findSchemaFile(schemaOrType, rootDir);
3914
+ const fullPath = path5__namespace.isAbsolute(schemaOrType) ? schemaOrType : findSchemaFile(schemaOrType, rootDir);
3651
3915
  const relNormalized = schemaOrType.replace(/\\/g, "/");
3652
3916
  const schema = loadJsonSchema(fullPath, relNormalized);
3653
3917
  return { schema, schemaId: schemaOrType };
@@ -3692,9 +3956,9 @@ function collectCandidateSchemaFiles(options, rootDir) {
3692
3956
  if (!options.targetPath) {
3693
3957
  return [...walkMdFiles(rootDir), ...walkHandoffSidecars(rootDir)];
3694
3958
  }
3695
- const target = path4__namespace.isAbsolute(options.targetPath) ? options.targetPath : path4__namespace.join(rootDir, options.targetPath);
3696
- if (!fs7__namespace.existsSync(target)) return [];
3697
- 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()) {
3698
3962
  return [...walkMdFiles(target), ...walkHandoffSidecars(target)];
3699
3963
  }
3700
3964
  if (target.endsWith(".md") || target.endsWith(".yaml") || target.endsWith(".yml")) {
@@ -3745,7 +4009,7 @@ function buildEvaluationFailure(relPath, err) {
3745
4009
  }
3746
4010
  function evaluateArtifactFile(file, relPath, rootDir) {
3747
4011
  try {
3748
- const content = fs7__namespace.readFileSync(file, "utf-8");
4012
+ const content = fs8__namespace.readFileSync(file, "utf-8");
3749
4013
  const frontmatter = extractArtifactData(file, content);
3750
4014
  if (!frontmatter || !frontmatter.id) return null;
3751
4015
  const schemaInfo = resolveSchemaForArtifact(frontmatter, rootDir);
@@ -3780,7 +4044,7 @@ function verifyArtifactsSchemas(options = {}) {
3780
4044
  let validCount = 0;
3781
4045
  let totalEvaluated = 0;
3782
4046
  for (const file of filesToScan) {
3783
- const relPath = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
4047
+ const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
3784
4048
  if (isIgnoredArtifactPath(relPath, Boolean(options.targetPath))) continue;
3785
4049
  const evaluation = evaluateArtifactFile(file, relPath, rootDir);
3786
4050
  if (!evaluation) continue;
@@ -3795,14 +4059,14 @@ function verifyArtifactsSchemas(options = {}) {
3795
4059
  }
3796
4060
 
3797
4061
  // src/adapters/sdd/scaffold.ts
3798
- function slugify(text) {
4062
+ function slugify2(text) {
3799
4063
  return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3800
4064
  }
3801
4065
  function extractDirMaxNum(dir) {
3802
- if (!fs7__namespace.existsSync(dir)) return 0;
4066
+ if (!fs8__namespace.existsSync(dir)) return 0;
3803
4067
  let max = 0;
3804
4068
  try {
3805
- const entries = fs7__namespace.readdirSync(dir, { withFileTypes: true });
4069
+ const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
3806
4070
  for (const e of entries) {
3807
4071
  if (!e.isDirectory()) continue;
3808
4072
  const match = e.name.match(/^(?:chg|CHG|patch|PATCH)-(\d+)/i);
@@ -3819,10 +4083,10 @@ function extractDirMaxNum(dir) {
3819
4083
  }
3820
4084
  function getNextCorrelativeNumber(rootDir) {
3821
4085
  const candidateDirs = [
3822
- path4__namespace.join(rootDir, "specs", "changes", "active"),
3823
- path4__namespace.join(rootDir, "specs", "changes", "completed"),
3824
- path4__namespace.join(rootDir, "specs"),
3825
- 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")
3826
4090
  ];
3827
4091
  let maxNum = 0;
3828
4092
  for (const dir of candidateDirs) {
@@ -3835,7 +4099,7 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
3835
4099
  const targetChangeId = options.changeId || options.id;
3836
4100
  if (targetChangeId) {
3837
4101
  const rawId = targetChangeId.trim();
3838
- const slug = slugify(rawId);
4102
+ const slug = slugify2(rawId);
3839
4103
  const canonicalId = rawId.toUpperCase();
3840
4104
  const match = rawId.match(/^(?:chg|CHG|patch|PATCH)-(\d+)/i);
3841
4105
  const padNum2 = match ? match[1] : "001";
@@ -3843,7 +4107,7 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
3843
4107
  }
3844
4108
  const nextNum = getNextCorrelativeNumber(rootDir);
3845
4109
  const padNum = String(nextNum).padStart(3, "0");
3846
- const nameSlug = slugify(name);
4110
+ const nameSlug = slugify2(name);
3847
4111
  const prefix = profile === "patch" ? "patch" : "chg";
3848
4112
  return {
3849
4113
  slug: `${prefix}-${padNum}-${nameSlug}`,
@@ -3852,13 +4116,13 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
3852
4116
  };
3853
4117
  }
3854
4118
  function ensureEmptyChangeDir(changeDir) {
3855
- if (fs7__namespace.existsSync(changeDir)) {
3856
- const existing = fs7__namespace.readdirSync(changeDir);
4119
+ if (fs8__namespace.existsSync(changeDir)) {
4120
+ const existing = fs8__namespace.readdirSync(changeDir);
3857
4121
  if (existing.length > 0) {
3858
4122
  return `El directorio del cambio '${changeDir}' ya existe y no est\xE1 vac\xEDo.`;
3859
4123
  }
3860
4124
  } else {
3861
- fs7__namespace.mkdirSync(changeDir, { recursive: true });
4125
+ fs8__namespace.mkdirSync(changeDir, { recursive: true });
3862
4126
  }
3863
4127
  return null;
3864
4128
  }
@@ -3867,7 +4131,7 @@ function scanCanonicalArtifacts(rootDir) {
3867
4131
  const artifactMap = /* @__PURE__ */ new Map();
3868
4132
  for (const file of allMdFiles) {
3869
4133
  try {
3870
- const content = fs7__namespace.readFileSync(file, "utf-8");
4134
+ const content = fs8__namespace.readFileSync(file, "utf-8");
3871
4135
  const { frontmatter } = parseFrontmatter(content);
3872
4136
  if (frontmatter?.id) {
3873
4137
  const id = String(frontmatter.id);
@@ -4246,7 +4510,7 @@ function buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors)
4246
4510
  changeId: slug,
4247
4511
  canonicalId,
4248
4512
  profile: "patch",
4249
- changeDir: path4__namespace.dirname(specPath),
4513
+ changeDir: path5__namespace.dirname(specPath),
4250
4514
  createdFiles: [specPath],
4251
4515
  citedArtifacts,
4252
4516
  errors
@@ -4257,10 +4521,10 @@ function scaffoldPatchChange(rootDir, changeDir, slug, canonicalId, name, inputF
4257
4521
  const citationsSection = citedArtifacts.length > 0 ? `citations:
4258
4522
  ${renderCitationsYamlBlock(citedArtifacts)}
4259
4523
  ` : "";
4260
- const specPath = path4__namespace.join(changeDir, "spec.md");
4524
+ const specPath = path5__namespace.join(changeDir, "spec.md");
4261
4525
  const specFmObj = buildPatchFmObj(canonicalId);
4262
4526
  const specContent = buildPatchSpecContent(canonicalId, name, citationsSection);
4263
- fs7__namespace.writeFileSync(specPath, specContent, "utf-8");
4527
+ fs8__namespace.writeFileSync(specPath, specContent, "utf-8");
4264
4528
  const errors = validatePatchArtifacts(rootDir, changeDir, specContent, specFmObj, adapter);
4265
4529
  return buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors);
4266
4530
  }
@@ -4271,46 +4535,46 @@ function buildGreenfieldContent(reqId, ucId, name, canonicalId, author, dateStr)
4271
4535
  ${body}`;
4272
4536
  }
4273
4537
  function createGreenfieldRequirement(rootDir, name, padNum, canonicalId, author, dateStr, createdFiles) {
4274
- const nameSlugUpper = slugify(name).toUpperCase();
4538
+ const nameSlugUpper = slugify2(name).toUpperCase();
4275
4539
  const reqId = `FR-${padNum}-${nameSlugUpper}-001`;
4276
4540
  const ucId = `UC-${padNum}-${nameSlugUpper}`;
4277
- const productDir = path4__namespace.join(rootDir, "specs", "product");
4278
- if (!fs7__namespace.existsSync(productDir)) fs7__namespace.mkdirSync(productDir, { recursive: true });
4279
- const reqFilePath = path4__namespace.join(productDir, `${reqId}.md`);
4280
- 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;
4281
4545
  const reqContent = buildGreenfieldContent(reqId, ucId, name, canonicalId, author, dateStr);
4282
- fs7__namespace.writeFileSync(reqFilePath, reqContent, "utf-8");
4546
+ fs8__namespace.writeFileSync(reqFilePath, reqContent, "utf-8");
4283
4547
  createdFiles.push(reqFilePath);
4284
4548
  const digest = computeCanonicalSha256(reqContent);
4285
4549
  return { reqId, ucId, reqFilePath, digest };
4286
4550
  }
4287
4551
  function resolveMissingDraftDestination(rootDir, id) {
4288
4552
  if (id.startsWith("PRIV-REQ-")) {
4289
- 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" };
4290
4554
  }
4291
4555
  if (id.startsWith("DATA-ASSET-")) {
4292
- 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" };
4293
4557
  }
4294
4558
  if (id.startsWith("PRIV-ROPA-")) {
4295
- 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" };
4296
4560
  }
4297
4561
  if (id.startsWith("UC-")) {
4298
- return { dir: path4__namespace.join(rootDir, "specs", "product"), type: "use-case" };
4562
+ return { dir: path5__namespace.join(rootDir, "specs", "product"), type: "use-case" };
4299
4563
  }
4300
4564
  if (id.startsWith("SEC-REQ-") || id.startsWith("SAF-REQ-")) {
4301
- return { dir: path4__namespace.join(rootDir, "specs", "product"), type: "security-requirement" };
4565
+ return { dir: path5__namespace.join(rootDir, "specs", "product"), type: "security-requirement" };
4302
4566
  }
4303
- return { dir: path4__namespace.join(rootDir, "specs", "product"), type: "requirement" };
4567
+ return { dir: path5__namespace.join(rootDir, "specs", "product"), type: "requirement" };
4304
4568
  }
4305
4569
  function createMissingDraftRequirement(rootDir, name, id, canonicalId, createdFiles) {
4306
4570
  const { dir, type: reqType } = resolveMissingDraftDestination(rootDir, id);
4307
- if (!fs7__namespace.existsSync(dir)) fs7__namespace.mkdirSync(dir, { recursive: true });
4308
- 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`);
4309
4573
  const header = MISSING_DRAFT_HEADER.replaceAll("{{ID}}", id).replaceAll("{{TYPE}}", reqType).replaceAll("{{NAME}}", name);
4310
4574
  const body = MISSING_DRAFT_BODY.replaceAll("{{ID}}", id).replaceAll("{{NAME}}", name).replaceAll("{{CANONICAL_ID}}", canonicalId);
4311
4575
  const reqContent = `${header}
4312
4576
  ${body}`;
4313
- fs7__namespace.writeFileSync(reqFilePath, reqContent, "utf-8");
4577
+ fs8__namespace.writeFileSync(reqFilePath, reqContent, "utf-8");
4314
4578
  createdFiles.push(reqFilePath);
4315
4579
  const digest = computeCanonicalSha256(reqContent);
4316
4580
  return { reqFilePath, digest };
@@ -4425,7 +4689,7 @@ function initEmptyCitations() {
4425
4689
  }
4426
4690
  function ensureFallbackRequirement(col, padNum, name) {
4427
4691
  if (col.subRequirements.length === 0) {
4428
- col.subRequirements.push(`FR-${padNum}-${slugify(name).toUpperCase()}-001`);
4692
+ col.subRequirements.push(`FR-${padNum}-${slugify2(name).toUpperCase()}-001`);
4429
4693
  }
4430
4694
  }
4431
4695
  function collectChangeCitations(rootDir, name, padNum, canonicalId, author, dateStr, inputFromIds, artifactMap, createdFiles) {
@@ -4439,7 +4703,7 @@ function collectChangeCitations(rootDir, name, padNum, canonicalId, author, date
4439
4703
  return col;
4440
4704
  }
4441
4705
  function renderProposalFile(changeDir, canonicalId, name, author, citationsYamlBlock) {
4442
- const proposalPath = path4__namespace.join(changeDir, "proposal.md");
4706
+ const proposalPath = path5__namespace.join(changeDir, "proposal.md");
4443
4707
  const citationsSection = citationsYamlBlock ? `citations:
4444
4708
  ${citationsYamlBlock}
4445
4709
  ` : "";
@@ -4459,11 +4723,11 @@ ${name}
4459
4723
  ## 2. Dependencias Externas Evaluadas
4460
4724
  - Lista de dependencias nuevas a incorporar y verificaci\xF3n de licencia seg\xFAn \`license-policy.yaml\`.
4461
4725
  `;
4462
- fs7__namespace.writeFileSync(proposalPath, proposalContent, "utf-8");
4726
+ fs8__namespace.writeFileSync(proposalPath, proposalContent, "utf-8");
4463
4727
  return proposalPath;
4464
4728
  }
4465
4729
  function renderSpecFile(changeDir, canonicalId, name, profile) {
4466
- const specPath = path4__namespace.join(changeDir, "spec.md");
4730
+ const specPath = path5__namespace.join(changeDir, "spec.md");
4467
4731
  const specFmObj = {
4468
4732
  id: `SPEC-${canonicalId}`,
4469
4733
  type: "delivery-spec",
@@ -4484,12 +4748,12 @@ cucumber-tags:
4484
4748
  ---
4485
4749
  `;
4486
4750
  const body = SPEC_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name);
4487
- fs7__namespace.writeFileSync(specPath, `${header}
4751
+ fs8__namespace.writeFileSync(specPath, `${header}
4488
4752
  ${body}`, "utf-8");
4489
4753
  return { specPath, specFmObj };
4490
4754
  }
4491
4755
  function renderDesignFile(changeDir, canonicalId, name, author, dateStr, citationsYamlBlock) {
4492
- const designPath = path4__namespace.join(changeDir, "design.md");
4756
+ const designPath = path5__namespace.join(changeDir, "design.md");
4493
4757
  const citationsSection = citationsYamlBlock ? `citations:
4494
4758
  ${citationsYamlBlock}
4495
4759
  ` : "";
@@ -4508,7 +4772,7 @@ contracts: []
4508
4772
  ${citationsSection}---
4509
4773
  `;
4510
4774
  const body = DESIGN_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
4511
- fs7__namespace.writeFileSync(designPath, `${header}
4775
+ fs8__namespace.writeFileSync(designPath, `${header}
4512
4776
  ${body}`, "utf-8");
4513
4777
  return designPath;
4514
4778
  }
@@ -4533,14 +4797,14 @@ function buildTasksFrontmatter(canonicalId, name, reqIds) {
4533
4797
  };
4534
4798
  }
4535
4799
  function renderTasksFile(changeDir, canonicalId, name, author, dateStr, reqIds) {
4536
- const tasksPath = path4__namespace.join(changeDir, "tasks.md");
4800
+ const tasksPath = path5__namespace.join(changeDir, "tasks.md");
4537
4801
  const tasksFm = buildTasksFrontmatter(canonicalId, name, reqIds);
4538
4802
  const header = `---
4539
4803
  ${yaml3__default.default.dump(tasksFm, { indent: 2, lineWidth: -1 }).trim()}
4540
4804
  ---
4541
4805
  `;
4542
4806
  const body = TASKS_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
4543
- fs7__namespace.writeFileSync(tasksPath, `${header}
4807
+ fs8__namespace.writeFileSync(tasksPath, `${header}
4544
4808
  ${body}`, "utf-8");
4545
4809
  return { tasksPath, tasksFm };
4546
4810
  }
@@ -4719,7 +4983,7 @@ function resolveCiEnvironment(env = process.env) {
4719
4983
  };
4720
4984
  }
4721
4985
  function updateExistingEnvFile(filePath, envString, uppercaseKey) {
4722
- const content = fs7__namespace.readFileSync(filePath, "utf-8");
4986
+ const content = fs8__namespace.readFileSync(filePath, "utf-8");
4723
4987
  const lines = content.split("\n");
4724
4988
  let found = false;
4725
4989
  const newLines = lines.map((line) => {
@@ -4730,15 +4994,15 @@ function updateExistingEnvFile(filePath, envString, uppercaseKey) {
4730
4994
  return line;
4731
4995
  });
4732
4996
  if (!found) newLines.push(envString);
4733
- 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");
4734
4998
  }
4735
4999
  function writeEnvOutputFile(envOutputFile, key, value) {
4736
5000
  const uppercaseKey = key.toUpperCase();
4737
5001
  const envString = `${uppercaseKey}=${value}`;
4738
- if (fs7__namespace.existsSync(envOutputFile)) {
5002
+ if (fs8__namespace.existsSync(envOutputFile)) {
4739
5003
  updateExistingEnvFile(envOutputFile, envString, uppercaseKey);
4740
5004
  } else {
4741
- fs7__namespace.writeFileSync(envOutputFile, envString + "\n", "utf-8");
5005
+ fs8__namespace.writeFileSync(envOutputFile, envString + "\n", "utf-8");
4742
5006
  }
4743
5007
  }
4744
5008
  function resolveEnvOutputFilePath(env) {
@@ -4748,8 +5012,8 @@ function resolveEnvOutputFilePath(env) {
4748
5012
  return void 0;
4749
5013
  }
4750
5014
  function emitCiOutput(key, value, env = process.env) {
4751
- if (env.GITHUB_OUTPUT && fs7__namespace.existsSync(env.GITHUB_OUTPUT)) {
4752
- 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}
4753
5017
  `, "utf-8");
4754
5018
  }
4755
5019
  if (env.TF_BUILD === "true") {
@@ -4791,11 +5055,11 @@ function isIgnoredEntry(name) {
4791
5055
  return name === "changes" || name === "active" || name === "node_modules";
4792
5056
  }
4793
5057
  function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
4794
- if (!fs7__namespace.existsSync(parentDir)) return;
4795
- const entries = fs7__namespace.readdirSync(parentDir, { withFileTypes: true });
5058
+ if (!fs8__namespace.existsSync(parentDir)) return;
5059
+ const entries = fs8__namespace.readdirSync(parentDir, { withFileTypes: true });
4796
5060
  for (const entry of entries) {
4797
5061
  if (!entry.isDirectory() || isIgnoredEntry(entry.name)) continue;
4798
- const changeDir = path4__namespace.join(parentDir, entry.name);
5062
+ const changeDir = path5__namespace.join(parentDir, entry.name);
4799
5063
  if (handooffsHasDir(handoffsMap, changeDir)) continue;
4800
5064
  const handoff = loadProductHandoffSidecar(changeDir);
4801
5065
  if (handoff?.id) {
@@ -4806,10 +5070,10 @@ function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
4806
5070
  function scanAllProductHandoffs(rootDir) {
4807
5071
  const handoffsMap = /* @__PURE__ */ new Map();
4808
5072
  const candidateParentDirs = [
4809
- path4__namespace.join(rootDir, "specs", "changes", "active"),
4810
- path4__namespace.join(rootDir, "specs", "changes", "completed"),
4811
- path4__namespace.join(rootDir, "specs"),
4812
- 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")
4813
5077
  ];
4814
5078
  for (const parentDir of candidateParentDirs) {
4815
5079
  scanParentDirectoryHandoffs(parentDir, handoffsMap);
@@ -4817,9 +5081,9 @@ function scanAllProductHandoffs(rootDir) {
4817
5081
  return handoffsMap;
4818
5082
  }
4819
5083
  function handooffsHasDir(map, dir) {
4820
- const norm = path4__namespace.resolve(dir);
5084
+ const norm = path5__namespace.resolve(dir);
4821
5085
  for (const key of map.keys()) {
4822
- if (path4__namespace.resolve(key) === norm) return true;
5086
+ if (path5__namespace.resolve(key) === norm) return true;
4823
5087
  }
4824
5088
  return false;
4825
5089
  }
@@ -4854,7 +5118,7 @@ function buildTraceabilityHeader(isOk, errors) {
4854
5118
  return [
4855
5119
  "# 360\xB0 Requirements Traceability Matrix (RTM)",
4856
5120
  "",
4857
- `*Verification Date: ${(/* @__PURE__ */ new Date()).toISOString()}*`,
5121
+ `*Verification Date: ${resolveDeterministicDate().toISOString()}*`,
4858
5122
  `*Overall Status: ${isOk ? "100% TRACEABLE (PASSED)" : `ERRORS DETECTED (${errors} gaps)`}*`,
4859
5123
  "",
4860
5124
  "## 1. End-to-End Coverage (PDaC Handoff \u2794 arc42/NAF v4 \u2794 BDD/Gherkin)",
@@ -4916,9 +5180,9 @@ function isCodeTestFile(name) {
4916
5180
  }
4917
5181
  function indexFeatureFile(fullPath, rootDir, map) {
4918
5182
  try {
4919
- const content = fs7__namespace.readFileSync(fullPath, "utf-8");
5183
+ const content = fs8__namespace.readFileSync(fullPath, "utf-8");
4920
5184
  const tags = extractFeatureTags(content);
4921
- const relFeature = path4__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
5185
+ const relFeature = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
4922
5186
  for (const tag of tags) {
4923
5187
  const existing = map.get(tag) || [];
4924
5188
  if (!existing.includes(relFeature)) existing.push(relFeature);
@@ -4928,11 +5192,11 @@ function indexFeatureFile(fullPath, rootDir, map) {
4928
5192
  }
4929
5193
  }
4930
5194
  function scanFeatureFiles(dir, rootDir, map) {
4931
- if (!fs7__namespace.existsSync(dir)) return;
5195
+ if (!fs8__namespace.existsSync(dir)) return;
4932
5196
  try {
4933
- const entries = fs7__namespace.readdirSync(dir, { withFileTypes: true });
5197
+ const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
4934
5198
  for (const e of entries) {
4935
- const fullPath = path4__namespace.join(dir, e.name);
5199
+ const fullPath = path5__namespace.join(dir, e.name);
4936
5200
  if (e.isDirectory() && !shouldSkipDir(e.name)) {
4937
5201
  scanFeatureFiles(fullPath, rootDir, map);
4938
5202
  } else if (e.name.endsWith(".feature")) {
@@ -4944,8 +5208,8 @@ function scanFeatureFiles(dir, rootDir, map) {
4944
5208
  }
4945
5209
  function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
4946
5210
  try {
4947
- const content = fs7__namespace.readFileSync(fullPath, "utf-8");
4948
- 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, "/");
4949
5213
  for (const artId of reqIds) {
4950
5214
  if (content.includes(artId)) {
4951
5215
  const existing = map.get(artId) || [];
@@ -4957,11 +5221,11 @@ function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
4957
5221
  }
4958
5222
  }
4959
5223
  function scanCodeTestFiles(dir, rootDir, reqIds, map) {
4960
- if (!fs7__namespace.existsSync(dir)) return;
5224
+ if (!fs8__namespace.existsSync(dir)) return;
4961
5225
  try {
4962
- const entries = fs7__namespace.readdirSync(dir, { withFileTypes: true });
5226
+ const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
4963
5227
  for (const e of entries) {
4964
- const fullPath = path4__namespace.join(dir, e.name);
5228
+ const fullPath = path5__namespace.join(dir, e.name);
4965
5229
  if (e.isDirectory() && !shouldSkipDir(e.name)) {
4966
5230
  scanCodeTestFiles(fullPath, rootDir, reqIds, map);
4967
5231
  } else if (isCodeTestFile(e.name)) {
@@ -4995,9 +5259,9 @@ function isArchitectureViewFile(relFile, frontmatter) {
4995
5259
  }
4996
5260
  function processSingleMdFile(file, rootDir, artifactMap, componentsList, architectureViews) {
4997
5261
  try {
4998
- const content = fs7__namespace.readFileSync(file, "utf-8");
5262
+ const content = fs8__namespace.readFileSync(file, "utf-8");
4999
5263
  const { frontmatter, body } = parseFrontmatter(content);
5000
- const relFile = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
5264
+ const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
5001
5265
  if (frontmatter.id) {
5002
5266
  artifactMap.set(frontmatter.id, {
5003
5267
  file: relFile,
@@ -5162,7 +5426,7 @@ function collectArchitectureViewTraces(reqId, architectureViews) {
5162
5426
  const traces = [];
5163
5427
  for (const view of architectureViews) {
5164
5428
  if (view.content.includes(reqId)) {
5165
- 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);
5166
5430
  traces.push(viewIdentifier);
5167
5431
  }
5168
5432
  }
@@ -5250,16 +5514,16 @@ function isProductionSourceFile(name) {
5250
5514
  return SOURCE_CODE_EXTS.some((ext) => name.endsWith(ext));
5251
5515
  }
5252
5516
  function collectCodeAndTestCorpora(dir, prodChunks = [], testChunks = []) {
5253
- if (!fs7__namespace.existsSync(dir)) return { prodCorpus: "", testCorpus: "" };
5517
+ if (!fs8__namespace.existsSync(dir)) return { prodCorpus: "", testCorpus: "" };
5254
5518
  try {
5255
- for (const e of fs7__namespace.readdirSync(dir, { withFileTypes: true })) {
5256
- 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);
5257
5521
  if (e.isDirectory() && !shouldSkipDir(e.name)) {
5258
5522
  collectCodeAndTestCorpora(fullPath, prodChunks, testChunks);
5259
5523
  } else if (isCodeTestFile(e.name) || e.name.endsWith(".feature")) {
5260
- testChunks.push(fs7__namespace.readFileSync(fullPath, "utf-8"));
5524
+ testChunks.push(fs8__namespace.readFileSync(fullPath, "utf-8"));
5261
5525
  } else if (isProductionSourceFile(e.name)) {
5262
- prodChunks.push(fs7__namespace.readFileSync(fullPath, "utf-8"));
5526
+ prodChunks.push(fs8__namespace.readFileSync(fullPath, "utf-8"));
5263
5527
  }
5264
5528
  }
5265
5529
  } catch {
@@ -5413,12 +5677,12 @@ function walkTaskFiles(dir, fileList = [], excludeDirs = [
5413
5677
  "examples"
5414
5678
  ]) {
5415
5679
  try {
5416
- if (!fs7__namespace.existsSync(dir)) return fileList;
5417
- const files = fs7__namespace.readdirSync(dir);
5680
+ if (!fs8__namespace.existsSync(dir)) return fileList;
5681
+ const files = fs8__namespace.readdirSync(dir).sort(canonicalSort);
5418
5682
  for (const file of files) {
5419
- const fullPath = path4__namespace.join(dir, file);
5683
+ const fullPath = path5__namespace.join(dir, file);
5420
5684
  try {
5421
- if (fs7__namespace.statSync(fullPath).isDirectory()) {
5685
+ if (fs8__namespace.statSync(fullPath).isDirectory()) {
5422
5686
  if (!excludeDirs.includes(file)) {
5423
5687
  walkTaskFiles(fullPath, fileList, excludeDirs);
5424
5688
  }
@@ -5470,7 +5734,7 @@ function generateGovernanceReportMarkdown(summaries, violationsCount, modeCounts
5470
5734
  const sections = [
5471
5735
  `# \u{1F4CB} Task Governance and Human Autonomy Classification Report
5472
5736
 
5473
- > **Audit Date:** ${(/* @__PURE__ */ new Date()).toISOString()}
5737
+ > **Audit Date:** ${resolveDeterministicDate().toISOString()}
5474
5738
  > **Overall Verdict:** ${verdict}
5475
5739
 
5476
5740
  ---`,
@@ -5531,9 +5795,9 @@ function updateGovernanceCounts(t, modeCounts, riskCounts) {
5531
5795
  }
5532
5796
  }
5533
5797
  function parseTasksFromFile(file) {
5534
- if (!fs7__namespace.existsSync(file)) return [];
5798
+ if (!fs8__namespace.existsSync(file)) return [];
5535
5799
  try {
5536
- const content = fs7__namespace.readFileSync(file, "utf-8");
5800
+ const content = fs8__namespace.readFileSync(file, "utf-8");
5537
5801
  const { frontmatter } = parseTasksDoc(content);
5538
5802
  return frontmatter.tasks || [];
5539
5803
  } catch {
@@ -5541,11 +5805,11 @@ function parseTasksFromFile(file) {
5541
5805
  }
5542
5806
  }
5543
5807
  function extractDesignExpectedScope(changeDir) {
5544
- const designFile = path4__namespace.join(changeDir, "design.md");
5545
- if (!fs7__namespace.existsSync(designFile)) return [];
5808
+ const designFile = path5__namespace.join(changeDir, "design.md");
5809
+ if (!fs8__namespace.existsSync(designFile)) return [];
5546
5810
  const expected = [];
5547
5811
  try {
5548
- const { frontmatter } = parseTasksDoc(fs7__namespace.readFileSync(designFile, "utf-8"));
5812
+ const { frontmatter } = parseTasksDoc(fs8__namespace.readFileSync(designFile, "utf-8"));
5549
5813
  if (typeof frontmatter["architecture-component"] === "string") {
5550
5814
  expected.push(frontmatter["architecture-component"]);
5551
5815
  }
@@ -5585,7 +5849,7 @@ function hasExplicitScopeBindings(tasks) {
5585
5849
  );
5586
5850
  }
5587
5851
  function auditDesignScopeForFile(file, tasks, options, unassignedList, violationsList) {
5588
- const changeDir = path4__namespace.dirname(file);
5852
+ const changeDir = path5__namespace.dirname(file);
5589
5853
  const expectedItems = [
5590
5854
  ...extractDesignExpectedScope(changeDir),
5591
5855
  ...extractHandoffExpectedScope(changeDir, options.maxUseCasesPerChange, violationsList)
@@ -5596,13 +5860,13 @@ function auditDesignScopeForFile(file, tasks, options, unassignedList, violation
5596
5860
  for (const item of new Set(expectedItems)) {
5597
5861
  if (!covered.has(item)) {
5598
5862
  unassignedList.push(item);
5599
- 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}`);
5600
5864
  }
5601
5865
  }
5602
5866
  }
5603
5867
  function processTasksFile(file, rootDir, options, acc) {
5604
5868
  const tasks = parseTasksFromFile(file);
5605
- const relPath = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
5869
+ const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
5606
5870
  let unverifiedDelta = 0;
5607
5871
  for (const t of tasks) {
5608
5872
  const { summary, violation, unverified } = evaluateTaskItem(t, relPath);
@@ -5901,11 +6165,11 @@ async function runAllExpandedScenarios(parsed, definitions) {
5901
6165
  }
5902
6166
  async function executeBddFeature(featurePath, registry, options = {}) {
5903
6167
  const rootDir = options.rootDir || process.cwd();
5904
- const fullPath = path4__namespace.isAbsolute(featurePath) ? featurePath : path4__namespace.join(rootDir, featurePath);
5905
- const parsed = parseFeatureDocument(fs7__namespace.readFileSync(fullPath, "utf-8"));
6168
+ const fullPath = path5__namespace.isAbsolute(featurePath) ? featurePath : path5__namespace.join(rootDir, featurePath);
6169
+ const parsed = parseFeatureDocument(fs8__namespace.readFileSync(fullPath, "utf-8"));
5906
6170
  const scenarios = await runAllExpandedScenarios(parsed, registry.getDefinitions());
5907
6171
  const report = {
5908
- uri: path4__namespace.relative(rootDir, fullPath).replace(/\\/g, "/"),
6172
+ uri: path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/"),
5909
6173
  featureName: parsed.featureName,
5910
6174
  tags: parsed.tags,
5911
6175
  scenarios,
@@ -5939,10 +6203,10 @@ function mapFeatureToCucumberJson(r) {
5939
6203
  };
5940
6204
  }
5941
6205
  function writeCucumberJsonReport(reports, outputPath) {
5942
- const dir = path4__namespace.dirname(outputPath);
5943
- if (!fs7__namespace.existsSync(dir)) fs7__namespace.mkdirSync(dir, { recursive: true });
6206
+ const dir = path5__namespace.dirname(outputPath);
6207
+ if (!fs8__namespace.existsSync(dir)) fs8__namespace.mkdirSync(dir, { recursive: true });
5944
6208
  const payload = JSON.stringify(reports.map(mapFeatureToCucumberJson), null, 2);
5945
- fs7__namespace.writeFileSync(outputPath, payload, "utf-8");
6209
+ fs8__namespace.writeFileSync(outputPath, payload, "utf-8");
5946
6210
  }
5947
6211
  function isToolAvailable(toolName) {
5948
6212
  try {
@@ -5988,8 +6252,8 @@ function findLicenseFile(pkgDir) {
5988
6252
  "license.txt"
5989
6253
  ];
5990
6254
  for (const c of candidates) {
5991
- const full = path4__namespace.join(pkgDir, c);
5992
- if (fs7__namespace.existsSync(full)) {
6255
+ const full = path5__namespace.join(pkgDir, c);
6256
+ if (fs8__namespace.existsSync(full)) {
5993
6257
  return full;
5994
6258
  }
5995
6259
  }
@@ -6006,9 +6270,9 @@ var LICENSE_FILE_PATTERNS = [
6006
6270
  [(t) => t.includes("Microsoft Reciprocal License") || t.includes("MS-RL"), "MS-RL"]
6007
6271
  ];
6008
6272
  function resolveLicenseFromFile(licenseFilePath) {
6009
- if (!licenseFilePath || !fs7__namespace.existsSync(licenseFilePath)) return void 0;
6273
+ if (!licenseFilePath || !fs8__namespace.existsSync(licenseFilePath)) return void 0;
6010
6274
  try {
6011
- const text = fs7__namespace.readFileSync(licenseFilePath, "utf-8");
6275
+ const text = fs8__namespace.readFileSync(licenseFilePath, "utf-8");
6012
6276
  for (const [matches, spdx] of LICENSE_FILE_PATTERNS) {
6013
6277
  if (matches(text)) return spdx;
6014
6278
  }
@@ -6017,16 +6281,16 @@ function resolveLicenseFromFile(licenseFilePath) {
6017
6281
  return void 0;
6018
6282
  }
6019
6283
  function readPackageJson(filePath) {
6020
- if (!fs7__namespace.existsSync(filePath)) return null;
6284
+ if (!fs8__namespace.existsSync(filePath)) return null;
6021
6285
  try {
6022
- return JSON.parse(fs7__namespace.readFileSync(filePath, "utf-8"));
6286
+ return JSON.parse(fs8__namespace.readFileSync(filePath, "utf-8"));
6023
6287
  } catch {
6024
6288
  return null;
6025
6289
  }
6026
6290
  }
6027
6291
  function extractDirectDepNames(rootDir, productionOnly) {
6028
6292
  const names = /* @__PURE__ */ new Set();
6029
- const pkgPath = path4__namespace.join(rootDir, "package.json");
6293
+ const pkgPath = path5__namespace.join(rootDir, "package.json");
6030
6294
  const pkg = readPackageJson(pkgPath);
6031
6295
  if (!pkg) return names;
6032
6296
  if (pkg.dependencies && typeof pkg.dependencies === "object") {
@@ -6046,7 +6310,7 @@ function resolveSpdxForPackage(pkg, licFile) {
6046
6310
  return spdx;
6047
6311
  }
6048
6312
  function processPackageDir(pkgDir, directNames, seen) {
6049
- const pkg = readPackageJson(path4__namespace.join(pkgDir, "package.json"));
6313
+ const pkg = readPackageJson(path5__namespace.join(pkgDir, "package.json"));
6050
6314
  if (!pkg?.name || !pkg.version) return;
6051
6315
  const key = `${pkg.name}@${pkg.version}`;
6052
6316
  if (seen.has(key)) return;
@@ -6067,40 +6331,40 @@ function processPackageDir(pkgDir, directNames, seen) {
6067
6331
  });
6068
6332
  }
6069
6333
  function scanScopeSubdirectory(scopeDir, directNames, seen) {
6070
- const scopedEntries = fs7__namespace.readdirSync(scopeDir, { withFileTypes: true });
6334
+ const scopedEntries = fs8__namespace.readdirSync(scopeDir, { withFileTypes: true });
6071
6335
  for (const scoped of scopedEntries) {
6072
6336
  if (scoped.isDirectory()) {
6073
- processPackageDir(path4__namespace.join(scopeDir, scoped.name), directNames, seen);
6337
+ processPackageDir(path5__namespace.join(scopeDir, scoped.name), directNames, seen);
6074
6338
  }
6075
6339
  }
6076
6340
  }
6077
6341
  function scanSubdirectories(baseDir, directNames, seen) {
6078
- if (!fs7__namespace.existsSync(baseDir)) return;
6079
- const entries = fs7__namespace.readdirSync(baseDir, { withFileTypes: true });
6342
+ if (!fs8__namespace.existsSync(baseDir)) return;
6343
+ const entries = fs8__namespace.readdirSync(baseDir, { withFileTypes: true });
6080
6344
  for (const entry of entries) {
6081
6345
  if (!entry.isDirectory()) continue;
6082
6346
  if (entry.name.startsWith("@")) {
6083
- scanScopeSubdirectory(path4__namespace.join(baseDir, entry.name), directNames, seen);
6347
+ scanScopeSubdirectory(path5__namespace.join(baseDir, entry.name), directNames, seen);
6084
6348
  } else if (entry.name !== ".bin" && entry.name !== ".pnpm") {
6085
- processPackageDir(path4__namespace.join(baseDir, entry.name), directNames, seen);
6349
+ processPackageDir(path5__namespace.join(baseDir, entry.name), directNames, seen);
6086
6350
  }
6087
6351
  }
6088
6352
  }
6089
6353
  function scanPnpmStore(pnpmDir, directNames, seen) {
6090
- if (!fs7__namespace.existsSync(pnpmDir)) return;
6091
- const entries = fs7__namespace.readdirSync(pnpmDir, { withFileTypes: true });
6354
+ if (!fs8__namespace.existsSync(pnpmDir)) return;
6355
+ const entries = fs8__namespace.readdirSync(pnpmDir, { withFileTypes: true });
6092
6356
  for (const entry of entries) {
6093
6357
  if (!entry.isDirectory()) continue;
6094
- const candidateModules = path4__namespace.join(pnpmDir, entry.name, "node_modules");
6095
- if (fs7__namespace.existsSync(candidateModules)) {
6358
+ const candidateModules = path5__namespace.join(pnpmDir, entry.name, "node_modules");
6359
+ if (fs8__namespace.existsSync(candidateModules)) {
6096
6360
  scanSubdirectories(candidateModules, directNames, seen);
6097
6361
  }
6098
6362
  }
6099
6363
  }
6100
6364
  function readTextIfExists(filePath) {
6101
- if (!fs7__namespace.existsSync(filePath)) return null;
6365
+ if (!fs8__namespace.existsSync(filePath)) return null;
6102
6366
  try {
6103
- return fs7__namespace.readFileSync(filePath, "utf-8");
6367
+ return fs8__namespace.readFileSync(filePath, "utf-8");
6104
6368
  } catch {
6105
6369
  return null;
6106
6370
  }
@@ -6160,14 +6424,14 @@ function parsePipfileSection(pkgs, isDirect, lockPath, seen) {
6160
6424
  }
6161
6425
  }
6162
6426
  function scanPythonDependencies(rootDir, seen) {
6163
- const pyproject = readTextIfExists(path4__namespace.join(rootDir, "pyproject.toml")) || "";
6427
+ const pyproject = readTextIfExists(path5__namespace.join(rootDir, "pyproject.toml")) || "";
6164
6428
  const directNames = extractPyprojectDirectDeps(pyproject);
6165
6429
  for (const lockName of ["poetry.lock", "uv.lock"]) {
6166
- const lockPath = path4__namespace.join(rootDir, lockName);
6430
+ const lockPath = path5__namespace.join(rootDir, lockName);
6167
6431
  const content = readTextIfExists(lockPath);
6168
6432
  if (content) parseTomlPackageBlocks(content, lockPath, directNames, seen);
6169
6433
  }
6170
- const pipfileLockPath = path4__namespace.join(rootDir, "Pipfile.lock");
6434
+ const pipfileLockPath = path5__namespace.join(rootDir, "Pipfile.lock");
6171
6435
  const pipfileRaw = readTextIfExists(pipfileLockPath);
6172
6436
  if (!pipfileRaw) return;
6173
6437
  try {
@@ -6210,7 +6474,7 @@ function extractGoCandidateLines(content) {
6210
6474
  return out;
6211
6475
  }
6212
6476
  function scanGoDependencies(rootDir, seen) {
6213
- const goModPath = path4__namespace.join(rootDir, "go.mod");
6477
+ const goModPath = path5__namespace.join(rootDir, "go.mod");
6214
6478
  const content = readTextIfExists(goModPath);
6215
6479
  if (!content) return;
6216
6480
  for (const line of extractGoCandidateLines(content)) {
@@ -6253,10 +6517,10 @@ function parseCargoLockBlock(block, rootName, directMeta, cargoLockPath) {
6253
6517
  };
6254
6518
  }
6255
6519
  function scanRustDependencies(rootDir, seen) {
6256
- const cargoLockPath = path4__namespace.join(rootDir, "Cargo.lock");
6520
+ const cargoLockPath = path5__namespace.join(rootDir, "Cargo.lock");
6257
6521
  const lockContent = readTextIfExists(cargoLockPath);
6258
6522
  if (!lockContent) return;
6259
- const cargoToml = readTextIfExists(path4__namespace.join(rootDir, "Cargo.toml")) || "";
6523
+ const cargoToml = readTextIfExists(path5__namespace.join(rootDir, "Cargo.toml")) || "";
6260
6524
  const directMeta = extractCargoDirectDeps(cargoToml);
6261
6525
  const rootName = extractTomlField(cargoToml, "name");
6262
6526
  for (const block of lockContent.split(/\[\[package\]\]/g).slice(1)) {
@@ -6292,7 +6556,7 @@ function parseMavenDepBlock(block, defaultLic, pomPath) {
6292
6556
  };
6293
6557
  }
6294
6558
  function scanMavenDependencies(rootDir, seen) {
6295
- const pomPath = path4__namespace.join(rootDir, "pom.xml");
6559
+ const pomPath = path5__namespace.join(rootDir, "pom.xml");
6296
6560
  const content = readTextIfExists(pomPath);
6297
6561
  if (!content) return;
6298
6562
  const projectLicBlock = content.match(/<licenses>([\s\S]*?)<\/licenses>/)?.[1] || "";
@@ -6308,11 +6572,11 @@ function scanMavenDependencies(rootDir, seen) {
6308
6572
  function scanWithNativeFs(rootDir, options = {}) {
6309
6573
  const directNames = extractDirectDepNames(rootDir, options.productionOnly);
6310
6574
  const seen = /* @__PURE__ */ new Map();
6311
- const nmDir = path4__namespace.join(rootDir, "node_modules");
6312
- if (fs7__namespace.existsSync(nmDir)) {
6575
+ const nmDir = path5__namespace.join(rootDir, "node_modules");
6576
+ if (fs8__namespace.existsSync(nmDir)) {
6313
6577
  scanSubdirectories(nmDir, directNames, seen);
6314
6578
  if (options.depth !== "direct") {
6315
- scanPnpmStore(path4__namespace.join(nmDir, ".pnpm"), directNames, seen);
6579
+ scanPnpmStore(path5__namespace.join(nmDir, ".pnpm"), directNames, seen);
6316
6580
  }
6317
6581
  }
6318
6582
  scanPythonDependencies(rootDir, seen);
@@ -6571,20 +6835,20 @@ function resolveProjectIdentity(options) {
6571
6835
  let name = options.projectName || "ai-sdlc-project";
6572
6836
  let version = options.projectVersion || "1.0.0";
6573
6837
  if (!options.rootDir) return { name, version };
6574
- const pkgPath = path4__namespace.join(options.rootDir, "package.json");
6575
- 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 };
6576
6840
  try {
6577
- const pkg = JSON.parse(fs7__namespace.readFileSync(pkgPath, "utf-8"));
6841
+ const pkg = JSON.parse(fs8__namespace.readFileSync(pkgPath, "utf-8"));
6578
6842
  if (pkg.name) name = pkg.name;
6579
6843
  if (pkg.version) version = pkg.version;
6580
6844
  } catch {
6581
6845
  }
6582
6846
  return { name, version };
6583
6847
  }
6584
- function buildBomMetadata(name, version) {
6848
+ function buildBomMetadata(name, version, rootDir) {
6585
6849
  return {
6586
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6587
- tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.4.0" }],
6850
+ timestamp: resolveDeterministicDate(rootDir).toISOString(),
6851
+ tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.5.0" }],
6588
6852
  component: { type: "application", name, version }
6589
6853
  };
6590
6854
  }
@@ -6595,52 +6859,21 @@ function generateCycloneDxSbom(dependencies, options = {}) {
6595
6859
  bomFormat: "CycloneDX",
6596
6860
  specVersion: "1.5",
6597
6861
  version: 1,
6598
- serialNumber: `urn:uuid:${crypto__namespace.randomUUID()}`,
6599
- 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),
6600
6864
  components: dependencies.map(buildCycloneDxComponent),
6601
6865
  ...cdxVulns.length > 0 ? { vulnerabilities: cdxVulns } : {}
6602
6866
  };
6603
6867
  }
6604
- function tryAtomicWrite(filePath, content) {
6605
- const tempPath = `${filePath}.${process.pid}.${Math.random().toString(36).substring(2)}.tmp`;
6606
- try {
6607
- fs7__namespace.writeFileSync(tempPath, content, "utf-8");
6608
- try {
6609
- if (fs7__namespace.existsSync(filePath)) {
6610
- fs7__namespace.rmSync(filePath, { force: true });
6611
- }
6612
- fs7__namespace.renameSync(tempPath, filePath);
6613
- } catch {
6614
- fs7__namespace.copyFileSync(tempPath, filePath);
6615
- fs7__namespace.unlinkSync(tempPath);
6616
- }
6617
- return true;
6618
- } catch {
6619
- return false;
6620
- }
6621
- }
6622
- function safeWriteFileSync(filePath, content) {
6623
- const dir = path4__namespace.dirname(filePath);
6624
- if (!fs7__namespace.existsSync(dir)) {
6625
- fs7__namespace.mkdirSync(dir, { recursive: true });
6626
- }
6627
- for (let attempt = 0; attempt < 10; attempt++) {
6628
- if (tryAtomicWrite(filePath, content)) return;
6629
- }
6630
- try {
6631
- fs7__namespace.writeFileSync(filePath, content, "utf-8");
6632
- } catch {
6633
- }
6634
- }
6635
6868
  function writeCycloneDxSbom(sbom, outputPath) {
6636
- safeWriteFileSync(outputPath, JSON.stringify(sbom, null, 2));
6869
+ writeReportFileIfChanged(outputPath, JSON.stringify(sbom, null, 2));
6637
6870
  }
6638
6871
  function buildThirdPartyHeader(projectName, count) {
6639
6872
  return [
6640
6873
  `# \u{1F4DC} Third-Party Software Notices and Information`,
6641
6874
  ``,
6642
6875
  `> **Proyecto:** ${projectName}`,
6643
- `> **Fecha:** ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
6876
+ `> **Fecha:** ${resolveDeterministicDate().toISOString().split("T")[0]}`,
6644
6877
  `> **Total Componentes:** ${count}`,
6645
6878
  ``,
6646
6879
  `Este documento contiene los avisos de copyright y t\xE9rminos de licencia aplicables a los componentes de terceros utilizados en este proyecto.`,
@@ -6654,9 +6887,9 @@ function buildThirdPartyHeader(projectName, count) {
6654
6887
  ];
6655
6888
  }
6656
6889
  function readLicenseTextPreview(licenseFile) {
6657
- if (!licenseFile || !fs7__namespace.existsSync(licenseFile)) return null;
6890
+ if (!licenseFile || !fs8__namespace.existsSync(licenseFile)) return null;
6658
6891
  try {
6659
- const text = fs7__namespace.readFileSync(licenseFile, "utf-8").trim();
6892
+ const text = fs8__namespace.readFileSync(licenseFile, "utf-8").trim();
6660
6893
  return text.slice(0, 1e3) + (text.length > 1e3 ? "\n... (texto truncado)" : "");
6661
6894
  } catch {
6662
6895
  return null;
@@ -6688,13 +6921,13 @@ function generateThirdPartyNotices(dependencies, projectName = "AI-SDLC") {
6688
6921
  "",
6689
6922
  "---",
6690
6923
  "",
6691
- "## Textos de Licencia y Atribuciones",
6924
+ "## 2. Atribuciones y T\xE9rminos de Licencia Individuales",
6692
6925
  "",
6693
6926
  ...attributions
6694
6927
  ].join("\n");
6695
6928
  }
6696
- function writeThirdPartyNotices(content, outputPath) {
6697
- safeWriteFileSync(outputPath, content);
6929
+ function writeThirdPartyNotices(noticesContent, outputPath) {
6930
+ writeReportFileIfChanged(outputPath, noticesContent);
6698
6931
  }
6699
6932
 
6700
6933
  // src/verifiers/licenses.ts
@@ -6771,22 +7004,20 @@ function parseVexStatements(doc) {
6771
7004
  if (!Array.isArray(rawList)) return [];
6772
7005
  return rawList.filter((item) => Boolean(item && typeof item.id === "string" && typeof item.state === "string"));
6773
7006
  }
6774
- function parseLicensePolicy(content) {
7007
+ function parseLicenseDoc(content) {
7008
+ let doc;
6775
7009
  try {
6776
- const doc = yaml3__default.default.load(content);
6777
- const { permitted, restricted, blocked } = classifyCategories(doc?.categories);
6778
- const exception_packages = parseExceptions(doc?.exceptions);
6779
- const vex_statements = parseVexStatements(doc);
6780
- return {
6781
- version: doc?.version || "1.0",
6782
- permitted: permitted.length > 0 ? permitted : DEFAULT_PERMITTED,
6783
- restricted: restricted.length > 0 ? restricted : DEFAULT_RESTRICTED,
6784
- blocked: blocked.length > 0 ? blocked : DEFAULT_BLOCKED,
6785
- exception_packages,
6786
- vex_statements,
6787
- rawPolicy: doc
6788
- };
6789
- } 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()) {
6790
7021
  return {
6791
7022
  version: "1.0",
6792
7023
  permitted: DEFAULT_PERMITTED,
@@ -6796,6 +7027,17 @@ function parseLicensePolicy(content) {
6796
7027
  vex_statements: []
6797
7028
  };
6798
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
+ };
6799
7041
  }
6800
7042
  function evaluateCompoundLicense(lic, policy) {
6801
7043
  const cleanLic = lic.replace(/[()]/g, "").trim();
@@ -6887,7 +7129,7 @@ function buildReportHeader(isOk, depCount, violationCount, sbomPath) {
6887
7129
  return [
6888
7130
  `# \u{1F4DC} Open Source License Compliance Audit (Dynamic SCA)`,
6889
7131
  ``,
6890
- `> **Verification Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
7132
+ `> **Verification Date:** ${resolveDeterministicDate().toISOString()}`,
6891
7133
  `> **Status:** ${statusStr}`,
6892
7134
  `> **Total Audited Dependencies:** ${depCount}`,
6893
7135
  sbomPath ? `> **Generated CycloneDX SBOM:** \`${sbomPath}\`` : "",
@@ -6912,9 +7154,9 @@ function generateLicenseReportMarkdown(dependencies, violations, policy, sbomPat
6912
7154
  return lines.filter(Boolean).join("\n");
6913
7155
  }
6914
7156
  function parseManifestItems(manifestFile) {
6915
- if (!fs7__namespace.existsSync(manifestFile)) return [];
7157
+ if (!fs8__namespace.existsSync(manifestFile)) return [];
6916
7158
  try {
6917
- 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"));
6918
7160
  return Array.isArray(doc?.evaluated_dependencies) ? doc.evaluated_dependencies : [];
6919
7161
  } catch {
6920
7162
  return [];
@@ -6946,10 +7188,10 @@ function verifyStaticManifest(manifestFile, policy) {
6946
7188
  function resolveManifestPath(rootDir, customPath) {
6947
7189
  if (customPath) return customPath;
6948
7190
  const candidates = [
6949
- path4__namespace.join(rootDir, "compliance", "license-manifest.yaml"),
6950
- path4__namespace.join(rootDir, "license-manifest.yaml")
7191
+ path5__namespace.join(rootDir, "compliance", "license-manifest.yaml"),
7192
+ path5__namespace.join(rootDir, "license-manifest.yaml")
6951
7193
  ];
6952
- return candidates.find((p) => fs7__namespace.existsSync(p)) || candidates[0];
7194
+ return candidates.find((p) => fs8__namespace.existsSync(p)) || candidates[0];
6953
7195
  }
6954
7196
  function handleStaticManifestVerification(options, rootDir, policy) {
6955
7197
  const manifestFile = resolveManifestPath(rootDir, options.manifestPath);
@@ -6970,7 +7212,7 @@ function shouldGenerateSbom(options, rawPolicy) {
6970
7212
  }
6971
7213
  function maybeGenerateSbom(options, rootDir, rawPolicy, dependencies, vulnerabilities, vexStatements) {
6972
7214
  if (!shouldGenerateSbom(options, rawPolicy)) return void 0;
6973
- 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");
6974
7216
  const sbom = generateCycloneDxSbom(dependencies, { rootDir, vulnerabilities, vexStatements });
6975
7217
  writeCycloneDxSbom(sbom, sbomPath);
6976
7218
  return sbomPath;
@@ -6979,7 +7221,7 @@ function maybeGenerateNotices(options, rootDir, rawPolicy, dependencies) {
6979
7221
  const shouldGen = options.generateNotices === true || Boolean(options.noticesPath || rawPolicy?.ci_gates?.output_notices_file);
6980
7222
  if (!shouldGen) return void 0;
6981
7223
  const noticesFileName = rawPolicy?.ci_gates?.output_notices_file || "THIRD_PARTY_NOTICES.md";
6982
- const noticesPath = options.noticesPath || path4__namespace.join(rootDir, noticesFileName);
7224
+ const noticesPath = options.noticesPath || path5__namespace.join(rootDir, noticesFileName);
6983
7225
  const noticesContent = generateThirdPartyNotices(dependencies);
6984
7226
  writeThirdPartyNotices(noticesContent, noticesPath);
6985
7227
  return noticesPath;
@@ -6991,8 +7233,8 @@ function handleDynamicScaArtifacts(options, rootDir, rawPolicy, dependencies, vu
6991
7233
  };
6992
7234
  }
6993
7235
  function loadLicensePolicy(rootDir, customPath) {
6994
- const policyFile = customPath || path4__namespace.join(rootDir, "license-policy.yaml");
6995
- 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("");
6996
7238
  }
6997
7239
  function evaluateDependencyList(dependencies, policy) {
6998
7240
  const violations = [];
@@ -7096,11 +7338,11 @@ var PROTECTED_BYPASS_PATTERNS = [
7096
7338
  /migrations?[\\/]/i
7097
7339
  ];
7098
7340
  function detectProfileFromChange(changeDir) {
7099
- if (!fs7__namespace.existsSync(changeDir)) return "standard";
7100
- const specFile = path4__namespace.join(changeDir, "spec.md");
7101
- 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)) {
7102
7344
  try {
7103
- const rawSpec = fs7__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
7345
+ const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
7104
7346
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
7105
7347
  if (match) {
7106
7348
  const parsedFm = yaml3__default.default.load(match[1]);
@@ -7118,24 +7360,29 @@ function isPathProtectedFromPatch(filePath) {
7118
7360
  return PROTECTED_BYPASS_PATTERNS.some((pattern) => pattern.test(normalized));
7119
7361
  }
7120
7362
  function detectBranchChangeMatch(rootDir, dirs) {
7121
- if (!fs7__namespace.existsSync(path4__namespace.join(rootDir, ".git"))) return void 0;
7122
- try {
7123
- const branch = child_process.execFileSync("git", ["branch", "--show-current"], {
7124
- cwd: rootDir,
7125
- encoding: "utf-8",
7126
- stdio: ["pipe", "pipe", "ignore"]
7127
- }).trim().toLowerCase();
7128
- if (!branch) return void 0;
7129
- return dirs.find((d) => branch.includes(path4__namespace.basename(d).toLowerCase()));
7130
- } catch {
7131
- 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;
7132
7377
  }
7378
+ if (!branch) return void 0;
7379
+ return dirs.find((d) => branch.includes(path5__namespace.basename(d).toLowerCase()));
7133
7380
  }
7134
7381
  function resolveActiveChangeDir(rootDir) {
7135
- const activeRoot = path4__namespace.join(rootDir, "specs", "changes", "active");
7136
- 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;
7137
7384
  try {
7138
- 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));
7139
7386
  const branchMatch = detectBranchChangeMatch(rootDir, dirs);
7140
7387
  if (branchMatch) return branchMatch;
7141
7388
  return dirs.find((d) => detectProfileFromChange(d) === "patch") || dirs[0];
@@ -7143,8 +7390,12 @@ function resolveActiveChangeDir(rootDir) {
7143
7390
  return void 0;
7144
7391
  }
7145
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
+ }
7146
7397
  function detectGitDiffFiles(rootDir) {
7147
- if (!fs7__namespace.existsSync(path4__namespace.join(rootDir, ".git"))) return [];
7398
+ if (!fs8__namespace.existsSync(path5__namespace.join(rootDir, ".git"))) return [];
7148
7399
  try {
7149
7400
  const tracked = child_process.execFileSync("git", ["diff", "--name-only", "HEAD"], {
7150
7401
  cwd: rootDir,
@@ -7157,7 +7408,7 @@ function detectGitDiffFiles(rootDir) {
7157
7408
  stdio: ["pipe", "pipe", "ignore"]
7158
7409
  });
7159
7410
  const combined = `${tracked}
7160
- ${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));
7161
7412
  return Array.from(new Set(combined));
7162
7413
  } catch {
7163
7414
  return [];
@@ -7167,19 +7418,19 @@ function resolveChangeDirectory(rootDir, options) {
7167
7418
  if (options.changeDir) return options.changeDir;
7168
7419
  if (options.changeId) {
7169
7420
  const candidates = [
7170
- path4__namespace.join(rootDir, "specs", options.changeId),
7171
- path4__namespace.join(rootDir, "specs", "changes", "active", options.changeId),
7172
- path4__namespace.join(rootDir, "examples", "specs", options.changeId),
7173
- 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)
7174
7425
  ];
7175
- return candidates.find((c) => fs7__namespace.existsSync(c));
7426
+ return candidates.find((c) => fs8__namespace.existsSync(c));
7176
7427
  }
7177
7428
  return resolveActiveChangeDir(rootDir);
7178
7429
  }
7179
7430
  function collectPatchViolations(rootDir, modifiedFiles) {
7180
7431
  const violations = [];
7181
7432
  for (const rawFile of modifiedFiles) {
7182
- 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;
7183
7434
  if (isPathProtectedFromPatch(relPath)) {
7184
7435
  violations.push(
7185
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'.`
@@ -7192,7 +7443,7 @@ function buildFrictionHeader(profile, success, fileCount) {
7192
7443
  return [
7193
7444
  `# \u{1F6E1}\uFE0F Progressive Friction and Anti-Bypass Verification Report`,
7194
7445
  ``,
7195
- `> **Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
7446
+ `> **Date:** ${resolveDeterministicDate().toISOString()}`,
7196
7447
  `> **Evaluated Profile:** \`${profile.toUpperCase()}\``,
7197
7448
  `> **Verdict:** ${success ? "COMPLIANT \u2705" : "BLOCKED \u274C"}`,
7198
7449
  ``,
@@ -7285,13 +7536,13 @@ function extractManualMetadata(fm, id, manualType, relPath, body) {
7285
7536
  }
7286
7537
  function buildManualItem(rootDir, filePath) {
7287
7538
  try {
7288
- const raw = fs7__namespace.readFileSync(filePath, "utf-8");
7539
+ const raw = fs8__namespace.readFileSync(filePath, "utf-8");
7289
7540
  const { fm, body } = parseMdFrontmatter(raw);
7290
7541
  const id = typeof fm.id === "string" ? fm.id.trim() : "";
7291
7542
  const rawType = typeof fm.type === "string" ? fm.type.trim() : "";
7292
7543
  const manualType = resolveManualType(id, rawType);
7293
7544
  if (!id || !manualType) return null;
7294
- const relPath = path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
7545
+ const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
7295
7546
  return extractManualMetadata(fm, id, manualType, relPath, body);
7296
7547
  } catch {
7297
7548
  return null;
@@ -7302,8 +7553,8 @@ function scanManualsInWorkspace(rootDir) {
7302
7553
  const seenIds = /* @__PURE__ */ new Set();
7303
7554
  const items = [];
7304
7555
  for (const relDir of manualDirs) {
7305
- const fullDir = path4__namespace.join(rootDir, relDir);
7306
- if (!fs7__namespace.existsSync(fullDir)) continue;
7556
+ const fullDir = path5__namespace.join(rootDir, relDir);
7557
+ if (!fs8__namespace.existsSync(fullDir)) continue;
7307
7558
  for (const file of walkMdFiles(fullDir)) {
7308
7559
  const item = buildManualItem(rootDir, file);
7309
7560
  if (item && !seenIds.has(item.id)) {
@@ -7326,10 +7577,10 @@ function classifyBaselineId(id, type, buckets) {
7326
7577
  }
7327
7578
  }
7328
7579
  function processBaselineFile(file, rootDir, buckets) {
7329
- const rel = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
7580
+ const rel = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
7330
7581
  if (rel.includes("/changes/") || rel.startsWith("changes/")) return;
7331
7582
  try {
7332
- const { fm } = parseMdFrontmatter(fs7__namespace.readFileSync(file, "utf-8"));
7583
+ const { fm } = parseMdFrontmatter(fs8__namespace.readFileSync(file, "utf-8"));
7333
7584
  const id = typeof fm.id === "string" ? fm.id.trim() : "";
7334
7585
  const status = typeof fm.status === "string" ? fm.status.trim().toLowerCase() : "active";
7335
7586
  if (!id || status === "retired" || status === "deprecated") return;
@@ -7350,8 +7601,8 @@ function collectBaselineTargets(rootDir) {
7350
7601
  enclaves: /* @__PURE__ */ new Set()
7351
7602
  };
7352
7603
  for (const dir of ["product", "architecture", "security", "specs"]) {
7353
- const fullDir = path4__namespace.join(rootDir, dir);
7354
- if (fs7__namespace.existsSync(fullDir)) {
7604
+ const fullDir = path5__namespace.join(rootDir, dir);
7605
+ if (fs8__namespace.existsSync(fullDir)) {
7355
7606
  scanDirectoryForBaseline(rootDir, fullDir, buckets);
7356
7607
  }
7357
7608
  }
@@ -7408,10 +7659,10 @@ function detectCurrentGitBranch(rootDir) {
7408
7659
  }
7409
7660
  }
7410
7661
  function resolveActiveChangeProfile(rootDir) {
7411
- const activeRoot = path4__namespace.join(rootDir, "specs", "changes", "active");
7412
- 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";
7413
7664
  try {
7414
- 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));
7415
7666
  const profiles = dirs.map((d) => detectProfileFromChange(d));
7416
7667
  if (profiles.includes("critical")) return "critical";
7417
7668
  if (profiles.includes("patch") && profiles.length === 1) return "patch";
@@ -7548,7 +7799,7 @@ var REQ_TYPES = /* @__PURE__ */ new Set(["requirement", "security-requirement",
7548
7799
  var REQ_CATEGORIES = /* @__PURE__ */ new Set(["functional", "quality", "constraint", "security", "safety"]);
7549
7800
  function computeSha256(content) {
7550
7801
  const normalized = content.replace(/\r\n/g, "\n");
7551
- return crypto__namespace.createHash("sha256").update(normalized, "utf-8").digest("hex");
7802
+ return crypto3__namespace.createHash("sha256").update(normalized, "utf-8").digest("hex");
7552
7803
  }
7553
7804
  function tokenizeTitle(title) {
7554
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));
@@ -7624,11 +7875,11 @@ function buildRequirementObject(content, fm, body, filePath, relFile) {
7624
7875
  }
7625
7876
  function parseRequirementFile(filePath, rootDir) {
7626
7877
  try {
7627
- const content = fs7__namespace.readFileSync(filePath, "utf-8");
7878
+ const content = fs8__namespace.readFileSync(filePath, "utf-8");
7628
7879
  const extracted = extractParsedFrontmatter(content);
7629
7880
  if (!extracted) return null;
7630
7881
  const body = content.substring(extracted.matchLen);
7631
- const relFile = path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
7882
+ const relFile = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
7632
7883
  return buildRequirementObject(content, extracted.fm, body, filePath, relFile);
7633
7884
  } catch {
7634
7885
  return null;
@@ -7640,7 +7891,7 @@ function isIgnoredScanPath(relFile) {
7640
7891
  function collectRequirements(rootDir) {
7641
7892
  const reqs = [];
7642
7893
  for (const file of walkMdFiles(rootDir)) {
7643
- const relFile = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
7894
+ const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
7644
7895
  if (isIgnoredScanPath(relFile)) continue;
7645
7896
  const req = parseRequirementFile(file, rootDir);
7646
7897
  if (req) reqs.push(req);
@@ -7774,7 +8025,7 @@ function buildDuplicatesHeader(result) {
7774
8025
  return [
7775
8026
  `# \u{1F6E1}\uFE0F Duplicate Requirements and Redundancy Audit (Shift-Left Pre-Flight)`,
7776
8027
  ``,
7777
- `> **Evaluation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
8028
+ `> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
7778
8029
  `> **Verdict:** ${isOk ? "\u2705 COMPLIANT (No blocking duplicates)" : `\u274C BLOCKED (${result.errorCount} duplicate errors detected)`}`,
7779
8030
  `> **Audited Requirements:** ${result.totalRequirements} | **Errors:** ${result.errorCount} | **Warnings:** ${result.warningCount}`,
7780
8031
  ``,
@@ -7897,7 +8148,7 @@ function buildAuditHeader(total, active, expired, invalid) {
7897
8148
  return [
7898
8149
  "# \u{1F4CB} Security Suppressions and Audit Exceptions Inventory",
7899
8150
  "",
7900
- `> **Generated at:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
8151
+ `> **Generated at:** ${resolveDeterministicDate().toISOString()}`,
7901
8152
  `> **Total Audited Suppressions:** ${total} | **Active:** ${active} | **Expired:** ${expired} | **Invalid:** ${invalid}`,
7902
8153
  "",
7903
8154
  "## 1. Executive Summary",
@@ -8128,10 +8379,10 @@ function isPathExcluded2(relPath, patterns) {
8128
8379
  function walkProjectFiles(dir, rootDir, excludedPatterns = DEFAULT_EXCLUDED_PATTERNS) {
8129
8380
  const results = [];
8130
8381
  try {
8131
- 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));
8132
8383
  for (const entry of entries) {
8133
- const fullPath = path4__namespace.join(dir, entry.name);
8134
- 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, "/");
8135
8386
  if (isPathExcluded2(relPath, excludedPatterns)) continue;
8136
8387
  if (entry.isDirectory()) {
8137
8388
  results.push(...walkProjectFiles(fullPath, rootDir, excludedPatterns));
@@ -8160,8 +8411,8 @@ function pickFirstString(...vals) {
8160
8411
  }
8161
8412
  function parseGitleaksItem(item, rootDir) {
8162
8413
  const rawFile = pickFirstString(item.File, item.file);
8163
- const filePath = path4__namespace.resolve(rootDir, rawFile);
8164
- 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, "/");
8165
8416
  const ruleId = pickFirstString(item.RuleID, item.ruleID) || "GITLEAKS-MATCH";
8166
8417
  const rawSecret = pickFirstString(item.Secret, item.secret, item.Match);
8167
8418
  const line = item.StartLine ?? item.line ?? 1;
@@ -8234,7 +8485,7 @@ function scanLineForSecrets(line, lineNumber, filePath, relPath, entropyThreshol
8234
8485
  function scanContentForSecrets(content, filePath, rootDir, entropyThreshold = 4.3, collectedSuppressions) {
8235
8486
  const violations = [];
8236
8487
  const lines = content.split(/\r?\n/);
8237
- const relPath = path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
8488
+ const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
8238
8489
  for (let i = 0; i < lines.length; i++) {
8239
8490
  violations.push(...scanLineForSecrets(lines[i], i + 1, filePath, relPath, entropyThreshold, collectedSuppressions));
8240
8491
  }
@@ -8255,7 +8506,7 @@ function scanDiffItems(diffLines, rootDir, excludePatterns, entropyThreshold) {
8255
8506
  const suppressions = [];
8256
8507
  for (const [relFile, items] of fileGroups) {
8257
8508
  if (isPathExcluded2(relFile, excludePatterns)) continue;
8258
- const absPath = path4__namespace.resolve(rootDir, relFile);
8509
+ const absPath = path5__namespace.resolve(rootDir, relFile);
8259
8510
  for (const item of items) {
8260
8511
  const lineViolations = scanLineForSecrets(item.content, item.lineNumber, absPath, relFile, entropyThreshold, suppressions);
8261
8512
  for (const v of lineViolations) {
@@ -8271,7 +8522,7 @@ function scanAllProjectFiles(rootDir, excludePatterns, entropyThreshold) {
8271
8522
  const suppressions = [];
8272
8523
  for (const file of allFiles) {
8273
8524
  try {
8274
- const content = fs7__namespace.readFileSync(file, "utf-8");
8525
+ const content = fs8__namespace.readFileSync(file, "utf-8");
8275
8526
  findings.push(...scanContentForSecrets(content, file, rootDir, entropyThreshold, suppressions));
8276
8527
  } catch {
8277
8528
  }
@@ -8328,7 +8579,7 @@ function buildReportHeader2(result) {
8328
8579
  return [
8329
8580
  `# \u{1F510} Deterministic Secrets and Credentials Audit (Gitleaks Gate)`,
8330
8581
  ``,
8331
- `> **Evaluation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
8582
+ `> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
8332
8583
  `> **Verdict:** ${isOk ? "\u2705 COMPLIANT (No exposed secrets or credentials)" : `\u274C BLOCKED (${result.findingsCount} exposed credentials detected)`}`,
8333
8584
  `> **Audited Files:** ${result.totalFilesScanned} | **Security Violations:** ${result.findingsCount}`,
8334
8585
  `> **Native Gitleaks Engine:** ${result.scannedWithGitleaks ? "\u{1F7E2} Active / Integrated" : "\u26AA Deterministic Native Scanner (@aisdlc/core)"}`,
@@ -8497,8 +8748,8 @@ function runSemgrepCli(rootDir) {
8497
8748
  const parsed = JSON.parse(output);
8498
8749
  if (Array.isArray(parsed.results)) {
8499
8750
  for (const item of parsed.results) {
8500
- const filePath = path4__namespace.resolve(rootDir, item.path);
8501
- 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, "/");
8502
8753
  violations.push({
8503
8754
  filePath,
8504
8755
  relPath,
@@ -8525,15 +8776,15 @@ function isSastExcluded(relPath, excludes) {
8525
8776
  function walkSourceFiles(dir, rootDir, excludes = DEFAULT_SAST_EXCLUDES) {
8526
8777
  const files = [];
8527
8778
  try {
8528
- 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));
8529
8780
  for (const entry of entries) {
8530
- const fullPath = path4__namespace.join(dir, entry.name);
8531
- 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, "/");
8532
8783
  if (isSastExcluded(relPath, excludes)) continue;
8533
8784
  if (entry.isDirectory()) {
8534
8785
  files.push(...walkSourceFiles(fullPath, rootDir, excludes));
8535
8786
  } else if (entry.isFile()) {
8536
- const ext = path4__namespace.extname(entry.name).toLowerCase();
8787
+ const ext = path5__namespace.extname(entry.name).toLowerCase();
8537
8788
  if (GENERAL_SOURCE_EXTENSIONS.includes(ext)) {
8538
8789
  files.push(fullPath);
8539
8790
  }
@@ -8558,11 +8809,11 @@ function checkRuleMatch(rule, line, ext, minSeverity) {
8558
8809
  }
8559
8810
  function scanFileForSast(filePath, rootDir, minSeverity = "HIGH", collectedSuppressions) {
8560
8811
  const violations = [];
8561
- const ext = path4__namespace.extname(filePath).toLowerCase();
8812
+ const ext = path5__namespace.extname(filePath).toLowerCase();
8562
8813
  try {
8563
- const content = fs7__namespace.readFileSync(filePath, "utf-8");
8814
+ const content = fs8__namespace.readFileSync(filePath, "utf-8");
8564
8815
  const lines = content.split(/\r?\n/);
8565
- const relPath = path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
8816
+ const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
8566
8817
  for (let i = 0; i < lines.length; i++) {
8567
8818
  const line = lines[i];
8568
8819
  if (checkLineSuppression(line, relPath, i + 1, collectedSuppressions)) continue;
@@ -8588,8 +8839,8 @@ function scanFileForSast(filePath, rootDir, minSeverity = "HIGH", collectedSuppr
8588
8839
  function collectSastFiles(rootDir, targetDirs) {
8589
8840
  const files = [];
8590
8841
  for (const tDir of targetDirs) {
8591
- const fullDir = path4__namespace.join(rootDir, tDir);
8592
- if (fs7__namespace.existsSync(fullDir)) {
8842
+ const fullDir = path5__namespace.join(rootDir, tDir);
8843
+ if (fs8__namespace.existsSync(fullDir)) {
8593
8844
  files.push(...walkSourceFiles(fullDir, rootDir));
8594
8845
  }
8595
8846
  }
@@ -8635,7 +8886,7 @@ function buildSastHeader(isOk, violationsCount, totalFilesScanned) {
8635
8886
  return [
8636
8887
  `# \u{1F6E1}\uFE0F Shift-Left SAST Audit (Static Application Security Testing)`,
8637
8888
  ``,
8638
- `> **Evaluation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
8889
+ `> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
8639
8890
  `> **Verdict:** ${verdict}`,
8640
8891
  `> **Audited Code Files:** ${totalFilesScanned} | **SAST Violations:** ${violationsCount}`,
8641
8892
  ``,
@@ -8824,22 +9075,31 @@ var PII_SAST_RULES = [
8824
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
8825
9076
  }
8826
9077
  ];
8827
- function loadPrivacyPolicy(rootDir, policyPath) {
8828
- const candidate = policyPath ? path4__namespace.isAbsolute(policyPath) ? policyPath : path4__namespace.join(rootDir, policyPath) : path4__namespace.join(rootDir, "privacy-policy.yaml");
8829
- if (!fs7__namespace.existsSync(candidate)) return DEFAULT_PRIVACY_POLICY;
9078
+ function parsePrivacyDoc(raw, candidate) {
9079
+ let parsed;
8830
9080
  try {
8831
- const parsed = yaml3__default.default.load(fs7__namespace.readFileSync(candidate, "utf-8"));
8832
- if (!parsed || typeof parsed !== "object") return DEFAULT_PRIVACY_POLICY;
8833
- return {
8834
- ...DEFAULT_PRIVACY_POLICY,
8835
- ...parsed,
8836
- classifications: { ...DEFAULT_PRIVACY_POLICY.classifications, ...parsed.classifications || {} },
8837
- agent_guardrails: { ...DEFAULT_PRIVACY_POLICY.agent_guardrails, ...parsed.agent_guardrails || {} },
8838
- ci_gates: { ...DEFAULT_PRIVACY_POLICY.ci_gates, ...parsed.ci_gates || {} }
8839
- };
8840
- } catch {
8841
- 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)}`);
8842
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`);
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
+ };
8843
9103
  }
8844
9104
  function isLineExemptFromPrivacyScan(line) {
8845
9105
  return line.includes("ai-sdlc:allow-pii") || line.includes("ai-sdlc:allow-sast") || line.includes("nosec");
@@ -8856,9 +9116,9 @@ function shouldApplyPiiRule(rule, policy) {
8856
9116
  function scanFileForPrivacyViolations(filePath, rootDir, policy = DEFAULT_PRIVACY_POLICY) {
8857
9117
  const violations = [];
8858
9118
  try {
8859
- const content = fs7__namespace.readFileSync(filePath, "utf-8");
9119
+ const content = fs8__namespace.readFileSync(filePath, "utf-8");
8860
9120
  const lines = content.split(/\r?\n/);
8861
- const relPath = path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
9121
+ const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
8862
9122
  for (let i = 0; i < lines.length; i++) {
8863
9123
  const line = lines[i];
8864
9124
  if (isLineExemptFromPrivacyScan(line)) continue;
@@ -8967,10 +9227,10 @@ function discoverPrivacyArtifacts(rootDir) {
8967
9227
  dpiaStatuses: /* @__PURE__ */ new Map()
8968
9228
  };
8969
9229
  for (const file of walkMdFiles(rootDir)) {
8970
- const relPath = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
9230
+ const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
8971
9231
  if (isExcludedPrivacyMd(relPath)) continue;
8972
9232
  try {
8973
- const fm = extractFrontmatter(fs7__namespace.readFileSync(file, "utf-8"));
9233
+ const fm = extractFrontmatter(fs8__namespace.readFileSync(file, "utf-8"));
8974
9234
  if (fm && fm.id) classifyPrivacyMarkdown(fm, relPath, acc);
8975
9235
  } catch {
8976
9236
  }
@@ -9015,7 +9275,7 @@ function checkAssetRopaRegistration(asset, ropaRecords, policy, fullPath) {
9015
9275
  }
9016
9276
  function auditDataAssetGovernance(asset, ropaRecords, policy, rootDir) {
9017
9277
  if (!isSensitiveClassification(asset.classification)) return [];
9018
- const fullPath = path4__namespace.join(rootDir, asset.filePath);
9278
+ const fullPath = path5__namespace.join(rootDir, asset.filePath);
9019
9279
  const v1 = checkAssetRetention(asset, policy, fullPath);
9020
9280
  const v2 = checkAssetRopaRegistration(asset, ropaRecords, policy, fullPath);
9021
9281
  return [v1, v2].filter((v) => v !== null);
@@ -9087,8 +9347,8 @@ function checkRopaDpiaApproval(ropa, dataAssets, dpiaStatuses, fullPath) {
9087
9347
  function collectSourceFilesForPrivacy(rootDir, targetDirs) {
9088
9348
  const files = [];
9089
9349
  for (const dir of targetDirs) {
9090
- const fullDir = path4__namespace.join(rootDir, dir);
9091
- if (fs7__namespace.existsSync(fullDir)) {
9350
+ const fullDir = path5__namespace.join(rootDir, dir);
9351
+ if (fs8__namespace.existsSync(fullDir)) {
9092
9352
  files.push(...walkSourceFiles(fullDir, rootDir));
9093
9353
  }
9094
9354
  }
@@ -9178,15 +9438,13 @@ function generatePrivacyReportMarkdown(result, policy = DEFAULT_PRIVACY_POLICY)
9178
9438
  function writePrivacyOutputsIfNeeded(result, options, policy, rootDir) {
9179
9439
  if (options.generateRopa) {
9180
9440
  const relRopa = options.ropaOutputPath || policy.ci_gates.output_ropa_file || "reports/ROPA_INVENTORY.md";
9181
- const fullRopa = path4__namespace.isAbsolute(relRopa) ? relRopa : path4__namespace.join(rootDir, relRopa);
9182
- fs7__namespace.mkdirSync(path4__namespace.dirname(fullRopa), { recursive: true });
9183
- 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);
9184
9443
  result.ropaOutputPath = fullRopa;
9185
9444
  }
9186
9445
  if (options.reportOutputPath) {
9187
- const fullReport = path4__namespace.isAbsolute(options.reportOutputPath) ? options.reportOutputPath : path4__namespace.join(rootDir, options.reportOutputPath);
9188
- fs7__namespace.mkdirSync(path4__namespace.dirname(fullReport), { recursive: true });
9189
- 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);
9190
9448
  result.reportOutputPath = fullReport;
9191
9449
  }
9192
9450
  }
@@ -9198,7 +9456,7 @@ function evaluateDiscoveredPrivacyGovernance(discovered, policy, rootDir) {
9198
9456
  }
9199
9457
  let dpiaRequiredCount = 0;
9200
9458
  for (const ropa of discovered.ropaRecords) {
9201
- const fullPath = path4__namespace.join(rootDir, ropa.filePath);
9459
+ const fullPath = path5__namespace.join(rootDir, ropa.filePath);
9202
9460
  violations.push(...checkRopaControlsAndSubprocessors(ropa, governedRopaIds, policy, fullPath));
9203
9461
  const dpiaCheck = checkRopaDpiaApproval(ropa, discovered.dataAssets, discovered.dpiaStatuses, fullPath);
9204
9462
  if (dpiaCheck.dpiaRequired) dpiaRequiredCount++;
@@ -9244,7 +9502,7 @@ function calculateRating(mi, cc) {
9244
9502
  return "F";
9245
9503
  }
9246
9504
  function detectLanguage(filePath) {
9247
- const ext = path4__namespace.extname(filePath).toLowerCase();
9505
+ const ext = path5__namespace.extname(filePath).toLowerCase();
9248
9506
  switch (ext) {
9249
9507
  case ".ts":
9250
9508
  return "TypeScript";
@@ -9269,7 +9527,7 @@ function detectLanguage(filePath) {
9269
9527
  }
9270
9528
  function parseJsonSummaryFile(filePath, relSource) {
9271
9529
  try {
9272
- const raw = JSON.parse(fs7__namespace.readFileSync(filePath, "utf-8"));
9530
+ const raw = JSON.parse(fs8__namespace.readFileSync(filePath, "utf-8"));
9273
9531
  const total = raw.total;
9274
9532
  if (!total || typeof total !== "object") return null;
9275
9533
  const linesPct = Number(total.lines?.pct);
@@ -9292,7 +9550,7 @@ function updateLcovCounts(line, counts) {
9292
9550
  }
9293
9551
  function parseLcovFile(filePath, relSource) {
9294
9552
  try {
9295
- const lines = fs7__namespace.readFileSync(filePath, "utf-8").split(/\r?\n/);
9553
+ const lines = fs8__namespace.readFileSync(filePath, "utf-8").split(/\r?\n/);
9296
9554
  const counts = { LF: 0, LH: 0, BRF: 0, BRH: 0, FNF: 0, FNH: 0 };
9297
9555
  for (const line of lines) {
9298
9556
  updateLcovCounts(line, counts);
@@ -9312,16 +9570,16 @@ function parseLcovFile(filePath, relSource) {
9312
9570
  function readCodeCoverageSummary(rootDir = process.cwd()) {
9313
9571
  const jsonCandidates = ["coverage/coverage-summary.json", "reports/coverage-summary.json"];
9314
9572
  for (const rel of jsonCandidates) {
9315
- const full = path4__namespace.join(rootDir, rel);
9316
- if (fs7__namespace.existsSync(full)) {
9573
+ const full = path5__namespace.join(rootDir, rel);
9574
+ if (fs8__namespace.existsSync(full)) {
9317
9575
  const parsed = parseJsonSummaryFile(full, rel);
9318
9576
  if (parsed) return parsed;
9319
9577
  }
9320
9578
  }
9321
9579
  const lcovCandidates = ["coverage/lcov.info", "lcov.info", "reports/lcov.info"];
9322
9580
  for (const rel of lcovCandidates) {
9323
- const full = path4__namespace.join(rootDir, rel);
9324
- if (fs7__namespace.existsSync(full)) {
9581
+ const full = path5__namespace.join(rootDir, rel);
9582
+ if (fs8__namespace.existsSync(full)) {
9325
9583
  const parsed = parseLcovFile(full, rel);
9326
9584
  if (parsed) return parsed;
9327
9585
  }
@@ -9406,7 +9664,7 @@ function buildReportHeader3(verdict, rating, mi, cc) {
9406
9664
  return [
9407
9665
  `# \u{1F4CA} Formal Quality and Release Gate Report (AI-SDLC)`,
9408
9666
  "",
9409
- `> **Generation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
9667
+ `> **Generation Date:** ${resolveDeterministicDate().toISOString()}`,
9410
9668
  `> **Release Gate Verdict:** ${status}`,
9411
9669
  `> **Global Rating:** **\`${rating}\`** (MI Index: ${mi}/100, Average CC: ${cc})`
9412
9670
  ];
@@ -9457,9 +9715,8 @@ function renderQualityReportMarkdown(gateResult, coverage, accumulated, averages
9457
9715
  return [...header, "", "---", "", ...table, "", "---", "", ...polyglot, ...violationsSection, "", EVALUATION_STANDARDS_MD].join("\n");
9458
9716
  }
9459
9717
  function writeQualityReportFile(rootDir, markdown) {
9460
- const reportsDir = path4__namespace.join(rootDir, "reports");
9461
- fs7__namespace.mkdirSync(reportsDir, { recursive: true });
9462
- 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);
9463
9720
  }
9464
9721
  function generateQualityReport(options = {}) {
9465
9722
  const rootDir = options.rootDir || process.cwd();
@@ -9601,9 +9858,9 @@ Flujo de Integraci\xF3n (Merges):`,
9601
9858
  };
9602
9859
  }
9603
9860
  function resolveVersionFromProposal(changeDir) {
9604
- const proposalFile = path4__namespace.join(changeDir, "proposal.md");
9605
- if (!fs7__namespace.existsSync(proposalFile)) return "1.0.0";
9606
- 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");
9607
9864
  const propFm = propContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
9608
9865
  if (!propFm) return "1.0.0";
9609
9866
  const parsedProp = yaml3__default.default.load(propFm[1]);
@@ -9620,9 +9877,9 @@ function isTaskMatchingId(currentId, cleanTaskId) {
9620
9877
  return currentLower === targetLower || currentLower.startsWith(`${targetLower}-`) || targetLower.startsWith(`${currentLower}-`);
9621
9878
  }
9622
9879
  function parseTasksFrontmatter(tasksFile) {
9623
- if (!fs7__namespace.existsSync(tasksFile)) return null;
9880
+ if (!fs8__namespace.existsSync(tasksFile)) return null;
9624
9881
  try {
9625
- const content = fs7__namespace.readFileSync(tasksFile, "utf-8");
9882
+ const content = fs8__namespace.readFileSync(tasksFile, "utf-8");
9626
9883
  const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
9627
9884
  if (!fmMatch) return null;
9628
9885
  const fm = yaml3__default.default.load(fmMatch[1]);
@@ -9644,7 +9901,7 @@ function buildTaskMatch(currentId, currentTitle, rawChangeId, changeDir, version
9644
9901
  };
9645
9902
  }
9646
9903
  function inspectChangeTasks(changeDir, entryName, cleanTaskId, availableTasks) {
9647
- const fm = parseTasksFrontmatter(path4__namespace.join(changeDir, "tasks.md"));
9904
+ const fm = parseTasksFrontmatter(path5__namespace.join(changeDir, "tasks.md"));
9648
9905
  if (!fm || !fm.tasks) return void 0;
9649
9906
  const rawChangeId = resolveRawChangeId(fm, entryName);
9650
9907
  const versionStr = resolveChangeVersion(fm, changeDir);
@@ -9665,16 +9922,16 @@ function locateTaskInActiveChanges(taskId, rootDir = process.cwd()) {
9665
9922
  if (!cleanTaskId) {
9666
9923
  return { found: false, error: "Debe especificar un identificador de tarea v\xE1lido (ej. TSK-001)." };
9667
9924
  }
9668
- const activeChangesDir = path4__namespace.join(rootDir, "specs", "changes", "active");
9669
- if (!fs7__namespace.existsSync(activeChangesDir)) {
9925
+ const activeChangesDir = path5__namespace.join(rootDir, "specs", "changes", "active");
9926
+ if (!fs8__namespace.existsSync(activeChangesDir)) {
9670
9927
  return { found: false, error: `No se encontr\xF3 el directorio de cambios activos en '${activeChangesDir}'.` };
9671
9928
  }
9672
9929
  const availableTasks = [];
9673
- const candidateDirs = fs7__namespace.readdirSync(activeChangesDir, { withFileTypes: true });
9930
+ const candidateDirs = fs8__namespace.readdirSync(activeChangesDir, { withFileTypes: true });
9674
9931
  let matchedTask;
9675
9932
  for (const entry of candidateDirs) {
9676
9933
  if (!entry.isDirectory()) continue;
9677
- const changeDir = path4__namespace.join(activeChangesDir, entry.name);
9934
+ const changeDir = path5__namespace.join(activeChangesDir, entry.name);
9678
9935
  const m = inspectChangeTasks(changeDir, entry.name, cleanTaskId, availableTasks);
9679
9936
  if (m && !matchedTask) {
9680
9937
  matchedTask = m;
@@ -9711,14 +9968,14 @@ function detectExistingFeatureBranch(branches, dirName, changeId, changeSlug) {
9711
9968
  (b) => b.startsWith("feat/") && (b.toLowerCase().includes(dirName.toLowerCase()) || b.toLowerCase().includes(changeId.toLowerCase())) && classifyBranch(b).valid
9712
9969
  );
9713
9970
  if (existing) return existing;
9714
- 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}`);
9715
9972
  return `feat/${normalizedName}`;
9716
9973
  }
9717
9974
  function buildDefaultTaskSuffix(taskId, taskTitle) {
9718
9975
  let taskSuffix = taskId.toLowerCase();
9719
9976
  const hasSlug = taskSuffix.includes("-") && taskSuffix.split("-").length > 2;
9720
9977
  if (!hasSlug && taskTitle) {
9721
- const titleSlug = slugify(taskTitle).split("-").slice(0, 4).join("-");
9978
+ const titleSlug = slugify2(taskTitle).split("-").slice(0, 4).join("-");
9722
9979
  if (titleSlug) {
9723
9980
  taskSuffix = `${taskSuffix}-${titleSlug}`;
9724
9981
  }
@@ -9738,7 +9995,7 @@ function resolveBranchHierarchy(match, rootDir = process.cwd()) {
9738
9995
  const branches = getGitBranchList(rootDir);
9739
9996
  const cleanVersion = (match.version || "1.0.0").replace(/^v/, "");
9740
9997
  const releaseBranch = detectExistingReleaseBranch(branches, cleanVersion);
9741
- const dirName = path4__namespace.basename(match.changeDir);
9998
+ const dirName = path5__namespace.basename(match.changeDir);
9742
9999
  const changeSlug = dirName.replace(/^(chg|CHG)-/i, (m) => m.toUpperCase());
9743
10000
  const featureBranch = detectExistingFeatureBranch(branches, dirName, match.changeId, changeSlug);
9744
10001
  const parentIdMatch = featureBranch.replace(/^(feat|feature)\//i, "").match(/^[a-zA-Z0-9]+-[a-zA-Z0-9]+/);
@@ -10045,13 +10302,13 @@ function getGitConfigValue(key, cwd) {
10045
10302
  }
10046
10303
  function readSessionTelemetry(cwd) {
10047
10304
  const possiblePaths = [
10048
- path4__namespace.join(cwd, ".ai-sdlc", "session-telemetry.json"),
10049
- 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")
10050
10307
  ];
10051
10308
  for (const p of possiblePaths) {
10052
- if (fs7__namespace.existsSync(p)) {
10309
+ if (fs8__namespace.existsSync(p)) {
10053
10310
  try {
10054
- const raw = fs7__namespace.readFileSync(p, "utf-8");
10311
+ const raw = fs8__namespace.readFileSync(p, "utf-8");
10055
10312
  return JSON.parse(raw);
10056
10313
  } catch {
10057
10314
  }
@@ -10271,20 +10528,20 @@ if [ -f "$TELEMETRY_FILE" ]; then
10271
10528
  fi
10272
10529
  `;
10273
10530
  function installGitHooks(rootDir = process.cwd()) {
10274
- const gitDir = path4__namespace.join(rootDir, ".git");
10275
- if (!fs7__namespace.existsSync(gitDir)) {
10531
+ const gitDir = path5__namespace.join(rootDir, ".git");
10532
+ if (!fs8__namespace.existsSync(gitDir)) {
10276
10533
  return {
10277
10534
  success: false,
10278
10535
  error: `No se encontr\xF3 el directorio Git en '${gitDir}'.`
10279
10536
  };
10280
10537
  }
10281
- const hooksDir = path4__namespace.join(gitDir, "hooks");
10282
- if (!fs7__namespace.existsSync(hooksDir)) {
10283
- 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 });
10284
10541
  }
10285
- const hookPath = path4__namespace.join(hooksDir, "prepare-commit-msg");
10542
+ const hookPath = path5__namespace.join(hooksDir, "prepare-commit-msg");
10286
10543
  try {
10287
- 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 });
10288
10545
  return {
10289
10546
  success: true,
10290
10547
  hookPath
@@ -10625,16 +10882,16 @@ function formatReleaseKpiMarkdown(report) {
10625
10882
  ].join("\n");
10626
10883
  }
10627
10884
  function writeReleaseKpiReport(report, options = {}) {
10628
- const root = options.outputDir || (options.rootDir ? path4__namespace.join(options.rootDir, "reports", "releases") : path4__namespace.join(process.cwd(), "reports", "releases"));
10629
- if (!fs7__namespace.existsSync(root)) {
10630
- 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 });
10631
10888
  }
10632
10889
  const cleanBranchName = report.releaseBranch.replace(/[^a-zA-Z0-9.-]/g, "_");
10633
- const mdPath = path4__namespace.join(root, `RELEASE_KPIS_${cleanBranchName}.md`);
10634
- 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`);
10635
10892
  const mdContent = formatReleaseKpiMarkdown(report);
10636
- fs7__namespace.writeFileSync(mdPath, mdContent, "utf-8");
10637
- 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");
10638
10895
  return { markdownPath: mdPath, jsonPath };
10639
10896
  }
10640
10897
  var cachedScript = null;
@@ -10643,19 +10900,19 @@ function resolveCytoscapePath() {
10643
10900
  try {
10644
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)));
10645
10902
  const entryPath = req.resolve("cytoscape");
10646
- const dir = path4__namespace.dirname(entryPath);
10647
- const minPath = path4__namespace.join(dir, "cytoscape.min.js");
10648
- 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)) {
10649
10906
  return minPath;
10650
10907
  }
10651
10908
  } catch {
10652
10909
  }
10653
10910
  const searchDirs = [
10654
- path4__namespace.join(process.cwd(), "node_modules", "cytoscape", "dist", "cytoscape.min.js"),
10655
- 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")
10656
10913
  ];
10657
10914
  for (const candidate of searchDirs) {
10658
- if (fs7__namespace.existsSync(candidate)) {
10915
+ if (fs8__namespace.existsSync(candidate)) {
10659
10916
  return candidate;
10660
10917
  }
10661
10918
  }
@@ -10668,7 +10925,7 @@ function getCytoscapeScript() {
10668
10925
  const scriptPath = resolveCytoscapePath();
10669
10926
  if (scriptPath) {
10670
10927
  try {
10671
- cachedScript = fs7__namespace.readFileSync(scriptPath, "utf-8");
10928
+ cachedScript = fs8__namespace.readFileSync(scriptPath, "utf-8");
10672
10929
  return cachedScript;
10673
10930
  } catch {
10674
10931
  }
@@ -10768,7 +11025,7 @@ function extractFrontmatter2(fileContent) {
10768
11025
  }
10769
11026
  }
10770
11027
  function toScannedArtifact(filePath, relPath) {
10771
- const fileContent = fs7__namespace.readFileSync(filePath, "utf-8");
11028
+ const fileContent = fs8__namespace.readFileSync(filePath, "utf-8");
10772
11029
  const fm = extractFrontmatter2(fileContent);
10773
11030
  const id = typeof fm?.id === "string" ? fm.id.trim() : "";
10774
11031
  if (!id) return null;
@@ -10780,12 +11037,12 @@ function toScannedArtifact(filePath, relPath) {
10780
11037
  return { id, type: classified.type, title, layer: classified.layer, filePath: relPath, content, frontmatter: fm };
10781
11038
  }
10782
11039
  function parseAndCatalogArtifactFile(rootDir, filePath, scannedMap, archViewByBasename) {
10783
- const relPath = path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
11040
+ const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
10784
11041
  if (relPath.includes("/changes/") || relPath.startsWith("changes/")) return;
10785
11042
  const scannedArt = toScannedArtifact(filePath, relPath);
10786
11043
  if (!scannedArt) return;
10787
11044
  if (scannedArt.layer === "architecture" && scannedArt.type === "architecture") {
10788
- archViewByBasename.set(path4__namespace.basename(relPath), scannedArt);
11045
+ archViewByBasename.set(path5__namespace.basename(relPath), scannedArt);
10789
11046
  } else {
10790
11047
  scannedMap.set(scannedArt.id, scannedArt);
10791
11048
  }
@@ -10795,8 +11052,8 @@ function scanWorkspaceArtifacts(rootDir) {
10795
11052
  const archViewByBasename = /* @__PURE__ */ new Map();
10796
11053
  const artifactDirs = ["product", "architecture", "specs", "security", "privacy"];
10797
11054
  for (const dirName of artifactDirs) {
10798
- const fullDirPath = path4__namespace.join(rootDir, dirName);
10799
- if (fs7__namespace.existsSync(fullDirPath)) {
11055
+ const fullDirPath = path5__namespace.join(rootDir, dirName);
11056
+ if (fs8__namespace.existsSync(fullDirPath)) {
10800
11057
  for (const f of walkMdFiles(fullDirPath)) {
10801
11058
  parseAndCatalogArtifactFile(rootDir, f, scannedArtifactsMap, archViewByBasename);
10802
11059
  }
@@ -10856,9 +11113,9 @@ function addTraceArchEdges(r, status, archViewByBasename, nodeMap, addEdge) {
10856
11113
  }
10857
11114
  }
10858
11115
  function loadTestContentPreview(filePath) {
10859
- if (!fs7__namespace.existsSync(filePath)) return void 0;
11116
+ if (!fs8__namespace.existsSync(filePath)) return void 0;
10860
11117
  try {
10861
- const testContent = fs7__namespace.readFileSync(filePath, "utf-8");
11118
+ const testContent = fs8__namespace.readFileSync(filePath, "utf-8");
10862
11119
  const lang = filePath.endsWith(".feature") ? "gherkin" : "typescript";
10863
11120
  const preview = testContent.substring(0, 1500) + (testContent.length > 1500 ? "\n... (truncated)" : "");
10864
11121
  return "```" + lang + "\n" + preview + "\n```";
@@ -10868,10 +11125,10 @@ function loadTestContentPreview(filePath) {
10868
11125
  }
10869
11126
  function ensureTestNode(t, testId, rootDir, nodeMap) {
10870
11127
  if (nodeMap.has(testId)) return;
10871
- 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);
10872
11129
  nodeMap.set(testId, {
10873
11130
  id: testId,
10874
- label: path4__namespace.basename(t),
11131
+ label: path5__namespace.basename(t),
10875
11132
  title: t,
10876
11133
  type: t.endsWith(".feature") ? "bdd-feature" : "code-test",
10877
11134
  layer: "test",
@@ -13016,9 +13273,10 @@ ${manualsTab}
13016
13273
  ${kpisTab}
13017
13274
  </main>`;
13018
13275
  }
13019
- 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) {
13020
13277
  const head = renderDashboardHead(title);
13021
- const header = renderHeaderHtml(graphData.issueCount, (/* @__PURE__ */ new Date()).toISOString());
13278
+ const effectiveDate = generationDate || resolveDeterministicDate().toISOString();
13279
+ const header = renderHeaderHtml(graphData.issueCount, effectiveDate);
13022
13280
  const nav = renderNavTabsHtml();
13023
13281
  const main = renderDashboardMain(metricsData);
13024
13282
  const sidebar = renderTaskSidebarHtml();
@@ -13048,9 +13306,9 @@ var IGNORED_SCAN_DIRS = /* @__PURE__ */ new Set([
13048
13306
  ".agent"
13049
13307
  ]);
13050
13308
  function readKpiFilesInDir(dir) {
13051
- if (!fs7__namespace.existsSync(dir)) return [];
13309
+ if (!fs8__namespace.existsSync(dir)) return [];
13052
13310
  try {
13053
- 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));
13054
13312
  } catch {
13055
13313
  return [];
13056
13314
  }
@@ -13058,11 +13316,11 @@ function readKpiFilesInDir(dir) {
13058
13316
  function scanSubdirKpiFiles(dir, depth, acc) {
13059
13317
  if (depth > 4) return;
13060
13318
  try {
13061
- const entries = fs7__namespace.readdirSync(dir, { withFileTypes: true });
13319
+ const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
13062
13320
  for (const entry of entries) {
13063
13321
  if (!entry.isDirectory() || IGNORED_SCAN_DIRS.has(entry.name)) continue;
13064
- const sub = path4__namespace.join(dir, entry.name);
13065
- 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") {
13066
13324
  acc.push(...readKpiFilesInDir(sub));
13067
13325
  } else {
13068
13326
  scanSubdirKpiFiles(sub, depth + 1, acc);
@@ -13072,7 +13330,7 @@ function scanSubdirKpiFiles(dir, depth, acc) {
13072
13330
  }
13073
13331
  }
13074
13332
  function findKpiJsonFiles(rootDir) {
13075
- const primaryFiles = readKpiFilesInDir(path4__namespace.join(rootDir, "reports", "releases"));
13333
+ const primaryFiles = readKpiFilesInDir(path5__namespace.join(rootDir, "reports", "releases"));
13076
13334
  if (primaryFiles.length > 0) return primaryFiles;
13077
13335
  const found = [];
13078
13336
  scanSubdirKpiFiles(rootDir, 0, found);
@@ -13083,7 +13341,7 @@ function collectHistoricalKpis(rootDir) {
13083
13341
  const kpiFiles = findKpiJsonFiles(rootDir);
13084
13342
  for (const fullPath of kpiFiles) {
13085
13343
  try {
13086
- const content = fs7__namespace.readFileSync(fullPath, "utf-8");
13344
+ const content = fs8__namespace.readFileSync(fullPath, "utf-8");
13087
13345
  const report = JSON.parse(content);
13088
13346
  if (!items.some((it) => it.id === report.releaseBranch && it.timestamp === report.generatedAt)) {
13089
13347
  items.push({
@@ -13289,15 +13547,11 @@ function computeGateAverages(gateResults) {
13289
13547
  }
13290
13548
  function generateDashboardReport(options = {}) {
13291
13549
  const rootDir = options.rootDir || process.cwd();
13292
- const outputPath = options.outputPath || path4__namespace.join(rootDir, "reports", "dashboard.html");
13550
+ const outputPath = options.outputPath || path5__namespace.join(rootDir, "reports", "dashboard.html");
13293
13551
  const graphData = buildGraphElements(rootDir);
13294
13552
  const metricsData = collectDashboardMetrics(rootDir);
13295
- const html = renderDashboardHtml(graphData, metricsData, options.title);
13296
- const outDir = path4__namespace.dirname(outputPath);
13297
- if (!fs7__namespace.existsSync(outDir)) {
13298
- fs7__namespace.mkdirSync(outDir, { recursive: true });
13299
- }
13300
- fs7__namespace.writeFileSync(outputPath, html, "utf-8");
13553
+ const html = renderDashboardHtml(graphData, metricsData, options.title, resolveDeterministicDate(rootDir).toISOString());
13554
+ writeReportFileIfChanged(outputPath, html);
13301
13555
  const { bddCoverageRatio, traceabilityRatio } = computeSummaryRatios(metricsData);
13302
13556
  const { avgMi, avgCc } = computeGateAverages(metricsData.gate.results);
13303
13557
  return {
@@ -13323,6 +13577,8 @@ function generateDashboardReport(options = {}) {
13323
13577
  html
13324
13578
  };
13325
13579
  }
13580
+
13581
+ // src/reporters/sarif.ts
13326
13582
  var SARIF_SCHEMA_2_1_0 = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json";
13327
13583
  var SARIF_VERSION = "2.1.0";
13328
13584
  function normalizeUri(filePath) {
@@ -13470,11 +13726,7 @@ function serializeSarifDocument(doc, pretty = true) {
13470
13726
  return JSON.stringify(doc, null, pretty ? 2 : 0);
13471
13727
  }
13472
13728
  function writeSecuritySarifReport(outputPath, doc, pretty = true) {
13473
- const dir = path4__namespace.dirname(outputPath);
13474
- if (!fs7__namespace.existsSync(dir)) {
13475
- fs7__namespace.mkdirSync(dir, { recursive: true });
13476
- }
13477
- fs7__namespace.writeFileSync(outputPath, serializeSarifDocument(doc, pretty), "utf-8");
13729
+ writeReportFileIfChanged(outputPath, serializeSarifDocument(doc, pretty));
13478
13730
  }
13479
13731
 
13480
13732
  // src/init/agent-templates.ts
@@ -13567,6 +13819,7 @@ var SPECIALIZED_AGENT_TEMPLATES = [
13567
13819
  - Document critical technological decisions using immutable \`ADR-*\` records conforming to \`schemas/architecture/adr.schema.json\`.
13568
13820
  - Validate that architectural choices comply with legal constraints in \`license-policy.yaml\` and data protection constraints in \`privacy-policy.yaml\`.
13569
13821
  - **Technical Security & Privacy Feedback Loop**: If architectural decisions (databases, queues, distributed caching, third-party APIs, LLM providers, authentication schemes) introduce new attack vectors or PII data flows, emit a return handoff to \`agent-threat-modeler\` (which chains to \`agent-privacy-officer\`) for technical modeling prior to the testing phase.
13822
+ - **Mandatory Observability Component (\`CMP-OBSERVABILITY\`)**: In every system architecture modeled under arc42/NAF v4, the system architect MUST define an explicit building block \`CMP-OBSERVABILITY\` (Level 2/3) in Section 5 alongside Section 8 Cross-Cutting Concepts (\`ARCH-CROSS-*\`) and an immutable ADR for Runtime Observability, standardizing on: (1) Structured JSON logging with configurable granularity (\`LOG_LEVEL\`), (2) Distributed tracing via OpenTelemetry with W3C Trace Context (\`traceparent\`), and (3) Operational metrics (RED: Rate, Errors, Duration) in Prometheus / OpenMetrics format for Grafana scraping, satisfying \`QR-OBS-*\`.
13570
13823
  - If autonomy is $\\ge$ \`HUMAN_REVIEW_PLAN\`, conclude by emitting the Workflow Handoff block (\`templates/workflow/agent-handoff.template.md\`) suggesting \`agent-qa-engineer\` (or \`agent-threat-modeler\` if technical security/privacy loop is required) and opening the Human Action Window. In \`AUTONOMOUS\` mode, omit interactive handoff.`
13571
13824
  },
13572
13825
  {
@@ -13626,9 +13879,12 @@ Execute every implementation task following this strict sequential order:
13626
13879
  - **Zero Fake-Wiring Guardrail**: Strictly forbidden from marking a task \`COMPLETED\` with hardcoded \`mockData\` arrays in production views, \`setTimeout\` simulating network calls, or empty UI event handlers (\`onClick={() => {}}\`, \`onSubmit={() => {}}\`). Frontend components must wire directly to the shared contract and real Backend endpoints.
13627
13880
  - **Mandatory 4 UI States Matrix**: Every data-driven UI component or screen must explicitly implement all 4 usability states: \`Loading\` (spinner/skeleton + disabled submit), \`Empty\` (clear message + CTA), \`Error\` (visible error recovery banner, never silent failure), and \`Nominal/Success\` (accessible semantic DOM + keyboard navigation).
13628
13881
  - **Live Runtime Smoke Verification**: For tasks touching UI or UI-to-Backend integration, unit tests alone are insufficient. Before emitting handoff, boot the real Backend and Frontend servers and verify (via Playwright E2E or Chrome DevTools MCP) that browser \`console.error\` and \`pageerror\` counts are \`0\` and API calls return real \`2xx\` responses without CORS or schema mismatches.
13882
+ - **Mandatory Structured Logging & Observability**: Never emit unstructured console logs (\`console.log\`) in production code (\`no_console_logs_in_prod\`). Integrate the structured logger (\`ILogger\`) with configurable \`LOG_LEVEL\`, propagate W3C distributed trace context (\`trace_id\`), and record RED operational metrics compatible with Prometheus and Grafana.
13629
13883
  - Before adding any external dependency, verify its SPDX license is permitted in \`license-policy.yaml\`.
13630
13884
  - Respect non-negotiable \`quality-policy.yaml\` thresholds: Cyclomatic Complexity $\\le 10$, Cognitive Complexity $\\le 15$, Maintainability Index $\\ge 50$, Function Length $\\le 40$ lines.
13631
13885
  - Run unified pre-flight with auto-fix (\`pnpm run check:fix\`) and full verification (\`pnpm run verify:all\`).
13886
+ - **Mandatory Regression Verification**: Never declare build, typecheck, or bug fixes complete without executing the relevant automated test suite (\`pnpm test\`, \`dotnet test\`) and confirming zero regressions.
13887
+ - **Global Styles Protection**: When touching or refactoring frontend views, verify that global stylesheet/asset imports remain intact and that layouts suffer no visual disruption.
13632
13888
  - Upon completing green implementation, suggest handoff to \`agent-expert-user\` for Post-Development Functional Validation prior to security and privacy audit.`
13633
13889
  },
13634
13890
  {
@@ -14056,7 +14312,7 @@ This file defines the operational rules and specialized role mappings for Google
14056
14312
  - FORBIDDEN to include production code or anticipate implementations.
14057
14313
  - **Agreed Project Technology**: Always use the testing frameworks, E2E/UI runners, and technology stack agreed upon for the project (if any), without requiring the user to specify it in the prompt.
14058
14314
  - **Mandatory E2E & UI Testing**: Never forget or omit End-to-End (E2E) and User Interface (UI) tests.
14059
- - **Per-Interactive-Element Coverage**: Create specific tests for every UI element the user can interact with, covering Happy Path, Sad Path, and Boundary Values for each one.
14315
+ - **Per-Interactive-Element Coverage**: Create specific tests for every UI element the user can interact with, using external tools such as Playwright or FlatUI (or the agreed project testing technology), covering Happy Path, Sad Path, and Boundary Values for each one.
14060
14316
  - Every \`FR-*\` must have at least nominal + boundary + out-of-range scenarios; missing these blocks handoff to \`agent-developer\`.
14061
14317
  - Mandatory tags: \`@<FR-ID> @automated @regression\`.
14062
14318
  - Run \`pnpm run verify:testing\` before emitting handoff.
@@ -14069,6 +14325,8 @@ This file defines the operational rules and specialized role mappings for Google
14069
14325
  - Make tests delivered by \`agent-qa-engineer\` pass green without altering them to accommodate code.
14070
14326
  - Respect \`quality-policy.yaml\` thresholds: CC $\\le 10$, Cognitive $\\le 15$, MI $\\ge 50$, LOC $\\le 40$.
14071
14327
  - Run pre-flight with auto-fix: \`pnpm run check:fix\` and full verification: \`pnpm run verify:all\`.
14328
+ - **Mandatory Regression Verification**: Never declare build, typecheck, or bug fixes complete without executing the relevant automated test suite (\`pnpm test\`, \`dotnet test\`) and confirming zero regressions.
14329
+ - **Global Styles Protection**: When touching or refactoring frontend views, verify that global stylesheet/asset imports remain intact and that layouts suffer no visual disruption.
14072
14330
  - Upon completing green implementation, suggest handoff to \`agent-expert-user\` for post-development functional validation prior to security and privacy audit.
14073
14331
 
14074
14332
  ### 7. \`agent-security-auditor\` (Adversarial Code Auditor)
@@ -14241,6 +14499,8 @@ Canonical Reference: [\`process/09_agent_protocols.md\`](process/09_agent_protoc
14241
14499
  - Apply Test-Driven Development: generate unit tests before or alongside component logic.
14242
14500
  - Tag tests linked to requirements with header comments citing the ID (\`FR-*\`, \`SEC-REQ-*\`, or \`PRIV-REQ-*\`) to enable reverse traceability.
14243
14501
  - Suppressing types (\`any\`), linter rules (\`eslint-disable\`), or unit tests to force pipeline passage is prohibited.
14502
+ - **Mandatory Regression Verification**: Never declare build, typecheck, or bug fixes complete without executing the relevant automated test suite (\`pnpm test\`, \`dotnet test\`) and confirming zero regressions.
14503
+ - **Global Styles Protection**: When touching or refactoring frontend views, verify that global stylesheet/asset imports remain intact and that layouts suffer no visual disruption.
14244
14504
 
14245
14505
  ## 3. Open Source License Policy (\`license-policy.yaml\`)
14246
14506
  - Before adding any dependency to \`package.json\`, verify its SPDX identifier.
@@ -14315,11 +14575,11 @@ npx tsx packages/cli/src/index.ts sdd integrate --change <chg-id>
14315
14575
 
14316
14576
  ### 5. \`agent-qa-engineer\` (QA Engineer and SDET)
14317
14577
  - **Mission**: Translate approved requirements (\`FR-*\`, \`SEC-REQ-*\`, \`PRIV-REQ-*\`, \`QR-*\`) into comprehensive FAILING (red) BDD/Gherkin, End-to-End (E2E), UI, and unit test suites, before \`agent-developer\` writes production code.
14318
- - **Guardrails**: Always use the agreed project technology/testing stack (if any). Never omit End-to-End (E2E) and UI tests. Create specific tests for every interactive UI element covering Happy Path, Sad Path, and Boundary Values. FORBIDDEN to include production code. Mandatory tags: \`@<FR-ID> @automated @regression\`.
14578
+ - **Guardrails**: Always use the agreed project technology/testing stack (if any). Never omit End-to-End (E2E) and UI tests. Create specific tests for every interactive UI element using external tools such as Playwright or FlatUI (or the agreed project testing technology), covering Happy Path, Sad Path, and Boundary Values. FORBIDDEN to include production code. Mandatory tags: \`@<FR-ID> @automated @regression\`.
14319
14579
 
14320
14580
  ### 6. \`agent-developer\` (Software Developer)
14321
14581
  - **Mission**: Implement atomic tasks from SDD specifications (\`tasks.md\`) with clean, strictly typed code and thorough tests.
14322
- - **Directives**: Execute the mandatory 4-step order: (1) Modular & Hexagonal Architecture (Ports & Adapters) definition/redefinition, (2) Inter-module interfaces first, (3) Component implementation, (4) Per-function unit tests (Happy Path, Sad Path, Boundary Values, Unexpected/Null/Empty values). Respect the autonomy mode. Make tests delivered by \`agent-qa-engineer\` pass green. Run \`pnpm run check:fix\` and \`pnpm run verify:all\`.
14582
+ - **Directives**: Execute the mandatory 4-step order: (1) Modular & Hexagonal Architecture (Ports & Adapters) definition/redefinition, (2) Inter-module interfaces first, (3) Component implementation, (4) Per-function unit tests (Happy Path, Sad Path, Boundary Values, Unexpected/Null/Empty values). Respect the autonomy mode. Make tests delivered by \`agent-qa-engineer\` pass green. Run \`pnpm run check:fix\` and \`pnpm run verify:all\`. Enforce Mandatory Regression Verification (\`pnpm test\`) with zero regressions before declaring completion, and enforce Global Styles Protection on frontend views.
14323
14583
 
14324
14584
  ### 7. \`agent-security-auditor\` (Adversarial Code Auditor)
14325
14585
  - **Mission**: Examine Pull Requests for business logic vulnerabilities, injection vectors, secret leaks, and authorization flaws as part of the Pre-Merge Audit Triad.
@@ -16392,24 +16652,32 @@ function collectAgentFiles(targets) {
16392
16652
  appendMcpFiles(dirs, files);
16393
16653
  return { dirs, files };
16394
16654
  }
16395
- function writeProjectFiles(destDir, files, dryRun) {
16396
- const filesCreated = [];
16655
+ function writeSingleFile(fullFilePath, content, relPath, opts, results) {
16656
+ const exists = fs8__namespace.existsSync(fullFilePath);
16657
+ if (opts.dryRun) {
16658
+ if (exists && opts.force) results.filesUpdated.push(relPath);
16659
+ else if (!exists) results.filesCreated.push(relPath);
16660
+ return;
16661
+ }
16662
+ const parentDir = path5__namespace.dirname(fullFilePath);
16663
+ if (!fs8__namespace.existsSync(parentDir)) {
16664
+ fs8__namespace.mkdirSync(parentDir, { recursive: true });
16665
+ }
16666
+ if (!exists) {
16667
+ fs8__namespace.writeFileSync(fullFilePath, content, "utf-8");
16668
+ results.filesCreated.push(relPath);
16669
+ } else if (opts.force) {
16670
+ fs8__namespace.writeFileSync(fullFilePath, content, "utf-8");
16671
+ results.filesUpdated.push(relPath);
16672
+ }
16673
+ }
16674
+ function writeProjectFiles(destDir, files, dryRun, force) {
16675
+ const results = { filesCreated: [], filesUpdated: [] };
16397
16676
  for (const f of files) {
16398
- const fullFilePath = path4__namespace.join(destDir, f.relPath);
16399
- const parentDir = path4__namespace.dirname(fullFilePath);
16400
- if (!dryRun) {
16401
- if (!fs7__namespace.existsSync(parentDir)) {
16402
- fs7__namespace.mkdirSync(parentDir, { recursive: true });
16403
- }
16404
- if (!fs7__namespace.existsSync(fullFilePath)) {
16405
- fs7__namespace.writeFileSync(fullFilePath, f.content, "utf-8");
16406
- filesCreated.push(f.relPath);
16407
- }
16408
- } else {
16409
- filesCreated.push(f.relPath);
16410
- }
16677
+ const fullFilePath = path5__namespace.join(destDir, f.relPath);
16678
+ writeSingleFile(fullFilePath, f.content, f.relPath, { dryRun, force }, results);
16411
16679
  }
16412
- return filesCreated;
16680
+ return results;
16413
16681
  }
16414
16682
  function validateCiProvider(ci) {
16415
16683
  if (!ci) return { valid: true };
@@ -16486,7 +16754,7 @@ function getCiWorkflowFiles(ciProvider) {
16486
16754
  function createDirectoriesOnDisk(destDir, directories, dryRun) {
16487
16755
  const created = [];
16488
16756
  for (const d of directories) {
16489
- if (!dryRun) fs7__namespace.mkdirSync(path4__namespace.join(destDir, d), { recursive: true });
16757
+ if (!dryRun) fs8__namespace.mkdirSync(path5__namespace.join(destDir, d), { recursive: true });
16490
16758
  created.push(d);
16491
16759
  }
16492
16760
  return created;
@@ -16501,7 +16769,7 @@ function collectStarterFiles(ciProvider, agentFiles = [], archGranularity = "min
16501
16769
  ...getCiWorkflowFiles(ciProvider),
16502
16770
  ...agentFiles,
16503
16771
  ...getArchitectureTemplates(archGranularity).map((t) => ({
16504
- relPath: path4__namespace.join("templates", "architecture", t.filename).replace(/\\/g, "/"),
16772
+ relPath: path5__namespace.join("templates", "architecture", t.filename).replace(/\\/g, "/"),
16505
16773
  content: t.content
16506
16774
  }))
16507
16775
  ];
@@ -16532,15 +16800,15 @@ function injectMissingScripts(scripts) {
16532
16800
  return modified;
16533
16801
  }
16534
16802
  function updateConsumerPackageJson(destDir, dryRun) {
16535
- const pkgPath = path4__namespace.join(destDir, "package.json");
16536
- if (!fs7__namespace.existsSync(pkgPath)) return false;
16803
+ const pkgPath = path5__namespace.join(destDir, "package.json");
16804
+ if (!fs8__namespace.existsSync(pkgPath)) return false;
16537
16805
  try {
16538
- const pkg = JSON.parse(fs7__namespace.readFileSync(pkgPath, "utf-8"));
16806
+ const pkg = JSON.parse(fs8__namespace.readFileSync(pkgPath, "utf-8"));
16539
16807
  if (!pkg || typeof pkg !== "object") return false;
16540
16808
  pkg.scripts = pkg.scripts || {};
16541
16809
  const modified = injectMissingScripts(pkg.scripts);
16542
16810
  if (modified && !dryRun) {
16543
- fs7__namespace.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
16811
+ fs8__namespace.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
16544
16812
  }
16545
16813
  return modified;
16546
16814
  } catch {
@@ -16548,7 +16816,7 @@ function updateConsumerPackageJson(destDir, dryRun) {
16548
16816
  }
16549
16817
  }
16550
16818
  function tryInstallGitHook(destDir, dryRun) {
16551
- if (!dryRun && fs7__namespace.existsSync(path4__namespace.join(destDir, ".git"))) {
16819
+ if (!dryRun && fs8__namespace.existsSync(path5__namespace.join(destDir, ".git"))) {
16552
16820
  return installGitHooks(destDir).success;
16553
16821
  }
16554
16822
  return false;
@@ -16564,6 +16832,7 @@ function buildInitError(targetDir, error) {
16564
16832
  targetDir,
16565
16833
  directoriesCreated: [],
16566
16834
  filesCreated: [],
16835
+ filesUpdated: [],
16567
16836
  agentsConfigured: [],
16568
16837
  gitHookInstalled: false,
16569
16838
  error
@@ -16576,12 +16845,13 @@ function mergeDirectories(base, extra) {
16576
16845
  }
16577
16846
  return result;
16578
16847
  }
16579
- function buildInitSuccessResult(destDir, directoriesCreated, filesCreated, ciProvider, agentsConfigured, architectureGranularity, gitHookInstalled) {
16848
+ function buildInitSuccessResult(destDir, directoriesCreated, filesCreated, filesUpdated, ciProvider, agentsConfigured, architectureGranularity, gitHookInstalled) {
16580
16849
  return {
16581
16850
  success: true,
16582
16851
  targetDir: destDir,
16583
16852
  directoriesCreated,
16584
16853
  filesCreated,
16854
+ filesUpdated,
16585
16855
  ciProvider,
16586
16856
  agentsConfigured,
16587
16857
  architectureGranularity,
@@ -16595,24 +16865,32 @@ function validateInitOptions(options) {
16595
16865
  if (!archRes.valid) return { valid: false, archGranularity: "minimal", error: archRes.error };
16596
16866
  return { valid: true, ciProvider: ciRes.provider, archGranularity: archRes.granularity };
16597
16867
  }
16868
+ function assembleScaffoldingPlan(ciProvider, agents, archGranularity) {
16869
+ const agentTargets = resolveAgentTargets(agents);
16870
+ const { dirs: agentDirs, files: agentFiles } = collectAgentFiles(agentTargets);
16871
+ const directories = mergeDirectories(getBaseDirectories(ciProvider), agentDirs);
16872
+ const starterFiles = collectStarterFiles(ciProvider, agentFiles, archGranularity);
16873
+ return { agentTargets, directories, starterFiles };
16874
+ }
16598
16875
  function initProject(options = {}) {
16599
- const baseDir = options.rootDir || process.cwd();
16600
- const destDir = path4__namespace.resolve(baseDir, options.targetDir || ".");
16876
+ const destDir = path5__namespace.resolve(options.rootDir || process.cwd(), options.targetDir || ".");
16601
16877
  const validated = validateInitOptions(options);
16602
16878
  if (!validated.valid) return buildInitError(destDir, validated.error);
16603
- const agentTargets = resolveAgentTargets(options.agents);
16604
- const { dirs: agentDirs, files: agentFiles } = collectAgentFiles(agentTargets);
16605
- const directories = mergeDirectories(getBaseDirectories(validated.ciProvider), agentDirs);
16606
- const directoriesCreated = createDirectoriesOnDisk(destDir, directories, options.dryRun);
16607
- const starterFiles = collectStarterFiles(validated.ciProvider, agentFiles, validated.archGranularity);
16608
- const filesCreated = writeProjectFiles(destDir, starterFiles, options.dryRun);
16879
+ const { agentTargets, directories, starterFiles } = assembleScaffoldingPlan(
16880
+ validated.ciProvider,
16881
+ options.agents,
16882
+ validated.archGranularity
16883
+ );
16884
+ const dirsCreated = createDirectoriesOnDisk(destDir, directories, options.dryRun);
16885
+ const { filesCreated, filesUpdated } = writeProjectFiles(destDir, starterFiles, options.dryRun, options.force);
16609
16886
  if (updateConsumerPackageJson(destDir, options.dryRun)) {
16610
16887
  filesCreated.push("package.json");
16611
16888
  }
16612
16889
  return buildInitSuccessResult(
16613
16890
  destDir,
16614
- directoriesCreated,
16891
+ dirsCreated,
16615
16892
  filesCreated,
16893
+ filesUpdated,
16616
16894
  validated.ciProvider,
16617
16895
  formatAgentsConfigured(agentTargets),
16618
16896
  validated.archGranularity,
@@ -16681,33 +16959,40 @@ function normalizeParsedRule(raw, idx) {
16681
16959
  enabled: raw.enabled !== false
16682
16960
  };
16683
16961
  }
16962
+ function parseDlpDoc(yamlContent) {
16963
+ let doc;
16964
+ try {
16965
+ doc = yaml3__default.default.load(yamlContent);
16966
+ } catch (err) {
16967
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in dlp-policy: ${err instanceof Error ? err.message : String(err)}`);
16968
+ }
16969
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
16970
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid dlp-policy structure: root must be a YAML mapping`);
16971
+ }
16972
+ const record = doc;
16973
+ if (record.rules !== void 0 && !Array.isArray(record.rules)) {
16974
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid dlp-policy: rules must be an array`);
16975
+ }
16976
+ return record;
16977
+ }
16684
16978
  function parseDlpPolicy(yamlContent) {
16685
16979
  const fallback = createDefaultDlpPolicy();
16686
16980
  if (!yamlContent || !yamlContent.trim()) return fallback;
16687
- try {
16688
- const doc = yaml3__default.default.load(yamlContent);
16689
- if (!doc || typeof doc !== "object") return fallback;
16690
- const rawRules = Array.isArray(doc.rules) ? doc.rules : [];
16691
- const parsedRules = rawRules.map((r, i) => normalizeParsedRule(r, i)).filter((r) => r !== null);
16692
- return {
16693
- version: typeof doc.version === "string" ? doc.version : "1.0",
16694
- enabled: doc.enabled !== false,
16695
- zero_data_retention_required: Boolean(doc.zero_data_retention_required ?? true),
16696
- rules: parsedRules.length > 0 ? parsedRules : fallback.rules
16697
- };
16698
- } catch {
16699
- return fallback;
16700
- }
16981
+ const record = parseDlpDoc(yamlContent);
16982
+ const rawRules = Array.isArray(record.rules) ? record.rules : [];
16983
+ const parsedRules = rawRules.map((r, i) => normalizeParsedRule(r, i)).filter((r) => r !== null);
16984
+ return {
16985
+ version: typeof record.version === "string" ? record.version : "1.0",
16986
+ enabled: record.enabled !== false,
16987
+ zero_data_retention_required: Boolean(record.zero_data_retention_required ?? true),
16988
+ rules: parsedRules.length > 0 ? parsedRules : fallback.rules
16989
+ };
16701
16990
  }
16702
16991
  function loadDlpPolicy(rootDir, customPolicyPath) {
16703
16992
  const base = rootDir || process.cwd();
16704
- const filePath = customPolicyPath ? path4__namespace.isAbsolute(customPolicyPath) ? customPolicyPath : path4__namespace.join(base, customPolicyPath) : path4__namespace.join(base, "dlp-policy.yaml");
16705
- if (!fs7__namespace.existsSync(filePath)) return createDefaultDlpPolicy();
16706
- try {
16707
- return parseDlpPolicy(fs7__namespace.readFileSync(filePath, "utf-8"));
16708
- } catch {
16709
- return createDefaultDlpPolicy();
16710
- }
16993
+ const filePath = customPolicyPath ? path5__namespace.isAbsolute(customPolicyPath) ? customPolicyPath : path5__namespace.join(base, customPolicyPath) : path5__namespace.join(base, "dlp-policy.yaml");
16994
+ if (!fs8__namespace.existsSync(filePath)) return createDefaultDlpPolicy();
16995
+ return parseDlpPolicy(fs8__namespace.readFileSync(filePath, "utf-8"));
16711
16996
  }
16712
16997
  function applySingleDlpRule(text, rule) {
16713
16998
  if (rule.enabled === false) return { output: text, matches: 0 };
@@ -16895,6 +17180,7 @@ exports.STARTER_WORKFLOW_HANDOFF_TEMPLATE = STARTER_WORKFLOW_HANDOFF_TEMPLATE;
16895
17180
  exports.STARTER__GITHUB_PULL_REQUEST_TEMPLATE_MD = STARTER__GITHUB_PULL_REQUEST_TEMPLATE_MD;
16896
17181
  exports.SpecKitAdapter = SpecKitAdapter;
16897
17182
  exports.TYPE_TO_SCHEMA_MAP = TYPE_TO_SCHEMA_MAP;
17183
+ exports.UNIT_TEST_SUFFIXES = UNIT_TEST_SUFFIXES;
16898
17184
  exports.VALID_AGENT_TARGETS = VALID_AGENT_TARGETS;
16899
17185
  exports.VALID_ARCHITECTURE_GRANULARITIES = VALID_ARCHITECTURE_GRANULARITIES;
16900
17186
  exports.VALID_AUTONOMY_MODES = VALID_AUTONOMY_MODES;
@@ -16914,6 +17200,7 @@ exports.calculateJaccardSimilarity = calculateJaccardSimilarity;
16914
17200
  exports.calculateMetrics = calculateMetrics;
16915
17201
  exports.calculateRating = calculateRating;
16916
17202
  exports.calculateShannonEntropy = calculateShannonEntropy;
17203
+ exports.canonicalSort = canonicalSort;
16917
17204
  exports.checkGherkinInSync = checkGherkinInSync;
16918
17205
  exports.checkoutTaskBranch = checkoutTaskBranch;
16919
17206
  exports.classifyBranch = classifyBranch;
@@ -16922,6 +17209,7 @@ exports.collectAgentFiles = collectAgentFiles;
16922
17209
  exports.collectDashboardMetrics = collectDashboardMetrics;
16923
17210
  exports.collectHistoricalKpis = collectHistoricalKpis;
16924
17211
  exports.computeCanonicalSha256 = computeCanonicalSha256;
17212
+ exports.computeRawSha256 = computeRawSha256;
16925
17213
  exports.computeSha256 = computeSha256;
16926
17214
  exports.computeViolationSignature = computeViolationSignature;
16927
17215
  exports.createDefaultDlpPolicy = createDefaultDlpPolicy;
@@ -16947,10 +17235,13 @@ exports.extractFunctions = extractFunctions;
16947
17235
  exports.extractFunctionsBraceLanguages = extractFunctionsBraceLanguages;
16948
17236
  exports.extractFunctionsPolyglot = extractFunctionsPolyglot;
16949
17237
  exports.extractFunctionsPython = extractFunctionsPython;
17238
+ exports.extractFunctionsPythonAst = extractFunctionsPythonAst;
16950
17239
  exports.extractFunctionsTypeScriptAst = extractFunctionsTypeScriptAst;
16951
17240
  exports.extractGherkinBlock = extractGherkinBlock;
16952
17241
  exports.extractGherkinFeatures = extractGherkinFeatures;
16953
17242
  exports.extractNormativeText = extractNormativeText;
17243
+ exports.extractPythonFunctionsPolyglot = extractPythonFunctionsPolyglot;
17244
+ exports.extractSemanticContent = extractSemanticContent;
16954
17245
  exports.findChangeWorkspaceDir = findChangeWorkspaceDir;
16955
17246
  exports.findLicenseFile = findLicenseFile;
16956
17247
  exports.formatCommitTrailers = formatCommitTrailers;
@@ -16959,6 +17250,7 @@ exports.formatReleaseKpiMarkdown = formatReleaseKpiMarkdown;
16959
17250
  exports.generateBranchHierarchyPlan = generateBranchHierarchyPlan;
16960
17251
  exports.generateCycloneDxSbom = generateCycloneDxSbom;
16961
17252
  exports.generateDashboardReport = generateDashboardReport;
17253
+ exports.generateDeterministicUuidV5 = generateDeterministicUuidV5;
16962
17254
  exports.generateDuplicatesReportMarkdown = generateDuplicatesReportMarkdown;
16963
17255
  exports.generateGovernanceReportMarkdown = generateGovernanceReportMarkdown;
16964
17256
  exports.generateLicenseReportMarkdown = generateLicenseReportMarkdown;
@@ -16984,10 +17276,13 @@ exports.initProject = initProject;
16984
17276
  exports.injectKpisIntoPrBody = injectKpisIntoPrBody;
16985
17277
  exports.installGitHooks = installGitHooks;
16986
17278
  exports.integrateSddChange = integrateSddChange;
17279
+ exports.isCliInstalled = isCliInstalled;
17280
+ exports.isFunctionReferencedInTests = isFunctionReferencedInTests;
16987
17281
  exports.isGitleaksInstalled = isGitleaksInstalled;
16988
17282
  exports.isMetricWorsened = isMetricWorsened;
16989
17283
  exports.isPathProtectedFromPatch = isPathProtectedFromPatch;
16990
17284
  exports.isPlaceholderOrIgnored = isPlaceholderOrIgnored;
17285
+ exports.isReportContentSemanticallyEqual = isReportContentSemanticallyEqual;
16991
17286
  exports.isRequirementArtifact = isRequirementArtifact2;
16992
17287
  exports.isSemgrepInstalled = isSemgrepInstalled;
16993
17288
  exports.isToolAvailable = isToolAvailable;
@@ -16998,8 +17293,10 @@ exports.loadQualityBaseline = loadQualityBaseline;
16998
17293
  exports.locateTaskInActiveChanges = locateTaskInActiveChanges;
16999
17294
  exports.mapSastSeverityToSarifLevel = mapSastSeverityToSarifLevel;
17000
17295
  exports.maskSecret = maskSecret;
17296
+ exports.normalizePath = normalizePath;
17001
17297
  exports.normalizeSpdxLicense = normalizeSpdxLicense;
17002
17298
  exports.normalizeUri = normalizeUri;
17299
+ exports.orchestrateNativeAstAnalysis = orchestrateNativeAstAnalysis;
17003
17300
  exports.parseCommitTrailers = parseCommitTrailers;
17004
17301
  exports.parseDlpPolicy = parseDlpPolicy;
17005
17302
  exports.parseFeatureDocument = parseFeatureDocument;
@@ -17034,6 +17331,7 @@ exports.resolveBranchHierarchy = resolveBranchHierarchy;
17034
17331
  exports.resolveCiEnvironment = resolveCiEnvironment;
17035
17332
  exports.resolveCytoscapePath = resolveCytoscapePath;
17036
17333
  exports.resolveDefaultBranchBase = resolveDefaultBranchBase;
17334
+ exports.resolveDeterministicDate = resolveDeterministicDate;
17037
17335
  exports.resolveFeatureFilePath = resolveFeatureFilePath;
17038
17336
  exports.resolveGitRef = resolveGitRef;
17039
17337
  exports.resolveLicenseFromFile = resolveLicenseFromFile;
@@ -17046,6 +17344,7 @@ exports.runGitleaksCli = runGitleaksCli;
17046
17344
  exports.runSemgrepCli = runSemgrepCli;
17047
17345
  exports.sanitizeDlpPayload = sanitizeDlpPayload;
17048
17346
  exports.sanitizeDlpText = sanitizeDlpText;
17347
+ exports.sanitizeTestCorpus = sanitizeTestCorpus;
17049
17348
  exports.scaffoldSddChange = scaffoldSddChange;
17050
17349
  exports.scanAllProductHandoffs = scanAllProductHandoffs;
17051
17350
  exports.scanAllTestArtifacts = scanAllTestArtifacts;
@@ -17064,11 +17363,13 @@ exports.scanWithNativeFs = scanWithNativeFs;
17064
17363
  exports.scanWithSyft = scanWithSyft;
17065
17364
  exports.scanWithTrivy = scanWithTrivy;
17066
17365
  exports.serializeSarifDocument = serializeSarifDocument;
17067
- exports.slugify = slugify;
17366
+ exports.slugify = slugify2;
17367
+ exports.stripVolatileReportMetadata = stripVolatileReportMetadata;
17068
17368
  exports.syncPdacDigests = syncPdacDigests;
17069
17369
  exports.tokenizeTitle = tokenizeTitle;
17070
17370
  exports.updateCitationsInContent = updateCitationsInContent;
17071
17371
  exports.validateAgainstSchema = validateAgainstSchema;
17372
+ exports.validateAnchorExistence = validateAnchorExistence;
17072
17373
  exports.validateArtifactSchema = validateArtifactSchema;
17073
17374
  exports.verifyArtifactDuplicates = verifyArtifactDuplicates;
17074
17375
  exports.verifyArtifactsSchemas = verifyArtifactsSchemas;
@@ -17093,6 +17394,7 @@ exports.writeCucumberJsonReport = writeCucumberJsonReport;
17093
17394
  exports.writeCycloneDxSbom = writeCycloneDxSbom;
17094
17395
  exports.writeQualityBaseline = writeQualityBaseline;
17095
17396
  exports.writeReleaseKpiReport = writeReleaseKpiReport;
17397
+ exports.writeReportFileIfChanged = writeReportFileIfChanged;
17096
17398
  exports.writeSecuritySarifReport = writeSecuritySarifReport;
17097
17399
  exports.writeThirdPartyNotices = writeThirdPartyNotices;
17098
17400
  //# sourceMappingURL=index.cjs.map