Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,6 @@ vite.config.ts.timestamp-*

# Eval outputs (scorecard is regenerated every run; history is recorded deliberately)
eval/scorecard.json

# ui-lineage scanned graphs
*.graph.json
67 changes: 67 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# ui-lineage

**Map UI components to their data sources and user journeys** — trace any screenshot or ticket back to the code, APIs, state, and navigation behind it. Deterministic static analysis for React/TSX. No LLM, no network calls.

`ui-lineage` scans a React codebase into a **lineage graph** and lets you query it three ways:

- **match** — text seen on screen → the component(s) that render it
- **trace** — a component → every API, state slice, and event that feeds it (attributed *per instance*, so a shared `<DataTable>` on the Users page reports `/api/users` while the same component on Invoices reports `/api/invoices`)
- **journeys** — a page → the user-action paths leading out of it (click → navigate → click…), lazily expanded and cycle-safe

## Install

```bash
npm install -g ui-lineage # CLI
npm install ui-lineage # library
```

Requires Node ≥ 20.

## CLI

```bash
# 1. Scan a React app into a graph
ui-lineage scan ./src -o app.graph.json

# 2. Find a component from on-screen text
ui-lineage find "All invoices" -g app.graph.json

# 3. Trace a component (or an instance id) to its data
ui-lineage trace InvoicesPage -g app.graph.json

# 4. Walk the user journeys from a page or route
ui-lineage journeys /users -g app.graph.json
```

`journeys` output reads left-to-right, with `↩ cycle` where a list ⇄ detail loop closes:

```
▸ /users • onClick() → /users/:userId ▸ /users/:userId • onClick() → /users ▸ /users ↩ cycle
▸ /users • onClick() ⇢ fetch /api/users
```

## Library

```ts
import { scanReact, resolveHookEdges, journeys, traceLineage, matchComponentsByText } from "ui-lineage";

const graph = resolveHookEdges(scanReact({ root: "./src" }));

const match = matchComponentsByText(graph, ["All invoices"]);
const lineage = traceLineage(graph, match.candidates[0].value.component.id);
const paths = journeys(graph, "/users", { depth: 3 });
```

Every query returns a `QueryResult` envelope — ranked `candidates` with evidence and confidence, or an honest `ambiguous` / `declined`.

## What it understands

Endpoints (constants, templates, API wrappers, react-query/SWR), i18n text, cross-file instance trees and per-instance prop-flow, Redux/Zustand stores, portals/modals/toasts, React Router & Next.js routes, and action effects (navigate / fetch / dispatch / setState) mined from event handlers.

## Status

Early (v0.1). The matching engine, screenshot adapter, and MCP server are on the roadmap. Output is deterministic and language-agnostic (plain JSON graph), designed to feed AI agents as a context provider — not to be one.

## License

MIT
46 changes: 37 additions & 9 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,54 @@
{
"name": "@coderadar/cli",
"name": "ui-lineage",
"version": "0.1.0",
"description": "CodeRadar CLI — scan a React codebase into a lineage graph and query it.",
"description": "Map UI components to their data sources and user journeys — trace any screenshot back to the code, APIs, state, and navigation behind it. Deterministic static analysis for React/TSX.",
"license": "MIT",
"type": "module",
"bin": {
"coderadar": "dist/index.js"
"ui-lineage": "dist/index.js"
},
"main": "dist/lib.js",
"module": "dist/lib.js",
"types": "dist/lib.d.ts",
"exports": {
".": {
"types": "./dist/lib.d.ts",
"default": "./dist/lib.js"
}
},
"files": [
"dist"
"dist",
"README.md"
],
"keywords": [
"react",
"static-analysis",
"data-lineage",
"data-flow",
"ast",
"ts-morph",
"user-journeys",
"component-graph",
"cli"
],
"engines": {
"node": ">=20"
},
"scripts": {
"build": "tsc -p tsconfig.json",
"typecheck": "tsc -p tsconfig.json --noEmit"
"build": "tsup",
"typecheck": "tsc -p tsconfig.json --noEmit",
"prepublishOnly": "pnpm build"
},
"dependencies": {
"@coderadar/core": "workspace:*",
"@coderadar/parser-react": "workspace:*",
"commander": "^13.0.0"
"commander": "^13.0.0",
"ts-morph": "^24.0.0",
"yaml": "^2.9.0"
},
"devDependencies": {
"@coderadar/core": "workspace:*",
"@coderadar/parser-react": "workspace:*",
"@types/node": "^22.20.1",
"tsup": "^8.5.1",
"typescript": "^5.7.0"
}
}
14 changes: 7 additions & 7 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ import { Command } from "commander";
const program = new Command();

program
.name("coderadar")
.name("ui-lineage")
.description(
"Map UI components to their data sources — trace any screenshot back to the code and data behind it.",
"Map UI components to their data sources and user journeys — trace any screenshot back to the code, APIs, state, and navigation behind it.",
)
.version("0.1.0");

program
.command("scan")
.description("Scan a React codebase and emit a lineage graph JSON")
.argument("<dir>", "directory to scan")
.option("-o, --out <file>", "output file", "coderadar.graph.json")
.option("-o, --out <file>", "output file", "ui-lineage.graph.json")
.action((dir: string, opts: { out: string }) => {
const meta = collectGraphMeta(path.resolve(dir));
const graph = { ...resolveHookEdges(scanReact({ root: dir })), meta };
Expand All @@ -56,7 +56,7 @@ program
.command("find")
.description("Find components by text visible on screen (e.g. read off a screenshot)")
.argument("<terms...>", "text fragments seen in the UI")
.option("-g, --graph <file>", "graph file", "coderadar.graph.json")
.option("-g, --graph <file>", "graph file", "ui-lineage.graph.json")
.action((terms: string[], opts: { graph: string }) => {
const graph = loadGraph(opts.graph);
const result = matchComponentsByText(graph, terms);
Expand All @@ -76,7 +76,7 @@ program
.command("trace")
.description("Trace a component to every data source, state, and event that feeds it")
.argument("<component>", "component name, definition id, or instance id")
.option("-g, --graph <file>", "graph file", "coderadar.graph.json")
.option("-g, --graph <file>", "graph file", "ui-lineage.graph.json")
.action((component: string, opts: { graph: string }) => {
const graph = loadGraph(opts.graph);
const node =
Expand Down Expand Up @@ -127,7 +127,7 @@ program
.command("journeys")
.description("Trace user-journey paths from a page or component (click → navigate → click…)")
.argument("<start>", "route path (/users/:id), component name, or instance id")
.option("-g, --graph <file>", "graph file", "coderadar.graph.json")
.option("-g, --graph <file>", "graph file", "ui-lineage.graph.json")
.option("-d, --depth <n>", "max navigation levels per path", "3")
.action((start: string, opts: { graph: string; depth: string }) => {
const graph = loadGraph(opts.graph);
Expand Down Expand Up @@ -181,7 +181,7 @@ function printMatchCandidate(candidate: Candidate<ComponentMatch>): void {

function loadGraph(file: string): LineageGraph {
if (!fs.existsSync(file)) {
console.error(`Graph file not found: ${file} — run \`coderadar scan <dir>\` first.`);
console.error(`Graph file not found: ${file} — run \`ui-lineage scan <dir>\` first.`);
process.exit(1);
}
try {
Expand Down
11 changes: 11 additions & 0 deletions packages/cli/src/lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* ui-lineage — public library API.
*
* One import gives you the whole toolkit: the React/TSX scanner plus the graph
* query layer (match, per-instance lineage, journeys). The internal monorepo
* packages are bundled in at build time, so consumers depend only on `ui-lineage`.
*
* import { scanReact, resolveHookEdges, journeys, traceLineage } from "ui-lineage";
*/
export * from "@coderadar/core";
export { resolveHookEdges, scanReact, type ScanOptions } from "@coderadar/parser-react";
20 changes: 20 additions & 0 deletions packages/cli/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { defineConfig } from "tsup";

// ui-lineage ships as a single self-contained package: the internal @coderadar/*
// workspace packages are bundled into the output, while the heavy third-party
// deps (ts-morph, yaml, commander) stay external and install normally.
export default defineConfig({
entry: {
index: "src/index.ts", // CLI bin (keeps its #!/usr/bin/env node shebang)
lib: "src/lib.ts", // library entry
},
format: ["esm"],
target: "node20",
// Inline the workspace packages' TYPES too, so the published .d.ts has no
// dangling references to the unpublished @coderadar/* internals.
dts: { resolve: true },
clean: true,
sourcemap: true,
noExternal: [/^@coderadar\//],
external: ["ts-morph", "yaml", "commander"],
});
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"name": "@coderadar/core",
"version": "0.1.0",
"description": "CodeRadar lineage graph schema — the language-agnostic contract every parser emits and every agent consumes.",
"private": true,
"description": "CodeRadar lineage graph schema — the language-agnostic contract every parser emits and every agent consumes. Bundled into the published `ui-lineage` package.",
"license": "MIT",
"type": "module",
"main": "dist/index.js",
Expand Down
3 changes: 2 additions & 1 deletion packages/parser-react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"name": "@coderadar/parser-react",
"version": "0.1.0",
"description": "React/TSX parser for CodeRadar — extracts components, hooks, data sources, state, and events into a lineage graph.",
"private": true,
"description": "React/TSX parser for CodeRadar — extracts components, hooks, data sources, state, and events into a lineage graph. Bundled into the published `ui-lineage` package.",
"license": "MIT",
"type": "module",
"main": "dist/index.js",
Expand Down
Loading
Loading