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