Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
dc209a6
Map React Router frontend features
moritzscheele May 16, 2026
752643b
Map FastAPI route features
moritzscheele May 16, 2026
8ceeaf4
feat(revalidate): add progress output
twidtwid May 16, 2026
774c98c
fix: share node script commands for react mapping
steipete May 16, 2026
f69996c
fix: handle common router mapping layouts
steipete May 16, 2026
e3bd7a0
fix: tighten frontend and fastapi mapping context
steipete May 16, 2026
7ed387f
fix: preserve nested route prefixes in mappers
steipete May 16, 2026
0b766fd
fix: cover index and aliased nested routes
steipete May 16, 2026
5267247
fix: harden route mapper edge cases
steipete May 16, 2026
2a5a1a9
fix: tighten route mapper receiver detection
steipete May 16, 2026
6682ccc
fix: scope fastapi router prefixes
steipete May 16, 2026
18a2d70
fix: handle wrapped and commented routes
steipete May 16, 2026
1a53279
fix: cover workspace and multiline route parsing
steipete May 16, 2026
7a5e4dd
fix: handle typed receivers and peer deps
steipete May 16, 2026
80d9470
fix: avoid stale route mapper records
steipete May 16, 2026
015dae9
fix: harden route mapper parsing
steipete May 16, 2026
7a3d0c3
fix: scope route mapper discovery
steipete May 16, 2026
177d19c
fix: ignore commented fastapi assignments
steipete May 16, 2026
d1f00f5
fix: resolve dotted route components
steipete May 16, 2026
04a8b83
fix: harden route mapper edge cases
steipete May 16, 2026
f56d2c8
fix: support repeated route mounts
steipete May 16, 2026
ec55477
fix: preserve aliased fastapi routers
steipete May 16, 2026
b7c78df
fix: tighten route prefix mapping
steipete May 16, 2026
4ebc1cc
fix: harden route mapper review gaps
steipete May 16, 2026
24bc0a0
fix: cover route mapper edge cases
steipete May 16, 2026
88f1413
fix: tighten route mapper ownership
steipete May 16, 2026
e2a4a45
fix: bound route mapper parsing
steipete May 16, 2026
88e9667
fix: resolve route mapper prefix edges
steipete May 16, 2026
b9a2e65
fix: cover nested mapper package edges
steipete May 16, 2026
a3382d0
fix: resolve nested mapper manager edges
steipete May 16, 2026
98b4ad7
fix: unwrap React route wrappers
steipete May 16, 2026
7c3b562
fix: ignore commented route parser hints
steipete May 16, 2026
3908c78
fix: ignore route examples inside strings
steipete May 16, 2026
bf9f87f
fix: strip commented FastAPI router prefixes
steipete May 16, 2026
e7d6bcc
fix: ignore Python comment quotes in route scanner
steipete May 16, 2026
4f52c60
fix: cover decorator comments and workspace fallbacks
steipete May 16, 2026
f7a8e1f
fix: preserve FastAPI router import prefixes
steipete May 16, 2026
3179313
fix: avoid false FastAPI prefixes
steipete May 16, 2026
b11b495
fix: parse FastAPI router arguments safely
steipete May 16, 2026
cb41080
fix: require exact FastAPI prefix constants
steipete May 16, 2026
fa19810
fix: refresh mapper resolution state
steipete May 16, 2026
db70956
fix: tighten FastAPI prefix mapping
steipete May 16, 2026
210c1e2
fix: propagate unresolved FastAPI prefixes
steipete May 16, 2026
4b67453
fix: parse FastAPI route arguments precisely
steipete May 16, 2026
d8fab6f
fix: bind router route prefixes precisely
steipete May 16, 2026
4b5c5cd
Merge origin/main into codex/react-router-mapper
moritzscheele May 16, 2026
ff3da9b
fix: tighten route mapper edge cases
steipete May 16, 2026
5a12405
Merge remote-tracking branch 'moritz/codex-react-router-mapper' into …
steipete May 16, 2026
5548d5d
fix: preserve route mapper context edges
steipete May 16, 2026
25ad8d4
fix: keep python mapper scope stable
steipete May 16, 2026
429a53f
fix: harden react route mapping edge cases
steipete May 16, 2026
d9f4198
fix: wire react route validation commands
steipete May 16, 2026
33b9c9d
fix: filter react mapper context imports
steipete May 16, 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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Added Next.js route mapping for `src/app` and `src/pages` layouts, thanks @obatried.
- Added first-pass Python mapping for project metadata, console scripts, source groups, pytest suites, and conservative validation defaults, thanks @xiamx.
- Added progress output for `clawpatch revalidate`, thanks @twidtwid.
- Added React Router and React component mapping, thanks @moritzscheele.
- Improved Node/TypeScript mapping for large workspaces by splitting package source trees into bounded review groups with package-local tests.
- Added generic nested SwiftPM, Apple/Xcode, and Gradle/Android app mapping.
- Fixed Codex provider execution on Windows paths with spaces and npm `.cmd` shims, thanks @1berto.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ validation commands and records a patch attempt under `.clawpatch/`.
- Nx project metadata from `project.json`, including project-scoped validation
targets
- Next.js `app/` and `pages/` routes, including routes inside monorepo apps
- React Router routes and React components
- Go package slices from `go list ./...`, including command packages
- Go package tests and same-repo imports as review context
- Java/Kotlin Gradle source groups and root Gradle build/test commands
Expand Down
9 changes: 9 additions & 0 deletions docs/feature-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Supported deterministic mappers today:
- Node/TypeScript workspace packages from `package.json` workspaces, `pnpm-workspace.yaml`, and common package folders
- Nx project metadata from `project.json`, including project names, source roots, project types, and target names
- bounded Node/TypeScript source groups under `src/`, `lib/`, `app/`, `pages/`, and `scripts/`
- React Router `<Route path element>` declarations and React components in
root or nested frontend packages such as `frontend/`, `client/`, `web/`,
workspaces, and packages under `apps/` or `packages/`
- Next.js `app/` and `pages/` routes at the repo root or inside discovered monorepo projects
- Go `cmd/*/main.go`
- Go `internal/*` packages
Expand Down Expand Up @@ -72,6 +75,12 @@ clawpatch next --project web
When an Nx project target is available, nearby tests use the project-scoped
command, such as `yarn nx test web`, instead of a repository-wide test command.

React mapping discovers packages with a React dependency, including common
nested frontend directories. It maps React Router route declarations to the
component they render when the component can be resolved from a local import or
lazy import, and also maps page/component files under `src/pages` and
`src/components` as UI-flow slices.

Native app mappers use the same bounded grouping model. SwiftPM packages can be
discovered below the repo root, Apple projects are grouped by Swift source area,
and Gradle modules are grouped from `src/main`, `src/test`, and `src/androidTest`.
Expand Down
1,329 changes: 1,329 additions & 0 deletions src/mapper.test.ts

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { gradleSeeds } from "./mappers/gradle.js";
import { nextSeeds } from "./mappers/next.js";
import { nodeSeeds } from "./mappers/node.js";
import { pythonSeeds } from "./mappers/python.js";
import { reactSeeds } from "./mappers/react.js";
import { discoverNodeProjects } from "./mappers/projects.js";
import { rubySeeds } from "./mappers/ruby.js";
import { rustSeeds } from "./mappers/rust.js";
Expand All @@ -25,6 +26,7 @@ export type MapResult = {
const featureMappers: FeatureMapper[] = [
{ name: "node", map: nodeSeeds },
{ name: "next", map: nextSeeds },
{ name: "react", map: reactSeeds },
{ name: "go", map: goSeeds },
{ name: "python", map: pythonSeeds },
{ name: "ruby", map: rubySeeds },
Expand Down
50 changes: 44 additions & 6 deletions src/mappers/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type NodeProjectInfo = {
projectType: string | null;
targets: Record<string, NodeProjectTarget>;
packageManager: string;
nxPackageManager: string;
};

type CandidateContextFile = {
Expand All @@ -37,7 +38,7 @@ type CandidateContextFile = {

export async function discoverNodeProjects(root: string): Promise<NodeProjectInfo[]> {
const rootPackage = await readPackageJson(root);
const packageManager = await detectNodePackageManager(root);
const rootPackageManager = await detectNodePackageManager(root);
const byRoot = new Map<string, NodeProjectInfo>();

for (const packageRoot of await discoverPackageRoots(root, rootPackage)) {
Expand All @@ -55,7 +56,8 @@ export async function discoverNodeProjects(root: string): Promise<NodeProjectInf
sourceRoot: null,
projectType: null,
targets: {},
packageManager,
packageManager: await nodePackageManagerForPackage(root, packageRoot, rootPackageManager),
nxPackageManager: rootPackageManager,
});
}

Expand Down Expand Up @@ -84,13 +86,37 @@ export async function discoverNodeProjects(root: string): Promise<NodeProjectInf
sourceRoot: nxProject.sourceRoot,
projectType: nxProject.projectType,
targets: nxProject.targets,
packageManager,
packageManager: await nodePackageManagerForPackage(root, projectRoot, rootPackageManager),
nxPackageManager: rootPackageManager,
});
}

return [...byRoot.values()].toSorted((left, right) => left.root.localeCompare(right.root));
}

async function nodePackageManagerForPackage(
root: string,
packageRoot: string,
rootPackageManager: string,
): Promise<string> {
if (packageRoot === ".") {
return rootPackageManager;
}
const packageDir = join(root, packageRoot);
for (const lockfile of [
"pnpm-lock.yaml",
"pnpm-workspace.yaml",
"yarn.lock",
"bun.lockb",
"package-lock.json",
]) {
if (await pathExists(join(packageDir, lockfile))) {
return detectNodePackageManager(packageDir);
}
}
return rootPackageManager;
}

export function projectTags(project: NodeProjectInfo): string[] {
const tags = [`project:${project.name}`, `project-root:${project.root}`];
if (project.projectType !== null) {
Expand All @@ -108,7 +134,7 @@ export function projectContextFiles(

export function projectTargetCommand(project: NodeProjectInfo, target: string): string | null {
if (project.targets[target] !== undefined) {
return nxCommand(project.packageManager, target, project.name);
return nxCommand(project.nxPackageManager, target, project.name);
}
if (project.packageJson !== null && packageScripts(project.packageJson)[target] !== undefined) {
return scriptCommand(project.packageManager, project.root, target);
Expand All @@ -122,6 +148,9 @@ export function packageRelativePath(packageRoot: string, path: string): string {

export function scriptCommand(packageManager: string, packageRoot: string, script: string): string {
if (packageRoot === ".") {
if (packageManager === "bun") {
return `bun run ${script}`;
}
return packageManager === "npm" ? `npm run ${script}` : `${packageManager} ${script}`;
}
if (packageManager === "pnpm") {
Expand Down Expand Up @@ -233,8 +262,17 @@ async function workspacePatterns(root: string, pkg: NodePackageJson | null): Pro
patterns.add(pattern);
}
}
for (const fallback of ["packages/*", "apps/*", "extensions/*", "plugins/*"]) {
if (await pathExists(join(root, fallback.slice(0, -2)))) {
for (const fallback of [
"frontend",
"client",
"web",
"ui",
"packages/*",
"apps/*",
"extensions/*",
"plugins/*",
]) {
if (await pathExists(join(root, fallback.replace(/\/\*$/u, "")))) {
patterns.add(fallback);
}
}
Expand Down
Loading