@aisdlc/core 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1129 -853
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +94 -39
- package/dist/index.d.ts +94 -39
- package/dist/index.js +1108 -851
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
3
|
+
var crypto3 = require('crypto');
|
|
4
|
+
var fs8 = require('fs');
|
|
5
|
+
var path5 = require('path');
|
|
6
6
|
var yaml3 = require('js-yaml');
|
|
7
|
-
var tsMorph = require('ts-morph');
|
|
8
7
|
var child_process = require('child_process');
|
|
8
|
+
var tsMorph = require('ts-morph');
|
|
9
|
+
var Ajv2020 = require('ajv/dist/2020.js');
|
|
10
|
+
var addFormats = require('ajv-formats');
|
|
9
11
|
var module$1 = require('module');
|
|
10
12
|
|
|
11
13
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -29,11 +31,13 @@ function _interopNamespace(e) {
|
|
|
29
31
|
return Object.freeze(n);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
var
|
|
33
|
-
var
|
|
34
|
-
var
|
|
34
|
+
var crypto3__namespace = /*#__PURE__*/_interopNamespace(crypto3);
|
|
35
|
+
var fs8__namespace = /*#__PURE__*/_interopNamespace(fs8);
|
|
36
|
+
var path5__namespace = /*#__PURE__*/_interopNamespace(path5);
|
|
35
37
|
var yaml3__default = /*#__PURE__*/_interopDefault(yaml3);
|
|
36
38
|
var child_process__namespace = /*#__PURE__*/_interopNamespace(child_process);
|
|
39
|
+
var Ajv2020__default = /*#__PURE__*/_interopDefault(Ajv2020);
|
|
40
|
+
var addFormats__default = /*#__PURE__*/_interopDefault(addFormats);
|
|
37
41
|
|
|
38
42
|
// src/types/index.ts
|
|
39
43
|
var VALID_CHANGE_PROFILES = ["patch", "standard", "critical"];
|
|
@@ -43,6 +47,168 @@ var VALID_AUTONOMY_MODES = [
|
|
|
43
47
|
"AMBIGUOUS",
|
|
44
48
|
"HIGH_RISK_MANUAL"
|
|
45
49
|
];
|
|
50
|
+
function canonicalSort(a, b) {
|
|
51
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
52
|
+
}
|
|
53
|
+
function normalizePath(p) {
|
|
54
|
+
return p.normalize("NFC").replace(/\\/g, "/");
|
|
55
|
+
}
|
|
56
|
+
function walkMdFiles(dir, fileList = [], excludeDirs = [
|
|
57
|
+
"node_modules",
|
|
58
|
+
".git",
|
|
59
|
+
"dist",
|
|
60
|
+
"bin",
|
|
61
|
+
"obj",
|
|
62
|
+
"build",
|
|
63
|
+
"out",
|
|
64
|
+
"target",
|
|
65
|
+
"coverage",
|
|
66
|
+
".changeset",
|
|
67
|
+
"scratch",
|
|
68
|
+
"test-scaffold",
|
|
69
|
+
"fixtures",
|
|
70
|
+
"templates",
|
|
71
|
+
"examples",
|
|
72
|
+
".agent",
|
|
73
|
+
".agents",
|
|
74
|
+
".claude",
|
|
75
|
+
".cursor",
|
|
76
|
+
".github",
|
|
77
|
+
".vscode",
|
|
78
|
+
".vs",
|
|
79
|
+
".idea"
|
|
80
|
+
]) {
|
|
81
|
+
try {
|
|
82
|
+
if (!fs8__namespace.existsSync(dir)) return fileList;
|
|
83
|
+
const files = fs8__namespace.readdirSync(dir).sort(canonicalSort);
|
|
84
|
+
for (const file of files) {
|
|
85
|
+
const fullPath = path5__namespace.join(dir, file);
|
|
86
|
+
try {
|
|
87
|
+
if (fs8__namespace.statSync(fullPath).isDirectory()) {
|
|
88
|
+
if (!excludeDirs.includes(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
|
|
89
|
+
walkMdFiles(fullPath, fileList, excludeDirs);
|
|
90
|
+
}
|
|
91
|
+
} else if (file.endsWith(".md")) {
|
|
92
|
+
fileList.push(fullPath);
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
return fileList.sort(canonicalSort);
|
|
100
|
+
}
|
|
101
|
+
function stripVolatileReportMetadata(content, _ext = "") {
|
|
102
|
+
let normalized = content.replace(/\r\n/g, "\n");
|
|
103
|
+
normalized = normalized.replace(/(?:^|\n)>\s*\*\*(?:Generation Date|Generated at|Fecha|Audit Date|Date):\*\*.*$/gim, "");
|
|
104
|
+
normalized = normalized.replace(/Generated:\s*<span>[\s\S]*?<\/span>/gi, "Generated: <span>[STRIPPED]</span>");
|
|
105
|
+
return normalized.trim();
|
|
106
|
+
}
|
|
107
|
+
function areCycloneDxSbomsEqual(existingContent, newContent) {
|
|
108
|
+
try {
|
|
109
|
+
const a = JSON.parse(existingContent);
|
|
110
|
+
const b = JSON.parse(newContent);
|
|
111
|
+
return a.serialNumber === b.serialNumber && JSON.stringify(a.components) === JSON.stringify(b.components) && JSON.stringify(a.vulnerabilities || []) === JSON.stringify(b.vulnerabilities || []);
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function areSarifReportsEqual(existingContent, newContent) {
|
|
117
|
+
try {
|
|
118
|
+
const a = JSON.parse(existingContent);
|
|
119
|
+
const b = JSON.parse(newContent);
|
|
120
|
+
return JSON.stringify(a.runs?.[0]?.results || []) === JSON.stringify(b.runs?.[0]?.results || []);
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function isReportContentSemanticallyEqual(existingContent, newContent, filePath) {
|
|
126
|
+
if (existingContent === newContent) return true;
|
|
127
|
+
if (filePath.endsWith(".cdx.json") || filePath.endsWith("sbom.json")) {
|
|
128
|
+
if (areCycloneDxSbomsEqual(existingContent, newContent)) return true;
|
|
129
|
+
}
|
|
130
|
+
if (filePath.endsWith(".sarif")) {
|
|
131
|
+
if (areSarifReportsEqual(existingContent, newContent)) return true;
|
|
132
|
+
}
|
|
133
|
+
const ext = path5__namespace.extname(filePath).toLowerCase();
|
|
134
|
+
return stripVolatileReportMetadata(existingContent, ext) === stripVolatileReportMetadata(newContent, ext);
|
|
135
|
+
}
|
|
136
|
+
function atomicWriteFile(filePath, content) {
|
|
137
|
+
const tempPath = `${filePath}.${process.pid}.${Math.random().toString(36).substring(2)}.tmp`;
|
|
138
|
+
try {
|
|
139
|
+
fs8__namespace.writeFileSync(tempPath, content, "utf-8");
|
|
140
|
+
try {
|
|
141
|
+
if (fs8__namespace.existsSync(filePath)) {
|
|
142
|
+
fs8__namespace.rmSync(filePath, { force: true });
|
|
143
|
+
}
|
|
144
|
+
fs8__namespace.renameSync(tempPath, filePath);
|
|
145
|
+
} catch {
|
|
146
|
+
fs8__namespace.copyFileSync(tempPath, filePath);
|
|
147
|
+
fs8__namespace.unlinkSync(tempPath);
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
fs8__namespace.writeFileSync(filePath, content, "utf-8");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function writeReportFileIfChanged(filePath, content) {
|
|
154
|
+
try {
|
|
155
|
+
if (fs8__namespace.existsSync(filePath)) {
|
|
156
|
+
const existing = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
157
|
+
if (isReportContentSemanticallyEqual(existing, content, filePath)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
const dir = path5__namespace.dirname(filePath);
|
|
164
|
+
if (!fs8__namespace.existsSync(dir)) {
|
|
165
|
+
fs8__namespace.mkdirSync(dir, { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
atomicWriteFile(filePath, content);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
var UUID_V5_NAMESPACE_OID = "6ba7b812-9dad-11d1-80b4-00c04fd430c8";
|
|
171
|
+
function resolveDeterministicDate(rootDir) {
|
|
172
|
+
const epoch = process.env.SOURCE_DATE_EPOCH;
|
|
173
|
+
if (epoch) {
|
|
174
|
+
const parsed = parseInt(epoch, 10);
|
|
175
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
176
|
+
return new Date(parsed * 1e3);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const targetDir = rootDir || process.cwd();
|
|
180
|
+
if (fs8__namespace.existsSync(path5__namespace.join(targetDir, ".git"))) {
|
|
181
|
+
try {
|
|
182
|
+
const gitOut = child_process.execFileSync("git", ["log", "-1", "--format=%ct"], {
|
|
183
|
+
cwd: targetDir,
|
|
184
|
+
encoding: "utf-8",
|
|
185
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
186
|
+
}).trim();
|
|
187
|
+
const parsed = parseInt(gitOut, 10);
|
|
188
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
189
|
+
return new Date(parsed * 1e3);
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return /* @__PURE__ */ new Date();
|
|
195
|
+
}
|
|
196
|
+
function generateDeterministicUuidV5(name, namespace = UUID_V5_NAMESPACE_OID) {
|
|
197
|
+
const nsHex = namespace.replace(/-/g, "");
|
|
198
|
+
const nsBytes = Buffer.from(nsHex, "hex");
|
|
199
|
+
const nameBytes = Buffer.from(name, "utf-8");
|
|
200
|
+
const hash = crypto3__namespace.createHash("sha1").update(nsBytes).update(nameBytes).digest();
|
|
201
|
+
hash[6] = hash[6] & 15 | 80;
|
|
202
|
+
hash[8] = hash[8] & 63 | 128;
|
|
203
|
+
const hex = hash.toString("hex").substring(0, 32);
|
|
204
|
+
return [
|
|
205
|
+
hex.substring(0, 8),
|
|
206
|
+
hex.substring(8, 12),
|
|
207
|
+
hex.substring(12, 16),
|
|
208
|
+
hex.substring(16, 20),
|
|
209
|
+
hex.substring(20, 32)
|
|
210
|
+
].join("-");
|
|
211
|
+
}
|
|
46
212
|
var CYCLOMATIC_KINDS = /* @__PURE__ */ new Set([
|
|
47
213
|
tsMorph.SyntaxKind.IfStatement,
|
|
48
214
|
tsMorph.SyntaxKind.ConditionalExpression,
|
|
@@ -199,7 +365,7 @@ function buildNodeMetrics(node, filePath) {
|
|
|
199
365
|
return { functionName, filePath, loc, cyclomatic, cognitive, maintainability: mi, codeSmells, line };
|
|
200
366
|
}
|
|
201
367
|
function createTempSourceFile(project, content, filePath) {
|
|
202
|
-
const ext =
|
|
368
|
+
const ext = path5__namespace.extname(filePath).toLowerCase() || ".ts";
|
|
203
369
|
const fileName = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 7)}${ext}`;
|
|
204
370
|
return project.createSourceFile(fileName, content, { overwrite: true });
|
|
205
371
|
}
|
|
@@ -236,8 +402,8 @@ function extractFunctionsTypeScriptAst(content, filePath) {
|
|
|
236
402
|
}
|
|
237
403
|
}
|
|
238
404
|
function extractFunctionsPolyglot(content, filePath) {
|
|
239
|
-
const ext =
|
|
240
|
-
return ext === ".py" ?
|
|
405
|
+
const ext = path5__namespace.extname(filePath).toLowerCase();
|
|
406
|
+
return ext === ".py" ? extractPythonFunctionsPolyglot(content, filePath) : extractBraceFunctions(content, filePath);
|
|
241
407
|
}
|
|
242
408
|
function isDocstringToggle(trimmed) {
|
|
243
409
|
const dCount = (trimmed.match(/"""/g) || []).length;
|
|
@@ -282,7 +448,7 @@ function processPythonLine(raw, state, blocks, lineIndex) {
|
|
|
282
448
|
handlePythonBodyLine(raw, state, blocks, lineIndex);
|
|
283
449
|
}
|
|
284
450
|
}
|
|
285
|
-
function
|
|
451
|
+
function extractPythonFunctionsPolyglot(content, filePath) {
|
|
286
452
|
const lines = content.split("\n");
|
|
287
453
|
const blocks = [];
|
|
288
454
|
const state = { currentFn: null, fnLines: [], baseIndent: 0, inDocstring: false };
|
|
@@ -320,7 +486,7 @@ function processBraceLine(line, state, blocks, lineIndex, ext) {
|
|
|
320
486
|
}
|
|
321
487
|
}
|
|
322
488
|
function extractBraceFunctions(content, filePath) {
|
|
323
|
-
const ext =
|
|
489
|
+
const ext = path5__namespace.extname(filePath).toLowerCase();
|
|
324
490
|
const lines = content.split("\n");
|
|
325
491
|
const blocks = [];
|
|
326
492
|
const state = {
|
|
@@ -529,7 +695,7 @@ function countCommentsAndLoc(rawLines, isPy) {
|
|
|
529
695
|
return { loc, commentCount, cleanLines };
|
|
530
696
|
}
|
|
531
697
|
function calculatePolyglotMetrics(body, name, filePath, startLine) {
|
|
532
|
-
const ext =
|
|
698
|
+
const ext = path5__namespace.extname(filePath).toLowerCase();
|
|
533
699
|
const rawLines = body.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
534
700
|
const { loc, commentCount } = countCommentsAndLoc(rawLines, ext === ".py");
|
|
535
701
|
const strippedBody = stripStringsAndComments(body, ext);
|
|
@@ -602,50 +768,129 @@ function computeBraceCognitive(lines) {
|
|
|
602
768
|
function computePolyglotCognitive(lines, isPy) {
|
|
603
769
|
return isPy ? computePythonCognitive(lines) : computeBraceCognitive(lines);
|
|
604
770
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
771
|
+
|
|
772
|
+
// src/verifiers/ast/python-ast.ts
|
|
773
|
+
var PYTHON_AST_RUNNER_SCRIPT = `
|
|
774
|
+
import ast, json, sys
|
|
775
|
+
|
|
776
|
+
def analyze(code, filepath):
|
|
777
|
+
try:
|
|
778
|
+
tree = ast.parse(code)
|
|
779
|
+
except Exception as e:
|
|
780
|
+
return {"language": "python", "filePath": filepath, "functions": [], "error": str(e), "unsafeTypesCount": 0, "emptyStubsCount": 0}
|
|
781
|
+
|
|
782
|
+
functions = []
|
|
783
|
+
unsafe_types = 0
|
|
784
|
+
empty_stubs = 0
|
|
785
|
+
for node in ast.walk(tree):
|
|
786
|
+
if isinstance(node, ast.Name) and node.id == "Any":
|
|
787
|
+
unsafe_types += 1
|
|
788
|
+
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
789
|
+
start = node.lineno
|
|
790
|
+
end = getattr(node, 'end_lineno', start + len(node.body))
|
|
791
|
+
loc = end - start + 1
|
|
792
|
+
cc = 1
|
|
793
|
+
for child in ast.walk(node):
|
|
794
|
+
if isinstance(child, (ast.If, ast.For, ast.AsyncFor, ast.While, ast.ExceptHandler, ast.With, ast.AsyncWith)):
|
|
795
|
+
cc += 1
|
|
796
|
+
elif isinstance(child, ast.BoolOp):
|
|
797
|
+
cc += len(child.values) - 1
|
|
798
|
+
|
|
799
|
+
real_body = [n for n in node.body if not (isinstance(n, ast.Expr) and isinstance(getattr(n, 'value', None), ast.Constant))]
|
|
800
|
+
is_stub = len(real_body) == 0 or (len(real_body) == 1 and isinstance(real_body[0], ast.Pass))
|
|
801
|
+
if is_stub:
|
|
802
|
+
empty_stubs += 1
|
|
803
|
+
|
|
804
|
+
code_smells = ["Empty stub detected"] if is_stub else []
|
|
805
|
+
mi = max(0.0, round(100.0 - (cc * 2.0) - (loc * 0.2), 1))
|
|
806
|
+
functions.append({
|
|
807
|
+
"functionName": node.name,
|
|
808
|
+
"filePath": filepath,
|
|
809
|
+
"line": start,
|
|
810
|
+
"loc": loc,
|
|
811
|
+
"cyclomatic": cc,
|
|
812
|
+
"cognitive": cc,
|
|
813
|
+
"maintainability": mi,
|
|
814
|
+
"codeSmells": code_smells
|
|
815
|
+
})
|
|
816
|
+
return {
|
|
817
|
+
"language": "python",
|
|
818
|
+
"filePath": filepath,
|
|
819
|
+
"functions": functions,
|
|
820
|
+
"unsafeTypesCount": unsafe_types,
|
|
821
|
+
"emptyStubsCount": empty_stubs
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
code = sys.stdin.read()
|
|
825
|
+
filepath = sys.argv[1] if len(sys.argv) > 1 else ""
|
|
826
|
+
print(json.dumps(analyze(code, filepath)))
|
|
827
|
+
`;
|
|
828
|
+
function extractFunctionsPythonAst(content, filePath) {
|
|
630
829
|
try {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
640
|
-
} else if (file.endsWith(".md")) {
|
|
641
|
-
fileList.push(fullPath);
|
|
642
|
-
}
|
|
643
|
-
} catch {
|
|
644
|
-
}
|
|
830
|
+
const res = child_process.spawnSync("python", ["-c", PYTHON_AST_RUNNER_SCRIPT, filePath], {
|
|
831
|
+
input: content,
|
|
832
|
+
encoding: "utf-8",
|
|
833
|
+
timeout: 1e4
|
|
834
|
+
});
|
|
835
|
+
if (res.status === 0 && res.stdout) {
|
|
836
|
+
const parsed = JSON.parse(res.stdout);
|
|
837
|
+
return parsed;
|
|
645
838
|
}
|
|
646
839
|
} catch {
|
|
647
840
|
}
|
|
648
|
-
|
|
841
|
+
const fallbackFns = extractPythonFunctionsPolyglot(content, filePath);
|
|
842
|
+
return {
|
|
843
|
+
language: "python",
|
|
844
|
+
filePath,
|
|
845
|
+
functions: fallbackFns,
|
|
846
|
+
unsafeTypesCount: 0,
|
|
847
|
+
emptyStubsCount: 0
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// src/verifiers/ast/native-ast-orchestrator.ts
|
|
852
|
+
function isCliInstalled(bin) {
|
|
853
|
+
try {
|
|
854
|
+
const res = child_process.spawnSync(bin, ["--version"], { encoding: "utf-8", timeout: 5e3, shell: false });
|
|
855
|
+
return res.status === 0 && !res.error;
|
|
856
|
+
} catch {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
function assertRequiredStack(stack) {
|
|
861
|
+
if (!stack) return;
|
|
862
|
+
if (!isCliInstalled(stack)) {
|
|
863
|
+
throw new Error(`[AI-SDLC Fail-Closed] Required toolchain CLI '${stack}' is not installed in the environment`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
function analyzeTypeScript(content, filePath) {
|
|
867
|
+
return {
|
|
868
|
+
language: "typescript",
|
|
869
|
+
filePath,
|
|
870
|
+
functions: extractFunctionsTypeScriptAst(content, filePath),
|
|
871
|
+
unsafeTypesCount: 0,
|
|
872
|
+
emptyStubsCount: 0
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function analyzePolyglot(content, filePath, ext) {
|
|
876
|
+
return {
|
|
877
|
+
language: ext.replace(/^\./, ""),
|
|
878
|
+
filePath,
|
|
879
|
+
functions: extractFunctionsPolyglot(content, filePath),
|
|
880
|
+
unsafeTypesCount: 0,
|
|
881
|
+
emptyStubsCount: 0
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
function orchestrateNativeAstAnalysis(content, filePath, options = {}) {
|
|
885
|
+
assertRequiredStack(options.requiredStack);
|
|
886
|
+
const ext = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
|
|
887
|
+
if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext)) {
|
|
888
|
+
return analyzeTypeScript(content, filePath);
|
|
889
|
+
}
|
|
890
|
+
if (ext === ".py") {
|
|
891
|
+
return extractFunctionsPythonAst(content, filePath);
|
|
892
|
+
}
|
|
893
|
+
return analyzePolyglot(content, filePath, ext);
|
|
649
894
|
}
|
|
650
895
|
function parseMarkdownFrontmatter(content) {
|
|
651
896
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
@@ -677,41 +922,41 @@ function extractGherkinBlock(body) {
|
|
|
677
922
|
}
|
|
678
923
|
function resolveTargetFiles(rootDir, targetPath) {
|
|
679
924
|
if (targetPath) {
|
|
680
|
-
const fullTarget =
|
|
681
|
-
if (!
|
|
682
|
-
return
|
|
925
|
+
const fullTarget = path5__namespace.isAbsolute(targetPath) ? targetPath : path5__namespace.join(rootDir, targetPath);
|
|
926
|
+
if (!fs8__namespace.existsSync(fullTarget)) return [];
|
|
927
|
+
return fs8__namespace.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
|
|
683
928
|
}
|
|
684
929
|
const searchDirs = [
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
930
|
+
path5__namespace.join(rootDir, "product"),
|
|
931
|
+
path5__namespace.join(rootDir, "security"),
|
|
932
|
+
path5__namespace.join(rootDir, "privacy"),
|
|
933
|
+
path5__namespace.join(rootDir, "specs"),
|
|
934
|
+
path5__namespace.join(rootDir, "examples", "product"),
|
|
935
|
+
path5__namespace.join(rootDir, "examples", "security"),
|
|
936
|
+
path5__namespace.join(rootDir, "examples", "privacy"),
|
|
937
|
+
path5__namespace.join(rootDir, "examples", "specs")
|
|
693
938
|
];
|
|
694
|
-
return searchDirs.filter((d) =>
|
|
939
|
+
return searchDirs.filter((d) => fs8__namespace.existsSync(d)).flatMap((d) => walkMdFiles(d));
|
|
695
940
|
}
|
|
696
941
|
function resolveFeatureFilePath(rootDir, filePath, id, customTarget) {
|
|
697
942
|
if (customTarget) {
|
|
698
|
-
return
|
|
699
|
-
}
|
|
700
|
-
const baseDir =
|
|
701
|
-
const candidateSecurity =
|
|
702
|
-
const candidatePrivacy =
|
|
703
|
-
const candidateRoot =
|
|
704
|
-
if (
|
|
705
|
-
if (
|
|
706
|
-
if (
|
|
943
|
+
return path5__namespace.isAbsolute(customTarget) ? customTarget : path5__namespace.join(rootDir, customTarget);
|
|
944
|
+
}
|
|
945
|
+
const baseDir = path5__namespace.join(rootDir, "tests", "features");
|
|
946
|
+
const candidateSecurity = path5__namespace.join(baseDir, "security", `${id.toLowerCase()}.feature`);
|
|
947
|
+
const candidatePrivacy = path5__namespace.join(baseDir, "privacy", `${id.toLowerCase()}.feature`);
|
|
948
|
+
const candidateRoot = path5__namespace.join(baseDir, `${id.toLowerCase()}.feature`);
|
|
949
|
+
if (fs8__namespace.existsSync(candidateSecurity)) return candidateSecurity;
|
|
950
|
+
if (fs8__namespace.existsSync(candidatePrivacy)) return candidatePrivacy;
|
|
951
|
+
if (fs8__namespace.existsSync(candidateRoot)) return candidateRoot;
|
|
707
952
|
const subDir = filePath.includes("security") ? "security" : filePath.includes("privacy") ? "privacy" : "";
|
|
708
|
-
return
|
|
953
|
+
return path5__namespace.join(baseDir, subDir, `${id.toLowerCase()}.feature`);
|
|
709
954
|
}
|
|
710
955
|
function buildFeatureFileContent(rootDir, filePath, id, version, gherkinBlocks) {
|
|
711
956
|
const banner = [
|
|
712
957
|
`# ==============================================================================`,
|
|
713
958
|
`# AUTO-GENERADO POR AI-SDLC (Cucumber Integration)`,
|
|
714
|
-
`# Origen: ${
|
|
959
|
+
`# Origen: ${path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/")}`,
|
|
715
960
|
`# ID Requerimiento: ${id}`,
|
|
716
961
|
`# Versi\xF3n: ${version || "1.0.0"}`,
|
|
717
962
|
`# NO EDITAR MANUALMENTE: Cualquier cambio debe realizarse en el Markdown origen.`,
|
|
@@ -736,20 +981,20 @@ function extractGherkinFeatures(options = {}) {
|
|
|
736
981
|
const targetFiles = resolveTargetFiles(rootDir, options.targetPath);
|
|
737
982
|
for (const filePath of targetFiles) {
|
|
738
983
|
try {
|
|
739
|
-
const content =
|
|
984
|
+
const content = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
740
985
|
const { frontmatter, body } = parseMarkdownFrontmatter(content);
|
|
741
986
|
const gherkinBlocks = extractGherkinBlock(body);
|
|
742
987
|
if (gherkinBlocks.length === 0) continue;
|
|
743
|
-
const id = frontmatter.id ||
|
|
988
|
+
const id = frontmatter.id || path5__namespace.basename(filePath, path5__namespace.extname(filePath));
|
|
744
989
|
const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
|
|
745
|
-
|
|
990
|
+
fs8__namespace.mkdirSync(path5__namespace.dirname(targetPath), { recursive: true });
|
|
746
991
|
const fullContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
|
|
747
|
-
|
|
992
|
+
fs8__namespace.writeFileSync(targetPath, fullContent, "utf-8");
|
|
748
993
|
const scenarioCount = countScenarios(gherkinBlocks);
|
|
749
994
|
totalScenarios += scenarioCount;
|
|
750
995
|
features.push({
|
|
751
|
-
sourceFile:
|
|
752
|
-
outputFile:
|
|
996
|
+
sourceFile: path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/"),
|
|
997
|
+
outputFile: path5__namespace.relative(rootDir, targetPath).replace(/\\/g, "/"),
|
|
753
998
|
featureName: id,
|
|
754
999
|
scenarioCount
|
|
755
1000
|
});
|
|
@@ -766,20 +1011,20 @@ function checkGherkinInSync(options = {}) {
|
|
|
766
1011
|
let totalFeatures = 0;
|
|
767
1012
|
for (const filePath of targetFiles) {
|
|
768
1013
|
try {
|
|
769
|
-
const content =
|
|
1014
|
+
const content = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
770
1015
|
const { frontmatter, body } = parseMarkdownFrontmatter(content);
|
|
771
1016
|
const gherkinBlocks = extractGherkinBlock(body);
|
|
772
1017
|
if (gherkinBlocks.length === 0) continue;
|
|
773
1018
|
totalFeatures++;
|
|
774
|
-
const id = frontmatter.id ||
|
|
1019
|
+
const id = frontmatter.id || path5__namespace.basename(filePath, path5__namespace.extname(filePath));
|
|
775
1020
|
const targetPath = resolveFeatureFilePath(rootDir, filePath, id, frontmatter["cucumber-feature-file"]);
|
|
776
|
-
const relTarget =
|
|
777
|
-
if (!
|
|
1021
|
+
const relTarget = path5__namespace.relative(rootDir, targetPath).replace(/\\/g, "/");
|
|
1022
|
+
if (!fs8__namespace.existsSync(targetPath)) {
|
|
778
1023
|
missingFiles.push(relTarget);
|
|
779
1024
|
continue;
|
|
780
1025
|
}
|
|
781
1026
|
const expectedContent = buildFeatureFileContent(rootDir, filePath, id, frontmatter.version, gherkinBlocks);
|
|
782
|
-
const actualContent =
|
|
1027
|
+
const actualContent = fs8__namespace.readFileSync(targetPath, "utf-8");
|
|
783
1028
|
if (actualContent.replace(/\r\n/g, "\n") !== expectedContent.replace(/\r\n/g, "\n")) {
|
|
784
1029
|
outOfSyncFiles.push(relTarget);
|
|
785
1030
|
}
|
|
@@ -878,7 +1123,7 @@ function generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, t
|
|
|
878
1123
|
const reportLines = [
|
|
879
1124
|
`# \u{1F9EA} Deterministic Test Coverage Audit for Requirements and Tasks`,
|
|
880
1125
|
``,
|
|
881
|
-
`> **Audit Date:** ${(
|
|
1126
|
+
`> **Audit Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
882
1127
|
`> **Overall Verdict:** ${verdict}`,
|
|
883
1128
|
``,
|
|
884
1129
|
`---`,
|
|
@@ -941,8 +1186,8 @@ function parseCodeTestIds(content, relTest, reqToTestsMap) {
|
|
|
941
1186
|
}
|
|
942
1187
|
function processTestFile(fullPath, rootDir, reqToTestsMap) {
|
|
943
1188
|
try {
|
|
944
|
-
const content =
|
|
945
|
-
const relTest =
|
|
1189
|
+
const content = fs8__namespace.readFileSync(fullPath, "utf-8");
|
|
1190
|
+
const relTest = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
946
1191
|
if (fullPath.endsWith(".feature")) {
|
|
947
1192
|
parseFeatureTags(content, relTest, reqToTestsMap);
|
|
948
1193
|
}
|
|
@@ -951,16 +1196,16 @@ function processTestFile(fullPath, rootDir, reqToTestsMap) {
|
|
|
951
1196
|
}
|
|
952
1197
|
}
|
|
953
1198
|
function collectTestFiles(dir, files = []) {
|
|
954
|
-
if (!
|
|
1199
|
+
if (!fs8__namespace.existsSync(dir)) return files;
|
|
955
1200
|
try {
|
|
956
|
-
const entries =
|
|
1201
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true }).sort((a, b) => canonicalSort(a.name, b.name));
|
|
957
1202
|
for (const e of entries) {
|
|
958
1203
|
if (e.isDirectory()) {
|
|
959
1204
|
if (!EXCLUDED_TEST_DIRS.has(e.name)) {
|
|
960
|
-
collectTestFiles(
|
|
1205
|
+
collectTestFiles(path5__namespace.join(dir, e.name), files);
|
|
961
1206
|
}
|
|
962
1207
|
} else if (isTestFile(e.name)) {
|
|
963
|
-
files.push(
|
|
1208
|
+
files.push(path5__namespace.join(dir, e.name));
|
|
964
1209
|
}
|
|
965
1210
|
}
|
|
966
1211
|
} catch {
|
|
@@ -1169,7 +1414,7 @@ function collectTestCorpus(rootDir, allMdFiles) {
|
|
|
1169
1414
|
const testContents = [];
|
|
1170
1415
|
for (const tf of collectTestFiles(rootDir)) {
|
|
1171
1416
|
try {
|
|
1172
|
-
const content =
|
|
1417
|
+
const content = fs8__namespace.readFileSync(tf, "utf-8");
|
|
1173
1418
|
testContents.push(content);
|
|
1174
1419
|
if (tf.endsWith(".feature")) gherkinSources.push(content);
|
|
1175
1420
|
} catch {
|
|
@@ -1177,7 +1422,7 @@ function collectTestCorpus(rootDir, allMdFiles) {
|
|
|
1177
1422
|
}
|
|
1178
1423
|
for (const md of allMdFiles) {
|
|
1179
1424
|
try {
|
|
1180
|
-
const blocks = extractGherkinBlock(
|
|
1425
|
+
const blocks = extractGherkinBlock(fs8__namespace.readFileSync(md, "utf-8"));
|
|
1181
1426
|
gherkinSources.push(...blocks);
|
|
1182
1427
|
testContents.push(...blocks);
|
|
1183
1428
|
} catch {
|
|
@@ -1206,9 +1451,9 @@ function countCucumberScenarios(raw) {
|
|
|
1206
1451
|
return { passed, failed };
|
|
1207
1452
|
}
|
|
1208
1453
|
function parseCucumberReportFile(reportPath) {
|
|
1209
|
-
if (!
|
|
1454
|
+
if (!fs8__namespace.existsSync(reportPath)) return void 0;
|
|
1210
1455
|
try {
|
|
1211
|
-
const raw = JSON.parse(
|
|
1456
|
+
const raw = JSON.parse(fs8__namespace.readFileSync(reportPath, "utf-8"));
|
|
1212
1457
|
if (!Array.isArray(raw)) return void 0;
|
|
1213
1458
|
const { passed, failed } = countCucumberScenarios(raw);
|
|
1214
1459
|
return {
|
|
@@ -1222,7 +1467,7 @@ function parseCucumberReportFile(reportPath) {
|
|
|
1222
1467
|
}
|
|
1223
1468
|
}
|
|
1224
1469
|
function resolveCucumberReport(rootDir, options) {
|
|
1225
|
-
const candidate = options.cucumberReportPath ?
|
|
1470
|
+
const candidate = options.cucumberReportPath ? path5__namespace.isAbsolute(options.cucumberReportPath) ? options.cucumberReportPath : path5__namespace.join(rootDir, options.cucumberReportPath) : path5__namespace.join(rootDir, "reports", "cucumber-report.json");
|
|
1226
1471
|
const parsed = parseCucumberReportFile(candidate);
|
|
1227
1472
|
if (parsed) return parsed;
|
|
1228
1473
|
if (options.enforceCucumberExecution) {
|
|
@@ -1256,8 +1501,8 @@ function recordUseCaseAuditIfPresent(frontmatter, id, relPath, isTemplate, combi
|
|
|
1256
1501
|
if (flowAudit.status === "FALLO_SIN_PRUEBA") state.ucErrors++;
|
|
1257
1502
|
}
|
|
1258
1503
|
function processMdFileForCoverage(file, rootDir, reqToTestsMap, corpus, options, state) {
|
|
1259
|
-
const { frontmatter } = parseGenericDoc(
|
|
1260
|
-
const relPath =
|
|
1504
|
+
const { frontmatter } = parseGenericDoc(fs8__namespace.readFileSync(file, "utf-8"));
|
|
1505
|
+
const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
1261
1506
|
const isTemplate = relPath.includes("templates/");
|
|
1262
1507
|
const id = frontmatter.id || "";
|
|
1263
1508
|
if (isRequirementArtifact(frontmatter, id)) {
|
|
@@ -1331,7 +1576,6 @@ var DEFAULT_EXCLUDES = [
|
|
|
1331
1576
|
"templates",
|
|
1332
1577
|
"fixtures"
|
|
1333
1578
|
];
|
|
1334
|
-
var TS_JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1335
1579
|
function createDefaultQualityPolicy() {
|
|
1336
1580
|
return {
|
|
1337
1581
|
max_cyclomatic: 10,
|
|
@@ -1412,27 +1656,38 @@ function applyOptionOverrides(policy, overrides) {
|
|
|
1412
1656
|
if (overrides.max_function_lines !== void 0) policy.max_function_lines = Number(overrides.max_function_lines);
|
|
1413
1657
|
if (overrides.enforce_mode !== void 0) policy.enforce_mode = overrides.enforce_mode;
|
|
1414
1658
|
}
|
|
1659
|
+
function parsePolicyYaml(content) {
|
|
1660
|
+
let doc;
|
|
1661
|
+
try {
|
|
1662
|
+
doc = yaml3__default.default.load(content);
|
|
1663
|
+
} catch (err) {
|
|
1664
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in quality-policy: ${err instanceof Error ? err.message : String(err)}`);
|
|
1665
|
+
}
|
|
1666
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
1667
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid quality policy structure: root must be a YAML mapping`);
|
|
1668
|
+
}
|
|
1669
|
+
return doc;
|
|
1670
|
+
}
|
|
1415
1671
|
function parseQualityPolicy(content, overrides) {
|
|
1416
1672
|
const policy = createDefaultQualityPolicy();
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
applyDocScope(policy, record);
|
|
1422
|
-
applyDocThresholds(policy, record);
|
|
1423
|
-
if (record.max_per_function !== void 0) policy.max_cyclomatic = Number(record.max_per_function);
|
|
1424
|
-
if (record.min_acceptable_score !== void 0) policy.min_maintainability = Number(record.min_acceptable_score);
|
|
1425
|
-
if (record.max_function_lines !== void 0) policy.max_function_lines = Number(record.max_function_lines);
|
|
1426
|
-
}
|
|
1427
|
-
} catch {
|
|
1673
|
+
if (!content || !content.trim()) {
|
|
1674
|
+
applyEnvThresholds(policy);
|
|
1675
|
+
applyOptionOverrides(policy, overrides);
|
|
1676
|
+
return policy;
|
|
1428
1677
|
}
|
|
1678
|
+
const record = parsePolicyYaml(content);
|
|
1679
|
+
applyDocScope(policy, record);
|
|
1680
|
+
applyDocThresholds(policy, record);
|
|
1681
|
+
if (record.max_per_function !== void 0) policy.max_cyclomatic = Number(record.max_per_function);
|
|
1682
|
+
if (record.min_acceptable_score !== void 0) policy.min_maintainability = Number(record.min_acceptable_score);
|
|
1683
|
+
if (record.max_function_lines !== void 0) policy.max_function_lines = Number(record.max_function_lines);
|
|
1429
1684
|
applyEnvThresholds(policy);
|
|
1430
1685
|
applyOptionOverrides(policy, overrides);
|
|
1431
1686
|
return policy;
|
|
1432
1687
|
}
|
|
1433
1688
|
function extractFunctions(content, filePath) {
|
|
1434
|
-
const
|
|
1435
|
-
return
|
|
1689
|
+
const report = orchestrateNativeAstAnalysis(content, filePath);
|
|
1690
|
+
return report.functions;
|
|
1436
1691
|
}
|
|
1437
1692
|
function extractFunctionsBraceLanguages(content, filePath) {
|
|
1438
1693
|
return extractFunctions(content, filePath);
|
|
@@ -1462,55 +1717,55 @@ function isExcludedFile(file, normalized, excludePatterns) {
|
|
|
1462
1717
|
);
|
|
1463
1718
|
}
|
|
1464
1719
|
function walkDir(dir, extensions = DEFAULT_EXTENSIONS, excludePatterns = DEFAULT_EXCLUDES) {
|
|
1465
|
-
if (!
|
|
1720
|
+
if (!fs8__namespace.existsSync(dir)) return [];
|
|
1466
1721
|
const results = [];
|
|
1467
|
-
for (const file of
|
|
1468
|
-
const fullPath =
|
|
1722
|
+
for (const file of fs8__namespace.readdirSync(dir).sort(canonicalSort)) {
|
|
1723
|
+
const fullPath = path5__namespace.join(dir, file);
|
|
1469
1724
|
const normalized = fullPath.replace(/\\/g, "/");
|
|
1470
1725
|
if (isExcludedFile(file, normalized, excludePatterns)) continue;
|
|
1471
|
-
const stat =
|
|
1726
|
+
const stat = fs8__namespace.statSync(fullPath);
|
|
1472
1727
|
if (stat.isDirectory()) {
|
|
1473
1728
|
results.push(...walkDir(fullPath, extensions, excludePatterns));
|
|
1474
|
-
} else if (extensions.includes(
|
|
1729
|
+
} else if (extensions.includes(path5__namespace.extname(file).toLowerCase())) {
|
|
1475
1730
|
results.push(fullPath);
|
|
1476
1731
|
}
|
|
1477
1732
|
}
|
|
1478
1733
|
return results;
|
|
1479
1734
|
}
|
|
1480
1735
|
function resolvePolicy(rootDir, options) {
|
|
1481
|
-
const policyFile = options.policyPath ||
|
|
1736
|
+
const policyFile = options.policyPath || path5__namespace.join(rootDir, "quality-policy.yaml");
|
|
1482
1737
|
if (options.policy) {
|
|
1483
1738
|
const policy = { ...options.policy };
|
|
1484
1739
|
applyOptionOverrides(policy, options.thresholds);
|
|
1485
1740
|
return policy;
|
|
1486
1741
|
}
|
|
1487
|
-
if (
|
|
1488
|
-
return parseQualityPolicy(
|
|
1742
|
+
if (fs8__namespace.existsSync(policyFile)) {
|
|
1743
|
+
return parseQualityPolicy(fs8__namespace.readFileSync(policyFile, "utf-8"), options.thresholds);
|
|
1489
1744
|
}
|
|
1490
1745
|
return parseQualityPolicy("", options.thresholds);
|
|
1491
1746
|
}
|
|
1492
1747
|
function isPathExcluded(targetPath, rootDir, excludePatterns) {
|
|
1493
|
-
const rel =
|
|
1494
|
-
const base =
|
|
1748
|
+
const rel = path5__namespace.relative(rootDir, targetPath).replace(/\\/g, "/");
|
|
1749
|
+
const base = path5__namespace.basename(targetPath);
|
|
1495
1750
|
return excludePatterns.some(
|
|
1496
1751
|
(pat) => base === pat || rel === pat || rel.startsWith(pat + "/") || rel.includes("/" + pat + "/") || rel.endsWith("/" + pat) || rel.includes(pat)
|
|
1497
1752
|
);
|
|
1498
1753
|
}
|
|
1499
1754
|
function scanContainerSubdir(containerDir, subdirName, targetDir, rootDir, policy, files) {
|
|
1500
1755
|
if (policy.exclude_patterns.includes(subdirName)) return;
|
|
1501
|
-
const subTarget =
|
|
1502
|
-
if (!
|
|
1756
|
+
const subTarget = path5__namespace.join(containerDir, subdirName, targetDir);
|
|
1757
|
+
if (!fs8__namespace.existsSync(subTarget) || !fs8__namespace.statSync(subTarget).isDirectory()) return;
|
|
1503
1758
|
if (isPathExcluded(subTarget, rootDir, policy.exclude_patterns)) return;
|
|
1504
1759
|
for (const f of walkDir(subTarget, policy.supported_extensions, policy.exclude_patterns)) {
|
|
1505
1760
|
files.add(f);
|
|
1506
1761
|
}
|
|
1507
1762
|
}
|
|
1508
1763
|
function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
|
|
1509
|
-
const containerDir =
|
|
1510
|
-
if (!
|
|
1764
|
+
const containerDir = path5__namespace.join(rootDir, container);
|
|
1765
|
+
if (!fs8__namespace.existsSync(containerDir) || !fs8__namespace.statSync(containerDir).isDirectory()) return;
|
|
1511
1766
|
if (isPathExcluded(containerDir, rootDir, policy.exclude_patterns)) return;
|
|
1512
1767
|
try {
|
|
1513
|
-
for (const subdir of
|
|
1768
|
+
for (const subdir of fs8__namespace.readdirSync(containerDir, { withFileTypes: true })) {
|
|
1514
1769
|
if (subdir.isDirectory()) {
|
|
1515
1770
|
scanContainerSubdir(containerDir, subdir.name, targetDir, rootDir, policy, files);
|
|
1516
1771
|
}
|
|
@@ -1519,13 +1774,14 @@ function scanMonorepoContainer(rootDir, container, targetDir, policy, files) {
|
|
|
1519
1774
|
}
|
|
1520
1775
|
}
|
|
1521
1776
|
function scanDirectTarget(rootDir, targetDir, policy, files) {
|
|
1522
|
-
const fullDir =
|
|
1523
|
-
if (
|
|
1777
|
+
const fullDir = path5__namespace.join(rootDir, targetDir);
|
|
1778
|
+
if (fs8__namespace.existsSync(fullDir) && !isPathExcluded(fullDir, rootDir, policy.exclude_patterns)) {
|
|
1524
1779
|
for (const f of walkDir(fullDir, policy.supported_extensions, policy.exclude_patterns)) {
|
|
1525
1780
|
files.add(f);
|
|
1526
1781
|
}
|
|
1527
1782
|
}
|
|
1528
1783
|
}
|
|
1784
|
+
var UNIT_TEST_SUFFIXES = [".spec.ts", ".test.ts", ".spec.js", ".test.js", "_test.py", "_test.go"];
|
|
1529
1785
|
function collectFilesToScan(rootDir, policy) {
|
|
1530
1786
|
const files = /* @__PURE__ */ new Set();
|
|
1531
1787
|
const monorepoContainers = ["packages", "apps", "libs", "modules", "services"];
|
|
@@ -1535,7 +1791,7 @@ function collectFilesToScan(rootDir, policy) {
|
|
|
1535
1791
|
scanMonorepoContainer(rootDir, container, targetDir, policy, files);
|
|
1536
1792
|
}
|
|
1537
1793
|
}
|
|
1538
|
-
return Array.from(files);
|
|
1794
|
+
return Array.from(files).filter((f) => !UNIT_TEST_SUFFIXES.some((s) => f.endsWith(s)));
|
|
1539
1795
|
}
|
|
1540
1796
|
function evaluateViolations(fn, policy) {
|
|
1541
1797
|
const violations = [];
|
|
@@ -1557,38 +1813,67 @@ function evaluateViolations(fn, policy) {
|
|
|
1557
1813
|
return violations;
|
|
1558
1814
|
}
|
|
1559
1815
|
function evaluateFileQuality(file, rootDir, policy) {
|
|
1560
|
-
const content =
|
|
1816
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
1561
1817
|
const fns = extractFunctions(content, file);
|
|
1562
1818
|
return fns.map((fn) => {
|
|
1563
1819
|
const violations = evaluateViolations(fn, policy);
|
|
1564
1820
|
return {
|
|
1565
1821
|
...fn,
|
|
1566
|
-
relPath:
|
|
1822
|
+
relPath: path5__namespace.relative(rootDir, file).replace(/\\/g, "/"),
|
|
1567
1823
|
status: violations.length === 0 ? "PASS" : "FAIL",
|
|
1568
1824
|
violations
|
|
1569
1825
|
};
|
|
1570
1826
|
});
|
|
1571
1827
|
}
|
|
1572
|
-
var UNIT_TEST_SUFFIXES = [".spec.ts", ".test.ts", ".spec.js", ".test.js", "_test.py", "_test.go"];
|
|
1573
1828
|
function collectUnitTestCorpus(rootDir) {
|
|
1574
1829
|
const allCodeFiles = walkDir(rootDir, DEFAULT_EXTENSIONS, DEFAULT_EXCLUDES);
|
|
1575
1830
|
const chunks = [];
|
|
1576
1831
|
for (const tf of allCodeFiles) {
|
|
1577
1832
|
if (!UNIT_TEST_SUFFIXES.some((s) => tf.endsWith(s))) continue;
|
|
1578
1833
|
try {
|
|
1579
|
-
chunks.push(
|
|
1834
|
+
chunks.push(fs8__namespace.readFileSync(tf, "utf-8"));
|
|
1580
1835
|
} catch {
|
|
1581
1836
|
}
|
|
1582
1837
|
}
|
|
1583
1838
|
return chunks.join("\n");
|
|
1584
1839
|
}
|
|
1840
|
+
function sanitizeTestCorpus(corpus, fnName) {
|
|
1841
|
+
let cleaned = corpus.replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
1842
|
+
cleaned = cleaned.replace(/(?:\/\/|#)[^\r\n]*/g, " ");
|
|
1843
|
+
cleaned = cleaned.replace(/(['"`])((?:\\.|(?!\1)[^\\])*)\1/g, (match, quote, inner) => {
|
|
1844
|
+
const trimmed = inner.trim();
|
|
1845
|
+
if (trimmed === fnName || trimmed === `#${fnName}` || trimmed === `.${fnName}` || trimmed === `${fnName}()`) {
|
|
1846
|
+
return match;
|
|
1847
|
+
}
|
|
1848
|
+
return `${quote}${quote}`;
|
|
1849
|
+
});
|
|
1850
|
+
return cleaned;
|
|
1851
|
+
}
|
|
1852
|
+
function buildTestReferencePatterns(escaped) {
|
|
1853
|
+
return [
|
|
1854
|
+
new RegExp(`(?:\\.|\\b)${escaped}\\s*\\(`, "m"),
|
|
1855
|
+
new RegExp(`\\b(?:import|require)\\b[^;\\n]*\\b${escaped}\\b`, "m"),
|
|
1856
|
+
new RegExp(`(?:\\bexpect|\\bassert|\\bshould)\\s*\\([^;\\n]*\\b${escaped}\\b`, "m"),
|
|
1857
|
+
new RegExp("(?:\\bspyOn|\\bmock)\\s*\\([^;\\n]*['\"`]" + escaped + "['\"`]", "m"),
|
|
1858
|
+
new RegExp(`\\b${escaped}\\s*\\.`, "m"),
|
|
1859
|
+
new RegExp("\\b(?:describe|it|test|context)\\s*\\(\\s*['\"`](?:#|\\.)?" + escaped + "['\"`]", "m"),
|
|
1860
|
+
new RegExp(`\\btest_?${escaped}\\b`, "im")
|
|
1861
|
+
];
|
|
1862
|
+
}
|
|
1863
|
+
function isFunctionReferencedInTests(fnName, rawCorpus) {
|
|
1864
|
+
if (!fnName || fnName === "anonymous" || fnName === "constructor") return true;
|
|
1865
|
+
const corpus = sanitizeTestCorpus(rawCorpus, fnName);
|
|
1866
|
+
const escaped = fnName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1867
|
+
const patterns = buildTestReferencePatterns(escaped);
|
|
1868
|
+
return patterns.some((p) => p.test(corpus));
|
|
1869
|
+
}
|
|
1585
1870
|
function auditUnitTestsPerFunction(results, rootDir) {
|
|
1586
1871
|
const corpus = collectUnitTestCorpus(rootDir);
|
|
1587
1872
|
const untested = [];
|
|
1588
1873
|
for (const r of results) {
|
|
1589
1874
|
const fnName = r.functionName.includes(".") ? r.functionName.split(".").pop() : r.functionName;
|
|
1590
1875
|
if (!fnName || fnName === "anonymous" || fnName === "constructor") continue;
|
|
1591
|
-
if (!
|
|
1876
|
+
if (!isFunctionReferencedInTests(fnName, corpus)) {
|
|
1592
1877
|
untested.push({ file: r.relPath, functionName: r.functionName });
|
|
1593
1878
|
r.violations.push(`Missing unit test for function '${r.functionName}'`);
|
|
1594
1879
|
r.status = "FAIL";
|
|
@@ -1598,11 +1883,11 @@ function auditUnitTestsPerFunction(results, rootDir) {
|
|
|
1598
1883
|
}
|
|
1599
1884
|
function isVitestMidFlightCoverage(rel, fullPath) {
|
|
1600
1885
|
if (!process.env.VITEST || rel !== "coverage/coverage-summary.json") return false;
|
|
1601
|
-
return
|
|
1886
|
+
return path5__namespace.resolve(fullPath) === path5__namespace.resolve(process.cwd(), rel);
|
|
1602
1887
|
}
|
|
1603
1888
|
function parseCoverageSummaryFile(filePath, source) {
|
|
1604
1889
|
try {
|
|
1605
|
-
const raw = JSON.parse(
|
|
1890
|
+
const raw = JSON.parse(fs8__namespace.readFileSync(filePath, "utf-8"));
|
|
1606
1891
|
const total = raw.total;
|
|
1607
1892
|
if (!total) return null;
|
|
1608
1893
|
return {
|
|
@@ -1617,8 +1902,8 @@ function parseCoverageSummaryFile(filePath, source) {
|
|
|
1617
1902
|
}
|
|
1618
1903
|
function readSummaryCoverageJson(rootDir) {
|
|
1619
1904
|
for (const rel of ["coverage/coverage-summary.json", "reports/coverage-summary.json"]) {
|
|
1620
|
-
const full =
|
|
1621
|
-
if (!
|
|
1905
|
+
const full = path5__namespace.join(rootDir, rel);
|
|
1906
|
+
if (!fs8__namespace.existsSync(full) || isVitestMidFlightCoverage(rel, full)) continue;
|
|
1622
1907
|
const summary = parseCoverageSummaryFile(full, rel);
|
|
1623
1908
|
if (summary) return summary;
|
|
1624
1909
|
}
|
|
@@ -1642,7 +1927,7 @@ function evaluateCoverageThresholds(rootDir, policy, requireReport) {
|
|
|
1642
1927
|
}
|
|
1643
1928
|
function computeViolationSignature(relPath, fnName, metric) {
|
|
1644
1929
|
const normPath = relPath.replace(/\\/g, "/");
|
|
1645
|
-
return
|
|
1930
|
+
return crypto3__namespace.createHash("sha256").update(normPath + ":" + fnName + ":" + metric).digest("hex");
|
|
1646
1931
|
}
|
|
1647
1932
|
function classifyMetricFromMessage(msg, fn) {
|
|
1648
1933
|
if (msg.startsWith("Cyclomatic Complexity")) {
|
|
@@ -1671,8 +1956,8 @@ function isMetricWorsened(baselineEntry, current) {
|
|
|
1671
1956
|
}
|
|
1672
1957
|
function loadQualityBaseline(baselineFile) {
|
|
1673
1958
|
try {
|
|
1674
|
-
if (!
|
|
1675
|
-
const content =
|
|
1959
|
+
if (!fs8__namespace.existsSync(baselineFile)) return null;
|
|
1960
|
+
const content = fs8__namespace.readFileSync(baselineFile, "utf-8");
|
|
1676
1961
|
return JSON.parse(content);
|
|
1677
1962
|
} catch {
|
|
1678
1963
|
return null;
|
|
@@ -1681,11 +1966,11 @@ function loadQualityBaseline(baselineFile) {
|
|
|
1681
1966
|
function writeQualityBaseline(baselineFile, entries) {
|
|
1682
1967
|
const baseline = {
|
|
1683
1968
|
version: "1.0",
|
|
1684
|
-
generatedAt: (
|
|
1969
|
+
generatedAt: resolveDeterministicDate(path5__namespace.dirname(baselineFile)).toISOString(),
|
|
1685
1970
|
totalViolations: entries.length,
|
|
1686
1971
|
violations: entries
|
|
1687
1972
|
};
|
|
1688
|
-
|
|
1973
|
+
fs8__namespace.writeFileSync(baselineFile, JSON.stringify(baseline, null, 2) + "\n", "utf-8");
|
|
1689
1974
|
return baseline;
|
|
1690
1975
|
}
|
|
1691
1976
|
function extractBaselineEntriesFromResults(results) {
|
|
@@ -1744,7 +2029,7 @@ function applyRatchetMode(results, baseline) {
|
|
|
1744
2029
|
function resolveBaselineFilePath(rootDir, options) {
|
|
1745
2030
|
if (options.baselinePath) return options.baselinePath;
|
|
1746
2031
|
if (typeof options.baseline === "string") return options.baseline;
|
|
1747
|
-
return
|
|
2032
|
+
return path5__namespace.join(rootDir, "quality-baseline.json");
|
|
1748
2033
|
}
|
|
1749
2034
|
function isRatchetModeActive(policy, options, baselineExists) {
|
|
1750
2035
|
if (options.ratchet !== void 0) return options.ratchet;
|
|
@@ -1752,7 +2037,7 @@ function isRatchetModeActive(policy, options, baselineExists) {
|
|
|
1752
2037
|
return Boolean(policy.ratchet_mode || options.baseline && baselineExists);
|
|
1753
2038
|
}
|
|
1754
2039
|
function evaluateRatchetIfActive(results, baselineFile, isActive) {
|
|
1755
|
-
if (!isActive || !
|
|
2040
|
+
if (!isActive || !fs8__namespace.existsSync(baselineFile)) {
|
|
1756
2041
|
return { toleratedViolationsCount: 0, newViolationsCount: 0, baselineViolationsCount: 0 };
|
|
1757
2042
|
}
|
|
1758
2043
|
const baseline = loadQualityBaseline(baselineFile);
|
|
@@ -1811,7 +2096,7 @@ function verifyQualityGate(options = {}) {
|
|
|
1811
2096
|
baselineViolationsCount: entries.length
|
|
1812
2097
|
});
|
|
1813
2098
|
}
|
|
1814
|
-
const baselineExists =
|
|
2099
|
+
const baselineExists = fs8__namespace.existsSync(baselineFile);
|
|
1815
2100
|
const isRatchet = isRatchetModeActive(policy, options, baselineExists);
|
|
1816
2101
|
const ratchetEval = evaluateRatchetIfActive(results, baselineFile, isRatchet);
|
|
1817
2102
|
return buildQualityGateResult(results, allFiles.length, policy, coverageViolations, untestedFunctions, {
|
|
@@ -1829,9 +2114,9 @@ function normalizeHandoffPayload(handoff, changeId) {
|
|
|
1829
2114
|
};
|
|
1830
2115
|
}
|
|
1831
2116
|
function parseChangeProfile(specFile) {
|
|
1832
|
-
if (!
|
|
2117
|
+
if (!fs8__namespace.existsSync(specFile)) return "standard";
|
|
1833
2118
|
try {
|
|
1834
|
-
const rawSpec =
|
|
2119
|
+
const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
|
|
1835
2120
|
const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1836
2121
|
if (match) {
|
|
1837
2122
|
const parsedFm = yaml3__default.default.load(match[1]);
|
|
@@ -1853,12 +2138,12 @@ function checkSpecGherkinFormat(parsedFm, rawSpec, changeWorkspaceDir, errors) {
|
|
|
1853
2138
|
}
|
|
1854
2139
|
}
|
|
1855
2140
|
function validateSpecFileContent(specFile, changeWorkspaceDir, errors) {
|
|
1856
|
-
if (!
|
|
2141
|
+
if (!fs8__namespace.existsSync(specFile)) {
|
|
1857
2142
|
errors.push(`Falta el archivo obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
|
|
1858
2143
|
return;
|
|
1859
2144
|
}
|
|
1860
2145
|
try {
|
|
1861
|
-
const rawSpec =
|
|
2146
|
+
const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
|
|
1862
2147
|
const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1863
2148
|
if (match) {
|
|
1864
2149
|
const parsedFm = yaml3__default.default.load(match[1]);
|
|
@@ -1877,19 +2162,19 @@ function validateHandoffSidecar(handoff, changeWorkspaceDir, errors) {
|
|
|
1877
2162
|
if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
|
|
1878
2163
|
errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
|
|
1879
2164
|
}
|
|
1880
|
-
return
|
|
2165
|
+
return path5__namespace.join(changeWorkspaceDir, "handoff.yaml");
|
|
1881
2166
|
}
|
|
1882
2167
|
function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
|
|
1883
|
-
const proposalFile =
|
|
1884
|
-
const tasksFile =
|
|
2168
|
+
const proposalFile = path5__namespace.join(changeWorkspaceDir, "proposal.md");
|
|
2169
|
+
const tasksFile = path5__namespace.join(changeWorkspaceDir, "tasks.md");
|
|
1885
2170
|
if (profile === "patch") {
|
|
1886
2171
|
const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
|
|
1887
|
-
return { handoff: handoff2, sidecarFile: handoff2 ?
|
|
2172
|
+
return { handoff: handoff2, sidecarFile: handoff2 ? path5__namespace.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
|
|
1888
2173
|
}
|
|
1889
|
-
if (!
|
|
2174
|
+
if (!fs8__namespace.existsSync(proposalFile)) {
|
|
1890
2175
|
warnings.push(`Falta 'proposal.md' en el espacio de trabajo OpenSpec.`);
|
|
1891
2176
|
}
|
|
1892
|
-
if (!
|
|
2177
|
+
if (!fs8__namespace.existsSync(tasksFile)) {
|
|
1893
2178
|
errors.push(`Falta el archivo obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
|
|
1894
2179
|
}
|
|
1895
2180
|
const handoff = adapter.loadSidecar(changeWorkspaceDir);
|
|
@@ -1897,20 +2182,20 @@ function validateOpenSpecStructure(profile, changeWorkspaceDir, adapter, errors,
|
|
|
1897
2182
|
return { handoff, sidecarFile };
|
|
1898
2183
|
}
|
|
1899
2184
|
function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
|
|
1900
|
-
const changeId =
|
|
1901
|
-
const proposalFile =
|
|
1902
|
-
const specFile =
|
|
1903
|
-
const designFile =
|
|
1904
|
-
const tasksFile =
|
|
2185
|
+
const changeId = path5__namespace.basename(changeWorkspaceDir);
|
|
2186
|
+
const proposalFile = path5__namespace.join(changeWorkspaceDir, "proposal.md");
|
|
2187
|
+
const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
|
|
2188
|
+
const designFile = path5__namespace.join(changeWorkspaceDir, "design.md");
|
|
2189
|
+
const tasksFile = path5__namespace.join(changeWorkspaceDir, "tasks.md");
|
|
1905
2190
|
return {
|
|
1906
2191
|
framework: "openspec",
|
|
1907
2192
|
changeId,
|
|
1908
2193
|
changeDir: changeWorkspaceDir,
|
|
1909
2194
|
profile,
|
|
1910
|
-
proposalFile:
|
|
1911
|
-
specFile:
|
|
1912
|
-
designFile:
|
|
1913
|
-
tasksFile:
|
|
2195
|
+
proposalFile: fs8__namespace.existsSync(proposalFile) ? proposalFile : void 0,
|
|
2196
|
+
specFile: fs8__namespace.existsSync(specFile) ? specFile : void 0,
|
|
2197
|
+
designFile: fs8__namespace.existsSync(designFile) ? designFile : void 0,
|
|
2198
|
+
tasksFile: fs8__namespace.existsSync(tasksFile) ? tasksFile : void 0,
|
|
1914
2199
|
sidecarFile,
|
|
1915
2200
|
handoff: handoff || void 0
|
|
1916
2201
|
};
|
|
@@ -1918,28 +2203,28 @@ function buildOpenSpecWorkspaceInfo(changeWorkspaceDir, profile, handoff, sideca
|
|
|
1918
2203
|
var OpenSpecAdapter = class {
|
|
1919
2204
|
framework = "openspec";
|
|
1920
2205
|
getChangeWorkspaceDir(rootDir, changeId) {
|
|
1921
|
-
const openspecDir =
|
|
1922
|
-
if (
|
|
1923
|
-
const activeChangeDir =
|
|
1924
|
-
if (
|
|
1925
|
-
const completedChangeDir =
|
|
1926
|
-
if (
|
|
2206
|
+
const openspecDir = path5__namespace.join(rootDir, "openspec", "changes", changeId);
|
|
2207
|
+
if (fs8__namespace.existsSync(openspecDir)) return openspecDir;
|
|
2208
|
+
const activeChangeDir = path5__namespace.join(rootDir, "specs", "changes", "active", changeId);
|
|
2209
|
+
if (fs8__namespace.existsSync(activeChangeDir)) return activeChangeDir;
|
|
2210
|
+
const completedChangeDir = path5__namespace.join(rootDir, "specs", "changes", "completed", changeId);
|
|
2211
|
+
if (fs8__namespace.existsSync(completedChangeDir)) return completedChangeDir;
|
|
1927
2212
|
return activeChangeDir;
|
|
1928
2213
|
}
|
|
1929
2214
|
depositSidecar(options) {
|
|
1930
2215
|
const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
|
|
1931
|
-
if (!
|
|
1932
|
-
|
|
2216
|
+
if (!fs8__namespace.existsSync(changeDir)) {
|
|
2217
|
+
fs8__namespace.mkdirSync(changeDir, { recursive: true });
|
|
1933
2218
|
}
|
|
1934
2219
|
const handoff = normalizeHandoffPayload(options.handoff, options.changeId);
|
|
1935
2220
|
const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
|
|
1936
|
-
const filePath =
|
|
2221
|
+
const filePath = path5__namespace.join(changeDir, filename);
|
|
1937
2222
|
const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3__default.default.dump(handoff, { indent: 2, lineWidth: -1 });
|
|
1938
|
-
|
|
2223
|
+
fs8__namespace.writeFileSync(filePath, content, "utf-8");
|
|
1939
2224
|
return filePath;
|
|
1940
2225
|
}
|
|
1941
2226
|
loadSidecar(changeWorkspaceDir) {
|
|
1942
|
-
if (!
|
|
2227
|
+
if (!fs8__namespace.existsSync(changeWorkspaceDir)) return null;
|
|
1943
2228
|
const candidateFiles = [
|
|
1944
2229
|
"handoff.yaml",
|
|
1945
2230
|
"handoff.yml",
|
|
@@ -1950,10 +2235,10 @@ var OpenSpecAdapter = class {
|
|
|
1950
2235
|
"product-handoff.json"
|
|
1951
2236
|
];
|
|
1952
2237
|
for (const file of candidateFiles) {
|
|
1953
|
-
const fullPath =
|
|
1954
|
-
if (
|
|
2238
|
+
const fullPath = path5__namespace.join(changeWorkspaceDir, file);
|
|
2239
|
+
if (fs8__namespace.existsSync(fullPath)) {
|
|
1955
2240
|
try {
|
|
1956
|
-
const raw =
|
|
2241
|
+
const raw = fs8__namespace.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
|
|
1957
2242
|
const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3__default.default.load(raw);
|
|
1958
2243
|
if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
|
|
1959
2244
|
return parsed;
|
|
@@ -1965,7 +2250,7 @@ var OpenSpecAdapter = class {
|
|
|
1965
2250
|
return null;
|
|
1966
2251
|
}
|
|
1967
2252
|
validateWorkspace(changeWorkspaceDir) {
|
|
1968
|
-
if (!
|
|
2253
|
+
if (!fs8__namespace.existsSync(changeWorkspaceDir)) {
|
|
1969
2254
|
return {
|
|
1970
2255
|
valid: false,
|
|
1971
2256
|
errors: [`El directorio del cambio OpenSpec no existe: ${changeWorkspaceDir}`],
|
|
@@ -1974,7 +2259,7 @@ var OpenSpecAdapter = class {
|
|
|
1974
2259
|
}
|
|
1975
2260
|
const errors = [];
|
|
1976
2261
|
const warnings = [];
|
|
1977
|
-
const specFile =
|
|
2262
|
+
const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
|
|
1978
2263
|
const profile = parseChangeProfile(specFile);
|
|
1979
2264
|
validateSpecFileContent(specFile, changeWorkspaceDir, errors);
|
|
1980
2265
|
const { handoff, sidecarFile } = validateOpenSpecStructure(profile, changeWorkspaceDir, this, errors, warnings);
|
|
@@ -1988,9 +2273,9 @@ var OpenSpecAdapter = class {
|
|
|
1988
2273
|
}
|
|
1989
2274
|
};
|
|
1990
2275
|
function parseChangeProfile2(specFile) {
|
|
1991
|
-
if (!
|
|
2276
|
+
if (!fs8__namespace.existsSync(specFile)) return "standard";
|
|
1992
2277
|
try {
|
|
1993
|
-
const rawSpec =
|
|
2278
|
+
const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
|
|
1994
2279
|
const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1995
2280
|
if (match) {
|
|
1996
2281
|
const parsedFm = yaml3__default.default.load(match[1]);
|
|
@@ -2012,12 +2297,12 @@ function checkSpecGherkinFormat2(parsedFm, rawSpec, changeWorkspaceDir, errors)
|
|
|
2012
2297
|
}
|
|
2013
2298
|
}
|
|
2014
2299
|
function validateSpecFileContent2(specFile, changeWorkspaceDir, errors) {
|
|
2015
|
-
if (!
|
|
2300
|
+
if (!fs8__namespace.existsSync(specFile)) {
|
|
2016
2301
|
errors.push(`Falta el archivo de especificaci\xF3n obligatorio 'spec.md' en ${changeWorkspaceDir}.`);
|
|
2017
2302
|
return;
|
|
2018
2303
|
}
|
|
2019
2304
|
try {
|
|
2020
|
-
const rawSpec =
|
|
2305
|
+
const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
|
|
2021
2306
|
const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
2022
2307
|
if (match) {
|
|
2023
2308
|
const parsedFm = yaml3__default.default.load(match[1]);
|
|
@@ -2036,19 +2321,19 @@ function validateHandoffSidecar2(handoff, changeWorkspaceDir, errors) {
|
|
|
2036
2321
|
if (!handoff.subgraph || !Array.isArray(handoff.subgraph.requirements)) {
|
|
2037
2322
|
errors.push(`El sidecar '${handoff.id}' no define un subgrafo de requerimientos v\xE1lido.`);
|
|
2038
2323
|
}
|
|
2039
|
-
return
|
|
2324
|
+
return path5__namespace.join(changeWorkspaceDir, "handoff.yaml");
|
|
2040
2325
|
}
|
|
2041
2326
|
function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors, warnings) {
|
|
2042
|
-
const planFile =
|
|
2043
|
-
const tasksFile =
|
|
2327
|
+
const planFile = path5__namespace.join(changeWorkspaceDir, "plan.md");
|
|
2328
|
+
const tasksFile = path5__namespace.join(changeWorkspaceDir, "tasks.md");
|
|
2044
2329
|
if (profile === "patch") {
|
|
2045
2330
|
const handoff2 = adapter.loadSidecar(changeWorkspaceDir);
|
|
2046
|
-
return { handoff: handoff2, sidecarFile: handoff2 ?
|
|
2331
|
+
return { handoff: handoff2, sidecarFile: handoff2 ? path5__namespace.join(changeWorkspaceDir, "handoff.yaml") : void 0 };
|
|
2047
2332
|
}
|
|
2048
|
-
if (!
|
|
2333
|
+
if (!fs8__namespace.existsSync(tasksFile)) {
|
|
2049
2334
|
errors.push(`Falta el archivo de tareas obligatorio 'tasks.md' en ${changeWorkspaceDir}.`);
|
|
2050
2335
|
}
|
|
2051
|
-
if (!
|
|
2336
|
+
if (!fs8__namespace.existsSync(planFile)) {
|
|
2052
2337
|
warnings.push(`Se recomienda incluir 'plan.md' en el espacio de trabajo Spec Kit.`);
|
|
2053
2338
|
}
|
|
2054
2339
|
const handoff = adapter.loadSidecar(changeWorkspaceDir);
|
|
@@ -2056,18 +2341,18 @@ function validateSpecKitStructure(profile, changeWorkspaceDir, adapter, errors,
|
|
|
2056
2341
|
return { handoff, sidecarFile };
|
|
2057
2342
|
}
|
|
2058
2343
|
function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecarFile) {
|
|
2059
|
-
const changeId =
|
|
2060
|
-
const specFile =
|
|
2061
|
-
const planFile =
|
|
2062
|
-
const tasksFile =
|
|
2344
|
+
const changeId = path5__namespace.basename(changeWorkspaceDir);
|
|
2345
|
+
const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
|
|
2346
|
+
const planFile = path5__namespace.join(changeWorkspaceDir, "plan.md");
|
|
2347
|
+
const tasksFile = path5__namespace.join(changeWorkspaceDir, "tasks.md");
|
|
2063
2348
|
return {
|
|
2064
2349
|
framework: "speckit",
|
|
2065
2350
|
changeId,
|
|
2066
2351
|
changeDir: changeWorkspaceDir,
|
|
2067
2352
|
profile,
|
|
2068
|
-
specFile:
|
|
2069
|
-
designFile:
|
|
2070
|
-
tasksFile:
|
|
2353
|
+
specFile: fs8__namespace.existsSync(specFile) ? specFile : void 0,
|
|
2354
|
+
designFile: fs8__namespace.existsSync(planFile) ? planFile : void 0,
|
|
2355
|
+
tasksFile: fs8__namespace.existsSync(tasksFile) ? tasksFile : void 0,
|
|
2071
2356
|
sidecarFile,
|
|
2072
2357
|
handoff: handoff || void 0
|
|
2073
2358
|
};
|
|
@@ -2075,18 +2360,18 @@ function buildSpecKitWorkspaceInfo(changeWorkspaceDir, profile, handoff, sidecar
|
|
|
2075
2360
|
var SpecKitAdapter = class {
|
|
2076
2361
|
framework = "speckit";
|
|
2077
2362
|
getChangeWorkspaceDir(rootDir, changeId) {
|
|
2078
|
-
const standardDir =
|
|
2079
|
-
if (
|
|
2080
|
-
const activeChangeDir =
|
|
2081
|
-
if (
|
|
2082
|
-
const completedChangeDir =
|
|
2083
|
-
if (
|
|
2363
|
+
const standardDir = path5__namespace.join(rootDir, "specs", changeId);
|
|
2364
|
+
if (fs8__namespace.existsSync(standardDir)) return standardDir;
|
|
2365
|
+
const activeChangeDir = path5__namespace.join(rootDir, "specs", "changes", "active", changeId);
|
|
2366
|
+
if (fs8__namespace.existsSync(activeChangeDir)) return activeChangeDir;
|
|
2367
|
+
const completedChangeDir = path5__namespace.join(rootDir, "specs", "changes", "completed", changeId);
|
|
2368
|
+
if (fs8__namespace.existsSync(completedChangeDir)) return completedChangeDir;
|
|
2084
2369
|
return standardDir;
|
|
2085
2370
|
}
|
|
2086
2371
|
depositSidecar(options) {
|
|
2087
2372
|
const changeDir = this.getChangeWorkspaceDir(options.rootDir, options.changeId);
|
|
2088
|
-
if (!
|
|
2089
|
-
|
|
2373
|
+
if (!fs8__namespace.existsSync(changeDir)) {
|
|
2374
|
+
fs8__namespace.mkdirSync(changeDir, { recursive: true });
|
|
2090
2375
|
}
|
|
2091
2376
|
const handoff = { ...options.handoff };
|
|
2092
2377
|
if (!handoff.id.startsWith("HOF-")) {
|
|
@@ -2099,20 +2384,20 @@ var SpecKitAdapter = class {
|
|
|
2099
2384
|
handoff.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2100
2385
|
}
|
|
2101
2386
|
const filename = options.format === "json" ? "handoff.json" : "handoff.yaml";
|
|
2102
|
-
const filePath =
|
|
2387
|
+
const filePath = path5__namespace.join(changeDir, filename);
|
|
2103
2388
|
const content = options.format === "json" ? JSON.stringify(handoff, null, 2) : yaml3__default.default.dump(handoff, { indent: 2, lineWidth: -1 });
|
|
2104
|
-
|
|
2105
|
-
const specifyMemoryDir =
|
|
2106
|
-
if (
|
|
2107
|
-
if (!
|
|
2108
|
-
|
|
2389
|
+
fs8__namespace.writeFileSync(filePath, content, "utf-8");
|
|
2390
|
+
const specifyMemoryDir = path5__namespace.join(options.rootDir, ".specify", "memory");
|
|
2391
|
+
if (fs8__namespace.existsSync(path5__namespace.join(options.rootDir, ".specify"))) {
|
|
2392
|
+
if (!fs8__namespace.existsSync(specifyMemoryDir)) {
|
|
2393
|
+
fs8__namespace.mkdirSync(specifyMemoryDir, { recursive: true });
|
|
2109
2394
|
}
|
|
2110
|
-
|
|
2395
|
+
fs8__namespace.writeFileSync(path5__namespace.join(specifyMemoryDir, `${options.changeId}.handoff.yaml`), content, "utf-8");
|
|
2111
2396
|
}
|
|
2112
2397
|
return filePath;
|
|
2113
2398
|
}
|
|
2114
2399
|
loadSidecar(changeWorkspaceDir) {
|
|
2115
|
-
if (!
|
|
2400
|
+
if (!fs8__namespace.existsSync(changeWorkspaceDir)) return null;
|
|
2116
2401
|
const candidateFiles = [
|
|
2117
2402
|
"handoff.yaml",
|
|
2118
2403
|
"handoff.yml",
|
|
@@ -2122,10 +2407,10 @@ var SpecKitAdapter = class {
|
|
|
2122
2407
|
"handoff.json"
|
|
2123
2408
|
];
|
|
2124
2409
|
for (const file of candidateFiles) {
|
|
2125
|
-
const fullPath =
|
|
2126
|
-
if (
|
|
2410
|
+
const fullPath = path5__namespace.join(changeWorkspaceDir, file);
|
|
2411
|
+
if (fs8__namespace.existsSync(fullPath)) {
|
|
2127
2412
|
try {
|
|
2128
|
-
const raw =
|
|
2413
|
+
const raw = fs8__namespace.readFileSync(fullPath, "utf-8").replace(/^\uFEFF/, "");
|
|
2129
2414
|
const parsed = file.endsWith(".json") ? JSON.parse(raw) : yaml3__default.default.load(raw);
|
|
2130
2415
|
if (parsed && typeof parsed === "object" && parsed.id && String(parsed.id).startsWith("HOF-")) {
|
|
2131
2416
|
return parsed;
|
|
@@ -2137,7 +2422,7 @@ var SpecKitAdapter = class {
|
|
|
2137
2422
|
return null;
|
|
2138
2423
|
}
|
|
2139
2424
|
validateWorkspace(changeWorkspaceDir) {
|
|
2140
|
-
if (!
|
|
2425
|
+
if (!fs8__namespace.existsSync(changeWorkspaceDir)) {
|
|
2141
2426
|
return {
|
|
2142
2427
|
valid: false,
|
|
2143
2428
|
errors: [`El directorio del cambio Spec Kit no existe: ${changeWorkspaceDir}`],
|
|
@@ -2146,7 +2431,7 @@ var SpecKitAdapter = class {
|
|
|
2146
2431
|
}
|
|
2147
2432
|
const errors = [];
|
|
2148
2433
|
const warnings = [];
|
|
2149
|
-
const specFile =
|
|
2434
|
+
const specFile = path5__namespace.join(changeWorkspaceDir, "spec.md");
|
|
2150
2435
|
const profile = parseChangeProfile2(specFile);
|
|
2151
2436
|
validateSpecFileContent2(specFile, changeWorkspaceDir, errors);
|
|
2152
2437
|
const { handoff, sidecarFile } = validateSpecKitStructure(profile, changeWorkspaceDir, this, errors, warnings);
|
|
@@ -2318,27 +2603,27 @@ ${frontmatter}---
|
|
|
2318
2603
|
${body}`;
|
|
2319
2604
|
}
|
|
2320
2605
|
function materializeCanonicalArtifact(rootDir, item, changeId, author, date) {
|
|
2321
|
-
const targetPath =
|
|
2322
|
-
if (
|
|
2323
|
-
|
|
2606
|
+
const targetPath = path5__namespace.join(rootDir, item.targetDir, `${item.id}.md`);
|
|
2607
|
+
if (fs8__namespace.existsSync(targetPath)) return targetPath;
|
|
2608
|
+
fs8__namespace.mkdirSync(path5__namespace.dirname(targetPath), { recursive: true });
|
|
2324
2609
|
const initialContent = buildCanonicalInitialContent(item, changeId, author, date);
|
|
2325
|
-
|
|
2610
|
+
fs8__namespace.writeFileSync(targetPath, initialContent, "utf-8");
|
|
2326
2611
|
return targetPath;
|
|
2327
2612
|
}
|
|
2328
2613
|
function findChangeWorkspaceDir(rootDir, changeId) {
|
|
2329
|
-
const activeDir =
|
|
2330
|
-
if (
|
|
2331
|
-
const completedDir =
|
|
2332
|
-
if (
|
|
2333
|
-
const openspecActive =
|
|
2334
|
-
if (
|
|
2614
|
+
const activeDir = path5__namespace.join(rootDir, "specs", "changes", "active", changeId);
|
|
2615
|
+
if (fs8__namespace.existsSync(activeDir)) return activeDir;
|
|
2616
|
+
const completedDir = path5__namespace.join(rootDir, "specs", "changes", "completed", changeId);
|
|
2617
|
+
if (fs8__namespace.existsSync(completedDir)) return completedDir;
|
|
2618
|
+
const openspecActive = path5__namespace.join(rootDir, "openspec", "changes", changeId);
|
|
2619
|
+
if (fs8__namespace.existsSync(openspecActive)) return openspecActive;
|
|
2335
2620
|
return null;
|
|
2336
2621
|
}
|
|
2337
2622
|
function checkTasksExecution(changeDir, changeId) {
|
|
2338
|
-
const tasksFile =
|
|
2623
|
+
const tasksFile = path5__namespace.join(changeDir, "tasks.md");
|
|
2339
2624
|
const allTasks = [];
|
|
2340
|
-
if (
|
|
2341
|
-
allTasks.push(...parseTasksFromContent(
|
|
2625
|
+
if (fs8__namespace.existsSync(tasksFile)) {
|
|
2626
|
+
allTasks.push(...parseTasksFromContent(fs8__namespace.readFileSync(tasksFile, "utf-8")));
|
|
2342
2627
|
}
|
|
2343
2628
|
const completedTasks = allTasks.filter((t) => t.status === "COMPLETED").map((t) => t.id);
|
|
2344
2629
|
const pendingTasks = allTasks.filter((t) => t.status !== "COMPLETED").map((t) => t.id);
|
|
@@ -2370,9 +2655,9 @@ function buildSubgraphFromProposalIds(ids) {
|
|
|
2370
2655
|
function resolveChangeHandoff(changeDir, changeId) {
|
|
2371
2656
|
const sidecar = loadProductHandoffSidecar(changeDir);
|
|
2372
2657
|
if (sidecar) return sidecar;
|
|
2373
|
-
const proposalFile =
|
|
2374
|
-
if (!
|
|
2375
|
-
const propContent =
|
|
2658
|
+
const proposalFile = path5__namespace.join(changeDir, "proposal.md");
|
|
2659
|
+
if (!fs8__namespace.existsSync(proposalFile)) return null;
|
|
2660
|
+
const propContent = fs8__namespace.readFileSync(proposalFile, "utf-8");
|
|
2376
2661
|
const { frontmatter } = parseFrontmatter(propContent);
|
|
2377
2662
|
const citations = Array.isArray(frontmatter.citations) ? frontmatter.citations : [];
|
|
2378
2663
|
const ids = citations.map((c) => c.id || "").filter(Boolean);
|
|
@@ -2386,9 +2671,9 @@ function resolveChangeHandoff(changeDir, changeId) {
|
|
|
2386
2671
|
}
|
|
2387
2672
|
function materializeNewCatalogArtifacts(changeDir, rootDir, changeId, author, date, errors) {
|
|
2388
2673
|
const created = [];
|
|
2389
|
-
const specFile =
|
|
2390
|
-
if (!
|
|
2391
|
-
const specContent =
|
|
2674
|
+
const specFile = path5__namespace.join(changeDir, "spec.md");
|
|
2675
|
+
if (!fs8__namespace.existsSync(specFile)) return created;
|
|
2676
|
+
const specContent = fs8__namespace.readFileSync(specFile, "utf-8");
|
|
2392
2677
|
const { newItems } = parseSpecCatalogScope(specContent);
|
|
2393
2678
|
for (const item of newItems) {
|
|
2394
2679
|
try {
|
|
@@ -2405,10 +2690,10 @@ function loadCanonicalArtifacts(rootDir) {
|
|
|
2405
2690
|
const allMdFiles = walkMdFiles(rootDir);
|
|
2406
2691
|
const canonicalArtifacts = /* @__PURE__ */ new Map();
|
|
2407
2692
|
for (const file of allMdFiles) {
|
|
2408
|
-
const relFile =
|
|
2693
|
+
const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
2409
2694
|
if (relFile.includes("specs/changes/") || relFile.includes("templates/")) continue;
|
|
2410
2695
|
try {
|
|
2411
|
-
const content =
|
|
2696
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
2412
2697
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
2413
2698
|
if (frontmatter.id) canonicalArtifacts.set(String(frontmatter.id), { file, frontmatter, body });
|
|
2414
2699
|
} catch {
|
|
@@ -2421,7 +2706,7 @@ function writeUpdatedArtifact(file, frontmatter, body) {
|
|
|
2421
2706
|
${yaml3__default.default.dump(frontmatter, { indent: 2, lineWidth: -1 }).trim()}
|
|
2422
2707
|
---
|
|
2423
2708
|
`;
|
|
2424
|
-
|
|
2709
|
+
fs8__namespace.writeFileSync(file, yamlHeader + body, "utf-8");
|
|
2425
2710
|
}
|
|
2426
2711
|
function updateSingleCanonicalRequirement(canonical, completedTasks, changeId, date, author) {
|
|
2427
2712
|
const fm = canonical.frontmatter;
|
|
@@ -2537,28 +2822,28 @@ function updateArchitectureComponents(citedComponents, canonicalArtifacts, targe
|
|
|
2537
2822
|
return updated;
|
|
2538
2823
|
}
|
|
2539
2824
|
function moveToCompletedFolder(changeDir, changeId, rootDir) {
|
|
2540
|
-
const completedParent =
|
|
2541
|
-
if (!
|
|
2542
|
-
const destDir =
|
|
2543
|
-
if (
|
|
2544
|
-
|
|
2545
|
-
const destProposal =
|
|
2546
|
-
if (
|
|
2547
|
-
const { frontmatter, body } = parseFrontmatter(
|
|
2825
|
+
const completedParent = path5__namespace.join(rootDir, "specs", "changes", "completed");
|
|
2826
|
+
if (!fs8__namespace.existsSync(completedParent)) fs8__namespace.mkdirSync(completedParent, { recursive: true });
|
|
2827
|
+
const destDir = path5__namespace.join(completedParent, changeId);
|
|
2828
|
+
if (fs8__namespace.existsSync(destDir)) fs8__namespace.rmSync(destDir, { recursive: true, force: true });
|
|
2829
|
+
fs8__namespace.renameSync(changeDir, destDir);
|
|
2830
|
+
const destProposal = path5__namespace.join(destDir, "proposal.md");
|
|
2831
|
+
if (fs8__namespace.existsSync(destProposal)) {
|
|
2832
|
+
const { frontmatter, body } = parseFrontmatter(fs8__namespace.readFileSync(destProposal, "utf-8"));
|
|
2548
2833
|
frontmatter.status = "applied";
|
|
2549
2834
|
writeUpdatedArtifact(destProposal, frontmatter, body);
|
|
2550
2835
|
}
|
|
2551
2836
|
return destDir;
|
|
2552
2837
|
}
|
|
2553
2838
|
function archiveChangeWorkspace(changeDir, changeId, rootDir, autoArchive, completedAll) {
|
|
2554
|
-
const isActiveFolder = changeDir.includes("specs" +
|
|
2839
|
+
const isActiveFolder = changeDir.includes("specs" + path5__namespace.sep + "changes" + path5__namespace.sep + "active");
|
|
2555
2840
|
if (completedAll && autoArchive && isActiveFolder) {
|
|
2556
2841
|
const destDir = moveToCompletedFolder(changeDir, changeId, rootDir);
|
|
2557
2842
|
return { archived: true, archivedPath: destDir };
|
|
2558
2843
|
}
|
|
2559
|
-
const proposalFile =
|
|
2560
|
-
if (
|
|
2561
|
-
const { frontmatter, body } = parseFrontmatter(
|
|
2844
|
+
const proposalFile = path5__namespace.join(changeDir, "proposal.md");
|
|
2845
|
+
if (fs8__namespace.existsSync(proposalFile)) {
|
|
2846
|
+
const { frontmatter, body } = parseFrontmatter(fs8__namespace.readFileSync(proposalFile, "utf-8"));
|
|
2562
2847
|
if (completedAll) frontmatter.status = "completed";
|
|
2563
2848
|
writeUpdatedArtifact(proposalFile, frontmatter, body);
|
|
2564
2849
|
}
|
|
@@ -2623,9 +2908,9 @@ function collectSubgraphPromotableIds(subgraph) {
|
|
|
2623
2908
|
return Array.from(new Set(combined));
|
|
2624
2909
|
}
|
|
2625
2910
|
function collectSpecAffectedNonArchIds(changeDir) {
|
|
2626
|
-
const specFile =
|
|
2627
|
-
if (!
|
|
2628
|
-
const { affectedItems } = parseSpecCatalogScope(
|
|
2911
|
+
const specFile = path5__namespace.join(changeDir, "spec.md");
|
|
2912
|
+
if (!fs8__namespace.existsSync(specFile)) return [];
|
|
2913
|
+
const { affectedItems } = parseSpecCatalogScope(fs8__namespace.readFileSync(specFile, "utf-8"));
|
|
2629
2914
|
return affectedItems.map((item) => item.id).filter((id) => !isArchitectureComponentId(id));
|
|
2630
2915
|
}
|
|
2631
2916
|
function resolveIntegrationOptions(options) {
|
|
@@ -2699,8 +2984,8 @@ function resolveAuditStatus(integratedCount, targetCount, gapsCount) {
|
|
|
2699
2984
|
function auditChangeEntry(changeDir, changeName, canonicalMap) {
|
|
2700
2985
|
const handoff = loadProductHandoffSidecar(changeDir);
|
|
2701
2986
|
if (!handoff) return null;
|
|
2702
|
-
const tasksFile =
|
|
2703
|
-
const tasks =
|
|
2987
|
+
const tasksFile = path5__namespace.join(changeDir, "tasks.md");
|
|
2988
|
+
const tasks = fs8__namespace.existsSync(tasksFile) ? parseTasksFromContent(fs8__namespace.readFileSync(tasksFile, "utf-8")) : [];
|
|
2704
2989
|
const completed = tasks.filter((t) => t.status === "COMPLETED");
|
|
2705
2990
|
const targetReqs = collectSubgraphPromotableIds(handoff.subgraph);
|
|
2706
2991
|
const { integrated, missing, gaps } = auditChangeRequirements(targetReqs, canonicalMap);
|
|
@@ -2716,11 +3001,11 @@ function auditChangeEntry(changeDir, changeName, canonicalMap) {
|
|
|
2716
3001
|
};
|
|
2717
3002
|
}
|
|
2718
3003
|
function collectParentDirAudits(parent, canonicalMap, audits, errors) {
|
|
2719
|
-
if (!
|
|
2720
|
-
const entries =
|
|
3004
|
+
if (!fs8__namespace.existsSync(parent)) return;
|
|
3005
|
+
const entries = fs8__namespace.readdirSync(parent, { withFileTypes: true });
|
|
2721
3006
|
for (const e of entries) {
|
|
2722
3007
|
if (!e.isDirectory()) continue;
|
|
2723
|
-
const audit = auditChangeEntry(
|
|
3008
|
+
const audit = auditChangeEntry(path5__namespace.join(parent, e.name), e.name, canonicalMap);
|
|
2724
3009
|
if (audit) {
|
|
2725
3010
|
if (audit.gaps.length > 0) errors.push(`[${e.name}] Desalineaci\xF3n: ${audit.gaps.join("; ")}`);
|
|
2726
3011
|
audits.push(audit);
|
|
@@ -2732,8 +3017,8 @@ function verifySddIntegration(options = {}) {
|
|
|
2732
3017
|
const audits = [];
|
|
2733
3018
|
const errors = [];
|
|
2734
3019
|
const searchDirs = [
|
|
2735
|
-
|
|
2736
|
-
|
|
3020
|
+
path5__namespace.join(rootDir, "specs", "changes", "completed"),
|
|
3021
|
+
path5__namespace.join(rootDir, "specs", "changes", "active")
|
|
2737
3022
|
];
|
|
2738
3023
|
const canonicalMap = loadCanonicalArtifacts(rootDir);
|
|
2739
3024
|
for (const parent of searchDirs) {
|
|
@@ -2781,10 +3066,10 @@ function resolveMatchFromMetadata(activeFolders, options) {
|
|
|
2781
3066
|
return {};
|
|
2782
3067
|
}
|
|
2783
3068
|
function validateActiveDirectory(activeParent) {
|
|
2784
|
-
if (!
|
|
3069
|
+
if (!fs8__namespace.existsSync(activeParent)) {
|
|
2785
3070
|
return { ok: false, folders: [], error: `No existe el directorio de cambios activos en '${activeParent}'.` };
|
|
2786
3071
|
}
|
|
2787
|
-
const entries =
|
|
3072
|
+
const entries = fs8__namespace.readdirSync(activeParent, { withFileTypes: true });
|
|
2788
3073
|
const activeFolders = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2789
3074
|
if (activeFolders.length === 0) {
|
|
2790
3075
|
return { ok: false, folders: [], error: "No se encontraron carpetas de cambios activos en specs/changes/active/." };
|
|
@@ -2816,7 +3101,7 @@ function evaluateTasksProgress(changeId, tasks) {
|
|
|
2816
3101
|
}
|
|
2817
3102
|
function detectActiveChangeForIntegration(options = {}) {
|
|
2818
3103
|
const rootDir = options.rootDir || process.cwd();
|
|
2819
|
-
const activeParent =
|
|
3104
|
+
const activeParent = path5__namespace.join(rootDir, "specs", "changes", "active");
|
|
2820
3105
|
const dirCheck = validateActiveDirectory(activeParent);
|
|
2821
3106
|
if (!dirCheck.ok) {
|
|
2822
3107
|
return {
|
|
@@ -2831,9 +3116,9 @@ function detectActiveChangeForIntegration(options = {}) {
|
|
|
2831
3116
|
if (!match.changeId) {
|
|
2832
3117
|
return buildUnmatchedActiveResult(dirCheck.folders, options);
|
|
2833
3118
|
}
|
|
2834
|
-
const changeDir =
|
|
2835
|
-
const tasksFile =
|
|
2836
|
-
const tasks =
|
|
3119
|
+
const changeDir = path5__namespace.join(activeParent, match.changeId);
|
|
3120
|
+
const tasksFile = path5__namespace.join(changeDir, "tasks.md");
|
|
3121
|
+
const tasks = fs8__namespace.existsSync(tasksFile) ? parseTasksFromContent(fs8__namespace.readFileSync(tasksFile, "utf-8")) : [];
|
|
2837
3122
|
const progress = evaluateTasksProgress(match.changeId, tasks);
|
|
2838
3123
|
const reasons = match.reason ? [match.reason] : [];
|
|
2839
3124
|
if (progress.error) reasons.push(progress.error);
|
|
@@ -2848,9 +3133,46 @@ function detectActiveChangeForIntegration(options = {}) {
|
|
|
2848
3133
|
reasons
|
|
2849
3134
|
};
|
|
2850
3135
|
}
|
|
2851
|
-
function
|
|
3136
|
+
function extractSemanticContent(content) {
|
|
3137
|
+
let normalized = content.replace(/\r\n/g, "\n");
|
|
3138
|
+
normalized = normalized.replace(/(?:\n|^)##?\s+.*(?:Historial de Revisiones|Revision History)(?:[\s\S]*?(?=(?:\n##?\s+[^\n]+)|\s*$))/i, "");
|
|
3139
|
+
normalized = normalized.replace(/(?:\n|^)\|\s*\*?Versi[oó]n\*?\s*\|\s*Fecha\s*\|\s*Autor[\s\S]*?(?=(?:\n##?\s+[^\n]+)|\s*$)/i, "");
|
|
3140
|
+
return normalized.trimEnd() + "\n";
|
|
3141
|
+
}
|
|
3142
|
+
function slugify(text) {
|
|
3143
|
+
return text.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-");
|
|
3144
|
+
}
|
|
3145
|
+
function matchesSlug(line, clean) {
|
|
3146
|
+
const headingText = line.replace(/^#+\s*/, "").trim();
|
|
3147
|
+
const slug = slugify(headingText);
|
|
3148
|
+
if (slug === clean || slug.includes(clean) || clean.includes(slug)) return true;
|
|
3149
|
+
const slugNoNums = slugify(headingText.replace(/^\d+[\.\)]\s*/, "").trim());
|
|
3150
|
+
return slugNoNums === clean || clean === slugNoNums;
|
|
3151
|
+
}
|
|
3152
|
+
function matchesHeading(content, clean) {
|
|
3153
|
+
for (const line of content.split(/\r?\n/)) {
|
|
3154
|
+
const trimmed = line.trim();
|
|
3155
|
+
if (trimmed.startsWith("#") && matchesSlug(trimmed, clean)) {
|
|
3156
|
+
return true;
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
return false;
|
|
3160
|
+
}
|
|
3161
|
+
function validateAnchorExistence(targetContent, anchor) {
|
|
3162
|
+
if (!anchor) return true;
|
|
3163
|
+
const clean = anchor.replace(/^#/, "").trim().toLowerCase();
|
|
3164
|
+
if (!clean || targetContent.toLowerCase().includes(clean)) return true;
|
|
3165
|
+
if (matchesHeading(targetContent, clean)) return true;
|
|
3166
|
+
const anchorRegex = new RegExp(`(?:id|name)=["']${clean}["']|\\{#${clean}\\}`, "i");
|
|
3167
|
+
return anchorRegex.test(targetContent);
|
|
3168
|
+
}
|
|
3169
|
+
function computeRawSha256(content) {
|
|
2852
3170
|
const normalized = content.replace(/\r\n/g, "\n");
|
|
2853
|
-
return
|
|
3171
|
+
return crypto3__namespace.createHash("sha256").update(normalized, "utf-8").digest("hex");
|
|
3172
|
+
}
|
|
3173
|
+
function computeCanonicalSha256(content) {
|
|
3174
|
+
const semantic = extractSemanticContent(content);
|
|
3175
|
+
return crypto3__namespace.createHash("sha256").update(semantic, "utf-8").digest("hex");
|
|
2854
3176
|
}
|
|
2855
3177
|
function parseFrontmatterId(content) {
|
|
2856
3178
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
@@ -2940,7 +3262,7 @@ function buildPdacReportHeader(isOk, driftsCount, nodesCount) {
|
|
|
2940
3262
|
return [
|
|
2941
3263
|
`# \u{1F512} PDaC Cryptographic Matrix and Drift Detection (Anti-Drift)`,
|
|
2942
3264
|
``,
|
|
2943
|
-
`> **Evaluation Date:** ${(
|
|
3265
|
+
`> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
2944
3266
|
`> **Verdict:** ${verdict}`,
|
|
2945
3267
|
``,
|
|
2946
3268
|
`---`,
|
|
@@ -2951,8 +3273,8 @@ function buildPdacReportHeader(isOk, driftsCount, nodesCount) {
|
|
|
2951
3273
|
`| :--- | :--- | :--- | :--- |`
|
|
2952
3274
|
];
|
|
2953
3275
|
}
|
|
2954
|
-
function generatePdacReportMarkdown(nodes, drifts, unresolvedCount) {
|
|
2955
|
-
const isOk = drifts.length === 0 && unresolvedCount === 0;
|
|
3276
|
+
function generatePdacReportMarkdown(nodes, drifts, unresolvedCount, invalidAnchors = []) {
|
|
3277
|
+
const isOk = drifts.length === 0 && unresolvedCount === 0 && invalidAnchors.length === 0;
|
|
2956
3278
|
const header = buildPdacReportHeader(isOk, drifts.length, nodes.length);
|
|
2957
3279
|
const nodeRows = nodes.map(
|
|
2958
3280
|
(n) => `| **\`${n.id}\`** | \`${n.type}\` | ${n.title || "Untitled"} | \`sha256:${n.digest.substring(0, 16)}...\` |`
|
|
@@ -2964,9 +3286,9 @@ function isPdacDeliveryDoc(relFile) {
|
|
|
2964
3286
|
}
|
|
2965
3287
|
function processPdacFile(file, rootDir, baselineMap, allCitations) {
|
|
2966
3288
|
try {
|
|
2967
|
-
const content =
|
|
3289
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
2968
3290
|
const { id, type, title } = parseFrontmatterId(content);
|
|
2969
|
-
const relFile =
|
|
3291
|
+
const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
2970
3292
|
if (id) {
|
|
2971
3293
|
const citations = extractCitations(content, relFile);
|
|
2972
3294
|
baselineMap.set(id, {
|
|
@@ -2975,6 +3297,7 @@ function processPdacFile(file, rootDir, baselineMap, allCitations) {
|
|
|
2975
3297
|
filePath: relFile,
|
|
2976
3298
|
title,
|
|
2977
3299
|
digest: computeCanonicalSha256(content),
|
|
3300
|
+
rawDigest: computeRawSha256(content),
|
|
2978
3301
|
rawContent: content,
|
|
2979
3302
|
citations: citations.map((c) => ({
|
|
2980
3303
|
targetId: c.targetId,
|
|
@@ -2998,38 +3321,65 @@ function scanPdacWorkspace(rootDir) {
|
|
|
2998
3321
|
}
|
|
2999
3322
|
return { baselineMap, allCitations };
|
|
3000
3323
|
}
|
|
3324
|
+
function checkAnchor(citation, targetNode, invalidAnchors) {
|
|
3325
|
+
if (citation.anchor && targetNode.rawContent) {
|
|
3326
|
+
if (!validateAnchorExistence(targetNode.rawContent, citation.anchor)) {
|
|
3327
|
+
invalidAnchors.push({
|
|
3328
|
+
sourceFile: citation.sourceFile,
|
|
3329
|
+
targetId: citation.targetId,
|
|
3330
|
+
anchor: citation.anchor,
|
|
3331
|
+
reason: `Anchor '${citation.anchor}' not found in target artifact '${citation.targetId}' (${targetNode.filePath})`
|
|
3332
|
+
});
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
function checkDigest(citation, targetNode, drifts) {
|
|
3337
|
+
if (!citation.expectedDigest) return;
|
|
3338
|
+
const matches = citation.expectedDigest === targetNode.digest || targetNode.rawDigest !== void 0 && citation.expectedDigest === targetNode.rawDigest;
|
|
3339
|
+
if (!matches) {
|
|
3340
|
+
drifts.push({
|
|
3341
|
+
sourceId: citation.sourceFile,
|
|
3342
|
+
targetId: citation.targetId,
|
|
3343
|
+
sourceFile: citation.sourceFile,
|
|
3344
|
+
expectedDigest: citation.expectedDigest,
|
|
3345
|
+
actualDigest: targetNode.digest
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3001
3349
|
function computePdacDrifts(allCitations, baselineMap) {
|
|
3002
3350
|
const drifts = [];
|
|
3351
|
+
const unresolvedCitations = [];
|
|
3352
|
+
const invalidAnchors = [];
|
|
3003
3353
|
let unresolvedCount = 0;
|
|
3004
3354
|
for (const citation of allCitations) {
|
|
3005
3355
|
const targetNode = baselineMap.get(citation.targetId);
|
|
3006
3356
|
if (!targetNode) {
|
|
3007
3357
|
unresolvedCount++;
|
|
3008
|
-
|
|
3009
|
-
}
|
|
3010
|
-
if (citation.expectedDigest && citation.expectedDigest !== targetNode.digest) {
|
|
3011
|
-
drifts.push({
|
|
3012
|
-
sourceId: citation.sourceFile,
|
|
3013
|
-
targetId: citation.targetId,
|
|
3358
|
+
unresolvedCitations.push({
|
|
3014
3359
|
sourceFile: citation.sourceFile,
|
|
3015
|
-
|
|
3016
|
-
actualDigest: targetNode.digest
|
|
3360
|
+
targetId: citation.targetId
|
|
3017
3361
|
});
|
|
3362
|
+
continue;
|
|
3018
3363
|
}
|
|
3364
|
+
checkAnchor(citation, targetNode, invalidAnchors);
|
|
3365
|
+
checkDigest(citation, targetNode, drifts);
|
|
3019
3366
|
}
|
|
3020
|
-
return { drifts, unresolvedCount };
|
|
3367
|
+
return { drifts, unresolvedCount, unresolvedCitations, invalidAnchors };
|
|
3021
3368
|
}
|
|
3022
3369
|
function verifyPdacGraph(options = {}) {
|
|
3023
3370
|
const rootDir = options.rootDir || process.cwd();
|
|
3024
3371
|
const { baselineMap, allCitations } = scanPdacWorkspace(rootDir);
|
|
3025
|
-
const { drifts, unresolvedCount } = computePdacDrifts(allCitations, baselineMap);
|
|
3372
|
+
const { drifts, unresolvedCount, unresolvedCitations, invalidAnchors } = computePdacDrifts(allCitations, baselineMap);
|
|
3026
3373
|
const nodes = Array.from(baselineMap.values());
|
|
3027
|
-
const reportMarkdown = generatePdacReportMarkdown(nodes, drifts, unresolvedCount);
|
|
3374
|
+
const reportMarkdown = generatePdacReportMarkdown(nodes, drifts, unresolvedCount, invalidAnchors);
|
|
3028
3375
|
return {
|
|
3029
|
-
success: drifts.length === 0,
|
|
3376
|
+
success: drifts.length === 0 && unresolvedCount === 0 && invalidAnchors.length === 0,
|
|
3030
3377
|
totalNodes: nodes.length,
|
|
3031
3378
|
totalEdges: allCitations.length,
|
|
3032
3379
|
drifts,
|
|
3380
|
+
unresolvedCount,
|
|
3381
|
+
unresolvedCitations,
|
|
3382
|
+
invalidAnchors,
|
|
3033
3383
|
reportMarkdown
|
|
3034
3384
|
};
|
|
3035
3385
|
}
|
|
@@ -3094,22 +3444,22 @@ function updateCitationsInContent(content, baselineMap, sourceFile, details) {
|
|
|
3094
3444
|
}
|
|
3095
3445
|
function collectCandidateFilesForSync(rootDir, targetPath) {
|
|
3096
3446
|
if (targetPath) {
|
|
3097
|
-
const fullTarget =
|
|
3098
|
-
if (!
|
|
3099
|
-
return
|
|
3447
|
+
const fullTarget = path5__namespace.isAbsolute(targetPath) ? targetPath : path5__namespace.join(rootDir, targetPath);
|
|
3448
|
+
if (!fs8__namespace.existsSync(fullTarget)) return [];
|
|
3449
|
+
return fs8__namespace.statSync(fullTarget).isDirectory() ? walkMdFiles(fullTarget) : [fullTarget];
|
|
3100
3450
|
}
|
|
3101
3451
|
return walkMdFiles(rootDir).filter((f) => {
|
|
3102
|
-
const relFile =
|
|
3452
|
+
const relFile = path5__namespace.relative(rootDir, f).replace(/\\/g, "/");
|
|
3103
3453
|
return isPdacDeliveryDoc(relFile);
|
|
3104
3454
|
});
|
|
3105
3455
|
}
|
|
3106
3456
|
function syncSingleFile(file, rootDir, baselineMap, details) {
|
|
3107
3457
|
try {
|
|
3108
|
-
const content =
|
|
3109
|
-
const relFile =
|
|
3458
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
3459
|
+
const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
3110
3460
|
const { updatedContent, changed } = updateCitationsInContent(content, baselineMap, relFile, details);
|
|
3111
3461
|
if (changed) {
|
|
3112
|
-
|
|
3462
|
+
fs8__namespace.writeFileSync(file, updatedContent, "utf-8");
|
|
3113
3463
|
return relFile;
|
|
3114
3464
|
}
|
|
3115
3465
|
} catch {
|
|
@@ -3312,6 +3662,36 @@ var STARTER_SCHEMAS = Object.entries(STARTER_SCHEMAS_MAP).map(([relPath, schema]
|
|
|
3312
3662
|
}));
|
|
3313
3663
|
|
|
3314
3664
|
// src/verifiers/schemas.ts
|
|
3665
|
+
var ajvInstance = new Ajv2020__default.default({
|
|
3666
|
+
allErrors: true,
|
|
3667
|
+
strict: false,
|
|
3668
|
+
validateFormats: true,
|
|
3669
|
+
verbose: true
|
|
3670
|
+
});
|
|
3671
|
+
addFormats__default.default(ajvInstance);
|
|
3672
|
+
var compiledSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
3673
|
+
function formatFieldPath(err, propPath) {
|
|
3674
|
+
const field = err.instancePath ? err.instancePath.replace(/^\//, "").replace(/\//g, ".") : propPath || "root";
|
|
3675
|
+
return propPath && field !== "root" && field ? `${propPath}.${field}` : field || "root";
|
|
3676
|
+
}
|
|
3677
|
+
var KEYWORD_FORMATTERS = {
|
|
3678
|
+
required: (p, _err, fullPath) => `'${fullPath}' missing required field '${String(p?.missingProperty)}'`,
|
|
3679
|
+
additionalProperties: (p, _err, fullPath) => `'${fullPath}' contains disallowed extra property '${String(p?.additionalProperty)}'`,
|
|
3680
|
+
minLength: (p, _err, fullPath) => `'${fullPath}' length must be >= ${String(p?.limit)}`,
|
|
3681
|
+
maxLength: (p, _err, fullPath) => `'${fullPath}' length must be <= ${String(p?.limit)}`,
|
|
3682
|
+
enum: (p, err, fullPath) => `'${fullPath}' must be one of [${Array.isArray(p?.allowedValues) ? p.allowedValues.join(", ") : ""}], received '${String(err.data)}'`,
|
|
3683
|
+
uniqueItems: (_p, _err, fullPath) => `'${fullPath}' contains duplicate items (must have unique items)`,
|
|
3684
|
+
pattern: (p, err, fullPath) => `'${fullPath}' value '${String(err.data)}' does not match pattern ${String(p?.pattern)}`
|
|
3685
|
+
};
|
|
3686
|
+
function formatKeywordMessage(err, fullPath) {
|
|
3687
|
+
const p = err.params;
|
|
3688
|
+
const formatter = KEYWORD_FORMATTERS[err.keyword];
|
|
3689
|
+
return formatter ? formatter(p, err, fullPath) : `'${fullPath}' ${err.message || "validation failed"}`;
|
|
3690
|
+
}
|
|
3691
|
+
function formatAjvError(err, propPath) {
|
|
3692
|
+
const fullPath = formatFieldPath(err, propPath);
|
|
3693
|
+
return formatKeywordMessage(err, fullPath);
|
|
3694
|
+
}
|
|
3315
3695
|
var TYPE_TO_SCHEMA_MAP = {
|
|
3316
3696
|
// Product
|
|
3317
3697
|
actor: "schemas/product/actor.schema.json",
|
|
@@ -3456,10 +3836,10 @@ var EXCLUDED_SCHEMA_DIRS = /* @__PURE__ */ new Set([
|
|
|
3456
3836
|
]);
|
|
3457
3837
|
function walkHandoffSidecars(dir, fileList = []) {
|
|
3458
3838
|
try {
|
|
3459
|
-
if (!
|
|
3460
|
-
for (const file of
|
|
3461
|
-
const fullPath =
|
|
3462
|
-
if (
|
|
3839
|
+
if (!fs8__namespace.existsSync(dir)) return fileList;
|
|
3840
|
+
for (const file of fs8__namespace.readdirSync(dir)) {
|
|
3841
|
+
const fullPath = path5__namespace.join(dir, file);
|
|
3842
|
+
if (fs8__namespace.statSync(fullPath).isDirectory()) {
|
|
3463
3843
|
if (!EXCLUDED_SCHEMA_DIRS.has(file) && !file.startsWith("temp-") && !file.startsWith(".temp-")) {
|
|
3464
3844
|
walkHandoffSidecars(fullPath, fileList);
|
|
3465
3845
|
}
|
|
@@ -3482,157 +3862,41 @@ function extractArtifactData(filePath, content) {
|
|
|
3482
3862
|
}
|
|
3483
3863
|
return extractFrontmatter(content);
|
|
3484
3864
|
}
|
|
3485
|
-
function
|
|
3486
|
-
|
|
3487
|
-
|
|
3865
|
+
function getOrCompileSchema(schema) {
|
|
3866
|
+
const schemaObj = schema;
|
|
3867
|
+
if (typeof schemaObj.$id === "string") {
|
|
3868
|
+
const existing = ajvInstance.getSchema(schemaObj.$id);
|
|
3869
|
+
if (existing) return existing;
|
|
3870
|
+
return ajvInstance.compile(schema);
|
|
3488
3871
|
}
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
}
|
|
3495
|
-
return [];
|
|
3496
|
-
}
|
|
3497
|
-
function validateCombinators(value, schema, propPath, currentProp) {
|
|
3498
|
-
if (schema.oneOf) {
|
|
3499
|
-
const matchCount = schema.oneOf.filter((sub) => validateAgainstSchema(value, sub, propPath).length === 0).length;
|
|
3500
|
-
if (matchCount !== 1) {
|
|
3501
|
-
return [`'${currentProp}' must match exactly one sub-schema in oneOf`];
|
|
3502
|
-
}
|
|
3503
|
-
}
|
|
3504
|
-
if (schema.anyOf) {
|
|
3505
|
-
const anyMatched = schema.anyOf.some((sub) => validateAgainstSchema(value, sub, propPath).length === 0);
|
|
3506
|
-
if (!anyMatched) {
|
|
3507
|
-
return [`'${currentProp}' did not match any allowed alternative in anyOf`];
|
|
3508
|
-
}
|
|
3509
|
-
}
|
|
3510
|
-
return [];
|
|
3511
|
-
}
|
|
3512
|
-
function getActualJsonType(value) {
|
|
3513
|
-
if (value === null) return "null";
|
|
3514
|
-
if (Array.isArray(value)) return "array";
|
|
3515
|
-
const t = typeof value;
|
|
3516
|
-
if (t === "number" && Number.isInteger(value)) return "integer";
|
|
3517
|
-
return t;
|
|
3518
|
-
}
|
|
3519
|
-
function validateType(value, schema, currentProp) {
|
|
3520
|
-
if (schema.type === void 0) return [];
|
|
3521
|
-
const allowed = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
3522
|
-
const actual = getActualJsonType(value);
|
|
3523
|
-
const matches = allowed.some((t) => t === actual || t === "number" && actual === "integer");
|
|
3524
|
-
if (!matches) {
|
|
3525
|
-
return [`'${currentProp}' expected type [${allowed.join("|")}], received '${actual}'`];
|
|
3526
|
-
}
|
|
3527
|
-
return [];
|
|
3528
|
-
}
|
|
3529
|
-
function validateString(value, schema, currentProp) {
|
|
3530
|
-
const errors = [];
|
|
3531
|
-
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
3532
|
-
errors.push(`'${currentProp}' length must be >= ${schema.minLength}`);
|
|
3533
|
-
}
|
|
3534
|
-
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
3535
|
-
errors.push(`'${currentProp}' length must be <= ${schema.maxLength}`);
|
|
3536
|
-
}
|
|
3537
|
-
if (schema.pattern !== void 0 && !new RegExp(schema.pattern).test(value)) {
|
|
3538
|
-
errors.push(`'${currentProp}' value '${value}' does not match pattern ${schema.pattern}`);
|
|
3539
|
-
}
|
|
3540
|
-
if (schema.format === "date") {
|
|
3541
|
-
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
|
3542
|
-
if (!dateRegex.test(value) || isNaN(Date.parse(value))) {
|
|
3543
|
-
errors.push(`'${currentProp}' must be a valid ISO-8601 date string (YYYY-MM-DD)`);
|
|
3544
|
-
}
|
|
3545
|
-
}
|
|
3546
|
-
return errors;
|
|
3547
|
-
}
|
|
3548
|
-
function validateNumber(value, schema, currentProp) {
|
|
3549
|
-
const errors = [];
|
|
3550
|
-
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
3551
|
-
errors.push(`'${currentProp}' must be >= ${schema.minimum}`);
|
|
3552
|
-
}
|
|
3553
|
-
if (schema.maximum !== void 0 && value > schema.maximum) {
|
|
3554
|
-
errors.push(`'${currentProp}' must be <= ${schema.maximum}`);
|
|
3555
|
-
}
|
|
3556
|
-
return errors;
|
|
3557
|
-
}
|
|
3558
|
-
function validateArray(value, schema, currentProp) {
|
|
3559
|
-
const errors = [];
|
|
3560
|
-
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
3561
|
-
errors.push(`'${currentProp}' items count must be >= ${schema.minItems}`);
|
|
3562
|
-
}
|
|
3563
|
-
if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
|
|
3564
|
-
errors.push(`'${currentProp}' items count must be <= ${schema.maxItems}`);
|
|
3565
|
-
}
|
|
3566
|
-
if (schema.items) {
|
|
3567
|
-
value.forEach((item, idx) => {
|
|
3568
|
-
errors.push(...validateAgainstSchema(item, schema.items, `${currentProp}[${idx}]`));
|
|
3569
|
-
});
|
|
3570
|
-
}
|
|
3571
|
-
return errors;
|
|
3572
|
-
}
|
|
3573
|
-
function checkRequiredProperties(record, required, currentProp) {
|
|
3574
|
-
if (!required) return [];
|
|
3575
|
-
const errors = [];
|
|
3576
|
-
for (const reqKey of required) {
|
|
3577
|
-
if (!(reqKey in record) || record[reqKey] === void 0) {
|
|
3578
|
-
errors.push(`'${currentProp}' missing required field '${reqKey}'`);
|
|
3579
|
-
}
|
|
3580
|
-
}
|
|
3581
|
-
return errors;
|
|
3582
|
-
}
|
|
3583
|
-
function checkObjectProperties(record, schema, propPath, currentProp) {
|
|
3584
|
-
const errors = [];
|
|
3585
|
-
const properties = schema.properties || {};
|
|
3586
|
-
for (const [key, propVal] of Object.entries(record)) {
|
|
3587
|
-
const subPath = propPath ? `${propPath}.${key}` : key;
|
|
3588
|
-
if (key in properties) {
|
|
3589
|
-
errors.push(...validateAgainstSchema(propVal, properties[key], subPath));
|
|
3590
|
-
} else if (schema.additionalProperties === false) {
|
|
3591
|
-
errors.push(`'${currentProp}' contains disallowed extra property '${key}'`);
|
|
3592
|
-
} else if (typeof schema.additionalProperties === "object") {
|
|
3593
|
-
errors.push(...validateAgainstSchema(propVal, schema.additionalProperties, subPath));
|
|
3594
|
-
}
|
|
3595
|
-
}
|
|
3596
|
-
return errors;
|
|
3597
|
-
}
|
|
3598
|
-
function validateObject(record, schema, propPath, currentProp) {
|
|
3599
|
-
return [
|
|
3600
|
-
...checkRequiredProperties(record, schema.required, currentProp),
|
|
3601
|
-
...checkObjectProperties(record, schema, propPath, currentProp)
|
|
3602
|
-
];
|
|
3603
|
-
}
|
|
3604
|
-
function validateTypeSpecificRules(value, schema, propPath, currentProp) {
|
|
3605
|
-
if (typeof value === "string") return validateString(value, schema, currentProp);
|
|
3606
|
-
if (typeof value === "number") return validateNumber(value, schema, currentProp);
|
|
3607
|
-
if (Array.isArray(value)) return validateArray(value, schema, currentProp);
|
|
3608
|
-
if (typeof value === "object" && value !== null) {
|
|
3609
|
-
return validateObject(value, schema, propPath, currentProp);
|
|
3610
|
-
}
|
|
3611
|
-
return [];
|
|
3872
|
+
const cached = compiledSchemaCache.get(schema);
|
|
3873
|
+
if (cached) return cached;
|
|
3874
|
+
const compiled = ajvInstance.compile(schema);
|
|
3875
|
+
compiledSchemaCache.set(schema, compiled);
|
|
3876
|
+
return compiled;
|
|
3612
3877
|
}
|
|
3613
3878
|
function validateAgainstSchema(value, schema, propPath = "") {
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
return validateTypeSpecificRules(value, schema, propPath, currentProp);
|
|
3879
|
+
if (!schema || typeof schema !== "object") return [];
|
|
3880
|
+
try {
|
|
3881
|
+
const validate = getOrCompileSchema(schema);
|
|
3882
|
+
const valid = validate(value);
|
|
3883
|
+
if (valid || !validate.errors) return [];
|
|
3884
|
+
return validate.errors.map((err) => formatAjvError(err, propPath));
|
|
3885
|
+
} catch (err) {
|
|
3886
|
+
return [`Schema error: ${err instanceof Error ? err.message : String(err)}`];
|
|
3887
|
+
}
|
|
3624
3888
|
}
|
|
3625
3889
|
function findSchemaFile(relSchemaPath, rootDir) {
|
|
3626
|
-
const fullPath =
|
|
3627
|
-
if (
|
|
3628
|
-
const fallbackPath =
|
|
3629
|
-
if (
|
|
3890
|
+
const fullPath = path5__namespace.join(rootDir, relSchemaPath);
|
|
3891
|
+
if (fs8__namespace.existsSync(fullPath)) return fullPath;
|
|
3892
|
+
const fallbackPath = path5__namespace.join(process.cwd(), relSchemaPath);
|
|
3893
|
+
if (fs8__namespace.existsSync(fallbackPath)) return fallbackPath;
|
|
3630
3894
|
return null;
|
|
3631
3895
|
}
|
|
3632
3896
|
function loadJsonSchema(fullPath, relSchemaPath) {
|
|
3633
|
-
if (fullPath &&
|
|
3897
|
+
if (fullPath && fs8__namespace.existsSync(fullPath)) {
|
|
3634
3898
|
try {
|
|
3635
|
-
return JSON.parse(
|
|
3899
|
+
return JSON.parse(fs8__namespace.readFileSync(fullPath, "utf-8"));
|
|
3636
3900
|
} catch {
|
|
3637
3901
|
}
|
|
3638
3902
|
}
|
|
@@ -3647,7 +3911,7 @@ function resolveSchemaForArtifact(frontmatter, rootDir) {
|
|
|
3647
3911
|
}
|
|
3648
3912
|
function loadSchemaFromPathOrType(schemaOrType, rootDir) {
|
|
3649
3913
|
if (schemaOrType.endsWith(".json")) {
|
|
3650
|
-
const fullPath =
|
|
3914
|
+
const fullPath = path5__namespace.isAbsolute(schemaOrType) ? schemaOrType : findSchemaFile(schemaOrType, rootDir);
|
|
3651
3915
|
const relNormalized = schemaOrType.replace(/\\/g, "/");
|
|
3652
3916
|
const schema = loadJsonSchema(fullPath, relNormalized);
|
|
3653
3917
|
return { schema, schemaId: schemaOrType };
|
|
@@ -3692,9 +3956,9 @@ function collectCandidateSchemaFiles(options, rootDir) {
|
|
|
3692
3956
|
if (!options.targetPath) {
|
|
3693
3957
|
return [...walkMdFiles(rootDir), ...walkHandoffSidecars(rootDir)];
|
|
3694
3958
|
}
|
|
3695
|
-
const target =
|
|
3696
|
-
if (!
|
|
3697
|
-
if (
|
|
3959
|
+
const target = path5__namespace.isAbsolute(options.targetPath) ? options.targetPath : path5__namespace.join(rootDir, options.targetPath);
|
|
3960
|
+
if (!fs8__namespace.existsSync(target)) return [];
|
|
3961
|
+
if (fs8__namespace.statSync(target).isDirectory()) {
|
|
3698
3962
|
return [...walkMdFiles(target), ...walkHandoffSidecars(target)];
|
|
3699
3963
|
}
|
|
3700
3964
|
if (target.endsWith(".md") || target.endsWith(".yaml") || target.endsWith(".yml")) {
|
|
@@ -3745,7 +4009,7 @@ function buildEvaluationFailure(relPath, err) {
|
|
|
3745
4009
|
}
|
|
3746
4010
|
function evaluateArtifactFile(file, relPath, rootDir) {
|
|
3747
4011
|
try {
|
|
3748
|
-
const content =
|
|
4012
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
3749
4013
|
const frontmatter = extractArtifactData(file, content);
|
|
3750
4014
|
if (!frontmatter || !frontmatter.id) return null;
|
|
3751
4015
|
const schemaInfo = resolveSchemaForArtifact(frontmatter, rootDir);
|
|
@@ -3780,7 +4044,7 @@ function verifyArtifactsSchemas(options = {}) {
|
|
|
3780
4044
|
let validCount = 0;
|
|
3781
4045
|
let totalEvaluated = 0;
|
|
3782
4046
|
for (const file of filesToScan) {
|
|
3783
|
-
const relPath =
|
|
4047
|
+
const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
3784
4048
|
if (isIgnoredArtifactPath(relPath, Boolean(options.targetPath))) continue;
|
|
3785
4049
|
const evaluation = evaluateArtifactFile(file, relPath, rootDir);
|
|
3786
4050
|
if (!evaluation) continue;
|
|
@@ -3795,14 +4059,14 @@ function verifyArtifactsSchemas(options = {}) {
|
|
|
3795
4059
|
}
|
|
3796
4060
|
|
|
3797
4061
|
// src/adapters/sdd/scaffold.ts
|
|
3798
|
-
function
|
|
4062
|
+
function slugify2(text) {
|
|
3799
4063
|
return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3800
4064
|
}
|
|
3801
4065
|
function extractDirMaxNum(dir) {
|
|
3802
|
-
if (!
|
|
4066
|
+
if (!fs8__namespace.existsSync(dir)) return 0;
|
|
3803
4067
|
let max = 0;
|
|
3804
4068
|
try {
|
|
3805
|
-
const entries =
|
|
4069
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
|
|
3806
4070
|
for (const e of entries) {
|
|
3807
4071
|
if (!e.isDirectory()) continue;
|
|
3808
4072
|
const match = e.name.match(/^(?:chg|CHG|patch|PATCH)-(\d+)/i);
|
|
@@ -3819,10 +4083,10 @@ function extractDirMaxNum(dir) {
|
|
|
3819
4083
|
}
|
|
3820
4084
|
function getNextCorrelativeNumber(rootDir) {
|
|
3821
4085
|
const candidateDirs = [
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
4086
|
+
path5__namespace.join(rootDir, "specs", "changes", "active"),
|
|
4087
|
+
path5__namespace.join(rootDir, "specs", "changes", "completed"),
|
|
4088
|
+
path5__namespace.join(rootDir, "specs"),
|
|
4089
|
+
path5__namespace.join(rootDir, "openspec", "changes")
|
|
3826
4090
|
];
|
|
3827
4091
|
let maxNum = 0;
|
|
3828
4092
|
for (const dir of candidateDirs) {
|
|
@@ -3835,7 +4099,7 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
|
|
|
3835
4099
|
const targetChangeId = options.changeId || options.id;
|
|
3836
4100
|
if (targetChangeId) {
|
|
3837
4101
|
const rawId = targetChangeId.trim();
|
|
3838
|
-
const slug =
|
|
4102
|
+
const slug = slugify2(rawId);
|
|
3839
4103
|
const canonicalId = rawId.toUpperCase();
|
|
3840
4104
|
const match = rawId.match(/^(?:chg|CHG|patch|PATCH)-(\d+)/i);
|
|
3841
4105
|
const padNum2 = match ? match[1] : "001";
|
|
@@ -3843,7 +4107,7 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
|
|
|
3843
4107
|
}
|
|
3844
4108
|
const nextNum = getNextCorrelativeNumber(rootDir);
|
|
3845
4109
|
const padNum = String(nextNum).padStart(3, "0");
|
|
3846
|
-
const nameSlug =
|
|
4110
|
+
const nameSlug = slugify2(name);
|
|
3847
4111
|
const prefix = profile === "patch" ? "patch" : "chg";
|
|
3848
4112
|
return {
|
|
3849
4113
|
slug: `${prefix}-${padNum}-${nameSlug}`,
|
|
@@ -3852,13 +4116,13 @@ function resolveChangeIdentifiers(rootDir, name, profile, options) {
|
|
|
3852
4116
|
};
|
|
3853
4117
|
}
|
|
3854
4118
|
function ensureEmptyChangeDir(changeDir) {
|
|
3855
|
-
if (
|
|
3856
|
-
const existing =
|
|
4119
|
+
if (fs8__namespace.existsSync(changeDir)) {
|
|
4120
|
+
const existing = fs8__namespace.readdirSync(changeDir);
|
|
3857
4121
|
if (existing.length > 0) {
|
|
3858
4122
|
return `El directorio del cambio '${changeDir}' ya existe y no est\xE1 vac\xEDo.`;
|
|
3859
4123
|
}
|
|
3860
4124
|
} else {
|
|
3861
|
-
|
|
4125
|
+
fs8__namespace.mkdirSync(changeDir, { recursive: true });
|
|
3862
4126
|
}
|
|
3863
4127
|
return null;
|
|
3864
4128
|
}
|
|
@@ -3867,7 +4131,7 @@ function scanCanonicalArtifacts(rootDir) {
|
|
|
3867
4131
|
const artifactMap = /* @__PURE__ */ new Map();
|
|
3868
4132
|
for (const file of allMdFiles) {
|
|
3869
4133
|
try {
|
|
3870
|
-
const content =
|
|
4134
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
3871
4135
|
const { frontmatter } = parseFrontmatter(content);
|
|
3872
4136
|
if (frontmatter?.id) {
|
|
3873
4137
|
const id = String(frontmatter.id);
|
|
@@ -4246,7 +4510,7 @@ function buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors)
|
|
|
4246
4510
|
changeId: slug,
|
|
4247
4511
|
canonicalId,
|
|
4248
4512
|
profile: "patch",
|
|
4249
|
-
changeDir:
|
|
4513
|
+
changeDir: path5__namespace.dirname(specPath),
|
|
4250
4514
|
createdFiles: [specPath],
|
|
4251
4515
|
citedArtifacts,
|
|
4252
4516
|
errors
|
|
@@ -4257,10 +4521,10 @@ function scaffoldPatchChange(rootDir, changeDir, slug, canonicalId, name, inputF
|
|
|
4257
4521
|
const citationsSection = citedArtifacts.length > 0 ? `citations:
|
|
4258
4522
|
${renderCitationsYamlBlock(citedArtifacts)}
|
|
4259
4523
|
` : "";
|
|
4260
|
-
const specPath =
|
|
4524
|
+
const specPath = path5__namespace.join(changeDir, "spec.md");
|
|
4261
4525
|
const specFmObj = buildPatchFmObj(canonicalId);
|
|
4262
4526
|
const specContent = buildPatchSpecContent(canonicalId, name, citationsSection);
|
|
4263
|
-
|
|
4527
|
+
fs8__namespace.writeFileSync(specPath, specContent, "utf-8");
|
|
4264
4528
|
const errors = validatePatchArtifacts(rootDir, changeDir, specContent, specFmObj, adapter);
|
|
4265
4529
|
return buildPatchSuccess(slug, canonicalId, specPath, citedArtifacts, errors);
|
|
4266
4530
|
}
|
|
@@ -4271,46 +4535,46 @@ function buildGreenfieldContent(reqId, ucId, name, canonicalId, author, dateStr)
|
|
|
4271
4535
|
${body}`;
|
|
4272
4536
|
}
|
|
4273
4537
|
function createGreenfieldRequirement(rootDir, name, padNum, canonicalId, author, dateStr, createdFiles) {
|
|
4274
|
-
const nameSlugUpper =
|
|
4538
|
+
const nameSlugUpper = slugify2(name).toUpperCase();
|
|
4275
4539
|
const reqId = `FR-${padNum}-${nameSlugUpper}-001`;
|
|
4276
4540
|
const ucId = `UC-${padNum}-${nameSlugUpper}`;
|
|
4277
|
-
const productDir =
|
|
4278
|
-
if (!
|
|
4279
|
-
const reqFilePath =
|
|
4280
|
-
if (
|
|
4541
|
+
const productDir = path5__namespace.join(rootDir, "specs", "product");
|
|
4542
|
+
if (!fs8__namespace.existsSync(productDir)) fs8__namespace.mkdirSync(productDir, { recursive: true });
|
|
4543
|
+
const reqFilePath = path5__namespace.join(productDir, `${reqId}.md`);
|
|
4544
|
+
if (fs8__namespace.existsSync(reqFilePath)) return null;
|
|
4281
4545
|
const reqContent = buildGreenfieldContent(reqId, ucId, name, canonicalId, author, dateStr);
|
|
4282
|
-
|
|
4546
|
+
fs8__namespace.writeFileSync(reqFilePath, reqContent, "utf-8");
|
|
4283
4547
|
createdFiles.push(reqFilePath);
|
|
4284
4548
|
const digest = computeCanonicalSha256(reqContent);
|
|
4285
4549
|
return { reqId, ucId, reqFilePath, digest };
|
|
4286
4550
|
}
|
|
4287
4551
|
function resolveMissingDraftDestination(rootDir, id) {
|
|
4288
4552
|
if (id.startsWith("PRIV-REQ-")) {
|
|
4289
|
-
return { dir:
|
|
4553
|
+
return { dir: path5__namespace.join(rootDir, "specs", "privacy", "requirements"), type: "privacy-requirement" };
|
|
4290
4554
|
}
|
|
4291
4555
|
if (id.startsWith("DATA-ASSET-")) {
|
|
4292
|
-
return { dir:
|
|
4556
|
+
return { dir: path5__namespace.join(rootDir, "specs", "privacy", "assets"), type: "data-asset" };
|
|
4293
4557
|
}
|
|
4294
4558
|
if (id.startsWith("PRIV-ROPA-")) {
|
|
4295
|
-
return { dir:
|
|
4559
|
+
return { dir: path5__namespace.join(rootDir, "specs", "privacy", "ropa"), type: "privacy-ropa" };
|
|
4296
4560
|
}
|
|
4297
4561
|
if (id.startsWith("UC-")) {
|
|
4298
|
-
return { dir:
|
|
4562
|
+
return { dir: path5__namespace.join(rootDir, "specs", "product"), type: "use-case" };
|
|
4299
4563
|
}
|
|
4300
4564
|
if (id.startsWith("SEC-REQ-") || id.startsWith("SAF-REQ-")) {
|
|
4301
|
-
return { dir:
|
|
4565
|
+
return { dir: path5__namespace.join(rootDir, "specs", "product"), type: "security-requirement" };
|
|
4302
4566
|
}
|
|
4303
|
-
return { dir:
|
|
4567
|
+
return { dir: path5__namespace.join(rootDir, "specs", "product"), type: "requirement" };
|
|
4304
4568
|
}
|
|
4305
4569
|
function createMissingDraftRequirement(rootDir, name, id, canonicalId, createdFiles) {
|
|
4306
4570
|
const { dir, type: reqType } = resolveMissingDraftDestination(rootDir, id);
|
|
4307
|
-
if (!
|
|
4308
|
-
const reqFilePath =
|
|
4571
|
+
if (!fs8__namespace.existsSync(dir)) fs8__namespace.mkdirSync(dir, { recursive: true });
|
|
4572
|
+
const reqFilePath = path5__namespace.join(dir, `${id}.md`);
|
|
4309
4573
|
const header = MISSING_DRAFT_HEADER.replaceAll("{{ID}}", id).replaceAll("{{TYPE}}", reqType).replaceAll("{{NAME}}", name);
|
|
4310
4574
|
const body = MISSING_DRAFT_BODY.replaceAll("{{ID}}", id).replaceAll("{{NAME}}", name).replaceAll("{{CANONICAL_ID}}", canonicalId);
|
|
4311
4575
|
const reqContent = `${header}
|
|
4312
4576
|
${body}`;
|
|
4313
|
-
|
|
4577
|
+
fs8__namespace.writeFileSync(reqFilePath, reqContent, "utf-8");
|
|
4314
4578
|
createdFiles.push(reqFilePath);
|
|
4315
4579
|
const digest = computeCanonicalSha256(reqContent);
|
|
4316
4580
|
return { reqFilePath, digest };
|
|
@@ -4425,7 +4689,7 @@ function initEmptyCitations() {
|
|
|
4425
4689
|
}
|
|
4426
4690
|
function ensureFallbackRequirement(col, padNum, name) {
|
|
4427
4691
|
if (col.subRequirements.length === 0) {
|
|
4428
|
-
col.subRequirements.push(`FR-${padNum}-${
|
|
4692
|
+
col.subRequirements.push(`FR-${padNum}-${slugify2(name).toUpperCase()}-001`);
|
|
4429
4693
|
}
|
|
4430
4694
|
}
|
|
4431
4695
|
function collectChangeCitations(rootDir, name, padNum, canonicalId, author, dateStr, inputFromIds, artifactMap, createdFiles) {
|
|
@@ -4439,7 +4703,7 @@ function collectChangeCitations(rootDir, name, padNum, canonicalId, author, date
|
|
|
4439
4703
|
return col;
|
|
4440
4704
|
}
|
|
4441
4705
|
function renderProposalFile(changeDir, canonicalId, name, author, citationsYamlBlock) {
|
|
4442
|
-
const proposalPath =
|
|
4706
|
+
const proposalPath = path5__namespace.join(changeDir, "proposal.md");
|
|
4443
4707
|
const citationsSection = citationsYamlBlock ? `citations:
|
|
4444
4708
|
${citationsYamlBlock}
|
|
4445
4709
|
` : "";
|
|
@@ -4459,11 +4723,11 @@ ${name}
|
|
|
4459
4723
|
## 2. Dependencias Externas Evaluadas
|
|
4460
4724
|
- Lista de dependencias nuevas a incorporar y verificaci\xF3n de licencia seg\xFAn \`license-policy.yaml\`.
|
|
4461
4725
|
`;
|
|
4462
|
-
|
|
4726
|
+
fs8__namespace.writeFileSync(proposalPath, proposalContent, "utf-8");
|
|
4463
4727
|
return proposalPath;
|
|
4464
4728
|
}
|
|
4465
4729
|
function renderSpecFile(changeDir, canonicalId, name, profile) {
|
|
4466
|
-
const specPath =
|
|
4730
|
+
const specPath = path5__namespace.join(changeDir, "spec.md");
|
|
4467
4731
|
const specFmObj = {
|
|
4468
4732
|
id: `SPEC-${canonicalId}`,
|
|
4469
4733
|
type: "delivery-spec",
|
|
@@ -4484,12 +4748,12 @@ cucumber-tags:
|
|
|
4484
4748
|
---
|
|
4485
4749
|
`;
|
|
4486
4750
|
const body = SPEC_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name);
|
|
4487
|
-
|
|
4751
|
+
fs8__namespace.writeFileSync(specPath, `${header}
|
|
4488
4752
|
${body}`, "utf-8");
|
|
4489
4753
|
return { specPath, specFmObj };
|
|
4490
4754
|
}
|
|
4491
4755
|
function renderDesignFile(changeDir, canonicalId, name, author, dateStr, citationsYamlBlock) {
|
|
4492
|
-
const designPath =
|
|
4756
|
+
const designPath = path5__namespace.join(changeDir, "design.md");
|
|
4493
4757
|
const citationsSection = citationsYamlBlock ? `citations:
|
|
4494
4758
|
${citationsYamlBlock}
|
|
4495
4759
|
` : "";
|
|
@@ -4508,7 +4772,7 @@ contracts: []
|
|
|
4508
4772
|
${citationsSection}---
|
|
4509
4773
|
`;
|
|
4510
4774
|
const body = DESIGN_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
|
|
4511
|
-
|
|
4775
|
+
fs8__namespace.writeFileSync(designPath, `${header}
|
|
4512
4776
|
${body}`, "utf-8");
|
|
4513
4777
|
return designPath;
|
|
4514
4778
|
}
|
|
@@ -4533,14 +4797,14 @@ function buildTasksFrontmatter(canonicalId, name, reqIds) {
|
|
|
4533
4797
|
};
|
|
4534
4798
|
}
|
|
4535
4799
|
function renderTasksFile(changeDir, canonicalId, name, author, dateStr, reqIds) {
|
|
4536
|
-
const tasksPath =
|
|
4800
|
+
const tasksPath = path5__namespace.join(changeDir, "tasks.md");
|
|
4537
4801
|
const tasksFm = buildTasksFrontmatter(canonicalId, name, reqIds);
|
|
4538
4802
|
const header = `---
|
|
4539
4803
|
${yaml3__default.default.dump(tasksFm, { indent: 2, lineWidth: -1 }).trim()}
|
|
4540
4804
|
---
|
|
4541
4805
|
`;
|
|
4542
4806
|
const body = TASKS_BODY.replaceAll("{{CANONICAL_ID}}", canonicalId).replaceAll("{{NAME}}", name).replaceAll("{{AUTHOR}}", author).replaceAll("{{DATE}}", dateStr);
|
|
4543
|
-
|
|
4807
|
+
fs8__namespace.writeFileSync(tasksPath, `${header}
|
|
4544
4808
|
${body}`, "utf-8");
|
|
4545
4809
|
return { tasksPath, tasksFm };
|
|
4546
4810
|
}
|
|
@@ -4719,7 +4983,7 @@ function resolveCiEnvironment(env = process.env) {
|
|
|
4719
4983
|
};
|
|
4720
4984
|
}
|
|
4721
4985
|
function updateExistingEnvFile(filePath, envString, uppercaseKey) {
|
|
4722
|
-
const content =
|
|
4986
|
+
const content = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
4723
4987
|
const lines = content.split("\n");
|
|
4724
4988
|
let found = false;
|
|
4725
4989
|
const newLines = lines.map((line) => {
|
|
@@ -4730,15 +4994,15 @@ function updateExistingEnvFile(filePath, envString, uppercaseKey) {
|
|
|
4730
4994
|
return line;
|
|
4731
4995
|
});
|
|
4732
4996
|
if (!found) newLines.push(envString);
|
|
4733
|
-
|
|
4997
|
+
fs8__namespace.writeFileSync(filePath, newLines.filter((l) => l.trim() !== "").join("\n") + "\n", "utf-8");
|
|
4734
4998
|
}
|
|
4735
4999
|
function writeEnvOutputFile(envOutputFile, key, value) {
|
|
4736
5000
|
const uppercaseKey = key.toUpperCase();
|
|
4737
5001
|
const envString = `${uppercaseKey}=${value}`;
|
|
4738
|
-
if (
|
|
5002
|
+
if (fs8__namespace.existsSync(envOutputFile)) {
|
|
4739
5003
|
updateExistingEnvFile(envOutputFile, envString, uppercaseKey);
|
|
4740
5004
|
} else {
|
|
4741
|
-
|
|
5005
|
+
fs8__namespace.writeFileSync(envOutputFile, envString + "\n", "utf-8");
|
|
4742
5006
|
}
|
|
4743
5007
|
}
|
|
4744
5008
|
function resolveEnvOutputFilePath(env) {
|
|
@@ -4748,8 +5012,8 @@ function resolveEnvOutputFilePath(env) {
|
|
|
4748
5012
|
return void 0;
|
|
4749
5013
|
}
|
|
4750
5014
|
function emitCiOutput(key, value, env = process.env) {
|
|
4751
|
-
if (env.GITHUB_OUTPUT &&
|
|
4752
|
-
|
|
5015
|
+
if (env.GITHUB_OUTPUT && fs8__namespace.existsSync(env.GITHUB_OUTPUT)) {
|
|
5016
|
+
fs8__namespace.appendFileSync(env.GITHUB_OUTPUT, `${key}=${value}
|
|
4753
5017
|
`, "utf-8");
|
|
4754
5018
|
}
|
|
4755
5019
|
if (env.TF_BUILD === "true") {
|
|
@@ -4791,11 +5055,11 @@ function isIgnoredEntry(name) {
|
|
|
4791
5055
|
return name === "changes" || name === "active" || name === "node_modules";
|
|
4792
5056
|
}
|
|
4793
5057
|
function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
|
|
4794
|
-
if (!
|
|
4795
|
-
const entries =
|
|
5058
|
+
if (!fs8__namespace.existsSync(parentDir)) return;
|
|
5059
|
+
const entries = fs8__namespace.readdirSync(parentDir, { withFileTypes: true });
|
|
4796
5060
|
for (const entry of entries) {
|
|
4797
5061
|
if (!entry.isDirectory() || isIgnoredEntry(entry.name)) continue;
|
|
4798
|
-
const changeDir =
|
|
5062
|
+
const changeDir = path5__namespace.join(parentDir, entry.name);
|
|
4799
5063
|
if (handooffsHasDir(handoffsMap, changeDir)) continue;
|
|
4800
5064
|
const handoff = loadProductHandoffSidecar(changeDir);
|
|
4801
5065
|
if (handoff?.id) {
|
|
@@ -4806,10 +5070,10 @@ function scanParentDirectoryHandoffs(parentDir, handoffsMap) {
|
|
|
4806
5070
|
function scanAllProductHandoffs(rootDir) {
|
|
4807
5071
|
const handoffsMap = /* @__PURE__ */ new Map();
|
|
4808
5072
|
const candidateParentDirs = [
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
5073
|
+
path5__namespace.join(rootDir, "specs", "changes", "active"),
|
|
5074
|
+
path5__namespace.join(rootDir, "specs", "changes", "completed"),
|
|
5075
|
+
path5__namespace.join(rootDir, "specs"),
|
|
5076
|
+
path5__namespace.join(rootDir, "openspec", "changes")
|
|
4813
5077
|
];
|
|
4814
5078
|
for (const parentDir of candidateParentDirs) {
|
|
4815
5079
|
scanParentDirectoryHandoffs(parentDir, handoffsMap);
|
|
@@ -4817,9 +5081,9 @@ function scanAllProductHandoffs(rootDir) {
|
|
|
4817
5081
|
return handoffsMap;
|
|
4818
5082
|
}
|
|
4819
5083
|
function handooffsHasDir(map, dir) {
|
|
4820
|
-
const norm =
|
|
5084
|
+
const norm = path5__namespace.resolve(dir);
|
|
4821
5085
|
for (const key of map.keys()) {
|
|
4822
|
-
if (
|
|
5086
|
+
if (path5__namespace.resolve(key) === norm) return true;
|
|
4823
5087
|
}
|
|
4824
5088
|
return false;
|
|
4825
5089
|
}
|
|
@@ -4854,7 +5118,7 @@ function buildTraceabilityHeader(isOk, errors) {
|
|
|
4854
5118
|
return [
|
|
4855
5119
|
"# 360\xB0 Requirements Traceability Matrix (RTM)",
|
|
4856
5120
|
"",
|
|
4857
|
-
`*Verification Date: ${(
|
|
5121
|
+
`*Verification Date: ${resolveDeterministicDate().toISOString()}*`,
|
|
4858
5122
|
`*Overall Status: ${isOk ? "100% TRACEABLE (PASSED)" : `ERRORS DETECTED (${errors} gaps)`}*`,
|
|
4859
5123
|
"",
|
|
4860
5124
|
"## 1. End-to-End Coverage (PDaC Handoff \u2794 arc42/NAF v4 \u2794 BDD/Gherkin)",
|
|
@@ -4916,9 +5180,9 @@ function isCodeTestFile(name) {
|
|
|
4916
5180
|
}
|
|
4917
5181
|
function indexFeatureFile(fullPath, rootDir, map) {
|
|
4918
5182
|
try {
|
|
4919
|
-
const content =
|
|
5183
|
+
const content = fs8__namespace.readFileSync(fullPath, "utf-8");
|
|
4920
5184
|
const tags = extractFeatureTags(content);
|
|
4921
|
-
const relFeature =
|
|
5185
|
+
const relFeature = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
4922
5186
|
for (const tag of tags) {
|
|
4923
5187
|
const existing = map.get(tag) || [];
|
|
4924
5188
|
if (!existing.includes(relFeature)) existing.push(relFeature);
|
|
@@ -4928,11 +5192,11 @@ function indexFeatureFile(fullPath, rootDir, map) {
|
|
|
4928
5192
|
}
|
|
4929
5193
|
}
|
|
4930
5194
|
function scanFeatureFiles(dir, rootDir, map) {
|
|
4931
|
-
if (!
|
|
5195
|
+
if (!fs8__namespace.existsSync(dir)) return;
|
|
4932
5196
|
try {
|
|
4933
|
-
const entries =
|
|
5197
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
|
|
4934
5198
|
for (const e of entries) {
|
|
4935
|
-
const fullPath =
|
|
5199
|
+
const fullPath = path5__namespace.join(dir, e.name);
|
|
4936
5200
|
if (e.isDirectory() && !shouldSkipDir(e.name)) {
|
|
4937
5201
|
scanFeatureFiles(fullPath, rootDir, map);
|
|
4938
5202
|
} else if (e.name.endsWith(".feature")) {
|
|
@@ -4944,8 +5208,8 @@ function scanFeatureFiles(dir, rootDir, map) {
|
|
|
4944
5208
|
}
|
|
4945
5209
|
function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
|
|
4946
5210
|
try {
|
|
4947
|
-
const content =
|
|
4948
|
-
const relTest =
|
|
5211
|
+
const content = fs8__namespace.readFileSync(fullPath, "utf-8");
|
|
5212
|
+
const relTest = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
4949
5213
|
for (const artId of reqIds) {
|
|
4950
5214
|
if (content.includes(artId)) {
|
|
4951
5215
|
const existing = map.get(artId) || [];
|
|
@@ -4957,11 +5221,11 @@ function indexCodeTestFile(fullPath, rootDir, reqIds, map) {
|
|
|
4957
5221
|
}
|
|
4958
5222
|
}
|
|
4959
5223
|
function scanCodeTestFiles(dir, rootDir, reqIds, map) {
|
|
4960
|
-
if (!
|
|
5224
|
+
if (!fs8__namespace.existsSync(dir)) return;
|
|
4961
5225
|
try {
|
|
4962
|
-
const entries =
|
|
5226
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
|
|
4963
5227
|
for (const e of entries) {
|
|
4964
|
-
const fullPath =
|
|
5228
|
+
const fullPath = path5__namespace.join(dir, e.name);
|
|
4965
5229
|
if (e.isDirectory() && !shouldSkipDir(e.name)) {
|
|
4966
5230
|
scanCodeTestFiles(fullPath, rootDir, reqIds, map);
|
|
4967
5231
|
} else if (isCodeTestFile(e.name)) {
|
|
@@ -4995,9 +5259,9 @@ function isArchitectureViewFile(relFile, frontmatter) {
|
|
|
4995
5259
|
}
|
|
4996
5260
|
function processSingleMdFile(file, rootDir, artifactMap, componentsList, architectureViews) {
|
|
4997
5261
|
try {
|
|
4998
|
-
const content =
|
|
5262
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
4999
5263
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
5000
|
-
const relFile =
|
|
5264
|
+
const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
5001
5265
|
if (frontmatter.id) {
|
|
5002
5266
|
artifactMap.set(frontmatter.id, {
|
|
5003
5267
|
file: relFile,
|
|
@@ -5162,7 +5426,7 @@ function collectArchitectureViewTraces(reqId, architectureViews) {
|
|
|
5162
5426
|
const traces = [];
|
|
5163
5427
|
for (const view of architectureViews) {
|
|
5164
5428
|
if (view.content.includes(reqId)) {
|
|
5165
|
-
const viewIdentifier = isEntityWithId(view) && view.id ? view.id :
|
|
5429
|
+
const viewIdentifier = isEntityWithId(view) && view.id ? view.id : path5__namespace.basename(view.file);
|
|
5166
5430
|
traces.push(viewIdentifier);
|
|
5167
5431
|
}
|
|
5168
5432
|
}
|
|
@@ -5250,16 +5514,16 @@ function isProductionSourceFile(name) {
|
|
|
5250
5514
|
return SOURCE_CODE_EXTS.some((ext) => name.endsWith(ext));
|
|
5251
5515
|
}
|
|
5252
5516
|
function collectCodeAndTestCorpora(dir, prodChunks = [], testChunks = []) {
|
|
5253
|
-
if (!
|
|
5517
|
+
if (!fs8__namespace.existsSync(dir)) return { prodCorpus: "", testCorpus: "" };
|
|
5254
5518
|
try {
|
|
5255
|
-
for (const e of
|
|
5256
|
-
const fullPath =
|
|
5519
|
+
for (const e of fs8__namespace.readdirSync(dir, { withFileTypes: true })) {
|
|
5520
|
+
const fullPath = path5__namespace.join(dir, e.name);
|
|
5257
5521
|
if (e.isDirectory() && !shouldSkipDir(e.name)) {
|
|
5258
5522
|
collectCodeAndTestCorpora(fullPath, prodChunks, testChunks);
|
|
5259
5523
|
} else if (isCodeTestFile(e.name) || e.name.endsWith(".feature")) {
|
|
5260
|
-
testChunks.push(
|
|
5524
|
+
testChunks.push(fs8__namespace.readFileSync(fullPath, "utf-8"));
|
|
5261
5525
|
} else if (isProductionSourceFile(e.name)) {
|
|
5262
|
-
prodChunks.push(
|
|
5526
|
+
prodChunks.push(fs8__namespace.readFileSync(fullPath, "utf-8"));
|
|
5263
5527
|
}
|
|
5264
5528
|
}
|
|
5265
5529
|
} catch {
|
|
@@ -5413,12 +5677,12 @@ function walkTaskFiles(dir, fileList = [], excludeDirs = [
|
|
|
5413
5677
|
"examples"
|
|
5414
5678
|
]) {
|
|
5415
5679
|
try {
|
|
5416
|
-
if (!
|
|
5417
|
-
const files =
|
|
5680
|
+
if (!fs8__namespace.existsSync(dir)) return fileList;
|
|
5681
|
+
const files = fs8__namespace.readdirSync(dir).sort(canonicalSort);
|
|
5418
5682
|
for (const file of files) {
|
|
5419
|
-
const fullPath =
|
|
5683
|
+
const fullPath = path5__namespace.join(dir, file);
|
|
5420
5684
|
try {
|
|
5421
|
-
if (
|
|
5685
|
+
if (fs8__namespace.statSync(fullPath).isDirectory()) {
|
|
5422
5686
|
if (!excludeDirs.includes(file)) {
|
|
5423
5687
|
walkTaskFiles(fullPath, fileList, excludeDirs);
|
|
5424
5688
|
}
|
|
@@ -5470,7 +5734,7 @@ function generateGovernanceReportMarkdown(summaries, violationsCount, modeCounts
|
|
|
5470
5734
|
const sections = [
|
|
5471
5735
|
`# \u{1F4CB} Task Governance and Human Autonomy Classification Report
|
|
5472
5736
|
|
|
5473
|
-
> **Audit Date:** ${(
|
|
5737
|
+
> **Audit Date:** ${resolveDeterministicDate().toISOString()}
|
|
5474
5738
|
> **Overall Verdict:** ${verdict}
|
|
5475
5739
|
|
|
5476
5740
|
---`,
|
|
@@ -5531,9 +5795,9 @@ function updateGovernanceCounts(t, modeCounts, riskCounts) {
|
|
|
5531
5795
|
}
|
|
5532
5796
|
}
|
|
5533
5797
|
function parseTasksFromFile(file) {
|
|
5534
|
-
if (!
|
|
5798
|
+
if (!fs8__namespace.existsSync(file)) return [];
|
|
5535
5799
|
try {
|
|
5536
|
-
const content =
|
|
5800
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
5537
5801
|
const { frontmatter } = parseTasksDoc(content);
|
|
5538
5802
|
return frontmatter.tasks || [];
|
|
5539
5803
|
} catch {
|
|
@@ -5541,11 +5805,11 @@ function parseTasksFromFile(file) {
|
|
|
5541
5805
|
}
|
|
5542
5806
|
}
|
|
5543
5807
|
function extractDesignExpectedScope(changeDir) {
|
|
5544
|
-
const designFile =
|
|
5545
|
-
if (!
|
|
5808
|
+
const designFile = path5__namespace.join(changeDir, "design.md");
|
|
5809
|
+
if (!fs8__namespace.existsSync(designFile)) return [];
|
|
5546
5810
|
const expected = [];
|
|
5547
5811
|
try {
|
|
5548
|
-
const { frontmatter } = parseTasksDoc(
|
|
5812
|
+
const { frontmatter } = parseTasksDoc(fs8__namespace.readFileSync(designFile, "utf-8"));
|
|
5549
5813
|
if (typeof frontmatter["architecture-component"] === "string") {
|
|
5550
5814
|
expected.push(frontmatter["architecture-component"]);
|
|
5551
5815
|
}
|
|
@@ -5585,7 +5849,7 @@ function hasExplicitScopeBindings(tasks) {
|
|
|
5585
5849
|
);
|
|
5586
5850
|
}
|
|
5587
5851
|
function auditDesignScopeForFile(file, tasks, options, unassignedList, violationsList) {
|
|
5588
|
-
const changeDir =
|
|
5852
|
+
const changeDir = path5__namespace.dirname(file);
|
|
5589
5853
|
const expectedItems = [
|
|
5590
5854
|
...extractDesignExpectedScope(changeDir),
|
|
5591
5855
|
...extractHandoffExpectedScope(changeDir, options.maxUseCasesPerChange, violationsList)
|
|
@@ -5596,13 +5860,13 @@ function auditDesignScopeForFile(file, tasks, options, unassignedList, violation
|
|
|
5596
5860
|
for (const item of new Set(expectedItems)) {
|
|
5597
5861
|
if (!covered.has(item)) {
|
|
5598
5862
|
unassignedList.push(item);
|
|
5599
|
-
violationsList.push(`[${
|
|
5863
|
+
violationsList.push(`[${path5__namespace.basename(changeDir)}] Unassigned design/handoff scope in tasks.md: missing task coverage for ${item}`);
|
|
5600
5864
|
}
|
|
5601
5865
|
}
|
|
5602
5866
|
}
|
|
5603
5867
|
function processTasksFile(file, rootDir, options, acc) {
|
|
5604
5868
|
const tasks = parseTasksFromFile(file);
|
|
5605
|
-
const relPath =
|
|
5869
|
+
const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
5606
5870
|
let unverifiedDelta = 0;
|
|
5607
5871
|
for (const t of tasks) {
|
|
5608
5872
|
const { summary, violation, unverified } = evaluateTaskItem(t, relPath);
|
|
@@ -5901,11 +6165,11 @@ async function runAllExpandedScenarios(parsed, definitions) {
|
|
|
5901
6165
|
}
|
|
5902
6166
|
async function executeBddFeature(featurePath, registry, options = {}) {
|
|
5903
6167
|
const rootDir = options.rootDir || process.cwd();
|
|
5904
|
-
const fullPath =
|
|
5905
|
-
const parsed = parseFeatureDocument(
|
|
6168
|
+
const fullPath = path5__namespace.isAbsolute(featurePath) ? featurePath : path5__namespace.join(rootDir, featurePath);
|
|
6169
|
+
const parsed = parseFeatureDocument(fs8__namespace.readFileSync(fullPath, "utf-8"));
|
|
5906
6170
|
const scenarios = await runAllExpandedScenarios(parsed, registry.getDefinitions());
|
|
5907
6171
|
const report = {
|
|
5908
|
-
uri:
|
|
6172
|
+
uri: path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/"),
|
|
5909
6173
|
featureName: parsed.featureName,
|
|
5910
6174
|
tags: parsed.tags,
|
|
5911
6175
|
scenarios,
|
|
@@ -5939,10 +6203,10 @@ function mapFeatureToCucumberJson(r) {
|
|
|
5939
6203
|
};
|
|
5940
6204
|
}
|
|
5941
6205
|
function writeCucumberJsonReport(reports, outputPath) {
|
|
5942
|
-
const dir =
|
|
5943
|
-
if (!
|
|
6206
|
+
const dir = path5__namespace.dirname(outputPath);
|
|
6207
|
+
if (!fs8__namespace.existsSync(dir)) fs8__namespace.mkdirSync(dir, { recursive: true });
|
|
5944
6208
|
const payload = JSON.stringify(reports.map(mapFeatureToCucumberJson), null, 2);
|
|
5945
|
-
|
|
6209
|
+
fs8__namespace.writeFileSync(outputPath, payload, "utf-8");
|
|
5946
6210
|
}
|
|
5947
6211
|
function isToolAvailable(toolName) {
|
|
5948
6212
|
try {
|
|
@@ -5988,8 +6252,8 @@ function findLicenseFile(pkgDir) {
|
|
|
5988
6252
|
"license.txt"
|
|
5989
6253
|
];
|
|
5990
6254
|
for (const c of candidates) {
|
|
5991
|
-
const full =
|
|
5992
|
-
if (
|
|
6255
|
+
const full = path5__namespace.join(pkgDir, c);
|
|
6256
|
+
if (fs8__namespace.existsSync(full)) {
|
|
5993
6257
|
return full;
|
|
5994
6258
|
}
|
|
5995
6259
|
}
|
|
@@ -6006,9 +6270,9 @@ var LICENSE_FILE_PATTERNS = [
|
|
|
6006
6270
|
[(t) => t.includes("Microsoft Reciprocal License") || t.includes("MS-RL"), "MS-RL"]
|
|
6007
6271
|
];
|
|
6008
6272
|
function resolveLicenseFromFile(licenseFilePath) {
|
|
6009
|
-
if (!licenseFilePath || !
|
|
6273
|
+
if (!licenseFilePath || !fs8__namespace.existsSync(licenseFilePath)) return void 0;
|
|
6010
6274
|
try {
|
|
6011
|
-
const text =
|
|
6275
|
+
const text = fs8__namespace.readFileSync(licenseFilePath, "utf-8");
|
|
6012
6276
|
for (const [matches, spdx] of LICENSE_FILE_PATTERNS) {
|
|
6013
6277
|
if (matches(text)) return spdx;
|
|
6014
6278
|
}
|
|
@@ -6017,16 +6281,16 @@ function resolveLicenseFromFile(licenseFilePath) {
|
|
|
6017
6281
|
return void 0;
|
|
6018
6282
|
}
|
|
6019
6283
|
function readPackageJson(filePath) {
|
|
6020
|
-
if (!
|
|
6284
|
+
if (!fs8__namespace.existsSync(filePath)) return null;
|
|
6021
6285
|
try {
|
|
6022
|
-
return JSON.parse(
|
|
6286
|
+
return JSON.parse(fs8__namespace.readFileSync(filePath, "utf-8"));
|
|
6023
6287
|
} catch {
|
|
6024
6288
|
return null;
|
|
6025
6289
|
}
|
|
6026
6290
|
}
|
|
6027
6291
|
function extractDirectDepNames(rootDir, productionOnly) {
|
|
6028
6292
|
const names = /* @__PURE__ */ new Set();
|
|
6029
|
-
const pkgPath =
|
|
6293
|
+
const pkgPath = path5__namespace.join(rootDir, "package.json");
|
|
6030
6294
|
const pkg = readPackageJson(pkgPath);
|
|
6031
6295
|
if (!pkg) return names;
|
|
6032
6296
|
if (pkg.dependencies && typeof pkg.dependencies === "object") {
|
|
@@ -6046,7 +6310,7 @@ function resolveSpdxForPackage(pkg, licFile) {
|
|
|
6046
6310
|
return spdx;
|
|
6047
6311
|
}
|
|
6048
6312
|
function processPackageDir(pkgDir, directNames, seen) {
|
|
6049
|
-
const pkg = readPackageJson(
|
|
6313
|
+
const pkg = readPackageJson(path5__namespace.join(pkgDir, "package.json"));
|
|
6050
6314
|
if (!pkg?.name || !pkg.version) return;
|
|
6051
6315
|
const key = `${pkg.name}@${pkg.version}`;
|
|
6052
6316
|
if (seen.has(key)) return;
|
|
@@ -6067,40 +6331,40 @@ function processPackageDir(pkgDir, directNames, seen) {
|
|
|
6067
6331
|
});
|
|
6068
6332
|
}
|
|
6069
6333
|
function scanScopeSubdirectory(scopeDir, directNames, seen) {
|
|
6070
|
-
const scopedEntries =
|
|
6334
|
+
const scopedEntries = fs8__namespace.readdirSync(scopeDir, { withFileTypes: true });
|
|
6071
6335
|
for (const scoped of scopedEntries) {
|
|
6072
6336
|
if (scoped.isDirectory()) {
|
|
6073
|
-
processPackageDir(
|
|
6337
|
+
processPackageDir(path5__namespace.join(scopeDir, scoped.name), directNames, seen);
|
|
6074
6338
|
}
|
|
6075
6339
|
}
|
|
6076
6340
|
}
|
|
6077
6341
|
function scanSubdirectories(baseDir, directNames, seen) {
|
|
6078
|
-
if (!
|
|
6079
|
-
const entries =
|
|
6342
|
+
if (!fs8__namespace.existsSync(baseDir)) return;
|
|
6343
|
+
const entries = fs8__namespace.readdirSync(baseDir, { withFileTypes: true });
|
|
6080
6344
|
for (const entry of entries) {
|
|
6081
6345
|
if (!entry.isDirectory()) continue;
|
|
6082
6346
|
if (entry.name.startsWith("@")) {
|
|
6083
|
-
scanScopeSubdirectory(
|
|
6347
|
+
scanScopeSubdirectory(path5__namespace.join(baseDir, entry.name), directNames, seen);
|
|
6084
6348
|
} else if (entry.name !== ".bin" && entry.name !== ".pnpm") {
|
|
6085
|
-
processPackageDir(
|
|
6349
|
+
processPackageDir(path5__namespace.join(baseDir, entry.name), directNames, seen);
|
|
6086
6350
|
}
|
|
6087
6351
|
}
|
|
6088
6352
|
}
|
|
6089
6353
|
function scanPnpmStore(pnpmDir, directNames, seen) {
|
|
6090
|
-
if (!
|
|
6091
|
-
const entries =
|
|
6354
|
+
if (!fs8__namespace.existsSync(pnpmDir)) return;
|
|
6355
|
+
const entries = fs8__namespace.readdirSync(pnpmDir, { withFileTypes: true });
|
|
6092
6356
|
for (const entry of entries) {
|
|
6093
6357
|
if (!entry.isDirectory()) continue;
|
|
6094
|
-
const candidateModules =
|
|
6095
|
-
if (
|
|
6358
|
+
const candidateModules = path5__namespace.join(pnpmDir, entry.name, "node_modules");
|
|
6359
|
+
if (fs8__namespace.existsSync(candidateModules)) {
|
|
6096
6360
|
scanSubdirectories(candidateModules, directNames, seen);
|
|
6097
6361
|
}
|
|
6098
6362
|
}
|
|
6099
6363
|
}
|
|
6100
6364
|
function readTextIfExists(filePath) {
|
|
6101
|
-
if (!
|
|
6365
|
+
if (!fs8__namespace.existsSync(filePath)) return null;
|
|
6102
6366
|
try {
|
|
6103
|
-
return
|
|
6367
|
+
return fs8__namespace.readFileSync(filePath, "utf-8");
|
|
6104
6368
|
} catch {
|
|
6105
6369
|
return null;
|
|
6106
6370
|
}
|
|
@@ -6160,14 +6424,14 @@ function parsePipfileSection(pkgs, isDirect, lockPath, seen) {
|
|
|
6160
6424
|
}
|
|
6161
6425
|
}
|
|
6162
6426
|
function scanPythonDependencies(rootDir, seen) {
|
|
6163
|
-
const pyproject = readTextIfExists(
|
|
6427
|
+
const pyproject = readTextIfExists(path5__namespace.join(rootDir, "pyproject.toml")) || "";
|
|
6164
6428
|
const directNames = extractPyprojectDirectDeps(pyproject);
|
|
6165
6429
|
for (const lockName of ["poetry.lock", "uv.lock"]) {
|
|
6166
|
-
const lockPath =
|
|
6430
|
+
const lockPath = path5__namespace.join(rootDir, lockName);
|
|
6167
6431
|
const content = readTextIfExists(lockPath);
|
|
6168
6432
|
if (content) parseTomlPackageBlocks(content, lockPath, directNames, seen);
|
|
6169
6433
|
}
|
|
6170
|
-
const pipfileLockPath =
|
|
6434
|
+
const pipfileLockPath = path5__namespace.join(rootDir, "Pipfile.lock");
|
|
6171
6435
|
const pipfileRaw = readTextIfExists(pipfileLockPath);
|
|
6172
6436
|
if (!pipfileRaw) return;
|
|
6173
6437
|
try {
|
|
@@ -6210,7 +6474,7 @@ function extractGoCandidateLines(content) {
|
|
|
6210
6474
|
return out;
|
|
6211
6475
|
}
|
|
6212
6476
|
function scanGoDependencies(rootDir, seen) {
|
|
6213
|
-
const goModPath =
|
|
6477
|
+
const goModPath = path5__namespace.join(rootDir, "go.mod");
|
|
6214
6478
|
const content = readTextIfExists(goModPath);
|
|
6215
6479
|
if (!content) return;
|
|
6216
6480
|
for (const line of extractGoCandidateLines(content)) {
|
|
@@ -6253,10 +6517,10 @@ function parseCargoLockBlock(block, rootName, directMeta, cargoLockPath) {
|
|
|
6253
6517
|
};
|
|
6254
6518
|
}
|
|
6255
6519
|
function scanRustDependencies(rootDir, seen) {
|
|
6256
|
-
const cargoLockPath =
|
|
6520
|
+
const cargoLockPath = path5__namespace.join(rootDir, "Cargo.lock");
|
|
6257
6521
|
const lockContent = readTextIfExists(cargoLockPath);
|
|
6258
6522
|
if (!lockContent) return;
|
|
6259
|
-
const cargoToml = readTextIfExists(
|
|
6523
|
+
const cargoToml = readTextIfExists(path5__namespace.join(rootDir, "Cargo.toml")) || "";
|
|
6260
6524
|
const directMeta = extractCargoDirectDeps(cargoToml);
|
|
6261
6525
|
const rootName = extractTomlField(cargoToml, "name");
|
|
6262
6526
|
for (const block of lockContent.split(/\[\[package\]\]/g).slice(1)) {
|
|
@@ -6292,7 +6556,7 @@ function parseMavenDepBlock(block, defaultLic, pomPath) {
|
|
|
6292
6556
|
};
|
|
6293
6557
|
}
|
|
6294
6558
|
function scanMavenDependencies(rootDir, seen) {
|
|
6295
|
-
const pomPath =
|
|
6559
|
+
const pomPath = path5__namespace.join(rootDir, "pom.xml");
|
|
6296
6560
|
const content = readTextIfExists(pomPath);
|
|
6297
6561
|
if (!content) return;
|
|
6298
6562
|
const projectLicBlock = content.match(/<licenses>([\s\S]*?)<\/licenses>/)?.[1] || "";
|
|
@@ -6308,11 +6572,11 @@ function scanMavenDependencies(rootDir, seen) {
|
|
|
6308
6572
|
function scanWithNativeFs(rootDir, options = {}) {
|
|
6309
6573
|
const directNames = extractDirectDepNames(rootDir, options.productionOnly);
|
|
6310
6574
|
const seen = /* @__PURE__ */ new Map();
|
|
6311
|
-
const nmDir =
|
|
6312
|
-
if (
|
|
6575
|
+
const nmDir = path5__namespace.join(rootDir, "node_modules");
|
|
6576
|
+
if (fs8__namespace.existsSync(nmDir)) {
|
|
6313
6577
|
scanSubdirectories(nmDir, directNames, seen);
|
|
6314
6578
|
if (options.depth !== "direct") {
|
|
6315
|
-
scanPnpmStore(
|
|
6579
|
+
scanPnpmStore(path5__namespace.join(nmDir, ".pnpm"), directNames, seen);
|
|
6316
6580
|
}
|
|
6317
6581
|
}
|
|
6318
6582
|
scanPythonDependencies(rootDir, seen);
|
|
@@ -6571,20 +6835,20 @@ function resolveProjectIdentity(options) {
|
|
|
6571
6835
|
let name = options.projectName || "ai-sdlc-project";
|
|
6572
6836
|
let version = options.projectVersion || "1.0.0";
|
|
6573
6837
|
if (!options.rootDir) return { name, version };
|
|
6574
|
-
const pkgPath =
|
|
6575
|
-
if (!
|
|
6838
|
+
const pkgPath = path5__namespace.join(options.rootDir, "package.json");
|
|
6839
|
+
if (!fs8__namespace.existsSync(pkgPath)) return { name, version };
|
|
6576
6840
|
try {
|
|
6577
|
-
const pkg = JSON.parse(
|
|
6841
|
+
const pkg = JSON.parse(fs8__namespace.readFileSync(pkgPath, "utf-8"));
|
|
6578
6842
|
if (pkg.name) name = pkg.name;
|
|
6579
6843
|
if (pkg.version) version = pkg.version;
|
|
6580
6844
|
} catch {
|
|
6581
6845
|
}
|
|
6582
6846
|
return { name, version };
|
|
6583
6847
|
}
|
|
6584
|
-
function buildBomMetadata(name, version) {
|
|
6848
|
+
function buildBomMetadata(name, version, rootDir) {
|
|
6585
6849
|
return {
|
|
6586
|
-
timestamp: (
|
|
6587
|
-
tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.
|
|
6850
|
+
timestamp: resolveDeterministicDate(rootDir).toISOString(),
|
|
6851
|
+
tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.5.0" }],
|
|
6588
6852
|
component: { type: "application", name, version }
|
|
6589
6853
|
};
|
|
6590
6854
|
}
|
|
@@ -6595,52 +6859,21 @@ function generateCycloneDxSbom(dependencies, options = {}) {
|
|
|
6595
6859
|
bomFormat: "CycloneDX",
|
|
6596
6860
|
specVersion: "1.5",
|
|
6597
6861
|
version: 1,
|
|
6598
|
-
serialNumber: `urn:uuid:${
|
|
6599
|
-
metadata: buildBomMetadata(name, version),
|
|
6862
|
+
serialNumber: `urn:uuid:${generateDeterministicUuidV5(`${name}@${version}|${dependencies.map((d) => `${d.name}@${d.version}:${d.purl || ""}`).sort(canonicalSort).join(";")}`)}`,
|
|
6863
|
+
metadata: buildBomMetadata(name, version, options.rootDir),
|
|
6600
6864
|
components: dependencies.map(buildCycloneDxComponent),
|
|
6601
6865
|
...cdxVulns.length > 0 ? { vulnerabilities: cdxVulns } : {}
|
|
6602
6866
|
};
|
|
6603
6867
|
}
|
|
6604
|
-
function tryAtomicWrite(filePath, content) {
|
|
6605
|
-
const tempPath = `${filePath}.${process.pid}.${Math.random().toString(36).substring(2)}.tmp`;
|
|
6606
|
-
try {
|
|
6607
|
-
fs7__namespace.writeFileSync(tempPath, content, "utf-8");
|
|
6608
|
-
try {
|
|
6609
|
-
if (fs7__namespace.existsSync(filePath)) {
|
|
6610
|
-
fs7__namespace.rmSync(filePath, { force: true });
|
|
6611
|
-
}
|
|
6612
|
-
fs7__namespace.renameSync(tempPath, filePath);
|
|
6613
|
-
} catch {
|
|
6614
|
-
fs7__namespace.copyFileSync(tempPath, filePath);
|
|
6615
|
-
fs7__namespace.unlinkSync(tempPath);
|
|
6616
|
-
}
|
|
6617
|
-
return true;
|
|
6618
|
-
} catch {
|
|
6619
|
-
return false;
|
|
6620
|
-
}
|
|
6621
|
-
}
|
|
6622
|
-
function safeWriteFileSync(filePath, content) {
|
|
6623
|
-
const dir = path4__namespace.dirname(filePath);
|
|
6624
|
-
if (!fs7__namespace.existsSync(dir)) {
|
|
6625
|
-
fs7__namespace.mkdirSync(dir, { recursive: true });
|
|
6626
|
-
}
|
|
6627
|
-
for (let attempt = 0; attempt < 10; attempt++) {
|
|
6628
|
-
if (tryAtomicWrite(filePath, content)) return;
|
|
6629
|
-
}
|
|
6630
|
-
try {
|
|
6631
|
-
fs7__namespace.writeFileSync(filePath, content, "utf-8");
|
|
6632
|
-
} catch {
|
|
6633
|
-
}
|
|
6634
|
-
}
|
|
6635
6868
|
function writeCycloneDxSbom(sbom, outputPath) {
|
|
6636
|
-
|
|
6869
|
+
writeReportFileIfChanged(outputPath, JSON.stringify(sbom, null, 2));
|
|
6637
6870
|
}
|
|
6638
6871
|
function buildThirdPartyHeader(projectName, count) {
|
|
6639
6872
|
return [
|
|
6640
6873
|
`# \u{1F4DC} Third-Party Software Notices and Information`,
|
|
6641
6874
|
``,
|
|
6642
6875
|
`> **Proyecto:** ${projectName}`,
|
|
6643
|
-
`> **Fecha:** ${(
|
|
6876
|
+
`> **Fecha:** ${resolveDeterministicDate().toISOString().split("T")[0]}`,
|
|
6644
6877
|
`> **Total Componentes:** ${count}`,
|
|
6645
6878
|
``,
|
|
6646
6879
|
`Este documento contiene los avisos de copyright y t\xE9rminos de licencia aplicables a los componentes de terceros utilizados en este proyecto.`,
|
|
@@ -6654,9 +6887,9 @@ function buildThirdPartyHeader(projectName, count) {
|
|
|
6654
6887
|
];
|
|
6655
6888
|
}
|
|
6656
6889
|
function readLicenseTextPreview(licenseFile) {
|
|
6657
|
-
if (!licenseFile || !
|
|
6890
|
+
if (!licenseFile || !fs8__namespace.existsSync(licenseFile)) return null;
|
|
6658
6891
|
try {
|
|
6659
|
-
const text =
|
|
6892
|
+
const text = fs8__namespace.readFileSync(licenseFile, "utf-8").trim();
|
|
6660
6893
|
return text.slice(0, 1e3) + (text.length > 1e3 ? "\n... (texto truncado)" : "");
|
|
6661
6894
|
} catch {
|
|
6662
6895
|
return null;
|
|
@@ -6688,13 +6921,13 @@ function generateThirdPartyNotices(dependencies, projectName = "AI-SDLC") {
|
|
|
6688
6921
|
"",
|
|
6689
6922
|
"---",
|
|
6690
6923
|
"",
|
|
6691
|
-
"##
|
|
6924
|
+
"## 2. Atribuciones y T\xE9rminos de Licencia Individuales",
|
|
6692
6925
|
"",
|
|
6693
6926
|
...attributions
|
|
6694
6927
|
].join("\n");
|
|
6695
6928
|
}
|
|
6696
|
-
function writeThirdPartyNotices(
|
|
6697
|
-
|
|
6929
|
+
function writeThirdPartyNotices(noticesContent, outputPath) {
|
|
6930
|
+
writeReportFileIfChanged(outputPath, noticesContent);
|
|
6698
6931
|
}
|
|
6699
6932
|
|
|
6700
6933
|
// src/verifiers/licenses.ts
|
|
@@ -6771,22 +7004,20 @@ function parseVexStatements(doc) {
|
|
|
6771
7004
|
if (!Array.isArray(rawList)) return [];
|
|
6772
7005
|
return rawList.filter((item) => Boolean(item && typeof item.id === "string" && typeof item.state === "string"));
|
|
6773
7006
|
}
|
|
6774
|
-
function
|
|
7007
|
+
function parseLicenseDoc(content) {
|
|
7008
|
+
let doc;
|
|
6775
7009
|
try {
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
rawPolicy: doc
|
|
6788
|
-
};
|
|
6789
|
-
} catch {
|
|
7010
|
+
doc = yaml3__default.default.load(content);
|
|
7011
|
+
} catch (err) {
|
|
7012
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in license-policy: ${err instanceof Error ? err.message : String(err)}`);
|
|
7013
|
+
}
|
|
7014
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
7015
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid license policy structure: root must be a YAML mapping`);
|
|
7016
|
+
}
|
|
7017
|
+
return doc;
|
|
7018
|
+
}
|
|
7019
|
+
function parseLicensePolicy(content) {
|
|
7020
|
+
if (!content || !content.trim()) {
|
|
6790
7021
|
return {
|
|
6791
7022
|
version: "1.0",
|
|
6792
7023
|
permitted: DEFAULT_PERMITTED,
|
|
@@ -6796,6 +7027,17 @@ function parseLicensePolicy(content) {
|
|
|
6796
7027
|
vex_statements: []
|
|
6797
7028
|
};
|
|
6798
7029
|
}
|
|
7030
|
+
const doc = parseLicenseDoc(content);
|
|
7031
|
+
const { permitted, restricted, blocked } = classifyCategories(doc?.categories);
|
|
7032
|
+
return {
|
|
7033
|
+
version: doc?.version || "1.0",
|
|
7034
|
+
permitted: permitted.length > 0 ? permitted : DEFAULT_PERMITTED,
|
|
7035
|
+
restricted: restricted.length > 0 ? restricted : DEFAULT_RESTRICTED,
|
|
7036
|
+
blocked: blocked.length > 0 ? blocked : DEFAULT_BLOCKED,
|
|
7037
|
+
exception_packages: parseExceptions(doc?.exceptions),
|
|
7038
|
+
vex_statements: parseVexStatements(doc),
|
|
7039
|
+
rawPolicy: doc
|
|
7040
|
+
};
|
|
6799
7041
|
}
|
|
6800
7042
|
function evaluateCompoundLicense(lic, policy) {
|
|
6801
7043
|
const cleanLic = lic.replace(/[()]/g, "").trim();
|
|
@@ -6887,7 +7129,7 @@ function buildReportHeader(isOk, depCount, violationCount, sbomPath) {
|
|
|
6887
7129
|
return [
|
|
6888
7130
|
`# \u{1F4DC} Open Source License Compliance Audit (Dynamic SCA)`,
|
|
6889
7131
|
``,
|
|
6890
|
-
`> **Verification Date:** ${(
|
|
7132
|
+
`> **Verification Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
6891
7133
|
`> **Status:** ${statusStr}`,
|
|
6892
7134
|
`> **Total Audited Dependencies:** ${depCount}`,
|
|
6893
7135
|
sbomPath ? `> **Generated CycloneDX SBOM:** \`${sbomPath}\`` : "",
|
|
@@ -6912,9 +7154,9 @@ function generateLicenseReportMarkdown(dependencies, violations, policy, sbomPat
|
|
|
6912
7154
|
return lines.filter(Boolean).join("\n");
|
|
6913
7155
|
}
|
|
6914
7156
|
function parseManifestItems(manifestFile) {
|
|
6915
|
-
if (!
|
|
7157
|
+
if (!fs8__namespace.existsSync(manifestFile)) return [];
|
|
6916
7158
|
try {
|
|
6917
|
-
const doc = yaml3__default.default.load(
|
|
7159
|
+
const doc = yaml3__default.default.load(fs8__namespace.readFileSync(manifestFile, "utf-8"));
|
|
6918
7160
|
return Array.isArray(doc?.evaluated_dependencies) ? doc.evaluated_dependencies : [];
|
|
6919
7161
|
} catch {
|
|
6920
7162
|
return [];
|
|
@@ -6946,10 +7188,10 @@ function verifyStaticManifest(manifestFile, policy) {
|
|
|
6946
7188
|
function resolveManifestPath(rootDir, customPath) {
|
|
6947
7189
|
if (customPath) return customPath;
|
|
6948
7190
|
const candidates = [
|
|
6949
|
-
|
|
6950
|
-
|
|
7191
|
+
path5__namespace.join(rootDir, "compliance", "license-manifest.yaml"),
|
|
7192
|
+
path5__namespace.join(rootDir, "license-manifest.yaml")
|
|
6951
7193
|
];
|
|
6952
|
-
return candidates.find((p) =>
|
|
7194
|
+
return candidates.find((p) => fs8__namespace.existsSync(p)) || candidates[0];
|
|
6953
7195
|
}
|
|
6954
7196
|
function handleStaticManifestVerification(options, rootDir, policy) {
|
|
6955
7197
|
const manifestFile = resolveManifestPath(rootDir, options.manifestPath);
|
|
@@ -6970,7 +7212,7 @@ function shouldGenerateSbom(options, rawPolicy) {
|
|
|
6970
7212
|
}
|
|
6971
7213
|
function maybeGenerateSbom(options, rootDir, rawPolicy, dependencies, vulnerabilities, vexStatements) {
|
|
6972
7214
|
if (!shouldGenerateSbom(options, rawPolicy)) return void 0;
|
|
6973
|
-
const sbomPath = options.sbomPath ||
|
|
7215
|
+
const sbomPath = options.sbomPath || path5__namespace.join(rootDir, "reports", "sbom.cdx.json");
|
|
6974
7216
|
const sbom = generateCycloneDxSbom(dependencies, { rootDir, vulnerabilities, vexStatements });
|
|
6975
7217
|
writeCycloneDxSbom(sbom, sbomPath);
|
|
6976
7218
|
return sbomPath;
|
|
@@ -6979,7 +7221,7 @@ function maybeGenerateNotices(options, rootDir, rawPolicy, dependencies) {
|
|
|
6979
7221
|
const shouldGen = options.generateNotices === true || Boolean(options.noticesPath || rawPolicy?.ci_gates?.output_notices_file);
|
|
6980
7222
|
if (!shouldGen) return void 0;
|
|
6981
7223
|
const noticesFileName = rawPolicy?.ci_gates?.output_notices_file || "THIRD_PARTY_NOTICES.md";
|
|
6982
|
-
const noticesPath = options.noticesPath ||
|
|
7224
|
+
const noticesPath = options.noticesPath || path5__namespace.join(rootDir, noticesFileName);
|
|
6983
7225
|
const noticesContent = generateThirdPartyNotices(dependencies);
|
|
6984
7226
|
writeThirdPartyNotices(noticesContent, noticesPath);
|
|
6985
7227
|
return noticesPath;
|
|
@@ -6991,8 +7233,8 @@ function handleDynamicScaArtifacts(options, rootDir, rawPolicy, dependencies, vu
|
|
|
6991
7233
|
};
|
|
6992
7234
|
}
|
|
6993
7235
|
function loadLicensePolicy(rootDir, customPath) {
|
|
6994
|
-
const policyFile = customPath ||
|
|
6995
|
-
return
|
|
7236
|
+
const policyFile = customPath || path5__namespace.join(rootDir, "license-policy.yaml");
|
|
7237
|
+
return fs8__namespace.existsSync(policyFile) ? parseLicensePolicy(fs8__namespace.readFileSync(policyFile, "utf-8")) : parseLicensePolicy("");
|
|
6996
7238
|
}
|
|
6997
7239
|
function evaluateDependencyList(dependencies, policy) {
|
|
6998
7240
|
const violations = [];
|
|
@@ -7096,11 +7338,11 @@ var PROTECTED_BYPASS_PATTERNS = [
|
|
|
7096
7338
|
/migrations?[\\/]/i
|
|
7097
7339
|
];
|
|
7098
7340
|
function detectProfileFromChange(changeDir) {
|
|
7099
|
-
if (!
|
|
7100
|
-
const specFile =
|
|
7101
|
-
if (
|
|
7341
|
+
if (!fs8__namespace.existsSync(changeDir)) return "standard";
|
|
7342
|
+
const specFile = path5__namespace.join(changeDir, "spec.md");
|
|
7343
|
+
if (fs8__namespace.existsSync(specFile)) {
|
|
7102
7344
|
try {
|
|
7103
|
-
const rawSpec =
|
|
7345
|
+
const rawSpec = fs8__namespace.readFileSync(specFile, "utf-8").replace(/^\uFEFF/, "");
|
|
7104
7346
|
const match = rawSpec.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
7105
7347
|
if (match) {
|
|
7106
7348
|
const parsedFm = yaml3__default.default.load(match[1]);
|
|
@@ -7118,24 +7360,29 @@ function isPathProtectedFromPatch(filePath) {
|
|
|
7118
7360
|
return PROTECTED_BYPASS_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
7119
7361
|
}
|
|
7120
7362
|
function detectBranchChangeMatch(rootDir, dirs) {
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7363
|
+
let branch = "";
|
|
7364
|
+
if (fs8__namespace.existsSync(path5__namespace.join(rootDir, ".git"))) {
|
|
7365
|
+
try {
|
|
7366
|
+
branch = child_process.execFileSync("git", ["branch", "--show-current"], {
|
|
7367
|
+
cwd: rootDir,
|
|
7368
|
+
encoding: "utf-8",
|
|
7369
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
7370
|
+
}).trim().toLowerCase();
|
|
7371
|
+
} catch {
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
if (!branch) {
|
|
7375
|
+
const ciBranch = (process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_REF_NAME || process.env.BRANCH_NAME || "").toLowerCase();
|
|
7376
|
+
branch = ciBranch;
|
|
7132
7377
|
}
|
|
7378
|
+
if (!branch) return void 0;
|
|
7379
|
+
return dirs.find((d) => branch.includes(path5__namespace.basename(d).toLowerCase()));
|
|
7133
7380
|
}
|
|
7134
7381
|
function resolveActiveChangeDir(rootDir) {
|
|
7135
|
-
const activeRoot =
|
|
7136
|
-
if (!
|
|
7382
|
+
const activeRoot = path5__namespace.join(rootDir, "specs", "changes", "active");
|
|
7383
|
+
if (!fs8__namespace.existsSync(activeRoot)) return void 0;
|
|
7137
7384
|
try {
|
|
7138
|
-
const dirs =
|
|
7385
|
+
const dirs = fs8__namespace.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path5__namespace.join(activeRoot, d.name)).sort((a, b) => canonicalSort(a, b));
|
|
7139
7386
|
const branchMatch = detectBranchChangeMatch(rootDir, dirs);
|
|
7140
7387
|
if (branchMatch) return branchMatch;
|
|
7141
7388
|
return dirs.find((d) => detectProfileFromChange(d) === "patch") || dirs[0];
|
|
@@ -7143,8 +7390,12 @@ function resolveActiveChangeDir(rootDir) {
|
|
|
7143
7390
|
return void 0;
|
|
7144
7391
|
}
|
|
7145
7392
|
}
|
|
7393
|
+
function isEphemeralOrIgnored(file) {
|
|
7394
|
+
const norm = file.replace(/\\/g, "/");
|
|
7395
|
+
return norm.startsWith("scratch/") || norm.startsWith("reports/") || norm.startsWith("coverage/") || norm.startsWith("dist/") || norm.startsWith("temp/") || norm.startsWith("tmp/");
|
|
7396
|
+
}
|
|
7146
7397
|
function detectGitDiffFiles(rootDir) {
|
|
7147
|
-
if (!
|
|
7398
|
+
if (!fs8__namespace.existsSync(path5__namespace.join(rootDir, ".git"))) return [];
|
|
7148
7399
|
try {
|
|
7149
7400
|
const tracked = child_process.execFileSync("git", ["diff", "--name-only", "HEAD"], {
|
|
7150
7401
|
cwd: rootDir,
|
|
@@ -7157,7 +7408,7 @@ function detectGitDiffFiles(rootDir) {
|
|
|
7157
7408
|
stdio: ["pipe", "pipe", "ignore"]
|
|
7158
7409
|
});
|
|
7159
7410
|
const combined = `${tracked}
|
|
7160
|
-
${untracked}`.split(/\r?\n/).map((f) => f.trim()).filter(Boolean);
|
|
7411
|
+
${untracked}`.split(/\r?\n/).map((f) => f.trim()).filter((f) => Boolean(f) && !isEphemeralOrIgnored(f));
|
|
7161
7412
|
return Array.from(new Set(combined));
|
|
7162
7413
|
} catch {
|
|
7163
7414
|
return [];
|
|
@@ -7167,19 +7418,19 @@ function resolveChangeDirectory(rootDir, options) {
|
|
|
7167
7418
|
if (options.changeDir) return options.changeDir;
|
|
7168
7419
|
if (options.changeId) {
|
|
7169
7420
|
const candidates = [
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7421
|
+
path5__namespace.join(rootDir, "specs", options.changeId),
|
|
7422
|
+
path5__namespace.join(rootDir, "specs", "changes", "active", options.changeId),
|
|
7423
|
+
path5__namespace.join(rootDir, "examples", "specs", options.changeId),
|
|
7424
|
+
path5__namespace.join(rootDir, "specs", "changes", "completed", options.changeId)
|
|
7174
7425
|
];
|
|
7175
|
-
return candidates.find((c) =>
|
|
7426
|
+
return candidates.find((c) => fs8__namespace.existsSync(c));
|
|
7176
7427
|
}
|
|
7177
7428
|
return resolveActiveChangeDir(rootDir);
|
|
7178
7429
|
}
|
|
7179
7430
|
function collectPatchViolations(rootDir, modifiedFiles) {
|
|
7180
7431
|
const violations = [];
|
|
7181
7432
|
for (const rawFile of modifiedFiles) {
|
|
7182
|
-
const relPath =
|
|
7433
|
+
const relPath = path5__namespace.isAbsolute(rawFile) ? path5__namespace.relative(rootDir, rawFile) : rawFile;
|
|
7183
7434
|
if (isPathProtectedFromPatch(relPath)) {
|
|
7184
7435
|
violations.push(
|
|
7185
7436
|
`[ANTI-PATCH-BYPASS] File '${relPath}' is a protected path (security, schemas, architectural decisions, or master policies). Changes with 'patch' profile are strictly prohibited from modifying these paths. Must be reclassified to 'standard' or 'critical'.`
|
|
@@ -7192,7 +7443,7 @@ function buildFrictionHeader(profile, success, fileCount) {
|
|
|
7192
7443
|
return [
|
|
7193
7444
|
`# \u{1F6E1}\uFE0F Progressive Friction and Anti-Bypass Verification Report`,
|
|
7194
7445
|
``,
|
|
7195
|
-
`> **Date:** ${(
|
|
7446
|
+
`> **Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
7196
7447
|
`> **Evaluated Profile:** \`${profile.toUpperCase()}\``,
|
|
7197
7448
|
`> **Verdict:** ${success ? "COMPLIANT \u2705" : "BLOCKED \u274C"}`,
|
|
7198
7449
|
``,
|
|
@@ -7285,13 +7536,13 @@ function extractManualMetadata(fm, id, manualType, relPath, body) {
|
|
|
7285
7536
|
}
|
|
7286
7537
|
function buildManualItem(rootDir, filePath) {
|
|
7287
7538
|
try {
|
|
7288
|
-
const raw =
|
|
7539
|
+
const raw = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
7289
7540
|
const { fm, body } = parseMdFrontmatter(raw);
|
|
7290
7541
|
const id = typeof fm.id === "string" ? fm.id.trim() : "";
|
|
7291
7542
|
const rawType = typeof fm.type === "string" ? fm.type.trim() : "";
|
|
7292
7543
|
const manualType = resolveManualType(id, rawType);
|
|
7293
7544
|
if (!id || !manualType) return null;
|
|
7294
|
-
const relPath =
|
|
7545
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
7295
7546
|
return extractManualMetadata(fm, id, manualType, relPath, body);
|
|
7296
7547
|
} catch {
|
|
7297
7548
|
return null;
|
|
@@ -7302,8 +7553,8 @@ function scanManualsInWorkspace(rootDir) {
|
|
|
7302
7553
|
const seenIds = /* @__PURE__ */ new Set();
|
|
7303
7554
|
const items = [];
|
|
7304
7555
|
for (const relDir of manualDirs) {
|
|
7305
|
-
const fullDir =
|
|
7306
|
-
if (!
|
|
7556
|
+
const fullDir = path5__namespace.join(rootDir, relDir);
|
|
7557
|
+
if (!fs8__namespace.existsSync(fullDir)) continue;
|
|
7307
7558
|
for (const file of walkMdFiles(fullDir)) {
|
|
7308
7559
|
const item = buildManualItem(rootDir, file);
|
|
7309
7560
|
if (item && !seenIds.has(item.id)) {
|
|
@@ -7326,10 +7577,10 @@ function classifyBaselineId(id, type, buckets) {
|
|
|
7326
7577
|
}
|
|
7327
7578
|
}
|
|
7328
7579
|
function processBaselineFile(file, rootDir, buckets) {
|
|
7329
|
-
const rel =
|
|
7580
|
+
const rel = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
7330
7581
|
if (rel.includes("/changes/") || rel.startsWith("changes/")) return;
|
|
7331
7582
|
try {
|
|
7332
|
-
const { fm } = parseMdFrontmatter(
|
|
7583
|
+
const { fm } = parseMdFrontmatter(fs8__namespace.readFileSync(file, "utf-8"));
|
|
7333
7584
|
const id = typeof fm.id === "string" ? fm.id.trim() : "";
|
|
7334
7585
|
const status = typeof fm.status === "string" ? fm.status.trim().toLowerCase() : "active";
|
|
7335
7586
|
if (!id || status === "retired" || status === "deprecated") return;
|
|
@@ -7350,8 +7601,8 @@ function collectBaselineTargets(rootDir) {
|
|
|
7350
7601
|
enclaves: /* @__PURE__ */ new Set()
|
|
7351
7602
|
};
|
|
7352
7603
|
for (const dir of ["product", "architecture", "security", "specs"]) {
|
|
7353
|
-
const fullDir =
|
|
7354
|
-
if (
|
|
7604
|
+
const fullDir = path5__namespace.join(rootDir, dir);
|
|
7605
|
+
if (fs8__namespace.existsSync(fullDir)) {
|
|
7355
7606
|
scanDirectoryForBaseline(rootDir, fullDir, buckets);
|
|
7356
7607
|
}
|
|
7357
7608
|
}
|
|
@@ -7408,10 +7659,10 @@ function detectCurrentGitBranch(rootDir) {
|
|
|
7408
7659
|
}
|
|
7409
7660
|
}
|
|
7410
7661
|
function resolveActiveChangeProfile(rootDir) {
|
|
7411
|
-
const activeRoot =
|
|
7412
|
-
if (!
|
|
7662
|
+
const activeRoot = path5__namespace.join(rootDir, "specs", "changes", "active");
|
|
7663
|
+
if (!fs8__namespace.existsSync(activeRoot)) return "standard";
|
|
7413
7664
|
try {
|
|
7414
|
-
const dirs =
|
|
7665
|
+
const dirs = fs8__namespace.readdirSync(activeRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path5__namespace.join(activeRoot, d.name));
|
|
7415
7666
|
const profiles = dirs.map((d) => detectProfileFromChange(d));
|
|
7416
7667
|
if (profiles.includes("critical")) return "critical";
|
|
7417
7668
|
if (profiles.includes("patch") && profiles.length === 1) return "patch";
|
|
@@ -7548,7 +7799,7 @@ var REQ_TYPES = /* @__PURE__ */ new Set(["requirement", "security-requirement",
|
|
|
7548
7799
|
var REQ_CATEGORIES = /* @__PURE__ */ new Set(["functional", "quality", "constraint", "security", "safety"]);
|
|
7549
7800
|
function computeSha256(content) {
|
|
7550
7801
|
const normalized = content.replace(/\r\n/g, "\n");
|
|
7551
|
-
return
|
|
7802
|
+
return crypto3__namespace.createHash("sha256").update(normalized, "utf-8").digest("hex");
|
|
7552
7803
|
}
|
|
7553
7804
|
function tokenizeTitle(title) {
|
|
7554
7805
|
const words = title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOP_WORDS.has(w));
|
|
@@ -7624,11 +7875,11 @@ function buildRequirementObject(content, fm, body, filePath, relFile) {
|
|
|
7624
7875
|
}
|
|
7625
7876
|
function parseRequirementFile(filePath, rootDir) {
|
|
7626
7877
|
try {
|
|
7627
|
-
const content =
|
|
7878
|
+
const content = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
7628
7879
|
const extracted = extractParsedFrontmatter(content);
|
|
7629
7880
|
if (!extracted) return null;
|
|
7630
7881
|
const body = content.substring(extracted.matchLen);
|
|
7631
|
-
const relFile =
|
|
7882
|
+
const relFile = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
7632
7883
|
return buildRequirementObject(content, extracted.fm, body, filePath, relFile);
|
|
7633
7884
|
} catch {
|
|
7634
7885
|
return null;
|
|
@@ -7640,7 +7891,7 @@ function isIgnoredScanPath(relFile) {
|
|
|
7640
7891
|
function collectRequirements(rootDir) {
|
|
7641
7892
|
const reqs = [];
|
|
7642
7893
|
for (const file of walkMdFiles(rootDir)) {
|
|
7643
|
-
const relFile =
|
|
7894
|
+
const relFile = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
7644
7895
|
if (isIgnoredScanPath(relFile)) continue;
|
|
7645
7896
|
const req = parseRequirementFile(file, rootDir);
|
|
7646
7897
|
if (req) reqs.push(req);
|
|
@@ -7774,7 +8025,7 @@ function buildDuplicatesHeader(result) {
|
|
|
7774
8025
|
return [
|
|
7775
8026
|
`# \u{1F6E1}\uFE0F Duplicate Requirements and Redundancy Audit (Shift-Left Pre-Flight)`,
|
|
7776
8027
|
``,
|
|
7777
|
-
`> **Evaluation Date:** ${(
|
|
8028
|
+
`> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
7778
8029
|
`> **Verdict:** ${isOk ? "\u2705 COMPLIANT (No blocking duplicates)" : `\u274C BLOCKED (${result.errorCount} duplicate errors detected)`}`,
|
|
7779
8030
|
`> **Audited Requirements:** ${result.totalRequirements} | **Errors:** ${result.errorCount} | **Warnings:** ${result.warningCount}`,
|
|
7780
8031
|
``,
|
|
@@ -7897,7 +8148,7 @@ function buildAuditHeader(total, active, expired, invalid) {
|
|
|
7897
8148
|
return [
|
|
7898
8149
|
"# \u{1F4CB} Security Suppressions and Audit Exceptions Inventory",
|
|
7899
8150
|
"",
|
|
7900
|
-
`> **Generated at:** ${(
|
|
8151
|
+
`> **Generated at:** ${resolveDeterministicDate().toISOString()}`,
|
|
7901
8152
|
`> **Total Audited Suppressions:** ${total} | **Active:** ${active} | **Expired:** ${expired} | **Invalid:** ${invalid}`,
|
|
7902
8153
|
"",
|
|
7903
8154
|
"## 1. Executive Summary",
|
|
@@ -8128,10 +8379,10 @@ function isPathExcluded2(relPath, patterns) {
|
|
|
8128
8379
|
function walkProjectFiles(dir, rootDir, excludedPatterns = DEFAULT_EXCLUDED_PATTERNS) {
|
|
8129
8380
|
const results = [];
|
|
8130
8381
|
try {
|
|
8131
|
-
const entries =
|
|
8382
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true }).sort((a, b) => canonicalSort(a.name, b.name));
|
|
8132
8383
|
for (const entry of entries) {
|
|
8133
|
-
const fullPath =
|
|
8134
|
-
const relPath =
|
|
8384
|
+
const fullPath = path5__namespace.join(dir, entry.name);
|
|
8385
|
+
const relPath = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
8135
8386
|
if (isPathExcluded2(relPath, excludedPatterns)) continue;
|
|
8136
8387
|
if (entry.isDirectory()) {
|
|
8137
8388
|
results.push(...walkProjectFiles(fullPath, rootDir, excludedPatterns));
|
|
@@ -8160,8 +8411,8 @@ function pickFirstString(...vals) {
|
|
|
8160
8411
|
}
|
|
8161
8412
|
function parseGitleaksItem(item, rootDir) {
|
|
8162
8413
|
const rawFile = pickFirstString(item.File, item.file);
|
|
8163
|
-
const filePath =
|
|
8164
|
-
const relPath =
|
|
8414
|
+
const filePath = path5__namespace.resolve(rootDir, rawFile);
|
|
8415
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
8165
8416
|
const ruleId = pickFirstString(item.RuleID, item.ruleID) || "GITLEAKS-MATCH";
|
|
8166
8417
|
const rawSecret = pickFirstString(item.Secret, item.secret, item.Match);
|
|
8167
8418
|
const line = item.StartLine ?? item.line ?? 1;
|
|
@@ -8234,7 +8485,7 @@ function scanLineForSecrets(line, lineNumber, filePath, relPath, entropyThreshol
|
|
|
8234
8485
|
function scanContentForSecrets(content, filePath, rootDir, entropyThreshold = 4.3, collectedSuppressions) {
|
|
8235
8486
|
const violations = [];
|
|
8236
8487
|
const lines = content.split(/\r?\n/);
|
|
8237
|
-
const relPath =
|
|
8488
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
8238
8489
|
for (let i = 0; i < lines.length; i++) {
|
|
8239
8490
|
violations.push(...scanLineForSecrets(lines[i], i + 1, filePath, relPath, entropyThreshold, collectedSuppressions));
|
|
8240
8491
|
}
|
|
@@ -8255,7 +8506,7 @@ function scanDiffItems(diffLines, rootDir, excludePatterns, entropyThreshold) {
|
|
|
8255
8506
|
const suppressions = [];
|
|
8256
8507
|
for (const [relFile, items] of fileGroups) {
|
|
8257
8508
|
if (isPathExcluded2(relFile, excludePatterns)) continue;
|
|
8258
|
-
const absPath =
|
|
8509
|
+
const absPath = path5__namespace.resolve(rootDir, relFile);
|
|
8259
8510
|
for (const item of items) {
|
|
8260
8511
|
const lineViolations = scanLineForSecrets(item.content, item.lineNumber, absPath, relFile, entropyThreshold, suppressions);
|
|
8261
8512
|
for (const v of lineViolations) {
|
|
@@ -8271,7 +8522,7 @@ function scanAllProjectFiles(rootDir, excludePatterns, entropyThreshold) {
|
|
|
8271
8522
|
const suppressions = [];
|
|
8272
8523
|
for (const file of allFiles) {
|
|
8273
8524
|
try {
|
|
8274
|
-
const content =
|
|
8525
|
+
const content = fs8__namespace.readFileSync(file, "utf-8");
|
|
8275
8526
|
findings.push(...scanContentForSecrets(content, file, rootDir, entropyThreshold, suppressions));
|
|
8276
8527
|
} catch {
|
|
8277
8528
|
}
|
|
@@ -8328,7 +8579,7 @@ function buildReportHeader2(result) {
|
|
|
8328
8579
|
return [
|
|
8329
8580
|
`# \u{1F510} Deterministic Secrets and Credentials Audit (Gitleaks Gate)`,
|
|
8330
8581
|
``,
|
|
8331
|
-
`> **Evaluation Date:** ${(
|
|
8582
|
+
`> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
8332
8583
|
`> **Verdict:** ${isOk ? "\u2705 COMPLIANT (No exposed secrets or credentials)" : `\u274C BLOCKED (${result.findingsCount} exposed credentials detected)`}`,
|
|
8333
8584
|
`> **Audited Files:** ${result.totalFilesScanned} | **Security Violations:** ${result.findingsCount}`,
|
|
8334
8585
|
`> **Native Gitleaks Engine:** ${result.scannedWithGitleaks ? "\u{1F7E2} Active / Integrated" : "\u26AA Deterministic Native Scanner (@aisdlc/core)"}`,
|
|
@@ -8497,8 +8748,8 @@ function runSemgrepCli(rootDir) {
|
|
|
8497
8748
|
const parsed = JSON.parse(output);
|
|
8498
8749
|
if (Array.isArray(parsed.results)) {
|
|
8499
8750
|
for (const item of parsed.results) {
|
|
8500
|
-
const filePath =
|
|
8501
|
-
const relPath =
|
|
8751
|
+
const filePath = path5__namespace.resolve(rootDir, item.path);
|
|
8752
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
8502
8753
|
violations.push({
|
|
8503
8754
|
filePath,
|
|
8504
8755
|
relPath,
|
|
@@ -8525,15 +8776,15 @@ function isSastExcluded(relPath, excludes) {
|
|
|
8525
8776
|
function walkSourceFiles(dir, rootDir, excludes = DEFAULT_SAST_EXCLUDES) {
|
|
8526
8777
|
const files = [];
|
|
8527
8778
|
try {
|
|
8528
|
-
const entries =
|
|
8779
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true }).sort((a, b) => canonicalSort(a.name, b.name));
|
|
8529
8780
|
for (const entry of entries) {
|
|
8530
|
-
const fullPath =
|
|
8531
|
-
const relPath =
|
|
8781
|
+
const fullPath = path5__namespace.join(dir, entry.name);
|
|
8782
|
+
const relPath = path5__namespace.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
8532
8783
|
if (isSastExcluded(relPath, excludes)) continue;
|
|
8533
8784
|
if (entry.isDirectory()) {
|
|
8534
8785
|
files.push(...walkSourceFiles(fullPath, rootDir, excludes));
|
|
8535
8786
|
} else if (entry.isFile()) {
|
|
8536
|
-
const ext =
|
|
8787
|
+
const ext = path5__namespace.extname(entry.name).toLowerCase();
|
|
8537
8788
|
if (GENERAL_SOURCE_EXTENSIONS.includes(ext)) {
|
|
8538
8789
|
files.push(fullPath);
|
|
8539
8790
|
}
|
|
@@ -8558,11 +8809,11 @@ function checkRuleMatch(rule, line, ext, minSeverity) {
|
|
|
8558
8809
|
}
|
|
8559
8810
|
function scanFileForSast(filePath, rootDir, minSeverity = "HIGH", collectedSuppressions) {
|
|
8560
8811
|
const violations = [];
|
|
8561
|
-
const ext =
|
|
8812
|
+
const ext = path5__namespace.extname(filePath).toLowerCase();
|
|
8562
8813
|
try {
|
|
8563
|
-
const content =
|
|
8814
|
+
const content = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
8564
8815
|
const lines = content.split(/\r?\n/);
|
|
8565
|
-
const relPath =
|
|
8816
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
8566
8817
|
for (let i = 0; i < lines.length; i++) {
|
|
8567
8818
|
const line = lines[i];
|
|
8568
8819
|
if (checkLineSuppression(line, relPath, i + 1, collectedSuppressions)) continue;
|
|
@@ -8588,8 +8839,8 @@ function scanFileForSast(filePath, rootDir, minSeverity = "HIGH", collectedSuppr
|
|
|
8588
8839
|
function collectSastFiles(rootDir, targetDirs) {
|
|
8589
8840
|
const files = [];
|
|
8590
8841
|
for (const tDir of targetDirs) {
|
|
8591
|
-
const fullDir =
|
|
8592
|
-
if (
|
|
8842
|
+
const fullDir = path5__namespace.join(rootDir, tDir);
|
|
8843
|
+
if (fs8__namespace.existsSync(fullDir)) {
|
|
8593
8844
|
files.push(...walkSourceFiles(fullDir, rootDir));
|
|
8594
8845
|
}
|
|
8595
8846
|
}
|
|
@@ -8635,7 +8886,7 @@ function buildSastHeader(isOk, violationsCount, totalFilesScanned) {
|
|
|
8635
8886
|
return [
|
|
8636
8887
|
`# \u{1F6E1}\uFE0F Shift-Left SAST Audit (Static Application Security Testing)`,
|
|
8637
8888
|
``,
|
|
8638
|
-
`> **Evaluation Date:** ${(
|
|
8889
|
+
`> **Evaluation Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
8639
8890
|
`> **Verdict:** ${verdict}`,
|
|
8640
8891
|
`> **Audited Code Files:** ${totalFilesScanned} | **SAST Violations:** ${violationsCount}`,
|
|
8641
8892
|
``,
|
|
@@ -8824,22 +9075,31 @@ var PII_SAST_RULES = [
|
|
|
8824
9075
|
pattern: /\b(?:ssn|dni|nif|tax_?id|national_?id)\s*[:=]\s*['"](?:\d{3}-\d{2}-\d{4}|\d{8}[A-HJ-NP-TV-Z])['"]/i
|
|
8825
9076
|
}
|
|
8826
9077
|
];
|
|
8827
|
-
function
|
|
8828
|
-
|
|
8829
|
-
if (!fs7__namespace.existsSync(candidate)) return DEFAULT_PRIVACY_POLICY;
|
|
9078
|
+
function parsePrivacyDoc(raw, candidate) {
|
|
9079
|
+
let parsed;
|
|
8830
9080
|
try {
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
agent_guardrails: { ...DEFAULT_PRIVACY_POLICY.agent_guardrails, ...parsed.agent_guardrails || {} },
|
|
8838
|
-
ci_gates: { ...DEFAULT_PRIVACY_POLICY.ci_gates, ...parsed.ci_gates || {} }
|
|
8839
|
-
};
|
|
8840
|
-
} catch {
|
|
8841
|
-
return DEFAULT_PRIVACY_POLICY;
|
|
9081
|
+
parsed = yaml3__default.default.load(raw);
|
|
9082
|
+
} catch (err) {
|
|
9083
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in privacy-policy '${candidate}': ${err instanceof Error ? err.message : String(err)}`);
|
|
9084
|
+
}
|
|
9085
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9086
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid privacy policy structure in '${candidate}': root must be a YAML mapping`);
|
|
8842
9087
|
}
|
|
9088
|
+
return parsed;
|
|
9089
|
+
}
|
|
9090
|
+
function loadPrivacyPolicy(rootDir = process.cwd(), policyPath) {
|
|
9091
|
+
const candidate = policyPath ? path5__namespace.isAbsolute(policyPath) ? policyPath : path5__namespace.join(rootDir, policyPath) : path5__namespace.join(rootDir, "privacy-policy.yaml");
|
|
9092
|
+
if (!fs8__namespace.existsSync(candidate)) return DEFAULT_PRIVACY_POLICY;
|
|
9093
|
+
const raw = fs8__namespace.readFileSync(candidate, "utf-8");
|
|
9094
|
+
if (!raw || !raw.trim()) return DEFAULT_PRIVACY_POLICY;
|
|
9095
|
+
const parsed = parsePrivacyDoc(raw, candidate);
|
|
9096
|
+
return {
|
|
9097
|
+
...DEFAULT_PRIVACY_POLICY,
|
|
9098
|
+
...parsed,
|
|
9099
|
+
classifications: { ...DEFAULT_PRIVACY_POLICY.classifications, ...parsed.classifications || {} },
|
|
9100
|
+
agent_guardrails: { ...DEFAULT_PRIVACY_POLICY.agent_guardrails, ...parsed.agent_guardrails || {} },
|
|
9101
|
+
ci_gates: { ...DEFAULT_PRIVACY_POLICY.ci_gates, ...parsed.ci_gates || {} }
|
|
9102
|
+
};
|
|
8843
9103
|
}
|
|
8844
9104
|
function isLineExemptFromPrivacyScan(line) {
|
|
8845
9105
|
return line.includes("ai-sdlc:allow-pii") || line.includes("ai-sdlc:allow-sast") || line.includes("nosec");
|
|
@@ -8856,9 +9116,9 @@ function shouldApplyPiiRule(rule, policy) {
|
|
|
8856
9116
|
function scanFileForPrivacyViolations(filePath, rootDir, policy = DEFAULT_PRIVACY_POLICY) {
|
|
8857
9117
|
const violations = [];
|
|
8858
9118
|
try {
|
|
8859
|
-
const content =
|
|
9119
|
+
const content = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
8860
9120
|
const lines = content.split(/\r?\n/);
|
|
8861
|
-
const relPath =
|
|
9121
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
8862
9122
|
for (let i = 0; i < lines.length; i++) {
|
|
8863
9123
|
const line = lines[i];
|
|
8864
9124
|
if (isLineExemptFromPrivacyScan(line)) continue;
|
|
@@ -8967,10 +9227,10 @@ function discoverPrivacyArtifacts(rootDir) {
|
|
|
8967
9227
|
dpiaStatuses: /* @__PURE__ */ new Map()
|
|
8968
9228
|
};
|
|
8969
9229
|
for (const file of walkMdFiles(rootDir)) {
|
|
8970
|
-
const relPath =
|
|
9230
|
+
const relPath = path5__namespace.relative(rootDir, file).replace(/\\/g, "/");
|
|
8971
9231
|
if (isExcludedPrivacyMd(relPath)) continue;
|
|
8972
9232
|
try {
|
|
8973
|
-
const fm = extractFrontmatter(
|
|
9233
|
+
const fm = extractFrontmatter(fs8__namespace.readFileSync(file, "utf-8"));
|
|
8974
9234
|
if (fm && fm.id) classifyPrivacyMarkdown(fm, relPath, acc);
|
|
8975
9235
|
} catch {
|
|
8976
9236
|
}
|
|
@@ -9015,7 +9275,7 @@ function checkAssetRopaRegistration(asset, ropaRecords, policy, fullPath) {
|
|
|
9015
9275
|
}
|
|
9016
9276
|
function auditDataAssetGovernance(asset, ropaRecords, policy, rootDir) {
|
|
9017
9277
|
if (!isSensitiveClassification(asset.classification)) return [];
|
|
9018
|
-
const fullPath =
|
|
9278
|
+
const fullPath = path5__namespace.join(rootDir, asset.filePath);
|
|
9019
9279
|
const v1 = checkAssetRetention(asset, policy, fullPath);
|
|
9020
9280
|
const v2 = checkAssetRopaRegistration(asset, ropaRecords, policy, fullPath);
|
|
9021
9281
|
return [v1, v2].filter((v) => v !== null);
|
|
@@ -9087,8 +9347,8 @@ function checkRopaDpiaApproval(ropa, dataAssets, dpiaStatuses, fullPath) {
|
|
|
9087
9347
|
function collectSourceFilesForPrivacy(rootDir, targetDirs) {
|
|
9088
9348
|
const files = [];
|
|
9089
9349
|
for (const dir of targetDirs) {
|
|
9090
|
-
const fullDir =
|
|
9091
|
-
if (
|
|
9350
|
+
const fullDir = path5__namespace.join(rootDir, dir);
|
|
9351
|
+
if (fs8__namespace.existsSync(fullDir)) {
|
|
9092
9352
|
files.push(...walkSourceFiles(fullDir, rootDir));
|
|
9093
9353
|
}
|
|
9094
9354
|
}
|
|
@@ -9178,15 +9438,13 @@ function generatePrivacyReportMarkdown(result, policy = DEFAULT_PRIVACY_POLICY)
|
|
|
9178
9438
|
function writePrivacyOutputsIfNeeded(result, options, policy, rootDir) {
|
|
9179
9439
|
if (options.generateRopa) {
|
|
9180
9440
|
const relRopa = options.ropaOutputPath || policy.ci_gates.output_ropa_file || "reports/ROPA_INVENTORY.md";
|
|
9181
|
-
const fullRopa =
|
|
9182
|
-
|
|
9183
|
-
fs7__namespace.writeFileSync(fullRopa, result.ropaMarkdown, "utf-8");
|
|
9441
|
+
const fullRopa = path5__namespace.isAbsolute(relRopa) ? relRopa : path5__namespace.join(rootDir, relRopa);
|
|
9442
|
+
writeReportFileIfChanged(fullRopa, result.ropaMarkdown);
|
|
9184
9443
|
result.ropaOutputPath = fullRopa;
|
|
9185
9444
|
}
|
|
9186
9445
|
if (options.reportOutputPath) {
|
|
9187
|
-
const fullReport =
|
|
9188
|
-
|
|
9189
|
-
fs7__namespace.writeFileSync(fullReport, result.reportMarkdown, "utf-8");
|
|
9446
|
+
const fullReport = path5__namespace.isAbsolute(options.reportOutputPath) ? options.reportOutputPath : path5__namespace.join(rootDir, options.reportOutputPath);
|
|
9447
|
+
writeReportFileIfChanged(fullReport, result.reportMarkdown);
|
|
9190
9448
|
result.reportOutputPath = fullReport;
|
|
9191
9449
|
}
|
|
9192
9450
|
}
|
|
@@ -9198,7 +9456,7 @@ function evaluateDiscoveredPrivacyGovernance(discovered, policy, rootDir) {
|
|
|
9198
9456
|
}
|
|
9199
9457
|
let dpiaRequiredCount = 0;
|
|
9200
9458
|
for (const ropa of discovered.ropaRecords) {
|
|
9201
|
-
const fullPath =
|
|
9459
|
+
const fullPath = path5__namespace.join(rootDir, ropa.filePath);
|
|
9202
9460
|
violations.push(...checkRopaControlsAndSubprocessors(ropa, governedRopaIds, policy, fullPath));
|
|
9203
9461
|
const dpiaCheck = checkRopaDpiaApproval(ropa, discovered.dataAssets, discovered.dpiaStatuses, fullPath);
|
|
9204
9462
|
if (dpiaCheck.dpiaRequired) dpiaRequiredCount++;
|
|
@@ -9244,7 +9502,7 @@ function calculateRating(mi, cc) {
|
|
|
9244
9502
|
return "F";
|
|
9245
9503
|
}
|
|
9246
9504
|
function detectLanguage(filePath) {
|
|
9247
|
-
const ext =
|
|
9505
|
+
const ext = path5__namespace.extname(filePath).toLowerCase();
|
|
9248
9506
|
switch (ext) {
|
|
9249
9507
|
case ".ts":
|
|
9250
9508
|
return "TypeScript";
|
|
@@ -9269,7 +9527,7 @@ function detectLanguage(filePath) {
|
|
|
9269
9527
|
}
|
|
9270
9528
|
function parseJsonSummaryFile(filePath, relSource) {
|
|
9271
9529
|
try {
|
|
9272
|
-
const raw = JSON.parse(
|
|
9530
|
+
const raw = JSON.parse(fs8__namespace.readFileSync(filePath, "utf-8"));
|
|
9273
9531
|
const total = raw.total;
|
|
9274
9532
|
if (!total || typeof total !== "object") return null;
|
|
9275
9533
|
const linesPct = Number(total.lines?.pct);
|
|
@@ -9292,7 +9550,7 @@ function updateLcovCounts(line, counts) {
|
|
|
9292
9550
|
}
|
|
9293
9551
|
function parseLcovFile(filePath, relSource) {
|
|
9294
9552
|
try {
|
|
9295
|
-
const lines =
|
|
9553
|
+
const lines = fs8__namespace.readFileSync(filePath, "utf-8").split(/\r?\n/);
|
|
9296
9554
|
const counts = { LF: 0, LH: 0, BRF: 0, BRH: 0, FNF: 0, FNH: 0 };
|
|
9297
9555
|
for (const line of lines) {
|
|
9298
9556
|
updateLcovCounts(line, counts);
|
|
@@ -9312,16 +9570,16 @@ function parseLcovFile(filePath, relSource) {
|
|
|
9312
9570
|
function readCodeCoverageSummary(rootDir = process.cwd()) {
|
|
9313
9571
|
const jsonCandidates = ["coverage/coverage-summary.json", "reports/coverage-summary.json"];
|
|
9314
9572
|
for (const rel of jsonCandidates) {
|
|
9315
|
-
const full =
|
|
9316
|
-
if (
|
|
9573
|
+
const full = path5__namespace.join(rootDir, rel);
|
|
9574
|
+
if (fs8__namespace.existsSync(full)) {
|
|
9317
9575
|
const parsed = parseJsonSummaryFile(full, rel);
|
|
9318
9576
|
if (parsed) return parsed;
|
|
9319
9577
|
}
|
|
9320
9578
|
}
|
|
9321
9579
|
const lcovCandidates = ["coverage/lcov.info", "lcov.info", "reports/lcov.info"];
|
|
9322
9580
|
for (const rel of lcovCandidates) {
|
|
9323
|
-
const full =
|
|
9324
|
-
if (
|
|
9581
|
+
const full = path5__namespace.join(rootDir, rel);
|
|
9582
|
+
if (fs8__namespace.existsSync(full)) {
|
|
9325
9583
|
const parsed = parseLcovFile(full, rel);
|
|
9326
9584
|
if (parsed) return parsed;
|
|
9327
9585
|
}
|
|
@@ -9406,7 +9664,7 @@ function buildReportHeader3(verdict, rating, mi, cc) {
|
|
|
9406
9664
|
return [
|
|
9407
9665
|
`# \u{1F4CA} Formal Quality and Release Gate Report (AI-SDLC)`,
|
|
9408
9666
|
"",
|
|
9409
|
-
`> **Generation Date:** ${(
|
|
9667
|
+
`> **Generation Date:** ${resolveDeterministicDate().toISOString()}`,
|
|
9410
9668
|
`> **Release Gate Verdict:** ${status}`,
|
|
9411
9669
|
`> **Global Rating:** **\`${rating}\`** (MI Index: ${mi}/100, Average CC: ${cc})`
|
|
9412
9670
|
];
|
|
@@ -9457,9 +9715,8 @@ function renderQualityReportMarkdown(gateResult, coverage, accumulated, averages
|
|
|
9457
9715
|
return [...header, "", "---", "", ...table, "", "---", "", ...polyglot, ...violationsSection, "", EVALUATION_STANDARDS_MD].join("\n");
|
|
9458
9716
|
}
|
|
9459
9717
|
function writeQualityReportFile(rootDir, markdown) {
|
|
9460
|
-
const
|
|
9461
|
-
|
|
9462
|
-
fs7__namespace.writeFileSync(path4__namespace.join(reportsDir, "QUALITY_REPORT.md"), markdown, "utf-8");
|
|
9718
|
+
const targetPath = path5__namespace.join(rootDir, "reports", "QUALITY_REPORT.md");
|
|
9719
|
+
return writeReportFileIfChanged(targetPath, markdown);
|
|
9463
9720
|
}
|
|
9464
9721
|
function generateQualityReport(options = {}) {
|
|
9465
9722
|
const rootDir = options.rootDir || process.cwd();
|
|
@@ -9601,9 +9858,9 @@ Flujo de Integraci\xF3n (Merges):`,
|
|
|
9601
9858
|
};
|
|
9602
9859
|
}
|
|
9603
9860
|
function resolveVersionFromProposal(changeDir) {
|
|
9604
|
-
const proposalFile =
|
|
9605
|
-
if (!
|
|
9606
|
-
const propContent =
|
|
9861
|
+
const proposalFile = path5__namespace.join(changeDir, "proposal.md");
|
|
9862
|
+
if (!fs8__namespace.existsSync(proposalFile)) return "1.0.0";
|
|
9863
|
+
const propContent = fs8__namespace.readFileSync(proposalFile, "utf-8");
|
|
9607
9864
|
const propFm = propContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
9608
9865
|
if (!propFm) return "1.0.0";
|
|
9609
9866
|
const parsedProp = yaml3__default.default.load(propFm[1]);
|
|
@@ -9620,9 +9877,9 @@ function isTaskMatchingId(currentId, cleanTaskId) {
|
|
|
9620
9877
|
return currentLower === targetLower || currentLower.startsWith(`${targetLower}-`) || targetLower.startsWith(`${currentLower}-`);
|
|
9621
9878
|
}
|
|
9622
9879
|
function parseTasksFrontmatter(tasksFile) {
|
|
9623
|
-
if (!
|
|
9880
|
+
if (!fs8__namespace.existsSync(tasksFile)) return null;
|
|
9624
9881
|
try {
|
|
9625
|
-
const content =
|
|
9882
|
+
const content = fs8__namespace.readFileSync(tasksFile, "utf-8");
|
|
9626
9883
|
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
9627
9884
|
if (!fmMatch) return null;
|
|
9628
9885
|
const fm = yaml3__default.default.load(fmMatch[1]);
|
|
@@ -9644,7 +9901,7 @@ function buildTaskMatch(currentId, currentTitle, rawChangeId, changeDir, version
|
|
|
9644
9901
|
};
|
|
9645
9902
|
}
|
|
9646
9903
|
function inspectChangeTasks(changeDir, entryName, cleanTaskId, availableTasks) {
|
|
9647
|
-
const fm = parseTasksFrontmatter(
|
|
9904
|
+
const fm = parseTasksFrontmatter(path5__namespace.join(changeDir, "tasks.md"));
|
|
9648
9905
|
if (!fm || !fm.tasks) return void 0;
|
|
9649
9906
|
const rawChangeId = resolveRawChangeId(fm, entryName);
|
|
9650
9907
|
const versionStr = resolveChangeVersion(fm, changeDir);
|
|
@@ -9665,16 +9922,16 @@ function locateTaskInActiveChanges(taskId, rootDir = process.cwd()) {
|
|
|
9665
9922
|
if (!cleanTaskId) {
|
|
9666
9923
|
return { found: false, error: "Debe especificar un identificador de tarea v\xE1lido (ej. TSK-001)." };
|
|
9667
9924
|
}
|
|
9668
|
-
const activeChangesDir =
|
|
9669
|
-
if (!
|
|
9925
|
+
const activeChangesDir = path5__namespace.join(rootDir, "specs", "changes", "active");
|
|
9926
|
+
if (!fs8__namespace.existsSync(activeChangesDir)) {
|
|
9670
9927
|
return { found: false, error: `No se encontr\xF3 el directorio de cambios activos en '${activeChangesDir}'.` };
|
|
9671
9928
|
}
|
|
9672
9929
|
const availableTasks = [];
|
|
9673
|
-
const candidateDirs =
|
|
9930
|
+
const candidateDirs = fs8__namespace.readdirSync(activeChangesDir, { withFileTypes: true });
|
|
9674
9931
|
let matchedTask;
|
|
9675
9932
|
for (const entry of candidateDirs) {
|
|
9676
9933
|
if (!entry.isDirectory()) continue;
|
|
9677
|
-
const changeDir =
|
|
9934
|
+
const changeDir = path5__namespace.join(activeChangesDir, entry.name);
|
|
9678
9935
|
const m = inspectChangeTasks(changeDir, entry.name, cleanTaskId, availableTasks);
|
|
9679
9936
|
if (m && !matchedTask) {
|
|
9680
9937
|
matchedTask = m;
|
|
@@ -9711,14 +9968,14 @@ function detectExistingFeatureBranch(branches, dirName, changeId, changeSlug) {
|
|
|
9711
9968
|
(b) => b.startsWith("feat/") && (b.toLowerCase().includes(dirName.toLowerCase()) || b.toLowerCase().includes(changeId.toLowerCase())) && classifyBranch(b).valid
|
|
9712
9969
|
);
|
|
9713
9970
|
if (existing) return existing;
|
|
9714
|
-
const normalizedName =
|
|
9971
|
+
const normalizedName = slugify2(changeSlug).replace(/^(chg|bug|feat)-(\d+)/i, (_, p, n) => `${p.toUpperCase()}-${n}`);
|
|
9715
9972
|
return `feat/${normalizedName}`;
|
|
9716
9973
|
}
|
|
9717
9974
|
function buildDefaultTaskSuffix(taskId, taskTitle) {
|
|
9718
9975
|
let taskSuffix = taskId.toLowerCase();
|
|
9719
9976
|
const hasSlug = taskSuffix.includes("-") && taskSuffix.split("-").length > 2;
|
|
9720
9977
|
if (!hasSlug && taskTitle) {
|
|
9721
|
-
const titleSlug =
|
|
9978
|
+
const titleSlug = slugify2(taskTitle).split("-").slice(0, 4).join("-");
|
|
9722
9979
|
if (titleSlug) {
|
|
9723
9980
|
taskSuffix = `${taskSuffix}-${titleSlug}`;
|
|
9724
9981
|
}
|
|
@@ -9738,7 +9995,7 @@ function resolveBranchHierarchy(match, rootDir = process.cwd()) {
|
|
|
9738
9995
|
const branches = getGitBranchList(rootDir);
|
|
9739
9996
|
const cleanVersion = (match.version || "1.0.0").replace(/^v/, "");
|
|
9740
9997
|
const releaseBranch = detectExistingReleaseBranch(branches, cleanVersion);
|
|
9741
|
-
const dirName =
|
|
9998
|
+
const dirName = path5__namespace.basename(match.changeDir);
|
|
9742
9999
|
const changeSlug = dirName.replace(/^(chg|CHG)-/i, (m) => m.toUpperCase());
|
|
9743
10000
|
const featureBranch = detectExistingFeatureBranch(branches, dirName, match.changeId, changeSlug);
|
|
9744
10001
|
const parentIdMatch = featureBranch.replace(/^(feat|feature)\//i, "").match(/^[a-zA-Z0-9]+-[a-zA-Z0-9]+/);
|
|
@@ -10045,13 +10302,13 @@ function getGitConfigValue(key, cwd) {
|
|
|
10045
10302
|
}
|
|
10046
10303
|
function readSessionTelemetry(cwd) {
|
|
10047
10304
|
const possiblePaths = [
|
|
10048
|
-
|
|
10049
|
-
|
|
10305
|
+
path5__namespace.join(cwd, ".ai-sdlc", "session-telemetry.json"),
|
|
10306
|
+
path5__namespace.join(cwd, ".ai-sdlc", "agent-session.json")
|
|
10050
10307
|
];
|
|
10051
10308
|
for (const p of possiblePaths) {
|
|
10052
|
-
if (
|
|
10309
|
+
if (fs8__namespace.existsSync(p)) {
|
|
10053
10310
|
try {
|
|
10054
|
-
const raw =
|
|
10311
|
+
const raw = fs8__namespace.readFileSync(p, "utf-8");
|
|
10055
10312
|
return JSON.parse(raw);
|
|
10056
10313
|
} catch {
|
|
10057
10314
|
}
|
|
@@ -10271,20 +10528,20 @@ if [ -f "$TELEMETRY_FILE" ]; then
|
|
|
10271
10528
|
fi
|
|
10272
10529
|
`;
|
|
10273
10530
|
function installGitHooks(rootDir = process.cwd()) {
|
|
10274
|
-
const gitDir =
|
|
10275
|
-
if (!
|
|
10531
|
+
const gitDir = path5__namespace.join(rootDir, ".git");
|
|
10532
|
+
if (!fs8__namespace.existsSync(gitDir)) {
|
|
10276
10533
|
return {
|
|
10277
10534
|
success: false,
|
|
10278
10535
|
error: `No se encontr\xF3 el directorio Git en '${gitDir}'.`
|
|
10279
10536
|
};
|
|
10280
10537
|
}
|
|
10281
|
-
const hooksDir =
|
|
10282
|
-
if (!
|
|
10283
|
-
|
|
10538
|
+
const hooksDir = path5__namespace.join(gitDir, "hooks");
|
|
10539
|
+
if (!fs8__namespace.existsSync(hooksDir)) {
|
|
10540
|
+
fs8__namespace.mkdirSync(hooksDir, { recursive: true });
|
|
10284
10541
|
}
|
|
10285
|
-
const hookPath =
|
|
10542
|
+
const hookPath = path5__namespace.join(hooksDir, "prepare-commit-msg");
|
|
10286
10543
|
try {
|
|
10287
|
-
|
|
10544
|
+
fs8__namespace.writeFileSync(hookPath, PREPARE_COMMIT_MSG_SCRIPT, { encoding: "utf-8", mode: 493 });
|
|
10288
10545
|
return {
|
|
10289
10546
|
success: true,
|
|
10290
10547
|
hookPath
|
|
@@ -10625,16 +10882,16 @@ function formatReleaseKpiMarkdown(report) {
|
|
|
10625
10882
|
].join("\n");
|
|
10626
10883
|
}
|
|
10627
10884
|
function writeReleaseKpiReport(report, options = {}) {
|
|
10628
|
-
const root = options.outputDir || (options.rootDir ?
|
|
10629
|
-
if (!
|
|
10630
|
-
|
|
10885
|
+
const root = options.outputDir || (options.rootDir ? path5__namespace.join(options.rootDir, "reports", "releases") : path5__namespace.join(process.cwd(), "reports", "releases"));
|
|
10886
|
+
if (!fs8__namespace.existsSync(root)) {
|
|
10887
|
+
fs8__namespace.mkdirSync(root, { recursive: true });
|
|
10631
10888
|
}
|
|
10632
10889
|
const cleanBranchName = report.releaseBranch.replace(/[^a-zA-Z0-9.-]/g, "_");
|
|
10633
|
-
const mdPath =
|
|
10634
|
-
const jsonPath =
|
|
10890
|
+
const mdPath = path5__namespace.join(root, `RELEASE_KPIS_${cleanBranchName}.md`);
|
|
10891
|
+
const jsonPath = path5__namespace.join(root, `release_${cleanBranchName}.kpis.json`);
|
|
10635
10892
|
const mdContent = formatReleaseKpiMarkdown(report);
|
|
10636
|
-
|
|
10637
|
-
|
|
10893
|
+
fs8__namespace.writeFileSync(mdPath, mdContent, "utf-8");
|
|
10894
|
+
fs8__namespace.writeFileSync(jsonPath, JSON.stringify(report, null, 2), "utf-8");
|
|
10638
10895
|
return { markdownPath: mdPath, jsonPath };
|
|
10639
10896
|
}
|
|
10640
10897
|
var cachedScript = null;
|
|
@@ -10643,19 +10900,19 @@ function resolveCytoscapePath() {
|
|
|
10643
10900
|
try {
|
|
10644
10901
|
const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
10645
10902
|
const entryPath = req.resolve("cytoscape");
|
|
10646
|
-
const dir =
|
|
10647
|
-
const minPath =
|
|
10648
|
-
if (
|
|
10903
|
+
const dir = path5__namespace.dirname(entryPath);
|
|
10904
|
+
const minPath = path5__namespace.join(dir, "cytoscape.min.js");
|
|
10905
|
+
if (fs8__namespace.existsSync(minPath)) {
|
|
10649
10906
|
return minPath;
|
|
10650
10907
|
}
|
|
10651
10908
|
} catch {
|
|
10652
10909
|
}
|
|
10653
10910
|
const searchDirs = [
|
|
10654
|
-
|
|
10655
|
-
|
|
10911
|
+
path5__namespace.join(process.cwd(), "node_modules", "cytoscape", "dist", "cytoscape.min.js"),
|
|
10912
|
+
path5__namespace.join(process.cwd(), "packages", "core", "node_modules", "cytoscape", "dist", "cytoscape.min.js")
|
|
10656
10913
|
];
|
|
10657
10914
|
for (const candidate of searchDirs) {
|
|
10658
|
-
if (
|
|
10915
|
+
if (fs8__namespace.existsSync(candidate)) {
|
|
10659
10916
|
return candidate;
|
|
10660
10917
|
}
|
|
10661
10918
|
}
|
|
@@ -10668,7 +10925,7 @@ function getCytoscapeScript() {
|
|
|
10668
10925
|
const scriptPath = resolveCytoscapePath();
|
|
10669
10926
|
if (scriptPath) {
|
|
10670
10927
|
try {
|
|
10671
|
-
cachedScript =
|
|
10928
|
+
cachedScript = fs8__namespace.readFileSync(scriptPath, "utf-8");
|
|
10672
10929
|
return cachedScript;
|
|
10673
10930
|
} catch {
|
|
10674
10931
|
}
|
|
@@ -10768,7 +11025,7 @@ function extractFrontmatter2(fileContent) {
|
|
|
10768
11025
|
}
|
|
10769
11026
|
}
|
|
10770
11027
|
function toScannedArtifact(filePath, relPath) {
|
|
10771
|
-
const fileContent =
|
|
11028
|
+
const fileContent = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
10772
11029
|
const fm = extractFrontmatter2(fileContent);
|
|
10773
11030
|
const id = typeof fm?.id === "string" ? fm.id.trim() : "";
|
|
10774
11031
|
if (!id) return null;
|
|
@@ -10780,12 +11037,12 @@ function toScannedArtifact(filePath, relPath) {
|
|
|
10780
11037
|
return { id, type: classified.type, title, layer: classified.layer, filePath: relPath, content, frontmatter: fm };
|
|
10781
11038
|
}
|
|
10782
11039
|
function parseAndCatalogArtifactFile(rootDir, filePath, scannedMap, archViewByBasename) {
|
|
10783
|
-
const relPath =
|
|
11040
|
+
const relPath = path5__namespace.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
10784
11041
|
if (relPath.includes("/changes/") || relPath.startsWith("changes/")) return;
|
|
10785
11042
|
const scannedArt = toScannedArtifact(filePath, relPath);
|
|
10786
11043
|
if (!scannedArt) return;
|
|
10787
11044
|
if (scannedArt.layer === "architecture" && scannedArt.type === "architecture") {
|
|
10788
|
-
archViewByBasename.set(
|
|
11045
|
+
archViewByBasename.set(path5__namespace.basename(relPath), scannedArt);
|
|
10789
11046
|
} else {
|
|
10790
11047
|
scannedMap.set(scannedArt.id, scannedArt);
|
|
10791
11048
|
}
|
|
@@ -10795,8 +11052,8 @@ function scanWorkspaceArtifacts(rootDir) {
|
|
|
10795
11052
|
const archViewByBasename = /* @__PURE__ */ new Map();
|
|
10796
11053
|
const artifactDirs = ["product", "architecture", "specs", "security", "privacy"];
|
|
10797
11054
|
for (const dirName of artifactDirs) {
|
|
10798
|
-
const fullDirPath =
|
|
10799
|
-
if (
|
|
11055
|
+
const fullDirPath = path5__namespace.join(rootDir, dirName);
|
|
11056
|
+
if (fs8__namespace.existsSync(fullDirPath)) {
|
|
10800
11057
|
for (const f of walkMdFiles(fullDirPath)) {
|
|
10801
11058
|
parseAndCatalogArtifactFile(rootDir, f, scannedArtifactsMap, archViewByBasename);
|
|
10802
11059
|
}
|
|
@@ -10856,9 +11113,9 @@ function addTraceArchEdges(r, status, archViewByBasename, nodeMap, addEdge) {
|
|
|
10856
11113
|
}
|
|
10857
11114
|
}
|
|
10858
11115
|
function loadTestContentPreview(filePath) {
|
|
10859
|
-
if (!
|
|
11116
|
+
if (!fs8__namespace.existsSync(filePath)) return void 0;
|
|
10860
11117
|
try {
|
|
10861
|
-
const testContent =
|
|
11118
|
+
const testContent = fs8__namespace.readFileSync(filePath, "utf-8");
|
|
10862
11119
|
const lang = filePath.endsWith(".feature") ? "gherkin" : "typescript";
|
|
10863
11120
|
const preview = testContent.substring(0, 1500) + (testContent.length > 1500 ? "\n... (truncated)" : "");
|
|
10864
11121
|
return "```" + lang + "\n" + preview + "\n```";
|
|
@@ -10868,10 +11125,10 @@ function loadTestContentPreview(filePath) {
|
|
|
10868
11125
|
}
|
|
10869
11126
|
function ensureTestNode(t, testId, rootDir, nodeMap) {
|
|
10870
11127
|
if (nodeMap.has(testId)) return;
|
|
10871
|
-
const fullTestPath =
|
|
11128
|
+
const fullTestPath = path5__namespace.isAbsolute(t) ? t : path5__namespace.join(rootDir, t);
|
|
10872
11129
|
nodeMap.set(testId, {
|
|
10873
11130
|
id: testId,
|
|
10874
|
-
label:
|
|
11131
|
+
label: path5__namespace.basename(t),
|
|
10875
11132
|
title: t,
|
|
10876
11133
|
type: t.endsWith(".feature") ? "bdd-feature" : "code-test",
|
|
10877
11134
|
layer: "test",
|
|
@@ -13016,9 +13273,10 @@ ${manualsTab}
|
|
|
13016
13273
|
${kpisTab}
|
|
13017
13274
|
</main>`;
|
|
13018
13275
|
}
|
|
13019
|
-
function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard Interactivo de Trazabilidad y Calidad") {
|
|
13276
|
+
function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard Interactivo de Trazabilidad y Calidad", generationDate) {
|
|
13020
13277
|
const head = renderDashboardHead(title);
|
|
13021
|
-
const
|
|
13278
|
+
const effectiveDate = generationDate || resolveDeterministicDate().toISOString();
|
|
13279
|
+
const header = renderHeaderHtml(graphData.issueCount, effectiveDate);
|
|
13022
13280
|
const nav = renderNavTabsHtml();
|
|
13023
13281
|
const main = renderDashboardMain(metricsData);
|
|
13024
13282
|
const sidebar = renderTaskSidebarHtml();
|
|
@@ -13048,9 +13306,9 @@ var IGNORED_SCAN_DIRS = /* @__PURE__ */ new Set([
|
|
|
13048
13306
|
".agent"
|
|
13049
13307
|
]);
|
|
13050
13308
|
function readKpiFilesInDir(dir) {
|
|
13051
|
-
if (!
|
|
13309
|
+
if (!fs8__namespace.existsSync(dir)) return [];
|
|
13052
13310
|
try {
|
|
13053
|
-
return
|
|
13311
|
+
return fs8__namespace.readdirSync(dir).filter((f) => f.endsWith(".kpis.json")).map((f) => path5__namespace.join(dir, f));
|
|
13054
13312
|
} catch {
|
|
13055
13313
|
return [];
|
|
13056
13314
|
}
|
|
@@ -13058,11 +13316,11 @@ function readKpiFilesInDir(dir) {
|
|
|
13058
13316
|
function scanSubdirKpiFiles(dir, depth, acc) {
|
|
13059
13317
|
if (depth > 4) return;
|
|
13060
13318
|
try {
|
|
13061
|
-
const entries =
|
|
13319
|
+
const entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
|
|
13062
13320
|
for (const entry of entries) {
|
|
13063
13321
|
if (!entry.isDirectory() || IGNORED_SCAN_DIRS.has(entry.name)) continue;
|
|
13064
|
-
const sub =
|
|
13065
|
-
if (entry.name === "releases" &&
|
|
13322
|
+
const sub = path5__namespace.join(dir, entry.name);
|
|
13323
|
+
if (entry.name === "releases" && path5__namespace.basename(dir) === "reports") {
|
|
13066
13324
|
acc.push(...readKpiFilesInDir(sub));
|
|
13067
13325
|
} else {
|
|
13068
13326
|
scanSubdirKpiFiles(sub, depth + 1, acc);
|
|
@@ -13072,7 +13330,7 @@ function scanSubdirKpiFiles(dir, depth, acc) {
|
|
|
13072
13330
|
}
|
|
13073
13331
|
}
|
|
13074
13332
|
function findKpiJsonFiles(rootDir) {
|
|
13075
|
-
const primaryFiles = readKpiFilesInDir(
|
|
13333
|
+
const primaryFiles = readKpiFilesInDir(path5__namespace.join(rootDir, "reports", "releases"));
|
|
13076
13334
|
if (primaryFiles.length > 0) return primaryFiles;
|
|
13077
13335
|
const found = [];
|
|
13078
13336
|
scanSubdirKpiFiles(rootDir, 0, found);
|
|
@@ -13083,7 +13341,7 @@ function collectHistoricalKpis(rootDir) {
|
|
|
13083
13341
|
const kpiFiles = findKpiJsonFiles(rootDir);
|
|
13084
13342
|
for (const fullPath of kpiFiles) {
|
|
13085
13343
|
try {
|
|
13086
|
-
const content =
|
|
13344
|
+
const content = fs8__namespace.readFileSync(fullPath, "utf-8");
|
|
13087
13345
|
const report = JSON.parse(content);
|
|
13088
13346
|
if (!items.some((it) => it.id === report.releaseBranch && it.timestamp === report.generatedAt)) {
|
|
13089
13347
|
items.push({
|
|
@@ -13289,15 +13547,11 @@ function computeGateAverages(gateResults) {
|
|
|
13289
13547
|
}
|
|
13290
13548
|
function generateDashboardReport(options = {}) {
|
|
13291
13549
|
const rootDir = options.rootDir || process.cwd();
|
|
13292
|
-
const outputPath = options.outputPath ||
|
|
13550
|
+
const outputPath = options.outputPath || path5__namespace.join(rootDir, "reports", "dashboard.html");
|
|
13293
13551
|
const graphData = buildGraphElements(rootDir);
|
|
13294
13552
|
const metricsData = collectDashboardMetrics(rootDir);
|
|
13295
|
-
const html = renderDashboardHtml(graphData, metricsData, options.title);
|
|
13296
|
-
|
|
13297
|
-
if (!fs7__namespace.existsSync(outDir)) {
|
|
13298
|
-
fs7__namespace.mkdirSync(outDir, { recursive: true });
|
|
13299
|
-
}
|
|
13300
|
-
fs7__namespace.writeFileSync(outputPath, html, "utf-8");
|
|
13553
|
+
const html = renderDashboardHtml(graphData, metricsData, options.title, resolveDeterministicDate(rootDir).toISOString());
|
|
13554
|
+
writeReportFileIfChanged(outputPath, html);
|
|
13301
13555
|
const { bddCoverageRatio, traceabilityRatio } = computeSummaryRatios(metricsData);
|
|
13302
13556
|
const { avgMi, avgCc } = computeGateAverages(metricsData.gate.results);
|
|
13303
13557
|
return {
|
|
@@ -13323,6 +13577,8 @@ function generateDashboardReport(options = {}) {
|
|
|
13323
13577
|
html
|
|
13324
13578
|
};
|
|
13325
13579
|
}
|
|
13580
|
+
|
|
13581
|
+
// src/reporters/sarif.ts
|
|
13326
13582
|
var SARIF_SCHEMA_2_1_0 = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json";
|
|
13327
13583
|
var SARIF_VERSION = "2.1.0";
|
|
13328
13584
|
function normalizeUri(filePath) {
|
|
@@ -13470,11 +13726,7 @@ function serializeSarifDocument(doc, pretty = true) {
|
|
|
13470
13726
|
return JSON.stringify(doc, null, pretty ? 2 : 0);
|
|
13471
13727
|
}
|
|
13472
13728
|
function writeSecuritySarifReport(outputPath, doc, pretty = true) {
|
|
13473
|
-
|
|
13474
|
-
if (!fs7__namespace.existsSync(dir)) {
|
|
13475
|
-
fs7__namespace.mkdirSync(dir, { recursive: true });
|
|
13476
|
-
}
|
|
13477
|
-
fs7__namespace.writeFileSync(outputPath, serializeSarifDocument(doc, pretty), "utf-8");
|
|
13729
|
+
writeReportFileIfChanged(outputPath, serializeSarifDocument(doc, pretty));
|
|
13478
13730
|
}
|
|
13479
13731
|
|
|
13480
13732
|
// src/init/agent-templates.ts
|
|
@@ -16395,14 +16647,14 @@ function collectAgentFiles(targets) {
|
|
|
16395
16647
|
function writeProjectFiles(destDir, files, dryRun) {
|
|
16396
16648
|
const filesCreated = [];
|
|
16397
16649
|
for (const f of files) {
|
|
16398
|
-
const fullFilePath =
|
|
16399
|
-
const parentDir =
|
|
16650
|
+
const fullFilePath = path5__namespace.join(destDir, f.relPath);
|
|
16651
|
+
const parentDir = path5__namespace.dirname(fullFilePath);
|
|
16400
16652
|
if (!dryRun) {
|
|
16401
|
-
if (!
|
|
16402
|
-
|
|
16653
|
+
if (!fs8__namespace.existsSync(parentDir)) {
|
|
16654
|
+
fs8__namespace.mkdirSync(parentDir, { recursive: true });
|
|
16403
16655
|
}
|
|
16404
|
-
if (!
|
|
16405
|
-
|
|
16656
|
+
if (!fs8__namespace.existsSync(fullFilePath)) {
|
|
16657
|
+
fs8__namespace.writeFileSync(fullFilePath, f.content, "utf-8");
|
|
16406
16658
|
filesCreated.push(f.relPath);
|
|
16407
16659
|
}
|
|
16408
16660
|
} else {
|
|
@@ -16486,7 +16738,7 @@ function getCiWorkflowFiles(ciProvider) {
|
|
|
16486
16738
|
function createDirectoriesOnDisk(destDir, directories, dryRun) {
|
|
16487
16739
|
const created = [];
|
|
16488
16740
|
for (const d of directories) {
|
|
16489
|
-
if (!dryRun)
|
|
16741
|
+
if (!dryRun) fs8__namespace.mkdirSync(path5__namespace.join(destDir, d), { recursive: true });
|
|
16490
16742
|
created.push(d);
|
|
16491
16743
|
}
|
|
16492
16744
|
return created;
|
|
@@ -16501,7 +16753,7 @@ function collectStarterFiles(ciProvider, agentFiles = [], archGranularity = "min
|
|
|
16501
16753
|
...getCiWorkflowFiles(ciProvider),
|
|
16502
16754
|
...agentFiles,
|
|
16503
16755
|
...getArchitectureTemplates(archGranularity).map((t) => ({
|
|
16504
|
-
relPath:
|
|
16756
|
+
relPath: path5__namespace.join("templates", "architecture", t.filename).replace(/\\/g, "/"),
|
|
16505
16757
|
content: t.content
|
|
16506
16758
|
}))
|
|
16507
16759
|
];
|
|
@@ -16532,15 +16784,15 @@ function injectMissingScripts(scripts) {
|
|
|
16532
16784
|
return modified;
|
|
16533
16785
|
}
|
|
16534
16786
|
function updateConsumerPackageJson(destDir, dryRun) {
|
|
16535
|
-
const pkgPath =
|
|
16536
|
-
if (!
|
|
16787
|
+
const pkgPath = path5__namespace.join(destDir, "package.json");
|
|
16788
|
+
if (!fs8__namespace.existsSync(pkgPath)) return false;
|
|
16537
16789
|
try {
|
|
16538
|
-
const pkg = JSON.parse(
|
|
16790
|
+
const pkg = JSON.parse(fs8__namespace.readFileSync(pkgPath, "utf-8"));
|
|
16539
16791
|
if (!pkg || typeof pkg !== "object") return false;
|
|
16540
16792
|
pkg.scripts = pkg.scripts || {};
|
|
16541
16793
|
const modified = injectMissingScripts(pkg.scripts);
|
|
16542
16794
|
if (modified && !dryRun) {
|
|
16543
|
-
|
|
16795
|
+
fs8__namespace.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
16544
16796
|
}
|
|
16545
16797
|
return modified;
|
|
16546
16798
|
} catch {
|
|
@@ -16548,7 +16800,7 @@ function updateConsumerPackageJson(destDir, dryRun) {
|
|
|
16548
16800
|
}
|
|
16549
16801
|
}
|
|
16550
16802
|
function tryInstallGitHook(destDir, dryRun) {
|
|
16551
|
-
if (!dryRun &&
|
|
16803
|
+
if (!dryRun && fs8__namespace.existsSync(path5__namespace.join(destDir, ".git"))) {
|
|
16552
16804
|
return installGitHooks(destDir).success;
|
|
16553
16805
|
}
|
|
16554
16806
|
return false;
|
|
@@ -16597,7 +16849,7 @@ function validateInitOptions(options) {
|
|
|
16597
16849
|
}
|
|
16598
16850
|
function initProject(options = {}) {
|
|
16599
16851
|
const baseDir = options.rootDir || process.cwd();
|
|
16600
|
-
const destDir =
|
|
16852
|
+
const destDir = path5__namespace.resolve(baseDir, options.targetDir || ".");
|
|
16601
16853
|
const validated = validateInitOptions(options);
|
|
16602
16854
|
if (!validated.valid) return buildInitError(destDir, validated.error);
|
|
16603
16855
|
const agentTargets = resolveAgentTargets(options.agents);
|
|
@@ -16681,33 +16933,40 @@ function normalizeParsedRule(raw, idx) {
|
|
|
16681
16933
|
enabled: raw.enabled !== false
|
|
16682
16934
|
};
|
|
16683
16935
|
}
|
|
16936
|
+
function parseDlpDoc(yamlContent) {
|
|
16937
|
+
let doc;
|
|
16938
|
+
try {
|
|
16939
|
+
doc = yaml3__default.default.load(yamlContent);
|
|
16940
|
+
} catch (err) {
|
|
16941
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid YAML in dlp-policy: ${err instanceof Error ? err.message : String(err)}`);
|
|
16942
|
+
}
|
|
16943
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
16944
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid dlp-policy structure: root must be a YAML mapping`);
|
|
16945
|
+
}
|
|
16946
|
+
const record = doc;
|
|
16947
|
+
if (record.rules !== void 0 && !Array.isArray(record.rules)) {
|
|
16948
|
+
throw new Error(`[AI-SDLC Fail-Closed] Invalid dlp-policy: rules must be an array`);
|
|
16949
|
+
}
|
|
16950
|
+
return record;
|
|
16951
|
+
}
|
|
16684
16952
|
function parseDlpPolicy(yamlContent) {
|
|
16685
16953
|
const fallback = createDefaultDlpPolicy();
|
|
16686
16954
|
if (!yamlContent || !yamlContent.trim()) return fallback;
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
16695
|
-
|
|
16696
|
-
rules: parsedRules.length > 0 ? parsedRules : fallback.rules
|
|
16697
|
-
};
|
|
16698
|
-
} catch {
|
|
16699
|
-
return fallback;
|
|
16700
|
-
}
|
|
16955
|
+
const record = parseDlpDoc(yamlContent);
|
|
16956
|
+
const rawRules = Array.isArray(record.rules) ? record.rules : [];
|
|
16957
|
+
const parsedRules = rawRules.map((r, i) => normalizeParsedRule(r, i)).filter((r) => r !== null);
|
|
16958
|
+
return {
|
|
16959
|
+
version: typeof record.version === "string" ? record.version : "1.0",
|
|
16960
|
+
enabled: record.enabled !== false,
|
|
16961
|
+
zero_data_retention_required: Boolean(record.zero_data_retention_required ?? true),
|
|
16962
|
+
rules: parsedRules.length > 0 ? parsedRules : fallback.rules
|
|
16963
|
+
};
|
|
16701
16964
|
}
|
|
16702
16965
|
function loadDlpPolicy(rootDir, customPolicyPath) {
|
|
16703
16966
|
const base = rootDir || process.cwd();
|
|
16704
|
-
const filePath = customPolicyPath ?
|
|
16705
|
-
if (!
|
|
16706
|
-
|
|
16707
|
-
return parseDlpPolicy(fs7__namespace.readFileSync(filePath, "utf-8"));
|
|
16708
|
-
} catch {
|
|
16709
|
-
return createDefaultDlpPolicy();
|
|
16710
|
-
}
|
|
16967
|
+
const filePath = customPolicyPath ? path5__namespace.isAbsolute(customPolicyPath) ? customPolicyPath : path5__namespace.join(base, customPolicyPath) : path5__namespace.join(base, "dlp-policy.yaml");
|
|
16968
|
+
if (!fs8__namespace.existsSync(filePath)) return createDefaultDlpPolicy();
|
|
16969
|
+
return parseDlpPolicy(fs8__namespace.readFileSync(filePath, "utf-8"));
|
|
16711
16970
|
}
|
|
16712
16971
|
function applySingleDlpRule(text, rule) {
|
|
16713
16972
|
if (rule.enabled === false) return { output: text, matches: 0 };
|
|
@@ -16895,6 +17154,7 @@ exports.STARTER_WORKFLOW_HANDOFF_TEMPLATE = STARTER_WORKFLOW_HANDOFF_TEMPLATE;
|
|
|
16895
17154
|
exports.STARTER__GITHUB_PULL_REQUEST_TEMPLATE_MD = STARTER__GITHUB_PULL_REQUEST_TEMPLATE_MD;
|
|
16896
17155
|
exports.SpecKitAdapter = SpecKitAdapter;
|
|
16897
17156
|
exports.TYPE_TO_SCHEMA_MAP = TYPE_TO_SCHEMA_MAP;
|
|
17157
|
+
exports.UNIT_TEST_SUFFIXES = UNIT_TEST_SUFFIXES;
|
|
16898
17158
|
exports.VALID_AGENT_TARGETS = VALID_AGENT_TARGETS;
|
|
16899
17159
|
exports.VALID_ARCHITECTURE_GRANULARITIES = VALID_ARCHITECTURE_GRANULARITIES;
|
|
16900
17160
|
exports.VALID_AUTONOMY_MODES = VALID_AUTONOMY_MODES;
|
|
@@ -16914,6 +17174,7 @@ exports.calculateJaccardSimilarity = calculateJaccardSimilarity;
|
|
|
16914
17174
|
exports.calculateMetrics = calculateMetrics;
|
|
16915
17175
|
exports.calculateRating = calculateRating;
|
|
16916
17176
|
exports.calculateShannonEntropy = calculateShannonEntropy;
|
|
17177
|
+
exports.canonicalSort = canonicalSort;
|
|
16917
17178
|
exports.checkGherkinInSync = checkGherkinInSync;
|
|
16918
17179
|
exports.checkoutTaskBranch = checkoutTaskBranch;
|
|
16919
17180
|
exports.classifyBranch = classifyBranch;
|
|
@@ -16922,6 +17183,7 @@ exports.collectAgentFiles = collectAgentFiles;
|
|
|
16922
17183
|
exports.collectDashboardMetrics = collectDashboardMetrics;
|
|
16923
17184
|
exports.collectHistoricalKpis = collectHistoricalKpis;
|
|
16924
17185
|
exports.computeCanonicalSha256 = computeCanonicalSha256;
|
|
17186
|
+
exports.computeRawSha256 = computeRawSha256;
|
|
16925
17187
|
exports.computeSha256 = computeSha256;
|
|
16926
17188
|
exports.computeViolationSignature = computeViolationSignature;
|
|
16927
17189
|
exports.createDefaultDlpPolicy = createDefaultDlpPolicy;
|
|
@@ -16947,10 +17209,13 @@ exports.extractFunctions = extractFunctions;
|
|
|
16947
17209
|
exports.extractFunctionsBraceLanguages = extractFunctionsBraceLanguages;
|
|
16948
17210
|
exports.extractFunctionsPolyglot = extractFunctionsPolyglot;
|
|
16949
17211
|
exports.extractFunctionsPython = extractFunctionsPython;
|
|
17212
|
+
exports.extractFunctionsPythonAst = extractFunctionsPythonAst;
|
|
16950
17213
|
exports.extractFunctionsTypeScriptAst = extractFunctionsTypeScriptAst;
|
|
16951
17214
|
exports.extractGherkinBlock = extractGherkinBlock;
|
|
16952
17215
|
exports.extractGherkinFeatures = extractGherkinFeatures;
|
|
16953
17216
|
exports.extractNormativeText = extractNormativeText;
|
|
17217
|
+
exports.extractPythonFunctionsPolyglot = extractPythonFunctionsPolyglot;
|
|
17218
|
+
exports.extractSemanticContent = extractSemanticContent;
|
|
16954
17219
|
exports.findChangeWorkspaceDir = findChangeWorkspaceDir;
|
|
16955
17220
|
exports.findLicenseFile = findLicenseFile;
|
|
16956
17221
|
exports.formatCommitTrailers = formatCommitTrailers;
|
|
@@ -16959,6 +17224,7 @@ exports.formatReleaseKpiMarkdown = formatReleaseKpiMarkdown;
|
|
|
16959
17224
|
exports.generateBranchHierarchyPlan = generateBranchHierarchyPlan;
|
|
16960
17225
|
exports.generateCycloneDxSbom = generateCycloneDxSbom;
|
|
16961
17226
|
exports.generateDashboardReport = generateDashboardReport;
|
|
17227
|
+
exports.generateDeterministicUuidV5 = generateDeterministicUuidV5;
|
|
16962
17228
|
exports.generateDuplicatesReportMarkdown = generateDuplicatesReportMarkdown;
|
|
16963
17229
|
exports.generateGovernanceReportMarkdown = generateGovernanceReportMarkdown;
|
|
16964
17230
|
exports.generateLicenseReportMarkdown = generateLicenseReportMarkdown;
|
|
@@ -16984,10 +17250,13 @@ exports.initProject = initProject;
|
|
|
16984
17250
|
exports.injectKpisIntoPrBody = injectKpisIntoPrBody;
|
|
16985
17251
|
exports.installGitHooks = installGitHooks;
|
|
16986
17252
|
exports.integrateSddChange = integrateSddChange;
|
|
17253
|
+
exports.isCliInstalled = isCliInstalled;
|
|
17254
|
+
exports.isFunctionReferencedInTests = isFunctionReferencedInTests;
|
|
16987
17255
|
exports.isGitleaksInstalled = isGitleaksInstalled;
|
|
16988
17256
|
exports.isMetricWorsened = isMetricWorsened;
|
|
16989
17257
|
exports.isPathProtectedFromPatch = isPathProtectedFromPatch;
|
|
16990
17258
|
exports.isPlaceholderOrIgnored = isPlaceholderOrIgnored;
|
|
17259
|
+
exports.isReportContentSemanticallyEqual = isReportContentSemanticallyEqual;
|
|
16991
17260
|
exports.isRequirementArtifact = isRequirementArtifact2;
|
|
16992
17261
|
exports.isSemgrepInstalled = isSemgrepInstalled;
|
|
16993
17262
|
exports.isToolAvailable = isToolAvailable;
|
|
@@ -16998,8 +17267,10 @@ exports.loadQualityBaseline = loadQualityBaseline;
|
|
|
16998
17267
|
exports.locateTaskInActiveChanges = locateTaskInActiveChanges;
|
|
16999
17268
|
exports.mapSastSeverityToSarifLevel = mapSastSeverityToSarifLevel;
|
|
17000
17269
|
exports.maskSecret = maskSecret;
|
|
17270
|
+
exports.normalizePath = normalizePath;
|
|
17001
17271
|
exports.normalizeSpdxLicense = normalizeSpdxLicense;
|
|
17002
17272
|
exports.normalizeUri = normalizeUri;
|
|
17273
|
+
exports.orchestrateNativeAstAnalysis = orchestrateNativeAstAnalysis;
|
|
17003
17274
|
exports.parseCommitTrailers = parseCommitTrailers;
|
|
17004
17275
|
exports.parseDlpPolicy = parseDlpPolicy;
|
|
17005
17276
|
exports.parseFeatureDocument = parseFeatureDocument;
|
|
@@ -17034,6 +17305,7 @@ exports.resolveBranchHierarchy = resolveBranchHierarchy;
|
|
|
17034
17305
|
exports.resolveCiEnvironment = resolveCiEnvironment;
|
|
17035
17306
|
exports.resolveCytoscapePath = resolveCytoscapePath;
|
|
17036
17307
|
exports.resolveDefaultBranchBase = resolveDefaultBranchBase;
|
|
17308
|
+
exports.resolveDeterministicDate = resolveDeterministicDate;
|
|
17037
17309
|
exports.resolveFeatureFilePath = resolveFeatureFilePath;
|
|
17038
17310
|
exports.resolveGitRef = resolveGitRef;
|
|
17039
17311
|
exports.resolveLicenseFromFile = resolveLicenseFromFile;
|
|
@@ -17046,6 +17318,7 @@ exports.runGitleaksCli = runGitleaksCli;
|
|
|
17046
17318
|
exports.runSemgrepCli = runSemgrepCli;
|
|
17047
17319
|
exports.sanitizeDlpPayload = sanitizeDlpPayload;
|
|
17048
17320
|
exports.sanitizeDlpText = sanitizeDlpText;
|
|
17321
|
+
exports.sanitizeTestCorpus = sanitizeTestCorpus;
|
|
17049
17322
|
exports.scaffoldSddChange = scaffoldSddChange;
|
|
17050
17323
|
exports.scanAllProductHandoffs = scanAllProductHandoffs;
|
|
17051
17324
|
exports.scanAllTestArtifacts = scanAllTestArtifacts;
|
|
@@ -17064,11 +17337,13 @@ exports.scanWithNativeFs = scanWithNativeFs;
|
|
|
17064
17337
|
exports.scanWithSyft = scanWithSyft;
|
|
17065
17338
|
exports.scanWithTrivy = scanWithTrivy;
|
|
17066
17339
|
exports.serializeSarifDocument = serializeSarifDocument;
|
|
17067
|
-
exports.slugify =
|
|
17340
|
+
exports.slugify = slugify2;
|
|
17341
|
+
exports.stripVolatileReportMetadata = stripVolatileReportMetadata;
|
|
17068
17342
|
exports.syncPdacDigests = syncPdacDigests;
|
|
17069
17343
|
exports.tokenizeTitle = tokenizeTitle;
|
|
17070
17344
|
exports.updateCitationsInContent = updateCitationsInContent;
|
|
17071
17345
|
exports.validateAgainstSchema = validateAgainstSchema;
|
|
17346
|
+
exports.validateAnchorExistence = validateAnchorExistence;
|
|
17072
17347
|
exports.validateArtifactSchema = validateArtifactSchema;
|
|
17073
17348
|
exports.verifyArtifactDuplicates = verifyArtifactDuplicates;
|
|
17074
17349
|
exports.verifyArtifactsSchemas = verifyArtifactsSchemas;
|
|
@@ -17093,6 +17368,7 @@ exports.writeCucumberJsonReport = writeCucumberJsonReport;
|
|
|
17093
17368
|
exports.writeCycloneDxSbom = writeCycloneDxSbom;
|
|
17094
17369
|
exports.writeQualityBaseline = writeQualityBaseline;
|
|
17095
17370
|
exports.writeReleaseKpiReport = writeReleaseKpiReport;
|
|
17371
|
+
exports.writeReportFileIfChanged = writeReportFileIfChanged;
|
|
17096
17372
|
exports.writeSecuritySarifReport = writeSecuritySarifReport;
|
|
17097
17373
|
exports.writeThirdPartyNotices = writeThirdPartyNotices;
|
|
17098
17374
|
//# sourceMappingURL=index.cjs.map
|