@praneeth_54/agentdoctor 0.2.0-beta → 0.3.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +32 -3
  2. package/README.md +46 -25
  3. package/dist/cli/commands/doctor.js +1 -1
  4. package/dist/cli/commands/explain.js +2 -2
  5. package/dist/cli/commands/fix.d.ts +7 -5
  6. package/dist/cli/commands/fix.js +57 -12
  7. package/dist/cli/commands/verify.d.ts +13 -0
  8. package/dist/cli/commands/verify.js +56 -0
  9. package/dist/cli/program.js +53 -30
  10. package/dist/constants.d.ts +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/core/fix/apply.d.ts +10 -0
  13. package/dist/core/fix/apply.js +27 -0
  14. package/dist/core/fix/patterns.d.ts +7 -0
  15. package/dist/core/fix/patterns.js +21 -0
  16. package/dist/core/fix/plan.d.ts +9 -0
  17. package/dist/core/fix/plan.js +140 -0
  18. package/dist/core/fix/render.d.ts +6 -0
  19. package/dist/core/fix/render.js +68 -0
  20. package/dist/core/fix/run.d.ts +15 -0
  21. package/dist/core/fix/run.js +54 -0
  22. package/dist/core/fix/types.d.ts +34 -0
  23. package/dist/core/fix/types.js +2 -0
  24. package/dist/core/fix/writers/cursorignore.d.ts +13 -0
  25. package/dist/core/fix/writers/cursorignore.js +74 -0
  26. package/dist/core/rules/context/generated-directory.js +10 -0
  27. package/dist/core/rules/instructions/missing-path-reference.js +90 -1
  28. package/dist/core/rules/path-kind.d.ts +14 -0
  29. package/dist/core/rules/path-kind.js +77 -0
  30. package/dist/core/rules/security/env-file-exposure.js +14 -14
  31. package/dist/core/rules/security/private-key-file.js +4 -0
  32. package/dist/core/verify/compare.d.ts +29 -0
  33. package/dist/core/verify/compare.js +56 -0
  34. package/dist/core/verify/load-baseline.d.ts +15 -0
  35. package/dist/core/verify/load-baseline.js +71 -0
  36. package/dist/core/verify/verify.d.ts +26 -0
  37. package/dist/core/verify/verify.js +40 -0
  38. package/dist/index.d.ts +8 -0
  39. package/dist/index.js +5 -0
  40. package/dist/reporters/terminal/report.js +7 -1
  41. package/dist/reporters/verify/json.d.ts +5 -0
  42. package/dist/reporters/verify/json.js +51 -0
  43. package/dist/reporters/verify/terminal.d.ts +2 -0
  44. package/dist/reporters/verify/terminal.js +45 -0
  45. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,10 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0-beta] — 2026-08-07
11
+
12
+ Minor beta: completes the Scan → Fix → Verify CLI loop and corrects release-facing honesty.
13
+
14
+ ### Added
15
+
16
+ - `agentdoctor verify` — re-scan and compare against a prior `scan --json` baseline
17
+ (`fixed` / `remaining` / `new` / `unchanged`). Supports `--json`, `--ci` (fails on new
18
+ findings), `--baseline`, and `--min-score`. Completes the Scan → Fix → Verify CLI loop.
19
+ - Terminal summary prints overall readiness (`N/100`); category/agent scores remain in JSON.
20
+
21
+ ### Fixed
22
+
23
+ - `agentdoctor scan --json` (and `--ci` / `--verbose` / `--min-score` on the `scan`
24
+ subcommand) now honor flags correctly. Overlapping root/subcommand options are read via
25
+ Commander `optsWithGlobals()`, so CI scripts using `scan … --json` receive JSON instead of
26
+ a terminal report.
27
+ - `instructions/missing-path-reference` no longer treats Go/npm module imports
28
+ (`github.com/…`, `@scope/pkg`), Go stdlib paths (`io/ioutil`), glob patterns, code tokens
29
+ (`try/finally`), or bare build roots (`dist/`) as missing local paths.
30
+ - `agentdoctor fix` now reports skip reasons for review/manual findings instead of an empty
31
+ “no applicable fixes” message with no explanation.
32
+ - Sample/test/example paths and env templates no longer inflate security/context false positives.
33
+
34
+ ### Compatibility
35
+
36
+ - Default Action `version` input is `0.3.0-beta` (pin CI smoke to last published until npm ships)
37
+ - Fix remains Cursor `.cursorignore` safe-context only; security findings stay review/manual
38
+
10
39
  ### Planned
11
40
 
12
- - Safe automatic fixes
13
- - Terminal readiness line and GitHub Action score-gate inputs (deferred; see scoring.md v2+)
41
+ - GitHub Action score-gate inputs (deferred; see scoring.md v2+)
14
42
 
15
43
  ## [0.2.0-beta] — 2026-08-02
16
44
 
@@ -143,7 +171,8 @@ First public beta.
143
171
  - Not a complete secret scanner
144
172
  - Git “tracked secret” detection deferred
145
173
 
146
- [Unreleased]: https://github.com/pranee54/AgentDoctor/compare/v0.2.0-beta...HEAD
174
+ [Unreleased]: https://github.com/pranee54/AgentDoctor/compare/v0.3.0-beta...HEAD
175
+ [0.3.0-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.3.0-beta
147
176
  [0.2.0-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.2.0-beta
148
177
  [0.1.4-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.1.4-beta
149
178
  [0.1.3-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.1.3-beta
package/README.md CHANGED
@@ -16,7 +16,7 @@ AgentDoctor is a local CLI that inspects project-level AI coding agent setup —
16
16
  npx @praneeth_54/agentdoctor
17
17
  ```
18
18
 
19
- Public beta (`0.1.x-beta`). Deterministic readiness scores ship in JSON; automatic fixes are not available yet.
19
+ Public beta (`0.3.0-beta`). Scan → Fix → Verify. Deterministic scores in the terminal and JSON.
20
20
 
21
21
  ---
22
22
 
@@ -24,12 +24,12 @@ Public beta (`0.1.x-beta`). Deterministic readiness scores ship in JSON; automat
24
24
 
25
25
  ![AgentDoctor scanning a repository and reporting coding-agent security findings](docs/images/cli-scan.png)
26
26
 
27
- _Real scan of the included `insecure-agent-project` fixture using AgentDoctor v0.2.0-beta._
27
+ _Real scan of the included `insecure-agent-project` fixture using AgentDoctor v0.3.0-beta._
28
28
 
29
29
  ```text
30
30
  $ npx @praneeth_54/agentdoctor
31
31
 
32
- 🩺 AgentDoctor v0.2.0-beta
32
+ 🩺 AgentDoctor v0.3.0-beta
33
33
 
34
34
  Scanning repository...
35
35
 
@@ -71,10 +71,11 @@ Summary
71
71
  1 warning
72
72
  0 info
73
73
 
74
- Scoring: not included in this release
74
+ Readiness: 13/100
75
+ Category and agent scores: agentdoctor scan --json
75
76
  ```
76
77
 
77
- Abbreviated text example from the same fixture for accessibility and search. Secret values are never printed.
78
+ Abbreviated text example from the same fixture for accessibility and search. Secret values are never printed. Re-run the scan if counts change.
78
79
 
79
80
  ---
80
81
 
@@ -99,7 +100,7 @@ Manually reviewing all of that across Cursor, Claude Code, and Codex is slow and
99
100
  | ESLint | Source code |
100
101
  | **AgentDoctor** | **AI coding agent environments** |
101
102
 
102
- AgentDoctor analyzes configuration. It does not run agents, call an LLM, or modify your repository in this release.
103
+ AgentDoctor analyzes configuration. It does not run agents or call an LLM. `agentdoctor fix` may append safe Cursor ignore patterns; it does not rewrite security settings or credentials.
103
104
 
104
105
  ---
105
106
 
@@ -139,8 +140,9 @@ You get deterministic findings with:
139
140
  - evidence paths
140
141
  - affected agents when exposure claims are supported
141
142
  - conservative recommendations
143
+ - readiness score (`scores.overall` in JSON; overall line in the terminal)
142
144
 
143
- Automatic repair is **not** included in this beta. Findings tell you what to review; you decide what to change.
145
+ Safe Cursor context exclusions can be applied with `agentdoctor fix`. Security and review findings stay manual — Fix explains why and does not invent unsafe edits.
144
146
 
145
147
  ---
146
148
 
@@ -155,7 +157,7 @@ npx @praneeth_54/agentdoctor
155
157
  Pin a beta version when you need a fixed install:
156
158
 
157
159
  ```bash
158
- npx @praneeth_54/agentdoctor@0.2.0-beta
160
+ npx @praneeth_54/agentdoctor@0.3.0-beta
159
161
  ```
160
162
 
161
163
  ### Global (optional)
@@ -165,12 +167,29 @@ npm install -g @praneeth_54/agentdoctor
165
167
  agentdoctor
166
168
  ```
167
169
 
170
+ ### Scan → Fix → Verify
171
+
172
+ ```bash
173
+ # 1. Scan (save a baseline for Verify)
174
+ npx @praneeth_54/agentdoctor scan . --json > agentdoctor-report.json
175
+
176
+ # 2. Fix safe Cursor context exclusions (preview first with --dry-run)
177
+ npx @praneeth_54/agentdoctor fix . --dry-run
178
+ npx @praneeth_54/agentdoctor fix . -y
179
+
180
+ # 3. Verify against the baseline
181
+ npx @praneeth_54/agentdoctor verify . --baseline agentdoctor-report.json
182
+ ```
183
+
184
+ `fix` currently writes `.cursorignore` patterns for safe context findings (for example unignored `build/` or large logs). Review/manual security findings are listed as skipped — address those yourself, then re-run `verify`.
185
+
168
186
  ### Common commands
169
187
 
170
188
  ```bash
171
189
  agentdoctor .
172
- agentdoctor . --json
173
- agentdoctor . --verbose
190
+ agentdoctor scan . --json
191
+ agentdoctor fix . --dry-run
192
+ agentdoctor verify . --ci --baseline agentdoctor-report.json
174
193
  agentdoctor explain security/env-file-exposure
175
194
  agentdoctor doctor
176
195
  ```
@@ -184,10 +203,11 @@ npm install @praneeth_54/agentdoctor
184
203
  ```
185
204
 
186
205
  ```ts
187
- import { scan } from "@praneeth_54/agentdoctor";
206
+ import { scan, verify, buildFixPlan, applyFixPlan } from "@praneeth_54/agentdoctor";
188
207
 
189
208
  const result = await scan({ cwd: process.cwd() });
190
209
  console.log(result.summary);
210
+ console.log(result.scores?.overall);
191
211
  console.log(result.agentSecurityAnalysis); // "full" | "limited"
192
212
  ```
193
213
 
@@ -259,7 +279,7 @@ steps:
259
279
 
260
280
  - name: Audit coding-agent configuration
261
281
  id: agentdoctor
262
- uses: pranee54/AgentDoctor@v0.2.0-beta
282
+ uses: pranee54/AgentDoctor@v0.3.0-beta
263
283
  with:
264
284
  path: .
265
285
  output-file: agentdoctor-report.json
@@ -271,7 +291,7 @@ steps:
271
291
  path: ${{ steps.agentdoctor.outputs.report-path }}
272
292
  ```
273
293
 
274
- The action installs the published `@praneeth_54/agentdoctor@0.2.0-beta` package, runs it with
294
+ The action installs the published `@praneeth_54/agentdoctor@0.3.0-beta` package, runs it with
275
295
  `--ci --json`, and writes the report inside the checked-out workspace. It sets up Node.js 20
276
296
  for the CLI. The optional `version` input accepts an exact npm version or the `latest` / `beta`
277
297
  dist-tag.
@@ -297,8 +317,8 @@ Exit codes: [docs/exit-codes.md](docs/exit-codes.md). Compatibility promises: [d
297
317
  ### Readiness scoring
298
318
 
299
319
  Scans populate `scoringAvailable: true` and a deterministic `scores` object
300
- (overall, categories, agents). The terminal report does not render a readiness line yet;
301
- scores are available in JSON (`--json`).
320
+ (overall, categories, agents). The terminal prints overall readiness; category and agent
321
+ scores are in JSON (`--json`).
302
322
 
303
323
  `--min-score N` is enforced by the CLI. Details (weights, security caps, threshold rules,
304
324
  and deferred v2 items): [docs/scoring.md](docs/scoring.md).
@@ -309,16 +329,17 @@ and deferred v2 items): [docs/scoring.md](docs/scoring.md).
309
329
 
310
330
  Honest limits of the current public beta:
311
331
 
312
- | Limitation | Status |
313
- | ----------------------------------- | --------------------------------------------------------------- |
314
- | Terminal readiness line | Scores ship in JSON only; terminal does not print N/100 yet |
315
- | GitHub Action score gates | Action remains `--ci --json` report-only (no `min-score` input) |
316
- | Automatic fixes (`agentdoctor fix`) | Stub only — does not modify files |
317
- | Secret-content scanning | Filename / config heuristics only |
318
- | Detection style | Intentionally conservative; false security findings are avoided |
319
- | Agent coverage | Cursor, Claude Code, Codex project configs |
320
-
321
- See [CHANGELOG.md](CHANGELOG.md) and [docs/release-notes-v0.2.0-beta.md](docs/release-notes-v0.2.0-beta.md).
332
+ | Limitation | Status |
333
+ | --------------------------- | ------------------------------------------------------------------- |
334
+ | Automatic fixes | Safe Cursor `.cursorignore` context exclusions only |
335
+ | Claude Code / Codex writers | Not implemented — Fix skips with an explicit reason |
336
+ | Security findings | Review/manual — Fix does not rewrite secrets or permission settings |
337
+ | GitHub Action score gates | Action remains `--ci --json` report-only (no `min-score` input) |
338
+ | Secret-content scanning | Filename / config heuristics only |
339
+ | Detection style | Intentionally conservative; false security findings are avoided |
340
+ | Agent coverage | Cursor, Claude Code, Codex project configs |
341
+
342
+ See [CHANGELOG.md](CHANGELOG.md) and [docs/compatibility.md](docs/compatibility.md).
322
343
 
323
344
  ---
324
345
 
@@ -15,7 +15,7 @@ export async function runDoctorCommand() {
15
15
  lines.push(` ${symbolOk()} Core scan API available`);
16
16
  lines.push("");
17
17
  lines.push(colors.dim("Environment looks ready."));
18
- lines.push(colors.dim("Automatic fixes are not available yet."));
18
+ lines.push(colors.dim("Run agentdoctor scan → fix → verify to complete the readiness loop."));
19
19
  lines.push("");
20
20
  process.stdout.write(lines.join("\n"));
21
21
  return EXIT_CODES.SUCCESS;
@@ -39,9 +39,9 @@ export async function runExplainCommand(ruleId) {
39
39
  lines.push("");
40
40
  lines.push(colors.bold("Can AgentDoctor safely fix it?"));
41
41
  lines.push(rule.fixability === "safe"
42
- ? " Potentially yes. Conservative auto-fix is planned but not applied today."
42
+ ? " Yes for Cursor context exclusions (`agentdoctor fix`). Other agents may still need a manual step."
43
43
  : rule.fixability === "review"
44
- ? " Only with review. Automatic fixes are not applied today."
44
+ ? " No — requires human review. Fix reports why and leaves the file unchanged."
45
45
  : rule.fixability === "manual"
46
46
  ? " No automatic fix. Manual remediation required."
47
47
  : " No fix available.");
@@ -1,8 +1,10 @@
1
1
  import { type ExitCode } from "../../types/index.js";
2
- /**
3
- * Safe automatic fixes are not implemented yet.
4
- */
5
- export declare function runFixCommand(options: {
2
+ export interface FixCommandOptions {
3
+ targetPath?: string;
6
4
  dryRun?: boolean;
7
5
  yes?: boolean;
8
- }): Promise<ExitCode>;
6
+ }
7
+ /**
8
+ * Safe Repository Mutation — apply allowlisted agent-config exclusions.
9
+ */
10
+ export declare function runFixCommand(options: FixCommandOptions): Promise<ExitCode>;
@@ -1,19 +1,64 @@
1
1
  import { EXIT_CODES } from "../../types/index.js";
2
+ import { isDirectory } from "../../utils/fs.js";
3
+ import { resolveRepoRoot } from "../../utils/path.js";
4
+ import { applyFixPlan, readCursorignore } from "../../core/fix/apply.js";
5
+ import { buildFixPlan } from "../../core/fix/plan.js";
6
+ import { renderFixPlanTerminal } from "../../core/fix/render.js";
7
+ import { runFix } from "../../core/fix/run.js";
8
+ import { scan } from "../../core/scanner/scan.js";
2
9
  import { colors } from "../../utils/colors.js";
3
10
  /**
4
- * Safe automatic fixes are not implemented yet.
11
+ * Safe Repository Mutation — apply allowlisted agent-config exclusions.
5
12
  */
6
13
  export async function runFixCommand(options) {
7
- const lines = [];
8
- lines.push("");
9
- lines.push(colors.bold("AgentDoctor fix"));
10
- lines.push("");
11
- if (options.dryRun) {
12
- lines.push(" Dry-run mode requested.");
14
+ const target = resolveRepoRoot(options.targetPath ?? process.cwd());
15
+ if (!(await isDirectory(target))) {
16
+ console.error(`Error: not a directory: ${target}`);
17
+ return EXIT_CODES.USAGE_ERROR;
18
+ }
19
+ const dryRun = options.dryRun === true;
20
+ const yes = options.yes === true;
21
+ try {
22
+ if (dryRun) {
23
+ const result = await scan({ cwd: target });
24
+ const plan = await buildFixPlan(result);
25
+ const cursorContent = await readCursorignore(plan.root);
26
+ const applyResult = await applyFixPlan(plan, { dryRun: true });
27
+ process.stdout.write(renderFixPlanTerminal(plan, {
28
+ dryRun: true,
29
+ cursorContent,
30
+ applyResult,
31
+ }));
32
+ return EXIT_CODES.SUCCESS;
33
+ }
34
+ const { plan, applyResult, cancelled } = await runFix({
35
+ cwd: target,
36
+ dryRun: false,
37
+ yes,
38
+ });
39
+ if (cancelled) {
40
+ process.stdout.write("\n Cancelled. No files were modified.\n\n");
41
+ return EXIT_CODES.SUCCESS;
42
+ }
43
+ const cursorContentAfter = await readCursorignore(plan.root);
44
+ process.stdout.write(renderFixPlanTerminal(plan, {
45
+ dryRun: false,
46
+ cursorContent: cursorContentAfter,
47
+ applyResult,
48
+ }));
49
+ // After apply, show a short re-scan hint / delta for Cursor-fixable rules
50
+ if (applyResult.writtenFiles.length > 0) {
51
+ const after = await scan({ cwd: target });
52
+ const remainingSafeContext = after.findings.filter((f) => f.fixability === "safe" &&
53
+ (f.ruleId === "context/generated-directory" || f.ruleId === "context/large-log-file") &&
54
+ f.affectedAgents.includes("cursor"));
55
+ process.stdout.write(colors.dim(` Re-scan: ${remainingSafeContext.length} Cursor-related safe context finding(s) remain.\n\n`));
56
+ }
57
+ return EXIT_CODES.SUCCESS;
58
+ }
59
+ catch (error) {
60
+ const message = error instanceof Error ? error.message : String(error);
61
+ console.error(`Error: ${message}`);
62
+ return EXIT_CODES.INTERNAL_ERROR;
13
63
  }
14
- lines.push(" Fix mode is not implemented yet.");
15
- lines.push(" No files were modified.");
16
- lines.push("");
17
- process.stdout.write(lines.join("\n"));
18
- return EXIT_CODES.SUCCESS;
19
64
  }
@@ -0,0 +1,13 @@
1
+ import { type ExitCode } from "../../types/index.js";
2
+ export interface VerifyCommandOptions {
3
+ targetPath?: string;
4
+ baselinePath?: string;
5
+ json?: boolean;
6
+ ci?: boolean;
7
+ verbose?: boolean;
8
+ minScore?: number;
9
+ }
10
+ /**
11
+ * Verify: re-scan after Fix and compare against a prior scan JSON baseline.
12
+ */
13
+ export declare function runVerifyCommand(options: VerifyCommandOptions): Promise<ExitCode>;
@@ -0,0 +1,56 @@
1
+ import { verify } from "../../core/verify/verify.js";
2
+ import { renderVerifyJsonReport } from "../../reporters/verify/json.js";
3
+ import { renderVerifyTerminalReport } from "../../reporters/verify/terminal.js";
4
+ import { EXIT_CODES } from "../../types/index.js";
5
+ import { isDirectory } from "../../utils/fs.js";
6
+ import { resolveRepoRoot } from "../../utils/path.js";
7
+ /**
8
+ * Verify: re-scan after Fix and compare against a prior scan JSON baseline.
9
+ */
10
+ export async function runVerifyCommand(options) {
11
+ const target = resolveRepoRoot(options.targetPath ?? process.cwd());
12
+ if (!(await isDirectory(target))) {
13
+ console.error(`Error: not a directory: ${target}`);
14
+ return EXIT_CODES.USAGE_ERROR;
15
+ }
16
+ try {
17
+ const result = await verify({
18
+ cwd: target,
19
+ verbose: options.verbose ?? false,
20
+ ...(options.baselinePath !== undefined ? { baselinePath: options.baselinePath } : {}),
21
+ });
22
+ if (options.json) {
23
+ process.stdout.write(renderVerifyJsonReport(result));
24
+ }
25
+ else {
26
+ process.stdout.write(renderVerifyTerminalReport(result, options.verbose === true));
27
+ }
28
+ if (options.minScore !== undefined && result.scores !== null) {
29
+ if (result.scores.overall < options.minScore) {
30
+ if (!options.json) {
31
+ console.error(`\nCI check failed: overall score ${result.scores.overall} is below --min-score ${options.minScore}`);
32
+ }
33
+ return EXIT_CODES.ISSUES_OR_THRESHOLD;
34
+ }
35
+ }
36
+ // In CI, newly introduced findings are regressions after Fix.
37
+ if (options.ci === true && result.summary.new > 0) {
38
+ if (!options.json) {
39
+ console.error(`\nCI check failed: verify found ${result.summary.new} new finding(s) not present in the baseline`);
40
+ }
41
+ return EXIT_CODES.ISSUES_OR_THRESHOLD;
42
+ }
43
+ return EXIT_CODES.SUCCESS;
44
+ }
45
+ catch (error) {
46
+ const message = error instanceof Error ? error.message : String(error);
47
+ console.error(`Error: ${message}`);
48
+ if (options.verbose && error instanceof Error && error.stack) {
49
+ console.error(error.stack);
50
+ }
51
+ if (message.includes("baseline") || message.includes("No verify baseline")) {
52
+ return EXIT_CODES.USAGE_ERROR;
53
+ }
54
+ return EXIT_CODES.INTERNAL_ERROR;
55
+ }
56
+ }
@@ -4,6 +4,7 @@ import { runDoctorCommand } from "./commands/doctor.js";
4
4
  import { runExplainCommand } from "./commands/explain.js";
5
5
  import { runFixCommand } from "./commands/fix.js";
6
6
  import { resolveTargetArgument, runScanCommand } from "./commands/scan.js";
7
+ import { runVerifyCommand } from "./commands/verify.js";
7
8
  import { EXIT_CODES } from "../types/index.js";
8
9
  function parseMinScore(value) {
9
10
  const parsed = Number(value);
@@ -21,59 +22,81 @@ function readMinScore(options) {
21
22
  }
22
23
  return undefined;
23
24
  }
25
+ /**
26
+ * Scan flags are declared on both the root program and the `scan` subcommand
27
+ * so `--help` stays accurate. Commander stores overlapping flags on the parent
28
+ * when `scan` is invoked, so callers must read `optsWithGlobals()`.
29
+ */
30
+ function addScanOptions(command) {
31
+ return command
32
+ .option("--json", "Emit machine-readable JSON (no decorative output)", false)
33
+ .option("--ci", "CI mode (non-interactive; report-only unless --min-score is set)", false)
34
+ .option("--verbose", "Show timing and extra diagnostics", false)
35
+ .option("--min-score <number>", "Exit 1 when overall readiness score is below this (0-100)", parseMinScore);
36
+ }
37
+ async function runScanFromCli(pathArg, command) {
38
+ const options = command.optsWithGlobals();
39
+ const minScore = readMinScore(options);
40
+ const code = await runScanCommand({
41
+ targetPath: resolveTargetArgument(pathArg),
42
+ json: Boolean(options.json),
43
+ ci: Boolean(options.ci),
44
+ verbose: Boolean(options.verbose),
45
+ ...(minScore !== undefined ? { minScore } : {}),
46
+ });
47
+ process.exitCode = code;
48
+ }
24
49
  export function createProgram() {
25
50
  const program = new Command();
26
- program
51
+ addScanOptions(program
27
52
  .name("agentdoctor")
28
53
  .description("Audit AI coding agent configuration in a repository (local, deterministic, no API key).")
29
54
  .version(PACKAGE_VERSION, "-V, --version", "Print AgentDoctor version")
30
- .argument("[path]", "Repository path to scan (default: current directory)")
31
- .option("--json", "Emit machine-readable JSON (no decorative output)", false)
32
- .option("--ci", "CI mode (non-interactive; report-only unless --min-score is set)", false)
33
- .option("--verbose", "Show timing and extra diagnostics", false)
34
- .option("--min-score <number>", "Exit 1 when overall readiness score is below this (0-100)", parseMinScore)
55
+ .argument("[path]", "Repository path to scan (default: current directory)")).action(async (pathArg, _options, command) => {
56
+ await runScanFromCli(pathArg, command);
57
+ });
58
+ addScanOptions(program
59
+ .command("scan")
60
+ .description("Scan a repository for AI coding agent configuration issues (default command)")
61
+ .argument("[path]", "Repository path to scan")).action(async (pathArg, _options, command) => {
62
+ await runScanFromCli(pathArg, command);
63
+ });
64
+ program
65
+ .command("fix")
66
+ .description("Apply safe automatic fixes (Cursor .cursorignore for safe context findings)")
67
+ .argument("[path]", "Repository path (default: current directory)")
68
+ .option("--dry-run", "Show proposed fixes without writing files", false)
69
+ .option("-y, --yes", "Skip confirmation prompts", false)
35
70
  .action(async (pathArg, options) => {
36
- const minScore = readMinScore(options);
37
- const code = await runScanCommand({
71
+ const code = await runFixCommand({
38
72
  targetPath: resolveTargetArgument(pathArg),
39
- json: Boolean(options.json),
40
- ci: Boolean(options.ci),
41
- verbose: Boolean(options.verbose),
42
- ...(minScore !== undefined ? { minScore } : {}),
73
+ dryRun: Boolean(options.dryRun),
74
+ yes: Boolean(options.yes),
43
75
  });
44
76
  process.exitCode = code;
45
77
  });
46
78
  program
47
- .command("scan")
48
- .description("Scan a repository for AI coding agent configuration issues (default command)")
49
- .argument("[path]", "Repository path to scan")
79
+ .command("verify")
80
+ .description("Re-scan and compare against a prior scan JSON baseline (Scan → Fix → Verify)")
81
+ .argument("[path]", "Repository path (default: current directory)")
50
82
  .option("--json", "Emit machine-readable JSON", false)
51
- .option("--ci", "CI mode (non-interactive; report-only unless --min-score is set)", false)
83
+ .option("--ci", "CI mode: exit 1 when new findings appear (also honors --min-score)", false)
52
84
  .option("--verbose", "Show timing and extra diagnostics", false)
85
+ .option("--baseline <file>", "Prior scan JSON report (default: agentdoctor-report.json or .agentdoctor-baseline.json)")
53
86
  .option("--min-score <number>", "Exit 1 when overall readiness score is below this (0-100)", parseMinScore)
54
- .action(async (pathArg, options) => {
87
+ .action(async (pathArg, _options, command) => {
88
+ const options = command.optsWithGlobals();
55
89
  const minScore = readMinScore(options);
56
- const code = await runScanCommand({
90
+ const code = await runVerifyCommand({
57
91
  targetPath: resolveTargetArgument(pathArg),
58
92
  json: Boolean(options.json),
59
93
  ci: Boolean(options.ci),
60
94
  verbose: Boolean(options.verbose),
95
+ ...(typeof options.baseline === "string" ? { baselinePath: options.baseline } : {}),
61
96
  ...(minScore !== undefined ? { minScore } : {}),
62
97
  });
63
98
  process.exitCode = code;
64
99
  });
65
- program
66
- .command("fix")
67
- .description("Apply safe automatic fixes (not implemented yet)")
68
- .option("--dry-run", "Show proposed fixes without writing files", false)
69
- .option("-y, --yes", "Skip confirmation prompts", false)
70
- .action(async (options) => {
71
- const code = await runFixCommand({
72
- dryRun: Boolean(options.dryRun),
73
- yes: Boolean(options.yes),
74
- });
75
- process.exitCode = code;
76
- });
77
100
  program
78
101
  .command("explain")
79
102
  .description("Explain a rule by id")
@@ -1,4 +1,4 @@
1
- export declare const PACKAGE_VERSION = "0.2.0-beta";
1
+ export declare const PACKAGE_VERSION = "0.3.0-beta";
2
2
  export declare const DEFAULT_MAX_FILE_SIZE_BYTES: number;
3
3
  /** Directories skipped during normal discovery (unless a rule needs them later). */
4
4
  export declare const DEFAULT_IGNORE_DIRECTORIES: Set<string>;
package/dist/constants.js CHANGED
@@ -1,4 +1,4 @@
1
- export const PACKAGE_VERSION = "0.2.0-beta";
1
+ export const PACKAGE_VERSION = "0.3.0-beta";
2
2
  export const DEFAULT_MAX_FILE_SIZE_BYTES = 2 * 1024 * 1024; // 2 MiB
3
3
  /** Directories skipped during normal discovery (unless a rule needs them later). */
4
4
  export const DEFAULT_IGNORE_DIRECTORIES = new Set([
@@ -0,0 +1,10 @@
1
+ import type { FixApplyResult, FixPlan } from "./types.js";
2
+ import { previewCursorignoreActions, readCursorignore } from "./writers/cursorignore.js";
3
+ /**
4
+ * Apply (or dry-run) a fix plan.
5
+ * Week 1: only `.cursorignore` mutations are supported.
6
+ */
7
+ export declare function applyFixPlan(plan: FixPlan, options: {
8
+ dryRun: boolean;
9
+ }): Promise<FixApplyResult>;
10
+ export { previewCursorignoreActions, readCursorignore };
@@ -0,0 +1,27 @@
1
+ import { previewCursorignoreActions, readCursorignore, writeCursorignore, } from "./writers/cursorignore.js";
2
+ /**
3
+ * Apply (or dry-run) a fix plan.
4
+ * Week 1: only `.cursorignore` mutations are supported.
5
+ */
6
+ export async function applyFixPlan(plan, options) {
7
+ const current = await readCursorignore(plan.root);
8
+ const preview = previewCursorignoreActions(current, plan.actions);
9
+ if (!preview) {
10
+ return {
11
+ plan,
12
+ dryRun: options.dryRun,
13
+ writtenFiles: [],
14
+ changedFiles: [],
15
+ };
16
+ }
17
+ if (!options.dryRun) {
18
+ await writeCursorignore(plan.root, preview.after);
19
+ }
20
+ return {
21
+ plan,
22
+ dryRun: options.dryRun,
23
+ writtenFiles: options.dryRun ? [] : [preview.targetRelativePath],
24
+ changedFiles: [preview.targetRelativePath],
25
+ };
26
+ }
27
+ export { previewCursorignoreActions, readCursorignore };
@@ -0,0 +1,7 @@
1
+ import type { Finding } from "../../types/index.js";
2
+ export declare function isSafeContextFixRule(ruleId: string): boolean;
3
+ /**
4
+ * Build a gitignore-style pattern that excludes the finding evidence path.
5
+ * Directories (generated-directory) get a trailing slash.
6
+ */
7
+ export declare function patternForFinding(finding: Finding): string | null;
@@ -0,0 +1,21 @@
1
+ const SAFE_CONTEXT_RULES = new Set(["context/generated-directory", "context/large-log-file"]);
2
+ export function isSafeContextFixRule(ruleId) {
3
+ return SAFE_CONTEXT_RULES.has(ruleId);
4
+ }
5
+ /**
6
+ * Build a gitignore-style pattern that excludes the finding evidence path.
7
+ * Directories (generated-directory) get a trailing slash.
8
+ */
9
+ export function patternForFinding(finding) {
10
+ const evidencePath = finding.evidence?.path?.replace(/\\/g, "/").replace(/^\/+/, "");
11
+ if (!evidencePath) {
12
+ return null;
13
+ }
14
+ if (finding.ruleId === "context/generated-directory") {
15
+ return evidencePath.endsWith("/") ? evidencePath : `${evidencePath}/`;
16
+ }
17
+ if (finding.ruleId === "context/large-log-file") {
18
+ return evidencePath;
19
+ }
20
+ return null;
21
+ }
@@ -0,0 +1,9 @@
1
+ import type { ScanResult } from "../../types/index.js";
2
+ import type { FixPlan } from "./types.js";
3
+ /**
4
+ * Build a fix plan from a scan result.
5
+ * Week 1: Cursor `.cursorignore` appends for safe context findings only.
6
+ */
7
+ export declare function buildFixPlan(result: ScanResult): Promise<FixPlan>;
8
+ /** Patterns that would be newly appended given current file contents. */
9
+ export declare function missingPatternsForCursorignore(currentContent: string | null, patterns: string[]): string[];