@vyuhlabs/dxkit 2.2.1 → 2.3.1

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 (103) hide show
  1. package/CHANGELOG.md +173 -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 +13 -0
  58. package/dist/analyzers/tools/tool-registry.d.ts.map +1 -1
  59. package/dist/analyzers/tools/tool-registry.js +81 -35
  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/dist/tools-cli.d.ts.map +1 -1
  85. package/dist/tools-cli.js +10 -4
  86. package/dist/tools-cli.js.map +1 -1
  87. package/package.json +1 -1
  88. package/templates/.claude/agents-available/dashboard-builder.md +7 -7
  89. package/templates/.claude/agents-available/dev-report.md +4 -4
  90. package/templates/.claude/agents-available/health-auditor.md +1 -1
  91. package/templates/.claude/agents-available/strategic-planner.md +7 -7
  92. package/templates/.claude/agents-available/vulnerability-scanner.md +3 -3
  93. package/templates/.claude/commands/dashboard.md +1 -1
  94. package/templates/.claude/commands/deps.md +1 -1
  95. package/templates/.claude/commands/dev-report.md +2 -2
  96. package/templates/.claude/commands/docs.md +1 -1
  97. package/templates/.claude/commands/export-pdf.md +3 -3
  98. package/templates/.claude/commands/health.md +3 -3
  99. package/templates/.claude/commands/plan.md +1 -1
  100. package/templates/.claude/commands/quality.md.template +2 -2
  101. package/templates/.claude/commands/stealth-mode.md +1 -1
  102. package/templates/.claude/commands/test-gaps.md +2 -2
  103. package/templates/.claude/commands/vulnerabilities.md +3 -3
package/CHANGELOG.md CHANGED
@@ -7,6 +7,179 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.3.1] - 2026-04-24
11
+
12
+ Patch release fixing three install-robustness issues reported on a
13
+ real vyuhlabs-platform install:
14
+
15
+ ### Fixed
16
+
17
+ - **`@vitest/coverage-v8` install crashed with `MODULE_NOT_FOUND`** on
18
+ repos that don't use vitest (mocha / jest / ava / lb-mocha). The
19
+ install command called `node -e "require('vitest/package.json')"`
20
+ to auto-detect the vitest major — unconditionally, so any non-
21
+ vitest project hit a hard crash during `tools install --yes`.
22
+ Now prefixed with `test -f node_modules/vitest/package.json ||
23
+ { echo 'vitest not present — skipping'; exit 0; }` so the install
24
+ no-ops cleanly when vitest isn't a target-repo dep.
25
+
26
+ - **Semgrep / pip-audit / ruff / pip-licenses / coverage dep pins
27
+ colliding in the shared venv**. Pre-2.3.1 installed every Python
28
+ CLI tool into one venv at `~/.cache/dxkit/tools-venv/`. semgrep's
29
+ `tomli~=2.0.1` pin lost to pip-audit's newer tomli, breaking
30
+ semgrep on repos where both tools installed. Every Python CLI
31
+ (semgrep, ruff, pip-audit, pip-licenses, coverage) now uses
32
+ `pipx install <tool>`, putting each in its own isolated venv
33
+ under `~/.local/pipx/venvs/<tool>/`. Binaries symlink into
34
+ `~/.local/bin/` which is already in `getSystemPaths()`'s probe
35
+ list, so `findTool()` picks them up without further changes.
36
+ Bootstrap fragment auto-installs pipx via `pip --user` when
37
+ absent (handles PEP-668 Debian/Ubuntu with
38
+ `--break-system-packages` fallback).
39
+
40
+ - **Graphify stays on the shared venv** — it's a Python *library*
41
+ that our graphify.ts subprocess imports, not a CLI tool, so pipx
42
+ doesn't apply. `TOOLS_VENV` narrows to graphify-only.
43
+
44
+ - **"Install command exited 0 without producing the binary" now
45
+ reports as skipped, not failed**. Any install command can
46
+ legitimately no-op (guarded installs like vitest-coverage);
47
+ those no-ops shouldn't clutter the failure summary. Real
48
+ failures (non-zero exit) still classify as `failed`.
49
+
50
+ ### Known limitations (not blocking)
51
+
52
+ - `npm install @vyuhlabs/dxkit` still emits deprecation warnings for
53
+ `inflight@1`, `glob@7`, `fstream`, `rimraf@2`, `lodash.isequal` —
54
+ all transitive under `exceljs` (via `archiver` → `archiver-utils`).
55
+ exceljs@4.4.0 is the latest available; the chain is upstream.
56
+ Warnings only, no functional impact; would require either switching
57
+ xlsx libraries (breaking) or upstream archiver modernization.
58
+
59
+ ### Validation on vyuhlabs-platform/userserver
60
+
61
+ - `vyuh-dxkit tools` reports 12/13 tools found (vitest-coverage
62
+ correctly listed as missing since lb-mocha is in use)
63
+ - `vyuh-dxkit tools install --yes` reports `0 installed, 1 skipped,
64
+ 0 failed` (clean)
65
+ - `vyuh-dxkit bom --xlsx --filter=top-level` completes in 17s,
66
+ writes `.dxkit/reports/bom-YYYY-MM-DD.{md,xlsx}` cleanly
67
+
68
+ ## [2.3.0] - 2026-04-24
69
+
70
+ Minor release — turns the `bom` report from enumeration (1700+ rows
71
+ of noise) into a **decision doc** (top 10 triage queue ranked by
72
+ composite exploit-risk). Every `DepVulnFinding` now carries five
73
+ exploitability signals — CVSS, EPSS, CISA KEV, reachability,
74
+ composite `riskScore` — that consumers can read individually or as
75
+ the ranked `Risk` column. `licenses` + `vulnerabilities` renders
76
+ gain parity with the new bom surface so any dxkit command shows the
77
+ same triage-relevant data.
78
+
79
+ Nine sub-commits (Phase 10h.5) landed behind PRs #4 / #5 / #6 /
80
+ #7 / #8 / #9 / #10 / #11 through the hardened 2.2.1 pipeline —
81
+ the first full release cut where every commit flowed PR → CI-green →
82
+ merge → tag → CI-publishes without deviation.
83
+
84
+ ### Added — exploitability enrichers
85
+
86
+ - **EPSS** (`DepVulnFinding.epssScore`, 0.0–1.0) from FIRST.org's
87
+ `api.first.org/data/v1/epss`. Batched (≤100 CVEs/call), session-
88
+ cached, graceful offline fallback. Non-CVE primaries (GHSA /
89
+ RUSTSEC / GO / PYSEC) resolve via OSV.dev alias lookup — no
90
+ coverage gap across packs. (10h.5.1)
91
+
92
+ - **CISA KEV** (`DepVulnFinding.kev`, boolean) from the official
93
+ catalog at `cisa.gov/.../known_exploited_vulnerabilities.json`.
94
+ Single bulk fetch per process, O(1) lookup. Badge `⚠` in every
95
+ render. (10h.5.2)
96
+
97
+ - **Reachability** (`DepVulnFinding.reachable`, tri-state) — does
98
+ this repo's source actually import the vulnerable package?
99
+ Built from per-pack `ImportsResult`'s specifier extraction;
100
+ `specifierToPackage` handles TS scoped/bare, Python dotted
101
+ modules, Go 3-segment module paths. Coarse name-level
102
+ matching; undefined when no imports data available. (10h.5.3)
103
+
104
+ - **Composite riskScore** (`DepVulnFinding.riskScore`, 0–100) —
105
+ `clamp(cvss*10 × kev? × (1+2*epss) × reach?, 0, 100)`. Formula
106
+ documented in `src/analyzers/tools/risk-score.ts`. Null when
107
+ CVSS missing (no fabrication from side signals). (10h.5.4)
108
+
109
+ - **"This Week's Triage"** section at the top of every bom report —
110
+ top 10 advisories with riskScore ≥ 15, rationale composed from
111
+ most decisive signals (KEV → reachable → CVSS → EPSS), fix
112
+ column with "PROPOSAL:" prefix stripped. (10h.5.5)
113
+
114
+ ### Added — decision-doc UX
115
+
116
+ - **`bom --filter=top-level`** drops transitive rows (1700+ → ~150
117
+ on typical repos) while the `byTopLevelDep` rollup still reflects
118
+ full blast radius — "upgrading `@loopback/cli` resolves 29
119
+ advisories" survives when those 29 transitive rows are hidden.
120
+ `BomEntry.isTopLevel` + `summary.filter` + `summary.unfilteredTotalPackages`
121
+ ride the shape. (10h.5.0)
122
+
123
+ - **Nested-project aggregation** (default ON; `--no-nested` opts
124
+ out). `src/analyzers/bom/discovery.ts` walks the repo,
125
+ discovers every directory with a language manifest
126
+ (package.json, pyproject.toml/requirements.txt/setup.py/Pipfile,
127
+ go.mod, Cargo.toml, *.csproj/*.sln), runs per-root gather, and
128
+ merges with dedup on `(package, version)`. `BomEntry.sources`
129
+ unions the roots each package was found in; `isTopLevel`
130
+ OR-merges; vulns dedup on `(id, package, installedVersion)`.
131
+ Closes **D001a** — `bom platform/` previously missed
132
+ `platform/userserver/` entirely. Side-benefit: naturally
133
+ addresses **D003** (C# multi-project) since each `.csproj`
134
+ becomes its own root. (10h.5.0b)
135
+
136
+ - **`LicenseFinding.releaseDate`** populated from the npm registry
137
+ for every TS-ecosystem package. Closes **D006** — xlsx col 10
138
+ ("Component Release Date") was previously empty. Bundled with
139
+ the EPSS fetcher roundtrip. (10h.5.1)
140
+
141
+ - **`licenses` render** sorts top-level deps (⭐) first, transitive
142
+ below. Adds `Direct` + `Released` columns. Matches bom's
143
+ `--filter=top-level` ordering so cross-referencing the two
144
+ reports Just Works. (10h.5.6)
145
+
146
+ - **`vulnerabilities` render (main, not --detailed)** per-advisory
147
+ table now sorted by `riskScore` desc with `Risk` / `KEV` /
148
+ `Reach` / `EPSS` columns alongside the existing fields. (10h.5.6)
149
+
150
+ ### Fixed
151
+
152
+ - **D013** — graphify's shared Python venv moved from
153
+ `/tmp/graphify-venv` (subject to systemd-tmpfiles sweep + race
154
+ on first install) to `~/.cache/dxkit/tools-venv` (XDG persistent).
155
+ Also fixed `Date.now()` script-tempfile collision class in
156
+ graphify.ts via `fs.mkdtempSync`. Affects every Python-based
157
+ tool dxkit installs (graphify, semgrep, ruff, pip-audit,
158
+ pip-licenses, coverage). Legacy `/tmp/graphify-venv` path still
159
+ probed, so existing installations aren't forced into a
160
+ reinstall. (10f.2)
161
+
162
+ - **OSV.dev GHSA case-sensitivity** — `api.osv.dev/v1/vulns/<GHSA>`
163
+ expects lowercase; npm-audit emits uppercase. `osv.ts`
164
+ `DEFAULT_FETCHER` normalizes the alphabetic portion. Silently
165
+ broke alias resolution for every TS finding pre-2.3.0.
166
+
167
+ ### Changed — output directory
168
+
169
+ - **Reports moved from `.ai/reports/` to `.dxkit/reports/`**.
170
+ Separates tool output (regenerated each run, can be gitignored)
171
+ from AI-agent context (`.ai/sessions/`, `.ai/prompts/` —
172
+ human-authored, version-controlled). All CLI commands + every
173
+ scaffolded slash command / agent / template updated to the new
174
+ path. Existing `.ai/reports/*.md` files become orphans after
175
+ upgrade — acceptable since reports regenerate each run.
176
+
177
+ ### Process
178
+
179
+ - First full release cut through the 2.2.1-hardened publish
180
+ pipeline: 8 PRs, every one PR→CI→admin-squash-merge→main. Each
181
+ dog-fooded the pre-push CI-mirror hooks landed in PR #3.
182
+
10
183
  ## [2.2.1] - 2026-04-23
11
184
 
12
185
  Patch release hardening the publish pipeline after `v2.2.0`'s Publish
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"}