Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
4 changes: 2 additions & 2 deletions .agents/skills/product-thinking/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Run the pass whenever a diff touches a surface a user — a developer running Re
| GitHub Action input/output | `action.yml` | Versioned independently (`vN`); workflows in other repos break when an input or output changes. |
| Terminal output / UX | `cli/utils/` renderers | The first impression and the daily experience; noise or confusion here is what makes people stop running it. |

**Not here:** lint rules go through the `rule-research` → `rule-writing` → `rule-validate` pipeline, and rule configuration through `doctor-explain` — this pass is for the surface _around_ the rules, not the rules themselves. Internal-only changes (the engine, private `core` types, tests, tooling) skip the pass entirely; note in one line why the change is internal and move on.
**Not here:** lint rules go through the `rule-research` → `rule-writing` → `rule-validate` pipeline, and rule configuration through `react-doctor/references/explain.md` — this pass is for the surface _around_ the rules, not the rules themselves. Internal-only changes (the engine, private `core` types, tests, tooling) skip the pass entirely; note in one line why the change is internal and move on.

## Steps

Expand Down Expand Up @@ -87,7 +87,7 @@ A surface nobody can discover is wasted, and a stale doc is a trust bug. Documen

- The `--help` / usage text next to the new flag or command, so it's discoverable from the CLI itself.
- The website page and the canonical prompt under `react.doctor/prompts/...`, which is what agents fetch at runtime.
- The distributed skills (`skills/react-doctor`, `skills/doctor-explain`) when the change alters the user-facing workflow.
- The distributed skills (`skills/react-doctor`, `skills/react-doctor/references/explain.md`) when the change alters the user-facing workflow.

### 6. Record the kill metric

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/project-info/count-source-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const countSourceFilesViaGit = (rootDirectory: string): number | null => {
// filesystem walk in countSourceFilesViaFilesystem.
const result = spawnSync(
"git",
["ls-files", "-z", "--cached", "--others", "--exclude-standard"],
["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", "."],
{
cwd: rootDirectory,
encoding: "utf-8",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/utils/list-source-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const listSourceFilesViaGit = (rootDirectory: string): string[] | null => {
// skipping submodule files entirely.
const result = spawnSync(
"git",
["ls-files", "-z", "--cached", "--others", "--exclude-standard"],
["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", "."],
{
cwd: rootDirectory,
encoding: "utf-8",
Expand Down
21 changes: 21 additions & 0 deletions packages/core/tests/list-source-files-with-size.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { spawnSync } from "node:child_process";
import * as fs from "node:fs";
import os from "node:os";
import * as path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test";
import { MINIFIED_MIN_SIZE_BYTES } from "../src/project-info/constants.js";
import { countSourceFiles } from "../src/project-info/count-source-files.js";
import { listSourceFiles, listSourceFilesWithSize } from "../src/utils/list-source-files.js";

describe("listSourceFilesWithSize", () => {
Expand Down Expand Up @@ -56,4 +58,23 @@ describe("listSourceFilesWithSize", () => {
listSourceFilesWithSize(temporaryDirectory).map((entry) => entry.path),
);
});

it("scopes git listings to the requested subdirectory", () => {
const workspaceDirectory = path.join(temporaryDirectory, "workspace");
const appDirectory = path.join(workspaceDirectory, "apps", "web");
const packageDirectory = path.join(workspaceDirectory, "packages", "ui");
fs.mkdirSync(appDirectory, { recursive: true });
fs.mkdirSync(packageDirectory, { recursive: true });
fs.writeFileSync(path.join(appDirectory, "App.tsx"), "export const App = () => null;\n");
fs.writeFileSync(
path.join(packageDirectory, "Button.tsx"),
"export const Button = () => null;\n",
);

spawnSync("git", ["init"], { cwd: workspaceDirectory });
spawnSync("git", ["add", "."], { cwd: workspaceDirectory });

expect(listSourceFiles(appDirectory)).toEqual(["App.tsx"]);
expect(countSourceFiles(appDirectory)).toBe(1);
});
});
12 changes: 1 addition & 11 deletions packages/core/tests/services/dead-code.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
import * as Stream from "effect/Stream";
import { describe, expect, it } from "vite-plus/test";
import type { Diagnostic, ProjectInfo } from "@react-doctor/core";
import type { Diagnostic } from "@react-doctor/core";
import { DeadCode } from "../../src/services/dead-code.js";

const sampleDiagnostic: Diagnostic = {
Expand All @@ -17,14 +17,6 @@ const sampleDiagnostic: Diagnostic = {
category: "Maintainability",
};

const sampleInput = {
rootDirectory: "/repo",
userConfig: null,
} satisfies {
rootDirectory: string;
userConfig: ProjectInfo["framework"] extends string ? null : null;
};

describe("DeadCode.layerOf", () => {
it("emits the supplied diagnostics as a stream", async () => {
const collected = await Effect.runPromise(
Expand Down Expand Up @@ -70,5 +62,3 @@ describe("DeadCode.layerNode", () => {
}
});
});

void sampleInput;
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ interface ProjectScanEntry {

const getScoreLabel = (score: number): string => {
if (score >= SCORE_GOOD_THRESHOLD) return "Great";
if (score >= SCORE_OK_THRESHOLD) return "OK";
return "Needs work";
if (score >= SCORE_OK_THRESHOLD) return "Needs work";
return "Critical";
};

const buildSummaryLine = (entry: ProjectScanEntry, longestProjectNameLength: number): string => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const resolveCliInspectOptions = (
noScore: flags.score === false || flags.telemetry === false || (userConfig?.noScore ?? false),
isCi: isCiEnvironment(),
silent: Boolean(flags.json),
suppressRendering: Boolean(flags.json),
concurrency: resolveParallelFlag(flags.parallel),
categoryFilters: resolveCliCategories(flags.category),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ describe("resolveCliInspectOptions: --no-telemetry alias", () => {
it("keeps scoring on by default", () => {
expect(resolveCliInspectOptions({}, null).noScore).toBe(false);
});

it("suppresses rendering when JSON owns stdout", () => {
expect(resolveCliInspectOptions({ json: true }, null)).toMatchObject({
silent: true,
suppressRendering: true,
});
});
});

describe("resolveCliInspectOptions: category filtering", () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/website/public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Diagnose React codebase health. Scans for security, performance, correctness, and architecture issues, then outputs a 0–100 score with actionable diagnostics.

Note: dead-code detection was removed in v0.2 (previously powered by knip). For dead-code analysis, run `npx knip` directly.
Dead-code detection is included by default and powered by deslop-js. Use `--no-dead-code` when you only want lint diagnostics.

## Usage

Expand Down
4 changes: 2 additions & 2 deletions packages/website/src/components/terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ const DIAGNOSTICS: RuleDiagnostic[] = [
location: "src/components/Dashboard.tsx:42",
},
{
ruleKey: "react-doctor/no-server-action-auth",
ruleKey: "react-doctor/server-auth-actions",
severity: "error",
message: 'Server action "deleteUser" has no auth check, so any caller could run it',
help: "Add an authentication check at the top of every server action.",
count: 2,
location: "src/app/actions/users.ts:18",
},
{
ruleKey: "react/no-array-index-key",
ruleKey: "react-doctor/no-array-index-key",
severity: "error",
message: "Array index is used as a key, so reordered items can keep the wrong state",
help: "Use a unique, stable identifier from each item as the key prop.",
Expand Down
131 changes: 0 additions & 131 deletions scripts/convert-node-imports.mjs

This file was deleted.

2 changes: 1 addition & 1 deletion skills/react-doctor/references/explain.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ npx react-doctor@latest rules explain react-doctor/no-array-index-as-key
5. Validate the change did what they wanted:

```bash
npx react-doctor@latest --verbose --diff
npx react-doctor@latest --verbose --scope changed
```

## Commands
Expand Down
Loading