Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
bb1da1d
feat(viz): re-integrate viz command on current main via the engine layer
BartWaardenburg Jul 16, 2026
21ffd9a
feat(viz): rebuild frontend as a fallow-styled codebase map with lenses
BartWaardenburg Jul 16, 2026
353b51f
docs(viz): changelog and README entries for the codebase map
BartWaardenburg Jul 16, 2026
f8481e3
chore(viz): dogfood-clean the frontend
BartWaardenburg Jul 16, 2026
c284d87
chore(viz): regenerate capability manifest and skill command matrix
BartWaardenburg Jul 16, 2026
37aedc0
feat(viz): replace interpunct fact rows with stat-tile grids
BartWaardenburg Jul 16, 2026
827ea81
feat(viz): drop status-tag prefixes from tooltip, panel, and legend
BartWaardenburg Jul 16, 2026
c79d7e2
feat(viz): data-driven layered graph with dependency roads and an ego…
BartWaardenburg Jul 16, 2026
7b66a0c
chore(viz): allow the LOD term and re-vendor skills against current c…
BartWaardenburg Jul 16, 2026
c4ef815
feat(viz): road drill-down, path trace, search zoom, and motion polish
BartWaardenburg Jul 16, 2026
d4d6748
feat(viz): hub badges and shift-click trace hardening
BartWaardenburg Jul 16, 2026
c3782c1
feat(viz): briefing insights, guided tour, and a help overlay
BartWaardenburg Jul 16, 2026
805c0d3
feat(viz): opening reveal choreography and lens-aware directory tooltips
BartWaardenburg Jul 16, 2026
c26e3b8
feat(viz): minimap, PNG export, and a reset-view key
BartWaardenburg Jul 16, 2026
3f7c295
docs(viz): teach the help overlay and hints the new controls
BartWaardenburg Jul 16, 2026
7d7684a
feat(viz): name the important files at deep zoom
BartWaardenburg Jul 16, 2026
d537ac7
refactor(viz): remove node dragging and the briefing drawer
BartWaardenburg Jul 16, 2026
e744c70
feat(viz): comprehension-first chrome with plain-language lenses
BartWaardenburg Jul 17, 2026
bdc39e8
feat(viz): expert-panel round 2, ranked findings and a filled frame
BartWaardenburg Jul 17, 2026
863ac24
feat(viz): expert-panel round 3, honesty pass and clone drill-down
BartWaardenburg Jul 17, 2026
4f047c7
fix(viz): keep entry tint out of collapsed treemap summary tiles
BartWaardenburg Jul 17, 2026
9493a6d
feat(viz): friendlier developer chrome, no bracket affordances
BartWaardenburg Jul 17, 2026
01f751f
feat(viz): drop the blinking cursor and slash placeholder
BartWaardenburg Jul 17, 2026
1ebdef0
feat(viz): one control height across the header
BartWaardenburg Jul 17, 2026
56fb67e
feat(viz): rebuild the header around a single navigation idea
BartWaardenburg Jul 17, 2026
c6ae1ce
fix(viz): define the radius tokens the chrome already uses
BartWaardenburg Jul 17, 2026
89fc535
fix(viz): wrap portrait cluster layouts into rows
BartWaardenburg Jul 17, 2026
a6a75d2
feat(viz): rebuild the hover neighborhood per UX/UI panel spec
BartWaardenburg Jul 17, 2026
f4e83ee
fix(viz): six findings from a fresh state sweep
BartWaardenburg Jul 17, 2026
91b4cce
fix(viz): treemap respects the open panel; halo zoom labels
BartWaardenburg Jul 17, 2026
1a350fe
refactor(viz): split the graph monolith into a layered module
BartWaardenburg Jul 17, 2026
240098f
refactor(viz): act on fallow's own findings, add a test layer
BartWaardenburg Jul 17, 2026
87ebfb3
refactor(viz): round two of fallow's complexity findings
BartWaardenburg Jul 17, 2026
b9888c1
fix(extract): bound the object-binding fixpoint against cyclic blowup
BartWaardenburg Jul 17, 2026
27dc177
test(engine): characterize the viz payload contract
BartWaardenburg Jul 17, 2026
0c2b87c
fix(cli): escape every < in the embedded viz payload
BartWaardenburg Jul 17, 2026
5143278
fix(engine): summary counts match the rendered cycle and violation ar…
BartWaardenburg Jul 17, 2026
ae6fffe
fix(cli): strip control characters from DOT and Mermaid labels
BartWaardenburg Jul 17, 2026
761a9bb
fix(cli): symlink-safe viz output write with parent creation
BartWaardenburg Jul 17, 2026
2e24fd1
fix(engine): bound the viz clone payload and flag truncation
BartWaardenburg Jul 17, 2026
9ad45fd
perf(viz): adjacency-driven hover and precomputed edge partitions
BartWaardenburg Jul 17, 2026
2011dca
fix(viz): render degenerate payloads sanely
BartWaardenburg Jul 17, 2026
647ebc9
fix(viz): track display environment changes at runtime
BartWaardenburg Jul 17, 2026
046cc77
perf(viz): gate panel rebuilds on panel-relevant state
BartWaardenburg Jul 17, 2026
1756ed6
perf(viz): cache the treemap layout between paint-only renders
BartWaardenburg Jul 17, 2026
27bb3f5
fix(viz): texture channel covers every finding lens in both views
BartWaardenburg Jul 17, 2026
7b848b4
fix(viz): focus management for help dialog and detail panel
BartWaardenburg Jul 17, 2026
a31b629
fix(viz): real tab semantics for the lens bar
BartWaardenburg Jul 17, 2026
1710167
perf(viz): spatial grid for pointer hit-testing
BartWaardenburg Jul 17, 2026
eb29e9a
ci: gate viz-frontend build, tests, and bundle drift
BartWaardenburg Jul 17, 2026
89e4d18
fix(cli): honor --out for viz text formats and escape Mermaid brackets
BartWaardenburg Jul 17, 2026
b98edcc
fix(viz): left-align ranked rows, fill the panel, highlight clone pre…
BartWaardenburg Jul 17, 2026
5370dd6
fix(viz): ranked findings list fills the panel instead of an internal…
BartWaardenburg Jul 18, 2026
04be898
feat(viz): transitive blast radius on a selected file
BartWaardenburg Jul 18, 2026
06aa37f
feat(viz): most-depended-on files panel for the overview lens
BartWaardenburg Jul 18, 2026
6cb0969
feat(viz): copy a whole-map markdown digest
BartWaardenburg Jul 18, 2026
c6452a2
feat(viz): multi-file blast view from search
BartWaardenburg Jul 18, 2026
ed8930a
fix(viz): surface clone-group truncation flag in the map view
BartWaardenburg Jul 18, 2026
be79333
fix(extract): bump CACHE_VERSION for object-binding depth cap
BartWaardenburg Jul 18, 2026
4628dba
fix(viz): resolve review nits in map chrome and ego rendering
BartWaardenburg Jul 18, 2026
7f1c525
fix(viz): plain-language copy, a11y, and visual-consistency pass
BartWaardenburg Jul 18, 2026
1b43777
fix(viz): motion continuity pass
BartWaardenburg Jul 18, 2026
9f83094
fix(viz): teach the treemap drill gesture at the root
BartWaardenburg Jul 18, 2026
58bc65a
refactor(viz): quality pass from fallow's own self-analysis
BartWaardenburg Jul 18, 2026
a59a48d
refactor(viz): bring viz-frontend under oxlint and conform to its rules
BartWaardenburg Jul 18, 2026
fd98612
style(viz): format viz-frontend with oxfmt and add it to fmt:js
BartWaardenburg Jul 18, 2026
bc7bff8
refactor(viz): extract pure helpers from the label draw functions
BartWaardenburg Jul 19, 2026
81e837f
refactor(viz): split render.ts into scene, annotations, and minimap
BartWaardenburg Jul 19, 2026
01f4c8e
feat(viz): cluster-label road highlight, smarter grouping, and layout…
BartWaardenburg Jul 20, 2026
0e1f365
fix(viz): mark cross-cutting import communities as "mixed"
BartWaardenburg Jul 20, 2026
6be3b0f
fix(viz): make the whole length of a road clickable
BartWaardenburg Jul 20, 2026
b4067d7
fix(viz): let road clicks through the d3-zoom gesture filter
BartWaardenburg Jul 20, 2026
05ef148
fix(viz): exclude minimap, standalone chip, and treemap from d3-zoom
BartWaardenburg Jul 20, 2026
749893d
fix(viz): drag to pan from anywhere, select on click
BartWaardenburg Jul 20, 2026
050d19b
fix(viz): explain terms in tooltips, drop interpunct from copy
BartWaardenburg Jul 20, 2026
8c56475
feat(viz): search-results sidebar with blast-radius panel
BartWaardenburg Jul 20, 2026
1ba742d
feat(viz): inline brand mark and typography-token legibility pass
BartWaardenburg Jul 20, 2026
2c15b37
chore(viz): rebuild embedded bundle for search panel and typography
BartWaardenburg Jul 20, 2026
6ce2b90
fix(viz): keep the search box from shifting as the match count grows
BartWaardenburg Jul 20, 2026
e5c65de
refactor(viz): collapse the lens sub-header to a single title
BartWaardenburg Jul 20, 2026
51b9703
fix(viz): ellipsis-truncate long filenames in the ranked list
BartWaardenburg Jul 20, 2026
db786d8
feat(viz): show the complexity hotspots as an aligned table
BartWaardenburg Jul 20, 2026
61055b0
feat(viz): render every sidebar list as an aligned table with instant…
BartWaardenburg Jul 20, 2026
c78a059
feat(viz): float the arrange toggle over the map, drop the axis labels
BartWaardenburg Jul 20, 2026
259fd38
feat(viz): copyable command hints, tabular clone/road lists, quieter …
BartWaardenburg Jul 20, 2026
aef5671
chore(viz): rebuild bundle for the map and panel polish
BartWaardenburg Jul 20, 2026
0099b9e
feat(viz): match the arrange toggle to the segmented control, tabular…
BartWaardenburg Jul 20, 2026
0a55e24
feat(viz): clone preview shows a context window with the duplicated l…
BartWaardenburg Jul 20, 2026
2be61aa
fix(viz): let the clone code preview grow instead of scrolling on its…
BartWaardenburg Jul 20, 2026
6897da6
fix(viz): keep leading context in the clone preview for blocks larger…
BartWaardenburg Jul 20, 2026
4a108a5
fix(viz): highlighted context preview in the per-file duplication sec…
BartWaardenburg Jul 20, 2026
7612603
feat(viz): mark truncated clone previews with a trailing more-lines note
BartWaardenburg Jul 20, 2026
e61ce3d
chore(viz): drop dead CSS and give every variable a descriptive name
BartWaardenburg Jul 20, 2026
dadbd5b
feat(viz): list every named function per file in the map payload
BartWaardenburg Jul 20, 2026
b3a751f
feat(viz): numbered sidebar tables, reused meters, and a 3-level type…
BartWaardenburg Jul 20, 2026
83e2732
feat(viz): floating tooltips, ego-view arrange fix, sentence-case
BartWaardenburg Jul 20, 2026
03c0469
feat(viz): map-style per-lens legends and an 8-color colorblind-safe …
BartWaardenburg Jul 20, 2026
fe8e180
chore(viz): rebuild bundle
BartWaardenburg Jul 20, 2026
5fc52cb
refactor(viz): keep the frontend fallow-clean
BartWaardenburg Jul 20, 2026
3a52bc4
chore(viz): rebuild bundle
BartWaardenburg Jul 20, 2026
a9e88d9
feat(viz): show importer counts on imported-by and imports lists
BartWaardenburg Jul 20, 2026
a44563b
fix(viz): stop ego breadcrumb overlapping the back-to-map chip
BartWaardenburg Jul 20, 2026
c271820
chore(viz): rebuild bundle
BartWaardenburg Jul 20, 2026
bb8c172
refactor(viz): remove Copy map and PNG export from the header
BartWaardenburg Jul 21, 2026
2777ef5
fix(viz): keep treemap tiles clear of the always-open panel
BartWaardenburg Jul 21, 2026
701a6bc
feat(viz): theme-aware, keyboard-only focus rings
BartWaardenburg Jul 21, 2026
4547f10
chore(viz): rebuild bundle
BartWaardenburg Jul 21, 2026
9215266
fix(viz): drop the PNG row from the help overlay
BartWaardenburg Jul 21, 2026
19118dc
fix(viz): sit stage content flush against the panel via one shared width
BartWaardenburg Jul 21, 2026
a1cd2bc
chore(viz): rebuild bundle
BartWaardenburg Jul 21, 2026
112efb7
feat(viz): corner copy-path icon and aligned lens-tab labels
BartWaardenburg Jul 21, 2026
09d6ac7
feat(viz): unify close and copy buttons as one square icon-btn family
BartWaardenburg Jul 21, 2026
0c4ccfc
chore(viz): rebuild bundle
BartWaardenburg Jul 21, 2026
41be14e
style(viz): neutral focus ring to match the chrome, not info-blue
BartWaardenburg Jul 21, 2026
b0dc8db
chore(viz): rebuild bundle
BartWaardenburg Jul 21, 2026
f30d3fb
fix(viz): neutral row focus, visible treemap edge, drop footer summary
BartWaardenburg Jul 21, 2026
b4ef84d
chore(viz): rebuild bundle
BartWaardenburg Jul 21, 2026
7830623
fix(viz): keep both graph and treemap content clear of the panel
BartWaardenburg Jul 21, 2026
a2a130c
chore(viz): rebuild bundle
BartWaardenburg Jul 21, 2026
ae6b6a9
fix(viz): canvas focus ring no longer runs under the panel
BartWaardenburg Jul 21, 2026
b893ff4
chore(viz): rebuild bundle
BartWaardenburg Jul 21, 2026
0f9dccc
fix(viz): canvas focus ring flush to the panel edge
BartWaardenburg Jul 21, 2026
347af74
feat(viz): set the fallow mark as the page favicon
BartWaardenburg Jul 21, 2026
3108b73
fix(viz): shrink panel clearance so the map edge reads flush, not gapped
BartWaardenburg Jul 21, 2026
45989f0
refactor(viz): move the dead-code reason to a hover tooltip
BartWaardenburg Jul 21, 2026
5a30486
refactor(viz): move panel ranking glosses onto hover tooltips
BartWaardenburg Jul 21, 2026
5d77245
Merge origin/main into feat/interactive-codebase-visualization
BartWaardenburg Jul 21, 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 change: 1 addition & 0 deletions .claude/rules/cli-crate.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Key modules:
- `migrate/` , Config migration from knip/jscpd/stylelint
- `init.rs` , Generate config files (`.fallowrc.json` or `fallow.toml`), scaffold starter agent guides (`--agents`), scaffold pre-commit git hooks (`--hooks`), record a deliberate stay-unconfigured decision (`--decline`, conflicts with the scaffolding flags; persists `onboarding_declined` in the impact store via `impact::decline_onboarding`, writes NO config file, suppresses the first-contact `setup` next-step + human `Setup:` hint)
- `list.rs` , Show active plugins, entry points, files, boundary zones/rules (`--boundaries`)
- `viz.rs` , `viz` command: one engine-owned project analysis (`AnalysisSession::analyze_project_with_artifacts`, complexity + graph retained) handed to `fallow_engine::viz::build_viz_data`, then emitted as a self-contained interactive HTML map (treemap + force graph with dead-code / duplication / boundaries / complexity lenses) or `--viz-format dot`/`mermaid` text. The HTML embeds the built TS frontend from `viz-frontend/` (rolldown bundle at `crates/cli/viz-assets/viz.js` + `viz.css`, `include_str!`'d; rebuild via `cd viz-frontend && npm ci && npm run build`). Read-only, telemetry `Workflow::ProjectInventory`.
- `plugin_check.rs` , `fallow plugin-check` read-only dry-run for external-plugin (manifestEntries) authoring. Loads config via `fallow_engine::project_config::config_for_project`, computes activation per plugin (`is_external_plugin_active`, empty discovered set + FS-fallback, no source walk), and for active plugins emits the shared `RuleReport` (`fallow_engine::plugins::check_manifest_entries`) as bespoke JSON with per-seed `path_exists` + typed `warnings[]`. Exit 0 always. See `.claude/rules/plugins.md` for the full loop.
- `schema.rs` , `schema`, `config-schema`, `plugin-schema`, `rule-pack-schema` commands (the schema printers live in `init.rs`; `rule-pack-schema` prints `RulePackDef::json_schema()`). `fallow schema` is the agent capability manifest: `manifest_version` "1", clap-derived commands/global_flags, `issue_types` derived from the explain rule slices (one row per rule, all analyses; per-id metadata table for filter_flag/fixable/suppress_comment/note/license; drift tests pin completeness vs the rule slices, IssueKind coverage, suppress-token round-trip through `IssueKind::parse`, and filter-flag existence on live clap), an `mcp_tools` block from `fallow_types::mcp_manifest`, a live-derived `plugins` block, a `task_matrix` block (the agent task-to-command cheat sheet from `crate::task_matrix::TASK_MATRIX`, the same rows as `init --agents`, the `hooks install --target agent` managed block, root `--help`, and the generated SKILL.md section; drift tests parse every row's probe through live clap and pin the no-mutating-commands contract), and user-facing-only env vars (internal plumbing excluded via a guard test). Always JSON; ignores `--format`.
- `security.rs` - opt-in `fallow security` command surfacing local security CANDIDATES (not verified vulnerabilities). MVP rule `client-server-leak` (detector in `crates/core/src/analyze/security/mod.rs`). `run()` loads config via `load_config_for_analysis`, forces `rules.security_client_server_leak` from `off` to `warn` (respecting an explicit user `error`), runs `fallow_core::analyze`, reads `results.security_findings` + `results.security_unresolved_edge_files` + unresolved-callee diagnostics, applies `--workspace` / `--changed-since` / `--file` retains, relativizes finding + trace paths, and renders `SecurityOutput` (human / JSON / SARIF). `--file` still builds the full project graph, then scopes candidates to matching finding anchors or trace hops and scopes unresolved-callee diagnostics by path. SARIF is hand-built at `level: note` with `partialFingerprints` (FNV) and no CWE; trace hops become `relatedLocations`. Exits 0 unless the user set the rule to `error` AND findings exist. `SecurityOutput`/`SecuritySchemaVersion` are a `FallowOutput::Security` variant registered in `schema_emit.rs`; `--format json` is in the published schema + TS contracts. Findings and unresolved-callee diagnostics are `#[serde(skip)]` on `AnalysisResults`, so they never appear under bare `fallow` or `audit`. **Agent-actionable candidate record (issue #900):** every `SecurityFinding` carries `candidate { source_kind, sink, boundary }` plus optional `taint_flow { source, sink, path }` and a top-level `finding_id`. Slot 1 `source_kind` is the stable catalogue source id, threaded through from `tainted_sink::sink_source` (it returns `(id, title)` now; the id was previously discarded after setting `source_backed`). The `sink` slot is self-contained (path/line/col/category/cwe/callee) and set by the detectors; the `boundary` slot (`client_server` from a `ClientBoundary` trace hop, `cross_module` from the reachability hop count, `architecture_zone` from the run's boundary-violation zone names) and `taint_flow` are filled by `rank::enrich_candidate`. `rank_security_findings` now takes a `boundary_crossings: FxHashMap<PathBuf, (from_zone, to_zone)>` map (built in `analyze/mod.rs`) instead of the old path set, so it can populate both `crosses_boundary` AND the zone slot. `taint_flow.path` is the compact `{ intra_module, cross_module_hops }` shape; the full ordered hops stay on `reachability.untrusted_source_trace`, never duplicated. There is deliberately NO `impact` wire field (agent-owned, documented in the schema doc comment). `finding_id` is the SARIF FNV fingerprint extracted to the shared `security_finding_id` helper, stamped in `run()` after relativization so it equals the SARIF `partialFingerprints` value; `relativize_finding` also relativizes the candidate/taint_flow paths. `export_visibility` and a package boundary are reserved follow-ups (absent, not always-false). The current security schema version is V4 after metadata V3 and unresolved-callee diagnostic samples in issue #1134. **`secret-to-network` exfil category (issue #890):** an INCLUDE-REQUIRED catalogue category (CWE-201) admitted only via `security.categories.include` (gated by `tainted_sink::is_include_required_category` calling `CategoryFilter::explicitly_admits`, like `hardcoded-secret`). A non-public `process.env` / `import.meta.env` secret reaching a network sink's body/options arg via same-identifier source-backing. Three load-bearing pieces: (1) the new `requires_source_kinds: Vec<String>` matcher field narrows `requires_source` to specific source ids; `matcher_admits_sink` now takes the matched source as `(id, title)` (the call site stopped discarding the id) and gates on `requires_source_kinds` containing the id. (2) The shared `is_public_env_var` / `is_public_env_path` predicate moved to `fallow_types::extract`; the extract layer skips public env vars in `tainted_source_path` AND `collect_source_paths_into` (the latter must return BEFORE recursing into the bare `process.env` object, or the object prefix re-introduces the source, the bug that broke the #876 public-env regression). `flatten_member_path` gained a `MetaProperty` arm so `import.meta.env.X` is modeled as a source. (3) `SinkSite.url_arg_literal` captures the network call's arg-0 URL literal (`call_url_arg_literal`); for `secret-to-network` findings, `tainted_sink` builds `candidate.network: SecurityNetworkContext { destination }` (the literal host, or `None` for a dynamic destination, the suspicious case). `CACHE_VERSION` 135 -> 136. Hardcoded-secret-as-source and a provider-match heuristic are reserved follow-ups. **`--gate <mode>` (issue #886, valued flag, `new` and `newly-reachable`; no `all` mode by design):** an opt-in regression gate that reports ONLY candidates introduced on a CHANGED LINE and exits **8** if any exist, so a PR gates on new exposure without gating on the backlog. Requires a diff source (`--changed-since`, `--diff-file`, or `--diff-stdin`); a diff it cannot compute is a LOUD exit 2 (via `changed_files::try_get_changed_diff`, which returns `Err` on git failure rather than `get_changed_files`' silent `None`), NEVER a green gate. The gate filter is the STRICT `check::filtering::retain_gate_new` (a SEPARATE pass from the advisory `filter_results_by_diff`, leaving advisory display untouched): keep a new sink anchor on an added line OR a `UntrustedSource`/`Sink` trace hop on an added line; DROP the `SecretSource && touches_file` file-level exception and `Intermediate`/`ClientBoundary` pass-through hops (so editing a secret-reading file does not trip the gate). `--changed-since` for the gate builds a LINE-level diff (`git diff --relative --unified=0 ref...HEAD`); the existing `--changed-since` file-level filter is unrelated. Exit contract: **8** = new candidate (PURE: never "could not run"), and the gate SUPERSEDES the advisory `--fail-on-issues` exit-1 path in gate mode (composing would re-gate the backlog). The `gate` block on `SecurityOutput` (`mode`/`verdict`/`new_count`, snake_case, emitted on pass too so consumers distinguish "gate ran and passed" from "gate did not run") is additive on `SecuritySchemaVersion` V2; human prose says "REVIEW REQUIRED" (not FAIL) with the unverified disclaimer; SARIF keeps `level: note` and rides the gate as a `run.properties.fallowGate` property. Canonical pre-commit recipe: `git diff --cached --unified=0 | fallow security --gate new --diff-stdin` (staged content; `--changed-since` checks committed HEAD). `--gate newly-reachable` requires `--changed-since <ref>`, materializes the base tree through the shared base worktree module, and compares reachable keys (`security-reach:<rel_path>:<kind>:<category>`) for findings with `reachability.reachable_from_entry == true`. It runs before changed-file advisory narrowing so unchanged sink anchors that became reachable through changed imports are not hidden. Diff-only inputs (`--diff-file` or `--diff-stdin` without `--changed-since`) exit 2 because this mode needs the base tree, not just added lines. Base reachable snapshots are cached under `.fallow/cache/security-base-v<N>/` unless `--no-cache` is set.
Expand Down
30 changes: 29 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
self-analyze: ${{ steps.filter.outputs.self-analyze }}
skills: ${{ steps.filter.outputs.skills }}
action-current: ${{ steps.filter.outputs.action-current }}
viz: ${{ steps.filter.outputs.viz }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
Expand Down Expand Up @@ -169,6 +170,9 @@ jobs:
- '.github/actions/setup-rust/**'
- '.github/workflows/ci.yml'
- 'scripts/workflow-policy.test.mjs'
viz:
- 'viz-frontend/**'
- 'crates/cli/viz-assets/**'

docker:
name: Docker
Expand Down Expand Up @@ -848,10 +852,34 @@ jobs:
cargo +"$MIRI_TOOLCHAIN" miri test -p fallow-extract --lib --tests suppress::
cargo +"$MIRI_TOOLCHAIN" miri test -p fallow-extract --lib --tests visitor::helpers::

viz-frontend:
name: Viz frontend
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.viz == 'true'
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '22'
- run: npm ci
working-directory: viz-frontend
- run: npm run lint
working-directory: viz-frontend
- run: npm run test
working-directory: viz-frontend
- run: npm run build
working-directory: viz-frontend
- name: Bundle drift
run: git diff --exit-code crates/cli/viz-assets

ci-ok:
name: CI
if: always()
needs: [check, windows-rust, doc, typos, js-lint, npm-package, action-current, fallow-self-analyze, vscode, test-gitlab-ci, audit, deny, shear, zizmor, msrv, miri, skills-vendor]
needs: [check, windows-rust, doc, typos, js-lint, npm-package, action-current, fallow-self-analyze, vscode, test-gitlab-ci, audit, deny, shear, zizmor, msrv, miri, skills-vendor, viz-frontend]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
Expand Down
6 changes: 6 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@
"rules": {
"unicorn/prefer-node-protocol": "off"
}
},
{
"files": ["viz-frontend/**/*.ts"],
"rules": {
"unicorn/no-new-array": "off"
}
}
]
}
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **`fallow viz` renders your codebase as an interactive map.** A new command that runs one project analysis and writes a single self-contained HTML file (no server, no external assets) styled like the rest of fallow: a nested treemap of files sized by bytes, plus a force-directed import graph with directory and import-community clustering. Both views share four lenses that recolor the same map: dead code (unused files, unused exports, entry points), duplication (share of duplicated lines per file, with clone previews), boundaries (architecture zones from your `boundaries` config, with violating imports drawn in red), and complexity hotspots (per-function cyclomatic and cognitive scores, including React context such as hook counts and JSX depth). Clicking any file opens a detail panel with the evidence: unused export names, clone groups and their other locations, boundary crossings, cycle membership, importers and imports as click-through navigation, and a runnable `fallow ... --trace` command to verify each finding. Search, breadcrumb drill-down, keyboard shortcuts, shareable URL deep links, and dark/light themes are built in; findings carry a hatch texture and `[E]`/`[W]` prefixes so color is never the only signal, and all motion honors `prefers-reduced-motion`. The HTML opens in your browser by default (`--no-open` to skip, `--out <path>` to choose the file); `--viz-format dot` and `--viz-format mermaid` emit the import graph as text for piping into other tools. Read-only, and respects `--production`, `--config`, and `--no-cache` like the analysis commands.

- **VS Code can set a changed-code baseline at HEAD in one command.** The new
`Fallow: Set Baseline at HEAD` palette command creates the local lightweight
`fallow-baseline` tag, writes `fallow.changedSince` to a single-folder
Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ crates/
api/ -- Programmatic API boundary for JS/native callers
napi/ -- napi-rs native Node addon (cdylib, #[napi] bindings) behind the @fallow/node package
cli/ -- CLI binary, split into per-command modules
audit.rs, check/, dupes.rs, health/, watch.rs, fix/, init.rs, list.rs, schema.rs, regression/, impact.rs, security.rs
audit.rs, check/, dupes.rs, health/, watch.rs, fix/, init.rs, list.rs, schema.rs, regression/, impact.rs, security.rs, viz.rs
license/ -- `fallow license {activate, status, refresh, deactivate}` with offline JWT verify plus live trial / refresh flows
coverage/ -- `fallow coverage setup` resumable first-run state machine for runtime coverage
report/ -- Output formatting (mod.rs dispatch, human/, json.rs, sarif.rs, compact.rs, markdown.rs)
Expand All @@ -35,6 +35,7 @@ crates/
multicall/ -- Packaged `fallow` binary bundling the CLI, LSP, and MCP servers into one engine (renamed to `fallow` at packaging time for npm platform packages and VS Code); publish = false, so `cargo install fallow-cli` stays the pure CLI
editors/
vscode/ -- VS Code extension (LSP client, tree views, status bar, auto-download)
viz-frontend/ -- TS source (rolldown) for the `fallow viz` interactive HTML; bundles to crates/cli/viz-assets/
npm/
fallow/ -- npm wrapper package with optionalDependencies pattern
action/ -- GitHub Action (composite)
Expand Down
48 changes: 39 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "io-st
# File watching
notify = "8"

# Browser
open = "5"

# Testing
criterion2 = { version = "3.0.3", default-features = false }
dhat = "0.3"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ Adopting on an existing codebase? `fallow audit` fails only on findings a change
| [`npx fallow recommend`](https://docs.fallow.tools/cli/recommend) | Detect the stack and propose a config; subjective choices stay open questions |
| [`npx fallow init`](https://docs.fallow.tools/cli/init) | Scaffold config; `--agents` scaffolds an AGENTS.md |
| `npx fallow migrate` | Migrate from knip, jscpd, or stylelint config |
| `npx fallow viz` | Interactive HTML map of the codebase: treemap + import graph with dead-code, duplication, boundaries, and hotspot lenses |
| `npx fallow schema` | Machine-readable capability manifest (always JSON) |

<details>
Expand Down
Loading
Loading