@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.
- package/CHANGELOG.md +53 -3
- package/README.md +72 -35
- package/dist/cli/commands/doctor.js +1 -1
- package/dist/cli/commands/explain.js +2 -2
- package/dist/cli/commands/fix.d.ts +7 -5
- package/dist/cli/commands/fix.js +57 -12
- package/dist/cli/commands/scan.js +4 -12
- package/dist/cli/commands/verify.d.ts +13 -0
- package/dist/cli/commands/verify.js +56 -0
- package/dist/cli/program.js +54 -31
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/core/fix/apply.d.ts +10 -0
- package/dist/core/fix/apply.js +27 -0
- package/dist/core/fix/patterns.d.ts +7 -0
- package/dist/core/fix/patterns.js +21 -0
- package/dist/core/fix/plan.d.ts +9 -0
- package/dist/core/fix/plan.js +140 -0
- package/dist/core/fix/render.d.ts +6 -0
- package/dist/core/fix/render.js +68 -0
- package/dist/core/fix/run.d.ts +15 -0
- package/dist/core/fix/run.js +54 -0
- package/dist/core/fix/types.d.ts +34 -0
- package/dist/core/fix/types.js +2 -0
- package/dist/core/fix/writers/cursorignore.d.ts +13 -0
- package/dist/core/fix/writers/cursorignore.js +74 -0
- package/dist/core/rules/context/generated-directory.js +10 -0
- package/dist/core/rules/instructions/missing-path-reference.js +90 -1
- package/dist/core/rules/path-kind.d.ts +14 -0
- package/dist/core/rules/path-kind.js +77 -0
- package/dist/core/rules/security/env-file-exposure.js +14 -14
- package/dist/core/rules/security/private-key-file.js +4 -0
- package/dist/core/scanner/scan.d.ts +1 -2
- package/dist/core/scanner/scan.js +8 -5
- package/dist/core/scoring/compute-scores.d.ts +6 -0
- package/dist/core/scoring/compute-scores.js +90 -0
- package/dist/core/scoring/placeholder.d.ts +3 -3
- package/dist/core/scoring/placeholder.js +3 -3
- package/dist/core/verify/compare.d.ts +29 -0
- package/dist/core/verify/compare.js +56 -0
- package/dist/core/verify/load-baseline.d.ts +15 -0
- package/dist/core/verify/load-baseline.js +71 -0
- package/dist/core/verify/verify.d.ts +26 -0
- package/dist/core/verify/verify.js +40 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +5 -0
- package/dist/reporters/terminal/report.js +7 -1
- package/dist/reporters/verify/json.d.ts +5 -0
- package/dist/reporters/verify/json.js +51 -0
- package/dist/reporters/verify/terminal.d.ts +2 -0
- package/dist/reporters/verify/terminal.js +45 -0
- package/dist/types/index.d.ts +3 -3
- 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
|
-
-
|
|
13
|
-
|
|
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.
|
|
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.
|
|
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
|

|
|
26
26
|
|
|
27
|
-
_Real scan of the included `insecure-agent-project` fixture using AgentDoctor v0.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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 . --
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
298
|
-
|
|
|
299
|
-
|
|
|
300
|
-
|
|
|
301
|
-
|
|
|
302
|
-
|
|
|
303
|
-
|
|
|
304
|
-
|
|
|
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/
|
|
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/
|
|
329
|
-
| [docs/
|
|
330
|
-
| [docs/
|
|
331
|
-
| [
|
|
332
|
-
| [
|
|
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("
|
|
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
|
-
? "
|
|
42
|
+
? " Yes for Cursor context exclusions (`agentdoctor fix`). Other agents may still need a manual step."
|
|
43
43
|
: rule.fixability === "review"
|
|
44
|
-
? "
|
|
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
|
-
|
|
4
|
-
*/
|
|
5
|
-
export declare function runFixCommand(options: {
|
|
2
|
+
export interface FixCommandOptions {
|
|
3
|
+
targetPath?: string;
|
|
6
4
|
dryRun?: boolean;
|
|
7
5
|
yes?: boolean;
|
|
8
|
-
}
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Safe Repository Mutation — apply allowlisted agent-config exclusions.
|
|
9
|
+
*/
|
|
10
|
+
export declare function runFixCommand(options: FixCommandOptions): Promise<ExitCode>;
|
package/dist/cli/commands/fix.js
CHANGED
|
@@ -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
|
|
11
|
+
* Safe Repository Mutation — apply allowlisted agent-config exclusions.
|
|
5
12
|
*/
|
|
6
13
|
export async function runFixCommand(options) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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.
|
|
28
|
-
if (
|
|
27
|
+
if (options.minScore !== undefined && result.scores !== null) {
|
|
28
|
+
if (result.scores.overall < options.minScore) {
|
|
29
29
|
if (!options.json) {
|
|
30
|
-
console.error(
|
|
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
|
+
}
|
package/dist/cli/program.js
CHANGED
|
@@ -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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
.
|
|
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
|
|
37
|
-
const code = await runScanCommand({
|
|
71
|
+
const code = await runFixCommand({
|
|
38
72
|
targetPath: resolveTargetArgument(pathArg),
|
|
39
|
-
|
|
40
|
-
|
|
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("
|
|
48
|
-
.description("
|
|
49
|
-
.argument("[path]", "Repository path
|
|
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
|
|
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("--
|
|
54
|
-
.
|
|
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
|
|
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")
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "0.
|
|
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
|
+
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([
|