Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
6c86beb
chore(deps): update tinymcp subproject commit
senamakel Sep 19, 2026
c841338
chore(dep-audit): add initial audit scripts and configuration
senamakel Sep 19, 2026
64f7993
fix(scripts): correct dependency audit report generation
senamakel Sep 19, 2026
25f5394
chore(deps): update Cargo.lock for dependency changes
senamakel Sep 19, 2026
b559b68
chore(dep-audit): refine unused dependency detection
senamakel Sep 19, 2026
1fd2b8d
feat(dep-audit): add reverse dependency graph to report
senamakel Sep 19, 2026
d9ca782
feat(dep-audit): filter version drift report to semver-incompatible d…
senamakel Sep 19, 2026
712a9db
feat(dep-audit): add snapshot mode and machine-readable summary
senamakel Sep 19, 2026
0d3cbbf
chore(scripts): add dep-audit script and document it
senamakel Sep 19, 2026
6cd7549
docs(dep-audit): add README for dependency audit script
senamakel Sep 19, 2026
ba5436c
fix(scripts/dep-audit): remove double-dash from pnpm examples and add…
senamakel Sep 19, 2026
39bc7b5
docs(scripts/dep-audit): align comment indentation in README
senamakel Sep 19, 2026
f1da47f
chore(dep-audit): snapshot and restore lockfiles rewritten by tinyana…
senamakel Sep 19, 2026
ad43951
docs(dep-audit): document that the audit tool preserves lockfiles
senamakel Sep 19, 2026
1f57451
feat(dep-audit): resolve dependency version per workspace package
senamakel Sep 19, 2026
7a9c056
merge upstream/main into dep-audit
senamakel Sep 19, 2026
51f2de3
chore(deps): update tinymcp submodule
senamakel Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,427 changes: 1,427 additions & 0 deletions docs/dep-audit/2026-09-19.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"rust:check": "pnpm --filter openhuman-app rust:check",
"rust:clippy": "cargo clippy -p openhuman -- -D warnings && pnpm --filter openhuman-app rust:clippy",
"rust:layout": "node scripts/ci/check-openhuman-rust-layout.mjs",
"dep:audit": "bash scripts/dep-audit/run.sh",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high critique confident

Resolve renamed dependencies before auditing imports

This command invokes the audit that scans source imports using the manifest dependency name rather than Cargo's resolved package/crate name. A dependency such as serde1 = { package = "serde" } can therefore be reported as unused even though the source correctly imports serde, encouraging deletion of a required dependency and breaking the target build. Resolve each dependency's package rename before textual scanning.

[RULE] renamed-dependency-detection ·

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high critique confident

Reject symlinked lockfiles before restoring them

The command snapshots lockfiles with cp and restores them by writing to the lockfile path. If a target's Cargo.lock is a symlink, both operations follow it, so an audit can overwrite or restore an unrelated file outside the target (and potentially outside the repository). Refuse symlinked lockfiles or snapshot and restore the symlink itself without following it.

[RULE] symlink-lockfile ·

"agent:runtime-boundary": "node scripts/ci/check-agent-runtime-boundary.mjs",
"typecheck": "pnpm --filter openhuman-app compile",
"tauri:ios:init": "bash scripts/ios-init.sh",
Expand Down
2 changes: 2 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ comments of the scripts inside them.
| `theme-codemod/` | Codemod collapsing audited `light dark:` Tailwind pairings into the semantic theme utilities (`node scripts/theme-codemod/migrate.mjs [--write]`; see `gitbooks/developing/theming.md`). |
| `agent-batch/` | Validates a batch spec of parallel agent branches, checks file overlap, prints per-agent launch prompts, and reports status (`pnpm agent-batch <validate\|overlap\|launch\|status> <spec.json>`). |
| `deep-work/` | Issue-to-PR workflow automation over worktrees and AI agents: `pnpm deep-work start\|pick\|continue\|status\|list\|cleanup`. |
| `dep-audit/` | Cargo dependency audit (unused, duplicate-version, heavy, cross-repo drift) over the root workspace, `openhuman-app`, and every Cargo submodule, driven by `tinyanalyzer` — see [dep-audit/README.md](dep-audit/README.md). |

## pnpm-wired entry points

Expand All @@ -37,6 +38,7 @@ comments of the scripts inside them.
| `pnpm mock:api` | `mock-api-server.mjs` |
| `pnpm debug ...` | `debug/cli.sh` |
| `pnpm rust:layout` | `ci/check-openhuman-rust-layout.mjs` |
| `pnpm dep:audit` | `dep-audit/run.sh` |
| `pnpm test:inventory` | `generate-test-inventory.mjs` |
| `pnpm pr:checklist` | `check-pr-checklist.mjs` |
| `pnpm prompt:report` | `prompt-report.sh` — every agent's fixed prefix (prompt + tool schemas), largest first; report-only |
Expand Down
149 changes: 149 additions & 0 deletions scripts/dep-audit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# `scripts/dep-audit/` — Cargo dependency audit

Finds dependencies we can drop, unify, or slim across OpenHuman **and every
Cargo submodule under `vendor/`**, using
[`tinyanalyzer`](https://github.com/tinyhumansai/tinyanalyzer).

```bash
pnpm dep:audit # full sweep -> target/dep-audit/REPORT.md
pnpm dep:audit --snapshot # ...and archive it as docs/dep-audit/<date>.md
pnpm dep:audit --targets '^(root|tinyagents)$' --top 25
```

A full sweep of 24 targets takes about 20 seconds; nothing is compiled, the
tool only runs `cargo metadata` and parses source.

The run has no side effects on the tree: `cargo metadata` rewrites a
`Cargo.lock` that is stale relative to its manifest (`crates/openhuman-app`'s
lockfile in particular), so `run.sh` snapshots every target's lockfile before
analyzing it and restores it afterwards, printing which ones it had to put
back. Refresh those deliberately if you want them refreshed.
Comment thread
senamakel marked this conversation as resolved.

## Files

| File | Role |
| --- | --- |
| `run.sh` | Discovers targets, runs `tinyanalyzer` once per target, then calls `report.mjs`. `--help` lists the flags. |
| `report.mjs` | Folds the per-target JSON into `REPORT.md` and `summary.json`. Re-runnable on its own: `node scripts/dep-audit/report.mjs --reports target/dep-audit`. |
| `tinyanalyzer.toml` | Shared analyzer config passed to every target (`--config`). Holds the `ignore_unused` list; see below before editing it. |
| `../../docs/dep-audit/<date>.md` | Committed snapshots from `--snapshot` runs, for diffing against the next run. |

## Prerequisites

- `tinyanalyzer` on `PATH` (`run.sh` prints the install one-liner if missing).
- Submodules checked out: `git submodule update --init --recursive vendor/`.
- Node 20+ (for `report.mjs`), `git`, `grep`.

## What gets analyzed

`run.sh` builds the target list itself, so a new submodule is picked up
automatically:

1. `root` — the OpenHuman workspace (`Cargo.toml` at the repo root).
2. `openhuman-app` — the Tauri host. It is `exclude`d from the root
workspace and has its own `Cargo.lock`, so it is a separate graph.
3. Every entry of `git submodule status --recursive` that has a `Cargo.toml`,
named after its directory (`tinyagents`, `tinybus`, `tinycortex`, …).

Nested checkouts (`vendor/tinymcp/vendor/tinybus`) are **skipped when a
checkout of the same repo at the same commit was already analyzed**, and
otherwise get a path-derived suffix (`tinybus@vendor_tinybox_vendor_tinybus`)
so both pins show up. A suffixed row in the report therefore *is* a finding:
that submodule pins a different commit of a shared dependency than its
siblings. `--keep-nested` analyzes every checkout regardless.

## Reading `REPORT.md`

### Targets

One row per target with its commit and headline counts. "Crates in graph" is
Comment thread
senamakel marked this conversation as resolved.
the *full* `cargo metadata` resolve — every platform and every optional
feature — which is why Windows-only crates appear on a Linux run and why a
duplicate listed here may not show in `cargo tree` on your host.

### 1. Declared dependencies no source file names

tinyanalyzer's check is textual: a dependency is "unused" if no `.rs` file in
the package mentions the crate. That misses crate names inside attributes
(`#[tokio::test]`, `#[derive(thiserror::Error)]`), so `report.mjs` re-checks
every flag with a grep over the package's own sources, **including
`[[test]]` / `[[example]]` targets declared by `path =` in its `Cargo.toml`**
(the root crate keeps its integration tests in `tests/` that way). Verdicts:

| Verdict | Meaning | Action |
| --- | --- | --- |
| **remove** | No `crate::…`, `use crate`, `#[crate…` or `crate!` anywhere. | Delete the line, `cargo check` (both feature-on and feature-off builds if it was `optional`), delete the `dep:` feature if one existed. |
| **remove** (name only) | The bare word occurs in a comment or string but never as a path. | Same as above; the mention is not a use. |
| keep (attribute/macro path) | Used through an attribute or macro body. | Nothing. Listed so the tool's false positives stay visible. |

**Graph win** is the number of crates that leave the target's build if that
one line is deleted. It is `0 (kept by …)` when another package in the same
workspace still depends on the crate: the manifest gets cleaner, the build
does not get smaller. Sort your effort by graph win.

A crate that is used *only* through attributes everywhere (currently

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Scope the thiserror suppression to verified false positives

The shared configuration globally suppresses thiserror for every target, including targets where it may be genuinely unused. This can hide a real removable dependency and prevents the report's re-check from evaluating it. Scope the exception to verified targets or remove the global suppression.

[RULE] overbroad-unused-suppression ·

`thiserror`) can be added to `ignore_unused` in `tinyanalyzer.toml` so it stops
being reported. Do that only for crates that can never be a real finding;
every entry hides the crate from the check in all 24 targets.

### 2. Crates resolved at more than one version

Each version is compiled and linked separately. **Only the `root` (and
`openhuman-app`) sections cost the shipped build**; submodule sections show
where a requirement should move so the root can unify.

"Pulled in via" names the *direct* dependencies whose subtree carries that
version, walked from tinyanalyzer's edge list. `direct dep of <pkg>` means one
of our own packages declares it. To unify:

- If one version's "via" list is a single old crate we control (a submodule
or a direct dep with a stale requirement), bump it.
- If the old version is only reachable through an *unused* direct dependency
(section 1), removing that dependency removes the duplicate too.
- If both versions are reached through third-party crates we do not control,
check whether one of them has a newer release; otherwise accept it.

`cargo tree -i <crate>@<version>` in that target gives the full chain.

### 3. Heaviest direct dependencies

Per target, the direct dependencies with the largest **exclusive** transitive

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Exclude development dependencies from shipped-build cost claims

The analyzer configuration explicitly includes development dependencies, but this section presents every direct dependency's exclusive footprint as build cost without distinguishing dev or build kinds. Test- and example-only dependencies can therefore be reported as costs of the shipped build even though Cargo does not link them into a normal release. Restrict the table to runtime dependencies or label the dependency kinds and change the claim.

[RULE] development-dependency-cost ·

footprint — crates that would leave the build entirely if this one were
dropped. "Reaches" is the raw transitive count, most of which something else
pulls in anyway. "Source" is checked-out source size, not binary size.

A high exclusive count usually means default features pulling in a subtree we
do not use. Try `default-features = false` plus the two or three features
needed, then re-run the audit and compare the row. `scripts/dep-sim.py` and
`scripts/assert-shed.sh` remain the tools for *proving* a reduction before
claiming it in a PR.

### 4. Version drift across repositories

Crates that two or more targets depend on directly but at
**semver-incompatible** versions (different major, or different minor for
`0.x`). Because the root workspace `[patch]`-es submodules in, every such row
becomes a duplicate in section 2 of `root`. Aligning the submodule's
requirement with the root's is usually a one-line change in that submodule
and removes a duplicate for free. Patch-level drift is counted but not
listed; cargo unifies it.

## Workflow for a clean-up pass

1. `pnpm dep:audit --snapshot` on a fresh branch.
2. Work section 1 by graph win, then section 4, then section 2's `root`
rows. Submodule changes go to that submodule's own upstream as their own
PR; bump the gitlink here afterwards (see the submodule PR conventions in
`AGENTS.md`).
3. Re-run without `--snapshot` and diff `target/dep-audit/REPORT.md` against
the committed snapshot. Commit a new snapshot with the clean-up PR.

## Extending

- `summary.json` next to `REPORT.md` has the same data as structured JSON
(`targets[].unused`, `.duplicates`, `.heavy`, and top-level `drift`) for a
future CI gate, e.g. failing on any new `remove` row with a graph win.
- Per-target `<name>.json` files are the raw tinyanalyzer reports and also
carry file, complexity and dead-code findings that this report ignores.
`tinyanalyzer vendor/<name>` opens the interactive dashboard over the same
data.
Loading
Loading