@aisdlc/core 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var fs6 = require('fs');
4
- var path5 = require('path');
5
- var yaml2 = require('js-yaml');
3
+ var fs7 = require('fs');
4
+ var path4 = require('path');
5
+ var yaml3 = require('js-yaml');
6
6
  var tsMorph = require('ts-morph');
7
7
  var crypto = require('crypto');
8
8
  var child_process = require('child_process');
@@ -29,9 +29,9 @@ function _interopNamespace(e) {
29
29
  return Object.freeze(n);
30
30
  }
31
31
 
32
- var fs6__namespace = /*#__PURE__*/_interopNamespace(fs6);
33
- var path5__namespace = /*#__PURE__*/_interopNamespace(path5);
34
- var yaml2__default = /*#__PURE__*/_interopDefault(yaml2);
32
+ var fs7__namespace = /*#__PURE__*/_interopNamespace(fs7);
33
+ var path4__namespace = /*#__PURE__*/_interopNamespace(path4);
34
+ var yaml3__default = /*#__PURE__*/_interopDefault(yaml3);
35
35
  var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
36
36
  var child_process__namespace = /*#__PURE__*/_interopNamespace(child_process);
37
37
 
@@ -198,7 +198,7 @@ function buildNodeMetrics(node, filePath) {
198
198
  return { functionName, filePath, loc, cyclomatic, cognitive, maintainability: mi, codeSmells };
199
199
  }
200
200
  function createTempSourceFile(project, content, filePath) {
201
- const ext = path5__namespace.extname(filePath).toLowerCase() || ".ts";
201
+ const ext = path4__namespace.extname(filePath).toLowerCase() || ".ts";
202
202
  const fileName = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 7)}${ext}`;
203
203
  return project.createSourceFile(fileName, content, { overwrite: true });
204
204
  }
@@ -235,7 +235,7 @@ function extractFunctionsTypeScriptAst(content, filePath) {
235
235
  }
236
236
  }
237
237
  function extractFunctionsPolyglot(content, filePath) {
238
- const ext = path5__namespace.extname(filePath).toLowerCase();
238
+ const ext = path4__namespace.extname(filePath).toLowerCase();
239
239
  return ext === ".py" ? extractPythonFunctions(content, filePath) : extractBraceFunctions(content, filePath);
240
240
  }
241
241
  function isDocstringToggle(trimmed) {
@@ -312,7 +312,7 @@ function processBraceLine(line, state, blocks, lineIndex, ext) {
312
312
  }
313
313
  }
314
314
  function extractBraceFunctions(content, filePath) {
315
- const ext = path5__namespace.extname(filePath).toLowerCase();
315
+ const ext = path4__namespace.extname(filePath).toLowerCase();
316
316
  const lines = content.split("\n");
317
317
  const blocks = [];
318
318
  const state = { currentFn: null, braceCount: 0, fnLines: [], inBlockComment: false };
@@ -457,7 +457,7 @@ function countCommentsAndLoc(rawLines, isPy) {
457
457
  return { loc, commentCount, cleanLines };
458
458
  }
459
459
  function calculatePolyglotMetrics(body, name, filePath) {
460
- const ext = path5__namespace.extname(filePath).toLowerCase();
460
+ const ext = path4__namespace.extname(filePath).toLowerCase();
461
461
  const rawLines = body.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
462
462
  const { loc, commentCount, cleanLines } = countCommentsAndLoc(rawLines, ext === ".py");
463
463
  const cyclomatic = computePolyglotCyclomatic(body, ext);
@@ -507,6 +507,652 @@ function computePolyglotCognitive(lines, isPy) {
507
507
  }
508
508
  return cognitive;
509
509
  }
510
+ function walkMdFiles(dir, fileList = [], excludeDirs = [
511
+ "node_modules",
512
+ ".git",
513
+ "dist",
514
+ "bin",
515
+ "obj",
516
+ "build",
517
+ "out",
518
+ "target",
519
+ "coverage",
520
+ ".changeset",
521
+ "scratch",
522
+ "test-scaffold",
523
+ "fixtures",
524
+ "templates",
525
+ "examples",
526
+ ".agent",
527
+ ".agents",
528
+ ".claude",
529
+ ".cursor",
530
+ ".github",
531
+ ".vscode",
532
+ ".vs",
533
+ ".idea"
534
+ ]) {
535
+ try {
536
+ if (!fs7__namespace.existsSync(dir)) return fileList;
537
+ const files = fs7__namespace.readdirSync(dir);
538
+ for (const file of files) {
539
+ const fullPath = path4__namespace.join(dir, file);
540
+ try {
541
+ if (fs7__namespace.statSync(fullPath).isDirectory()) {
542
+ if (!excludeDirs.includes(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
543
+ walkMdFiles(fullPath, fileList, excludeDirs);
544
+ }
545
+ } else if (file.endsWith(".md")) {
546
+ fileList.push(fullPath);
547
+ }
548
+ } catch {
549
+ }
550
+ }
551
+ } catch {
552
+ }
553
+ return fileList;
554
+ }
555
+ function parseMarkdownFrontmatter(content) {
556
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
557
+ if (!match) return { frontmatter: {}, body: content };
558
+ const yamlLines = match[1].split("\n");
559
+ const frontmatter = {};
560
+ for (const line of yamlLines) {
561
+ const parts = line.split(":");
562
+ if (parts.length >= 2) {
563
+ const key = parts[0].trim();
564
+ let val = parts.slice(1).join(":").trim();
565
+ if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
566
+ val = val.substring(1, val.length - 1);
567
+ }
568
+ frontmatter[key] = val;
569
+ }
570
+ }
571
+ const body = content.substring(match[0].length);
572
+ return { frontmatter, body };
573
+ }
574
+ function extractGherkinBlock(body) {
575
+ const gherkinRegex = /```gherkin\r?\n([\s\S]*?)\r?\n```/g;
576
+ const blocks = [];
577
+ let match;
578
+ while ((match = gherkinRegex.exec(body)) !== null) {
579
+ blocks.push(match[1].trim());
580
+ }
581
+ return blocks;
582
+ }
583
+ function resolveTargetFiles(rootDir, targetPath) {
584
+ if (targetPath) {
585
+ const fullTarget = path4__namespace.isAbsolute(targetPath) ? targetPath : path4__namespace.join(rootDir, targetPath);
586
+ if (!fs7__namespace.existsSync(fullTarget)) return [];
587
+ return fs7__namespace.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
588
+ }
589
+ const searchDirs = [
590
+ path4__namespace.join(rootDir, "product"),
591
+ path4__namespace.join(rootDir, "security"),
592
+ path4__namespace.join(rootDir, "specs"),
593
+ path4__namespace.join(rootDir, "examples", "product"),
594
+ path4__namespace.join(rootDir, "examples", "security"),
595
+ path4__namespace.join(rootDir, "examples", "specs")
596
+ ];
597
+ return searchDirs.filter((d) => fs7__namespace.existsSync(d)).flatMap((d) => walkMdFiles(d));
598
+ }
599
+ function resolveFeatureFilePath(rootDir, filePath, id, customTarget) {
600
+ if (customTarget) {
601
+ return path4__namespace.isAbsolute(customTarget) ? customTarget : path4__namespace.join(rootDir, customTarget);
602
+ }
603
+ const baseDir = path4__namespace.join(rootDir, "tests", "features");
604
+ const candidateSecurity = path4__namespace.join(baseDir, "security", `${id.toLowerCase()}.feature`);
605
+ const candidateRoot = path4__namespace.join(baseDir, `${id.toLowerCase()}.feature`);
606
+ if (fs7__namespace.existsSync(candidateSecurity)) return candidateSecurity;
607
+ if (fs7__namespace.existsSync(candidateRoot)) return candidateRoot;
608
+ const subDir = filePath.includes("security") ? "security" : "";
609
+ return path4__namespace.join(baseDir, subDir, `${id.toLowerCase()}.feature`);
610
+ }
611
+ function buildFeatureFileContent(rootDir, filePath, id, version, gherkinBlocks) {
612
+ const banner = [
613
+ `# ==============================================================================`,
614
+ `# AUTO-GENERADO POR AI-SDLC (Cucumber Integration)`,
615
+ `# Origen: ${path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/")}`,
616
+ `# ID Requerimiento: ${id}`,
617
+ `# Versi\xF3n: ${version || "1.0.0"}`,
618
+ `# NO EDITAR MANUALMENTE: Cualquier cambio debe realizarse en el Markdown origen.`,
619
+ `# ==============================================================================`,
620
+ "",
621
+ ""
622
+ ].join("\n");
623
+ return banner + gherkinBlocks.join("\n\n") + "\n";
624
+ }
625
+ function countScenarios(gherkinBlocks) {
626
+ let count = 0;
627
+ for (const block of gherkinBlocks) {
628
+ const matches = block.match(/\b(Scenario|Escenario|Scenario Outline|Esquema del escenario):/g);
629
+ if (matches) count += matches.length;
630
+ }
631
+ return count;
632
+ }
633
+ function extractGherkinFeatures(options = {}) {
634
+ const rootDir = options.rootDir || process.cwd();
635
+ const features = [];
636
+ let totalScenarios = 0;
637
+ const targetFiles = resolveTargetFiles(rootDir, options.targetPath);
638
+ for (const filePath of targetFiles) {
639
+ try {
640
+ const content = fs7__namespace.readFileSync(filePath, "utf-8");
641
+ const { frontmatter, body } = parseMarkdownFrontmatter(content);
642
+ const gherkinBlocks = extractGherkinBlock(body);
643
+ if (gherkinBlocks.length === 0) continue;
644
+ const id = frontmatter.id || path4__namespace.basename(filePath, path4__namespace.extname(filePath));
645
+ const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
646
+ fs7__namespace.mkdirSync(path4__namespace.dirname(targetPath), { recursive: true });
647
+ const fullContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
648
+ fs7__namespace.writeFileSync(targetPath, fullContent, "utf-8");
649
+ const scenarioCount = countScenarios(gherkinBlocks);
650
+ totalScenarios += scenarioCount;
651
+ features.push({
652
+ sourceFile: path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/"),
653
+ outputFile: path4__namespace.relative(rootDir, targetPath).replace(/\\/g, "/"),
654
+ featureName: id,
655
+ scenarioCount
656
+ });
657
+ } catch {
658
+ }
659
+ }
660
+ return { success: true, features, totalScenarios };
661
+ }
662
+ function checkGherkinInSync(options = {}) {
663
+ const rootDir = options.rootDir || process.cwd();
664
+ const targetFiles = resolveTargetFiles(rootDir, options.targetPath);
665
+ const outOfSyncFiles = [];
666
+ const missingFiles = [];
667
+ let totalFeatures = 0;
668
+ for (const filePath of targetFiles) {
669
+ try {
670
+ const content = fs7__namespace.readFileSync(filePath, "utf-8");
671
+ const { frontmatter, body } = parseMarkdownFrontmatter(content);
672
+ const gherkinBlocks = extractGherkinBlock(body);
673
+ if (gherkinBlocks.length === 0) continue;
674
+ totalFeatures++;
675
+ const id = frontmatter.id || path4__namespace.basename(filePath, path4__namespace.extname(filePath));
676
+ const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
677
+ const relTarget = path4__namespace.relative(rootDir, targetPath).replace(/\\/g, "/");
678
+ if (!fs7__namespace.existsSync(targetPath)) {
679
+ missingFiles.push(relTarget);
680
+ continue;
681
+ }
682
+ const expectedContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
683
+ const actualContent = fs7__namespace.readFileSync(targetPath, "utf-8");
684
+ if (actualContent.replace(/\r\n/g, "\n") !== expectedContent.replace(/\r\n/g, "\n")) {
685
+ outOfSyncFiles.push(relTarget);
686
+ }
687
+ } catch {
688
+ }
689
+ }
690
+ return {
691
+ inSync: missingFiles.length === 0 && outOfSyncFiles.length === 0,
692
+ outOfSyncFiles,
693
+ missingFiles,
694
+ totalFeatures
695
+ };
696
+ }
697
+
698
+ // src/verifiers/testing-coverage.ts
699
+ function parseTaskVerification(v) {
700
+ const verification = {};
701
+ if (v && typeof v === "object") {
702
+ const rec = v;
703
+ if (typeof rec["method"] === "string") verification.method = rec["method"];
704
+ if (typeof rec["command-or-criteria"] === "string") verification.criteria = rec["command-or-criteria"];
705
+ }
706
+ return verification;
707
+ }
708
+ function parseTaskItem(t) {
709
+ return {
710
+ id: typeof t["id"] === "string" ? t["id"] : String(t["id"] ?? ""),
711
+ title: typeof t["title"] === "string" ? t["title"] : void 0,
712
+ verification: parseTaskVerification(t["verification"])
713
+ };
714
+ }
715
+ function parseVerifiedByTests(verifiedBy) {
716
+ if (Array.isArray(verifiedBy)) {
717
+ return verifiedBy.map(String);
718
+ }
719
+ if (typeof verifiedBy === "string") {
720
+ return [verifiedBy];
721
+ }
722
+ return void 0;
723
+ }
724
+ function parseGenericDoc(content) {
725
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
726
+ if (!match) return { frontmatter: {}, body: content };
727
+ let parsed;
728
+ try {
729
+ parsed = yaml3__default.default.load(match[1]);
730
+ } catch {
731
+ return { frontmatter: {}, body: content };
732
+ }
733
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
734
+ return { frontmatter: {}, body: content };
735
+ }
736
+ const raw = parsed;
737
+ const rawTasks = Array.isArray(raw["tasks"]) ? raw["tasks"] : [];
738
+ const tasks = rawTasks.map(parseTaskItem);
739
+ const frontmatter = {
740
+ ...raw,
741
+ tasks,
742
+ "verified-by-tests": parseVerifiedByTests(raw["verified-by-tests"])
743
+ };
744
+ return { frontmatter, body: content.substring(match[0].length) };
745
+ }
746
+ function buildRequirementsTable(reqAudit) {
747
+ const lines = [
748
+ `## 1. Requirements Audit (Product, Quality, and Security)`,
749
+ ``,
750
+ `| Requirement ID | Title | Source File | Declared Method | Linked Test Files | Status |`,
751
+ `| :--- | :--- | :--- | :---: | :--- | :---: |`
752
+ ];
753
+ for (const r of reqAudit) {
754
+ const icon = r.status === "VERIFICADO_CON_PRUEBA" ? "\u2705 VERIFIED" : "\u274C NO TESTS";
755
+ lines.push(
756
+ `| **\`${r.id}\`** | ${r.title} | \`${r.file}\` | \`${r.method}\` | \`${r.testRefs}\` | ${icon} |`
757
+ );
758
+ }
759
+ return lines;
760
+ }
761
+ function buildTasksTable(taskAudit) {
762
+ const lines = [
763
+ `## 2. Tasks Audit (SDD Delivery Engineering)`,
764
+ ``,
765
+ `| Task ID | Title | tasks.md File | Method | Verification Command / Criterion | Status |`,
766
+ `| :--- | :--- | :--- | :---: | :--- | :---: |`
767
+ ];
768
+ for (const t of taskAudit) {
769
+ const icon = t.status === "VERIFICADO_CON_PRUEBA" ? "\u2705 VERIFIED" : "\u274C NO TESTS";
770
+ lines.push(
771
+ `| **\`${t.id}\`** | ${t.title} | \`${t.file}\` | \`${t.method}\` | \`${t.commandOrCriteria}\` | ${icon} |`
772
+ );
773
+ }
774
+ return lines;
775
+ }
776
+ function generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, taskErrors) {
777
+ const totalErrors = reqErrors + taskErrors;
778
+ const verdict = totalErrors === 0 ? "100% VERIFIED WITH TESTS (PASSED)" : `BLOCKED: Detected ${totalErrors} items without tests (${reqErrors} Requirements, ${taskErrors} Tasks)`;
779
+ const reportLines = [
780
+ `# \u{1F9EA} Deterministic Test Coverage Audit for Requirements and Tasks`,
781
+ ``,
782
+ `> **Audit Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
783
+ `> **Overall Verdict:** ${verdict}`,
784
+ ``,
785
+ `---`,
786
+ ``,
787
+ ...buildRequirementsTable(reqAudit),
788
+ ``,
789
+ `---`,
790
+ ``,
791
+ ...buildTasksTable(taskAudit)
792
+ ];
793
+ return reportLines.join("\n");
794
+ }
795
+ var EXCLUDED_TEST_DIRS = /* @__PURE__ */ new Set([
796
+ "node_modules",
797
+ ".git",
798
+ "dist",
799
+ "bin",
800
+ "obj",
801
+ "build",
802
+ "out",
803
+ "target",
804
+ "coverage",
805
+ ".changeset",
806
+ "scratch",
807
+ "fixtures",
808
+ "templates",
809
+ "examples",
810
+ ".vs",
811
+ ".idea"
812
+ ]);
813
+ var TEST_EXTENSIONS = [".feature", ".spec.ts", ".spec.js", ".test.ts", ".test.js", "_test.py", "_test.go"];
814
+ function isTestFile(fileName) {
815
+ return TEST_EXTENSIONS.some((ext) => fileName.endsWith(ext));
816
+ }
817
+ function recordRequirementTest(reqToTestsMap, reqId, relTest) {
818
+ const existing = reqToTestsMap.get(reqId) || [];
819
+ if (!existing.includes(relTest)) {
820
+ existing.push(relTest);
821
+ reqToTestsMap.set(reqId, existing);
822
+ }
823
+ }
824
+ function parseFeatureTags(content, relTest, reqToTestsMap) {
825
+ const lines = content.split("\n");
826
+ for (const line of lines) {
827
+ const trimmed = line.trim();
828
+ if (!trimmed.startsWith("@")) continue;
829
+ const tags = trimmed.split(/\s+/).filter((t) => t.startsWith("@"));
830
+ for (const t of tags) {
831
+ recordRequirementTest(reqToTestsMap, t.substring(1), relTest);
832
+ }
833
+ }
834
+ }
835
+ var REQ_ID_REGEX = /\b(FR|QR|CON|SEC-REQ)-[A-Z0-9]+(-[A-Z0-9]+)*\b/g;
836
+ function parseCodeTestIds(content, relTest, reqToTestsMap) {
837
+ const idMatches = content.match(REQ_ID_REGEX);
838
+ if (!idMatches) return;
839
+ for (const reqId of idMatches) {
840
+ recordRequirementTest(reqToTestsMap, reqId, relTest);
841
+ }
842
+ }
843
+ function processTestFile(fullPath, rootDir, reqToTestsMap) {
844
+ try {
845
+ const content = fs7__namespace.readFileSync(fullPath, "utf-8");
846
+ const relTest = path4__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
847
+ if (fullPath.endsWith(".feature")) {
848
+ parseFeatureTags(content, relTest, reqToTestsMap);
849
+ }
850
+ parseCodeTestIds(content, relTest, reqToTestsMap);
851
+ } catch {
852
+ }
853
+ }
854
+ function collectTestFiles(dir, files = []) {
855
+ if (!fs7__namespace.existsSync(dir)) return files;
856
+ try {
857
+ const entries = fs7__namespace.readdirSync(dir, { withFileTypes: true });
858
+ for (const e of entries) {
859
+ if (e.isDirectory()) {
860
+ if (!EXCLUDED_TEST_DIRS.has(e.name)) {
861
+ collectTestFiles(path4__namespace.join(dir, e.name), files);
862
+ }
863
+ } else if (isTestFile(e.name)) {
864
+ files.push(path4__namespace.join(dir, e.name));
865
+ }
866
+ }
867
+ } catch {
868
+ }
869
+ return files;
870
+ }
871
+ function scanAllTestArtifacts(rootDir) {
872
+ const reqToTestsMap = /* @__PURE__ */ new Map();
873
+ const testFiles = collectTestFiles(rootDir);
874
+ for (const file of testFiles) {
875
+ processTestFile(file, rootDir, reqToTestsMap);
876
+ }
877
+ return reqToTestsMap;
878
+ }
879
+ var NOMINAL_KEYWORDS = /\b(nominal|happy|valid|exitoso|normal|standard|flujo principal)\b/i;
880
+ var BOUNDARY_KEYWORDS = /\b(boundary|limit|edge|min|max|l[ií]mite|borde|extremo|rango)\b/i;
881
+ var INVALID_KEYWORDS = /\b(invalid|out[- ]of[- ]range|error|null|empty|reject|fail|unauthorized|exception|inv[aá]lido|fuera de rango|rechaz|excepci[oó]n|fallo)\b/i;
882
+ function extractScenariosForReq(gherkinText, reqId) {
883
+ const lines = gherkinText.split(/\r?\n/);
884
+ const featureApplies = gherkinText.includes(`@${reqId}`);
885
+ if (!featureApplies) return [];
886
+ const chunks = [];
887
+ let pendingTags = "";
888
+ for (const rawLine of lines) {
889
+ const line = rawLine.trim();
890
+ if (line.startsWith("@")) {
891
+ pendingTags += " " + line;
892
+ } else if (/^(Scenario|Escenario|Scenario Outline|Esquema del escenario):/i.test(line)) {
893
+ const isOutline = /^(Scenario Outline|Esquema del escenario):/i.test(line);
894
+ chunks.push({ tags: pendingTags.toLowerCase(), header: line, isOutline });
895
+ pendingTags = "";
896
+ }
897
+ }
898
+ return chunks;
899
+ }
900
+ var INVALID_TAG_RE = /@(invalid|error|out-of-range)\b/i;
901
+ function isInvalidScenario(chunk, combined) {
902
+ return INVALID_TAG_RE.test(chunk.tags) || INVALID_KEYWORDS.test(combined);
903
+ }
904
+ function isBoundaryScenario(chunk, combined, isInvalid) {
905
+ if (chunk.tags.includes("@boundary") || BOUNDARY_KEYWORDS.test(combined)) return true;
906
+ return chunk.isOutline && !isInvalid;
907
+ }
908
+ function classifyScenarioChunk(chunk, cov) {
909
+ const combined = `${chunk.tags} ${chunk.header}`;
910
+ const isInvalid = isInvalidScenario(chunk, combined);
911
+ const isBoundary = isBoundaryScenario(chunk, combined, isInvalid);
912
+ const isNominal = chunk.tags.includes("@nominal") || NOMINAL_KEYWORDS.test(combined) || !isInvalid && !isBoundary;
913
+ if (isNominal) cov.nominal = true;
914
+ if (isBoundary) cov.boundary = true;
915
+ if (isInvalid) cov.invalid = true;
916
+ }
917
+ function analyzeBddVariantsForRequirement(reqId, gherkinSources) {
918
+ const cov = { nominal: false, boundary: false, invalid: false, totalScenarios: 0 };
919
+ for (const src of gherkinSources) {
920
+ const chunks = extractScenariosForReq(src, reqId);
921
+ cov.totalScenarios += chunks.length;
922
+ for (const chunk of chunks) {
923
+ classifyScenarioChunk(chunk, cov);
924
+ }
925
+ }
926
+ return cov;
927
+ }
928
+ function computeMissingVariants(cov) {
929
+ const missing = [];
930
+ if (!cov.nominal) missing.push("nominal");
931
+ if (!cov.boundary) missing.push("boundary");
932
+ if (!cov.invalid) missing.push("invalid");
933
+ return missing;
934
+ }
935
+ var REQ_PREFIXES = ["FR-", "QR-", "CON-", "SEC-REQ-"];
936
+ function isRequirementArtifact(frontmatter, id) {
937
+ if (frontmatter.type === "requirement" || frontmatter.type === "security-requirement") {
938
+ return true;
939
+ }
940
+ return REQ_PREFIXES.some((prefix) => id.startsWith(prefix));
941
+ }
942
+ function resolveRequirementMethod(declaredMethod, matchingTests) {
943
+ if (typeof declaredMethod === "string" && declaredMethod) {
944
+ return declaredMethod;
945
+ }
946
+ return matchingTests.some((t) => t.endsWith(".feature")) ? "BDD_CUCUMBER" : "TEST_EXECUTION";
947
+ }
948
+ function shouldEnforceVariantsForReq(id, frontmatter, options) {
949
+ if (!id.startsWith("FR-")) return false;
950
+ return Boolean(options.enforceBddVariants || frontmatter["enforce-bdd-variants"] === true);
951
+ }
952
+ function evaluateReqVerification(id, isTemplate, hasTests, missingVariants, frontmatter, options) {
953
+ if (isTemplate) return true;
954
+ const enforceVariants = shouldEnforceVariantsForReq(id, frontmatter, options);
955
+ const variantsOk = !enforceVariants || missingVariants.length === 0;
956
+ return hasTests && variantsOk;
957
+ }
958
+ function formatTestRefs(matchingTests, relPath, totalScenarios) {
959
+ if (matchingTests.length > 0) return matchingTests.join(", ");
960
+ return totalScenarios > 0 ? `${relPath} (gherkin)` : "NINGUNA DETECTADA";
961
+ }
962
+ function buildRequirementAuditItem(frontmatter, id, relPath, isTemplate, matchingTests, bddVariants, missingVariants, isVerified) {
963
+ const status = isVerified ? "VERIFICADO_CON_PRUEBA" : "FALLO_SIN_PRUEBA";
964
+ return {
965
+ id: id || "SIN-ID",
966
+ title: frontmatter.title || "Sin t\xEDtulo",
967
+ file: relPath,
968
+ isTemplate,
969
+ method: resolveRequirementMethod(frontmatter["verifiable-by"], matchingTests),
970
+ testRefs: formatTestRefs(matchingTests, relPath, bddVariants.totalScenarios),
971
+ existingCount: matchingTests.length || bddVariants.totalScenarios,
972
+ bddVariants,
973
+ missingVariants,
974
+ status
975
+ };
976
+ }
977
+ function auditRequirement(frontmatter, id, relPath, isTemplate, reqToTestsMap, gherkinSources, options) {
978
+ const matchingTests = reqToTestsMap.get(id) || [];
979
+ const bddVariants = analyzeBddVariantsForRequirement(id, gherkinSources);
980
+ const missingVariants = id.startsWith("FR-") ? computeMissingVariants(bddVariants) : [];
981
+ const hasTests = matchingTests.length > 0 || bddVariants.totalScenarios > 0;
982
+ const isVerified = evaluateReqVerification(id, isTemplate, hasTests, missingVariants, frontmatter, options);
983
+ const item = buildRequirementAuditItem(
984
+ frontmatter,
985
+ id,
986
+ relPath,
987
+ isTemplate,
988
+ matchingTests,
989
+ bddVariants,
990
+ missingVariants,
991
+ isVerified
992
+ );
993
+ return { item, isError: !isVerified };
994
+ }
995
+ function parseSingleFlowEntry(entry) {
996
+ if (typeof entry === "string") return entry.trim();
997
+ if (entry && typeof entry === "object") {
998
+ const rec = entry;
999
+ return String(rec.id || rec.name || "").trim();
1000
+ }
1001
+ return "";
1002
+ }
1003
+ function extractDeclaredUseCaseFlows(frontmatter) {
1004
+ const flows = [];
1005
+ const rawLists = [frontmatter["alternative-flows"], frontmatter["exception-flows"]];
1006
+ for (const list of rawLists) {
1007
+ if (!Array.isArray(list)) continue;
1008
+ for (const entry of list) {
1009
+ const flowId = parseSingleFlowEntry(entry);
1010
+ if (flowId) flows.push(flowId);
1011
+ }
1012
+ }
1013
+ return flows;
1014
+ }
1015
+ function auditUseCaseFlowItem(id, relPath, declaredFlows, combinedTestsText) {
1016
+ const coveredFlows = [];
1017
+ const missingFlows = [];
1018
+ const lowerCorpus = combinedTestsText.toLowerCase();
1019
+ for (const flow of declaredFlows) {
1020
+ if (lowerCorpus.includes(flow.toLowerCase())) {
1021
+ coveredFlows.push(flow);
1022
+ } else {
1023
+ missingFlows.push(flow);
1024
+ }
1025
+ }
1026
+ return {
1027
+ id,
1028
+ file: relPath,
1029
+ declaredFlows,
1030
+ coveredFlows,
1031
+ missingFlows,
1032
+ status: missingFlows.length === 0 ? "VERIFICADO_CON_PRUEBA" : "FALLO_SIN_PRUEBA"
1033
+ };
1034
+ }
1035
+ function isTaskProofValid(criteria, isTemplate) {
1036
+ if (isTemplate) return true;
1037
+ return criteria.length > 4 && !criteria.includes("TODO") && !criteria.includes("TBD");
1038
+ }
1039
+ function auditSingleTask(t, relPath, isTemplate) {
1040
+ const crit = t.verification?.criteria || "";
1041
+ const method = t.verification?.method || "TEST_EXECUTION";
1042
+ const hasProof = isTaskProofValid(crit, isTemplate);
1043
+ return {
1044
+ item: {
1045
+ id: t.id,
1046
+ title: t.title || "Sin t\xEDtulo",
1047
+ file: relPath,
1048
+ method,
1049
+ commandOrCriteria: crit || "AUSENTE",
1050
+ status: hasProof ? "VERIFICADO_CON_PRUEBA" : "FALLO_SIN_PRUEBA"
1051
+ },
1052
+ hasProof
1053
+ };
1054
+ }
1055
+ function auditTasks(tasks, relPath, isTemplate) {
1056
+ if (!Array.isArray(tasks) || tasks.length === 0) {
1057
+ return { items: [], errorCount: 0 };
1058
+ }
1059
+ const items = [];
1060
+ let errorCount = 0;
1061
+ for (const t of tasks) {
1062
+ const { item, hasProof } = auditSingleTask(t, relPath, isTemplate);
1063
+ if (!hasProof) errorCount++;
1064
+ items.push(item);
1065
+ }
1066
+ return { items, errorCount };
1067
+ }
1068
+ function collectTestCorpus(rootDir, allMdFiles) {
1069
+ const gherkinSources = [];
1070
+ const testContents = [];
1071
+ for (const tf of collectTestFiles(rootDir)) {
1072
+ try {
1073
+ const content = fs7__namespace.readFileSync(tf, "utf-8");
1074
+ testContents.push(content);
1075
+ if (tf.endsWith(".feature")) gherkinSources.push(content);
1076
+ } catch {
1077
+ }
1078
+ }
1079
+ for (const md of allMdFiles) {
1080
+ try {
1081
+ const blocks = extractGherkinBlock(fs7__namespace.readFileSync(md, "utf-8"));
1082
+ gherkinSources.push(...blocks);
1083
+ testContents.push(...blocks);
1084
+ } catch {
1085
+ }
1086
+ }
1087
+ return { gherkinSources, combinedTests: testContents.join("\n") };
1088
+ }
1089
+ function assembleCoverageResult(reqAudit, taskAudit, useCaseFlows, reqErrors, taskErrors, ucErrors) {
1090
+ return {
1091
+ success: reqErrors === 0 && taskErrors === 0 && ucErrors === 0,
1092
+ totalRequirements: reqAudit.length,
1093
+ passedRequirements: reqAudit.length - reqErrors,
1094
+ failedRequirements: reqErrors,
1095
+ totalTasks: taskAudit.length,
1096
+ passedTasks: taskAudit.length - taskErrors,
1097
+ failedTasks: taskErrors,
1098
+ requirements: reqAudit,
1099
+ tasks: taskAudit,
1100
+ useCaseFlows,
1101
+ failedUseCaseFlows: ucErrors,
1102
+ reportMarkdown: generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, taskErrors)
1103
+ };
1104
+ }
1105
+ function recordUseCaseAuditIfPresent(frontmatter, id, relPath, isTemplate, combinedTests, state) {
1106
+ if (isTemplate || !(frontmatter.type === "use-case" || id.startsWith("UC-"))) return;
1107
+ const declaredFlows = extractDeclaredUseCaseFlows(frontmatter);
1108
+ if (declaredFlows.length === 0) return;
1109
+ const flowAudit = auditUseCaseFlowItem(id, relPath, declaredFlows, combinedTests);
1110
+ state.useCaseFlows.push(flowAudit);
1111
+ if (flowAudit.status === "FALLO_SIN_PRUEBA") state.ucErrors++;
1112
+ }
1113
+ function processMdFileForCoverage(file, rootDir, reqToTestsMap, corpus, options, state) {
1114
+ const { frontmatter } = parseGenericDoc(fs7__namespace.readFileSync(file, "utf-8"));
1115
+ const relPath = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
1116
+ const isTemplate = relPath.includes("templates/");
1117
+ const id = frontmatter.id || "";
1118
+ if (isRequirementArtifact(frontmatter, id)) {
1119
+ const audited = auditRequirement(frontmatter, id, relPath, isTemplate, reqToTestsMap, corpus.gherkinSources, options);
1120
+ state.reqAudit.push(audited.item);
1121
+ if (audited.isError) state.reqErrors++;
1122
+ }
1123
+ recordUseCaseAuditIfPresent(frontmatter, id, relPath, isTemplate, corpus.combinedTests, state);
1124
+ const auditedTasks = auditTasks(frontmatter.tasks, relPath, isTemplate);
1125
+ state.taskAudit.push(...auditedTasks.items);
1126
+ state.taskErrors += auditedTasks.errorCount;
1127
+ }
1128
+ function verifyTestingCoverage(options = {}) {
1129
+ const rootDir = options.rootDir || process.cwd();
1130
+ const allMdFiles = walkMdFiles(rootDir);
1131
+ const reqToTestsMap = scanAllTestArtifacts(rootDir);
1132
+ const corpus = collectTestCorpus(rootDir, allMdFiles);
1133
+ const state = {
1134
+ reqAudit: [],
1135
+ taskAudit: [],
1136
+ useCaseFlows: [],
1137
+ reqErrors: 0,
1138
+ taskErrors: 0,
1139
+ ucErrors: 0
1140
+ };
1141
+ for (const file of allMdFiles) {
1142
+ try {
1143
+ processMdFileForCoverage(file, rootDir, reqToTestsMap, corpus, options, state);
1144
+ } catch {
1145
+ }
1146
+ }
1147
+ return assembleCoverageResult(
1148
+ state.reqAudit,
1149
+ state.taskAudit,
1150
+ state.useCaseFlows,
1151
+ state.reqErrors,
1152
+ state.taskErrors,
1153
+ state.ucErrors
1154
+ );
1155
+ }
510
1156
 
511
1157
  // src/verifiers/quality-gate.ts
512
1158
  var DEFAULT_EXTENSIONS = [
@@ -621,7 +1267,7 @@ function applyOptionOverrides(policy, overrides) {
621
1267
  function parseQualityPolicy(content, overrides) {
622
1268
  const policy = createDefaultQualityPolicy();
623
1269
  try {
624
- const doc = yaml2__default.default.load(content);
1270
+ const doc = yaml3__default.default.load(content);
625
1271
  if (doc && typeof doc === "object") {
626
1272
  const record = doc;
627
1273
  applyDocScope(policy, record);
@@ -637,7 +1283,7 @@ function parseQualityPolicy(content, overrides) {
637
1283
  return policy;
638
1284
  }
639
1285
  function extractFunctions(content, filePath) {
640
- const ext = path5__namespace.extname(filePath).toLowerCase();
1286
+ const ext = path4__namespace.extname(filePath).toLowerCase();
641
1287
  return TS_JS_EXTENSIONS.includes(ext) ? extractFunctionsTypeScriptAst(content, filePath) : extractFunctionsPolyglot(content, filePath);
642
1288
  }
643
1289
  function extractFunctionsBraceLanguages(content, filePath) {
@@ -668,55 +1314,55 @@ function isExcludedFile(file, normalized, excludePatterns) {
668
1314
  );
669
1315
  }
670
1316
  function walkDir(dir, extensions = DEFAULT_EXTENSIONS, excludePatterns = DEFAULT_EXCLUDES) {
671
- if (!fs6__namespace.existsSync(dir)) return [];
1317
+ if (!fs7__namespace.existsSync(dir)) return [];
672
1318
  const results = [];
673
- for (const file of fs6__namespace.readdirSync(dir)) {
674
- const fullPath = path5__namespace.join(dir, file);
1319
+ for (const file of fs7__namespace.readdirSync(dir)) {
1320
+ const fullPath = path4__namespace.join(dir, file);
675
1321
  const normalized = fullPath.replace(/\\/g, "/");
676
1322
  if (isExcludedFile(file, normalized, excludePatterns)) continue;
677
- const stat = fs6__namespace.statSync(fullPath);
1323
+ const stat = fs7__namespace.statSync(fullPath);
678
1324
  if (stat.isDirectory()) {
679
1325
  results.push(...walkDir(fullPath, extensions, excludePatterns));
680
- } else if (extensions.includes(path5__namespace.extname(file).toLowerCase())) {
1326
+ } else if (extensions.includes(path4__namespace.extname(file).toLowerCase())) {
681
1327
  results.push(fullPath);
682
1328
  }
683
1329
  }
684
1330
  return results;
685
1331
  }
686
1332
  function resolvePolicy(rootDir, options) {
687
- const policyFile = options.policyPath || path5__namespace.join(rootDir, "quality-policy.yaml");
1333
+ const policyFile = options.policyPath || path4__namespace.join(rootDir, "quality-policy.yaml");
688
1334
  if (options.policy) {
689
1335
  const policy = { ...options.policy };
690
1336
  applyOptionOverrides(policy, options.thresholds);
691
1337
  return policy;
692
1338
  }
693
- if (fs6__namespace.existsSync(policyFile)) {
694
- return parseQualityPolicy(fs6__namespace.readFileSync(policyFile, "utf-8"), options.thresholds);
1339
+ if (fs7__namespace.existsSync(policyFile)) {
1340
+ return parseQualityPolicy(fs7__namespace.readFileSync(policyFile, "utf-8"), options.thresholds);
695
1341
  }
696
1342
  return parseQualityPolicy("", options.thresholds);
697
1343
  }
698
1344
  function isPathExcluded(targetPath, rootDir, excludePatterns) {
699
- const rel = path5__namespace.relative(rootDir, targetPath).replace(/\\/g, "/");
700
- const base = path5__namespace.basename(targetPath);
1345
+ const rel = path4__namespace.relative(rootDir, targetPath).replace(/\\/g, "/");
1346
+ const base = path4__namespace.basename(targetPath);
701
1347
  return excludePatterns.some(
702
1348
  (pat) => base === pat || rel === pat || rel.startsWith(pat + "/") || rel.includes("/" + pat + "/") || rel.endsWith("/" + pat) || rel.includes(pat)
703
1349
  );
704
1350
  }
705
1351
  function scanContainerSubdir(containerDir, subdirName, targetDir, rootDir, policy, files) {
706
1352
  if (policy.exclude_patterns.includes(subdirName)) return;
707
- const subTarget = path5__namespace.join(containerDir, subdirName, targetDir);
708
- if (!fs6__namespace.existsSync(subTarget) || !fs6__namespace.statSync(subTarget).isDirectory()) return;
1353
+ const subTarget = path4__namespace.join(containerDir, subdirName, targetDir);
1354
+ if (!fs7__namespace.existsSync(subTarget) || !fs7__namespace.statSync(subTarget).isDirectory()) return;
709
1355
  if (isPathExcluded(subTarget, rootDir, policy.exclude_patterns)) return;
710
1356
  for (const f of walkDir(subTarget, policy.supported_extensions, policy.exclude_patterns)) {
711
1357
  files.add(f);
712
1358
  }
713
1359
  }
714
1360
  function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
715
- const containerDir = path5__namespace.join(rootDir, container);
716
- if (!fs6__namespace.existsSync(containerDir) || !fs6__namespace.statSync(containerDir).isDirectory()) return;
1361
+ const containerDir = path4__namespace.join(rootDir, container);
1362
+ if (!fs7__namespace.existsSync(containerDir) || !fs7__namespace.statSync(containerDir).isDirectory()) return;
717
1363
  if (isPathExcluded(containerDir, rootDir, policy.exclude_patterns)) return;
718
1364
  try {
719
- for (const subdir of fs6__namespace.readdirSync(containerDir, { withFileTypes: true })) {
1365
+ for (const subdir of fs7__namespace.readdirSync(containerDir, { withFileTypes: true })) {
720
1366
  if (subdir.isDirectory()) {
721
1367
  scanContainerSubdir(containerDir, subdir.name, targetDir, rootDir, policy, files);
722
1368
  }
@@ -725,8 +1371,8 @@ function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
725
1371
  }
726
1372
  }
727
1373
  function scanDirectTarget(rootDir, targetDir, policy, files) {
728
- const fullDir = path5__namespace.join(rootDir, targetDir);
729
- if (fs6__namespace.existsSync(fullDir) && !isPathExcluded(fullDir, rootDir, policy.exclude_patterns)) {
1374
+ const fullDir = path4__namespace.join(rootDir, targetDir);
1375
+ if (fs7__namespace.existsSync(fullDir) && !isPathExcluded(fullDir, rootDir, policy.exclude_patterns)) {
730
1376
  for (const f of walkDir(fullDir, policy.supported_extensions, policy.exclude_patterns)) {
731
1377
  files.add(f);
732
1378
  }
@@ -763,23 +1409,86 @@ function evaluateViolations(fn, policy) {
763
1409
  return violations;
764
1410
  }
765
1411
  function evaluateFileQuality(file, rootDir, policy) {
766
- const content = fs6__namespace.readFileSync(file, "utf-8");
1412
+ const content = fs7__namespace.readFileSync(file, "utf-8");
767
1413
  const fns = extractFunctions(content, file);
768
1414
  return fns.map((fn) => {
769
1415
  const violations = evaluateViolations(fn, policy);
770
1416
  return {
771
1417
  ...fn,
772
- relPath: path5__namespace.relative(rootDir, file).replace(/\\/g, "/"),
1418
+ relPath: path4__namespace.relative(rootDir, file).replace(/\\/g, "/"),
773
1419
  status: violations.length === 0 ? "PASS" : "FAIL",
774
1420
  violations
775
1421
  };
776
1422
  });
777
1423
  }
778
- function buildQualityGateResult(results, totalFiles, policy) {
1424
+ var UNIT_TEST_SUFFIXES = [".spec.ts", ".test.ts", ".spec.js", ".test.js", "_test.py", "_test.go"];
1425
+ function collectUnitTestCorpus(rootDir) {
1426
+ const allCodeFiles = walkDir(rootDir, DEFAULT_EXTENSIONS, DEFAULT_EXCLUDES);
1427
+ const chunks = [];
1428
+ for (const tf of allCodeFiles) {
1429
+ if (!UNIT_TEST_SUFFIXES.some((s) => tf.endsWith(s))) continue;
1430
+ try {
1431
+ chunks.push(fs7__namespace.readFileSync(tf, "utf-8"));
1432
+ } catch {
1433
+ }
1434
+ }
1435
+ return chunks.join("\n");
1436
+ }
1437
+ function auditUnitTestsPerFunction(results, rootDir) {
1438
+ const corpus = collectUnitTestCorpus(rootDir);
1439
+ const untested = [];
1440
+ for (const r of results) {
1441
+ const fnName = r.functionName.includes(".") ? r.functionName.split(".").pop() : r.functionName;
1442
+ if (!fnName || fnName === "anonymous" || fnName === "constructor") continue;
1443
+ if (!corpus.includes(fnName)) {
1444
+ untested.push({ file: r.relPath, functionName: r.functionName });
1445
+ r.violations.push(`Missing unit test for function '${r.functionName}'`);
1446
+ r.status = "FAIL";
1447
+ }
1448
+ }
1449
+ return untested;
1450
+ }
1451
+ function readSummaryCoverageJson(rootDir) {
1452
+ for (const rel of ["coverage/coverage-summary.json", "reports/coverage-summary.json"]) {
1453
+ const full = path4__namespace.join(rootDir, rel);
1454
+ if (!fs7__namespace.existsSync(full)) continue;
1455
+ try {
1456
+ const raw = JSON.parse(fs7__namespace.readFileSync(full, "utf-8"));
1457
+ const total = raw.total;
1458
+ if (!total) continue;
1459
+ return {
1460
+ linesPct: Number(total.lines?.pct ?? 0),
1461
+ branchesPct: Number(total.branches?.pct ?? 0),
1462
+ functionsPct: Number(total.functions?.pct ?? 0),
1463
+ source: rel
1464
+ };
1465
+ } catch {
1466
+ }
1467
+ }
1468
+ return null;
1469
+ }
1470
+ function checkCoverageThreshold(label, actual, min, out) {
1471
+ if (actual < min) {
1472
+ out.push(`${label} coverage ${actual}% is below minimum threshold of ${min}%`);
1473
+ }
1474
+ }
1475
+ function evaluateCoverageThresholds(rootDir, policy, requireReport) {
1476
+ const cov = readSummaryCoverageJson(rootDir);
1477
+ if (!cov) {
1478
+ return requireReport ? ["Missing code coverage summary report (coverage/coverage-summary.json)"] : [];
1479
+ }
1480
+ const violations = [];
1481
+ checkCoverageThreshold("Line", cov.linesPct, policy.min_line_coverage, violations);
1482
+ checkCoverageThreshold("Branch", cov.branchesPct, policy.min_branch_coverage, violations);
1483
+ checkCoverageThreshold("Function", cov.functionsPct, policy.min_function_coverage, violations);
1484
+ return violations;
1485
+ }
1486
+ function buildQualityGateResult(results, totalFiles, policy, coverageViolations, untestedFunctions) {
779
1487
  const failCount = results.filter((r) => r.status === "FAIL").length;
780
1488
  const passCount = results.length - failCount;
781
- const violationsCount = results.reduce((acc, r) => acc + r.violations.length, 0);
782
- const success = policy.enforce_mode === "STRICT" ? failCount === 0 : true;
1489
+ const violationsCount = results.reduce((acc, r) => acc + r.violations.length, 0) + coverageViolations.length;
1490
+ const hasFailures = failCount > 0 || coverageViolations.length > 0;
1491
+ const success = policy.enforce_mode === "STRICT" ? !hasFailures : true;
783
1492
  return {
784
1493
  success,
785
1494
  totalFiles,
@@ -788,7 +1497,9 @@ function buildQualityGateResult(results, totalFiles, policy) {
788
1497
  failCount,
789
1498
  violationsCount,
790
1499
  results,
791
- policy
1500
+ policy,
1501
+ coverageViolations,
1502
+ untestedFunctions
792
1503
  };
793
1504
  }
794
1505
  function verifyQualityGate(options = {}) {
@@ -802,199 +1513,10 @@ function verifyQualityGate(options = {}) {
802
1513
  } catch {
803
1514
  }
804
1515
  }
805
- return buildQualityGateResult(results, allFiles.length, policy);
806
- }
807
- function walkMdFiles(dir, fileList = [], excludeDirs = [
808
- "node_modules",
809
- ".git",
810
- "dist",
811
- "bin",
812
- "obj",
813
- "build",
814
- "out",
815
- "target",
816
- "coverage",
817
- ".changeset",
818
- "scratch",
819
- "test-scaffold",
820
- "fixtures",
821
- "templates",
822
- "examples",
823
- ".agent",
824
- ".agents",
825
- ".claude",
826
- ".cursor",
827
- ".github",
828
- ".vscode",
829
- ".vs",
830
- ".idea"
831
- ]) {
832
- try {
833
- if (!fs6__namespace.existsSync(dir)) return fileList;
834
- const files = fs6__namespace.readdirSync(dir);
835
- for (const file of files) {
836
- const fullPath = path5__namespace.join(dir, file);
837
- try {
838
- if (fs6__namespace.statSync(fullPath).isDirectory()) {
839
- if (!excludeDirs.includes(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
840
- walkMdFiles(fullPath, fileList, excludeDirs);
841
- }
842
- } else if (file.endsWith(".md")) {
843
- fileList.push(fullPath);
844
- }
845
- } catch {
846
- }
847
- }
848
- } catch {
849
- }
850
- return fileList;
851
- }
852
-
853
- // src/verifiers/gherkin.ts
854
- function parseMarkdownFrontmatter(content) {
855
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
856
- if (!match) return { frontmatter: {}, body: content };
857
- const yamlLines = match[1].split("\n");
858
- const frontmatter = {};
859
- for (const line of yamlLines) {
860
- const parts = line.split(":");
861
- if (parts.length >= 2) {
862
- const key = parts[0].trim();
863
- let val = parts.slice(1).join(":").trim();
864
- if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
865
- val = val.substring(1, val.length - 1);
866
- }
867
- frontmatter[key] = val;
868
- }
869
- }
870
- const body = content.substring(match[0].length);
871
- return { frontmatter, body };
872
- }
873
- function extractGherkinBlock(body) {
874
- const gherkinRegex = /```gherkin\r?\n([\s\S]*?)\r?\n```/g;
875
- const blocks = [];
876
- let match;
877
- while ((match = gherkinRegex.exec(body)) !== null) {
878
- blocks.push(match[1].trim());
879
- }
880
- return blocks;
881
- }
882
- function resolveTargetFiles(rootDir, targetPath) {
883
- if (targetPath) {
884
- const fullTarget = path5__namespace.isAbsolute(targetPath) ? targetPath : path5__namespace.join(rootDir, targetPath);
885
- if (!fs6__namespace.existsSync(fullTarget)) return [];
886
- return fs6__namespace.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
887
- }
888
- const searchDirs = [
889
- path5__namespace.join(rootDir, "product"),
890
- path5__namespace.join(rootDir, "security"),
891
- path5__namespace.join(rootDir, "specs"),
892
- path5__namespace.join(rootDir, "examples", "product"),
893
- path5__namespace.join(rootDir, "examples", "security"),
894
- path5__namespace.join(rootDir, "examples", "specs")
895
- ];
896
- return searchDirs.filter((d) => fs6__namespace.existsSync(d)).flatMap((d) => walkMdFiles(d));
897
- }
898
- function resolveFeatureFilePath(rootDir, filePath, id, customTarget) {
899
- if (customTarget) {
900
- return path5__namespace.isAbsolute(customTarget) ? customTarget : path5__namespace.join(rootDir, customTarget);
901
- }
902
- const baseDir = path5__namespace.join(rootDir, "tests", "features");
903
- const candidateSecurity = path5__namespace.join(baseDir, "security", `${id.toLowerCase()}.feature`);
904
- const candidateRoot = path5__namespace.join(baseDir, `${id.toLowerCase()}.feature`);
905
- if (fs6__namespace.existsSync(candidateSecurity)) return candidateSecurity;
906
- if (fs6__namespace.existsSync(candidateRoot)) return candidateRoot;
907
- const subDir = filePath.includes("security") ? "security" : "";
908
- return path5__namespace.join(baseDir, subDir, `${id.toLowerCase()}.feature`);
909
- }
910
- function buildFeatureFileContent(rootDir, filePath, id, version, gherkinBlocks) {
911
- const banner = [
912
- `# ==============================================================================`,
913
- `# AUTO-GENERADO POR AI-SDLC (Cucumber Integration)`,
914
- `# Origen: ${path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/")}`,
915
- `# ID Requerimiento: ${id}`,
916
- `# Versi\xF3n: ${version || "1.0.0"}`,
917
- `# NO EDITAR MANUALMENTE: Cualquier cambio debe realizarse en el Markdown origen.`,
918
- `# ==============================================================================`,
919
- "",
920
- ""
921
- ].join("\n");
922
- return banner + gherkinBlocks.join("\n\n") + "\n";
923
- }
924
- function countScenarios(gherkinBlocks) {
925
- let count = 0;
926
- for (const block of gherkinBlocks) {
927
- const matches = block.match(/\b(Scenario|Escenario|Scenario Outline|Esquema del escenario):/g);
928
- if (matches) count += matches.length;
929
- }
930
- return count;
931
- }
932
- function extractGherkinFeatures(options = {}) {
933
- const rootDir = options.rootDir || process.cwd();
934
- const features = [];
935
- let totalScenarios = 0;
936
- const targetFiles = resolveTargetFiles(rootDir, options.targetPath);
937
- for (const filePath of targetFiles) {
938
- try {
939
- const content = fs6__namespace.readFileSync(filePath, "utf-8");
940
- const { frontmatter, body } = parseMarkdownFrontmatter(content);
941
- const gherkinBlocks = extractGherkinBlock(body);
942
- if (gherkinBlocks.length === 0) continue;
943
- const id = frontmatter.id || path5__namespace.basename(filePath, path5__namespace.extname(filePath));
944
- const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
945
- fs6__namespace.mkdirSync(path5__namespace.dirname(targetPath), { recursive: true });
946
- const fullContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
947
- fs6__namespace.writeFileSync(targetPath, fullContent, "utf-8");
948
- const scenarioCount = countScenarios(gherkinBlocks);
949
- totalScenarios += scenarioCount;
950
- features.push({
951
- sourceFile: path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/"),
952
- outputFile: path5__namespace.relative(rootDir, targetPath).replace(/\\/g, "/"),
953
- featureName: id,
954
- scenarioCount
955
- });
956
- } catch {
957
- }
958
- }
959
- return { success: true, features, totalScenarios };
960
- }
961
- function checkGherkinInSync(options = {}) {
962
- const rootDir = options.rootDir || process.cwd();
963
- const targetFiles = resolveTargetFiles(rootDir, options.targetPath);
964
- const outOfSyncFiles = [];
965
- const missingFiles = [];
966
- let totalFeatures = 0;
967
- for (const filePath of targetFiles) {
968
- try {
969
- const content = fs6__namespace.readFileSync(filePath, "utf-8");
970
- const { frontmatter, body } = parseMarkdownFrontmatter(content);
971
- const gherkinBlocks = extractGherkinBlock(body);
972
- if (gherkinBlocks.length === 0) continue;
973
- totalFeatures++;
974
- const id = frontmatter.id || path5__namespace.basename(filePath, path5__namespace.extname(filePath));
975
- const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
976
- const relTarget = path5__namespace.relative(rootDir, targetPath).replace(/\\/g, "/");
977
- if (!fs6__namespace.existsSync(targetPath)) {
978
- missingFiles.push(relTarget);
979
- continue;
980
- }
981
- const expectedContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
982
- const actualContent = fs6__namespace.readFileSync(targetPath, "utf-8");
983
- if (actualContent.replace(/\r\n/g, "\n") !== expectedContent.replace(/\r\n/g, "\n")) {
984
- outOfSyncFiles.push(relTarget);
985
- }
986
- } catch {
987
- }
988
- }
989
- return {
990
- inSync: missingFiles.length === 0 && outOfSyncFiles.length === 0,
991
- outOfSyncFiles,
992
- missingFiles,
993
- totalFeatures
994
- };
1516
+ const untestedFunctions = options.requireFunctionUnitTests ? auditUnitTestsPerFunction(results, rootDir) : [];
1517
+ const coverageViolations = evaluateCoverageThresholds(rootDir, policy, options.requireCoverageReport);
1518
+ return buildQualityGateResult(results, allFiles.length, policy, coverageViolations, untestedFunctions);
995
1519
  }
996
-
997
- // src/adapters/sdd/openspec.ts
998
1520
  function normalizeHandoffPayload(handoff, changeId) {
999
1521
  return {
1000
1522
  ...handoff,
@@ -1004,12 +1526,12 @@ function normalizeHandoffPayload(handoff, changeId) {
1004
1526
  };
1005
1527
  }
1006
1528
  function parseChangeProfile(specFile) {
1007
- if (!fs6__namespace.existsSync(specFile)) return "standard";
1529
+ if (!fs7__namespace.existsSync(specFile)) return "standard";
1008
1530
  try {
1009
- const rawSpec = fs6__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1531
+ const rawSpec = fs7__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1010
1532
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1011
1533
  if (match) {
1012
- const parsedFm = yaml2__default.default.load(match[1]);
1534
+ const parsedFm = yaml3__default.default.load(match[1]);
1013
1535
  if (parsedFm?.profile && ["patch", "standard", "critical"].includes(String(parsedFm.profile))) {
1014
1536
  return parsedFm.profile;
1015
1537
  }
@@ -1028,15 +1550,15 @@ function checkSpecGherkinFormat(parsedFm, rawSpec, changeWorkspaceDir, errors) {
1028
1550
  }
1029
1551
  }
1030
1552
  function validateSpecFileContent(specFile, changeWorkspaceDir, errors) {
1031
- if (!fs6__namespace.existsSync(specFile)) {
1553
+ if (!fs7__namespace.existsSync(specFile)) {
1032
1554
  errors.push(`Falta el archivo obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
1033
1555
  return;
1034
1556
  }
1035
1557
  try {
1036
- const rawSpec = fs6__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1558
+ const rawSpec = fs7__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1037
1559
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1038
1560
  if (match) {
1039
- const parsedFm = yaml2__default.default.load(match[1]);
1561
+ const parsedFm = yaml3__default.default.load(match[1]);
1040
1562
  if (parsedFm) checkSpecGherkinFormat(parsedFm, rawSpec, changeWorkspaceDir, errors);
1041
1563
  }
1042
1564
  } catch {
@@ -1052,19 +1574,19 @@ function validateHandoffSidecar(handoff, changeWorkspaceDir, errors) {
1052
1574
  if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
1053
1575
  errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
1054
1576
  }
1055
- return path5__namespace.join(changeWorkspaceDir, "handoff.yaml");
1577
+ return path4__namespace.join(changeWorkspaceDir, "handoff.yaml");
1056
1578
  }
1057
1579
  function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
1058
- const proposalFile = path5__namespace.join(changeWorkspaceDir, "proposal.md");
1059
- const tasksFile = path5__namespace.join(changeWorkspaceDir, "tasks.md");
1580
+ const proposalFile = path4__namespace.join(changeWorkspaceDir, "proposal.md");
1581
+ const tasksFile = path4__namespace.join(changeWorkspaceDir, "tasks.md");
1060
1582
  if (profile === "patch") {
1061
1583
  const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
1062
- return { handoff: handoff2, sidecarFile: handoff2 ? path5__namespace.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
1584
+ return { handoff: handoff2, sidecarFile: handoff2 ? path4__namespace.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
1063
1585
  }
1064
- if (!fs6__namespace.existsSync(proposalFile)) {
1586
+ if (!fs7__namespace.existsSync(proposalFile)) {
1065
1587
  warnings.push(`Falta 'proposal.md' en el espacio de trabajo OpenSpec.`);
1066
1588
  }
1067
- if (!fs6__namespace.existsSync(tasksFile)) {
1589
+ if (!fs7__namespace.existsSync(tasksFile)) {
1068
1590
  errors.push(`Falta el archivo obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
1069
1591
  }
1070
1592
  const handoff = adapter.loadSidecar(changeWorkspaceDir);
@@ -1072,20 +1594,20 @@ function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors,
1072
1594
  return { handoff, sidecarFile };
1073
1595
  }
1074
1596
  function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
1075
- const changeId = path5__namespace.basename(changeWorkspaceDir);
1076
- const proposalFile = path5__namespace.join(changeWorkspaceDir, "proposal.md");
1077
- const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
1078
- const designFile = path5__namespace.join(changeWorkspaceDir, "design.md");
1079
- const tasksFile = path5__namespace.join(changeWorkspaceDir, "tasks.md");
1597
+ const changeId = path4__namespace.basename(changeWorkspaceDir);
1598
+ const proposalFile = path4__namespace.join(changeWorkspaceDir, "proposal.md");
1599
+ const specFile = path4__namespace.join(changeWorkspaceDir, "spec.md");
1600
+ const designFile = path4__namespace.join(changeWorkspaceDir, "design.md");
1601
+ const tasksFile = path4__namespace.join(changeWorkspaceDir, "tasks.md");
1080
1602
  return {
1081
1603
  framework: "openspec",
1082
1604
  changeId,
1083
1605
  changeDir: changeWorkspaceDir,
1084
1606
  profile,
1085
- proposalFile: fs6__namespace.existsSync(proposalFile) ? proposalFile : void 0,
1086
- specFile: fs6__namespace.existsSync(specFile) ? specFile : void 0,
1087
- designFile: fs6__namespace.existsSync(designFile) ? designFile : void 0,
1088
- tasksFile: fs6__namespace.existsSync(tasksFile) ? tasksFile : void 0,
1607
+ proposalFile: fs7__namespace.existsSync(proposalFile) ? proposalFile : void 0,
1608
+ specFile: fs7__namespace.existsSync(specFile) ? specFile : void 0,
1609
+ designFile: fs7__namespace.existsSync(designFile) ? designFile : void 0,
1610
+ tasksFile: fs7__namespace.existsSync(tasksFile) ? tasksFile : void 0,
1089
1611
  sidecarFile,
1090
1612
  handoff: handoff || void 0
1091
1613
  };
@@ -1093,28 +1615,28 @@ function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sideca
1093
1615
  var OpenSpecAdapter = class {
1094
1616
  framework = "openspec";
1095
1617
  getChangeWorkspaceDir(rootDir, changeId) {
1096
- const openspecDir = path5__namespace.join(rootDir, "openspec", "changes", changeId);
1097
- if (fs6__namespace.existsSync(openspecDir)) return openspecDir;
1098
- const activeChangeDir = path5__namespace.join(rootDir, "specs", "changes", "active", changeId);
1099
- if (fs6__namespace.existsSync(activeChangeDir)) return activeChangeDir;
1100
- const completedChangeDir = path5__namespace.join(rootDir, "specs", "changes", "completed", changeId);
1101
- if (fs6__namespace.existsSync(completedChangeDir)) return completedChangeDir;
1618
+ const openspecDir = path4__namespace.join(rootDir, "openspec", "changes", changeId);
1619
+ if (fs7__namespace.existsSync(openspecDir)) return openspecDir;
1620
+ const activeChangeDir = path4__namespace.join(rootDir, "specs", "changes", "active", changeId);
1621
+ if (fs7__namespace.existsSync(activeChangeDir)) return activeChangeDir;
1622
+ const completedChangeDir = path4__namespace.join(rootDir, "specs", "changes", "completed", changeId);
1623
+ if (fs7__namespace.existsSync(completedChangeDir)) return completedChangeDir;
1102
1624
  return activeChangeDir;
1103
1625
  }
1104
1626
  depositSidecar(options) {
1105
1627
  const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
1106
- if (!fs6__namespace.existsSync(changeDir)) {
1107
- fs6__namespace.mkdirSync(changeDir, { recursive: true });
1628
+ if (!fs7__namespace.existsSync(changeDir)) {
1629
+ fs7__namespace.mkdirSync(changeDir, { recursive: true });
1108
1630
  }
1109
1631
  const handoff = normalizeHandoffPayload(options.handoff, options.changeId);
1110
1632
  const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
1111
- const filePath = path5__namespace.join(changeDir, filename);
1112
- const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml2__default.default.dump(handoff, { indent: 2, lineWidth: -1 });
1113
- fs6__namespace.writeFileSync(filePath, content, "utf-8");
1633
+ const filePath = path4__namespace.join(changeDir, filename);
1634
+ const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3__default.default.dump(handoff, { indent: 2, lineWidth: -1 });
1635
+ fs7__namespace.writeFileSync(filePath, content, "utf-8");
1114
1636
  return filePath;
1115
1637
  }
1116
1638
  loadSidecar(changeWorkspaceDir) {
1117
- if (!fs6__namespace.existsSync(changeWorkspaceDir)) return null;
1639
+ if (!fs7__namespace.existsSync(changeWorkspaceDir)) return null;
1118
1640
  const candidateFiles = [
1119
1641
  "handoff.yaml",
1120
1642
  "handoff.yml",
@@ -1125,11 +1647,11 @@ var OpenSpecAdapter = class {
1125
1647
  "product-handoff.json"
1126
1648
  ];
1127
1649
  for (const file of candidateFiles) {
1128
- const fullPath = path5__namespace.join(changeWorkspaceDir, file);
1129
- if (fs6__namespace.existsSync(fullPath)) {
1650
+ const fullPath = path4__namespace.join(changeWorkspaceDir, file);
1651
+ if (fs7__namespace.existsSync(fullPath)) {
1130
1652
  try {
1131
- const raw = fs6__namespace.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
1132
- const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml2__default.default.load(raw);
1653
+ const raw = fs7__namespace.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
1654
+ const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3__default.default.load(raw);
1133
1655
  if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
1134
1656
  return parsed;
1135
1657
  }
@@ -1140,7 +1662,7 @@ var OpenSpecAdapter = class {
1140
1662
  return null;
1141
1663
  }
1142
1664
  validateWorkspace(changeWorkspaceDir) {
1143
- if (!fs6__namespace.existsSync(changeWorkspaceDir)) {
1665
+ if (!fs7__namespace.existsSync(changeWorkspaceDir)) {
1144
1666
  return {
1145
1667
  valid: false,
1146
1668
  errors: [`El directorio del cambio OpenSpec no existe: ${changeWorkspaceDir}`],
@@ -1149,7 +1671,7 @@ var OpenSpecAdapter = class {
1149
1671
  }
1150
1672
  const errors = [];
1151
1673
  const warnings = [];
1152
- const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
1674
+ const specFile = path4__namespace.join(changeWorkspaceDir, "spec.md");
1153
1675
  const profile = parseChangeProfile(specFile);
1154
1676
  validateSpecFileContent(specFile, changeWorkspaceDir, errors);
1155
1677
  const { handoff, sidecarFile } = validateOpenSpecStructure(profile, changeWorkspaceDir, this, errors, warnings);
@@ -1163,12 +1685,12 @@ var OpenSpecAdapter = class {
1163
1685
  }
1164
1686
  };
1165
1687
  function parseChangeProfile2(specFile) {
1166
- if (!fs6__namespace.existsSync(specFile)) return "standard";
1688
+ if (!fs7__namespace.existsSync(specFile)) return "standard";
1167
1689
  try {
1168
- const rawSpec = fs6__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1690
+ const rawSpec = fs7__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1169
1691
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1170
1692
  if (match) {
1171
- const parsedFm = yaml2__default.default.load(match[1]);
1693
+ const parsedFm = yaml3__default.default.load(match[1]);
1172
1694
  if (parsedFm?.profile && ["patch", "standard", "critical"].includes(String(parsedFm.profile))) {
1173
1695
  return parsedFm.profile;
1174
1696
  }
@@ -1187,15 +1709,15 @@ function checkSpecGherkinFormat2(parsedFm, rawSpec, changeWorkspaceDir, errors)
1187
1709
  }
1188
1710
  }
1189
1711
  function validateSpecFileContent2(specFile, changeWorkspaceDir, errors) {
1190
- if (!fs6__namespace.existsSync(specFile)) {
1712
+ if (!fs7__namespace.existsSync(specFile)) {
1191
1713
  errors.push(`Falta el archivo de especificaci\xF3n obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
1192
1714
  return;
1193
1715
  }
1194
1716
  try {
1195
- const rawSpec = fs6__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1717
+ const rawSpec = fs7__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1196
1718
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1197
1719
  if (match) {
1198
- const parsedFm = yaml2__default.default.load(match[1]);
1720
+ const parsedFm = yaml3__default.default.load(match[1]);
1199
1721
  if (parsedFm) checkSpecGherkinFormat2(parsedFm, rawSpec, changeWorkspaceDir, errors);
1200
1722
  }
1201
1723
  } catch {
@@ -1211,19 +1733,19 @@ function validateHandoffSidecar2(handoff, changeWorkspaceDir, errors) {
1211
1733
  if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
1212
1734
  errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
1213
1735
  }
1214
- return path5__namespace.join(changeWorkspaceDir, "handoff.yaml");
1736
+ return path4__namespace.join(changeWorkspaceDir, "handoff.yaml");
1215
1737
  }
1216
1738
  function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
1217
- const planFile = path5__namespace.join(changeWorkspaceDir, "plan.md");
1218
- const tasksFile = path5__namespace.join(changeWorkspaceDir, "tasks.md");
1739
+ const planFile = path4__namespace.join(changeWorkspaceDir, "plan.md");
1740
+ const tasksFile = path4__namespace.join(changeWorkspaceDir, "tasks.md");
1219
1741
  if (profile === "patch") {
1220
1742
  const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
1221
- return { handoff: handoff2, sidecarFile: handoff2 ? path5__namespace.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
1743
+ return { handoff: handoff2, sidecarFile: handoff2 ? path4__namespace.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
1222
1744
  }
1223
- if (!fs6__namespace.existsSync(tasksFile)) {
1745
+ if (!fs7__namespace.existsSync(tasksFile)) {
1224
1746
  errors.push(`Falta el archivo de tareas obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
1225
1747
  }
1226
- if (!fs6__namespace.existsSync(planFile)) {
1748
+ if (!fs7__namespace.existsSync(planFile)) {
1227
1749
  warnings.push(`Se recomienda incluir 'plan.md' en el espacio de trabajo Spec Kit.`);
1228
1750
  }
1229
1751
  const handoff = adapter.loadSidecar(changeWorkspaceDir);
@@ -1231,18 +1753,18 @@ function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors,
1231
1753
  return { handoff, sidecarFile };
1232
1754
  }
1233
1755
  function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
1234
- const changeId = path5__namespace.basename(changeWorkspaceDir);
1235
- const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
1236
- const planFile = path5__namespace.join(changeWorkspaceDir, "plan.md");
1237
- const tasksFile = path5__namespace.join(changeWorkspaceDir, "tasks.md");
1756
+ const changeId = path4__namespace.basename(changeWorkspaceDir);
1757
+ const specFile = path4__namespace.join(changeWorkspaceDir, "spec.md");
1758
+ const planFile = path4__namespace.join(changeWorkspaceDir, "plan.md");
1759
+ const tasksFile = path4__namespace.join(changeWorkspaceDir, "tasks.md");
1238
1760
  return {
1239
1761
  framework: "speckit",
1240
1762
  changeId,
1241
1763
  changeDir: changeWorkspaceDir,
1242
1764
  profile,
1243
- specFile: fs6__namespace.existsSync(specFile) ? specFile : void 0,
1244
- designFile: fs6__namespace.existsSync(planFile) ? planFile : void 0,
1245
- tasksFile: fs6__namespace.existsSync(tasksFile) ? tasksFile : void 0,
1765
+ specFile: fs7__namespace.existsSync(specFile) ? specFile : void 0,
1766
+ designFile: fs7__namespace.existsSync(planFile) ? planFile : void 0,
1767
+ tasksFile: fs7__namespace.existsSync(tasksFile) ? tasksFile : void 0,
1246
1768
  sidecarFile,
1247
1769
  handoff: handoff || void 0
1248
1770
  };
@@ -1250,18 +1772,18 @@ function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecar
1250
1772
  var SpecKitAdapter = class {
1251
1773
  framework = "speckit";
1252
1774
  getChangeWorkspaceDir(rootDir, changeId) {
1253
- const standardDir = path5__namespace.join(rootDir, "specs", changeId);
1254
- if (fs6__namespace.existsSync(standardDir)) return standardDir;
1255
- const activeChangeDir = path5__namespace.join(rootDir, "specs", "changes", "active", changeId);
1256
- if (fs6__namespace.existsSync(activeChangeDir)) return activeChangeDir;
1257
- const completedChangeDir = path5__namespace.join(rootDir, "specs", "changes", "completed", changeId);
1258
- if (fs6__namespace.existsSync(completedChangeDir)) return completedChangeDir;
1775
+ const standardDir = path4__namespace.join(rootDir, "specs", changeId);
1776
+ if (fs7__namespace.existsSync(standardDir)) return standardDir;
1777
+ const activeChangeDir = path4__namespace.join(rootDir, "specs", "changes", "active", changeId);
1778
+ if (fs7__namespace.existsSync(activeChangeDir)) return activeChangeDir;
1779
+ const completedChangeDir = path4__namespace.join(rootDir, "specs", "changes", "completed", changeId);
1780
+ if (fs7__namespace.existsSync(completedChangeDir)) return completedChangeDir;
1259
1781
  return standardDir;
1260
1782
  }
1261
1783
  depositSidecar(options) {
1262
1784
  const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
1263
- if (!fs6__namespace.existsSync(changeDir)) {
1264
- fs6__namespace.mkdirSync(changeDir, { recursive: true });
1785
+ if (!fs7__namespace.existsSync(changeDir)) {
1786
+ fs7__namespace.mkdirSync(changeDir, { recursive: true });
1265
1787
  }
1266
1788
  const handoff = { ...options.handoff };
1267
1789
  if (!handoff.id.startsWith("HOF-")) {
@@ -1274,20 +1796,20 @@ var SpecKitAdapter = class {
1274
1796
  handoff.createdAt = (/* @__PURE__ */ new Date()).toISOString();
1275
1797
  }
1276
1798
  const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
1277
- const filePath = path5__namespace.join(changeDir, filename);
1278
- const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml2__default.default.dump(handoff, { indent: 2, lineWidth: -1 });
1279
- fs6__namespace.writeFileSync(filePath, content, "utf-8");
1280
- const specifyMemoryDir = path5__namespace.join(options.rootDir, ".specify", "memory");
1281
- if (fs6__namespace.existsSync(path5__namespace.join(options.rootDir, ".specify"))) {
1282
- if (!fs6__namespace.existsSync(specifyMemoryDir)) {
1283
- fs6__namespace.mkdirSync(specifyMemoryDir, { recursive: true });
1799
+ const filePath = path4__namespace.join(changeDir, filename);
1800
+ const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3__default.default.dump(handoff, { indent: 2, lineWidth: -1 });
1801
+ fs7__namespace.writeFileSync(filePath, content, "utf-8");
1802
+ const specifyMemoryDir = path4__namespace.join(options.rootDir, ".specify", "memory");
1803
+ if (fs7__namespace.existsSync(path4__namespace.join(options.rootDir, ".specify"))) {
1804
+ if (!fs7__namespace.existsSync(specifyMemoryDir)) {
1805
+ fs7__namespace.mkdirSync(specifyMemoryDir, { recursive: true });
1284
1806
  }
1285
- fs6__namespace.writeFileSync(path5__namespace.join(specifyMemoryDir, `${options.changeId}.handoff.yaml`), content, "utf-8");
1807
+ fs7__namespace.writeFileSync(path4__namespace.join(specifyMemoryDir, `${options.changeId}.handoff.yaml`), content, "utf-8");
1286
1808
  }
1287
1809
  return filePath;
1288
1810
  }
1289
1811
  loadSidecar(changeWorkspaceDir) {
1290
- if (!fs6__namespace.existsSync(changeWorkspaceDir)) return null;
1812
+ if (!fs7__namespace.existsSync(changeWorkspaceDir)) return null;
1291
1813
  const candidateFiles = [
1292
1814
  "handoff.yaml",
1293
1815
  "handoff.yml",
@@ -1297,11 +1819,11 @@ var SpecKitAdapter = class {
1297
1819
  "handoff.json"
1298
1820
  ];
1299
1821
  for (const file of candidateFiles) {
1300
- const fullPath = path5__namespace.join(changeWorkspaceDir, file);
1301
- if (fs6__namespace.existsSync(fullPath)) {
1822
+ const fullPath = path4__namespace.join(changeWorkspaceDir, file);
1823
+ if (fs7__namespace.existsSync(fullPath)) {
1302
1824
  try {
1303
- const raw = fs6__namespace.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
1304
- const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml2__default.default.load(raw);
1825
+ const raw = fs7__namespace.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
1826
+ const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3__default.default.load(raw);
1305
1827
  if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
1306
1828
  return parsed;
1307
1829
  }
@@ -1312,7 +1834,7 @@ var SpecKitAdapter = class {
1312
1834
  return null;
1313
1835
  }
1314
1836
  validateWorkspace(changeWorkspaceDir) {
1315
- if (!fs6__namespace.existsSync(changeWorkspaceDir)) {
1837
+ if (!fs7__namespace.existsSync(changeWorkspaceDir)) {
1316
1838
  return {
1317
1839
  valid: false,
1318
1840
  errors: [`El directorio del cambio Spec Kit no existe: ${changeWorkspaceDir}`],
@@ -1321,7 +1843,7 @@ var SpecKitAdapter = class {
1321
1843
  }
1322
1844
  const errors = [];
1323
1845
  const warnings = [];
1324
- const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
1846
+ const specFile = path4__namespace.join(changeWorkspaceDir, "spec.md");
1325
1847
  const profile = parseChangeProfile2(specFile);
1326
1848
  validateSpecFileContent2(specFile, changeWorkspaceDir, errors);
1327
1849
  const { handoff, sidecarFile } = validateSpecKitStructure(profile, changeWorkspaceDir, this, errors, warnings);
@@ -1464,27 +1986,27 @@ ${frontmatter}---
1464
1986
  ${body}`;
1465
1987
  }
1466
1988
  function materializeCanonicalArtifact(rootDir, item, changeId, author, date) {
1467
- const targetPath = path5__namespace.join(rootDir, item.targetDir, `${item.id}.md`);
1468
- if (fs6__namespace.existsSync(targetPath)) return targetPath;
1469
- fs6__namespace.mkdirSync(path5__namespace.dirname(targetPath), { recursive: true });
1989
+ const targetPath = path4__namespace.join(rootDir, item.targetDir, `${item.id}.md`);
1990
+ if (fs7__namespace.existsSync(targetPath)) return targetPath;
1991
+ fs7__namespace.mkdirSync(path4__namespace.dirname(targetPath), { recursive: true });
1470
1992
  const initialContent = buildCanonicalInitialContent(item, changeId, author, date);
1471
- fs6__namespace.writeFileSync(targetPath, initialContent, "utf-8");
1993
+ fs7__namespace.writeFileSync(targetPath, initialContent, "utf-8");
1472
1994
  return targetPath;
1473
1995
  }
1474
1996
  function findChangeWorkspaceDir(rootDir, changeId) {
1475
- const activeDir = path5__namespace.join(rootDir, "specs", "changes", "active", changeId);
1476
- if (fs6__namespace.existsSync(activeDir)) return activeDir;
1477
- const completedDir = path5__namespace.join(rootDir, "specs", "changes", "completed", changeId);
1478
- if (fs6__namespace.existsSync(completedDir)) return completedDir;
1479
- const openspecActive = path5__namespace.join(rootDir, "openspec", "changes", changeId);
1480
- if (fs6__namespace.existsSync(openspecActive)) return openspecActive;
1997
+ const activeDir = path4__namespace.join(rootDir, "specs", "changes", "active", changeId);
1998
+ if (fs7__namespace.existsSync(activeDir)) return activeDir;
1999
+ const completedDir = path4__namespace.join(rootDir, "specs", "changes", "completed", changeId);
2000
+ if (fs7__namespace.existsSync(completedDir)) return completedDir;
2001
+ const openspecActive = path4__namespace.join(rootDir, "openspec", "changes", changeId);
2002
+ if (fs7__namespace.existsSync(openspecActive)) return openspecActive;
1481
2003
  return null;
1482
2004
  }
1483
2005
  function checkTasksExecution(changeDir, changeId) {
1484
- const tasksFile = path5__namespace.join(changeDir, "tasks.md");
2006
+ const tasksFile = path4__namespace.join(changeDir, "tasks.md");
1485
2007
  const allTasks = [];
1486
- if (fs6__namespace.existsSync(tasksFile)) {
1487
- allTasks.push(...parseTasksFromContent(fs6__namespace.readFileSync(tasksFile, "utf-8")));
2008
+ if (fs7__namespace.existsSync(tasksFile)) {
2009
+ allTasks.push(...parseTasksFromContent(fs7__namespace.readFileSync(tasksFile, "utf-8")));
1488
2010
  }
1489
2011
  const completedTasks = allTasks.filter((t) => t.status === "COMPLETED").map((t) => t.id);
1490
2012
  const pendingTasks = allTasks.filter((t) => t.status !== "COMPLETED").map((t) => t.id);
@@ -1501,9 +2023,9 @@ function checkTasksExecution(changeDir, changeId) {
1501
2023
  function resolveChangeHandoff(changeDir, changeId) {
1502
2024
  const sidecar = loadProductHandoffSidecar(changeDir);
1503
2025
  if (sidecar) return sidecar;
1504
- const proposalFile = path5__namespace.join(changeDir, "proposal.md");
1505
- if (!fs6__namespace.existsSync(proposalFile)) return null;
1506
- const propContent = fs6__namespace.readFileSync(proposalFile, "utf-8");
2026
+ const proposalFile = path4__namespace.join(changeDir, "proposal.md");
2027
+ if (!fs7__namespace.existsSync(proposalFile)) return null;
2028
+ const propContent = fs7__namespace.readFileSync(proposalFile, "utf-8");
1507
2029
  const { frontmatter } = parseFrontmatter(propContent);
1508
2030
  const citations = Array.isArray(frontmatter.citations) ? frontmatter.citations : [];
1509
2031
  const reqs = citations.map((c) => c.id || "").filter((id) => id.startsWith("FR-") || id.startsWith("QR-") || id.startsWith("CON-") || id.startsWith("SEC-REQ-"));
@@ -1517,9 +2039,9 @@ function resolveChangeHandoff(changeDir, changeId) {
1517
2039
  }
1518
2040
  function materializeNewCatalogArtifacts(changeDir, rootDir, changeId, author, date, errors) {
1519
2041
  const created = [];
1520
- const specFile = path5__namespace.join(changeDir, "spec.md");
1521
- if (!fs6__namespace.existsSync(specFile)) return created;
1522
- const specContent = fs6__namespace.readFileSync(specFile, "utf-8");
2042
+ const specFile = path4__namespace.join(changeDir, "spec.md");
2043
+ if (!fs7__namespace.existsSync(specFile)) return created;
2044
+ const specContent = fs7__namespace.readFileSync(specFile, "utf-8");
1523
2045
  const { newItems } = parseSpecCatalogScope(specContent);
1524
2046
  for (const item of newItems) {
1525
2047
  try {
@@ -1536,10 +2058,10 @@ function loadCanonicalArtifacts(rootDir) {
1536
2058
  const allMdFiles = walkMdFiles(rootDir);
1537
2059
  const canonicalArtifacts = /* @__PURE__ */ new Map();
1538
2060
  for (const file of allMdFiles) {
1539
- const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
2061
+ const relFile = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
1540
2062
  if (relFile.includes("specs/changes/") || relFile.includes("templates/")) continue;
1541
2063
  try {
1542
- const content = fs6__namespace.readFileSync(file, "utf-8");
2064
+ const content = fs7__namespace.readFileSync(file, "utf-8");
1543
2065
  const { frontmatter, body } = parseFrontmatter(content);
1544
2066
  if (frontmatter.id) canonicalArtifacts.set(String(frontmatter.id), { file, frontmatter, body });
1545
2067
  } catch {
@@ -1549,10 +2071,10 @@ function loadCanonicalArtifacts(rootDir) {
1549
2071
  }
1550
2072
  function writeUpdatedArtifact(file, frontmatter, body) {
1551
2073
  const yamlHeader = `---
1552
- ${yaml2__default.default.dump(frontmatter, { indent: 2, lineWidth: -1 }).trim()}
2074
+ ${yaml3__default.default.dump(frontmatter, { indent: 2, lineWidth: -1 }).trim()}
1553
2075
  ---
1554
2076
  `;
1555
- fs6__namespace.writeFileSync(file, yamlHeader + body, "utf-8");
2077
+ fs7__namespace.writeFileSync(file, yamlHeader + body, "utf-8");
1556
2078
  }
1557
2079
  function updateSingleCanonicalRequirement(canonical, completedTasks, changeId, date, author) {
1558
2080
  const fm = canonical.frontmatter;
@@ -1664,28 +2186,28 @@ function updateArchitectureComponents(citedComponents, canonicalArtifacts, targe
1664
2186
  return updated;
1665
2187
  }
1666
2188
  function moveToCompletedFolder(changeDir, changeId, rootDir) {
1667
- const completedParent = path5__namespace.join(rootDir, "specs", "changes", "completed");
1668
- if (!fs6__namespace.existsSync(completedParent)) fs6__namespace.mkdirSync(completedParent, { recursive: true });
1669
- const destDir = path5__namespace.join(completedParent, changeId);
1670
- if (fs6__namespace.existsSync(destDir)) fs6__namespace.rmSync(destDir, { recursive: true, force: true });
1671
- fs6__namespace.renameSync(changeDir, destDir);
1672
- const destProposal = path5__namespace.join(destDir, "proposal.md");
1673
- if (fs6__namespace.existsSync(destProposal)) {
1674
- const { frontmatter, body } = parseFrontmatter(fs6__namespace.readFileSync(destProposal, "utf-8"));
2189
+ const completedParent = path4__namespace.join(rootDir, "specs", "changes", "completed");
2190
+ if (!fs7__namespace.existsSync(completedParent)) fs7__namespace.mkdirSync(completedParent, { recursive: true });
2191
+ const destDir = path4__namespace.join(completedParent, changeId);
2192
+ if (fs7__namespace.existsSync(destDir)) fs7__namespace.rmSync(destDir, { recursive: true, force: true });
2193
+ fs7__namespace.renameSync(changeDir, destDir);
2194
+ const destProposal = path4__namespace.join(destDir, "proposal.md");
2195
+ if (fs7__namespace.existsSync(destProposal)) {
2196
+ const { frontmatter, body } = parseFrontmatter(fs7__namespace.readFileSync(destProposal, "utf-8"));
1675
2197
  frontmatter.status = "applied";
1676
2198
  writeUpdatedArtifact(destProposal, frontmatter, body);
1677
2199
  }
1678
2200
  return destDir;
1679
2201
  }
1680
2202
  function archiveChangeWorkspace(changeDir, changeId, rootDir, autoArchive, completedAll) {
1681
- const isActiveFolder = changeDir.includes("specs" + path5__namespace.sep + "changes" + path5__namespace.sep + "active");
2203
+ const isActiveFolder = changeDir.includes("specs" + path4__namespace.sep + "changes" + path4__namespace.sep + "active");
1682
2204
  if (completedAll && autoArchive && isActiveFolder) {
1683
2205
  const destDir = moveToCompletedFolder(changeDir, changeId, rootDir);
1684
2206
  return { archived: true, archivedPath: destDir };
1685
2207
  }
1686
- const proposalFile = path5__namespace.join(changeDir, "proposal.md");
1687
- if (fs6__namespace.existsSync(proposalFile)) {
1688
- const { frontmatter, body } = parseFrontmatter(fs6__namespace.readFileSync(proposalFile, "utf-8"));
2208
+ const proposalFile = path4__namespace.join(changeDir, "proposal.md");
2209
+ if (fs7__namespace.existsSync(proposalFile)) {
2210
+ const { frontmatter, body } = parseFrontmatter(fs7__namespace.readFileSync(proposalFile, "utf-8"));
1689
2211
  if (completedAll) frontmatter.status = "completed";
1690
2212
  writeUpdatedArtifact(proposalFile, frontmatter, body);
1691
2213
  }
@@ -1779,8 +2301,8 @@ function resolveAuditStatus(integratedCount, targetCount, gapsCount) {
1779
2301
  function auditChangeEntry(changeDir, changeName, canonicalMap) {
1780
2302
  const handoff = loadProductHandoffSidecar(changeDir);
1781
2303
  if (!handoff) return null;
1782
- const tasksFile = path5__namespace.join(changeDir, "tasks.md");
1783
- const tasks = fs6__namespace.existsSync(tasksFile) ? parseTasksFromContent(fs6__namespace.readFileSync(tasksFile, "utf-8")) : [];
2304
+ const tasksFile = path4__namespace.join(changeDir, "tasks.md");
2305
+ const tasks = fs7__namespace.existsSync(tasksFile) ? parseTasksFromContent(fs7__namespace.readFileSync(tasksFile, "utf-8")) : [];
1784
2306
  const completed = tasks.filter((t) => t.status === "COMPLETED");
1785
2307
  const targetReqs = handoff.subgraph?.requirements || [];
1786
2308
  const { integrated, missing, gaps } = auditChangeRequirements(targetReqs, canonicalMap);
@@ -1796,11 +2318,11 @@ function auditChangeEntry(changeDir, changeName, canonicalMap) {
1796
2318
  };
1797
2319
  }
1798
2320
  function collectParentDirAudits(parent, canonicalMap, audits, errors) {
1799
- if (!fs6__namespace.existsSync(parent)) return;
1800
- const entries = fs6__namespace.readdirSync(parent, { withFileTypes: true });
2321
+ if (!fs7__namespace.existsSync(parent)) return;
2322
+ const entries = fs7__namespace.readdirSync(parent, { withFileTypes: true });
1801
2323
  for (const e of entries) {
1802
2324
  if (!e.isDirectory()) continue;
1803
- const audit = auditChangeEntry(path5__namespace.join(parent, e.name), e.name, canonicalMap);
2325
+ const audit = auditChangeEntry(path4__namespace.join(parent, e.name), e.name, canonicalMap);
1804
2326
  if (audit) {
1805
2327
  if (audit.gaps.length > 0) errors.push(`[${e.name}] Desalineaci\xF3n: ${audit.gaps.join("; ")}`);
1806
2328
  audits.push(audit);
@@ -1812,8 +2334,8 @@ function verifySddIntegration(options = {}) {
1812
2334
  const audits = [];
1813
2335
  const errors = [];
1814
2336
  const searchDirs = [
1815
- path5__namespace.join(rootDir, "specs", "changes", "completed"),
1816
- path5__namespace.join(rootDir, "specs", "changes", "active")
2337
+ path4__namespace.join(rootDir, "specs", "changes", "completed"),
2338
+ path4__namespace.join(rootDir, "specs", "changes", "active")
1817
2339
  ];
1818
2340
  const canonicalMap = loadCanonicalArtifacts(rootDir);
1819
2341
  for (const parent of searchDirs) {
@@ -1861,10 +2383,10 @@ function resolveMatchFromMetadata(activeFolders, options) {
1861
2383
  return {};
1862
2384
  }
1863
2385
  function validateActiveDirectory(activeParent) {
1864
- if (!fs6__namespace.existsSync(activeParent)) {
2386
+ if (!fs7__namespace.existsSync(activeParent)) {
1865
2387
  return { ok: false, folders: [], error: `No existe el directorio de cambios activos en '${activeParent}'.` };
1866
2388
  }
1867
- const entries = fs6__namespace.readdirSync(activeParent, { withFileTypes: true });
2389
+ const entries = fs7__namespace.readdirSync(activeParent, { withFileTypes: true });
1868
2390
  const activeFolders = entries.filter((e) => e.isDirectory()).map((e) => e.name);
1869
2391
  if (activeFolders.length === 0) {
1870
2392
  return { ok: false, folders: [], error: "No se encontraron carpetas de cambios activos en specs/changes/active/." };
@@ -1896,7 +2418,7 @@ function evaluateTasksProgress(changeId, tasks) {
1896
2418
  }
1897
2419
  function detectActiveChangeForIntegration(options = {}) {
1898
2420
  const rootDir = options.rootDir || process.cwd();
1899
- const activeParent = path5__namespace.join(rootDir, "specs", "changes", "active");
2421
+ const activeParent = path4__namespace.join(rootDir, "specs", "changes", "active");
1900
2422
  const dirCheck = validateActiveDirectory(activeParent);
1901
2423
  if (!dirCheck.ok) {
1902
2424
  return {
@@ -1911,9 +2433,9 @@ function detectActiveChangeForIntegration(options = {}) {
1911
2433
  if (!match.changeId) {
1912
2434
  return buildUnmatchedActiveResult(dirCheck.folders, options);
1913
2435
  }
1914
- const changeDir = path5__namespace.join(activeParent, match.changeId);
1915
- const tasksFile = path5__namespace.join(changeDir, "tasks.md");
1916
- const tasks = fs6__namespace.existsSync(tasksFile) ? parseTasksFromContent(fs6__namespace.readFileSync(tasksFile, "utf-8")) : [];
2436
+ const changeDir = path4__namespace.join(activeParent, match.changeId);
2437
+ const tasksFile = path4__namespace.join(changeDir, "tasks.md");
2438
+ const tasks = fs7__namespace.existsSync(tasksFile) ? parseTasksFromContent(fs7__namespace.readFileSync(tasksFile, "utf-8")) : [];
1917
2439
  const progress = evaluateTasksProgress(match.changeId, tasks);
1918
2440
  const reasons = match.reason ? [match.reason] : [];
1919
2441
  if (progress.error) reasons.push(progress.error);
@@ -2044,9 +2566,9 @@ function isPdacDeliveryDoc(relFile) {
2044
2566
  }
2045
2567
  function processPdacFile(file, rootDir, baselineMap, allCitations) {
2046
2568
  try {
2047
- const content = fs6__namespace.readFileSync(file, "utf-8");
2569
+ const content = fs7__namespace.readFileSync(file, "utf-8");
2048
2570
  const { id, type, title } = parseFrontmatterId(content);
2049
- const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
2571
+ const relFile = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
2050
2572
  if (id) {
2051
2573
  const citations = extractCitations(content, relFile);
2052
2574
  baselineMap.set(id, {
@@ -2174,22 +2696,22 @@ function updateCitationsInContent(content, baselineMap, sourceFile, details) {
2174
2696
  }
2175
2697
  function collectCandidateFilesForSync(rootDir, targetPath) {
2176
2698
  if (targetPath) {
2177
- const fullTarget = path5__namespace.isAbsolute(targetPath) ? targetPath : path5__namespace.join(rootDir, targetPath);
2178
- if (!fs6__namespace.existsSync(fullTarget)) return [];
2179
- return fs6__namespace.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
2699
+ const fullTarget = path4__namespace.isAbsolute(targetPath) ? targetPath : path4__namespace.join(rootDir, targetPath);
2700
+ if (!fs7__namespace.existsSync(fullTarget)) return [];
2701
+ return fs7__namespace.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
2180
2702
  }
2181
2703
  return walkMdFiles(rootDir).filter((f) => {
2182
- const relFile = path5__namespace.relative(rootDir, f).replace(/\\/g, "/");
2704
+ const relFile = path4__namespace.relative(rootDir, f).replace(/\\/g, "/");
2183
2705
  return isPdacDeliveryDoc(relFile);
2184
2706
  });
2185
2707
  }
2186
2708
  function syncSingleFile(file, rootDir, baselineMap, details) {
2187
2709
  try {
2188
- const content = fs6__namespace.readFileSync(file, "utf-8");
2189
- const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
2710
+ const content = fs7__namespace.readFileSync(file, "utf-8");
2711
+ const relFile = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
2190
2712
  const { updatedContent, changed } = updateCitationsInContent(content, baselineMap, relFile, details);
2191
2713
  if (changed) {
2192
- fs6__namespace.writeFileSync(file, updatedContent, "utf-8");
2714
+ fs7__namespace.writeFileSync(file, updatedContent, "utf-8");
2193
2715
  return relFile;
2194
2716
  }
2195
2717
  } catch {
@@ -2309,7 +2831,7 @@ function extractFrontmatter(content) {
2309
2831
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
2310
2832
  if (!match) return null;
2311
2833
  try {
2312
- const parsed = yaml2__default.default.load(match[1]);
2834
+ const parsed = yaml3__default.default.load(match[1]);
2313
2835
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2314
2836
  return parsed;
2315
2837
  }
@@ -2345,10 +2867,10 @@ var EXCLUDED_SCHEMA_DIRS = /* @__PURE__ */ new Set([
2345
2867
  ]);
2346
2868
  function walkHandoffSidecars(dir, fileList = []) {
2347
2869
  try {
2348
- if (!fs6__namespace.existsSync(dir)) return fileList;
2349
- for (const file of fs6__namespace.readdirSync(dir)) {
2350
- const fullPath = path5__namespace.join(dir, file);
2351
- if (fs6__namespace.statSync(fullPath).isDirectory()) {
2870
+ if (!fs7__namespace.existsSync(dir)) return fileList;
2871
+ for (const file of fs7__namespace.readdirSync(dir)) {
2872
+ const fullPath = path4__namespace.join(dir, file);
2873
+ if (fs7__namespace.statSync(fullPath).isDirectory()) {
2352
2874
  if (!EXCLUDED_SCHEMA_DIRS.has(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
2353
2875
  walkHandoffSidecars(fullPath, fileList);
2354
2876
  }
@@ -2363,7 +2885,7 @@ function walkHandoffSidecars(dir, fileList = []) {
2363
2885
  function extractArtifactData(filePath, content) {
2364
2886
  if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
2365
2887
  try {
2366
- const parsed = yaml2__default.default.load(content);
2888
+ const parsed = yaml3__default.default.load(content);
2367
2889
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
2368
2890
  } catch {
2369
2891
  return null;
@@ -2512,10 +3034,10 @@ function validateAgainstSchema(value, schema, propPath = "") {
2512
3034
  return validateTypeSpecificRules(value, schema, propPath, currentProp);
2513
3035
  }
2514
3036
  function findSchemaFile(relSchemaPath, rootDir) {
2515
- const fullPath = path5__namespace.join(rootDir, relSchemaPath);
2516
- if (fs6__namespace.existsSync(fullPath)) return fullPath;
2517
- const fallbackPath = path5__namespace.join(process.cwd(), relSchemaPath);
2518
- if (fs6__namespace.existsSync(fallbackPath)) return fallbackPath;
3037
+ const fullPath = path4__namespace.join(rootDir, relSchemaPath);
3038
+ if (fs7__namespace.existsSync(fullPath)) return fullPath;
3039
+ const fallbackPath = path4__namespace.join(process.cwd(), relSchemaPath);
3040
+ if (fs7__namespace.existsSync(fallbackPath)) return fallbackPath;
2519
3041
  return null;
2520
3042
  }
2521
3043
  function resolveSchemaForArtifact(frontmatter, rootDir) {
@@ -2525,7 +3047,7 @@ function resolveSchemaForArtifact(frontmatter, rootDir) {
2525
3047
  const fullPath = findSchemaFile(relSchemaPath, rootDir);
2526
3048
  if (!fullPath) return null;
2527
3049
  try {
2528
- const raw = fs6__namespace.readFileSync(fullPath, "utf-8");
3050
+ const raw = fs7__namespace.readFileSync(fullPath, "utf-8");
2529
3051
  return { schemaPath: relSchemaPath, schema: JSON.parse(raw) };
2530
3052
  } catch {
2531
3053
  return null;
@@ -2533,15 +3055,15 @@ function resolveSchemaForArtifact(frontmatter, rootDir) {
2533
3055
  }
2534
3056
  function loadSchemaFromPathOrType(schemaOrType, rootDir) {
2535
3057
  if (schemaOrType.endsWith(".json")) {
2536
- const fullPath = path5__namespace.isAbsolute(schemaOrType) ? schemaOrType : findSchemaFile(schemaOrType, rootDir);
2537
- if (fullPath && fs6__namespace.existsSync(fullPath)) {
2538
- return { schema: JSON.parse(fs6__namespace.readFileSync(fullPath, "utf-8")), schemaId: schemaOrType };
3058
+ const fullPath = path4__namespace.isAbsolute(schemaOrType) ? schemaOrType : findSchemaFile(schemaOrType, rootDir);
3059
+ if (fullPath && fs7__namespace.existsSync(fullPath)) {
3060
+ return { schema: JSON.parse(fs7__namespace.readFileSync(fullPath, "utf-8")), schemaId: schemaOrType };
2539
3061
  }
2540
3062
  } else if (TYPE_TO_SCHEMA_MAP[schemaOrType]) {
2541
3063
  const relPath = TYPE_TO_SCHEMA_MAP[schemaOrType];
2542
3064
  const fullPath = findSchemaFile(relPath, rootDir);
2543
- if (fullPath && fs6__namespace.existsSync(fullPath)) {
2544
- return { schema: JSON.parse(fs6__namespace.readFileSync(fullPath, "utf-8")), schemaId: relPath };
3065
+ if (fullPath && fs7__namespace.existsSync(fullPath)) {
3066
+ return { schema: JSON.parse(fs7__namespace.readFileSync(fullPath, "utf-8")), schemaId: relPath };
2545
3067
  }
2546
3068
  }
2547
3069
  return { schema: null };
@@ -2579,9 +3101,9 @@ function collectCandidateSchemaFiles(options, rootDir) {
2579
3101
  if (!options.targetPath) {
2580
3102
  return [...walkMdFiles(rootDir), ...walkHandoffSidecars(rootDir)];
2581
3103
  }
2582
- const target = path5__namespace.isAbsolute(options.targetPath) ? options.targetPath : path5__namespace.join(rootDir, options.targetPath);
2583
- if (!fs6__namespace.existsSync(target)) return [];
2584
- if (fs6__namespace.statSync(target).isDirectory()) {
3104
+ const target = path4__namespace.isAbsolute(options.targetPath) ? options.targetPath : path4__namespace.join(rootDir, options.targetPath);
3105
+ if (!fs7__namespace.existsSync(target)) return [];
3106
+ if (fs7__namespace.statSync(target).isDirectory()) {
2585
3107
  return [...walkMdFiles(target), ...walkHandoffSidecars(target)];
2586
3108
  }
2587
3109
  if (target.endsWith(".md") || target.endsWith(".yaml") || target.endsWith(".yml")) {
@@ -2632,7 +3154,7 @@ function buildEvaluationFailure(relPath, err) {
2632
3154
  }
2633
3155
  function evaluateArtifactFile(file, relPath, rootDir) {
2634
3156
  try {
2635
- const content = fs6__namespace.readFileSync(file, "utf-8");
3157
+ const content = fs7__namespace.readFileSync(file, "utf-8");
2636
3158
  const frontmatter = extractArtifactData(file, content);
2637
3159
  if (!frontmatter || !frontmatter.id) return null;
2638
3160
  const schemaInfo = resolveSchemaForArtifact(frontmatter, rootDir);
@@ -2667,7 +3189,7 @@ function verifyArtifactsSchemas(options = {}) {
2667
3189
  let validCount = 0;
2668
3190
  let totalEvaluated = 0;
2669
3191
  for (const file of filesToScan) {
2670
- const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
3192
+ const relPath = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
2671
3193
  if (isIgnoredArtifactPath(relPath, Boolean(options.targetPath))) continue;
2672
3194
  const evaluation = evaluateArtifactFile(file, relPath, rootDir);
2673
3195
  if (!evaluation) continue;
@@ -2686,10 +3208,10 @@ function slugify(text) {
2686
3208
  return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2687
3209
  }
2688
3210
  function extractDirMaxNum(dir) {
2689
- if (!fs6__namespace.existsSync(dir)) return 0;
3211
+ if (!fs7__namespace.existsSync(dir)) return 0;
2690
3212
  let max = 0;
2691
3213
  try {
2692
- const entries = fs6__namespace.readdirSync(dir, { withFileTypes: true });
3214
+ const entries = fs7__namespace.readdirSync(dir, { withFileTypes: true });
2693
3215
  for (const e of entries) {
2694
3216
  if (!e.isDirectory()) continue;
2695
3217
  const match = e.name.match(/^(?:chg|CHG|patch|PATCH)-(\d+)/i);
@@ -2706,10 +3228,10 @@ function extractDirMaxNum(dir) {
2706
3228
  }
2707
3229
  function getNextCorrelativeNumber(rootDir) {
2708
3230
  const candidateDirs = [
2709
- path5__namespace.join(rootDir, "specs", "changes", "active"),
2710
- path5__namespace.join(rootDir, "specs", "changes", "completed"),
2711
- path5__namespace.join(rootDir, "specs"),
2712
- path5__namespace.join(rootDir, "openspec", "changes")
3231
+ path4__namespace.join(rootDir, "specs", "changes", "active"),
3232
+ path4__namespace.join(rootDir, "specs", "changes", "completed"),
3233
+ path4__namespace.join(rootDir, "specs"),
3234
+ path4__namespace.join(rootDir, "openspec", "changes")
2713
3235
  ];
2714
3236
  let maxNum = 0;
2715
3237
  for (const dir of candidateDirs) {
@@ -2739,13 +3261,13 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
2739
3261
  };
2740
3262
  }
2741
3263
  function ensureEmptyChangeDir(changeDir) {
2742
- if (fs6__namespace.existsSync(changeDir)) {
2743
- const existing = fs6__namespace.readdirSync(changeDir);
3264
+ if (fs7__namespace.existsSync(changeDir)) {
3265
+ const existing = fs7__namespace.readdirSync(changeDir);
2744
3266
  if (existing.length > 0) {
2745
3267
  return `El directorio del cambio '${changeDir}' ya existe y no est\xE1 vac\xEDo.`;
2746
3268
  }
2747
3269
  } else {
2748
- fs6__namespace.mkdirSync(changeDir, { recursive: true });
3270
+ fs7__namespace.mkdirSync(changeDir, { recursive: true });
2749
3271
  }
2750
3272
  return null;
2751
3273
  }
@@ -2754,7 +3276,7 @@ function scanCanonicalArtifacts(rootDir) {
2754
3276
  const artifactMap = /* @__PURE__ */ new Map();
2755
3277
  for (const file of allMdFiles) {
2756
3278
  try {
2757
- const content = fs6__namespace.readFileSync(file, "utf-8");
3279
+ const content = fs7__namespace.readFileSync(file, "utf-8");
2758
3280
  const { frontmatter } = parseFrontmatter(content);
2759
3281
  if (frontmatter?.id) {
2760
3282
  const id = String(frontmatter.id);
@@ -2782,7 +3304,7 @@ function parseInputFromIds(rawFrom) {
2782
3304
  }
2783
3305
  function renderCitationsYamlBlock(citedArtifacts) {
2784
3306
  if (citedArtifacts.length === 0) return "";
2785
- return yaml2__default.default.dump(
3307
+ return yaml3__default.default.dump(
2786
3308
  citedArtifacts.map((c) => ({ id: c.id, digest: c.digest, comment: c.comment })),
2787
3309
  { indent: 2, lineWidth: -1 }
2788
3310
  ).trim().split("\n").map((line) => ` ${line}`).join("\n");
@@ -2817,16 +3339,39 @@ El sistema DEBE implementar y soportar la capacidad de {{NAME}}.
2817
3339
  ## 2. Criterios de Aceptaci\xF3n en Formato Gherkin (Cucumber)
2818
3340
 
2819
3341
  \`\`\`gherkin
2820
- @{{REQ_ID}} @automated
3342
+ @{{REQ_ID}} @automated @regression
2821
3343
  Feature: {{NAME}}
2822
3344
  As a user or client system
2823
3345
  I want to execute the capability for {{NAME}}
2824
3346
  So that the expected outcome and value are achieved
2825
3347
 
3348
+ @nominal
2826
3349
  Scenario: Nominal successful flow
2827
3350
  Given the system is in an operational state
2828
3351
  When the operation for "{{NAME}}" is executed
2829
3352
  Then the operation completes successfully
3353
+
3354
+ @boundary
3355
+ Scenario Outline: Boundary limits validation
3356
+ Given the system receives boundary input "<boundary_input>"
3357
+ When the operation for "{{NAME}}" is evaluated at range limits
3358
+ Then the system responds with expected boundary status "<expected_status>"
3359
+
3360
+ Examples:
3361
+ | boundary_input | expected_status |
3362
+ | min_bound | 200 |
3363
+ | max_bound | 200 |
3364
+
3365
+ @invalid
3366
+ Scenario Outline: Out-of-range and invalid payload rejection
3367
+ Given the system receives invalid or out-of-range input "<invalid_input>"
3368
+ When the operation for "{{NAME}}" is invoked
3369
+ Then the system rejects the request with error "<error_code>"
3370
+
3371
+ Examples:
3372
+ | invalid_input | error_code |
3373
+ | empty_payload | ERR_INVALID_ARGS |
3374
+ | overflow_val | ERR_OUT_OF_RANGE |
2830
3375
  \`\`\`
2831
3376
 
2832
3377
  ---
@@ -2845,12 +3390,33 @@ Definici\xF3n de artefacto citado en {{CANONICAL_ID}}.
2845
3390
  ## 2. Criterios de Aceptaci\xF3n en Formato Gherkin (Cucumber)
2846
3391
 
2847
3392
  \`\`\`gherkin
2848
- @{{ID}} @automated
3393
+ @{{ID}} @automated @regression
2849
3394
  Feature: {{NAME}}
3395
+ @nominal
2850
3396
  Scenario: Expected behavior verification
2851
3397
  Given the system is initialized for "{{NAME}}"
2852
3398
  When the request associated with {{ID}} is processed
2853
3399
  Then the response satisfies acceptance criteria
3400
+
3401
+ @boundary
3402
+ Scenario Outline: Boundary limits verification
3403
+ Given boundary input "<input>" for {{ID}}
3404
+ When the request is processed
3405
+ Then the response status is "<status>"
3406
+
3407
+ Examples:
3408
+ | input | status |
3409
+ | min | 200 |
3410
+
3411
+ @invalid
3412
+ Scenario Outline: Invalid input rejection
3413
+ Given invalid input "<input>" for {{ID}}
3414
+ When the request is processed
3415
+ Then the request is rejected with "<error>"
3416
+
3417
+ Examples:
3418
+ | input | error |
3419
+ | null | ERR_INVALID_ARGS |
2854
3420
  \`\`\`
2855
3421
  `;
2856
3422
  var SPEC_BODY = `# Delivery Specification: {{CANONICAL_ID}}
@@ -2864,10 +3430,33 @@ Feature: {{NAME}}
2864
3430
  I want to process the operation for {{NAME}}
2865
3431
  So that delivery criteria are satisfied
2866
3432
 
3433
+ @nominal
2867
3434
  Scenario: Nominal successful flow
2868
3435
  Given the system is in a nominal operational state
2869
3436
  When the operation for "{{NAME}}" is processed
2870
3437
  Then the operation completes successfully satisfying acceptance criteria
3438
+
3439
+ @boundary
3440
+ Scenario Outline: Boundary conditions verification
3441
+ Given the system is configured with boundary value "<value>"
3442
+ When the operation for "{{NAME}}" is processed
3443
+ Then the boundary result matches "<expected>"
3444
+
3445
+ Examples:
3446
+ | value | expected |
3447
+ | min | accepted |
3448
+ | max | accepted |
3449
+
3450
+ @invalid
3451
+ Scenario Outline: Out-of-range and invalid input handling
3452
+ Given an invalid or malformed payload "<payload>"
3453
+ When the operation for "{{NAME}}" is attempted
3454
+ Then the system rejects the operation with "<error>"
3455
+
3456
+ Examples:
3457
+ | payload | error |
3458
+ | null | ERR_INVALID_DATA |
3459
+ | overflow | ERR_OUT_OF_RANGE |
2871
3460
  \`\`\`
2872
3461
 
2873
3462
  ---
@@ -2972,6 +3561,7 @@ derives-from:
2972
3561
  - {{UC_ID}}
2973
3562
  verifiable-by: cucumber-bdd
2974
3563
  acceptance-format: gherkin
3564
+ enforce-bdd-variants: true
2975
3565
  cucumber-tags:
2976
3566
  - "@{{REQ_ID}}"
2977
3567
  - "@automated"
@@ -2989,6 +3579,7 @@ schema-version: "1.0"
2989
3579
  category: functional
2990
3580
  verifiable-by: cucumber-bdd
2991
3581
  acceptance-format: gherkin
3582
+ enforce-bdd-variants: true
2992
3583
  cucumber-tags:
2993
3584
  - "@{{ID}}"
2994
3585
  - "@automated"
@@ -3064,7 +3655,7 @@ function buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors)
3064
3655
  changeId: slug,
3065
3656
  canonicalId,
3066
3657
  profile: "patch",
3067
- changeDir: path5__namespace.dirname(specPath),
3658
+ changeDir: path4__namespace.dirname(specPath),
3068
3659
  createdFiles: [specPath],
3069
3660
  citedArtifacts,
3070
3661
  errors
@@ -3075,10 +3666,10 @@ function scaffoldPatchChange(rootDir, changeDir, slug, canonicalId, name, inputF
3075
3666
  const citationsSection = citedArtifacts.length > 0 ? `citations:
3076
3667
  ${renderCitationsYamlBlock(citedArtifacts)}
3077
3668
  ` : "";
3078
- const specPath = path5__namespace.join(changeDir, "spec.md");
3669
+ const specPath = path4__namespace.join(changeDir, "spec.md");
3079
3670
  const specFmObj = buildPatchFmObj(canonicalId);
3080
3671
  const specContent = buildPatchSpecContent(canonicalId, name, citationsSection);
3081
- fs6__namespace.writeFileSync(specPath, specContent, "utf-8");
3672
+ fs7__namespace.writeFileSync(specPath, specContent, "utf-8");
3082
3673
  const errors = validatePatchArtifacts(rootDir, changeDir, specContent, specFmObj, adapter);
3083
3674
  return buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors);
3084
3675
  }
@@ -3092,26 +3683,26 @@ function createGreenfieldRequirement(rootDir, name, padNum, canonicalId, author,
3092
3683
  const nameSlugUpper = slugify(name).toUpperCase();
3093
3684
  const reqId = `FR-${padNum}-${nameSlugUpper}-001`;
3094
3685
  const ucId = `UC-${padNum}-${nameSlugUpper}`;
3095
- const productDir = path5__namespace.join(rootDir, "specs", "product");
3096
- if (!fs6__namespace.existsSync(productDir)) fs6__namespace.mkdirSync(productDir, { recursive: true });
3097
- const reqFilePath = path5__namespace.join(productDir, `${reqId}.md`);
3098
- if (fs6__namespace.existsSync(reqFilePath)) return null;
3686
+ const productDir = path4__namespace.join(rootDir, "specs", "product");
3687
+ if (!fs7__namespace.existsSync(productDir)) fs7__namespace.mkdirSync(productDir, { recursive: true });
3688
+ const reqFilePath = path4__namespace.join(productDir, `${reqId}.md`);
3689
+ if (fs7__namespace.existsSync(reqFilePath)) return null;
3099
3690
  const reqContent = buildGreenfieldContent(reqId, ucId, name, canonicalId, author, dateStr);
3100
- fs6__namespace.writeFileSync(reqFilePath, reqContent, "utf-8");
3691
+ fs7__namespace.writeFileSync(reqFilePath, reqContent, "utf-8");
3101
3692
  createdFiles.push(reqFilePath);
3102
3693
  const digest = computeCanonicalSha256(reqContent);
3103
3694
  return { reqId, ucId, reqFilePath, digest };
3104
3695
  }
3105
3696
  function createMissingDraftRequirement(rootDir, name, id, canonicalId, createdFiles) {
3106
- const productDir = path5__namespace.join(rootDir, "specs", "product");
3107
- if (!fs6__namespace.existsSync(productDir)) fs6__namespace.mkdirSync(productDir, { recursive: true });
3108
- const reqFilePath = path5__namespace.join(productDir, `${id}.md`);
3697
+ const productDir = path4__namespace.join(rootDir, "specs", "product");
3698
+ if (!fs7__namespace.existsSync(productDir)) fs7__namespace.mkdirSync(productDir, { recursive: true });
3699
+ const reqFilePath = path4__namespace.join(productDir, `${id}.md`);
3109
3700
  const reqType = id.startsWith("UC-") ? "use-case" : id.startsWith("SEC-REQ-") ? "security-requirement" : "requirement";
3110
3701
  const header = MISSING_DRAFT_HEADER.replaceAll("{{ID}}", id).replaceAll("{{TYPE}}", reqType).replaceAll("{{NAME}}", name);
3111
3702
  const body = MISSING_DRAFT_BODY.replaceAll("{{ID}}", id).replaceAll("{{NAME}}", name).replaceAll("{{CANONICAL_ID}}", canonicalId);
3112
3703
  const reqContent = `${header}
3113
3704
  ${body}`;
3114
- fs6__namespace.writeFileSync(reqFilePath, reqContent, "utf-8");
3705
+ fs7__namespace.writeFileSync(reqFilePath, reqContent, "utf-8");
3115
3706
  createdFiles.push(reqFilePath);
3116
3707
  const digest = computeCanonicalSha256(reqContent);
3117
3708
  return { reqFilePath, digest };
@@ -3199,7 +3790,7 @@ function collectChangeCitations(rootDir, name, padNum, canonicalId, author, date
3199
3790
  return col;
3200
3791
  }
3201
3792
  function renderProposalFile(changeDir, canonicalId, name, author, citationsYamlBlock) {
3202
- const proposalPath = path5__namespace.join(changeDir, "proposal.md");
3793
+ const proposalPath = path4__namespace.join(changeDir, "proposal.md");
3203
3794
  const citationsSection = citationsYamlBlock ? `citations:
3204
3795
  ${citationsYamlBlock}
3205
3796
  ` : "";
@@ -3219,11 +3810,11 @@ ${name}
3219
3810
  ## 2. Dependencias Externas Evaluadas
3220
3811
  - Lista de dependencias nuevas a incorporar y verificaci\xF3n de licencia seg\xFAn \`license-policy.yaml\`.
3221
3812
  `;
3222
- fs6__namespace.writeFileSync(proposalPath, proposalContent, "utf-8");
3813
+ fs7__namespace.writeFileSync(proposalPath, proposalContent, "utf-8");
3223
3814
  return proposalPath;
3224
3815
  }
3225
3816
  function renderSpecFile(changeDir, canonicalId, name, profile) {
3226
- const specPath = path5__namespace.join(changeDir, "spec.md");
3817
+ const specPath = path4__namespace.join(changeDir, "spec.md");
3227
3818
  const specFmObj = {
3228
3819
  id: `SPEC-${canonicalId}`,
3229
3820
  type: "delivery-spec",
@@ -3244,12 +3835,12 @@ cucumber-tags:
3244
3835
  ---
3245
3836
  `;
3246
3837
  const body = SPEC_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name);
3247
- fs6__namespace.writeFileSync(specPath, `${header}
3838
+ fs7__namespace.writeFileSync(specPath, `${header}
3248
3839
  ${body}`, "utf-8");
3249
3840
  return { specPath, specFmObj };
3250
3841
  }
3251
3842
  function renderDesignFile(changeDir, canonicalId, name, author, dateStr, citationsYamlBlock) {
3252
- const designPath = path5__namespace.join(changeDir, "design.md");
3843
+ const designPath = path4__namespace.join(changeDir, "design.md");
3253
3844
  const citationsSection = citationsYamlBlock ? `citations:
3254
3845
  ${citationsYamlBlock}
3255
3846
  ` : "";
@@ -3264,14 +3855,20 @@ handoff: "HOF-${canonicalId}"
3264
3855
  architecture-component: "CMP-CORE"
3265
3856
  enclave: "SEC-ENC-DMZ"
3266
3857
  status: draft
3858
+ contracts: []
3267
3859
  ${citationsSection}---
3268
3860
  `;
3269
3861
  const body = DESIGN_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
3270
- fs6__namespace.writeFileSync(designPath, `${header}
3862
+ fs7__namespace.writeFileSync(designPath, `${header}
3271
3863
  ${body}`, "utf-8");
3272
3864
  return designPath;
3273
3865
  }
3274
- function buildTasksFrontmatter(canonicalId, name) {
3866
+ function buildTasksFrontmatter(canonicalId, name, reqIds) {
3867
+ const tasks = DEFAULT_TASKS_ARRAY.map((t) => ({
3868
+ ...t,
3869
+ "implements-components": ["CMP-CORE"],
3870
+ ...reqIds.length > 0 ? { "satisfies-requirements": reqIds } : {}
3871
+ }));
3275
3872
  return {
3276
3873
  id: `TSK-PLAN-${canonicalId}`,
3277
3874
  type: "task-plan",
@@ -3281,20 +3878,20 @@ function buildTasksFrontmatter(canonicalId, name) {
3281
3878
  "schema-version": "1.0",
3282
3879
  status: "draft",
3283
3880
  "governance-summary": GOVERNANCE_SUMMARY_DEFAULT,
3284
- tasks: DEFAULT_TASKS_ARRAY,
3881
+ tasks,
3285
3882
  supersedes: null,
3286
3883
  "superseded-by": null
3287
3884
  };
3288
3885
  }
3289
- function renderTasksFile(changeDir, canonicalId, name, author, dateStr) {
3290
- const tasksPath = path5__namespace.join(changeDir, "tasks.md");
3291
- const tasksFm = buildTasksFrontmatter(canonicalId, name);
3886
+ function renderTasksFile(changeDir, canonicalId, name, author, dateStr, reqIds) {
3887
+ const tasksPath = path4__namespace.join(changeDir, "tasks.md");
3888
+ const tasksFm = buildTasksFrontmatter(canonicalId, name, reqIds);
3292
3889
  const header = `---
3293
- ${yaml2__default.default.dump(tasksFm, { indent: 2, lineWidth: -1 }).trim()}
3890
+ ${yaml3__default.default.dump(tasksFm, { indent: 2, lineWidth: -1 }).trim()}
3294
3891
  ---
3295
3892
  `;
3296
3893
  const body = TASKS_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
3297
- fs6__namespace.writeFileSync(tasksPath, `${header}
3894
+ fs7__namespace.writeFileSync(tasksPath, `${header}
3298
3895
  ${body}`, "utf-8");
3299
3896
  return { tasksPath, tasksFm };
3300
3897
  }
@@ -3346,12 +3943,14 @@ function buildScaffoldFailure(profile, error, slug = "", canonicalId = "", chang
3346
3943
  errors: [error]
3347
3944
  };
3348
3945
  }
3349
- function renderAndCollectArtifactFiles(changeDir, canonicalId, name, author, dateStr, profile, citationsBlock, createdFiles) {
3946
+ function renderAndCollectArtifactFiles(changeDir, canonicalId, name, author, dateStr, profile, citations, createdFiles) {
3947
+ const citationsBlock = renderCitationsYamlBlock(citations.citedArtifacts);
3948
+ const reqIds = [...citations.subRequirements, ...citations.subSecReqs];
3350
3949
  createdFiles.push(renderProposalFile(changeDir, canonicalId, name, author, citationsBlock));
3351
3950
  const { specPath, specFmObj } = renderSpecFile(changeDir, canonicalId, name, profile);
3352
3951
  createdFiles.push(specPath);
3353
3952
  createdFiles.push(renderDesignFile(changeDir, canonicalId, name, author, dateStr, citationsBlock));
3354
- const { tasksPath, tasksFm } = renderTasksFile(changeDir, canonicalId, name, author, dateStr);
3953
+ const { tasksPath, tasksFm } = renderTasksFile(changeDir, canonicalId, name, author, dateStr, reqIds);
3355
3954
  createdFiles.push(tasksPath);
3356
3955
  return { specFmObj, tasksFm };
3357
3956
  }
@@ -3371,8 +3970,7 @@ function buildScaffoldSuccess(slug, canonicalId, profile, changeDir, createdFile
3371
3970
  function executeStandardScaffolding(rootDir, changeDir, slug, canonicalId, name, padNum, author, dateStr, profile, framework, inputFromIds, artifactMap, adapter) {
3372
3971
  const createdFiles = [];
3373
3972
  const citations = collectChangeCitations(rootDir, name, padNum, canonicalId, author, dateStr, inputFromIds, artifactMap, createdFiles);
3374
- const citationsBlock = renderCitationsYamlBlock(citations.citedArtifacts);
3375
- const { specFmObj, tasksFm } = renderAndCollectArtifactFiles(changeDir, canonicalId, name, author, dateStr, profile, citationsBlock, createdFiles);
3973
+ const { specFmObj, tasksFm } = renderAndCollectArtifactFiles(changeDir, canonicalId, name, author, dateStr, profile, citations, createdFiles);
3376
3974
  const { depositedPath, handoff } = renderHandoffSidecar(rootDir, slug, canonicalId, name, framework, citations);
3377
3975
  createdFiles.push(depositedPath);
3378
3976
  const errors = validateScaffoldedArtifacts(rootDir, changeDir, adapter, specFmObj, tasksFm, handoff);
@@ -3469,7 +4067,7 @@ function resolveCiEnvironment(env = process.env) {
3469
4067
  };
3470
4068
  }
3471
4069
  function updateExistingEnvFile(filePath, envString, uppercaseKey) {
3472
- const content = fs6__namespace.readFileSync(filePath, "utf-8");
4070
+ const content = fs7__namespace.readFileSync(filePath, "utf-8");
3473
4071
  const lines = content.split("\n");
3474
4072
  let found = false;
3475
4073
  const newLines = lines.map((line) => {
@@ -3480,15 +4078,15 @@ function updateExistingEnvFile(filePath, envString, uppercaseKey) {
3480
4078
  return line;
3481
4079
  });
3482
4080
  if (!found) newLines.push(envString);
3483
- fs6__namespace.writeFileSync(filePath, newLines.filter((l) => l.trim() !== "").join("\n") + "\n", "utf-8");
4081
+ fs7__namespace.writeFileSync(filePath, newLines.filter((l) => l.trim() !== "").join("\n") + "\n", "utf-8");
3484
4082
  }
3485
4083
  function writeEnvOutputFile(envOutputFile, key, value) {
3486
4084
  const uppercaseKey = key.toUpperCase();
3487
4085
  const envString = `${uppercaseKey}=${value}`;
3488
- if (fs6__namespace.existsSync(envOutputFile)) {
4086
+ if (fs7__namespace.existsSync(envOutputFile)) {
3489
4087
  updateExistingEnvFile(envOutputFile, envString, uppercaseKey);
3490
4088
  } else {
3491
- fs6__namespace.writeFileSync(envOutputFile, envString + "\n", "utf-8");
4089
+ fs7__namespace.writeFileSync(envOutputFile, envString + "\n", "utf-8");
3492
4090
  }
3493
4091
  }
3494
4092
  function resolveEnvOutputFilePath(env) {
@@ -3498,8 +4096,8 @@ function resolveEnvOutputFilePath(env) {
3498
4096
  return void 0;
3499
4097
  }
3500
4098
  function emitCiOutput(key, value, env = process.env) {
3501
- if (env.GITHUB_OUTPUT && fs6__namespace.existsSync(env.GITHUB_OUTPUT)) {
3502
- fs6__namespace.appendFileSync(env.GITHUB_OUTPUT, `${key}=${value}
4099
+ if (env.GITHUB_OUTPUT && fs7__namespace.existsSync(env.GITHUB_OUTPUT)) {
4100
+ fs7__namespace.appendFileSync(env.GITHUB_OUTPUT, `${key}=${value}
3503
4101
  `, "utf-8");
3504
4102
  }
3505
4103
  if (env.TF_BUILD === "true") {
@@ -3541,11 +4139,11 @@ function isIgnoredEntry(name) {
3541
4139
  return name === "changes" || name === "active" || name === "node_modules";
3542
4140
  }
3543
4141
  function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
3544
- if (!fs6__namespace.existsSync(parentDir)) return;
3545
- const entries = fs6__namespace.readdirSync(parentDir, { withFileTypes: true });
4142
+ if (!fs7__namespace.existsSync(parentDir)) return;
4143
+ const entries = fs7__namespace.readdirSync(parentDir, { withFileTypes: true });
3546
4144
  for (const entry of entries) {
3547
4145
  if (!entry.isDirectory() || isIgnoredEntry(entry.name)) continue;
3548
- const changeDir = path5__namespace.join(parentDir, entry.name);
4146
+ const changeDir = path4__namespace.join(parentDir, entry.name);
3549
4147
  if (handooffsHasDir(handoffsMap, changeDir)) continue;
3550
4148
  const handoff = loadProductHandoffSidecar(changeDir);
3551
4149
  if (handoff?.id) {
@@ -3556,10 +4154,10 @@ function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
3556
4154
  function scanAllProductHandoffs(rootDir) {
3557
4155
  const handoffsMap = /* @__PURE__ */ new Map();
3558
4156
  const candidateParentDirs = [
3559
- path5__namespace.join(rootDir, "specs", "changes", "active"),
3560
- path5__namespace.join(rootDir, "specs", "changes", "completed"),
3561
- path5__namespace.join(rootDir, "specs"),
3562
- path5__namespace.join(rootDir, "openspec", "changes")
4157
+ path4__namespace.join(rootDir, "specs", "changes", "active"),
4158
+ path4__namespace.join(rootDir, "specs", "changes", "completed"),
4159
+ path4__namespace.join(rootDir, "specs"),
4160
+ path4__namespace.join(rootDir, "openspec", "changes")
3563
4161
  ];
3564
4162
  for (const parentDir of candidateParentDirs) {
3565
4163
  scanParentDirectoryHandoffs(parentDir, handoffsMap);
@@ -3567,9 +4165,9 @@ function scanAllProductHandoffs(rootDir) {
3567
4165
  return handoffsMap;
3568
4166
  }
3569
4167
  function handooffsHasDir(map, dir) {
3570
- const norm = path5__namespace.resolve(dir);
4168
+ const norm = path4__namespace.resolve(dir);
3571
4169
  for (const key of map.keys()) {
3572
- if (path5__namespace.resolve(key) === norm) return true;
4170
+ if (path4__namespace.resolve(key) === norm) return true;
3573
4171
  }
3574
4172
  return false;
3575
4173
  }
@@ -3579,7 +4177,7 @@ function parseFrontmatter(content) {
3579
4177
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
3580
4178
  if (!match) return { frontmatter: {}, body: content };
3581
4179
  try {
3582
- const parsed = yaml2__default.default.load(match[1]);
4180
+ const parsed = yaml3__default.default.load(match[1]);
3583
4181
  const frontmatter = parsed && typeof parsed === "object" ? parsed : {};
3584
4182
  return { frontmatter, body: content.substring(match[0].length) };
3585
4183
  } catch {
@@ -3666,9 +4264,9 @@ function isCodeTestFile(name) {
3666
4264
  }
3667
4265
  function indexFeatureFile(fullPath, rootDir, map) {
3668
4266
  try {
3669
- const content = fs6__namespace.readFileSync(fullPath, "utf-8");
4267
+ const content = fs7__namespace.readFileSync(fullPath, "utf-8");
3670
4268
  const tags = extractFeatureTags(content);
3671
- const relFeature = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
4269
+ const relFeature = path4__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
3672
4270
  for (const tag of tags) {
3673
4271
  const existing = map.get(tag) || [];
3674
4272
  if (!existing.includes(relFeature)) existing.push(relFeature);
@@ -3678,11 +4276,11 @@ function indexFeatureFile(fullPath, rootDir, map) {
3678
4276
  }
3679
4277
  }
3680
4278
  function scanFeatureFiles(dir, rootDir, map) {
3681
- if (!fs6__namespace.existsSync(dir)) return;
4279
+ if (!fs7__namespace.existsSync(dir)) return;
3682
4280
  try {
3683
- const entries = fs6__namespace.readdirSync(dir, { withFileTypes: true });
4281
+ const entries = fs7__namespace.readdirSync(dir, { withFileTypes: true });
3684
4282
  for (const e of entries) {
3685
- const fullPath = path5__namespace.join(dir, e.name);
4283
+ const fullPath = path4__namespace.join(dir, e.name);
3686
4284
  if (e.isDirectory() && !shouldSkipDir(e.name)) {
3687
4285
  scanFeatureFiles(fullPath, rootDir, map);
3688
4286
  } else if (e.name.endsWith(".feature")) {
@@ -3694,8 +4292,8 @@ function scanFeatureFiles(dir, rootDir, map) {
3694
4292
  }
3695
4293
  function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
3696
4294
  try {
3697
- const content = fs6__namespace.readFileSync(fullPath, "utf-8");
3698
- const relTest = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
4295
+ const content = fs7__namespace.readFileSync(fullPath, "utf-8");
4296
+ const relTest = path4__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
3699
4297
  for (const artId of reqIds) {
3700
4298
  if (content.includes(artId)) {
3701
4299
  const existing = map.get(artId) || [];
@@ -3707,11 +4305,11 @@ function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
3707
4305
  }
3708
4306
  }
3709
4307
  function scanCodeTestFiles(dir, rootDir, reqIds, map) {
3710
- if (!fs6__namespace.existsSync(dir)) return;
4308
+ if (!fs7__namespace.existsSync(dir)) return;
3711
4309
  try {
3712
- const entries = fs6__namespace.readdirSync(dir, { withFileTypes: true });
4310
+ const entries = fs7__namespace.readdirSync(dir, { withFileTypes: true });
3713
4311
  for (const e of entries) {
3714
- const fullPath = path5__namespace.join(dir, e.name);
4312
+ const fullPath = path4__namespace.join(dir, e.name);
3715
4313
  if (e.isDirectory() && !shouldSkipDir(e.name)) {
3716
4314
  scanCodeTestFiles(fullPath, rootDir, reqIds, map);
3717
4315
  } else if (isCodeTestFile(e.name)) {
@@ -3745,9 +4343,9 @@ function isArchitectureViewFile(relFile, frontmatter) {
3745
4343
  }
3746
4344
  function processSingleMdFile(file, rootDir, artifactMap, componentsList, architectureViews) {
3747
4345
  try {
3748
- const content = fs6__namespace.readFileSync(file, "utf-8");
4346
+ const content = fs7__namespace.readFileSync(file, "utf-8");
3749
4347
  const { frontmatter, body } = parseFrontmatter(content);
3750
- const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
4348
+ const relFile = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
3751
4349
  if (frontmatter.id) {
3752
4350
  artifactMap.set(frontmatter.id, {
3753
4351
  file: relFile,
@@ -3908,7 +4506,7 @@ function collectArchitectureViewTraces(reqId, architectureViews) {
3908
4506
  const traces = [];
3909
4507
  for (const view of architectureViews) {
3910
4508
  if (view.content.includes(reqId)) {
3911
- const viewIdentifier = isEntityWithId(view) && view.id ? view.id : path5__namespace.basename(view.file);
4509
+ const viewIdentifier = isEntityWithId(view) && view.id ? view.id : path4__namespace.basename(view.file);
3912
4510
  traces.push(viewIdentifier);
3913
4511
  }
3914
4512
  }
@@ -3990,33 +4588,115 @@ function evaluateAllRequirements(requirements, ctx) {
3990
4588
  }
3991
4589
  return { matrixRows, orphans, errorCount };
3992
4590
  }
3993
- function verifyTraceability(options = {}) {
3994
- const rootDir = options.rootDir || process.cwd();
3995
- const allMdFiles = walkMdFiles(rootDir);
4591
+ var SOURCE_CODE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".java", ".cs", ".rs"];
4592
+ function isProductionSourceFile(name) {
4593
+ if (isCodeTestFile(name) || name.endsWith(".d.ts")) return false;
4594
+ return SOURCE_CODE_EXTS.some((ext) => name.endsWith(ext));
4595
+ }
4596
+ function collectCodeAndTestCorpora(dir, prodChunks = [], testChunks = []) {
4597
+ if (!fs7__namespace.existsSync(dir)) return { prodCorpus: "", testCorpus: "" };
4598
+ try {
4599
+ for (const e of fs7__namespace.readdirSync(dir, { withFileTypes: true })) {
4600
+ const fullPath = path4__namespace.join(dir, e.name);
4601
+ if (e.isDirectory() && !shouldSkipDir(e.name)) {
4602
+ collectCodeAndTestCorpora(fullPath, prodChunks, testChunks);
4603
+ } else if (isCodeTestFile(e.name) || e.name.endsWith(".feature")) {
4604
+ testChunks.push(fs7__namespace.readFileSync(fullPath, "utf-8"));
4605
+ } else if (isProductionSourceFile(e.name)) {
4606
+ prodChunks.push(fs7__namespace.readFileSync(fullPath, "utf-8"));
4607
+ }
4608
+ }
4609
+ } catch {
4610
+ }
4611
+ return { prodCorpus: prodChunks.join("\n"), testCorpus: testChunks.join("\n") };
4612
+ }
4613
+ function parseStringContractEntry(designId, defaultComp, raw) {
4614
+ const trimmed = raw.trim();
4615
+ if (!trimmed) return null;
4616
+ const parts = trimmed.split(".");
4617
+ const method = parts.pop();
4618
+ const interfaceName = parts.length > 0 ? parts.join(".") : void 0;
4619
+ return { designId, component: defaultComp, interfaceName, method };
4620
+ }
4621
+ function parseObjectContractEntries(designId, defaultComp, item) {
4622
+ if (!Array.isArray(item.methods)) return [];
4623
+ const component = item.component || defaultComp;
4624
+ return item.methods.filter((m) => typeof m === "string" && Boolean(m.trim())).map((m) => ({ designId, component, interfaceName: item.interface, method: m.trim() }));
4625
+ }
4626
+ function expandContractDeclarations(designId, defaultComp, rawContracts) {
4627
+ const expanded = [];
4628
+ for (const item of rawContracts) {
4629
+ if (typeof item === "string") {
4630
+ const parsed = parseStringContractEntry(designId, defaultComp, item);
4631
+ if (parsed) expanded.push(parsed);
4632
+ } else if (item && typeof item === "object") {
4633
+ expanded.push(...parseObjectContractEntries(designId, defaultComp, item));
4634
+ }
4635
+ }
4636
+ return expanded;
4637
+ }
4638
+ function evaluateSingleContractMethod(entry, prodCorpus, testCorpus) {
4639
+ const symbol = entry.method.includes(".") ? entry.method.split(".").pop() : entry.method;
4640
+ const implementedInCode = prodCorpus.includes(symbol);
4641
+ const testedInSuite = testCorpus.includes(symbol);
4642
+ const status = !implementedInCode ? "MISSING_IMPLEMENTATION" : !testedInSuite ? "MISSING_UNIT_TEST" : "COMPLIANT";
4643
+ return {
4644
+ designId: entry.designId,
4645
+ component: entry.component,
4646
+ interfaceName: entry.interfaceName,
4647
+ method: entry.method,
4648
+ implementedInCode,
4649
+ testedInSuite,
4650
+ status
4651
+ };
4652
+ }
4653
+ function auditDesignContracts(rootDir, artifactMap) {
4654
+ const pending = [];
4655
+ for (const [id, entry] of artifactMap.entries()) {
4656
+ if (entry.file.includes("templates/") || entry.file.includes(".template.")) continue;
4657
+ const rawContracts = entry.frontmatter.contracts;
4658
+ if (!Array.isArray(rawContracts) || rawContracts.length === 0) continue;
4659
+ const defaultComp = typeof entry.frontmatter["architecture-component"] === "string" ? entry.frontmatter["architecture-component"] : void 0;
4660
+ pending.push(...expandContractDeclarations(id, defaultComp, rawContracts));
4661
+ }
4662
+ if (pending.length === 0) {
4663
+ return { contractAudits: [], missingContractsCount: 0 };
4664
+ }
4665
+ const { prodCorpus, testCorpus } = collectCodeAndTestCorpora(rootDir);
4666
+ const contractAudits = pending.map((p) => evaluateSingleContractMethod(p, prodCorpus, testCorpus));
4667
+ const missingContractsCount = contractAudits.filter((c) => c.status !== "COMPLIANT").length;
4668
+ return { contractAudits, missingContractsCount };
4669
+ }
4670
+ function buildTraceabilityContext(rootDir, allMdFiles) {
3996
4671
  const { artifactMap, componentsList, architectureViews } = indexAllArtifacts(allMdFiles, rootDir);
3997
4672
  const handoffList = buildHandoffList(rootDir, artifactMap);
3998
4673
  const reqToHandoffMap = indexRequirementsToHandoff(handoffList);
3999
4674
  const { featureTagMap, codeTestMap } = buildTestMaps(rootDir, artifactMap);
4000
4675
  const requirements = collectRequirementsToVerify(artifactMap);
4001
- const ctx = {
4002
- reqToHandoffMap,
4003
- artifactMap,
4004
- componentsList,
4005
- architectureViews,
4006
- featureTagMap,
4007
- codeTestMap
4676
+ return {
4677
+ ctx: { reqToHandoffMap, artifactMap, componentsList, architectureViews, featureTagMap, codeTestMap },
4678
+ requirements
4008
4679
  };
4680
+ }
4681
+ function verifyTraceability(options = {}) {
4682
+ const rootDir = options.rootDir || process.cwd();
4683
+ const allMdFiles = walkMdFiles(rootDir);
4684
+ const { ctx, requirements } = buildTraceabilityContext(rootDir, allMdFiles);
4009
4685
  const { matrixRows, orphans, errorCount } = evaluateAllRequirements(requirements, ctx);
4686
+ const { contractAudits, missingContractsCount } = auditDesignContracts(rootDir, ctx.artifactMap);
4687
+ const totalIssues = errorCount + missingContractsCount;
4010
4688
  return {
4011
- success: errorCount === 0,
4689
+ success: totalIssues === 0,
4012
4690
  rows: matrixRows,
4013
4691
  orphans,
4014
4692
  orphanCount: orphans.length,
4015
4693
  totalRequirements: requirements.length,
4016
- reportMarkdown: generateTraceabilityReportMarkdown(matrixRows, errorCount)
4694
+ contractAudits,
4695
+ missingContractsCount,
4696
+ reportMarkdown: generateTraceabilityReportMarkdown(matrixRows, totalIssues)
4017
4697
  };
4018
4698
  }
4019
- function parseTaskVerification(rawVerification) {
4699
+ function parseTaskVerification2(rawVerification) {
4020
4700
  const verification = {};
4021
4701
  if (rawVerification && typeof rawVerification === "object") {
4022
4702
  const v = rawVerification;
@@ -4025,6 +4705,12 @@ function parseTaskVerification(rawVerification) {
4025
4705
  }
4026
4706
  return verification;
4027
4707
  }
4708
+ function parseOptionalStringList(val) {
4709
+ if (Array.isArray(val)) {
4710
+ return val.map(String).filter(Boolean);
4711
+ }
4712
+ return void 0;
4713
+ }
4028
4714
  function mapRawTaskItem(t) {
4029
4715
  return {
4030
4716
  id: typeof t["id"] === "string" ? t["id"] : String(t["id"] ?? ""),
@@ -4035,7 +4721,10 @@ function mapRawTaskItem(t) {
4035
4721
  assignedTo: typeof t["assigned-to"] === "string" ? t["assigned-to"] : void 0,
4036
4722
  status: typeof t["status"] === "string" ? t["status"] : void 0,
4037
4723
  blockingReason: typeof t["blocking-reason"] === "string" ? t["blocking-reason"] : void 0,
4038
- verification: parseTaskVerification(t["verification"])
4724
+ implementsComponents: parseOptionalStringList(t["implements-components"]),
4725
+ satisfiesRequirements: parseOptionalStringList(t["satisfies-requirements"]),
4726
+ implementsContracts: parseOptionalStringList(t["implements-contracts"]),
4727
+ verification: parseTaskVerification2(t["verification"])
4039
4728
  };
4040
4729
  }
4041
4730
  function parseTasksDoc(content) {
@@ -4043,7 +4732,7 @@ function parseTasksDoc(content) {
4043
4732
  if (!match) return { frontmatter: { tasks: [] }, body: content };
4044
4733
  let parsed;
4045
4734
  try {
4046
- parsed = yaml2__default.default.load(match[1]);
4735
+ parsed = yaml3__default.default.load(match[1]);
4047
4736
  } catch {
4048
4737
  return { frontmatter: { tasks: [] }, body: content };
4049
4738
  }
@@ -4068,12 +4757,12 @@ function walkTaskFiles(dir, fileList = [], excludeDirs = [
4068
4757
  "examples"
4069
4758
  ]) {
4070
4759
  try {
4071
- if (!fs6__namespace.existsSync(dir)) return fileList;
4072
- const files = fs6__namespace.readdirSync(dir);
4760
+ if (!fs7__namespace.existsSync(dir)) return fileList;
4761
+ const files = fs7__namespace.readdirSync(dir);
4073
4762
  for (const file of files) {
4074
- const fullPath = path5__namespace.join(dir, file);
4763
+ const fullPath = path4__namespace.join(dir, file);
4075
4764
  try {
4076
- if (fs6__namespace.statSync(fullPath).isDirectory()) {
4765
+ if (fs7__namespace.statSync(fullPath).isDirectory()) {
4077
4766
  if (!excludeDirs.includes(file)) {
4078
4767
  walkTaskFiles(fullPath, fileList, excludeDirs);
4079
4768
  }
@@ -4186,26 +4875,86 @@ function updateGovernanceCounts(t, modeCounts, riskCounts) {
4186
4875
  }
4187
4876
  }
4188
4877
  function parseTasksFromFile(file) {
4189
- if (!fs6__namespace.existsSync(file)) return [];
4878
+ if (!fs7__namespace.existsSync(file)) return [];
4190
4879
  try {
4191
- const content = fs6__namespace.readFileSync(file, "utf-8");
4880
+ const content = fs7__namespace.readFileSync(file, "utf-8");
4192
4881
  const { frontmatter } = parseTasksDoc(content);
4193
4882
  return frontmatter.tasks || [];
4194
4883
  } catch {
4195
4884
  return [];
4196
4885
  }
4197
4886
  }
4198
- function processTasksFile(file, rootDir, modeCounts, riskCounts, taskSummaries, violationsList) {
4887
+ function extractDesignExpectedScope(changeDir) {
4888
+ const designFile = path4__namespace.join(changeDir, "design.md");
4889
+ if (!fs7__namespace.existsSync(designFile)) return [];
4890
+ const expected = [];
4891
+ try {
4892
+ const { frontmatter } = parseTasksDoc(fs7__namespace.readFileSync(designFile, "utf-8"));
4893
+ if (typeof frontmatter["architecture-component"] === "string") {
4894
+ expected.push(frontmatter["architecture-component"]);
4895
+ }
4896
+ if (Array.isArray(frontmatter["architecture-components"])) {
4897
+ expected.push(...frontmatter["architecture-components"].map(String));
4898
+ }
4899
+ } catch {
4900
+ }
4901
+ return expected;
4902
+ }
4903
+ function extractHandoffExpectedScope(changeDir, maxUc, violations) {
4904
+ const handoff = loadProductHandoffSidecar(changeDir);
4905
+ if (!handoff?.subgraph) return [];
4906
+ const ucList = handoff.subgraph.useCases || [];
4907
+ if (maxUc !== void 0 && ucList.length > maxUc && violations) {
4908
+ violations.push(
4909
+ `[${handoff.changeId}] Greenfield monolith detected: change bundles ${ucList.length} use cases (max ${maxUc}). Decompose into incremental SDD changes per Use Case.`
4910
+ );
4911
+ }
4912
+ const reqs = handoff.subgraph.requirements || [];
4913
+ const secReqs = handoff.subgraph.securityRequirements || [];
4914
+ return [...reqs, ...secReqs];
4915
+ }
4916
+ function collectTaskCoveredScope(tasks) {
4917
+ const covered = /* @__PURE__ */ new Set();
4918
+ for (const t of tasks) {
4919
+ for (const c of t.implementsComponents || []) covered.add(c);
4920
+ for (const r of t.satisfiesRequirements || []) covered.add(r);
4921
+ for (const m of t.implementsContracts || []) covered.add(m);
4922
+ }
4923
+ return covered;
4924
+ }
4925
+ function hasExplicitScopeBindings(tasks) {
4926
+ return tasks.some(
4927
+ (t) => t.implementsComponents && t.implementsComponents.length > 0 || t.satisfiesRequirements && t.satisfiesRequirements.length > 0 || t.implementsContracts && t.implementsContracts.length > 0
4928
+ );
4929
+ }
4930
+ function auditDesignScopeForFile(file, tasks, options, unassignedList, violationsList) {
4931
+ const changeDir = path4__namespace.dirname(file);
4932
+ const expectedItems = [
4933
+ ...extractDesignExpectedScope(changeDir),
4934
+ ...extractHandoffExpectedScope(changeDir, options.maxUseCasesPerChange, violationsList)
4935
+ ];
4936
+ const shouldEnforce = Boolean(options.enforceDesignCoverage || hasExplicitScopeBindings(tasks));
4937
+ if (!shouldEnforce || expectedItems.length === 0) return;
4938
+ const covered = collectTaskCoveredScope(tasks);
4939
+ for (const item of new Set(expectedItems)) {
4940
+ if (!covered.has(item)) {
4941
+ unassignedList.push(item);
4942
+ violationsList.push(`[${path4__namespace.basename(changeDir)}] Unassigned design/handoff scope in tasks.md: missing task coverage for ${item}`);
4943
+ }
4944
+ }
4945
+ }
4946
+ function processTasksFile(file, rootDir, options, acc) {
4199
4947
  const tasks = parseTasksFromFile(file);
4200
- const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
4948
+ const relPath = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
4201
4949
  let unverifiedDelta = 0;
4202
4950
  for (const t of tasks) {
4203
4951
  const { summary, violation, unverified } = evaluateTaskItem(t, relPath);
4204
- if (violation) violationsList.push(violation);
4952
+ if (violation) acc.violationsList.push(violation);
4205
4953
  if (unverified) unverifiedDelta++;
4206
- updateGovernanceCounts(t, modeCounts, riskCounts);
4207
- taskSummaries.push(summary);
4954
+ updateGovernanceCounts(t, acc.modeCounts, acc.riskCounts);
4955
+ acc.taskSummaries.push(summary);
4208
4956
  }
4957
+ auditDesignScopeForFile(file, tasks, options, acc.unassignedDesignScope, acc.violationsList);
4209
4958
  return { tasksCount: tasks.length, unverifiedDelta };
4210
4959
  }
4211
4960
  function createInitialModeCounts() {
@@ -4224,30 +4973,32 @@ function createInitialRiskCounts() {
4224
4973
  CRITICAL: 0
4225
4974
  };
4226
4975
  }
4227
- function assembleGovernanceResult(totalTasks, unverifiedCount, riskCounts, modeCounts, taskSummaries, violationsList, reportMarkdown) {
4976
+ function assembleGovernanceResult(acc, reportMarkdown) {
4228
4977
  return {
4229
- success: violationsList.length === 0,
4230
- totalTasks,
4231
- verifiedCount: totalTasks - unverifiedCount,
4232
- unverifiedCount,
4233
- riskCounts,
4234
- modeCounts,
4235
- tasks: taskSummaries,
4236
- violations: violationsList,
4978
+ success: acc.violationsList.length === 0,
4979
+ totalTasks: acc.totalTasks,
4980
+ verifiedCount: acc.totalTasks - acc.unverifiedCount,
4981
+ unverifiedCount: acc.unverifiedCount,
4982
+ riskCounts: acc.riskCounts,
4983
+ modeCounts: acc.modeCounts,
4984
+ tasks: acc.taskSummaries,
4985
+ violations: acc.violationsList,
4986
+ unassignedDesignScope: acc.unassignedDesignScope,
4237
4987
  reportMarkdown
4238
4988
  };
4239
4989
  }
4240
- function collectAllTasksData(taskFiles, rootDir) {
4990
+ function collectAllTasksData(taskFiles, rootDir, options) {
4241
4991
  const acc = {
4242
4992
  totalTasks: 0,
4243
4993
  unverifiedCount: 0,
4244
4994
  taskSummaries: [],
4245
4995
  violationsList: [],
4996
+ unassignedDesignScope: [],
4246
4997
  modeCounts: createInitialModeCounts(),
4247
4998
  riskCounts: createInitialRiskCounts()
4248
4999
  };
4249
5000
  for (const file of taskFiles) {
4250
- const delta = processTasksFile(file, rootDir, acc.modeCounts, acc.riskCounts, acc.taskSummaries, acc.violationsList);
5001
+ const delta = processTasksFile(file, rootDir, options, acc);
4251
5002
  acc.totalTasks += delta.tasksCount;
4252
5003
  acc.unverifiedCount += delta.unverifiedDelta;
4253
5004
  }
@@ -4256,308 +5007,14 @@ function collectAllTasksData(taskFiles, rootDir) {
4256
5007
  function verifyTasksGovernance(options = {}) {
4257
5008
  const rootDir = options.rootDir || process.cwd();
4258
5009
  const searchDir = options.specsDir || rootDir;
4259
- const data = collectAllTasksData(walkTaskFiles(searchDir), rootDir);
5010
+ const data = collectAllTasksData(walkTaskFiles(searchDir), rootDir, options);
4260
5011
  const reportMarkdown = generateGovernanceReportMarkdown(
4261
5012
  data.taskSummaries,
4262
5013
  data.violationsList.length,
4263
5014
  data.modeCounts,
4264
5015
  data.totalTasks
4265
5016
  );
4266
- return assembleGovernanceResult(
4267
- data.totalTasks,
4268
- data.unverifiedCount,
4269
- data.riskCounts,
4270
- data.modeCounts,
4271
- data.taskSummaries,
4272
- data.violationsList,
4273
- reportMarkdown
4274
- );
4275
- }
4276
- function parseTaskVerification2(v) {
4277
- const verification = {};
4278
- if (v && typeof v === "object") {
4279
- const rec = v;
4280
- if (typeof rec["method"] === "string") verification.method = rec["method"];
4281
- if (typeof rec["command-or-criteria"] === "string") verification.criteria = rec["command-or-criteria"];
4282
- }
4283
- return verification;
4284
- }
4285
- function parseTaskItem(t) {
4286
- return {
4287
- id: typeof t["id"] === "string" ? t["id"] : String(t["id"] ?? ""),
4288
- title: typeof t["title"] === "string" ? t["title"] : void 0,
4289
- verification: parseTaskVerification2(t["verification"])
4290
- };
4291
- }
4292
- function parseVerifiedByTests(verifiedBy) {
4293
- if (Array.isArray(verifiedBy)) {
4294
- return verifiedBy.map(String);
4295
- }
4296
- if (typeof verifiedBy === "string") {
4297
- return [verifiedBy];
4298
- }
4299
- return void 0;
4300
- }
4301
- function parseGenericDoc(content) {
4302
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
4303
- if (!match) return { frontmatter: {}, body: content };
4304
- let parsed;
4305
- try {
4306
- parsed = yaml2__default.default.load(match[1]);
4307
- } catch {
4308
- return { frontmatter: {}, body: content };
4309
- }
4310
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
4311
- return { frontmatter: {}, body: content };
4312
- }
4313
- const raw = parsed;
4314
- const rawTasks = Array.isArray(raw["tasks"]) ? raw["tasks"] : [];
4315
- const tasks = rawTasks.map(parseTaskItem);
4316
- const frontmatter = {
4317
- ...raw,
4318
- tasks,
4319
- "verified-by-tests": parseVerifiedByTests(raw["verified-by-tests"])
4320
- };
4321
- return { frontmatter, body: content.substring(match[0].length) };
4322
- }
4323
- function buildRequirementsTable(reqAudit) {
4324
- const lines = [
4325
- `## 1. Requirements Audit (Product, Quality, and Security)`,
4326
- ``,
4327
- `| Requirement ID | Title | Source File | Declared Method | Linked Test Files | Status |`,
4328
- `| :--- | :--- | :--- | :---: | :--- | :---: |`
4329
- ];
4330
- for (const r of reqAudit) {
4331
- const icon = r.status === "VERIFICADO_CON_PRUEBA" ? "\u2705 VERIFIED" : "\u274C NO TESTS";
4332
- lines.push(
4333
- `| **\`${r.id}\`** | ${r.title} | \`${r.file}\` | \`${r.method}\` | \`${r.testRefs}\` | ${icon} |`
4334
- );
4335
- }
4336
- return lines;
4337
- }
4338
- function buildTasksTable(taskAudit) {
4339
- const lines = [
4340
- `## 2. Tasks Audit (SDD Delivery Engineering)`,
4341
- ``,
4342
- `| Task ID | Title | tasks.md File | Method | Verification Command / Criterion | Status |`,
4343
- `| :--- | :--- | :--- | :---: | :--- | :---: |`
4344
- ];
4345
- for (const t of taskAudit) {
4346
- const icon = t.status === "VERIFICADO_CON_PRUEBA" ? "\u2705 VERIFIED" : "\u274C NO TESTS";
4347
- lines.push(
4348
- `| **\`${t.id}\`** | ${t.title} | \`${t.file}\` | \`${t.method}\` | \`${t.commandOrCriteria}\` | ${icon} |`
4349
- );
4350
- }
4351
- return lines;
4352
- }
4353
- function generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, taskErrors) {
4354
- const totalErrors = reqErrors + taskErrors;
4355
- const verdict = totalErrors === 0 ? "100% VERIFIED WITH TESTS (PASSED)" : `BLOCKED: Detected ${totalErrors} items without tests (${reqErrors} Requirements, ${taskErrors} Tasks)`;
4356
- const reportLines = [
4357
- `# \u{1F9EA} Deterministic Test Coverage Audit for Requirements and Tasks`,
4358
- ``,
4359
- `> **Audit Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
4360
- `> **Overall Verdict:** ${verdict}`,
4361
- ``,
4362
- `---`,
4363
- ``,
4364
- ...buildRequirementsTable(reqAudit),
4365
- ``,
4366
- `---`,
4367
- ``,
4368
- ...buildTasksTable(taskAudit)
4369
- ];
4370
- return reportLines.join("\n");
4371
- }
4372
- var EXCLUDED_TEST_DIRS = /* @__PURE__ */ new Set([
4373
- "node_modules",
4374
- ".git",
4375
- "dist",
4376
- "bin",
4377
- "obj",
4378
- "build",
4379
- "out",
4380
- "target",
4381
- "coverage",
4382
- ".changeset",
4383
- "scratch",
4384
- "fixtures",
4385
- "templates",
4386
- "examples",
4387
- ".vs",
4388
- ".idea"
4389
- ]);
4390
- var TEST_EXTENSIONS = [".feature", ".spec.ts", ".spec.js", ".test.ts", ".test.js", "_test.py", "_test.go"];
4391
- function isTestFile(fileName) {
4392
- return TEST_EXTENSIONS.some((ext) => fileName.endsWith(ext));
4393
- }
4394
- function recordRequirementTest(reqToTestsMap, reqId, relTest) {
4395
- const existing = reqToTestsMap.get(reqId) || [];
4396
- if (!existing.includes(relTest)) {
4397
- existing.push(relTest);
4398
- reqToTestsMap.set(reqId, existing);
4399
- }
4400
- }
4401
- function parseFeatureTags(content, relTest, reqToTestsMap) {
4402
- const lines = content.split("\n");
4403
- for (const line of lines) {
4404
- const trimmed = line.trim();
4405
- if (!trimmed.startsWith("@")) continue;
4406
- const tags = trimmed.split(/\s+/).filter((t) => t.startsWith("@"));
4407
- for (const t of tags) {
4408
- recordRequirementTest(reqToTestsMap, t.substring(1), relTest);
4409
- }
4410
- }
4411
- }
4412
- var REQ_ID_REGEX = /\b(FR|QR|CON|SEC-REQ)-[A-Z0-9]+(-[A-Z0-9]+)*\b/g;
4413
- function parseCodeTestIds(content, relTest, reqToTestsMap) {
4414
- const idMatches = content.match(REQ_ID_REGEX);
4415
- if (!idMatches) return;
4416
- for (const reqId of idMatches) {
4417
- recordRequirementTest(reqToTestsMap, reqId, relTest);
4418
- }
4419
- }
4420
- function processTestFile(fullPath, rootDir, reqToTestsMap) {
4421
- try {
4422
- const content = fs6__namespace.readFileSync(fullPath, "utf-8");
4423
- const relTest = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
4424
- if (fullPath.endsWith(".feature")) {
4425
- parseFeatureTags(content, relTest, reqToTestsMap);
4426
- }
4427
- parseCodeTestIds(content, relTest, reqToTestsMap);
4428
- } catch {
4429
- }
4430
- }
4431
- function collectTestFiles(dir, files = []) {
4432
- if (!fs6__namespace.existsSync(dir)) return files;
4433
- try {
4434
- const entries = fs6__namespace.readdirSync(dir, { withFileTypes: true });
4435
- for (const e of entries) {
4436
- if (e.isDirectory()) {
4437
- if (!EXCLUDED_TEST_DIRS.has(e.name)) {
4438
- collectTestFiles(path5__namespace.join(dir, e.name), files);
4439
- }
4440
- } else if (isTestFile(e.name)) {
4441
- files.push(path5__namespace.join(dir, e.name));
4442
- }
4443
- }
4444
- } catch {
4445
- }
4446
- return files;
4447
- }
4448
- function scanAllTestArtifacts(rootDir) {
4449
- const reqToTestsMap = /* @__PURE__ */ new Map();
4450
- const testFiles = collectTestFiles(rootDir);
4451
- for (const file of testFiles) {
4452
- processTestFile(file, rootDir, reqToTestsMap);
4453
- }
4454
- return reqToTestsMap;
4455
- }
4456
- var REQ_PREFIXES = ["FR-", "QR-", "CON-", "SEC-REQ-"];
4457
- function isRequirementArtifact(frontmatter, id) {
4458
- if (frontmatter.type === "requirement" || frontmatter.type === "security-requirement") {
4459
- return true;
4460
- }
4461
- return REQ_PREFIXES.some((prefix) => id.startsWith(prefix));
4462
- }
4463
- function resolveRequirementMethod(declaredMethod, matchingTests) {
4464
- if (typeof declaredMethod === "string" && declaredMethod) {
4465
- return declaredMethod;
4466
- }
4467
- return matchingTests.some((t) => t.endsWith(".feature")) ? "BDD_CUCUMBER" : "TEST_EXECUTION";
4468
- }
4469
- function auditRequirement(frontmatter, id, relPath, isTemplate, reqToTestsMap) {
4470
- const matchingTests = reqToTestsMap.get(id) || [];
4471
- const isVerified = isTemplate || matchingTests.length > 0;
4472
- const method = resolveRequirementMethod(frontmatter["verifiable-by"], matchingTests);
4473
- return {
4474
- item: {
4475
- id: id || "SIN-ID",
4476
- title: frontmatter.title || "Sin t\xEDtulo",
4477
- file: relPath,
4478
- isTemplate,
4479
- method,
4480
- testRefs: matchingTests.join(", ") || "NINGUNA DETECTADA",
4481
- existingCount: matchingTests.length,
4482
- status: isVerified ? "VERIFICADO_CON_PRUEBA" : "FALLO_SIN_PRUEBA"
4483
- },
4484
- isError: !isVerified
4485
- };
4486
- }
4487
- function isTaskProofValid(criteria, isTemplate) {
4488
- if (isTemplate) return true;
4489
- return criteria.length > 4 && !criteria.includes("TODO") && !criteria.includes("TBD");
4490
- }
4491
- function auditSingleTask(t, relPath, isTemplate) {
4492
- const crit = t.verification?.criteria || "";
4493
- const method = t.verification?.method || "TEST_EXECUTION";
4494
- const hasProof = isTaskProofValid(crit, isTemplate);
4495
- return {
4496
- item: {
4497
- id: t.id,
4498
- title: t.title || "Sin t\xEDtulo",
4499
- file: relPath,
4500
- method,
4501
- commandOrCriteria: crit || "AUSENTE",
4502
- status: hasProof ? "VERIFICADO_CON_PRUEBA" : "FALLO_SIN_PRUEBA"
4503
- },
4504
- hasProof
4505
- };
4506
- }
4507
- function auditTasks(tasks, relPath, isTemplate) {
4508
- if (!Array.isArray(tasks) || tasks.length === 0) {
4509
- return { items: [], errorCount: 0 };
4510
- }
4511
- const items = [];
4512
- let errorCount = 0;
4513
- for (const t of tasks) {
4514
- const { item, hasProof } = auditSingleTask(t, relPath, isTemplate);
4515
- if (!hasProof) errorCount++;
4516
- items.push(item);
4517
- }
4518
- return { items, errorCount };
4519
- }
4520
- function assembleCoverageResult(reqAudit, taskAudit, reqErrors, taskErrors) {
4521
- return {
4522
- success: reqErrors === 0 && taskErrors === 0,
4523
- totalRequirements: reqAudit.length,
4524
- passedRequirements: reqAudit.length - reqErrors,
4525
- failedRequirements: reqErrors,
4526
- totalTasks: taskAudit.length,
4527
- passedTasks: taskAudit.length - taskErrors,
4528
- failedTasks: taskErrors,
4529
- requirements: reqAudit,
4530
- tasks: taskAudit,
4531
- reportMarkdown: generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, taskErrors)
4532
- };
4533
- }
4534
- function verifyTestingCoverage(options = {}) {
4535
- const rootDir = options.rootDir || process.cwd();
4536
- const allMdFiles = walkMdFiles(rootDir);
4537
- const reqToTestsMap = scanAllTestArtifacts(rootDir);
4538
- const reqAudit = [];
4539
- const taskAudit = [];
4540
- let reqErrors = 0;
4541
- let taskErrors = 0;
4542
- for (const file of allMdFiles) {
4543
- try {
4544
- const content = fs6__namespace.readFileSync(file, "utf-8");
4545
- const { frontmatter } = parseGenericDoc(content);
4546
- const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
4547
- const isTemplate = relPath.includes("templates/");
4548
- const id = frontmatter.id || "";
4549
- if (isRequirementArtifact(frontmatter, id)) {
4550
- const auditedReq = auditRequirement(frontmatter, id, relPath, isTemplate, reqToTestsMap);
4551
- reqAudit.push(auditedReq.item);
4552
- if (auditedReq.isError) reqErrors++;
4553
- }
4554
- const auditedTasks = auditTasks(frontmatter.tasks, relPath, isTemplate);
4555
- taskAudit.push(...auditedTasks.items);
4556
- taskErrors += auditedTasks.errorCount;
4557
- } catch {
4558
- }
4559
- }
4560
- return assembleCoverageResult(reqAudit, taskAudit, reqErrors, taskErrors);
5017
+ return assembleGovernanceResult(data, reportMarkdown);
4561
5018
  }
4562
5019
  function isToolAvailable(toolName) {
4563
5020
  try {
@@ -4603,8 +5060,8 @@ function findLicenseFile(pkgDir) {
4603
5060
  "license.txt"
4604
5061
  ];
4605
5062
  for (const c of candidates) {
4606
- const full = path5__namespace.join(pkgDir, c);
4607
- if (fs6__namespace.existsSync(full)) {
5063
+ const full = path4__namespace.join(pkgDir, c);
5064
+ if (fs7__namespace.existsSync(full)) {
4608
5065
  return full;
4609
5066
  }
4610
5067
  }
@@ -4621,9 +5078,9 @@ var LICENSE_FILE_PATTERNS = [
4621
5078
  [(t) => t.includes("Microsoft Reciprocal License") || t.includes("MS-RL"), "MS-RL"]
4622
5079
  ];
4623
5080
  function resolveLicenseFromFile(licenseFilePath) {
4624
- if (!licenseFilePath || !fs6__namespace.existsSync(licenseFilePath)) return void 0;
5081
+ if (!licenseFilePath || !fs7__namespace.existsSync(licenseFilePath)) return void 0;
4625
5082
  try {
4626
- const text = fs6__namespace.readFileSync(licenseFilePath, "utf-8");
5083
+ const text = fs7__namespace.readFileSync(licenseFilePath, "utf-8");
4627
5084
  for (const [matches, spdx] of LICENSE_FILE_PATTERNS) {
4628
5085
  if (matches(text)) return spdx;
4629
5086
  }
@@ -4632,16 +5089,16 @@ function resolveLicenseFromFile(licenseFilePath) {
4632
5089
  return void 0;
4633
5090
  }
4634
5091
  function readPackageJson(filePath) {
4635
- if (!fs6__namespace.existsSync(filePath)) return null;
5092
+ if (!fs7__namespace.existsSync(filePath)) return null;
4636
5093
  try {
4637
- return JSON.parse(fs6__namespace.readFileSync(filePath, "utf-8"));
5094
+ return JSON.parse(fs7__namespace.readFileSync(filePath, "utf-8"));
4638
5095
  } catch {
4639
5096
  return null;
4640
5097
  }
4641
5098
  }
4642
5099
  function extractDirectDepNames(rootDir, productionOnly) {
4643
5100
  const names = /* @__PURE__ */ new Set();
4644
- const pkgPath = path5__namespace.join(rootDir, "package.json");
5101
+ const pkgPath = path4__namespace.join(rootDir, "package.json");
4645
5102
  const pkg = readPackageJson(pkgPath);
4646
5103
  if (!pkg) return names;
4647
5104
  if (pkg.dependencies && typeof pkg.dependencies === "object") {
@@ -4661,7 +5118,7 @@ function resolveSpdxForPackage(pkg, licFile) {
4661
5118
  return spdx;
4662
5119
  }
4663
5120
  function processPackageDir(pkgDir, directNames, seen) {
4664
- const pkg = readPackageJson(path5__namespace.join(pkgDir, "package.json"));
5121
+ const pkg = readPackageJson(path4__namespace.join(pkgDir, "package.json"));
4665
5122
  if (!pkg?.name || !pkg.version) return;
4666
5123
  const key = `${pkg.name}@${pkg.version}`;
4667
5124
  if (seen.has(key)) return;
@@ -4682,32 +5139,32 @@ function processPackageDir(pkgDir, directNames, seen) {
4682
5139
  });
4683
5140
  }
4684
5141
  function scanScopeSubdirectory(scopeDir, directNames, seen) {
4685
- const scopedEntries = fs6__namespace.readdirSync(scopeDir, { withFileTypes: true });
5142
+ const scopedEntries = fs7__namespace.readdirSync(scopeDir, { withFileTypes: true });
4686
5143
  for (const scoped of scopedEntries) {
4687
5144
  if (scoped.isDirectory()) {
4688
- processPackageDir(path5__namespace.join(scopeDir, scoped.name), directNames, seen);
5145
+ processPackageDir(path4__namespace.join(scopeDir, scoped.name), directNames, seen);
4689
5146
  }
4690
5147
  }
4691
5148
  }
4692
5149
  function scanSubdirectories(baseDir, directNames, seen) {
4693
- if (!fs6__namespace.existsSync(baseDir)) return;
4694
- const entries = fs6__namespace.readdirSync(baseDir, { withFileTypes: true });
5150
+ if (!fs7__namespace.existsSync(baseDir)) return;
5151
+ const entries = fs7__namespace.readdirSync(baseDir, { withFileTypes: true });
4695
5152
  for (const entry of entries) {
4696
5153
  if (!entry.isDirectory()) continue;
4697
5154
  if (entry.name.startsWith("@")) {
4698
- scanScopeSubdirectory(path5__namespace.join(baseDir, entry.name), directNames, seen);
5155
+ scanScopeSubdirectory(path4__namespace.join(baseDir, entry.name), directNames, seen);
4699
5156
  } else if (entry.name !== ".bin" && entry.name !== ".pnpm") {
4700
- processPackageDir(path5__namespace.join(baseDir, entry.name), directNames, seen);
5157
+ processPackageDir(path4__namespace.join(baseDir, entry.name), directNames, seen);
4701
5158
  }
4702
5159
  }
4703
5160
  }
4704
5161
  function scanPnpmStore(pnpmDir, directNames, seen) {
4705
- if (!fs6__namespace.existsSync(pnpmDir)) return;
4706
- const entries = fs6__namespace.readdirSync(pnpmDir, { withFileTypes: true });
5162
+ if (!fs7__namespace.existsSync(pnpmDir)) return;
5163
+ const entries = fs7__namespace.readdirSync(pnpmDir, { withFileTypes: true });
4707
5164
  for (const entry of entries) {
4708
5165
  if (!entry.isDirectory()) continue;
4709
- const candidateModules = path5__namespace.join(pnpmDir, entry.name, "node_modules");
4710
- if (fs6__namespace.existsSync(candidateModules)) {
5166
+ const candidateModules = path4__namespace.join(pnpmDir, entry.name, "node_modules");
5167
+ if (fs7__namespace.existsSync(candidateModules)) {
4711
5168
  scanSubdirectories(candidateModules, directNames, seen);
4712
5169
  }
4713
5170
  }
@@ -4715,11 +5172,11 @@ function scanPnpmStore(pnpmDir, directNames, seen) {
4715
5172
  function scanWithNativeFs(rootDir, options = {}) {
4716
5173
  const directNames = extractDirectDepNames(rootDir, options.productionOnly);
4717
5174
  const seen = /* @__PURE__ */ new Map();
4718
- const nmDir = path5__namespace.join(rootDir, "node_modules");
4719
- if (fs6__namespace.existsSync(nmDir)) {
5175
+ const nmDir = path4__namespace.join(rootDir, "node_modules");
5176
+ if (fs7__namespace.existsSync(nmDir)) {
4720
5177
  scanSubdirectories(nmDir, directNames, seen);
4721
5178
  if (options.depth !== "direct") {
4722
- scanPnpmStore(path5__namespace.join(nmDir, ".pnpm"), directNames, seen);
5179
+ scanPnpmStore(path4__namespace.join(nmDir, ".pnpm"), directNames, seen);
4723
5180
  }
4724
5181
  }
4725
5182
  const allDeps = Array.from(seen.values());
@@ -4843,10 +5300,10 @@ function generateCycloneDxSbom(dependencies, options = {}) {
4843
5300
  let name = options.projectName || "ai-sdlc-project";
4844
5301
  let version = options.projectVersion || "1.0.0";
4845
5302
  if (options.rootDir) {
4846
- const pkgPath = path5__namespace.join(options.rootDir, "package.json");
4847
- if (fs6__namespace.existsSync(pkgPath)) {
5303
+ const pkgPath = path4__namespace.join(options.rootDir, "package.json");
5304
+ if (fs7__namespace.existsSync(pkgPath)) {
4848
5305
  try {
4849
- const pkg = JSON.parse(fs6__namespace.readFileSync(pkgPath, "utf-8"));
5306
+ const pkg = JSON.parse(fs7__namespace.readFileSync(pkgPath, "utf-8"));
4850
5307
  if (pkg.name) name = pkg.name;
4851
5308
  if (pkg.version) version = pkg.version;
4852
5309
  } catch {
@@ -4880,15 +5337,15 @@ function generateCycloneDxSbom(dependencies, options = {}) {
4880
5337
  function tryAtomicWrite(filePath, content) {
4881
5338
  const tempPath = `${filePath}.${process.pid}.${Math.random().toString(36).substring(2)}.tmp`;
4882
5339
  try {
4883
- fs6__namespace.writeFileSync(tempPath, content, "utf-8");
5340
+ fs7__namespace.writeFileSync(tempPath, content, "utf-8");
4884
5341
  try {
4885
- if (fs6__namespace.existsSync(filePath)) {
4886
- fs6__namespace.rmSync(filePath, { force: true });
5342
+ if (fs7__namespace.existsSync(filePath)) {
5343
+ fs7__namespace.rmSync(filePath, { force: true });
4887
5344
  }
4888
- fs6__namespace.renameSync(tempPath, filePath);
5345
+ fs7__namespace.renameSync(tempPath, filePath);
4889
5346
  } catch {
4890
- fs6__namespace.copyFileSync(tempPath, filePath);
4891
- fs6__namespace.unlinkSync(tempPath);
5347
+ fs7__namespace.copyFileSync(tempPath, filePath);
5348
+ fs7__namespace.unlinkSync(tempPath);
4892
5349
  }
4893
5350
  return true;
4894
5351
  } catch {
@@ -4896,15 +5353,15 @@ function tryAtomicWrite(filePath, content) {
4896
5353
  }
4897
5354
  }
4898
5355
  function safeWriteFileSync(filePath, content) {
4899
- const dir = path5__namespace.dirname(filePath);
4900
- if (!fs6__namespace.existsSync(dir)) {
4901
- fs6__namespace.mkdirSync(dir, { recursive: true });
5356
+ const dir = path4__namespace.dirname(filePath);
5357
+ if (!fs7__namespace.existsSync(dir)) {
5358
+ fs7__namespace.mkdirSync(dir, { recursive: true });
4902
5359
  }
4903
5360
  for (let attempt = 0; attempt < 10; attempt++) {
4904
5361
  if (tryAtomicWrite(filePath, content)) return;
4905
5362
  }
4906
5363
  try {
4907
- fs6__namespace.writeFileSync(filePath, content, "utf-8");
5364
+ fs7__namespace.writeFileSync(filePath, content, "utf-8");
4908
5365
  } catch {
4909
5366
  }
4910
5367
  }
@@ -4930,9 +5387,9 @@ function buildThirdPartyHeader(projectName, count) {
4930
5387
  ];
4931
5388
  }
4932
5389
  function readLicenseTextPreview(licenseFile) {
4933
- if (!licenseFile || !fs6__namespace.existsSync(licenseFile)) return null;
5390
+ if (!licenseFile || !fs7__namespace.existsSync(licenseFile)) return null;
4934
5391
  try {
4935
- const text = fs6__namespace.readFileSync(licenseFile, "utf-8").trim();
5392
+ const text = fs7__namespace.readFileSync(licenseFile, "utf-8").trim();
4936
5393
  return text.slice(0, 1e3) + (text.length > 1e3 ? "\n... (texto truncado)" : "");
4937
5394
  } catch {
4938
5395
  return null;
@@ -5044,7 +5501,7 @@ function parseExceptions(exceptions) {
5044
5501
  }
5045
5502
  function parseLicensePolicy(content) {
5046
5503
  try {
5047
- const doc = yaml2__default.default.load(content);
5504
+ const doc = yaml3__default.default.load(content);
5048
5505
  const { permitted, restricted, blocked } = classifyCategories(doc?.categories);
5049
5506
  const exception_packages = parseExceptions(doc?.exceptions);
5050
5507
  return {
@@ -5180,9 +5637,9 @@ function generateLicenseReportMarkdown(dependencies, violations, policy, sbomPat
5180
5637
  return lines.filter(Boolean).join("\n");
5181
5638
  }
5182
5639
  function parseManifestItems(manifestFile) {
5183
- if (!fs6__namespace.existsSync(manifestFile)) return [];
5640
+ if (!fs7__namespace.existsSync(manifestFile)) return [];
5184
5641
  try {
5185
- const doc = yaml2__default.default.load(fs6__namespace.readFileSync(manifestFile, "utf-8"));
5642
+ const doc = yaml3__default.default.load(fs7__namespace.readFileSync(manifestFile, "utf-8"));
5186
5643
  return Array.isArray(doc?.evaluated_dependencies) ? doc.evaluated_dependencies : [];
5187
5644
  } catch {
5188
5645
  return [];
@@ -5214,10 +5671,10 @@ function verifyStaticManifest(manifestFile, policy) {
5214
5671
  function resolveManifestPath(rootDir, customPath) {
5215
5672
  if (customPath) return customPath;
5216
5673
  const candidates = [
5217
- path5__namespace.join(rootDir, "compliance", "license-manifest.yaml"),
5218
- path5__namespace.join(rootDir, "license-manifest.yaml")
5674
+ path4__namespace.join(rootDir, "compliance", "license-manifest.yaml"),
5675
+ path4__namespace.join(rootDir, "license-manifest.yaml")
5219
5676
  ];
5220
- return candidates.find((p) => fs6__namespace.existsSync(p)) || candidates[0];
5677
+ return candidates.find((p) => fs7__namespace.existsSync(p)) || candidates[0];
5221
5678
  }
5222
5679
  function handleStaticManifestVerification(options, rootDir, policy) {
5223
5680
  const manifestFile = resolveManifestPath(rootDir, options.manifestPath);
@@ -5238,7 +5695,7 @@ function shouldGenerateSbom(options, rawPolicy) {
5238
5695
  }
5239
5696
  function maybeGenerateSbom(options, rootDir, rawPolicy, dependencies) {
5240
5697
  if (!shouldGenerateSbom(options, rawPolicy)) return void 0;
5241
- const sbomPath = options.sbomPath || path5__namespace.join(rootDir, "reports", "sbom.cdx.json");
5698
+ const sbomPath = options.sbomPath || path4__namespace.join(rootDir, "reports", "sbom.cdx.json");
5242
5699
  const sbom = generateCycloneDxSbom(dependencies, { rootDir });
5243
5700
  writeCycloneDxSbom(sbom, sbomPath);
5244
5701
  return sbomPath;
@@ -5247,7 +5704,7 @@ function maybeGenerateNotices(options, rootDir, rawPolicy, dependencies) {
5247
5704
  const shouldGen = options.generateNotices === true || Boolean(options.noticesPath || rawPolicy?.ci_gates?.output_notices_file);
5248
5705
  if (!shouldGen) return void 0;
5249
5706
  const noticesFileName = rawPolicy?.ci_gates?.output_notices_file || "THIRD_PARTY_NOTICES.md";
5250
- const noticesPath = options.noticesPath || path5__namespace.join(rootDir, noticesFileName);
5707
+ const noticesPath = options.noticesPath || path4__namespace.join(rootDir, noticesFileName);
5251
5708
  const noticesContent = generateThirdPartyNotices(dependencies);
5252
5709
  writeThirdPartyNotices(noticesContent, noticesPath);
5253
5710
  return noticesPath;
@@ -5259,8 +5716,8 @@ function handleDynamicScaArtifacts(options, rootDir, rawPolicy, dependencies) {
5259
5716
  };
5260
5717
  }
5261
5718
  function loadLicensePolicy(rootDir, customPath) {
5262
- const policyFile = customPath || path5__namespace.join(rootDir, "license-policy.yaml");
5263
- return fs6__namespace.existsSync(policyFile) ? parseLicensePolicy(fs6__namespace.readFileSync(policyFile, "utf-8")) : parseLicensePolicy("");
5719
+ const policyFile = customPath || path4__namespace.join(rootDir, "license-policy.yaml");
5720
+ return fs7__namespace.existsSync(policyFile) ? parseLicensePolicy(fs7__namespace.readFileSync(policyFile, "utf-8")) : parseLicensePolicy("");
5264
5721
  }
5265
5722
  function evaluateDependencyList(dependencies, policy) {
5266
5723
  const violations = [];
@@ -5330,14 +5787,14 @@ var PROTECTED_BYPASS_PATTERNS = [
5330
5787
  /migrations?[\\/]/i
5331
5788
  ];
5332
5789
  function detectProfileFromChange(changeDir) {
5333
- if (!fs6__namespace.existsSync(changeDir)) return "standard";
5334
- const specFile = path5__namespace.join(changeDir, "spec.md");
5335
- if (fs6__namespace.existsSync(specFile)) {
5790
+ if (!fs7__namespace.existsSync(changeDir)) return "standard";
5791
+ const specFile = path4__namespace.join(changeDir, "spec.md");
5792
+ if (fs7__namespace.existsSync(specFile)) {
5336
5793
  try {
5337
- const rawSpec = fs6__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
5794
+ const rawSpec = fs7__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
5338
5795
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
5339
5796
  if (match) {
5340
- const parsedFm = yaml2__default.default.load(match[1]);
5797
+ const parsedFm = yaml3__default.default.load(match[1]);
5341
5798
  if (parsedFm && parsedFm.profile && ["patch", "standard", "critical"].includes(String(parsedFm.profile))) {
5342
5799
  return parsedFm.profile;
5343
5800
  }
@@ -5352,17 +5809,17 @@ function isPathProtectedFromPatch(filePath) {
5352
5809
  return PROTECTED_BYPASS_PATTERNS.some((pattern) => pattern.test(normalized));
5353
5810
  }
5354
5811
  function resolveActiveChangeDir(rootDir) {
5355
- const activeRoot = path5__namespace.join(rootDir, "specs", "changes", "active");
5356
- if (!fs6__namespace.existsSync(activeRoot)) return void 0;
5812
+ const activeRoot = path4__namespace.join(rootDir, "specs", "changes", "active");
5813
+ if (!fs7__namespace.existsSync(activeRoot)) return void 0;
5357
5814
  try {
5358
- const dirs = fs6__namespace.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path5__namespace.join(activeRoot, d.name));
5815
+ const dirs = fs7__namespace.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path4__namespace.join(activeRoot, d.name));
5359
5816
  return dirs.find((d) => detectProfileFromChange(d) === "patch") || dirs[0];
5360
5817
  } catch {
5361
5818
  return void 0;
5362
5819
  }
5363
5820
  }
5364
5821
  function detectGitDiffFiles(rootDir) {
5365
- if (!fs6__namespace.existsSync(path5__namespace.join(rootDir, ".git"))) return [];
5822
+ if (!fs7__namespace.existsSync(path4__namespace.join(rootDir, ".git"))) return [];
5366
5823
  try {
5367
5824
  const tracked = child_process.execFileSync("git", ["diff", "--name-only", "HEAD"], {
5368
5825
  cwd: rootDir,
@@ -5385,19 +5842,19 @@ function resolveChangeDirectory(rootDir, options) {
5385
5842
  if (options.changeDir) return options.changeDir;
5386
5843
  if (options.changeId) {
5387
5844
  const candidates = [
5388
- path5__namespace.join(rootDir, "specs", options.changeId),
5389
- path5__namespace.join(rootDir, "specs", "changes", "active", options.changeId),
5390
- path5__namespace.join(rootDir, "examples", "specs", options.changeId),
5391
- path5__namespace.join(rootDir, "specs", "changes", "completed", options.changeId)
5845
+ path4__namespace.join(rootDir, "specs", options.changeId),
5846
+ path4__namespace.join(rootDir, "specs", "changes", "active", options.changeId),
5847
+ path4__namespace.join(rootDir, "examples", "specs", options.changeId),
5848
+ path4__namespace.join(rootDir, "specs", "changes", "completed", options.changeId)
5392
5849
  ];
5393
- return candidates.find((c) => fs6__namespace.existsSync(c));
5850
+ return candidates.find((c) => fs7__namespace.existsSync(c));
5394
5851
  }
5395
5852
  return resolveActiveChangeDir(rootDir);
5396
5853
  }
5397
5854
  function collectPatchViolations(rootDir, modifiedFiles) {
5398
5855
  const violations = [];
5399
5856
  for (const rawFile of modifiedFiles) {
5400
- const relPath = path5__namespace.isAbsolute(rawFile) ? path5__namespace.relative(rootDir, rawFile) : rawFile;
5857
+ const relPath = path4__namespace.isAbsolute(rawFile) ? path4__namespace.relative(rootDir, rawFile) : rawFile;
5401
5858
  if (isPathProtectedFromPatch(relPath)) {
5402
5859
  violations.push(
5403
5860
  `[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'.`
@@ -5463,7 +5920,7 @@ function parseMdFrontmatter(raw) {
5463
5920
  return { fm: {}, body: cleaned.trim() };
5464
5921
  }
5465
5922
  try {
5466
- const parsed = yaml2__default.default.load(match[1]);
5923
+ const parsed = yaml3__default.default.load(match[1]);
5467
5924
  const fm = parsed && typeof parsed === "object" ? parsed : {};
5468
5925
  return { fm, body: cleaned.substring(match[0].length).trim() };
5469
5926
  } catch {
@@ -5503,13 +5960,13 @@ function extractManualMetadata(fm, id, manualType, relPath, body) {
5503
5960
  }
5504
5961
  function buildManualItem(rootDir, filePath) {
5505
5962
  try {
5506
- const raw = fs6__namespace.readFileSync(filePath, "utf-8");
5963
+ const raw = fs7__namespace.readFileSync(filePath, "utf-8");
5507
5964
  const { fm, body } = parseMdFrontmatter(raw);
5508
5965
  const id = typeof fm.id === "string" ? fm.id.trim() : "";
5509
5966
  const rawType = typeof fm.type === "string" ? fm.type.trim() : "";
5510
5967
  const manualType = resolveManualType(id, rawType);
5511
5968
  if (!id || !manualType) return null;
5512
- const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
5969
+ const relPath = path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
5513
5970
  return extractManualMetadata(fm, id, manualType, relPath, body);
5514
5971
  } catch {
5515
5972
  return null;
@@ -5520,8 +5977,8 @@ function scanManualsInWorkspace(rootDir) {
5520
5977
  const seenIds = /* @__PURE__ */ new Set();
5521
5978
  const items = [];
5522
5979
  for (const relDir of manualDirs) {
5523
- const fullDir = path5__namespace.join(rootDir, relDir);
5524
- if (!fs6__namespace.existsSync(fullDir)) continue;
5980
+ const fullDir = path4__namespace.join(rootDir, relDir);
5981
+ if (!fs7__namespace.existsSync(fullDir)) continue;
5525
5982
  for (const file of walkMdFiles(fullDir)) {
5526
5983
  const item = buildManualItem(rootDir, file);
5527
5984
  if (item && !seenIds.has(item.id)) {
@@ -5544,10 +6001,10 @@ function classifyBaselineId(id, type, buckets) {
5544
6001
  }
5545
6002
  }
5546
6003
  function processBaselineFile(file, rootDir, buckets) {
5547
- const rel = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
6004
+ const rel = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
5548
6005
  if (rel.includes("/changes/") || rel.startsWith("changes/")) return;
5549
6006
  try {
5550
- const { fm } = parseMdFrontmatter(fs6__namespace.readFileSync(file, "utf-8"));
6007
+ const { fm } = parseMdFrontmatter(fs7__namespace.readFileSync(file, "utf-8"));
5551
6008
  const id = typeof fm.id === "string" ? fm.id.trim() : "";
5552
6009
  const status = typeof fm.status === "string" ? fm.status.trim().toLowerCase() : "active";
5553
6010
  if (!id || status === "retired" || status === "deprecated") return;
@@ -5568,8 +6025,8 @@ function collectBaselineTargets(rootDir) {
5568
6025
  enclaves: /* @__PURE__ */ new Set()
5569
6026
  };
5570
6027
  for (const dir of ["product", "architecture", "security", "specs"]) {
5571
- const fullDir = path5__namespace.join(rootDir, dir);
5572
- if (fs6__namespace.existsSync(fullDir)) {
6028
+ const fullDir = path4__namespace.join(rootDir, dir);
6029
+ if (fs7__namespace.existsSync(fullDir)) {
5573
6030
  scanDirectoryForBaseline(rootDir, fullDir, buckets);
5574
6031
  }
5575
6032
  }
@@ -5626,10 +6083,10 @@ function detectCurrentGitBranch(rootDir) {
5626
6083
  }
5627
6084
  }
5628
6085
  function resolveActiveChangeProfile(rootDir) {
5629
- const activeRoot = path5__namespace.join(rootDir, "specs", "changes", "active");
5630
- if (!fs6__namespace.existsSync(activeRoot)) return "standard";
6086
+ const activeRoot = path4__namespace.join(rootDir, "specs", "changes", "active");
6087
+ if (!fs7__namespace.existsSync(activeRoot)) return "standard";
5631
6088
  try {
5632
- const dirs = fs6__namespace.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path5__namespace.join(activeRoot, d.name));
6089
+ const dirs = fs7__namespace.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path4__namespace.join(activeRoot, d.name));
5633
6090
  const profiles = dirs.map((d) => detectProfileFromChange(d));
5634
6091
  if (profiles.includes("critical")) return "critical";
5635
6092
  if (profiles.includes("patch") && profiles.length === 1) return "patch";
@@ -5813,7 +6270,7 @@ function extractParsedFrontmatter(content) {
5813
6270
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
5814
6271
  if (!match) return null;
5815
6272
  try {
5816
- const parsed = yaml2__default.default.load(match[1]);
6273
+ const parsed = yaml3__default.default.load(match[1]);
5817
6274
  if (parsed && typeof parsed === "object") {
5818
6275
  const fm = parsed;
5819
6276
  if (fm.id && isRequirementArtifact2(fm)) {
@@ -5842,11 +6299,11 @@ function buildRequirementObject(content, fm, body, filePath, relFile) {
5842
6299
  }
5843
6300
  function parseRequirementFile(filePath, rootDir) {
5844
6301
  try {
5845
- const content = fs6__namespace.readFileSync(filePath, "utf-8");
6302
+ const content = fs7__namespace.readFileSync(filePath, "utf-8");
5846
6303
  const extracted = extractParsedFrontmatter(content);
5847
6304
  if (!extracted) return null;
5848
6305
  const body = content.substring(extracted.matchLen);
5849
- const relFile = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
6306
+ const relFile = path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
5850
6307
  return buildRequirementObject(content, extracted.fm, body, filePath, relFile);
5851
6308
  } catch {
5852
6309
  return null;
@@ -5858,7 +6315,7 @@ function isIgnoredScanPath(relFile) {
5858
6315
  function collectRequirements(rootDir) {
5859
6316
  const reqs = [];
5860
6317
  for (const file of walkMdFiles(rootDir)) {
5861
- const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
6318
+ const relFile = path4__namespace.relative(rootDir, file).replace(/\\/g, "/");
5862
6319
  if (isIgnoredScanPath(relFile)) continue;
5863
6320
  const req = parseRequirementFile(file, rootDir);
5864
6321
  if (req) reqs.push(req);
@@ -6217,10 +6674,10 @@ function isPathExcluded2(relPath, patterns) {
6217
6674
  function walkProjectFiles(dir, rootDir, excludedPatterns = DEFAULT_EXCLUDED_PATTERNS) {
6218
6675
  const results = [];
6219
6676
  try {
6220
- const entries = fs6__namespace.readdirSync(dir, { withFileTypes: true });
6677
+ const entries = fs7__namespace.readdirSync(dir, { withFileTypes: true });
6221
6678
  for (const entry of entries) {
6222
- const fullPath = path5__namespace.join(dir, entry.name);
6223
- const relPath = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
6679
+ const fullPath = path4__namespace.join(dir, entry.name);
6680
+ const relPath = path4__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
6224
6681
  if (isPathExcluded2(relPath, excludedPatterns)) continue;
6225
6682
  if (entry.isDirectory()) {
6226
6683
  results.push(...walkProjectFiles(fullPath, rootDir, excludedPatterns));
@@ -6249,8 +6706,8 @@ function pickFirstString(...vals) {
6249
6706
  }
6250
6707
  function parseGitleaksItem(item, rootDir) {
6251
6708
  const rawFile = pickFirstString(item.File, item.file);
6252
- const filePath = path5__namespace.resolve(rootDir, rawFile);
6253
- const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
6709
+ const filePath = path4__namespace.resolve(rootDir, rawFile);
6710
+ const relPath = path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
6254
6711
  const ruleId = pickFirstString(item.RuleID, item.ruleID) || "GITLEAKS-MATCH";
6255
6712
  const rawSecret = pickFirstString(item.Secret, item.secret, item.Match);
6256
6713
  const line = item.StartLine ?? item.line ?? 1;
@@ -6323,7 +6780,7 @@ function scanLineForSecrets(line, lineNumber, filePath, relPath, entropyThreshol
6323
6780
  function scanContentForSecrets(content, filePath, rootDir, entropyThreshold = 4.3) {
6324
6781
  const violations = [];
6325
6782
  const lines = content.split(/\r?\n/);
6326
- const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
6783
+ const relPath = path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
6327
6784
  for (let i = 0; i < lines.length; i++) {
6328
6785
  violations.push(...scanLineForSecrets(lines[i], i + 1, filePath, relPath, entropyThreshold));
6329
6786
  }
@@ -6343,7 +6800,7 @@ function scanDiffItems(diffLines, rootDir, excludePatterns, entropyThreshold) {
6343
6800
  const findings = [];
6344
6801
  for (const [relFile, items] of fileGroups) {
6345
6802
  if (isPathExcluded2(relFile, excludePatterns)) continue;
6346
- const absPath = path5__namespace.resolve(rootDir, relFile);
6803
+ const absPath = path4__namespace.resolve(rootDir, relFile);
6347
6804
  for (const item of items) {
6348
6805
  const lineViolations = scanLineForSecrets(item.content, item.lineNumber, absPath, relFile, entropyThreshold);
6349
6806
  for (const v of lineViolations) {
@@ -6358,7 +6815,7 @@ function scanAllProjectFiles(rootDir, excludePatterns, entropyThreshold) {
6358
6815
  const findings = [];
6359
6816
  for (const file of allFiles) {
6360
6817
  try {
6361
- const content = fs6__namespace.readFileSync(file, "utf-8");
6818
+ const content = fs7__namespace.readFileSync(file, "utf-8");
6362
6819
  findings.push(...scanContentForSecrets(content, file, rootDir, entropyThreshold));
6363
6820
  } catch {
6364
6821
  }
@@ -6582,8 +7039,8 @@ function runSemgrepCli(rootDir) {
6582
7039
  const parsed = JSON.parse(output);
6583
7040
  if (Array.isArray(parsed.results)) {
6584
7041
  for (const item of parsed.results) {
6585
- const filePath = path5__namespace.resolve(rootDir, item.path);
6586
- const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
7042
+ const filePath = path4__namespace.resolve(rootDir, item.path);
7043
+ const relPath = path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
6587
7044
  violations.push({
6588
7045
  filePath,
6589
7046
  relPath,
@@ -6610,15 +7067,15 @@ function isSastExcluded(relPath, excludes) {
6610
7067
  function walkSourceFiles(dir, rootDir, excludes = DEFAULT_SAST_EXCLUDES) {
6611
7068
  const files = [];
6612
7069
  try {
6613
- const entries = fs6__namespace.readdirSync(dir, { withFileTypes: true });
7070
+ const entries = fs7__namespace.readdirSync(dir, { withFileTypes: true });
6614
7071
  for (const entry of entries) {
6615
- const fullPath = path5__namespace.join(dir, entry.name);
6616
- const relPath = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
7072
+ const fullPath = path4__namespace.join(dir, entry.name);
7073
+ const relPath = path4__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
6617
7074
  if (isSastExcluded(relPath, excludes)) continue;
6618
7075
  if (entry.isDirectory()) {
6619
7076
  files.push(...walkSourceFiles(fullPath, rootDir, excludes));
6620
7077
  } else if (entry.isFile()) {
6621
- const ext = path5__namespace.extname(entry.name).toLowerCase();
7078
+ const ext = path4__namespace.extname(entry.name).toLowerCase();
6622
7079
  if (GENERAL_SOURCE_EXTENSIONS.includes(ext)) {
6623
7080
  files.push(fullPath);
6624
7081
  }
@@ -6639,11 +7096,11 @@ function checkRuleMatch(rule, line, ext, minSeverity) {
6639
7096
  }
6640
7097
  function scanFileForSast(filePath, rootDir, minSeverity = "HIGH") {
6641
7098
  const violations = [];
6642
- const ext = path5__namespace.extname(filePath).toLowerCase();
7099
+ const ext = path4__namespace.extname(filePath).toLowerCase();
6643
7100
  try {
6644
- const content = fs6__namespace.readFileSync(filePath, "utf-8");
7101
+ const content = fs7__namespace.readFileSync(filePath, "utf-8");
6645
7102
  const lines = content.split(/\r?\n/);
6646
- const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
7103
+ const relPath = path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
6647
7104
  for (let i = 0; i < lines.length; i++) {
6648
7105
  const line = lines[i];
6649
7106
  if (isLineExemptFromSast(line)) continue;
@@ -6669,8 +7126,8 @@ function scanFileForSast(filePath, rootDir, minSeverity = "HIGH") {
6669
7126
  function collectSastFiles(rootDir, targetDirs) {
6670
7127
  const files = [];
6671
7128
  for (const tDir of targetDirs) {
6672
- const fullDir = path5__namespace.join(rootDir, tDir);
6673
- if (fs6__namespace.existsSync(fullDir)) {
7129
+ const fullDir = path4__namespace.join(rootDir, tDir);
7130
+ if (fs7__namespace.existsSync(fullDir)) {
6674
7131
  files.push(...walkSourceFiles(fullDir, rootDir));
6675
7132
  }
6676
7133
  }
@@ -6825,7 +7282,7 @@ function calculateRating(mi, cc) {
6825
7282
  return "F";
6826
7283
  }
6827
7284
  function detectLanguage(filePath) {
6828
- const ext = path5__namespace.extname(filePath).toLowerCase();
7285
+ const ext = path4__namespace.extname(filePath).toLowerCase();
6829
7286
  switch (ext) {
6830
7287
  case ".ts":
6831
7288
  return "TypeScript";
@@ -6850,7 +7307,7 @@ function detectLanguage(filePath) {
6850
7307
  }
6851
7308
  function parseJsonSummaryFile(filePath, relSource) {
6852
7309
  try {
6853
- const raw = JSON.parse(fs6__namespace.readFileSync(filePath, "utf-8"));
7310
+ const raw = JSON.parse(fs7__namespace.readFileSync(filePath, "utf-8"));
6854
7311
  const total = raw.total;
6855
7312
  if (!total || typeof total !== "object") return null;
6856
7313
  const linesPct = Number(total.lines?.pct);
@@ -6873,7 +7330,7 @@ function updateLcovCounts(line, counts) {
6873
7330
  }
6874
7331
  function parseLcovFile(filePath, relSource) {
6875
7332
  try {
6876
- const lines = fs6__namespace.readFileSync(filePath, "utf-8").split(/\r?\n/);
7333
+ const lines = fs7__namespace.readFileSync(filePath, "utf-8").split(/\r?\n/);
6877
7334
  const counts = { LF: 0, LH: 0, BRF: 0, BRH: 0, FNF: 0, FNH: 0 };
6878
7335
  for (const line of lines) {
6879
7336
  updateLcovCounts(line, counts);
@@ -6893,16 +7350,16 @@ function parseLcovFile(filePath, relSource) {
6893
7350
  function readCodeCoverageSummary(rootDir = process.cwd()) {
6894
7351
  const jsonCandidates = ["coverage/coverage-summary.json", "reports/coverage-summary.json"];
6895
7352
  for (const rel of jsonCandidates) {
6896
- const full = path5__namespace.join(rootDir, rel);
6897
- if (fs6__namespace.existsSync(full)) {
7353
+ const full = path4__namespace.join(rootDir, rel);
7354
+ if (fs7__namespace.existsSync(full)) {
6898
7355
  const parsed = parseJsonSummaryFile(full, rel);
6899
7356
  if (parsed) return parsed;
6900
7357
  }
6901
7358
  }
6902
7359
  const lcovCandidates = ["coverage/lcov.info", "lcov.info", "reports/lcov.info"];
6903
7360
  for (const rel of lcovCandidates) {
6904
- const full = path5__namespace.join(rootDir, rel);
6905
- if (fs6__namespace.existsSync(full)) {
7361
+ const full = path4__namespace.join(rootDir, rel);
7362
+ if (fs7__namespace.existsSync(full)) {
6906
7363
  const parsed = parseLcovFile(full, rel);
6907
7364
  if (parsed) return parsed;
6908
7365
  }
@@ -7038,9 +7495,9 @@ function renderQualityReportMarkdown(gateResult, coverage, accumulated, averages
7038
7495
  return [...header, "", "---", "", ...table, "", "---", "", ...polyglot, ...violationsSection, "", EVALUATION_STANDARDS_MD].join("\n");
7039
7496
  }
7040
7497
  function writeQualityReportFile(rootDir, markdown) {
7041
- const reportsDir = path5__namespace.join(rootDir, "reports");
7042
- fs6__namespace.mkdirSync(reportsDir, { recursive: true });
7043
- fs6__namespace.writeFileSync(path5__namespace.join(reportsDir, "QUALITY_REPORT.md"), markdown, "utf-8");
7498
+ const reportsDir = path4__namespace.join(rootDir, "reports");
7499
+ fs7__namespace.mkdirSync(reportsDir, { recursive: true });
7500
+ fs7__namespace.writeFileSync(path4__namespace.join(reportsDir, "QUALITY_REPORT.md"), markdown, "utf-8");
7044
7501
  }
7045
7502
  function generateQualityReport(options = {}) {
7046
7503
  const rootDir = options.rootDir || process.cwd();
@@ -7182,12 +7639,12 @@ Flujo de Integraci\xF3n (Merges):`,
7182
7639
  };
7183
7640
  }
7184
7641
  function resolveVersionFromProposal(changeDir) {
7185
- const proposalFile = path5__namespace.join(changeDir, "proposal.md");
7186
- if (!fs6__namespace.existsSync(proposalFile)) return "1.0.0";
7187
- const propContent = fs6__namespace.readFileSync(proposalFile, "utf-8");
7642
+ const proposalFile = path4__namespace.join(changeDir, "proposal.md");
7643
+ if (!fs7__namespace.existsSync(proposalFile)) return "1.0.0";
7644
+ const propContent = fs7__namespace.readFileSync(proposalFile, "utf-8");
7188
7645
  const propFm = propContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
7189
7646
  if (!propFm) return "1.0.0";
7190
- const parsedProp = yaml2__default.default.load(propFm[1]);
7647
+ const parsedProp = yaml3__default.default.load(propFm[1]);
7191
7648
  return parsedProp?.version ? String(parsedProp.version) : "1.0.0";
7192
7649
  }
7193
7650
  function resolveChangeVersion(fm, changeDir) {
@@ -7201,12 +7658,12 @@ function isTaskMatchingId(currentId, cleanTaskId) {
7201
7658
  return currentLower === targetLower || currentLower.startsWith(`${targetLower}-`) || targetLower.startsWith(`${currentLower}-`);
7202
7659
  }
7203
7660
  function parseTasksFrontmatter(tasksFile) {
7204
- if (!fs6__namespace.existsSync(tasksFile)) return null;
7661
+ if (!fs7__namespace.existsSync(tasksFile)) return null;
7205
7662
  try {
7206
- const content = fs6__namespace.readFileSync(tasksFile, "utf-8");
7663
+ const content = fs7__namespace.readFileSync(tasksFile, "utf-8");
7207
7664
  const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
7208
7665
  if (!fmMatch) return null;
7209
- const fm = yaml2__default.default.load(fmMatch[1]);
7666
+ const fm = yaml3__default.default.load(fmMatch[1]);
7210
7667
  return fm && Array.isArray(fm.tasks) ? fm : null;
7211
7668
  } catch {
7212
7669
  return null;
@@ -7225,7 +7682,7 @@ function buildTaskMatch(currentId, currentTitle, rawChangeId, changeDir, version
7225
7682
  };
7226
7683
  }
7227
7684
  function inspectChangeTasks(changeDir, entryName, cleanTaskId, availableTasks) {
7228
- const fm = parseTasksFrontmatter(path5__namespace.join(changeDir, "tasks.md"));
7685
+ const fm = parseTasksFrontmatter(path4__namespace.join(changeDir, "tasks.md"));
7229
7686
  if (!fm || !fm.tasks) return void 0;
7230
7687
  const rawChangeId = resolveRawChangeId(fm, entryName);
7231
7688
  const versionStr = resolveChangeVersion(fm, changeDir);
@@ -7246,16 +7703,16 @@ function locateTaskInActiveChanges(taskId, rootDir = process.cwd()) {
7246
7703
  if (!cleanTaskId) {
7247
7704
  return { found: false, error: "Debe especificar un identificador de tarea v\xE1lido (ej. TSK-001)." };
7248
7705
  }
7249
- const activeChangesDir = path5__namespace.join(rootDir, "specs", "changes", "active");
7250
- if (!fs6__namespace.existsSync(activeChangesDir)) {
7706
+ const activeChangesDir = path4__namespace.join(rootDir, "specs", "changes", "active");
7707
+ if (!fs7__namespace.existsSync(activeChangesDir)) {
7251
7708
  return { found: false, error: `No se encontr\xF3 el directorio de cambios activos en '${activeChangesDir}'.` };
7252
7709
  }
7253
7710
  const availableTasks = [];
7254
- const candidateDirs = fs6__namespace.readdirSync(activeChangesDir, { withFileTypes: true });
7711
+ const candidateDirs = fs7__namespace.readdirSync(activeChangesDir, { withFileTypes: true });
7255
7712
  let matchedTask;
7256
7713
  for (const entry of candidateDirs) {
7257
7714
  if (!entry.isDirectory()) continue;
7258
- const changeDir = path5__namespace.join(activeChangesDir, entry.name);
7715
+ const changeDir = path4__namespace.join(activeChangesDir, entry.name);
7259
7716
  const m = inspectChangeTasks(changeDir, entry.name, cleanTaskId, availableTasks);
7260
7717
  if (m && !matchedTask) {
7261
7718
  matchedTask = m;
@@ -7319,7 +7776,7 @@ function resolveBranchHierarchy(match, rootDir = process.cwd()) {
7319
7776
  const branches = getGitBranchList(rootDir);
7320
7777
  const cleanVersion = (match.version || "1.0.0").replace(/^v/, "");
7321
7778
  const releaseBranch = detectExistingReleaseBranch(branches, cleanVersion);
7322
- const dirName = path5__namespace.basename(match.changeDir);
7779
+ const dirName = path4__namespace.basename(match.changeDir);
7323
7780
  const changeSlug = dirName.replace(/^(chg|CHG)-/i, (m) => m.toUpperCase());
7324
7781
  const featureBranch = detectExistingFeatureBranch(branches, dirName, match.changeId, changeSlug);
7325
7782
  const parentIdMatch = featureBranch.replace(/^(feat|feature)\//i, "").match(/^[a-zA-Z0-9]+-[a-zA-Z0-9]+/);
@@ -7626,13 +8083,13 @@ function getGitConfigValue(key, cwd) {
7626
8083
  }
7627
8084
  function readSessionTelemetry(cwd) {
7628
8085
  const possiblePaths = [
7629
- path5__namespace.join(cwd, ".ai-sdlc", "session-telemetry.json"),
7630
- path5__namespace.join(cwd, ".ai-sdlc", "agent-session.json")
8086
+ path4__namespace.join(cwd, ".ai-sdlc", "session-telemetry.json"),
8087
+ path4__namespace.join(cwd, ".ai-sdlc", "agent-session.json")
7631
8088
  ];
7632
8089
  for (const p of possiblePaths) {
7633
- if (fs6__namespace.existsSync(p)) {
8090
+ if (fs7__namespace.existsSync(p)) {
7634
8091
  try {
7635
- const raw = fs6__namespace.readFileSync(p, "utf-8");
8092
+ const raw = fs7__namespace.readFileSync(p, "utf-8");
7636
8093
  return JSON.parse(raw);
7637
8094
  } catch {
7638
8095
  }
@@ -7852,20 +8309,20 @@ if [ -f "$TELEMETRY_FILE" ]; then
7852
8309
  fi
7853
8310
  `;
7854
8311
  function installGitHooks(rootDir = process.cwd()) {
7855
- const gitDir = path5__namespace.join(rootDir, ".git");
7856
- if (!fs6__namespace.existsSync(gitDir)) {
8312
+ const gitDir = path4__namespace.join(rootDir, ".git");
8313
+ if (!fs7__namespace.existsSync(gitDir)) {
7857
8314
  return {
7858
8315
  success: false,
7859
8316
  error: `No se encontr\xF3 el directorio Git en '${gitDir}'.`
7860
8317
  };
7861
8318
  }
7862
- const hooksDir = path5__namespace.join(gitDir, "hooks");
7863
- if (!fs6__namespace.existsSync(hooksDir)) {
7864
- fs6__namespace.mkdirSync(hooksDir, { recursive: true });
8319
+ const hooksDir = path4__namespace.join(gitDir, "hooks");
8320
+ if (!fs7__namespace.existsSync(hooksDir)) {
8321
+ fs7__namespace.mkdirSync(hooksDir, { recursive: true });
7865
8322
  }
7866
- const hookPath = path5__namespace.join(hooksDir, "prepare-commit-msg");
8323
+ const hookPath = path4__namespace.join(hooksDir, "prepare-commit-msg");
7867
8324
  try {
7868
- fs6__namespace.writeFileSync(hookPath, PREPARE_COMMIT_MSG_SCRIPT, { encoding: "utf-8", mode: 493 });
8325
+ fs7__namespace.writeFileSync(hookPath, PREPARE_COMMIT_MSG_SCRIPT, { encoding: "utf-8", mode: 493 });
7869
8326
  return {
7870
8327
  success: true,
7871
8328
  hookPath
@@ -8177,16 +8634,16 @@ function formatReleaseKpiMarkdown(report) {
8177
8634
  ].join("\n");
8178
8635
  }
8179
8636
  function writeReleaseKpiReport(report, options = {}) {
8180
- const root = options.outputDir || (options.rootDir ? path5__namespace.join(options.rootDir, "reports", "releases") : path5__namespace.join(process.cwd(), "reports", "releases"));
8181
- if (!fs6__namespace.existsSync(root)) {
8182
- fs6__namespace.mkdirSync(root, { recursive: true });
8637
+ const root = options.outputDir || (options.rootDir ? path4__namespace.join(options.rootDir, "reports", "releases") : path4__namespace.join(process.cwd(), "reports", "releases"));
8638
+ if (!fs7__namespace.existsSync(root)) {
8639
+ fs7__namespace.mkdirSync(root, { recursive: true });
8183
8640
  }
8184
8641
  const cleanBranchName = report.releaseBranch.replace(/[^a-zA-Z0-9.-]/g, "_");
8185
- const mdPath = path5__namespace.join(root, `RELEASE_KPIS_${cleanBranchName}.md`);
8186
- const jsonPath = path5__namespace.join(root, `release_${cleanBranchName}.kpis.json`);
8642
+ const mdPath = path4__namespace.join(root, `RELEASE_KPIS_${cleanBranchName}.md`);
8643
+ const jsonPath = path4__namespace.join(root, `release_${cleanBranchName}.kpis.json`);
8187
8644
  const mdContent = formatReleaseKpiMarkdown(report);
8188
- fs6__namespace.writeFileSync(mdPath, mdContent, "utf-8");
8189
- fs6__namespace.writeFileSync(jsonPath, JSON.stringify(report, null, 2), "utf-8");
8645
+ fs7__namespace.writeFileSync(mdPath, mdContent, "utf-8");
8646
+ fs7__namespace.writeFileSync(jsonPath, JSON.stringify(report, null, 2), "utf-8");
8190
8647
  return { markdownPath: mdPath, jsonPath };
8191
8648
  }
8192
8649
  var cachedScript = null;
@@ -8195,19 +8652,19 @@ function resolveCytoscapePath() {
8195
8652
  try {
8196
8653
  const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
8197
8654
  const entryPath = req.resolve("cytoscape");
8198
- const dir = path5__namespace.dirname(entryPath);
8199
- const minPath = path5__namespace.join(dir, "cytoscape.min.js");
8200
- if (fs6__namespace.existsSync(minPath)) {
8655
+ const dir = path4__namespace.dirname(entryPath);
8656
+ const minPath = path4__namespace.join(dir, "cytoscape.min.js");
8657
+ if (fs7__namespace.existsSync(minPath)) {
8201
8658
  return minPath;
8202
8659
  }
8203
8660
  } catch {
8204
8661
  }
8205
8662
  const searchDirs = [
8206
- path5__namespace.join(process.cwd(), "node_modules", "cytoscape", "dist", "cytoscape.min.js"),
8207
- path5__namespace.join(process.cwd(), "packages", "core", "node_modules", "cytoscape", "dist", "cytoscape.min.js")
8663
+ path4__namespace.join(process.cwd(), "node_modules", "cytoscape", "dist", "cytoscape.min.js"),
8664
+ path4__namespace.join(process.cwd(), "packages", "core", "node_modules", "cytoscape", "dist", "cytoscape.min.js")
8208
8665
  ];
8209
8666
  for (const candidate of searchDirs) {
8210
- if (fs6__namespace.existsSync(candidate)) {
8667
+ if (fs7__namespace.existsSync(candidate)) {
8211
8668
  return candidate;
8212
8669
  }
8213
8670
  }
@@ -8220,7 +8677,7 @@ function getCytoscapeScript() {
8220
8677
  const scriptPath = resolveCytoscapePath();
8221
8678
  if (scriptPath) {
8222
8679
  try {
8223
- cachedScript = fs6__namespace.readFileSync(scriptPath, "utf-8");
8680
+ cachedScript = fs7__namespace.readFileSync(scriptPath, "utf-8");
8224
8681
  return cachedScript;
8225
8682
  } catch {
8226
8683
  }
@@ -8304,13 +8761,13 @@ function extractFrontmatter2(fileContent) {
8304
8761
  const match = fileContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
8305
8762
  if (!match) return null;
8306
8763
  try {
8307
- return yaml2__default.default.load(match[1]) || null;
8764
+ return yaml3__default.default.load(match[1]) || null;
8308
8765
  } catch {
8309
8766
  return null;
8310
8767
  }
8311
8768
  }
8312
8769
  function toScannedArtifact(filePath, relPath) {
8313
- const fileContent = fs6__namespace.readFileSync(filePath, "utf-8");
8770
+ const fileContent = fs7__namespace.readFileSync(filePath, "utf-8");
8314
8771
  const fm = extractFrontmatter2(fileContent);
8315
8772
  const id = typeof fm?.id === "string" ? fm.id.trim() : "";
8316
8773
  if (!id) return null;
@@ -8322,12 +8779,12 @@ function toScannedArtifact(filePath, relPath) {
8322
8779
  return { id, type: classified.type, title, layer: classified.layer, filePath: relPath, content, frontmatter: fm };
8323
8780
  }
8324
8781
  function parseAndCatalogArtifactFile(rootDir, filePath, scannedMap, archViewByBasename) {
8325
- const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
8782
+ const relPath = path4__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
8326
8783
  if (relPath.includes("/changes/") || relPath.startsWith("changes/")) return;
8327
8784
  const scannedArt = toScannedArtifact(filePath, relPath);
8328
8785
  if (!scannedArt) return;
8329
8786
  if (scannedArt.layer === "architecture" && scannedArt.type === "architecture") {
8330
- archViewByBasename.set(path5__namespace.basename(relPath), scannedArt);
8787
+ archViewByBasename.set(path4__namespace.basename(relPath), scannedArt);
8331
8788
  } else {
8332
8789
  scannedMap.set(scannedArt.id, scannedArt);
8333
8790
  }
@@ -8337,8 +8794,8 @@ function scanWorkspaceArtifacts(rootDir) {
8337
8794
  const archViewByBasename = /* @__PURE__ */ new Map();
8338
8795
  const artifactDirs = ["product", "architecture", "specs", "security"];
8339
8796
  for (const dirName of artifactDirs) {
8340
- const fullDirPath = path5__namespace.join(rootDir, dirName);
8341
- if (fs6__namespace.existsSync(fullDirPath)) {
8797
+ const fullDirPath = path4__namespace.join(rootDir, dirName);
8798
+ if (fs7__namespace.existsSync(fullDirPath)) {
8342
8799
  for (const f of walkMdFiles(fullDirPath)) {
8343
8800
  parseAndCatalogArtifactFile(rootDir, f, scannedArtifactsMap, archViewByBasename);
8344
8801
  }
@@ -8396,9 +8853,9 @@ function addTraceArchEdges(r, status, archViewByBasename, nodeMap, addEdge) {
8396
8853
  }
8397
8854
  }
8398
8855
  function loadTestContentPreview(filePath) {
8399
- if (!fs6__namespace.existsSync(filePath)) return void 0;
8856
+ if (!fs7__namespace.existsSync(filePath)) return void 0;
8400
8857
  try {
8401
- const testContent = fs6__namespace.readFileSync(filePath, "utf-8");
8858
+ const testContent = fs7__namespace.readFileSync(filePath, "utf-8");
8402
8859
  const lang = filePath.endsWith(".feature") ? "gherkin" : "typescript";
8403
8860
  const preview = testContent.substring(0, 1500) + (testContent.length > 1500 ? "\n... (truncated)" : "");
8404
8861
  return "```" + lang + "\n" + preview + "\n```";
@@ -8408,10 +8865,10 @@ function loadTestContentPreview(filePath) {
8408
8865
  }
8409
8866
  function ensureTestNode(t, testId, rootDir, nodeMap) {
8410
8867
  if (nodeMap.has(testId)) return;
8411
- const fullTestPath = path5__namespace.isAbsolute(t) ? t : path5__namespace.join(rootDir, t);
8868
+ const fullTestPath = path4__namespace.isAbsolute(t) ? t : path4__namespace.join(rootDir, t);
8412
8869
  nodeMap.set(testId, {
8413
8870
  id: testId,
8414
- label: path5__namespace.basename(t),
8871
+ label: path4__namespace.basename(t),
8415
8872
  title: t,
8416
8873
  type: t.endsWith(".feature") ? "bdd-feature" : "code-test",
8417
8874
  layer: "test",
@@ -10446,9 +10903,9 @@ var IGNORED_SCAN_DIRS = /* @__PURE__ */ new Set([
10446
10903
  ".agent"
10447
10904
  ]);
10448
10905
  function readKpiFilesInDir(dir) {
10449
- if (!fs6__namespace.existsSync(dir)) return [];
10906
+ if (!fs7__namespace.existsSync(dir)) return [];
10450
10907
  try {
10451
- return fs6__namespace.readdirSync(dir).filter((f) => f.endsWith(".kpis.json")).map((f) => path5__namespace.join(dir, f));
10908
+ return fs7__namespace.readdirSync(dir).filter((f) => f.endsWith(".kpis.json")).map((f) => path4__namespace.join(dir, f));
10452
10909
  } catch {
10453
10910
  return [];
10454
10911
  }
@@ -10456,11 +10913,11 @@ function readKpiFilesInDir(dir) {
10456
10913
  function scanSubdirKpiFiles(dir, depth, acc) {
10457
10914
  if (depth > 4) return;
10458
10915
  try {
10459
- const entries = fs6__namespace.readdirSync(dir, { withFileTypes: true });
10916
+ const entries = fs7__namespace.readdirSync(dir, { withFileTypes: true });
10460
10917
  for (const entry of entries) {
10461
10918
  if (!entry.isDirectory() || IGNORED_SCAN_DIRS.has(entry.name)) continue;
10462
- const sub = path5__namespace.join(dir, entry.name);
10463
- if (entry.name === "releases" && path5__namespace.basename(dir) === "reports") {
10919
+ const sub = path4__namespace.join(dir, entry.name);
10920
+ if (entry.name === "releases" && path4__namespace.basename(dir) === "reports") {
10464
10921
  acc.push(...readKpiFilesInDir(sub));
10465
10922
  } else {
10466
10923
  scanSubdirKpiFiles(sub, depth + 1, acc);
@@ -10470,7 +10927,7 @@ function scanSubdirKpiFiles(dir, depth, acc) {
10470
10927
  }
10471
10928
  }
10472
10929
  function findKpiJsonFiles(rootDir) {
10473
- const primaryFiles = readKpiFilesInDir(path5__namespace.join(rootDir, "reports", "releases"));
10930
+ const primaryFiles = readKpiFilesInDir(path4__namespace.join(rootDir, "reports", "releases"));
10474
10931
  if (primaryFiles.length > 0) return primaryFiles;
10475
10932
  const found = [];
10476
10933
  scanSubdirKpiFiles(rootDir, 0, found);
@@ -10481,7 +10938,7 @@ function collectHistoricalKpis(rootDir) {
10481
10938
  const kpiFiles = findKpiJsonFiles(rootDir);
10482
10939
  for (const fullPath of kpiFiles) {
10483
10940
  try {
10484
- const content = fs6__namespace.readFileSync(fullPath, "utf-8");
10941
+ const content = fs7__namespace.readFileSync(fullPath, "utf-8");
10485
10942
  const report = JSON.parse(content);
10486
10943
  if (!items.some((it) => it.id === report.releaseBranch && it.timestamp === report.generatedAt)) {
10487
10944
  items.push({
@@ -10678,15 +11135,15 @@ function computeGateAverages(gateResults) {
10678
11135
  }
10679
11136
  function generateDashboardReport(options = {}) {
10680
11137
  const rootDir = options.rootDir || process.cwd();
10681
- const outputPath = options.outputPath || path5__namespace.join(rootDir, "reports", "dashboard.html");
11138
+ const outputPath = options.outputPath || path4__namespace.join(rootDir, "reports", "dashboard.html");
10682
11139
  const graphData = buildGraphElements(rootDir);
10683
11140
  const metricsData = collectDashboardMetrics(rootDir);
10684
11141
  const html = renderDashboardHtml(graphData, metricsData, options.title);
10685
- const outDir = path5__namespace.dirname(outputPath);
10686
- if (!fs6__namespace.existsSync(outDir)) {
10687
- fs6__namespace.mkdirSync(outDir, { recursive: true });
11142
+ const outDir = path4__namespace.dirname(outputPath);
11143
+ if (!fs7__namespace.existsSync(outDir)) {
11144
+ fs7__namespace.mkdirSync(outDir, { recursive: true });
10688
11145
  }
10689
- fs6__namespace.writeFileSync(outputPath, html, "utf-8");
11146
+ fs7__namespace.writeFileSync(outputPath, html, "utf-8");
10690
11147
  const { bddCoverageRatio, traceabilityRatio } = computeSummaryRatios(metricsData);
10691
11148
  const { avgMi, avgCc } = computeGateAverages(metricsData.gate.results);
10692
11149
  return {
@@ -10782,18 +11239,25 @@ var SPECIALIZED_AGENT_TEMPLATES = [
10782
11239
  {
10783
11240
  id: "agent-qa-engineer",
10784
11241
  title: "QA Engineer and SDET",
10785
- description: "Translate approved requirements (FR-*, SEC-REQ-*, QR-*) into comprehensive FAILING (red) BDD/Gherkin and unit test suites before production code is written.",
11242
+ description: "Translate approved requirements (FR-*, SEC-REQ-*, QR-*) into comprehensive FAILING (red) BDD/Gherkin, End-to-End (E2E), UI, and unit test suites before production code is written.",
10786
11243
  globs: "tests/**, specs/changes/**/spec.md",
10787
11244
  body: `## Role & Mission
10788
11245
  - **Role**: QA Engineer and SDET (Software Development Engineer in Test) Agent (\`agent-qa-engineer\`).
10789
- - **Mission**: Translate approved requirements (\`FR-*\`, \`SEC-REQ-*\`, \`QR-*\`) into comprehensive FAILING (red) BDD/Gherkin test suites before \`agent-developer\` writes a single line of production code.
11246
+ - **Mission**: Translate approved requirements (\`FR-*\`, \`SEC-REQ-*\`, \`QR-*\`) into comprehensive FAILING (red) BDD/Gherkin, End-to-End (E2E), UI, and unit test suites before \`agent-developer\` writes a single line of production code.
10790
11247
 
10791
11248
  ## Operational Directives & Guardrails
10792
11249
  - Read exclusively requirements cited in the \`handoff.yaml\` sidecar (\`HOF-*\`) and their referenced artifacts.
10793
- - For each \`FR-*\` requirement, generate the three mandatory test categories:
10794
- 1. **Nominal Case** (\`Scenario\`): happy path with valid inputs within expected boundaries.
10795
- 2. **Boundary Cases** (\`Scenario Outline\` + \`Examples\` table): values at the extremes of the accepted range.
10796
- 3. **Out-of-Range / Invalid Cases** (\`Scenario Outline\` + \`Examples\` table): null, empty, wrong types, values exceeding bounds.
11250
+ - **Agreed Project Technology**: Always use the testing frameworks, E2E/UI runners, and technology stack agreed upon for the project (as defined in \`ADR-*\`, \`design.md\`, \`package.json\`, or project configuration) if present, without requiring the user to specify it in the prompt.
11251
+ - **Mandatory End-to-End (E2E) & User Interface (UI) Testing**: Never forget or omit End-to-End (E2E) and User Interface (UI) tests. Automatically include E2E and UI test suites alongside BDD/integration tests whenever requirements involve user-facing flows or interfaces, so the user never has to remind you in the prompt.
11252
+ - **Per-Interactive-Element Exhaustive Testing**: Create specific tests for **every** UI element with which the user can interact (buttons, inputs, selects, forms, modals, links, toggles, tabs, etc.), explicitly evaluating for each element:
11253
+ 1. **Happy Path**: nominal user interaction with valid inputs producing the expected UI state/response.
11254
+ 2. **Sad Path**: error handling, invalid interactions, validation rejections, disabled/blocked states, and failure recovery.
11255
+ 3. **Boundary Values**: minimum/maximum lengths, numeric extremes, empty/whitespace inputs, edge selections, and rapid/repeated interactions.
11256
+ - For each \`FR-*\` requirement, generate the three mandatory test categories tagged with \`@nominal\`, \`@boundary\`, and \`@invalid\`:
11257
+ 1. **Nominal Case** (\`@nominal\` \`Scenario\`): happy path with valid inputs within expected boundaries.
11258
+ 2. **Boundary Cases** (\`@boundary\` \`Scenario Outline\` + \`Examples\` table): values at the extremes of the accepted range.
11259
+ 3. **Out-of-Range / Invalid Cases** (\`@invalid\` \`Scenario Outline\` + \`Examples\` table): null, empty, wrong types, values exceeding bounds.
11260
+ - **Use Case Variant Coverage**: Inspect every \`UC-*\` in \`handoff.yaml\` and generate BDD scenarios covering 100% of its \`alternative-flows\` and \`exception-flows\`.
10797
11261
  - Apply conditional categories when applicable: Security (\`@security @mitigation\`), Performance (\`@performance\`), Idempotency (\`@idempotence\`), Postconditions, and Interface Contracts.
10798
11262
  - **Zero-Mock Integration Rule**: When an \`FR-*\` spans UI and Backend, unit tests mocking \`fetch\`/HTTP in isolation are insufficient; include integration/contract or E2E test scenarios verifying real request/response wiring and UI state transitions (\`Loading\`, \`Empty\`, \`Error\`, \`Nominal\`).
10799
11263
  - Tag all scenarios with \`@<FR-ID> @automated @regression\`.
@@ -10810,9 +11274,21 @@ var SPECIALIZED_AGENT_TEMPLATES = [
10810
11274
  - **Role**: High-Precision Software Developer / Coder Agent (\`agent-developer\`).
10811
11275
  - **Mission**: Implement atomic tasks from SDD specifications (\`tasks.md\`) delivering clean, strictly typed, and tested code.
10812
11276
 
11277
+ ## Mandatory 4-Step Implementation Order
11278
+ Execute every implementation task following this strict sequential order:
11279
+ 1. **Clean Architecture Definition / Redefinition**: Define or refine the module/layer architecture adhering to Clean Architecture patterns (Domain/Entities, Application/Use Cases, Interface Adapters, Frameworks/Drivers), strictly enforcing the Dependency Rule (dependencies point inward toward the domain).
11280
+ 2. **Inter-Module Interfaces First**: Define explicit interfaces/ports (contracts, DTOs, types) for all interactions across module boundaries before writing concrete logic, avoiding direct coupling between modules.
11281
+ 3. **Component Implementation**: Implement the concrete components, use cases, and adapters that implement and consume the defined interfaces.
11282
+ 4. **Exhaustive Per-Function Unit Testing**: Write unit tests for every function in \`tests/unit/\`, always verifying:
11283
+ - **Happy Path**: nominal execution with valid inputs.
11284
+ - **Sad Path**: controlled error paths, rejections, and business exceptions.
11285
+ - **Boundary Values**: minimum, maximum, exact limits, and off-by-one edges.
11286
+ - **Unexpected, Null & Empty Values**: \`null\`, \`undefined\`, empty strings/arrays/objects, and malformed inputs.
11287
+
10813
11288
  ## Operational Directives & Guardrails
10814
11289
  - Respect the autonomy mode assigned to each task in \`tasks.md\` (\`AUTONOMOUS\`, \`HUMAN_REVIEW_PLAN\`, \`AMBIGUOUS\`, \`HIGH_RISK_MANUAL\`).
10815
- - Apply Test-Driven Development (TDD): make failing tests delivered by \`agent-qa-engineer\` pass green without altering tests to accommodate buggy code.
11290
+ - **100% Design & Handoff Scope Coverage in Tasks**: Ensure every task in \`tasks.md\` declares \`implements-components\` (\`CMP-*\`), \`satisfies-requirements\` (\`FR-*\`, \`SEC-REQ-*\`), and \`implements-contracts\` so 100% of \`design.md\` and \`handoff.yaml\` is mapped and implemented.
11291
+ - Apply Test-Driven Development (TDD): make failing tests delivered by \`agent-qa-engineer\` pass green without altering tests to accommodate buggy code, and write direct unit tests in \`tests/unit/\` for every exported function and contract method declared in \`design.md\` or implemented in \`src/\`.
10816
11292
  - Cite implemented \`FR-*\` or \`SEC-REQ-*\` IDs in test header comments to maintain 360\xB0 reverse traceability.
10817
11293
  - **Zero Fake-Wiring Guardrail**: Strictly forbidden from marking a task \`COMPLETED\` with hardcoded \`mockData\` arrays in production views, \`setTimeout\` simulating network calls, or empty UI event handlers (\`onClick={() => {}}\`, \`onSubmit={() => {}}\`). Frontend components must wire directly to the shared contract and real Backend endpoints.
10818
11294
  - **Mandatory 4 UI States Matrix**: Every data-driven UI component or screen must explicitly implement all 4 usability states: \`Loading\` (spinner/skeleton + disabled submit), \`Empty\` (clear message + CTA), \`Error\` (visible error recovery banner, never silent failure), and \`Nominal/Success\` (accessible semantic DOM + keyboard navigation).
@@ -11006,11 +11482,14 @@ This file defines the operational rules and specialized role mappings for Google
11006
11482
  - Emit a return handoff to \`agent-threat-modeler\` in the technical security feedback loop when infrastructure or persistence decisions introduce new attack vectors.
11007
11483
 
11008
11484
  ### 4. \`agent-qa-engineer\` (QA Engineer and SDET)
11009
- - **Mission**: Translate approved requirements (\`FR-*\`, \`SEC-REQ-*\`, \`QR-*\`) into comprehensive FAILING (red) BDD/Gherkin test suites, before \`agent-developer\` writes production code.
11485
+ - **Mission**: Translate approved requirements (\`FR-*\`, \`SEC-REQ-*\`, \`QR-*\`) into comprehensive FAILING (red) BDD/Gherkin, End-to-End (E2E), UI, and unit test suites, before \`agent-developer\` writes production code.
11010
11486
  - **Input**: Approved requirements from the \`handoff.yaml\` sidecar (\`HOF-*\`).
11011
- - **Output**: Failing Gherkin scenarios covering: nominal case, boundary cases, out-of-range cases, and conditional categories (security, performance, idempotency, postconditions, interface contract).
11487
+ - **Output**: Failing Gherkin, E2E, and UI test suites covering: nominal case, boundary cases, out-of-range cases, per-interactive-element tests (Happy Path, Sad Path, Boundary Values), and conditional categories (security, performance, idempotency, postconditions, interface contract).
11012
11488
  - **Guardrails**:
11013
11489
  - FORBIDDEN to include production code or anticipate implementations.
11490
+ - **Agreed Project Technology**: Always use the testing frameworks, E2E/UI runners, and technology stack agreed upon for the project (if any), without requiring the user to specify it in the prompt.
11491
+ - **Mandatory E2E & UI Testing**: Never forget or omit End-to-End (E2E) and User Interface (UI) tests.
11492
+ - **Per-Interactive-Element Coverage**: Create specific tests for every UI element the user can interact with, covering Happy Path, Sad Path, and Boundary Values for each one.
11014
11493
  - Every \`FR-*\` must have at least nominal + boundary + out-of-range scenarios; missing these blocks handoff to \`agent-developer\`.
11015
11494
  - Mandatory tags: \`@<FR-ID> @automated @regression\`.
11016
11495
  - Run \`pnpm run verify:testing\` before emitting handoff.
@@ -11018,6 +11497,7 @@ This file defines the operational rules and specialized role mappings for Google
11018
11497
  ### 5. \`agent-developer\` (Software Developer)
11019
11498
  - **Mission**: Implement atomic tasks from SDD specifications (\`tasks.md\`) with clean, strictly typed code and thorough tests.
11020
11499
  - **Directives**:
11500
+ - **Mandatory 4-Step Implementation Order**: (1) Define/refine Clean Architecture layers & boundaries, (2) Define interfaces/ports for all inter-module interactions to avoid direct module coupling, (3) Implement components consuming/fulfilling those interfaces, (4) Write unit tests for every function covering Happy Path, Sad Path, Boundary Values, and Unexpected/Null/Empty values.
11021
11501
  - Respect the autonomy mode assigned in \`tasks.md\` (\`AUTONOMOUS\`, \`HUMAN_REVIEW_PLAN\`, \`AMBIGUOUS\`, \`HIGH_RISK_MANUAL\`).
11022
11502
  - Make tests delivered by \`agent-qa-engineer\` pass green without altering them to accommodate code.
11023
11503
  - Respect \`quality-policy.yaml\` thresholds: CC $\\le 10$, Cognitive $\\le 15$, MI $\\ge 50$, LOC $\\le 40$.
@@ -11116,7 +11596,7 @@ Before finalizing changes or proposing commits:
11116
11596
  | \`agent-product-analyst\` | Phase 0 NAF v4 Enterprise & Capability Vision (\`ARCH-INTRO-*\`, 3\u20135 question interview) + ProductShape artifacts (\`ACT-*\`, \`UC-*\`, \`FR-*\`, \`QR-*\`, \`BR-*\`); status starts \`draft\`. |
11117
11597
  | \`agent-threat-modeler\` | STRIDE/OWASP threat triad (\`ACT-THREAT-*\`, \`ABUSE-*\`, \`SEC-REQ-*\`). |
11118
11598
  | \`agent-system-architect\` | Modular arc42 + NAF v4 architecture (\`CMP-*\`, Mermaid diagrams, \`ADR-*\`). |
11119
- | \`agent-qa-engineer\` | FAILING BDD/Gherkin suites before \`agent-developer\` writes code. |
11599
+ | \`agent-qa-engineer\` | FAILING BDD/Gherkin, E2E, and UI suites (using agreed project tech; Happy Path, Sad Path & Boundary Values per interactive UI element) before \`agent-developer\` writes code. |
11120
11600
  | \`agent-developer\` | Atomic tasks from \`tasks.md\`; respects autonomy mode; runs \`pnpm run check:fix\` + \`pnpm run verify:all\`. |
11121
11601
  | \`agent-security-auditor\` | Adversarial PR security audit, SAST, secrets, and CVSS v3.1 evaluation. |
11122
11602
  | \`agent-compliance-checker\` | SPDX license compliance audit against \`license-policy.yaml\`. |
@@ -11183,7 +11663,8 @@ Canonical Reference: [\`process/09_agent_protocols.md\`](process/09_agent_protoc
11183
11663
  - **Function Length**: $\\le 40$ lines (warning if $> 30$).
11184
11664
  - If a function exceeds these limits, decompose logic into cohesive helper functions before completing the task.
11185
11665
 
11186
- ## 2. TDD Methodology and Unit Testing
11666
+ ## 2. TDD Methodology, Clean Architecture & Unit Testing
11667
+ - **Mandatory 4-Step Implementation Order**: (1) Define/refine Clean Architecture layers & boundaries, (2) Define interfaces/ports for all inter-module interactions to avoid direct module coupling, (3) Implement components consuming/fulfilling those interfaces, (4) Write unit tests for every function covering Happy Path, Sad Path, Boundary Values, and Unexpected/Null/Empty values.
11187
11668
  - Apply Test-Driven Development: generate unit tests before or alongside component logic.
11188
11669
  - Tag tests linked to requirements with header comments citing the ID (\`FR-*\` or \`SEC-REQ-*\`) to enable reverse traceability.
11189
11670
  - Suppressing types (\`any\`), linter rules (\`eslint-disable\`), or unit tests to force pipeline passage is prohibited.
@@ -11256,12 +11737,12 @@ npx tsx packages/cli/src/index.ts sdd integrate --change <chg-id>
11256
11737
  - **Guardrails**: Declare \`satisfies-requirements\` on every \`CMP-*\` and trigger the technical security feedback loop toward \`agent-threat-modeler\` when infrastructure decisions introduce new attack vectors.
11257
11738
 
11258
11739
  ### 4. \`agent-qa-engineer\` (QA Engineer and SDET)
11259
- - **Mission**: Translate approved requirements (\`FR-*\`, \`SEC-REQ-*\`, \`QR-*\`) into comprehensive FAILING (red) BDD/Gherkin test suites, before \`agent-developer\` writes production code.
11260
- - **Guardrails**: FORBIDDEN to include production code. Mandatory tags: \`@<FR-ID> @automated @regression\`.
11740
+ - **Mission**: Translate approved requirements (\`FR-*\`, \`SEC-REQ-*\`, \`QR-*\`) into comprehensive FAILING (red) BDD/Gherkin, End-to-End (E2E), UI, and unit test suites, before \`agent-developer\` writes production code.
11741
+ - **Guardrails**: Always use the agreed project technology/testing stack (if any). Never omit End-to-End (E2E) and UI tests. Create specific tests for every interactive UI element covering Happy Path, Sad Path, and Boundary Values. FORBIDDEN to include production code. Mandatory tags: \`@<FR-ID> @automated @regression\`.
11261
11742
 
11262
11743
  ### 5. \`agent-developer\` (Software Developer)
11263
11744
  - **Mission**: Implement atomic tasks from SDD specifications (\`tasks.md\`) with clean, strictly typed code and thorough tests.
11264
- - **Directives**: Respect the autonomy mode. Make tests delivered by \`agent-qa-engineer\` pass green. Run \`pnpm run check:fix\` and \`pnpm run verify:all\`.
11745
+ - **Directives**: Execute the mandatory 4-step order: (1) Clean Architecture definition/redefinition, (2) Inter-module interfaces first, (3) Component implementation, (4) Per-function unit tests (Happy Path, Sad Path, Boundary Values, Unexpected/Null/Empty values). Respect the autonomy mode. Make tests delivered by \`agent-qa-engineer\` pass green. Run \`pnpm run check:fix\` and \`pnpm run verify:all\`.
11265
11746
 
11266
11747
  ### 6. \`agent-security-auditor\` (Adversarial Code Auditor)
11267
11748
  - **Mission**: Examine Pull Requests for business logic vulnerabilities, injection vectors, secret leaks, and authorization flaws as part of the Pre-Merge Audit Triad.
@@ -11406,7 +11887,7 @@ Non-Negotiable Quality Thresholds (\`quality-policy.yaml\`):
11406
11887
  | \`agent-product-analyst\` | Phase 0 NAF v4 Enterprise & Capability Vision (\`ARCH-INTRO-*\`, 3\u20135 question interview) + ProductShape artifacts (\`ACT-*\`, \`UC-*\`, \`FR-*\`, \`QR-*\`, \`BR-*\`); \`status\` starts \`draft\`. |
11407
11888
  | \`agent-threat-modeler\` | STRIDE/OWASP threat triad (\`ACT-THREAT-*\`, \`ABUSE-*\`, \`SEC-REQ-*\`). |
11408
11889
  | \`agent-system-architect\` | Modular arc42 + NAF v4 architecture (\`CMP-*\`, Mermaid diagrams, \`ADR-*\`). |
11409
- | \`agent-qa-engineer\` | FAILING BDD/Gherkin suites before \`agent-developer\` writes production code. |
11890
+ | \`agent-qa-engineer\` | FAILING BDD/Gherkin, E2E, and UI suites (using agreed project tech; Happy Path, Sad Path & Boundary Values per interactive UI element) before \`agent-developer\` writes production code. |
11410
11891
  | \`agent-developer\` | Atomic tasks from \`tasks.md\`; autonomy mode must be respected; runs \`pnpm run check:fix\` + \`pnpm run verify:all\`. |
11411
11892
  | \`agent-security-auditor\` | Adversarial PR security audit, SAST, secrets, and CVSS v3.1 evaluation. |
11412
11893
  | \`agent-compliance-checker\` | SPDX license compliance audit against \`license-policy.yaml\`. |
@@ -12832,14 +13313,14 @@ function collectAgentFiles(targets) {
12832
13313
  function writeProjectFiles(destDir, files, dryRun) {
12833
13314
  const filesCreated = [];
12834
13315
  for (const f of files) {
12835
- const fullFilePath = path5__namespace.join(destDir, f.relPath);
12836
- const parentDir = path5__namespace.dirname(fullFilePath);
13316
+ const fullFilePath = path4__namespace.join(destDir, f.relPath);
13317
+ const parentDir = path4__namespace.dirname(fullFilePath);
12837
13318
  if (!dryRun) {
12838
- if (!fs6__namespace.existsSync(parentDir)) {
12839
- fs6__namespace.mkdirSync(parentDir, { recursive: true });
13319
+ if (!fs7__namespace.existsSync(parentDir)) {
13320
+ fs7__namespace.mkdirSync(parentDir, { recursive: true });
12840
13321
  }
12841
- if (!fs6__namespace.existsSync(fullFilePath)) {
12842
- fs6__namespace.writeFileSync(fullFilePath, f.content, "utf-8");
13322
+ if (!fs7__namespace.existsSync(fullFilePath)) {
13323
+ fs7__namespace.writeFileSync(fullFilePath, f.content, "utf-8");
12843
13324
  filesCreated.push(f.relPath);
12844
13325
  }
12845
13326
  } else {
@@ -12912,7 +13393,7 @@ function getCiWorkflowFiles(ciProvider) {
12912
13393
  function createDirectoriesOnDisk(destDir, directories, dryRun) {
12913
13394
  const created = [];
12914
13395
  for (const d of directories) {
12915
- if (!dryRun) fs6__namespace.mkdirSync(path5__namespace.join(destDir, d), { recursive: true });
13396
+ if (!dryRun) fs7__namespace.mkdirSync(path4__namespace.join(destDir, d), { recursive: true });
12916
13397
  created.push(d);
12917
13398
  }
12918
13399
  return created;
@@ -12930,13 +13411,13 @@ function collectStarterFiles(ciProvider, agentFiles = [], archGranularity = "min
12930
13411
  ...getCiWorkflowFiles(ciProvider),
12931
13412
  ...agentFiles,
12932
13413
  ...getArchitectureTemplates(archGranularity).map((t) => ({
12933
- relPath: path5__namespace.join("templates", "architecture", t.filename).replace(/\\/g, "/"),
13414
+ relPath: path4__namespace.join("templates", "architecture", t.filename).replace(/\\/g, "/"),
12934
13415
  content: t.content
12935
13416
  }))
12936
13417
  ];
12937
13418
  }
12938
13419
  function tryInstallGitHook(destDir, dryRun) {
12939
- if (!dryRun && fs6__namespace.existsSync(path5__namespace.join(destDir, ".git"))) {
13420
+ if (!dryRun && fs7__namespace.existsSync(path4__namespace.join(destDir, ".git"))) {
12940
13421
  return installGitHooks(destDir).success;
12941
13422
  }
12942
13423
  return false;
@@ -12978,7 +13459,7 @@ function buildInitSuccessResult(destDir, directoriesCreated, filesCreated, ciPro
12978
13459
  }
12979
13460
  function initProject(options = {}) {
12980
13461
  const baseDir = options.rootDir || process.cwd();
12981
- const destDir = path5__namespace.resolve(baseDir, options.targetDir || ".");
13462
+ const destDir = path4__namespace.resolve(baseDir, options.targetDir || ".");
12982
13463
  const ciRes = validateCiProvider(options.ci);
12983
13464
  if (!ciRes.valid) return buildInitError(destDir, ciRes.error);
12984
13465
  const archRes = validateArchitectureGranularity(options.architecture);
@@ -13058,6 +13539,7 @@ exports.VALID_CI_PROVIDERS = VALID_CI_PROVIDERS;
13058
13539
  exports.aggregateCommitKpiRecords = aggregateCommitKpiRecords;
13059
13540
  exports.aggregatePrKpis = aggregatePrKpis;
13060
13541
  exports.aggregateReleaseKpis = aggregateReleaseKpis;
13542
+ exports.analyzeBddVariantsForRequirement = analyzeBddVariantsForRequirement;
13061
13543
  exports.appendRevisionHistoryRow = appendRevisionHistoryRow;
13062
13544
  exports.buildCycloneDxComponent = buildCycloneDxComponent;
13063
13545
  exports.buildFeatureFileContent = buildFeatureFileContent;