@vyuhlabs/dxkit 2.2.0 → 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.
Files changed (145) hide show
  1. package/CHANGELOG.md +160 -0
  2. package/README.md +40 -29
  3. package/dist/analyzers/bom/discovery.d.ts +38 -0
  4. package/dist/analyzers/bom/discovery.d.ts.map +1 -0
  5. package/dist/analyzers/bom/discovery.js +166 -0
  6. package/dist/analyzers/bom/discovery.js.map +1 -0
  7. package/dist/analyzers/bom/gather.d.ts +28 -0
  8. package/dist/analyzers/bom/gather.d.ts.map +1 -1
  9. package/dist/analyzers/bom/gather.js +98 -0
  10. package/dist/analyzers/bom/gather.js.map +1 -1
  11. package/dist/analyzers/bom/index.d.ts +49 -2
  12. package/dist/analyzers/bom/index.d.ts.map +1 -1
  13. package/dist/analyzers/bom/index.js +188 -12
  14. package/dist/analyzers/bom/index.js.map +1 -1
  15. package/dist/analyzers/bom/types.d.ts +33 -1
  16. package/dist/analyzers/bom/types.d.ts.map +1 -1
  17. package/dist/analyzers/licenses/index.d.ts +1 -1
  18. package/dist/analyzers/licenses/index.d.ts.map +1 -1
  19. package/dist/analyzers/licenses/index.js +22 -7
  20. package/dist/analyzers/licenses/index.js.map +1 -1
  21. package/dist/analyzers/security/detailed.d.ts.map +1 -1
  22. package/dist/analyzers/security/detailed.js +21 -8
  23. package/dist/analyzers/security/detailed.js.map +1 -1
  24. package/dist/analyzers/security/gather.d.ts.map +1 -1
  25. package/dist/analyzers/security/gather.js +76 -1
  26. package/dist/analyzers/security/gather.js.map +1 -1
  27. package/dist/analyzers/security/index.d.ts.map +1 -1
  28. package/dist/analyzers/security/index.js +20 -7
  29. package/dist/analyzers/security/index.js.map +1 -1
  30. package/dist/analyzers/tools/epss.d.ts +55 -0
  31. package/dist/analyzers/tools/epss.d.ts.map +1 -0
  32. package/dist/analyzers/tools/epss.js +133 -0
  33. package/dist/analyzers/tools/epss.js.map +1 -0
  34. package/dist/analyzers/tools/graphify.d.ts.map +1 -1
  35. package/dist/analyzers/tools/graphify.js +17 -7
  36. package/dist/analyzers/tools/graphify.js.map +1 -1
  37. package/dist/analyzers/tools/kev.d.ts +52 -0
  38. package/dist/analyzers/tools/kev.d.ts.map +1 -0
  39. package/dist/analyzers/tools/kev.js +95 -0
  40. package/dist/analyzers/tools/kev.js.map +1 -0
  41. package/dist/analyzers/tools/npm-registry.d.ts +43 -0
  42. package/dist/analyzers/tools/npm-registry.d.ts.map +1 -0
  43. package/dist/analyzers/tools/npm-registry.js +107 -0
  44. package/dist/analyzers/tools/npm-registry.js.map +1 -0
  45. package/dist/analyzers/tools/osv.d.ts +12 -0
  46. package/dist/analyzers/tools/osv.d.ts.map +1 -1
  47. package/dist/analyzers/tools/osv.js +45 -2
  48. package/dist/analyzers/tools/osv.js.map +1 -1
  49. package/dist/analyzers/tools/reachability.d.ts +60 -0
  50. package/dist/analyzers/tools/reachability.d.ts.map +1 -0
  51. package/dist/analyzers/tools/reachability.js +104 -0
  52. package/dist/analyzers/tools/reachability.js.map +1 -0
  53. package/dist/analyzers/tools/risk-score.d.ts +69 -0
  54. package/dist/analyzers/tools/risk-score.d.ts.map +1 -0
  55. package/dist/analyzers/tools/risk-score.js +86 -0
  56. package/dist/analyzers/tools/risk-score.js.map +1 -0
  57. package/dist/analyzers/tools/tool-registry.d.ts +10 -0
  58. package/dist/analyzers/tools/tool-registry.d.ts.map +1 -1
  59. package/dist/analyzers/tools/tool-registry.js +35 -20
  60. package/dist/analyzers/tools/tool-registry.js.map +1 -1
  61. package/dist/analyzers/xlsx/bom.d.ts.map +1 -1
  62. package/dist/analyzers/xlsx/bom.js +1 -2
  63. package/dist/analyzers/xlsx/bom.js.map +1 -1
  64. package/dist/cli.d.ts.map +1 -1
  65. package/dist/cli.js +41 -10
  66. package/dist/cli.js.map +1 -1
  67. package/dist/languages/capabilities/types.d.ts +6 -0
  68. package/dist/languages/capabilities/types.d.ts.map +1 -1
  69. package/dist/languages/csharp.d.ts.map +1 -1
  70. package/dist/languages/csharp.js +8 -0
  71. package/dist/languages/csharp.js.map +1 -1
  72. package/dist/languages/go.d.ts.map +1 -1
  73. package/dist/languages/go.js +24 -7
  74. package/dist/languages/go.js.map +1 -1
  75. package/dist/languages/python.d.ts.map +1 -1
  76. package/dist/languages/python.js +8 -0
  77. package/dist/languages/python.js.map +1 -1
  78. package/dist/languages/rust.d.ts.map +1 -1
  79. package/dist/languages/rust.js +9 -0
  80. package/dist/languages/rust.js.map +1 -1
  81. package/dist/languages/typescript.d.ts.map +1 -1
  82. package/dist/languages/typescript.js +23 -1
  83. package/dist/languages/typescript.js.map +1 -1
  84. package/package.json +4 -3
  85. package/templates/.claude/agents-available/dashboard-builder.md +7 -7
  86. package/templates/.claude/agents-available/dev-report.md +4 -4
  87. package/templates/.claude/agents-available/health-auditor.md +1 -1
  88. package/templates/.claude/agents-available/strategic-planner.md +7 -7
  89. package/templates/.claude/agents-available/vulnerability-scanner.md +3 -3
  90. package/templates/.claude/commands/dashboard.md +1 -1
  91. package/templates/.claude/commands/deps.md +1 -1
  92. package/templates/.claude/commands/dev-report.md +2 -2
  93. package/templates/.claude/commands/docs.md +1 -1
  94. package/templates/.claude/commands/export-pdf.md +3 -3
  95. package/templates/.claude/commands/health.md +3 -3
  96. package/templates/.claude/commands/plan.md +1 -1
  97. package/templates/.claude/commands/quality.md.template +2 -2
  98. package/templates/.claude/commands/stealth-mode.md +1 -1
  99. package/templates/.claude/commands/test-gaps.md +2 -2
  100. package/templates/.claude/commands/vulnerabilities.md +3 -3
  101. package/dist/agents/extract.d.ts +0 -25
  102. package/dist/agents/extract.d.ts.map +0 -1
  103. package/dist/agents/extract.js +0 -186
  104. package/dist/agents/extract.js.map +0 -1
  105. package/dist/agents/schemas.d.ts +0 -106
  106. package/dist/agents/schemas.d.ts.map +0 -1
  107. package/dist/agents/schemas.js +0 -86
  108. package/dist/agents/schemas.js.map +0 -1
  109. package/dist/agents/session.d.ts +0 -28
  110. package/dist/agents/session.d.ts.map +0 -1
  111. package/dist/agents/session.js +0 -223
  112. package/dist/agents/session.js.map +0 -1
  113. package/dist/analyzers/index.d.ts +0 -3
  114. package/dist/analyzers/index.d.ts.map +0 -1
  115. package/dist/analyzers/index.js +0 -6
  116. package/dist/analyzers/index.js.map +0 -1
  117. package/dist/analyzers/security/report.d.ts +0 -6
  118. package/dist/analyzers/security/report.d.ts.map +0 -1
  119. package/dist/analyzers/security/report.js +0 -118
  120. package/dist/analyzers/security/report.js.map +0 -1
  121. package/dist/analyzers/tools/dotnet.d.ts +0 -8
  122. package/dist/analyzers/tools/dotnet.d.ts.map +0 -1
  123. package/dist/analyzers/tools/dotnet.js +0 -81
  124. package/dist/analyzers/tools/dotnet.js.map +0 -1
  125. package/dist/analyzers/tools/gather-cache.d.ts +0 -16
  126. package/dist/analyzers/tools/gather-cache.d.ts.map +0 -1
  127. package/dist/analyzers/tools/gather-cache.js +0 -126
  128. package/dist/analyzers/tools/gather-cache.js.map +0 -1
  129. package/dist/analyzers/tools/go.d.ts +0 -8
  130. package/dist/analyzers/tools/go.d.ts.map +0 -1
  131. package/dist/analyzers/tools/go.js +0 -84
  132. package/dist/analyzers/tools/go.js.map +0 -1
  133. package/dist/analyzers/tools/node.d.ts +0 -8
  134. package/dist/analyzers/tools/node.d.ts.map +0 -1
  135. package/dist/analyzers/tools/node.js +0 -160
  136. package/dist/analyzers/tools/node.js.map +0 -1
  137. package/dist/analyzers/tools/python.d.ts +0 -8
  138. package/dist/analyzers/tools/python.d.ts.map +0 -1
  139. package/dist/analyzers/tools/python.js +0 -81
  140. package/dist/analyzers/tools/python.js.map +0 -1
  141. package/dist/analyzers/tools/rust.d.ts +0 -8
  142. package/dist/analyzers/tools/rust.d.ts.map +0 -1
  143. package/dist/analyzers/tools/rust.js +0 -86
  144. package/dist/analyzers/tools/rust.js.map +0 -1
  145. package/templates/.ai/templates/session-checkpoint-template.md +0 -97
package/CHANGELOG.md CHANGED
@@ -7,6 +7,166 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.3.0] - 2026-04-24
11
+
12
+ Minor release — turns the `bom` report from enumeration (1700+ rows
13
+ of noise) into a **decision doc** (top 10 triage queue ranked by
14
+ composite exploit-risk). Every `DepVulnFinding` now carries five
15
+ exploitability signals — CVSS, EPSS, CISA KEV, reachability,
16
+ composite `riskScore` — that consumers can read individually or as
17
+ the ranked `Risk` column. `licenses` + `vulnerabilities` renders
18
+ gain parity with the new bom surface so any dxkit command shows the
19
+ same triage-relevant data.
20
+
21
+ Nine sub-commits (Phase 10h.5) landed behind PRs #4 / #5 / #6 /
22
+ #7 / #8 / #9 / #10 / #11 through the hardened 2.2.1 pipeline —
23
+ the first full release cut where every commit flowed PR → CI-green →
24
+ merge → tag → CI-publishes without deviation.
25
+
26
+ ### Added — exploitability enrichers
27
+
28
+ - **EPSS** (`DepVulnFinding.epssScore`, 0.0–1.0) from FIRST.org's
29
+ `api.first.org/data/v1/epss`. Batched (≤100 CVEs/call), session-
30
+ cached, graceful offline fallback. Non-CVE primaries (GHSA /
31
+ RUSTSEC / GO / PYSEC) resolve via OSV.dev alias lookup — no
32
+ coverage gap across packs. (10h.5.1)
33
+
34
+ - **CISA KEV** (`DepVulnFinding.kev`, boolean) from the official
35
+ catalog at `cisa.gov/.../known_exploited_vulnerabilities.json`.
36
+ Single bulk fetch per process, O(1) lookup. Badge `⚠` in every
37
+ render. (10h.5.2)
38
+
39
+ - **Reachability** (`DepVulnFinding.reachable`, tri-state) — does
40
+ this repo's source actually import the vulnerable package?
41
+ Built from per-pack `ImportsResult`'s specifier extraction;
42
+ `specifierToPackage` handles TS scoped/bare, Python dotted
43
+ modules, Go 3-segment module paths. Coarse name-level
44
+ matching; undefined when no imports data available. (10h.5.3)
45
+
46
+ - **Composite riskScore** (`DepVulnFinding.riskScore`, 0–100) —
47
+ `clamp(cvss*10 × kev? × (1+2*epss) × reach?, 0, 100)`. Formula
48
+ documented in `src/analyzers/tools/risk-score.ts`. Null when
49
+ CVSS missing (no fabrication from side signals). (10h.5.4)
50
+
51
+ - **"This Week's Triage"** section at the top of every bom report —
52
+ top 10 advisories with riskScore ≥ 15, rationale composed from
53
+ most decisive signals (KEV → reachable → CVSS → EPSS), fix
54
+ column with "PROPOSAL:" prefix stripped. (10h.5.5)
55
+
56
+ ### Added — decision-doc UX
57
+
58
+ - **`bom --filter=top-level`** drops transitive rows (1700+ → ~150
59
+ on typical repos) while the `byTopLevelDep` rollup still reflects
60
+ full blast radius — "upgrading `@loopback/cli` resolves 29
61
+ advisories" survives when those 29 transitive rows are hidden.
62
+ `BomEntry.isTopLevel` + `summary.filter` + `summary.unfilteredTotalPackages`
63
+ ride the shape. (10h.5.0)
64
+
65
+ - **Nested-project aggregation** (default ON; `--no-nested` opts
66
+ out). `src/analyzers/bom/discovery.ts` walks the repo,
67
+ discovers every directory with a language manifest
68
+ (package.json, pyproject.toml/requirements.txt/setup.py/Pipfile,
69
+ go.mod, Cargo.toml, *.csproj/*.sln), runs per-root gather, and
70
+ merges with dedup on `(package, version)`. `BomEntry.sources`
71
+ unions the roots each package was found in; `isTopLevel`
72
+ OR-merges; vulns dedup on `(id, package, installedVersion)`.
73
+ Closes **D001a** — `bom platform/` previously missed
74
+ `platform/userserver/` entirely. Side-benefit: naturally
75
+ addresses **D003** (C# multi-project) since each `.csproj`
76
+ becomes its own root. (10h.5.0b)
77
+
78
+ - **`LicenseFinding.releaseDate`** populated from the npm registry
79
+ for every TS-ecosystem package. Closes **D006** — xlsx col 10
80
+ ("Component Release Date") was previously empty. Bundled with
81
+ the EPSS fetcher roundtrip. (10h.5.1)
82
+
83
+ - **`licenses` render** sorts top-level deps (⭐) first, transitive
84
+ below. Adds `Direct` + `Released` columns. Matches bom's
85
+ `--filter=top-level` ordering so cross-referencing the two
86
+ reports Just Works. (10h.5.6)
87
+
88
+ - **`vulnerabilities` render (main, not --detailed)** per-advisory
89
+ table now sorted by `riskScore` desc with `Risk` / `KEV` /
90
+ `Reach` / `EPSS` columns alongside the existing fields. (10h.5.6)
91
+
92
+ ### Fixed
93
+
94
+ - **D013** — graphify's shared Python venv moved from
95
+ `/tmp/graphify-venv` (subject to systemd-tmpfiles sweep + race
96
+ on first install) to `~/.cache/dxkit/tools-venv` (XDG persistent).
97
+ Also fixed `Date.now()` script-tempfile collision class in
98
+ graphify.ts via `fs.mkdtempSync`. Affects every Python-based
99
+ tool dxkit installs (graphify, semgrep, ruff, pip-audit,
100
+ pip-licenses, coverage). Legacy `/tmp/graphify-venv` path still
101
+ probed, so existing installations aren't forced into a
102
+ reinstall. (10f.2)
103
+
104
+ - **OSV.dev GHSA case-sensitivity** — `api.osv.dev/v1/vulns/<GHSA>`
105
+ expects lowercase; npm-audit emits uppercase. `osv.ts`
106
+ `DEFAULT_FETCHER` normalizes the alphabetic portion. Silently
107
+ broke alias resolution for every TS finding pre-2.3.0.
108
+
109
+ ### Changed — output directory
110
+
111
+ - **Reports moved from `.ai/reports/` to `.dxkit/reports/`**.
112
+ Separates tool output (regenerated each run, can be gitignored)
113
+ from AI-agent context (`.ai/sessions/`, `.ai/prompts/` —
114
+ human-authored, version-controlled). All CLI commands + every
115
+ scaffolded slash command / agent / template updated to the new
116
+ path. Existing `.ai/reports/*.md` files become orphans after
117
+ upgrade — acceptable since reports regenerate each run.
118
+
119
+ ### Process
120
+
121
+ - First full release cut through the 2.2.1-hardened publish
122
+ pipeline: 8 PRs, every one PR→CI→admin-squash-merge→main. Each
123
+ dog-fooded the pre-push CI-mirror hooks landed in PR #3.
124
+
125
+ ## [2.2.1] - 2026-04-23
126
+
127
+ Patch release hardening the publish pipeline after `v2.2.0`'s Publish
128
+ workflow failed with `403 — version already published`. The failure
129
+ was caused by a local `npm publish` that preceded the
130
+ Release-triggered CI publish, not a code defect — the tarball on npm
131
+ byte-matches main. No functional changes in this release; all work
132
+ is on the release path (tracked internally as D015).
133
+
134
+ ### Added — publish pipeline guardrails
135
+
136
+ - **`scripts/require-ci.js` + `prepublishOnly` guard** — any `npm publish`
137
+ invocation outside GitHub Actions now fails at the script hook with
138
+ a clear error pointing to `CLAUDE.md §"Release procedure"`. Prevents
139
+ accidental local publish before the registry is ever contacted.
140
+
141
+ - **`publishConfig.provenance: true`** — npm publishes now carry a
142
+ GitHub Actions provenance attestation. Provenance requires an OIDC
143
+ token that only exists inside Actions; tarball-mode publishes
144
+ (`npm publish *.tgz`, which skips `prepublishOnly`) also fail outside
145
+ CI. Belt-and-suspenders with the script guard.
146
+
147
+ - **Publish-workflow preflights** (`.github/workflows/publish.yml`) —
148
+ before `npm publish` runs, the workflow now verifies (in order):
149
+ 1. tag `vX.Y.Z` matches `package.json` version `X.Y.Z`
150
+ 2. tagged commit is reachable from `origin/main` (blocks
151
+ feature-branch tags)
152
+ 3. the `CI` workflow succeeded on the tagged commit SHA
153
+ 4. `X.Y.Z` is not already on npm (catches the exact 2.2.0 failure)
154
+
155
+ - **Explicit pack + publish + verify** — workflow packs the tarball,
156
+ records its sha1, publishes that exact file, then fetches
157
+ `npm view dist.shasum` and fails on mismatch. Eliminates drift
158
+ between "what npm packed" and "what we audited."
159
+
160
+ - **Tarball workflow artifact** — every release archives the published
161
+ `.tgz` as a workflow artifact (90-day retention) for post-mortem
162
+ auditability.
163
+
164
+ ### Documented — `CLAUDE.md`
165
+
166
+ New "Release procedure" section codifying PR → CI-green → merge → tag
167
+ → CI publishes as the only path. Explicit "no local `npm publish`"
168
+ rule.
169
+
10
170
  ## [2.2.0] - 2026-04-23
11
171
 
12
172
  Minor release adding Snyk-style top-level dep attribution across every
package/README.md CHANGED
@@ -15,8 +15,12 @@ Built so agent-written code has deterministic guardrails before it ships. Scores
15
15
  cd your-repo
16
16
  npx @vyuhlabs/dxkit tools install --yes # one-time: install cloc, gitleaks, etc.
17
17
  npx @vyuhlabs/dxkit health --detailed # 6-dimension score + remediation plan
18
- npx @vyuhlabs/dxkit vulnerabilities # secret + SAST + dep-audit scan
18
+ npx @vyuhlabs/dxkit vulnerabilities # secret + SAST + dep-audit (ranked by risk)
19
+ npx @vyuhlabs/dxkit bom --filter=top-level # Bill of Materials w/ "This Week's Triage"
19
20
  npx @vyuhlabs/dxkit test-gaps # import-graph + coverage-aware
21
+ npx @vyuhlabs/dxkit quality # slop + duplication + lint
22
+ npx @vyuhlabs/dxkit licenses # dependency license inventory
23
+ npx @vyuhlabs/dxkit dev-report # git activity + contributors
20
24
  ```
21
25
 
22
26
  **Scaffold AI tooling into a repo:**
@@ -32,31 +36,33 @@ The two modes are complementary. The analyzers run anywhere; the scaffolder writ
32
36
 
33
37
  ## Analyzer CLI (`vyuh-dxkit <command>`)
34
38
 
35
- Seven deterministic analyzers. Each emits a markdown report to `.ai/reports/` and optional structured JSON.
39
+ Seven deterministic analyzers. Each emits a markdown report to `.dxkit/reports/` and optional structured JSON.
36
40
 
37
- | Command | What it does | Runtime | Output |
38
- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------ |
39
- | `health` | 6-dimension score (Testing, Quality, Docs, Security, Maint, DX) | 10–20s | `.ai/reports/health-audit-<date>.md` |
40
- | `vulnerabilities` | gitleaks + semgrep + per-pack dep-audit (per-advisory detail in `--detailed`) | 5–30s | `.ai/reports/vulnerability-scan-<date>.md` |
41
- | `test-gaps` | Coverage artifact → import-graph → filename (strongest wins) | <1s | `.ai/reports/test-gaps-<date>.md` |
42
- | `quality` | Slop score + jscpd duplication + eslint/ruff + hygiene | 5–15s | `.ai/reports/quality-review-<date>.md` |
43
- | `dev-report` | Commits, contributors, hot files, velocity, conventional % | <1s | `.ai/reports/developer-report-<date>.md` |
44
- | `licenses` | Dependency license inventory across every active pack (TS/Python/Go/Rust/C#) | 5–20s | `.ai/reports/licenses-<date>.md` |
45
- | `bom` | **Bill of Materials** — joins licenses + vulnerabilities per package, 15-col XLSX; groups advisories by top-level manifest dep (Snyk-style) | 10–40s | `.ai/reports/bom-<date>.{md,xlsx}` |
41
+ | Command | What it does | Runtime | Output |
42
+ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | --------------------------------------------- |
43
+ | `health` | 6-dimension score (Testing, Quality, Docs, Security, Maint, DX) | 10–20s | `.dxkit/reports/health-audit-<date>.md` |
44
+ | `vulnerabilities` | gitleaks + semgrep + per-pack dep-audit (per-advisory detail in `--detailed`) | 5–30s | `.dxkit/reports/vulnerability-scan-<date>.md` |
45
+ | `test-gaps` | Coverage artifact → import-graph → filename (strongest wins) | <1s | `.dxkit/reports/test-gaps-<date>.md` |
46
+ | `quality` | Slop score + jscpd duplication + eslint/ruff + hygiene | 5–15s | `.dxkit/reports/quality-review-<date>.md` |
47
+ | `dev-report` | Commits, contributors, hot files, velocity, conventional % | <1s | `.dxkit/reports/developer-report-<date>.md` |
48
+ | `licenses` | Dependency license inventory across every active pack (TS/Python/Go/Rust/C#) | 5–20s | `.dxkit/reports/licenses-<date>.md` |
49
+ | `bom` | **Bill of Materials** — joins licenses + vulns per package, groups by top-level manifest dep (Snyk-style), enriches with CISA KEV + EPSS + reachability, ranks by composite risk score with "This Week's Triage" summary, aggregates nested sub-projects, `--filter=top-level` collapses transitive rows, 15-col XLSX | 10–40s | `.dxkit/reports/bom-<date>.{md,xlsx}` |
46
50
 
47
51
  Plus a converter: `vyuh-dxkit to-xlsx <json-file>` renders any `licenses` or `bom` detailed JSON as the canonical 15-column XLSX.
48
52
 
49
53
  ### Flags (apply to all analyzer commands)
50
54
 
51
- | Flag | Effect |
52
- | ---------------- | -------------------------------------------------------------------------------------- |
53
- | `--detailed` | Also writes `<name>-detailed.md` + `.json` with evidence + ranked remediation actions |
54
- | `--json` | Emit pure JSON on stdout. Logs go to stderr so pipes stay clean |
55
- | `--verbose` | Print per-tool timing to stderr |
56
- | `--no-save` | Skip writing markdown; useful with `--json` |
57
- | `--xlsx` | (`licenses`, `bom` only) Also write 15-col `.xlsx` — drop-in for spreadsheet workflows |
58
- | `-o <file>` | (`licenses`, `bom`, `to-xlsx`) Override output path for xlsx / converted file |
59
- | `--since <date>` | (`dev-report` only) Analyze commits on or after `YYYY-MM-DD` |
55
+ | Flag | Effect |
56
+ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
57
+ | `--detailed` | Also writes `<name>-detailed.md` + `.json` with evidence + ranked remediation actions |
58
+ | `--json` | Emit pure JSON on stdout. Logs go to stderr so pipes stay clean |
59
+ | `--verbose` | Print per-tool timing to stderr |
60
+ | `--no-save` | Skip writing markdown; useful with `--json` |
61
+ | `--xlsx` | (`licenses`, `bom` only) Also write 15-col `.xlsx` — drop-in for spreadsheet workflows |
62
+ | `-o <file>` | (`licenses`, `bom`, `to-xlsx`) Override output path for xlsx / converted file |
63
+ | `--since <date>` | (`dev-report` only) Analyze commits on or after `YYYY-MM-DD` |
64
+ | `--filter` | (`bom` only) `all` (default) or `top-level` — keep only root manifest deps; the byTopLevelDep rollup still reflects transitives |
65
+ | `--no-nested` | (`bom` only) Disable nested-project aggregation. Default discovers every sub-project with a language manifest under cwd and merges their BOMs |
60
66
 
61
67
  ### Detailed mode — evidence + ranked fixes
62
68
 
@@ -188,7 +194,7 @@ CLAUDE.md # Main context file for Claude Code
188
194
 
189
195
  The scaffolded slash commands (`/health`, `/vulnerabilities`, `/test-gaps`, `/quality`, `/dev-report`) use a three-tier fallback:
190
196
 
191
- 1. **Check for an existing report** in `.ai/reports/` from today
197
+ 1. **Check for an existing report** in `.dxkit/reports/` from today
192
198
  2. **Run `vyuh-dxkit <command>`** — deterministic, fast, same output
193
199
  3. **Fall back to LLM analysis** only if the CLI isn't available
194
200
 
@@ -313,10 +319,10 @@ Both loops use the session framework — checkpoints, skill evolution, progress
313
319
 
314
320
  ## Reports
315
321
 
316
- All analyzer commands save timestamped reports to `.ai/reports/`:
322
+ All analyzer commands save timestamped reports to `.dxkit/reports/`:
317
323
 
318
324
  ```
319
- .ai/reports/
325
+ .dxkit/reports/
320
326
  health-audit-<date>.md
321
327
  health-audit-<date>-detailed.md # with --detailed
322
328
  health-audit-<date>-detailed.json # agent-consumable
@@ -359,12 +365,17 @@ When create-devstack writes `.project.yaml` before calling dxkit, detection and
359
365
  ## CLI Reference
360
366
 
361
367
  ```bash
362
- # Analyzer commands
363
- vyuh-dxkit health [path] # 6-dimension score
364
- vyuh-dxkit vulnerabilities [path] # Security scan
365
- vyuh-dxkit test-gaps [path] # Coverage + gaps + actions
366
- vyuh-dxkit quality [path] # Slop + duplication + lint
367
- vyuh-dxkit dev-report [path] # Git activity report
368
+ # Analyzer commands — each writes to .dxkit/reports/<name>-<date>.md
369
+ vyuh-dxkit health [path] # 6-dimension score
370
+ vyuh-dxkit vulnerabilities [path] # Security scan, ranked by composite risk
371
+ vyuh-dxkit test-gaps [path] # Coverage + gaps + actions
372
+ vyuh-dxkit quality [path] # Slop + duplication + lint
373
+ vyuh-dxkit dev-report [path] [--since <date>] # Git activity report
374
+ vyuh-dxkit licenses [path] # Dependency license inventory
375
+ vyuh-dxkit bom [path] [--filter=top-level] # Bill of Materials + risk-ranked triage
376
+
377
+ # Data conversion
378
+ vyuh-dxkit to-xlsx <json-file> # render licenses/bom detailed JSON as 15-col XLSX
368
379
 
369
380
  # Tool management
370
381
  vyuh-dxkit tools # status
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Project-root discovery for nested BOM aggregation.
3
+ *
4
+ * `vyuh-dxkit bom <path>` historically scanned `<path>` as a single
5
+ * project root. Repos like `vyuhlabs-platform/` (root devtools +
6
+ * `userserver/` product) fell through the cracks — the scanner saw
7
+ * only whichever `package.json`/lockfile lived at `<path>`, missing
8
+ * every sibling or nested sub-project. See D001a in the internal
9
+ * defect log for the incident write-up.
10
+ *
11
+ * This module walks the filesystem starting at cwd and returns every
12
+ * directory that looks like an independent project root (i.e. has any
13
+ * language manifest, regardless of whether a parent also does). The
14
+ * BOM analyzer then runs the existing per-root gather against each
15
+ * and merges the results.
16
+ *
17
+ * Why a hardcoded skip-set rather than `exclusions.ts`: this is a
18
+ * structural traversal, not a gitignore-based code scan. `exclusions.ts`
19
+ * is tuned for "which files does the user consider source code?" and
20
+ * pulls in `.gitignore` rules that would incorrectly hide sibling
21
+ * projects (e.g. `.gitignore: dist/` would skip a sub-project under
22
+ * `dist/` even though it might legitimately be a shippable artifact
23
+ * the user wants inventoried).
24
+ */
25
+ /**
26
+ * Walk `cwd` and return every directory that contains at least one
27
+ * language manifest. Always includes `cwd` itself when it has one,
28
+ * even if nested sub-projects also exist (the aggregator treats all
29
+ * roots symmetrically and dedupes findings across them).
30
+ *
31
+ * Pure over the filesystem: no caching, no side effects beyond
32
+ * filesystem reads. Returns absolute paths, sorted alphabetically
33
+ * for deterministic output.
34
+ *
35
+ * Exported for unit tests.
36
+ */
37
+ export declare function discoverProjectRoots(cwd: string, maxDepth?: number): string[];
38
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../../src/analyzers/bom/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AA2DH;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAA0B,GAAG,MAAM,EAAE,CAIhG"}
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ /**
3
+ * Project-root discovery for nested BOM aggregation.
4
+ *
5
+ * `vyuh-dxkit bom <path>` historically scanned `<path>` as a single
6
+ * project root. Repos like `vyuhlabs-platform/` (root devtools +
7
+ * `userserver/` product) fell through the cracks — the scanner saw
8
+ * only whichever `package.json`/lockfile lived at `<path>`, missing
9
+ * every sibling or nested sub-project. See D001a in the internal
10
+ * defect log for the incident write-up.
11
+ *
12
+ * This module walks the filesystem starting at cwd and returns every
13
+ * directory that looks like an independent project root (i.e. has any
14
+ * language manifest, regardless of whether a parent also does). The
15
+ * BOM analyzer then runs the existing per-root gather against each
16
+ * and merges the results.
17
+ *
18
+ * Why a hardcoded skip-set rather than `exclusions.ts`: this is a
19
+ * structural traversal, not a gitignore-based code scan. `exclusions.ts`
20
+ * is tuned for "which files does the user consider source code?" and
21
+ * pulls in `.gitignore` rules that would incorrectly hide sibling
22
+ * projects (e.g. `.gitignore: dist/` would skip a sub-project under
23
+ * `dist/` even though it might legitimately be a shippable artifact
24
+ * the user wants inventoried).
25
+ */
26
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ var desc = Object.getOwnPropertyDescriptor(m, k);
29
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
30
+ desc = { enumerable: true, get: function() { return m[k]; } };
31
+ }
32
+ Object.defineProperty(o, k2, desc);
33
+ }) : (function(o, m, k, k2) {
34
+ if (k2 === undefined) k2 = k;
35
+ o[k2] = m[k];
36
+ }));
37
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
38
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
39
+ }) : function(o, v) {
40
+ o["default"] = v;
41
+ });
42
+ var __importStar = (this && this.__importStar) || (function () {
43
+ var ownKeys = function(o) {
44
+ ownKeys = Object.getOwnPropertyNames || function (o) {
45
+ var ar = [];
46
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
47
+ return ar;
48
+ };
49
+ return ownKeys(o);
50
+ };
51
+ return function (mod) {
52
+ if (mod && mod.__esModule) return mod;
53
+ var result = {};
54
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
55
+ __setModuleDefault(result, mod);
56
+ return result;
57
+ };
58
+ })();
59
+ Object.defineProperty(exports, "__esModule", { value: true });
60
+ exports.discoverProjectRoots = discoverProjectRoots;
61
+ const fs = __importStar(require("fs"));
62
+ const path = __importStar(require("path"));
63
+ /** File basenames that mark a directory as a project root. */
64
+ const MANIFEST_BASENAMES = new Set([
65
+ 'package.json', // Node
66
+ 'pyproject.toml', // Python (PEP 621 / poetry)
67
+ 'requirements.txt', // Python (pip)
68
+ 'setup.py', // Python (legacy)
69
+ 'Pipfile', // Python (pipenv)
70
+ 'go.mod', // Go
71
+ 'Cargo.toml', // Rust
72
+ ]);
73
+ /** File extensions that mark a directory as a project root. */
74
+ const MANIFEST_EXTENSIONS = [
75
+ '.csproj', // C# project
76
+ '.sln', // C# solution
77
+ ];
78
+ /**
79
+ * Directories we never descend into during discovery.
80
+ *
81
+ * - Dependency trees (`node_modules`, `vendor`, `venv`, `.venv`,
82
+ * `target`, `bin`, `obj`): contain manifests from installed
83
+ * packages, not user projects.
84
+ * - Build output (`dist`, `build`, `out`, `.next`, `.turbo`,
85
+ * `.cache`): derived, not source-of-truth.
86
+ * - VCS / tool metadata (`.git`, `.svn`, `.hg`): never has
87
+ * meaningful manifests.
88
+ *
89
+ * Any dotfile directory is also skipped — caches, IDE state, etc.
90
+ */
91
+ const SKIP_DIR_BASENAMES = new Set([
92
+ 'node_modules',
93
+ 'vendor',
94
+ 'venv',
95
+ '.venv',
96
+ 'target',
97
+ 'bin',
98
+ 'obj',
99
+ 'dist',
100
+ 'build',
101
+ 'out',
102
+ '.next',
103
+ '.turbo',
104
+ '.cache',
105
+ '.git',
106
+ '.svn',
107
+ '.hg',
108
+ 'TestResults',
109
+ 'packages',
110
+ ]);
111
+ /** Default depth cap: enough for `packages/foo/sub`, excess discouraged. */
112
+ const DEFAULT_MAX_DEPTH = 4;
113
+ /**
114
+ * Walk `cwd` and return every directory that contains at least one
115
+ * language manifest. Always includes `cwd` itself when it has one,
116
+ * even if nested sub-projects also exist (the aggregator treats all
117
+ * roots symmetrically and dedupes findings across them).
118
+ *
119
+ * Pure over the filesystem: no caching, no side effects beyond
120
+ * filesystem reads. Returns absolute paths, sorted alphabetically
121
+ * for deterministic output.
122
+ *
123
+ * Exported for unit tests.
124
+ */
125
+ function discoverProjectRoots(cwd, maxDepth = DEFAULT_MAX_DEPTH) {
126
+ const roots = new Set();
127
+ walk(cwd, 0, maxDepth, roots);
128
+ return [...roots].sort();
129
+ }
130
+ function walk(dir, depth, maxDepth, roots) {
131
+ if (depth > maxDepth)
132
+ return;
133
+ let entries;
134
+ try {
135
+ entries = fs.readdirSync(dir, { withFileTypes: true });
136
+ }
137
+ catch {
138
+ return;
139
+ }
140
+ let isRoot = false;
141
+ for (const e of entries) {
142
+ if (!e.isFile())
143
+ continue;
144
+ if (MANIFEST_BASENAMES.has(e.name)) {
145
+ isRoot = true;
146
+ break;
147
+ }
148
+ if (MANIFEST_EXTENSIONS.some((ext) => e.name.endsWith(ext))) {
149
+ isRoot = true;
150
+ break;
151
+ }
152
+ }
153
+ if (isRoot)
154
+ roots.add(dir);
155
+ for (const e of entries) {
156
+ if (!e.isDirectory())
157
+ continue;
158
+ if (SKIP_DIR_BASENAMES.has(e.name))
159
+ continue;
160
+ // Skip dotfile directories (caches, IDE state) except the repo root's own.
161
+ if (e.name.startsWith('.') && depth > 0)
162
+ continue;
163
+ walk(path.join(dir, e.name), depth + 1, maxDepth, roots);
164
+ }
165
+ }
166
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../src/analyzers/bom/discovery.ts"],"names":[],"mappings":"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"}
@@ -58,6 +58,34 @@ export interface BomGatherResult {
58
58
  entries: BomEntry[];
59
59
  toolsUsed: string[];
60
60
  toolsUnavailable: string[];
61
+ /** Cwd-relative project-root paths the gather walked. Length 1 for
62
+ * single-root scans ("." ); length >1 for nested aggregation. */
63
+ projectRoots: string[];
61
64
  }
65
+ /**
66
+ * Merge per-root gather results into one deduplicated set.
67
+ *
68
+ * Dedupe key is `(package, version)` — the same logical package at
69
+ * the same version installed under two roots is the same artifact,
70
+ * so reporting two rows would be noise. When the same key appears
71
+ * under multiple roots:
72
+ *
73
+ * - `sources` unions the sub-paths
74
+ * - `isTopLevel` OR-merges — if any root treats the package as
75
+ * top-level, the merged entry is top-level (upgrade decisions
76
+ * surface under Top-Level Dep Groups)
77
+ * - `vulns` unions with dedup on `(id, package, installedVersion)`
78
+ * — the same advisory reported from two roots collapses into
79
+ * one finding but its `topLevelDep` list unions
80
+ * - license metadata (licenseType, sourceUrl, etc.) prefers the
81
+ * first root with non-UNKNOWN data, falling back to whatever
82
+ * the first-seen entry carried
83
+ *
84
+ * Pure function; unit-testable without filesystem.
85
+ */
86
+ export declare function mergeNestedBomEntries(perRoot: ReadonlyArray<{
87
+ relPath: string;
88
+ result: BomGatherResult;
89
+ }>): BomGatherResult;
62
90
  export declare function gatherBomEntries(cwd: string): Promise<BomGatherResult>;
63
91
  //# sourceMappingURL=gather.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gather.d.ts","sourceRoot":"","sources":["../../../src/analyzers/bom/gather.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,oCAAoC,CAAC;AACzF,OAAO,KAAK,EAAE,QAAQ,EAAe,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAIxE;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAgB1D;AAED;mDACmD;AACnD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAGpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,MAAM,CAQrE;AAYD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAkCzF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAiF5E"}
1
+ {"version":3,"file":"gather.d.ts","sourceRoot":"","sources":["../../../src/analyzers/bom/gather.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,oCAAoC,CAAC;AACzF,OAAO,KAAK,EAAE,QAAQ,EAAe,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAIxE;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAgB1D;AAED;mDACmD;AACnD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAGpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,MAAM,CAQrE;AAYD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAkCzF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B;sEACkE;IAClE,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,aAAa,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,CAAC,GACnE,eAAe,CAkEjB;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAkF5E"}