@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.js CHANGED
@@ -1,6 +1,6 @@
1
- import * as fs6 from 'fs';
2
- import * as path5 from 'path';
3
- import yaml2 from 'js-yaml';
1
+ import * as fs7 from 'fs';
2
+ import * as path4 from 'path';
3
+ import yaml3 from 'js-yaml';
4
4
  import { SyntaxKind, Project, Node } from 'ts-morph';
5
5
  import * as crypto from 'crypto';
6
6
  import * as child_process from 'child_process';
@@ -170,7 +170,7 @@ function buildNodeMetrics(node, filePath) {
170
170
  return { functionName, filePath, loc, cyclomatic, cognitive, maintainability: mi, codeSmells };
171
171
  }
172
172
  function createTempSourceFile(project, content, filePath) {
173
- const ext = path5.extname(filePath).toLowerCase() || ".ts";
173
+ const ext = path4.extname(filePath).toLowerCase() || ".ts";
174
174
  const fileName = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 7)}${ext}`;
175
175
  return project.createSourceFile(fileName, content, { overwrite: true });
176
176
  }
@@ -207,7 +207,7 @@ function extractFunctionsTypeScriptAst(content, filePath) {
207
207
  }
208
208
  }
209
209
  function extractFunctionsPolyglot(content, filePath) {
210
- const ext = path5.extname(filePath).toLowerCase();
210
+ const ext = path4.extname(filePath).toLowerCase();
211
211
  return ext === ".py" ? extractPythonFunctions(content, filePath) : extractBraceFunctions(content, filePath);
212
212
  }
213
213
  function isDocstringToggle(trimmed) {
@@ -284,7 +284,7 @@ function processBraceLine(line, state, blocks, lineIndex, ext) {
284
284
  }
285
285
  }
286
286
  function extractBraceFunctions(content, filePath) {
287
- const ext = path5.extname(filePath).toLowerCase();
287
+ const ext = path4.extname(filePath).toLowerCase();
288
288
  const lines = content.split("\n");
289
289
  const blocks = [];
290
290
  const state = { currentFn: null, braceCount: 0, fnLines: [], inBlockComment: false };
@@ -429,7 +429,7 @@ function countCommentsAndLoc(rawLines, isPy) {
429
429
  return { loc, commentCount, cleanLines };
430
430
  }
431
431
  function calculatePolyglotMetrics(body, name, filePath) {
432
- const ext = path5.extname(filePath).toLowerCase();
432
+ const ext = path4.extname(filePath).toLowerCase();
433
433
  const rawLines = body.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
434
434
  const { loc, commentCount, cleanLines } = countCommentsAndLoc(rawLines, ext === ".py");
435
435
  const cyclomatic = computePolyglotCyclomatic(body, ext);
@@ -479,6 +479,652 @@ function computePolyglotCognitive(lines, isPy) {
479
479
  }
480
480
  return cognitive;
481
481
  }
482
+ function walkMdFiles(dir, fileList = [], excludeDirs = [
483
+ "node_modules",
484
+ ".git",
485
+ "dist",
486
+ "bin",
487
+ "obj",
488
+ "build",
489
+ "out",
490
+ "target",
491
+ "coverage",
492
+ ".changeset",
493
+ "scratch",
494
+ "test-scaffold",
495
+ "fixtures",
496
+ "templates",
497
+ "examples",
498
+ ".agent",
499
+ ".agents",
500
+ ".claude",
501
+ ".cursor",
502
+ ".github",
503
+ ".vscode",
504
+ ".vs",
505
+ ".idea"
506
+ ]) {
507
+ try {
508
+ if (!fs7.existsSync(dir)) return fileList;
509
+ const files = fs7.readdirSync(dir);
510
+ for (const file of files) {
511
+ const fullPath = path4.join(dir, file);
512
+ try {
513
+ if (fs7.statSync(fullPath).isDirectory()) {
514
+ if (!excludeDirs.includes(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
515
+ walkMdFiles(fullPath, fileList, excludeDirs);
516
+ }
517
+ } else if (file.endsWith(".md")) {
518
+ fileList.push(fullPath);
519
+ }
520
+ } catch {
521
+ }
522
+ }
523
+ } catch {
524
+ }
525
+ return fileList;
526
+ }
527
+ function parseMarkdownFrontmatter(content) {
528
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
529
+ if (!match) return { frontmatter: {}, body: content };
530
+ const yamlLines = match[1].split("\n");
531
+ const frontmatter = {};
532
+ for (const line of yamlLines) {
533
+ const parts = line.split(":");
534
+ if (parts.length >= 2) {
535
+ const key = parts[0].trim();
536
+ let val = parts.slice(1).join(":").trim();
537
+ if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
538
+ val = val.substring(1, val.length - 1);
539
+ }
540
+ frontmatter[key] = val;
541
+ }
542
+ }
543
+ const body = content.substring(match[0].length);
544
+ return { frontmatter, body };
545
+ }
546
+ function extractGherkinBlock(body) {
547
+ const gherkinRegex = /```gherkin\r?\n([\s\S]*?)\r?\n```/g;
548
+ const blocks = [];
549
+ let match;
550
+ while ((match = gherkinRegex.exec(body)) !== null) {
551
+ blocks.push(match[1].trim());
552
+ }
553
+ return blocks;
554
+ }
555
+ function resolveTargetFiles(rootDir, targetPath) {
556
+ if (targetPath) {
557
+ const fullTarget = path4.isAbsolute(targetPath) ? targetPath : path4.join(rootDir, targetPath);
558
+ if (!fs7.existsSync(fullTarget)) return [];
559
+ return fs7.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
560
+ }
561
+ const searchDirs = [
562
+ path4.join(rootDir, "product"),
563
+ path4.join(rootDir, "security"),
564
+ path4.join(rootDir, "specs"),
565
+ path4.join(rootDir, "examples", "product"),
566
+ path4.join(rootDir, "examples", "security"),
567
+ path4.join(rootDir, "examples", "specs")
568
+ ];
569
+ return searchDirs.filter((d) => fs7.existsSync(d)).flatMap((d) => walkMdFiles(d));
570
+ }
571
+ function resolveFeatureFilePath(rootDir, filePath, id, customTarget) {
572
+ if (customTarget) {
573
+ return path4.isAbsolute(customTarget) ? customTarget : path4.join(rootDir, customTarget);
574
+ }
575
+ const baseDir = path4.join(rootDir, "tests", "features");
576
+ const candidateSecurity = path4.join(baseDir, "security", `${id.toLowerCase()}.feature`);
577
+ const candidateRoot = path4.join(baseDir, `${id.toLowerCase()}.feature`);
578
+ if (fs7.existsSync(candidateSecurity)) return candidateSecurity;
579
+ if (fs7.existsSync(candidateRoot)) return candidateRoot;
580
+ const subDir = filePath.includes("security") ? "security" : "";
581
+ return path4.join(baseDir, subDir, `${id.toLowerCase()}.feature`);
582
+ }
583
+ function buildFeatureFileContent(rootDir, filePath, id, version, gherkinBlocks) {
584
+ const banner = [
585
+ `# ==============================================================================`,
586
+ `# AUTO-GENERADO POR AI-SDLC (Cucumber Integration)`,
587
+ `# Origen: ${path4.relative(rootDir, filePath).replace(/\\/g, "/")}`,
588
+ `# ID Requerimiento: ${id}`,
589
+ `# Versi\xF3n: ${version || "1.0.0"}`,
590
+ `# NO EDITAR MANUALMENTE: Cualquier cambio debe realizarse en el Markdown origen.`,
591
+ `# ==============================================================================`,
592
+ "",
593
+ ""
594
+ ].join("\n");
595
+ return banner + gherkinBlocks.join("\n\n") + "\n";
596
+ }
597
+ function countScenarios(gherkinBlocks) {
598
+ let count = 0;
599
+ for (const block of gherkinBlocks) {
600
+ const matches = block.match(/\b(Scenario|Escenario|Scenario Outline|Esquema del escenario):/g);
601
+ if (matches) count += matches.length;
602
+ }
603
+ return count;
604
+ }
605
+ function extractGherkinFeatures(options = {}) {
606
+ const rootDir = options.rootDir || process.cwd();
607
+ const features = [];
608
+ let totalScenarios = 0;
609
+ const targetFiles = resolveTargetFiles(rootDir, options.targetPath);
610
+ for (const filePath of targetFiles) {
611
+ try {
612
+ const content = fs7.readFileSync(filePath, "utf-8");
613
+ const { frontmatter, body } = parseMarkdownFrontmatter(content);
614
+ const gherkinBlocks = extractGherkinBlock(body);
615
+ if (gherkinBlocks.length === 0) continue;
616
+ const id = frontmatter.id || path4.basename(filePath, path4.extname(filePath));
617
+ const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
618
+ fs7.mkdirSync(path4.dirname(targetPath), { recursive: true });
619
+ const fullContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
620
+ fs7.writeFileSync(targetPath, fullContent, "utf-8");
621
+ const scenarioCount = countScenarios(gherkinBlocks);
622
+ totalScenarios += scenarioCount;
623
+ features.push({
624
+ sourceFile: path4.relative(rootDir, filePath).replace(/\\/g, "/"),
625
+ outputFile: path4.relative(rootDir, targetPath).replace(/\\/g, "/"),
626
+ featureName: id,
627
+ scenarioCount
628
+ });
629
+ } catch {
630
+ }
631
+ }
632
+ return { success: true, features, totalScenarios };
633
+ }
634
+ function checkGherkinInSync(options = {}) {
635
+ const rootDir = options.rootDir || process.cwd();
636
+ const targetFiles = resolveTargetFiles(rootDir, options.targetPath);
637
+ const outOfSyncFiles = [];
638
+ const missingFiles = [];
639
+ let totalFeatures = 0;
640
+ for (const filePath of targetFiles) {
641
+ try {
642
+ const content = fs7.readFileSync(filePath, "utf-8");
643
+ const { frontmatter, body } = parseMarkdownFrontmatter(content);
644
+ const gherkinBlocks = extractGherkinBlock(body);
645
+ if (gherkinBlocks.length === 0) continue;
646
+ totalFeatures++;
647
+ const id = frontmatter.id || path4.basename(filePath, path4.extname(filePath));
648
+ const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
649
+ const relTarget = path4.relative(rootDir, targetPath).replace(/\\/g, "/");
650
+ if (!fs7.existsSync(targetPath)) {
651
+ missingFiles.push(relTarget);
652
+ continue;
653
+ }
654
+ const expectedContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
655
+ const actualContent = fs7.readFileSync(targetPath, "utf-8");
656
+ if (actualContent.replace(/\r\n/g, "\n") !== expectedContent.replace(/\r\n/g, "\n")) {
657
+ outOfSyncFiles.push(relTarget);
658
+ }
659
+ } catch {
660
+ }
661
+ }
662
+ return {
663
+ inSync: missingFiles.length === 0 && outOfSyncFiles.length === 0,
664
+ outOfSyncFiles,
665
+ missingFiles,
666
+ totalFeatures
667
+ };
668
+ }
669
+
670
+ // src/verifiers/testing-coverage.ts
671
+ function parseTaskVerification(v) {
672
+ const verification = {};
673
+ if (v && typeof v === "object") {
674
+ const rec = v;
675
+ if (typeof rec["method"] === "string") verification.method = rec["method"];
676
+ if (typeof rec["command-or-criteria"] === "string") verification.criteria = rec["command-or-criteria"];
677
+ }
678
+ return verification;
679
+ }
680
+ function parseTaskItem(t) {
681
+ return {
682
+ id: typeof t["id"] === "string" ? t["id"] : String(t["id"] ?? ""),
683
+ title: typeof t["title"] === "string" ? t["title"] : void 0,
684
+ verification: parseTaskVerification(t["verification"])
685
+ };
686
+ }
687
+ function parseVerifiedByTests(verifiedBy) {
688
+ if (Array.isArray(verifiedBy)) {
689
+ return verifiedBy.map(String);
690
+ }
691
+ if (typeof verifiedBy === "string") {
692
+ return [verifiedBy];
693
+ }
694
+ return void 0;
695
+ }
696
+ function parseGenericDoc(content) {
697
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
698
+ if (!match) return { frontmatter: {}, body: content };
699
+ let parsed;
700
+ try {
701
+ parsed = yaml3.load(match[1]);
702
+ } catch {
703
+ return { frontmatter: {}, body: content };
704
+ }
705
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
706
+ return { frontmatter: {}, body: content };
707
+ }
708
+ const raw = parsed;
709
+ const rawTasks = Array.isArray(raw["tasks"]) ? raw["tasks"] : [];
710
+ const tasks = rawTasks.map(parseTaskItem);
711
+ const frontmatter = {
712
+ ...raw,
713
+ tasks,
714
+ "verified-by-tests": parseVerifiedByTests(raw["verified-by-tests"])
715
+ };
716
+ return { frontmatter, body: content.substring(match[0].length) };
717
+ }
718
+ function buildRequirementsTable(reqAudit) {
719
+ const lines = [
720
+ `## 1. Requirements Audit (Product, Quality, and Security)`,
721
+ ``,
722
+ `| Requirement ID | Title | Source File | Declared Method | Linked Test Files | Status |`,
723
+ `| :--- | :--- | :--- | :---: | :--- | :---: |`
724
+ ];
725
+ for (const r of reqAudit) {
726
+ const icon = r.status === "VERIFICADO_CON_PRUEBA" ? "\u2705 VERIFIED" : "\u274C NO TESTS";
727
+ lines.push(
728
+ `| **\`${r.id}\`** | ${r.title} | \`${r.file}\` | \`${r.method}\` | \`${r.testRefs}\` | ${icon} |`
729
+ );
730
+ }
731
+ return lines;
732
+ }
733
+ function buildTasksTable(taskAudit) {
734
+ const lines = [
735
+ `## 2. Tasks Audit (SDD Delivery Engineering)`,
736
+ ``,
737
+ `| Task ID | Title | tasks.md File | Method | Verification Command / Criterion | Status |`,
738
+ `| :--- | :--- | :--- | :---: | :--- | :---: |`
739
+ ];
740
+ for (const t of taskAudit) {
741
+ const icon = t.status === "VERIFICADO_CON_PRUEBA" ? "\u2705 VERIFIED" : "\u274C NO TESTS";
742
+ lines.push(
743
+ `| **\`${t.id}\`** | ${t.title} | \`${t.file}\` | \`${t.method}\` | \`${t.commandOrCriteria}\` | ${icon} |`
744
+ );
745
+ }
746
+ return lines;
747
+ }
748
+ function generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, taskErrors) {
749
+ const totalErrors = reqErrors + taskErrors;
750
+ const verdict = totalErrors === 0 ? "100% VERIFIED WITH TESTS (PASSED)" : `BLOCKED: Detected ${totalErrors} items without tests (${reqErrors} Requirements, ${taskErrors} Tasks)`;
751
+ const reportLines = [
752
+ `# \u{1F9EA} Deterministic Test Coverage Audit for Requirements and Tasks`,
753
+ ``,
754
+ `> **Audit Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
755
+ `> **Overall Verdict:** ${verdict}`,
756
+ ``,
757
+ `---`,
758
+ ``,
759
+ ...buildRequirementsTable(reqAudit),
760
+ ``,
761
+ `---`,
762
+ ``,
763
+ ...buildTasksTable(taskAudit)
764
+ ];
765
+ return reportLines.join("\n");
766
+ }
767
+ var EXCLUDED_TEST_DIRS = /* @__PURE__ */ new Set([
768
+ "node_modules",
769
+ ".git",
770
+ "dist",
771
+ "bin",
772
+ "obj",
773
+ "build",
774
+ "out",
775
+ "target",
776
+ "coverage",
777
+ ".changeset",
778
+ "scratch",
779
+ "fixtures",
780
+ "templates",
781
+ "examples",
782
+ ".vs",
783
+ ".idea"
784
+ ]);
785
+ var TEST_EXTENSIONS = [".feature", ".spec.ts", ".spec.js", ".test.ts", ".test.js", "_test.py", "_test.go"];
786
+ function isTestFile(fileName) {
787
+ return TEST_EXTENSIONS.some((ext) => fileName.endsWith(ext));
788
+ }
789
+ function recordRequirementTest(reqToTestsMap, reqId, relTest) {
790
+ const existing = reqToTestsMap.get(reqId) || [];
791
+ if (!existing.includes(relTest)) {
792
+ existing.push(relTest);
793
+ reqToTestsMap.set(reqId, existing);
794
+ }
795
+ }
796
+ function parseFeatureTags(content, relTest, reqToTestsMap) {
797
+ const lines = content.split("\n");
798
+ for (const line of lines) {
799
+ const trimmed = line.trim();
800
+ if (!trimmed.startsWith("@")) continue;
801
+ const tags = trimmed.split(/\s+/).filter((t) => t.startsWith("@"));
802
+ for (const t of tags) {
803
+ recordRequirementTest(reqToTestsMap, t.substring(1), relTest);
804
+ }
805
+ }
806
+ }
807
+ var REQ_ID_REGEX = /\b(FR|QR|CON|SEC-REQ)-[A-Z0-9]+(-[A-Z0-9]+)*\b/g;
808
+ function parseCodeTestIds(content, relTest, reqToTestsMap) {
809
+ const idMatches = content.match(REQ_ID_REGEX);
810
+ if (!idMatches) return;
811
+ for (const reqId of idMatches) {
812
+ recordRequirementTest(reqToTestsMap, reqId, relTest);
813
+ }
814
+ }
815
+ function processTestFile(fullPath, rootDir, reqToTestsMap) {
816
+ try {
817
+ const content = fs7.readFileSync(fullPath, "utf-8");
818
+ const relTest = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
819
+ if (fullPath.endsWith(".feature")) {
820
+ parseFeatureTags(content, relTest, reqToTestsMap);
821
+ }
822
+ parseCodeTestIds(content, relTest, reqToTestsMap);
823
+ } catch {
824
+ }
825
+ }
826
+ function collectTestFiles(dir, files = []) {
827
+ if (!fs7.existsSync(dir)) return files;
828
+ try {
829
+ const entries = fs7.readdirSync(dir, { withFileTypes: true });
830
+ for (const e of entries) {
831
+ if (e.isDirectory()) {
832
+ if (!EXCLUDED_TEST_DIRS.has(e.name)) {
833
+ collectTestFiles(path4.join(dir, e.name), files);
834
+ }
835
+ } else if (isTestFile(e.name)) {
836
+ files.push(path4.join(dir, e.name));
837
+ }
838
+ }
839
+ } catch {
840
+ }
841
+ return files;
842
+ }
843
+ function scanAllTestArtifacts(rootDir) {
844
+ const reqToTestsMap = /* @__PURE__ */ new Map();
845
+ const testFiles = collectTestFiles(rootDir);
846
+ for (const file of testFiles) {
847
+ processTestFile(file, rootDir, reqToTestsMap);
848
+ }
849
+ return reqToTestsMap;
850
+ }
851
+ var NOMINAL_KEYWORDS = /\b(nominal|happy|valid|exitoso|normal|standard|flujo principal)\b/i;
852
+ var BOUNDARY_KEYWORDS = /\b(boundary|limit|edge|min|max|l[ií]mite|borde|extremo|rango)\b/i;
853
+ 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;
854
+ function extractScenariosForReq(gherkinText, reqId) {
855
+ const lines = gherkinText.split(/\r?\n/);
856
+ const featureApplies = gherkinText.includes(`@${reqId}`);
857
+ if (!featureApplies) return [];
858
+ const chunks = [];
859
+ let pendingTags = "";
860
+ for (const rawLine of lines) {
861
+ const line = rawLine.trim();
862
+ if (line.startsWith("@")) {
863
+ pendingTags += " " + line;
864
+ } else if (/^(Scenario|Escenario|Scenario Outline|Esquema del escenario):/i.test(line)) {
865
+ const isOutline = /^(Scenario Outline|Esquema del escenario):/i.test(line);
866
+ chunks.push({ tags: pendingTags.toLowerCase(), header: line, isOutline });
867
+ pendingTags = "";
868
+ }
869
+ }
870
+ return chunks;
871
+ }
872
+ var INVALID_TAG_RE = /@(invalid|error|out-of-range)\b/i;
873
+ function isInvalidScenario(chunk, combined) {
874
+ return INVALID_TAG_RE.test(chunk.tags) || INVALID_KEYWORDS.test(combined);
875
+ }
876
+ function isBoundaryScenario(chunk, combined, isInvalid) {
877
+ if (chunk.tags.includes("@boundary") || BOUNDARY_KEYWORDS.test(combined)) return true;
878
+ return chunk.isOutline && !isInvalid;
879
+ }
880
+ function classifyScenarioChunk(chunk, cov) {
881
+ const combined = `${chunk.tags} ${chunk.header}`;
882
+ const isInvalid = isInvalidScenario(chunk, combined);
883
+ const isBoundary = isBoundaryScenario(chunk, combined, isInvalid);
884
+ const isNominal = chunk.tags.includes("@nominal") || NOMINAL_KEYWORDS.test(combined) || !isInvalid && !isBoundary;
885
+ if (isNominal) cov.nominal = true;
886
+ if (isBoundary) cov.boundary = true;
887
+ if (isInvalid) cov.invalid = true;
888
+ }
889
+ function analyzeBddVariantsForRequirement(reqId, gherkinSources) {
890
+ const cov = { nominal: false, boundary: false, invalid: false, totalScenarios: 0 };
891
+ for (const src of gherkinSources) {
892
+ const chunks = extractScenariosForReq(src, reqId);
893
+ cov.totalScenarios += chunks.length;
894
+ for (const chunk of chunks) {
895
+ classifyScenarioChunk(chunk, cov);
896
+ }
897
+ }
898
+ return cov;
899
+ }
900
+ function computeMissingVariants(cov) {
901
+ const missing = [];
902
+ if (!cov.nominal) missing.push("nominal");
903
+ if (!cov.boundary) missing.push("boundary");
904
+ if (!cov.invalid) missing.push("invalid");
905
+ return missing;
906
+ }
907
+ var REQ_PREFIXES = ["FR-", "QR-", "CON-", "SEC-REQ-"];
908
+ function isRequirementArtifact(frontmatter, id) {
909
+ if (frontmatter.type === "requirement" || frontmatter.type === "security-requirement") {
910
+ return true;
911
+ }
912
+ return REQ_PREFIXES.some((prefix) => id.startsWith(prefix));
913
+ }
914
+ function resolveRequirementMethod(declaredMethod, matchingTests) {
915
+ if (typeof declaredMethod === "string" && declaredMethod) {
916
+ return declaredMethod;
917
+ }
918
+ return matchingTests.some((t) => t.endsWith(".feature")) ? "BDD_CUCUMBER" : "TEST_EXECUTION";
919
+ }
920
+ function shouldEnforceVariantsForReq(id, frontmatter, options) {
921
+ if (!id.startsWith("FR-")) return false;
922
+ return Boolean(options.enforceBddVariants || frontmatter["enforce-bdd-variants"] === true);
923
+ }
924
+ function evaluateReqVerification(id, isTemplate, hasTests, missingVariants, frontmatter, options) {
925
+ if (isTemplate) return true;
926
+ const enforceVariants = shouldEnforceVariantsForReq(id, frontmatter, options);
927
+ const variantsOk = !enforceVariants || missingVariants.length === 0;
928
+ return hasTests && variantsOk;
929
+ }
930
+ function formatTestRefs(matchingTests, relPath, totalScenarios) {
931
+ if (matchingTests.length > 0) return matchingTests.join(", ");
932
+ return totalScenarios > 0 ? `${relPath} (gherkin)` : "NINGUNA DETECTADA";
933
+ }
934
+ function buildRequirementAuditItem(frontmatter, id, relPath, isTemplate, matchingTests, bddVariants, missingVariants, isVerified) {
935
+ const status = isVerified ? "VERIFICADO_CON_PRUEBA" : "FALLO_SIN_PRUEBA";
936
+ return {
937
+ id: id || "SIN-ID",
938
+ title: frontmatter.title || "Sin t\xEDtulo",
939
+ file: relPath,
940
+ isTemplate,
941
+ method: resolveRequirementMethod(frontmatter["verifiable-by"], matchingTests),
942
+ testRefs: formatTestRefs(matchingTests, relPath, bddVariants.totalScenarios),
943
+ existingCount: matchingTests.length || bddVariants.totalScenarios,
944
+ bddVariants,
945
+ missingVariants,
946
+ status
947
+ };
948
+ }
949
+ function auditRequirement(frontmatter, id, relPath, isTemplate, reqToTestsMap, gherkinSources, options) {
950
+ const matchingTests = reqToTestsMap.get(id) || [];
951
+ const bddVariants = analyzeBddVariantsForRequirement(id, gherkinSources);
952
+ const missingVariants = id.startsWith("FR-") ? computeMissingVariants(bddVariants) : [];
953
+ const hasTests = matchingTests.length > 0 || bddVariants.totalScenarios > 0;
954
+ const isVerified = evaluateReqVerification(id, isTemplate, hasTests, missingVariants, frontmatter, options);
955
+ const item = buildRequirementAuditItem(
956
+ frontmatter,
957
+ id,
958
+ relPath,
959
+ isTemplate,
960
+ matchingTests,
961
+ bddVariants,
962
+ missingVariants,
963
+ isVerified
964
+ );
965
+ return { item, isError: !isVerified };
966
+ }
967
+ function parseSingleFlowEntry(entry) {
968
+ if (typeof entry === "string") return entry.trim();
969
+ if (entry && typeof entry === "object") {
970
+ const rec = entry;
971
+ return String(rec.id || rec.name || "").trim();
972
+ }
973
+ return "";
974
+ }
975
+ function extractDeclaredUseCaseFlows(frontmatter) {
976
+ const flows = [];
977
+ const rawLists = [frontmatter["alternative-flows"], frontmatter["exception-flows"]];
978
+ for (const list of rawLists) {
979
+ if (!Array.isArray(list)) continue;
980
+ for (const entry of list) {
981
+ const flowId = parseSingleFlowEntry(entry);
982
+ if (flowId) flows.push(flowId);
983
+ }
984
+ }
985
+ return flows;
986
+ }
987
+ function auditUseCaseFlowItem(id, relPath, declaredFlows, combinedTestsText) {
988
+ const coveredFlows = [];
989
+ const missingFlows = [];
990
+ const lowerCorpus = combinedTestsText.toLowerCase();
991
+ for (const flow of declaredFlows) {
992
+ if (lowerCorpus.includes(flow.toLowerCase())) {
993
+ coveredFlows.push(flow);
994
+ } else {
995
+ missingFlows.push(flow);
996
+ }
997
+ }
998
+ return {
999
+ id,
1000
+ file: relPath,
1001
+ declaredFlows,
1002
+ coveredFlows,
1003
+ missingFlows,
1004
+ status: missingFlows.length === 0 ? "VERIFICADO_CON_PRUEBA" : "FALLO_SIN_PRUEBA"
1005
+ };
1006
+ }
1007
+ function isTaskProofValid(criteria, isTemplate) {
1008
+ if (isTemplate) return true;
1009
+ return criteria.length > 4 && !criteria.includes("TODO") && !criteria.includes("TBD");
1010
+ }
1011
+ function auditSingleTask(t, relPath, isTemplate) {
1012
+ const crit = t.verification?.criteria || "";
1013
+ const method = t.verification?.method || "TEST_EXECUTION";
1014
+ const hasProof = isTaskProofValid(crit, isTemplate);
1015
+ return {
1016
+ item: {
1017
+ id: t.id,
1018
+ title: t.title || "Sin t\xEDtulo",
1019
+ file: relPath,
1020
+ method,
1021
+ commandOrCriteria: crit || "AUSENTE",
1022
+ status: hasProof ? "VERIFICADO_CON_PRUEBA" : "FALLO_SIN_PRUEBA"
1023
+ },
1024
+ hasProof
1025
+ };
1026
+ }
1027
+ function auditTasks(tasks, relPath, isTemplate) {
1028
+ if (!Array.isArray(tasks) || tasks.length === 0) {
1029
+ return { items: [], errorCount: 0 };
1030
+ }
1031
+ const items = [];
1032
+ let errorCount = 0;
1033
+ for (const t of tasks) {
1034
+ const { item, hasProof } = auditSingleTask(t, relPath, isTemplate);
1035
+ if (!hasProof) errorCount++;
1036
+ items.push(item);
1037
+ }
1038
+ return { items, errorCount };
1039
+ }
1040
+ function collectTestCorpus(rootDir, allMdFiles) {
1041
+ const gherkinSources = [];
1042
+ const testContents = [];
1043
+ for (const tf of collectTestFiles(rootDir)) {
1044
+ try {
1045
+ const content = fs7.readFileSync(tf, "utf-8");
1046
+ testContents.push(content);
1047
+ if (tf.endsWith(".feature")) gherkinSources.push(content);
1048
+ } catch {
1049
+ }
1050
+ }
1051
+ for (const md of allMdFiles) {
1052
+ try {
1053
+ const blocks = extractGherkinBlock(fs7.readFileSync(md, "utf-8"));
1054
+ gherkinSources.push(...blocks);
1055
+ testContents.push(...blocks);
1056
+ } catch {
1057
+ }
1058
+ }
1059
+ return { gherkinSources, combinedTests: testContents.join("\n") };
1060
+ }
1061
+ function assembleCoverageResult(reqAudit, taskAudit, useCaseFlows, reqErrors, taskErrors, ucErrors) {
1062
+ return {
1063
+ success: reqErrors === 0 && taskErrors === 0 && ucErrors === 0,
1064
+ totalRequirements: reqAudit.length,
1065
+ passedRequirements: reqAudit.length - reqErrors,
1066
+ failedRequirements: reqErrors,
1067
+ totalTasks: taskAudit.length,
1068
+ passedTasks: taskAudit.length - taskErrors,
1069
+ failedTasks: taskErrors,
1070
+ requirements: reqAudit,
1071
+ tasks: taskAudit,
1072
+ useCaseFlows,
1073
+ failedUseCaseFlows: ucErrors,
1074
+ reportMarkdown: generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, taskErrors)
1075
+ };
1076
+ }
1077
+ function recordUseCaseAuditIfPresent(frontmatter, id, relPath, isTemplate, combinedTests, state) {
1078
+ if (isTemplate || !(frontmatter.type === "use-case" || id.startsWith("UC-"))) return;
1079
+ const declaredFlows = extractDeclaredUseCaseFlows(frontmatter);
1080
+ if (declaredFlows.length === 0) return;
1081
+ const flowAudit = auditUseCaseFlowItem(id, relPath, declaredFlows, combinedTests);
1082
+ state.useCaseFlows.push(flowAudit);
1083
+ if (flowAudit.status === "FALLO_SIN_PRUEBA") state.ucErrors++;
1084
+ }
1085
+ function processMdFileForCoverage(file, rootDir, reqToTestsMap, corpus, options, state) {
1086
+ const { frontmatter } = parseGenericDoc(fs7.readFileSync(file, "utf-8"));
1087
+ const relPath = path4.relative(rootDir, file).replace(/\\/g, "/");
1088
+ const isTemplate = relPath.includes("templates/");
1089
+ const id = frontmatter.id || "";
1090
+ if (isRequirementArtifact(frontmatter, id)) {
1091
+ const audited = auditRequirement(frontmatter, id, relPath, isTemplate, reqToTestsMap, corpus.gherkinSources, options);
1092
+ state.reqAudit.push(audited.item);
1093
+ if (audited.isError) state.reqErrors++;
1094
+ }
1095
+ recordUseCaseAuditIfPresent(frontmatter, id, relPath, isTemplate, corpus.combinedTests, state);
1096
+ const auditedTasks = auditTasks(frontmatter.tasks, relPath, isTemplate);
1097
+ state.taskAudit.push(...auditedTasks.items);
1098
+ state.taskErrors += auditedTasks.errorCount;
1099
+ }
1100
+ function verifyTestingCoverage(options = {}) {
1101
+ const rootDir = options.rootDir || process.cwd();
1102
+ const allMdFiles = walkMdFiles(rootDir);
1103
+ const reqToTestsMap = scanAllTestArtifacts(rootDir);
1104
+ const corpus = collectTestCorpus(rootDir, allMdFiles);
1105
+ const state = {
1106
+ reqAudit: [],
1107
+ taskAudit: [],
1108
+ useCaseFlows: [],
1109
+ reqErrors: 0,
1110
+ taskErrors: 0,
1111
+ ucErrors: 0
1112
+ };
1113
+ for (const file of allMdFiles) {
1114
+ try {
1115
+ processMdFileForCoverage(file, rootDir, reqToTestsMap, corpus, options, state);
1116
+ } catch {
1117
+ }
1118
+ }
1119
+ return assembleCoverageResult(
1120
+ state.reqAudit,
1121
+ state.taskAudit,
1122
+ state.useCaseFlows,
1123
+ state.reqErrors,
1124
+ state.taskErrors,
1125
+ state.ucErrors
1126
+ );
1127
+ }
482
1128
 
483
1129
  // src/verifiers/quality-gate.ts
484
1130
  var DEFAULT_EXTENSIONS = [
@@ -593,7 +1239,7 @@ function applyOptionOverrides(policy, overrides) {
593
1239
  function parseQualityPolicy(content, overrides) {
594
1240
  const policy = createDefaultQualityPolicy();
595
1241
  try {
596
- const doc = yaml2.load(content);
1242
+ const doc = yaml3.load(content);
597
1243
  if (doc && typeof doc === "object") {
598
1244
  const record = doc;
599
1245
  applyDocScope(policy, record);
@@ -609,7 +1255,7 @@ function parseQualityPolicy(content, overrides) {
609
1255
  return policy;
610
1256
  }
611
1257
  function extractFunctions(content, filePath) {
612
- const ext = path5.extname(filePath).toLowerCase();
1258
+ const ext = path4.extname(filePath).toLowerCase();
613
1259
  return TS_JS_EXTENSIONS.includes(ext) ? extractFunctionsTypeScriptAst(content, filePath) : extractFunctionsPolyglot(content, filePath);
614
1260
  }
615
1261
  function extractFunctionsBraceLanguages(content, filePath) {
@@ -640,55 +1286,55 @@ function isExcludedFile(file, normalized, excludePatterns) {
640
1286
  );
641
1287
  }
642
1288
  function walkDir(dir, extensions = DEFAULT_EXTENSIONS, excludePatterns = DEFAULT_EXCLUDES) {
643
- if (!fs6.existsSync(dir)) return [];
1289
+ if (!fs7.existsSync(dir)) return [];
644
1290
  const results = [];
645
- for (const file of fs6.readdirSync(dir)) {
646
- const fullPath = path5.join(dir, file);
1291
+ for (const file of fs7.readdirSync(dir)) {
1292
+ const fullPath = path4.join(dir, file);
647
1293
  const normalized = fullPath.replace(/\\/g, "/");
648
1294
  if (isExcludedFile(file, normalized, excludePatterns)) continue;
649
- const stat = fs6.statSync(fullPath);
1295
+ const stat = fs7.statSync(fullPath);
650
1296
  if (stat.isDirectory()) {
651
1297
  results.push(...walkDir(fullPath, extensions, excludePatterns));
652
- } else if (extensions.includes(path5.extname(file).toLowerCase())) {
1298
+ } else if (extensions.includes(path4.extname(file).toLowerCase())) {
653
1299
  results.push(fullPath);
654
1300
  }
655
1301
  }
656
1302
  return results;
657
1303
  }
658
1304
  function resolvePolicy(rootDir, options) {
659
- const policyFile = options.policyPath || path5.join(rootDir, "quality-policy.yaml");
1305
+ const policyFile = options.policyPath || path4.join(rootDir, "quality-policy.yaml");
660
1306
  if (options.policy) {
661
1307
  const policy = { ...options.policy };
662
1308
  applyOptionOverrides(policy, options.thresholds);
663
1309
  return policy;
664
1310
  }
665
- if (fs6.existsSync(policyFile)) {
666
- return parseQualityPolicy(fs6.readFileSync(policyFile, "utf-8"), options.thresholds);
1311
+ if (fs7.existsSync(policyFile)) {
1312
+ return parseQualityPolicy(fs7.readFileSync(policyFile, "utf-8"), options.thresholds);
667
1313
  }
668
1314
  return parseQualityPolicy("", options.thresholds);
669
1315
  }
670
1316
  function isPathExcluded(targetPath, rootDir, excludePatterns) {
671
- const rel = path5.relative(rootDir, targetPath).replace(/\\/g, "/");
672
- const base = path5.basename(targetPath);
1317
+ const rel = path4.relative(rootDir, targetPath).replace(/\\/g, "/");
1318
+ const base = path4.basename(targetPath);
673
1319
  return excludePatterns.some(
674
1320
  (pat) => base === pat || rel === pat || rel.startsWith(pat + "/") || rel.includes("/" + pat + "/") || rel.endsWith("/" + pat) || rel.includes(pat)
675
1321
  );
676
1322
  }
677
1323
  function scanContainerSubdir(containerDir, subdirName, targetDir, rootDir, policy, files) {
678
1324
  if (policy.exclude_patterns.includes(subdirName)) return;
679
- const subTarget = path5.join(containerDir, subdirName, targetDir);
680
- if (!fs6.existsSync(subTarget) || !fs6.statSync(subTarget).isDirectory()) return;
1325
+ const subTarget = path4.join(containerDir, subdirName, targetDir);
1326
+ if (!fs7.existsSync(subTarget) || !fs7.statSync(subTarget).isDirectory()) return;
681
1327
  if (isPathExcluded(subTarget, rootDir, policy.exclude_patterns)) return;
682
1328
  for (const f of walkDir(subTarget, policy.supported_extensions, policy.exclude_patterns)) {
683
1329
  files.add(f);
684
1330
  }
685
1331
  }
686
1332
  function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
687
- const containerDir = path5.join(rootDir, container);
688
- if (!fs6.existsSync(containerDir) || !fs6.statSync(containerDir).isDirectory()) return;
1333
+ const containerDir = path4.join(rootDir, container);
1334
+ if (!fs7.existsSync(containerDir) || !fs7.statSync(containerDir).isDirectory()) return;
689
1335
  if (isPathExcluded(containerDir, rootDir, policy.exclude_patterns)) return;
690
1336
  try {
691
- for (const subdir of fs6.readdirSync(containerDir, { withFileTypes: true })) {
1337
+ for (const subdir of fs7.readdirSync(containerDir, { withFileTypes: true })) {
692
1338
  if (subdir.isDirectory()) {
693
1339
  scanContainerSubdir(containerDir, subdir.name, targetDir, rootDir, policy, files);
694
1340
  }
@@ -697,8 +1343,8 @@ function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
697
1343
  }
698
1344
  }
699
1345
  function scanDirectTarget(rootDir, targetDir, policy, files) {
700
- const fullDir = path5.join(rootDir, targetDir);
701
- if (fs6.existsSync(fullDir) && !isPathExcluded(fullDir, rootDir, policy.exclude_patterns)) {
1346
+ const fullDir = path4.join(rootDir, targetDir);
1347
+ if (fs7.existsSync(fullDir) && !isPathExcluded(fullDir, rootDir, policy.exclude_patterns)) {
702
1348
  for (const f of walkDir(fullDir, policy.supported_extensions, policy.exclude_patterns)) {
703
1349
  files.add(f);
704
1350
  }
@@ -735,23 +1381,86 @@ function evaluateViolations(fn, policy) {
735
1381
  return violations;
736
1382
  }
737
1383
  function evaluateFileQuality(file, rootDir, policy) {
738
- const content = fs6.readFileSync(file, "utf-8");
1384
+ const content = fs7.readFileSync(file, "utf-8");
739
1385
  const fns = extractFunctions(content, file);
740
1386
  return fns.map((fn) => {
741
1387
  const violations = evaluateViolations(fn, policy);
742
1388
  return {
743
1389
  ...fn,
744
- relPath: path5.relative(rootDir, file).replace(/\\/g, "/"),
1390
+ relPath: path4.relative(rootDir, file).replace(/\\/g, "/"),
745
1391
  status: violations.length === 0 ? "PASS" : "FAIL",
746
1392
  violations
747
1393
  };
748
1394
  });
749
1395
  }
750
- function buildQualityGateResult(results, totalFiles, policy) {
1396
+ var UNIT_TEST_SUFFIXES = [".spec.ts", ".test.ts", ".spec.js", ".test.js", "_test.py", "_test.go"];
1397
+ function collectUnitTestCorpus(rootDir) {
1398
+ const allCodeFiles = walkDir(rootDir, DEFAULT_EXTENSIONS, DEFAULT_EXCLUDES);
1399
+ const chunks = [];
1400
+ for (const tf of allCodeFiles) {
1401
+ if (!UNIT_TEST_SUFFIXES.some((s) => tf.endsWith(s))) continue;
1402
+ try {
1403
+ chunks.push(fs7.readFileSync(tf, "utf-8"));
1404
+ } catch {
1405
+ }
1406
+ }
1407
+ return chunks.join("\n");
1408
+ }
1409
+ function auditUnitTestsPerFunction(results, rootDir) {
1410
+ const corpus = collectUnitTestCorpus(rootDir);
1411
+ const untested = [];
1412
+ for (const r of results) {
1413
+ const fnName = r.functionName.includes(".") ? r.functionName.split(".").pop() : r.functionName;
1414
+ if (!fnName || fnName === "anonymous" || fnName === "constructor") continue;
1415
+ if (!corpus.includes(fnName)) {
1416
+ untested.push({ file: r.relPath, functionName: r.functionName });
1417
+ r.violations.push(`Missing unit test for function '${r.functionName}'`);
1418
+ r.status = "FAIL";
1419
+ }
1420
+ }
1421
+ return untested;
1422
+ }
1423
+ function readSummaryCoverageJson(rootDir) {
1424
+ for (const rel of ["coverage/coverage-summary.json", "reports/coverage-summary.json"]) {
1425
+ const full = path4.join(rootDir, rel);
1426
+ if (!fs7.existsSync(full)) continue;
1427
+ try {
1428
+ const raw = JSON.parse(fs7.readFileSync(full, "utf-8"));
1429
+ const total = raw.total;
1430
+ if (!total) continue;
1431
+ return {
1432
+ linesPct: Number(total.lines?.pct ?? 0),
1433
+ branchesPct: Number(total.branches?.pct ?? 0),
1434
+ functionsPct: Number(total.functions?.pct ?? 0),
1435
+ source: rel
1436
+ };
1437
+ } catch {
1438
+ }
1439
+ }
1440
+ return null;
1441
+ }
1442
+ function checkCoverageThreshold(label, actual, min, out) {
1443
+ if (actual < min) {
1444
+ out.push(`${label} coverage ${actual}% is below minimum threshold of ${min}%`);
1445
+ }
1446
+ }
1447
+ function evaluateCoverageThresholds(rootDir, policy, requireReport) {
1448
+ const cov = readSummaryCoverageJson(rootDir);
1449
+ if (!cov) {
1450
+ return requireReport ? ["Missing code coverage summary report (coverage/coverage-summary.json)"] : [];
1451
+ }
1452
+ const violations = [];
1453
+ checkCoverageThreshold("Line", cov.linesPct, policy.min_line_coverage, violations);
1454
+ checkCoverageThreshold("Branch", cov.branchesPct, policy.min_branch_coverage, violations);
1455
+ checkCoverageThreshold("Function", cov.functionsPct, policy.min_function_coverage, violations);
1456
+ return violations;
1457
+ }
1458
+ function buildQualityGateResult(results, totalFiles, policy, coverageViolations, untestedFunctions) {
751
1459
  const failCount = results.filter((r) => r.status === "FAIL").length;
752
1460
  const passCount = results.length - failCount;
753
- const violationsCount = results.reduce((acc, r) => acc + r.violations.length, 0);
754
- const success = policy.enforce_mode === "STRICT" ? failCount === 0 : true;
1461
+ const violationsCount = results.reduce((acc, r) => acc + r.violations.length, 0) + coverageViolations.length;
1462
+ const hasFailures = failCount > 0 || coverageViolations.length > 0;
1463
+ const success = policy.enforce_mode === "STRICT" ? !hasFailures : true;
755
1464
  return {
756
1465
  success,
757
1466
  totalFiles,
@@ -760,7 +1469,9 @@ function buildQualityGateResult(results, totalFiles, policy) {
760
1469
  failCount,
761
1470
  violationsCount,
762
1471
  results,
763
- policy
1472
+ policy,
1473
+ coverageViolations,
1474
+ untestedFunctions
764
1475
  };
765
1476
  }
766
1477
  function verifyQualityGate(options = {}) {
@@ -774,199 +1485,10 @@ function verifyQualityGate(options = {}) {
774
1485
  } catch {
775
1486
  }
776
1487
  }
777
- return buildQualityGateResult(results, allFiles.length, policy);
778
- }
779
- function walkMdFiles(dir, fileList = [], excludeDirs = [
780
- "node_modules",
781
- ".git",
782
- "dist",
783
- "bin",
784
- "obj",
785
- "build",
786
- "out",
787
- "target",
788
- "coverage",
789
- ".changeset",
790
- "scratch",
791
- "test-scaffold",
792
- "fixtures",
793
- "templates",
794
- "examples",
795
- ".agent",
796
- ".agents",
797
- ".claude",
798
- ".cursor",
799
- ".github",
800
- ".vscode",
801
- ".vs",
802
- ".idea"
803
- ]) {
804
- try {
805
- if (!fs6.existsSync(dir)) return fileList;
806
- const files = fs6.readdirSync(dir);
807
- for (const file of files) {
808
- const fullPath = path5.join(dir, file);
809
- try {
810
- if (fs6.statSync(fullPath).isDirectory()) {
811
- if (!excludeDirs.includes(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
812
- walkMdFiles(fullPath, fileList, excludeDirs);
813
- }
814
- } else if (file.endsWith(".md")) {
815
- fileList.push(fullPath);
816
- }
817
- } catch {
818
- }
819
- }
820
- } catch {
821
- }
822
- return fileList;
823
- }
824
-
825
- // src/verifiers/gherkin.ts
826
- function parseMarkdownFrontmatter(content) {
827
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
828
- if (!match) return { frontmatter: {}, body: content };
829
- const yamlLines = match[1].split("\n");
830
- const frontmatter = {};
831
- for (const line of yamlLines) {
832
- const parts = line.split(":");
833
- if (parts.length >= 2) {
834
- const key = parts[0].trim();
835
- let val = parts.slice(1).join(":").trim();
836
- if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
837
- val = val.substring(1, val.length - 1);
838
- }
839
- frontmatter[key] = val;
840
- }
841
- }
842
- const body = content.substring(match[0].length);
843
- return { frontmatter, body };
844
- }
845
- function extractGherkinBlock(body) {
846
- const gherkinRegex = /```gherkin\r?\n([\s\S]*?)\r?\n```/g;
847
- const blocks = [];
848
- let match;
849
- while ((match = gherkinRegex.exec(body)) !== null) {
850
- blocks.push(match[1].trim());
851
- }
852
- return blocks;
853
- }
854
- function resolveTargetFiles(rootDir, targetPath) {
855
- if (targetPath) {
856
- const fullTarget = path5.isAbsolute(targetPath) ? targetPath : path5.join(rootDir, targetPath);
857
- if (!fs6.existsSync(fullTarget)) return [];
858
- return fs6.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
859
- }
860
- const searchDirs = [
861
- path5.join(rootDir, "product"),
862
- path5.join(rootDir, "security"),
863
- path5.join(rootDir, "specs"),
864
- path5.join(rootDir, "examples", "product"),
865
- path5.join(rootDir, "examples", "security"),
866
- path5.join(rootDir, "examples", "specs")
867
- ];
868
- return searchDirs.filter((d) => fs6.existsSync(d)).flatMap((d) => walkMdFiles(d));
869
- }
870
- function resolveFeatureFilePath(rootDir, filePath, id, customTarget) {
871
- if (customTarget) {
872
- return path5.isAbsolute(customTarget) ? customTarget : path5.join(rootDir, customTarget);
873
- }
874
- const baseDir = path5.join(rootDir, "tests", "features");
875
- const candidateSecurity = path5.join(baseDir, "security", `${id.toLowerCase()}.feature`);
876
- const candidateRoot = path5.join(baseDir, `${id.toLowerCase()}.feature`);
877
- if (fs6.existsSync(candidateSecurity)) return candidateSecurity;
878
- if (fs6.existsSync(candidateRoot)) return candidateRoot;
879
- const subDir = filePath.includes("security") ? "security" : "";
880
- return path5.join(baseDir, subDir, `${id.toLowerCase()}.feature`);
881
- }
882
- function buildFeatureFileContent(rootDir, filePath, id, version, gherkinBlocks) {
883
- const banner = [
884
- `# ==============================================================================`,
885
- `# AUTO-GENERADO POR AI-SDLC (Cucumber Integration)`,
886
- `# Origen: ${path5.relative(rootDir, filePath).replace(/\\/g, "/")}`,
887
- `# ID Requerimiento: ${id}`,
888
- `# Versi\xF3n: ${version || "1.0.0"}`,
889
- `# NO EDITAR MANUALMENTE: Cualquier cambio debe realizarse en el Markdown origen.`,
890
- `# ==============================================================================`,
891
- "",
892
- ""
893
- ].join("\n");
894
- return banner + gherkinBlocks.join("\n\n") + "\n";
895
- }
896
- function countScenarios(gherkinBlocks) {
897
- let count = 0;
898
- for (const block of gherkinBlocks) {
899
- const matches = block.match(/\b(Scenario|Escenario|Scenario Outline|Esquema del escenario):/g);
900
- if (matches) count += matches.length;
901
- }
902
- return count;
903
- }
904
- function extractGherkinFeatures(options = {}) {
905
- const rootDir = options.rootDir || process.cwd();
906
- const features = [];
907
- let totalScenarios = 0;
908
- const targetFiles = resolveTargetFiles(rootDir, options.targetPath);
909
- for (const filePath of targetFiles) {
910
- try {
911
- const content = fs6.readFileSync(filePath, "utf-8");
912
- const { frontmatter, body } = parseMarkdownFrontmatter(content);
913
- const gherkinBlocks = extractGherkinBlock(body);
914
- if (gherkinBlocks.length === 0) continue;
915
- const id = frontmatter.id || path5.basename(filePath, path5.extname(filePath));
916
- const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
917
- fs6.mkdirSync(path5.dirname(targetPath), { recursive: true });
918
- const fullContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
919
- fs6.writeFileSync(targetPath, fullContent, "utf-8");
920
- const scenarioCount = countScenarios(gherkinBlocks);
921
- totalScenarios += scenarioCount;
922
- features.push({
923
- sourceFile: path5.relative(rootDir, filePath).replace(/\\/g, "/"),
924
- outputFile: path5.relative(rootDir, targetPath).replace(/\\/g, "/"),
925
- featureName: id,
926
- scenarioCount
927
- });
928
- } catch {
929
- }
930
- }
931
- return { success: true, features, totalScenarios };
932
- }
933
- function checkGherkinInSync(options = {}) {
934
- const rootDir = options.rootDir || process.cwd();
935
- const targetFiles = resolveTargetFiles(rootDir, options.targetPath);
936
- const outOfSyncFiles = [];
937
- const missingFiles = [];
938
- let totalFeatures = 0;
939
- for (const filePath of targetFiles) {
940
- try {
941
- const content = fs6.readFileSync(filePath, "utf-8");
942
- const { frontmatter, body } = parseMarkdownFrontmatter(content);
943
- const gherkinBlocks = extractGherkinBlock(body);
944
- if (gherkinBlocks.length === 0) continue;
945
- totalFeatures++;
946
- const id = frontmatter.id || path5.basename(filePath, path5.extname(filePath));
947
- const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
948
- const relTarget = path5.relative(rootDir, targetPath).replace(/\\/g, "/");
949
- if (!fs6.existsSync(targetPath)) {
950
- missingFiles.push(relTarget);
951
- continue;
952
- }
953
- const expectedContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
954
- const actualContent = fs6.readFileSync(targetPath, "utf-8");
955
- if (actualContent.replace(/\r\n/g, "\n") !== expectedContent.replace(/\r\n/g, "\n")) {
956
- outOfSyncFiles.push(relTarget);
957
- }
958
- } catch {
959
- }
960
- }
961
- return {
962
- inSync: missingFiles.length === 0 && outOfSyncFiles.length === 0,
963
- outOfSyncFiles,
964
- missingFiles,
965
- totalFeatures
966
- };
1488
+ const untestedFunctions = options.requireFunctionUnitTests ? auditUnitTestsPerFunction(results, rootDir) : [];
1489
+ const coverageViolations = evaluateCoverageThresholds(rootDir, policy, options.requireCoverageReport);
1490
+ return buildQualityGateResult(results, allFiles.length, policy, coverageViolations, untestedFunctions);
967
1491
  }
968
-
969
- // src/adapters/sdd/openspec.ts
970
1492
  function normalizeHandoffPayload(handoff, changeId) {
971
1493
  return {
972
1494
  ...handoff,
@@ -976,12 +1498,12 @@ function normalizeHandoffPayload(handoff, changeId) {
976
1498
  };
977
1499
  }
978
1500
  function parseChangeProfile(specFile) {
979
- if (!fs6.existsSync(specFile)) return "standard";
1501
+ if (!fs7.existsSync(specFile)) return "standard";
980
1502
  try {
981
- const rawSpec = fs6.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1503
+ const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
982
1504
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
983
1505
  if (match) {
984
- const parsedFm = yaml2.load(match[1]);
1506
+ const parsedFm = yaml3.load(match[1]);
985
1507
  if (parsedFm?.profile && ["patch", "standard", "critical"].includes(String(parsedFm.profile))) {
986
1508
  return parsedFm.profile;
987
1509
  }
@@ -1000,15 +1522,15 @@ function checkSpecGherkinFormat(parsedFm, rawSpec, changeWorkspaceDir, errors) {
1000
1522
  }
1001
1523
  }
1002
1524
  function validateSpecFileContent(specFile, changeWorkspaceDir, errors) {
1003
- if (!fs6.existsSync(specFile)) {
1525
+ if (!fs7.existsSync(specFile)) {
1004
1526
  errors.push(`Falta el archivo obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
1005
1527
  return;
1006
1528
  }
1007
1529
  try {
1008
- const rawSpec = fs6.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1530
+ const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1009
1531
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1010
1532
  if (match) {
1011
- const parsedFm = yaml2.load(match[1]);
1533
+ const parsedFm = yaml3.load(match[1]);
1012
1534
  if (parsedFm) checkSpecGherkinFormat(parsedFm, rawSpec, changeWorkspaceDir, errors);
1013
1535
  }
1014
1536
  } catch {
@@ -1024,19 +1546,19 @@ function validateHandoffSidecar(handoff, changeWorkspaceDir, errors) {
1024
1546
  if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
1025
1547
  errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
1026
1548
  }
1027
- return path5.join(changeWorkspaceDir, "handoff.yaml");
1549
+ return path4.join(changeWorkspaceDir, "handoff.yaml");
1028
1550
  }
1029
1551
  function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
1030
- const proposalFile = path5.join(changeWorkspaceDir, "proposal.md");
1031
- const tasksFile = path5.join(changeWorkspaceDir, "tasks.md");
1552
+ const proposalFile = path4.join(changeWorkspaceDir, "proposal.md");
1553
+ const tasksFile = path4.join(changeWorkspaceDir, "tasks.md");
1032
1554
  if (profile === "patch") {
1033
1555
  const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
1034
- return { handoff: handoff2, sidecarFile: handoff2 ? path5.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
1556
+ return { handoff: handoff2, sidecarFile: handoff2 ? path4.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
1035
1557
  }
1036
- if (!fs6.existsSync(proposalFile)) {
1558
+ if (!fs7.existsSync(proposalFile)) {
1037
1559
  warnings.push(`Falta 'proposal.md' en el espacio de trabajo OpenSpec.`);
1038
1560
  }
1039
- if (!fs6.existsSync(tasksFile)) {
1561
+ if (!fs7.existsSync(tasksFile)) {
1040
1562
  errors.push(`Falta el archivo obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
1041
1563
  }
1042
1564
  const handoff = adapter.loadSidecar(changeWorkspaceDir);
@@ -1044,20 +1566,20 @@ function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors,
1044
1566
  return { handoff, sidecarFile };
1045
1567
  }
1046
1568
  function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
1047
- const changeId = path5.basename(changeWorkspaceDir);
1048
- const proposalFile = path5.join(changeWorkspaceDir, "proposal.md");
1049
- const specFile = path5.join(changeWorkspaceDir, "spec.md");
1050
- const designFile = path5.join(changeWorkspaceDir, "design.md");
1051
- const tasksFile = path5.join(changeWorkspaceDir, "tasks.md");
1569
+ const changeId = path4.basename(changeWorkspaceDir);
1570
+ const proposalFile = path4.join(changeWorkspaceDir, "proposal.md");
1571
+ const specFile = path4.join(changeWorkspaceDir, "spec.md");
1572
+ const designFile = path4.join(changeWorkspaceDir, "design.md");
1573
+ const tasksFile = path4.join(changeWorkspaceDir, "tasks.md");
1052
1574
  return {
1053
1575
  framework: "openspec",
1054
1576
  changeId,
1055
1577
  changeDir: changeWorkspaceDir,
1056
1578
  profile,
1057
- proposalFile: fs6.existsSync(proposalFile) ? proposalFile : void 0,
1058
- specFile: fs6.existsSync(specFile) ? specFile : void 0,
1059
- designFile: fs6.existsSync(designFile) ? designFile : void 0,
1060
- tasksFile: fs6.existsSync(tasksFile) ? tasksFile : void 0,
1579
+ proposalFile: fs7.existsSync(proposalFile) ? proposalFile : void 0,
1580
+ specFile: fs7.existsSync(specFile) ? specFile : void 0,
1581
+ designFile: fs7.existsSync(designFile) ? designFile : void 0,
1582
+ tasksFile: fs7.existsSync(tasksFile) ? tasksFile : void 0,
1061
1583
  sidecarFile,
1062
1584
  handoff: handoff || void 0
1063
1585
  };
@@ -1065,28 +1587,28 @@ function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sideca
1065
1587
  var OpenSpecAdapter = class {
1066
1588
  framework = "openspec";
1067
1589
  getChangeWorkspaceDir(rootDir, changeId) {
1068
- const openspecDir = path5.join(rootDir, "openspec", "changes", changeId);
1069
- if (fs6.existsSync(openspecDir)) return openspecDir;
1070
- const activeChangeDir = path5.join(rootDir, "specs", "changes", "active", changeId);
1071
- if (fs6.existsSync(activeChangeDir)) return activeChangeDir;
1072
- const completedChangeDir = path5.join(rootDir, "specs", "changes", "completed", changeId);
1073
- if (fs6.existsSync(completedChangeDir)) return completedChangeDir;
1590
+ const openspecDir = path4.join(rootDir, "openspec", "changes", changeId);
1591
+ if (fs7.existsSync(openspecDir)) return openspecDir;
1592
+ const activeChangeDir = path4.join(rootDir, "specs", "changes", "active", changeId);
1593
+ if (fs7.existsSync(activeChangeDir)) return activeChangeDir;
1594
+ const completedChangeDir = path4.join(rootDir, "specs", "changes", "completed", changeId);
1595
+ if (fs7.existsSync(completedChangeDir)) return completedChangeDir;
1074
1596
  return activeChangeDir;
1075
1597
  }
1076
1598
  depositSidecar(options) {
1077
1599
  const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
1078
- if (!fs6.existsSync(changeDir)) {
1079
- fs6.mkdirSync(changeDir, { recursive: true });
1600
+ if (!fs7.existsSync(changeDir)) {
1601
+ fs7.mkdirSync(changeDir, { recursive: true });
1080
1602
  }
1081
1603
  const handoff = normalizeHandoffPayload(options.handoff, options.changeId);
1082
1604
  const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
1083
- const filePath = path5.join(changeDir, filename);
1084
- const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml2.dump(handoff, { indent: 2, lineWidth: -1 });
1085
- fs6.writeFileSync(filePath, content, "utf-8");
1605
+ const filePath = path4.join(changeDir, filename);
1606
+ const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3.dump(handoff, { indent: 2, lineWidth: -1 });
1607
+ fs7.writeFileSync(filePath, content, "utf-8");
1086
1608
  return filePath;
1087
1609
  }
1088
1610
  loadSidecar(changeWorkspaceDir) {
1089
- if (!fs6.existsSync(changeWorkspaceDir)) return null;
1611
+ if (!fs7.existsSync(changeWorkspaceDir)) return null;
1090
1612
  const candidateFiles = [
1091
1613
  "handoff.yaml",
1092
1614
  "handoff.yml",
@@ -1097,11 +1619,11 @@ var OpenSpecAdapter = class {
1097
1619
  "product-handoff.json"
1098
1620
  ];
1099
1621
  for (const file of candidateFiles) {
1100
- const fullPath = path5.join(changeWorkspaceDir, file);
1101
- if (fs6.existsSync(fullPath)) {
1622
+ const fullPath = path4.join(changeWorkspaceDir, file);
1623
+ if (fs7.existsSync(fullPath)) {
1102
1624
  try {
1103
- const raw = fs6.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
1104
- const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml2.load(raw);
1625
+ const raw = fs7.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
1626
+ const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3.load(raw);
1105
1627
  if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
1106
1628
  return parsed;
1107
1629
  }
@@ -1112,7 +1634,7 @@ var OpenSpecAdapter = class {
1112
1634
  return null;
1113
1635
  }
1114
1636
  validateWorkspace(changeWorkspaceDir) {
1115
- if (!fs6.existsSync(changeWorkspaceDir)) {
1637
+ if (!fs7.existsSync(changeWorkspaceDir)) {
1116
1638
  return {
1117
1639
  valid: false,
1118
1640
  errors: [`El directorio del cambio OpenSpec no existe: ${changeWorkspaceDir}`],
@@ -1121,7 +1643,7 @@ var OpenSpecAdapter = class {
1121
1643
  }
1122
1644
  const errors = [];
1123
1645
  const warnings = [];
1124
- const specFile = path5.join(changeWorkspaceDir, "spec.md");
1646
+ const specFile = path4.join(changeWorkspaceDir, "spec.md");
1125
1647
  const profile = parseChangeProfile(specFile);
1126
1648
  validateSpecFileContent(specFile, changeWorkspaceDir, errors);
1127
1649
  const { handoff, sidecarFile } = validateOpenSpecStructure(profile, changeWorkspaceDir, this, errors, warnings);
@@ -1135,12 +1657,12 @@ var OpenSpecAdapter = class {
1135
1657
  }
1136
1658
  };
1137
1659
  function parseChangeProfile2(specFile) {
1138
- if (!fs6.existsSync(specFile)) return "standard";
1660
+ if (!fs7.existsSync(specFile)) return "standard";
1139
1661
  try {
1140
- const rawSpec = fs6.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1662
+ const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1141
1663
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1142
1664
  if (match) {
1143
- const parsedFm = yaml2.load(match[1]);
1665
+ const parsedFm = yaml3.load(match[1]);
1144
1666
  if (parsedFm?.profile && ["patch", "standard", "critical"].includes(String(parsedFm.profile))) {
1145
1667
  return parsedFm.profile;
1146
1668
  }
@@ -1159,15 +1681,15 @@ function checkSpecGherkinFormat2(parsedFm, rawSpec, changeWorkspaceDir, errors)
1159
1681
  }
1160
1682
  }
1161
1683
  function validateSpecFileContent2(specFile, changeWorkspaceDir, errors) {
1162
- if (!fs6.existsSync(specFile)) {
1684
+ if (!fs7.existsSync(specFile)) {
1163
1685
  errors.push(`Falta el archivo de especificaci\xF3n obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
1164
1686
  return;
1165
1687
  }
1166
1688
  try {
1167
- const rawSpec = fs6.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1689
+ const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
1168
1690
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1169
1691
  if (match) {
1170
- const parsedFm = yaml2.load(match[1]);
1692
+ const parsedFm = yaml3.load(match[1]);
1171
1693
  if (parsedFm) checkSpecGherkinFormat2(parsedFm, rawSpec, changeWorkspaceDir, errors);
1172
1694
  }
1173
1695
  } catch {
@@ -1183,19 +1705,19 @@ function validateHandoffSidecar2(handoff, changeWorkspaceDir, errors) {
1183
1705
  if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
1184
1706
  errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
1185
1707
  }
1186
- return path5.join(changeWorkspaceDir, "handoff.yaml");
1708
+ return path4.join(changeWorkspaceDir, "handoff.yaml");
1187
1709
  }
1188
1710
  function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
1189
- const planFile = path5.join(changeWorkspaceDir, "plan.md");
1190
- const tasksFile = path5.join(changeWorkspaceDir, "tasks.md");
1711
+ const planFile = path4.join(changeWorkspaceDir, "plan.md");
1712
+ const tasksFile = path4.join(changeWorkspaceDir, "tasks.md");
1191
1713
  if (profile === "patch") {
1192
1714
  const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
1193
- return { handoff: handoff2, sidecarFile: handoff2 ? path5.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
1715
+ return { handoff: handoff2, sidecarFile: handoff2 ? path4.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
1194
1716
  }
1195
- if (!fs6.existsSync(tasksFile)) {
1717
+ if (!fs7.existsSync(tasksFile)) {
1196
1718
  errors.push(`Falta el archivo de tareas obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
1197
1719
  }
1198
- if (!fs6.existsSync(planFile)) {
1720
+ if (!fs7.existsSync(planFile)) {
1199
1721
  warnings.push(`Se recomienda incluir 'plan.md' en el espacio de trabajo Spec Kit.`);
1200
1722
  }
1201
1723
  const handoff = adapter.loadSidecar(changeWorkspaceDir);
@@ -1203,18 +1725,18 @@ function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors,
1203
1725
  return { handoff, sidecarFile };
1204
1726
  }
1205
1727
  function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
1206
- const changeId = path5.basename(changeWorkspaceDir);
1207
- const specFile = path5.join(changeWorkspaceDir, "spec.md");
1208
- const planFile = path5.join(changeWorkspaceDir, "plan.md");
1209
- const tasksFile = path5.join(changeWorkspaceDir, "tasks.md");
1728
+ const changeId = path4.basename(changeWorkspaceDir);
1729
+ const specFile = path4.join(changeWorkspaceDir, "spec.md");
1730
+ const planFile = path4.join(changeWorkspaceDir, "plan.md");
1731
+ const tasksFile = path4.join(changeWorkspaceDir, "tasks.md");
1210
1732
  return {
1211
1733
  framework: "speckit",
1212
1734
  changeId,
1213
1735
  changeDir: changeWorkspaceDir,
1214
1736
  profile,
1215
- specFile: fs6.existsSync(specFile) ? specFile : void 0,
1216
- designFile: fs6.existsSync(planFile) ? planFile : void 0,
1217
- tasksFile: fs6.existsSync(tasksFile) ? tasksFile : void 0,
1737
+ specFile: fs7.existsSync(specFile) ? specFile : void 0,
1738
+ designFile: fs7.existsSync(planFile) ? planFile : void 0,
1739
+ tasksFile: fs7.existsSync(tasksFile) ? tasksFile : void 0,
1218
1740
  sidecarFile,
1219
1741
  handoff: handoff || void 0
1220
1742
  };
@@ -1222,18 +1744,18 @@ function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecar
1222
1744
  var SpecKitAdapter = class {
1223
1745
  framework = "speckit";
1224
1746
  getChangeWorkspaceDir(rootDir, changeId) {
1225
- const standardDir = path5.join(rootDir, "specs", changeId);
1226
- if (fs6.existsSync(standardDir)) return standardDir;
1227
- const activeChangeDir = path5.join(rootDir, "specs", "changes", "active", changeId);
1228
- if (fs6.existsSync(activeChangeDir)) return activeChangeDir;
1229
- const completedChangeDir = path5.join(rootDir, "specs", "changes", "completed", changeId);
1230
- if (fs6.existsSync(completedChangeDir)) return completedChangeDir;
1747
+ const standardDir = path4.join(rootDir, "specs", changeId);
1748
+ if (fs7.existsSync(standardDir)) return standardDir;
1749
+ const activeChangeDir = path4.join(rootDir, "specs", "changes", "active", changeId);
1750
+ if (fs7.existsSync(activeChangeDir)) return activeChangeDir;
1751
+ const completedChangeDir = path4.join(rootDir, "specs", "changes", "completed", changeId);
1752
+ if (fs7.existsSync(completedChangeDir)) return completedChangeDir;
1231
1753
  return standardDir;
1232
1754
  }
1233
1755
  depositSidecar(options) {
1234
1756
  const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
1235
- if (!fs6.existsSync(changeDir)) {
1236
- fs6.mkdirSync(changeDir, { recursive: true });
1757
+ if (!fs7.existsSync(changeDir)) {
1758
+ fs7.mkdirSync(changeDir, { recursive: true });
1237
1759
  }
1238
1760
  const handoff = { ...options.handoff };
1239
1761
  if (!handoff.id.startsWith("HOF-")) {
@@ -1246,20 +1768,20 @@ var SpecKitAdapter = class {
1246
1768
  handoff.createdAt = (/* @__PURE__ */ new Date()).toISOString();
1247
1769
  }
1248
1770
  const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
1249
- const filePath = path5.join(changeDir, filename);
1250
- const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml2.dump(handoff, { indent: 2, lineWidth: -1 });
1251
- fs6.writeFileSync(filePath, content, "utf-8");
1252
- const specifyMemoryDir = path5.join(options.rootDir, ".specify", "memory");
1253
- if (fs6.existsSync(path5.join(options.rootDir, ".specify"))) {
1254
- if (!fs6.existsSync(specifyMemoryDir)) {
1255
- fs6.mkdirSync(specifyMemoryDir, { recursive: true });
1771
+ const filePath = path4.join(changeDir, filename);
1772
+ const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3.dump(handoff, { indent: 2, lineWidth: -1 });
1773
+ fs7.writeFileSync(filePath, content, "utf-8");
1774
+ const specifyMemoryDir = path4.join(options.rootDir, ".specify", "memory");
1775
+ if (fs7.existsSync(path4.join(options.rootDir, ".specify"))) {
1776
+ if (!fs7.existsSync(specifyMemoryDir)) {
1777
+ fs7.mkdirSync(specifyMemoryDir, { recursive: true });
1256
1778
  }
1257
- fs6.writeFileSync(path5.join(specifyMemoryDir, `${options.changeId}.handoff.yaml`), content, "utf-8");
1779
+ fs7.writeFileSync(path4.join(specifyMemoryDir, `${options.changeId}.handoff.yaml`), content, "utf-8");
1258
1780
  }
1259
1781
  return filePath;
1260
1782
  }
1261
1783
  loadSidecar(changeWorkspaceDir) {
1262
- if (!fs6.existsSync(changeWorkspaceDir)) return null;
1784
+ if (!fs7.existsSync(changeWorkspaceDir)) return null;
1263
1785
  const candidateFiles = [
1264
1786
  "handoff.yaml",
1265
1787
  "handoff.yml",
@@ -1269,11 +1791,11 @@ var SpecKitAdapter = class {
1269
1791
  "handoff.json"
1270
1792
  ];
1271
1793
  for (const file of candidateFiles) {
1272
- const fullPath = path5.join(changeWorkspaceDir, file);
1273
- if (fs6.existsSync(fullPath)) {
1794
+ const fullPath = path4.join(changeWorkspaceDir, file);
1795
+ if (fs7.existsSync(fullPath)) {
1274
1796
  try {
1275
- const raw = fs6.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
1276
- const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml2.load(raw);
1797
+ const raw = fs7.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
1798
+ const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3.load(raw);
1277
1799
  if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
1278
1800
  return parsed;
1279
1801
  }
@@ -1284,7 +1806,7 @@ var SpecKitAdapter = class {
1284
1806
  return null;
1285
1807
  }
1286
1808
  validateWorkspace(changeWorkspaceDir) {
1287
- if (!fs6.existsSync(changeWorkspaceDir)) {
1809
+ if (!fs7.existsSync(changeWorkspaceDir)) {
1288
1810
  return {
1289
1811
  valid: false,
1290
1812
  errors: [`El directorio del cambio Spec Kit no existe: ${changeWorkspaceDir}`],
@@ -1293,7 +1815,7 @@ var SpecKitAdapter = class {
1293
1815
  }
1294
1816
  const errors = [];
1295
1817
  const warnings = [];
1296
- const specFile = path5.join(changeWorkspaceDir, "spec.md");
1818
+ const specFile = path4.join(changeWorkspaceDir, "spec.md");
1297
1819
  const profile = parseChangeProfile2(specFile);
1298
1820
  validateSpecFileContent2(specFile, changeWorkspaceDir, errors);
1299
1821
  const { handoff, sidecarFile } = validateSpecKitStructure(profile, changeWorkspaceDir, this, errors, warnings);
@@ -1436,27 +1958,27 @@ ${frontmatter}---
1436
1958
  ${body}`;
1437
1959
  }
1438
1960
  function materializeCanonicalArtifact(rootDir, item, changeId, author, date) {
1439
- const targetPath = path5.join(rootDir, item.targetDir, `${item.id}.md`);
1440
- if (fs6.existsSync(targetPath)) return targetPath;
1441
- fs6.mkdirSync(path5.dirname(targetPath), { recursive: true });
1961
+ const targetPath = path4.join(rootDir, item.targetDir, `${item.id}.md`);
1962
+ if (fs7.existsSync(targetPath)) return targetPath;
1963
+ fs7.mkdirSync(path4.dirname(targetPath), { recursive: true });
1442
1964
  const initialContent = buildCanonicalInitialContent(item, changeId, author, date);
1443
- fs6.writeFileSync(targetPath, initialContent, "utf-8");
1965
+ fs7.writeFileSync(targetPath, initialContent, "utf-8");
1444
1966
  return targetPath;
1445
1967
  }
1446
1968
  function findChangeWorkspaceDir(rootDir, changeId) {
1447
- const activeDir = path5.join(rootDir, "specs", "changes", "active", changeId);
1448
- if (fs6.existsSync(activeDir)) return activeDir;
1449
- const completedDir = path5.join(rootDir, "specs", "changes", "completed", changeId);
1450
- if (fs6.existsSync(completedDir)) return completedDir;
1451
- const openspecActive = path5.join(rootDir, "openspec", "changes", changeId);
1452
- if (fs6.existsSync(openspecActive)) return openspecActive;
1969
+ const activeDir = path4.join(rootDir, "specs", "changes", "active", changeId);
1970
+ if (fs7.existsSync(activeDir)) return activeDir;
1971
+ const completedDir = path4.join(rootDir, "specs", "changes", "completed", changeId);
1972
+ if (fs7.existsSync(completedDir)) return completedDir;
1973
+ const openspecActive = path4.join(rootDir, "openspec", "changes", changeId);
1974
+ if (fs7.existsSync(openspecActive)) return openspecActive;
1453
1975
  return null;
1454
1976
  }
1455
1977
  function checkTasksExecution(changeDir, changeId) {
1456
- const tasksFile = path5.join(changeDir, "tasks.md");
1978
+ const tasksFile = path4.join(changeDir, "tasks.md");
1457
1979
  const allTasks = [];
1458
- if (fs6.existsSync(tasksFile)) {
1459
- allTasks.push(...parseTasksFromContent(fs6.readFileSync(tasksFile, "utf-8")));
1980
+ if (fs7.existsSync(tasksFile)) {
1981
+ allTasks.push(...parseTasksFromContent(fs7.readFileSync(tasksFile, "utf-8")));
1460
1982
  }
1461
1983
  const completedTasks = allTasks.filter((t) => t.status === "COMPLETED").map((t) => t.id);
1462
1984
  const pendingTasks = allTasks.filter((t) => t.status !== "COMPLETED").map((t) => t.id);
@@ -1473,9 +1995,9 @@ function checkTasksExecution(changeDir, changeId) {
1473
1995
  function resolveChangeHandoff(changeDir, changeId) {
1474
1996
  const sidecar = loadProductHandoffSidecar(changeDir);
1475
1997
  if (sidecar) return sidecar;
1476
- const proposalFile = path5.join(changeDir, "proposal.md");
1477
- if (!fs6.existsSync(proposalFile)) return null;
1478
- const propContent = fs6.readFileSync(proposalFile, "utf-8");
1998
+ const proposalFile = path4.join(changeDir, "proposal.md");
1999
+ if (!fs7.existsSync(proposalFile)) return null;
2000
+ const propContent = fs7.readFileSync(proposalFile, "utf-8");
1479
2001
  const { frontmatter } = parseFrontmatter(propContent);
1480
2002
  const citations = Array.isArray(frontmatter.citations) ? frontmatter.citations : [];
1481
2003
  const reqs = citations.map((c) => c.id || "").filter((id) => id.startsWith("FR-") || id.startsWith("QR-") || id.startsWith("CON-") || id.startsWith("SEC-REQ-"));
@@ -1489,9 +2011,9 @@ function resolveChangeHandoff(changeDir, changeId) {
1489
2011
  }
1490
2012
  function materializeNewCatalogArtifacts(changeDir, rootDir, changeId, author, date, errors) {
1491
2013
  const created = [];
1492
- const specFile = path5.join(changeDir, "spec.md");
1493
- if (!fs6.existsSync(specFile)) return created;
1494
- const specContent = fs6.readFileSync(specFile, "utf-8");
2014
+ const specFile = path4.join(changeDir, "spec.md");
2015
+ if (!fs7.existsSync(specFile)) return created;
2016
+ const specContent = fs7.readFileSync(specFile, "utf-8");
1495
2017
  const { newItems } = parseSpecCatalogScope(specContent);
1496
2018
  for (const item of newItems) {
1497
2019
  try {
@@ -1508,10 +2030,10 @@ function loadCanonicalArtifacts(rootDir) {
1508
2030
  const allMdFiles = walkMdFiles(rootDir);
1509
2031
  const canonicalArtifacts = /* @__PURE__ */ new Map();
1510
2032
  for (const file of allMdFiles) {
1511
- const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
2033
+ const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
1512
2034
  if (relFile.includes("specs/changes/") || relFile.includes("templates/")) continue;
1513
2035
  try {
1514
- const content = fs6.readFileSync(file, "utf-8");
2036
+ const content = fs7.readFileSync(file, "utf-8");
1515
2037
  const { frontmatter, body } = parseFrontmatter(content);
1516
2038
  if (frontmatter.id) canonicalArtifacts.set(String(frontmatter.id), { file, frontmatter, body });
1517
2039
  } catch {
@@ -1521,10 +2043,10 @@ function loadCanonicalArtifacts(rootDir) {
1521
2043
  }
1522
2044
  function writeUpdatedArtifact(file, frontmatter, body) {
1523
2045
  const yamlHeader = `---
1524
- ${yaml2.dump(frontmatter, { indent: 2, lineWidth: -1 }).trim()}
2046
+ ${yaml3.dump(frontmatter, { indent: 2, lineWidth: -1 }).trim()}
1525
2047
  ---
1526
2048
  `;
1527
- fs6.writeFileSync(file, yamlHeader + body, "utf-8");
2049
+ fs7.writeFileSync(file, yamlHeader + body, "utf-8");
1528
2050
  }
1529
2051
  function updateSingleCanonicalRequirement(canonical, completedTasks, changeId, date, author) {
1530
2052
  const fm = canonical.frontmatter;
@@ -1636,28 +2158,28 @@ function updateArchitectureComponents(citedComponents, canonicalArtifacts, targe
1636
2158
  return updated;
1637
2159
  }
1638
2160
  function moveToCompletedFolder(changeDir, changeId, rootDir) {
1639
- const completedParent = path5.join(rootDir, "specs", "changes", "completed");
1640
- if (!fs6.existsSync(completedParent)) fs6.mkdirSync(completedParent, { recursive: true });
1641
- const destDir = path5.join(completedParent, changeId);
1642
- if (fs6.existsSync(destDir)) fs6.rmSync(destDir, { recursive: true, force: true });
1643
- fs6.renameSync(changeDir, destDir);
1644
- const destProposal = path5.join(destDir, "proposal.md");
1645
- if (fs6.existsSync(destProposal)) {
1646
- const { frontmatter, body } = parseFrontmatter(fs6.readFileSync(destProposal, "utf-8"));
2161
+ const completedParent = path4.join(rootDir, "specs", "changes", "completed");
2162
+ if (!fs7.existsSync(completedParent)) fs7.mkdirSync(completedParent, { recursive: true });
2163
+ const destDir = path4.join(completedParent, changeId);
2164
+ if (fs7.existsSync(destDir)) fs7.rmSync(destDir, { recursive: true, force: true });
2165
+ fs7.renameSync(changeDir, destDir);
2166
+ const destProposal = path4.join(destDir, "proposal.md");
2167
+ if (fs7.existsSync(destProposal)) {
2168
+ const { frontmatter, body } = parseFrontmatter(fs7.readFileSync(destProposal, "utf-8"));
1647
2169
  frontmatter.status = "applied";
1648
2170
  writeUpdatedArtifact(destProposal, frontmatter, body);
1649
2171
  }
1650
2172
  return destDir;
1651
2173
  }
1652
2174
  function archiveChangeWorkspace(changeDir, changeId, rootDir, autoArchive, completedAll) {
1653
- const isActiveFolder = changeDir.includes("specs" + path5.sep + "changes" + path5.sep + "active");
2175
+ const isActiveFolder = changeDir.includes("specs" + path4.sep + "changes" + path4.sep + "active");
1654
2176
  if (completedAll && autoArchive && isActiveFolder) {
1655
2177
  const destDir = moveToCompletedFolder(changeDir, changeId, rootDir);
1656
2178
  return { archived: true, archivedPath: destDir };
1657
2179
  }
1658
- const proposalFile = path5.join(changeDir, "proposal.md");
1659
- if (fs6.existsSync(proposalFile)) {
1660
- const { frontmatter, body } = parseFrontmatter(fs6.readFileSync(proposalFile, "utf-8"));
2180
+ const proposalFile = path4.join(changeDir, "proposal.md");
2181
+ if (fs7.existsSync(proposalFile)) {
2182
+ const { frontmatter, body } = parseFrontmatter(fs7.readFileSync(proposalFile, "utf-8"));
1661
2183
  if (completedAll) frontmatter.status = "completed";
1662
2184
  writeUpdatedArtifact(proposalFile, frontmatter, body);
1663
2185
  }
@@ -1751,8 +2273,8 @@ function resolveAuditStatus(integratedCount, targetCount, gapsCount) {
1751
2273
  function auditChangeEntry(changeDir, changeName, canonicalMap) {
1752
2274
  const handoff = loadProductHandoffSidecar(changeDir);
1753
2275
  if (!handoff) return null;
1754
- const tasksFile = path5.join(changeDir, "tasks.md");
1755
- const tasks = fs6.existsSync(tasksFile) ? parseTasksFromContent(fs6.readFileSync(tasksFile, "utf-8")) : [];
2276
+ const tasksFile = path4.join(changeDir, "tasks.md");
2277
+ const tasks = fs7.existsSync(tasksFile) ? parseTasksFromContent(fs7.readFileSync(tasksFile, "utf-8")) : [];
1756
2278
  const completed = tasks.filter((t) => t.status === "COMPLETED");
1757
2279
  const targetReqs = handoff.subgraph?.requirements || [];
1758
2280
  const { integrated, missing, gaps } = auditChangeRequirements(targetReqs, canonicalMap);
@@ -1768,11 +2290,11 @@ function auditChangeEntry(changeDir, changeName, canonicalMap) {
1768
2290
  };
1769
2291
  }
1770
2292
  function collectParentDirAudits(parent, canonicalMap, audits, errors) {
1771
- if (!fs6.existsSync(parent)) return;
1772
- const entries = fs6.readdirSync(parent, { withFileTypes: true });
2293
+ if (!fs7.existsSync(parent)) return;
2294
+ const entries = fs7.readdirSync(parent, { withFileTypes: true });
1773
2295
  for (const e of entries) {
1774
2296
  if (!e.isDirectory()) continue;
1775
- const audit = auditChangeEntry(path5.join(parent, e.name), e.name, canonicalMap);
2297
+ const audit = auditChangeEntry(path4.join(parent, e.name), e.name, canonicalMap);
1776
2298
  if (audit) {
1777
2299
  if (audit.gaps.length > 0) errors.push(`[${e.name}] Desalineaci\xF3n: ${audit.gaps.join("; ")}`);
1778
2300
  audits.push(audit);
@@ -1784,8 +2306,8 @@ function verifySddIntegration(options = {}) {
1784
2306
  const audits = [];
1785
2307
  const errors = [];
1786
2308
  const searchDirs = [
1787
- path5.join(rootDir, "specs", "changes", "completed"),
1788
- path5.join(rootDir, "specs", "changes", "active")
2309
+ path4.join(rootDir, "specs", "changes", "completed"),
2310
+ path4.join(rootDir, "specs", "changes", "active")
1789
2311
  ];
1790
2312
  const canonicalMap = loadCanonicalArtifacts(rootDir);
1791
2313
  for (const parent of searchDirs) {
@@ -1833,10 +2355,10 @@ function resolveMatchFromMetadata(activeFolders, options) {
1833
2355
  return {};
1834
2356
  }
1835
2357
  function validateActiveDirectory(activeParent) {
1836
- if (!fs6.existsSync(activeParent)) {
2358
+ if (!fs7.existsSync(activeParent)) {
1837
2359
  return { ok: false, folders: [], error: `No existe el directorio de cambios activos en '${activeParent}'.` };
1838
2360
  }
1839
- const entries = fs6.readdirSync(activeParent, { withFileTypes: true });
2361
+ const entries = fs7.readdirSync(activeParent, { withFileTypes: true });
1840
2362
  const activeFolders = entries.filter((e) => e.isDirectory()).map((e) => e.name);
1841
2363
  if (activeFolders.length === 0) {
1842
2364
  return { ok: false, folders: [], error: "No se encontraron carpetas de cambios activos en specs/changes/active/." };
@@ -1868,7 +2390,7 @@ function evaluateTasksProgress(changeId, tasks) {
1868
2390
  }
1869
2391
  function detectActiveChangeForIntegration(options = {}) {
1870
2392
  const rootDir = options.rootDir || process.cwd();
1871
- const activeParent = path5.join(rootDir, "specs", "changes", "active");
2393
+ const activeParent = path4.join(rootDir, "specs", "changes", "active");
1872
2394
  const dirCheck = validateActiveDirectory(activeParent);
1873
2395
  if (!dirCheck.ok) {
1874
2396
  return {
@@ -1883,9 +2405,9 @@ function detectActiveChangeForIntegration(options = {}) {
1883
2405
  if (!match.changeId) {
1884
2406
  return buildUnmatchedActiveResult(dirCheck.folders, options);
1885
2407
  }
1886
- const changeDir = path5.join(activeParent, match.changeId);
1887
- const tasksFile = path5.join(changeDir, "tasks.md");
1888
- const tasks = fs6.existsSync(tasksFile) ? parseTasksFromContent(fs6.readFileSync(tasksFile, "utf-8")) : [];
2408
+ const changeDir = path4.join(activeParent, match.changeId);
2409
+ const tasksFile = path4.join(changeDir, "tasks.md");
2410
+ const tasks = fs7.existsSync(tasksFile) ? parseTasksFromContent(fs7.readFileSync(tasksFile, "utf-8")) : [];
1889
2411
  const progress = evaluateTasksProgress(match.changeId, tasks);
1890
2412
  const reasons = match.reason ? [match.reason] : [];
1891
2413
  if (progress.error) reasons.push(progress.error);
@@ -2016,9 +2538,9 @@ function isPdacDeliveryDoc(relFile) {
2016
2538
  }
2017
2539
  function processPdacFile(file, rootDir, baselineMap, allCitations) {
2018
2540
  try {
2019
- const content = fs6.readFileSync(file, "utf-8");
2541
+ const content = fs7.readFileSync(file, "utf-8");
2020
2542
  const { id, type, title } = parseFrontmatterId(content);
2021
- const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
2543
+ const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
2022
2544
  if (id) {
2023
2545
  const citations = extractCitations(content, relFile);
2024
2546
  baselineMap.set(id, {
@@ -2146,22 +2668,22 @@ function updateCitationsInContent(content, baselineMap, sourceFile, details) {
2146
2668
  }
2147
2669
  function collectCandidateFilesForSync(rootDir, targetPath) {
2148
2670
  if (targetPath) {
2149
- const fullTarget = path5.isAbsolute(targetPath) ? targetPath : path5.join(rootDir, targetPath);
2150
- if (!fs6.existsSync(fullTarget)) return [];
2151
- return fs6.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
2671
+ const fullTarget = path4.isAbsolute(targetPath) ? targetPath : path4.join(rootDir, targetPath);
2672
+ if (!fs7.existsSync(fullTarget)) return [];
2673
+ return fs7.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
2152
2674
  }
2153
2675
  return walkMdFiles(rootDir).filter((f) => {
2154
- const relFile = path5.relative(rootDir, f).replace(/\\/g, "/");
2676
+ const relFile = path4.relative(rootDir, f).replace(/\\/g, "/");
2155
2677
  return isPdacDeliveryDoc(relFile);
2156
2678
  });
2157
2679
  }
2158
2680
  function syncSingleFile(file, rootDir, baselineMap, details) {
2159
2681
  try {
2160
- const content = fs6.readFileSync(file, "utf-8");
2161
- const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
2682
+ const content = fs7.readFileSync(file, "utf-8");
2683
+ const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
2162
2684
  const { updatedContent, changed } = updateCitationsInContent(content, baselineMap, relFile, details);
2163
2685
  if (changed) {
2164
- fs6.writeFileSync(file, updatedContent, "utf-8");
2686
+ fs7.writeFileSync(file, updatedContent, "utf-8");
2165
2687
  return relFile;
2166
2688
  }
2167
2689
  } catch {
@@ -2281,7 +2803,7 @@ function extractFrontmatter(content) {
2281
2803
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
2282
2804
  if (!match) return null;
2283
2805
  try {
2284
- const parsed = yaml2.load(match[1]);
2806
+ const parsed = yaml3.load(match[1]);
2285
2807
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2286
2808
  return parsed;
2287
2809
  }
@@ -2317,10 +2839,10 @@ var EXCLUDED_SCHEMA_DIRS = /* @__PURE__ */ new Set([
2317
2839
  ]);
2318
2840
  function walkHandoffSidecars(dir, fileList = []) {
2319
2841
  try {
2320
- if (!fs6.existsSync(dir)) return fileList;
2321
- for (const file of fs6.readdirSync(dir)) {
2322
- const fullPath = path5.join(dir, file);
2323
- if (fs6.statSync(fullPath).isDirectory()) {
2842
+ if (!fs7.existsSync(dir)) return fileList;
2843
+ for (const file of fs7.readdirSync(dir)) {
2844
+ const fullPath = path4.join(dir, file);
2845
+ if (fs7.statSync(fullPath).isDirectory()) {
2324
2846
  if (!EXCLUDED_SCHEMA_DIRS.has(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
2325
2847
  walkHandoffSidecars(fullPath, fileList);
2326
2848
  }
@@ -2335,7 +2857,7 @@ function walkHandoffSidecars(dir, fileList = []) {
2335
2857
  function extractArtifactData(filePath, content) {
2336
2858
  if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
2337
2859
  try {
2338
- const parsed = yaml2.load(content);
2860
+ const parsed = yaml3.load(content);
2339
2861
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
2340
2862
  } catch {
2341
2863
  return null;
@@ -2484,10 +3006,10 @@ function validateAgainstSchema(value, schema, propPath = "") {
2484
3006
  return validateTypeSpecificRules(value, schema, propPath, currentProp);
2485
3007
  }
2486
3008
  function findSchemaFile(relSchemaPath, rootDir) {
2487
- const fullPath = path5.join(rootDir, relSchemaPath);
2488
- if (fs6.existsSync(fullPath)) return fullPath;
2489
- const fallbackPath = path5.join(process.cwd(), relSchemaPath);
2490
- if (fs6.existsSync(fallbackPath)) return fallbackPath;
3009
+ const fullPath = path4.join(rootDir, relSchemaPath);
3010
+ if (fs7.existsSync(fullPath)) return fullPath;
3011
+ const fallbackPath = path4.join(process.cwd(), relSchemaPath);
3012
+ if (fs7.existsSync(fallbackPath)) return fallbackPath;
2491
3013
  return null;
2492
3014
  }
2493
3015
  function resolveSchemaForArtifact(frontmatter, rootDir) {
@@ -2497,7 +3019,7 @@ function resolveSchemaForArtifact(frontmatter, rootDir) {
2497
3019
  const fullPath = findSchemaFile(relSchemaPath, rootDir);
2498
3020
  if (!fullPath) return null;
2499
3021
  try {
2500
- const raw = fs6.readFileSync(fullPath, "utf-8");
3022
+ const raw = fs7.readFileSync(fullPath, "utf-8");
2501
3023
  return { schemaPath: relSchemaPath, schema: JSON.parse(raw) };
2502
3024
  } catch {
2503
3025
  return null;
@@ -2505,15 +3027,15 @@ function resolveSchemaForArtifact(frontmatter, rootDir) {
2505
3027
  }
2506
3028
  function loadSchemaFromPathOrType(schemaOrType, rootDir) {
2507
3029
  if (schemaOrType.endsWith(".json")) {
2508
- const fullPath = path5.isAbsolute(schemaOrType) ? schemaOrType : findSchemaFile(schemaOrType, rootDir);
2509
- if (fullPath && fs6.existsSync(fullPath)) {
2510
- return { schema: JSON.parse(fs6.readFileSync(fullPath, "utf-8")), schemaId: schemaOrType };
3030
+ const fullPath = path4.isAbsolute(schemaOrType) ? schemaOrType : findSchemaFile(schemaOrType, rootDir);
3031
+ if (fullPath && fs7.existsSync(fullPath)) {
3032
+ return { schema: JSON.parse(fs7.readFileSync(fullPath, "utf-8")), schemaId: schemaOrType };
2511
3033
  }
2512
3034
  } else if (TYPE_TO_SCHEMA_MAP[schemaOrType]) {
2513
3035
  const relPath = TYPE_TO_SCHEMA_MAP[schemaOrType];
2514
3036
  const fullPath = findSchemaFile(relPath, rootDir);
2515
- if (fullPath && fs6.existsSync(fullPath)) {
2516
- return { schema: JSON.parse(fs6.readFileSync(fullPath, "utf-8")), schemaId: relPath };
3037
+ if (fullPath && fs7.existsSync(fullPath)) {
3038
+ return { schema: JSON.parse(fs7.readFileSync(fullPath, "utf-8")), schemaId: relPath };
2517
3039
  }
2518
3040
  }
2519
3041
  return { schema: null };
@@ -2551,9 +3073,9 @@ function collectCandidateSchemaFiles(options, rootDir) {
2551
3073
  if (!options.targetPath) {
2552
3074
  return [...walkMdFiles(rootDir), ...walkHandoffSidecars(rootDir)];
2553
3075
  }
2554
- const target = path5.isAbsolute(options.targetPath) ? options.targetPath : path5.join(rootDir, options.targetPath);
2555
- if (!fs6.existsSync(target)) return [];
2556
- if (fs6.statSync(target).isDirectory()) {
3076
+ const target = path4.isAbsolute(options.targetPath) ? options.targetPath : path4.join(rootDir, options.targetPath);
3077
+ if (!fs7.existsSync(target)) return [];
3078
+ if (fs7.statSync(target).isDirectory()) {
2557
3079
  return [...walkMdFiles(target), ...walkHandoffSidecars(target)];
2558
3080
  }
2559
3081
  if (target.endsWith(".md") || target.endsWith(".yaml") || target.endsWith(".yml")) {
@@ -2604,7 +3126,7 @@ function buildEvaluationFailure(relPath, err) {
2604
3126
  }
2605
3127
  function evaluateArtifactFile(file, relPath, rootDir) {
2606
3128
  try {
2607
- const content = fs6.readFileSync(file, "utf-8");
3129
+ const content = fs7.readFileSync(file, "utf-8");
2608
3130
  const frontmatter = extractArtifactData(file, content);
2609
3131
  if (!frontmatter || !frontmatter.id) return null;
2610
3132
  const schemaInfo = resolveSchemaForArtifact(frontmatter, rootDir);
@@ -2639,7 +3161,7 @@ function verifyArtifactsSchemas(options = {}) {
2639
3161
  let validCount = 0;
2640
3162
  let totalEvaluated = 0;
2641
3163
  for (const file of filesToScan) {
2642
- const relPath = path5.relative(rootDir, file).replace(/\\/g, "/");
3164
+ const relPath = path4.relative(rootDir, file).replace(/\\/g, "/");
2643
3165
  if (isIgnoredArtifactPath(relPath, Boolean(options.targetPath))) continue;
2644
3166
  const evaluation = evaluateArtifactFile(file, relPath, rootDir);
2645
3167
  if (!evaluation) continue;
@@ -2658,10 +3180,10 @@ function slugify(text) {
2658
3180
  return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2659
3181
  }
2660
3182
  function extractDirMaxNum(dir) {
2661
- if (!fs6.existsSync(dir)) return 0;
3183
+ if (!fs7.existsSync(dir)) return 0;
2662
3184
  let max = 0;
2663
3185
  try {
2664
- const entries = fs6.readdirSync(dir, { withFileTypes: true });
3186
+ const entries = fs7.readdirSync(dir, { withFileTypes: true });
2665
3187
  for (const e of entries) {
2666
3188
  if (!e.isDirectory()) continue;
2667
3189
  const match = e.name.match(/^(?:chg|CHG|patch|PATCH)-(\d+)/i);
@@ -2678,10 +3200,10 @@ function extractDirMaxNum(dir) {
2678
3200
  }
2679
3201
  function getNextCorrelativeNumber(rootDir) {
2680
3202
  const candidateDirs = [
2681
- path5.join(rootDir, "specs", "changes", "active"),
2682
- path5.join(rootDir, "specs", "changes", "completed"),
2683
- path5.join(rootDir, "specs"),
2684
- path5.join(rootDir, "openspec", "changes")
3203
+ path4.join(rootDir, "specs", "changes", "active"),
3204
+ path4.join(rootDir, "specs", "changes", "completed"),
3205
+ path4.join(rootDir, "specs"),
3206
+ path4.join(rootDir, "openspec", "changes")
2685
3207
  ];
2686
3208
  let maxNum = 0;
2687
3209
  for (const dir of candidateDirs) {
@@ -2711,13 +3233,13 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
2711
3233
  };
2712
3234
  }
2713
3235
  function ensureEmptyChangeDir(changeDir) {
2714
- if (fs6.existsSync(changeDir)) {
2715
- const existing = fs6.readdirSync(changeDir);
3236
+ if (fs7.existsSync(changeDir)) {
3237
+ const existing = fs7.readdirSync(changeDir);
2716
3238
  if (existing.length > 0) {
2717
3239
  return `El directorio del cambio '${changeDir}' ya existe y no est\xE1 vac\xEDo.`;
2718
3240
  }
2719
3241
  } else {
2720
- fs6.mkdirSync(changeDir, { recursive: true });
3242
+ fs7.mkdirSync(changeDir, { recursive: true });
2721
3243
  }
2722
3244
  return null;
2723
3245
  }
@@ -2726,7 +3248,7 @@ function scanCanonicalArtifacts(rootDir) {
2726
3248
  const artifactMap = /* @__PURE__ */ new Map();
2727
3249
  for (const file of allMdFiles) {
2728
3250
  try {
2729
- const content = fs6.readFileSync(file, "utf-8");
3251
+ const content = fs7.readFileSync(file, "utf-8");
2730
3252
  const { frontmatter } = parseFrontmatter(content);
2731
3253
  if (frontmatter?.id) {
2732
3254
  const id = String(frontmatter.id);
@@ -2754,7 +3276,7 @@ function parseInputFromIds(rawFrom) {
2754
3276
  }
2755
3277
  function renderCitationsYamlBlock(citedArtifacts) {
2756
3278
  if (citedArtifacts.length === 0) return "";
2757
- return yaml2.dump(
3279
+ return yaml3.dump(
2758
3280
  citedArtifacts.map((c) => ({ id: c.id, digest: c.digest, comment: c.comment })),
2759
3281
  { indent: 2, lineWidth: -1 }
2760
3282
  ).trim().split("\n").map((line) => ` ${line}`).join("\n");
@@ -2789,16 +3311,39 @@ El sistema DEBE implementar y soportar la capacidad de {{NAME}}.
2789
3311
  ## 2. Criterios de Aceptaci\xF3n en Formato Gherkin (Cucumber)
2790
3312
 
2791
3313
  \`\`\`gherkin
2792
- @{{REQ_ID}} @automated
3314
+ @{{REQ_ID}} @automated @regression
2793
3315
  Feature: {{NAME}}
2794
3316
  As a user or client system
2795
3317
  I want to execute the capability for {{NAME}}
2796
3318
  So that the expected outcome and value are achieved
2797
3319
 
3320
+ @nominal
2798
3321
  Scenario: Nominal successful flow
2799
3322
  Given the system is in an operational state
2800
3323
  When the operation for "{{NAME}}" is executed
2801
3324
  Then the operation completes successfully
3325
+
3326
+ @boundary
3327
+ Scenario Outline: Boundary limits validation
3328
+ Given the system receives boundary input "<boundary_input>"
3329
+ When the operation for "{{NAME}}" is evaluated at range limits
3330
+ Then the system responds with expected boundary status "<expected_status>"
3331
+
3332
+ Examples:
3333
+ | boundary_input | expected_status |
3334
+ | min_bound | 200 |
3335
+ | max_bound | 200 |
3336
+
3337
+ @invalid
3338
+ Scenario Outline: Out-of-range and invalid payload rejection
3339
+ Given the system receives invalid or out-of-range input "<invalid_input>"
3340
+ When the operation for "{{NAME}}" is invoked
3341
+ Then the system rejects the request with error "<error_code>"
3342
+
3343
+ Examples:
3344
+ | invalid_input | error_code |
3345
+ | empty_payload | ERR_INVALID_ARGS |
3346
+ | overflow_val | ERR_OUT_OF_RANGE |
2802
3347
  \`\`\`
2803
3348
 
2804
3349
  ---
@@ -2817,12 +3362,33 @@ Definici\xF3n de artefacto citado en {{CANONICAL_ID}}.
2817
3362
  ## 2. Criterios de Aceptaci\xF3n en Formato Gherkin (Cucumber)
2818
3363
 
2819
3364
  \`\`\`gherkin
2820
- @{{ID}} @automated
3365
+ @{{ID}} @automated @regression
2821
3366
  Feature: {{NAME}}
3367
+ @nominal
2822
3368
  Scenario: Expected behavior verification
2823
3369
  Given the system is initialized for "{{NAME}}"
2824
3370
  When the request associated with {{ID}} is processed
2825
3371
  Then the response satisfies acceptance criteria
3372
+
3373
+ @boundary
3374
+ Scenario Outline: Boundary limits verification
3375
+ Given boundary input "<input>" for {{ID}}
3376
+ When the request is processed
3377
+ Then the response status is "<status>"
3378
+
3379
+ Examples:
3380
+ | input | status |
3381
+ | min | 200 |
3382
+
3383
+ @invalid
3384
+ Scenario Outline: Invalid input rejection
3385
+ Given invalid input "<input>" for {{ID}}
3386
+ When the request is processed
3387
+ Then the request is rejected with "<error>"
3388
+
3389
+ Examples:
3390
+ | input | error |
3391
+ | null | ERR_INVALID_ARGS |
2826
3392
  \`\`\`
2827
3393
  `;
2828
3394
  var SPEC_BODY = `# Delivery Specification: {{CANONICAL_ID}}
@@ -2836,10 +3402,33 @@ Feature: {{NAME}}
2836
3402
  I want to process the operation for {{NAME}}
2837
3403
  So that delivery criteria are satisfied
2838
3404
 
3405
+ @nominal
2839
3406
  Scenario: Nominal successful flow
2840
3407
  Given the system is in a nominal operational state
2841
3408
  When the operation for "{{NAME}}" is processed
2842
3409
  Then the operation completes successfully satisfying acceptance criteria
3410
+
3411
+ @boundary
3412
+ Scenario Outline: Boundary conditions verification
3413
+ Given the system is configured with boundary value "<value>"
3414
+ When the operation for "{{NAME}}" is processed
3415
+ Then the boundary result matches "<expected>"
3416
+
3417
+ Examples:
3418
+ | value | expected |
3419
+ | min | accepted |
3420
+ | max | accepted |
3421
+
3422
+ @invalid
3423
+ Scenario Outline: Out-of-range and invalid input handling
3424
+ Given an invalid or malformed payload "<payload>"
3425
+ When the operation for "{{NAME}}" is attempted
3426
+ Then the system rejects the operation with "<error>"
3427
+
3428
+ Examples:
3429
+ | payload | error |
3430
+ | null | ERR_INVALID_DATA |
3431
+ | overflow | ERR_OUT_OF_RANGE |
2843
3432
  \`\`\`
2844
3433
 
2845
3434
  ---
@@ -2944,6 +3533,7 @@ derives-from:
2944
3533
  - {{UC_ID}}
2945
3534
  verifiable-by: cucumber-bdd
2946
3535
  acceptance-format: gherkin
3536
+ enforce-bdd-variants: true
2947
3537
  cucumber-tags:
2948
3538
  - "@{{REQ_ID}}"
2949
3539
  - "@automated"
@@ -2961,6 +3551,7 @@ schema-version: "1.0"
2961
3551
  category: functional
2962
3552
  verifiable-by: cucumber-bdd
2963
3553
  acceptance-format: gherkin
3554
+ enforce-bdd-variants: true
2964
3555
  cucumber-tags:
2965
3556
  - "@{{ID}}"
2966
3557
  - "@automated"
@@ -3036,7 +3627,7 @@ function buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors)
3036
3627
  changeId: slug,
3037
3628
  canonicalId,
3038
3629
  profile: "patch",
3039
- changeDir: path5.dirname(specPath),
3630
+ changeDir: path4.dirname(specPath),
3040
3631
  createdFiles: [specPath],
3041
3632
  citedArtifacts,
3042
3633
  errors
@@ -3047,10 +3638,10 @@ function scaffoldPatchChange(rootDir, changeDir, slug, canonicalId, name, inputF
3047
3638
  const citationsSection = citedArtifacts.length > 0 ? `citations:
3048
3639
  ${renderCitationsYamlBlock(citedArtifacts)}
3049
3640
  ` : "";
3050
- const specPath = path5.join(changeDir, "spec.md");
3641
+ const specPath = path4.join(changeDir, "spec.md");
3051
3642
  const specFmObj = buildPatchFmObj(canonicalId);
3052
3643
  const specContent = buildPatchSpecContent(canonicalId, name, citationsSection);
3053
- fs6.writeFileSync(specPath, specContent, "utf-8");
3644
+ fs7.writeFileSync(specPath, specContent, "utf-8");
3054
3645
  const errors = validatePatchArtifacts(rootDir, changeDir, specContent, specFmObj, adapter);
3055
3646
  return buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors);
3056
3647
  }
@@ -3064,26 +3655,26 @@ function createGreenfieldRequirement(rootDir, name, padNum, canonicalId, author,
3064
3655
  const nameSlugUpper = slugify(name).toUpperCase();
3065
3656
  const reqId = `FR-${padNum}-${nameSlugUpper}-001`;
3066
3657
  const ucId = `UC-${padNum}-${nameSlugUpper}`;
3067
- const productDir = path5.join(rootDir, "specs", "product");
3068
- if (!fs6.existsSync(productDir)) fs6.mkdirSync(productDir, { recursive: true });
3069
- const reqFilePath = path5.join(productDir, `${reqId}.md`);
3070
- if (fs6.existsSync(reqFilePath)) return null;
3658
+ const productDir = path4.join(rootDir, "specs", "product");
3659
+ if (!fs7.existsSync(productDir)) fs7.mkdirSync(productDir, { recursive: true });
3660
+ const reqFilePath = path4.join(productDir, `${reqId}.md`);
3661
+ if (fs7.existsSync(reqFilePath)) return null;
3071
3662
  const reqContent = buildGreenfieldContent(reqId, ucId, name, canonicalId, author, dateStr);
3072
- fs6.writeFileSync(reqFilePath, reqContent, "utf-8");
3663
+ fs7.writeFileSync(reqFilePath, reqContent, "utf-8");
3073
3664
  createdFiles.push(reqFilePath);
3074
3665
  const digest = computeCanonicalSha256(reqContent);
3075
3666
  return { reqId, ucId, reqFilePath, digest };
3076
3667
  }
3077
3668
  function createMissingDraftRequirement(rootDir, name, id, canonicalId, createdFiles) {
3078
- const productDir = path5.join(rootDir, "specs", "product");
3079
- if (!fs6.existsSync(productDir)) fs6.mkdirSync(productDir, { recursive: true });
3080
- const reqFilePath = path5.join(productDir, `${id}.md`);
3669
+ const productDir = path4.join(rootDir, "specs", "product");
3670
+ if (!fs7.existsSync(productDir)) fs7.mkdirSync(productDir, { recursive: true });
3671
+ const reqFilePath = path4.join(productDir, `${id}.md`);
3081
3672
  const reqType = id.startsWith("UC-") ? "use-case" : id.startsWith("SEC-REQ-") ? "security-requirement" : "requirement";
3082
3673
  const header = MISSING_DRAFT_HEADER.replaceAll("{{ID}}", id).replaceAll("{{TYPE}}", reqType).replaceAll("{{NAME}}", name);
3083
3674
  const body = MISSING_DRAFT_BODY.replaceAll("{{ID}}", id).replaceAll("{{NAME}}", name).replaceAll("{{CANONICAL_ID}}", canonicalId);
3084
3675
  const reqContent = `${header}
3085
3676
  ${body}`;
3086
- fs6.writeFileSync(reqFilePath, reqContent, "utf-8");
3677
+ fs7.writeFileSync(reqFilePath, reqContent, "utf-8");
3087
3678
  createdFiles.push(reqFilePath);
3088
3679
  const digest = computeCanonicalSha256(reqContent);
3089
3680
  return { reqFilePath, digest };
@@ -3171,7 +3762,7 @@ function collectChangeCitations(rootDir, name, padNum, canonicalId, author, date
3171
3762
  return col;
3172
3763
  }
3173
3764
  function renderProposalFile(changeDir, canonicalId, name, author, citationsYamlBlock) {
3174
- const proposalPath = path5.join(changeDir, "proposal.md");
3765
+ const proposalPath = path4.join(changeDir, "proposal.md");
3175
3766
  const citationsSection = citationsYamlBlock ? `citations:
3176
3767
  ${citationsYamlBlock}
3177
3768
  ` : "";
@@ -3191,11 +3782,11 @@ ${name}
3191
3782
  ## 2. Dependencias Externas Evaluadas
3192
3783
  - Lista de dependencias nuevas a incorporar y verificaci\xF3n de licencia seg\xFAn \`license-policy.yaml\`.
3193
3784
  `;
3194
- fs6.writeFileSync(proposalPath, proposalContent, "utf-8");
3785
+ fs7.writeFileSync(proposalPath, proposalContent, "utf-8");
3195
3786
  return proposalPath;
3196
3787
  }
3197
3788
  function renderSpecFile(changeDir, canonicalId, name, profile) {
3198
- const specPath = path5.join(changeDir, "spec.md");
3789
+ const specPath = path4.join(changeDir, "spec.md");
3199
3790
  const specFmObj = {
3200
3791
  id: `SPEC-${canonicalId}`,
3201
3792
  type: "delivery-spec",
@@ -3216,12 +3807,12 @@ cucumber-tags:
3216
3807
  ---
3217
3808
  `;
3218
3809
  const body = SPEC_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name);
3219
- fs6.writeFileSync(specPath, `${header}
3810
+ fs7.writeFileSync(specPath, `${header}
3220
3811
  ${body}`, "utf-8");
3221
3812
  return { specPath, specFmObj };
3222
3813
  }
3223
3814
  function renderDesignFile(changeDir, canonicalId, name, author, dateStr, citationsYamlBlock) {
3224
- const designPath = path5.join(changeDir, "design.md");
3815
+ const designPath = path4.join(changeDir, "design.md");
3225
3816
  const citationsSection = citationsYamlBlock ? `citations:
3226
3817
  ${citationsYamlBlock}
3227
3818
  ` : "";
@@ -3236,14 +3827,20 @@ handoff: "HOF-${canonicalId}"
3236
3827
  architecture-component: "CMP-CORE"
3237
3828
  enclave: "SEC-ENC-DMZ"
3238
3829
  status: draft
3830
+ contracts: []
3239
3831
  ${citationsSection}---
3240
3832
  `;
3241
3833
  const body = DESIGN_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
3242
- fs6.writeFileSync(designPath, `${header}
3834
+ fs7.writeFileSync(designPath, `${header}
3243
3835
  ${body}`, "utf-8");
3244
3836
  return designPath;
3245
3837
  }
3246
- function buildTasksFrontmatter(canonicalId, name) {
3838
+ function buildTasksFrontmatter(canonicalId, name, reqIds) {
3839
+ const tasks = DEFAULT_TASKS_ARRAY.map((t) => ({
3840
+ ...t,
3841
+ "implements-components": ["CMP-CORE"],
3842
+ ...reqIds.length > 0 ? { "satisfies-requirements": reqIds } : {}
3843
+ }));
3247
3844
  return {
3248
3845
  id: `TSK-PLAN-${canonicalId}`,
3249
3846
  type: "task-plan",
@@ -3253,20 +3850,20 @@ function buildTasksFrontmatter(canonicalId, name) {
3253
3850
  "schema-version": "1.0",
3254
3851
  status: "draft",
3255
3852
  "governance-summary": GOVERNANCE_SUMMARY_DEFAULT,
3256
- tasks: DEFAULT_TASKS_ARRAY,
3853
+ tasks,
3257
3854
  supersedes: null,
3258
3855
  "superseded-by": null
3259
3856
  };
3260
3857
  }
3261
- function renderTasksFile(changeDir, canonicalId, name, author, dateStr) {
3262
- const tasksPath = path5.join(changeDir, "tasks.md");
3263
- const tasksFm = buildTasksFrontmatter(canonicalId, name);
3858
+ function renderTasksFile(changeDir, canonicalId, name, author, dateStr, reqIds) {
3859
+ const tasksPath = path4.join(changeDir, "tasks.md");
3860
+ const tasksFm = buildTasksFrontmatter(canonicalId, name, reqIds);
3264
3861
  const header = `---
3265
- ${yaml2.dump(tasksFm, { indent: 2, lineWidth: -1 }).trim()}
3862
+ ${yaml3.dump(tasksFm, { indent: 2, lineWidth: -1 }).trim()}
3266
3863
  ---
3267
3864
  `;
3268
3865
  const body = TASKS_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
3269
- fs6.writeFileSync(tasksPath, `${header}
3866
+ fs7.writeFileSync(tasksPath, `${header}
3270
3867
  ${body}`, "utf-8");
3271
3868
  return { tasksPath, tasksFm };
3272
3869
  }
@@ -3318,12 +3915,14 @@ function buildScaffoldFailure(profile, error, slug = "", canonicalId = "", chang
3318
3915
  errors: [error]
3319
3916
  };
3320
3917
  }
3321
- function renderAndCollectArtifactFiles(changeDir, canonicalId, name, author, dateStr, profile, citationsBlock, createdFiles) {
3918
+ function renderAndCollectArtifactFiles(changeDir, canonicalId, name, author, dateStr, profile, citations, createdFiles) {
3919
+ const citationsBlock = renderCitationsYamlBlock(citations.citedArtifacts);
3920
+ const reqIds = [...citations.subRequirements, ...citations.subSecReqs];
3322
3921
  createdFiles.push(renderProposalFile(changeDir, canonicalId, name, author, citationsBlock));
3323
3922
  const { specPath, specFmObj } = renderSpecFile(changeDir, canonicalId, name, profile);
3324
3923
  createdFiles.push(specPath);
3325
3924
  createdFiles.push(renderDesignFile(changeDir, canonicalId, name, author, dateStr, citationsBlock));
3326
- const { tasksPath, tasksFm } = renderTasksFile(changeDir, canonicalId, name, author, dateStr);
3925
+ const { tasksPath, tasksFm } = renderTasksFile(changeDir, canonicalId, name, author, dateStr, reqIds);
3327
3926
  createdFiles.push(tasksPath);
3328
3927
  return { specFmObj, tasksFm };
3329
3928
  }
@@ -3343,8 +3942,7 @@ function buildScaffoldSuccess(slug, canonicalId, profile, changeDir, createdFile
3343
3942
  function executeStandardScaffolding(rootDir, changeDir, slug, canonicalId, name, padNum, author, dateStr, profile, framework, inputFromIds, artifactMap, adapter) {
3344
3943
  const createdFiles = [];
3345
3944
  const citations = collectChangeCitations(rootDir, name, padNum, canonicalId, author, dateStr, inputFromIds, artifactMap, createdFiles);
3346
- const citationsBlock = renderCitationsYamlBlock(citations.citedArtifacts);
3347
- const { specFmObj, tasksFm } = renderAndCollectArtifactFiles(changeDir, canonicalId, name, author, dateStr, profile, citationsBlock, createdFiles);
3945
+ const { specFmObj, tasksFm } = renderAndCollectArtifactFiles(changeDir, canonicalId, name, author, dateStr, profile, citations, createdFiles);
3348
3946
  const { depositedPath, handoff } = renderHandoffSidecar(rootDir, slug, canonicalId, name, framework, citations);
3349
3947
  createdFiles.push(depositedPath);
3350
3948
  const errors = validateScaffoldedArtifacts(rootDir, changeDir, adapter, specFmObj, tasksFm, handoff);
@@ -3441,7 +4039,7 @@ function resolveCiEnvironment(env = process.env) {
3441
4039
  };
3442
4040
  }
3443
4041
  function updateExistingEnvFile(filePath, envString, uppercaseKey) {
3444
- const content = fs6.readFileSync(filePath, "utf-8");
4042
+ const content = fs7.readFileSync(filePath, "utf-8");
3445
4043
  const lines = content.split("\n");
3446
4044
  let found = false;
3447
4045
  const newLines = lines.map((line) => {
@@ -3452,15 +4050,15 @@ function updateExistingEnvFile(filePath, envString, uppercaseKey) {
3452
4050
  return line;
3453
4051
  });
3454
4052
  if (!found) newLines.push(envString);
3455
- fs6.writeFileSync(filePath, newLines.filter((l) => l.trim() !== "").join("\n") + "\n", "utf-8");
4053
+ fs7.writeFileSync(filePath, newLines.filter((l) => l.trim() !== "").join("\n") + "\n", "utf-8");
3456
4054
  }
3457
4055
  function writeEnvOutputFile(envOutputFile, key, value) {
3458
4056
  const uppercaseKey = key.toUpperCase();
3459
4057
  const envString = `${uppercaseKey}=${value}`;
3460
- if (fs6.existsSync(envOutputFile)) {
4058
+ if (fs7.existsSync(envOutputFile)) {
3461
4059
  updateExistingEnvFile(envOutputFile, envString, uppercaseKey);
3462
4060
  } else {
3463
- fs6.writeFileSync(envOutputFile, envString + "\n", "utf-8");
4061
+ fs7.writeFileSync(envOutputFile, envString + "\n", "utf-8");
3464
4062
  }
3465
4063
  }
3466
4064
  function resolveEnvOutputFilePath(env) {
@@ -3470,8 +4068,8 @@ function resolveEnvOutputFilePath(env) {
3470
4068
  return void 0;
3471
4069
  }
3472
4070
  function emitCiOutput(key, value, env = process.env) {
3473
- if (env.GITHUB_OUTPUT && fs6.existsSync(env.GITHUB_OUTPUT)) {
3474
- fs6.appendFileSync(env.GITHUB_OUTPUT, `${key}=${value}
4071
+ if (env.GITHUB_OUTPUT && fs7.existsSync(env.GITHUB_OUTPUT)) {
4072
+ fs7.appendFileSync(env.GITHUB_OUTPUT, `${key}=${value}
3475
4073
  `, "utf-8");
3476
4074
  }
3477
4075
  if (env.TF_BUILD === "true") {
@@ -3513,11 +4111,11 @@ function isIgnoredEntry(name) {
3513
4111
  return name === "changes" || name === "active" || name === "node_modules";
3514
4112
  }
3515
4113
  function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
3516
- if (!fs6.existsSync(parentDir)) return;
3517
- const entries = fs6.readdirSync(parentDir, { withFileTypes: true });
4114
+ if (!fs7.existsSync(parentDir)) return;
4115
+ const entries = fs7.readdirSync(parentDir, { withFileTypes: true });
3518
4116
  for (const entry of entries) {
3519
4117
  if (!entry.isDirectory() || isIgnoredEntry(entry.name)) continue;
3520
- const changeDir = path5.join(parentDir, entry.name);
4118
+ const changeDir = path4.join(parentDir, entry.name);
3521
4119
  if (handooffsHasDir(handoffsMap, changeDir)) continue;
3522
4120
  const handoff = loadProductHandoffSidecar(changeDir);
3523
4121
  if (handoff?.id) {
@@ -3528,10 +4126,10 @@ function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
3528
4126
  function scanAllProductHandoffs(rootDir) {
3529
4127
  const handoffsMap = /* @__PURE__ */ new Map();
3530
4128
  const candidateParentDirs = [
3531
- path5.join(rootDir, "specs", "changes", "active"),
3532
- path5.join(rootDir, "specs", "changes", "completed"),
3533
- path5.join(rootDir, "specs"),
3534
- path5.join(rootDir, "openspec", "changes")
4129
+ path4.join(rootDir, "specs", "changes", "active"),
4130
+ path4.join(rootDir, "specs", "changes", "completed"),
4131
+ path4.join(rootDir, "specs"),
4132
+ path4.join(rootDir, "openspec", "changes")
3535
4133
  ];
3536
4134
  for (const parentDir of candidateParentDirs) {
3537
4135
  scanParentDirectoryHandoffs(parentDir, handoffsMap);
@@ -3539,9 +4137,9 @@ function scanAllProductHandoffs(rootDir) {
3539
4137
  return handoffsMap;
3540
4138
  }
3541
4139
  function handooffsHasDir(map, dir) {
3542
- const norm = path5.resolve(dir);
4140
+ const norm = path4.resolve(dir);
3543
4141
  for (const key of map.keys()) {
3544
- if (path5.resolve(key) === norm) return true;
4142
+ if (path4.resolve(key) === norm) return true;
3545
4143
  }
3546
4144
  return false;
3547
4145
  }
@@ -3551,7 +4149,7 @@ function parseFrontmatter(content) {
3551
4149
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
3552
4150
  if (!match) return { frontmatter: {}, body: content };
3553
4151
  try {
3554
- const parsed = yaml2.load(match[1]);
4152
+ const parsed = yaml3.load(match[1]);
3555
4153
  const frontmatter = parsed && typeof parsed === "object" ? parsed : {};
3556
4154
  return { frontmatter, body: content.substring(match[0].length) };
3557
4155
  } catch {
@@ -3638,9 +4236,9 @@ function isCodeTestFile(name) {
3638
4236
  }
3639
4237
  function indexFeatureFile(fullPath, rootDir, map) {
3640
4238
  try {
3641
- const content = fs6.readFileSync(fullPath, "utf-8");
4239
+ const content = fs7.readFileSync(fullPath, "utf-8");
3642
4240
  const tags = extractFeatureTags(content);
3643
- const relFeature = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
4241
+ const relFeature = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
3644
4242
  for (const tag of tags) {
3645
4243
  const existing = map.get(tag) || [];
3646
4244
  if (!existing.includes(relFeature)) existing.push(relFeature);
@@ -3650,11 +4248,11 @@ function indexFeatureFile(fullPath, rootDir, map) {
3650
4248
  }
3651
4249
  }
3652
4250
  function scanFeatureFiles(dir, rootDir, map) {
3653
- if (!fs6.existsSync(dir)) return;
4251
+ if (!fs7.existsSync(dir)) return;
3654
4252
  try {
3655
- const entries = fs6.readdirSync(dir, { withFileTypes: true });
4253
+ const entries = fs7.readdirSync(dir, { withFileTypes: true });
3656
4254
  for (const e of entries) {
3657
- const fullPath = path5.join(dir, e.name);
4255
+ const fullPath = path4.join(dir, e.name);
3658
4256
  if (e.isDirectory() && !shouldSkipDir(e.name)) {
3659
4257
  scanFeatureFiles(fullPath, rootDir, map);
3660
4258
  } else if (e.name.endsWith(".feature")) {
@@ -3666,8 +4264,8 @@ function scanFeatureFiles(dir, rootDir, map) {
3666
4264
  }
3667
4265
  function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
3668
4266
  try {
3669
- const content = fs6.readFileSync(fullPath, "utf-8");
3670
- const relTest = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
4267
+ const content = fs7.readFileSync(fullPath, "utf-8");
4268
+ const relTest = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
3671
4269
  for (const artId of reqIds) {
3672
4270
  if (content.includes(artId)) {
3673
4271
  const existing = map.get(artId) || [];
@@ -3679,11 +4277,11 @@ function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
3679
4277
  }
3680
4278
  }
3681
4279
  function scanCodeTestFiles(dir, rootDir, reqIds, map) {
3682
- if (!fs6.existsSync(dir)) return;
4280
+ if (!fs7.existsSync(dir)) return;
3683
4281
  try {
3684
- const entries = fs6.readdirSync(dir, { withFileTypes: true });
4282
+ const entries = fs7.readdirSync(dir, { withFileTypes: true });
3685
4283
  for (const e of entries) {
3686
- const fullPath = path5.join(dir, e.name);
4284
+ const fullPath = path4.join(dir, e.name);
3687
4285
  if (e.isDirectory() && !shouldSkipDir(e.name)) {
3688
4286
  scanCodeTestFiles(fullPath, rootDir, reqIds, map);
3689
4287
  } else if (isCodeTestFile(e.name)) {
@@ -3717,9 +4315,9 @@ function isArchitectureViewFile(relFile, frontmatter) {
3717
4315
  }
3718
4316
  function processSingleMdFile(file, rootDir, artifactMap, componentsList, architectureViews) {
3719
4317
  try {
3720
- const content = fs6.readFileSync(file, "utf-8");
4318
+ const content = fs7.readFileSync(file, "utf-8");
3721
4319
  const { frontmatter, body } = parseFrontmatter(content);
3722
- const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
4320
+ const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
3723
4321
  if (frontmatter.id) {
3724
4322
  artifactMap.set(frontmatter.id, {
3725
4323
  file: relFile,
@@ -3880,7 +4478,7 @@ function collectArchitectureViewTraces(reqId, architectureViews) {
3880
4478
  const traces = [];
3881
4479
  for (const view of architectureViews) {
3882
4480
  if (view.content.includes(reqId)) {
3883
- const viewIdentifier = isEntityWithId(view) && view.id ? view.id : path5.basename(view.file);
4481
+ const viewIdentifier = isEntityWithId(view) && view.id ? view.id : path4.basename(view.file);
3884
4482
  traces.push(viewIdentifier);
3885
4483
  }
3886
4484
  }
@@ -3962,33 +4560,115 @@ function evaluateAllRequirements(requirements, ctx) {
3962
4560
  }
3963
4561
  return { matrixRows, orphans, errorCount };
3964
4562
  }
3965
- function verifyTraceability(options = {}) {
3966
- const rootDir = options.rootDir || process.cwd();
3967
- const allMdFiles = walkMdFiles(rootDir);
4563
+ var SOURCE_CODE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".java", ".cs", ".rs"];
4564
+ function isProductionSourceFile(name) {
4565
+ if (isCodeTestFile(name) || name.endsWith(".d.ts")) return false;
4566
+ return SOURCE_CODE_EXTS.some((ext) => name.endsWith(ext));
4567
+ }
4568
+ function collectCodeAndTestCorpora(dir, prodChunks = [], testChunks = []) {
4569
+ if (!fs7.existsSync(dir)) return { prodCorpus: "", testCorpus: "" };
4570
+ try {
4571
+ for (const e of fs7.readdirSync(dir, { withFileTypes: true })) {
4572
+ const fullPath = path4.join(dir, e.name);
4573
+ if (e.isDirectory() && !shouldSkipDir(e.name)) {
4574
+ collectCodeAndTestCorpora(fullPath, prodChunks, testChunks);
4575
+ } else if (isCodeTestFile(e.name) || e.name.endsWith(".feature")) {
4576
+ testChunks.push(fs7.readFileSync(fullPath, "utf-8"));
4577
+ } else if (isProductionSourceFile(e.name)) {
4578
+ prodChunks.push(fs7.readFileSync(fullPath, "utf-8"));
4579
+ }
4580
+ }
4581
+ } catch {
4582
+ }
4583
+ return { prodCorpus: prodChunks.join("\n"), testCorpus: testChunks.join("\n") };
4584
+ }
4585
+ function parseStringContractEntry(designId, defaultComp, raw) {
4586
+ const trimmed = raw.trim();
4587
+ if (!trimmed) return null;
4588
+ const parts = trimmed.split(".");
4589
+ const method = parts.pop();
4590
+ const interfaceName = parts.length > 0 ? parts.join(".") : void 0;
4591
+ return { designId, component: defaultComp, interfaceName, method };
4592
+ }
4593
+ function parseObjectContractEntries(designId, defaultComp, item) {
4594
+ if (!Array.isArray(item.methods)) return [];
4595
+ const component = item.component || defaultComp;
4596
+ return item.methods.filter((m) => typeof m === "string" && Boolean(m.trim())).map((m) => ({ designId, component, interfaceName: item.interface, method: m.trim() }));
4597
+ }
4598
+ function expandContractDeclarations(designId, defaultComp, rawContracts) {
4599
+ const expanded = [];
4600
+ for (const item of rawContracts) {
4601
+ if (typeof item === "string") {
4602
+ const parsed = parseStringContractEntry(designId, defaultComp, item);
4603
+ if (parsed) expanded.push(parsed);
4604
+ } else if (item && typeof item === "object") {
4605
+ expanded.push(...parseObjectContractEntries(designId, defaultComp, item));
4606
+ }
4607
+ }
4608
+ return expanded;
4609
+ }
4610
+ function evaluateSingleContractMethod(entry, prodCorpus, testCorpus) {
4611
+ const symbol = entry.method.includes(".") ? entry.method.split(".").pop() : entry.method;
4612
+ const implementedInCode = prodCorpus.includes(symbol);
4613
+ const testedInSuite = testCorpus.includes(symbol);
4614
+ const status = !implementedInCode ? "MISSING_IMPLEMENTATION" : !testedInSuite ? "MISSING_UNIT_TEST" : "COMPLIANT";
4615
+ return {
4616
+ designId: entry.designId,
4617
+ component: entry.component,
4618
+ interfaceName: entry.interfaceName,
4619
+ method: entry.method,
4620
+ implementedInCode,
4621
+ testedInSuite,
4622
+ status
4623
+ };
4624
+ }
4625
+ function auditDesignContracts(rootDir, artifactMap) {
4626
+ const pending = [];
4627
+ for (const [id, entry] of artifactMap.entries()) {
4628
+ if (entry.file.includes("templates/") || entry.file.includes(".template.")) continue;
4629
+ const rawContracts = entry.frontmatter.contracts;
4630
+ if (!Array.isArray(rawContracts) || rawContracts.length === 0) continue;
4631
+ const defaultComp = typeof entry.frontmatter["architecture-component"] === "string" ? entry.frontmatter["architecture-component"] : void 0;
4632
+ pending.push(...expandContractDeclarations(id, defaultComp, rawContracts));
4633
+ }
4634
+ if (pending.length === 0) {
4635
+ return { contractAudits: [], missingContractsCount: 0 };
4636
+ }
4637
+ const { prodCorpus, testCorpus } = collectCodeAndTestCorpora(rootDir);
4638
+ const contractAudits = pending.map((p) => evaluateSingleContractMethod(p, prodCorpus, testCorpus));
4639
+ const missingContractsCount = contractAudits.filter((c) => c.status !== "COMPLIANT").length;
4640
+ return { contractAudits, missingContractsCount };
4641
+ }
4642
+ function buildTraceabilityContext(rootDir, allMdFiles) {
3968
4643
  const { artifactMap, componentsList, architectureViews } = indexAllArtifacts(allMdFiles, rootDir);
3969
4644
  const handoffList = buildHandoffList(rootDir, artifactMap);
3970
4645
  const reqToHandoffMap = indexRequirementsToHandoff(handoffList);
3971
4646
  const { featureTagMap, codeTestMap } = buildTestMaps(rootDir, artifactMap);
3972
4647
  const requirements = collectRequirementsToVerify(artifactMap);
3973
- const ctx = {
3974
- reqToHandoffMap,
3975
- artifactMap,
3976
- componentsList,
3977
- architectureViews,
3978
- featureTagMap,
3979
- codeTestMap
4648
+ return {
4649
+ ctx: { reqToHandoffMap, artifactMap, componentsList, architectureViews, featureTagMap, codeTestMap },
4650
+ requirements
3980
4651
  };
4652
+ }
4653
+ function verifyTraceability(options = {}) {
4654
+ const rootDir = options.rootDir || process.cwd();
4655
+ const allMdFiles = walkMdFiles(rootDir);
4656
+ const { ctx, requirements } = buildTraceabilityContext(rootDir, allMdFiles);
3981
4657
  const { matrixRows, orphans, errorCount } = evaluateAllRequirements(requirements, ctx);
4658
+ const { contractAudits, missingContractsCount } = auditDesignContracts(rootDir, ctx.artifactMap);
4659
+ const totalIssues = errorCount + missingContractsCount;
3982
4660
  return {
3983
- success: errorCount === 0,
4661
+ success: totalIssues === 0,
3984
4662
  rows: matrixRows,
3985
4663
  orphans,
3986
4664
  orphanCount: orphans.length,
3987
4665
  totalRequirements: requirements.length,
3988
- reportMarkdown: generateTraceabilityReportMarkdown(matrixRows, errorCount)
4666
+ contractAudits,
4667
+ missingContractsCount,
4668
+ reportMarkdown: generateTraceabilityReportMarkdown(matrixRows, totalIssues)
3989
4669
  };
3990
4670
  }
3991
- function parseTaskVerification(rawVerification) {
4671
+ function parseTaskVerification2(rawVerification) {
3992
4672
  const verification = {};
3993
4673
  if (rawVerification && typeof rawVerification === "object") {
3994
4674
  const v = rawVerification;
@@ -3997,6 +4677,12 @@ function parseTaskVerification(rawVerification) {
3997
4677
  }
3998
4678
  return verification;
3999
4679
  }
4680
+ function parseOptionalStringList(val) {
4681
+ if (Array.isArray(val)) {
4682
+ return val.map(String).filter(Boolean);
4683
+ }
4684
+ return void 0;
4685
+ }
4000
4686
  function mapRawTaskItem(t) {
4001
4687
  return {
4002
4688
  id: typeof t["id"] === "string" ? t["id"] : String(t["id"] ?? ""),
@@ -4007,7 +4693,10 @@ function mapRawTaskItem(t) {
4007
4693
  assignedTo: typeof t["assigned-to"] === "string" ? t["assigned-to"] : void 0,
4008
4694
  status: typeof t["status"] === "string" ? t["status"] : void 0,
4009
4695
  blockingReason: typeof t["blocking-reason"] === "string" ? t["blocking-reason"] : void 0,
4010
- verification: parseTaskVerification(t["verification"])
4696
+ implementsComponents: parseOptionalStringList(t["implements-components"]),
4697
+ satisfiesRequirements: parseOptionalStringList(t["satisfies-requirements"]),
4698
+ implementsContracts: parseOptionalStringList(t["implements-contracts"]),
4699
+ verification: parseTaskVerification2(t["verification"])
4011
4700
  };
4012
4701
  }
4013
4702
  function parseTasksDoc(content) {
@@ -4015,7 +4704,7 @@ function parseTasksDoc(content) {
4015
4704
  if (!match) return { frontmatter: { tasks: [] }, body: content };
4016
4705
  let parsed;
4017
4706
  try {
4018
- parsed = yaml2.load(match[1]);
4707
+ parsed = yaml3.load(match[1]);
4019
4708
  } catch {
4020
4709
  return { frontmatter: { tasks: [] }, body: content };
4021
4710
  }
@@ -4040,12 +4729,12 @@ function walkTaskFiles(dir, fileList = [], excludeDirs = [
4040
4729
  "examples"
4041
4730
  ]) {
4042
4731
  try {
4043
- if (!fs6.existsSync(dir)) return fileList;
4044
- const files = fs6.readdirSync(dir);
4732
+ if (!fs7.existsSync(dir)) return fileList;
4733
+ const files = fs7.readdirSync(dir);
4045
4734
  for (const file of files) {
4046
- const fullPath = path5.join(dir, file);
4735
+ const fullPath = path4.join(dir, file);
4047
4736
  try {
4048
- if (fs6.statSync(fullPath).isDirectory()) {
4737
+ if (fs7.statSync(fullPath).isDirectory()) {
4049
4738
  if (!excludeDirs.includes(file)) {
4050
4739
  walkTaskFiles(fullPath, fileList, excludeDirs);
4051
4740
  }
@@ -4158,26 +4847,86 @@ function updateGovernanceCounts(t, modeCounts, riskCounts) {
4158
4847
  }
4159
4848
  }
4160
4849
  function parseTasksFromFile(file) {
4161
- if (!fs6.existsSync(file)) return [];
4850
+ if (!fs7.existsSync(file)) return [];
4162
4851
  try {
4163
- const content = fs6.readFileSync(file, "utf-8");
4852
+ const content = fs7.readFileSync(file, "utf-8");
4164
4853
  const { frontmatter } = parseTasksDoc(content);
4165
4854
  return frontmatter.tasks || [];
4166
4855
  } catch {
4167
4856
  return [];
4168
4857
  }
4169
4858
  }
4170
- function processTasksFile(file, rootDir, modeCounts, riskCounts, taskSummaries, violationsList) {
4859
+ function extractDesignExpectedScope(changeDir) {
4860
+ const designFile = path4.join(changeDir, "design.md");
4861
+ if (!fs7.existsSync(designFile)) return [];
4862
+ const expected = [];
4863
+ try {
4864
+ const { frontmatter } = parseTasksDoc(fs7.readFileSync(designFile, "utf-8"));
4865
+ if (typeof frontmatter["architecture-component"] === "string") {
4866
+ expected.push(frontmatter["architecture-component"]);
4867
+ }
4868
+ if (Array.isArray(frontmatter["architecture-components"])) {
4869
+ expected.push(...frontmatter["architecture-components"].map(String));
4870
+ }
4871
+ } catch {
4872
+ }
4873
+ return expected;
4874
+ }
4875
+ function extractHandoffExpectedScope(changeDir, maxUc, violations) {
4876
+ const handoff = loadProductHandoffSidecar(changeDir);
4877
+ if (!handoff?.subgraph) return [];
4878
+ const ucList = handoff.subgraph.useCases || [];
4879
+ if (maxUc !== void 0 && ucList.length > maxUc && violations) {
4880
+ violations.push(
4881
+ `[${handoff.changeId}] Greenfield monolith detected: change bundles ${ucList.length} use cases (max ${maxUc}). Decompose into incremental SDD changes per Use Case.`
4882
+ );
4883
+ }
4884
+ const reqs = handoff.subgraph.requirements || [];
4885
+ const secReqs = handoff.subgraph.securityRequirements || [];
4886
+ return [...reqs, ...secReqs];
4887
+ }
4888
+ function collectTaskCoveredScope(tasks) {
4889
+ const covered = /* @__PURE__ */ new Set();
4890
+ for (const t of tasks) {
4891
+ for (const c of t.implementsComponents || []) covered.add(c);
4892
+ for (const r of t.satisfiesRequirements || []) covered.add(r);
4893
+ for (const m of t.implementsContracts || []) covered.add(m);
4894
+ }
4895
+ return covered;
4896
+ }
4897
+ function hasExplicitScopeBindings(tasks) {
4898
+ return tasks.some(
4899
+ (t) => t.implementsComponents && t.implementsComponents.length > 0 || t.satisfiesRequirements && t.satisfiesRequirements.length > 0 || t.implementsContracts && t.implementsContracts.length > 0
4900
+ );
4901
+ }
4902
+ function auditDesignScopeForFile(file, tasks, options, unassignedList, violationsList) {
4903
+ const changeDir = path4.dirname(file);
4904
+ const expectedItems = [
4905
+ ...extractDesignExpectedScope(changeDir),
4906
+ ...extractHandoffExpectedScope(changeDir, options.maxUseCasesPerChange, violationsList)
4907
+ ];
4908
+ const shouldEnforce = Boolean(options.enforceDesignCoverage || hasExplicitScopeBindings(tasks));
4909
+ if (!shouldEnforce || expectedItems.length === 0) return;
4910
+ const covered = collectTaskCoveredScope(tasks);
4911
+ for (const item of new Set(expectedItems)) {
4912
+ if (!covered.has(item)) {
4913
+ unassignedList.push(item);
4914
+ violationsList.push(`[${path4.basename(changeDir)}] Unassigned design/handoff scope in tasks.md: missing task coverage for ${item}`);
4915
+ }
4916
+ }
4917
+ }
4918
+ function processTasksFile(file, rootDir, options, acc) {
4171
4919
  const tasks = parseTasksFromFile(file);
4172
- const relPath = path5.relative(rootDir, file).replace(/\\/g, "/");
4920
+ const relPath = path4.relative(rootDir, file).replace(/\\/g, "/");
4173
4921
  let unverifiedDelta = 0;
4174
4922
  for (const t of tasks) {
4175
4923
  const { summary, violation, unverified } = evaluateTaskItem(t, relPath);
4176
- if (violation) violationsList.push(violation);
4924
+ if (violation) acc.violationsList.push(violation);
4177
4925
  if (unverified) unverifiedDelta++;
4178
- updateGovernanceCounts(t, modeCounts, riskCounts);
4179
- taskSummaries.push(summary);
4926
+ updateGovernanceCounts(t, acc.modeCounts, acc.riskCounts);
4927
+ acc.taskSummaries.push(summary);
4180
4928
  }
4929
+ auditDesignScopeForFile(file, tasks, options, acc.unassignedDesignScope, acc.violationsList);
4181
4930
  return { tasksCount: tasks.length, unverifiedDelta };
4182
4931
  }
4183
4932
  function createInitialModeCounts() {
@@ -4196,30 +4945,32 @@ function createInitialRiskCounts() {
4196
4945
  CRITICAL: 0
4197
4946
  };
4198
4947
  }
4199
- function assembleGovernanceResult(totalTasks, unverifiedCount, riskCounts, modeCounts, taskSummaries, violationsList, reportMarkdown) {
4948
+ function assembleGovernanceResult(acc, reportMarkdown) {
4200
4949
  return {
4201
- success: violationsList.length === 0,
4202
- totalTasks,
4203
- verifiedCount: totalTasks - unverifiedCount,
4204
- unverifiedCount,
4205
- riskCounts,
4206
- modeCounts,
4207
- tasks: taskSummaries,
4208
- violations: violationsList,
4950
+ success: acc.violationsList.length === 0,
4951
+ totalTasks: acc.totalTasks,
4952
+ verifiedCount: acc.totalTasks - acc.unverifiedCount,
4953
+ unverifiedCount: acc.unverifiedCount,
4954
+ riskCounts: acc.riskCounts,
4955
+ modeCounts: acc.modeCounts,
4956
+ tasks: acc.taskSummaries,
4957
+ violations: acc.violationsList,
4958
+ unassignedDesignScope: acc.unassignedDesignScope,
4209
4959
  reportMarkdown
4210
4960
  };
4211
4961
  }
4212
- function collectAllTasksData(taskFiles, rootDir) {
4962
+ function collectAllTasksData(taskFiles, rootDir, options) {
4213
4963
  const acc = {
4214
4964
  totalTasks: 0,
4215
4965
  unverifiedCount: 0,
4216
4966
  taskSummaries: [],
4217
4967
  violationsList: [],
4968
+ unassignedDesignScope: [],
4218
4969
  modeCounts: createInitialModeCounts(),
4219
4970
  riskCounts: createInitialRiskCounts()
4220
4971
  };
4221
4972
  for (const file of taskFiles) {
4222
- const delta = processTasksFile(file, rootDir, acc.modeCounts, acc.riskCounts, acc.taskSummaries, acc.violationsList);
4973
+ const delta = processTasksFile(file, rootDir, options, acc);
4223
4974
  acc.totalTasks += delta.tasksCount;
4224
4975
  acc.unverifiedCount += delta.unverifiedDelta;
4225
4976
  }
@@ -4228,308 +4979,14 @@ function collectAllTasksData(taskFiles, rootDir) {
4228
4979
  function verifyTasksGovernance(options = {}) {
4229
4980
  const rootDir = options.rootDir || process.cwd();
4230
4981
  const searchDir = options.specsDir || rootDir;
4231
- const data = collectAllTasksData(walkTaskFiles(searchDir), rootDir);
4982
+ const data = collectAllTasksData(walkTaskFiles(searchDir), rootDir, options);
4232
4983
  const reportMarkdown = generateGovernanceReportMarkdown(
4233
4984
  data.taskSummaries,
4234
4985
  data.violationsList.length,
4235
4986
  data.modeCounts,
4236
4987
  data.totalTasks
4237
4988
  );
4238
- return assembleGovernanceResult(
4239
- data.totalTasks,
4240
- data.unverifiedCount,
4241
- data.riskCounts,
4242
- data.modeCounts,
4243
- data.taskSummaries,
4244
- data.violationsList,
4245
- reportMarkdown
4246
- );
4247
- }
4248
- function parseTaskVerification2(v) {
4249
- const verification = {};
4250
- if (v && typeof v === "object") {
4251
- const rec = v;
4252
- if (typeof rec["method"] === "string") verification.method = rec["method"];
4253
- if (typeof rec["command-or-criteria"] === "string") verification.criteria = rec["command-or-criteria"];
4254
- }
4255
- return verification;
4256
- }
4257
- function parseTaskItem(t) {
4258
- return {
4259
- id: typeof t["id"] === "string" ? t["id"] : String(t["id"] ?? ""),
4260
- title: typeof t["title"] === "string" ? t["title"] : void 0,
4261
- verification: parseTaskVerification2(t["verification"])
4262
- };
4263
- }
4264
- function parseVerifiedByTests(verifiedBy) {
4265
- if (Array.isArray(verifiedBy)) {
4266
- return verifiedBy.map(String);
4267
- }
4268
- if (typeof verifiedBy === "string") {
4269
- return [verifiedBy];
4270
- }
4271
- return void 0;
4272
- }
4273
- function parseGenericDoc(content) {
4274
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
4275
- if (!match) return { frontmatter: {}, body: content };
4276
- let parsed;
4277
- try {
4278
- parsed = yaml2.load(match[1]);
4279
- } catch {
4280
- return { frontmatter: {}, body: content };
4281
- }
4282
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
4283
- return { frontmatter: {}, body: content };
4284
- }
4285
- const raw = parsed;
4286
- const rawTasks = Array.isArray(raw["tasks"]) ? raw["tasks"] : [];
4287
- const tasks = rawTasks.map(parseTaskItem);
4288
- const frontmatter = {
4289
- ...raw,
4290
- tasks,
4291
- "verified-by-tests": parseVerifiedByTests(raw["verified-by-tests"])
4292
- };
4293
- return { frontmatter, body: content.substring(match[0].length) };
4294
- }
4295
- function buildRequirementsTable(reqAudit) {
4296
- const lines = [
4297
- `## 1. Requirements Audit (Product, Quality, and Security)`,
4298
- ``,
4299
- `| Requirement ID | Title | Source File | Declared Method | Linked Test Files | Status |`,
4300
- `| :--- | :--- | :--- | :---: | :--- | :---: |`
4301
- ];
4302
- for (const r of reqAudit) {
4303
- const icon = r.status === "VERIFICADO_CON_PRUEBA" ? "\u2705 VERIFIED" : "\u274C NO TESTS";
4304
- lines.push(
4305
- `| **\`${r.id}\`** | ${r.title} | \`${r.file}\` | \`${r.method}\` | \`${r.testRefs}\` | ${icon} |`
4306
- );
4307
- }
4308
- return lines;
4309
- }
4310
- function buildTasksTable(taskAudit) {
4311
- const lines = [
4312
- `## 2. Tasks Audit (SDD Delivery Engineering)`,
4313
- ``,
4314
- `| Task ID | Title | tasks.md File | Method | Verification Command / Criterion | Status |`,
4315
- `| :--- | :--- | :--- | :---: | :--- | :---: |`
4316
- ];
4317
- for (const t of taskAudit) {
4318
- const icon = t.status === "VERIFICADO_CON_PRUEBA" ? "\u2705 VERIFIED" : "\u274C NO TESTS";
4319
- lines.push(
4320
- `| **\`${t.id}\`** | ${t.title} | \`${t.file}\` | \`${t.method}\` | \`${t.commandOrCriteria}\` | ${icon} |`
4321
- );
4322
- }
4323
- return lines;
4324
- }
4325
- function generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, taskErrors) {
4326
- const totalErrors = reqErrors + taskErrors;
4327
- const verdict = totalErrors === 0 ? "100% VERIFIED WITH TESTS (PASSED)" : `BLOCKED: Detected ${totalErrors} items without tests (${reqErrors} Requirements, ${taskErrors} Tasks)`;
4328
- const reportLines = [
4329
- `# \u{1F9EA} Deterministic Test Coverage Audit for Requirements and Tasks`,
4330
- ``,
4331
- `> **Audit Date:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
4332
- `> **Overall Verdict:** ${verdict}`,
4333
- ``,
4334
- `---`,
4335
- ``,
4336
- ...buildRequirementsTable(reqAudit),
4337
- ``,
4338
- `---`,
4339
- ``,
4340
- ...buildTasksTable(taskAudit)
4341
- ];
4342
- return reportLines.join("\n");
4343
- }
4344
- var EXCLUDED_TEST_DIRS = /* @__PURE__ */ new Set([
4345
- "node_modules",
4346
- ".git",
4347
- "dist",
4348
- "bin",
4349
- "obj",
4350
- "build",
4351
- "out",
4352
- "target",
4353
- "coverage",
4354
- ".changeset",
4355
- "scratch",
4356
- "fixtures",
4357
- "templates",
4358
- "examples",
4359
- ".vs",
4360
- ".idea"
4361
- ]);
4362
- var TEST_EXTENSIONS = [".feature", ".spec.ts", ".spec.js", ".test.ts", ".test.js", "_test.py", "_test.go"];
4363
- function isTestFile(fileName) {
4364
- return TEST_EXTENSIONS.some((ext) => fileName.endsWith(ext));
4365
- }
4366
- function recordRequirementTest(reqToTestsMap, reqId, relTest) {
4367
- const existing = reqToTestsMap.get(reqId) || [];
4368
- if (!existing.includes(relTest)) {
4369
- existing.push(relTest);
4370
- reqToTestsMap.set(reqId, existing);
4371
- }
4372
- }
4373
- function parseFeatureTags(content, relTest, reqToTestsMap) {
4374
- const lines = content.split("\n");
4375
- for (const line of lines) {
4376
- const trimmed = line.trim();
4377
- if (!trimmed.startsWith("@")) continue;
4378
- const tags = trimmed.split(/\s+/).filter((t) => t.startsWith("@"));
4379
- for (const t of tags) {
4380
- recordRequirementTest(reqToTestsMap, t.substring(1), relTest);
4381
- }
4382
- }
4383
- }
4384
- var REQ_ID_REGEX = /\b(FR|QR|CON|SEC-REQ)-[A-Z0-9]+(-[A-Z0-9]+)*\b/g;
4385
- function parseCodeTestIds(content, relTest, reqToTestsMap) {
4386
- const idMatches = content.match(REQ_ID_REGEX);
4387
- if (!idMatches) return;
4388
- for (const reqId of idMatches) {
4389
- recordRequirementTest(reqToTestsMap, reqId, relTest);
4390
- }
4391
- }
4392
- function processTestFile(fullPath, rootDir, reqToTestsMap) {
4393
- try {
4394
- const content = fs6.readFileSync(fullPath, "utf-8");
4395
- const relTest = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
4396
- if (fullPath.endsWith(".feature")) {
4397
- parseFeatureTags(content, relTest, reqToTestsMap);
4398
- }
4399
- parseCodeTestIds(content, relTest, reqToTestsMap);
4400
- } catch {
4401
- }
4402
- }
4403
- function collectTestFiles(dir, files = []) {
4404
- if (!fs6.existsSync(dir)) return files;
4405
- try {
4406
- const entries = fs6.readdirSync(dir, { withFileTypes: true });
4407
- for (const e of entries) {
4408
- if (e.isDirectory()) {
4409
- if (!EXCLUDED_TEST_DIRS.has(e.name)) {
4410
- collectTestFiles(path5.join(dir, e.name), files);
4411
- }
4412
- } else if (isTestFile(e.name)) {
4413
- files.push(path5.join(dir, e.name));
4414
- }
4415
- }
4416
- } catch {
4417
- }
4418
- return files;
4419
- }
4420
- function scanAllTestArtifacts(rootDir) {
4421
- const reqToTestsMap = /* @__PURE__ */ new Map();
4422
- const testFiles = collectTestFiles(rootDir);
4423
- for (const file of testFiles) {
4424
- processTestFile(file, rootDir, reqToTestsMap);
4425
- }
4426
- return reqToTestsMap;
4427
- }
4428
- var REQ_PREFIXES = ["FR-", "QR-", "CON-", "SEC-REQ-"];
4429
- function isRequirementArtifact(frontmatter, id) {
4430
- if (frontmatter.type === "requirement" || frontmatter.type === "security-requirement") {
4431
- return true;
4432
- }
4433
- return REQ_PREFIXES.some((prefix) => id.startsWith(prefix));
4434
- }
4435
- function resolveRequirementMethod(declaredMethod, matchingTests) {
4436
- if (typeof declaredMethod === "string" && declaredMethod) {
4437
- return declaredMethod;
4438
- }
4439
- return matchingTests.some((t) => t.endsWith(".feature")) ? "BDD_CUCUMBER" : "TEST_EXECUTION";
4440
- }
4441
- function auditRequirement(frontmatter, id, relPath, isTemplate, reqToTestsMap) {
4442
- const matchingTests = reqToTestsMap.get(id) || [];
4443
- const isVerified = isTemplate || matchingTests.length > 0;
4444
- const method = resolveRequirementMethod(frontmatter["verifiable-by"], matchingTests);
4445
- return {
4446
- item: {
4447
- id: id || "SIN-ID",
4448
- title: frontmatter.title || "Sin t\xEDtulo",
4449
- file: relPath,
4450
- isTemplate,
4451
- method,
4452
- testRefs: matchingTests.join(", ") || "NINGUNA DETECTADA",
4453
- existingCount: matchingTests.length,
4454
- status: isVerified ? "VERIFICADO_CON_PRUEBA" : "FALLO_SIN_PRUEBA"
4455
- },
4456
- isError: !isVerified
4457
- };
4458
- }
4459
- function isTaskProofValid(criteria, isTemplate) {
4460
- if (isTemplate) return true;
4461
- return criteria.length > 4 && !criteria.includes("TODO") && !criteria.includes("TBD");
4462
- }
4463
- function auditSingleTask(t, relPath, isTemplate) {
4464
- const crit = t.verification?.criteria || "";
4465
- const method = t.verification?.method || "TEST_EXECUTION";
4466
- const hasProof = isTaskProofValid(crit, isTemplate);
4467
- return {
4468
- item: {
4469
- id: t.id,
4470
- title: t.title || "Sin t\xEDtulo",
4471
- file: relPath,
4472
- method,
4473
- commandOrCriteria: crit || "AUSENTE",
4474
- status: hasProof ? "VERIFICADO_CON_PRUEBA" : "FALLO_SIN_PRUEBA"
4475
- },
4476
- hasProof
4477
- };
4478
- }
4479
- function auditTasks(tasks, relPath, isTemplate) {
4480
- if (!Array.isArray(tasks) || tasks.length === 0) {
4481
- return { items: [], errorCount: 0 };
4482
- }
4483
- const items = [];
4484
- let errorCount = 0;
4485
- for (const t of tasks) {
4486
- const { item, hasProof } = auditSingleTask(t, relPath, isTemplate);
4487
- if (!hasProof) errorCount++;
4488
- items.push(item);
4489
- }
4490
- return { items, errorCount };
4491
- }
4492
- function assembleCoverageResult(reqAudit, taskAudit, reqErrors, taskErrors) {
4493
- return {
4494
- success: reqErrors === 0 && taskErrors === 0,
4495
- totalRequirements: reqAudit.length,
4496
- passedRequirements: reqAudit.length - reqErrors,
4497
- failedRequirements: reqErrors,
4498
- totalTasks: taskAudit.length,
4499
- passedTasks: taskAudit.length - taskErrors,
4500
- failedTasks: taskErrors,
4501
- requirements: reqAudit,
4502
- tasks: taskAudit,
4503
- reportMarkdown: generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, taskErrors)
4504
- };
4505
- }
4506
- function verifyTestingCoverage(options = {}) {
4507
- const rootDir = options.rootDir || process.cwd();
4508
- const allMdFiles = walkMdFiles(rootDir);
4509
- const reqToTestsMap = scanAllTestArtifacts(rootDir);
4510
- const reqAudit = [];
4511
- const taskAudit = [];
4512
- let reqErrors = 0;
4513
- let taskErrors = 0;
4514
- for (const file of allMdFiles) {
4515
- try {
4516
- const content = fs6.readFileSync(file, "utf-8");
4517
- const { frontmatter } = parseGenericDoc(content);
4518
- const relPath = path5.relative(rootDir, file).replace(/\\/g, "/");
4519
- const isTemplate = relPath.includes("templates/");
4520
- const id = frontmatter.id || "";
4521
- if (isRequirementArtifact(frontmatter, id)) {
4522
- const auditedReq = auditRequirement(frontmatter, id, relPath, isTemplate, reqToTestsMap);
4523
- reqAudit.push(auditedReq.item);
4524
- if (auditedReq.isError) reqErrors++;
4525
- }
4526
- const auditedTasks = auditTasks(frontmatter.tasks, relPath, isTemplate);
4527
- taskAudit.push(...auditedTasks.items);
4528
- taskErrors += auditedTasks.errorCount;
4529
- } catch {
4530
- }
4531
- }
4532
- return assembleCoverageResult(reqAudit, taskAudit, reqErrors, taskErrors);
4989
+ return assembleGovernanceResult(data, reportMarkdown);
4533
4990
  }
4534
4991
  function isToolAvailable(toolName) {
4535
4992
  try {
@@ -4575,8 +5032,8 @@ function findLicenseFile(pkgDir) {
4575
5032
  "license.txt"
4576
5033
  ];
4577
5034
  for (const c of candidates) {
4578
- const full = path5.join(pkgDir, c);
4579
- if (fs6.existsSync(full)) {
5035
+ const full = path4.join(pkgDir, c);
5036
+ if (fs7.existsSync(full)) {
4580
5037
  return full;
4581
5038
  }
4582
5039
  }
@@ -4593,9 +5050,9 @@ var LICENSE_FILE_PATTERNS = [
4593
5050
  [(t) => t.includes("Microsoft Reciprocal License") || t.includes("MS-RL"), "MS-RL"]
4594
5051
  ];
4595
5052
  function resolveLicenseFromFile(licenseFilePath) {
4596
- if (!licenseFilePath || !fs6.existsSync(licenseFilePath)) return void 0;
5053
+ if (!licenseFilePath || !fs7.existsSync(licenseFilePath)) return void 0;
4597
5054
  try {
4598
- const text = fs6.readFileSync(licenseFilePath, "utf-8");
5055
+ const text = fs7.readFileSync(licenseFilePath, "utf-8");
4599
5056
  for (const [matches, spdx] of LICENSE_FILE_PATTERNS) {
4600
5057
  if (matches(text)) return spdx;
4601
5058
  }
@@ -4604,16 +5061,16 @@ function resolveLicenseFromFile(licenseFilePath) {
4604
5061
  return void 0;
4605
5062
  }
4606
5063
  function readPackageJson(filePath) {
4607
- if (!fs6.existsSync(filePath)) return null;
5064
+ if (!fs7.existsSync(filePath)) return null;
4608
5065
  try {
4609
- return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
5066
+ return JSON.parse(fs7.readFileSync(filePath, "utf-8"));
4610
5067
  } catch {
4611
5068
  return null;
4612
5069
  }
4613
5070
  }
4614
5071
  function extractDirectDepNames(rootDir, productionOnly) {
4615
5072
  const names = /* @__PURE__ */ new Set();
4616
- const pkgPath = path5.join(rootDir, "package.json");
5073
+ const pkgPath = path4.join(rootDir, "package.json");
4617
5074
  const pkg = readPackageJson(pkgPath);
4618
5075
  if (!pkg) return names;
4619
5076
  if (pkg.dependencies && typeof pkg.dependencies === "object") {
@@ -4633,7 +5090,7 @@ function resolveSpdxForPackage(pkg, licFile) {
4633
5090
  return spdx;
4634
5091
  }
4635
5092
  function processPackageDir(pkgDir, directNames, seen) {
4636
- const pkg = readPackageJson(path5.join(pkgDir, "package.json"));
5093
+ const pkg = readPackageJson(path4.join(pkgDir, "package.json"));
4637
5094
  if (!pkg?.name || !pkg.version) return;
4638
5095
  const key = `${pkg.name}@${pkg.version}`;
4639
5096
  if (seen.has(key)) return;
@@ -4654,32 +5111,32 @@ function processPackageDir(pkgDir, directNames, seen) {
4654
5111
  });
4655
5112
  }
4656
5113
  function scanScopeSubdirectory(scopeDir, directNames, seen) {
4657
- const scopedEntries = fs6.readdirSync(scopeDir, { withFileTypes: true });
5114
+ const scopedEntries = fs7.readdirSync(scopeDir, { withFileTypes: true });
4658
5115
  for (const scoped of scopedEntries) {
4659
5116
  if (scoped.isDirectory()) {
4660
- processPackageDir(path5.join(scopeDir, scoped.name), directNames, seen);
5117
+ processPackageDir(path4.join(scopeDir, scoped.name), directNames, seen);
4661
5118
  }
4662
5119
  }
4663
5120
  }
4664
5121
  function scanSubdirectories(baseDir, directNames, seen) {
4665
- if (!fs6.existsSync(baseDir)) return;
4666
- const entries = fs6.readdirSync(baseDir, { withFileTypes: true });
5122
+ if (!fs7.existsSync(baseDir)) return;
5123
+ const entries = fs7.readdirSync(baseDir, { withFileTypes: true });
4667
5124
  for (const entry of entries) {
4668
5125
  if (!entry.isDirectory()) continue;
4669
5126
  if (entry.name.startsWith("@")) {
4670
- scanScopeSubdirectory(path5.join(baseDir, entry.name), directNames, seen);
5127
+ scanScopeSubdirectory(path4.join(baseDir, entry.name), directNames, seen);
4671
5128
  } else if (entry.name !== ".bin" && entry.name !== ".pnpm") {
4672
- processPackageDir(path5.join(baseDir, entry.name), directNames, seen);
5129
+ processPackageDir(path4.join(baseDir, entry.name), directNames, seen);
4673
5130
  }
4674
5131
  }
4675
5132
  }
4676
5133
  function scanPnpmStore(pnpmDir, directNames, seen) {
4677
- if (!fs6.existsSync(pnpmDir)) return;
4678
- const entries = fs6.readdirSync(pnpmDir, { withFileTypes: true });
5134
+ if (!fs7.existsSync(pnpmDir)) return;
5135
+ const entries = fs7.readdirSync(pnpmDir, { withFileTypes: true });
4679
5136
  for (const entry of entries) {
4680
5137
  if (!entry.isDirectory()) continue;
4681
- const candidateModules = path5.join(pnpmDir, entry.name, "node_modules");
4682
- if (fs6.existsSync(candidateModules)) {
5138
+ const candidateModules = path4.join(pnpmDir, entry.name, "node_modules");
5139
+ if (fs7.existsSync(candidateModules)) {
4683
5140
  scanSubdirectories(candidateModules, directNames, seen);
4684
5141
  }
4685
5142
  }
@@ -4687,11 +5144,11 @@ function scanPnpmStore(pnpmDir, directNames, seen) {
4687
5144
  function scanWithNativeFs(rootDir, options = {}) {
4688
5145
  const directNames = extractDirectDepNames(rootDir, options.productionOnly);
4689
5146
  const seen = /* @__PURE__ */ new Map();
4690
- const nmDir = path5.join(rootDir, "node_modules");
4691
- if (fs6.existsSync(nmDir)) {
5147
+ const nmDir = path4.join(rootDir, "node_modules");
5148
+ if (fs7.existsSync(nmDir)) {
4692
5149
  scanSubdirectories(nmDir, directNames, seen);
4693
5150
  if (options.depth !== "direct") {
4694
- scanPnpmStore(path5.join(nmDir, ".pnpm"), directNames, seen);
5151
+ scanPnpmStore(path4.join(nmDir, ".pnpm"), directNames, seen);
4695
5152
  }
4696
5153
  }
4697
5154
  const allDeps = Array.from(seen.values());
@@ -4815,10 +5272,10 @@ function generateCycloneDxSbom(dependencies, options = {}) {
4815
5272
  let name = options.projectName || "ai-sdlc-project";
4816
5273
  let version = options.projectVersion || "1.0.0";
4817
5274
  if (options.rootDir) {
4818
- const pkgPath = path5.join(options.rootDir, "package.json");
4819
- if (fs6.existsSync(pkgPath)) {
5275
+ const pkgPath = path4.join(options.rootDir, "package.json");
5276
+ if (fs7.existsSync(pkgPath)) {
4820
5277
  try {
4821
- const pkg = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
5278
+ const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
4822
5279
  if (pkg.name) name = pkg.name;
4823
5280
  if (pkg.version) version = pkg.version;
4824
5281
  } catch {
@@ -4852,15 +5309,15 @@ function generateCycloneDxSbom(dependencies, options = {}) {
4852
5309
  function tryAtomicWrite(filePath, content) {
4853
5310
  const tempPath = `${filePath}.${process.pid}.${Math.random().toString(36).substring(2)}.tmp`;
4854
5311
  try {
4855
- fs6.writeFileSync(tempPath, content, "utf-8");
5312
+ fs7.writeFileSync(tempPath, content, "utf-8");
4856
5313
  try {
4857
- if (fs6.existsSync(filePath)) {
4858
- fs6.rmSync(filePath, { force: true });
5314
+ if (fs7.existsSync(filePath)) {
5315
+ fs7.rmSync(filePath, { force: true });
4859
5316
  }
4860
- fs6.renameSync(tempPath, filePath);
5317
+ fs7.renameSync(tempPath, filePath);
4861
5318
  } catch {
4862
- fs6.copyFileSync(tempPath, filePath);
4863
- fs6.unlinkSync(tempPath);
5319
+ fs7.copyFileSync(tempPath, filePath);
5320
+ fs7.unlinkSync(tempPath);
4864
5321
  }
4865
5322
  return true;
4866
5323
  } catch {
@@ -4868,15 +5325,15 @@ function tryAtomicWrite(filePath, content) {
4868
5325
  }
4869
5326
  }
4870
5327
  function safeWriteFileSync(filePath, content) {
4871
- const dir = path5.dirname(filePath);
4872
- if (!fs6.existsSync(dir)) {
4873
- fs6.mkdirSync(dir, { recursive: true });
5328
+ const dir = path4.dirname(filePath);
5329
+ if (!fs7.existsSync(dir)) {
5330
+ fs7.mkdirSync(dir, { recursive: true });
4874
5331
  }
4875
5332
  for (let attempt = 0; attempt < 10; attempt++) {
4876
5333
  if (tryAtomicWrite(filePath, content)) return;
4877
5334
  }
4878
5335
  try {
4879
- fs6.writeFileSync(filePath, content, "utf-8");
5336
+ fs7.writeFileSync(filePath, content, "utf-8");
4880
5337
  } catch {
4881
5338
  }
4882
5339
  }
@@ -4902,9 +5359,9 @@ function buildThirdPartyHeader(projectName, count) {
4902
5359
  ];
4903
5360
  }
4904
5361
  function readLicenseTextPreview(licenseFile) {
4905
- if (!licenseFile || !fs6.existsSync(licenseFile)) return null;
5362
+ if (!licenseFile || !fs7.existsSync(licenseFile)) return null;
4906
5363
  try {
4907
- const text = fs6.readFileSync(licenseFile, "utf-8").trim();
5364
+ const text = fs7.readFileSync(licenseFile, "utf-8").trim();
4908
5365
  return text.slice(0, 1e3) + (text.length > 1e3 ? "\n... (texto truncado)" : "");
4909
5366
  } catch {
4910
5367
  return null;
@@ -5016,7 +5473,7 @@ function parseExceptions(exceptions) {
5016
5473
  }
5017
5474
  function parseLicensePolicy(content) {
5018
5475
  try {
5019
- const doc = yaml2.load(content);
5476
+ const doc = yaml3.load(content);
5020
5477
  const { permitted, restricted, blocked } = classifyCategories(doc?.categories);
5021
5478
  const exception_packages = parseExceptions(doc?.exceptions);
5022
5479
  return {
@@ -5152,9 +5609,9 @@ function generateLicenseReportMarkdown(dependencies, violations, policy, sbomPat
5152
5609
  return lines.filter(Boolean).join("\n");
5153
5610
  }
5154
5611
  function parseManifestItems(manifestFile) {
5155
- if (!fs6.existsSync(manifestFile)) return [];
5612
+ if (!fs7.existsSync(manifestFile)) return [];
5156
5613
  try {
5157
- const doc = yaml2.load(fs6.readFileSync(manifestFile, "utf-8"));
5614
+ const doc = yaml3.load(fs7.readFileSync(manifestFile, "utf-8"));
5158
5615
  return Array.isArray(doc?.evaluated_dependencies) ? doc.evaluated_dependencies : [];
5159
5616
  } catch {
5160
5617
  return [];
@@ -5186,10 +5643,10 @@ function verifyStaticManifest(manifestFile, policy) {
5186
5643
  function resolveManifestPath(rootDir, customPath) {
5187
5644
  if (customPath) return customPath;
5188
5645
  const candidates = [
5189
- path5.join(rootDir, "compliance", "license-manifest.yaml"),
5190
- path5.join(rootDir, "license-manifest.yaml")
5646
+ path4.join(rootDir, "compliance", "license-manifest.yaml"),
5647
+ path4.join(rootDir, "license-manifest.yaml")
5191
5648
  ];
5192
- return candidates.find((p) => fs6.existsSync(p)) || candidates[0];
5649
+ return candidates.find((p) => fs7.existsSync(p)) || candidates[0];
5193
5650
  }
5194
5651
  function handleStaticManifestVerification(options, rootDir, policy) {
5195
5652
  const manifestFile = resolveManifestPath(rootDir, options.manifestPath);
@@ -5210,7 +5667,7 @@ function shouldGenerateSbom(options, rawPolicy) {
5210
5667
  }
5211
5668
  function maybeGenerateSbom(options, rootDir, rawPolicy, dependencies) {
5212
5669
  if (!shouldGenerateSbom(options, rawPolicy)) return void 0;
5213
- const sbomPath = options.sbomPath || path5.join(rootDir, "reports", "sbom.cdx.json");
5670
+ const sbomPath = options.sbomPath || path4.join(rootDir, "reports", "sbom.cdx.json");
5214
5671
  const sbom = generateCycloneDxSbom(dependencies, { rootDir });
5215
5672
  writeCycloneDxSbom(sbom, sbomPath);
5216
5673
  return sbomPath;
@@ -5219,7 +5676,7 @@ function maybeGenerateNotices(options, rootDir, rawPolicy, dependencies) {
5219
5676
  const shouldGen = options.generateNotices === true || Boolean(options.noticesPath || rawPolicy?.ci_gates?.output_notices_file);
5220
5677
  if (!shouldGen) return void 0;
5221
5678
  const noticesFileName = rawPolicy?.ci_gates?.output_notices_file || "THIRD_PARTY_NOTICES.md";
5222
- const noticesPath = options.noticesPath || path5.join(rootDir, noticesFileName);
5679
+ const noticesPath = options.noticesPath || path4.join(rootDir, noticesFileName);
5223
5680
  const noticesContent = generateThirdPartyNotices(dependencies);
5224
5681
  writeThirdPartyNotices(noticesContent, noticesPath);
5225
5682
  return noticesPath;
@@ -5231,8 +5688,8 @@ function handleDynamicScaArtifacts(options, rootDir, rawPolicy, dependencies) {
5231
5688
  };
5232
5689
  }
5233
5690
  function loadLicensePolicy(rootDir, customPath) {
5234
- const policyFile = customPath || path5.join(rootDir, "license-policy.yaml");
5235
- return fs6.existsSync(policyFile) ? parseLicensePolicy(fs6.readFileSync(policyFile, "utf-8")) : parseLicensePolicy("");
5691
+ const policyFile = customPath || path4.join(rootDir, "license-policy.yaml");
5692
+ return fs7.existsSync(policyFile) ? parseLicensePolicy(fs7.readFileSync(policyFile, "utf-8")) : parseLicensePolicy("");
5236
5693
  }
5237
5694
  function evaluateDependencyList(dependencies, policy) {
5238
5695
  const violations = [];
@@ -5302,14 +5759,14 @@ var PROTECTED_BYPASS_PATTERNS = [
5302
5759
  /migrations?[\\/]/i
5303
5760
  ];
5304
5761
  function detectProfileFromChange(changeDir) {
5305
- if (!fs6.existsSync(changeDir)) return "standard";
5306
- const specFile = path5.join(changeDir, "spec.md");
5307
- if (fs6.existsSync(specFile)) {
5762
+ if (!fs7.existsSync(changeDir)) return "standard";
5763
+ const specFile = path4.join(changeDir, "spec.md");
5764
+ if (fs7.existsSync(specFile)) {
5308
5765
  try {
5309
- const rawSpec = fs6.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
5766
+ const rawSpec = fs7.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
5310
5767
  const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
5311
5768
  if (match) {
5312
- const parsedFm = yaml2.load(match[1]);
5769
+ const parsedFm = yaml3.load(match[1]);
5313
5770
  if (parsedFm && parsedFm.profile && ["patch", "standard", "critical"].includes(String(parsedFm.profile))) {
5314
5771
  return parsedFm.profile;
5315
5772
  }
@@ -5324,17 +5781,17 @@ function isPathProtectedFromPatch(filePath) {
5324
5781
  return PROTECTED_BYPASS_PATTERNS.some((pattern) => pattern.test(normalized));
5325
5782
  }
5326
5783
  function resolveActiveChangeDir(rootDir) {
5327
- const activeRoot = path5.join(rootDir, "specs", "changes", "active");
5328
- if (!fs6.existsSync(activeRoot)) return void 0;
5784
+ const activeRoot = path4.join(rootDir, "specs", "changes", "active");
5785
+ if (!fs7.existsSync(activeRoot)) return void 0;
5329
5786
  try {
5330
- const dirs = fs6.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path5.join(activeRoot, d.name));
5787
+ const dirs = fs7.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path4.join(activeRoot, d.name));
5331
5788
  return dirs.find((d) => detectProfileFromChange(d) === "patch") || dirs[0];
5332
5789
  } catch {
5333
5790
  return void 0;
5334
5791
  }
5335
5792
  }
5336
5793
  function detectGitDiffFiles(rootDir) {
5337
- if (!fs6.existsSync(path5.join(rootDir, ".git"))) return [];
5794
+ if (!fs7.existsSync(path4.join(rootDir, ".git"))) return [];
5338
5795
  try {
5339
5796
  const tracked = execFileSync("git", ["diff", "--name-only", "HEAD"], {
5340
5797
  cwd: rootDir,
@@ -5357,19 +5814,19 @@ function resolveChangeDirectory(rootDir, options) {
5357
5814
  if (options.changeDir) return options.changeDir;
5358
5815
  if (options.changeId) {
5359
5816
  const candidates = [
5360
- path5.join(rootDir, "specs", options.changeId),
5361
- path5.join(rootDir, "specs", "changes", "active", options.changeId),
5362
- path5.join(rootDir, "examples", "specs", options.changeId),
5363
- path5.join(rootDir, "specs", "changes", "completed", options.changeId)
5817
+ path4.join(rootDir, "specs", options.changeId),
5818
+ path4.join(rootDir, "specs", "changes", "active", options.changeId),
5819
+ path4.join(rootDir, "examples", "specs", options.changeId),
5820
+ path4.join(rootDir, "specs", "changes", "completed", options.changeId)
5364
5821
  ];
5365
- return candidates.find((c) => fs6.existsSync(c));
5822
+ return candidates.find((c) => fs7.existsSync(c));
5366
5823
  }
5367
5824
  return resolveActiveChangeDir(rootDir);
5368
5825
  }
5369
5826
  function collectPatchViolations(rootDir, modifiedFiles) {
5370
5827
  const violations = [];
5371
5828
  for (const rawFile of modifiedFiles) {
5372
- const relPath = path5.isAbsolute(rawFile) ? path5.relative(rootDir, rawFile) : rawFile;
5829
+ const relPath = path4.isAbsolute(rawFile) ? path4.relative(rootDir, rawFile) : rawFile;
5373
5830
  if (isPathProtectedFromPatch(relPath)) {
5374
5831
  violations.push(
5375
5832
  `[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'.`
@@ -5435,7 +5892,7 @@ function parseMdFrontmatter(raw) {
5435
5892
  return { fm: {}, body: cleaned.trim() };
5436
5893
  }
5437
5894
  try {
5438
- const parsed = yaml2.load(match[1]);
5895
+ const parsed = yaml3.load(match[1]);
5439
5896
  const fm = parsed && typeof parsed === "object" ? parsed : {};
5440
5897
  return { fm, body: cleaned.substring(match[0].length).trim() };
5441
5898
  } catch {
@@ -5475,13 +5932,13 @@ function extractManualMetadata(fm, id, manualType, relPath, body) {
5475
5932
  }
5476
5933
  function buildManualItem(rootDir, filePath) {
5477
5934
  try {
5478
- const raw = fs6.readFileSync(filePath, "utf-8");
5935
+ const raw = fs7.readFileSync(filePath, "utf-8");
5479
5936
  const { fm, body } = parseMdFrontmatter(raw);
5480
5937
  const id = typeof fm.id === "string" ? fm.id.trim() : "";
5481
5938
  const rawType = typeof fm.type === "string" ? fm.type.trim() : "";
5482
5939
  const manualType = resolveManualType(id, rawType);
5483
5940
  if (!id || !manualType) return null;
5484
- const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
5941
+ const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
5485
5942
  return extractManualMetadata(fm, id, manualType, relPath, body);
5486
5943
  } catch {
5487
5944
  return null;
@@ -5492,8 +5949,8 @@ function scanManualsInWorkspace(rootDir) {
5492
5949
  const seenIds = /* @__PURE__ */ new Set();
5493
5950
  const items = [];
5494
5951
  for (const relDir of manualDirs) {
5495
- const fullDir = path5.join(rootDir, relDir);
5496
- if (!fs6.existsSync(fullDir)) continue;
5952
+ const fullDir = path4.join(rootDir, relDir);
5953
+ if (!fs7.existsSync(fullDir)) continue;
5497
5954
  for (const file of walkMdFiles(fullDir)) {
5498
5955
  const item = buildManualItem(rootDir, file);
5499
5956
  if (item && !seenIds.has(item.id)) {
@@ -5516,10 +5973,10 @@ function classifyBaselineId(id, type, buckets) {
5516
5973
  }
5517
5974
  }
5518
5975
  function processBaselineFile(file, rootDir, buckets) {
5519
- const rel = path5.relative(rootDir, file).replace(/\\/g, "/");
5976
+ const rel = path4.relative(rootDir, file).replace(/\\/g, "/");
5520
5977
  if (rel.includes("/changes/") || rel.startsWith("changes/")) return;
5521
5978
  try {
5522
- const { fm } = parseMdFrontmatter(fs6.readFileSync(file, "utf-8"));
5979
+ const { fm } = parseMdFrontmatter(fs7.readFileSync(file, "utf-8"));
5523
5980
  const id = typeof fm.id === "string" ? fm.id.trim() : "";
5524
5981
  const status = typeof fm.status === "string" ? fm.status.trim().toLowerCase() : "active";
5525
5982
  if (!id || status === "retired" || status === "deprecated") return;
@@ -5540,8 +5997,8 @@ function collectBaselineTargets(rootDir) {
5540
5997
  enclaves: /* @__PURE__ */ new Set()
5541
5998
  };
5542
5999
  for (const dir of ["product", "architecture", "security", "specs"]) {
5543
- const fullDir = path5.join(rootDir, dir);
5544
- if (fs6.existsSync(fullDir)) {
6000
+ const fullDir = path4.join(rootDir, dir);
6001
+ if (fs7.existsSync(fullDir)) {
5545
6002
  scanDirectoryForBaseline(rootDir, fullDir, buckets);
5546
6003
  }
5547
6004
  }
@@ -5598,10 +6055,10 @@ function detectCurrentGitBranch(rootDir) {
5598
6055
  }
5599
6056
  }
5600
6057
  function resolveActiveChangeProfile(rootDir) {
5601
- const activeRoot = path5.join(rootDir, "specs", "changes", "active");
5602
- if (!fs6.existsSync(activeRoot)) return "standard";
6058
+ const activeRoot = path4.join(rootDir, "specs", "changes", "active");
6059
+ if (!fs7.existsSync(activeRoot)) return "standard";
5603
6060
  try {
5604
- const dirs = fs6.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path5.join(activeRoot, d.name));
6061
+ const dirs = fs7.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path4.join(activeRoot, d.name));
5605
6062
  const profiles = dirs.map((d) => detectProfileFromChange(d));
5606
6063
  if (profiles.includes("critical")) return "critical";
5607
6064
  if (profiles.includes("patch") && profiles.length === 1) return "patch";
@@ -5785,7 +6242,7 @@ function extractParsedFrontmatter(content) {
5785
6242
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
5786
6243
  if (!match) return null;
5787
6244
  try {
5788
- const parsed = yaml2.load(match[1]);
6245
+ const parsed = yaml3.load(match[1]);
5789
6246
  if (parsed && typeof parsed === "object") {
5790
6247
  const fm = parsed;
5791
6248
  if (fm.id && isRequirementArtifact2(fm)) {
@@ -5814,11 +6271,11 @@ function buildRequirementObject(content, fm, body, filePath, relFile) {
5814
6271
  }
5815
6272
  function parseRequirementFile(filePath, rootDir) {
5816
6273
  try {
5817
- const content = fs6.readFileSync(filePath, "utf-8");
6274
+ const content = fs7.readFileSync(filePath, "utf-8");
5818
6275
  const extracted = extractParsedFrontmatter(content);
5819
6276
  if (!extracted) return null;
5820
6277
  const body = content.substring(extracted.matchLen);
5821
- const relFile = path5.relative(rootDir, filePath).replace(/\\/g, "/");
6278
+ const relFile = path4.relative(rootDir, filePath).replace(/\\/g, "/");
5822
6279
  return buildRequirementObject(content, extracted.fm, body, filePath, relFile);
5823
6280
  } catch {
5824
6281
  return null;
@@ -5830,7 +6287,7 @@ function isIgnoredScanPath(relFile) {
5830
6287
  function collectRequirements(rootDir) {
5831
6288
  const reqs = [];
5832
6289
  for (const file of walkMdFiles(rootDir)) {
5833
- const relFile = path5.relative(rootDir, file).replace(/\\/g, "/");
6290
+ const relFile = path4.relative(rootDir, file).replace(/\\/g, "/");
5834
6291
  if (isIgnoredScanPath(relFile)) continue;
5835
6292
  const req = parseRequirementFile(file, rootDir);
5836
6293
  if (req) reqs.push(req);
@@ -6189,10 +6646,10 @@ function isPathExcluded2(relPath, patterns) {
6189
6646
  function walkProjectFiles(dir, rootDir, excludedPatterns = DEFAULT_EXCLUDED_PATTERNS) {
6190
6647
  const results = [];
6191
6648
  try {
6192
- const entries = fs6.readdirSync(dir, { withFileTypes: true });
6649
+ const entries = fs7.readdirSync(dir, { withFileTypes: true });
6193
6650
  for (const entry of entries) {
6194
- const fullPath = path5.join(dir, entry.name);
6195
- const relPath = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
6651
+ const fullPath = path4.join(dir, entry.name);
6652
+ const relPath = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
6196
6653
  if (isPathExcluded2(relPath, excludedPatterns)) continue;
6197
6654
  if (entry.isDirectory()) {
6198
6655
  results.push(...walkProjectFiles(fullPath, rootDir, excludedPatterns));
@@ -6221,8 +6678,8 @@ function pickFirstString(...vals) {
6221
6678
  }
6222
6679
  function parseGitleaksItem(item, rootDir) {
6223
6680
  const rawFile = pickFirstString(item.File, item.file);
6224
- const filePath = path5.resolve(rootDir, rawFile);
6225
- const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
6681
+ const filePath = path4.resolve(rootDir, rawFile);
6682
+ const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
6226
6683
  const ruleId = pickFirstString(item.RuleID, item.ruleID) || "GITLEAKS-MATCH";
6227
6684
  const rawSecret = pickFirstString(item.Secret, item.secret, item.Match);
6228
6685
  const line = item.StartLine ?? item.line ?? 1;
@@ -6295,7 +6752,7 @@ function scanLineForSecrets(line, lineNumber, filePath, relPath, entropyThreshol
6295
6752
  function scanContentForSecrets(content, filePath, rootDir, entropyThreshold = 4.3) {
6296
6753
  const violations = [];
6297
6754
  const lines = content.split(/\r?\n/);
6298
- const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
6755
+ const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
6299
6756
  for (let i = 0; i < lines.length; i++) {
6300
6757
  violations.push(...scanLineForSecrets(lines[i], i + 1, filePath, relPath, entropyThreshold));
6301
6758
  }
@@ -6315,7 +6772,7 @@ function scanDiffItems(diffLines, rootDir, excludePatterns, entropyThreshold) {
6315
6772
  const findings = [];
6316
6773
  for (const [relFile, items] of fileGroups) {
6317
6774
  if (isPathExcluded2(relFile, excludePatterns)) continue;
6318
- const absPath = path5.resolve(rootDir, relFile);
6775
+ const absPath = path4.resolve(rootDir, relFile);
6319
6776
  for (const item of items) {
6320
6777
  const lineViolations = scanLineForSecrets(item.content, item.lineNumber, absPath, relFile, entropyThreshold);
6321
6778
  for (const v of lineViolations) {
@@ -6330,7 +6787,7 @@ function scanAllProjectFiles(rootDir, excludePatterns, entropyThreshold) {
6330
6787
  const findings = [];
6331
6788
  for (const file of allFiles) {
6332
6789
  try {
6333
- const content = fs6.readFileSync(file, "utf-8");
6790
+ const content = fs7.readFileSync(file, "utf-8");
6334
6791
  findings.push(...scanContentForSecrets(content, file, rootDir, entropyThreshold));
6335
6792
  } catch {
6336
6793
  }
@@ -6554,8 +7011,8 @@ function runSemgrepCli(rootDir) {
6554
7011
  const parsed = JSON.parse(output);
6555
7012
  if (Array.isArray(parsed.results)) {
6556
7013
  for (const item of parsed.results) {
6557
- const filePath = path5.resolve(rootDir, item.path);
6558
- const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
7014
+ const filePath = path4.resolve(rootDir, item.path);
7015
+ const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
6559
7016
  violations.push({
6560
7017
  filePath,
6561
7018
  relPath,
@@ -6582,15 +7039,15 @@ function isSastExcluded(relPath, excludes) {
6582
7039
  function walkSourceFiles(dir, rootDir, excludes = DEFAULT_SAST_EXCLUDES) {
6583
7040
  const files = [];
6584
7041
  try {
6585
- const entries = fs6.readdirSync(dir, { withFileTypes: true });
7042
+ const entries = fs7.readdirSync(dir, { withFileTypes: true });
6586
7043
  for (const entry of entries) {
6587
- const fullPath = path5.join(dir, entry.name);
6588
- const relPath = path5.relative(rootDir, fullPath).replace(/\\/g, "/");
7044
+ const fullPath = path4.join(dir, entry.name);
7045
+ const relPath = path4.relative(rootDir, fullPath).replace(/\\/g, "/");
6589
7046
  if (isSastExcluded(relPath, excludes)) continue;
6590
7047
  if (entry.isDirectory()) {
6591
7048
  files.push(...walkSourceFiles(fullPath, rootDir, excludes));
6592
7049
  } else if (entry.isFile()) {
6593
- const ext = path5.extname(entry.name).toLowerCase();
7050
+ const ext = path4.extname(entry.name).toLowerCase();
6594
7051
  if (GENERAL_SOURCE_EXTENSIONS.includes(ext)) {
6595
7052
  files.push(fullPath);
6596
7053
  }
@@ -6611,11 +7068,11 @@ function checkRuleMatch(rule, line, ext, minSeverity) {
6611
7068
  }
6612
7069
  function scanFileForSast(filePath, rootDir, minSeverity = "HIGH") {
6613
7070
  const violations = [];
6614
- const ext = path5.extname(filePath).toLowerCase();
7071
+ const ext = path4.extname(filePath).toLowerCase();
6615
7072
  try {
6616
- const content = fs6.readFileSync(filePath, "utf-8");
7073
+ const content = fs7.readFileSync(filePath, "utf-8");
6617
7074
  const lines = content.split(/\r?\n/);
6618
- const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
7075
+ const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
6619
7076
  for (let i = 0; i < lines.length; i++) {
6620
7077
  const line = lines[i];
6621
7078
  if (isLineExemptFromSast(line)) continue;
@@ -6641,8 +7098,8 @@ function scanFileForSast(filePath, rootDir, minSeverity = "HIGH") {
6641
7098
  function collectSastFiles(rootDir, targetDirs) {
6642
7099
  const files = [];
6643
7100
  for (const tDir of targetDirs) {
6644
- const fullDir = path5.join(rootDir, tDir);
6645
- if (fs6.existsSync(fullDir)) {
7101
+ const fullDir = path4.join(rootDir, tDir);
7102
+ if (fs7.existsSync(fullDir)) {
6646
7103
  files.push(...walkSourceFiles(fullDir, rootDir));
6647
7104
  }
6648
7105
  }
@@ -6797,7 +7254,7 @@ function calculateRating(mi, cc) {
6797
7254
  return "F";
6798
7255
  }
6799
7256
  function detectLanguage(filePath) {
6800
- const ext = path5.extname(filePath).toLowerCase();
7257
+ const ext = path4.extname(filePath).toLowerCase();
6801
7258
  switch (ext) {
6802
7259
  case ".ts":
6803
7260
  return "TypeScript";
@@ -6822,7 +7279,7 @@ function detectLanguage(filePath) {
6822
7279
  }
6823
7280
  function parseJsonSummaryFile(filePath, relSource) {
6824
7281
  try {
6825
- const raw = JSON.parse(fs6.readFileSync(filePath, "utf-8"));
7282
+ const raw = JSON.parse(fs7.readFileSync(filePath, "utf-8"));
6826
7283
  const total = raw.total;
6827
7284
  if (!total || typeof total !== "object") return null;
6828
7285
  const linesPct = Number(total.lines?.pct);
@@ -6845,7 +7302,7 @@ function updateLcovCounts(line, counts) {
6845
7302
  }
6846
7303
  function parseLcovFile(filePath, relSource) {
6847
7304
  try {
6848
- const lines = fs6.readFileSync(filePath, "utf-8").split(/\r?\n/);
7305
+ const lines = fs7.readFileSync(filePath, "utf-8").split(/\r?\n/);
6849
7306
  const counts = { LF: 0, LH: 0, BRF: 0, BRH: 0, FNF: 0, FNH: 0 };
6850
7307
  for (const line of lines) {
6851
7308
  updateLcovCounts(line, counts);
@@ -6865,16 +7322,16 @@ function parseLcovFile(filePath, relSource) {
6865
7322
  function readCodeCoverageSummary(rootDir = process.cwd()) {
6866
7323
  const jsonCandidates = ["coverage/coverage-summary.json", "reports/coverage-summary.json"];
6867
7324
  for (const rel of jsonCandidates) {
6868
- const full = path5.join(rootDir, rel);
6869
- if (fs6.existsSync(full)) {
7325
+ const full = path4.join(rootDir, rel);
7326
+ if (fs7.existsSync(full)) {
6870
7327
  const parsed = parseJsonSummaryFile(full, rel);
6871
7328
  if (parsed) return parsed;
6872
7329
  }
6873
7330
  }
6874
7331
  const lcovCandidates = ["coverage/lcov.info", "lcov.info", "reports/lcov.info"];
6875
7332
  for (const rel of lcovCandidates) {
6876
- const full = path5.join(rootDir, rel);
6877
- if (fs6.existsSync(full)) {
7333
+ const full = path4.join(rootDir, rel);
7334
+ if (fs7.existsSync(full)) {
6878
7335
  const parsed = parseLcovFile(full, rel);
6879
7336
  if (parsed) return parsed;
6880
7337
  }
@@ -7010,9 +7467,9 @@ function renderQualityReportMarkdown(gateResult, coverage, accumulated, averages
7010
7467
  return [...header, "", "---", "", ...table, "", "---", "", ...polyglot, ...violationsSection, "", EVALUATION_STANDARDS_MD].join("\n");
7011
7468
  }
7012
7469
  function writeQualityReportFile(rootDir, markdown) {
7013
- const reportsDir = path5.join(rootDir, "reports");
7014
- fs6.mkdirSync(reportsDir, { recursive: true });
7015
- fs6.writeFileSync(path5.join(reportsDir, "QUALITY_REPORT.md"), markdown, "utf-8");
7470
+ const reportsDir = path4.join(rootDir, "reports");
7471
+ fs7.mkdirSync(reportsDir, { recursive: true });
7472
+ fs7.writeFileSync(path4.join(reportsDir, "QUALITY_REPORT.md"), markdown, "utf-8");
7016
7473
  }
7017
7474
  function generateQualityReport(options = {}) {
7018
7475
  const rootDir = options.rootDir || process.cwd();
@@ -7154,12 +7611,12 @@ Flujo de Integraci\xF3n (Merges):`,
7154
7611
  };
7155
7612
  }
7156
7613
  function resolveVersionFromProposal(changeDir) {
7157
- const proposalFile = path5.join(changeDir, "proposal.md");
7158
- if (!fs6.existsSync(proposalFile)) return "1.0.0";
7159
- const propContent = fs6.readFileSync(proposalFile, "utf-8");
7614
+ const proposalFile = path4.join(changeDir, "proposal.md");
7615
+ if (!fs7.existsSync(proposalFile)) return "1.0.0";
7616
+ const propContent = fs7.readFileSync(proposalFile, "utf-8");
7160
7617
  const propFm = propContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
7161
7618
  if (!propFm) return "1.0.0";
7162
- const parsedProp = yaml2.load(propFm[1]);
7619
+ const parsedProp = yaml3.load(propFm[1]);
7163
7620
  return parsedProp?.version ? String(parsedProp.version) : "1.0.0";
7164
7621
  }
7165
7622
  function resolveChangeVersion(fm, changeDir) {
@@ -7173,12 +7630,12 @@ function isTaskMatchingId(currentId, cleanTaskId) {
7173
7630
  return currentLower === targetLower || currentLower.startsWith(`${targetLower}-`) || targetLower.startsWith(`${currentLower}-`);
7174
7631
  }
7175
7632
  function parseTasksFrontmatter(tasksFile) {
7176
- if (!fs6.existsSync(tasksFile)) return null;
7633
+ if (!fs7.existsSync(tasksFile)) return null;
7177
7634
  try {
7178
- const content = fs6.readFileSync(tasksFile, "utf-8");
7635
+ const content = fs7.readFileSync(tasksFile, "utf-8");
7179
7636
  const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
7180
7637
  if (!fmMatch) return null;
7181
- const fm = yaml2.load(fmMatch[1]);
7638
+ const fm = yaml3.load(fmMatch[1]);
7182
7639
  return fm && Array.isArray(fm.tasks) ? fm : null;
7183
7640
  } catch {
7184
7641
  return null;
@@ -7197,7 +7654,7 @@ function buildTaskMatch(currentId, currentTitle, rawChangeId, changeDir, version
7197
7654
  };
7198
7655
  }
7199
7656
  function inspectChangeTasks(changeDir, entryName, cleanTaskId, availableTasks) {
7200
- const fm = parseTasksFrontmatter(path5.join(changeDir, "tasks.md"));
7657
+ const fm = parseTasksFrontmatter(path4.join(changeDir, "tasks.md"));
7201
7658
  if (!fm || !fm.tasks) return void 0;
7202
7659
  const rawChangeId = resolveRawChangeId(fm, entryName);
7203
7660
  const versionStr = resolveChangeVersion(fm, changeDir);
@@ -7218,16 +7675,16 @@ function locateTaskInActiveChanges(taskId, rootDir = process.cwd()) {
7218
7675
  if (!cleanTaskId) {
7219
7676
  return { found: false, error: "Debe especificar un identificador de tarea v\xE1lido (ej. TSK-001)." };
7220
7677
  }
7221
- const activeChangesDir = path5.join(rootDir, "specs", "changes", "active");
7222
- if (!fs6.existsSync(activeChangesDir)) {
7678
+ const activeChangesDir = path4.join(rootDir, "specs", "changes", "active");
7679
+ if (!fs7.existsSync(activeChangesDir)) {
7223
7680
  return { found: false, error: `No se encontr\xF3 el directorio de cambios activos en '${activeChangesDir}'.` };
7224
7681
  }
7225
7682
  const availableTasks = [];
7226
- const candidateDirs = fs6.readdirSync(activeChangesDir, { withFileTypes: true });
7683
+ const candidateDirs = fs7.readdirSync(activeChangesDir, { withFileTypes: true });
7227
7684
  let matchedTask;
7228
7685
  for (const entry of candidateDirs) {
7229
7686
  if (!entry.isDirectory()) continue;
7230
- const changeDir = path5.join(activeChangesDir, entry.name);
7687
+ const changeDir = path4.join(activeChangesDir, entry.name);
7231
7688
  const m = inspectChangeTasks(changeDir, entry.name, cleanTaskId, availableTasks);
7232
7689
  if (m && !matchedTask) {
7233
7690
  matchedTask = m;
@@ -7291,7 +7748,7 @@ function resolveBranchHierarchy(match, rootDir = process.cwd()) {
7291
7748
  const branches = getGitBranchList(rootDir);
7292
7749
  const cleanVersion = (match.version || "1.0.0").replace(/^v/, "");
7293
7750
  const releaseBranch = detectExistingReleaseBranch(branches, cleanVersion);
7294
- const dirName = path5.basename(match.changeDir);
7751
+ const dirName = path4.basename(match.changeDir);
7295
7752
  const changeSlug = dirName.replace(/^(chg|CHG)-/i, (m) => m.toUpperCase());
7296
7753
  const featureBranch = detectExistingFeatureBranch(branches, dirName, match.changeId, changeSlug);
7297
7754
  const parentIdMatch = featureBranch.replace(/^(feat|feature)\//i, "").match(/^[a-zA-Z0-9]+-[a-zA-Z0-9]+/);
@@ -7598,13 +8055,13 @@ function getGitConfigValue(key, cwd) {
7598
8055
  }
7599
8056
  function readSessionTelemetry(cwd) {
7600
8057
  const possiblePaths = [
7601
- path5.join(cwd, ".ai-sdlc", "session-telemetry.json"),
7602
- path5.join(cwd, ".ai-sdlc", "agent-session.json")
8058
+ path4.join(cwd, ".ai-sdlc", "session-telemetry.json"),
8059
+ path4.join(cwd, ".ai-sdlc", "agent-session.json")
7603
8060
  ];
7604
8061
  for (const p of possiblePaths) {
7605
- if (fs6.existsSync(p)) {
8062
+ if (fs7.existsSync(p)) {
7606
8063
  try {
7607
- const raw = fs6.readFileSync(p, "utf-8");
8064
+ const raw = fs7.readFileSync(p, "utf-8");
7608
8065
  return JSON.parse(raw);
7609
8066
  } catch {
7610
8067
  }
@@ -7824,20 +8281,20 @@ if [ -f "$TELEMETRY_FILE" ]; then
7824
8281
  fi
7825
8282
  `;
7826
8283
  function installGitHooks(rootDir = process.cwd()) {
7827
- const gitDir = path5.join(rootDir, ".git");
7828
- if (!fs6.existsSync(gitDir)) {
8284
+ const gitDir = path4.join(rootDir, ".git");
8285
+ if (!fs7.existsSync(gitDir)) {
7829
8286
  return {
7830
8287
  success: false,
7831
8288
  error: `No se encontr\xF3 el directorio Git en '${gitDir}'.`
7832
8289
  };
7833
8290
  }
7834
- const hooksDir = path5.join(gitDir, "hooks");
7835
- if (!fs6.existsSync(hooksDir)) {
7836
- fs6.mkdirSync(hooksDir, { recursive: true });
8291
+ const hooksDir = path4.join(gitDir, "hooks");
8292
+ if (!fs7.existsSync(hooksDir)) {
8293
+ fs7.mkdirSync(hooksDir, { recursive: true });
7837
8294
  }
7838
- const hookPath = path5.join(hooksDir, "prepare-commit-msg");
8295
+ const hookPath = path4.join(hooksDir, "prepare-commit-msg");
7839
8296
  try {
7840
- fs6.writeFileSync(hookPath, PREPARE_COMMIT_MSG_SCRIPT, { encoding: "utf-8", mode: 493 });
8297
+ fs7.writeFileSync(hookPath, PREPARE_COMMIT_MSG_SCRIPT, { encoding: "utf-8", mode: 493 });
7841
8298
  return {
7842
8299
  success: true,
7843
8300
  hookPath
@@ -8149,16 +8606,16 @@ function formatReleaseKpiMarkdown(report) {
8149
8606
  ].join("\n");
8150
8607
  }
8151
8608
  function writeReleaseKpiReport(report, options = {}) {
8152
- const root = options.outputDir || (options.rootDir ? path5.join(options.rootDir, "reports", "releases") : path5.join(process.cwd(), "reports", "releases"));
8153
- if (!fs6.existsSync(root)) {
8154
- fs6.mkdirSync(root, { recursive: true });
8609
+ const root = options.outputDir || (options.rootDir ? path4.join(options.rootDir, "reports", "releases") : path4.join(process.cwd(), "reports", "releases"));
8610
+ if (!fs7.existsSync(root)) {
8611
+ fs7.mkdirSync(root, { recursive: true });
8155
8612
  }
8156
8613
  const cleanBranchName = report.releaseBranch.replace(/[^a-zA-Z0-9.-]/g, "_");
8157
- const mdPath = path5.join(root, `RELEASE_KPIS_${cleanBranchName}.md`);
8158
- const jsonPath = path5.join(root, `release_${cleanBranchName}.kpis.json`);
8614
+ const mdPath = path4.join(root, `RELEASE_KPIS_${cleanBranchName}.md`);
8615
+ const jsonPath = path4.join(root, `release_${cleanBranchName}.kpis.json`);
8159
8616
  const mdContent = formatReleaseKpiMarkdown(report);
8160
- fs6.writeFileSync(mdPath, mdContent, "utf-8");
8161
- fs6.writeFileSync(jsonPath, JSON.stringify(report, null, 2), "utf-8");
8617
+ fs7.writeFileSync(mdPath, mdContent, "utf-8");
8618
+ fs7.writeFileSync(jsonPath, JSON.stringify(report, null, 2), "utf-8");
8162
8619
  return { markdownPath: mdPath, jsonPath };
8163
8620
  }
8164
8621
  var cachedScript = null;
@@ -8167,19 +8624,19 @@ function resolveCytoscapePath() {
8167
8624
  try {
8168
8625
  const req = createRequire(import.meta.url);
8169
8626
  const entryPath = req.resolve("cytoscape");
8170
- const dir = path5.dirname(entryPath);
8171
- const minPath = path5.join(dir, "cytoscape.min.js");
8172
- if (fs6.existsSync(minPath)) {
8627
+ const dir = path4.dirname(entryPath);
8628
+ const minPath = path4.join(dir, "cytoscape.min.js");
8629
+ if (fs7.existsSync(minPath)) {
8173
8630
  return minPath;
8174
8631
  }
8175
8632
  } catch {
8176
8633
  }
8177
8634
  const searchDirs = [
8178
- path5.join(process.cwd(), "node_modules", "cytoscape", "dist", "cytoscape.min.js"),
8179
- path5.join(process.cwd(), "packages", "core", "node_modules", "cytoscape", "dist", "cytoscape.min.js")
8635
+ path4.join(process.cwd(), "node_modules", "cytoscape", "dist", "cytoscape.min.js"),
8636
+ path4.join(process.cwd(), "packages", "core", "node_modules", "cytoscape", "dist", "cytoscape.min.js")
8180
8637
  ];
8181
8638
  for (const candidate of searchDirs) {
8182
- if (fs6.existsSync(candidate)) {
8639
+ if (fs7.existsSync(candidate)) {
8183
8640
  return candidate;
8184
8641
  }
8185
8642
  }
@@ -8192,7 +8649,7 @@ function getCytoscapeScript() {
8192
8649
  const scriptPath = resolveCytoscapePath();
8193
8650
  if (scriptPath) {
8194
8651
  try {
8195
- cachedScript = fs6.readFileSync(scriptPath, "utf-8");
8652
+ cachedScript = fs7.readFileSync(scriptPath, "utf-8");
8196
8653
  return cachedScript;
8197
8654
  } catch {
8198
8655
  }
@@ -8276,13 +8733,13 @@ function extractFrontmatter2(fileContent) {
8276
8733
  const match = fileContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
8277
8734
  if (!match) return null;
8278
8735
  try {
8279
- return yaml2.load(match[1]) || null;
8736
+ return yaml3.load(match[1]) || null;
8280
8737
  } catch {
8281
8738
  return null;
8282
8739
  }
8283
8740
  }
8284
8741
  function toScannedArtifact(filePath, relPath) {
8285
- const fileContent = fs6.readFileSync(filePath, "utf-8");
8742
+ const fileContent = fs7.readFileSync(filePath, "utf-8");
8286
8743
  const fm = extractFrontmatter2(fileContent);
8287
8744
  const id = typeof fm?.id === "string" ? fm.id.trim() : "";
8288
8745
  if (!id) return null;
@@ -8294,12 +8751,12 @@ function toScannedArtifact(filePath, relPath) {
8294
8751
  return { id, type: classified.type, title, layer: classified.layer, filePath: relPath, content, frontmatter: fm };
8295
8752
  }
8296
8753
  function parseAndCatalogArtifactFile(rootDir, filePath, scannedMap, archViewByBasename) {
8297
- const relPath = path5.relative(rootDir, filePath).replace(/\\/g, "/");
8754
+ const relPath = path4.relative(rootDir, filePath).replace(/\\/g, "/");
8298
8755
  if (relPath.includes("/changes/") || relPath.startsWith("changes/")) return;
8299
8756
  const scannedArt = toScannedArtifact(filePath, relPath);
8300
8757
  if (!scannedArt) return;
8301
8758
  if (scannedArt.layer === "architecture" && scannedArt.type === "architecture") {
8302
- archViewByBasename.set(path5.basename(relPath), scannedArt);
8759
+ archViewByBasename.set(path4.basename(relPath), scannedArt);
8303
8760
  } else {
8304
8761
  scannedMap.set(scannedArt.id, scannedArt);
8305
8762
  }
@@ -8309,8 +8766,8 @@ function scanWorkspaceArtifacts(rootDir) {
8309
8766
  const archViewByBasename = /* @__PURE__ */ new Map();
8310
8767
  const artifactDirs = ["product", "architecture", "specs", "security"];
8311
8768
  for (const dirName of artifactDirs) {
8312
- const fullDirPath = path5.join(rootDir, dirName);
8313
- if (fs6.existsSync(fullDirPath)) {
8769
+ const fullDirPath = path4.join(rootDir, dirName);
8770
+ if (fs7.existsSync(fullDirPath)) {
8314
8771
  for (const f of walkMdFiles(fullDirPath)) {
8315
8772
  parseAndCatalogArtifactFile(rootDir, f, scannedArtifactsMap, archViewByBasename);
8316
8773
  }
@@ -8368,9 +8825,9 @@ function addTraceArchEdges(r, status, archViewByBasename, nodeMap, addEdge) {
8368
8825
  }
8369
8826
  }
8370
8827
  function loadTestContentPreview(filePath) {
8371
- if (!fs6.existsSync(filePath)) return void 0;
8828
+ if (!fs7.existsSync(filePath)) return void 0;
8372
8829
  try {
8373
- const testContent = fs6.readFileSync(filePath, "utf-8");
8830
+ const testContent = fs7.readFileSync(filePath, "utf-8");
8374
8831
  const lang = filePath.endsWith(".feature") ? "gherkin" : "typescript";
8375
8832
  const preview = testContent.substring(0, 1500) + (testContent.length > 1500 ? "\n... (truncated)" : "");
8376
8833
  return "```" + lang + "\n" + preview + "\n```";
@@ -8380,10 +8837,10 @@ function loadTestContentPreview(filePath) {
8380
8837
  }
8381
8838
  function ensureTestNode(t, testId, rootDir, nodeMap) {
8382
8839
  if (nodeMap.has(testId)) return;
8383
- const fullTestPath = path5.isAbsolute(t) ? t : path5.join(rootDir, t);
8840
+ const fullTestPath = path4.isAbsolute(t) ? t : path4.join(rootDir, t);
8384
8841
  nodeMap.set(testId, {
8385
8842
  id: testId,
8386
- label: path5.basename(t),
8843
+ label: path4.basename(t),
8387
8844
  title: t,
8388
8845
  type: t.endsWith(".feature") ? "bdd-feature" : "code-test",
8389
8846
  layer: "test",
@@ -10418,9 +10875,9 @@ var IGNORED_SCAN_DIRS = /* @__PURE__ */ new Set([
10418
10875
  ".agent"
10419
10876
  ]);
10420
10877
  function readKpiFilesInDir(dir) {
10421
- if (!fs6.existsSync(dir)) return [];
10878
+ if (!fs7.existsSync(dir)) return [];
10422
10879
  try {
10423
- return fs6.readdirSync(dir).filter((f) => f.endsWith(".kpis.json")).map((f) => path5.join(dir, f));
10880
+ return fs7.readdirSync(dir).filter((f) => f.endsWith(".kpis.json")).map((f) => path4.join(dir, f));
10424
10881
  } catch {
10425
10882
  return [];
10426
10883
  }
@@ -10428,11 +10885,11 @@ function readKpiFilesInDir(dir) {
10428
10885
  function scanSubdirKpiFiles(dir, depth, acc) {
10429
10886
  if (depth > 4) return;
10430
10887
  try {
10431
- const entries = fs6.readdirSync(dir, { withFileTypes: true });
10888
+ const entries = fs7.readdirSync(dir, { withFileTypes: true });
10432
10889
  for (const entry of entries) {
10433
10890
  if (!entry.isDirectory() || IGNORED_SCAN_DIRS.has(entry.name)) continue;
10434
- const sub = path5.join(dir, entry.name);
10435
- if (entry.name === "releases" && path5.basename(dir) === "reports") {
10891
+ const sub = path4.join(dir, entry.name);
10892
+ if (entry.name === "releases" && path4.basename(dir) === "reports") {
10436
10893
  acc.push(...readKpiFilesInDir(sub));
10437
10894
  } else {
10438
10895
  scanSubdirKpiFiles(sub, depth + 1, acc);
@@ -10442,7 +10899,7 @@ function scanSubdirKpiFiles(dir, depth, acc) {
10442
10899
  }
10443
10900
  }
10444
10901
  function findKpiJsonFiles(rootDir) {
10445
- const primaryFiles = readKpiFilesInDir(path5.join(rootDir, "reports", "releases"));
10902
+ const primaryFiles = readKpiFilesInDir(path4.join(rootDir, "reports", "releases"));
10446
10903
  if (primaryFiles.length > 0) return primaryFiles;
10447
10904
  const found = [];
10448
10905
  scanSubdirKpiFiles(rootDir, 0, found);
@@ -10453,7 +10910,7 @@ function collectHistoricalKpis(rootDir) {
10453
10910
  const kpiFiles = findKpiJsonFiles(rootDir);
10454
10911
  for (const fullPath of kpiFiles) {
10455
10912
  try {
10456
- const content = fs6.readFileSync(fullPath, "utf-8");
10913
+ const content = fs7.readFileSync(fullPath, "utf-8");
10457
10914
  const report = JSON.parse(content);
10458
10915
  if (!items.some((it) => it.id === report.releaseBranch && it.timestamp === report.generatedAt)) {
10459
10916
  items.push({
@@ -10650,15 +11107,15 @@ function computeGateAverages(gateResults) {
10650
11107
  }
10651
11108
  function generateDashboardReport(options = {}) {
10652
11109
  const rootDir = options.rootDir || process.cwd();
10653
- const outputPath = options.outputPath || path5.join(rootDir, "reports", "dashboard.html");
11110
+ const outputPath = options.outputPath || path4.join(rootDir, "reports", "dashboard.html");
10654
11111
  const graphData = buildGraphElements(rootDir);
10655
11112
  const metricsData = collectDashboardMetrics(rootDir);
10656
11113
  const html = renderDashboardHtml(graphData, metricsData, options.title);
10657
- const outDir = path5.dirname(outputPath);
10658
- if (!fs6.existsSync(outDir)) {
10659
- fs6.mkdirSync(outDir, { recursive: true });
11114
+ const outDir = path4.dirname(outputPath);
11115
+ if (!fs7.existsSync(outDir)) {
11116
+ fs7.mkdirSync(outDir, { recursive: true });
10660
11117
  }
10661
- fs6.writeFileSync(outputPath, html, "utf-8");
11118
+ fs7.writeFileSync(outputPath, html, "utf-8");
10662
11119
  const { bddCoverageRatio, traceabilityRatio } = computeSummaryRatios(metricsData);
10663
11120
  const { avgMi, avgCc } = computeGateAverages(metricsData.gate.results);
10664
11121
  return {
@@ -10754,18 +11211,25 @@ var SPECIALIZED_AGENT_TEMPLATES = [
10754
11211
  {
10755
11212
  id: "agent-qa-engineer",
10756
11213
  title: "QA Engineer and SDET",
10757
- description: "Translate approved requirements (FR-*, SEC-REQ-*, QR-*) into comprehensive FAILING (red) BDD/Gherkin and unit test suites before production code is written.",
11214
+ 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.",
10758
11215
  globs: "tests/**, specs/changes/**/spec.md",
10759
11216
  body: `## Role & Mission
10760
11217
  - **Role**: QA Engineer and SDET (Software Development Engineer in Test) Agent (\`agent-qa-engineer\`).
10761
- - **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.
11218
+ - **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.
10762
11219
 
10763
11220
  ## Operational Directives & Guardrails
10764
11221
  - Read exclusively requirements cited in the \`handoff.yaml\` sidecar (\`HOF-*\`) and their referenced artifacts.
10765
- - For each \`FR-*\` requirement, generate the three mandatory test categories:
10766
- 1. **Nominal Case** (\`Scenario\`): happy path with valid inputs within expected boundaries.
10767
- 2. **Boundary Cases** (\`Scenario Outline\` + \`Examples\` table): values at the extremes of the accepted range.
10768
- 3. **Out-of-Range / Invalid Cases** (\`Scenario Outline\` + \`Examples\` table): null, empty, wrong types, values exceeding bounds.
11222
+ - **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.
11223
+ - **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.
11224
+ - **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:
11225
+ 1. **Happy Path**: nominal user interaction with valid inputs producing the expected UI state/response.
11226
+ 2. **Sad Path**: error handling, invalid interactions, validation rejections, disabled/blocked states, and failure recovery.
11227
+ 3. **Boundary Values**: minimum/maximum lengths, numeric extremes, empty/whitespace inputs, edge selections, and rapid/repeated interactions.
11228
+ - For each \`FR-*\` requirement, generate the three mandatory test categories tagged with \`@nominal\`, \`@boundary\`, and \`@invalid\`:
11229
+ 1. **Nominal Case** (\`@nominal\` \`Scenario\`): happy path with valid inputs within expected boundaries.
11230
+ 2. **Boundary Cases** (\`@boundary\` \`Scenario Outline\` + \`Examples\` table): values at the extremes of the accepted range.
11231
+ 3. **Out-of-Range / Invalid Cases** (\`@invalid\` \`Scenario Outline\` + \`Examples\` table): null, empty, wrong types, values exceeding bounds.
11232
+ - **Use Case Variant Coverage**: Inspect every \`UC-*\` in \`handoff.yaml\` and generate BDD scenarios covering 100% of its \`alternative-flows\` and \`exception-flows\`.
10769
11233
  - Apply conditional categories when applicable: Security (\`@security @mitigation\`), Performance (\`@performance\`), Idempotency (\`@idempotence\`), Postconditions, and Interface Contracts.
10770
11234
  - **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\`).
10771
11235
  - Tag all scenarios with \`@<FR-ID> @automated @regression\`.
@@ -10782,9 +11246,21 @@ var SPECIALIZED_AGENT_TEMPLATES = [
10782
11246
  - **Role**: High-Precision Software Developer / Coder Agent (\`agent-developer\`).
10783
11247
  - **Mission**: Implement atomic tasks from SDD specifications (\`tasks.md\`) delivering clean, strictly typed, and tested code.
10784
11248
 
11249
+ ## Mandatory 4-Step Implementation Order
11250
+ Execute every implementation task following this strict sequential order:
11251
+ 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).
11252
+ 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.
11253
+ 3. **Component Implementation**: Implement the concrete components, use cases, and adapters that implement and consume the defined interfaces.
11254
+ 4. **Exhaustive Per-Function Unit Testing**: Write unit tests for every function in \`tests/unit/\`, always verifying:
11255
+ - **Happy Path**: nominal execution with valid inputs.
11256
+ - **Sad Path**: controlled error paths, rejections, and business exceptions.
11257
+ - **Boundary Values**: minimum, maximum, exact limits, and off-by-one edges.
11258
+ - **Unexpected, Null & Empty Values**: \`null\`, \`undefined\`, empty strings/arrays/objects, and malformed inputs.
11259
+
10785
11260
  ## Operational Directives & Guardrails
10786
11261
  - Respect the autonomy mode assigned to each task in \`tasks.md\` (\`AUTONOMOUS\`, \`HUMAN_REVIEW_PLAN\`, \`AMBIGUOUS\`, \`HIGH_RISK_MANUAL\`).
10787
- - Apply Test-Driven Development (TDD): make failing tests delivered by \`agent-qa-engineer\` pass green without altering tests to accommodate buggy code.
11262
+ - **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.
11263
+ - 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/\`.
10788
11264
  - Cite implemented \`FR-*\` or \`SEC-REQ-*\` IDs in test header comments to maintain 360\xB0 reverse traceability.
10789
11265
  - **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.
10790
11266
  - **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).
@@ -10978,11 +11454,14 @@ This file defines the operational rules and specialized role mappings for Google
10978
11454
  - Emit a return handoff to \`agent-threat-modeler\` in the technical security feedback loop when infrastructure or persistence decisions introduce new attack vectors.
10979
11455
 
10980
11456
  ### 4. \`agent-qa-engineer\` (QA Engineer and SDET)
10981
- - **Mission**: Translate approved requirements (\`FR-*\`, \`SEC-REQ-*\`, \`QR-*\`) into comprehensive FAILING (red) BDD/Gherkin test suites, before \`agent-developer\` writes production code.
11457
+ - **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.
10982
11458
  - **Input**: Approved requirements from the \`handoff.yaml\` sidecar (\`HOF-*\`).
10983
- - **Output**: Failing Gherkin scenarios covering: nominal case, boundary cases, out-of-range cases, and conditional categories (security, performance, idempotency, postconditions, interface contract).
11459
+ - **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).
10984
11460
  - **Guardrails**:
10985
11461
  - FORBIDDEN to include production code or anticipate implementations.
11462
+ - **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.
11463
+ - **Mandatory E2E & UI Testing**: Never forget or omit End-to-End (E2E) and User Interface (UI) tests.
11464
+ - **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.
10986
11465
  - Every \`FR-*\` must have at least nominal + boundary + out-of-range scenarios; missing these blocks handoff to \`agent-developer\`.
10987
11466
  - Mandatory tags: \`@<FR-ID> @automated @regression\`.
10988
11467
  - Run \`pnpm run verify:testing\` before emitting handoff.
@@ -10990,6 +11469,7 @@ This file defines the operational rules and specialized role mappings for Google
10990
11469
  ### 5. \`agent-developer\` (Software Developer)
10991
11470
  - **Mission**: Implement atomic tasks from SDD specifications (\`tasks.md\`) with clean, strictly typed code and thorough tests.
10992
11471
  - **Directives**:
11472
+ - **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.
10993
11473
  - Respect the autonomy mode assigned in \`tasks.md\` (\`AUTONOMOUS\`, \`HUMAN_REVIEW_PLAN\`, \`AMBIGUOUS\`, \`HIGH_RISK_MANUAL\`).
10994
11474
  - Make tests delivered by \`agent-qa-engineer\` pass green without altering them to accommodate code.
10995
11475
  - Respect \`quality-policy.yaml\` thresholds: CC $\\le 10$, Cognitive $\\le 15$, MI $\\ge 50$, LOC $\\le 40$.
@@ -11088,7 +11568,7 @@ Before finalizing changes or proposing commits:
11088
11568
  | \`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\`. |
11089
11569
  | \`agent-threat-modeler\` | STRIDE/OWASP threat triad (\`ACT-THREAT-*\`, \`ABUSE-*\`, \`SEC-REQ-*\`). |
11090
11570
  | \`agent-system-architect\` | Modular arc42 + NAF v4 architecture (\`CMP-*\`, Mermaid diagrams, \`ADR-*\`). |
11091
- | \`agent-qa-engineer\` | FAILING BDD/Gherkin suites before \`agent-developer\` writes code. |
11571
+ | \`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. |
11092
11572
  | \`agent-developer\` | Atomic tasks from \`tasks.md\`; respects autonomy mode; runs \`pnpm run check:fix\` + \`pnpm run verify:all\`. |
11093
11573
  | \`agent-security-auditor\` | Adversarial PR security audit, SAST, secrets, and CVSS v3.1 evaluation. |
11094
11574
  | \`agent-compliance-checker\` | SPDX license compliance audit against \`license-policy.yaml\`. |
@@ -11155,7 +11635,8 @@ Canonical Reference: [\`process/09_agent_protocols.md\`](process/09_agent_protoc
11155
11635
  - **Function Length**: $\\le 40$ lines (warning if $> 30$).
11156
11636
  - If a function exceeds these limits, decompose logic into cohesive helper functions before completing the task.
11157
11637
 
11158
- ## 2. TDD Methodology and Unit Testing
11638
+ ## 2. TDD Methodology, Clean Architecture & Unit Testing
11639
+ - **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.
11159
11640
  - Apply Test-Driven Development: generate unit tests before or alongside component logic.
11160
11641
  - Tag tests linked to requirements with header comments citing the ID (\`FR-*\` or \`SEC-REQ-*\`) to enable reverse traceability.
11161
11642
  - Suppressing types (\`any\`), linter rules (\`eslint-disable\`), or unit tests to force pipeline passage is prohibited.
@@ -11228,12 +11709,12 @@ npx tsx packages/cli/src/index.ts sdd integrate --change <chg-id>
11228
11709
  - **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.
11229
11710
 
11230
11711
  ### 4. \`agent-qa-engineer\` (QA Engineer and SDET)
11231
- - **Mission**: Translate approved requirements (\`FR-*\`, \`SEC-REQ-*\`, \`QR-*\`) into comprehensive FAILING (red) BDD/Gherkin test suites, before \`agent-developer\` writes production code.
11232
- - **Guardrails**: FORBIDDEN to include production code. Mandatory tags: \`@<FR-ID> @automated @regression\`.
11712
+ - **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.
11713
+ - **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\`.
11233
11714
 
11234
11715
  ### 5. \`agent-developer\` (Software Developer)
11235
11716
  - **Mission**: Implement atomic tasks from SDD specifications (\`tasks.md\`) with clean, strictly typed code and thorough tests.
11236
- - **Directives**: Respect the autonomy mode. Make tests delivered by \`agent-qa-engineer\` pass green. Run \`pnpm run check:fix\` and \`pnpm run verify:all\`.
11717
+ - **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\`.
11237
11718
 
11238
11719
  ### 6. \`agent-security-auditor\` (Adversarial Code Auditor)
11239
11720
  - **Mission**: Examine Pull Requests for business logic vulnerabilities, injection vectors, secret leaks, and authorization flaws as part of the Pre-Merge Audit Triad.
@@ -11378,7 +11859,7 @@ Non-Negotiable Quality Thresholds (\`quality-policy.yaml\`):
11378
11859
  | \`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\`. |
11379
11860
  | \`agent-threat-modeler\` | STRIDE/OWASP threat triad (\`ACT-THREAT-*\`, \`ABUSE-*\`, \`SEC-REQ-*\`). |
11380
11861
  | \`agent-system-architect\` | Modular arc42 + NAF v4 architecture (\`CMP-*\`, Mermaid diagrams, \`ADR-*\`). |
11381
- | \`agent-qa-engineer\` | FAILING BDD/Gherkin suites before \`agent-developer\` writes production code. |
11862
+ | \`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. |
11382
11863
  | \`agent-developer\` | Atomic tasks from \`tasks.md\`; autonomy mode must be respected; runs \`pnpm run check:fix\` + \`pnpm run verify:all\`. |
11383
11864
  | \`agent-security-auditor\` | Adversarial PR security audit, SAST, secrets, and CVSS v3.1 evaluation. |
11384
11865
  | \`agent-compliance-checker\` | SPDX license compliance audit against \`license-policy.yaml\`. |
@@ -12804,14 +13285,14 @@ function collectAgentFiles(targets) {
12804
13285
  function writeProjectFiles(destDir, files, dryRun) {
12805
13286
  const filesCreated = [];
12806
13287
  for (const f of files) {
12807
- const fullFilePath = path5.join(destDir, f.relPath);
12808
- const parentDir = path5.dirname(fullFilePath);
13288
+ const fullFilePath = path4.join(destDir, f.relPath);
13289
+ const parentDir = path4.dirname(fullFilePath);
12809
13290
  if (!dryRun) {
12810
- if (!fs6.existsSync(parentDir)) {
12811
- fs6.mkdirSync(parentDir, { recursive: true });
13291
+ if (!fs7.existsSync(parentDir)) {
13292
+ fs7.mkdirSync(parentDir, { recursive: true });
12812
13293
  }
12813
- if (!fs6.existsSync(fullFilePath)) {
12814
- fs6.writeFileSync(fullFilePath, f.content, "utf-8");
13294
+ if (!fs7.existsSync(fullFilePath)) {
13295
+ fs7.writeFileSync(fullFilePath, f.content, "utf-8");
12815
13296
  filesCreated.push(f.relPath);
12816
13297
  }
12817
13298
  } else {
@@ -12884,7 +13365,7 @@ function getCiWorkflowFiles(ciProvider) {
12884
13365
  function createDirectoriesOnDisk(destDir, directories, dryRun) {
12885
13366
  const created = [];
12886
13367
  for (const d of directories) {
12887
- if (!dryRun) fs6.mkdirSync(path5.join(destDir, d), { recursive: true });
13368
+ if (!dryRun) fs7.mkdirSync(path4.join(destDir, d), { recursive: true });
12888
13369
  created.push(d);
12889
13370
  }
12890
13371
  return created;
@@ -12902,13 +13383,13 @@ function collectStarterFiles(ciProvider, agentFiles = [], archGranularity = "min
12902
13383
  ...getCiWorkflowFiles(ciProvider),
12903
13384
  ...agentFiles,
12904
13385
  ...getArchitectureTemplates(archGranularity).map((t) => ({
12905
- relPath: path5.join("templates", "architecture", t.filename).replace(/\\/g, "/"),
13386
+ relPath: path4.join("templates", "architecture", t.filename).replace(/\\/g, "/"),
12906
13387
  content: t.content
12907
13388
  }))
12908
13389
  ];
12909
13390
  }
12910
13391
  function tryInstallGitHook(destDir, dryRun) {
12911
- if (!dryRun && fs6.existsSync(path5.join(destDir, ".git"))) {
13392
+ if (!dryRun && fs7.existsSync(path4.join(destDir, ".git"))) {
12912
13393
  return installGitHooks(destDir).success;
12913
13394
  }
12914
13395
  return false;
@@ -12950,7 +13431,7 @@ function buildInitSuccessResult(destDir, directoriesCreated, filesCreated, ciPro
12950
13431
  }
12951
13432
  function initProject(options = {}) {
12952
13433
  const baseDir = options.rootDir || process.cwd();
12953
- const destDir = path5.resolve(baseDir, options.targetDir || ".");
13434
+ const destDir = path4.resolve(baseDir, options.targetDir || ".");
12954
13435
  const ciRes = validateCiProvider(options.ci);
12955
13436
  if (!ciRes.valid) return buildInitError(destDir, ciRes.error);
12956
13437
  const archRes = validateArchitectureGranularity(options.architecture);
@@ -12972,6 +13453,6 @@ function initProject(options = {}) {
12972
13453
  );
12973
13454
  }
12974
13455
 
12975
- export { ALL_ARCHITECTURE_TEMPLATES, BRANCH_PATTERNS, DEFAULT_EXCLUDED_PATTERNS, DEFAULT_EXCLUDES, DEFAULT_EXTENSIONS, DEFAULT_SAST_EXCLUDES, GENERAL_SOURCE_EXTENSIONS, KPI_SUMMARY_END_TAG, KPI_SUMMARY_START_TAG, OpenSpecAdapter, PREPARE_COMMIT_MSG_SCRIPT, PROMPT_INJECTION_SIGNATURES, PROTECTED_BYPASS_PATTERNS, SAST_RULES, SECRET_RULES, SPECIALIZED_AGENT_TEMPLATES, STARTER_ANTIGRAVITY_MCP, STARTER_ANTIGRAVITY_RULES, STARTER_ARCH_ADR, STARTER_ARCH_ARCHITECTURE_CONSTRAINTS, STARTER_ARCH_COMPONENT, STARTER_ARCH_CONTEXT_AND_SCOPE, STARTER_ARCH_CROSS_CUTTING_CONCEPTS, STARTER_ARCH_DEPLOYMENT_VIEW, STARTER_ARCH_GLOSSARY, STARTER_ARCH_INTRODUCTION_AND_GOALS, STARTER_ARCH_LEVEL_1_WHITEBOX, STARTER_ARCH_QUALITY_REQUIREMENTS, STARTER_ARCH_RISKS_AND_TECHNICAL_DEBT, STARTER_ARCH_RUNTIME_VIEW, STARTER_ARCH_SOLUTION_STRATEGY, STARTER_AZURE_PIPELINES, STARTER_BITBUCKET_PIPELINES, STARTER_CLAUDE_RULES, STARTER_COPILOT_RULES, STARTER_CURSOR_CORE_RULES, STARTER_CURSOR_MCP, STARTER_CURSOR_PRODUCT_RULES, STARTER_CURSOR_QUALITY_RULES, STARTER_GITHUB_CI, STARTER_GITHUB_SDD_INTEGRATE, STARTER_GITLAB_CI, STARTER_LICENSE_POLICY, STARTER_QUALITY_POLICY, STARTER_REQUIREMENT_TEMPLATE, STARTER_TASK_TEMPLATE, STARTER_VSCODE_MCP, STARTER_WORKFLOW_HANDOFF_TEMPLATE, SpecKitAdapter, TYPE_TO_SCHEMA_MAP, VALID_AGENT_TARGETS, VALID_ARCHITECTURE_GRANULARITIES, VALID_AUTONOMY_MODES, VALID_CHANGE_PROFILES, VALID_CI_PROVIDERS, aggregateCommitKpiRecords, aggregatePrKpis, aggregateReleaseKpis, appendRevisionHistoryRow, buildCycloneDxComponent, buildFeatureFileContent, buildGraphElements, calculateJaccardSimilarity, calculateMetrics, calculateRating, calculateShannonEntropy, checkGherkinInSync, checkoutTaskBranch, classifyBranch, collectAgentFiles, collectDashboardMetrics, collectHistoricalKpis, computeCanonicalSha256, computeSha256, createDefaultQualityPolicy, depositProductHandoffSidecar, detectActiveChangeForIntegration, detectAuthorIdentity, detectLanguage, detectProfileFromChange, detectPromptInjection, emitCiOutput, evaluateCompoundLicense, evaluateSingleDependency, extractCitations, extractCommitKpisFromRange, extractDirectDepNames, extractFeatureTags, extractFrontmatter, extractFunctions, extractFunctionsBraceLanguages, extractFunctionsPolyglot, extractFunctionsPython, extractFunctionsTypeScriptAst, extractGherkinBlock, extractGherkinFeatures, extractNormativeText, findChangeWorkspaceDir, findLicenseFile, formatCommitTrailers, formatPrKpiMarkdown, formatReleaseKpiMarkdown, generateBranchHierarchyPlan, generateCycloneDxSbom, generateDashboardReport, generateDuplicatesReportMarkdown, generateGovernanceReportMarkdown, generateLicenseReportMarkdown, generatePdacReportMarkdown, generateQualityReport, generateSastReportMarkdown, generateSecretsReportMarkdown, generateTestingCoverageReportMarkdown, generateThirdPartyNotices, generateTraceabilityReportMarkdown, getArchitectureTemplates, getCytoscapeScript, getGitDiffLines, getNextCorrelativeNumber, getSddAdapter, inferArtifactTypeFromId, inferMissingTrailers, initProject, injectKpisIntoPrBody, installGitHooks, integrateSddChange, isGitleaksInstalled, isPathProtectedFromPatch, isPlaceholderOrIgnored, isRequirementArtifact2 as isRequirementArtifact, isSemgrepInstalled, isToolAvailable, loadProductHandoffSidecar, locateTaskInActiveChanges, maskSecret, normalizeSpdxLicense, parseCommitTrailers, parseFrontmatter, parseFrontmatterId, parseGenericDoc, parseLicensePolicy, parseMarkdownFrontmatter, parseQualityPolicy, parseRequirementFile, parseSpecCatalogScope, parseTasksDoc, parseTasksFromContent, processPackageDir, readCodeCoverageSummary, readPackageJson, renderAntigravityAgentFile, renderAutonomyModeSection, renderClaudeAgentFile, renderCopilotAgentFile, renderCursorAgentFile, renderDashboardHtml, renderManualsTab, resolveAgentTargets, resolveBranchHierarchy, resolveCiEnvironment, resolveCytoscapePath, resolveFeatureFilePath, resolveGitRef, resolveLicenseFromFile, resolveSchemaForArtifact, resolveTargetFiles, runGitleaksCli, runSemgrepCli, scaffoldSddChange, scanAllProductHandoffs, scanAllTestArtifacts, scanContentForSecrets, scanFileForSast, scanInstalledLicenses, scanManualsInWorkspace, scanPnpmStore, scanSubdirectories, scanWithNativeFs, scanWithSyft, scanWithTrivy, slugify, syncPdacDigests, tokenizeTitle, updateCitationsInContent, validateAgainstSchema, validateArtifactSchema, verifyArtifactDuplicates, verifyArtifactsSchemas, verifyLicenses, verifyManuals, verifyPdacGraph, verifyProgressiveFriction, verifyQualityGate, verifySast, verifySddIntegration, verifySecrets, verifyTasksGovernance, verifyTestingCoverage, verifyTraceability, walkDir, walkMdFiles, walkProjectFiles, walkSourceFiles, walkTaskFiles, writeCycloneDxSbom, writeReleaseKpiReport, writeThirdPartyNotices };
13456
+ export { ALL_ARCHITECTURE_TEMPLATES, BRANCH_PATTERNS, DEFAULT_EXCLUDED_PATTERNS, DEFAULT_EXCLUDES, DEFAULT_EXTENSIONS, DEFAULT_SAST_EXCLUDES, GENERAL_SOURCE_EXTENSIONS, KPI_SUMMARY_END_TAG, KPI_SUMMARY_START_TAG, OpenSpecAdapter, PREPARE_COMMIT_MSG_SCRIPT, PROMPT_INJECTION_SIGNATURES, PROTECTED_BYPASS_PATTERNS, SAST_RULES, SECRET_RULES, SPECIALIZED_AGENT_TEMPLATES, STARTER_ANTIGRAVITY_MCP, STARTER_ANTIGRAVITY_RULES, STARTER_ARCH_ADR, STARTER_ARCH_ARCHITECTURE_CONSTRAINTS, STARTER_ARCH_COMPONENT, STARTER_ARCH_CONTEXT_AND_SCOPE, STARTER_ARCH_CROSS_CUTTING_CONCEPTS, STARTER_ARCH_DEPLOYMENT_VIEW, STARTER_ARCH_GLOSSARY, STARTER_ARCH_INTRODUCTION_AND_GOALS, STARTER_ARCH_LEVEL_1_WHITEBOX, STARTER_ARCH_QUALITY_REQUIREMENTS, STARTER_ARCH_RISKS_AND_TECHNICAL_DEBT, STARTER_ARCH_RUNTIME_VIEW, STARTER_ARCH_SOLUTION_STRATEGY, STARTER_AZURE_PIPELINES, STARTER_BITBUCKET_PIPELINES, STARTER_CLAUDE_RULES, STARTER_COPILOT_RULES, STARTER_CURSOR_CORE_RULES, STARTER_CURSOR_MCP, STARTER_CURSOR_PRODUCT_RULES, STARTER_CURSOR_QUALITY_RULES, STARTER_GITHUB_CI, STARTER_GITHUB_SDD_INTEGRATE, STARTER_GITLAB_CI, STARTER_LICENSE_POLICY, STARTER_QUALITY_POLICY, STARTER_REQUIREMENT_TEMPLATE, STARTER_TASK_TEMPLATE, STARTER_VSCODE_MCP, STARTER_WORKFLOW_HANDOFF_TEMPLATE, SpecKitAdapter, TYPE_TO_SCHEMA_MAP, VALID_AGENT_TARGETS, VALID_ARCHITECTURE_GRANULARITIES, VALID_AUTONOMY_MODES, VALID_CHANGE_PROFILES, VALID_CI_PROVIDERS, aggregateCommitKpiRecords, aggregatePrKpis, aggregateReleaseKpis, analyzeBddVariantsForRequirement, appendRevisionHistoryRow, buildCycloneDxComponent, buildFeatureFileContent, buildGraphElements, calculateJaccardSimilarity, calculateMetrics, calculateRating, calculateShannonEntropy, checkGherkinInSync, checkoutTaskBranch, classifyBranch, collectAgentFiles, collectDashboardMetrics, collectHistoricalKpis, computeCanonicalSha256, computeSha256, createDefaultQualityPolicy, depositProductHandoffSidecar, detectActiveChangeForIntegration, detectAuthorIdentity, detectLanguage, detectProfileFromChange, detectPromptInjection, emitCiOutput, evaluateCompoundLicense, evaluateSingleDependency, extractCitations, extractCommitKpisFromRange, extractDirectDepNames, extractFeatureTags, extractFrontmatter, extractFunctions, extractFunctionsBraceLanguages, extractFunctionsPolyglot, extractFunctionsPython, extractFunctionsTypeScriptAst, extractGherkinBlock, extractGherkinFeatures, extractNormativeText, findChangeWorkspaceDir, findLicenseFile, formatCommitTrailers, formatPrKpiMarkdown, formatReleaseKpiMarkdown, generateBranchHierarchyPlan, generateCycloneDxSbom, generateDashboardReport, generateDuplicatesReportMarkdown, generateGovernanceReportMarkdown, generateLicenseReportMarkdown, generatePdacReportMarkdown, generateQualityReport, generateSastReportMarkdown, generateSecretsReportMarkdown, generateTestingCoverageReportMarkdown, generateThirdPartyNotices, generateTraceabilityReportMarkdown, getArchitectureTemplates, getCytoscapeScript, getGitDiffLines, getNextCorrelativeNumber, getSddAdapter, inferArtifactTypeFromId, inferMissingTrailers, initProject, injectKpisIntoPrBody, installGitHooks, integrateSddChange, isGitleaksInstalled, isPathProtectedFromPatch, isPlaceholderOrIgnored, isRequirementArtifact2 as isRequirementArtifact, isSemgrepInstalled, isToolAvailable, loadProductHandoffSidecar, locateTaskInActiveChanges, maskSecret, normalizeSpdxLicense, parseCommitTrailers, parseFrontmatter, parseFrontmatterId, parseGenericDoc, parseLicensePolicy, parseMarkdownFrontmatter, parseQualityPolicy, parseRequirementFile, parseSpecCatalogScope, parseTasksDoc, parseTasksFromContent, processPackageDir, readCodeCoverageSummary, readPackageJson, renderAntigravityAgentFile, renderAutonomyModeSection, renderClaudeAgentFile, renderCopilotAgentFile, renderCursorAgentFile, renderDashboardHtml, renderManualsTab, resolveAgentTargets, resolveBranchHierarchy, resolveCiEnvironment, resolveCytoscapePath, resolveFeatureFilePath, resolveGitRef, resolveLicenseFromFile, resolveSchemaForArtifact, resolveTargetFiles, runGitleaksCli, runSemgrepCli, scaffoldSddChange, scanAllProductHandoffs, scanAllTestArtifacts, scanContentForSecrets, scanFileForSast, scanInstalledLicenses, scanManualsInWorkspace, scanPnpmStore, scanSubdirectories, scanWithNativeFs, scanWithSyft, scanWithTrivy, slugify, syncPdacDigests, tokenizeTitle, updateCitationsInContent, validateAgainstSchema, validateArtifactSchema, verifyArtifactDuplicates, verifyArtifactsSchemas, verifyLicenses, verifyManuals, verifyPdacGraph, verifyProgressiveFriction, verifyQualityGate, verifySast, verifySddIntegration, verifySecrets, verifyTasksGovernance, verifyTestingCoverage, verifyTraceability, walkDir, walkMdFiles, walkProjectFiles, walkSourceFiles, walkTaskFiles, writeCycloneDxSbom, writeReleaseKpiReport, writeThirdPartyNotices };
12976
13457
  //# sourceMappingURL=index.js.map
12977
13458
  //# sourceMappingURL=index.js.map