@praneeth_54/agentdoctor 0.3.0-beta → 1.0.0
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 +76 -1
- package/README.md +60 -30
- package/dist/agents/inspect.js +58 -51
- package/dist/cli/commands/explain.js +1 -1
- package/dist/cli/commands/fix.js +22 -6
- package/dist/cli/commands/scan.d.ts +5 -0
- package/dist/cli/commands/scan.js +25 -6
- package/dist/cli/commands/verify.d.ts +6 -0
- package/dist/cli/commands/verify.js +25 -11
- package/dist/cli/program.js +48 -7
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/core/fix/apply.d.ts +5 -1
- package/dist/core/fix/apply.js +34 -14
- package/dist/core/fix/plan.d.ts +1 -1
- package/dist/core/fix/plan.js +110 -3
- package/dist/core/fix/render.d.ts +2 -0
- package/dist/core/fix/render.js +57 -9
- package/dist/core/fix/run.js +5 -1
- package/dist/core/fix/types.d.ts +1 -1
- package/dist/core/fix/types.js +5 -1
- package/dist/core/fix/writers/claude-settings.d.ts +16 -0
- package/dist/core/fix/writers/claude-settings.js +100 -0
- package/dist/core/fix/writers/codex-config.d.ts +22 -0
- package/dist/core/fix/writers/codex-config.js +183 -0
- package/dist/core/fix/writers/cursorignore.js +2 -8
- package/dist/core/path-resolution/index.d.ts +5 -0
- package/dist/core/path-resolution/index.js +5 -0
- package/dist/core/path-resolution/prepare.d.ts +33 -0
- package/dist/core/path-resolution/prepare.js +98 -0
- package/dist/core/policy/evaluate.d.ts +44 -0
- package/dist/core/policy/evaluate.js +120 -0
- package/dist/core/rules/claude-deny.d.ts +8 -0
- package/dist/core/rules/claude-deny.js +39 -0
- package/dist/core/rules/codex-deny.d.ts +5 -0
- package/dist/core/rules/codex-deny.js +42 -0
- package/dist/core/rules/context/generated-directory.js +43 -9
- package/dist/core/rules/context/large-log-file.js +27 -10
- package/dist/core/rules/ignore.js +2 -5
- package/dist/core/rules/instructions/missing-path-reference.d.ts +5 -1
- package/dist/core/rules/instructions/missing-path-reference.js +24 -6
- package/dist/core/rules/path-kind.d.ts +15 -2
- package/dist/core/rules/path-kind.js +113 -10
- package/dist/core/rules/security/env-file-exposure.js +9 -24
- package/dist/core/rules/text-cache.js +46 -40
- package/dist/discovery/files.js +7 -2
- package/dist/discovery/log-like.d.ts +2 -0
- package/dist/discovery/log-like.js +7 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/reporters/github/annotations.d.ts +8 -0
- package/dist/reporters/github/annotations.js +42 -0
- package/dist/reporters/github/emit.d.ts +23 -0
- package/dist/reporters/github/emit.js +31 -0
- package/dist/reporters/github/summary.d.ts +20 -0
- package/dist/reporters/github/summary.js +111 -0
- package/dist/reporters/terminal/report.d.ts +5 -0
- package/dist/reporters/terminal/report.js +68 -5
- package/dist/reporters/verify/terminal.d.ts +4 -0
- package/dist/reporters/verify/terminal.js +26 -0
- package/dist/utils/fs.d.ts +5 -0
- package/dist/utils/fs.js +34 -0
- package/dist/utils/path.d.ts +5 -0
- package/dist/utils/path.js +16 -0
- package/package.json +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,80 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Notes
|
|
11
|
+
|
|
12
|
+
- Owner release sequence for `1.0.0`: `npm publish` → `git tag v1.0.0` → GitHub Release → then bump Action `version` default `0.3.0-beta` → `1.0.0` (only after npm serves `1.0.0`).
|
|
13
|
+
- Project Brain V1 remains a parallel laboratory under `src/core/understanding/brain` and is excluded from the published npm pack. Docs: [docs/project-brain.md](docs/project-brain.md).
|
|
14
|
+
|
|
15
|
+
## [1.0.0] — 2026-08-12
|
|
16
|
+
|
|
17
|
+
First production release: Scan → Fix → Verify → CI contract frozen for v1.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Claude Code safe-context Fix writer: `agentdoctor fix` appends allowlisted
|
|
22
|
+
`permissions.deny` Read rules to `.claude/settings.json` for
|
|
23
|
+
`context/generated-directory` and `context/large-log-file` when Claude Code is
|
|
24
|
+
configured (alongside existing Cursor `.cursorignore` fixes).
|
|
25
|
+
- Codex safe-context Fix writer: `agentdoctor fix` merges allowlisted filesystem
|
|
26
|
+
`deny` keys into `.codex/config.toml` permission profiles for the same safe
|
|
27
|
+
context findings when Codex is detected. Skips when `sandbox_mode` is set or
|
|
28
|
+
`default_permissions` selects a built-in `:…` profile.
|
|
29
|
+
- GitHub Action / CLI CI policy enforcement: `minimum-score` / `--min-score`,
|
|
30
|
+
`fail-on-severity` / `--fail-on-severity`, `fail-on-rule` / `--fail-on-rule`,
|
|
31
|
+
`fail-on-new` / `--fail-on-new`, `verify-baseline`, `json-output`, `summary` /
|
|
32
|
+
`--summary`, and `annotations` / `--annotations`. Action `version: workspace`
|
|
33
|
+
runs the checked-out `dist/cli` for local CI.
|
|
34
|
+
- Guided Next steps on failed `scan` / `fix` / `verify` terminal output, and on
|
|
35
|
+
GitHub Step Summary when a policy gate fails — shortest path back to green
|
|
36
|
+
(reproduce → fix or explain → verify).
|
|
37
|
+
- Windows CI quality job (Node 20) and Windows-safe Fix/Action overwrite writes.
|
|
38
|
+
- Action smoke coverage for `fail-on-rule`, `verify-baseline`, and baseline
|
|
39
|
+
symlink escape rejection.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- `instructions/missing-path-reference` also resolves non-`./` paths relative to the
|
|
44
|
+
instruction file directory (monorepo package docs), while root-level instruction
|
|
45
|
+
files still require repository-root paths. Corpus-100: 121 → 88 findings for this
|
|
46
|
+
rule (−33); other rules unchanged.
|
|
47
|
+
- `context/generated-directory` and `context/large-log-file` honor Claude Code Read
|
|
48
|
+
deny exclusions when computing `affectedAgents`, so Fix → Verify clears Claude
|
|
49
|
+
context findings after a deny rule is applied.
|
|
50
|
+
- The same rules honor Codex filesystem deny keys in `.codex/config.toml` when
|
|
51
|
+
computing `affectedAgents`.
|
|
52
|
+
- Action writes the JSON report even when a policy gate fails (exit `1`), so
|
|
53
|
+
artifacts remain available for triage.
|
|
54
|
+
- `--min-score` / Action `minimum-score` fail when no supported agents are
|
|
55
|
+
configured (`agentSecurityAnalysis: limited`) instead of passing on a vacuous 100.
|
|
56
|
+
- Terminal readiness prints `n/a` when analysis is limited (no agents).
|
|
57
|
+
- Agentless first scan no longer shows a green “No agent-configuration findings”
|
|
58
|
+
success line; it tells the user to add Cursor / Claude Code / Codex config and
|
|
59
|
+
re-run.
|
|
60
|
+
- Invalid `--min-score` values exit `2` (usage) instead of `3` (internal).
|
|
61
|
+
- Codex Fix refuses invalid `.codex/config.toml` during planning (same as Claude
|
|
62
|
+
invalid JSON) instead of silently skipping Codex and writing Cursor-only fixes.
|
|
63
|
+
- Codex Fix refuses unrecognizable / invalid `.codex/config.toml` content instead of
|
|
64
|
+
appending permission profiles into garbage TOML.
|
|
65
|
+
- `agentdoctor fix` exits `2` when confirmation is cancelled (non-TTY without `--yes`)
|
|
66
|
+
or when Fix refuses invalid settings / cannot write due to permissions.
|
|
67
|
+
- `scan --ci` now fails (exit `1`) when any **critical** finding exists. Omit `--ci` for
|
|
68
|
+
report-only scans. The GitHub Action stays report-only unless policy inputs are set
|
|
69
|
+
(it no longer passes a bare `--ci`).
|
|
70
|
+
- Discovery keeps oversized log/dump-like paths as size metadata so
|
|
71
|
+
`context/large-log-file` flags files above the content-read limit (previously silent
|
|
72
|
+
false negatives for the largest logs).
|
|
73
|
+
- Action `verify-baseline` re-checks workspace containment after `realpath` so a
|
|
74
|
+
workspace-relative symlink cannot escape to an outside file.
|
|
75
|
+
- Fix writers and Action report overwrite use Windows-safe replace (rename cannot
|
|
76
|
+
overwrite an existing destination on Windows).
|
|
77
|
+
|
|
78
|
+
### Compatibility
|
|
79
|
+
|
|
80
|
+
- CLI + JSON + rule ID contracts frozen for v1 (see [docs/compatibility.md](docs/compatibility.md))
|
|
81
|
+
- Action `version` input default remains `0.3.0-beta` until `1.0.0` is published to npm;
|
|
82
|
+
pin `1.0.0` or use `version: workspace` after the release is cut
|
|
83
|
+
|
|
10
84
|
## [0.3.0-beta] — 2026-08-07
|
|
11
85
|
|
|
12
86
|
Minor beta: completes the Scan → Fix → Verify CLI loop and corrects release-facing honesty.
|
|
@@ -171,7 +245,8 @@ First public beta.
|
|
|
171
245
|
- Not a complete secret scanner
|
|
172
246
|
- Git “tracked secret” detection deferred
|
|
173
247
|
|
|
174
|
-
[Unreleased]: https://github.com/pranee54/AgentDoctor/compare/
|
|
248
|
+
[Unreleased]: https://github.com/pranee54/AgentDoctor/compare/v1.0.0...HEAD
|
|
249
|
+
[1.0.0]: https://github.com/pranee54/AgentDoctor/releases/tag/v1.0.0
|
|
175
250
|
[0.3.0-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.3.0-beta
|
|
176
251
|
[0.2.0-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.2.0-beta
|
|
177
252
|
[0.1.4-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.1.4-beta
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AgentDoctor
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@praneeth_54/agentdoctor)
|
|
3
|
+
[](https://www.npmjs.com/package/@praneeth_54/agentdoctor)
|
|
4
4
|
[](https://www.npmjs.com/package/@praneeth_54/agentdoctor)
|
|
5
5
|
[](https://github.com/pranee54/AgentDoctor/actions)
|
|
6
6
|
[](https://nodejs.org)
|
|
@@ -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
|
|
19
|
+
Public release (`1.0.0`). Scan → Fix → Verify → CI. Deterministic scores in the terminal and JSON.
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -24,12 +24,12 @@ Public beta (`0.3.0-beta`). Scan → Fix → Verify. Deterministic scores in the
|
|
|
24
24
|
|
|
25
25
|

|
|
26
26
|
|
|
27
|
-
_Real scan of the included `insecure-agent-project` fixture using AgentDoctor
|
|
27
|
+
_Real scan of the included `insecure-agent-project` fixture using AgentDoctor v1.0.0._
|
|
28
28
|
|
|
29
29
|
```text
|
|
30
30
|
$ npx @praneeth_54/agentdoctor
|
|
31
31
|
|
|
32
|
-
🩺 AgentDoctor
|
|
32
|
+
🩺 AgentDoctor v1.0.0
|
|
33
33
|
|
|
34
34
|
Scanning repository...
|
|
35
35
|
|
|
@@ -100,7 +100,7 @@ Manually reviewing all of that across Cursor, Claude Code, and Codex is slow and
|
|
|
100
100
|
| ESLint | Source code |
|
|
101
101
|
| **AgentDoctor** | **AI coding agent environments** |
|
|
102
102
|
|
|
103
|
-
AgentDoctor analyzes configuration. It does not run agents or call an LLM. `agentdoctor fix` may append safe Cursor
|
|
103
|
+
AgentDoctor analyzes configuration. It does not run agents or call an LLM. `agentdoctor fix` may append safe context exclusions (Cursor `.cursorignore`, Claude Code Read deny rules, and Codex filesystem deny keys); it does not rewrite secrets, credentials, or security modes such as `bypassPermissions`.
|
|
104
104
|
|
|
105
105
|
---
|
|
106
106
|
|
|
@@ -142,7 +142,7 @@ You get deterministic findings with:
|
|
|
142
142
|
- conservative recommendations
|
|
143
143
|
- readiness score (`scores.overall` in JSON; overall line in the terminal)
|
|
144
144
|
|
|
145
|
-
Safe
|
|
145
|
+
Safe context exclusions (Cursor / Claude Code / Codex) can be applied with `agentdoctor fix`. Security and review findings stay manual — Fix explains why and does not invent unsafe edits.
|
|
146
146
|
|
|
147
147
|
---
|
|
148
148
|
|
|
@@ -154,10 +154,10 @@ Safe Cursor context exclusions can be applied with `agentdoctor fix`. Security a
|
|
|
154
154
|
npx @praneeth_54/agentdoctor
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
-
Pin a
|
|
157
|
+
Pin a version when you need a fixed install:
|
|
158
158
|
|
|
159
159
|
```bash
|
|
160
|
-
npx @praneeth_54/agentdoctor@0.
|
|
160
|
+
npx @praneeth_54/agentdoctor@1.0.0
|
|
161
161
|
```
|
|
162
162
|
|
|
163
163
|
### Global (optional)
|
|
@@ -173,7 +173,7 @@ agentdoctor
|
|
|
173
173
|
# 1. Scan (save a baseline for Verify)
|
|
174
174
|
npx @praneeth_54/agentdoctor scan . --json > agentdoctor-report.json
|
|
175
175
|
|
|
176
|
-
# 2. Fix safe
|
|
176
|
+
# 2. Fix safe context exclusions (preview first with --dry-run)
|
|
177
177
|
npx @praneeth_54/agentdoctor fix . --dry-run
|
|
178
178
|
npx @praneeth_54/agentdoctor fix . -y
|
|
179
179
|
|
|
@@ -181,7 +181,11 @@ npx @praneeth_54/agentdoctor fix . -y
|
|
|
181
181
|
npx @praneeth_54/agentdoctor verify . --baseline agentdoctor-report.json
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
`fix`
|
|
184
|
+
`fix` writes safe context exclusions for Cursor (`.cursorignore`), Claude Code
|
|
185
|
+
(`permissions.deny` Read rules in `.claude/settings.json`), and Codex (filesystem `deny`
|
|
186
|
+
keys under a permissions profile in `.codex/config.toml`) for findings such as unignored
|
|
187
|
+
`build/` or large logs. Review/manual security findings are listed as skipped — address those
|
|
188
|
+
yourself, then re-run `verify`.
|
|
185
189
|
|
|
186
190
|
### Common commands
|
|
187
191
|
|
|
@@ -279,10 +283,14 @@ steps:
|
|
|
279
283
|
|
|
280
284
|
- name: Audit coding-agent configuration
|
|
281
285
|
id: agentdoctor
|
|
282
|
-
uses: pranee54/AgentDoctor@
|
|
286
|
+
uses: pranee54/AgentDoctor@v1.0.0
|
|
283
287
|
with:
|
|
284
288
|
path: .
|
|
289
|
+
version: "1.0.0"
|
|
285
290
|
output-file: agentdoctor-report.json
|
|
291
|
+
minimum-score: "70"
|
|
292
|
+
fail-on-severity: critical
|
|
293
|
+
summary: "true"
|
|
286
294
|
|
|
287
295
|
- name: Upload AgentDoctor report
|
|
288
296
|
uses: actions/upload-artifact@v4
|
|
@@ -291,10 +299,13 @@ steps:
|
|
|
291
299
|
path: ${{ steps.agentdoctor.outputs.report-path }}
|
|
292
300
|
```
|
|
293
301
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
302
|
+
Policy inputs: `minimum-score`, `fail-on-severity`, `fail-on-rule`, `fail-on-new`,
|
|
303
|
+
`verify-baseline`, `summary`, `annotations`. The Action stays report-only until you set a
|
|
304
|
+
policy input. Explicitly set `version: "1.0.0"` after npm publish (the Action default remains
|
|
305
|
+
`0.3.0-beta` until that post-publish bump). For local CI against this repo, use
|
|
306
|
+
`version: workspace` after `npm run build`. The action installs `@praneeth_54/agentdoctor`,
|
|
307
|
+
runs scan (or `verify` when `verify-baseline` is set) with `--json`, and writes the report
|
|
308
|
+
inside the workspace.
|
|
298
309
|
|
|
299
310
|
### CLI
|
|
300
311
|
|
|
@@ -302,15 +313,21 @@ Use JSON directly in other CI systems:
|
|
|
302
313
|
|
|
303
314
|
```bash
|
|
304
315
|
# Report-only (exit 0 even when findings exist; scores still in JSON)
|
|
316
|
+
npx @praneeth_54/agentdoctor --json
|
|
317
|
+
|
|
318
|
+
# Fail when any critical finding exists
|
|
305
319
|
npx @praneeth_54/agentdoctor --ci --json
|
|
306
320
|
|
|
307
|
-
# Fail
|
|
321
|
+
# Fail when overall readiness is below 70 (with --ci also fails on criticals)
|
|
308
322
|
npx @praneeth_54/agentdoctor --ci --json --min-score 70
|
|
323
|
+
|
|
324
|
+
# Fail on warning-or-higher (overrides the default critical gate from --ci)
|
|
325
|
+
npx @praneeth_54/agentdoctor --ci --json --fail-on-severity warning
|
|
309
326
|
```
|
|
310
327
|
|
|
311
|
-
`--ci`
|
|
312
|
-
|
|
313
|
-
`
|
|
328
|
+
`--ci` fails when any **critical** finding exists. Override the severity floor with
|
|
329
|
+
`--fail-on-severity`, and use `--min-score` / `--fail-on-rule` for additional gates.
|
|
330
|
+
Omit `--ci` for report-only JSON (exit `0` even when findings exist).
|
|
314
331
|
|
|
315
332
|
Exit codes: [docs/exit-codes.md](docs/exit-codes.md). Compatibility promises: [docs/compatibility.md](docs/compatibility.md).
|
|
316
333
|
|
|
@@ -325,19 +342,18 @@ and deferred v2 items): [docs/scoring.md](docs/scoring.md).
|
|
|
325
342
|
|
|
326
343
|
---
|
|
327
344
|
|
|
328
|
-
##
|
|
345
|
+
## Known limitations
|
|
329
346
|
|
|
330
|
-
Honest limits of
|
|
347
|
+
Honest limits of v1:
|
|
331
348
|
|
|
332
|
-
| Limitation | Status
|
|
333
|
-
| --------------------------- |
|
|
334
|
-
| Automatic fixes | Safe Cursor
|
|
335
|
-
|
|
|
336
|
-
|
|
|
337
|
-
|
|
|
338
|
-
|
|
|
339
|
-
|
|
|
340
|
-
| Agent coverage | Cursor, Claude Code, Codex project configs |
|
|
349
|
+
| Limitation | Status |
|
|
350
|
+
| --------------------------- | ------------------------------------------------------------------------------- |
|
|
351
|
+
| Automatic fixes | Safe Cursor / Claude Code / Codex context exclusions only |
|
|
352
|
+
| Security findings | Review/manual — Fix does not rewrite secrets or security modes |
|
|
353
|
+
| Secret-content scanning | Filename / config heuristics only |
|
|
354
|
+
| Detection style | Intentionally conservative; false security findings are avoided |
|
|
355
|
+
| Agent coverage | Cursor, Claude Code, Codex project configs |
|
|
356
|
+
| Missing-path residual noise | Broad path-lattice expansion deferred; instruction-directory resolution shipped |
|
|
341
357
|
|
|
342
358
|
See [CHANGELOG.md](CHANGELOG.md) and [docs/compatibility.md](docs/compatibility.md).
|
|
343
359
|
|
|
@@ -388,6 +404,20 @@ node dist/cli/index.js ./fixtures/clean-configured-project
|
|
|
388
404
|
|
|
389
405
|
---
|
|
390
406
|
|
|
407
|
+
## Project Brain (separate laboratory capability)
|
|
408
|
+
|
|
409
|
+
AgentDoctor V1 ships the **safety** product: Scan → Fix → Verify → Policy → CI.
|
|
410
|
+
|
|
411
|
+
A parallel **Project Brain** engineering layer lives under `src/core/understanding/` (durable local claims, evidence, snapshots, query/trace/delta). It is:
|
|
412
|
+
|
|
413
|
+
- **not** part of the published npm package (`tsconfig.build` excludes it)
|
|
414
|
+
- **not** wired into the public CLI
|
|
415
|
+
- documented in [docs/project-brain.md](docs/project-brain.md)
|
|
416
|
+
|
|
417
|
+
See [PROJECT_AUDIT.txt](PROJECT_AUDIT.txt) and [RELEASE_CHECKLIST.txt](RELEASE_CHECKLIST.txt).
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
391
421
|
## Next steps
|
|
392
422
|
|
|
393
423
|
- **Try it:** `npx @praneeth_54/agentdoctor`
|
package/dist/agents/inspect.js
CHANGED
|
@@ -23,11 +23,29 @@ export async function inspectRepoFile(root, relativePath, maxFileSizeBytes = DEF
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
try {
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
26
|
+
// Open first, then inspect via the same handle (avoids TOCTOU with prior lstat/stat).
|
|
27
|
+
const handle = await fs.open(absolutePath, "r");
|
|
28
|
+
try {
|
|
29
|
+
const lstat = await fs.lstat(absolutePath);
|
|
30
|
+
const isSymlink = lstat.isSymbolicLink();
|
|
31
|
+
if (isSymlink) {
|
|
32
|
+
const real = await fs.realpath(absolutePath);
|
|
33
|
+
if (!isPathInsideRoot(root, real)) {
|
|
34
|
+
return {
|
|
35
|
+
relativePath: normalizedRelative,
|
|
36
|
+
absolutePath,
|
|
37
|
+
exists: true,
|
|
38
|
+
readable: false,
|
|
39
|
+
sizeBytes: 0,
|
|
40
|
+
empty: true,
|
|
41
|
+
isSymlink: true,
|
|
42
|
+
text: null,
|
|
43
|
+
error: "Symlink target is outside repository root",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const stat = await handle.stat();
|
|
48
|
+
if (!stat.isFile()) {
|
|
31
49
|
return {
|
|
32
50
|
relativePath: normalizedRelative,
|
|
33
51
|
absolutePath,
|
|
@@ -35,64 +53,53 @@ export async function inspectRepoFile(root, relativePath, maxFileSizeBytes = DEF
|
|
|
35
53
|
readable: false,
|
|
36
54
|
sizeBytes: 0,
|
|
37
55
|
empty: true,
|
|
38
|
-
isSymlink
|
|
56
|
+
isSymlink,
|
|
39
57
|
text: null,
|
|
40
|
-
error: "
|
|
58
|
+
error: "Not a regular file",
|
|
41
59
|
};
|
|
42
60
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
const sizeBytes = Number(stat.size);
|
|
62
|
+
if (sizeBytes === 0) {
|
|
63
|
+
return {
|
|
64
|
+
relativePath: normalizedRelative,
|
|
65
|
+
absolutePath,
|
|
66
|
+
exists: true,
|
|
67
|
+
readable: true,
|
|
68
|
+
sizeBytes: 0,
|
|
69
|
+
empty: true,
|
|
70
|
+
isSymlink,
|
|
71
|
+
text: "",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (sizeBytes > maxFileSizeBytes) {
|
|
75
|
+
return {
|
|
76
|
+
relativePath: normalizedRelative,
|
|
77
|
+
absolutePath,
|
|
78
|
+
exists: true,
|
|
79
|
+
readable: false,
|
|
80
|
+
sizeBytes,
|
|
81
|
+
empty: false,
|
|
82
|
+
isSymlink,
|
|
83
|
+
text: null,
|
|
84
|
+
error: "File exceeds max size limit",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const text = await handle.readFile("utf8");
|
|
88
|
+
const trimmedEmpty = text.trim().length === 0;
|
|
60
89
|
return {
|
|
61
90
|
relativePath: normalizedRelative,
|
|
62
91
|
absolutePath,
|
|
63
92
|
exists: true,
|
|
64
93
|
readable: true,
|
|
65
|
-
sizeBytes: 0,
|
|
66
|
-
empty: true,
|
|
67
|
-
isSymlink,
|
|
68
|
-
text: "",
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
if (sizeBytes > maxFileSizeBytes) {
|
|
72
|
-
return {
|
|
73
|
-
relativePath: normalizedRelative,
|
|
74
|
-
absolutePath,
|
|
75
|
-
exists: true,
|
|
76
|
-
readable: false,
|
|
77
94
|
sizeBytes,
|
|
78
|
-
empty:
|
|
95
|
+
empty: trimmedEmpty,
|
|
79
96
|
isSymlink,
|
|
80
|
-
text
|
|
81
|
-
error: "File exceeds max size limit",
|
|
97
|
+
text,
|
|
82
98
|
};
|
|
83
99
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
relativePath: normalizedRelative,
|
|
88
|
-
absolutePath,
|
|
89
|
-
exists: true,
|
|
90
|
-
readable: true,
|
|
91
|
-
sizeBytes,
|
|
92
|
-
empty: trimmedEmpty,
|
|
93
|
-
isSymlink,
|
|
94
|
-
text,
|
|
95
|
-
};
|
|
100
|
+
finally {
|
|
101
|
+
await handle.close();
|
|
102
|
+
}
|
|
96
103
|
}
|
|
97
104
|
catch (error) {
|
|
98
105
|
const message = error instanceof Error ? error.message : "Unable to read file";
|
|
@@ -39,7 +39,7 @@ 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
|
-
? " Yes for Cursor
|
|
42
|
+
? " Yes for Cursor (`.cursorignore`), Claude Code (Read deny in `.claude/settings.json`), and Codex (filesystem deny in `.codex/config.toml`)."
|
|
43
43
|
: rule.fixability === "review"
|
|
44
44
|
? " No — requires human review. Fix reports why and leaves the file unchanged."
|
|
45
45
|
: rule.fixability === "manual"
|
package/dist/cli/commands/fix.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EXIT_CODES } from "../../types/index.js";
|
|
2
2
|
import { isDirectory } from "../../utils/fs.js";
|
|
3
3
|
import { resolveRepoRoot } from "../../utils/path.js";
|
|
4
|
-
import { applyFixPlan, readCursorignore } from "../../core/fix/apply.js";
|
|
4
|
+
import { applyFixPlan, readClaudeSettings, readCodexConfig, readCursorignore, } from "../../core/fix/apply.js";
|
|
5
5
|
import { buildFixPlan } from "../../core/fix/plan.js";
|
|
6
6
|
import { renderFixPlanTerminal } from "../../core/fix/render.js";
|
|
7
7
|
import { runFix } from "../../core/fix/run.js";
|
|
@@ -23,10 +23,14 @@ export async function runFixCommand(options) {
|
|
|
23
23
|
const result = await scan({ cwd: target });
|
|
24
24
|
const plan = await buildFixPlan(result);
|
|
25
25
|
const cursorContent = await readCursorignore(plan.root);
|
|
26
|
+
const claudeSettingsContent = await readClaudeSettings(plan.root);
|
|
27
|
+
const codexConfigContent = await readCodexConfig(plan.root);
|
|
26
28
|
const applyResult = await applyFixPlan(plan, { dryRun: true });
|
|
27
29
|
process.stdout.write(renderFixPlanTerminal(plan, {
|
|
28
30
|
dryRun: true,
|
|
29
31
|
cursorContent,
|
|
32
|
+
claudeSettingsContent,
|
|
33
|
+
codexConfigContent,
|
|
30
34
|
applyResult,
|
|
31
35
|
}));
|
|
32
36
|
return EXIT_CODES.SUCCESS;
|
|
@@ -38,27 +42,39 @@ export async function runFixCommand(options) {
|
|
|
38
42
|
});
|
|
39
43
|
if (cancelled) {
|
|
40
44
|
process.stdout.write("\n Cancelled. No files were modified.\n\n");
|
|
41
|
-
return EXIT_CODES.
|
|
45
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
42
46
|
}
|
|
43
47
|
const cursorContentAfter = await readCursorignore(plan.root);
|
|
48
|
+
const claudeSettingsAfter = await readClaudeSettings(plan.root);
|
|
49
|
+
const codexConfigAfter = await readCodexConfig(plan.root);
|
|
44
50
|
process.stdout.write(renderFixPlanTerminal(plan, {
|
|
45
51
|
dryRun: false,
|
|
46
52
|
cursorContent: cursorContentAfter,
|
|
53
|
+
claudeSettingsContent: claudeSettingsAfter,
|
|
54
|
+
codexConfigContent: codexConfigAfter,
|
|
47
55
|
applyResult,
|
|
48
56
|
}));
|
|
49
|
-
// After apply, show a short re-scan hint / delta for Cursor-fixable rules
|
|
50
57
|
if (applyResult.writtenFiles.length > 0) {
|
|
51
58
|
const after = await scan({ cwd: target });
|
|
52
59
|
const remainingSafeContext = after.findings.filter((f) => f.fixability === "safe" &&
|
|
53
|
-
(f.ruleId === "context/generated-directory" || f.ruleId === "context/large-log-file")
|
|
54
|
-
|
|
55
|
-
process.stdout.write(colors.dim(` Re-scan: ${remainingSafeContext.length} Cursor-related safe context finding(s) remain.\n\n`));
|
|
60
|
+
(f.ruleId === "context/generated-directory" || f.ruleId === "context/large-log-file"));
|
|
61
|
+
process.stdout.write(colors.dim(` Re-scan: ${remainingSafeContext.length} safe context finding(s) remain.\n\n`));
|
|
56
62
|
}
|
|
57
63
|
return EXIT_CODES.SUCCESS;
|
|
58
64
|
}
|
|
59
65
|
catch (error) {
|
|
60
66
|
const message = error instanceof Error ? error.message : String(error);
|
|
61
67
|
console.error(`Error: ${message}`);
|
|
68
|
+
if (isFixConfigOrPermissionError(message)) {
|
|
69
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
70
|
+
}
|
|
62
71
|
return EXIT_CODES.INTERNAL_ERROR;
|
|
63
72
|
}
|
|
64
73
|
}
|
|
74
|
+
function isFixConfigOrPermissionError(message) {
|
|
75
|
+
return (message.includes("refusing") ||
|
|
76
|
+
message.includes("not valid JSON") ||
|
|
77
|
+
message.includes("EACCES") ||
|
|
78
|
+
message.includes("EPERM") ||
|
|
79
|
+
message.includes("permission denied"));
|
|
80
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PolicyOptions } from "../../core/policy/evaluate.js";
|
|
1
2
|
import { type ExitCode } from "../../types/index.js";
|
|
2
3
|
export interface ScanCommandOptions {
|
|
3
4
|
targetPath?: string;
|
|
@@ -5,6 +6,10 @@ export interface ScanCommandOptions {
|
|
|
5
6
|
ci?: boolean;
|
|
6
7
|
verbose?: boolean;
|
|
7
8
|
minScore?: number;
|
|
9
|
+
failOnSeverity?: PolicyOptions["failOnSeverity"];
|
|
10
|
+
failOnRules?: string[];
|
|
11
|
+
summary?: boolean;
|
|
12
|
+
annotations?: boolean;
|
|
8
13
|
}
|
|
9
14
|
export declare function runScanCommand(options: ScanCommandOptions): Promise<ExitCode>;
|
|
10
15
|
export declare function resolveTargetArgument(pathArg: string | undefined, cwd?: string): string;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { evaluateScanPolicy } from "../../core/policy/evaluate.js";
|
|
2
3
|
import { scan } from "../../core/scanner/scan.js";
|
|
4
|
+
import { emitGithubReports } from "../../reporters/github/emit.js";
|
|
3
5
|
import { renderJsonReport } from "../../reporters/json/report.js";
|
|
4
6
|
import { renderTerminalReport } from "../../reporters/terminal/report.js";
|
|
5
7
|
import { EXIT_CODES } from "../../types/index.js";
|
|
6
|
-
import { resolveRepoRoot } from "../../utils/path.js";
|
|
7
8
|
import { isDirectory } from "../../utils/fs.js";
|
|
9
|
+
import { resolveRepoRoot } from "../../utils/path.js";
|
|
8
10
|
export async function runScanCommand(options) {
|
|
9
11
|
const target = resolveRepoRoot(options.targetPath ?? process.cwd());
|
|
10
12
|
if (!(await isDirectory(target))) {
|
|
@@ -24,13 +26,30 @@ export async function runScanCommand(options) {
|
|
|
24
26
|
verbose: options.verbose === true,
|
|
25
27
|
}));
|
|
26
28
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const failOnSeverity = options.failOnSeverity ?? (options.ci === true ? "critical" : undefined);
|
|
30
|
+
const policy = {
|
|
31
|
+
...(options.minScore !== undefined ? { minimumScore: options.minScore } : {}),
|
|
32
|
+
...(failOnSeverity !== undefined ? { failOnSeverity } : {}),
|
|
33
|
+
...(options.failOnRules && options.failOnRules.length > 0
|
|
34
|
+
? { failOnRules: options.failOnRules }
|
|
35
|
+
: {}),
|
|
36
|
+
};
|
|
37
|
+
const violations = evaluateScanPolicy(result, policy);
|
|
38
|
+
await emitGithubReports({
|
|
39
|
+
mode: "scan",
|
|
40
|
+
findings: result.findings,
|
|
41
|
+
overallScore: result.scores?.overall ?? null,
|
|
42
|
+
violations,
|
|
43
|
+
summary: options.summary === true,
|
|
44
|
+
annotations: options.annotations === true,
|
|
45
|
+
});
|
|
46
|
+
if (violations.length > 0) {
|
|
47
|
+
if (!options.json) {
|
|
48
|
+
for (const violation of violations) {
|
|
49
|
+
console.error(`\n${violation.message}`);
|
|
31
50
|
}
|
|
32
|
-
return EXIT_CODES.ISSUES_OR_THRESHOLD;
|
|
33
51
|
}
|
|
52
|
+
return EXIT_CODES.ISSUES_OR_THRESHOLD;
|
|
34
53
|
}
|
|
35
54
|
return EXIT_CODES.SUCCESS;
|
|
36
55
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PolicyOptions } from "../../core/policy/evaluate.js";
|
|
1
2
|
import { type ExitCode } from "../../types/index.js";
|
|
2
3
|
export interface VerifyCommandOptions {
|
|
3
4
|
targetPath?: string;
|
|
@@ -6,6 +7,11 @@ export interface VerifyCommandOptions {
|
|
|
6
7
|
ci?: boolean;
|
|
7
8
|
verbose?: boolean;
|
|
8
9
|
minScore?: number;
|
|
10
|
+
failOnSeverity?: PolicyOptions["failOnSeverity"];
|
|
11
|
+
failOnRules?: string[];
|
|
12
|
+
failOnNew?: boolean;
|
|
13
|
+
summary?: boolean;
|
|
14
|
+
annotations?: boolean;
|
|
9
15
|
}
|
|
10
16
|
/**
|
|
11
17
|
* Verify: re-scan after Fix and compare against a prior scan JSON baseline.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { evaluateVerifyPolicy } from "../../core/policy/evaluate.js";
|
|
1
2
|
import { verify } from "../../core/verify/verify.js";
|
|
3
|
+
import { emitGithubReports } from "../../reporters/github/emit.js";
|
|
2
4
|
import { renderVerifyJsonReport } from "../../reporters/verify/json.js";
|
|
3
5
|
import { renderVerifyTerminalReport } from "../../reporters/verify/terminal.js";
|
|
4
6
|
import { EXIT_CODES } from "../../types/index.js";
|
|
@@ -25,18 +27,30 @@ export async function runVerifyCommand(options) {
|
|
|
25
27
|
else {
|
|
26
28
|
process.stdout.write(renderVerifyTerminalReport(result, options.verbose === true));
|
|
27
29
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
const failOnNew = options.failOnNew === true || options.ci === true;
|
|
31
|
+
const policy = {
|
|
32
|
+
...(options.minScore !== undefined ? { minimumScore: options.minScore } : {}),
|
|
33
|
+
...(options.failOnSeverity !== undefined ? { failOnSeverity: options.failOnSeverity } : {}),
|
|
34
|
+
...(options.failOnRules && options.failOnRules.length > 0
|
|
35
|
+
? { failOnRules: options.failOnRules }
|
|
36
|
+
: {}),
|
|
37
|
+
...(failOnNew ? { failOnNew: true } : {}),
|
|
38
|
+
};
|
|
39
|
+
const violations = evaluateVerifyPolicy(result, policy);
|
|
40
|
+
await emitGithubReports({
|
|
41
|
+
mode: "verify",
|
|
42
|
+
findings: result.after.findings,
|
|
43
|
+
overallScore: result.scores?.overall ?? null,
|
|
44
|
+
violations,
|
|
45
|
+
verifySummary: result.summary,
|
|
46
|
+
summary: options.summary === true,
|
|
47
|
+
annotations: options.annotations === true,
|
|
48
|
+
});
|
|
49
|
+
if (violations.length > 0) {
|
|
38
50
|
if (!options.json) {
|
|
39
|
-
|
|
51
|
+
for (const violation of violations) {
|
|
52
|
+
console.error(`\n${violation.message}`);
|
|
53
|
+
}
|
|
40
54
|
}
|
|
41
55
|
return EXIT_CODES.ISSUES_OR_THRESHOLD;
|
|
42
56
|
}
|