@aisdlc/core 2.2.3 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -5685,6 +5685,214 @@ function scanPnpmStore(pnpmDir, directNames, seen) {
5685
5685
  }
5686
5686
  }
5687
5687
  }
5688
+ function readTextIfExists(filePath) {
5689
+ if (!fs7__namespace.existsSync(filePath)) return null;
5690
+ try {
5691
+ return fs7__namespace.readFileSync(filePath, "utf-8");
5692
+ } catch {
5693
+ return null;
5694
+ }
5695
+ }
5696
+ function extractTomlField(block, key) {
5697
+ const m = block.match(new RegExp(`^\\s*${key}\\s*=\\s*["']([^"']+)["']`, "m"));
5698
+ return m ? m[1].trim() : void 0;
5699
+ }
5700
+ function extractPyprojectDirectDeps(content) {
5701
+ const direct = /* @__PURE__ */ new Set();
5702
+ const matches = content.matchAll(/^([a-zA-Z0-9_.-]+)\s*=\s*(?:["'{]|true)/gm);
5703
+ for (const m of matches) {
5704
+ const key = m[1].toLowerCase();
5705
+ if (!["name", "version", "description", "license", "python", "readme"].includes(key)) {
5706
+ direct.add(key);
5707
+ }
5708
+ }
5709
+ return direct;
5710
+ }
5711
+ function parseSingleTomlBlock(block, lockPath, directNames) {
5712
+ const name = extractTomlField(block, "name");
5713
+ const version = extractTomlField(block, "version");
5714
+ if (!name || !version) return null;
5715
+ const spdx = normalizeSpdxLicense(extractTomlField(block, "license") || "UNKNOWN");
5716
+ return {
5717
+ name,
5718
+ version,
5719
+ spdxLicense: spdx,
5720
+ purl: `pkg:pypi/${name.toLowerCase()}@${version}`,
5721
+ path: lockPath,
5722
+ isDirect: directNames.size === 0 || directNames.has(name.toLowerCase())
5723
+ };
5724
+ }
5725
+ function parseTomlPackageBlocks(content, lockPath, directNames, seen) {
5726
+ for (const block of content.split(/\[\[package\]\]/g).slice(1)) {
5727
+ const dep = parseSingleTomlBlock(block, lockPath, directNames);
5728
+ if (dep && !seen.has(`pypi:${dep.name}@${dep.version}`)) {
5729
+ seen.set(`pypi:${dep.name}@${dep.version}`, dep);
5730
+ }
5731
+ }
5732
+ }
5733
+ function parsePipfileSection(pkgs, isDirect, lockPath, seen) {
5734
+ for (const [name, meta] of Object.entries(pkgs)) {
5735
+ const rawVer = String(meta.version || "").replace(/^==/, "") || "unknown";
5736
+ const spdx = normalizeSpdxLicense(meta.license || "UNKNOWN");
5737
+ const key = `pypi:${name}@${rawVer}`;
5738
+ if (!seen.has(key)) {
5739
+ seen.set(key, {
5740
+ name,
5741
+ version: rawVer,
5742
+ spdxLicense: spdx,
5743
+ purl: `pkg:pypi/${name.toLowerCase()}@${rawVer}`,
5744
+ path: lockPath,
5745
+ isDirect
5746
+ });
5747
+ }
5748
+ }
5749
+ }
5750
+ function scanPythonDependencies(rootDir, seen) {
5751
+ const pyproject = readTextIfExists(path4__namespace.join(rootDir, "pyproject.toml")) || "";
5752
+ const directNames = extractPyprojectDirectDeps(pyproject);
5753
+ for (const lockName of ["poetry.lock", "uv.lock"]) {
5754
+ const lockPath = path4__namespace.join(rootDir, lockName);
5755
+ const content = readTextIfExists(lockPath);
5756
+ if (content) parseTomlPackageBlocks(content, lockPath, directNames, seen);
5757
+ }
5758
+ const pipfileLockPath = path4__namespace.join(rootDir, "Pipfile.lock");
5759
+ const pipfileRaw = readTextIfExists(pipfileLockPath);
5760
+ if (!pipfileRaw) return;
5761
+ try {
5762
+ const data = JSON.parse(pipfileRaw);
5763
+ parsePipfileSection(data.default || {}, true, pipfileLockPath, seen);
5764
+ parsePipfileSection(data.develop || {}, false, pipfileLockPath, seen);
5765
+ } catch {
5766
+ }
5767
+ }
5768
+ function parseGoRequireLine(line, goModPath) {
5769
+ const cleaned = line.replace(/^require\s+/, "").trim();
5770
+ const commentIdx = cleaned.indexOf("//");
5771
+ const codePart = (commentIdx >= 0 ? cleaned.slice(0, commentIdx) : cleaned).trim();
5772
+ const commentPart = commentIdx >= 0 ? cleaned.slice(commentIdx + 2).trim() : "";
5773
+ const parts = codePart.split(/\s+/);
5774
+ if (parts.length < 2) return null;
5775
+ const modName = parts[0];
5776
+ const rawVer = parts[1].replace(/^v/, "");
5777
+ const isIndirect = /\bindirect\b/i.test(commentPart);
5778
+ const licenseMatch = commentPart.match(/(?:spdx|license)\s*:\s*([A-Za-z0-9.+-]+)/i);
5779
+ const spdx = licenseMatch ? normalizeSpdxLicense(licenseMatch[1]) : "UNKNOWN";
5780
+ return {
5781
+ name: modName,
5782
+ version: rawVer,
5783
+ spdxLicense: spdx,
5784
+ purl: `pkg:golang/${modName}@v${rawVer}`,
5785
+ path: goModPath,
5786
+ isDirect: !isIndirect
5787
+ };
5788
+ }
5789
+ function extractGoCandidateLines(content) {
5790
+ const out = [];
5791
+ let inBlock = false;
5792
+ for (const rawLine of content.split(/\r?\n/)) {
5793
+ const trimmed = rawLine.trim();
5794
+ if (/^require\s*\($/.test(trimmed)) inBlock = true;
5795
+ else if (inBlock && trimmed === ")") inBlock = false;
5796
+ else if (inBlock || trimmed.startsWith("require ")) out.push(trimmed);
5797
+ }
5798
+ return out;
5799
+ }
5800
+ function scanGoDependencies(rootDir, seen) {
5801
+ const goModPath = path4__namespace.join(rootDir, "go.mod");
5802
+ const content = readTextIfExists(goModPath);
5803
+ if (!content) return;
5804
+ for (const line of extractGoCandidateLines(content)) {
5805
+ const dep = parseGoRequireLine(line, goModPath);
5806
+ if (dep && !seen.has(`golang:${dep.name}@${dep.version}`)) {
5807
+ seen.set(`golang:${dep.name}@${dep.version}`, dep);
5808
+ }
5809
+ }
5810
+ }
5811
+ function extractCargoDirectDeps(cargoToml) {
5812
+ const map = /* @__PURE__ */ new Map();
5813
+ let inDeps = false;
5814
+ for (const line of cargoToml.split(/\r?\n/)) {
5815
+ const trimmed = line.trim();
5816
+ if (trimmed.startsWith("[")) {
5817
+ inDeps = /^\[(?:dev-|build-)?dependencies\]$/.test(trimmed);
5818
+ continue;
5819
+ }
5820
+ if (!inDeps || !trimmed || trimmed.startsWith("#")) continue;
5821
+ const m = trimmed.match(/^([a-zA-Z0-9_-]+)\s*=\s*(.+)$/);
5822
+ if (m) {
5823
+ const licMatch = m[2].match(/license\s*=\s*["']([^"']+)["']/);
5824
+ map.set(m[1], licMatch ? licMatch[1] : "UNKNOWN");
5825
+ }
5826
+ }
5827
+ return map;
5828
+ }
5829
+ function parseCargoLockBlock(block, rootName, directMeta, cargoLockPath) {
5830
+ const name = extractTomlField(block, "name");
5831
+ const version = extractTomlField(block, "version");
5832
+ if (!name || !version || name === rootName) return null;
5833
+ const blockLic = extractTomlField(block, "license") || directMeta.get(name) || "UNKNOWN";
5834
+ return {
5835
+ name,
5836
+ version,
5837
+ spdxLicense: normalizeSpdxLicense(blockLic),
5838
+ purl: `pkg:cargo/${name}@${version}`,
5839
+ path: cargoLockPath,
5840
+ isDirect: directMeta.size === 0 || directMeta.has(name)
5841
+ };
5842
+ }
5843
+ function scanRustDependencies(rootDir, seen) {
5844
+ const cargoLockPath = path4__namespace.join(rootDir, "Cargo.lock");
5845
+ const lockContent = readTextIfExists(cargoLockPath);
5846
+ if (!lockContent) return;
5847
+ const cargoToml = readTextIfExists(path4__namespace.join(rootDir, "Cargo.toml")) || "";
5848
+ const directMeta = extractCargoDirectDeps(cargoToml);
5849
+ const rootName = extractTomlField(cargoToml, "name");
5850
+ for (const block of lockContent.split(/\[\[package\]\]/g).slice(1)) {
5851
+ const dep = parseCargoLockBlock(block, rootName, directMeta, cargoLockPath);
5852
+ if (dep && !seen.has(`cargo:${dep.name}@${dep.version}`)) {
5853
+ seen.set(`cargo:${dep.name}@${dep.version}`, dep);
5854
+ }
5855
+ }
5856
+ }
5857
+ function extractXmlTag(xml, tag) {
5858
+ const m = xml.match(new RegExp(`<${tag}>([^<]+)</${tag}>`));
5859
+ return m ? m[1].trim() : void 0;
5860
+ }
5861
+ function resolveMavenBlockLicense(block, defaultLic) {
5862
+ const tagLic = extractXmlTag(block, "license");
5863
+ if (tagLic) return tagLic;
5864
+ const commentLic = block.match(/<!--\s*(?:spdx|license)\s*:\s*([A-Za-z0-9.+-]+)\s*-->/i)?.[1];
5865
+ return commentLic || defaultLic;
5866
+ }
5867
+ function parseMavenDepBlock(block, defaultLic, pomPath) {
5868
+ const groupId = extractXmlTag(block, "groupId");
5869
+ const artifactId = extractXmlTag(block, "artifactId");
5870
+ if (!groupId || !artifactId) return null;
5871
+ const version = extractXmlTag(block, "version") || "unknown";
5872
+ const inlineLic = resolveMavenBlockLicense(block, defaultLic);
5873
+ return {
5874
+ name: `${groupId}:${artifactId}`,
5875
+ version,
5876
+ spdxLicense: normalizeSpdxLicense(inlineLic),
5877
+ purl: `pkg:maven/${groupId}/${artifactId}@${version}`,
5878
+ path: pomPath,
5879
+ isDirect: true
5880
+ };
5881
+ }
5882
+ function scanMavenDependencies(rootDir, seen) {
5883
+ const pomPath = path4__namespace.join(rootDir, "pom.xml");
5884
+ const content = readTextIfExists(pomPath);
5885
+ if (!content) return;
5886
+ const projectLicBlock = content.match(/<licenses>([\s\S]*?)<\/licenses>/)?.[1] || "";
5887
+ const defaultLic = extractXmlTag(projectLicBlock, "name") || "UNKNOWN";
5888
+ const depBlocks = content.match(/<dependency>[\s\S]*?<\/dependency>/g) || [];
5889
+ for (const block of depBlocks) {
5890
+ const dep = parseMavenDepBlock(block, defaultLic, pomPath);
5891
+ if (dep && !seen.has(`maven:${dep.name}@${dep.version}`)) {
5892
+ seen.set(`maven:${dep.name}@${dep.version}`, dep);
5893
+ }
5894
+ }
5895
+ }
5688
5896
  function scanWithNativeFs(rootDir, options = {}) {
5689
5897
  const directNames = extractDirectDepNames(rootDir, options.productionOnly);
5690
5898
  const seen = /* @__PURE__ */ new Map();
@@ -5695,6 +5903,10 @@ function scanWithNativeFs(rootDir, options = {}) {
5695
5903
  scanPnpmStore(path4__namespace.join(nmDir, ".pnpm"), directNames, seen);
5696
5904
  }
5697
5905
  }
5906
+ scanPythonDependencies(rootDir, seen);
5907
+ scanGoDependencies(rootDir, seen);
5908
+ scanRustDependencies(rootDir, seen);
5909
+ scanMavenDependencies(rootDir, seen);
5698
5910
  const allDeps = Array.from(seen.values());
5699
5911
  if (options.depth === "direct") {
5700
5912
  return allDeps.filter((d) => d.isDirect);
@@ -5764,10 +5976,17 @@ var SEVERITY_RANK = {
5764
5976
  LOW: 1,
5765
5977
  UNKNOWN: 0
5766
5978
  };
5979
+ function isExemptByVex(v, vexStatements) {
5980
+ if (!vexStatements || vexStatements.length === 0) return false;
5981
+ return vexStatements.some(
5982
+ (stmt) => stmt.id === v.id && (stmt.state === "not_affected" || stmt.state === "fixed") && (!stmt.package || stmt.package === v.packageName)
5983
+ );
5984
+ }
5767
5985
  function evaluateVulnerabilityThresholds(vulnerabilities, options) {
5768
5986
  const minRank = options?.failOnSeverity ? SEVERITY_RANK[options.failOnSeverity] ?? 3 : void 0;
5769
5987
  const maxScore = options?.maxCvssScore;
5770
5988
  const failing = vulnerabilities.filter((v) => {
5989
+ if (isExemptByVex(v, options?.vexStatements)) return false;
5771
5990
  const rankMatch = minRank !== void 0 && SEVERITY_RANK[v.severity] >= minRank;
5772
5991
  const scoreMatch = maxScore !== void 0 && v.cvssV3Score !== void 0 && v.cvssV3Score >= maxScore;
5773
5992
  return rankMatch || scoreMatch;
@@ -5897,42 +6116,77 @@ function buildCycloneDxComponent(dep) {
5897
6116
  }
5898
6117
  return comp;
5899
6118
  }
5900
- function generateCycloneDxSbom(dependencies, options = {}) {
6119
+ function buildVexAnalysis(vex) {
6120
+ if (!vex) return void 0;
6121
+ return {
6122
+ state: vex.state,
6123
+ justification: vex.justification,
6124
+ detail: vex.detail,
6125
+ response: vex.response
6126
+ };
6127
+ }
6128
+ function mapScaVulnToCycloneDx(v, vexStatements) {
6129
+ const matchingVex = vexStatements.find(
6130
+ (stmt) => stmt.id === v.id && (!stmt.package || stmt.package === v.packageName)
6131
+ );
6132
+ return {
6133
+ id: v.id,
6134
+ source: v.primaryUrl ? { name: "NVD/GHSA", url: v.primaryUrl } : void 0,
6135
+ ratings: [{ score: v.cvssV3Score, severity: v.severity.toLowerCase(), method: "CVSSv3", vector: v.cvssV3Vector }],
6136
+ analysis: buildVexAnalysis(matchingVex),
6137
+ affects: [{ ref: `pkg:npm/${v.packageName}@${v.installedVersion}` }]
6138
+ };
6139
+ }
6140
+ function mapStandaloneVex(stmt) {
6141
+ return {
6142
+ id: stmt.id,
6143
+ analysis: buildVexAnalysis(stmt),
6144
+ affects: stmt.package ? [{ ref: stmt.package }] : void 0
6145
+ };
6146
+ }
6147
+ function buildCycloneDxVulnerabilities(vulnerabilities = [], vexStatements = []) {
6148
+ const out = vulnerabilities.map((v) => mapScaVulnToCycloneDx(v, vexStatements));
6149
+ const seenIds = new Set(out.map((item) => item.id));
6150
+ for (const stmt of vexStatements) {
6151
+ if (!seenIds.has(stmt.id)) {
6152
+ out.push(mapStandaloneVex(stmt));
6153
+ seenIds.add(stmt.id);
6154
+ }
6155
+ }
6156
+ return out;
6157
+ }
6158
+ function resolveProjectIdentity(options) {
5901
6159
  let name = options.projectName || "ai-sdlc-project";
5902
6160
  let version = options.projectVersion || "1.0.0";
5903
- if (options.rootDir) {
5904
- const pkgPath = path4__namespace.join(options.rootDir, "package.json");
5905
- if (fs7__namespace.existsSync(pkgPath)) {
5906
- try {
5907
- const pkg = JSON.parse(fs7__namespace.readFileSync(pkgPath, "utf-8"));
5908
- if (pkg.name) name = pkg.name;
5909
- if (pkg.version) version = pkg.version;
5910
- } catch {
5911
- }
5912
- }
6161
+ if (!options.rootDir) return { name, version };
6162
+ const pkgPath = path4__namespace.join(options.rootDir, "package.json");
6163
+ if (!fs7__namespace.existsSync(pkgPath)) return { name, version };
6164
+ try {
6165
+ const pkg = JSON.parse(fs7__namespace.readFileSync(pkgPath, "utf-8"));
6166
+ if (pkg.name) name = pkg.name;
6167
+ if (pkg.version) version = pkg.version;
6168
+ } catch {
5913
6169
  }
5914
- const components = dependencies.map(buildCycloneDxComponent);
6170
+ return { name, version };
6171
+ }
6172
+ function buildBomMetadata(name, version) {
6173
+ return {
6174
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6175
+ tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.3.0" }],
6176
+ component: { type: "application", name, version }
6177
+ };
6178
+ }
6179
+ function generateCycloneDxSbom(dependencies, options = {}) {
6180
+ const { name, version } = resolveProjectIdentity(options);
6181
+ const cdxVulns = buildCycloneDxVulnerabilities(options.vulnerabilities, options.vexStatements);
5915
6182
  return {
5916
6183
  bomFormat: "CycloneDX",
5917
6184
  specVersion: "1.5",
5918
6185
  version: 1,
5919
6186
  serialNumber: `urn:uuid:${crypto__namespace.randomUUID()}`,
5920
- metadata: {
5921
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5922
- tools: [
5923
- {
5924
- vendor: "AI-SDLC",
5925
- name: "@aisdlc/core",
5926
- version: "1.0.0"
5927
- }
5928
- ],
5929
- component: {
5930
- type: "application",
5931
- name,
5932
- version
5933
- }
5934
- },
5935
- components
6187
+ metadata: buildBomMetadata(name, version),
6188
+ components: dependencies.map(buildCycloneDxComponent),
6189
+ ...cdxVulns.length > 0 ? { vulnerabilities: cdxVulns } : {}
5936
6190
  };
5937
6191
  }
5938
6192
  function tryAtomicWrite(filePath, content) {
@@ -6100,17 +6354,24 @@ function parseExceptions(exceptions) {
6100
6354
  }
6101
6355
  return map;
6102
6356
  }
6357
+ function parseVexStatements(doc) {
6358
+ const rawList = doc?.vex_statements || doc?.exceptions?.vex_statements || [];
6359
+ if (!Array.isArray(rawList)) return [];
6360
+ return rawList.filter((item) => Boolean(item && typeof item.id === "string" && typeof item.state === "string"));
6361
+ }
6103
6362
  function parseLicensePolicy(content) {
6104
6363
  try {
6105
6364
  const doc = yaml3__default.default.load(content);
6106
6365
  const { permitted, restricted, blocked } = classifyCategories(doc?.categories);
6107
6366
  const exception_packages = parseExceptions(doc?.exceptions);
6367
+ const vex_statements = parseVexStatements(doc);
6108
6368
  return {
6109
6369
  version: doc?.version || "1.0",
6110
6370
  permitted: permitted.length > 0 ? permitted : DEFAULT_PERMITTED,
6111
6371
  restricted: restricted.length > 0 ? restricted : DEFAULT_RESTRICTED,
6112
6372
  blocked: blocked.length > 0 ? blocked : DEFAULT_BLOCKED,
6113
6373
  exception_packages,
6374
+ vex_statements,
6114
6375
  rawPolicy: doc
6115
6376
  };
6116
6377
  } catch {
@@ -6119,7 +6380,8 @@ function parseLicensePolicy(content) {
6119
6380
  permitted: DEFAULT_PERMITTED,
6120
6381
  restricted: DEFAULT_RESTRICTED,
6121
6382
  blocked: DEFAULT_BLOCKED,
6122
- exception_packages: {}
6383
+ exception_packages: {},
6384
+ vex_statements: []
6123
6385
  };
6124
6386
  }
6125
6387
  }
@@ -6294,10 +6556,10 @@ function shouldGenerateSbom(options, rawPolicy) {
6294
6556
  if (options.generateSbom === false) return false;
6295
6557
  return Boolean(options.generateSbom || options.sbomPath || rawPolicy?.ci_gates?.generate_sbom);
6296
6558
  }
6297
- function maybeGenerateSbom(options, rootDir, rawPolicy, dependencies) {
6559
+ function maybeGenerateSbom(options, rootDir, rawPolicy, dependencies, vulnerabilities, vexStatements) {
6298
6560
  if (!shouldGenerateSbom(options, rawPolicy)) return void 0;
6299
6561
  const sbomPath = options.sbomPath || path4__namespace.join(rootDir, "reports", "sbom.cdx.json");
6300
- const sbom = generateCycloneDxSbom(dependencies, { rootDir });
6562
+ const sbom = generateCycloneDxSbom(dependencies, { rootDir, vulnerabilities, vexStatements });
6301
6563
  writeCycloneDxSbom(sbom, sbomPath);
6302
6564
  return sbomPath;
6303
6565
  }
@@ -6310,9 +6572,9 @@ function maybeGenerateNotices(options, rootDir, rawPolicy, dependencies) {
6310
6572
  writeThirdPartyNotices(noticesContent, noticesPath);
6311
6573
  return noticesPath;
6312
6574
  }
6313
- function handleDynamicScaArtifacts(options, rootDir, rawPolicy, dependencies) {
6575
+ function handleDynamicScaArtifacts(options, rootDir, rawPolicy, dependencies, vulnerabilities, vexStatements) {
6314
6576
  return {
6315
- sbomPath: maybeGenerateSbom(options, rootDir, rawPolicy, dependencies),
6577
+ sbomPath: maybeGenerateSbom(options, rootDir, rawPolicy, dependencies, vulnerabilities, vexStatements),
6316
6578
  noticesPath: maybeGenerateNotices(options, rootDir, rawPolicy, dependencies)
6317
6579
  };
6318
6580
  }
@@ -6341,7 +6603,7 @@ function buildSkippedVulnResult() {
6341
6603
  skipReason: "Trivy scanner is not installed on host environment. Vulnerability check gracefully skipped."
6342
6604
  };
6343
6605
  }
6344
- function checkVulnerabilities(rootDir, options) {
6606
+ function checkVulnerabilities(rootDir, options, vexStatements) {
6345
6607
  if (options.scanVulnerabilities === false) {
6346
6608
  return { vulnerabilities: [], blocked: false };
6347
6609
  }
@@ -6349,7 +6611,10 @@ function checkVulnerabilities(rootDir, options) {
6349
6611
  if (!res.executed) {
6350
6612
  return buildSkippedVulnResult();
6351
6613
  }
6352
- const evalResult = evaluateVulnerabilityThresholds(res.vulnerabilities, options.vulnerabilityThresholds);
6614
+ const evalResult = evaluateVulnerabilityThresholds(res.vulnerabilities, {
6615
+ ...options.vulnerabilityThresholds,
6616
+ vexStatements
6617
+ });
6353
6618
  return { vulnerabilities: res.vulnerabilities, blocked: evalResult.blocked };
6354
6619
  }
6355
6620
  function assembleDynamicLicenseResult(dependencies, permittedCount, violations, reportMarkdown, artifacts, vulnData) {
@@ -6373,7 +6638,8 @@ function toCleanPolicy(policyWithRaw) {
6373
6638
  permitted: policyWithRaw.permitted,
6374
6639
  restricted: policyWithRaw.restricted,
6375
6640
  blocked: policyWithRaw.blocked,
6376
- exception_packages: policyWithRaw.exception_packages
6641
+ exception_packages: policyWithRaw.exception_packages,
6642
+ vex_statements: policyWithRaw.vex_statements
6377
6643
  };
6378
6644
  }
6379
6645
  function scanDependenciesForVerification(options, rootDir, rawPolicy) {
@@ -6393,8 +6659,15 @@ function verifyLicenses(options = {}) {
6393
6659
  }
6394
6660
  const dependencies = scanDependenciesForVerification(options, rootDir, policyWithRaw.rawPolicy);
6395
6661
  const { violations, permittedCount } = evaluateDependencyList(dependencies, policy);
6396
- const artifacts = handleDynamicScaArtifacts(options, rootDir, policyWithRaw.rawPolicy, dependencies);
6397
- const vulnData = checkVulnerabilities(rootDir, options);
6662
+ const vulnData = checkVulnerabilities(rootDir, options, policy.vex_statements);
6663
+ const artifacts = handleDynamicScaArtifacts(
6664
+ options,
6665
+ rootDir,
6666
+ policyWithRaw.rawPolicy,
6667
+ dependencies,
6668
+ vulnData.vulnerabilities,
6669
+ policy.vex_statements
6670
+ );
6398
6671
  const reportMarkdown = generateLicenseReportMarkdown(dependencies, violations, policy, artifacts.sbomPath);
6399
6672
  return assembleDynamicLicenseResult(dependencies, permittedCount, violations, reportMarkdown, artifacts, vulnData);
6400
6673
  }
@@ -13090,6 +13363,7 @@ var LIFECYCLE_SKILL_TEMPLATES = [
13090
13363
  title: "Greenfield Project Inception & Delivery Playbook",
13091
13364
  description: "Orchestrate end-to-end AI-SDLC onboarding and Phase 0 Inception for brand-new (Greenfield) projects, from repository initialization (aisdlc init) and NAF v4 Vision (ARCH-INTRO-*) through ProductShape, STRIDE Threat Modeling, arc42 Architecture, and initial SDD increment delivery.",
13092
13365
  references: "[`process/02_product_definition.md`](../../process/02_product_definition.md), [`process/04b_data_protection_and_privacy.md`](../../process/04b_data_protection_and_privacy.md), [`process/05_architecture_arc42_nafv4.md`](../../process/05_architecture_arc42_nafv4.md), [`process/06_spec_driven_development.md`](../../process/06_spec_driven_development.md), and [`process/09_agent_protocols.md`](../../process/09_agent_protocols.md).",
13366
+ globs: "product/**, specs/product/**, architecture/**, templates/**",
13093
13367
  body: `## Purpose & Trigger
13094
13368
  Use this skill when starting a **brand-new software project from scratch (Greenfield)** or when \`specs/product/\` has no canonical baseline yet. It coordinates the sequential execution of AI-SDLC MCP/CLI tools and specialized role agents so no code is written before product vision, security enclaves, privacy-by-design controls, and architecture contracts exist.
13095
13369
 
@@ -13147,6 +13421,7 @@ Use this skill when starting a **brand-new software project from scratch (Greenf
13147
13421
  title: "Legacy / Brownfield Retro-Inception Playbook",
13148
13422
  description: "Orchestrate progressive AI-SDLC adoption and Brownfield retro-inception on existing or legacy repositories without breaking production code, reverse-engineering architecture (ARCH-INTRO-*, CMP-*) and establishing baseline quality, security, and license audits before new SDD increments.",
13149
13423
  references: "[`process/02_product_definition.md`](../../process/02_product_definition.md), [`process/04_open_source_license_compliance.md`](../../process/04_open_source_license_compliance.md), [`process/04b_data_protection_and_privacy.md`](../../process/04b_data_protection_and_privacy.md), [`process/05_architecture_arc42_nafv4.md`](../../process/05_architecture_arc42_nafv4.md), [`process/10_quality_management_and_release_gates.md`](../../process/10_quality_management_and_release_gates.md), and [`process/09_agent_protocols.md`](../../process/09_agent_protocols.md).",
13424
+ globs: "architecture/**, specs/**, src/**, packages/**",
13150
13425
  body: `## Purpose & Trigger
13151
13426
  Use this skill when **adopting AI-SDLC in an existing or legacy codebase (Brownfield)** that already has production source code (\`src/\`, \`packages/\`, etc.) but lacks canonical PDaC specifications, arc42 architecture views, or deterministic quality/license/privacy gates.
13152
13427
 
@@ -13188,6 +13463,7 @@ Once the target domain's baseline artifacts (\`ARCH-INTRO-*\`, \`UC-*\`, \`FR-*\
13188
13463
  title: "Bounded SDD Feature & Bugfix Delivery Playbook",
13189
13464
  description: "Orchestrate the complete Spec-Driven Development (SDD) lifecycle for bounded feature increments, enhancements, and bugfixes (CHG-* / TSK-*), from upstream requirement derivation and 4-tier Git branching through Red-Green-Refactor TDD, Pre-Merge Audit Triad, all 11 Quality Gates, and canonical baseline integration.",
13190
13465
  references: "[`process/06_spec_driven_development.md`](../../process/06_spec_driven_development.md), [`process/08_citation_contract_and_drift.md`](../../process/08_citation_contract_and_drift.md), [`process/10_quality_management_and_release_gates.md`](../../process/10_quality_management_and_release_gates.md), [`process/11_git_branching_and_lifecycle.md`](../../process/11_git_branching_and_lifecycle.md), and [`process/09_agent_protocols.md`](../../process/09_agent_protocols.md).",
13466
+ globs: "specs/changes/**, src/**, packages/**, tests/**",
13191
13467
  body: `## Purpose & Trigger
13192
13468
  Use this skill for **every evolutionary increment (new feature, enhancement, refactoring, or bugfix)** on a project that already has an AI-SDLC baseline. It enforces strict 360\xB0 traceability from upstream ProductShape (\`UC-*\`, \`FR-*\`) through SDD (\`CHG-*\`, \`HOF-*\`, \`TSK-*\`), TDD implementation, the 12 Quality Gates, and baseline promotion.
13193
13469
 
@@ -13261,6 +13537,47 @@ description: >-
13261
13537
 
13262
13538
  Canonical Reference: ${skill.references}
13263
13539
 
13540
+ ${skill.body}
13541
+ `;
13542
+ }
13543
+ function renderCursorSkillFile(skill) {
13544
+ const globs = skill.globs || "";
13545
+ return `---
13546
+ description: ${skill.id} (${skill.title}) - ${skill.description}
13547
+ globs: ${globs}
13548
+ alwaysApply: false
13549
+ ---
13550
+
13551
+ # ${skill.id} (${skill.title})
13552
+
13553
+ Canonical Reference: ${skill.references}
13554
+
13555
+ ${skill.body}
13556
+ `;
13557
+ }
13558
+ function renderClaudeSkillFile(skill) {
13559
+ return `---
13560
+ name: ${skill.id}
13561
+ description: ${skill.description}
13562
+ ---
13563
+
13564
+ # ${skill.id} (${skill.title})
13565
+
13566
+ Canonical Reference: ${skill.references}
13567
+
13568
+ ${skill.body}
13569
+ `;
13570
+ }
13571
+ function renderCopilotSkillFile(skill) {
13572
+ return `---
13573
+ name: ${skill.id}
13574
+ description: ${skill.description}
13575
+ ---
13576
+
13577
+ # ${skill.id} (${skill.title})
13578
+
13579
+ Canonical Reference: ${skill.references}
13580
+
13264
13581
  ${skill.body}
13265
13582
  `;
13266
13583
  }
@@ -15612,12 +15929,24 @@ function appendCursorFiles(dirs, files) {
15612
15929
  for (const agent of SPECIALIZED_AGENT_TEMPLATES) {
15613
15930
  files.push({ relPath: `.cursor/rules/${agent.id}.mdc`, content: renderCursorAgentFile(agent) });
15614
15931
  }
15932
+ for (const skill of LIFECYCLE_SKILL_TEMPLATES) {
15933
+ files.push({ relPath: `.cursor/rules/${skill.id}.mdc`, content: renderCursorSkillFile(skill) });
15934
+ }
15615
15935
  }
15616
15936
  function appendClaudeFiles(dirs, files) {
15617
- dirs.push(".claude/agents");
15937
+ dirs.push(".claude/agents", ".claude/skills");
15618
15938
  files.push({ relPath: "CLAUDE.md", content: STARTER_CLAUDE_RULES });
15619
15939
  for (const agent of SPECIALIZED_AGENT_TEMPLATES) {
15620
- files.push({ relPath: `.claude/agents/${agent.id}.md`, content: renderClaudeAgentFile(agent) });
15940
+ const content = renderClaudeAgentFile(agent);
15941
+ dirs.push(`.claude/skills/${agent.id}`);
15942
+ files.push(
15943
+ { relPath: `.claude/agents/${agent.id}.md`, content },
15944
+ { relPath: `.claude/skills/${agent.id}/SKILL.md`, content }
15945
+ );
15946
+ }
15947
+ for (const skill of LIFECYCLE_SKILL_TEMPLATES) {
15948
+ dirs.push(`.claude/skills/${skill.id}`);
15949
+ files.push({ relPath: `.claude/skills/${skill.id}/SKILL.md`, content: renderClaudeSkillFile(skill) });
15621
15950
  }
15622
15951
  }
15623
15952
  function appendCopilotFiles(dirs, files) {
@@ -15626,6 +15955,9 @@ function appendCopilotFiles(dirs, files) {
15626
15955
  for (const agent of SPECIALIZED_AGENT_TEMPLATES) {
15627
15956
  files.push({ relPath: `.github/agents/${agent.id}.agent.md`, content: renderCopilotAgentFile(agent) });
15628
15957
  }
15958
+ for (const skill of LIFECYCLE_SKILL_TEMPLATES) {
15959
+ files.push({ relPath: `.github/agents/${skill.id}.agent.md`, content: renderCopilotSkillFile(skill) });
15960
+ }
15629
15961
  }
15630
15962
  function isTargetIncluded(targets, name) {
15631
15963
  return targets.has("all") || targets.has(name);
@@ -15875,9 +16207,148 @@ function initProject(options = {}) {
15875
16207
  tryInstallGitHook(destDir, options.dryRun)
15876
16208
  );
15877
16209
  }
16210
+ var DEFAULT_DLP_RULES = [
16211
+ {
16212
+ id: "DLP-001-PRIVATE-KEY",
16213
+ description: "PEM / RSA / EC / OPENSSH Private Key Block",
16214
+ pattern: "-----BEGIN (?:[A-Z0-9_-]+ )?PRIVATE KEY-----[\\s\\S]*?-----END (?:[A-Z0-9_-]+ )?PRIVATE KEY-----",
16215
+ replacement: "[REDACTED:PRIVATE_KEY]",
16216
+ enabled: true
16217
+ },
16218
+ {
16219
+ id: "DLP-002-API-TOKEN",
16220
+ description: "Cloud, GitHub, Slack, OpenAI, Stripe, and AWS Access Tokens",
16221
+ pattern: "\\b(?:gh[pousr]_[A-Za-z0-9_]{36,255}|github_pat_[0-9a-zA-Z_]{82}|sk-(?:proj-|live-)?[a-zA-Z0-9_-]{32,}|AKIA[0-9A-Z]{16}|xox[baprs]-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*|(?:sk|rk)_(?:live|test)_[0-9a-zA-Z]{24,99})\\b",
16222
+ replacement: "[REDACTED:API_TOKEN]",
16223
+ enabled: true
16224
+ },
16225
+ {
16226
+ id: "DLP-003-JWT",
16227
+ description: "Plaintext JSON Web Token (JWT)",
16228
+ pattern: "\\beyJ[A-Za-z0-9_-]{10,}\\.eyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\b",
16229
+ replacement: "[REDACTED:JWT_TOKEN]",
16230
+ enabled: true
16231
+ },
16232
+ {
16233
+ id: "DLP-004-CREDIT-CARD",
16234
+ description: "Payment Card Number (13-19 digits formatted)",
16235
+ pattern: "\\b(?:\\d{4}[- ]\\d{4}[- ]\\d{4}[- ]\\d{4}|\\d{16})\\b",
16236
+ replacement: "[REDACTED:CREDIT_CARD]",
16237
+ enabled: true
16238
+ },
16239
+ {
16240
+ id: "DLP-005-EMAIL",
16241
+ description: "Personal or Corporate Email Address",
16242
+ pattern: "\\b[A-Za-z0-9._%+-]+@(?!example\\.com|localhost|test\\.local)[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b",
16243
+ replacement: "[REDACTED:EMAIL]",
16244
+ enabled: true
16245
+ },
16246
+ {
16247
+ id: "DLP-006-INTERNAL-URL",
16248
+ description: "Internal Intranet / Corp Service URL",
16249
+ pattern: "https?://(?:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+@|[A-Za-z0-9.-]+\\.(?:internal|corp|intranet|local)\\b)[^\\s\"'`<>]*",
16250
+ replacement: "[REDACTED:INTERNAL_URL]",
16251
+ enabled: true
16252
+ }
16253
+ ];
16254
+ function createDefaultDlpPolicy() {
16255
+ return {
16256
+ version: "1.0",
16257
+ enabled: true,
16258
+ zero_data_retention_required: true,
16259
+ rules: [...DEFAULT_DLP_RULES]
16260
+ };
16261
+ }
16262
+ function normalizeParsedRule(raw, idx) {
16263
+ if (typeof raw.pattern !== "string" || !raw.pattern) return null;
16264
+ return {
16265
+ id: typeof raw.id === "string" ? raw.id : `DLP-CUSTOM-${idx + 1}`,
16266
+ description: typeof raw.description === "string" ? raw.description : "Custom DLP rule",
16267
+ pattern: raw.pattern,
16268
+ replacement: typeof raw.replacement === "string" ? raw.replacement : "[REDACTED]",
16269
+ enabled: raw.enabled !== false
16270
+ };
16271
+ }
16272
+ function parseDlpPolicy(yamlContent) {
16273
+ const fallback = createDefaultDlpPolicy();
16274
+ if (!yamlContent || !yamlContent.trim()) return fallback;
16275
+ try {
16276
+ const doc = yaml3__default.default.load(yamlContent);
16277
+ if (!doc || typeof doc !== "object") return fallback;
16278
+ const rawRules = Array.isArray(doc.rules) ? doc.rules : [];
16279
+ const parsedRules = rawRules.map((r, i) => normalizeParsedRule(r, i)).filter((r) => r !== null);
16280
+ return {
16281
+ version: typeof doc.version === "string" ? doc.version : "1.0",
16282
+ enabled: doc.enabled !== false,
16283
+ zero_data_retention_required: Boolean(doc.zero_data_retention_required ?? true),
16284
+ rules: parsedRules.length > 0 ? parsedRules : fallback.rules
16285
+ };
16286
+ } catch {
16287
+ return fallback;
16288
+ }
16289
+ }
16290
+ function loadDlpPolicy(rootDir, customPolicyPath) {
16291
+ const base = rootDir || process.cwd();
16292
+ const filePath = customPolicyPath ? path4__namespace.isAbsolute(customPolicyPath) ? customPolicyPath : path4__namespace.join(base, customPolicyPath) : path4__namespace.join(base, "dlp-policy.yaml");
16293
+ if (!fs7__namespace.existsSync(filePath)) return createDefaultDlpPolicy();
16294
+ try {
16295
+ return parseDlpPolicy(fs7__namespace.readFileSync(filePath, "utf-8"));
16296
+ } catch {
16297
+ return createDefaultDlpPolicy();
16298
+ }
16299
+ }
16300
+ function applySingleDlpRule(text, rule) {
16301
+ if (rule.enabled === false) return { output: text, matches: 0 };
16302
+ try {
16303
+ const regex = new RegExp(rule.pattern, "g");
16304
+ let matches = 0;
16305
+ const output = text.replace(regex, () => {
16306
+ matches++;
16307
+ return rule.replacement;
16308
+ });
16309
+ return { output, matches };
16310
+ } catch {
16311
+ return { output: text, matches: 0 };
16312
+ }
16313
+ }
16314
+ function sanitizeDlpText(text, policy = createDefaultDlpPolicy()) {
16315
+ if (!policy.enabled || !text) {
16316
+ return { sanitizedText: text, redactionCount: 0, triggeredRuleIds: [] };
16317
+ }
16318
+ let current = text;
16319
+ let totalRedactions = 0;
16320
+ const triggered = [];
16321
+ for (const rule of policy.rules) {
16322
+ const res = applySingleDlpRule(current, rule);
16323
+ if (res.matches > 0) {
16324
+ current = res.output;
16325
+ totalRedactions += res.matches;
16326
+ triggered.push(rule.id);
16327
+ }
16328
+ }
16329
+ return {
16330
+ sanitizedText: current,
16331
+ redactionCount: totalRedactions,
16332
+ triggeredRuleIds: triggered
16333
+ };
16334
+ }
16335
+ function sanitizeDlpPayload(payload, policy = createDefaultDlpPolicy()) {
16336
+ if (!policy.enabled || payload === null || payload === void 0) return payload;
16337
+ if (typeof payload === "string") {
16338
+ return sanitizeDlpText(payload, policy).sanitizedText;
16339
+ }
16340
+ const serialized = JSON.stringify(payload);
16341
+ const { sanitizedText } = sanitizeDlpText(serialized, policy);
16342
+ try {
16343
+ return JSON.parse(sanitizedText);
16344
+ } catch {
16345
+ return payload;
16346
+ }
16347
+ }
15878
16348
 
15879
16349
  exports.ALL_ARCHITECTURE_TEMPLATES = ALL_ARCHITECTURE_TEMPLATES;
15880
16350
  exports.BRANCH_PATTERNS = BRANCH_PATTERNS;
16351
+ exports.DEFAULT_DLP_RULES = DEFAULT_DLP_RULES;
15881
16352
  exports.DEFAULT_EXCLUDED_PATTERNS = DEFAULT_EXCLUDED_PATTERNS;
15882
16353
  exports.DEFAULT_EXCLUDES = DEFAULT_EXCLUDES;
15883
16354
  exports.DEFAULT_EXTENSIONS = DEFAULT_EXTENSIONS;
@@ -16023,6 +16494,7 @@ exports.analyzeBddVariantsForRequirement = analyzeBddVariantsForRequirement;
16023
16494
  exports.appendRevisionHistoryRow = appendRevisionHistoryRow;
16024
16495
  exports.applyRatchetMode = applyRatchetMode;
16025
16496
  exports.buildCycloneDxComponent = buildCycloneDxComponent;
16497
+ exports.buildCycloneDxVulnerabilities = buildCycloneDxVulnerabilities;
16026
16498
  exports.buildFeatureFileContent = buildFeatureFileContent;
16027
16499
  exports.buildGraphElements = buildGraphElements;
16028
16500
  exports.calculateJaccardSimilarity = calculateJaccardSimilarity;
@@ -16039,6 +16511,7 @@ exports.collectHistoricalKpis = collectHistoricalKpis;
16039
16511
  exports.computeCanonicalSha256 = computeCanonicalSha256;
16040
16512
  exports.computeSha256 = computeSha256;
16041
16513
  exports.computeViolationSignature = computeViolationSignature;
16514
+ exports.createDefaultDlpPolicy = createDefaultDlpPolicy;
16042
16515
  exports.createDefaultQualityPolicy = createDefaultQualityPolicy;
16043
16516
  exports.depositProductHandoffSidecar = depositProductHandoffSidecar;
16044
16517
  exports.detectActiveChangeForIntegration = detectActiveChangeForIntegration;
@@ -16104,6 +16577,7 @@ exports.isPlaceholderOrIgnored = isPlaceholderOrIgnored;
16104
16577
  exports.isRequirementArtifact = isRequirementArtifact2;
16105
16578
  exports.isSemgrepInstalled = isSemgrepInstalled;
16106
16579
  exports.isToolAvailable = isToolAvailable;
16580
+ exports.loadDlpPolicy = loadDlpPolicy;
16107
16581
  exports.loadPrivacyPolicy = loadPrivacyPolicy;
16108
16582
  exports.loadProductHandoffSidecar = loadProductHandoffSidecar;
16109
16583
  exports.loadQualityBaseline = loadQualityBaseline;
@@ -16113,6 +16587,7 @@ exports.maskSecret = maskSecret;
16113
16587
  exports.normalizeSpdxLicense = normalizeSpdxLicense;
16114
16588
  exports.normalizeUri = normalizeUri;
16115
16589
  exports.parseCommitTrailers = parseCommitTrailers;
16590
+ exports.parseDlpPolicy = parseDlpPolicy;
16116
16591
  exports.parseFrontmatter = parseFrontmatter;
16117
16592
  exports.parseFrontmatterId = parseFrontmatterId;
16118
16593
  exports.parseGenericDoc = parseGenericDoc;
@@ -16132,8 +16607,11 @@ exports.renderAntigravityAgentFile = renderAntigravityAgentFile;
16132
16607
  exports.renderAntigravitySkillFile = renderAntigravitySkillFile;
16133
16608
  exports.renderAutonomyModeSection = renderAutonomyModeSection;
16134
16609
  exports.renderClaudeAgentFile = renderClaudeAgentFile;
16610
+ exports.renderClaudeSkillFile = renderClaudeSkillFile;
16135
16611
  exports.renderCopilotAgentFile = renderCopilotAgentFile;
16612
+ exports.renderCopilotSkillFile = renderCopilotSkillFile;
16136
16613
  exports.renderCursorAgentFile = renderCursorAgentFile;
16614
+ exports.renderCursorSkillFile = renderCursorSkillFile;
16137
16615
  exports.renderDashboardHtml = renderDashboardHtml;
16138
16616
  exports.renderManualsTab = renderManualsTab;
16139
16617
  exports.resolveAgentTargets = resolveAgentTargets;
@@ -16151,15 +16629,21 @@ exports.resolveSchemaForArtifact = resolveSchemaForArtifact;
16151
16629
  exports.resolveTargetFiles = resolveTargetFiles;
16152
16630
  exports.runGitleaksCli = runGitleaksCli;
16153
16631
  exports.runSemgrepCli = runSemgrepCli;
16632
+ exports.sanitizeDlpPayload = sanitizeDlpPayload;
16633
+ exports.sanitizeDlpText = sanitizeDlpText;
16154
16634
  exports.scaffoldSddChange = scaffoldSddChange;
16155
16635
  exports.scanAllProductHandoffs = scanAllProductHandoffs;
16156
16636
  exports.scanAllTestArtifacts = scanAllTestArtifacts;
16157
16637
  exports.scanContentForSecrets = scanContentForSecrets;
16158
16638
  exports.scanFileForPrivacyViolations = scanFileForPrivacyViolations;
16159
16639
  exports.scanFileForSast = scanFileForSast;
16640
+ exports.scanGoDependencies = scanGoDependencies;
16160
16641
  exports.scanInstalledLicenses = scanInstalledLicenses;
16161
16642
  exports.scanManualsInWorkspace = scanManualsInWorkspace;
16643
+ exports.scanMavenDependencies = scanMavenDependencies;
16162
16644
  exports.scanPnpmStore = scanPnpmStore;
16645
+ exports.scanPythonDependencies = scanPythonDependencies;
16646
+ exports.scanRustDependencies = scanRustDependencies;
16163
16647
  exports.scanSubdirectories = scanSubdirectories;
16164
16648
  exports.scanWithNativeFs = scanWithNativeFs;
16165
16649
  exports.scanWithSyft = scanWithSyft;