diff --git a/docs/superpowers/plans/2026-04-02-sbom-implementation.md b/docs/superpowers/plans/2026-04-02-sbom-implementation.md
new file mode 100644
index 0000000..029b0f2
--- /dev/null
+++ b/docs/superpowers/plans/2026-04-02-sbom-implementation.md
@@ -0,0 +1,1635 @@
+# SBOM Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Generate comprehensive SBOMs (CycloneDX 1.6, SPDX 2.3 JSON, SPDX 2.3 tag-value, and Markdown) for all cveClient runtime and dev components, automated via GitHub Actions.
+
+**Architecture:** A modular Node.js generator under `scripts/sbom/` extracts component data from project source files, then serializes to four output formats in `docs/sbom/`. A GitHub Action runs the generator on relevant file changes and opens a PR via `gh`.
+
+**Tech Stack:** Node.js (built-in `fs`, `path`, `crypto`), Vitest (testing), `npx ajv-cli` (CI validation), GitHub Actions + `gh` CLI (automation).
+
+---
+
+## File Structure
+
+```
+scripts/
+ generate-sbom.mjs # Main entry point - orchestrates extraction and generation
+ sbom/
+ extract.mjs # Extracts component data from project files
+ cyclonedx.mjs # Generates CycloneDX 1.6 JSON
+ spdx.mjs # Generates SPDX 2.3 JSON and tag-value
+ markdown.mjs # Generates SBOM.md summary
+
+tests/
+ sbom/
+ extract.test.js # Tests for data extraction
+ cyclonedx.test.js # Tests for CycloneDX output
+ spdx.test.js # Tests for SPDX JSON and tag-value output
+ markdown.test.js # Tests for Markdown output
+
+docs/sbom/ # Output directory (generated files)
+ cyclonedx-runtime.json
+ cyclonedx-dev.json
+ spdx-runtime.json
+ spdx-dev.json
+ spdx-runtime.spdx
+ spdx-dev.spdx
+ SBOM.md
+
+.github/workflows/
+ generate-sbom.yml # GitHub Action workflow
+```
+
+**Why `.mjs` for scripts:** The project's `package.json` has no `"type": "module"`, so `.mjs` enables ESM imports without affecting existing code. Test files stay `.js` since vitest handles ESM natively.
+
+---
+
+### Task 1: Component Data Extraction Module
+
+**Files:**
+
+- Create: `scripts/sbom/extract.mjs`
+- Create: `tests/sbom/extract.test.js`
+
+This is the core module - it reads project files and returns structured component inventories.
+
+- [ ] **Step 1: Write failing tests for HTML CDN extraction**
+
+Create `tests/sbom/extract.test.js`:
+
+```js
+import { describe, it, expect } from "vitest";
+import { extractCdnDeps } from "../../scripts/sbom/extract.mjs";
+
+describe("extractCdnDeps", () => {
+ it("extracts script tags with integrity hashes", () => {
+ const html = `
+
+ `;
+ const deps = extractCdnDeps(html);
+ expect(deps).toHaveLength(1);
+ expect(deps[0]).toMatchObject({
+ name: "jquery",
+ version: "3.5.1",
+ url: "https://code.jquery.com/jquery-3.5.1.min.js",
+ integrity:
+ "sha384-ZvpUoO/+PpLXR1lu4jmpXWu80pZlYUAfxl5NsBMWOEPSjUn/6Z/hRTt8+pR6L4N2",
+ type: "script",
+ });
+ });
+
+ it("extracts link tags with integrity hashes", () => {
+ const html = `
+
+ `;
+ const deps = extractCdnDeps(html);
+ expect(deps).toHaveLength(1);
+ expect(deps[0]).toMatchObject({
+ name: "bootstrap",
+ version: "4.3.1",
+ type: "stylesheet",
+ });
+ });
+
+ it("extracts multiple deps from full HTML", () => {
+ const html = `
+
+
+
+
+
+
+ `;
+ const deps = extractCdnDeps(html);
+ expect(deps).toHaveLength(6);
+ const names = deps.map((d) => d.name);
+ expect(names).toContain("jquery");
+ expect(names).toContain("popper.js");
+ expect(names).toContain("bootstrap");
+ expect(names).toContain("bootstrap-table");
+ });
+
+ it("skips local scripts without integrity", () => {
+ const html = `
+
+
+ `;
+ const deps = extractCdnDeps(html);
+ expect(deps).toHaveLength(1);
+ expect(deps[0].name).toBe("jquery");
+ });
+});
+```
+
+- [ ] **Step 2: Run tests to verify they fail**
+
+Run: `npx vitest run tests/sbom/extract.test.js`
+Expected: FAIL - module not found
+
+- [ ] **Step 3: Write failing tests for source file version extraction**
+
+Append to `tests/sbom/extract.test.js`:
+
+```js
+import { extractSourceVersions } from "../../scripts/sbom/extract.mjs";
+
+describe("extractSourceVersions", () => {
+ it("extracts this._version pattern", () => {
+ const content = `class Foo {\n constructor() {\n this._version = "1.0.12";\n }\n}`;
+ const version = extractSourceVersions.parseVersion(content);
+ expect(version).toBe("1.0.12");
+ });
+
+ it("extracts const _version pattern", () => {
+ const content = `const _version = "1.0.25";`;
+ const version = extractSourceVersions.parseVersion(content);
+ expect(version).toBe("1.0.25");
+ });
+
+ it("extracts const name_version pattern", () => {
+ const content = `const encrypt_storage_version = "1.1.15";`;
+ const version = extractSourceVersions.parseVersion(content);
+ expect(version).toBe("1.1.15");
+ });
+});
+```
+
+- [ ] **Step 4: Write failing tests for vendored dependency extraction**
+
+Append to `tests/sbom/extract.test.js`:
+
+```js
+import { extractVendoredVersion } from "../../scripts/sbom/extract.mjs";
+
+describe("extractVendoredVersion", () => {
+ it("extracts SweetAlert2 version from header comment", () => {
+ const content = `/*!\n* sweetalert2 v11.26.24\n* Released under the MIT License.\n*/`;
+ const result = extractVendoredVersion.parseSweetalert(content);
+ expect(result).toMatchObject({
+ name: "sweetalert2",
+ version: "11.26.24",
+ license: "MIT",
+ });
+ });
+
+ it("extracts Ace Editor version from source", () => {
+ const content = `version="1.4.12"}),ace.define("ace/mouse"`;
+ const result = extractVendoredVersion.parseAce(content);
+ expect(result).toMatchObject({
+ name: "ace-editor",
+ version: "1.4.12",
+ license: "Apache-2.0",
+ });
+ });
+});
+```
+
+- [ ] **Step 5: Write failing tests for dev dependency extraction**
+
+Append to `tests/sbom/extract.test.js`:
+
+```js
+import { extractDevDeps } from "../../scripts/sbom/extract.mjs";
+
+describe("extractDevDeps", () => {
+ it("extracts npm dev dependencies from package.json and lock", () => {
+ const pkg = {
+ devDependencies: { vitest: "^3.1.0", jsdom: "^26.1.0" },
+ };
+ const lock = {
+ packages: {
+ "node_modules/vitest": { version: "3.2.4", license: "MIT" },
+ "node_modules/jsdom": { version: "26.1.0", license: "MIT" },
+ "node_modules/chai": { version: "5.2.0", license: "MIT" },
+ },
+ };
+ const result = extractDevDeps.fromNpm(pkg, lock);
+ // Direct deps
+ expect(result.direct).toHaveLength(2);
+ expect(result.direct[0]).toMatchObject({
+ name: "vitest",
+ version: "3.2.4",
+ });
+ // Transitive deps
+ expect(result.transitive.length).toBeGreaterThan(0);
+ expect(result.transitive[0]).toMatchObject({
+ name: "chai",
+ version: "5.2.0",
+ });
+ });
+
+ it("extracts GitHub Actions from workflow YAML", () => {
+ const yaml = `
+name: Tests
+on: push
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ - run: npm test
+`;
+ const actions = extractDevDeps.fromWorkflowYaml(yaml);
+ expect(actions).toHaveLength(2);
+ expect(actions[0]).toMatchObject({
+ name: "actions/checkout",
+ version: "v4",
+ });
+ expect(actions[1]).toMatchObject({
+ name: "actions/setup-node",
+ version: "v4",
+ });
+ });
+});
+```
+
+- [ ] **Step 6: Implement `scripts/sbom/extract.mjs`**
+
+Create `scripts/sbom/extract.mjs`:
+
+```js
+import { readFileSync, readdirSync } from "node:fs";
+import { join } from "node:path";
+
+/**
+ * Extract CDN dependencies from HTML string.
+ * Finds