@praneeth_54/agentdoctor 0.2.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +107 -3
  2. package/README.md +86 -35
  3. package/dist/agents/inspect.js +58 -51
  4. package/dist/cli/commands/doctor.js +1 -1
  5. package/dist/cli/commands/explain.js +2 -2
  6. package/dist/cli/commands/fix.d.ts +7 -5
  7. package/dist/cli/commands/fix.js +73 -12
  8. package/dist/cli/commands/scan.d.ts +5 -0
  9. package/dist/cli/commands/scan.js +25 -6
  10. package/dist/cli/commands/verify.d.ts +19 -0
  11. package/dist/cli/commands/verify.js +70 -0
  12. package/dist/cli/program.js +97 -33
  13. package/dist/constants.d.ts +1 -1
  14. package/dist/constants.js +1 -1
  15. package/dist/core/fix/apply.d.ts +14 -0
  16. package/dist/core/fix/apply.js +47 -0
  17. package/dist/core/fix/patterns.d.ts +7 -0
  18. package/dist/core/fix/patterns.js +21 -0
  19. package/dist/core/fix/plan.d.ts +9 -0
  20. package/dist/core/fix/plan.js +247 -0
  21. package/dist/core/fix/render.d.ts +8 -0
  22. package/dist/core/fix/render.js +116 -0
  23. package/dist/core/fix/run.d.ts +15 -0
  24. package/dist/core/fix/run.js +58 -0
  25. package/dist/core/fix/types.d.ts +34 -0
  26. package/dist/core/fix/types.js +6 -0
  27. package/dist/core/fix/writers/claude-settings.d.ts +16 -0
  28. package/dist/core/fix/writers/claude-settings.js +100 -0
  29. package/dist/core/fix/writers/codex-config.d.ts +22 -0
  30. package/dist/core/fix/writers/codex-config.js +183 -0
  31. package/dist/core/fix/writers/cursorignore.d.ts +13 -0
  32. package/dist/core/fix/writers/cursorignore.js +68 -0
  33. package/dist/core/path-resolution/index.d.ts +5 -0
  34. package/dist/core/path-resolution/index.js +5 -0
  35. package/dist/core/path-resolution/prepare.d.ts +33 -0
  36. package/dist/core/path-resolution/prepare.js +98 -0
  37. package/dist/core/policy/evaluate.d.ts +44 -0
  38. package/dist/core/policy/evaluate.js +120 -0
  39. package/dist/core/rules/claude-deny.d.ts +8 -0
  40. package/dist/core/rules/claude-deny.js +39 -0
  41. package/dist/core/rules/codex-deny.d.ts +5 -0
  42. package/dist/core/rules/codex-deny.js +42 -0
  43. package/dist/core/rules/context/generated-directory.js +52 -8
  44. package/dist/core/rules/context/large-log-file.js +27 -10
  45. package/dist/core/rules/ignore.js +2 -5
  46. package/dist/core/rules/instructions/missing-path-reference.d.ts +5 -1
  47. package/dist/core/rules/instructions/missing-path-reference.js +114 -7
  48. package/dist/core/rules/path-kind.d.ts +27 -0
  49. package/dist/core/rules/path-kind.js +180 -0
  50. package/dist/core/rules/security/env-file-exposure.js +23 -38
  51. package/dist/core/rules/security/private-key-file.js +4 -0
  52. package/dist/core/rules/text-cache.js +46 -40
  53. package/dist/core/verify/compare.d.ts +29 -0
  54. package/dist/core/verify/compare.js +56 -0
  55. package/dist/core/verify/load-baseline.d.ts +15 -0
  56. package/dist/core/verify/load-baseline.js +71 -0
  57. package/dist/core/verify/verify.d.ts +26 -0
  58. package/dist/core/verify/verify.js +40 -0
  59. package/dist/discovery/files.js +7 -2
  60. package/dist/discovery/log-like.d.ts +2 -0
  61. package/dist/discovery/log-like.js +7 -0
  62. package/dist/index.d.ts +10 -0
  63. package/dist/index.js +6 -0
  64. package/dist/reporters/github/annotations.d.ts +8 -0
  65. package/dist/reporters/github/annotations.js +42 -0
  66. package/dist/reporters/github/emit.d.ts +23 -0
  67. package/dist/reporters/github/emit.js +31 -0
  68. package/dist/reporters/github/summary.d.ts +20 -0
  69. package/dist/reporters/github/summary.js +111 -0
  70. package/dist/reporters/terminal/report.d.ts +5 -0
  71. package/dist/reporters/terminal/report.js +73 -4
  72. package/dist/reporters/verify/json.d.ts +5 -0
  73. package/dist/reporters/verify/json.js +51 -0
  74. package/dist/reporters/verify/terminal.d.ts +6 -0
  75. package/dist/reporters/verify/terminal.js +71 -0
  76. package/dist/utils/fs.d.ts +5 -0
  77. package/dist/utils/fs.js +34 -0
  78. package/dist/utils/path.d.ts +5 -0
  79. package/dist/utils/path.js +16 -0
  80. package/package.json +11 -1
package/CHANGELOG.md CHANGED
@@ -7,10 +7,112 @@ 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
+
84
+ ## [0.3.0-beta] — 2026-08-07
85
+
86
+ Minor beta: completes the Scan → Fix → Verify CLI loop and corrects release-facing honesty.
87
+
88
+ ### Added
89
+
90
+ - `agentdoctor verify` — re-scan and compare against a prior `scan --json` baseline
91
+ (`fixed` / `remaining` / `new` / `unchanged`). Supports `--json`, `--ci` (fails on new
92
+ findings), `--baseline`, and `--min-score`. Completes the Scan → Fix → Verify CLI loop.
93
+ - Terminal summary prints overall readiness (`N/100`); category/agent scores remain in JSON.
94
+
95
+ ### Fixed
96
+
97
+ - `agentdoctor scan --json` (and `--ci` / `--verbose` / `--min-score` on the `scan`
98
+ subcommand) now honor flags correctly. Overlapping root/subcommand options are read via
99
+ Commander `optsWithGlobals()`, so CI scripts using `scan … --json` receive JSON instead of
100
+ a terminal report.
101
+ - `instructions/missing-path-reference` no longer treats Go/npm module imports
102
+ (`github.com/…`, `@scope/pkg`), Go stdlib paths (`io/ioutil`), glob patterns, code tokens
103
+ (`try/finally`), or bare build roots (`dist/`) as missing local paths.
104
+ - `agentdoctor fix` now reports skip reasons for review/manual findings instead of an empty
105
+ “no applicable fixes” message with no explanation.
106
+ - Sample/test/example paths and env templates no longer inflate security/context false positives.
107
+
108
+ ### Compatibility
109
+
110
+ - Default Action `version` input is `0.3.0-beta` (pin CI smoke to last published until npm ships)
111
+ - Fix remains Cursor `.cursorignore` safe-context only; security findings stay review/manual
112
+
10
113
  ### Planned
11
114
 
12
- - Safe automatic fixes
13
- - Terminal readiness line and GitHub Action score-gate inputs (deferred; see scoring.md v2+)
115
+ - GitHub Action score-gate inputs (deferred; see scoring.md v2+)
14
116
 
15
117
  ## [0.2.0-beta] — 2026-08-02
16
118
 
@@ -143,7 +245,9 @@ First public beta.
143
245
  - Not a complete secret scanner
144
246
  - Git “tracked secret” detection deferred
145
247
 
146
- [Unreleased]: https://github.com/pranee54/AgentDoctor/compare/v0.2.0-beta...HEAD
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
250
+ [0.3.0-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.3.0-beta
147
251
  [0.2.0-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.2.0-beta
148
252
  [0.1.4-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.1.4-beta
149
253
  [0.1.3-beta]: https://github.com/pranee54/AgentDoctor/releases/tag/v0.1.3-beta
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AgentDoctor
2
2
 
3
- [![npm](https://img.shields.io/npm/v/@praneeth_54/agentdoctor)](https://www.npmjs.com/package/@praneeth_54/agentdoctor)
3
+ [![npm](https://img.shields.io/npm/v/@praneeth_54/agentdoctor?label=npm)](https://www.npmjs.com/package/@praneeth_54/agentdoctor)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@praneeth_54/agentdoctor)](https://www.npmjs.com/package/@praneeth_54/agentdoctor)
5
5
  [![CI](https://img.shields.io/github/actions/workflow/status/pranee54/AgentDoctor/ci.yml?branch=main&label=CI)](https://github.com/pranee54/AgentDoctor/actions)
6
6
  [![Node](https://img.shields.io/node/v/@praneeth_54/agentdoctor)](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 beta (`0.1.x-beta`). Deterministic readiness scores ship in JSON; automatic fixes are not available yet.
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.1.x-beta`). Deterministic readiness scores ship in JSON; automat
24
24
 
25
25
  ![AgentDoctor scanning a repository and reporting coding-agent security findings](docs/images/cli-scan.png)
26
26
 
27
- _Real scan of the included `insecure-agent-project` fixture using AgentDoctor v0.2.0-beta._
27
+ _Real scan of the included `insecure-agent-project` fixture using AgentDoctor v1.0.0._
28
28
 
29
29
  ```text
30
30
  $ npx @praneeth_54/agentdoctor
31
31
 
32
- 🩺 AgentDoctor v0.2.0-beta
32
+ 🩺 AgentDoctor v1.0.0
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 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`.
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 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.
144
146
 
145
147
  ---
146
148
 
@@ -152,10 +154,10 @@ Automatic repair is **not** included in this beta. Findings tell you what to rev
152
154
  npx @praneeth_54/agentdoctor
153
155
  ```
154
156
 
155
- Pin a beta version when you need a fixed install:
157
+ Pin a version when you need a fixed install:
156
158
 
157
159
  ```bash
158
- npx @praneeth_54/agentdoctor@0.2.0-beta
160
+ npx @praneeth_54/agentdoctor@1.0.0
159
161
  ```
160
162
 
161
163
  ### Global (optional)
@@ -165,12 +167,33 @@ 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 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` 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`.
189
+
168
190
  ### Common commands
169
191
 
170
192
  ```bash
171
193
  agentdoctor .
172
- agentdoctor . --json
173
- agentdoctor . --verbose
194
+ agentdoctor scan . --json
195
+ agentdoctor fix . --dry-run
196
+ agentdoctor verify . --ci --baseline agentdoctor-report.json
174
197
  agentdoctor explain security/env-file-exposure
175
198
  agentdoctor doctor
176
199
  ```
@@ -184,10 +207,11 @@ npm install @praneeth_54/agentdoctor
184
207
  ```
185
208
 
186
209
  ```ts
187
- import { scan } from "@praneeth_54/agentdoctor";
210
+ import { scan, verify, buildFixPlan, applyFixPlan } from "@praneeth_54/agentdoctor";
188
211
 
189
212
  const result = await scan({ cwd: process.cwd() });
190
213
  console.log(result.summary);
214
+ console.log(result.scores?.overall);
191
215
  console.log(result.agentSecurityAnalysis); // "full" | "limited"
192
216
  ```
193
217
 
@@ -259,10 +283,14 @@ steps:
259
283
 
260
284
  - name: Audit coding-agent configuration
261
285
  id: agentdoctor
262
- uses: pranee54/AgentDoctor@v0.2.0-beta
286
+ uses: pranee54/AgentDoctor@v1.0.0
263
287
  with:
264
288
  path: .
289
+ version: "1.0.0"
265
290
  output-file: agentdoctor-report.json
291
+ minimum-score: "70"
292
+ fail-on-severity: critical
293
+ summary: "true"
266
294
 
267
295
  - name: Upload AgentDoctor report
268
296
  uses: actions/upload-artifact@v4
@@ -271,10 +299,13 @@ steps:
271
299
  path: ${{ steps.agentdoctor.outputs.report-path }}
272
300
  ```
273
301
 
274
- The action installs the published `@praneeth_54/agentdoctor@0.2.0-beta` package, runs it with
275
- `--ci --json`, and writes the report inside the checked-out workspace. It sets up Node.js 20
276
- for the CLI. The optional `version` input accepts an exact npm version or the `latest` / `beta`
277
- dist-tag.
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.
278
309
 
279
310
  ### CLI
280
311
 
@@ -282,43 +313,49 @@ Use JSON directly in other CI systems:
282
313
 
283
314
  ```bash
284
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
285
319
  npx @praneeth_54/agentdoctor --ci --json
286
320
 
287
- # Fail CI when overall readiness is below 70
321
+ # Fail when overall readiness is below 70 (with --ci also fails on criticals)
288
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
289
326
  ```
290
327
 
291
- `--ci` runs non-interactively and does **not** apply an implicit score threshold.
292
- Use `--min-score N` (with or without `--ci`) to fail with exit code `1` when
293
- `scores.overall < N`.
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).
294
331
 
295
332
  Exit codes: [docs/exit-codes.md](docs/exit-codes.md). Compatibility promises: [docs/compatibility.md](docs/compatibility.md).
296
333
 
297
334
  ### Readiness scoring
298
335
 
299
336
  Scans populate `scoringAvailable: true` and a deterministic `scores` object
300
- (overall, categories, agents). The terminal report does not render a readiness line yet;
301
- scores are available in JSON (`--json`).
337
+ (overall, categories, agents). The terminal prints overall readiness; category and agent
338
+ scores are in JSON (`--json`).
302
339
 
303
340
  `--min-score N` is enforced by the CLI. Details (weights, security caps, threshold rules,
304
341
  and deferred v2 items): [docs/scoring.md](docs/scoring.md).
305
342
 
306
343
  ---
307
344
 
308
- ## Beta limitations
345
+ ## Known limitations
309
346
 
310
- Honest limits of the current public beta:
347
+ Honest limits of v1:
311
348
 
312
- | Limitation | Status |
313
- | ----------------------------------- | --------------------------------------------------------------- |
314
- | Terminal readiness line | Scores ship in JSON only; terminal does not print N/100 yet |
315
- | GitHub Action score gates | Action remains `--ci --json` report-only (no `min-score` input) |
316
- | Automatic fixes (`agentdoctor fix`) | Stub only — does not modify files |
317
- | Secret-content scanning | Filename / config heuristics only |
318
- | Detection style | Intentionally conservative; false security findings are avoided |
319
- | Agent coverage | Cursor, Claude Code, Codex project configs |
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 |
320
357
 
321
- See [CHANGELOG.md](CHANGELOG.md) and [docs/release-notes-v0.2.0-beta.md](docs/release-notes-v0.2.0-beta.md).
358
+ See [CHANGELOG.md](CHANGELOG.md) and [docs/compatibility.md](docs/compatibility.md).
322
359
 
323
360
  ---
324
361
 
@@ -367,6 +404,20 @@ node dist/cli/index.js ./fixtures/clean-configured-project
367
404
 
368
405
  ---
369
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
+
370
421
  ## Next steps
371
422
 
372
423
  - **Try it:** `npx @praneeth_54/agentdoctor`
@@ -23,11 +23,29 @@ export async function inspectRepoFile(root, relativePath, maxFileSizeBytes = DEF
23
23
  };
24
24
  }
25
25
  try {
26
- const lstat = await fs.lstat(absolutePath);
27
- const isSymlink = lstat.isSymbolicLink();
28
- if (isSymlink) {
29
- const real = await fs.realpath(absolutePath);
30
- if (!isPathInsideRoot(root, real)) {
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: true,
56
+ isSymlink,
39
57
  text: null,
40
- error: "Symlink target is outside repository root",
58
+ error: "Not a regular file",
41
59
  };
42
60
  }
43
- }
44
- const stat = await fs.stat(absolutePath);
45
- if (!stat.isFile()) {
46
- return {
47
- relativePath: normalizedRelative,
48
- absolutePath,
49
- exists: true,
50
- readable: false,
51
- sizeBytes: 0,
52
- empty: true,
53
- isSymlink,
54
- text: null,
55
- error: "Not a regular file",
56
- };
57
- }
58
- const sizeBytes = Number(stat.size);
59
- if (sizeBytes === 0) {
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: false,
95
+ empty: trimmedEmpty,
79
96
  isSymlink,
80
- text: null,
81
- error: "File exceeds max size limit",
97
+ text,
82
98
  };
83
99
  }
84
- const text = await fs.readFile(absolutePath, "utf8");
85
- const trimmedEmpty = text.trim().length === 0;
86
- return {
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";
@@ -15,7 +15,7 @@ export async function runDoctorCommand() {
15
15
  lines.push(` ${symbolOk()} Core scan API available`);
16
16
  lines.push("");
17
17
  lines.push(colors.dim("Environment looks ready."));
18
- lines.push(colors.dim("Automatic fixes are not available yet."));
18
+ lines.push(colors.dim("Run agentdoctor scan → fix → verify to complete the readiness loop."));
19
19
  lines.push("");
20
20
  process.stdout.write(lines.join("\n"));
21
21
  return EXIT_CODES.SUCCESS;
@@ -39,9 +39,9 @@ export async function runExplainCommand(ruleId) {
39
39
  lines.push("");
40
40
  lines.push(colors.bold("Can AgentDoctor safely fix it?"));
41
41
  lines.push(rule.fixability === "safe"
42
- ? " Potentially yes. Conservative auto-fix is planned but not applied today."
42
+ ? " Yes for Cursor (`.cursorignore`), Claude Code (Read deny in `.claude/settings.json`), and Codex (filesystem deny in `.codex/config.toml`)."
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,80 @@
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, readClaudeSettings, readCodexConfig, 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;
13
18
  }
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
+ 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 claudeSettingsContent = await readClaudeSettings(plan.root);
27
+ const codexConfigContent = await readCodexConfig(plan.root);
28
+ const applyResult = await applyFixPlan(plan, { dryRun: true });
29
+ process.stdout.write(renderFixPlanTerminal(plan, {
30
+ dryRun: true,
31
+ cursorContent,
32
+ claudeSettingsContent,
33
+ codexConfigContent,
34
+ applyResult,
35
+ }));
36
+ return EXIT_CODES.SUCCESS;
37
+ }
38
+ const { plan, applyResult, cancelled } = await runFix({
39
+ cwd: target,
40
+ dryRun: false,
41
+ yes,
42
+ });
43
+ if (cancelled) {
44
+ process.stdout.write("\n Cancelled. No files were modified.\n\n");
45
+ return EXIT_CODES.USAGE_ERROR;
46
+ }
47
+ const cursorContentAfter = await readCursorignore(plan.root);
48
+ const claudeSettingsAfter = await readClaudeSettings(plan.root);
49
+ const codexConfigAfter = await readCodexConfig(plan.root);
50
+ process.stdout.write(renderFixPlanTerminal(plan, {
51
+ dryRun: false,
52
+ cursorContent: cursorContentAfter,
53
+ claudeSettingsContent: claudeSettingsAfter,
54
+ codexConfigContent: codexConfigAfter,
55
+ applyResult,
56
+ }));
57
+ if (applyResult.writtenFiles.length > 0) {
58
+ const after = await scan({ cwd: target });
59
+ const remainingSafeContext = after.findings.filter((f) => f.fixability === "safe" &&
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`));
62
+ }
63
+ return EXIT_CODES.SUCCESS;
64
+ }
65
+ catch (error) {
66
+ const message = error instanceof Error ? error.message : String(error);
67
+ console.error(`Error: ${message}`);
68
+ if (isFixConfigOrPermissionError(message)) {
69
+ return EXIT_CODES.USAGE_ERROR;
70
+ }
71
+ return EXIT_CODES.INTERNAL_ERROR;
72
+ }
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"));
19
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;