@chimix/agentscan 0.6.0 → 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 +225 -0
- package/README.md +168 -63
- package/dist/cli.js +4812 -561
- package/package.json +6 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to `@chimix/agentscan`. Dates are UTC publish days from git
|
|
4
|
+
and the existing GitHub releases. The `check` path stays offline and
|
|
5
|
+
read-only in every version below.
|
|
6
|
+
|
|
7
|
+
GitHub already has release pages for [v0.1.0](https://github.com/SimaAlexandru99/agentscan/releases/tag/v0.1.0)
|
|
8
|
+
and [v0.4.0](https://github.com/SimaAlexandru99/agentscan/releases/tag/v0.4.0).
|
|
9
|
+
0.9.0 was planned as a separate train and shipped inside 1.0.0.
|
|
10
|
+
|
|
11
|
+
## 1.0.0 — 2026-08-30
|
|
12
|
+
|
|
13
|
+
First stable release. 59 checks, 345 tests. The coverage matrix in the
|
|
14
|
+
README is the honesty contract: `full` only where discovery, spec-required
|
|
15
|
+
fields, and a conformance fixture (or equivalent tests) all exist.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Portable Agent Skills required fields (`name`, `description`, directory
|
|
20
|
+
match) for `.agents/skills`, nested Cursor `.cursor/skills`, and
|
|
21
|
+
`.codex/skills`.
|
|
22
|
+
- Path-keyed MCP parsers: VS Code `servers`, Cursor, Antigravity
|
|
23
|
+
`serverUrl`, Codex TOML, Gemini `command` / `url` / `httpUrl`, OpenCode
|
|
24
|
+
V1 and V2, Continue `config.yaml` plus `.continue/mcpServers/*`.
|
|
25
|
+
- Nested / walk-up `AGENTS.md` with nearest-wins; Codex instruction budget
|
|
26
|
+
uses the effective per-directory file and `project_doc_max_bytes`.
|
|
27
|
+
- Walk-up `.claude/agents`; VS Code `.github/agents` without Claude
|
|
28
|
+
missing-name errors.
|
|
29
|
+
- VS Code `.github/hooks/*.json` (eight events) and per-provider hook
|
|
30
|
+
event sets.
|
|
31
|
+
- Cursor `.cursor/rules/**/*.mdc` 500-line recommendation
|
|
32
|
+
(`cursor.rule.too-large`, info). Claude rules are inventoried only.
|
|
33
|
+
- Official-shaped fixtures under `tests/fixtures/conformance/`.
|
|
34
|
+
- `spec:check` tracks 21 captured surfaces, not only Claude hook events.
|
|
35
|
+
- Launch `cwd` and `windows` / `linux` / `osx` overrides on hooks and MCP.
|
|
36
|
+
- GitHub Action pin `@v1`.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- Product copy describes what 1.0.0 actually implements. Heuristics stay
|
|
41
|
+
at `info` and are labeled in the registry.
|
|
42
|
+
- `--global` also scans `~/.codex/skills` under the Agent Skills contract.
|
|
43
|
+
- In a monorepo, each `skills-lock.json` governs only the skills under
|
|
44
|
+
its directory (nearest lock wins).
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- Nested `SKILL.md` trees are one skill, not a missing-md folder.
|
|
49
|
+
- Codex instruction chain: at most one file per directory; honour
|
|
50
|
+
`project_doc_max_bytes`.
|
|
51
|
+
- Interpreter argv (`node script.js`), MCP `command` arrays, and OS
|
|
52
|
+
overrides are path-checked. `--` inline code (`node -e`) is not a path.
|
|
53
|
+
- JSONC (comments and trailing commas) parses via `jsonc-parser`; the
|
|
54
|
+
published bundle imports the ESM entry so Node can load `dist/cli.js`.
|
|
55
|
+
- Scan boundary stays at Git/workspace. A child `.cursor` does not hide
|
|
56
|
+
parent Claude or Codex.
|
|
57
|
+
- `uses:` is a launch only on Continue. Other profiles treat a uses-only
|
|
58
|
+
entry as non-launchable.
|
|
59
|
+
- OpenCode V2 requires `type` plus the matching launch field. V1
|
|
60
|
+
enabled-only overrides that inherit externally do not emit a hard error.
|
|
61
|
+
- Windows drive / UNC cwd values and OS-specific script paths
|
|
62
|
+
(`scripts\format.ps1`) are not joined as POSIX relative paths when the
|
|
63
|
+
scanner runs on Linux or macOS. Those launches are inventoried;
|
|
64
|
+
`scriptExists` / `commandExists` stay unset. Host-matching and
|
|
65
|
+
platform-neutral launches stay fully checked. `C:foo` is unresolved,
|
|
66
|
+
not absolute.
|
|
67
|
+
- Old 0.7 rule ids still work in `ignoreRules` and `explain`.
|
|
68
|
+
|
|
69
|
+
## 0.8.0 — 2026-08-30
|
|
70
|
+
|
|
71
|
+
Honesty pass after the multi-provider audit. 38 checks, 267 tests.
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
|
|
75
|
+
- Agent Skills profile on `.agents/skills` (required `name` /
|
|
76
|
+
`description`).
|
|
77
|
+
- MCP profile parsers for VS Code `.vscode/mcp.json` (`servers`), Cursor
|
|
78
|
+
`.cursor/mcp.json`, Antigravity `serverUrl`, and Codex
|
|
79
|
+
`[mcp_servers.*]` TOML.
|
|
80
|
+
- Nearest-signal project root: a child `.cursor` / `.claude` wins over a
|
|
81
|
+
parent `package.json`.
|
|
82
|
+
- Provenance on every registry entry (`spec-required`,
|
|
83
|
+
`vendor-recommendation`, `security`, `internal-consistency`,
|
|
84
|
+
`heuristic`).
|
|
85
|
+
- First coverage matrix (full / partial / none) for the surfaces this
|
|
86
|
+
version actually implemented.
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
|
|
90
|
+
- Claude check ids are namespaced (`claude.hook.missing-script`, …).
|
|
91
|
+
`ignoreRules` and `explain` accept the old 0.7 ids as aliases.
|
|
92
|
+
- README no longer claims every check is spec-sourced or that there are
|
|
93
|
+
no heuristics. Size opinions and installer policy stay at `info`.
|
|
94
|
+
- `budget.claude-md` quotes the official “target under 200 lines”
|
|
95
|
+
recommendation instead of an unsourced 150–200 / 50-token story.
|
|
96
|
+
|
|
97
|
+
## 0.7.0 — 2026-08-24
|
|
98
|
+
|
|
99
|
+
27 checks, 250 tests.
|
|
100
|
+
|
|
101
|
+
### Added
|
|
102
|
+
|
|
103
|
+
- Hooks from in-tree plugin `hooks/hooks.json` and from skill / subagent
|
|
104
|
+
frontmatter, not only the two settings files.
|
|
105
|
+
`${CLAUDE_PLUGIN_ROOT}` expands only for a plugin-sourced hook.
|
|
106
|
+
|
|
107
|
+
### Changed
|
|
108
|
+
|
|
109
|
+
- `engines.bun` is `>=1.4.0`. The repo ships the text `bun.lock`
|
|
110
|
+
(lockfileVersion 1); Bun 1.1 wrote `bun.lockb` and cannot read it, so
|
|
111
|
+
the old `>=1.1.0` floor was false. `engines.node` stays `>=20.11.0`.
|
|
112
|
+
- CI pins Bun; `Bun.YAML` is rejected in favour of the `yaml` package.
|
|
113
|
+
|
|
114
|
+
## 0.6.0 — 2026-08-24
|
|
115
|
+
|
|
116
|
+
Stop reporting valid config as broken. 27 checks, 235 tests.
|
|
117
|
+
|
|
118
|
+
### Added
|
|
119
|
+
|
|
120
|
+
- `scan.truncated` (info): a file past the 64 KB skill or 100 KB policy
|
|
121
|
+
cap is parsed from its prefix. The finding is about this scanner’s
|
|
122
|
+
reach, not about the file being invalid.
|
|
123
|
+
|
|
124
|
+
### Changed
|
|
125
|
+
|
|
126
|
+
- `skill.duplicate-description` and `skill.description-budget` group by
|
|
127
|
+
the directory that owns the skill, so two apps in a monorepo no longer
|
|
128
|
+
look like a collision.
|
|
129
|
+
- `agent.invalid-name` drops from error to warning. The subagent
|
|
130
|
+
reference specifies the name format but documents a load failure only
|
|
131
|
+
for `:`.
|
|
132
|
+
- `--verbose` prints each finding’s id so the value `explain` and
|
|
133
|
+
`ignoreFindings` take can be copied out of the report.
|
|
134
|
+
|
|
135
|
+
### Fixed
|
|
136
|
+
|
|
137
|
+
- A `SKILL.md` over the scan cap is no longer `config.unreadable` at
|
|
138
|
+
error. Claude Code still loads that file; the frontmatter sits in the
|
|
139
|
+
first 300 bytes.
|
|
140
|
+
- The Action’s `GITHUB_OUTPUT` heredoc delimiter is randomised.
|
|
141
|
+
|
|
142
|
+
Measured on the three projects that reproduced the defects: touchagency
|
|
143
|
+
0 → 34, kronstadt-ehs-2026 40 → 94, optimad 0 → 85.
|
|
144
|
+
|
|
145
|
+
## 0.5.0 — 2026-08-12
|
|
146
|
+
|
|
147
|
+
26 checks.
|
|
148
|
+
|
|
149
|
+
### Added
|
|
150
|
+
|
|
151
|
+
- `agentscan demo`: throwaway fixture with the killer missing-hook case,
|
|
152
|
+
human report, then delete.
|
|
153
|
+
- Trees with agent config and no `package.json` are scannable.
|
|
154
|
+
- `mcp.command-missing`: path-like `command` values are compared to
|
|
155
|
+
disk. Bare PATH binaries, shell compounds, and unresolved env vars
|
|
156
|
+
are skipped.
|
|
157
|
+
|
|
158
|
+
### Changed
|
|
159
|
+
|
|
160
|
+
- Severity labels and neutral multi-event group headlines in the text
|
|
161
|
+
report.
|
|
162
|
+
- Public check count synced to 26.
|
|
163
|
+
|
|
164
|
+
## 0.4.0 — 2026-08-09
|
|
165
|
+
|
|
166
|
+
Colour, a face, and a report that reads at a glance. 23 checks, 195
|
|
167
|
+
tests. [GitHub release](https://github.com/SimaAlexandru99/agentscan/releases/tag/v0.4.0).
|
|
168
|
+
|
|
169
|
+
### Added
|
|
170
|
+
|
|
171
|
+
- Terminal header with score, face, and bar. Piped output stays
|
|
172
|
+
byte-identical to the uncoloured report. `--json`, `--output prompt`,
|
|
173
|
+
and `--copy` are never coloured. `--no-color` and `NO_COLOR` are
|
|
174
|
+
honoured.
|
|
175
|
+
|
|
176
|
+
### Changed
|
|
177
|
+
|
|
178
|
+
- The YAML rule engine is gone (709 lines, 9 info findings across 21
|
|
179
|
+
projects, unused extension point). Its five rules are code, with
|
|
180
|
+
byte-identical output.
|
|
181
|
+
|
|
182
|
+
## 0.3.0 — 2026-08-09
|
|
183
|
+
|
|
184
|
+
### Changed
|
|
185
|
+
|
|
186
|
+
- Repeated findings group: six dead hooks print one header with `×6`
|
|
187
|
+
and one line per occurrence.
|
|
188
|
+
- Paths are shown relative to the scanned project.
|
|
189
|
+
- The score word (`broken`, …) comes from the same thresholds as the
|
|
190
|
+
face and the colour, so they cannot disagree.
|
|
191
|
+
|
|
192
|
+
## 0.2.1 — 2026-08-09
|
|
193
|
+
|
|
194
|
+
### Changed
|
|
195
|
+
|
|
196
|
+
- Bare invocation (`npx @chimix/agentscan`) scans the current
|
|
197
|
+
directory. The quick start shows findings instead of a usage screen.
|
|
198
|
+
|
|
199
|
+
## 0.2.0 — 2026-08-09
|
|
200
|
+
|
|
201
|
+
### Added
|
|
202
|
+
|
|
203
|
+
- Published `dist/cli.js` runs on Node 20.11+ as well as Bun. `npx
|
|
204
|
+
@chimix/agentscan@latest` works without Bun installed. One bundled
|
|
205
|
+
file, no install-time dependencies.
|
|
206
|
+
|
|
207
|
+
## 0.1.0 — 2026-08-09
|
|
208
|
+
|
|
209
|
+
First alpha. [GitHub release](https://github.com/SimaAlexandru99/agentscan/releases/tag/v0.1.0).
|
|
210
|
+
23 checks, each written against a published spec line in `docs/spec/`.
|
|
211
|
+
|
|
212
|
+
### Added
|
|
213
|
+
|
|
214
|
+
- Deterministic offline CLI: broken hooks, unlaunchable MCP, credentials
|
|
215
|
+
pasted into config, skills that disagree with their lockfile.
|
|
216
|
+
- `check`, `--output prompt`, `--fail-on`, `--json`.
|
|
217
|
+
- Published score: `max(0, 100 - 10 × errors - 3 × warnings)`.
|
|
218
|
+
Coverage-ratio scoring was built, measured, and rejected.
|
|
219
|
+
- Published as `@chimix/agentscan` — npm rejects the bare name as too
|
|
220
|
+
close to an unrelated `agent-scan`. The command stays `agentscan`.
|
|
221
|
+
|
|
222
|
+
An earlier build reported 37 findings across 17 projects of which 25
|
|
223
|
+
were false, because two checks were written from observation. Both were
|
|
224
|
+
deleted before this release. Requires Bun for source development; `check`
|
|
225
|
+
never opens a socket and never writes the scanned tree.
|
package/README.md
CHANGED
|
@@ -5,18 +5,19 @@
|
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<img src="https://img.shields.io/badge/checks-
|
|
9
|
-
<img src="https://img.shields.io/badge/tests-
|
|
8
|
+
<img src="https://img.shields.io/badge/checks-59-111111?style=flat-square" alt="59 checks">
|
|
9
|
+
<img src="https://img.shields.io/badge/tests-345%20passing-111111?style=flat-square" alt="345 tests">
|
|
10
10
|
<img src="https://img.shields.io/badge/network-none-111111?style=flat-square" alt="No network">
|
|
11
11
|
<img src="https://img.shields.io/badge/writes-none-111111?style=flat-square" alt="Writes nothing">
|
|
12
12
|
<img src="https://img.shields.io/badge/runs%20on-node%20%C2%B7%20bun-111111?style=flat-square" alt="Node or Bun">
|
|
13
13
|
<img src="https://img.shields.io/npm/v/@chimix/agentscan?style=flat-square&color=111111&label=npm" alt="npm version">
|
|
14
14
|
<img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT license">
|
|
15
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-1.0.0-111111?style=flat-square" alt="Changelog"></a>
|
|
15
16
|
</p>
|
|
16
17
|
|
|
17
18
|
<p align="center">
|
|
18
|
-
<strong>
|
|
19
|
-
<sub>
|
|
19
|
+
<strong>59 checks · offline · 0 network calls on <code>check</code> · provenance on every rule</strong><br>
|
|
20
|
+
<sub>An offline linter for Claude Code, portable Agent Skills, nested AGENTS.md, and the MCP / hooks / rules surfaces that 1.0.0 actually implements. Spec-required checks cite a published line in <a href="docs/spec/">docs/spec/</a>. Heuristics stay at <code>info</code> and say so. The coverage matrix below is the honesty contract — <code>full</code> only where discovery, spec-required fields, and a conformance fixture (or equivalent tests) all exist.</sub>
|
|
20
21
|
</p>
|
|
21
22
|
|
|
22
23
|
---
|
|
@@ -47,7 +48,7 @@ think you have has silently not existed for six weeks.
|
|
|
47
48
|
```console
|
|
48
49
|
$ agentscan check
|
|
49
50
|
|
|
50
|
-
ERROR rule:hook.missing-script
|
|
51
|
+
ERROR rule:claude.hook.missing-script
|
|
51
52
|
PreToolUse hook points at a script that does not exist: .claude/hooks/guard-destructive-bash.js
|
|
52
53
|
PreToolUse @ .claude/settings.json · .claude/hooks/guard-destructive-bash.js
|
|
53
54
|
|
|
@@ -59,12 +60,13 @@ of the filesystem**, where nothing else in your stack will ever tell you.
|
|
|
59
60
|
|
|
60
61
|
## How it works
|
|
61
62
|
|
|
62
|
-
No AI, no network
|
|
63
|
+
No AI, no network on `check`. Read the config, read the disk, compare:
|
|
63
64
|
|
|
64
65
|
```
|
|
65
|
-
1. Discover .claude/ .agents/ .
|
|
66
|
+
1. Discover .claude/ .agents/ .vscode/ .cursor/ .codex/ .gemini/ .github/ .continue/ AGENTS.md skills-lock.json
|
|
66
67
|
2. Extract immutable facts — never re-read during checking
|
|
67
|
-
3. Check
|
|
68
|
+
3. Check 59 checks, each labeled spec-required, vendor-recommendation,
|
|
69
|
+
security, internal-consistency, or heuristic
|
|
68
70
|
4. Report text · --json · --output prompt (handoff for a fixing agent)
|
|
69
71
|
```
|
|
70
72
|
|
|
@@ -73,13 +75,17 @@ scans and never opens a socket.
|
|
|
73
75
|
|
|
74
76
|
## What it will not do
|
|
75
77
|
|
|
76
|
-
It will not compare a skill's frontmatter `name` to its directory
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
the
|
|
78
|
+
It will not compare a Claude-native skill's frontmatter `name` to its directory
|
|
79
|
+
(portable Agent Skills under `.agents/skills` and Cursor `.cursor/skills` still
|
|
80
|
+
require that match), and it will not validate model ids or MCP tool names.
|
|
81
|
+
Those last two need a hardcoded list of valid values, with everything absent
|
|
82
|
+
from the list reported as broken. That is exactly how the 25 false findings
|
|
83
|
+
happened: a hook-event list with 9 names when the spec has 31, calling working
|
|
84
|
+
hooks dead at severity `error`.
|
|
81
85
|
|
|
82
|
-
A check
|
|
86
|
+
A spec-required check has to point at a published line. If it cannot, it does
|
|
87
|
+
not ship as an error. Size opinions and installer policy remain as `info`
|
|
88
|
+
heuristics and are labeled that way in the registry.
|
|
83
89
|
|
|
84
90
|
## Try it in 30 seconds
|
|
85
91
|
|
|
@@ -123,11 +129,13 @@ bunx @chimix/agentscan rules
|
|
|
123
129
|
bunx @chimix/agentscan check ~/your-project --verbose
|
|
124
130
|
|
|
125
131
|
# why one specific finding fired
|
|
126
|
-
bunx @chimix/agentscan explain hook.missing-script:hook:PreToolUse:./x.js ~/your-project
|
|
132
|
+
bunx @chimix/agentscan explain claude.hook.missing-script:hook:PreToolUse:./x.js ~/your-project
|
|
127
133
|
```
|
|
128
134
|
|
|
129
135
|
`check` never opens a socket and never writes to the tree it scans. Contributing
|
|
130
|
-
needs **Bun** 1.
|
|
136
|
+
needs **Bun** 1.4+ — the version CI pins and every gate is verified on; the
|
|
137
|
+
text `bun.lock` this repo ships cannot be read by 1.1 at all. The repo runs
|
|
138
|
+
TypeScript directly and `bun run build`
|
|
131
139
|
bundles it for Node.
|
|
132
140
|
|
|
133
141
|
### In CI
|
|
@@ -136,7 +144,7 @@ The Action runs from its own checkout, so it uses the ref you pin rather than
|
|
|
136
144
|
whatever is on npm:
|
|
137
145
|
|
|
138
146
|
```yaml
|
|
139
|
-
- uses: SimaAlexandru99/agentscan@
|
|
147
|
+
- uses: SimaAlexandru99/agentscan@v1
|
|
140
148
|
with:
|
|
141
149
|
fail-on: error
|
|
142
150
|
```
|
|
@@ -191,18 +199,18 @@ Flags for `check`:
|
|
|
191
199
|
│ ⌒ │ 6 error · 2 warning · 11 info hidden (--verbose)
|
|
192
200
|
└─────┘
|
|
193
201
|
|
|
194
|
-
agentscan
|
|
202
|
+
agentscan v1.0.0 — touchagency
|
|
195
203
|
|
|
196
204
|
Scanned: 46 deps · 108 skills · 1 mcp · 2 agents · packageManager=bun
|
|
197
205
|
|
|
198
|
-
ERROR rule:hook.missing-script ×6
|
|
206
|
+
ERROR rule:claude.hook.missing-script ×6
|
|
199
207
|
Hook points at a script that does not exist
|
|
200
208
|
PostToolUse @ .claude/settings.json · .claude/hooks/notify-related-tests.js
|
|
201
209
|
PreToolUse @ .claude/settings.json · .claude/hooks/guard-destructive-bash.js
|
|
202
210
|
PreToolUse @ .claude/settings.json · .claude/hooks/protect-artists-json.js
|
|
203
211
|
PreToolUse @ .claude/settings.json · .claude/hooks/protect-env.js
|
|
204
212
|
|
|
205
|
-
WARN rule:agent.invalid-name ×2
|
|
213
|
+
WARN rule:claude.agent.invalid-name ×2
|
|
206
214
|
2 findings
|
|
207
215
|
.claude/agents/marketing-seo-specialist.md
|
|
208
216
|
.claude/agents/testing-accessibility-auditor.md
|
|
@@ -223,7 +231,7 @@ JSON shape (abridged):
|
|
|
223
231
|
|
|
224
232
|
```json
|
|
225
233
|
{
|
|
226
|
-
"version": "0.
|
|
234
|
+
"version": "1.0.0",
|
|
227
235
|
"root": "/path/to/project",
|
|
228
236
|
"factsSummary": {
|
|
229
237
|
"packageManager": "bun",
|
|
@@ -233,8 +241,8 @@ JSON shape (abridged):
|
|
|
233
241
|
},
|
|
234
242
|
"findings": [
|
|
235
243
|
{
|
|
236
|
-
"id": "hook.missing-script:hook:PreToolUse:.claude/hooks/protect-env.js",
|
|
237
|
-
"ruleId": "hook.missing-script",
|
|
244
|
+
"id": "claude.hook.missing-script:hook:PreToolUse:.claude/hooks/protect-env.js",
|
|
245
|
+
"ruleId": "claude.hook.missing-script",
|
|
238
246
|
"action": "warn",
|
|
239
247
|
"severity": "error",
|
|
240
248
|
"subject": "hook:PreToolUse:.claude/hooks/protect-env.js",
|
|
@@ -253,11 +261,14 @@ Same tree → same sorted findings, with stable unique `id`s.
|
|
|
253
261
|
|
|
254
262
|
Adds `~/.claude/skills` and `~/.codex/skills` to the structural checks: a
|
|
255
263
|
`SKILL.md` that is malformed is malformed wherever it lives, and those findings
|
|
256
|
-
carry a `source: global` evidence entry so you can tell them apart.
|
|
264
|
+
carry a `source: global` evidence entry so you can tell them apart. Codex skills
|
|
265
|
+
use the Agent Skills contract (`name` and `description` required).
|
|
257
266
|
|
|
258
267
|
Lockfile checks stay project-scoped — a project lockfile cannot pin a skill that
|
|
259
268
|
lives in your home directory, so reporting one as "not in the lockfile" could
|
|
260
|
-
only ever be wrong.
|
|
269
|
+
only ever be wrong. In a monorepo, each `skills-lock.json` governs only the
|
|
270
|
+
skills under its directory (nearest lock wins). A skill with no ancestor
|
|
271
|
+
lockfile is not compared to a distant lock.
|
|
261
272
|
|
|
262
273
|
`includeGlobal: true` in `.agentscanrc.json` does the same thing without the
|
|
263
274
|
flag.
|
|
@@ -303,7 +314,7 @@ only says how many points of unspecified trouble is too much.
|
|
|
303
314
|
## CI
|
|
304
315
|
|
|
305
316
|
```yaml
|
|
306
|
-
- uses: SimaAlexandru99/agentscan@
|
|
317
|
+
- uses: SimaAlexandru99/agentscan@v1
|
|
307
318
|
with:
|
|
308
319
|
fail-on: error # never | warning | error
|
|
309
320
|
output: human # human | json | prompt
|
|
@@ -331,8 +342,8 @@ Optional `.agentscanrc.json` (create with `agentscan init`):
|
|
|
331
342
|
|
|
332
343
|
```json
|
|
333
344
|
{
|
|
334
|
-
"skillPaths": [".agents/skills", ".claude/skills", "skills", ".cursor/skills"],
|
|
335
|
-
"mcpPaths": [".mcp.json", ".claude/mcp.json", "mcp.json"],
|
|
345
|
+
"skillPaths": [".agents/skills", ".claude/skills", "skills", ".cursor/skills", ".codex/skills"],
|
|
346
|
+
"mcpPaths": [".mcp.json", ".claude/mcp.json", "mcp.json", ".vscode/mcp.json", ".cursor/mcp.json", ".agents/mcp_config.json", ".codex/config.toml"],
|
|
336
347
|
"policyFiles": ["AGENTS.md", "CLAUDE.md"],
|
|
337
348
|
"ignoreSkills": [],
|
|
338
349
|
"ignoreRules": [],
|
|
@@ -399,7 +410,7 @@ each finding, and `--json` carries it as `.findings[].id`, so the value you copy
|
|
|
399
410
|
out of the report is the value you paste:
|
|
400
411
|
|
|
401
412
|
```json
|
|
402
|
-
{ "ignoreFindings": ["hook.missing-script:hook:PreToolUse:./bin/wrapper"] }
|
|
413
|
+
{ "ignoreFindings": ["claude.hook.missing-script:hook:PreToolUse:./bin/wrapper"] }
|
|
403
414
|
```
|
|
404
415
|
|
|
405
416
|
## What it checks
|
|
@@ -407,41 +418,92 @@ out of the report is the value you paste:
|
|
|
407
418
|
Every check lives in `src/checks/` and runs on every `check`. `agentscan rules`
|
|
408
419
|
lists all of them with their ids; `agentscan explain <id>` details any finding.
|
|
409
420
|
|
|
410
|
-
Most validate one discovered item against its own file on disk.
|
|
411
|
-
|
|
412
|
-
`
|
|
413
|
-
|
|
414
|
-
`.agentscanrc.json` rather than switched off.
|
|
421
|
+
Most validate one discovered item against its own file on disk. Budget and
|
|
422
|
+
hygiene checks are **info** (hidden unless `--verbose`). Each registry entry
|
|
423
|
+
carries `provenance`: spec-required, vendor-recommendation, security,
|
|
424
|
+
internal-consistency, or heuristic. `agentscan rules` lists them all.
|
|
415
425
|
|
|
416
426
|
| id | Severity | Catches |
|
|
417
427
|
|----|----------|---------|
|
|
418
428
|
| `config.unreadable` | error | A config file that is not valid JSON, so whatever it declares is silently not in effect |
|
|
419
429
|
| `scan.truncated` | info | A file past the scan cap, so the checks that read its body saw only a prefix — about this tool's reach, not about your project |
|
|
420
|
-
| `hook.missing-script` | error | A registered hook whose script does not exist — it never runs |
|
|
421
|
-
| `hook.unknown-event` | error | A hook registered under an event name that is never dispatched |
|
|
422
|
-
| `
|
|
423
|
-
| `
|
|
424
|
-
| `agent.missing-
|
|
425
|
-
| `agent.
|
|
426
|
-
| `agent.
|
|
427
|
-
| `
|
|
430
|
+
| `claude.hook.missing-script` | error | A registered Claude command hook whose script does not exist — it never runs |
|
|
431
|
+
| `claude.hook.unknown-event` | error | A Claude hook registered under an event name that is never dispatched |
|
|
432
|
+
| `vscode.hook.missing-script` | error | A VS Code command hook whose script does not exist |
|
|
433
|
+
| `vscode.hook.unknown-event` | error | A VS Code hook registered under an event name that is never dispatched |
|
|
434
|
+
| `claude.agent.missing-frontmatter` | error | A Claude agent definition with no `---` block |
|
|
435
|
+
| `claude.agent.missing-description` | error | Claude agent frontmatter has no `description` |
|
|
436
|
+
| `claude.agent.missing-name` | error | Claude agent frontmatter has no `name` |
|
|
437
|
+
| `claude.agent.invalid-name` | warning | Claude agent name is not lowercase letters, numbers, and hyphens |
|
|
438
|
+
| `claude.agent.duplicate-name` | error | Multiple Claude agent files declare the same name |
|
|
439
|
+
| `claude.mcp.no-launch` | error | A Claude MCP server with neither `command` nor `url` |
|
|
440
|
+
| `claude.mcp.url-without-type` | error | A Claude remote MCP server with a `url` but no `type` — read as stdio and skipped |
|
|
441
|
+
| `vscode.mcp.no-launch` | error | A VS Code MCP server (`servers`) with neither `command` nor `url` |
|
|
442
|
+
| `cursor.mcp.no-launch` | error | A Cursor MCP server with neither `command` nor `url` |
|
|
443
|
+
| `antigravity.mcp.no-launch` | error | An Antigravity MCP server with neither `command` nor `serverUrl` |
|
|
444
|
+
| `codex.mcp.no-launch` | error | A Codex `[mcp_servers.*]` entry with neither `command` nor `url` |
|
|
445
|
+
| `gemini.mcp.no-launch` | error | A Gemini MCP server with neither `command`, `url`, nor `httpUrl` |
|
|
446
|
+
| `opencode.mcp.no-launch` | error | An OpenCode MCP server with neither `command` nor `url` |
|
|
447
|
+
| `opencode.mcp.missing-type` | error | An OpenCode MCP server missing `type: local` or `type: remote` |
|
|
448
|
+
| `opencode.mcp.local-without-command` | error | An OpenCode `type: local` server with no `command` |
|
|
449
|
+
| `opencode.mcp.remote-without-url` | error | An OpenCode `type: remote` server with no `url` |
|
|
450
|
+
| `opencode.mcp.invalid-launch-for-type` | error | An OpenCode server whose launch field does not match its `type` |
|
|
451
|
+
| `continue.mcp.no-launch` | error | A Continue MCP server with neither `command`, `url`, nor `uses` |
|
|
452
|
+
| `claude.hook.command-without-command` | error | A Claude `type: command` hook with no `command` |
|
|
453
|
+
| `claude.hook.invalid-group` | error | A Claude hook matcher group with no `hooks` array |
|
|
428
454
|
| `mcp.command-missing` | error | An MCP `command` that is a path-like value whose file does not exist on disk |
|
|
429
|
-
| `
|
|
430
|
-
| `mcp.
|
|
431
|
-
| `mcp.literal-env` | warning | Long literal `env` values that should be `${VAR}` |
|
|
455
|
+
| `security.hardcoded-secret` | error | A token-shaped literal in MCP config (the value is never echoed back) |
|
|
456
|
+
| `mcp.literal-env` | warning | Secret-named `env` values that are literals instead of interpolation |
|
|
432
457
|
| `skill.missing-skill-md` | warning | A directory under a skill path with no `SKILL.md` |
|
|
433
|
-
| `skill.missing-frontmatter` | warning | `SKILL.md` with no `---` block |
|
|
458
|
+
| `claude.skill.missing-frontmatter` | warning | Claude `SKILL.md` with no `---` block |
|
|
459
|
+
| `claude.skill.missing-description` | info | Claude frontmatter has no `description` (Recommended) |
|
|
460
|
+
| `agent-skills.skill.missing-frontmatter` | error | Portable skill `SKILL.md` has no YAML frontmatter |
|
|
461
|
+
| `agent-skills.skill.missing-name` | error | Portable skill frontmatter has no required `name` |
|
|
462
|
+
| `agent-skills.skill.missing-description` | error | Portable skill frontmatter has no required `description` |
|
|
463
|
+
| `agent-skills.skill.invalid-name` | error | Portable skill name is not `[a-z0-9]` + hyphens |
|
|
464
|
+
| `agent-skills.skill.name-does-not-match-directory` | error | Portable skill `name` does not match its directory |
|
|
465
|
+
| `agent-skills.skill.name-too-long` | error | Portable skill name exceeds 64 characters |
|
|
466
|
+
| `agent-skills.skill.description-too-long` | error | Portable skill description exceeds 1024 characters |
|
|
434
467
|
| `skill.broken-reference` | warning | The body points at a bundled file that does not exist |
|
|
435
|
-
| `skill.duplicate-description` |
|
|
436
|
-
| `skill.missing-description` | info | Frontmatter has no `description`, so Claude will not load the skill on its own |
|
|
468
|
+
| `skill.duplicate-description` | info | Two or more skills carry an identical description (heuristic) |
|
|
437
469
|
| `skill.locked-not-installed` | warning | `skills-lock.json` pins a skill that is not on disk |
|
|
438
|
-
| `skill.not-in-lock` | info | A skill on disk that the lockfile does not track
|
|
439
|
-
| `skill.description-budget` | info |
|
|
470
|
+
| `skill.not-in-lock` | info | A skill on disk that the lockfile does not track (installer policy) |
|
|
471
|
+
| `skill.description-budget` | info | Claude skill names + descriptions exceed the startup character budget (heuristic) |
|
|
440
472
|
| `skill.no-lockfile` | info | Skills present with no lockfile at all (only with `requireLock`) |
|
|
441
|
-
| `budget.agents-md` | info | `AGENTS.md` past
|
|
442
|
-
| `budget.claude-md` | info | `CLAUDE.md` past the
|
|
443
|
-
| `budget.agents` | info | More agent definitions than a focused set (>8
|
|
444
|
-
| `budget.mcp` | info | More MCP servers than
|
|
473
|
+
| `budget.agents-md` | info | `AGENTS.md` past a secondary 150-line hint (heuristic; not an AGENTS.md requirement) |
|
|
474
|
+
| `budget.claude-md` | info | `CLAUDE.md` past the official “target under 200 lines” recommendation |
|
|
475
|
+
| `budget.agents` | info | More agent definitions than a focused set (>8) — heuristic proxy |
|
|
476
|
+
| `budget.mcp` | info | More MCP servers than a tool-selection hint (>5) — heuristic proxy |
|
|
477
|
+
| `codex.budget.instructions` | info | Codex root→cwd `AGENTS.md` chain exceeds the default 32 KiB |
|
|
478
|
+
| `cursor.rule.too-large` | info | A `.cursor/rules/*.mdc` file exceeds the 500-line recommendation |
|
|
479
|
+
|
|
480
|
+
### Coverage in 1.0.0
|
|
481
|
+
|
|
482
|
+
`full` means documented locations are discovered, spec-required fields are checked, and a conformance fixture (or equivalent tests) is green. `partial` means some locations or some checks. `none` means not implemented. Official pages that could not be captured are `none`, not guessed.
|
|
483
|
+
|
|
484
|
+
| Ecosystem | instructions | skills | agents | hooks | MCP |
|
|
485
|
+
|-----------|--------------|--------|--------|-------|-----|
|
|
486
|
+
| Agent Skills | none | full | none | none | none |
|
|
487
|
+
| AGENTS.md | partial (nested + nearest-wins; no required fields) | none | none | none | none |
|
|
488
|
+
| Claude Code | partial (walk-up `CLAUDE.md`; 200-line target) | partial (native; `name` optional) | partial (walk-up `.claude/agents`) | partial (33 events; command handlers only) | full (`claude-json`) |
|
|
489
|
+
| Codex | partial (32 KiB chain; no invented agents TOML) | full (Agent Skills; `.codex/skills`) | none | none | full (`codex-toml`) |
|
|
490
|
+
| VS Code | partial (`.github` instruction files; no required fields) | none | partial (filename may be the name) | full (8 events) | full (`servers`) |
|
|
491
|
+
| Cursor | none | full (Agent Skills; nested `SKILL.md`) | none | none | full |
|
|
492
|
+
| Grok | none | none | none | none | none |
|
|
493
|
+
| Antigravity | none | via Agent Skills | none | none | full (`serverUrl`) |
|
|
494
|
+
| Gemini | none | none | none | none | full (`command` / `url` / `httpUrl`) |
|
|
495
|
+
| Windsurf | none | none | none | none | none (official page is global-only) |
|
|
496
|
+
| Kiro | none | none | none | none | none |
|
|
497
|
+
| Cline | none | none | none | none | none |
|
|
498
|
+
| Roo | none | none | none | none | none |
|
|
499
|
+
| Kilo | none | none | none | none | none |
|
|
500
|
+
| OpenCode | none | none | none | none | full (V1 and V2) |
|
|
501
|
+
| Junie | none | none | none | none | none |
|
|
502
|
+
| Continue | none | none | none | none | full (`config.yaml` + `.continue/mcpServers/*` + `uses`) |
|
|
503
|
+
|
|
504
|
+
Cursor project rules (`.cursor/rules/**/*.mdc`) are a separate surface: discovery plus `cursor.rule.too-large` at info. Claude `.claude/rules/**/*.md` is inventoried and has no published line budget.
|
|
505
|
+
|
|
506
|
+
Old 0.7 rule ids still work in `ignoreRules` and `explain` (for example `hook.missing-script` aliases `claude.hook.missing-script`).
|
|
445
507
|
|
|
446
508
|
Agent definitions are checked for structure only. An agent's frontmatter `name`
|
|
447
509
|
is not compared to the filename; it must simply be a valid lowercase identifier
|
|
@@ -457,7 +519,26 @@ references measured across 17 projects, 1645 resolved skill-relative and 12 only
|
|
|
457
519
|
at the root. Fenced code blocks are stripped first: a path in an example is
|
|
458
520
|
illustration, not a pointer.
|
|
459
521
|
|
|
460
|
-
|
|
522
|
+
### Where it looks for hooks
|
|
523
|
+
|
|
524
|
+
The Claude hooks reference lists seven places a hook can be registered. agentscan
|
|
525
|
+
reads the four that live inside the scanned project, plus VS Code workspace hooks:
|
|
526
|
+
|
|
527
|
+
| Registered in | Script paths resolve against |
|
|
528
|
+
|---|---|
|
|
529
|
+
| `.claude/settings.json` · `.claude/settings.local.json` | project root, `${CLAUDE_PROJECT_DIR}` |
|
|
530
|
+
| A plugin's `hooks/hooks.json` (a directory with `.claude-plugin/plugin.json`, or the scan root itself) | the plugin root, `${CLAUDE_PLUGIN_ROOT}` |
|
|
531
|
+
| `SKILL.md` frontmatter | the skill's own directory, then the project root |
|
|
532
|
+
| `.claude/agents/*.md` frontmatter | the agent file's directory, then the project root |
|
|
533
|
+
| `.github/hooks/*.json` (VS Code) | project root and the hooks directory |
|
|
534
|
+
|
|
535
|
+
`${CLAUDE_PLUGIN_ROOT}` is expanded **only** for a hook that came from a plugin.
|
|
536
|
+
In a settings file it names nothing, so the path is skipped rather than guessed
|
|
537
|
+
at — measured across 17 installed plugins, 31 of 33 plugin hook commands use it
|
|
538
|
+
and none of them are settings hooks. `claude.hook.unknown-event` covers all four sites
|
|
539
|
+
at no extra cost. Sources and measurements: [docs/spec/hook-sources.md](docs/spec/hook-sources.md).
|
|
540
|
+
|
|
541
|
+
`claude.hook.missing-script` and `mcp.command-missing` are deliberately conservative.
|
|
461
542
|
A value is only resolved when it is path-like and the answer is certain; shell
|
|
462
543
|
programs (`a && b`, `$(...)`, pipes), bare PATH binaries (`npx`, `uvx`), and
|
|
463
544
|
unresolved env vars are skipped. A false "broken" on a working `npx` server
|
|
@@ -466,15 +547,17 @@ Only `$CLAUDE_PROJECT_DIR` is expanded — other variables are left alone rather
|
|
|
466
547
|
than guessed at. Schema checks judge misconfiguration, not whether a correctly
|
|
467
548
|
shaped entry will start at runtime.
|
|
468
549
|
|
|
469
|
-
|
|
470
|
-
appear in real projects. Two that were written the other way round
|
|
471
|
-
false positives — a nine-name hook-event list where the spec has 31,
|
|
472
|
-
`name` must equal the directory rule that
|
|
473
|
-
|
|
550
|
+
Spec-required checks are written against published pages, not against what
|
|
551
|
+
happens to appear in real projects. Two that were written the other way round
|
|
552
|
+
shipped as false positives — a nine-name hook-event list where the spec has 31,
|
|
553
|
+
and a `name` must equal the directory rule that Claude's spec explicitly
|
|
554
|
+
contradicts. Both are gone. Heuristics that remain (`skill.duplicate-description`,
|
|
555
|
+
the installer lock checks, and the size budgets other than `budget.claude-md`)
|
|
556
|
+
are labeled in the registry and stay at `info`.
|
|
474
557
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
558
|
+
Assumptions are recorded in **[docs/spec/](docs/spec/)** with source URL, read
|
|
559
|
+
date, and provenance. When adding a spec-required check, add its spec line there
|
|
560
|
+
first.
|
|
478
561
|
|
|
479
562
|
## Known limits
|
|
480
563
|
|
|
@@ -489,6 +572,11 @@ When adding a check, add its spec line there first.
|
|
|
489
572
|
- **Runtime split.** Source development and the composite Action use Bun; the
|
|
490
573
|
published `dist/cli.js` runs on Node 20.11+ or Bun. The source entrypoint uses
|
|
491
574
|
Bun's `import.meta.dir` / `import.meta.main` and is not the Node entrypoint.
|
|
575
|
+
- **Three hook locations are unread.** `~/.claude/settings.json`, managed policy
|
|
576
|
+
settings, and installed marketplace plugins under `~/.claude/plugins` all sit
|
|
577
|
+
outside the scanned project, and the docs say a plugin's install directory
|
|
578
|
+
changes on every update. A hook registered in one of those, pointing at a
|
|
579
|
+
missing script, is not reported.
|
|
492
580
|
- **Bounded reads.** A `SKILL.md` is read to 64 KB and a policy file to 100 KB,
|
|
493
581
|
so `skill.broken-reference` and `policyLines` see only that much of a larger
|
|
494
582
|
file. This is reported — `scan.truncated`, at `info` — rather than left
|
|
@@ -496,6 +584,21 @@ When adding a check, add its spec line there first.
|
|
|
496
584
|
that read it see all of it. Reporting the cap as `config.unreadable` at
|
|
497
585
|
severity error is a defect this tool shipped and [plan 019](plans/019-findings-say-true-things.md) fixed.
|
|
498
586
|
|
|
587
|
+
## Releases
|
|
588
|
+
|
|
589
|
+
Notes for every published version are in [CHANGELOG.md](CHANGELOG.md).
|
|
590
|
+
GitHub already has pages for [v0.1.0](https://github.com/SimaAlexandru99/agentscan/releases/tag/v0.1.0)
|
|
591
|
+
and [v0.4.0](https://github.com/SimaAlexandru99/agentscan/releases/tag/v0.4.0).
|
|
592
|
+
|
|
593
|
+
**1.0.0** (30 August 2026) is the first stable release: 59 checks, 345 tests,
|
|
594
|
+
still offline on `check`. It adds portable Agent Skills (including Cursor and
|
|
595
|
+
Codex skill trees), MCP profiles for VS Code / Cursor / Codex / Antigravity /
|
|
596
|
+
Gemini / OpenCode / Continue, nested `AGENTS.md`, walk-up agents, VS Code
|
|
597
|
+
hooks, and platform-aware launch checks — a `windows:` script is not
|
|
598
|
+
POSIX-joined when the scanner runs on Linux. Old 0.7 rule ids still work in
|
|
599
|
+
`ignoreRules` and `explain`. 0.9.0 was planned as a separate train and shipped
|
|
600
|
+
inside this release.
|
|
601
|
+
|
|
499
602
|
## Development
|
|
500
603
|
|
|
501
604
|
```bash
|
|
@@ -520,6 +623,8 @@ Before publishing a release:
|
|
|
520
623
|
2. Run `bun run build`, then verify `node dist/cli.js --version` prints it.
|
|
521
624
|
3. Run `bun run typecheck`, `bun test`, and `bun run spec:check`.
|
|
522
625
|
4. Refresh the test-count badge and version examples above from that release run.
|
|
626
|
+
5. Add a section to [CHANGELOG.md](CHANGELOG.md) for the version, then publish
|
|
627
|
+
the GitHub release from that text.
|
|
523
628
|
|
|
524
629
|
## Design docs
|
|
525
630
|
|