From adac4dba741e527067c90b119fb05ef9f033e320 Mon Sep 17 00:00:00 2001 From: Long Ho Date: Fri, 29 May 2026 08:45:45 -0400 Subject: [PATCH] docs: expand query examples --- ARCHITECTURE.md | 32 +++++ CONTRIBUTING.md | 11 +- README.md | 86 +++++++++++- docs/src/render.ts | 252 ++++++++++++++++++++++++++++++++++ docs/src/site.css | 20 +++ packages/codescythe/README.md | 13 +- 6 files changed, 403 insertions(+), 11 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 08406f5..5fb0853 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -313,6 +313,38 @@ project unresolved issues. Missing local paths, root-like paths, package imports starting with `#`, and common alias forms such as `@/` and `~/` are reported as unresolved. +## Dependency Query Graph + +The `query` command builds a graph from the same parsed file metadata and module +resolver used by normal analysis. Query nodes are either project files or +exported symbols written as `:`. Edges preserve the source shape +that created the dependency: named imports, side-effect imports, string-literal +dynamic imports, `import.meta.glob`, re-exports, namespace exports, namespace +member reads, and export-definition edges from symbols back to their declaring +files. + +Selectors are resolved after graph construction: + +- File selectors match one project file node. +- Directory selectors match every project file node under that directory. +- Export selectors match one exported-symbol node. + +`somepath` and `somepaths` run breadth-first search from the resolved source +nodes. They keep one visited bit and one parent edge per node, so dependency +cycles cannot create infinite walks. `somepath` stops after the first matched +target. `somepaths` keeps traversing and reconstructs one shortest path for each +reachable matched target. + +`allpaths` intentionally does not enumerate every simple path. It computes the +set of nodes reachable forward from the sources, the set of nodes that can reach +the targets by reverse traversal, intersects those sets, and returns every edge +whose endpoints are both in that intersection. This returns the finite path +subgraph even when the dependency graph contains cycles. + +Query output supports text, JSON, Mermaid, and SVG. Mermaid is the diagram IR +(`flowchart LR`), and SVG is rendered from that Mermaid source with +`mermaid-rs-renderer`. + ## Reachability Algorithm `analyze_path` seeds three mutable collections: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 43d5c02..c17d6c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,15 +50,20 @@ The public Rust API is intentionally narrow: - `codescythe::run_and_fix(cwd, config_path)` applies supported removals and returns a fix report. - `codescythe::doctor(cwd, config_path)` returns config-risk diagnostics. +- `codescythe::query(cwd, config_path, request)` returns dependency paths or a + path subgraph for `somepath`, `somepaths`, and `allpaths`. +- `codescythe::render_query_mermaid(result)` and + `codescythe::render_query_svg(result)` render query results as diagrams. The core crate has no npm or CLI concerns. That keeps conformance tests and future analysis work centered on one library boundary. ### Runtime Adapters -`crates/codescythe_cli` is a thin `clap` wrapper around the core crate. It -supports text and JSON output, exits with `1` when issues are found, and exits -with `2` for runtime/config errors. +`crates/codescythe_cli` is a thin `clap` wrapper around the core crate. Normal +analysis supports text and JSON output, exits with `1` when issues are found, +and exits with `2` for runtime/config errors. The `query` subcommand supports +text, JSON, Mermaid, and SVG output for dependency-path inspection. `crates/codescythe_napi` exposes the same core behavior to Node through N-API: `analyze`, `fix`, and `doctor` all return JSON strings from Rust, while the diff --git a/README.md b/README.md index 420490e..4833dc6 100644 --- a/README.md +++ b/README.md @@ -146,13 +146,85 @@ codescythe query allpaths src/main.ts src/runtime.ts:initRuntime --output svg > ``` Selectors can point at files, directories, or exported symbols written as -`:`. `somepath` returns one shortest path, `somepaths` returns one -shortest path per reachable matched target, and `allpaths` returns the subgraph -of every node and edge that lies on a path from the source selector to the target -selector. JSON output includes stable file/export nodes and typed import or -re-export edges. Mermaid output renders the same query graph as a `flowchart LR` -diagram, and SVG output renders that Mermaid source with the pure-Rust -`mermaid-rs-renderer` crate. +`:`. Relative selectors are resolved from the analysis root chosen +by `-C` or `--config`. + +- `somepath` returns one shortest path from the source selector to any matched + target. +- `somepaths` returns one shortest path per reachable matched target, which is + useful for file-to-folder queries. +- `allpaths` returns the subgraph of every node and edge that lies on a path + from the source selector to the target selector. + +Text output is optimized for terminal inspection. JSON output includes stable +file/export nodes and typed import or re-export edges. Mermaid output renders +the same query graph as a `flowchart LR` diagram, and SVG output renders that +Mermaid source with the pure-Rust `mermaid-rs-renderer` crate. + +`somepath` and `somepaths` use breadth-first search with visited nodes, while +`allpaths` intersects forward reachability from the source with reverse +reachability from the target. That makes dependency cycles finite without +enumerating every possible walk through the graph. + +Fixture-backed Mermaid examples: + +```sh +codescythe query somepath -C tests/fixtures/test-file-usage --output mermaid src/main.ts src/module.ts:used +``` + +```mermaid +flowchart LR + n0["src/module.ts:used"] + n1["src/main.ts"] + n1 -->|"named import ./module:used"| n0 +``` + +```sh +codescythe query somepaths -C tests/fixtures/oxc-resolution --output mermaid app/index.ts app/ +``` + +```mermaid +flowchart LR + n0["app/aliased.ts:aliased"] + n1["app/extension.ts:extension"] + n2["app/internal.ts:internal"] + n3["app/aliased.ts"] + n4["app/extension.ts"] + n5["app/index.ts"] + n6["app/internal.ts"] + n0 -->|"defined in file aliased"| n3 + n1 -->|"defined in file extension"| n4 + n2 -->|"defined in file internal"| n6 + n5 -->|"named import @/aliased:aliased"| n0 + n5 -->|"named import ./extension.js:extension"| n1 + n5 -->|"named import #internal:internal"| n2 +``` + +```sh +codescythe query allpaths -C tests/fixtures/knip-export-basics --output mermaid index.ts my-namespace.ts:y +``` + +```mermaid +flowchart LR + n0["index.ts"] + n1["my-module.ts"] + n2["my-module.ts:myExport"] + n3["my-namespace.ts:y"] + n2 -->|"defined in file myExport"| n1 + n0 -->|"named import ./my-module.js:myExport"| n2 + n1 -->|"namespace member ./my-namespace.js:y"| n3 +``` + +```sh +codescythe query somepath -C tests/fixtures/runfiles-fixture --output mermaid workspace/frontend/apps/client/platform/platformRuntime.ts protobuf/generated/client.ts:client +``` + +```mermaid +flowchart LR + n0["protobuf/generated/client.ts:client"] + n1["workspace/frontend/apps/client/platform/platformRuntime.ts"] + n1 -->|"named import #bazel_generated/client:client"| n0 +``` ## Contributing diff --git a/docs/src/render.ts b/docs/src/render.ts index c98a5af..040d3f1 100644 --- a/docs/src/render.ts +++ b/docs/src/render.ts @@ -91,6 +91,11 @@ const homeCards: DocLink[] = [ title: 'Features', description: 'Review the analyzer, fix mode, verbose JSON, export explanations, and doctor diagnostics.', }, + { + href: './queries/', + title: 'Dependency Queries', + description: 'Trace somepath, somepaths, and allpaths results with text, JSON, Mermaid, or SVG output.', + }, { href: './reports/', title: 'Reports', @@ -129,6 +134,27 @@ function Callout({ title, children }: { title: string; children: React.ReactNode ); } +function QueryFixtureSample({ + command, + description, + mermaid, + title, +}: { + command: string; + description: string; + mermaid: string; + title: string; +}) { + return h( + 'article', + { className: 'query-sample' }, + h('h3', null, title), + h('p', null, description), + h(CodeBlock, null, command), + h(CodeBlock, { language: 'mermaid' }, mermaid), + ); +} + function FieldTable({ rows, }: { @@ -630,6 +656,7 @@ const pages: Page[] = [ description: 'Review the analyzer, fix mode, verbose diagnostics, export explanations, and doctor checks.', sections: [ { id: 'graph', title: 'Import Graph' }, + { id: 'query', title: 'Dependency Queries' }, { id: 'unused', title: 'Unused Files and Exports' }, { id: 'fix', title: 'Fix Mode' }, { id: 'explain', title: 'Explanations' }, @@ -643,6 +670,23 @@ const pages: Page[] = [ { id: 'graph', title: 'Import Graph' }, h('p', null, 'Codescythe follows static imports, re-exports, string-literal dynamic imports, destructured CommonJS requires, and supported ', h('code', null, 'import.meta.glob'), ' patterns.'), ), + h( + PageSection, + { id: 'query', title: 'Dependency Queries' }, + h( + 'p', + null, + 'Use ', + h('code', null, 'query somepath'), + ', ', + h('code', null, 'query somepaths'), + ', and ', + h('code', null, 'query allpaths'), + ' to inspect dependency routes through that graph. Queries can target files, folders, or exported symbols and can render text, JSON, Mermaid, or SVG output. The ', + h(InlineLink, { href: '../queries/' }, 'dependency query guide'), + ' includes fixture-backed Mermaid samples.', + ), + ), h( PageSection, { id: 'unused', title: 'Unused Files and Exports' }, @@ -685,6 +729,176 @@ const pages: Page[] = [ ), ), }, + { + slug: 'queries', + title: 'Dependency Queries', + eyebrow: 'Path inspection', + description: 'Use somepath, somepaths, and allpaths to explain dependency paths through the same graph Codescythe analyzes.', + sections: [ + { id: 'commands', title: 'Commands' }, + { id: 'selectors', title: 'Selectors' }, + { id: 'formats', title: 'Output Formats' }, + { id: 'fixture-examples', title: 'Fixture Examples' }, + { id: 'cycles', title: 'Cycles' }, + ], + body: h( + React.Fragment, + null, + h( + PageSection, + { id: 'commands', title: 'Commands' }, + h( + 'p', + null, + 'The query command traces dependency paths between two selectors without changing analysis results or editing files. It uses the same parsed import, export, resolver, alias, dynamic import, and glob edges as normal analysis.', + ), + h(CodeBlock, null, `npx codescythe query somepath src/main.ts src/module.ts +npx codescythe query somepaths src/main.ts src/features/ +npx codescythe query allpaths src/main.ts src/runtime.ts:initRuntime`), + h(FieldTable, { + rows: [ + { + field: 'somepath', + purpose: 'One shortest path', + type: 'query verb', + values: 'source selector, target selector', + notes: 'Returns the first shortest dependency path found from any matched source node to any matched target node.', + }, + { + field: 'somepaths', + purpose: 'One path per target', + type: 'query verb', + values: 'source selector, target selector', + notes: 'Returns one shortest path for each reachable matched target, which is useful when the target selector is a directory.', + }, + { + field: 'allpaths', + purpose: 'Every path edge as a subgraph', + type: 'query verb', + values: 'source selector, target selector', + notes: 'Returns every node and edge that lies on at least one route from the source selector to the target selector.', + }, + ], + }), + ), + h( + PageSection, + { id: 'selectors', title: 'Selectors' }, + h('p', null, 'Selectors can point at files, directories, or exported symbols. Relative selectors are resolved from the analysis root selected by ', h('code', null, '-C'), ' or ', h('code', null, '--config'), '.'), + h( + 'ul', + null, + h('li', null, h('code', null, 'src/main.ts'), ' selects a project file.'), + h('li', null, h('code', null, 'src/features/'), ' selects every project file under a directory.'), + h('li', null, h('code', null, 'src/module.ts:used'), ' selects one exported symbol from a file.'), + ), + h( + Callout, + { title: 'Export selectors stay symbol-aware' }, + h('p', null, 'A named import path can stop at ', h('code', null, 'file.ts:symbol'), ' before following the export-definition edge into the file node. This keeps file reachability and export usage distinguishable.'), + ), + ), + h( + PageSection, + { id: 'formats', title: 'Output Formats' }, + h('p', null, 'Text output is optimized for terminal inspection. JSON is the stable machine-readable surface. Mermaid and SVG render the same query graph as a diagram.'), + h(CodeBlock, null, `npx codescythe query allpaths src/main.ts src/runtime.ts:initRuntime --output text +npx codescythe query allpaths src/main.ts src/runtime.ts:initRuntime --json +npx codescythe query allpaths src/main.ts src/runtime.ts:initRuntime --output mermaid +npx codescythe query allpaths src/main.ts src/runtime.ts:initRuntime --output svg > graph.svg`), + h( + 'p', + null, + h('code', null, '--json'), + ' is a shortcut for ', + h('code', null, '--output json'), + '. SVG output is rendered from the Mermaid graph with ', + h('code', null, 'mermaid-rs-renderer'), + '.', + ), + ), + h( + PageSection, + { id: 'fixture-examples', title: 'Fixture Examples' }, + h( + 'p', + null, + 'These examples are generated from checked-in repository fixtures. The Mermaid snippets below are exact CLI output from ', + h('code', null, '--output mermaid'), + '.', + ), + h( + 'div', + { className: 'query-samples' }, + h(QueryFixtureSample, { + title: 'test-file-usage: somepath to one export', + description: 'A file-to-export query shows a named import edge directly to the exported symbol.', + command: 'codescythe query somepath -C tests/fixtures/test-file-usage --output mermaid src/main.ts src/module.ts:used', + mermaid: `flowchart LR + n0["src/module.ts:used"] + n1["src/main.ts"] + n1 -->|"named import ./module:used"| n0`, + }), + h(QueryFixtureSample, { + title: 'oxc-resolution: somepaths to a folder', + description: 'A file-to-directory query returns one shortest path for each reachable matched target file.', + command: 'codescythe query somepaths -C tests/fixtures/oxc-resolution --output mermaid app/index.ts app/', + mermaid: `flowchart LR + n0["app/aliased.ts:aliased"] + n1["app/extension.ts:extension"] + n2["app/internal.ts:internal"] + n3["app/aliased.ts"] + n4["app/extension.ts"] + n5["app/index.ts"] + n6["app/internal.ts"] + n0 -->|"defined in file aliased"| n3 + n1 -->|"defined in file extension"| n4 + n2 -->|"defined in file internal"| n6 + n5 -->|"named import @/aliased:aliased"| n0 + n5 -->|"named import ./extension.js:extension"| n1 + n5 -->|"named import #internal:internal"| n2`, + }), + h(QueryFixtureSample, { + title: 'knip-export-basics: allpaths through namespace use', + description: 'An allpaths query keeps every node and edge that can carry the source file to the target export.', + command: 'codescythe query allpaths -C tests/fixtures/knip-export-basics --output mermaid index.ts my-namespace.ts:y', + mermaid: `flowchart LR + n0["index.ts"] + n1["my-module.ts"] + n2["my-module.ts:myExport"] + n3["my-namespace.ts:y"] + n2 -->|"defined in file myExport"| n1 + n0 -->|"named import ./my-module.js:myExport"| n2 + n1 -->|"namespace member ./my-namespace.js:y"| n3`, + }), + h(QueryFixtureSample, { + title: 'runfiles-fixture: somepath through an alias', + description: 'Alias resolution is represented in the edge label, while the target still resolves to the project file export.', + command: 'codescythe query somepath -C tests/fixtures/runfiles-fixture --output mermaid workspace/frontend/apps/client/platform/platformRuntime.ts protobuf/generated/client.ts:client', + mermaid: `flowchart LR + n0["protobuf/generated/client.ts:client"] + n1["workspace/frontend/apps/client/platform/platformRuntime.ts"] + n1 -->|"named import #bazel_generated/client:client"| n0`, + }), + ), + ), + h( + PageSection, + { id: 'cycles', title: 'Cycles' }, + h( + 'p', + null, + 'Dependency cycles are finite in query output. ', + h('code', null, 'somepath'), + ' and ', + h('code', null, 'somepaths'), + ' run breadth-first search with visited nodes and parent edges, so they return shortest acyclic paths. ', + h('code', null, 'allpaths'), + ' does not enumerate paths; it intersects forward reachability from the source with reverse reachability from the target, then returns the induced path subgraph.', + ), + ), + ), + }, { slug: 'reports', title: 'Reports', @@ -696,6 +910,7 @@ const pages: Page[] = [ { id: 'doctor-warning-codes', title: 'Doctor Warning Codes' }, { id: 'unresolved-diagnostics', title: 'Unresolved Diagnostics' }, { id: 'explain-report', title: 'Explain Export Report' }, + { id: 'query-output', title: 'Query Output' }, { id: 'review-workflow', title: 'Review Workflow' }, ], body: h( @@ -849,6 +1064,43 @@ npx codescythe --json --explain-export src/constants.ts:oldFlag`), ], }), ), + h( + PageSection, + { id: 'query-output', title: 'Query Output' }, + h( + 'p', + null, + 'Query JSON includes the parsed selectors, matched source and target nodes, unresolved imports observed while building the graph, and either paths or a graph depending on the query kind.', + ), + h(CodeBlock, { language: 'json' }, `{ + "kind": "somepath", + "from": { "kind": "file", "path": "src/main.ts" }, + "to": { "kind": "export", "path": "src/module.ts", "symbol": "used" }, + "paths": [ + { + "nodes": [ + { "id": "file:src/main.ts", "kind": "file", "path": "src/main.ts" }, + { "id": "export:src/module.ts:used", "kind": "export", "path": "src/module.ts", "symbol": "used" } + ], + "edges": [ + { "kind": "namedImport", "specifier": "./module", "imported": "used" } + ] + } + ] +}`), + h( + 'p', + null, + h('code', null, 'allpaths'), + ' returns ', + h('code', null, 'graph.nodes'), + ' and ', + h('code', null, 'graph.edges'), + ' instead of ', + h('code', null, 'paths'), + '. Diagram formats render that same data as Mermaid or SVG.', + ), + ), h( PageSection, { id: 'review-workflow', title: 'Review Workflow' }, diff --git a/docs/src/site.css b/docs/src/site.css index 83b5769..6921fb4 100644 --- a/docs/src/site.css +++ b/docs/src/site.css @@ -605,6 +605,26 @@ li code, margin-bottom: 0; } +.query-samples { + display: grid; + gap: 1.2rem; +} + +.query-sample { + border: 1px solid var(--line); + border-radius: 8px; + padding: 1rem; + background: var(--paper-strong); +} + +.query-sample h3 { + margin-bottom: 0.55rem; +} + +.query-sample .code-block + .code-block { + margin-top: 0.75rem; +} + .steps { margin: 0; padding-left: 1.4rem; diff --git a/packages/codescythe/README.md b/packages/codescythe/README.md index 885eb3d..70faec9 100644 --- a/packages/codescythe/README.md +++ b/packages/codescythe/README.md @@ -13,10 +13,21 @@ The package entrypoints are TypeScript files and require Node's Documentation: https://perplexityai.github.io/codescythe/ -The public API exposes `analyze(options)`, `fix(options)`, and +The public JavaScript API exposes `analyze(options)`, `fix(options)`, and `doctor(options)`. Options mirror the Rust CLI: pass `cwd`, `config`, `verbose`, `force`, or `explainExport` as needed. Results are parsed JavaScript objects returned from the native binding's JSON output. Pass `verbose: true` to `analyze` or `fix`, or use `--verbose` with the package bin, to include the same analysis diagnostics exposed by the Rust CLI. + +The package bin also exposes dependency-path queries: + +```sh +npx codescythe query somepath src/main.ts src/module.ts +npx codescythe query somepaths src/main.ts src/features/ --output mermaid +npx codescythe query allpaths src/main.ts src/runtime.ts:initRuntime --output svg > graph.svg +``` + +Query selectors can target files, directories, or exported symbols written as +`:`. Query output supports text, JSON, Mermaid, and SVG.