@aisdlc/core 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
- import * as crypto from 'crypto';
2
- import * as fs7 from 'fs';
3
- import * as path4 from 'path';
1
+ import * as crypto3 from 'crypto';
2
+ import * as fs8 from 'fs';
3
+ import * as path5 from 'path';
4
4
  import yaml3 from 'js-yaml';
5
- import { SyntaxKind, Project, Node } from 'ts-morph';
6
5
  import * as child_process from 'child_process';
7
- import { execFileSync } from 'child_process';
6
+ import { execFileSync, spawnSync } from 'child_process';
7
+ import { SyntaxKind, Project, Node } from 'ts-morph';
8
+ import Ajv2020 from 'ajv/dist/2020.js';
9
+ import addFormats from 'ajv-formats';
8
10
  import { createRequire } from 'module';
9
11
 
10
12
  // src/types/index.ts
@@ -15,6 +17,168 @@ var VALID_AUTONOMY_MODES = [
15
17
  "AMBIGUOUS",
16
18
  "HIGH_RISK_MANUAL"
17
19
  ];
20
+ function canonicalSort(a, b) {
21
+ return a < b ? -1 : a > b ? 1 : 0;
22
+ }
23
+ function normalizePath(p) {
24
+ return p.normalize("NFC").replace(/\\/g, "/");
25
+ }
26
+ function walkMdFiles(dir, fileList = [], excludeDirs = [
27
+ "node_modules",
28
+ ".git",
29
+ "dist",
30
+ "bin",
31
+ "obj",
32
+ "build",
33
+ "out",
34
+ "target",
35
+ "coverage",
36
+ ".changeset",
37
+ "scratch",
38
+ "test-scaffold",
39
+ "fixtures",
40
+ "templates",
41
+ "examples",
42
+ ".agent",
43
+ ".agents",
44
+ ".claude",
45
+ ".cursor",
46
+ ".github",
47
+ ".vscode",
48
+ ".vs",
49
+ ".idea"
50
+ ]) {
51
+ try {
52
+ if (!fs8.existsSync(dir)) return fileList;
53
+ const files = fs8.readdirSync(dir).sort(canonicalSort);
54
+ for (const file of files) {
55
+ const fullPath = path5.join(dir, file);
56
+ try {
57
+ if (fs8.statSync(fullPath).isDirectory()) {
58
+ if (!excludeDirs.includes(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
59
+ walkMdFiles(fullPath, fileList, excludeDirs);
60
+ }
61
+ } else if (file.endsWith(".md")) {
62
+ fileList.push(fullPath);
63
+ }
64
+ } catch {
65
+ }
66
+ }
67
+ } catch {
68
+ }
69
+ return fileList.sort(canonicalSort);
70
+ }
71
+ function stripVolatileReportMetadata(content, _ext = "") {
72
+ let normalized = content.replace(/\r\n/g, "\n");
73
+ normalized = normalized.replace(/(?:^|\n)>\s*\*\*(?:Generation Date|Generated at|Fecha|Audit Date|Date):\*\*.*$/gim, "");
74
+ normalized = normalized.replace(/Generated:\s*<span>[\s\S]*?<\/span>/gi, "Generated: <span>[STRIPPED]</span>");
75
+ return normalized.trim();
76
+ }
77
+ function areCycloneDxSbomsEqual(existingContent, newContent) {
78
+ try {
79
+ const a = JSON.parse(existingContent);
80
+ const b = JSON.parse(newContent);
81
+ return a.serialNumber === b.serialNumber && JSON.stringify(a.components) === JSON.stringify(b.components) && JSON.stringify(a.vulnerabilities || []) === JSON.stringify(b.vulnerabilities || []);
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+ function areSarifReportsEqual(existingContent, newContent) {
87
+ try {
88
+ const a = JSON.parse(existingContent);
89
+ const b = JSON.parse(newContent);
90
+ return JSON.stringify(a.runs?.[0]?.results || []) === JSON.stringify(b.runs?.[0]?.results || []);
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+ function isReportContentSemanticallyEqual(existingContent, newContent, filePath) {
96
+ if (existingContent === newContent) return true;
97
+ if (filePath.endsWith(".cdx.json") || filePath.endsWith("sbom.json")) {
98
+ if (areCycloneDxSbomsEqual(existingContent, newContent)) return true;
99
+ }
100
+ if (filePath.endsWith(".sarif")) {
101
+ if (areSarifReportsEqual(existingContent, newContent)) return true;
102
+ }
103
+ const ext = path5.extname(filePath).toLowerCase();
104
+ return stripVolatileReportMetadata(existingContent, ext) === stripVolatileReportMetadata(newContent, ext);
105
+ }
106
+ function atomicWriteFile(filePath, content) {
107
+ const tempPath = `${filePath}.${process.pid}.${Math.random().toString(36).substring(2)}.tmp`;
108
+ try {
109
+ fs8.writeFileSync(tempPath, content, "utf-8");
110
+ try {
111
+ if (fs8.existsSync(filePath)) {
112
+ fs8.rmSync(filePath, { force: true });
113
+ }
114
+ fs8.renameSync(tempPath, filePath);
115
+ } catch {
116
+ fs8.copyFileSync(tempPath, filePath);
117
+ fs8.unlinkSync(tempPath);
118
+ }
119
+ } catch {
120
+ fs8.writeFileSync(filePath, content, "utf-8");
121
+ }
122
+ }
123
+ function writeReportFileIfChanged(filePath, content) {
124
+ try {
125
+ if (fs8.existsSync(filePath)) {
126
+ const existing = fs8.readFileSync(filePath, "utf-8");
127
+ if (isReportContentSemanticallyEqual(existing, content, filePath)) {
128
+ return false;
129
+ }
130
+ }
131
+ } catch {
132
+ }
133
+ const dir = path5.dirname(filePath);
134
+ if (!fs8.existsSync(dir)) {
135
+ fs8.mkdirSync(dir, { recursive: true });
136
+ }
137
+ atomicWriteFile(filePath, content);
138
+ return true;
139
+ }
140
+ var UUID_V5_NAMESPACE_OID = "6ba7b812-9dad-11d1-80b4-00c04fd430c8";
141
+ function resolveDeterministicDate(rootDir) {
142
+ const epoch = process.env.SOURCE_DATE_EPOCH;
143
+ if (epoch) {
144
+ const parsed = parseInt(epoch, 10);
145
+ if (!isNaN(parsed) && parsed > 0) {
146
+ return new Date(parsed * 1e3);
147
+ }
148
+ }
149
+ const targetDir = rootDir || process.cwd();
150
+ if (fs8.existsSync(path5.join(targetDir, ".git"))) {
151
+ try {
152
+ const gitOut = execFileSync("git", ["log", "-1", "--format=%ct"], {
153
+ cwd: targetDir,
154
+ encoding: "utf-8",
155
+ stdio: ["pipe", "pipe", "ignore"]
156
+ }).trim();
157
+ const parsed = parseInt(gitOut, 10);
158
+ if (!isNaN(parsed) && parsed > 0) {
159
+ return new Date(parsed * 1e3);
160
+ }
161
+ } catch {
162
+ }
163
+ }
164
+ return /* @__PURE__ */ new Date();
165
+ }
166
+ function generateDeterministicUuidV5(name, namespace = UUID_V5_NAMESPACE_OID) {
167
+ const nsHex = namespace.replace(/-/g, "");
168
+ const nsBytes = Buffer.from(nsHex, "hex");
169
+ const nameBytes = Buffer.from(name, "utf-8");
170
+ const hash = crypto3.createHash("sha1").update(nsBytes).update(nameBytes).digest();
171
+ hash[6] = hash[6] & 15 | 80;
172
+ hash[8] = hash[8] & 63 | 128;
173
+ const hex = hash.toString("hex").substring(0, 32);
174
+ return [
175
+ hex.substring(0, 8),
176
+ hex.substring(8, 12),
177
+ hex.substring(12, 16),
178
+ hex.substring(16, 20),
179
+ hex.substring(20, 32)
180
+ ].join("-");
181
+ }
18
182
  var CYCLOMATIC_KINDS = /* @__PURE__ */ new Set([
19
183
  SyntaxKind.IfStatement,
20
184
  SyntaxKind.ConditionalExpression,
@@ -171,7 +335,7 @@ function buildNodeMetrics(node, filePath) {
171
335
  return { functionName, filePath, loc, cyclomatic, cognitive, maintainability: mi, codeSmells, line };
172
336
  }
173
337
  function createTempSourceFile(project, content, filePath) {
174
- const ext = path4.extname(filePath).toLowerCase() || ".ts";
338
+ const ext = path5.extname(filePath).toLowerCase() || ".ts";
175
339
  const fileName = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 7)}${ext}`;
176
340
  return project.createSourceFile(fileName, content, { overwrite: true });
177
341
  }
@@ -208,8 +372,8 @@ function extractFunctionsTypeScriptAst(content, filePath) {
208
372
  }
209
373
  }
210
374
  function extractFunctionsPolyglot(content, filePath) {
211
- const ext = path4.extname(filePath).toLowerCase();
212
- return ext === ".py" ? extractPythonFunctions(content, filePath) : extractBraceFunctions(content, filePath);
375
+ const ext = path5.extname(filePath).toLowerCase();
376
+ return ext === ".py" ? extractPythonFunctionsPolyglot(content, filePath) : extractBraceFunctions(content, filePath);
213
377
  }
214
378
  function isDocstringToggle(trimmed) {
215
379
  const dCount = (trimmed.match(/"""/g) || []).length;
@@ -254,7 +418,7 @@ function processPythonLine(raw, state, blocks, lineIndex) {
254
418
  handlePythonBodyLine(raw, state, blocks, lineIndex);
255
419
  }
256
420
  }
257
- function extractPythonFunctions(content, filePath) {
421
+ function extractPythonFunctionsPolyglot(content, filePath) {
258
422
  const lines = content.split("\n");
259
423
  const blocks = [];
260
424
  const state = { currentFn: null, fnLines: [], baseIndent: 0, inDocstring: false };
@@ -292,7 +456,7 @@ function processBraceLine(line, state, blocks, lineIndex, ext) {
292
456
  }
293
457
  }
294
458
  function extractBraceFunctions(content, filePath) {
295
- const ext = path4.extname(filePath).toLowerCase();
459
+ const ext = path5.extname(filePath).toLowerCase();
296
460
  const lines = content.split("\n");
297
461
  const blocks = [];
298
462
  const state = {
@@ -501,7 +665,7 @@ function countCommentsAndLoc(rawLines, isPy) {
501
665
  return { loc, commentCount, cleanLines };
502
666
  }
503
667
  function calculatePolyglotMetrics(body, name, filePath, startLine) {
504
- const ext = path4.extname(filePath).toLowerCase();
668
+ const ext = path5.extname(filePath).toLowerCase();
505
669
  const rawLines = body.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
506
670
  const { loc, commentCount } = countCommentsAndLoc(rawLines, ext === ".py");
507
671
  const strippedBody = stripStringsAndComments(body, ext);
@@ -574,50 +738,129 @@ function computeBraceCognitive(lines) {
574
738
  function computePolyglotCognitive(lines, isPy) {
575
739
  return isPy ? computePythonCognitive(lines) : computeBraceCognitive(lines);
576
740
  }
577
- function walkMdFiles(dir, fileList = [], excludeDirs = [
578
- "node_modules",
579
- ".git",
580
- "dist",
581
- "bin",
582
- "obj",
583
- "build",
584
- "out",
585
- "target",
586
- "coverage",
587
- ".changeset",
588
- "scratch",
589
- "test-scaffold",
590
- "fixtures",
591
- "templates",
592
- "examples",
593
- ".agent",
594
- ".agents",
595
- ".claude",
596
- ".cursor",
597
- ".github",
598
- ".vscode",
599
- ".vs",
600
- ".idea"
601
- ]) {
741
+
742
+ // src/verifiers/ast/python-ast.ts
743
+ var PYTHON_AST_RUNNER_SCRIPT = `
744
+ import ast, json, sys
745
+
746
+ def analyze(code, filepath):
747
+ try:
748
+ tree = ast.parse(code)
749
+ except Exception as e:
750
+ return {"language": "python", "filePath": filepath, "functions": [], "error": str(e), "unsafeTypesCount": 0, "emptyStubsCount": 0}
751
+
752
+ functions = []
753
+ unsafe_types = 0
754
+ empty_stubs = 0
755
+ for node in ast.walk(tree):
756
+ if isinstance(node, ast.Name) and node.id == "Any":
757
+ unsafe_types += 1
758
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
759
+ start = node.lineno
760
+ end = getattr(node, 'end_lineno', start + len(node.body))
761
+ loc = end - start + 1
762
+ cc = 1
763
+ for child in ast.walk(node):
764
+ if isinstance(child, (ast.If, ast.For, ast.AsyncFor, ast.While, ast.ExceptHandler, ast.With, ast.AsyncWith)):
765
+ cc += 1
766
+ elif isinstance(child, ast.BoolOp):
767
+ cc += len(child.values) - 1
768
+
769
+ real_body = [n for n in node.body if not (isinstance(n, ast.Expr) and isinstance(getattr(n, 'value', None), ast.Constant))]
770
+ is_stub = len(real_body) == 0 or (len(real_body) == 1 and isinstance(real_body[0], ast.Pass))
771
+ if is_stub:
772
+ empty_stubs += 1
773
+
774
+ code_smells = ["Empty stub detected"] if is_stub else []
775
+ mi = max(0.0, round(100.0 - (cc * 2.0) - (loc * 0.2), 1))
776
+ functions.append({
777
+ "functionName": node.name,
778
+ "filePath": filepath,
779
+ "line": start,
780
+ "loc": loc,
781
+ "cyclomatic": cc,
782
+ "cognitive": cc,
783
+ "maintainability": mi,
784
+ "codeSmells": code_smells
785
+ })
786
+ return {
787
+ "language": "python",
788
+ "filePath": filepath,
789
+ "functions": functions,
790
+ "unsafeTypesCount": unsafe_types,
791
+ "emptyStubsCount": empty_stubs
792
+ }
793
+
794
+ code = sys.stdin.read()
795
+ filepath = sys.argv[1] if len(sys.argv) > 1 else ""
796
+ print(json.dumps(analyze(code, filepath)))
797
+ `;
798
+ function extractFunctionsPythonAst(content, filePath) {
602
799
  try {
603
- if (!fs7.existsSync(dir)) return fileList;
604
- const files = fs7.readdirSync(dir);
605
- for (const file of files) {
606
- const fullPath = path4.join(dir, file);
607
- try {
608
- if (fs7.statSync(fullPath).isDirectory()) {
609
- if (!excludeDirs.includes(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
610
- walkMdFiles(fullPath, fileList, excludeDirs);
611
- }
612
- } else if (file.endsWith(".md")) {
613
- fileList.push(fullPath);
614
- }
615
- } catch {
616
- }
800
+ const res = spawnSync("python", ["-c", PYTHON_AST_RUNNER_SCRIPT, filePath], {
801
+ input: content,
802
+ encoding: "utf-8",
803
+ timeout: 1e4
804
+ });
805
+ if (res.status === 0 && res.stdout) {
806
+ const parsed = JSON.parse(res.stdout);
807
+ return parsed;
617
808
  }
618
809
  } catch {
619
810
  }
620
- return fileList;
811
+ const fallbackFns = extractPythonFunctionsPolyglot(content, filePath);
812
+ return {
813
+ language: "python",
814
+ filePath,
815
+ functions: fallbackFns,
816
+ unsafeTypesCount: 0,
817
+ emptyStubsCount: 0
818
+ };
819
+ }
820
+
821
+ // src/verifiers/ast/native-ast-orchestrator.ts
822
+ function isCliInstalled(bin) {
823
+ try {
824
+ const res = spawnSync(bin, ["--version"], { encoding: "utf-8", timeout: 5e3, shell: false });
825
+ return res.status === 0 && !res.error;
826
+ } catch {
827
+ return false;
828
+ }
829
+ }
830
+ function assertRequiredStack(stack) {
831
+ if (!stack) return;
832
+ if (!isCliInstalled(stack)) {
833
+ throw new Error(`[AI-SDLC Fail-Closed] Required toolchain CLI '${stack}' is not installed in the environment`);
834
+ }
835
+ }
836
+ function analyzeTypeScript(content, filePath) {
837
+ return {
838
+ language: "typescript",
839
+ filePath,
840
+ functions: extractFunctionsTypeScriptAst(content, filePath),
841
+ unsafeTypesCount: 0,
842
+ emptyStubsCount: 0
843
+ };
844
+ }
845
+ function analyzePolyglot(content, filePath, ext) {
846
+ return {
847
+ language: ext.replace(/^\./, ""),
848
+ filePath,
849
+ functions: extractFunctionsPolyglot(content, filePath),
850
+ unsafeTypesCount: 0,
851
+ emptyStubsCount: 0
852
+ };
853
+ }
854
+ function orchestrateNativeAstAnalysis(content, filePath, options = {}) {
855
+ assertRequiredStack(options.requiredStack);
856
+ const ext = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
857
+ if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext)) {
858
+ return analyzeTypeScript(content, filePath);
859
+ }
860
+ if (ext === ".py") {
861
+ return extractFunctionsPythonAst(content, filePath);
862
+ }
863
+ return analyzePolyglot(content, filePath, ext);
621
864
  }
622
865
  function parseMarkdownFrontmatter(content) {
623
866
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -649,41 +892,41 @@ function extractGherkinBlock(body) {
649
892
  }
650
893
  function resolveTargetFiles(rootDir, targetPath) {
651
894
  if (targetPath) {
652
- const fullTarget = path4.isAbsolute(targetPath) ? targetPath : path4.join(rootDir, targetPath);
653
- if (!fs7.existsSync(fullTarget)) return [];
654
- return fs7.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
895
+ const fullTarget = path5.isAbsolute(targetPath) ? targetPath : path5.join(rootDir, targetPath);
896
+ if (!fs8.existsSync(fullTarget)) return [];
897
+ return fs8.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
655
898
  }
656
899
  const searchDirs = [
657
- path4.join(rootDir, "product"),
658
- path4.join(rootDir, "security"),
659
- path4.join(rootDir, "privacy"),
660
- path4.join(rootDir, "specs"),
661
- path4.join(rootDir, "examples", "product"),
662
- path4.join(rootDir, "examples", "security"),
663
- path4.join(rootDir, "examples", "privacy"),
664
- path4.join(rootDir, "examples", "specs")
900
+ path5.join(rootDir, "product"),
901
+ path5.join(rootDir, "security"),
902
+ path5.join(rootDir, "privacy"),
903
+ path5.join(rootDir, "specs"),
904
+ path5.join(rootDir, "examples", "product"),
905
+ path5.join(rootDir, "examples", "security"),
906
+ path5.join(rootDir, "examples", "privacy"),
907
+ path5.join(rootDir, "examples", "specs")
665
908
  ];
666
- return searchDirs.filter((d) => fs7.existsSync(d)).flatMap((d) => walkMdFiles(d));
909
+ return searchDirs.filter((d) => fs8.existsSync(d)).flatMap((d) => walkMdFiles(d));
667
910
  }
668
911
  function resolveFeatureFilePath(rootDir, filePath, id, customTarget) {
669
912
  if (customTarget) {
670
- return path4.isAbsolute(customTarget) ? customTarget : path4.join(rootDir, customTarget);
671
- }
672
- const baseDir = path4.join(rootDir, "tests", "features");
673
- const candidateSecurity = path4.join(baseDir, "security", `${id.toLowerCase()}.feature`);
674
- const candidatePrivacy = path4.join(baseDir, "privacy", `${id.toLowerCase()}.feature`);
675
- const candidateRoot = path4.join(baseDir, `${id.toLowerCase()}.feature`);
676
- if (fs7.existsSync(candidateSecurity)) return candidateSecurity;
677
- if (fs7.existsSync(candidatePrivacy)) return candidatePrivacy;
678
- if (fs7.existsSync(candidateRoot)) return candidateRoot;
913
+ return path5.isAbsolute(customTarget) ? customTarget : path5.join(rootDir, customTarget);
914
+ }
915
+ const baseDir = path5.join(rootDir, "tests", "features");
916
+ const candidateSecurity = path5.join(baseDir, "security", `${id.toLowerCase()}.feature`);
917
+ const candidatePrivacy = path5.join(baseDir, "privacy", `${id.toLowerCase()}.feature`);
918
+ const candidateRoot = path5.join(baseDir, `${id.toLowerCase()}.feature`);
919
+ if (fs8.existsSync(candidateSecurity)) return candidateSecurity;
920
+ if (fs8.existsSync(candidatePrivacy)) return candidatePrivacy;
921
+ if (fs8.existsSync(candidateRoot)) return candidateRoot;
679
922
  const subDir = filePath.includes("security") ? "security" : filePath.includes("privacy") ? "privacy" : "";
680
- return path4.join(baseDir, subDir, `${id.toLowerCase()}.feature`);
923
+ return path5.join(baseDir, subDir, `${id.toLowerCase()}.feature`);
681
924
  }
682
925
  function buildFeatureFileContent(rootDir, filePath, id, version, gherkinBlocks) {
683
926
  const banner = [
684
927
  `# ==============================================================================`,
685
928
  `# AUTO-GENERADO POR AI-SDLC (Cucumber Integration)`,
686
- `# Origen: ${path4.relative(rootDir, filePath).replace(/\\/g, "/")}`,
929
+ `# Origen: ${path5.relative(rootDir, filePath).replace(/\\/g, "/")}`,
687
930
  `# ID Requerimiento: ${id}`,
688
931
  `# Versi\xF3n: ${version || "1.0.0"}`,
689
932
  `# NO EDITAR MANUALMENTE: Cualquier cambio debe realizarse en el Markdown origen.`,
@@ -708,20 +951,20 @@ function extractGherkinFeatures(options = {}) {
708
951
  const targetFiles = resolveTargetFiles(rootDir, options.targetPath);
709
952
  for (const filePath of targetFiles) {
710
953
  try {
711
- const content = fs7.readFileSync(filePath, "utf-8");
954
+ const content = fs8.readFileSync(filePath, "utf-8");
712
955
  const { frontmatter, body } = parseMarkdownFrontmatter(content);
713
956
  const gherkinBlocks = extractGherkinBlock(body);
714
957
  if (gherkinBlocks.length === 0) continue;
715
- const id = frontmatter.id || path4.basename(filePath, path4.extname(filePath));
958
+ const id = frontmatter.id || path5.basename(filePath, path5.extname(filePath));
716
959
  const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
717
- fs7.mkdirSync(path4.dirname(targetPath), { recursive: true });
960
+ fs8.mkdirSync(path5.dirname(targetPath), { recursive: true });
718
961
  const fullContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
719
- fs7.writeFileSync(targetPath, fullContent, "utf-8");
962
+ fs8.writeFileSync(targetPath, fullContent, "utf-8");
720
963
  const scenarioCount = countScenarios(gherkinBlocks);
721
964
  totalScenarios += scenarioCount;
722
965
  features.push({
723
- sourceFile: path4.relative(rootDir, filePath).replace(/\\/g, "/"),
724
- outputFile: path4.relative(rootDir, targetPath).replace(/\\/g, "/"),
966
+ sourceFile: path5.relative(rootDir, filePath).replace(/\\/g, "/"),
967
+ outputFile: path5.relative(rootDir, targetPath).replace(/\\/g, "/"),
725
968
  featureName: id,
726
969
  scenarioCount
727
970
  });
@@ -738,20 +981,20 @@ function checkGherkinInSync(options = {}) {
738
981
  let totalFeatures = 0;
739
982
  for (const filePath of targetFiles) {
740
983
  try {
741
- const content = fs7.readFileSync(filePath, "utf-8");
984
+ const content = fs8.readFileSync(filePath, "utf-8");
742
985
  const { frontmatter, body } = parseMarkdownFrontmatter(content);
743
986
  const gherkinBlocks = extractGherkinBlock(body);
744
987
  if (gherkinBlocks.length === 0) continue;
745
988
  totalFeatures++;
746
- const id = frontmatter.id || path4.basename(filePath, path4.extname(filePath));
989
+ const id = frontmatter.id || path5.basename(filePath, path5.extname(filePath));
747
990
  const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
748
- const relTarget = path4.relative(rootDir, targetPath).replace(/\\/g, "/");
749
- if (!fs7.existsSync(targetPath)) {
991
+ const relTarget = path5.relative(rootDir, targetPath).replace(/\\/g, "/");
992
+ if (!fs8.existsSync(targetPath)) {
750
993
  missingFiles.push(relTarget);
751
994
  continue;
752
995
  }
753
996
  const expectedContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
754
- const actualContent = fs7.readFileSync(targetPath, "utf-8");
997
+ const actualContent = fs8.readFileSync(targetPath, "utf-8");
755
998
  if (actualContent.replace(/\r\n/g, "\n") !== expectedContent.replace(/\r\n/g, "\n")) {
756
999
  outOfSyncFiles.push(relTarget);
757
1000
  }
@@ -850,7 +1093,7 @@ function generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, t
850
1093
  const reportLines = [
851
1094
  `# \u{1F9EA} Deterministic Test Coverage Audit for Requirements and Tasks`,
852
1095
  ``,
853
- `> **Audit Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
1096
+ `> **Audit Date:** ${resolveDeterministicDate().toISOString()}`,
854
1097
  `> **Overall Verdict:** ${verdict}`,
855
1098
  ``,
856
1099
  `---`,
@@ -913,8 +1156,8 @@ function parseCodeTestIds(content, relTest, reqToTestsMap) {
913
1156
  }
914
1157
  function processTestFile(fullPath, rootDir, reqToTestsMap) {
915
1158
  try {
916
- const content = fs7.readFileSync(fullPath, "utf-8");
917
- const relTest = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
1159
+ const content = fs8.readFileSync(fullPath, "utf-8");
1160
+ const relTest = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
918
1161
  if (fullPath.endsWith(".feature")) {
919
1162
  parseFeatureTags(content, relTest, reqToTestsMap);
920
1163
  }
@@ -923,16 +1166,16 @@ function processTestFile(fullPath, rootDir, reqToTestsMap) {
923
1166
  }
924
1167
  }
925
1168
  function collectTestFiles(dir, files = []) {
926
- if (!fs7.existsSync(dir)) return files;
1169
+ if (!fs8.existsSync(dir)) return files;
927
1170
  try {
928
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
1171
+ const entries = fs8.readdirSync(dir, { withFileTypes: true }).sort((a, b) => canonicalSort(a.name, b.name));
929
1172
  for (const e of entries) {
930
1173
  if (e.isDirectory()) {
931
1174
  if (!EXCLUDED_TEST_DIRS.has(e.name)) {
932
- collectTestFiles(path4.join(dir, e.name), files);
1175
+ collectTestFiles(path5.join(dir, e.name), files);
933
1176
  }
934
1177
  } else if (isTestFile(e.name)) {
935
- files.push(path4.join(dir, e.name));
1178
+ files.push(path5.join(dir, e.name));
936
1179
  }
937
1180
  }
938
1181
  } catch {
@@ -1141,7 +1384,7 @@ function collectTestCorpus(rootDir, allMdFiles) {
1141
1384
  const testContents = [];
1142
1385
  for (const tf of collectTestFiles(rootDir)) {
1143
1386
  try {
1144
- const content = fs7.readFileSync(tf, "utf-8");
1387
+ const content = fs8.readFileSync(tf, "utf-8");
1145
1388
  testContents.push(content);
1146
1389
  if (tf.endsWith(".feature")) gherkinSources.push(content);
1147
1390
  } catch {
@@ -1149,7 +1392,7 @@ function collectTestCorpus(rootDir, allMdFiles) {
1149
1392
  }
1150
1393
  for (const md of allMdFiles) {
1151
1394
  try {
1152
- const blocks = extractGherkinBlock(fs7.readFileSync(md, "utf-8"));
1395
+ const blocks = extractGherkinBlock(fs8.readFileSync(md, "utf-8"));
1153
1396
  gherkinSources.push(...blocks);
1154
1397
  testContents.push(...blocks);
1155
1398
  } catch {
@@ -1178,9 +1421,9 @@ function countCucumberScenarios(raw) {
1178
1421
  return { passed, failed };
1179
1422
  }
1180
1423
  function parseCucumberReportFile(reportPath) {
1181
- if (!fs7.existsSync(reportPath)) return void 0;
1424
+ if (!fs8.existsSync(reportPath)) return void 0;
1182
1425
  try {
1183
- const raw = JSON.parse(fs7.readFileSync(reportPath, "utf-8"));
1426
+ const raw = JSON.parse(fs8.readFileSync(reportPath, "utf-8"));
1184
1427
  if (!Array.isArray(raw)) return void 0;
1185
1428
  const { passed, failed } = countCucumberScenarios(raw);
1186
1429
  return {
@@ -1194,7 +1437,7 @@ function parseCucumberReportFile(reportPath) {
1194
1437
  }
1195
1438
  }
1196
1439
  function resolveCucumberReport(rootDir, options) {
1197
- const candidate = options.cucumberReportPath ? path4.isAbsolute(options.cucumberReportPath) ? options.cucumberReportPath : path4.join(rootDir, options.cucumberReportPath) : path4.join(rootDir, "reports", "cucumber-report.json");
1440
+ const candidate = options.cucumberReportPath ? path5.isAbsolute(options.cucumberReportPath) ? options.cucumberReportPath : path5.join(rootDir, options.cucumberReportPath) : path5.join(rootDir, "reports", "cucumber-report.json");
1198
1441
  const parsed = parseCucumberReportFile(candidate);
1199
1442
  if (parsed) return parsed;
1200
1443
  if (options.enforceCucumberExecution) {
@@ -1228,8 +1471,8 @@ function recordUseCaseAuditIfPresent(frontmatter, id, relPath, isTemplate, combi
1228
1471
  if (flowAudit.status === "FALLO_SIN_PRUEBA") state.ucErrors++;
1229
1472
  }
1230
1473
  function processMdFileForCoverage(file, rootDir, reqToTestsMap, corpus, options, state) {
1231
- const { frontmatter } = parseGenericDoc(fs7.readFileSync(file, "utf-8"));
1232
- const relPath = path4.relative(rootDir, file).replace(/\\/g, "/");
1474
+ const { frontmatter } = parseGenericDoc(fs8.readFileSync(file, "utf-8"));
1475
+ const relPath = path5.relative(rootDir, file).replace(/\\/g, "/");
1233
1476
  const isTemplate = relPath.includes("templates/");
1234
1477
  const id = frontmatter.id || "";
1235
1478
  if (isRequirementArtifact(frontmatter, id)) {
@@ -1303,7 +1546,6 @@ var DEFAULT_EXCLUDES = [
1303
1546
  "templates",
1304
1547
  "fixtures"
1305
1548
  ];
1306
- var TS_JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1307
1549
  function createDefaultQualityPolicy() {
1308
1550
  return {
1309
1551
  max_cyclomatic: 10,
@@ -1384,27 +1626,38 @@ function applyOptionOverrides(policy, overrides) {
1384
1626
  if (overrides.max_function_lines !== void 0) policy.max_function_lines = Number(overrides.max_function_lines);
1385
1627
  if (overrides.enforce_mode !== void 0) policy.enforce_mode = overrides.enforce_mode;
1386
1628
  }
1629
+ function parsePolicyYaml(content) {
1630
+ let doc;
1631
+ try {
1632
+ doc = yaml3.load(content);
1633
+ } catch (err) {
1634
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in quality-policy: ${err instanceof Error ? err.message : String(err)}`);
1635
+ }
1636
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
1637
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid quality policy structure: root must be a YAML mapping`);
1638
+ }
1639
+ return doc;
1640
+ }
1387
1641
  function parseQualityPolicy(content, overrides) {
1388
1642
  const policy = createDefaultQualityPolicy();
1389
- try {
1390
- const doc = yaml3.load(content);
1391
- if (doc && typeof doc === "object") {
1392
- const record = doc;
1393
- applyDocScope(policy, record);
1394
- applyDocThresholds(policy, record);
1395
- if (record.max_per_function !== void 0) policy.max_cyclomatic = Number(record.max_per_function);
1396
- if (record.min_acceptable_score !== void 0) policy.min_maintainability = Number(record.min_acceptable_score);
1397
- if (record.max_function_lines !== void 0) policy.max_function_lines = Number(record.max_function_lines);
1398
- }
1399
- } catch {
1643
+ if (!content || !content.trim()) {
1644
+ applyEnvThresholds(policy);
1645
+ applyOptionOverrides(policy, overrides);
1646
+ return policy;
1400
1647
  }
1648
+ const record = parsePolicyYaml(content);
1649
+ applyDocScope(policy, record);
1650
+ applyDocThresholds(policy, record);
1651
+ if (record.max_per_function !== void 0) policy.max_cyclomatic = Number(record.max_per_function);
1652
+ if (record.min_acceptable_score !== void 0) policy.min_maintainability = Number(record.min_acceptable_score);
1653
+ if (record.max_function_lines !== void 0) policy.max_function_lines = Number(record.max_function_lines);
1401
1654
  applyEnvThresholds(policy);
1402
1655
  applyOptionOverrides(policy, overrides);
1403
1656
  return policy;
1404
1657
  }
1405
1658
  function extractFunctions(content, filePath) {
1406
- const ext = path4.extname(filePath).toLowerCase();
1407
- return TS_JS_EXTENSIONS.includes(ext) ? extractFunctionsTypeScriptAst(content, filePath) : extractFunctionsPolyglot(content, filePath);
1659
+ const report = orchestrateNativeAstAnalysis(content, filePath);
1660
+ return report.functions;
1408
1661
  }
1409
1662
  function extractFunctionsBraceLanguages(content, filePath) {
1410
1663
  return extractFunctions(content, filePath);
@@ -1434,55 +1687,55 @@ function isExcludedFile(file, normalized, excludePatterns) {
1434
1687
  );
1435
1688
  }
1436
1689
  function walkDir(dir, extensions = DEFAULT_EXTENSIONS, excludePatterns = DEFAULT_EXCLUDES) {
1437
- if (!fs7.existsSync(dir)) return [];
1690
+ if (!fs8.existsSync(dir)) return [];
1438
1691
  const results = [];
1439
- for (const file of fs7.readdirSync(dir)) {
1440
- const fullPath = path4.join(dir, file);
1692
+ for (const file of fs8.readdirSync(dir).sort(canonicalSort)) {
1693
+ const fullPath = path5.join(dir, file);
1441
1694
  const normalized = fullPath.replace(/\\/g, "/");
1442
1695
  if (isExcludedFile(file, normalized, excludePatterns)) continue;
1443
- const stat = fs7.statSync(fullPath);
1696
+ const stat = fs8.statSync(fullPath);
1444
1697
  if (stat.isDirectory()) {
1445
1698
  results.push(...walkDir(fullPath, extensions, excludePatterns));
1446
- } else if (extensions.includes(path4.extname(file).toLowerCase())) {
1699
+ } else if (extensions.includes(path5.extname(file).toLowerCase())) {
1447
1700
  results.push(fullPath);
1448
1701
  }
1449
1702
  }
1450
1703
  return results;
1451
1704
  }
1452
1705
  function resolvePolicy(rootDir, options) {
1453
- const policyFile = options.policyPath || path4.join(rootDir, "quality-policy.yaml");
1706
+ const policyFile = options.policyPath || path5.join(rootDir, "quality-policy.yaml");
1454
1707
  if (options.policy) {
1455
1708
  const policy = { ...options.policy };
1456
1709
  applyOptionOverrides(policy, options.thresholds);
1457
1710
  return policy;
1458
1711
  }
1459
- if (fs7.existsSync(policyFile)) {
1460
- return parseQualityPolicy(fs7.readFileSync(policyFile, "utf-8"), options.thresholds);
1712
+ if (fs8.existsSync(policyFile)) {
1713
+ return parseQualityPolicy(fs8.readFileSync(policyFile, "utf-8"), options.thresholds);
1461
1714
  }
1462
1715
  return parseQualityPolicy("", options.thresholds);
1463
1716
  }
1464
1717
  function isPathExcluded(targetPath, rootDir, excludePatterns) {
1465
- const rel = path4.relative(rootDir, targetPath).replace(/\\/g, "/");
1466
- const base = path4.basename(targetPath);
1718
+ const rel = path5.relative(rootDir, targetPath).replace(/\\/g, "/");
1719
+ const base = path5.basename(targetPath);
1467
1720
  return excludePatterns.some(
1468
1721
  (pat) => base === pat || rel === pat || rel.startsWith(pat + "/") || rel.includes("/" + pat + "/") || rel.endsWith("/" + pat) || rel.includes(pat)
1469
1722
  );
1470
1723
  }
1471
1724
  function scanContainerSubdir(containerDir, subdirName, targetDir, rootDir, policy, files) {
1472
1725
  if (policy.exclude_patterns.includes(subdirName)) return;
1473
- const subTarget = path4.join(containerDir, subdirName, targetDir);
1474
- if (!fs7.existsSync(subTarget) || !fs7.statSync(subTarget).isDirectory()) return;
1726
+ const subTarget = path5.join(containerDir, subdirName, targetDir);
1727
+ if (!fs8.existsSync(subTarget) || !fs8.statSync(subTarget).isDirectory()) return;
1475
1728
  if (isPathExcluded(subTarget, rootDir, policy.exclude_patterns)) return;
1476
1729
  for (const f of walkDir(subTarget, policy.supported_extensions, policy.exclude_patterns)) {
1477
1730
  files.add(f);
1478
1731
  }
1479
1732
  }
1480
1733
  function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
1481
- const containerDir = path4.join(rootDir, container);
1482
- if (!fs7.existsSync(containerDir) || !fs7.statSync(containerDir).isDirectory()) return;
1734
+ const containerDir = path5.join(rootDir, container);
1735
+ if (!fs8.existsSync(containerDir) || !fs8.statSync(containerDir).isDirectory()) return;
1483
1736
  if (isPathExcluded(containerDir, rootDir, policy.exclude_patterns)) return;
1484
1737
  try {
1485
- for (const subdir of fs7.readdirSync(containerDir, { withFileTypes: true })) {
1738
+ for (const subdir of fs8.readdirSync(containerDir, { withFileTypes: true })) {
1486
1739
  if (subdir.isDirectory()) {
1487
1740
  scanContainerSubdir(containerDir, subdir.name, targetDir, rootDir, policy, files);
1488
1741
  }
@@ -1491,13 +1744,14 @@ function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
1491
1744
  }
1492
1745
  }
1493
1746
  function scanDirectTarget(rootDir, targetDir, policy, files) {
1494
- const fullDir = path4.join(rootDir, targetDir);
1495
- if (fs7.existsSync(fullDir) && !isPathExcluded(fullDir, rootDir, policy.exclude_patterns)) {
1747
+ const fullDir = path5.join(rootDir, targetDir);
1748
+ if (fs8.existsSync(fullDir) && !isPathExcluded(fullDir, rootDir, policy.exclude_patterns)) {
1496
1749
  for (const f of walkDir(fullDir, policy.supported_extensions, policy.exclude_patterns)) {
1497
1750
  files.add(f);
1498
1751
  }
1499
1752
  }
1500
1753
  }
1754
+ var UNIT_TEST_SUFFIXES = [".spec.ts", ".test.ts", ".spec.js", ".test.js", "_test.py", "_test.go"];
1501
1755
  function collectFilesToScan(rootDir, policy) {
1502
1756
  const files = /* @__PURE__ */ new Set();
1503
1757
  const monorepoContainers = ["packages", "apps", "libs", "modules", "services"];
@@ -1507,7 +1761,7 @@ function collectFilesToScan(rootDir, policy) {
1507
1761
  scanMonorepoContainer(rootDir, container, targetDir, policy, files);
1508
1762
  }
1509
1763
  }
1510
- return Array.from(files);
1764
+ return Array.from(files).filter((f) => !UNIT_TEST_SUFFIXES.some((s) => f.endsWith(s)));
1511
1765
  }
1512
1766
  function evaluateViolations(fn, policy) {
1513
1767
  const violations = [];
@@ -1529,38 +1783,67 @@ function evaluateViolations(fn, policy) {
1529
1783
  return violations;
1530
1784
  }
1531
1785
  function evaluateFileQuality(file, rootDir, policy) {
1532
- const content = fs7.readFileSync(file, "utf-8");
1786
+ const content = fs8.readFileSync(file, "utf-8");
1533
1787
  const fns = extractFunctions(content, file);
1534
1788
  return fns.map((fn) => {
1535
1789
  const violations = evaluateViolations(fn, policy);
1536
1790
  return {
1537
1791
  ...fn,
1538
- relPath: path4.relative(rootDir, file).replace(/\\/g, "/"),
1792
+ relPath: path5.relative(rootDir, file).replace(/\\/g, "/"),
1539
1793
  status: violations.length === 0 ? "PASS" : "FAIL",
1540
1794
  violations
1541
1795
  };
1542
1796
  });
1543
1797
  }
1544
- var UNIT_TEST_SUFFIXES = [".spec.ts", ".test.ts", ".spec.js", ".test.js", "_test.py", "_test.go"];
1545
1798
  function collectUnitTestCorpus(rootDir) {
1546
1799
  const allCodeFiles = walkDir(rootDir, DEFAULT_EXTENSIONS, DEFAULT_EXCLUDES);
1547
1800
  const chunks = [];
1548
1801
  for (const tf of allCodeFiles) {
1549
1802
  if (!UNIT_TEST_SUFFIXES.some((s) => tf.endsWith(s))) continue;
1550
1803
  try {
1551
- chunks.push(fs7.readFileSync(tf, "utf-8"));
1804
+ chunks.push(fs8.readFileSync(tf, "utf-8"));
1552
1805
  } catch {
1553
1806
  }
1554
1807
  }
1555
1808
  return chunks.join("\n");
1556
1809
  }
1810
+ function sanitizeTestCorpus(corpus, fnName) {
1811
+ let cleaned = corpus.replace(/\/\*[\s\S]*?\*\//g, " ");
1812
+ cleaned = cleaned.replace(/(?:\/\/|#)[^\r\n]*/g, " ");
1813
+ cleaned = cleaned.replace(/(['"`])((?:\\.|(?!\1)[^\\])*)\1/g, (match, quote, inner) => {
1814
+ const trimmed = inner.trim();
1815
+ if (trimmed === fnName || trimmed === `#${fnName}` || trimmed === `.${fnName}` || trimmed === `${fnName}()`) {
1816
+ return match;
1817
+ }
1818
+ return `${quote}${quote}`;
1819
+ });
1820
+ return cleaned;
1821
+ }
1822
+ function buildTestReferencePatterns(escaped) {
1823
+ return [
1824
+ new RegExp(`(?:\\.|\\b)${escaped}\\s*\\(`, "m"),
1825
+ new RegExp(`\\b(?:import|require)\\b[^;\\n]*\\b${escaped}\\b`, "m"),
1826
+ new RegExp(`(?:\\bexpect|\\bassert|\\bshould)\\s*\\([^;\\n]*\\b${escaped}\\b`, "m"),
1827
+ new RegExp("(?:\\bspyOn|\\bmock)\\s*\\([^;\\n]*['\"`]" + escaped + "['\"`]", "m"),
1828
+ new RegExp(`\\b${escaped}\\s*\\.`, "m"),
1829
+ new RegExp("\\b(?:describe|it|test|context)\\s*\\(\\s*['\"`](?:#|\\.)?" + escaped + "['\"`]", "m"),
1830
+ new RegExp(`\\btest_?${escaped}\\b`, "im")
1831
+ ];
1832
+ }
1833
+ function isFunctionReferencedInTests(fnName, rawCorpus) {
1834
+ if (!fnName || fnName === "anonymous" || fnName === "constructor") return true;
1835
+ const corpus = sanitizeTestCorpus(rawCorpus, fnName);
1836
+ const escaped = fnName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1837
+ const patterns = buildTestReferencePatterns(escaped);
1838
+ return patterns.some((p) => p.test(corpus));
1839
+ }
1557
1840
  function auditUnitTestsPerFunction(results, rootDir) {
1558
1841
  const corpus = collectUnitTestCorpus(rootDir);
1559
1842
  const untested = [];
1560
1843
  for (const r of results) {
1561
1844
  const fnName = r.functionName.includes(".") ? r.functionName.split(".").pop() : r.functionName;
1562
1845
  if (!fnName || fnName === "anonymous" || fnName === "constructor") continue;
1563
- if (!corpus.includes(fnName)) {
1846
+ if (!isFunctionReferencedInTests(fnName, corpus)) {
1564
1847
  untested.push({ file: r.relPath, functionName: r.functionName });
1565
1848
  r.violations.push(`Missing unit test for function '${r.functionName}'`);
1566
1849
  r.status = "FAIL";
@@ -1570,11 +1853,11 @@ function auditUnitTestsPerFunction(results, rootDir) {
1570
1853
  }
1571
1854
  function isVitestMidFlightCoverage(rel, fullPath) {
1572
1855
  if (!process.env.VITEST || rel !== "coverage/coverage-summary.json") return false;
1573
- return path4.resolve(fullPath) === path4.resolve(process.cwd(), rel);
1856
+ return path5.resolve(fullPath) === path5.resolve(process.cwd(), rel);
1574
1857
  }
1575
1858
  function parseCoverageSummaryFile(filePath, source) {
1576
1859
  try {
1577
- const raw = JSON.parse(fs7.readFileSync(filePath, "utf-8"));
1860
+ const raw = JSON.parse(fs8.readFileSync(filePath, "utf-8"));
1578
1861
  const total = raw.total;
1579
1862
  if (!total) return null;
1580
1863
  return {
@@ -1589,8 +1872,8 @@ function parseCoverageSummaryFile(filePath, source) {
1589
1872
  }
1590
1873
  function readSummaryCoverageJson(rootDir) {
1591
1874
  for (const rel of ["coverage/coverage-summary.json", "reports/coverage-summary.json"]) {
1592
- const full = path4.join(rootDir, rel);
1593
- if (!fs7.existsSync(full) || isVitestMidFlightCoverage(rel, full)) continue;
1875
+ const full = path5.join(rootDir, rel);
1876
+ if (!fs8.existsSync(full) || isVitestMidFlightCoverage(rel, full)) continue;
1594
1877
  const summary = parseCoverageSummaryFile(full, rel);
1595
1878
  if (summary) return summary;
1596
1879
  }
@@ -1614,7 +1897,7 @@ function evaluateCoverageThresholds(rootDir, policy, requireReport) {
1614
1897
  }
1615
1898
  function computeViolationSignature(relPath, fnName, metric) {
1616
1899
  const normPath = relPath.replace(/\\/g, "/");
1617
- return crypto.createHash("sha256").update(normPath + ":" + fnName + ":" + metric).digest("hex");
1900
+ return crypto3.createHash("sha256").update(normPath + ":" + fnName + ":" + metric).digest("hex");
1618
1901
  }
1619
1902
  function classifyMetricFromMessage(msg, fn) {
1620
1903
  if (msg.startsWith("Cyclomatic Complexity")) {
@@ -1643,8 +1926,8 @@ function isMetricWorsened(baselineEntry, current) {
1643
1926
  }
1644
1927
  function loadQualityBaseline(baselineFile) {
1645
1928
  try {
1646
- if (!fs7.existsSync(baselineFile)) return null;
1647
- const content = fs7.readFileSync(baselineFile, "utf-8");
1929
+ if (!fs8.existsSync(baselineFile)) return null;
1930
+ const content = fs8.readFileSync(baselineFile, "utf-8");
1648
1931
  return JSON.parse(content);
1649
1932
  } catch {
1650
1933
  return null;
@@ -1653,11 +1936,11 @@ function loadQualityBaseline(baselineFile) {
1653
1936
  function writeQualityBaseline(baselineFile, entries) {
1654
1937
  const baseline = {
1655
1938
  version: "1.0",
1656
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1939
+ generatedAt: resolveDeterministicDate(path5.dirname(baselineFile)).toISOString(),
1657
1940
  totalViolations: entries.length,
1658
1941
  violations: entries
1659
1942
  };
1660
- fs7.writeFileSync(baselineFile, JSON.stringify(baseline, null, 2) + "\n", "utf-8");
1943
+ fs8.writeFileSync(baselineFile, JSON.stringify(baseline, null, 2) + "\n", "utf-8");
1661
1944
  return baseline;
1662
1945
  }
1663
1946
  function extractBaselineEntriesFromResults(results) {
@@ -1716,7 +1999,7 @@ function applyRatchetMode(results, baseline) {
1716
1999
  function resolveBaselineFilePath(rootDir, options) {
1717
2000
  if (options.baselinePath) return options.baselinePath;
1718
2001
  if (typeof options.baseline === "string") return options.baseline;
1719
- return path4.join(rootDir, "quality-baseline.json");
2002
+ return path5.join(rootDir, "quality-baseline.json");
1720
2003
  }
1721
2004
  function isRatchetModeActive(policy, options, baselineExists) {
1722
2005
  if (options.ratchet !== void 0) return options.ratchet;
@@ -1724,7 +2007,7 @@ function isRatchetModeActive(policy, options, baselineExists) {
1724
2007
  return Boolean(policy.ratchet_mode || options.baseline && baselineExists);
1725
2008
  }
1726
2009
  function evaluateRatchetIfActive(results, baselineFile, isActive) {
1727
- if (!isActive || !fs7.existsSync(baselineFile)) {
2010
+ if (!isActive || !fs8.existsSync(baselineFile)) {
1728
2011
  return { toleratedViolationsCount: 0, newViolationsCount: 0, baselineViolationsCount: 0 };
1729
2012
  }
1730
2013
  const baseline = loadQualityBaseline(baselineFile);
@@ -1783,7 +2066,7 @@ function verifyQualityGate(options = {}) {
1783
2066
  baselineViolationsCount: entries.length
1784
2067
  });
1785
2068
  }
1786
- const baselineExists = fs7.existsSync(baselineFile);
2069
+ const baselineExists = fs8.existsSync(baselineFile);
1787
2070
  const isRatchet = isRatchetModeActive(policy, options, baselineExists);
1788
2071
  const ratchetEval = evaluateRatchetIfActive(results, baselineFile, isRatchet);
1789
2072
  return buildQualityGateResult(results, allFiles.length, policy, coverageViolations, untestedFunctions, {
@@ -1801,9 +2084,9 @@ function normalizeHandoffPayload(handoff, changeId) {
1801
2084
  };
1802
2085
  }
1803
2086
  function parseChangeProfile(specFile) {
1804
- if (!fs7.existsSync(specFile)) return "standard";
2087
+ if (!fs8.existsSync(specFile)) return "standard";
1805
2088
  try {
1806
- const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
2089
+ const rawSpec = fs8.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1807
2090
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1808
2091
  if (match) {
1809
2092
  const parsedFm = yaml3.load(match[1]);
@@ -1825,12 +2108,12 @@ function checkSpecGherkinFormat(parsedFm, rawSpec, changeWorkspaceDir, errors) {
1825
2108
  }
1826
2109
  }
1827
2110
  function validateSpecFileContent(specFile, changeWorkspaceDir, errors) {
1828
- if (!fs7.existsSync(specFile)) {
2111
+ if (!fs8.existsSync(specFile)) {
1829
2112
  errors.push(`Falta el archivo obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
1830
2113
  return;
1831
2114
  }
1832
2115
  try {
1833
- const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
2116
+ const rawSpec = fs8.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1834
2117
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1835
2118
  if (match) {
1836
2119
  const parsedFm = yaml3.load(match[1]);
@@ -1849,19 +2132,19 @@ function validateHandoffSidecar(handoff, changeWorkspaceDir, errors) {
1849
2132
  if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
1850
2133
  errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
1851
2134
  }
1852
- return path4.join(changeWorkspaceDir, "handoff.yaml");
2135
+ return path5.join(changeWorkspaceDir, "handoff.yaml");
1853
2136
  }
1854
2137
  function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
1855
- const proposalFile = path4.join(changeWorkspaceDir, "proposal.md");
1856
- const tasksFile = path4.join(changeWorkspaceDir, "tasks.md");
2138
+ const proposalFile = path5.join(changeWorkspaceDir, "proposal.md");
2139
+ const tasksFile = path5.join(changeWorkspaceDir, "tasks.md");
1857
2140
  if (profile === "patch") {
1858
2141
  const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
1859
- return { handoff: handoff2, sidecarFile: handoff2 ? path4.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
2142
+ return { handoff: handoff2, sidecarFile: handoff2 ? path5.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
1860
2143
  }
1861
- if (!fs7.existsSync(proposalFile)) {
2144
+ if (!fs8.existsSync(proposalFile)) {
1862
2145
  warnings.push(`Falta 'proposal.md' en el espacio de trabajo OpenSpec.`);
1863
2146
  }
1864
- if (!fs7.existsSync(tasksFile)) {
2147
+ if (!fs8.existsSync(tasksFile)) {
1865
2148
  errors.push(`Falta el archivo obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
1866
2149
  }
1867
2150
  const handoff = adapter.loadSidecar(changeWorkspaceDir);
@@ -1869,20 +2152,20 @@ function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors,
1869
2152
  return { handoff, sidecarFile };
1870
2153
  }
1871
2154
  function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
1872
- const changeId = path4.basename(changeWorkspaceDir);
1873
- const proposalFile = path4.join(changeWorkspaceDir, "proposal.md");
1874
- const specFile = path4.join(changeWorkspaceDir, "spec.md");
1875
- const designFile = path4.join(changeWorkspaceDir, "design.md");
1876
- const tasksFile = path4.join(changeWorkspaceDir, "tasks.md");
2155
+ const changeId = path5.basename(changeWorkspaceDir);
2156
+ const proposalFile = path5.join(changeWorkspaceDir, "proposal.md");
2157
+ const specFile = path5.join(changeWorkspaceDir, "spec.md");
2158
+ const designFile = path5.join(changeWorkspaceDir, "design.md");
2159
+ const tasksFile = path5.join(changeWorkspaceDir, "tasks.md");
1877
2160
  return {
1878
2161
  framework: "openspec",
1879
2162
  changeId,
1880
2163
  changeDir: changeWorkspaceDir,
1881
2164
  profile,
1882
- proposalFile: fs7.existsSync(proposalFile) ? proposalFile : void 0,
1883
- specFile: fs7.existsSync(specFile) ? specFile : void 0,
1884
- designFile: fs7.existsSync(designFile) ? designFile : void 0,
1885
- tasksFile: fs7.existsSync(tasksFile) ? tasksFile : void 0,
2165
+ proposalFile: fs8.existsSync(proposalFile) ? proposalFile : void 0,
2166
+ specFile: fs8.existsSync(specFile) ? specFile : void 0,
2167
+ designFile: fs8.existsSync(designFile) ? designFile : void 0,
2168
+ tasksFile: fs8.existsSync(tasksFile) ? tasksFile : void 0,
1886
2169
  sidecarFile,
1887
2170
  handoff: handoff || void 0
1888
2171
  };
@@ -1890,28 +2173,28 @@ function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sideca
1890
2173
  var OpenSpecAdapter = class {
1891
2174
  framework = "openspec";
1892
2175
  getChangeWorkspaceDir(rootDir, changeId) {
1893
- const openspecDir = path4.join(rootDir, "openspec", "changes", changeId);
1894
- if (fs7.existsSync(openspecDir)) return openspecDir;
1895
- const activeChangeDir = path4.join(rootDir, "specs", "changes", "active", changeId);
1896
- if (fs7.existsSync(activeChangeDir)) return activeChangeDir;
1897
- const completedChangeDir = path4.join(rootDir, "specs", "changes", "completed", changeId);
1898
- if (fs7.existsSync(completedChangeDir)) return completedChangeDir;
2176
+ const openspecDir = path5.join(rootDir, "openspec", "changes", changeId);
2177
+ if (fs8.existsSync(openspecDir)) return openspecDir;
2178
+ const activeChangeDir = path5.join(rootDir, "specs", "changes", "active", changeId);
2179
+ if (fs8.existsSync(activeChangeDir)) return activeChangeDir;
2180
+ const completedChangeDir = path5.join(rootDir, "specs", "changes", "completed", changeId);
2181
+ if (fs8.existsSync(completedChangeDir)) return completedChangeDir;
1899
2182
  return activeChangeDir;
1900
2183
  }
1901
2184
  depositSidecar(options) {
1902
2185
  const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
1903
- if (!fs7.existsSync(changeDir)) {
1904
- fs7.mkdirSync(changeDir, { recursive: true });
2186
+ if (!fs8.existsSync(changeDir)) {
2187
+ fs8.mkdirSync(changeDir, { recursive: true });
1905
2188
  }
1906
2189
  const handoff = normalizeHandoffPayload(options.handoff, options.changeId);
1907
2190
  const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
1908
- const filePath = path4.join(changeDir, filename);
2191
+ const filePath = path5.join(changeDir, filename);
1909
2192
  const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3.dump(handoff, { indent: 2, lineWidth: -1 });
1910
- fs7.writeFileSync(filePath, content, "utf-8");
2193
+ fs8.writeFileSync(filePath, content, "utf-8");
1911
2194
  return filePath;
1912
2195
  }
1913
2196
  loadSidecar(changeWorkspaceDir) {
1914
- if (!fs7.existsSync(changeWorkspaceDir)) return null;
2197
+ if (!fs8.existsSync(changeWorkspaceDir)) return null;
1915
2198
  const candidateFiles = [
1916
2199
  "handoff.yaml",
1917
2200
  "handoff.yml",
@@ -1922,10 +2205,10 @@ var OpenSpecAdapter = class {
1922
2205
  "product-handoff.json"
1923
2206
  ];
1924
2207
  for (const file of candidateFiles) {
1925
- const fullPath = path4.join(changeWorkspaceDir, file);
1926
- if (fs7.existsSync(fullPath)) {
2208
+ const fullPath = path5.join(changeWorkspaceDir, file);
2209
+ if (fs8.existsSync(fullPath)) {
1927
2210
  try {
1928
- const raw = fs7.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
2211
+ const raw = fs8.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
1929
2212
  const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3.load(raw);
1930
2213
  if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
1931
2214
  return parsed;
@@ -1937,7 +2220,7 @@ var OpenSpecAdapter = class {
1937
2220
  return null;
1938
2221
  }
1939
2222
  validateWorkspace(changeWorkspaceDir) {
1940
- if (!fs7.existsSync(changeWorkspaceDir)) {
2223
+ if (!fs8.existsSync(changeWorkspaceDir)) {
1941
2224
  return {
1942
2225
  valid: false,
1943
2226
  errors: [`El directorio del cambio OpenSpec no existe: ${changeWorkspaceDir}`],
@@ -1946,7 +2229,7 @@ var OpenSpecAdapter = class {
1946
2229
  }
1947
2230
  const errors = [];
1948
2231
  const warnings = [];
1949
- const specFile = path4.join(changeWorkspaceDir, "spec.md");
2232
+ const specFile = path5.join(changeWorkspaceDir, "spec.md");
1950
2233
  const profile = parseChangeProfile(specFile);
1951
2234
  validateSpecFileContent(specFile, changeWorkspaceDir, errors);
1952
2235
  const { handoff, sidecarFile } = validateOpenSpecStructure(profile, changeWorkspaceDir, this, errors, warnings);
@@ -1960,9 +2243,9 @@ var OpenSpecAdapter = class {
1960
2243
  }
1961
2244
  };
1962
2245
  function parseChangeProfile2(specFile) {
1963
- if (!fs7.existsSync(specFile)) return "standard";
2246
+ if (!fs8.existsSync(specFile)) return "standard";
1964
2247
  try {
1965
- const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
2248
+ const rawSpec = fs8.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1966
2249
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1967
2250
  if (match) {
1968
2251
  const parsedFm = yaml3.load(match[1]);
@@ -1984,12 +2267,12 @@ function checkSpecGherkinFormat2(parsedFm, rawSpec, changeWorkspaceDir, errors)
1984
2267
  }
1985
2268
  }
1986
2269
  function validateSpecFileContent2(specFile, changeWorkspaceDir, errors) {
1987
- if (!fs7.existsSync(specFile)) {
2270
+ if (!fs8.existsSync(specFile)) {
1988
2271
  errors.push(`Falta el archivo de especificaci\xF3n obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
1989
2272
  return;
1990
2273
  }
1991
2274
  try {
1992
- const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
2275
+ const rawSpec = fs8.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1993
2276
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1994
2277
  if (match) {
1995
2278
  const parsedFm = yaml3.load(match[1]);
@@ -2008,19 +2291,19 @@ function validateHandoffSidecar2(handoff, changeWorkspaceDir, errors) {
2008
2291
  if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
2009
2292
  errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
2010
2293
  }
2011
- return path4.join(changeWorkspaceDir, "handoff.yaml");
2294
+ return path5.join(changeWorkspaceDir, "handoff.yaml");
2012
2295
  }
2013
2296
  function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
2014
- const planFile = path4.join(changeWorkspaceDir, "plan.md");
2015
- const tasksFile = path4.join(changeWorkspaceDir, "tasks.md");
2297
+ const planFile = path5.join(changeWorkspaceDir, "plan.md");
2298
+ const tasksFile = path5.join(changeWorkspaceDir, "tasks.md");
2016
2299
  if (profile === "patch") {
2017
2300
  const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
2018
- return { handoff: handoff2, sidecarFile: handoff2 ? path4.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
2301
+ return { handoff: handoff2, sidecarFile: handoff2 ? path5.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
2019
2302
  }
2020
- if (!fs7.existsSync(tasksFile)) {
2303
+ if (!fs8.existsSync(tasksFile)) {
2021
2304
  errors.push(`Falta el archivo de tareas obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
2022
2305
  }
2023
- if (!fs7.existsSync(planFile)) {
2306
+ if (!fs8.existsSync(planFile)) {
2024
2307
  warnings.push(`Se recomienda incluir 'plan.md' en el espacio de trabajo Spec Kit.`);
2025
2308
  }
2026
2309
  const handoff = adapter.loadSidecar(changeWorkspaceDir);
@@ -2028,18 +2311,18 @@ function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors,
2028
2311
  return { handoff, sidecarFile };
2029
2312
  }
2030
2313
  function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
2031
- const changeId = path4.basename(changeWorkspaceDir);
2032
- const specFile = path4.join(changeWorkspaceDir, "spec.md");
2033
- const planFile = path4.join(changeWorkspaceDir, "plan.md");
2034
- const tasksFile = path4.join(changeWorkspaceDir, "tasks.md");
2314
+ const changeId = path5.basename(changeWorkspaceDir);
2315
+ const specFile = path5.join(changeWorkspaceDir, "spec.md");
2316
+ const planFile = path5.join(changeWorkspaceDir, "plan.md");
2317
+ const tasksFile = path5.join(changeWorkspaceDir, "tasks.md");
2035
2318
  return {
2036
2319
  framework: "speckit",
2037
2320
  changeId,
2038
2321
  changeDir: changeWorkspaceDir,
2039
2322
  profile,
2040
- specFile: fs7.existsSync(specFile) ? specFile : void 0,
2041
- designFile: fs7.existsSync(planFile) ? planFile : void 0,
2042
- tasksFile: fs7.existsSync(tasksFile) ? tasksFile : void 0,
2323
+ specFile: fs8.existsSync(specFile) ? specFile : void 0,
2324
+ designFile: fs8.existsSync(planFile) ? planFile : void 0,
2325
+ tasksFile: fs8.existsSync(tasksFile) ? tasksFile : void 0,
2043
2326
  sidecarFile,
2044
2327
  handoff: handoff || void 0
2045
2328
  };
@@ -2047,18 +2330,18 @@ function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecar
2047
2330
  var SpecKitAdapter = class {
2048
2331
  framework = "speckit";
2049
2332
  getChangeWorkspaceDir(rootDir, changeId) {
2050
- const standardDir = path4.join(rootDir, "specs", changeId);
2051
- if (fs7.existsSync(standardDir)) return standardDir;
2052
- const activeChangeDir = path4.join(rootDir, "specs", "changes", "active", changeId);
2053
- if (fs7.existsSync(activeChangeDir)) return activeChangeDir;
2054
- const completedChangeDir = path4.join(rootDir, "specs", "changes", "completed", changeId);
2055
- if (fs7.existsSync(completedChangeDir)) return completedChangeDir;
2333
+ const standardDir = path5.join(rootDir, "specs", changeId);
2334
+ if (fs8.existsSync(standardDir)) return standardDir;
2335
+ const activeChangeDir = path5.join(rootDir, "specs", "changes", "active", changeId);
2336
+ if (fs8.existsSync(activeChangeDir)) return activeChangeDir;
2337
+ const completedChangeDir = path5.join(rootDir, "specs", "changes", "completed", changeId);
2338
+ if (fs8.existsSync(completedChangeDir)) return completedChangeDir;
2056
2339
  return standardDir;
2057
2340
  }
2058
2341
  depositSidecar(options) {
2059
2342
  const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
2060
- if (!fs7.existsSync(changeDir)) {
2061
- fs7.mkdirSync(changeDir, { recursive: true });
2343
+ if (!fs8.existsSync(changeDir)) {
2344
+ fs8.mkdirSync(changeDir, { recursive: true });
2062
2345
  }
2063
2346
  const handoff = { ...options.handoff };
2064
2347
  if (!handoff.id.startsWith("HOF-")) {
@@ -2071,20 +2354,20 @@ var SpecKitAdapter = class {
2071
2354
  handoff.createdAt = (/* @__PURE__ */ new Date()).toISOString();
2072
2355
  }
2073
2356
  const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
2074
- const filePath = path4.join(changeDir, filename);
2357
+ const filePath = path5.join(changeDir, filename);
2075
2358
  const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3.dump(handoff, { indent: 2, lineWidth: -1 });
2076
- fs7.writeFileSync(filePath, content, "utf-8");
2077
- const specifyMemoryDir = path4.join(options.rootDir, ".specify", "memory");
2078
- if (fs7.existsSync(path4.join(options.rootDir, ".specify"))) {
2079
- if (!fs7.existsSync(specifyMemoryDir)) {
2080
- fs7.mkdirSync(specifyMemoryDir, { recursive: true });
2359
+ fs8.writeFileSync(filePath, content, "utf-8");
2360
+ const specifyMemoryDir = path5.join(options.rootDir, ".specify", "memory");
2361
+ if (fs8.existsSync(path5.join(options.rootDir, ".specify"))) {
2362
+ if (!fs8.existsSync(specifyMemoryDir)) {
2363
+ fs8.mkdirSync(specifyMemoryDir, { recursive: true });
2081
2364
  }
2082
- fs7.writeFileSync(path4.join(specifyMemoryDir, `${options.changeId}.handoff.yaml`), content, "utf-8");
2365
+ fs8.writeFileSync(path5.join(specifyMemoryDir, `${options.changeId}.handoff.yaml`), content, "utf-8");
2083
2366
  }
2084
2367
  return filePath;
2085
2368
  }
2086
2369
  loadSidecar(changeWorkspaceDir) {
2087
- if (!fs7.existsSync(changeWorkspaceDir)) return null;
2370
+ if (!fs8.existsSync(changeWorkspaceDir)) return null;
2088
2371
  const candidateFiles = [
2089
2372
  "handoff.yaml",
2090
2373
  "handoff.yml",
@@ -2094,10 +2377,10 @@ var SpecKitAdapter = class {
2094
2377
  "handoff.json"
2095
2378
  ];
2096
2379
  for (const file of candidateFiles) {
2097
- const fullPath = path4.join(changeWorkspaceDir, file);
2098
- if (fs7.existsSync(fullPath)) {
2380
+ const fullPath = path5.join(changeWorkspaceDir, file);
2381
+ if (fs8.existsSync(fullPath)) {
2099
2382
  try {
2100
- const raw = fs7.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
2383
+ const raw = fs8.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
2101
2384
  const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3.load(raw);
2102
2385
  if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
2103
2386
  return parsed;
@@ -2109,7 +2392,7 @@ var SpecKitAdapter = class {
2109
2392
  return null;
2110
2393
  }
2111
2394
  validateWorkspace(changeWorkspaceDir) {
2112
- if (!fs7.existsSync(changeWorkspaceDir)) {
2395
+ if (!fs8.existsSync(changeWorkspaceDir)) {
2113
2396
  return {
2114
2397
  valid: false,
2115
2398
  errors: [`El directorio del cambio Spec Kit no existe: ${changeWorkspaceDir}`],
@@ -2118,7 +2401,7 @@ var SpecKitAdapter = class {
2118
2401
  }
2119
2402
  const errors = [];
2120
2403
  const warnings = [];
2121
- const specFile = path4.join(changeWorkspaceDir, "spec.md");
2404
+ const specFile = path5.join(changeWorkspaceDir, "spec.md");
2122
2405
  const profile = parseChangeProfile2(specFile);
2123
2406
  validateSpecFileContent2(specFile, changeWorkspaceDir, errors);
2124
2407
  const { handoff, sidecarFile } = validateSpecKitStructure(profile, changeWorkspaceDir, this, errors, warnings);
@@ -2290,27 +2573,27 @@ ${frontmatter}---
2290
2573
  ${body}`;
2291
2574
  }
2292
2575
  function materializeCanonicalArtifact(rootDir, item, changeId, author, date) {
2293
- const targetPath = path4.join(rootDir, item.targetDir, `${item.id}.md`);
2294
- if (fs7.existsSync(targetPath)) return targetPath;
2295
- fs7.mkdirSync(path4.dirname(targetPath), { recursive: true });
2576
+ const targetPath = path5.join(rootDir, item.targetDir, `${item.id}.md`);
2577
+ if (fs8.existsSync(targetPath)) return targetPath;
2578
+ fs8.mkdirSync(path5.dirname(targetPath), { recursive: true });
2296
2579
  const initialContent = buildCanonicalInitialContent(item, changeId, author, date);
2297
- fs7.writeFileSync(targetPath, initialContent, "utf-8");
2580
+ fs8.writeFileSync(targetPath, initialContent, "utf-8");
2298
2581
  return targetPath;
2299
2582
  }
2300
2583
  function findChangeWorkspaceDir(rootDir, changeId) {
2301
- const activeDir = path4.join(rootDir, "specs", "changes", "active", changeId);
2302
- if (fs7.existsSync(activeDir)) return activeDir;
2303
- const completedDir = path4.join(rootDir, "specs", "changes", "completed", changeId);
2304
- if (fs7.existsSync(completedDir)) return completedDir;
2305
- const openspecActive = path4.join(rootDir, "openspec", "changes", changeId);
2306
- if (fs7.existsSync(openspecActive)) return openspecActive;
2584
+ const activeDir = path5.join(rootDir, "specs", "changes", "active", changeId);
2585
+ if (fs8.existsSync(activeDir)) return activeDir;
2586
+ const completedDir = path5.join(rootDir, "specs", "changes", "completed", changeId);
2587
+ if (fs8.existsSync(completedDir)) return completedDir;
2588
+ const openspecActive = path5.join(rootDir, "openspec", "changes", changeId);
2589
+ if (fs8.existsSync(openspecActive)) return openspecActive;
2307
2590
  return null;
2308
2591
  }
2309
2592
  function checkTasksExecution(changeDir, changeId) {
2310
- const tasksFile = path4.join(changeDir, "tasks.md");
2593
+ const tasksFile = path5.join(changeDir, "tasks.md");
2311
2594
  const allTasks = [];
2312
- if (fs7.existsSync(tasksFile)) {
2313
- allTasks.push(...parseTasksFromContent(fs7.readFileSync(tasksFile, "utf-8")));
2595
+ if (fs8.existsSync(tasksFile)) {
2596
+ allTasks.push(...parseTasksFromContent(fs8.readFileSync(tasksFile, "utf-8")));
2314
2597
  }
2315
2598
  const completedTasks = allTasks.filter((t) => t.status === "COMPLETED").map((t) => t.id);
2316
2599
  const pendingTasks = allTasks.filter((t) => t.status !== "COMPLETED").map((t) => t.id);
@@ -2342,9 +2625,9 @@ function buildSubgraphFromProposalIds(ids) {
2342
2625
  function resolveChangeHandoff(changeDir, changeId) {
2343
2626
  const sidecar = loadProductHandoffSidecar(changeDir);
2344
2627
  if (sidecar) return sidecar;
2345
- const proposalFile = path4.join(changeDir, "proposal.md");
2346
- if (!fs7.existsSync(proposalFile)) return null;
2347
- const propContent = fs7.readFileSync(proposalFile, "utf-8");
2628
+ const proposalFile = path5.join(changeDir, "proposal.md");
2629
+ if (!fs8.existsSync(proposalFile)) return null;
2630
+ const propContent = fs8.readFileSync(proposalFile, "utf-8");
2348
2631
  const { frontmatter } = parseFrontmatter(propContent);
2349
2632
  const citations = Array.isArray(frontmatter.citations) ? frontmatter.citations : [];
2350
2633
  const ids = citations.map((c) => c.id || "").filter(Boolean);
@@ -2358,9 +2641,9 @@ function resolveChangeHandoff(changeDir, changeId) {
2358
2641
  }
2359
2642
  function materializeNewCatalogArtifacts(changeDir, rootDir, changeId, author, date, errors) {
2360
2643
  const created = [];
2361
- const specFile = path4.join(changeDir, "spec.md");
2362
- if (!fs7.existsSync(specFile)) return created;
2363
- const specContent = fs7.readFileSync(specFile, "utf-8");
2644
+ const specFile = path5.join(changeDir, "spec.md");
2645
+ if (!fs8.existsSync(specFile)) return created;
2646
+ const specContent = fs8.readFileSync(specFile, "utf-8");
2364
2647
  const { newItems } = parseSpecCatalogScope(specContent);
2365
2648
  for (const item of newItems) {
2366
2649
  try {
@@ -2377,10 +2660,10 @@ function loadCanonicalArtifacts(rootDir) {
2377
2660
  const allMdFiles = walkMdFiles(rootDir);
2378
2661
  const canonicalArtifacts = /* @__PURE__ */ new Map();
2379
2662
  for (const file of allMdFiles) {
2380
- const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
2663
+ const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
2381
2664
  if (relFile.includes("specs/changes/") || relFile.includes("templates/")) continue;
2382
2665
  try {
2383
- const content = fs7.readFileSync(file, "utf-8");
2666
+ const content = fs8.readFileSync(file, "utf-8");
2384
2667
  const { frontmatter, body } = parseFrontmatter(content);
2385
2668
  if (frontmatter.id) canonicalArtifacts.set(String(frontmatter.id), { file, frontmatter, body });
2386
2669
  } catch {
@@ -2393,7 +2676,7 @@ function writeUpdatedArtifact(file, frontmatter, body) {
2393
2676
  ${yaml3.dump(frontmatter, { indent: 2, lineWidth: -1 }).trim()}
2394
2677
  ---
2395
2678
  `;
2396
- fs7.writeFileSync(file, yamlHeader + body, "utf-8");
2679
+ fs8.writeFileSync(file, yamlHeader + body, "utf-8");
2397
2680
  }
2398
2681
  function updateSingleCanonicalRequirement(canonical, completedTasks, changeId, date, author) {
2399
2682
  const fm = canonical.frontmatter;
@@ -2509,28 +2792,28 @@ function updateArchitectureComponents(citedComponents, canonicalArtifacts, targe
2509
2792
  return updated;
2510
2793
  }
2511
2794
  function moveToCompletedFolder(changeDir, changeId, rootDir) {
2512
- const completedParent = path4.join(rootDir, "specs", "changes", "completed");
2513
- if (!fs7.existsSync(completedParent)) fs7.mkdirSync(completedParent, { recursive: true });
2514
- const destDir = path4.join(completedParent, changeId);
2515
- if (fs7.existsSync(destDir)) fs7.rmSync(destDir, { recursive: true, force: true });
2516
- fs7.renameSync(changeDir, destDir);
2517
- const destProposal = path4.join(destDir, "proposal.md");
2518
- if (fs7.existsSync(destProposal)) {
2519
- const { frontmatter, body } = parseFrontmatter(fs7.readFileSync(destProposal, "utf-8"));
2795
+ const completedParent = path5.join(rootDir, "specs", "changes", "completed");
2796
+ if (!fs8.existsSync(completedParent)) fs8.mkdirSync(completedParent, { recursive: true });
2797
+ const destDir = path5.join(completedParent, changeId);
2798
+ if (fs8.existsSync(destDir)) fs8.rmSync(destDir, { recursive: true, force: true });
2799
+ fs8.renameSync(changeDir, destDir);
2800
+ const destProposal = path5.join(destDir, "proposal.md");
2801
+ if (fs8.existsSync(destProposal)) {
2802
+ const { frontmatter, body } = parseFrontmatter(fs8.readFileSync(destProposal, "utf-8"));
2520
2803
  frontmatter.status = "applied";
2521
2804
  writeUpdatedArtifact(destProposal, frontmatter, body);
2522
2805
  }
2523
2806
  return destDir;
2524
2807
  }
2525
2808
  function archiveChangeWorkspace(changeDir, changeId, rootDir, autoArchive, completedAll) {
2526
- const isActiveFolder = changeDir.includes("specs" + path4.sep + "changes" + path4.sep + "active");
2809
+ const isActiveFolder = changeDir.includes("specs" + path5.sep + "changes" + path5.sep + "active");
2527
2810
  if (completedAll && autoArchive && isActiveFolder) {
2528
2811
  const destDir = moveToCompletedFolder(changeDir, changeId, rootDir);
2529
2812
  return { archived: true, archivedPath: destDir };
2530
2813
  }
2531
- const proposalFile = path4.join(changeDir, "proposal.md");
2532
- if (fs7.existsSync(proposalFile)) {
2533
- const { frontmatter, body } = parseFrontmatter(fs7.readFileSync(proposalFile, "utf-8"));
2814
+ const proposalFile = path5.join(changeDir, "proposal.md");
2815
+ if (fs8.existsSync(proposalFile)) {
2816
+ const { frontmatter, body } = parseFrontmatter(fs8.readFileSync(proposalFile, "utf-8"));
2534
2817
  if (completedAll) frontmatter.status = "completed";
2535
2818
  writeUpdatedArtifact(proposalFile, frontmatter, body);
2536
2819
  }
@@ -2595,9 +2878,9 @@ function collectSubgraphPromotableIds(subgraph) {
2595
2878
  return Array.from(new Set(combined));
2596
2879
  }
2597
2880
  function collectSpecAffectedNonArchIds(changeDir) {
2598
- const specFile = path4.join(changeDir, "spec.md");
2599
- if (!fs7.existsSync(specFile)) return [];
2600
- const { affectedItems } = parseSpecCatalogScope(fs7.readFileSync(specFile, "utf-8"));
2881
+ const specFile = path5.join(changeDir, "spec.md");
2882
+ if (!fs8.existsSync(specFile)) return [];
2883
+ const { affectedItems } = parseSpecCatalogScope(fs8.readFileSync(specFile, "utf-8"));
2601
2884
  return affectedItems.map((item) => item.id).filter((id) => !isArchitectureComponentId(id));
2602
2885
  }
2603
2886
  function resolveIntegrationOptions(options) {
@@ -2671,8 +2954,8 @@ function resolveAuditStatus(integratedCount, targetCount, gapsCount) {
2671
2954
  function auditChangeEntry(changeDir, changeName, canonicalMap) {
2672
2955
  const handoff = loadProductHandoffSidecar(changeDir);
2673
2956
  if (!handoff) return null;
2674
- const tasksFile = path4.join(changeDir, "tasks.md");
2675
- const tasks = fs7.existsSync(tasksFile) ? parseTasksFromContent(fs7.readFileSync(tasksFile, "utf-8")) : [];
2957
+ const tasksFile = path5.join(changeDir, "tasks.md");
2958
+ const tasks = fs8.existsSync(tasksFile) ? parseTasksFromContent(fs8.readFileSync(tasksFile, "utf-8")) : [];
2676
2959
  const completed = tasks.filter((t) => t.status === "COMPLETED");
2677
2960
  const targetReqs = collectSubgraphPromotableIds(handoff.subgraph);
2678
2961
  const { integrated, missing, gaps } = auditChangeRequirements(targetReqs, canonicalMap);
@@ -2688,11 +2971,11 @@ function auditChangeEntry(changeDir, changeName, canonicalMap) {
2688
2971
  };
2689
2972
  }
2690
2973
  function collectParentDirAudits(parent, canonicalMap, audits, errors) {
2691
- if (!fs7.existsSync(parent)) return;
2692
- const entries = fs7.readdirSync(parent, { withFileTypes: true });
2974
+ if (!fs8.existsSync(parent)) return;
2975
+ const entries = fs8.readdirSync(parent, { withFileTypes: true });
2693
2976
  for (const e of entries) {
2694
2977
  if (!e.isDirectory()) continue;
2695
- const audit = auditChangeEntry(path4.join(parent, e.name), e.name, canonicalMap);
2978
+ const audit = auditChangeEntry(path5.join(parent, e.name), e.name, canonicalMap);
2696
2979
  if (audit) {
2697
2980
  if (audit.gaps.length > 0) errors.push(`[${e.name}] Desalineaci\xF3n: ${audit.gaps.join("; ")}`);
2698
2981
  audits.push(audit);
@@ -2704,8 +2987,8 @@ function verifySddIntegration(options = {}) {
2704
2987
  const audits = [];
2705
2988
  const errors = [];
2706
2989
  const searchDirs = [
2707
- path4.join(rootDir, "specs", "changes", "completed"),
2708
- path4.join(rootDir, "specs", "changes", "active")
2990
+ path5.join(rootDir, "specs", "changes", "completed"),
2991
+ path5.join(rootDir, "specs", "changes", "active")
2709
2992
  ];
2710
2993
  const canonicalMap = loadCanonicalArtifacts(rootDir);
2711
2994
  for (const parent of searchDirs) {
@@ -2753,10 +3036,10 @@ function resolveMatchFromMetadata(activeFolders, options) {
2753
3036
  return {};
2754
3037
  }
2755
3038
  function validateActiveDirectory(activeParent) {
2756
- if (!fs7.existsSync(activeParent)) {
3039
+ if (!fs8.existsSync(activeParent)) {
2757
3040
  return { ok: false, folders: [], error: `No existe el directorio de cambios activos en '${activeParent}'.` };
2758
3041
  }
2759
- const entries = fs7.readdirSync(activeParent, { withFileTypes: true });
3042
+ const entries = fs8.readdirSync(activeParent, { withFileTypes: true });
2760
3043
  const activeFolders = entries.filter((e) => e.isDirectory()).map((e) => e.name);
2761
3044
  if (activeFolders.length === 0) {
2762
3045
  return { ok: false, folders: [], error: "No se encontraron carpetas de cambios activos en specs/changes/active/." };
@@ -2788,7 +3071,7 @@ function evaluateTasksProgress(changeId, tasks) {
2788
3071
  }
2789
3072
  function detectActiveChangeForIntegration(options = {}) {
2790
3073
  const rootDir = options.rootDir || process.cwd();
2791
- const activeParent = path4.join(rootDir, "specs", "changes", "active");
3074
+ const activeParent = path5.join(rootDir, "specs", "changes", "active");
2792
3075
  const dirCheck = validateActiveDirectory(activeParent);
2793
3076
  if (!dirCheck.ok) {
2794
3077
  return {
@@ -2803,9 +3086,9 @@ function detectActiveChangeForIntegration(options = {}) {
2803
3086
  if (!match.changeId) {
2804
3087
  return buildUnmatchedActiveResult(dirCheck.folders, options);
2805
3088
  }
2806
- const changeDir = path4.join(activeParent, match.changeId);
2807
- const tasksFile = path4.join(changeDir, "tasks.md");
2808
- const tasks = fs7.existsSync(tasksFile) ? parseTasksFromContent(fs7.readFileSync(tasksFile, "utf-8")) : [];
3089
+ const changeDir = path5.join(activeParent, match.changeId);
3090
+ const tasksFile = path5.join(changeDir, "tasks.md");
3091
+ const tasks = fs8.existsSync(tasksFile) ? parseTasksFromContent(fs8.readFileSync(tasksFile, "utf-8")) : [];
2809
3092
  const progress = evaluateTasksProgress(match.changeId, tasks);
2810
3093
  const reasons = match.reason ? [match.reason] : [];
2811
3094
  if (progress.error) reasons.push(progress.error);
@@ -2820,9 +3103,46 @@ function detectActiveChangeForIntegration(options = {}) {
2820
3103
  reasons
2821
3104
  };
2822
3105
  }
2823
- function computeCanonicalSha256(content) {
3106
+ function extractSemanticContent(content) {
3107
+ let normalized = content.replace(/\r\n/g, "\n");
3108
+ normalized = normalized.replace(/(?:\n|^)##?\s+.*(?:Historial de Revisiones|Revision History)(?:[\s\S]*?(?=(?:\n##?\s+[^\n]+)|\s*$))/i, "");
3109
+ normalized = normalized.replace(/(?:\n|^)\|\s*\*?Versi[oó]n\*?\s*\|\s*Fecha\s*\|\s*Autor[\s\S]*?(?=(?:\n##?\s+[^\n]+)|\s*$)/i, "");
3110
+ return normalized.trimEnd() + "\n";
3111
+ }
3112
+ function slugify(text) {
3113
+ return text.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-");
3114
+ }
3115
+ function matchesSlug(line, clean) {
3116
+ const headingText = line.replace(/^#+\s*/, "").trim();
3117
+ const slug = slugify(headingText);
3118
+ if (slug === clean || slug.includes(clean) || clean.includes(slug)) return true;
3119
+ const slugNoNums = slugify(headingText.replace(/^\d+[\.\)]\s*/, "").trim());
3120
+ return slugNoNums === clean || clean === slugNoNums;
3121
+ }
3122
+ function matchesHeading(content, clean) {
3123
+ for (const line of content.split(/\r?\n/)) {
3124
+ const trimmed = line.trim();
3125
+ if (trimmed.startsWith("#") && matchesSlug(trimmed, clean)) {
3126
+ return true;
3127
+ }
3128
+ }
3129
+ return false;
3130
+ }
3131
+ function validateAnchorExistence(targetContent, anchor) {
3132
+ if (!anchor) return true;
3133
+ const clean = anchor.replace(/^#/, "").trim().toLowerCase();
3134
+ if (!clean || targetContent.toLowerCase().includes(clean)) return true;
3135
+ if (matchesHeading(targetContent, clean)) return true;
3136
+ const anchorRegex = new RegExp(`(?:id|name)=["']${clean}["']|\\{#${clean}\\}`, "i");
3137
+ return anchorRegex.test(targetContent);
3138
+ }
3139
+ function computeRawSha256(content) {
2824
3140
  const normalized = content.replace(/\r\n/g, "\n");
2825
- return crypto.createHash("sha256").update(normalized, "utf-8").digest("hex");
3141
+ return crypto3.createHash("sha256").update(normalized, "utf-8").digest("hex");
3142
+ }
3143
+ function computeCanonicalSha256(content) {
3144
+ const semantic = extractSemanticContent(content);
3145
+ return crypto3.createHash("sha256").update(semantic, "utf-8").digest("hex");
2826
3146
  }
2827
3147
  function parseFrontmatterId(content) {
2828
3148
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -2912,7 +3232,7 @@ function buildPdacReportHeader(isOk, driftsCount, nodesCount) {
2912
3232
  return [
2913
3233
  `# \u{1F512} PDaC Cryptographic Matrix and Drift Detection (Anti-Drift)`,
2914
3234
  ``,
2915
- `> **Evaluation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
3235
+ `> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
2916
3236
  `> **Verdict:** ${verdict}`,
2917
3237
  ``,
2918
3238
  `---`,
@@ -2923,8 +3243,8 @@ function buildPdacReportHeader(isOk, driftsCount, nodesCount) {
2923
3243
  `| :--- | :--- | :--- | :--- |`
2924
3244
  ];
2925
3245
  }
2926
- function generatePdacReportMarkdown(nodes, drifts, unresolvedCount) {
2927
- const isOk = drifts.length === 0 && unresolvedCount === 0;
3246
+ function generatePdacReportMarkdown(nodes, drifts, unresolvedCount, invalidAnchors = []) {
3247
+ const isOk = drifts.length === 0 && unresolvedCount === 0 && invalidAnchors.length === 0;
2928
3248
  const header = buildPdacReportHeader(isOk, drifts.length, nodes.length);
2929
3249
  const nodeRows = nodes.map(
2930
3250
  (n) => `| **\`${n.id}\`** | \`${n.type}\` | ${n.title || "Untitled"} | \`sha256:${n.digest.substring(0, 16)}...\` |`
@@ -2936,9 +3256,9 @@ function isPdacDeliveryDoc(relFile) {
2936
3256
  }
2937
3257
  function processPdacFile(file, rootDir, baselineMap, allCitations) {
2938
3258
  try {
2939
- const content = fs7.readFileSync(file, "utf-8");
3259
+ const content = fs8.readFileSync(file, "utf-8");
2940
3260
  const { id, type, title } = parseFrontmatterId(content);
2941
- const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
3261
+ const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
2942
3262
  if (id) {
2943
3263
  const citations = extractCitations(content, relFile);
2944
3264
  baselineMap.set(id, {
@@ -2947,6 +3267,7 @@ function processPdacFile(file, rootDir, baselineMap, allCitations) {
2947
3267
  filePath: relFile,
2948
3268
  title,
2949
3269
  digest: computeCanonicalSha256(content),
3270
+ rawDigest: computeRawSha256(content),
2950
3271
  rawContent: content,
2951
3272
  citations: citations.map((c) => ({
2952
3273
  targetId: c.targetId,
@@ -2970,38 +3291,65 @@ function scanPdacWorkspace(rootDir) {
2970
3291
  }
2971
3292
  return { baselineMap, allCitations };
2972
3293
  }
3294
+ function checkAnchor(citation, targetNode, invalidAnchors) {
3295
+ if (citation.anchor && targetNode.rawContent) {
3296
+ if (!validateAnchorExistence(targetNode.rawContent, citation.anchor)) {
3297
+ invalidAnchors.push({
3298
+ sourceFile: citation.sourceFile,
3299
+ targetId: citation.targetId,
3300
+ anchor: citation.anchor,
3301
+ reason: `Anchor '${citation.anchor}' not found in target artifact '${citation.targetId}' (${targetNode.filePath})`
3302
+ });
3303
+ }
3304
+ }
3305
+ }
3306
+ function checkDigest(citation, targetNode, drifts) {
3307
+ if (!citation.expectedDigest) return;
3308
+ const matches = citation.expectedDigest === targetNode.digest || targetNode.rawDigest !== void 0 && citation.expectedDigest === targetNode.rawDigest;
3309
+ if (!matches) {
3310
+ drifts.push({
3311
+ sourceId: citation.sourceFile,
3312
+ targetId: citation.targetId,
3313
+ sourceFile: citation.sourceFile,
3314
+ expectedDigest: citation.expectedDigest,
3315
+ actualDigest: targetNode.digest
3316
+ });
3317
+ }
3318
+ }
2973
3319
  function computePdacDrifts(allCitations, baselineMap) {
2974
3320
  const drifts = [];
3321
+ const unresolvedCitations = [];
3322
+ const invalidAnchors = [];
2975
3323
  let unresolvedCount = 0;
2976
3324
  for (const citation of allCitations) {
2977
3325
  const targetNode = baselineMap.get(citation.targetId);
2978
3326
  if (!targetNode) {
2979
3327
  unresolvedCount++;
2980
- continue;
2981
- }
2982
- if (citation.expectedDigest && citation.expectedDigest !== targetNode.digest) {
2983
- drifts.push({
2984
- sourceId: citation.sourceFile,
2985
- targetId: citation.targetId,
3328
+ unresolvedCitations.push({
2986
3329
  sourceFile: citation.sourceFile,
2987
- expectedDigest: citation.expectedDigest,
2988
- actualDigest: targetNode.digest
3330
+ targetId: citation.targetId
2989
3331
  });
3332
+ continue;
2990
3333
  }
3334
+ checkAnchor(citation, targetNode, invalidAnchors);
3335
+ checkDigest(citation, targetNode, drifts);
2991
3336
  }
2992
- return { drifts, unresolvedCount };
3337
+ return { drifts, unresolvedCount, unresolvedCitations, invalidAnchors };
2993
3338
  }
2994
3339
  function verifyPdacGraph(options = {}) {
2995
3340
  const rootDir = options.rootDir || process.cwd();
2996
3341
  const { baselineMap, allCitations } = scanPdacWorkspace(rootDir);
2997
- const { drifts, unresolvedCount } = computePdacDrifts(allCitations, baselineMap);
3342
+ const { drifts, unresolvedCount, unresolvedCitations, invalidAnchors } = computePdacDrifts(allCitations, baselineMap);
2998
3343
  const nodes = Array.from(baselineMap.values());
2999
- const reportMarkdown = generatePdacReportMarkdown(nodes, drifts, unresolvedCount);
3344
+ const reportMarkdown = generatePdacReportMarkdown(nodes, drifts, unresolvedCount, invalidAnchors);
3000
3345
  return {
3001
- success: drifts.length === 0,
3346
+ success: drifts.length === 0 && unresolvedCount === 0 && invalidAnchors.length === 0,
3002
3347
  totalNodes: nodes.length,
3003
3348
  totalEdges: allCitations.length,
3004
3349
  drifts,
3350
+ unresolvedCount,
3351
+ unresolvedCitations,
3352
+ invalidAnchors,
3005
3353
  reportMarkdown
3006
3354
  };
3007
3355
  }
@@ -3066,22 +3414,22 @@ function updateCitationsInContent(content, baselineMap, sourceFile, details) {
3066
3414
  }
3067
3415
  function collectCandidateFilesForSync(rootDir, targetPath) {
3068
3416
  if (targetPath) {
3069
- const fullTarget = path4.isAbsolute(targetPath) ? targetPath : path4.join(rootDir, targetPath);
3070
- if (!fs7.existsSync(fullTarget)) return [];
3071
- return fs7.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
3417
+ const fullTarget = path5.isAbsolute(targetPath) ? targetPath : path5.join(rootDir, targetPath);
3418
+ if (!fs8.existsSync(fullTarget)) return [];
3419
+ return fs8.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
3072
3420
  }
3073
3421
  return walkMdFiles(rootDir).filter((f) => {
3074
- const relFile = path4.relative(rootDir, f).replace(/\\/g, "/");
3422
+ const relFile = path5.relative(rootDir, f).replace(/\\/g, "/");
3075
3423
  return isPdacDeliveryDoc(relFile);
3076
3424
  });
3077
3425
  }
3078
3426
  function syncSingleFile(file, rootDir, baselineMap, details) {
3079
3427
  try {
3080
- const content = fs7.readFileSync(file, "utf-8");
3081
- const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
3428
+ const content = fs8.readFileSync(file, "utf-8");
3429
+ const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
3082
3430
  const { updatedContent, changed } = updateCitationsInContent(content, baselineMap, relFile, details);
3083
3431
  if (changed) {
3084
- fs7.writeFileSync(file, updatedContent, "utf-8");
3432
+ fs8.writeFileSync(file, updatedContent, "utf-8");
3085
3433
  return relFile;
3086
3434
  }
3087
3435
  } catch {
@@ -3284,6 +3632,36 @@ var STARTER_SCHEMAS = Object.entries(STARTER_SCHEMAS_MAP).map(([relPath, schema]
3284
3632
  }));
3285
3633
 
3286
3634
  // src/verifiers/schemas.ts
3635
+ var ajvInstance = new Ajv2020({
3636
+ allErrors: true,
3637
+ strict: false,
3638
+ validateFormats: true,
3639
+ verbose: true
3640
+ });
3641
+ addFormats(ajvInstance);
3642
+ var compiledSchemaCache = /* @__PURE__ */ new WeakMap();
3643
+ function formatFieldPath(err, propPath) {
3644
+ const field = err.instancePath ? err.instancePath.replace(/^\//, "").replace(/\//g, ".") : propPath || "root";
3645
+ return propPath && field !== "root" && field ? `${propPath}.${field}` : field || "root";
3646
+ }
3647
+ var KEYWORD_FORMATTERS = {
3648
+ required: (p, _err, fullPath) => `'${fullPath}' missing required field '${String(p?.missingProperty)}'`,
3649
+ additionalProperties: (p, _err, fullPath) => `'${fullPath}' contains disallowed extra property '${String(p?.additionalProperty)}'`,
3650
+ minLength: (p, _err, fullPath) => `'${fullPath}' length must be >= ${String(p?.limit)}`,
3651
+ maxLength: (p, _err, fullPath) => `'${fullPath}' length must be <= ${String(p?.limit)}`,
3652
+ enum: (p, err, fullPath) => `'${fullPath}' must be one of [${Array.isArray(p?.allowedValues) ? p.allowedValues.join(", ") : ""}], received '${String(err.data)}'`,
3653
+ uniqueItems: (_p, _err, fullPath) => `'${fullPath}' contains duplicate items (must have unique items)`,
3654
+ pattern: (p, err, fullPath) => `'${fullPath}' value '${String(err.data)}' does not match pattern ${String(p?.pattern)}`
3655
+ };
3656
+ function formatKeywordMessage(err, fullPath) {
3657
+ const p = err.params;
3658
+ const formatter = KEYWORD_FORMATTERS[err.keyword];
3659
+ return formatter ? formatter(p, err, fullPath) : `'${fullPath}' ${err.message || "validation failed"}`;
3660
+ }
3661
+ function formatAjvError(err, propPath) {
3662
+ const fullPath = formatFieldPath(err, propPath);
3663
+ return formatKeywordMessage(err, fullPath);
3664
+ }
3287
3665
  var TYPE_TO_SCHEMA_MAP = {
3288
3666
  // Product
3289
3667
  actor: "schemas/product/actor.schema.json",
@@ -3428,10 +3806,10 @@ var EXCLUDED_SCHEMA_DIRS = /* @__PURE__ */ new Set([
3428
3806
  ]);
3429
3807
  function walkHandoffSidecars(dir, fileList = []) {
3430
3808
  try {
3431
- if (!fs7.existsSync(dir)) return fileList;
3432
- for (const file of fs7.readdirSync(dir)) {
3433
- const fullPath = path4.join(dir, file);
3434
- if (fs7.statSync(fullPath).isDirectory()) {
3809
+ if (!fs8.existsSync(dir)) return fileList;
3810
+ for (const file of fs8.readdirSync(dir)) {
3811
+ const fullPath = path5.join(dir, file);
3812
+ if (fs8.statSync(fullPath).isDirectory()) {
3435
3813
  if (!EXCLUDED_SCHEMA_DIRS.has(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
3436
3814
  walkHandoffSidecars(fullPath, fileList);
3437
3815
  }
@@ -3454,157 +3832,41 @@ function extractArtifactData(filePath, content) {
3454
3832
  }
3455
3833
  return extractFrontmatter(content);
3456
3834
  }
3457
- function validateConst(value, schema, currentProp) {
3458
- if (schema.const !== void 0 && value !== schema.const) {
3459
- return [`'${currentProp}' must equal '${schema.const}', received '${String(value)}'`];
3835
+ function getOrCompileSchema(schema) {
3836
+ const schemaObj = schema;
3837
+ if (typeof schemaObj.$id === "string") {
3838
+ const existing = ajvInstance.getSchema(schemaObj.$id);
3839
+ if (existing) return existing;
3840
+ return ajvInstance.compile(schema);
3460
3841
  }
3461
- return [];
3462
- }
3463
- function validateEnum(value, schema, currentProp) {
3464
- if (schema.enum !== void 0 && !schema.enum.includes(value)) {
3465
- return [`'${currentProp}' must be one of [${schema.enum.join(", ")}], received '${String(value)}'`];
3466
- }
3467
- return [];
3468
- }
3469
- function validateCombinators(value, schema, propPath, currentProp) {
3470
- if (schema.oneOf) {
3471
- const matchCount = schema.oneOf.filter((sub) => validateAgainstSchema(value, sub, propPath).length === 0).length;
3472
- if (matchCount !== 1) {
3473
- return [`'${currentProp}' must match exactly one sub-schema in oneOf`];
3474
- }
3475
- }
3476
- if (schema.anyOf) {
3477
- const anyMatched = schema.anyOf.some((sub) => validateAgainstSchema(value, sub, propPath).length === 0);
3478
- if (!anyMatched) {
3479
- return [`'${currentProp}' did not match any allowed alternative in anyOf`];
3480
- }
3481
- }
3482
- return [];
3483
- }
3484
- function getActualJsonType(value) {
3485
- if (value === null) return "null";
3486
- if (Array.isArray(value)) return "array";
3487
- const t = typeof value;
3488
- if (t === "number" && Number.isInteger(value)) return "integer";
3489
- return t;
3490
- }
3491
- function validateType(value, schema, currentProp) {
3492
- if (schema.type === void 0) return [];
3493
- const allowed = Array.isArray(schema.type) ? schema.type : [schema.type];
3494
- const actual = getActualJsonType(value);
3495
- const matches = allowed.some((t) => t === actual || t === "number" && actual === "integer");
3496
- if (!matches) {
3497
- return [`'${currentProp}' expected type [${allowed.join("|")}], received '${actual}'`];
3498
- }
3499
- return [];
3500
- }
3501
- function validateString(value, schema, currentProp) {
3502
- const errors = [];
3503
- if (schema.minLength !== void 0 && value.length < schema.minLength) {
3504
- errors.push(`'${currentProp}' length must be >= ${schema.minLength}`);
3505
- }
3506
- if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
3507
- errors.push(`'${currentProp}' length must be <= ${schema.maxLength}`);
3508
- }
3509
- if (schema.pattern !== void 0 && !new RegExp(schema.pattern).test(value)) {
3510
- errors.push(`'${currentProp}' value '${value}' does not match pattern ${schema.pattern}`);
3511
- }
3512
- if (schema.format === "date") {
3513
- const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
3514
- if (!dateRegex.test(value) || isNaN(Date.parse(value))) {
3515
- errors.push(`'${currentProp}' must be a valid ISO-8601 date string (YYYY-MM-DD)`);
3516
- }
3517
- }
3518
- return errors;
3519
- }
3520
- function validateNumber(value, schema, currentProp) {
3521
- const errors = [];
3522
- if (schema.minimum !== void 0 && value < schema.minimum) {
3523
- errors.push(`'${currentProp}' must be >= ${schema.minimum}`);
3524
- }
3525
- if (schema.maximum !== void 0 && value > schema.maximum) {
3526
- errors.push(`'${currentProp}' must be <= ${schema.maximum}`);
3527
- }
3528
- return errors;
3529
- }
3530
- function validateArray(value, schema, currentProp) {
3531
- const errors = [];
3532
- if (schema.minItems !== void 0 && value.length < schema.minItems) {
3533
- errors.push(`'${currentProp}' items count must be >= ${schema.minItems}`);
3534
- }
3535
- if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
3536
- errors.push(`'${currentProp}' items count must be <= ${schema.maxItems}`);
3537
- }
3538
- if (schema.items) {
3539
- value.forEach((item, idx) => {
3540
- errors.push(...validateAgainstSchema(item, schema.items, `${currentProp}[${idx}]`));
3541
- });
3542
- }
3543
- return errors;
3544
- }
3545
- function checkRequiredProperties(record, required, currentProp) {
3546
- if (!required) return [];
3547
- const errors = [];
3548
- for (const reqKey of required) {
3549
- if (!(reqKey in record) || record[reqKey] === void 0) {
3550
- errors.push(`'${currentProp}' missing required field '${reqKey}'`);
3551
- }
3552
- }
3553
- return errors;
3554
- }
3555
- function checkObjectProperties(record, schema, propPath, currentProp) {
3556
- const errors = [];
3557
- const properties = schema.properties || {};
3558
- for (const [key, propVal] of Object.entries(record)) {
3559
- const subPath = propPath ? `${propPath}.${key}` : key;
3560
- if (key in properties) {
3561
- errors.push(...validateAgainstSchema(propVal, properties[key], subPath));
3562
- } else if (schema.additionalProperties === false) {
3563
- errors.push(`'${currentProp}' contains disallowed extra property '${key}'`);
3564
- } else if (typeof schema.additionalProperties === "object") {
3565
- errors.push(...validateAgainstSchema(propVal, schema.additionalProperties, subPath));
3566
- }
3567
- }
3568
- return errors;
3569
- }
3570
- function validateObject(record, schema, propPath, currentProp) {
3571
- return [
3572
- ...checkRequiredProperties(record, schema.required, currentProp),
3573
- ...checkObjectProperties(record, schema, propPath, currentProp)
3574
- ];
3575
- }
3576
- function validateTypeSpecificRules(value, schema, propPath, currentProp) {
3577
- if (typeof value === "string") return validateString(value, schema, currentProp);
3578
- if (typeof value === "number") return validateNumber(value, schema, currentProp);
3579
- if (Array.isArray(value)) return validateArray(value, schema, currentProp);
3580
- if (typeof value === "object" && value !== null) {
3581
- return validateObject(value, schema, propPath, currentProp);
3582
- }
3583
- return [];
3842
+ const cached = compiledSchemaCache.get(schema);
3843
+ if (cached) return cached;
3844
+ const compiled = ajvInstance.compile(schema);
3845
+ compiledSchemaCache.set(schema, compiled);
3846
+ return compiled;
3584
3847
  }
3585
3848
  function validateAgainstSchema(value, schema, propPath = "") {
3586
- const currentProp = propPath || "root";
3587
- const constErr = validateConst(value, schema, currentProp);
3588
- if (constErr.length > 0) return constErr;
3589
- const enumErr = validateEnum(value, schema, currentProp);
3590
- if (enumErr.length > 0) return enumErr;
3591
- const combErr = validateCombinators(value, schema, propPath, currentProp);
3592
- if (combErr.length > 0) return combErr;
3593
- const typeErr = validateType(value, schema, currentProp);
3594
- if (typeErr.length > 0) return typeErr;
3595
- return validateTypeSpecificRules(value, schema, propPath, currentProp);
3849
+ if (!schema || typeof schema !== "object") return [];
3850
+ try {
3851
+ const validate = getOrCompileSchema(schema);
3852
+ const valid = validate(value);
3853
+ if (valid || !validate.errors) return [];
3854
+ return validate.errors.map((err) => formatAjvError(err, propPath));
3855
+ } catch (err) {
3856
+ return [`Schema error: ${err instanceof Error ? err.message : String(err)}`];
3857
+ }
3596
3858
  }
3597
3859
  function findSchemaFile(relSchemaPath, rootDir) {
3598
- const fullPath = path4.join(rootDir, relSchemaPath);
3599
- if (fs7.existsSync(fullPath)) return fullPath;
3600
- const fallbackPath = path4.join(process.cwd(), relSchemaPath);
3601
- if (fs7.existsSync(fallbackPath)) return fallbackPath;
3860
+ const fullPath = path5.join(rootDir, relSchemaPath);
3861
+ if (fs8.existsSync(fullPath)) return fullPath;
3862
+ const fallbackPath = path5.join(process.cwd(), relSchemaPath);
3863
+ if (fs8.existsSync(fallbackPath)) return fallbackPath;
3602
3864
  return null;
3603
3865
  }
3604
3866
  function loadJsonSchema(fullPath, relSchemaPath) {
3605
- if (fullPath && fs7.existsSync(fullPath)) {
3867
+ if (fullPath && fs8.existsSync(fullPath)) {
3606
3868
  try {
3607
- return JSON.parse(fs7.readFileSync(fullPath, "utf-8"));
3869
+ return JSON.parse(fs8.readFileSync(fullPath, "utf-8"));
3608
3870
  } catch {
3609
3871
  }
3610
3872
  }
@@ -3619,7 +3881,7 @@ function resolveSchemaForArtifact(frontmatter, rootDir) {
3619
3881
  }
3620
3882
  function loadSchemaFromPathOrType(schemaOrType, rootDir) {
3621
3883
  if (schemaOrType.endsWith(".json")) {
3622
- const fullPath = path4.isAbsolute(schemaOrType) ? schemaOrType : findSchemaFile(schemaOrType, rootDir);
3884
+ const fullPath = path5.isAbsolute(schemaOrType) ? schemaOrType : findSchemaFile(schemaOrType, rootDir);
3623
3885
  const relNormalized = schemaOrType.replace(/\\/g, "/");
3624
3886
  const schema = loadJsonSchema(fullPath, relNormalized);
3625
3887
  return { schema, schemaId: schemaOrType };
@@ -3664,9 +3926,9 @@ function collectCandidateSchemaFiles(options, rootDir) {
3664
3926
  if (!options.targetPath) {
3665
3927
  return [...walkMdFiles(rootDir), ...walkHandoffSidecars(rootDir)];
3666
3928
  }
3667
- const target = path4.isAbsolute(options.targetPath) ? options.targetPath : path4.join(rootDir, options.targetPath);
3668
- if (!fs7.existsSync(target)) return [];
3669
- if (fs7.statSync(target).isDirectory()) {
3929
+ const target = path5.isAbsolute(options.targetPath) ? options.targetPath : path5.join(rootDir, options.targetPath);
3930
+ if (!fs8.existsSync(target)) return [];
3931
+ if (fs8.statSync(target).isDirectory()) {
3670
3932
  return [...walkMdFiles(target), ...walkHandoffSidecars(target)];
3671
3933
  }
3672
3934
  if (target.endsWith(".md") || target.endsWith(".yaml") || target.endsWith(".yml")) {
@@ -3717,7 +3979,7 @@ function buildEvaluationFailure(relPath, err) {
3717
3979
  }
3718
3980
  function evaluateArtifactFile(file, relPath, rootDir) {
3719
3981
  try {
3720
- const content = fs7.readFileSync(file, "utf-8");
3982
+ const content = fs8.readFileSync(file, "utf-8");
3721
3983
  const frontmatter = extractArtifactData(file, content);
3722
3984
  if (!frontmatter || !frontmatter.id) return null;
3723
3985
  const schemaInfo = resolveSchemaForArtifact(frontmatter, rootDir);
@@ -3752,7 +4014,7 @@ function verifyArtifactsSchemas(options = {}) {
3752
4014
  let validCount = 0;
3753
4015
  let totalEvaluated = 0;
3754
4016
  for (const file of filesToScan) {
3755
- const relPath = path4.relative(rootDir, file).replace(/\\/g, "/");
4017
+ const relPath = path5.relative(rootDir, file).replace(/\\/g, "/");
3756
4018
  if (isIgnoredArtifactPath(relPath, Boolean(options.targetPath))) continue;
3757
4019
  const evaluation = evaluateArtifactFile(file, relPath, rootDir);
3758
4020
  if (!evaluation) continue;
@@ -3767,14 +4029,14 @@ function verifyArtifactsSchemas(options = {}) {
3767
4029
  }
3768
4030
 
3769
4031
  // src/adapters/sdd/scaffold.ts
3770
- function slugify(text) {
4032
+ function slugify2(text) {
3771
4033
  return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3772
4034
  }
3773
4035
  function extractDirMaxNum(dir) {
3774
- if (!fs7.existsSync(dir)) return 0;
4036
+ if (!fs8.existsSync(dir)) return 0;
3775
4037
  let max = 0;
3776
4038
  try {
3777
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
4039
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
3778
4040
  for (const e of entries) {
3779
4041
  if (!e.isDirectory()) continue;
3780
4042
  const match = e.name.match(/^(?:chg|CHG|patch|PATCH)-(\d+)/i);
@@ -3791,10 +4053,10 @@ function extractDirMaxNum(dir) {
3791
4053
  }
3792
4054
  function getNextCorrelativeNumber(rootDir) {
3793
4055
  const candidateDirs = [
3794
- path4.join(rootDir, "specs", "changes", "active"),
3795
- path4.join(rootDir, "specs", "changes", "completed"),
3796
- path4.join(rootDir, "specs"),
3797
- path4.join(rootDir, "openspec", "changes")
4056
+ path5.join(rootDir, "specs", "changes", "active"),
4057
+ path5.join(rootDir, "specs", "changes", "completed"),
4058
+ path5.join(rootDir, "specs"),
4059
+ path5.join(rootDir, "openspec", "changes")
3798
4060
  ];
3799
4061
  let maxNum = 0;
3800
4062
  for (const dir of candidateDirs) {
@@ -3807,7 +4069,7 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
3807
4069
  const targetChangeId = options.changeId || options.id;
3808
4070
  if (targetChangeId) {
3809
4071
  const rawId = targetChangeId.trim();
3810
- const slug = slugify(rawId);
4072
+ const slug = slugify2(rawId);
3811
4073
  const canonicalId = rawId.toUpperCase();
3812
4074
  const match = rawId.match(/^(?:chg|CHG|patch|PATCH)-(\d+)/i);
3813
4075
  const padNum2 = match ? match[1] : "001";
@@ -3815,7 +4077,7 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
3815
4077
  }
3816
4078
  const nextNum = getNextCorrelativeNumber(rootDir);
3817
4079
  const padNum = String(nextNum).padStart(3, "0");
3818
- const nameSlug = slugify(name);
4080
+ const nameSlug = slugify2(name);
3819
4081
  const prefix = profile === "patch" ? "patch" : "chg";
3820
4082
  return {
3821
4083
  slug: `${prefix}-${padNum}-${nameSlug}`,
@@ -3824,13 +4086,13 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
3824
4086
  };
3825
4087
  }
3826
4088
  function ensureEmptyChangeDir(changeDir) {
3827
- if (fs7.existsSync(changeDir)) {
3828
- const existing = fs7.readdirSync(changeDir);
4089
+ if (fs8.existsSync(changeDir)) {
4090
+ const existing = fs8.readdirSync(changeDir);
3829
4091
  if (existing.length > 0) {
3830
4092
  return `El directorio del cambio '${changeDir}' ya existe y no est\xE1 vac\xEDo.`;
3831
4093
  }
3832
4094
  } else {
3833
- fs7.mkdirSync(changeDir, { recursive: true });
4095
+ fs8.mkdirSync(changeDir, { recursive: true });
3834
4096
  }
3835
4097
  return null;
3836
4098
  }
@@ -3839,7 +4101,7 @@ function scanCanonicalArtifacts(rootDir) {
3839
4101
  const artifactMap = /* @__PURE__ */ new Map();
3840
4102
  for (const file of allMdFiles) {
3841
4103
  try {
3842
- const content = fs7.readFileSync(file, "utf-8");
4104
+ const content = fs8.readFileSync(file, "utf-8");
3843
4105
  const { frontmatter } = parseFrontmatter(content);
3844
4106
  if (frontmatter?.id) {
3845
4107
  const id = String(frontmatter.id);
@@ -4218,7 +4480,7 @@ function buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors)
4218
4480
  changeId: slug,
4219
4481
  canonicalId,
4220
4482
  profile: "patch",
4221
- changeDir: path4.dirname(specPath),
4483
+ changeDir: path5.dirname(specPath),
4222
4484
  createdFiles: [specPath],
4223
4485
  citedArtifacts,
4224
4486
  errors
@@ -4229,10 +4491,10 @@ function scaffoldPatchChange(rootDir, changeDir, slug, canonicalId, name, inputF
4229
4491
  const citationsSection = citedArtifacts.length > 0 ? `citations:
4230
4492
  ${renderCitationsYamlBlock(citedArtifacts)}
4231
4493
  ` : "";
4232
- const specPath = path4.join(changeDir, "spec.md");
4494
+ const specPath = path5.join(changeDir, "spec.md");
4233
4495
  const specFmObj = buildPatchFmObj(canonicalId);
4234
4496
  const specContent = buildPatchSpecContent(canonicalId, name, citationsSection);
4235
- fs7.writeFileSync(specPath, specContent, "utf-8");
4497
+ fs8.writeFileSync(specPath, specContent, "utf-8");
4236
4498
  const errors = validatePatchArtifacts(rootDir, changeDir, specContent, specFmObj, adapter);
4237
4499
  return buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors);
4238
4500
  }
@@ -4243,46 +4505,46 @@ function buildGreenfieldContent(reqId, ucId, name, canonicalId, author, dateStr)
4243
4505
  ${body}`;
4244
4506
  }
4245
4507
  function createGreenfieldRequirement(rootDir, name, padNum, canonicalId, author, dateStr, createdFiles) {
4246
- const nameSlugUpper = slugify(name).toUpperCase();
4508
+ const nameSlugUpper = slugify2(name).toUpperCase();
4247
4509
  const reqId = `FR-${padNum}-${nameSlugUpper}-001`;
4248
4510
  const ucId = `UC-${padNum}-${nameSlugUpper}`;
4249
- const productDir = path4.join(rootDir, "specs", "product");
4250
- if (!fs7.existsSync(productDir)) fs7.mkdirSync(productDir, { recursive: true });
4251
- const reqFilePath = path4.join(productDir, `${reqId}.md`);
4252
- if (fs7.existsSync(reqFilePath)) return null;
4511
+ const productDir = path5.join(rootDir, "specs", "product");
4512
+ if (!fs8.existsSync(productDir)) fs8.mkdirSync(productDir, { recursive: true });
4513
+ const reqFilePath = path5.join(productDir, `${reqId}.md`);
4514
+ if (fs8.existsSync(reqFilePath)) return null;
4253
4515
  const reqContent = buildGreenfieldContent(reqId, ucId, name, canonicalId, author, dateStr);
4254
- fs7.writeFileSync(reqFilePath, reqContent, "utf-8");
4516
+ fs8.writeFileSync(reqFilePath, reqContent, "utf-8");
4255
4517
  createdFiles.push(reqFilePath);
4256
4518
  const digest = computeCanonicalSha256(reqContent);
4257
4519
  return { reqId, ucId, reqFilePath, digest };
4258
4520
  }
4259
4521
  function resolveMissingDraftDestination(rootDir, id) {
4260
4522
  if (id.startsWith("PRIV-REQ-")) {
4261
- return { dir: path4.join(rootDir, "specs", "privacy", "requirements"), type: "privacy-requirement" };
4523
+ return { dir: path5.join(rootDir, "specs", "privacy", "requirements"), type: "privacy-requirement" };
4262
4524
  }
4263
4525
  if (id.startsWith("DATA-ASSET-")) {
4264
- return { dir: path4.join(rootDir, "specs", "privacy", "assets"), type: "data-asset" };
4526
+ return { dir: path5.join(rootDir, "specs", "privacy", "assets"), type: "data-asset" };
4265
4527
  }
4266
4528
  if (id.startsWith("PRIV-ROPA-")) {
4267
- return { dir: path4.join(rootDir, "specs", "privacy", "ropa"), type: "privacy-ropa" };
4529
+ return { dir: path5.join(rootDir, "specs", "privacy", "ropa"), type: "privacy-ropa" };
4268
4530
  }
4269
4531
  if (id.startsWith("UC-")) {
4270
- return { dir: path4.join(rootDir, "specs", "product"), type: "use-case" };
4532
+ return { dir: path5.join(rootDir, "specs", "product"), type: "use-case" };
4271
4533
  }
4272
4534
  if (id.startsWith("SEC-REQ-") || id.startsWith("SAF-REQ-")) {
4273
- return { dir: path4.join(rootDir, "specs", "product"), type: "security-requirement" };
4535
+ return { dir: path5.join(rootDir, "specs", "product"), type: "security-requirement" };
4274
4536
  }
4275
- return { dir: path4.join(rootDir, "specs", "product"), type: "requirement" };
4537
+ return { dir: path5.join(rootDir, "specs", "product"), type: "requirement" };
4276
4538
  }
4277
4539
  function createMissingDraftRequirement(rootDir, name, id, canonicalId, createdFiles) {
4278
4540
  const { dir, type: reqType } = resolveMissingDraftDestination(rootDir, id);
4279
- if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
4280
- const reqFilePath = path4.join(dir, `${id}.md`);
4541
+ if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
4542
+ const reqFilePath = path5.join(dir, `${id}.md`);
4281
4543
  const header = MISSING_DRAFT_HEADER.replaceAll("{{ID}}", id).replaceAll("{{TYPE}}", reqType).replaceAll("{{NAME}}", name);
4282
4544
  const body = MISSING_DRAFT_BODY.replaceAll("{{ID}}", id).replaceAll("{{NAME}}", name).replaceAll("{{CANONICAL_ID}}", canonicalId);
4283
4545
  const reqContent = `${header}
4284
4546
  ${body}`;
4285
- fs7.writeFileSync(reqFilePath, reqContent, "utf-8");
4547
+ fs8.writeFileSync(reqFilePath, reqContent, "utf-8");
4286
4548
  createdFiles.push(reqFilePath);
4287
4549
  const digest = computeCanonicalSha256(reqContent);
4288
4550
  return { reqFilePath, digest };
@@ -4397,7 +4659,7 @@ function initEmptyCitations() {
4397
4659
  }
4398
4660
  function ensureFallbackRequirement(col, padNum, name) {
4399
4661
  if (col.subRequirements.length === 0) {
4400
- col.subRequirements.push(`FR-${padNum}-${slugify(name).toUpperCase()}-001`);
4662
+ col.subRequirements.push(`FR-${padNum}-${slugify2(name).toUpperCase()}-001`);
4401
4663
  }
4402
4664
  }
4403
4665
  function collectChangeCitations(rootDir, name, padNum, canonicalId, author, dateStr, inputFromIds, artifactMap, createdFiles) {
@@ -4411,7 +4673,7 @@ function collectChangeCitations(rootDir, name, padNum, canonicalId, author, date
4411
4673
  return col;
4412
4674
  }
4413
4675
  function renderProposalFile(changeDir, canonicalId, name, author, citationsYamlBlock) {
4414
- const proposalPath = path4.join(changeDir, "proposal.md");
4676
+ const proposalPath = path5.join(changeDir, "proposal.md");
4415
4677
  const citationsSection = citationsYamlBlock ? `citations:
4416
4678
  ${citationsYamlBlock}
4417
4679
  ` : "";
@@ -4431,11 +4693,11 @@ ${name}
4431
4693
  ## 2. Dependencias Externas Evaluadas
4432
4694
  - Lista de dependencias nuevas a incorporar y verificaci\xF3n de licencia seg\xFAn \`license-policy.yaml\`.
4433
4695
  `;
4434
- fs7.writeFileSync(proposalPath, proposalContent, "utf-8");
4696
+ fs8.writeFileSync(proposalPath, proposalContent, "utf-8");
4435
4697
  return proposalPath;
4436
4698
  }
4437
4699
  function renderSpecFile(changeDir, canonicalId, name, profile) {
4438
- const specPath = path4.join(changeDir, "spec.md");
4700
+ const specPath = path5.join(changeDir, "spec.md");
4439
4701
  const specFmObj = {
4440
4702
  id: `SPEC-${canonicalId}`,
4441
4703
  type: "delivery-spec",
@@ -4456,12 +4718,12 @@ cucumber-tags:
4456
4718
  ---
4457
4719
  `;
4458
4720
  const body = SPEC_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name);
4459
- fs7.writeFileSync(specPath, `${header}
4721
+ fs8.writeFileSync(specPath, `${header}
4460
4722
  ${body}`, "utf-8");
4461
4723
  return { specPath, specFmObj };
4462
4724
  }
4463
4725
  function renderDesignFile(changeDir, canonicalId, name, author, dateStr, citationsYamlBlock) {
4464
- const designPath = path4.join(changeDir, "design.md");
4726
+ const designPath = path5.join(changeDir, "design.md");
4465
4727
  const citationsSection = citationsYamlBlock ? `citations:
4466
4728
  ${citationsYamlBlock}
4467
4729
  ` : "";
@@ -4480,7 +4742,7 @@ contracts: []
4480
4742
  ${citationsSection}---
4481
4743
  `;
4482
4744
  const body = DESIGN_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
4483
- fs7.writeFileSync(designPath, `${header}
4745
+ fs8.writeFileSync(designPath, `${header}
4484
4746
  ${body}`, "utf-8");
4485
4747
  return designPath;
4486
4748
  }
@@ -4505,14 +4767,14 @@ function buildTasksFrontmatter(canonicalId, name, reqIds) {
4505
4767
  };
4506
4768
  }
4507
4769
  function renderTasksFile(changeDir, canonicalId, name, author, dateStr, reqIds) {
4508
- const tasksPath = path4.join(changeDir, "tasks.md");
4770
+ const tasksPath = path5.join(changeDir, "tasks.md");
4509
4771
  const tasksFm = buildTasksFrontmatter(canonicalId, name, reqIds);
4510
4772
  const header = `---
4511
4773
  ${yaml3.dump(tasksFm, { indent: 2, lineWidth: -1 }).trim()}
4512
4774
  ---
4513
4775
  `;
4514
4776
  const body = TASKS_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
4515
- fs7.writeFileSync(tasksPath, `${header}
4777
+ fs8.writeFileSync(tasksPath, `${header}
4516
4778
  ${body}`, "utf-8");
4517
4779
  return { tasksPath, tasksFm };
4518
4780
  }
@@ -4691,7 +4953,7 @@ function resolveCiEnvironment(env = process.env) {
4691
4953
  };
4692
4954
  }
4693
4955
  function updateExistingEnvFile(filePath, envString, uppercaseKey) {
4694
- const content = fs7.readFileSync(filePath, "utf-8");
4956
+ const content = fs8.readFileSync(filePath, "utf-8");
4695
4957
  const lines = content.split("\n");
4696
4958
  let found = false;
4697
4959
  const newLines = lines.map((line) => {
@@ -4702,15 +4964,15 @@ function updateExistingEnvFile(filePath, envString, uppercaseKey) {
4702
4964
  return line;
4703
4965
  });
4704
4966
  if (!found) newLines.push(envString);
4705
- fs7.writeFileSync(filePath, newLines.filter((l) => l.trim() !== "").join("\n") + "\n", "utf-8");
4967
+ fs8.writeFileSync(filePath, newLines.filter((l) => l.trim() !== "").join("\n") + "\n", "utf-8");
4706
4968
  }
4707
4969
  function writeEnvOutputFile(envOutputFile, key, value) {
4708
4970
  const uppercaseKey = key.toUpperCase();
4709
4971
  const envString = `${uppercaseKey}=${value}`;
4710
- if (fs7.existsSync(envOutputFile)) {
4972
+ if (fs8.existsSync(envOutputFile)) {
4711
4973
  updateExistingEnvFile(envOutputFile, envString, uppercaseKey);
4712
4974
  } else {
4713
- fs7.writeFileSync(envOutputFile, envString + "\n", "utf-8");
4975
+ fs8.writeFileSync(envOutputFile, envString + "\n", "utf-8");
4714
4976
  }
4715
4977
  }
4716
4978
  function resolveEnvOutputFilePath(env) {
@@ -4720,8 +4982,8 @@ function resolveEnvOutputFilePath(env) {
4720
4982
  return void 0;
4721
4983
  }
4722
4984
  function emitCiOutput(key, value, env = process.env) {
4723
- if (env.GITHUB_OUTPUT && fs7.existsSync(env.GITHUB_OUTPUT)) {
4724
- fs7.appendFileSync(env.GITHUB_OUTPUT, `${key}=${value}
4985
+ if (env.GITHUB_OUTPUT && fs8.existsSync(env.GITHUB_OUTPUT)) {
4986
+ fs8.appendFileSync(env.GITHUB_OUTPUT, `${key}=${value}
4725
4987
  `, "utf-8");
4726
4988
  }
4727
4989
  if (env.TF_BUILD === "true") {
@@ -4763,11 +5025,11 @@ function isIgnoredEntry(name) {
4763
5025
  return name === "changes" || name === "active" || name === "node_modules";
4764
5026
  }
4765
5027
  function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
4766
- if (!fs7.existsSync(parentDir)) return;
4767
- const entries = fs7.readdirSync(parentDir, { withFileTypes: true });
5028
+ if (!fs8.existsSync(parentDir)) return;
5029
+ const entries = fs8.readdirSync(parentDir, { withFileTypes: true });
4768
5030
  for (const entry of entries) {
4769
5031
  if (!entry.isDirectory() || isIgnoredEntry(entry.name)) continue;
4770
- const changeDir = path4.join(parentDir, entry.name);
5032
+ const changeDir = path5.join(parentDir, entry.name);
4771
5033
  if (handooffsHasDir(handoffsMap, changeDir)) continue;
4772
5034
  const handoff = loadProductHandoffSidecar(changeDir);
4773
5035
  if (handoff?.id) {
@@ -4778,10 +5040,10 @@ function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
4778
5040
  function scanAllProductHandoffs(rootDir) {
4779
5041
  const handoffsMap = /* @__PURE__ */ new Map();
4780
5042
  const candidateParentDirs = [
4781
- path4.join(rootDir, "specs", "changes", "active"),
4782
- path4.join(rootDir, "specs", "changes", "completed"),
4783
- path4.join(rootDir, "specs"),
4784
- path4.join(rootDir, "openspec", "changes")
5043
+ path5.join(rootDir, "specs", "changes", "active"),
5044
+ path5.join(rootDir, "specs", "changes", "completed"),
5045
+ path5.join(rootDir, "specs"),
5046
+ path5.join(rootDir, "openspec", "changes")
4785
5047
  ];
4786
5048
  for (const parentDir of candidateParentDirs) {
4787
5049
  scanParentDirectoryHandoffs(parentDir, handoffsMap);
@@ -4789,9 +5051,9 @@ function scanAllProductHandoffs(rootDir) {
4789
5051
  return handoffsMap;
4790
5052
  }
4791
5053
  function handooffsHasDir(map, dir) {
4792
- const norm = path4.resolve(dir);
5054
+ const norm = path5.resolve(dir);
4793
5055
  for (const key of map.keys()) {
4794
- if (path4.resolve(key) === norm) return true;
5056
+ if (path5.resolve(key) === norm) return true;
4795
5057
  }
4796
5058
  return false;
4797
5059
  }
@@ -4826,7 +5088,7 @@ function buildTraceabilityHeader(isOk, errors) {
4826
5088
  return [
4827
5089
  "# 360\xB0 Requirements Traceability Matrix (RTM)",
4828
5090
  "",
4829
- `*Verification Date: ${(/* @__PURE__ */ new Date()).toISOString()}*`,
5091
+ `*Verification Date: ${resolveDeterministicDate().toISOString()}*`,
4830
5092
  `*Overall Status: ${isOk ? "100% TRACEABLE (PASSED)" : `ERRORS DETECTED (${errors} gaps)`}*`,
4831
5093
  "",
4832
5094
  "## 1. End-to-End Coverage (PDaC Handoff \u2794 arc42/NAF v4 \u2794 BDD/Gherkin)",
@@ -4888,9 +5150,9 @@ function isCodeTestFile(name) {
4888
5150
  }
4889
5151
  function indexFeatureFile(fullPath, rootDir, map) {
4890
5152
  try {
4891
- const content = fs7.readFileSync(fullPath, "utf-8");
5153
+ const content = fs8.readFileSync(fullPath, "utf-8");
4892
5154
  const tags = extractFeatureTags(content);
4893
- const relFeature = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
5155
+ const relFeature = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
4894
5156
  for (const tag of tags) {
4895
5157
  const existing = map.get(tag) || [];
4896
5158
  if (!existing.includes(relFeature)) existing.push(relFeature);
@@ -4900,11 +5162,11 @@ function indexFeatureFile(fullPath, rootDir, map) {
4900
5162
  }
4901
5163
  }
4902
5164
  function scanFeatureFiles(dir, rootDir, map) {
4903
- if (!fs7.existsSync(dir)) return;
5165
+ if (!fs8.existsSync(dir)) return;
4904
5166
  try {
4905
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
5167
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
4906
5168
  for (const e of entries) {
4907
- const fullPath = path4.join(dir, e.name);
5169
+ const fullPath = path5.join(dir, e.name);
4908
5170
  if (e.isDirectory() && !shouldSkipDir(e.name)) {
4909
5171
  scanFeatureFiles(fullPath, rootDir, map);
4910
5172
  } else if (e.name.endsWith(".feature")) {
@@ -4916,8 +5178,8 @@ function scanFeatureFiles(dir, rootDir, map) {
4916
5178
  }
4917
5179
  function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
4918
5180
  try {
4919
- const content = fs7.readFileSync(fullPath, "utf-8");
4920
- const relTest = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
5181
+ const content = fs8.readFileSync(fullPath, "utf-8");
5182
+ const relTest = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
4921
5183
  for (const artId of reqIds) {
4922
5184
  if (content.includes(artId)) {
4923
5185
  const existing = map.get(artId) || [];
@@ -4929,11 +5191,11 @@ function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
4929
5191
  }
4930
5192
  }
4931
5193
  function scanCodeTestFiles(dir, rootDir, reqIds, map) {
4932
- if (!fs7.existsSync(dir)) return;
5194
+ if (!fs8.existsSync(dir)) return;
4933
5195
  try {
4934
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
5196
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
4935
5197
  for (const e of entries) {
4936
- const fullPath = path4.join(dir, e.name);
5198
+ const fullPath = path5.join(dir, e.name);
4937
5199
  if (e.isDirectory() && !shouldSkipDir(e.name)) {
4938
5200
  scanCodeTestFiles(fullPath, rootDir, reqIds, map);
4939
5201
  } else if (isCodeTestFile(e.name)) {
@@ -4967,9 +5229,9 @@ function isArchitectureViewFile(relFile, frontmatter) {
4967
5229
  }
4968
5230
  function processSingleMdFile(file, rootDir, artifactMap, componentsList, architectureViews) {
4969
5231
  try {
4970
- const content = fs7.readFileSync(file, "utf-8");
5232
+ const content = fs8.readFileSync(file, "utf-8");
4971
5233
  const { frontmatter, body } = parseFrontmatter(content);
4972
- const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
5234
+ const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
4973
5235
  if (frontmatter.id) {
4974
5236
  artifactMap.set(frontmatter.id, {
4975
5237
  file: relFile,
@@ -5134,7 +5396,7 @@ function collectArchitectureViewTraces(reqId, architectureViews) {
5134
5396
  const traces = [];
5135
5397
  for (const view of architectureViews) {
5136
5398
  if (view.content.includes(reqId)) {
5137
- const viewIdentifier = isEntityWithId(view) && view.id ? view.id : path4.basename(view.file);
5399
+ const viewIdentifier = isEntityWithId(view) && view.id ? view.id : path5.basename(view.file);
5138
5400
  traces.push(viewIdentifier);
5139
5401
  }
5140
5402
  }
@@ -5222,16 +5484,16 @@ function isProductionSourceFile(name) {
5222
5484
  return SOURCE_CODE_EXTS.some((ext) => name.endsWith(ext));
5223
5485
  }
5224
5486
  function collectCodeAndTestCorpora(dir, prodChunks = [], testChunks = []) {
5225
- if (!fs7.existsSync(dir)) return { prodCorpus: "", testCorpus: "" };
5487
+ if (!fs8.existsSync(dir)) return { prodCorpus: "", testCorpus: "" };
5226
5488
  try {
5227
- for (const e of fs7.readdirSync(dir, { withFileTypes: true })) {
5228
- const fullPath = path4.join(dir, e.name);
5489
+ for (const e of fs8.readdirSync(dir, { withFileTypes: true })) {
5490
+ const fullPath = path5.join(dir, e.name);
5229
5491
  if (e.isDirectory() && !shouldSkipDir(e.name)) {
5230
5492
  collectCodeAndTestCorpora(fullPath, prodChunks, testChunks);
5231
5493
  } else if (isCodeTestFile(e.name) || e.name.endsWith(".feature")) {
5232
- testChunks.push(fs7.readFileSync(fullPath, "utf-8"));
5494
+ testChunks.push(fs8.readFileSync(fullPath, "utf-8"));
5233
5495
  } else if (isProductionSourceFile(e.name)) {
5234
- prodChunks.push(fs7.readFileSync(fullPath, "utf-8"));
5496
+ prodChunks.push(fs8.readFileSync(fullPath, "utf-8"));
5235
5497
  }
5236
5498
  }
5237
5499
  } catch {
@@ -5385,12 +5647,12 @@ function walkTaskFiles(dir, fileList = [], excludeDirs = [
5385
5647
  "examples"
5386
5648
  ]) {
5387
5649
  try {
5388
- if (!fs7.existsSync(dir)) return fileList;
5389
- const files = fs7.readdirSync(dir);
5650
+ if (!fs8.existsSync(dir)) return fileList;
5651
+ const files = fs8.readdirSync(dir).sort(canonicalSort);
5390
5652
  for (const file of files) {
5391
- const fullPath = path4.join(dir, file);
5653
+ const fullPath = path5.join(dir, file);
5392
5654
  try {
5393
- if (fs7.statSync(fullPath).isDirectory()) {
5655
+ if (fs8.statSync(fullPath).isDirectory()) {
5394
5656
  if (!excludeDirs.includes(file)) {
5395
5657
  walkTaskFiles(fullPath, fileList, excludeDirs);
5396
5658
  }
@@ -5442,7 +5704,7 @@ function generateGovernanceReportMarkdown(summaries, violationsCount, modeCounts
5442
5704
  const sections = [
5443
5705
  `# \u{1F4CB} Task Governance and Human Autonomy Classification Report
5444
5706
 
5445
- > **Audit Date:** ${(/* @__PURE__ */ new Date()).toISOString()}
5707
+ > **Audit Date:** ${resolveDeterministicDate().toISOString()}
5446
5708
  > **Overall Verdict:** ${verdict}
5447
5709
 
5448
5710
  ---`,
@@ -5503,9 +5765,9 @@ function updateGovernanceCounts(t, modeCounts, riskCounts) {
5503
5765
  }
5504
5766
  }
5505
5767
  function parseTasksFromFile(file) {
5506
- if (!fs7.existsSync(file)) return [];
5768
+ if (!fs8.existsSync(file)) return [];
5507
5769
  try {
5508
- const content = fs7.readFileSync(file, "utf-8");
5770
+ const content = fs8.readFileSync(file, "utf-8");
5509
5771
  const { frontmatter } = parseTasksDoc(content);
5510
5772
  return frontmatter.tasks || [];
5511
5773
  } catch {
@@ -5513,11 +5775,11 @@ function parseTasksFromFile(file) {
5513
5775
  }
5514
5776
  }
5515
5777
  function extractDesignExpectedScope(changeDir) {
5516
- const designFile = path4.join(changeDir, "design.md");
5517
- if (!fs7.existsSync(designFile)) return [];
5778
+ const designFile = path5.join(changeDir, "design.md");
5779
+ if (!fs8.existsSync(designFile)) return [];
5518
5780
  const expected = [];
5519
5781
  try {
5520
- const { frontmatter } = parseTasksDoc(fs7.readFileSync(designFile, "utf-8"));
5782
+ const { frontmatter } = parseTasksDoc(fs8.readFileSync(designFile, "utf-8"));
5521
5783
  if (typeof frontmatter["architecture-component"] === "string") {
5522
5784
  expected.push(frontmatter["architecture-component"]);
5523
5785
  }
@@ -5557,7 +5819,7 @@ function hasExplicitScopeBindings(tasks) {
5557
5819
  );
5558
5820
  }
5559
5821
  function auditDesignScopeForFile(file, tasks, options, unassignedList, violationsList) {
5560
- const changeDir = path4.dirname(file);
5822
+ const changeDir = path5.dirname(file);
5561
5823
  const expectedItems = [
5562
5824
  ...extractDesignExpectedScope(changeDir),
5563
5825
  ...extractHandoffExpectedScope(changeDir, options.maxUseCasesPerChange, violationsList)
@@ -5568,13 +5830,13 @@ function auditDesignScopeForFile(file, tasks, options, unassignedList, violation
5568
5830
  for (const item of new Set(expectedItems)) {
5569
5831
  if (!covered.has(item)) {
5570
5832
  unassignedList.push(item);
5571
- violationsList.push(`[${path4.basename(changeDir)}] Unassigned design/handoff scope in tasks.md: missing task coverage for ${item}`);
5833
+ violationsList.push(`[${path5.basename(changeDir)}] Unassigned design/handoff scope in tasks.md: missing task coverage for ${item}`);
5572
5834
  }
5573
5835
  }
5574
5836
  }
5575
5837
  function processTasksFile(file, rootDir, options, acc) {
5576
5838
  const tasks = parseTasksFromFile(file);
5577
- const relPath = path4.relative(rootDir, file).replace(/\\/g, "/");
5839
+ const relPath = path5.relative(rootDir, file).replace(/\\/g, "/");
5578
5840
  let unverifiedDelta = 0;
5579
5841
  for (const t of tasks) {
5580
5842
  const { summary, violation, unverified } = evaluateTaskItem(t, relPath);
@@ -5873,11 +6135,11 @@ async function runAllExpandedScenarios(parsed, definitions) {
5873
6135
  }
5874
6136
  async function executeBddFeature(featurePath, registry, options = {}) {
5875
6137
  const rootDir = options.rootDir || process.cwd();
5876
- const fullPath = path4.isAbsolute(featurePath) ? featurePath : path4.join(rootDir, featurePath);
5877
- const parsed = parseFeatureDocument(fs7.readFileSync(fullPath, "utf-8"));
6138
+ const fullPath = path5.isAbsolute(featurePath) ? featurePath : path5.join(rootDir, featurePath);
6139
+ const parsed = parseFeatureDocument(fs8.readFileSync(fullPath, "utf-8"));
5878
6140
  const scenarios = await runAllExpandedScenarios(parsed, registry.getDefinitions());
5879
6141
  const report = {
5880
- uri: path4.relative(rootDir, fullPath).replace(/\\/g, "/"),
6142
+ uri: path5.relative(rootDir, fullPath).replace(/\\/g, "/"),
5881
6143
  featureName: parsed.featureName,
5882
6144
  tags: parsed.tags,
5883
6145
  scenarios,
@@ -5911,10 +6173,10 @@ function mapFeatureToCucumberJson(r) {
5911
6173
  };
5912
6174
  }
5913
6175
  function writeCucumberJsonReport(reports, outputPath) {
5914
- const dir = path4.dirname(outputPath);
5915
- if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
6176
+ const dir = path5.dirname(outputPath);
6177
+ if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
5916
6178
  const payload = JSON.stringify(reports.map(mapFeatureToCucumberJson), null, 2);
5917
- fs7.writeFileSync(outputPath, payload, "utf-8");
6179
+ fs8.writeFileSync(outputPath, payload, "utf-8");
5918
6180
  }
5919
6181
  function isToolAvailable(toolName) {
5920
6182
  try {
@@ -5960,8 +6222,8 @@ function findLicenseFile(pkgDir) {
5960
6222
  "license.txt"
5961
6223
  ];
5962
6224
  for (const c of candidates) {
5963
- const full = path4.join(pkgDir, c);
5964
- if (fs7.existsSync(full)) {
6225
+ const full = path5.join(pkgDir, c);
6226
+ if (fs8.existsSync(full)) {
5965
6227
  return full;
5966
6228
  }
5967
6229
  }
@@ -5978,9 +6240,9 @@ var LICENSE_FILE_PATTERNS = [
5978
6240
  [(t) => t.includes("Microsoft Reciprocal License") || t.includes("MS-RL"), "MS-RL"]
5979
6241
  ];
5980
6242
  function resolveLicenseFromFile(licenseFilePath) {
5981
- if (!licenseFilePath || !fs7.existsSync(licenseFilePath)) return void 0;
6243
+ if (!licenseFilePath || !fs8.existsSync(licenseFilePath)) return void 0;
5982
6244
  try {
5983
- const text = fs7.readFileSync(licenseFilePath, "utf-8");
6245
+ const text = fs8.readFileSync(licenseFilePath, "utf-8");
5984
6246
  for (const [matches, spdx] of LICENSE_FILE_PATTERNS) {
5985
6247
  if (matches(text)) return spdx;
5986
6248
  }
@@ -5989,16 +6251,16 @@ function resolveLicenseFromFile(licenseFilePath) {
5989
6251
  return void 0;
5990
6252
  }
5991
6253
  function readPackageJson(filePath) {
5992
- if (!fs7.existsSync(filePath)) return null;
6254
+ if (!fs8.existsSync(filePath)) return null;
5993
6255
  try {
5994
- return JSON.parse(fs7.readFileSync(filePath, "utf-8"));
6256
+ return JSON.parse(fs8.readFileSync(filePath, "utf-8"));
5995
6257
  } catch {
5996
6258
  return null;
5997
6259
  }
5998
6260
  }
5999
6261
  function extractDirectDepNames(rootDir, productionOnly) {
6000
6262
  const names = /* @__PURE__ */ new Set();
6001
- const pkgPath = path4.join(rootDir, "package.json");
6263
+ const pkgPath = path5.join(rootDir, "package.json");
6002
6264
  const pkg = readPackageJson(pkgPath);
6003
6265
  if (!pkg) return names;
6004
6266
  if (pkg.dependencies && typeof pkg.dependencies === "object") {
@@ -6018,7 +6280,7 @@ function resolveSpdxForPackage(pkg, licFile) {
6018
6280
  return spdx;
6019
6281
  }
6020
6282
  function processPackageDir(pkgDir, directNames, seen) {
6021
- const pkg = readPackageJson(path4.join(pkgDir, "package.json"));
6283
+ const pkg = readPackageJson(path5.join(pkgDir, "package.json"));
6022
6284
  if (!pkg?.name || !pkg.version) return;
6023
6285
  const key = `${pkg.name}@${pkg.version}`;
6024
6286
  if (seen.has(key)) return;
@@ -6039,40 +6301,40 @@ function processPackageDir(pkgDir, directNames, seen) {
6039
6301
  });
6040
6302
  }
6041
6303
  function scanScopeSubdirectory(scopeDir, directNames, seen) {
6042
- const scopedEntries = fs7.readdirSync(scopeDir, { withFileTypes: true });
6304
+ const scopedEntries = fs8.readdirSync(scopeDir, { withFileTypes: true });
6043
6305
  for (const scoped of scopedEntries) {
6044
6306
  if (scoped.isDirectory()) {
6045
- processPackageDir(path4.join(scopeDir, scoped.name), directNames, seen);
6307
+ processPackageDir(path5.join(scopeDir, scoped.name), directNames, seen);
6046
6308
  }
6047
6309
  }
6048
6310
  }
6049
6311
  function scanSubdirectories(baseDir, directNames, seen) {
6050
- if (!fs7.existsSync(baseDir)) return;
6051
- const entries = fs7.readdirSync(baseDir, { withFileTypes: true });
6312
+ if (!fs8.existsSync(baseDir)) return;
6313
+ const entries = fs8.readdirSync(baseDir, { withFileTypes: true });
6052
6314
  for (const entry of entries) {
6053
6315
  if (!entry.isDirectory()) continue;
6054
6316
  if (entry.name.startsWith("@")) {
6055
- scanScopeSubdirectory(path4.join(baseDir, entry.name), directNames, seen);
6317
+ scanScopeSubdirectory(path5.join(baseDir, entry.name), directNames, seen);
6056
6318
  } else if (entry.name !== ".bin" && entry.name !== ".pnpm") {
6057
- processPackageDir(path4.join(baseDir, entry.name), directNames, seen);
6319
+ processPackageDir(path5.join(baseDir, entry.name), directNames, seen);
6058
6320
  }
6059
6321
  }
6060
6322
  }
6061
6323
  function scanPnpmStore(pnpmDir, directNames, seen) {
6062
- if (!fs7.existsSync(pnpmDir)) return;
6063
- const entries = fs7.readdirSync(pnpmDir, { withFileTypes: true });
6324
+ if (!fs8.existsSync(pnpmDir)) return;
6325
+ const entries = fs8.readdirSync(pnpmDir, { withFileTypes: true });
6064
6326
  for (const entry of entries) {
6065
6327
  if (!entry.isDirectory()) continue;
6066
- const candidateModules = path4.join(pnpmDir, entry.name, "node_modules");
6067
- if (fs7.existsSync(candidateModules)) {
6328
+ const candidateModules = path5.join(pnpmDir, entry.name, "node_modules");
6329
+ if (fs8.existsSync(candidateModules)) {
6068
6330
  scanSubdirectories(candidateModules, directNames, seen);
6069
6331
  }
6070
6332
  }
6071
6333
  }
6072
6334
  function readTextIfExists(filePath) {
6073
- if (!fs7.existsSync(filePath)) return null;
6335
+ if (!fs8.existsSync(filePath)) return null;
6074
6336
  try {
6075
- return fs7.readFileSync(filePath, "utf-8");
6337
+ return fs8.readFileSync(filePath, "utf-8");
6076
6338
  } catch {
6077
6339
  return null;
6078
6340
  }
@@ -6132,14 +6394,14 @@ function parsePipfileSection(pkgs, isDirect, lockPath, seen) {
6132
6394
  }
6133
6395
  }
6134
6396
  function scanPythonDependencies(rootDir, seen) {
6135
- const pyproject = readTextIfExists(path4.join(rootDir, "pyproject.toml")) || "";
6397
+ const pyproject = readTextIfExists(path5.join(rootDir, "pyproject.toml")) || "";
6136
6398
  const directNames = extractPyprojectDirectDeps(pyproject);
6137
6399
  for (const lockName of ["poetry.lock", "uv.lock"]) {
6138
- const lockPath = path4.join(rootDir, lockName);
6400
+ const lockPath = path5.join(rootDir, lockName);
6139
6401
  const content = readTextIfExists(lockPath);
6140
6402
  if (content) parseTomlPackageBlocks(content, lockPath, directNames, seen);
6141
6403
  }
6142
- const pipfileLockPath = path4.join(rootDir, "Pipfile.lock");
6404
+ const pipfileLockPath = path5.join(rootDir, "Pipfile.lock");
6143
6405
  const pipfileRaw = readTextIfExists(pipfileLockPath);
6144
6406
  if (!pipfileRaw) return;
6145
6407
  try {
@@ -6182,7 +6444,7 @@ function extractGoCandidateLines(content) {
6182
6444
  return out;
6183
6445
  }
6184
6446
  function scanGoDependencies(rootDir, seen) {
6185
- const goModPath = path4.join(rootDir, "go.mod");
6447
+ const goModPath = path5.join(rootDir, "go.mod");
6186
6448
  const content = readTextIfExists(goModPath);
6187
6449
  if (!content) return;
6188
6450
  for (const line of extractGoCandidateLines(content)) {
@@ -6225,10 +6487,10 @@ function parseCargoLockBlock(block, rootName, directMeta, cargoLockPath) {
6225
6487
  };
6226
6488
  }
6227
6489
  function scanRustDependencies(rootDir, seen) {
6228
- const cargoLockPath = path4.join(rootDir, "Cargo.lock");
6490
+ const cargoLockPath = path5.join(rootDir, "Cargo.lock");
6229
6491
  const lockContent = readTextIfExists(cargoLockPath);
6230
6492
  if (!lockContent) return;
6231
- const cargoToml = readTextIfExists(path4.join(rootDir, "Cargo.toml")) || "";
6493
+ const cargoToml = readTextIfExists(path5.join(rootDir, "Cargo.toml")) || "";
6232
6494
  const directMeta = extractCargoDirectDeps(cargoToml);
6233
6495
  const rootName = extractTomlField(cargoToml, "name");
6234
6496
  for (const block of lockContent.split(/\[\[package\]\]/g).slice(1)) {
@@ -6264,7 +6526,7 @@ function parseMavenDepBlock(block, defaultLic, pomPath) {
6264
6526
  };
6265
6527
  }
6266
6528
  function scanMavenDependencies(rootDir, seen) {
6267
- const pomPath = path4.join(rootDir, "pom.xml");
6529
+ const pomPath = path5.join(rootDir, "pom.xml");
6268
6530
  const content = readTextIfExists(pomPath);
6269
6531
  if (!content) return;
6270
6532
  const projectLicBlock = content.match(/<licenses>([\s\S]*?)<\/licenses>/)?.[1] || "";
@@ -6280,11 +6542,11 @@ function scanMavenDependencies(rootDir, seen) {
6280
6542
  function scanWithNativeFs(rootDir, options = {}) {
6281
6543
  const directNames = extractDirectDepNames(rootDir, options.productionOnly);
6282
6544
  const seen = /* @__PURE__ */ new Map();
6283
- const nmDir = path4.join(rootDir, "node_modules");
6284
- if (fs7.existsSync(nmDir)) {
6545
+ const nmDir = path5.join(rootDir, "node_modules");
6546
+ if (fs8.existsSync(nmDir)) {
6285
6547
  scanSubdirectories(nmDir, directNames, seen);
6286
6548
  if (options.depth !== "direct") {
6287
- scanPnpmStore(path4.join(nmDir, ".pnpm"), directNames, seen);
6549
+ scanPnpmStore(path5.join(nmDir, ".pnpm"), directNames, seen);
6288
6550
  }
6289
6551
  }
6290
6552
  scanPythonDependencies(rootDir, seen);
@@ -6543,20 +6805,20 @@ function resolveProjectIdentity(options) {
6543
6805
  let name = options.projectName || "ai-sdlc-project";
6544
6806
  let version = options.projectVersion || "1.0.0";
6545
6807
  if (!options.rootDir) return { name, version };
6546
- const pkgPath = path4.join(options.rootDir, "package.json");
6547
- if (!fs7.existsSync(pkgPath)) return { name, version };
6808
+ const pkgPath = path5.join(options.rootDir, "package.json");
6809
+ if (!fs8.existsSync(pkgPath)) return { name, version };
6548
6810
  try {
6549
- const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
6811
+ const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
6550
6812
  if (pkg.name) name = pkg.name;
6551
6813
  if (pkg.version) version = pkg.version;
6552
6814
  } catch {
6553
6815
  }
6554
6816
  return { name, version };
6555
6817
  }
6556
- function buildBomMetadata(name, version) {
6818
+ function buildBomMetadata(name, version, rootDir) {
6557
6819
  return {
6558
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6559
- tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.4.0" }],
6820
+ timestamp: resolveDeterministicDate(rootDir).toISOString(),
6821
+ tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.5.0" }],
6560
6822
  component: { type: "application", name, version }
6561
6823
  };
6562
6824
  }
@@ -6567,52 +6829,21 @@ function generateCycloneDxSbom(dependencies, options = {}) {
6567
6829
  bomFormat: "CycloneDX",
6568
6830
  specVersion: "1.5",
6569
6831
  version: 1,
6570
- serialNumber: `urn:uuid:${crypto.randomUUID()}`,
6571
- metadata: buildBomMetadata(name, version),
6832
+ serialNumber: `urn:uuid:${generateDeterministicUuidV5(`${name}@${version}|${dependencies.map((d) => `${d.name}@${d.version}:${d.purl || ""}`).sort(canonicalSort).join(";")}`)}`,
6833
+ metadata: buildBomMetadata(name, version, options.rootDir),
6572
6834
  components: dependencies.map(buildCycloneDxComponent),
6573
6835
  ...cdxVulns.length > 0 ? { vulnerabilities: cdxVulns } : {}
6574
6836
  };
6575
6837
  }
6576
- function tryAtomicWrite(filePath, content) {
6577
- const tempPath = `${filePath}.${process.pid}.${Math.random().toString(36).substring(2)}.tmp`;
6578
- try {
6579
- fs7.writeFileSync(tempPath, content, "utf-8");
6580
- try {
6581
- if (fs7.existsSync(filePath)) {
6582
- fs7.rmSync(filePath, { force: true });
6583
- }
6584
- fs7.renameSync(tempPath, filePath);
6585
- } catch {
6586
- fs7.copyFileSync(tempPath, filePath);
6587
- fs7.unlinkSync(tempPath);
6588
- }
6589
- return true;
6590
- } catch {
6591
- return false;
6592
- }
6593
- }
6594
- function safeWriteFileSync(filePath, content) {
6595
- const dir = path4.dirname(filePath);
6596
- if (!fs7.existsSync(dir)) {
6597
- fs7.mkdirSync(dir, { recursive: true });
6598
- }
6599
- for (let attempt = 0; attempt < 10; attempt++) {
6600
- if (tryAtomicWrite(filePath, content)) return;
6601
- }
6602
- try {
6603
- fs7.writeFileSync(filePath, content, "utf-8");
6604
- } catch {
6605
- }
6606
- }
6607
6838
  function writeCycloneDxSbom(sbom, outputPath) {
6608
- safeWriteFileSync(outputPath, JSON.stringify(sbom, null, 2));
6839
+ writeReportFileIfChanged(outputPath, JSON.stringify(sbom, null, 2));
6609
6840
  }
6610
6841
  function buildThirdPartyHeader(projectName, count) {
6611
6842
  return [
6612
6843
  `# \u{1F4DC} Third-Party Software Notices and Information`,
6613
6844
  ``,
6614
6845
  `> **Proyecto:** ${projectName}`,
6615
- `> **Fecha:** ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
6846
+ `> **Fecha:** ${resolveDeterministicDate().toISOString().split("T")[0]}`,
6616
6847
  `> **Total Componentes:** ${count}`,
6617
6848
  ``,
6618
6849
  `Este documento contiene los avisos de copyright y t\xE9rminos de licencia aplicables a los componentes de terceros utilizados en este proyecto.`,
@@ -6626,9 +6857,9 @@ function buildThirdPartyHeader(projectName, count) {
6626
6857
  ];
6627
6858
  }
6628
6859
  function readLicenseTextPreview(licenseFile) {
6629
- if (!licenseFile || !fs7.existsSync(licenseFile)) return null;
6860
+ if (!licenseFile || !fs8.existsSync(licenseFile)) return null;
6630
6861
  try {
6631
- const text = fs7.readFileSync(licenseFile, "utf-8").trim();
6862
+ const text = fs8.readFileSync(licenseFile, "utf-8").trim();
6632
6863
  return text.slice(0, 1e3) + (text.length > 1e3 ? "\n... (texto truncado)" : "");
6633
6864
  } catch {
6634
6865
  return null;
@@ -6660,13 +6891,13 @@ function generateThirdPartyNotices(dependencies, projectName = "AI-SDLC") {
6660
6891
  "",
6661
6892
  "---",
6662
6893
  "",
6663
- "## Textos de Licencia y Atribuciones",
6894
+ "## 2. Atribuciones y T\xE9rminos de Licencia Individuales",
6664
6895
  "",
6665
6896
  ...attributions
6666
6897
  ].join("\n");
6667
6898
  }
6668
- function writeThirdPartyNotices(content, outputPath) {
6669
- safeWriteFileSync(outputPath, content);
6899
+ function writeThirdPartyNotices(noticesContent, outputPath) {
6900
+ writeReportFileIfChanged(outputPath, noticesContent);
6670
6901
  }
6671
6902
 
6672
6903
  // src/verifiers/licenses.ts
@@ -6743,22 +6974,20 @@ function parseVexStatements(doc) {
6743
6974
  if (!Array.isArray(rawList)) return [];
6744
6975
  return rawList.filter((item) => Boolean(item && typeof item.id === "string" && typeof item.state === "string"));
6745
6976
  }
6746
- function parseLicensePolicy(content) {
6977
+ function parseLicenseDoc(content) {
6978
+ let doc;
6747
6979
  try {
6748
- const doc = yaml3.load(content);
6749
- const { permitted, restricted, blocked } = classifyCategories(doc?.categories);
6750
- const exception_packages = parseExceptions(doc?.exceptions);
6751
- const vex_statements = parseVexStatements(doc);
6752
- return {
6753
- version: doc?.version || "1.0",
6754
- permitted: permitted.length > 0 ? permitted : DEFAULT_PERMITTED,
6755
- restricted: restricted.length > 0 ? restricted : DEFAULT_RESTRICTED,
6756
- blocked: blocked.length > 0 ? blocked : DEFAULT_BLOCKED,
6757
- exception_packages,
6758
- vex_statements,
6759
- rawPolicy: doc
6760
- };
6761
- } catch {
6980
+ doc = yaml3.load(content);
6981
+ } catch (err) {
6982
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in license-policy: ${err instanceof Error ? err.message : String(err)}`);
6983
+ }
6984
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
6985
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid license policy structure: root must be a YAML mapping`);
6986
+ }
6987
+ return doc;
6988
+ }
6989
+ function parseLicensePolicy(content) {
6990
+ if (!content || !content.trim()) {
6762
6991
  return {
6763
6992
  version: "1.0",
6764
6993
  permitted: DEFAULT_PERMITTED,
@@ -6768,6 +6997,17 @@ function parseLicensePolicy(content) {
6768
6997
  vex_statements: []
6769
6998
  };
6770
6999
  }
7000
+ const doc = parseLicenseDoc(content);
7001
+ const { permitted, restricted, blocked } = classifyCategories(doc?.categories);
7002
+ return {
7003
+ version: doc?.version || "1.0",
7004
+ permitted: permitted.length > 0 ? permitted : DEFAULT_PERMITTED,
7005
+ restricted: restricted.length > 0 ? restricted : DEFAULT_RESTRICTED,
7006
+ blocked: blocked.length > 0 ? blocked : DEFAULT_BLOCKED,
7007
+ exception_packages: parseExceptions(doc?.exceptions),
7008
+ vex_statements: parseVexStatements(doc),
7009
+ rawPolicy: doc
7010
+ };
6771
7011
  }
6772
7012
  function evaluateCompoundLicense(lic, policy) {
6773
7013
  const cleanLic = lic.replace(/[()]/g, "").trim();
@@ -6859,7 +7099,7 @@ function buildReportHeader(isOk, depCount, violationCount, sbomPath) {
6859
7099
  return [
6860
7100
  `# \u{1F4DC} Open Source License Compliance Audit (Dynamic SCA)`,
6861
7101
  ``,
6862
- `> **Verification Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
7102
+ `> **Verification Date:** ${resolveDeterministicDate().toISOString()}`,
6863
7103
  `> **Status:** ${statusStr}`,
6864
7104
  `> **Total Audited Dependencies:** ${depCount}`,
6865
7105
  sbomPath ? `> **Generated CycloneDX SBOM:** \`${sbomPath}\`` : "",
@@ -6884,9 +7124,9 @@ function generateLicenseReportMarkdown(dependencies, violations, policy, sbomPat
6884
7124
  return lines.filter(Boolean).join("\n");
6885
7125
  }
6886
7126
  function parseManifestItems(manifestFile) {
6887
- if (!fs7.existsSync(manifestFile)) return [];
7127
+ if (!fs8.existsSync(manifestFile)) return [];
6888
7128
  try {
6889
- const doc = yaml3.load(fs7.readFileSync(manifestFile, "utf-8"));
7129
+ const doc = yaml3.load(fs8.readFileSync(manifestFile, "utf-8"));
6890
7130
  return Array.isArray(doc?.evaluated_dependencies) ? doc.evaluated_dependencies : [];
6891
7131
  } catch {
6892
7132
  return [];
@@ -6918,10 +7158,10 @@ function verifyStaticManifest(manifestFile, policy) {
6918
7158
  function resolveManifestPath(rootDir, customPath) {
6919
7159
  if (customPath) return customPath;
6920
7160
  const candidates = [
6921
- path4.join(rootDir, "compliance", "license-manifest.yaml"),
6922
- path4.join(rootDir, "license-manifest.yaml")
7161
+ path5.join(rootDir, "compliance", "license-manifest.yaml"),
7162
+ path5.join(rootDir, "license-manifest.yaml")
6923
7163
  ];
6924
- return candidates.find((p) => fs7.existsSync(p)) || candidates[0];
7164
+ return candidates.find((p) => fs8.existsSync(p)) || candidates[0];
6925
7165
  }
6926
7166
  function handleStaticManifestVerification(options, rootDir, policy) {
6927
7167
  const manifestFile = resolveManifestPath(rootDir, options.manifestPath);
@@ -6942,7 +7182,7 @@ function shouldGenerateSbom(options, rawPolicy) {
6942
7182
  }
6943
7183
  function maybeGenerateSbom(options, rootDir, rawPolicy, dependencies, vulnerabilities, vexStatements) {
6944
7184
  if (!shouldGenerateSbom(options, rawPolicy)) return void 0;
6945
- const sbomPath = options.sbomPath || path4.join(rootDir, "reports", "sbom.cdx.json");
7185
+ const sbomPath = options.sbomPath || path5.join(rootDir, "reports", "sbom.cdx.json");
6946
7186
  const sbom = generateCycloneDxSbom(dependencies, { rootDir, vulnerabilities, vexStatements });
6947
7187
  writeCycloneDxSbom(sbom, sbomPath);
6948
7188
  return sbomPath;
@@ -6951,7 +7191,7 @@ function maybeGenerateNotices(options, rootDir, rawPolicy, dependencies) {
6951
7191
  const shouldGen = options.generateNotices === true || Boolean(options.noticesPath || rawPolicy?.ci_gates?.output_notices_file);
6952
7192
  if (!shouldGen) return void 0;
6953
7193
  const noticesFileName = rawPolicy?.ci_gates?.output_notices_file || "THIRD_PARTY_NOTICES.md";
6954
- const noticesPath = options.noticesPath || path4.join(rootDir, noticesFileName);
7194
+ const noticesPath = options.noticesPath || path5.join(rootDir, noticesFileName);
6955
7195
  const noticesContent = generateThirdPartyNotices(dependencies);
6956
7196
  writeThirdPartyNotices(noticesContent, noticesPath);
6957
7197
  return noticesPath;
@@ -6963,8 +7203,8 @@ function handleDynamicScaArtifacts(options, rootDir, rawPolicy, dependencies, vu
6963
7203
  };
6964
7204
  }
6965
7205
  function loadLicensePolicy(rootDir, customPath) {
6966
- const policyFile = customPath || path4.join(rootDir, "license-policy.yaml");
6967
- return fs7.existsSync(policyFile) ? parseLicensePolicy(fs7.readFileSync(policyFile, "utf-8")) : parseLicensePolicy("");
7206
+ const policyFile = customPath || path5.join(rootDir, "license-policy.yaml");
7207
+ return fs8.existsSync(policyFile) ? parseLicensePolicy(fs8.readFileSync(policyFile, "utf-8")) : parseLicensePolicy("");
6968
7208
  }
6969
7209
  function evaluateDependencyList(dependencies, policy) {
6970
7210
  const violations = [];
@@ -7068,11 +7308,11 @@ var PROTECTED_BYPASS_PATTERNS = [
7068
7308
  /migrations?[\\/]/i
7069
7309
  ];
7070
7310
  function detectProfileFromChange(changeDir) {
7071
- if (!fs7.existsSync(changeDir)) return "standard";
7072
- const specFile = path4.join(changeDir, "spec.md");
7073
- if (fs7.existsSync(specFile)) {
7311
+ if (!fs8.existsSync(changeDir)) return "standard";
7312
+ const specFile = path5.join(changeDir, "spec.md");
7313
+ if (fs8.existsSync(specFile)) {
7074
7314
  try {
7075
- const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
7315
+ const rawSpec = fs8.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
7076
7316
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
7077
7317
  if (match) {
7078
7318
  const parsedFm = yaml3.load(match[1]);
@@ -7090,24 +7330,29 @@ function isPathProtectedFromPatch(filePath) {
7090
7330
  return PROTECTED_BYPASS_PATTERNS.some((pattern) => pattern.test(normalized));
7091
7331
  }
7092
7332
  function detectBranchChangeMatch(rootDir, dirs) {
7093
- if (!fs7.existsSync(path4.join(rootDir, ".git"))) return void 0;
7094
- try {
7095
- const branch = execFileSync("git", ["branch", "--show-current"], {
7096
- cwd: rootDir,
7097
- encoding: "utf-8",
7098
- stdio: ["pipe", "pipe", "ignore"]
7099
- }).trim().toLowerCase();
7100
- if (!branch) return void 0;
7101
- return dirs.find((d) => branch.includes(path4.basename(d).toLowerCase()));
7102
- } catch {
7103
- return void 0;
7333
+ let branch = "";
7334
+ if (fs8.existsSync(path5.join(rootDir, ".git"))) {
7335
+ try {
7336
+ branch = execFileSync("git", ["branch", "--show-current"], {
7337
+ cwd: rootDir,
7338
+ encoding: "utf-8",
7339
+ stdio: ["pipe", "pipe", "ignore"]
7340
+ }).trim().toLowerCase();
7341
+ } catch {
7342
+ }
7343
+ }
7344
+ if (!branch) {
7345
+ const ciBranch = (process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_REF_NAME || process.env.BRANCH_NAME || "").toLowerCase();
7346
+ branch = ciBranch;
7104
7347
  }
7348
+ if (!branch) return void 0;
7349
+ return dirs.find((d) => branch.includes(path5.basename(d).toLowerCase()));
7105
7350
  }
7106
7351
  function resolveActiveChangeDir(rootDir) {
7107
- const activeRoot = path4.join(rootDir, "specs", "changes", "active");
7108
- if (!fs7.existsSync(activeRoot)) return void 0;
7352
+ const activeRoot = path5.join(rootDir, "specs", "changes", "active");
7353
+ if (!fs8.existsSync(activeRoot)) return void 0;
7109
7354
  try {
7110
- const dirs = fs7.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path4.join(activeRoot, d.name));
7355
+ const dirs = fs8.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path5.join(activeRoot, d.name)).sort((a, b) => canonicalSort(a, b));
7111
7356
  const branchMatch = detectBranchChangeMatch(rootDir, dirs);
7112
7357
  if (branchMatch) return branchMatch;
7113
7358
  return dirs.find((d) => detectProfileFromChange(d) === "patch") || dirs[0];
@@ -7115,8 +7360,12 @@ function resolveActiveChangeDir(rootDir) {
7115
7360
  return void 0;
7116
7361
  }
7117
7362
  }
7363
+ function isEphemeralOrIgnored(file) {
7364
+ const norm = file.replace(/\\/g, "/");
7365
+ return norm.startsWith("scratch/") || norm.startsWith("reports/") || norm.startsWith("coverage/") || norm.startsWith("dist/") || norm.startsWith("temp/") || norm.startsWith("tmp/");
7366
+ }
7118
7367
  function detectGitDiffFiles(rootDir) {
7119
- if (!fs7.existsSync(path4.join(rootDir, ".git"))) return [];
7368
+ if (!fs8.existsSync(path5.join(rootDir, ".git"))) return [];
7120
7369
  try {
7121
7370
  const tracked = execFileSync("git", ["diff", "--name-only", "HEAD"], {
7122
7371
  cwd: rootDir,
@@ -7129,7 +7378,7 @@ function detectGitDiffFiles(rootDir) {
7129
7378
  stdio: ["pipe", "pipe", "ignore"]
7130
7379
  });
7131
7380
  const combined = `${tracked}
7132
- ${untracked}`.split(/\r?\n/).map((f) => f.trim()).filter(Boolean);
7381
+ ${untracked}`.split(/\r?\n/).map((f) => f.trim()).filter((f) => Boolean(f) && !isEphemeralOrIgnored(f));
7133
7382
  return Array.from(new Set(combined));
7134
7383
  } catch {
7135
7384
  return [];
@@ -7139,19 +7388,19 @@ function resolveChangeDirectory(rootDir, options) {
7139
7388
  if (options.changeDir) return options.changeDir;
7140
7389
  if (options.changeId) {
7141
7390
  const candidates = [
7142
- path4.join(rootDir, "specs", options.changeId),
7143
- path4.join(rootDir, "specs", "changes", "active", options.changeId),
7144
- path4.join(rootDir, "examples", "specs", options.changeId),
7145
- path4.join(rootDir, "specs", "changes", "completed", options.changeId)
7391
+ path5.join(rootDir, "specs", options.changeId),
7392
+ path5.join(rootDir, "specs", "changes", "active", options.changeId),
7393
+ path5.join(rootDir, "examples", "specs", options.changeId),
7394
+ path5.join(rootDir, "specs", "changes", "completed", options.changeId)
7146
7395
  ];
7147
- return candidates.find((c) => fs7.existsSync(c));
7396
+ return candidates.find((c) => fs8.existsSync(c));
7148
7397
  }
7149
7398
  return resolveActiveChangeDir(rootDir);
7150
7399
  }
7151
7400
  function collectPatchViolations(rootDir, modifiedFiles) {
7152
7401
  const violations = [];
7153
7402
  for (const rawFile of modifiedFiles) {
7154
- const relPath = path4.isAbsolute(rawFile) ? path4.relative(rootDir, rawFile) : rawFile;
7403
+ const relPath = path5.isAbsolute(rawFile) ? path5.relative(rootDir, rawFile) : rawFile;
7155
7404
  if (isPathProtectedFromPatch(relPath)) {
7156
7405
  violations.push(
7157
7406
  `[ANTI-PATCH-BYPASS] File '${relPath}' is a protected path (security, schemas, architectural decisions, or master policies). Changes with 'patch' profile are strictly prohibited from modifying these paths. Must be reclassified to 'standard' or 'critical'.`
@@ -7164,7 +7413,7 @@ function buildFrictionHeader(profile, success, fileCount) {
7164
7413
  return [
7165
7414
  `# \u{1F6E1}\uFE0F Progressive Friction and Anti-Bypass Verification Report`,
7166
7415
  ``,
7167
- `> **Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
7416
+ `> **Date:** ${resolveDeterministicDate().toISOString()}`,
7168
7417
  `> **Evaluated Profile:** \`${profile.toUpperCase()}\``,
7169
7418
  `> **Verdict:** ${success ? "COMPLIANT \u2705" : "BLOCKED \u274C"}`,
7170
7419
  ``,
@@ -7257,13 +7506,13 @@ function extractManualMetadata(fm, id, manualType, relPath, body) {
7257
7506
  }
7258
7507
  function buildManualItem(rootDir, filePath) {
7259
7508
  try {
7260
- const raw = fs7.readFileSync(filePath, "utf-8");
7509
+ const raw = fs8.readFileSync(filePath, "utf-8");
7261
7510
  const { fm, body } = parseMdFrontmatter(raw);
7262
7511
  const id = typeof fm.id === "string" ? fm.id.trim() : "";
7263
7512
  const rawType = typeof fm.type === "string" ? fm.type.trim() : "";
7264
7513
  const manualType = resolveManualType(id, rawType);
7265
7514
  if (!id || !manualType) return null;
7266
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
7515
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
7267
7516
  return extractManualMetadata(fm, id, manualType, relPath, body);
7268
7517
  } catch {
7269
7518
  return null;
@@ -7274,8 +7523,8 @@ function scanManualsInWorkspace(rootDir) {
7274
7523
  const seenIds = /* @__PURE__ */ new Set();
7275
7524
  const items = [];
7276
7525
  for (const relDir of manualDirs) {
7277
- const fullDir = path4.join(rootDir, relDir);
7278
- if (!fs7.existsSync(fullDir)) continue;
7526
+ const fullDir = path5.join(rootDir, relDir);
7527
+ if (!fs8.existsSync(fullDir)) continue;
7279
7528
  for (const file of walkMdFiles(fullDir)) {
7280
7529
  const item = buildManualItem(rootDir, file);
7281
7530
  if (item && !seenIds.has(item.id)) {
@@ -7298,10 +7547,10 @@ function classifyBaselineId(id, type, buckets) {
7298
7547
  }
7299
7548
  }
7300
7549
  function processBaselineFile(file, rootDir, buckets) {
7301
- const rel = path4.relative(rootDir, file).replace(/\\/g, "/");
7550
+ const rel = path5.relative(rootDir, file).replace(/\\/g, "/");
7302
7551
  if (rel.includes("/changes/") || rel.startsWith("changes/")) return;
7303
7552
  try {
7304
- const { fm } = parseMdFrontmatter(fs7.readFileSync(file, "utf-8"));
7553
+ const { fm } = parseMdFrontmatter(fs8.readFileSync(file, "utf-8"));
7305
7554
  const id = typeof fm.id === "string" ? fm.id.trim() : "";
7306
7555
  const status = typeof fm.status === "string" ? fm.status.trim().toLowerCase() : "active";
7307
7556
  if (!id || status === "retired" || status === "deprecated") return;
@@ -7322,8 +7571,8 @@ function collectBaselineTargets(rootDir) {
7322
7571
  enclaves: /* @__PURE__ */ new Set()
7323
7572
  };
7324
7573
  for (const dir of ["product", "architecture", "security", "specs"]) {
7325
- const fullDir = path4.join(rootDir, dir);
7326
- if (fs7.existsSync(fullDir)) {
7574
+ const fullDir = path5.join(rootDir, dir);
7575
+ if (fs8.existsSync(fullDir)) {
7327
7576
  scanDirectoryForBaseline(rootDir, fullDir, buckets);
7328
7577
  }
7329
7578
  }
@@ -7380,10 +7629,10 @@ function detectCurrentGitBranch(rootDir) {
7380
7629
  }
7381
7630
  }
7382
7631
  function resolveActiveChangeProfile(rootDir) {
7383
- const activeRoot = path4.join(rootDir, "specs", "changes", "active");
7384
- if (!fs7.existsSync(activeRoot)) return "standard";
7632
+ const activeRoot = path5.join(rootDir, "specs", "changes", "active");
7633
+ if (!fs8.existsSync(activeRoot)) return "standard";
7385
7634
  try {
7386
- const dirs = fs7.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path4.join(activeRoot, d.name));
7635
+ const dirs = fs8.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path5.join(activeRoot, d.name));
7387
7636
  const profiles = dirs.map((d) => detectProfileFromChange(d));
7388
7637
  if (profiles.includes("critical")) return "critical";
7389
7638
  if (profiles.includes("patch") && profiles.length === 1) return "patch";
@@ -7520,7 +7769,7 @@ var REQ_TYPES = /* @__PURE__ */ new Set(["requirement", "security-requirement",
7520
7769
  var REQ_CATEGORIES = /* @__PURE__ */ new Set(["functional", "quality", "constraint", "security", "safety"]);
7521
7770
  function computeSha256(content) {
7522
7771
  const normalized = content.replace(/\r\n/g, "\n");
7523
- return crypto.createHash("sha256").update(normalized, "utf-8").digest("hex");
7772
+ return crypto3.createHash("sha256").update(normalized, "utf-8").digest("hex");
7524
7773
  }
7525
7774
  function tokenizeTitle(title) {
7526
7775
  const words = title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOP_WORDS.has(w));
@@ -7596,11 +7845,11 @@ function buildRequirementObject(content, fm, body, filePath, relFile) {
7596
7845
  }
7597
7846
  function parseRequirementFile(filePath, rootDir) {
7598
7847
  try {
7599
- const content = fs7.readFileSync(filePath, "utf-8");
7848
+ const content = fs8.readFileSync(filePath, "utf-8");
7600
7849
  const extracted = extractParsedFrontmatter(content);
7601
7850
  if (!extracted) return null;
7602
7851
  const body = content.substring(extracted.matchLen);
7603
- const relFile = path4.relative(rootDir, filePath).replace(/\\/g, "/");
7852
+ const relFile = path5.relative(rootDir, filePath).replace(/\\/g, "/");
7604
7853
  return buildRequirementObject(content, extracted.fm, body, filePath, relFile);
7605
7854
  } catch {
7606
7855
  return null;
@@ -7612,7 +7861,7 @@ function isIgnoredScanPath(relFile) {
7612
7861
  function collectRequirements(rootDir) {
7613
7862
  const reqs = [];
7614
7863
  for (const file of walkMdFiles(rootDir)) {
7615
- const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
7864
+ const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
7616
7865
  if (isIgnoredScanPath(relFile)) continue;
7617
7866
  const req = parseRequirementFile(file, rootDir);
7618
7867
  if (req) reqs.push(req);
@@ -7746,7 +7995,7 @@ function buildDuplicatesHeader(result) {
7746
7995
  return [
7747
7996
  `# \u{1F6E1}\uFE0F Duplicate Requirements and Redundancy Audit (Shift-Left Pre-Flight)`,
7748
7997
  ``,
7749
- `> **Evaluation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
7998
+ `> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
7750
7999
  `> **Verdict:** ${isOk ? "\u2705 COMPLIANT (No blocking duplicates)" : `\u274C BLOCKED (${result.errorCount} duplicate errors detected)`}`,
7751
8000
  `> **Audited Requirements:** ${result.totalRequirements} | **Errors:** ${result.errorCount} | **Warnings:** ${result.warningCount}`,
7752
8001
  ``,
@@ -7869,7 +8118,7 @@ function buildAuditHeader(total, active, expired, invalid) {
7869
8118
  return [
7870
8119
  "# \u{1F4CB} Security Suppressions and Audit Exceptions Inventory",
7871
8120
  "",
7872
- `> **Generated at:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
8121
+ `> **Generated at:** ${resolveDeterministicDate().toISOString()}`,
7873
8122
  `> **Total Audited Suppressions:** ${total} | **Active:** ${active} | **Expired:** ${expired} | **Invalid:** ${invalid}`,
7874
8123
  "",
7875
8124
  "## 1. Executive Summary",
@@ -8100,10 +8349,10 @@ function isPathExcluded2(relPath, patterns) {
8100
8349
  function walkProjectFiles(dir, rootDir, excludedPatterns = DEFAULT_EXCLUDED_PATTERNS) {
8101
8350
  const results = [];
8102
8351
  try {
8103
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
8352
+ const entries = fs8.readdirSync(dir, { withFileTypes: true }).sort((a, b) => canonicalSort(a.name, b.name));
8104
8353
  for (const entry of entries) {
8105
- const fullPath = path4.join(dir, entry.name);
8106
- const relPath = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
8354
+ const fullPath = path5.join(dir, entry.name);
8355
+ const relPath = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
8107
8356
  if (isPathExcluded2(relPath, excludedPatterns)) continue;
8108
8357
  if (entry.isDirectory()) {
8109
8358
  results.push(...walkProjectFiles(fullPath, rootDir, excludedPatterns));
@@ -8132,8 +8381,8 @@ function pickFirstString(...vals) {
8132
8381
  }
8133
8382
  function parseGitleaksItem(item, rootDir) {
8134
8383
  const rawFile = pickFirstString(item.File, item.file);
8135
- const filePath = path4.resolve(rootDir, rawFile);
8136
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
8384
+ const filePath = path5.resolve(rootDir, rawFile);
8385
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
8137
8386
  const ruleId = pickFirstString(item.RuleID, item.ruleID) || "GITLEAKS-MATCH";
8138
8387
  const rawSecret = pickFirstString(item.Secret, item.secret, item.Match);
8139
8388
  const line = item.StartLine ?? item.line ?? 1;
@@ -8206,7 +8455,7 @@ function scanLineForSecrets(line, lineNumber, filePath, relPath, entropyThreshol
8206
8455
  function scanContentForSecrets(content, filePath, rootDir, entropyThreshold = 4.3, collectedSuppressions) {
8207
8456
  const violations = [];
8208
8457
  const lines = content.split(/\r?\n/);
8209
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
8458
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
8210
8459
  for (let i = 0; i < lines.length; i++) {
8211
8460
  violations.push(...scanLineForSecrets(lines[i], i + 1, filePath, relPath, entropyThreshold, collectedSuppressions));
8212
8461
  }
@@ -8227,7 +8476,7 @@ function scanDiffItems(diffLines, rootDir, excludePatterns, entropyThreshold) {
8227
8476
  const suppressions = [];
8228
8477
  for (const [relFile, items] of fileGroups) {
8229
8478
  if (isPathExcluded2(relFile, excludePatterns)) continue;
8230
- const absPath = path4.resolve(rootDir, relFile);
8479
+ const absPath = path5.resolve(rootDir, relFile);
8231
8480
  for (const item of items) {
8232
8481
  const lineViolations = scanLineForSecrets(item.content, item.lineNumber, absPath, relFile, entropyThreshold, suppressions);
8233
8482
  for (const v of lineViolations) {
@@ -8243,7 +8492,7 @@ function scanAllProjectFiles(rootDir, excludePatterns, entropyThreshold) {
8243
8492
  const suppressions = [];
8244
8493
  for (const file of allFiles) {
8245
8494
  try {
8246
- const content = fs7.readFileSync(file, "utf-8");
8495
+ const content = fs8.readFileSync(file, "utf-8");
8247
8496
  findings.push(...scanContentForSecrets(content, file, rootDir, entropyThreshold, suppressions));
8248
8497
  } catch {
8249
8498
  }
@@ -8300,7 +8549,7 @@ function buildReportHeader2(result) {
8300
8549
  return [
8301
8550
  `# \u{1F510} Deterministic Secrets and Credentials Audit (Gitleaks Gate)`,
8302
8551
  ``,
8303
- `> **Evaluation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
8552
+ `> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
8304
8553
  `> **Verdict:** ${isOk ? "\u2705 COMPLIANT (No exposed secrets or credentials)" : `\u274C BLOCKED (${result.findingsCount} exposed credentials detected)`}`,
8305
8554
  `> **Audited Files:** ${result.totalFilesScanned} | **Security Violations:** ${result.findingsCount}`,
8306
8555
  `> **Native Gitleaks Engine:** ${result.scannedWithGitleaks ? "\u{1F7E2} Active / Integrated" : "\u26AA Deterministic Native Scanner (@aisdlc/core)"}`,
@@ -8469,8 +8718,8 @@ function runSemgrepCli(rootDir) {
8469
8718
  const parsed = JSON.parse(output);
8470
8719
  if (Array.isArray(parsed.results)) {
8471
8720
  for (const item of parsed.results) {
8472
- const filePath = path4.resolve(rootDir, item.path);
8473
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
8721
+ const filePath = path5.resolve(rootDir, item.path);
8722
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
8474
8723
  violations.push({
8475
8724
  filePath,
8476
8725
  relPath,
@@ -8497,15 +8746,15 @@ function isSastExcluded(relPath, excludes) {
8497
8746
  function walkSourceFiles(dir, rootDir, excludes = DEFAULT_SAST_EXCLUDES) {
8498
8747
  const files = [];
8499
8748
  try {
8500
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
8749
+ const entries = fs8.readdirSync(dir, { withFileTypes: true }).sort((a, b) => canonicalSort(a.name, b.name));
8501
8750
  for (const entry of entries) {
8502
- const fullPath = path4.join(dir, entry.name);
8503
- const relPath = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
8751
+ const fullPath = path5.join(dir, entry.name);
8752
+ const relPath = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
8504
8753
  if (isSastExcluded(relPath, excludes)) continue;
8505
8754
  if (entry.isDirectory()) {
8506
8755
  files.push(...walkSourceFiles(fullPath, rootDir, excludes));
8507
8756
  } else if (entry.isFile()) {
8508
- const ext = path4.extname(entry.name).toLowerCase();
8757
+ const ext = path5.extname(entry.name).toLowerCase();
8509
8758
  if (GENERAL_SOURCE_EXTENSIONS.includes(ext)) {
8510
8759
  files.push(fullPath);
8511
8760
  }
@@ -8530,11 +8779,11 @@ function checkRuleMatch(rule, line, ext, minSeverity) {
8530
8779
  }
8531
8780
  function scanFileForSast(filePath, rootDir, minSeverity = "HIGH", collectedSuppressions) {
8532
8781
  const violations = [];
8533
- const ext = path4.extname(filePath).toLowerCase();
8782
+ const ext = path5.extname(filePath).toLowerCase();
8534
8783
  try {
8535
- const content = fs7.readFileSync(filePath, "utf-8");
8784
+ const content = fs8.readFileSync(filePath, "utf-8");
8536
8785
  const lines = content.split(/\r?\n/);
8537
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
8786
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
8538
8787
  for (let i = 0; i < lines.length; i++) {
8539
8788
  const line = lines[i];
8540
8789
  if (checkLineSuppression(line, relPath, i + 1, collectedSuppressions)) continue;
@@ -8560,8 +8809,8 @@ function scanFileForSast(filePath, rootDir, minSeverity = "HIGH", collectedSuppr
8560
8809
  function collectSastFiles(rootDir, targetDirs) {
8561
8810
  const files = [];
8562
8811
  for (const tDir of targetDirs) {
8563
- const fullDir = path4.join(rootDir, tDir);
8564
- if (fs7.existsSync(fullDir)) {
8812
+ const fullDir = path5.join(rootDir, tDir);
8813
+ if (fs8.existsSync(fullDir)) {
8565
8814
  files.push(...walkSourceFiles(fullDir, rootDir));
8566
8815
  }
8567
8816
  }
@@ -8607,7 +8856,7 @@ function buildSastHeader(isOk, violationsCount, totalFilesScanned) {
8607
8856
  return [
8608
8857
  `# \u{1F6E1}\uFE0F Shift-Left SAST Audit (Static Application Security Testing)`,
8609
8858
  ``,
8610
- `> **Evaluation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
8859
+ `> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
8611
8860
  `> **Verdict:** ${verdict}`,
8612
8861
  `> **Audited Code Files:** ${totalFilesScanned} | **SAST Violations:** ${violationsCount}`,
8613
8862
  ``,
@@ -8796,22 +9045,31 @@ var PII_SAST_RULES = [
8796
9045
  pattern: /\b(?:ssn|dni|nif|tax_?id|national_?id)\s*[:=]\s*['"](?:\d{3}-\d{2}-\d{4}|\d{8}[A-HJ-NP-TV-Z])['"]/i
8797
9046
  }
8798
9047
  ];
8799
- function loadPrivacyPolicy(rootDir, policyPath) {
8800
- const candidate = policyPath ? path4.isAbsolute(policyPath) ? policyPath : path4.join(rootDir, policyPath) : path4.join(rootDir, "privacy-policy.yaml");
8801
- if (!fs7.existsSync(candidate)) return DEFAULT_PRIVACY_POLICY;
9048
+ function parsePrivacyDoc(raw, candidate) {
9049
+ let parsed;
8802
9050
  try {
8803
- const parsed = yaml3.load(fs7.readFileSync(candidate, "utf-8"));
8804
- if (!parsed || typeof parsed !== "object") return DEFAULT_PRIVACY_POLICY;
8805
- return {
8806
- ...DEFAULT_PRIVACY_POLICY,
8807
- ...parsed,
8808
- classifications: { ...DEFAULT_PRIVACY_POLICY.classifications, ...parsed.classifications || {} },
8809
- agent_guardrails: { ...DEFAULT_PRIVACY_POLICY.agent_guardrails, ...parsed.agent_guardrails || {} },
8810
- ci_gates: { ...DEFAULT_PRIVACY_POLICY.ci_gates, ...parsed.ci_gates || {} }
8811
- };
8812
- } catch {
8813
- return DEFAULT_PRIVACY_POLICY;
9051
+ parsed = yaml3.load(raw);
9052
+ } catch (err) {
9053
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in privacy-policy '${candidate}': ${err instanceof Error ? err.message : String(err)}`);
9054
+ }
9055
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
9056
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid privacy policy structure in '${candidate}': root must be a YAML mapping`);
8814
9057
  }
9058
+ return parsed;
9059
+ }
9060
+ function loadPrivacyPolicy(rootDir = process.cwd(), policyPath) {
9061
+ const candidate = policyPath ? path5.isAbsolute(policyPath) ? policyPath : path5.join(rootDir, policyPath) : path5.join(rootDir, "privacy-policy.yaml");
9062
+ if (!fs8.existsSync(candidate)) return DEFAULT_PRIVACY_POLICY;
9063
+ const raw = fs8.readFileSync(candidate, "utf-8");
9064
+ if (!raw || !raw.trim()) return DEFAULT_PRIVACY_POLICY;
9065
+ const parsed = parsePrivacyDoc(raw, candidate);
9066
+ return {
9067
+ ...DEFAULT_PRIVACY_POLICY,
9068
+ ...parsed,
9069
+ classifications: { ...DEFAULT_PRIVACY_POLICY.classifications, ...parsed.classifications || {} },
9070
+ agent_guardrails: { ...DEFAULT_PRIVACY_POLICY.agent_guardrails, ...parsed.agent_guardrails || {} },
9071
+ ci_gates: { ...DEFAULT_PRIVACY_POLICY.ci_gates, ...parsed.ci_gates || {} }
9072
+ };
8815
9073
  }
8816
9074
  function isLineExemptFromPrivacyScan(line) {
8817
9075
  return line.includes("ai-sdlc:allow-pii") || line.includes("ai-sdlc:allow-sast") || line.includes("nosec");
@@ -8828,9 +9086,9 @@ function shouldApplyPiiRule(rule, policy) {
8828
9086
  function scanFileForPrivacyViolations(filePath, rootDir, policy = DEFAULT_PRIVACY_POLICY) {
8829
9087
  const violations = [];
8830
9088
  try {
8831
- const content = fs7.readFileSync(filePath, "utf-8");
9089
+ const content = fs8.readFileSync(filePath, "utf-8");
8832
9090
  const lines = content.split(/\r?\n/);
8833
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
9091
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
8834
9092
  for (let i = 0; i < lines.length; i++) {
8835
9093
  const line = lines[i];
8836
9094
  if (isLineExemptFromPrivacyScan(line)) continue;
@@ -8939,10 +9197,10 @@ function discoverPrivacyArtifacts(rootDir) {
8939
9197
  dpiaStatuses: /* @__PURE__ */ new Map()
8940
9198
  };
8941
9199
  for (const file of walkMdFiles(rootDir)) {
8942
- const relPath = path4.relative(rootDir, file).replace(/\\/g, "/");
9200
+ const relPath = path5.relative(rootDir, file).replace(/\\/g, "/");
8943
9201
  if (isExcludedPrivacyMd(relPath)) continue;
8944
9202
  try {
8945
- const fm = extractFrontmatter(fs7.readFileSync(file, "utf-8"));
9203
+ const fm = extractFrontmatter(fs8.readFileSync(file, "utf-8"));
8946
9204
  if (fm && fm.id) classifyPrivacyMarkdown(fm, relPath, acc);
8947
9205
  } catch {
8948
9206
  }
@@ -8987,7 +9245,7 @@ function checkAssetRopaRegistration(asset, ropaRecords, policy, fullPath) {
8987
9245
  }
8988
9246
  function auditDataAssetGovernance(asset, ropaRecords, policy, rootDir) {
8989
9247
  if (!isSensitiveClassification(asset.classification)) return [];
8990
- const fullPath = path4.join(rootDir, asset.filePath);
9248
+ const fullPath = path5.join(rootDir, asset.filePath);
8991
9249
  const v1 = checkAssetRetention(asset, policy, fullPath);
8992
9250
  const v2 = checkAssetRopaRegistration(asset, ropaRecords, policy, fullPath);
8993
9251
  return [v1, v2].filter((v) => v !== null);
@@ -9059,8 +9317,8 @@ function checkRopaDpiaApproval(ropa, dataAssets, dpiaStatuses, fullPath) {
9059
9317
  function collectSourceFilesForPrivacy(rootDir, targetDirs) {
9060
9318
  const files = [];
9061
9319
  for (const dir of targetDirs) {
9062
- const fullDir = path4.join(rootDir, dir);
9063
- if (fs7.existsSync(fullDir)) {
9320
+ const fullDir = path5.join(rootDir, dir);
9321
+ if (fs8.existsSync(fullDir)) {
9064
9322
  files.push(...walkSourceFiles(fullDir, rootDir));
9065
9323
  }
9066
9324
  }
@@ -9150,15 +9408,13 @@ function generatePrivacyReportMarkdown(result, policy = DEFAULT_PRIVACY_POLICY)
9150
9408
  function writePrivacyOutputsIfNeeded(result, options, policy, rootDir) {
9151
9409
  if (options.generateRopa) {
9152
9410
  const relRopa = options.ropaOutputPath || policy.ci_gates.output_ropa_file || "reports/ROPA_INVENTORY.md";
9153
- const fullRopa = path4.isAbsolute(relRopa) ? relRopa : path4.join(rootDir, relRopa);
9154
- fs7.mkdirSync(path4.dirname(fullRopa), { recursive: true });
9155
- fs7.writeFileSync(fullRopa, result.ropaMarkdown, "utf-8");
9411
+ const fullRopa = path5.isAbsolute(relRopa) ? relRopa : path5.join(rootDir, relRopa);
9412
+ writeReportFileIfChanged(fullRopa, result.ropaMarkdown);
9156
9413
  result.ropaOutputPath = fullRopa;
9157
9414
  }
9158
9415
  if (options.reportOutputPath) {
9159
- const fullReport = path4.isAbsolute(options.reportOutputPath) ? options.reportOutputPath : path4.join(rootDir, options.reportOutputPath);
9160
- fs7.mkdirSync(path4.dirname(fullReport), { recursive: true });
9161
- fs7.writeFileSync(fullReport, result.reportMarkdown, "utf-8");
9416
+ const fullReport = path5.isAbsolute(options.reportOutputPath) ? options.reportOutputPath : path5.join(rootDir, options.reportOutputPath);
9417
+ writeReportFileIfChanged(fullReport, result.reportMarkdown);
9162
9418
  result.reportOutputPath = fullReport;
9163
9419
  }
9164
9420
  }
@@ -9170,7 +9426,7 @@ function evaluateDiscoveredPrivacyGovernance(discovered, policy, rootDir) {
9170
9426
  }
9171
9427
  let dpiaRequiredCount = 0;
9172
9428
  for (const ropa of discovered.ropaRecords) {
9173
- const fullPath = path4.join(rootDir, ropa.filePath);
9429
+ const fullPath = path5.join(rootDir, ropa.filePath);
9174
9430
  violations.push(...checkRopaControlsAndSubprocessors(ropa, governedRopaIds, policy, fullPath));
9175
9431
  const dpiaCheck = checkRopaDpiaApproval(ropa, discovered.dataAssets, discovered.dpiaStatuses, fullPath);
9176
9432
  if (dpiaCheck.dpiaRequired) dpiaRequiredCount++;
@@ -9216,7 +9472,7 @@ function calculateRating(mi, cc) {
9216
9472
  return "F";
9217
9473
  }
9218
9474
  function detectLanguage(filePath) {
9219
- const ext = path4.extname(filePath).toLowerCase();
9475
+ const ext = path5.extname(filePath).toLowerCase();
9220
9476
  switch (ext) {
9221
9477
  case ".ts":
9222
9478
  return "TypeScript";
@@ -9241,7 +9497,7 @@ function detectLanguage(filePath) {
9241
9497
  }
9242
9498
  function parseJsonSummaryFile(filePath, relSource) {
9243
9499
  try {
9244
- const raw = JSON.parse(fs7.readFileSync(filePath, "utf-8"));
9500
+ const raw = JSON.parse(fs8.readFileSync(filePath, "utf-8"));
9245
9501
  const total = raw.total;
9246
9502
  if (!total || typeof total !== "object") return null;
9247
9503
  const linesPct = Number(total.lines?.pct);
@@ -9264,7 +9520,7 @@ function updateLcovCounts(line, counts) {
9264
9520
  }
9265
9521
  function parseLcovFile(filePath, relSource) {
9266
9522
  try {
9267
- const lines = fs7.readFileSync(filePath, "utf-8").split(/\r?\n/);
9523
+ const lines = fs8.readFileSync(filePath, "utf-8").split(/\r?\n/);
9268
9524
  const counts = { LF: 0, LH: 0, BRF: 0, BRH: 0, FNF: 0, FNH: 0 };
9269
9525
  for (const line of lines) {
9270
9526
  updateLcovCounts(line, counts);
@@ -9284,16 +9540,16 @@ function parseLcovFile(filePath, relSource) {
9284
9540
  function readCodeCoverageSummary(rootDir = process.cwd()) {
9285
9541
  const jsonCandidates = ["coverage/coverage-summary.json", "reports/coverage-summary.json"];
9286
9542
  for (const rel of jsonCandidates) {
9287
- const full = path4.join(rootDir, rel);
9288
- if (fs7.existsSync(full)) {
9543
+ const full = path5.join(rootDir, rel);
9544
+ if (fs8.existsSync(full)) {
9289
9545
  const parsed = parseJsonSummaryFile(full, rel);
9290
9546
  if (parsed) return parsed;
9291
9547
  }
9292
9548
  }
9293
9549
  const lcovCandidates = ["coverage/lcov.info", "lcov.info", "reports/lcov.info"];
9294
9550
  for (const rel of lcovCandidates) {
9295
- const full = path4.join(rootDir, rel);
9296
- if (fs7.existsSync(full)) {
9551
+ const full = path5.join(rootDir, rel);
9552
+ if (fs8.existsSync(full)) {
9297
9553
  const parsed = parseLcovFile(full, rel);
9298
9554
  if (parsed) return parsed;
9299
9555
  }
@@ -9378,7 +9634,7 @@ function buildReportHeader3(verdict, rating, mi, cc) {
9378
9634
  return [
9379
9635
  `# \u{1F4CA} Formal Quality and Release Gate Report (AI-SDLC)`,
9380
9636
  "",
9381
- `> **Generation Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
9637
+ `> **Generation Date:** ${resolveDeterministicDate().toISOString()}`,
9382
9638
  `> **Release Gate Verdict:** ${status}`,
9383
9639
  `> **Global Rating:** **\`${rating}\`** (MI Index: ${mi}/100, Average CC: ${cc})`
9384
9640
  ];
@@ -9429,9 +9685,8 @@ function renderQualityReportMarkdown(gateResult, coverage, accumulated, averages
9429
9685
  return [...header, "", "---", "", ...table, "", "---", "", ...polyglot, ...violationsSection, "", EVALUATION_STANDARDS_MD].join("\n");
9430
9686
  }
9431
9687
  function writeQualityReportFile(rootDir, markdown) {
9432
- const reportsDir = path4.join(rootDir, "reports");
9433
- fs7.mkdirSync(reportsDir, { recursive: true });
9434
- fs7.writeFileSync(path4.join(reportsDir, "QUALITY_REPORT.md"), markdown, "utf-8");
9688
+ const targetPath = path5.join(rootDir, "reports", "QUALITY_REPORT.md");
9689
+ return writeReportFileIfChanged(targetPath, markdown);
9435
9690
  }
9436
9691
  function generateQualityReport(options = {}) {
9437
9692
  const rootDir = options.rootDir || process.cwd();
@@ -9573,9 +9828,9 @@ Flujo de Integraci\xF3n (Merges):`,
9573
9828
  };
9574
9829
  }
9575
9830
  function resolveVersionFromProposal(changeDir) {
9576
- const proposalFile = path4.join(changeDir, "proposal.md");
9577
- if (!fs7.existsSync(proposalFile)) return "1.0.0";
9578
- const propContent = fs7.readFileSync(proposalFile, "utf-8");
9831
+ const proposalFile = path5.join(changeDir, "proposal.md");
9832
+ if (!fs8.existsSync(proposalFile)) return "1.0.0";
9833
+ const propContent = fs8.readFileSync(proposalFile, "utf-8");
9579
9834
  const propFm = propContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
9580
9835
  if (!propFm) return "1.0.0";
9581
9836
  const parsedProp = yaml3.load(propFm[1]);
@@ -9592,9 +9847,9 @@ function isTaskMatchingId(currentId, cleanTaskId) {
9592
9847
  return currentLower === targetLower || currentLower.startsWith(`${targetLower}-`) || targetLower.startsWith(`${currentLower}-`);
9593
9848
  }
9594
9849
  function parseTasksFrontmatter(tasksFile) {
9595
- if (!fs7.existsSync(tasksFile)) return null;
9850
+ if (!fs8.existsSync(tasksFile)) return null;
9596
9851
  try {
9597
- const content = fs7.readFileSync(tasksFile, "utf-8");
9852
+ const content = fs8.readFileSync(tasksFile, "utf-8");
9598
9853
  const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
9599
9854
  if (!fmMatch) return null;
9600
9855
  const fm = yaml3.load(fmMatch[1]);
@@ -9616,7 +9871,7 @@ function buildTaskMatch(currentId, currentTitle, rawChangeId, changeDir, version
9616
9871
  };
9617
9872
  }
9618
9873
  function inspectChangeTasks(changeDir, entryName, cleanTaskId, availableTasks) {
9619
- const fm = parseTasksFrontmatter(path4.join(changeDir, "tasks.md"));
9874
+ const fm = parseTasksFrontmatter(path5.join(changeDir, "tasks.md"));
9620
9875
  if (!fm || !fm.tasks) return void 0;
9621
9876
  const rawChangeId = resolveRawChangeId(fm, entryName);
9622
9877
  const versionStr = resolveChangeVersion(fm, changeDir);
@@ -9637,16 +9892,16 @@ function locateTaskInActiveChanges(taskId, rootDir = process.cwd()) {
9637
9892
  if (!cleanTaskId) {
9638
9893
  return { found: false, error: "Debe especificar un identificador de tarea v\xE1lido (ej. TSK-001)." };
9639
9894
  }
9640
- const activeChangesDir = path4.join(rootDir, "specs", "changes", "active");
9641
- if (!fs7.existsSync(activeChangesDir)) {
9895
+ const activeChangesDir = path5.join(rootDir, "specs", "changes", "active");
9896
+ if (!fs8.existsSync(activeChangesDir)) {
9642
9897
  return { found: false, error: `No se encontr\xF3 el directorio de cambios activos en '${activeChangesDir}'.` };
9643
9898
  }
9644
9899
  const availableTasks = [];
9645
- const candidateDirs = fs7.readdirSync(activeChangesDir, { withFileTypes: true });
9900
+ const candidateDirs = fs8.readdirSync(activeChangesDir, { withFileTypes: true });
9646
9901
  let matchedTask;
9647
9902
  for (const entry of candidateDirs) {
9648
9903
  if (!entry.isDirectory()) continue;
9649
- const changeDir = path4.join(activeChangesDir, entry.name);
9904
+ const changeDir = path5.join(activeChangesDir, entry.name);
9650
9905
  const m = inspectChangeTasks(changeDir, entry.name, cleanTaskId, availableTasks);
9651
9906
  if (m && !matchedTask) {
9652
9907
  matchedTask = m;
@@ -9683,14 +9938,14 @@ function detectExistingFeatureBranch(branches, dirName, changeId, changeSlug) {
9683
9938
  (b) => b.startsWith("feat/") && (b.toLowerCase().includes(dirName.toLowerCase()) || b.toLowerCase().includes(changeId.toLowerCase())) && classifyBranch(b).valid
9684
9939
  );
9685
9940
  if (existing) return existing;
9686
- const normalizedName = slugify(changeSlug).replace(/^(chg|bug|feat)-(\d+)/i, (_, p, n) => `${p.toUpperCase()}-${n}`);
9941
+ const normalizedName = slugify2(changeSlug).replace(/^(chg|bug|feat)-(\d+)/i, (_, p, n) => `${p.toUpperCase()}-${n}`);
9687
9942
  return `feat/${normalizedName}`;
9688
9943
  }
9689
9944
  function buildDefaultTaskSuffix(taskId, taskTitle) {
9690
9945
  let taskSuffix = taskId.toLowerCase();
9691
9946
  const hasSlug = taskSuffix.includes("-") && taskSuffix.split("-").length > 2;
9692
9947
  if (!hasSlug && taskTitle) {
9693
- const titleSlug = slugify(taskTitle).split("-").slice(0, 4).join("-");
9948
+ const titleSlug = slugify2(taskTitle).split("-").slice(0, 4).join("-");
9694
9949
  if (titleSlug) {
9695
9950
  taskSuffix = `${taskSuffix}-${titleSlug}`;
9696
9951
  }
@@ -9710,7 +9965,7 @@ function resolveBranchHierarchy(match, rootDir = process.cwd()) {
9710
9965
  const branches = getGitBranchList(rootDir);
9711
9966
  const cleanVersion = (match.version || "1.0.0").replace(/^v/, "");
9712
9967
  const releaseBranch = detectExistingReleaseBranch(branches, cleanVersion);
9713
- const dirName = path4.basename(match.changeDir);
9968
+ const dirName = path5.basename(match.changeDir);
9714
9969
  const changeSlug = dirName.replace(/^(chg|CHG)-/i, (m) => m.toUpperCase());
9715
9970
  const featureBranch = detectExistingFeatureBranch(branches, dirName, match.changeId, changeSlug);
9716
9971
  const parentIdMatch = featureBranch.replace(/^(feat|feature)\//i, "").match(/^[a-zA-Z0-9]+-[a-zA-Z0-9]+/);
@@ -10017,13 +10272,13 @@ function getGitConfigValue(key, cwd) {
10017
10272
  }
10018
10273
  function readSessionTelemetry(cwd) {
10019
10274
  const possiblePaths = [
10020
- path4.join(cwd, ".ai-sdlc", "session-telemetry.json"),
10021
- path4.join(cwd, ".ai-sdlc", "agent-session.json")
10275
+ path5.join(cwd, ".ai-sdlc", "session-telemetry.json"),
10276
+ path5.join(cwd, ".ai-sdlc", "agent-session.json")
10022
10277
  ];
10023
10278
  for (const p of possiblePaths) {
10024
- if (fs7.existsSync(p)) {
10279
+ if (fs8.existsSync(p)) {
10025
10280
  try {
10026
- const raw = fs7.readFileSync(p, "utf-8");
10281
+ const raw = fs8.readFileSync(p, "utf-8");
10027
10282
  return JSON.parse(raw);
10028
10283
  } catch {
10029
10284
  }
@@ -10243,20 +10498,20 @@ if [ -f "$TELEMETRY_FILE" ]; then
10243
10498
  fi
10244
10499
  `;
10245
10500
  function installGitHooks(rootDir = process.cwd()) {
10246
- const gitDir = path4.join(rootDir, ".git");
10247
- if (!fs7.existsSync(gitDir)) {
10501
+ const gitDir = path5.join(rootDir, ".git");
10502
+ if (!fs8.existsSync(gitDir)) {
10248
10503
  return {
10249
10504
  success: false,
10250
10505
  error: `No se encontr\xF3 el directorio Git en '${gitDir}'.`
10251
10506
  };
10252
10507
  }
10253
- const hooksDir = path4.join(gitDir, "hooks");
10254
- if (!fs7.existsSync(hooksDir)) {
10255
- fs7.mkdirSync(hooksDir, { recursive: true });
10508
+ const hooksDir = path5.join(gitDir, "hooks");
10509
+ if (!fs8.existsSync(hooksDir)) {
10510
+ fs8.mkdirSync(hooksDir, { recursive: true });
10256
10511
  }
10257
- const hookPath = path4.join(hooksDir, "prepare-commit-msg");
10512
+ const hookPath = path5.join(hooksDir, "prepare-commit-msg");
10258
10513
  try {
10259
- fs7.writeFileSync(hookPath, PREPARE_COMMIT_MSG_SCRIPT, { encoding: "utf-8", mode: 493 });
10514
+ fs8.writeFileSync(hookPath, PREPARE_COMMIT_MSG_SCRIPT, { encoding: "utf-8", mode: 493 });
10260
10515
  return {
10261
10516
  success: true,
10262
10517
  hookPath
@@ -10597,16 +10852,16 @@ function formatReleaseKpiMarkdown(report) {
10597
10852
  ].join("\n");
10598
10853
  }
10599
10854
  function writeReleaseKpiReport(report, options = {}) {
10600
- const root = options.outputDir || (options.rootDir ? path4.join(options.rootDir, "reports", "releases") : path4.join(process.cwd(), "reports", "releases"));
10601
- if (!fs7.existsSync(root)) {
10602
- fs7.mkdirSync(root, { recursive: true });
10855
+ const root = options.outputDir || (options.rootDir ? path5.join(options.rootDir, "reports", "releases") : path5.join(process.cwd(), "reports", "releases"));
10856
+ if (!fs8.existsSync(root)) {
10857
+ fs8.mkdirSync(root, { recursive: true });
10603
10858
  }
10604
10859
  const cleanBranchName = report.releaseBranch.replace(/[^a-zA-Z0-9.-]/g, "_");
10605
- const mdPath = path4.join(root, `RELEASE_KPIS_${cleanBranchName}.md`);
10606
- const jsonPath = path4.join(root, `release_${cleanBranchName}.kpis.json`);
10860
+ const mdPath = path5.join(root, `RELEASE_KPIS_${cleanBranchName}.md`);
10861
+ const jsonPath = path5.join(root, `release_${cleanBranchName}.kpis.json`);
10607
10862
  const mdContent = formatReleaseKpiMarkdown(report);
10608
- fs7.writeFileSync(mdPath, mdContent, "utf-8");
10609
- fs7.writeFileSync(jsonPath, JSON.stringify(report, null, 2), "utf-8");
10863
+ fs8.writeFileSync(mdPath, mdContent, "utf-8");
10864
+ fs8.writeFileSync(jsonPath, JSON.stringify(report, null, 2), "utf-8");
10610
10865
  return { markdownPath: mdPath, jsonPath };
10611
10866
  }
10612
10867
  var cachedScript = null;
@@ -10615,19 +10870,19 @@ function resolveCytoscapePath() {
10615
10870
  try {
10616
10871
  const req = createRequire(import.meta.url);
10617
10872
  const entryPath = req.resolve("cytoscape");
10618
- const dir = path4.dirname(entryPath);
10619
- const minPath = path4.join(dir, "cytoscape.min.js");
10620
- if (fs7.existsSync(minPath)) {
10873
+ const dir = path5.dirname(entryPath);
10874
+ const minPath = path5.join(dir, "cytoscape.min.js");
10875
+ if (fs8.existsSync(minPath)) {
10621
10876
  return minPath;
10622
10877
  }
10623
10878
  } catch {
10624
10879
  }
10625
10880
  const searchDirs = [
10626
- path4.join(process.cwd(), "node_modules", "cytoscape", "dist", "cytoscape.min.js"),
10627
- path4.join(process.cwd(), "packages", "core", "node_modules", "cytoscape", "dist", "cytoscape.min.js")
10881
+ path5.join(process.cwd(), "node_modules", "cytoscape", "dist", "cytoscape.min.js"),
10882
+ path5.join(process.cwd(), "packages", "core", "node_modules", "cytoscape", "dist", "cytoscape.min.js")
10628
10883
  ];
10629
10884
  for (const candidate of searchDirs) {
10630
- if (fs7.existsSync(candidate)) {
10885
+ if (fs8.existsSync(candidate)) {
10631
10886
  return candidate;
10632
10887
  }
10633
10888
  }
@@ -10640,7 +10895,7 @@ function getCytoscapeScript() {
10640
10895
  const scriptPath = resolveCytoscapePath();
10641
10896
  if (scriptPath) {
10642
10897
  try {
10643
- cachedScript = fs7.readFileSync(scriptPath, "utf-8");
10898
+ cachedScript = fs8.readFileSync(scriptPath, "utf-8");
10644
10899
  return cachedScript;
10645
10900
  } catch {
10646
10901
  }
@@ -10740,7 +10995,7 @@ function extractFrontmatter2(fileContent) {
10740
10995
  }
10741
10996
  }
10742
10997
  function toScannedArtifact(filePath, relPath) {
10743
- const fileContent = fs7.readFileSync(filePath, "utf-8");
10998
+ const fileContent = fs8.readFileSync(filePath, "utf-8");
10744
10999
  const fm = extractFrontmatter2(fileContent);
10745
11000
  const id = typeof fm?.id === "string" ? fm.id.trim() : "";
10746
11001
  if (!id) return null;
@@ -10752,12 +11007,12 @@ function toScannedArtifact(filePath, relPath) {
10752
11007
  return { id, type: classified.type, title, layer: classified.layer, filePath: relPath, content, frontmatter: fm };
10753
11008
  }
10754
11009
  function parseAndCatalogArtifactFile(rootDir, filePath, scannedMap, archViewByBasename) {
10755
- const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
11010
+ const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
10756
11011
  if (relPath.includes("/changes/") || relPath.startsWith("changes/")) return;
10757
11012
  const scannedArt = toScannedArtifact(filePath, relPath);
10758
11013
  if (!scannedArt) return;
10759
11014
  if (scannedArt.layer === "architecture" && scannedArt.type === "architecture") {
10760
- archViewByBasename.set(path4.basename(relPath), scannedArt);
11015
+ archViewByBasename.set(path5.basename(relPath), scannedArt);
10761
11016
  } else {
10762
11017
  scannedMap.set(scannedArt.id, scannedArt);
10763
11018
  }
@@ -10767,8 +11022,8 @@ function scanWorkspaceArtifacts(rootDir) {
10767
11022
  const archViewByBasename = /* @__PURE__ */ new Map();
10768
11023
  const artifactDirs = ["product", "architecture", "specs", "security", "privacy"];
10769
11024
  for (const dirName of artifactDirs) {
10770
- const fullDirPath = path4.join(rootDir, dirName);
10771
- if (fs7.existsSync(fullDirPath)) {
11025
+ const fullDirPath = path5.join(rootDir, dirName);
11026
+ if (fs8.existsSync(fullDirPath)) {
10772
11027
  for (const f of walkMdFiles(fullDirPath)) {
10773
11028
  parseAndCatalogArtifactFile(rootDir, f, scannedArtifactsMap, archViewByBasename);
10774
11029
  }
@@ -10828,9 +11083,9 @@ function addTraceArchEdges(r, status, archViewByBasename, nodeMap, addEdge) {
10828
11083
  }
10829
11084
  }
10830
11085
  function loadTestContentPreview(filePath) {
10831
- if (!fs7.existsSync(filePath)) return void 0;
11086
+ if (!fs8.existsSync(filePath)) return void 0;
10832
11087
  try {
10833
- const testContent = fs7.readFileSync(filePath, "utf-8");
11088
+ const testContent = fs8.readFileSync(filePath, "utf-8");
10834
11089
  const lang = filePath.endsWith(".feature") ? "gherkin" : "typescript";
10835
11090
  const preview = testContent.substring(0, 1500) + (testContent.length > 1500 ? "\n... (truncated)" : "");
10836
11091
  return "```" + lang + "\n" + preview + "\n```";
@@ -10840,10 +11095,10 @@ function loadTestContentPreview(filePath) {
10840
11095
  }
10841
11096
  function ensureTestNode(t, testId, rootDir, nodeMap) {
10842
11097
  if (nodeMap.has(testId)) return;
10843
- const fullTestPath = path4.isAbsolute(t) ? t : path4.join(rootDir, t);
11098
+ const fullTestPath = path5.isAbsolute(t) ? t : path5.join(rootDir, t);
10844
11099
  nodeMap.set(testId, {
10845
11100
  id: testId,
10846
- label: path4.basename(t),
11101
+ label: path5.basename(t),
10847
11102
  title: t,
10848
11103
  type: t.endsWith(".feature") ? "bdd-feature" : "code-test",
10849
11104
  layer: "test",
@@ -12988,9 +13243,10 @@ ${manualsTab}
12988
13243
  ${kpisTab}
12989
13244
  </main>`;
12990
13245
  }
12991
- function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard Interactivo de Trazabilidad y Calidad") {
13246
+ function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard Interactivo de Trazabilidad y Calidad", generationDate) {
12992
13247
  const head = renderDashboardHead(title);
12993
- const header = renderHeaderHtml(graphData.issueCount, (/* @__PURE__ */ new Date()).toISOString());
13248
+ const effectiveDate = generationDate || resolveDeterministicDate().toISOString();
13249
+ const header = renderHeaderHtml(graphData.issueCount, effectiveDate);
12994
13250
  const nav = renderNavTabsHtml();
12995
13251
  const main = renderDashboardMain(metricsData);
12996
13252
  const sidebar = renderTaskSidebarHtml();
@@ -13020,9 +13276,9 @@ var IGNORED_SCAN_DIRS = /* @__PURE__ */ new Set([
13020
13276
  ".agent"
13021
13277
  ]);
13022
13278
  function readKpiFilesInDir(dir) {
13023
- if (!fs7.existsSync(dir)) return [];
13279
+ if (!fs8.existsSync(dir)) return [];
13024
13280
  try {
13025
- return fs7.readdirSync(dir).filter((f) => f.endsWith(".kpis.json")).map((f) => path4.join(dir, f));
13281
+ return fs8.readdirSync(dir).filter((f) => f.endsWith(".kpis.json")).map((f) => path5.join(dir, f));
13026
13282
  } catch {
13027
13283
  return [];
13028
13284
  }
@@ -13030,11 +13286,11 @@ function readKpiFilesInDir(dir) {
13030
13286
  function scanSubdirKpiFiles(dir, depth, acc) {
13031
13287
  if (depth > 4) return;
13032
13288
  try {
13033
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
13289
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
13034
13290
  for (const entry of entries) {
13035
13291
  if (!entry.isDirectory() || IGNORED_SCAN_DIRS.has(entry.name)) continue;
13036
- const sub = path4.join(dir, entry.name);
13037
- if (entry.name === "releases" && path4.basename(dir) === "reports") {
13292
+ const sub = path5.join(dir, entry.name);
13293
+ if (entry.name === "releases" && path5.basename(dir) === "reports") {
13038
13294
  acc.push(...readKpiFilesInDir(sub));
13039
13295
  } else {
13040
13296
  scanSubdirKpiFiles(sub, depth + 1, acc);
@@ -13044,7 +13300,7 @@ function scanSubdirKpiFiles(dir, depth, acc) {
13044
13300
  }
13045
13301
  }
13046
13302
  function findKpiJsonFiles(rootDir) {
13047
- const primaryFiles = readKpiFilesInDir(path4.join(rootDir, "reports", "releases"));
13303
+ const primaryFiles = readKpiFilesInDir(path5.join(rootDir, "reports", "releases"));
13048
13304
  if (primaryFiles.length > 0) return primaryFiles;
13049
13305
  const found = [];
13050
13306
  scanSubdirKpiFiles(rootDir, 0, found);
@@ -13055,7 +13311,7 @@ function collectHistoricalKpis(rootDir) {
13055
13311
  const kpiFiles = findKpiJsonFiles(rootDir);
13056
13312
  for (const fullPath of kpiFiles) {
13057
13313
  try {
13058
- const content = fs7.readFileSync(fullPath, "utf-8");
13314
+ const content = fs8.readFileSync(fullPath, "utf-8");
13059
13315
  const report = JSON.parse(content);
13060
13316
  if (!items.some((it) => it.id === report.releaseBranch && it.timestamp === report.generatedAt)) {
13061
13317
  items.push({
@@ -13261,15 +13517,11 @@ function computeGateAverages(gateResults) {
13261
13517
  }
13262
13518
  function generateDashboardReport(options = {}) {
13263
13519
  const rootDir = options.rootDir || process.cwd();
13264
- const outputPath = options.outputPath || path4.join(rootDir, "reports", "dashboard.html");
13520
+ const outputPath = options.outputPath || path5.join(rootDir, "reports", "dashboard.html");
13265
13521
  const graphData = buildGraphElements(rootDir);
13266
13522
  const metricsData = collectDashboardMetrics(rootDir);
13267
- const html = renderDashboardHtml(graphData, metricsData, options.title);
13268
- const outDir = path4.dirname(outputPath);
13269
- if (!fs7.existsSync(outDir)) {
13270
- fs7.mkdirSync(outDir, { recursive: true });
13271
- }
13272
- fs7.writeFileSync(outputPath, html, "utf-8");
13523
+ const html = renderDashboardHtml(graphData, metricsData, options.title, resolveDeterministicDate(rootDir).toISOString());
13524
+ writeReportFileIfChanged(outputPath, html);
13273
13525
  const { bddCoverageRatio, traceabilityRatio } = computeSummaryRatios(metricsData);
13274
13526
  const { avgMi, avgCc } = computeGateAverages(metricsData.gate.results);
13275
13527
  return {
@@ -13295,6 +13547,8 @@ function generateDashboardReport(options = {}) {
13295
13547
  html
13296
13548
  };
13297
13549
  }
13550
+
13551
+ // src/reporters/sarif.ts
13298
13552
  var SARIF_SCHEMA_2_1_0 = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json";
13299
13553
  var SARIF_VERSION = "2.1.0";
13300
13554
  function normalizeUri(filePath) {
@@ -13442,11 +13696,7 @@ function serializeSarifDocument(doc, pretty = true) {
13442
13696
  return JSON.stringify(doc, null, pretty ? 2 : 0);
13443
13697
  }
13444
13698
  function writeSecuritySarifReport(outputPath, doc, pretty = true) {
13445
- const dir = path4.dirname(outputPath);
13446
- if (!fs7.existsSync(dir)) {
13447
- fs7.mkdirSync(dir, { recursive: true });
13448
- }
13449
- fs7.writeFileSync(outputPath, serializeSarifDocument(doc, pretty), "utf-8");
13699
+ writeReportFileIfChanged(outputPath, serializeSarifDocument(doc, pretty));
13450
13700
  }
13451
13701
 
13452
13702
  // src/init/agent-templates.ts
@@ -16367,14 +16617,14 @@ function collectAgentFiles(targets) {
16367
16617
  function writeProjectFiles(destDir, files, dryRun) {
16368
16618
  const filesCreated = [];
16369
16619
  for (const f of files) {
16370
- const fullFilePath = path4.join(destDir, f.relPath);
16371
- const parentDir = path4.dirname(fullFilePath);
16620
+ const fullFilePath = path5.join(destDir, f.relPath);
16621
+ const parentDir = path5.dirname(fullFilePath);
16372
16622
  if (!dryRun) {
16373
- if (!fs7.existsSync(parentDir)) {
16374
- fs7.mkdirSync(parentDir, { recursive: true });
16623
+ if (!fs8.existsSync(parentDir)) {
16624
+ fs8.mkdirSync(parentDir, { recursive: true });
16375
16625
  }
16376
- if (!fs7.existsSync(fullFilePath)) {
16377
- fs7.writeFileSync(fullFilePath, f.content, "utf-8");
16626
+ if (!fs8.existsSync(fullFilePath)) {
16627
+ fs8.writeFileSync(fullFilePath, f.content, "utf-8");
16378
16628
  filesCreated.push(f.relPath);
16379
16629
  }
16380
16630
  } else {
@@ -16458,7 +16708,7 @@ function getCiWorkflowFiles(ciProvider) {
16458
16708
  function createDirectoriesOnDisk(destDir, directories, dryRun) {
16459
16709
  const created = [];
16460
16710
  for (const d of directories) {
16461
- if (!dryRun) fs7.mkdirSync(path4.join(destDir, d), { recursive: true });
16711
+ if (!dryRun) fs8.mkdirSync(path5.join(destDir, d), { recursive: true });
16462
16712
  created.push(d);
16463
16713
  }
16464
16714
  return created;
@@ -16473,7 +16723,7 @@ function collectStarterFiles(ciProvider, agentFiles = [], archGranularity = "min
16473
16723
  ...getCiWorkflowFiles(ciProvider),
16474
16724
  ...agentFiles,
16475
16725
  ...getArchitectureTemplates(archGranularity).map((t) => ({
16476
- relPath: path4.join("templates", "architecture", t.filename).replace(/\\/g, "/"),
16726
+ relPath: path5.join("templates", "architecture", t.filename).replace(/\\/g, "/"),
16477
16727
  content: t.content
16478
16728
  }))
16479
16729
  ];
@@ -16504,15 +16754,15 @@ function injectMissingScripts(scripts) {
16504
16754
  return modified;
16505
16755
  }
16506
16756
  function updateConsumerPackageJson(destDir, dryRun) {
16507
- const pkgPath = path4.join(destDir, "package.json");
16508
- if (!fs7.existsSync(pkgPath)) return false;
16757
+ const pkgPath = path5.join(destDir, "package.json");
16758
+ if (!fs8.existsSync(pkgPath)) return false;
16509
16759
  try {
16510
- const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
16760
+ const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
16511
16761
  if (!pkg || typeof pkg !== "object") return false;
16512
16762
  pkg.scripts = pkg.scripts || {};
16513
16763
  const modified = injectMissingScripts(pkg.scripts);
16514
16764
  if (modified && !dryRun) {
16515
- fs7.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
16765
+ fs8.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
16516
16766
  }
16517
16767
  return modified;
16518
16768
  } catch {
@@ -16520,7 +16770,7 @@ function updateConsumerPackageJson(destDir, dryRun) {
16520
16770
  }
16521
16771
  }
16522
16772
  function tryInstallGitHook(destDir, dryRun) {
16523
- if (!dryRun && fs7.existsSync(path4.join(destDir, ".git"))) {
16773
+ if (!dryRun && fs8.existsSync(path5.join(destDir, ".git"))) {
16524
16774
  return installGitHooks(destDir).success;
16525
16775
  }
16526
16776
  return false;
@@ -16569,7 +16819,7 @@ function validateInitOptions(options) {
16569
16819
  }
16570
16820
  function initProject(options = {}) {
16571
16821
  const baseDir = options.rootDir || process.cwd();
16572
- const destDir = path4.resolve(baseDir, options.targetDir || ".");
16822
+ const destDir = path5.resolve(baseDir, options.targetDir || ".");
16573
16823
  const validated = validateInitOptions(options);
16574
16824
  if (!validated.valid) return buildInitError(destDir, validated.error);
16575
16825
  const agentTargets = resolveAgentTargets(options.agents);
@@ -16653,33 +16903,40 @@ function normalizeParsedRule(raw, idx) {
16653
16903
  enabled: raw.enabled !== false
16654
16904
  };
16655
16905
  }
16906
+ function parseDlpDoc(yamlContent) {
16907
+ let doc;
16908
+ try {
16909
+ doc = yaml3.load(yamlContent);
16910
+ } catch (err) {
16911
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in dlp-policy: ${err instanceof Error ? err.message : String(err)}`);
16912
+ }
16913
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
16914
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid dlp-policy structure: root must be a YAML mapping`);
16915
+ }
16916
+ const record = doc;
16917
+ if (record.rules !== void 0 && !Array.isArray(record.rules)) {
16918
+ throw new Error(`[AI-SDLC Fail-Closed] Invalid dlp-policy: rules must be an array`);
16919
+ }
16920
+ return record;
16921
+ }
16656
16922
  function parseDlpPolicy(yamlContent) {
16657
16923
  const fallback = createDefaultDlpPolicy();
16658
16924
  if (!yamlContent || !yamlContent.trim()) return fallback;
16659
- try {
16660
- const doc = yaml3.load(yamlContent);
16661
- if (!doc || typeof doc !== "object") return fallback;
16662
- const rawRules = Array.isArray(doc.rules) ? doc.rules : [];
16663
- const parsedRules = rawRules.map((r, i) => normalizeParsedRule(r, i)).filter((r) => r !== null);
16664
- return {
16665
- version: typeof doc.version === "string" ? doc.version : "1.0",
16666
- enabled: doc.enabled !== false,
16667
- zero_data_retention_required: Boolean(doc.zero_data_retention_required ?? true),
16668
- rules: parsedRules.length > 0 ? parsedRules : fallback.rules
16669
- };
16670
- } catch {
16671
- return fallback;
16672
- }
16925
+ const record = parseDlpDoc(yamlContent);
16926
+ const rawRules = Array.isArray(record.rules) ? record.rules : [];
16927
+ const parsedRules = rawRules.map((r, i) => normalizeParsedRule(r, i)).filter((r) => r !== null);
16928
+ return {
16929
+ version: typeof record.version === "string" ? record.version : "1.0",
16930
+ enabled: record.enabled !== false,
16931
+ zero_data_retention_required: Boolean(record.zero_data_retention_required ?? true),
16932
+ rules: parsedRules.length > 0 ? parsedRules : fallback.rules
16933
+ };
16673
16934
  }
16674
16935
  function loadDlpPolicy(rootDir, customPolicyPath) {
16675
16936
  const base = rootDir || process.cwd();
16676
- const filePath = customPolicyPath ? path4.isAbsolute(customPolicyPath) ? customPolicyPath : path4.join(base, customPolicyPath) : path4.join(base, "dlp-policy.yaml");
16677
- if (!fs7.existsSync(filePath)) return createDefaultDlpPolicy();
16678
- try {
16679
- return parseDlpPolicy(fs7.readFileSync(filePath, "utf-8"));
16680
- } catch {
16681
- return createDefaultDlpPolicy();
16682
- }
16937
+ const filePath = customPolicyPath ? path5.isAbsolute(customPolicyPath) ? customPolicyPath : path5.join(base, customPolicyPath) : path5.join(base, "dlp-policy.yaml");
16938
+ if (!fs8.existsSync(filePath)) return createDefaultDlpPolicy();
16939
+ return parseDlpPolicy(fs8.readFileSync(filePath, "utf-8"));
16683
16940
  }
16684
16941
  function applySingleDlpRule(text, rule) {
16685
16942
  if (rule.enabled === false) return { output: text, matches: 0 };
@@ -16730,6 +16987,6 @@ function sanitizeDlpPayload(payload, policy = createDefaultDlpPolicy()) {
16730
16987
  }
16731
16988
  }
16732
16989
 
16733
- export { ALL_ARCHITECTURE_TEMPLATES, BRANCH_PATTERNS, BddStepRegistry, DEFAULT_DLP_RULES, DEFAULT_EXCLUDED_PATTERNS, DEFAULT_EXCLUDES, DEFAULT_EXTENSIONS, DEFAULT_PRIVACY_POLICY, DEFAULT_SAST_EXCLUDES, GENERAL_SOURCE_EXTENSIONS, KPI_SUMMARY_END_TAG, KPI_SUMMARY_START_TAG, LIFECYCLE_SKILL_TEMPLATES, OpenSpecAdapter, PII_SAST_RULES, PREPARE_COMMIT_MSG_SCRIPT, PROMPT_INJECTION_SIGNATURES, PROTECTED_BYPASS_PATTERNS, SARIF_SCHEMA_2_1_0, SARIF_VERSION, SAST_RULES, SCHEMA_SCHEMAS_ARCHITECTURE_ADR_SCHEMA_JSON, SCHEMA_SCHEMAS_ARCHITECTURE_COMPONENT_SCHEMA_JSON, SCHEMA_SCHEMAS_ARCHITECTURE_INTRODUCTION_AND_GOALS_SCHEMA_JSON, SCHEMA_SCHEMAS_COMPLIANCE_LICENSE_MANIFEST_SCHEMA_JSON, SCHEMA_SCHEMAS_COMPLIANCE_LICENSE_POLICY_SCHEMA_JSON, SCHEMA_SCHEMAS_COMPLIANCE_QUALITY_POLICY_SCHEMA_JSON, SCHEMA_SCHEMAS_IDE_MCP_CONFIG_SCHEMA_JSON, SCHEMA_SCHEMAS_MANUALS_PRODUCTION_MANUAL_SCHEMA_JSON, SCHEMA_SCHEMAS_MANUALS_USER_MANUAL_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_DATA_ASSET_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_PRIVACY_POLICY_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_PRIVACY_REQ_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_PRIVACY_ROPA_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_ACTOR_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_BOUNDED_CONTEXT_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_BUSINESS_RULE_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_JOURNEY_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_REQUIREMENT_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_TERM_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_USE_CASE_SCHEMA_JSON, SCHEMA_SCHEMAS_SAFETY_HAZARD_SCHEMA_JSON, SCHEMA_SCHEMAS_SAFETY_SAFETY_REQ_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_DESIGN_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_HANDOFF_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_PROPOSAL_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_RELEASE_KPIS_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_SESSION_TELEMETRY_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_SPEC_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_TASKS_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_ABUSE_CASE_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_ENCLAVE_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_SECURITY_REQ_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_THREAT_ACTOR_SCHEMA_JSON, SCHEMA_SCHEMAS_WORKFLOW_AGENT_HANDOFF_SCHEMA_JSON, SECRET_RULES, SPECIALIZED_AGENT_TEMPLATES, STARTER_ANTIGRAVITY_MCP, STARTER_ANTIGRAVITY_RULES, STARTER_ARCH_ADR, STARTER_ARCH_ARCHITECTURE_CONSTRAINTS, STARTER_ARCH_COMPONENT, STARTER_ARCH_CONTEXT_AND_SCOPE, STARTER_ARCH_CROSS_CUTTING_CONCEPTS, STARTER_ARCH_DEPLOYMENT_VIEW, STARTER_ARCH_GLOSSARY, STARTER_ARCH_INTRODUCTION_AND_GOALS, STARTER_ARCH_LEVEL_1_WHITEBOX, STARTER_ARCH_QUALITY_REQUIREMENTS, STARTER_ARCH_RISKS_AND_TECHNICAL_DEBT, STARTER_ARCH_RUNTIME_VIEW, STARTER_ARCH_SOLUTION_STRATEGY, STARTER_AZURE_PIPELINES, STARTER_BASE_TEMPLATES, STARTER_BITBUCKET_PIPELINES, STARTER_CLAUDE_RULES, STARTER_COMPLIANCE_LICENSE_MANIFEST_YAML, STARTER_COPILOT_RULES, STARTER_CURSOR_CORE_RULES, STARTER_CURSOR_MCP, STARTER_CURSOR_PRODUCT_RULES, STARTER_CURSOR_QUALITY_RULES, STARTER_GITHUB_CI, STARTER_GITHUB_DEPLOY_DASHBOARD, STARTER_GITHUB_PR_KPI, STARTER_GITHUB_PULL_REQUEST_TEMPLATE, STARTER_GITHUB_SDD_INTEGRATE, STARTER_GITLAB_CI, STARTER_LICENSE_POLICY, STARTER_PRIVACY_POLICY, STARTER_PROCESS_README, STARTER_PULL_REQUEST_TEMPLATE, STARTER_QUALITY_POLICY, STARTER_REQUIREMENT_TEMPLATE, STARTER_SCHEMAS, STARTER_SCHEMAS_MAP, STARTER_TASK_TEMPLATE, STARTER_TEMPLATES_CI_AZURE_PIPELINES_YML, STARTER_TEMPLATES_CI_BITBUCKET_PIPELINES_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_CI_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_DEPLOY_DASHBOARD_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_PR_KPI_SUMMARY_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_SDD_INTEGRATE_ON_MERGE_YML, STARTER_TEMPLATES_CI__GITLAB_CI_YML, STARTER_TEMPLATES_COMPLIANCE_COMMERCIAL_ACQUISITION_REQUEST_TEMPLATE_MD, STARTER_TEMPLATES_MANUALS_PRODUCTION_MANUAL_TEMPLATE_MD, STARTER_TEMPLATES_MANUALS_USER_MANUAL_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_DATA_ASSET_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_DPIA_REQUEST_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_PRIVACY_REQ_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_PRIVACY_ROPA_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_ACTOR_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_BOUNDED_CONTEXT_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_BUSINESS_RULE_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_JOURNEY_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_REQUIREMENT_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_TERM_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_USER_DESIGN_FEEDBACK_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_USE_CASE_TEMPLATE_MD, STARTER_TEMPLATES_SAFETY_HAZARD_TEMPLATE_MD, STARTER_TEMPLATES_SAFETY_SAFETY_REQ_TEMPLATE_MD, STARTER_TEMPLATES_SDD_DESIGN_TEMPLATE_MD, STARTER_TEMPLATES_SDD_HANDOFF_TEMPLATE_YAML, STARTER_TEMPLATES_SDD_PROPOSAL_TEMPLATE_MD, STARTER_TEMPLATES_SDD_PULL_REQUEST_TEMPLATE_MD, STARTER_TEMPLATES_SDD_SPEC_TEMPLATE_MD, STARTER_TEMPLATES_SDD_TASKS_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_ABUSE_CASE_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_ENCLAVE_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_SECURITY_REQ_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_THREAT_ACTOR_TEMPLATE_MD, STARTER_TEMPLATES_WORKFLOW_AGENT_HANDOFF_TEMPLATE_MD, STARTER_VSCODE_MCP, STARTER_WORKFLOW_HANDOFF_TEMPLATE, STARTER__GITHUB_PULL_REQUEST_TEMPLATE_MD, SpecKitAdapter, TYPE_TO_SCHEMA_MAP, VALID_AGENT_TARGETS, VALID_ARCHITECTURE_GRANULARITIES, VALID_AUTONOMY_MODES, VALID_CHANGE_PROFILES, VALID_CI_PROVIDERS, aggregateCommitKpiRecords, aggregatePrKpis, aggregateReleaseKpis, analyzeBddVariantsForRequirement, appendRevisionHistoryRow, applyRatchetMode, buildCycloneDxComponent, buildCycloneDxVulnerabilities, buildFeatureFileContent, buildGraphElements, calculateJaccardSimilarity, calculateMetrics, calculateRating, calculateShannonEntropy, checkGherkinInSync, checkoutTaskBranch, classifyBranch, classifyMetricFromMessage, collectAgentFiles, collectDashboardMetrics, collectHistoricalKpis, computeCanonicalSha256, computeSha256, computeViolationSignature, createDefaultDlpPolicy, createDefaultQualityPolicy, depositProductHandoffSidecar, detectActiveChangeForIntegration, detectAuthorIdentity, detectLanguage, detectProfileFromChange, detectPromptInjection, emitCiOutput, evaluateCompoundLicense, evaluateSingleDependency, evaluateVulnerabilityThresholds, executeBddFeature, extractBaselineEntriesFromResults, extractCitations, extractCommitKpisFromRange, extractDirectDepNames, extractFeatureTags, extractFrontmatter, extractFunctions, extractFunctionsBraceLanguages, extractFunctionsPolyglot, extractFunctionsPython, extractFunctionsTypeScriptAst, extractGherkinBlock, extractGherkinFeatures, extractNormativeText, findChangeWorkspaceDir, findLicenseFile, formatCommitTrailers, formatPrKpiMarkdown, formatReleaseKpiMarkdown, generateBranchHierarchyPlan, generateCycloneDxSbom, generateDashboardReport, generateDuplicatesReportMarkdown, generateGovernanceReportMarkdown, generateLicenseReportMarkdown, generatePdacReportMarkdown, generatePrivacyReportMarkdown, generateQualityReport, generateRopaInventoryMarkdown, generateSastReportMarkdown, generateSecretsReportMarkdown, generateSecuritySarifReport, generateSuppressionsAuditReport, generateTestingCoverageReportMarkdown, generateThirdPartyNotices, generateTraceabilityReportMarkdown, getArchitectureTemplates, getCytoscapeScript, getGitDiffLines, getNextCorrelativeNumber, getSddAdapter, inferArtifactTypeFromId, inferMissingTrailers, initProject, injectKpisIntoPrBody, installGitHooks, integrateSddChange, isGitleaksInstalled, isMetricWorsened, isPathProtectedFromPatch, isPlaceholderOrIgnored, isRequirementArtifact2 as isRequirementArtifact, isSemgrepInstalled, isToolAvailable, loadDlpPolicy, loadPrivacyPolicy, loadProductHandoffSidecar, loadQualityBaseline, locateTaskInActiveChanges, mapSastSeverityToSarifLevel, maskSecret, normalizeSpdxLicense, normalizeUri, parseCommitTrailers, parseDlpPolicy, parseFeatureDocument, parseFrontmatter, parseFrontmatterId, parseGenericDoc, parseLicensePolicy, parseMarkdownFrontmatter, parseQualityPolicy, parseRequirementFile, parseSecuritySuppression, parseSpecCatalogScope, parseTasksDoc, parseTasksFromContent, parseTrivyVulnerabilities, processPackageDir, readCodeCoverageSummary, readPackageJson, renderAntigravityAgentFile, renderAntigravitySkillFile, renderAutonomyModeSection, renderClaudeAgentFile, renderClaudeSkillFile, renderCopilotAgentFile, renderCopilotSkillFile, renderCursorAgentFile, renderCursorSkillFile, renderDashboardHtml, renderManualsTab, resolveAgentTargets, resolveBranchHierarchy, resolveCiEnvironment, resolveCytoscapePath, resolveDefaultBranchBase, resolveFeatureFilePath, resolveGitRef, resolveLicenseFromFile, resolveReleaseBaseBranch, resolveSastCwe, resolveSastOwasp, resolveSchemaForArtifact, resolveTargetFiles, runGitleaksCli, runSemgrepCli, sanitizeDlpPayload, sanitizeDlpText, scaffoldSddChange, scanAllProductHandoffs, scanAllTestArtifacts, scanContentForSecrets, scanFileForPrivacyViolations, scanFileForSast, scanGoDependencies, scanInstalledLicenses, scanManualsInWorkspace, scanMavenDependencies, scanPnpmStore, scanPythonDependencies, scanRustDependencies, scanSubdirectories, scanWithNativeFs, scanWithSyft, scanWithTrivy, serializeSarifDocument, slugify, syncPdacDigests, tokenizeTitle, updateCitationsInContent, validateAgainstSchema, validateArtifactSchema, verifyArtifactDuplicates, verifyArtifactsSchemas, verifyLicenses, verifyManuals, verifyPdacGraph, verifyPrivacy, verifyProgressiveFriction, verifyQualityGate, verifySast, verifySddIntegration, verifySecrets, verifyTasksGovernance, verifyTestingCoverage, verifyTraceability, walkDir, walkMdFiles, walkProjectFiles, walkSourceFiles, walkTaskFiles, writeCucumberJsonReport, writeCycloneDxSbom, writeQualityBaseline, writeReleaseKpiReport, writeSecuritySarifReport, writeThirdPartyNotices };
16990
+ export { ALL_ARCHITECTURE_TEMPLATES, BRANCH_PATTERNS, BddStepRegistry, DEFAULT_DLP_RULES, DEFAULT_EXCLUDED_PATTERNS, DEFAULT_EXCLUDES, DEFAULT_EXTENSIONS, DEFAULT_PRIVACY_POLICY, DEFAULT_SAST_EXCLUDES, GENERAL_SOURCE_EXTENSIONS, KPI_SUMMARY_END_TAG, KPI_SUMMARY_START_TAG, LIFECYCLE_SKILL_TEMPLATES, OpenSpecAdapter, PII_SAST_RULES, PREPARE_COMMIT_MSG_SCRIPT, PROMPT_INJECTION_SIGNATURES, PROTECTED_BYPASS_PATTERNS, SARIF_SCHEMA_2_1_0, SARIF_VERSION, SAST_RULES, SCHEMA_SCHEMAS_ARCHITECTURE_ADR_SCHEMA_JSON, SCHEMA_SCHEMAS_ARCHITECTURE_COMPONENT_SCHEMA_JSON, SCHEMA_SCHEMAS_ARCHITECTURE_INTRODUCTION_AND_GOALS_SCHEMA_JSON, SCHEMA_SCHEMAS_COMPLIANCE_LICENSE_MANIFEST_SCHEMA_JSON, SCHEMA_SCHEMAS_COMPLIANCE_LICENSE_POLICY_SCHEMA_JSON, SCHEMA_SCHEMAS_COMPLIANCE_QUALITY_POLICY_SCHEMA_JSON, SCHEMA_SCHEMAS_IDE_MCP_CONFIG_SCHEMA_JSON, SCHEMA_SCHEMAS_MANUALS_PRODUCTION_MANUAL_SCHEMA_JSON, SCHEMA_SCHEMAS_MANUALS_USER_MANUAL_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_DATA_ASSET_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_PRIVACY_POLICY_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_PRIVACY_REQ_SCHEMA_JSON, SCHEMA_SCHEMAS_PRIVACY_PRIVACY_ROPA_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_ACTOR_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_BOUNDED_CONTEXT_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_BUSINESS_RULE_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_JOURNEY_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_REQUIREMENT_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_TERM_SCHEMA_JSON, SCHEMA_SCHEMAS_PRODUCT_USE_CASE_SCHEMA_JSON, SCHEMA_SCHEMAS_SAFETY_HAZARD_SCHEMA_JSON, SCHEMA_SCHEMAS_SAFETY_SAFETY_REQ_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_DESIGN_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_HANDOFF_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_PROPOSAL_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_RELEASE_KPIS_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_SESSION_TELEMETRY_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_SPEC_SCHEMA_JSON, SCHEMA_SCHEMAS_SDD_TASKS_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_ABUSE_CASE_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_ENCLAVE_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_SECURITY_REQ_SCHEMA_JSON, SCHEMA_SCHEMAS_SECURITY_THREAT_ACTOR_SCHEMA_JSON, SCHEMA_SCHEMAS_WORKFLOW_AGENT_HANDOFF_SCHEMA_JSON, SECRET_RULES, SPECIALIZED_AGENT_TEMPLATES, STARTER_ANTIGRAVITY_MCP, STARTER_ANTIGRAVITY_RULES, STARTER_ARCH_ADR, STARTER_ARCH_ARCHITECTURE_CONSTRAINTS, STARTER_ARCH_COMPONENT, STARTER_ARCH_CONTEXT_AND_SCOPE, STARTER_ARCH_CROSS_CUTTING_CONCEPTS, STARTER_ARCH_DEPLOYMENT_VIEW, STARTER_ARCH_GLOSSARY, STARTER_ARCH_INTRODUCTION_AND_GOALS, STARTER_ARCH_LEVEL_1_WHITEBOX, STARTER_ARCH_QUALITY_REQUIREMENTS, STARTER_ARCH_RISKS_AND_TECHNICAL_DEBT, STARTER_ARCH_RUNTIME_VIEW, STARTER_ARCH_SOLUTION_STRATEGY, STARTER_AZURE_PIPELINES, STARTER_BASE_TEMPLATES, STARTER_BITBUCKET_PIPELINES, STARTER_CLAUDE_RULES, STARTER_COMPLIANCE_LICENSE_MANIFEST_YAML, STARTER_COPILOT_RULES, STARTER_CURSOR_CORE_RULES, STARTER_CURSOR_MCP, STARTER_CURSOR_PRODUCT_RULES, STARTER_CURSOR_QUALITY_RULES, STARTER_GITHUB_CI, STARTER_GITHUB_DEPLOY_DASHBOARD, STARTER_GITHUB_PR_KPI, STARTER_GITHUB_PULL_REQUEST_TEMPLATE, STARTER_GITHUB_SDD_INTEGRATE, STARTER_GITLAB_CI, STARTER_LICENSE_POLICY, STARTER_PRIVACY_POLICY, STARTER_PROCESS_README, STARTER_PULL_REQUEST_TEMPLATE, STARTER_QUALITY_POLICY, STARTER_REQUIREMENT_TEMPLATE, STARTER_SCHEMAS, STARTER_SCHEMAS_MAP, STARTER_TASK_TEMPLATE, STARTER_TEMPLATES_CI_AZURE_PIPELINES_YML, STARTER_TEMPLATES_CI_BITBUCKET_PIPELINES_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_CI_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_DEPLOY_DASHBOARD_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_PR_KPI_SUMMARY_YML, STARTER_TEMPLATES_CI_GITHUB_WORKFLOWS_SDD_INTEGRATE_ON_MERGE_YML, STARTER_TEMPLATES_CI__GITLAB_CI_YML, STARTER_TEMPLATES_COMPLIANCE_COMMERCIAL_ACQUISITION_REQUEST_TEMPLATE_MD, STARTER_TEMPLATES_MANUALS_PRODUCTION_MANUAL_TEMPLATE_MD, STARTER_TEMPLATES_MANUALS_USER_MANUAL_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_DATA_ASSET_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_DPIA_REQUEST_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_PRIVACY_REQ_TEMPLATE_MD, STARTER_TEMPLATES_PRIVACY_PRIVACY_ROPA_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_ACTOR_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_BOUNDED_CONTEXT_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_BUSINESS_RULE_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_JOURNEY_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_REQUIREMENT_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_TERM_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_USER_DESIGN_FEEDBACK_TEMPLATE_MD, STARTER_TEMPLATES_PRODUCT_USE_CASE_TEMPLATE_MD, STARTER_TEMPLATES_SAFETY_HAZARD_TEMPLATE_MD, STARTER_TEMPLATES_SAFETY_SAFETY_REQ_TEMPLATE_MD, STARTER_TEMPLATES_SDD_DESIGN_TEMPLATE_MD, STARTER_TEMPLATES_SDD_HANDOFF_TEMPLATE_YAML, STARTER_TEMPLATES_SDD_PROPOSAL_TEMPLATE_MD, STARTER_TEMPLATES_SDD_PULL_REQUEST_TEMPLATE_MD, STARTER_TEMPLATES_SDD_SPEC_TEMPLATE_MD, STARTER_TEMPLATES_SDD_TASKS_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_ABUSE_CASE_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_ENCLAVE_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_SECURITY_REQ_TEMPLATE_MD, STARTER_TEMPLATES_SECURITY_THREAT_ACTOR_TEMPLATE_MD, STARTER_TEMPLATES_WORKFLOW_AGENT_HANDOFF_TEMPLATE_MD, STARTER_VSCODE_MCP, STARTER_WORKFLOW_HANDOFF_TEMPLATE, STARTER__GITHUB_PULL_REQUEST_TEMPLATE_MD, SpecKitAdapter, TYPE_TO_SCHEMA_MAP, UNIT_TEST_SUFFIXES, VALID_AGENT_TARGETS, VALID_ARCHITECTURE_GRANULARITIES, VALID_AUTONOMY_MODES, VALID_CHANGE_PROFILES, VALID_CI_PROVIDERS, aggregateCommitKpiRecords, aggregatePrKpis, aggregateReleaseKpis, analyzeBddVariantsForRequirement, appendRevisionHistoryRow, applyRatchetMode, buildCycloneDxComponent, buildCycloneDxVulnerabilities, buildFeatureFileContent, buildGraphElements, calculateJaccardSimilarity, calculateMetrics, calculateRating, calculateShannonEntropy, canonicalSort, checkGherkinInSync, checkoutTaskBranch, classifyBranch, classifyMetricFromMessage, collectAgentFiles, collectDashboardMetrics, collectHistoricalKpis, computeCanonicalSha256, computeRawSha256, computeSha256, computeViolationSignature, createDefaultDlpPolicy, createDefaultQualityPolicy, depositProductHandoffSidecar, detectActiveChangeForIntegration, detectAuthorIdentity, detectLanguage, detectProfileFromChange, detectPromptInjection, emitCiOutput, evaluateCompoundLicense, evaluateSingleDependency, evaluateVulnerabilityThresholds, executeBddFeature, extractBaselineEntriesFromResults, extractCitations, extractCommitKpisFromRange, extractDirectDepNames, extractFeatureTags, extractFrontmatter, extractFunctions, extractFunctionsBraceLanguages, extractFunctionsPolyglot, extractFunctionsPython, extractFunctionsPythonAst, extractFunctionsTypeScriptAst, extractGherkinBlock, extractGherkinFeatures, extractNormativeText, extractPythonFunctionsPolyglot, extractSemanticContent, findChangeWorkspaceDir, findLicenseFile, formatCommitTrailers, formatPrKpiMarkdown, formatReleaseKpiMarkdown, generateBranchHierarchyPlan, generateCycloneDxSbom, generateDashboardReport, generateDeterministicUuidV5, generateDuplicatesReportMarkdown, generateGovernanceReportMarkdown, generateLicenseReportMarkdown, generatePdacReportMarkdown, generatePrivacyReportMarkdown, generateQualityReport, generateRopaInventoryMarkdown, generateSastReportMarkdown, generateSecretsReportMarkdown, generateSecuritySarifReport, generateSuppressionsAuditReport, generateTestingCoverageReportMarkdown, generateThirdPartyNotices, generateTraceabilityReportMarkdown, getArchitectureTemplates, getCytoscapeScript, getGitDiffLines, getNextCorrelativeNumber, getSddAdapter, inferArtifactTypeFromId, inferMissingTrailers, initProject, injectKpisIntoPrBody, installGitHooks, integrateSddChange, isCliInstalled, isFunctionReferencedInTests, isGitleaksInstalled, isMetricWorsened, isPathProtectedFromPatch, isPlaceholderOrIgnored, isReportContentSemanticallyEqual, isRequirementArtifact2 as isRequirementArtifact, isSemgrepInstalled, isToolAvailable, loadDlpPolicy, loadPrivacyPolicy, loadProductHandoffSidecar, loadQualityBaseline, locateTaskInActiveChanges, mapSastSeverityToSarifLevel, maskSecret, normalizePath, normalizeSpdxLicense, normalizeUri, orchestrateNativeAstAnalysis, parseCommitTrailers, parseDlpPolicy, parseFeatureDocument, parseFrontmatter, parseFrontmatterId, parseGenericDoc, parseLicensePolicy, parseMarkdownFrontmatter, parseQualityPolicy, parseRequirementFile, parseSecuritySuppression, parseSpecCatalogScope, parseTasksDoc, parseTasksFromContent, parseTrivyVulnerabilities, processPackageDir, readCodeCoverageSummary, readPackageJson, renderAntigravityAgentFile, renderAntigravitySkillFile, renderAutonomyModeSection, renderClaudeAgentFile, renderClaudeSkillFile, renderCopilotAgentFile, renderCopilotSkillFile, renderCursorAgentFile, renderCursorSkillFile, renderDashboardHtml, renderManualsTab, resolveAgentTargets, resolveBranchHierarchy, resolveCiEnvironment, resolveCytoscapePath, resolveDefaultBranchBase, resolveDeterministicDate, resolveFeatureFilePath, resolveGitRef, resolveLicenseFromFile, resolveReleaseBaseBranch, resolveSastCwe, resolveSastOwasp, resolveSchemaForArtifact, resolveTargetFiles, runGitleaksCli, runSemgrepCli, sanitizeDlpPayload, sanitizeDlpText, sanitizeTestCorpus, scaffoldSddChange, scanAllProductHandoffs, scanAllTestArtifacts, scanContentForSecrets, scanFileForPrivacyViolations, scanFileForSast, scanGoDependencies, scanInstalledLicenses, scanManualsInWorkspace, scanMavenDependencies, scanPnpmStore, scanPythonDependencies, scanRustDependencies, scanSubdirectories, scanWithNativeFs, scanWithSyft, scanWithTrivy, serializeSarifDocument, slugify2 as slugify, stripVolatileReportMetadata, syncPdacDigests, tokenizeTitle, updateCitationsInContent, validateAgainstSchema, validateAnchorExistence, validateArtifactSchema, verifyArtifactDuplicates, verifyArtifactsSchemas, verifyLicenses, verifyManuals, verifyPdacGraph, verifyPrivacy, verifyProgressiveFriction, verifyQualityGate, verifySast, verifySddIntegration, verifySecrets, verifyTasksGovernance, verifyTestingCoverage, verifyTraceability, walkDir, walkMdFiles, walkProjectFiles, walkSourceFiles, walkTaskFiles, writeCucumberJsonReport, writeCycloneDxSbom, writeQualityBaseline, writeReleaseKpiReport, writeReportFileIfChanged, writeSecuritySarifReport, writeThirdPartyNotices };
16734
16991
  //# sourceMappingURL=index.js.map
16735
16992
  //# sourceMappingURL=index.js.map