@praneeth_54/agentdoctor 0.1.4-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 (53) hide show
  1. package/CHANGELOG.md +53 -3
  2. package/README.md +72 -35
  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/scan.js +4 -12
  8. package/dist/cli/commands/verify.d.ts +13 -0
  9. package/dist/cli/commands/verify.js +56 -0
  10. package/dist/cli/program.js +54 -31
  11. package/dist/constants.d.ts +1 -1
  12. package/dist/constants.js +1 -1
  13. package/dist/core/fix/apply.d.ts +10 -0
  14. package/dist/core/fix/apply.js +27 -0
  15. package/dist/core/fix/patterns.d.ts +7 -0
  16. package/dist/core/fix/patterns.js +21 -0
  17. package/dist/core/fix/plan.d.ts +9 -0
  18. package/dist/core/fix/plan.js +140 -0
  19. package/dist/core/fix/render.d.ts +6 -0
  20. package/dist/core/fix/render.js +68 -0
  21. package/dist/core/fix/run.d.ts +15 -0
  22. package/dist/core/fix/run.js +54 -0
  23. package/dist/core/fix/types.d.ts +34 -0
  24. package/dist/core/fix/types.js +2 -0
  25. package/dist/core/fix/writers/cursorignore.d.ts +13 -0
  26. package/dist/core/fix/writers/cursorignore.js +74 -0
  27. package/dist/core/rules/context/generated-directory.js +10 -0
  28. package/dist/core/rules/instructions/missing-path-reference.js +90 -1
  29. package/dist/core/rules/path-kind.d.ts +14 -0
  30. package/dist/core/rules/path-kind.js +77 -0
  31. package/dist/core/rules/security/env-file-exposure.js +14 -14
  32. package/dist/core/rules/security/private-key-file.js +4 -0
  33. package/dist/core/scanner/scan.d.ts +1 -2
  34. package/dist/core/scanner/scan.js +8 -5
  35. package/dist/core/scoring/compute-scores.d.ts +6 -0
  36. package/dist/core/scoring/compute-scores.js +90 -0
  37. package/dist/core/scoring/placeholder.d.ts +3 -3
  38. package/dist/core/scoring/placeholder.js +3 -3
  39. package/dist/core/verify/compare.d.ts +29 -0
  40. package/dist/core/verify/compare.js +56 -0
  41. package/dist/core/verify/load-baseline.d.ts +15 -0
  42. package/dist/core/verify/load-baseline.js +71 -0
  43. package/dist/core/verify/verify.d.ts +26 -0
  44. package/dist/core/verify/verify.js +40 -0
  45. package/dist/index.d.ts +8 -0
  46. package/dist/index.js +5 -0
  47. package/dist/reporters/terminal/report.js +7 -1
  48. package/dist/reporters/verify/json.d.ts +5 -0
  49. package/dist/reporters/verify/json.js +51 -0
  50. package/dist/reporters/verify/terminal.d.ts +2 -0
  51. package/dist/reporters/verify/terminal.js +45 -0
  52. package/dist/types/index.d.ts +3 -3
  53. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,10 +7,58 @@ 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
- - Deterministic readiness scoring
13
- - Safe automatic fixes
41
+ - GitHub Action score-gate inputs (deferred; see scoring.md v2+)
42
+
43
+ ## [0.2.0-beta] — 2026-08-02
44
+
45
+ Minor beta: deterministic readiness scoring and CLI `--min-score` enforcement.
46
+
47
+ ### Added
48
+
49
+ - Deterministic readiness scoring (v1): `scan()` populates `scoringAvailable: true` and
50
+ `scores` (`overall`, `categories`, `agents`) from post-dedupe findings
51
+ ([docs/scoring.md](docs/scoring.md))
52
+ - CLI `--min-score N` enforcement: exit code `1` when `scores.overall < N`
53
+ - `--ci` without `--min-score` remains report-only (exit `0` on successful scan)
54
+ - Scoring specification and compatibility / exit-code docs updated for shipped behavior
55
+
56
+ ### Compatibility
57
+
58
+ - No new JSON top-level fields (`scoringModel` / `scoreExplanation` deferred)
59
+ - Findings, rule IDs, and agent detection unchanged
60
+ - GitHub Action remains `--ci --json` report-only (no score-gate inputs)
61
+ - Default Action `version` input is `0.2.0-beta`
14
62
 
15
63
  ## [0.1.4-beta] — 2026-08-02
16
64
 
@@ -123,7 +171,9 @@ First public beta.
123
171
  - Not a complete secret scanner
124
172
  - Git “tracked secret” detection deferred
125
173
 
126
- [Unreleased]: https://github.com/pranee54/AgentDoctor/compare/v0.1.4-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
176
+ [0.2.0-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.2.0-beta
127
177
  [0.1.4-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.1.4-beta
128
178
  [0.1.3-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.1.3-beta
129
179
  [0.1.2-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.1.2-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`). Readiness scoring and 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`). Readiness scoring and automatic fixes are not availa
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.1.4-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.1.4-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.1.4-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.1.4-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.1.4-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.
@@ -281,36 +301,52 @@ dist-tag.
281
301
  Use JSON directly in other CI systems:
282
302
 
283
303
  ```bash
304
+ # Report-only (exit 0 even when findings exist; scores still in JSON)
284
305
  npx @praneeth_54/agentdoctor --ci --json
306
+
307
+ # Fail CI when overall readiness is below 70
308
+ npx @praneeth_54/agentdoctor --ci --json --min-score 70
285
309
  ```
286
310
 
287
- `--ci` runs non-interactively. Until readiness scoring ships, successful scans exit `0` even when findings exist, and `--min-score` is accepted but ignored.
311
+ `--ci` runs non-interactively and does **not** apply an implicit score threshold.
312
+ Use `--min-score N` (with or without `--ci`) to fail with exit code `1` when
313
+ `scores.overall < N`.
288
314
 
289
315
  Exit codes: [docs/exit-codes.md](docs/exit-codes.md). Compatibility promises: [docs/compatibility.md](docs/compatibility.md).
290
316
 
317
+ ### Readiness scoring
318
+
319
+ Scans populate `scoringAvailable: true` and a deterministic `scores` object
320
+ (overall, categories, agents). The terminal prints overall readiness; category and agent
321
+ scores are in JSON (`--json`).
322
+
323
+ `--min-score N` is enforced by the CLI. Details (weights, security caps, threshold rules,
324
+ and deferred v2 items): [docs/scoring.md](docs/scoring.md).
325
+
291
326
  ---
292
327
 
293
328
  ## Beta limitations
294
329
 
295
330
  Honest limits of the current public beta:
296
331
 
297
- | Limitation | Status |
298
- | ----------------------------------- | --------------------------------------------------------------- |
299
- | Readiness scoring | Not available (`scores` is `null`, `scoringAvailable: false`) |
300
- | `--min-score` | Accepted, ignored until scoring ships |
301
- | Automatic fixes (`agentdoctor fix`) | Stub only — does not modify files |
302
- | Secret-content scanning | Filename / config heuristics only |
303
- | Detection style | Intentionally conservative; false security findings are avoided |
304
- | Agent coverage | Cursor, Claude Code, Codex project configs |
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 |
305
341
 
306
- See [CHANGELOG.md](CHANGELOG.md) and [docs/release-notes-v0.1.4-beta.md](docs/release-notes-v0.1.4-beta.md).
342
+ See [CHANGELOG.md](CHANGELOG.md) and [docs/compatibility.md](docs/compatibility.md).
307
343
 
308
344
  ---
309
345
 
310
346
  ## Architecture
311
347
 
312
348
  ```text
313
- Discovery → Project detect → Agent adapters → Rule engine → Findings → Terminal / JSON
349
+ Discovery → Project detect → Agent adapters → Rule engine → Findings → Scores → Terminal / JSON
314
350
  ```
315
351
 
316
352
  Details: [docs/architecture.md](docs/architecture.md)
@@ -319,17 +355,18 @@ Details: [docs/architecture.md](docs/architecture.md)
319
355
 
320
356
  ## Documentation
321
357
 
322
- | Doc | Contents |
323
- | ------------------------------------------------------------------ | ------------------------------ |
324
- | [docs/README.md](docs/README.md) | Documentation index |
325
- | [docs/architecture.md](docs/architecture.md) | Scan pipeline |
326
- | [docs/rules.md](docs/rules.md) | Stable rule IDs |
327
- | [docs/exit-codes.md](docs/exit-codes.md) | Process exit codes |
328
- | [docs/compatibility.md](docs/compatibility.md) | Beta compatibility promises |
329
- | [docs/development.md](docs/development.md) | Local development |
330
- | [docs/github-launch-checklist.md](docs/github-launch-checklist.md) | GitHub About / topics / launch |
331
- | [ROADMAP.md](ROADMAP.md) | Near- and medium-term plans |
332
- | [CHANGELOG.md](CHANGELOG.md) | Release history |
358
+ | Doc | Contents |
359
+ | ------------------------------------------------------------------ | ------------------------------- |
360
+ | [docs/README.md](docs/README.md) | Documentation index |
361
+ | [docs/architecture.md](docs/architecture.md) | Scan pipeline |
362
+ | [docs/rules.md](docs/rules.md) | Stable rule IDs |
363
+ | [docs/exit-codes.md](docs/exit-codes.md) | Process exit codes |
364
+ | [docs/scoring.md](docs/scoring.md) | Readiness scoring specification |
365
+ | [docs/compatibility.md](docs/compatibility.md) | Beta compatibility promises |
366
+ | [docs/development.md](docs/development.md) | Local development |
367
+ | [docs/github-launch-checklist.md](docs/github-launch-checklist.md) | GitHub About / topics / launch |
368
+ | [ROADMAP.md](ROADMAP.md) | Near- and medium-term plans |
369
+ | [CHANGELOG.md](CHANGELOG.md) | Release history |
333
370
 
334
371
  ---
335
372
 
@@ -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("Readiness scoring and 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
  }
@@ -24,20 +24,12 @@ export async function runScanCommand(options) {
24
24
  verbose: options.verbose === true,
25
25
  }));
26
26
  }
27
- if (options.ci || options.minScore !== undefined) {
28
- if (!result.scoringAvailable || result.scores === null) {
27
+ if (options.minScore !== undefined && result.scores !== null) {
28
+ if (result.scores.overall < options.minScore) {
29
29
  if (!options.json) {
30
- console.error("Note: readiness scoring is not available in this release; --min-score was ignored.");
31
- }
32
- }
33
- else {
34
- const threshold = options.minScore ?? 0;
35
- if (result.scores.overall < threshold) {
36
- if (!options.json) {
37
- console.error(`\nCI check failed: overall score ${result.scores.overall} is below --min-score ${threshold}`);
38
- }
39
- return EXIT_CODES.ISSUES_OR_THRESHOLD;
30
+ console.error(`\nCI check failed: overall score ${result.scores.overall} is below --min-score ${options.minScore}`);
40
31
  }
32
+ return EXIT_CODES.ISSUES_OR_THRESHOLD;
41
33
  }
42
34
  }
43
35
  return EXIT_CODES.SUCCESS;
@@ -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; --min-score is ignored until scoring ships)", false)
33
- .option("--verbose", "Show timing and extra diagnostics", false)
34
- .option("--min-score <number>", "Fail when overall score is below this (no-op until scoring ships)", 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 (--min-score ignored until scoring ships)", 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)
53
- .option("--min-score <number>", "Fail when overall score is below this (no-op until scoring ships)", parseMinScore)
54
- .action(async (pathArg, options) => {
85
+ .option("--baseline <file>", "Prior scan JSON report (default: agentdoctor-report.json or .agentdoctor-baseline.json)")
86
+ .option("--min-score <number>", "Exit 1 when overall readiness score is below this (0-100)", parseMinScore)
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.1.4-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.1.4-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([