@chimix/agentscan 0.7.0 → 1.1.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 +368 -0
- package/README.md +250 -88
- package/dist/cli.js +6863 -833
- package/package.json +6 -2
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-87-111111?style=flat-square" alt="87 checks">
|
|
9
|
+
<img src="https://img.shields.io/badge/tests-420%20passing-111111?style=flat-square" alt="420 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.1.0-111111?style=flat-square" alt="Changelog"></a>
|
|
15
16
|
</p>
|
|
16
17
|
|
|
17
18
|
<p align="center">
|
|
18
|
-
<strong>
|
|
19
|
-
<sub>
|
|
19
|
+
<strong>87 checks · offline · 0 network calls on <code>check</code> · provenance on every rule</strong><br>
|
|
20
|
+
<sub>An offline linter for Claude Code, Command Code, portable Agent Skills, nested AGENTS.md, Copilot CLI hooks, and the MCP / hooks / rules surfaces that 1.1.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 — five dimensions, and a documented global location that is not scanned stays unread.</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/ .commandcode/ .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 87 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
|
|
|
@@ -110,10 +116,10 @@ too close to an unrelated `agent-scan`. The command you type stays `agentscan`.
|
|
|
110
116
|
bun add -d @chimix/agentscan # then: bunx agentscan check
|
|
111
117
|
```
|
|
112
118
|
|
|
113
|
-
Point it at a project that actually has agent config — a `.claude/`
|
|
114
|
-
an `.mcp.json`, an `AGENTS.md`. A directory with
|
|
115
|
-
`package.json` is still scannable. On a directory with
|
|
116
|
-
clear error.
|
|
119
|
+
Point it at a project that actually has agent config — a `.claude/` or
|
|
120
|
+
`.commandcode/` directory, an `.mcp.json`, an `AGENTS.md`. A directory with
|
|
121
|
+
agent config and no `package.json` is still scannable. On a directory with
|
|
122
|
+
neither it exits with a clear error.
|
|
117
123
|
|
|
118
124
|
```bash
|
|
119
125
|
# what it could possibly report, before you run it
|
|
@@ -123,7 +129,7 @@ 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
|
|
@@ -138,7 +144,7 @@ The Action runs from its own checkout, so it uses the ref you pin rather than
|
|
|
138
144
|
whatever is on npm:
|
|
139
145
|
|
|
140
146
|
```yaml
|
|
141
|
-
- uses: SimaAlexandru99/agentscan@
|
|
147
|
+
- uses: SimaAlexandru99/agentscan@v1
|
|
142
148
|
with:
|
|
143
149
|
fail-on: error
|
|
144
150
|
```
|
|
@@ -180,7 +186,7 @@ Flags for `check`:
|
|
|
180
186
|
| `--verbose` | Show KEEP + info-severity findings, and print each finding's id |
|
|
181
187
|
| `--fail-on <level>` | `never` (default) · `warning` · `error` |
|
|
182
188
|
| `--fail-under <0-100>` | Fail when the score drops below this floor |
|
|
183
|
-
| `--global` | Also scan `~/.claude/skills
|
|
189
|
+
| `--global` | Also scan `~/.claude/skills`, `~/.codex/skills`, `~/.commandcode/skills`, and `~/.agents/skills` (see below) |
|
|
184
190
|
| `--config <path>` | Config file path |
|
|
185
191
|
|
|
186
192
|
**v1 does not write the tree** — no `apply`, no skill delete/install. Findings may *suggest* shell commands; you run them yourself.
|
|
@@ -193,18 +199,18 @@ Flags for `check`:
|
|
|
193
199
|
│ ⌒ │ 6 error · 2 warning · 11 info hidden (--verbose)
|
|
194
200
|
└─────┘
|
|
195
201
|
|
|
196
|
-
agentscan
|
|
202
|
+
agentscan v1.1.0 — touchagency
|
|
197
203
|
|
|
198
204
|
Scanned: 46 deps · 108 skills · 1 mcp · 2 agents · packageManager=bun
|
|
199
205
|
|
|
200
|
-
ERROR rule:hook.missing-script ×6
|
|
206
|
+
ERROR rule:claude.hook.missing-script ×6
|
|
201
207
|
Hook points at a script that does not exist
|
|
202
208
|
PostToolUse @ .claude/settings.json · .claude/hooks/notify-related-tests.js
|
|
203
209
|
PreToolUse @ .claude/settings.json · .claude/hooks/guard-destructive-bash.js
|
|
204
210
|
PreToolUse @ .claude/settings.json · .claude/hooks/protect-artists-json.js
|
|
205
211
|
PreToolUse @ .claude/settings.json · .claude/hooks/protect-env.js
|
|
206
212
|
|
|
207
|
-
WARN rule:agent.invalid-name ×2
|
|
213
|
+
WARN rule:claude.agent.invalid-name ×2
|
|
208
214
|
2 findings
|
|
209
215
|
.claude/agents/marketing-seo-specialist.md
|
|
210
216
|
.claude/agents/testing-accessibility-auditor.md
|
|
@@ -225,7 +231,7 @@ JSON shape (abridged):
|
|
|
225
231
|
|
|
226
232
|
```json
|
|
227
233
|
{
|
|
228
|
-
"version": "
|
|
234
|
+
"version": "1.1.0",
|
|
229
235
|
"root": "/path/to/project",
|
|
230
236
|
"factsSummary": {
|
|
231
237
|
"packageManager": "bun",
|
|
@@ -235,8 +241,8 @@ JSON shape (abridged):
|
|
|
235
241
|
},
|
|
236
242
|
"findings": [
|
|
237
243
|
{
|
|
238
|
-
"id": "hook.missing-script:hook:PreToolUse:.claude/hooks/protect-env.js",
|
|
239
|
-
"ruleId": "hook.missing-script",
|
|
244
|
+
"id": "claude.hook.missing-script:hook:PreToolUse:.claude/hooks/protect-env.js",
|
|
245
|
+
"ruleId": "claude.hook.missing-script",
|
|
240
246
|
"action": "warn",
|
|
241
247
|
"severity": "error",
|
|
242
248
|
"subject": "hook:PreToolUse:.claude/hooks/protect-env.js",
|
|
@@ -253,13 +259,20 @@ Same tree → same sorted findings, with stable unique `id`s.
|
|
|
253
259
|
|
|
254
260
|
### `--global`
|
|
255
261
|
|
|
256
|
-
Adds `~/.claude/skills
|
|
257
|
-
`SKILL.md` that is malformed is
|
|
258
|
-
carry a `source: global`
|
|
262
|
+
Adds `~/.claude/skills`, `~/.codex/skills`, `~/.commandcode/skills`, and
|
|
263
|
+
`~/.agents/skills` to the structural checks: a `SKILL.md` that is malformed is
|
|
264
|
+
malformed wherever it lives, and those findings carry a `source: global`
|
|
265
|
+
evidence entry so you can tell them apart. Codex and Command Code skills use
|
|
266
|
+
the Agent Skills contract (`name` and `description` required). `--global` also
|
|
267
|
+
inventories user Command Code agents (`~/.commandcode/agents`), MCP
|
|
268
|
+
(`~/.commandcode/mcp.json` and user settings `mcp.servers`), and memory
|
|
269
|
+
(`~/.commandcode/AGENTS.md`). It never opens `auth.json` or `mcp-tokens.json`.
|
|
259
270
|
|
|
260
271
|
Lockfile checks stay project-scoped — a project lockfile cannot pin a skill that
|
|
261
272
|
lives in your home directory, so reporting one as "not in the lockfile" could
|
|
262
|
-
only ever be wrong.
|
|
273
|
+
only ever be wrong. In a monorepo, each `skills-lock.json` governs only the
|
|
274
|
+
skills under its directory (nearest lock wins). A skill with no ancestor
|
|
275
|
+
lockfile is not compared to a distant lock.
|
|
263
276
|
|
|
264
277
|
`includeGlobal: true` in `.agentscanrc.json` does the same thing without the
|
|
265
278
|
flag.
|
|
@@ -305,7 +318,7 @@ only says how many points of unspecified trouble is too much.
|
|
|
305
318
|
## CI
|
|
306
319
|
|
|
307
320
|
```yaml
|
|
308
|
-
- uses: SimaAlexandru99/agentscan@
|
|
321
|
+
- uses: SimaAlexandru99/agentscan@v1
|
|
309
322
|
with:
|
|
310
323
|
fail-on: error # never | warning | error
|
|
311
324
|
output: human # human | json | prompt
|
|
@@ -333,8 +346,8 @@ Optional `.agentscanrc.json` (create with `agentscan init`):
|
|
|
333
346
|
|
|
334
347
|
```json
|
|
335
348
|
{
|
|
336
|
-
"skillPaths": [".agents/skills", ".claude/skills", "skills", ".cursor/skills"],
|
|
337
|
-
"mcpPaths": [".mcp.json", ".claude/mcp.json", "mcp.json"],
|
|
349
|
+
"skillPaths": [".agents/skills", ".claude/skills", "skills", ".cursor/skills", ".codex/skills", ".commandcode/skills"],
|
|
350
|
+
"mcpPaths": [".mcp.json", ".claude/mcp.json", "mcp.json", ".vscode/mcp.json", ".cursor/mcp.json", ".agents/mcp_config.json", ".codex/config.toml", ".gemini/settings.json", "opencode.json", "opencode.jsonc", ".opencode/opencode.json", ".opencode/opencode.jsonc", ".continue/config.yaml", ".continue/mcpServers"],
|
|
338
351
|
"policyFiles": ["AGENTS.md", "CLAUDE.md"],
|
|
339
352
|
"ignoreSkills": [],
|
|
340
353
|
"ignoreRules": [],
|
|
@@ -343,8 +356,8 @@ Optional `.agentscanrc.json` (create with `agentscan init`):
|
|
|
343
356
|
"includeGlobal": false,
|
|
344
357
|
"requireLock": false,
|
|
345
358
|
"thresholds": {
|
|
346
|
-
"
|
|
347
|
-
"
|
|
359
|
+
"skillListingChars": 8000,
|
|
360
|
+
"skillListingMaxDescChars": 1536,
|
|
348
361
|
"mcp": 5,
|
|
349
362
|
"agentsMdLines": 150,
|
|
350
363
|
"claudeMdLines": 200,
|
|
@@ -401,7 +414,7 @@ each finding, and `--json` carries it as `.findings[].id`, so the value you copy
|
|
|
401
414
|
out of the report is the value you paste:
|
|
402
415
|
|
|
403
416
|
```json
|
|
404
|
-
{ "ignoreFindings": ["hook.missing-script:hook:PreToolUse:./bin/wrapper"] }
|
|
417
|
+
{ "ignoreFindings": ["claude.hook.missing-script:hook:PreToolUse:./bin/wrapper"] }
|
|
405
418
|
```
|
|
406
419
|
|
|
407
420
|
## What it checks
|
|
@@ -409,60 +422,161 @@ out of the report is the value you paste:
|
|
|
409
422
|
Every check lives in `src/checks/` and runs on every `check`. `agentscan rules`
|
|
410
423
|
lists all of them with their ids; `agentscan explain <id>` details any finding.
|
|
411
424
|
|
|
412
|
-
Most validate one discovered item against its own file on disk.
|
|
413
|
-
|
|
414
|
-
`
|
|
415
|
-
|
|
416
|
-
`.agentscanrc.json` rather than switched off.
|
|
425
|
+
Most validate one discovered item against its own file on disk. Budget and
|
|
426
|
+
hygiene checks are **info** (hidden unless `--verbose`). Each registry entry
|
|
427
|
+
carries `provenance`: spec-required, vendor-recommendation, security,
|
|
428
|
+
internal-consistency, or heuristic. `agentscan rules` lists them all.
|
|
417
429
|
|
|
418
430
|
| id | Severity | Catches |
|
|
419
431
|
|----|----------|---------|
|
|
420
432
|
| `config.unreadable` | error | A config file that is not valid JSON, so whatever it declares is silently not in effect |
|
|
421
433
|
| `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 |
|
|
422
|
-
| `hook.missing-script` | error | A registered hook whose script does not exist — it never runs
|
|
423
|
-
| `hook.unknown-event` | error | A hook registered under an event name that is never dispatched |
|
|
424
|
-
| `
|
|
425
|
-
| `
|
|
426
|
-
| `agent.missing-
|
|
427
|
-
| `agent.
|
|
428
|
-
| `agent.
|
|
429
|
-
| `
|
|
434
|
+
| `claude.hook.missing-script` | error | A registered Claude command hook whose script does not exist — it never runs |
|
|
435
|
+
| `claude.hook.unknown-event` | error | A Claude hook registered under an event name that is never dispatched |
|
|
436
|
+
| `vscode.hook.missing-script` | error | A VS Code command hook whose script does not exist |
|
|
437
|
+
| `vscode.hook.unknown-event` | error | A VS Code hook registered under an event name that is never dispatched |
|
|
438
|
+
| `claude.agent.missing-frontmatter` | error | A Claude agent definition with no `---` block |
|
|
439
|
+
| `claude.agent.missing-description` | error | Claude agent frontmatter has no `description` |
|
|
440
|
+
| `claude.agent.missing-name` | error | Claude agent frontmatter has no `name` |
|
|
441
|
+
| `claude.agent.invalid-name` | warning | Claude agent name is not lowercase letters and hyphens (error if it starts with `-` or contains `:`; filename is not compared) |
|
|
442
|
+
| `claude.agent.duplicate-name` | error | Multiple Claude agent files declare the same name |
|
|
443
|
+
| `claude.mcp.no-launch` | error | A Claude MCP server with neither `command` nor `url` |
|
|
444
|
+
| `claude.mcp.url-without-type` | error | A Claude remote MCP server with a `url` but no `type` — read as stdio and skipped. On shared `.mcp.json`, a Command Code `transport` field satisfies the other consumer and this check does not fire |
|
|
445
|
+
| `claude.mcp.reserved-name` | error | A Claude-consumed MCP server named with a reserved identifier (`workspace`, `claude-in-chrome`, `computer-use`, `Claude Preview`, `Claude Browser`) — skipped at load |
|
|
446
|
+
| `vscode.mcp.no-launch` | error | A VS Code MCP server (`servers`) with neither `command` nor `url` |
|
|
447
|
+
| `cursor.mcp.no-launch` | error | A Cursor MCP server with neither `command` nor `url` |
|
|
448
|
+
| `antigravity.mcp.no-launch` | error | An Antigravity MCP server with neither `command` nor `serverUrl` |
|
|
449
|
+
| `codex.mcp.no-launch` | error | A Codex `[mcp_servers.*]` entry with neither `command` nor `url` |
|
|
450
|
+
| `gemini.mcp.no-launch` | error | A Gemini MCP server with neither `command`, `url`, nor `httpUrl` |
|
|
451
|
+
| `gemini.mcp.underscore-alias` | warning | A Gemini MCP server alias contains `_`, which can make policies fail silently |
|
|
452
|
+
| `opencode.mcp.no-launch` | error | An OpenCode MCP server with neither `command` nor `url` |
|
|
453
|
+
| `opencode.mcp.missing-type` | error | An OpenCode MCP server missing `type: local` or `type: remote` |
|
|
454
|
+
| `opencode.mcp.local-without-command` | error | An OpenCode `type: local` server with no `command` |
|
|
455
|
+
| `opencode.mcp.remote-without-url` | error | An OpenCode `type: remote` server with no `url` |
|
|
456
|
+
| `opencode.mcp.invalid-launch-for-type` | error | An OpenCode server whose launch field does not match its `type` |
|
|
457
|
+
| `opencode.mcp.command-not-array` | error | An OpenCode V2 local `command` that is not an argv array |
|
|
458
|
+
| `continue.mcp.no-launch` | error | A Continue MCP server with neither `command`, `url`, nor `uses` |
|
|
459
|
+
| `continue.mcp.missing-block-metadata` | error | A standalone `.continue/mcpServers/*.yaml` block missing `name`, `version`, or `schema` (not applied to copied JSON) |
|
|
460
|
+
| `commandcode.mcp.no-launch` | error | A Command Code settings / user MCP server with neither `command` nor `url` |
|
|
461
|
+
| `commandcode.mcp.invalid-transport` | error | A Command Code MCP `transport` / `type` that is not `http` or `stdio` (Claude `sse` / `ws` on shared `.mcp.json` is skipped) |
|
|
462
|
+
| `commandcode.mcp.http-without-url` | error | A Command Code HTTP MCP server with no `url` |
|
|
463
|
+
| `commandcode.mcp.stdio-without-command` | error | A Command Code stdio MCP server with no `command` |
|
|
464
|
+
| `claude.hook.command-without-command` | error | A Claude `type: command` hook with no `command` |
|
|
465
|
+
| `claude.hook.http-without-url` | error | A Claude `type: http` hook with no `url` |
|
|
466
|
+
| `claude.hook.mcp-tool-without-server-or-tool` | error | A Claude `type: mcp_tool` hook missing `server` or `tool` |
|
|
467
|
+
| `claude.hook.unknown-handler-type` | error | A Claude hook whose required `type` is missing or not one of command, http, mcp_tool, prompt, agent |
|
|
468
|
+
| `claude.hook.prompt-without-prompt` | error | A Claude `type: prompt` or `type: agent` hook with no `prompt` |
|
|
469
|
+
| `claude.hook.incompatible-handler` | error | A Claude handler type that the event does not support |
|
|
470
|
+
| `claude.hook.invalid-group` | error | A Claude hook matcher group with no nested `hooks` array (flat handler arrays are invalid) |
|
|
471
|
+
| `vscode.hook.invalid-group` | error | A VS Code hook group that is not a command handler array |
|
|
472
|
+
| `vscode.hook.command-without-command` | error | A native VS Code `type: command` hook with no `command` |
|
|
473
|
+
| `vscode.hook.unknown-handler-type` | error | A native VS Code hook whose `type` is not `command` |
|
|
474
|
+
| `copilot.hook.unknown-event` | error | A Copilot CLI hook registered under an event that is never dispatched |
|
|
475
|
+
| `copilot.hook.missing-script` | error | A Copilot CLI command hook whose script does not exist |
|
|
476
|
+
| `copilot.hook.command-without-command` | error | A Copilot CLI command hook with none of `bash`, `powershell`, or `command` |
|
|
477
|
+
| `copilot.hook.http-without-url` | error | A Copilot CLI `type: http` hook with no `url` |
|
|
478
|
+
| `copilot.hook.prompt-without-prompt` | error | A Copilot CLI `type: prompt` hook with no `prompt` |
|
|
479
|
+
| `copilot.hook.unknown-handler-type` | error | A Copilot CLI hook whose `type` is not command, http, or prompt |
|
|
480
|
+
| `copilot.hook.incompatible-handler` | error | A Copilot CLI prompt hook registered on an event other than `sessionStart` |
|
|
481
|
+
| `commandcode.hook.unknown-event` | error | A Command Code hook registered under an event that is never dispatched (four events only) |
|
|
482
|
+
| `commandcode.hook.missing-script` | error | A Command Code command hook whose script does not exist |
|
|
483
|
+
| `commandcode.hook.invalid-group` | error | A Command Code hook group missing the required nested `hooks` array, or a non-string `matcher` |
|
|
484
|
+
| `commandcode.hook.command-without-command` | error | A Command Code `type: command` hook whose `command` is missing, empty, or not a string |
|
|
485
|
+
| `commandcode.hook.unknown-handler-type` | error | A Command Code hook whose `type` is missing, not a string, or not `"command"` |
|
|
486
|
+
| `commandcode.hook.timeout-out-of-bounds` | error | A Command Code hook `timeout` outside 0–600 seconds |
|
|
487
|
+
| `commandcode.agent.reserved-name` | error | A Command Code custom agent named `explore`, `plan`, `review`, or `general` — ignored at load |
|
|
488
|
+
| `commandcode.agent.invalid-permission-mode` | error | A Command Code agent `permissionMode` that is not a documented value |
|
|
489
|
+
| `commandcode.agent.invalid-field-type` | error | A Command Code agent field whose type the spec does not accept |
|
|
430
490
|
| `mcp.command-missing` | error | An MCP `command` that is a path-like value whose file does not exist on disk |
|
|
431
|
-
| `
|
|
432
|
-
| `mcp.
|
|
433
|
-
| `mcp.literal-env` | warning | Long literal `env` values that should be `${VAR}` |
|
|
491
|
+
| `security.hardcoded-secret` | error | A token-shaped literal in MCP config (the value is never echoed back) |
|
|
492
|
+
| `mcp.literal-env` | warning | Secret-named `env` values that are literals instead of interpolation |
|
|
434
493
|
| `skill.missing-skill-md` | warning | A directory under a skill path with no `SKILL.md` |
|
|
435
|
-
| `skill.missing-frontmatter` | warning | `SKILL.md` with no `---` block |
|
|
494
|
+
| `claude.skill.missing-frontmatter` | warning | Claude `SKILL.md` with no `---` block |
|
|
495
|
+
| `claude.skill.missing-description` | info | Claude frontmatter has no `description` and no first markdown paragraph |
|
|
496
|
+
| `agent-skills.skill.missing-frontmatter` | error | Portable skill `SKILL.md` has no YAML frontmatter |
|
|
497
|
+
| `agent-skills.skill.missing-name` | error | Portable skill frontmatter has no required `name` |
|
|
498
|
+
| `agent-skills.skill.missing-description` | error | Portable skill frontmatter has no required `description` |
|
|
499
|
+
| `agent-skills.skill.invalid-name` | error | Portable skill name is not `[a-z0-9]` + hyphens |
|
|
500
|
+
| `agent-skills.skill.name-does-not-match-directory` | error | Portable skill `name` does not match its directory |
|
|
501
|
+
| `agent-skills.skill.name-too-long` | error | Portable skill name exceeds 64 characters |
|
|
502
|
+
| `agent-skills.skill.description-too-long` | error | Portable skill description exceeds 1024 characters |
|
|
503
|
+
| `agent-skills.skill.invalid-compatibility` | error | Portable skill `compatibility` is present but is not a 1–500 character string |
|
|
504
|
+
| `agent-skills.skill.invalid-metadata` | error | Portable skill `metadata` is present but is not `map<string, string>` |
|
|
505
|
+
| `agent-skills.skill.invalid-allowed-tools` | error | Portable skill `allowed-tools` is present but is not a string |
|
|
506
|
+
| `agent-skills.skill.body-too-large` | info | Portable skill `SKILL.md` exceeds the 500-line recommendation |
|
|
436
507
|
| `skill.broken-reference` | warning | The body points at a bundled file that does not exist |
|
|
437
|
-
| `skill.duplicate-description` |
|
|
438
|
-
| `skill.missing-description` | info | Frontmatter has no `description`, so Claude will not load the skill on its own |
|
|
508
|
+
| `skill.duplicate-description` | info | Two or more skills carry an identical description (heuristic) |
|
|
439
509
|
| `skill.locked-not-installed` | warning | `skills-lock.json` pins a skill that is not on disk |
|
|
440
|
-
| `skill.not-in-lock` | info | A skill on disk that the lockfile does not track
|
|
441
|
-
| `skill.description-budget` | info |
|
|
510
|
+
| `skill.not-in-lock` | info | A skill on disk that the lockfile does not track (installer policy) |
|
|
511
|
+
| `skill.description-budget` | info | Claude skill listing text (description or first paragraph, plus `when_to_use`) exceeds the 1% context-window budget, with an 8000-character fallback and a 1536-character per-entry cap |
|
|
442
512
|
| `skill.no-lockfile` | info | Skills present with no lockfile at all (only with `requireLock`) |
|
|
443
|
-
| `budget.agents-md` | info | `AGENTS.md` past
|
|
444
|
-
| `budget.claude-md` | info | `CLAUDE.md` past the
|
|
445
|
-
| `budget.agents` | info | More agent definitions than a focused set (>8
|
|
446
|
-
| `budget.mcp` | info | More MCP servers than
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
513
|
+
| `budget.agents-md` | info | `AGENTS.md` past a secondary 150-line hint (heuristic; not an AGENTS.md requirement) |
|
|
514
|
+
| `budget.claude-md` | info | `CLAUDE.md` past the official “target under 200 lines” recommendation |
|
|
515
|
+
| `budget.agents` | info | More agent definitions than a focused set (>8) — heuristic proxy |
|
|
516
|
+
| `budget.mcp` | info | More MCP servers than a tool-selection hint (>5) — heuristic proxy |
|
|
517
|
+
| `codex.budget.instructions` | info | Codex root→cwd `AGENTS.md` chain exceeds the default 32 KiB |
|
|
518
|
+
| `cursor.rule.too-large` | info | A `.cursor/rules/*.mdc` file exceeds the 500-line recommendation |
|
|
519
|
+
|
|
520
|
+
### Coverage in 1.1.0
|
|
521
|
+
|
|
522
|
+
Each cell is one coverage dimension. There is no `full` / `partial` label: a
|
|
523
|
+
documented global or static location that is not opened stays **unread**.
|
|
524
|
+
|
|
525
|
+
Dimensions:
|
|
526
|
+
|
|
527
|
+
- **project discovery** — documented in-repo files the scanner opens
|
|
528
|
+
- **global discovery** — documented user / machine locations; `--global` is named when that is the only path that opens them
|
|
529
|
+
- **schema** — spec-required (and labeled vendor-recommendation) field checks
|
|
530
|
+
- **precedence** — documented merge / override / shadowing
|
|
531
|
+
- **conformance** — official-shaped fixture or equivalent negative tests
|
|
532
|
+
|
|
533
|
+
| Ecosystem | project discovery | global discovery | schema | precedence | conformance |
|
|
534
|
+
|-----------|-------------------|------------------|--------|------------|-------------|
|
|
535
|
+
| Agent Skills | `.agents/skills` (and Cursor / Codex / Command Code skill trees) | `--global` `~/.agents/skills`, `~/.codex/skills` | required `name` / `description` plus optional `compatibility` / `metadata` / `allowed-tools`; 500-line info | n/a | `agent-skills` fixture |
|
|
536
|
+
| AGENTS.md | nested walk-up | n/a | no required fields | nearest-wins | tests |
|
|
537
|
+
| Claude Code | project settings, skills, agents, `CLAUDE.md`, `.mcp.json` / `.claude/mcp.json` | unread `~/.claude/settings.json`, managed policy, marketplace plugins | 33 events; required handler `type`; MCP reserved names; first-paragraph skill description; listing budget | walk-up `CLAUDE.md` / `.claude/agents` | `claude-json` fixture |
|
|
538
|
+
| Command Code | git-root project files; per-directory `AGENTS.md` else `.commandcode/AGENTS.md` | `--global` `~/.commandcode/*`; unread `projects/{slug}/mcp.json` | 4 events; command handlers; Agent Skills; MCP `transport` / `type` | settings merge; project+user hooks coexist; skill/MCP shadow | `commandcode` fixture |
|
|
539
|
+
| Codex | `.codex/config.toml`, `.codex/skills`, AGENTS chain | `--global` `~/.codex/AGENTS.override.md` then `AGENTS.md` | TOML MCP; `project_doc_max_bytes`; Agent Skills | override > `AGENTS.md` > fallbacks; one file per dir; root→cwd; `project_root_markers` | `codex-toml` fixture |
|
|
540
|
+
| VS Code | `.github/hooks` without `version: 1`, instruction files, `.github/agents`, `.vscode/mcp.json` | `--global` `~/.copilot/hooks` without `version: 1`; unread policy dirs | 8 events; command-only | workspace over user | `vscode-hooks`, `vscode-json` |
|
|
541
|
+
| Copilot CLI | `.github/hooks` with `version: 1` | `--global` `~/.copilot/hooks` with `version: 1`; unread `/etc/github-copilot/policy.d` | camelCase + PascalCase map; `bash` / `powershell` / `command`; `cwd`; `timeoutSec`; prompt on `sessionStart` | documented sources coexist; policy unread | `copilot-hooks` fixture |
|
|
542
|
+
| Cursor | nested `.cursor/skills`, `.cursor/mcp.json`, `.cursor/rules` | unread documented Cursor user/global paths | Agent Skills; MCP launch; 500-line rules | n/a | `cursor-json` fixture |
|
|
543
|
+
| Grok | none | none | none | n/a | none |
|
|
544
|
+
| Antigravity | `.agents/mcp_config.json` | none | `serverUrl` launch | n/a | `antigravity-json` fixture |
|
|
545
|
+
| Gemini | `.gemini/settings.json` | unread `~/.gemini/settings.json` unless `--global` is wired for it (it is not) | `command` / `url` / `httpUrl`; underscore-alias warning | n/a | `gemini-json` fixture |
|
|
546
|
+
| Windsurf | none | unread (official page is global-only) | none | n/a | none |
|
|
547
|
+
| Kiro | none | none | none | n/a | none |
|
|
548
|
+
| Cline | none | none | none | n/a | none |
|
|
549
|
+
| Roo | none | none | none | n/a | none |
|
|
550
|
+
| Kilo | none | none | none | n/a | none |
|
|
551
|
+
| OpenCode | `opencode.json(c)` V1 and V2 | none | V2 local `command` must be an argv array | n/a | `opencode-json` fixture |
|
|
552
|
+
| Junie | none | none | none | n/a | none |
|
|
553
|
+
| Continue | `.continue/config.yaml`, `.continue/mcpServers/*` | none | launch `command` / `url` / `uses`; standalone YAML `name` / `version` / `schema` | n/a | `continue-yaml`, `continue-mcpservers` |
|
|
554
|
+
|
|
555
|
+
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.
|
|
556
|
+
|
|
557
|
+
Old 0.7 rule ids still work in `ignoreRules` and `explain` (for example `hook.missing-script` aliases `claude.hook.missing-script`).
|
|
558
|
+
|
|
559
|
+
Agent definitions are checked for structure only. A Claude agent's frontmatter
|
|
560
|
+
`name` is not compared to the filename; it must be lowercase letters and
|
|
561
|
+
hyphens and unique. Command Code supplies `name` from the filename when
|
|
562
|
+
frontmatter omits it, and does not emit missing-name or missing-description.
|
|
563
|
+
Model identifiers and tool names are likewise not validated: both would need a
|
|
564
|
+
hardcoded list, which is the shape that already shipped a false error. See
|
|
565
|
+
[plans/003](plans/003-validate-agent-definitions.md).
|
|
453
566
|
|
|
454
567
|
`skill.broken-reference` reads the body, not just the frontmatter. It looks for
|
|
455
568
|
paths under the conventional bundled directories (`scripts/`, `references/`,
|
|
456
|
-
`assets/`, `templates/`, `examples/`)
|
|
457
|
-
|
|
569
|
+
`assets/`, `templates/`, `examples/`). Agent Skills references resolve from the
|
|
570
|
+
skill root only. Claude native skills also try the repo root — of 1674
|
|
458
571
|
references measured across 17 projects, 1645 resolved skill-relative and 12 only
|
|
459
572
|
at the root. Fenced code blocks are stripped first: a path in an example is
|
|
460
573
|
illustration, not a pointer.
|
|
461
574
|
|
|
462
575
|
### Where it looks for hooks
|
|
463
576
|
|
|
464
|
-
The hooks reference lists seven places a hook can be registered. agentscan
|
|
465
|
-
the four that live inside the scanned project
|
|
577
|
+
The Claude hooks reference lists seven places a hook can be registered. agentscan
|
|
578
|
+
reads the four that live inside the scanned project, plus VS Code workspace hooks
|
|
579
|
+
and Command Code settings hooks:
|
|
466
580
|
|
|
467
581
|
| Registered in | Script paths resolve against |
|
|
468
582
|
|---|---|
|
|
@@ -470,31 +584,38 @@ the four that live inside the scanned project:
|
|
|
470
584
|
| A plugin's `hooks/hooks.json` (a directory with `.claude-plugin/plugin.json`, or the scan root itself) | the plugin root, `${CLAUDE_PLUGIN_ROOT}` |
|
|
471
585
|
| `SKILL.md` frontmatter | the skill's own directory, then the project root |
|
|
472
586
|
| `.claude/agents/*.md` frontmatter | the agent file's directory, then the project root |
|
|
587
|
+
| `.github/hooks/*.json` without `version: 1` (native VS Code) | project root and the hooks directory |
|
|
588
|
+
| `.github/hooks/*.json` with `version: 1` (Copilot CLI) | project root, `cwd`, host `bash` / `powershell` / `command` |
|
|
589
|
+
| `~/.copilot/hooks` under `--global` | same split on `version: 1` |
|
|
590
|
+
| `.commandcode/settings.json` · `.commandcode/settings.local.json` (and user settings under `--global`) | Command Code project root (git root, or cwd outside a git repo), `$COMMANDCODE_PROJECT_DIR`, `$COMMANDCODE_CWD` |
|
|
473
591
|
|
|
474
592
|
`${CLAUDE_PLUGIN_ROOT}` is expanded **only** for a hook that came from a plugin.
|
|
475
593
|
In a settings file it names nothing, so the path is skipped rather than guessed
|
|
476
594
|
at — measured across 17 installed plugins, 31 of 33 plugin hook commands use it
|
|
477
|
-
and none of them are settings hooks. `hook.unknown-event` covers all four sites
|
|
595
|
+
and none of them are settings hooks. `claude.hook.unknown-event` covers all four sites
|
|
478
596
|
at no extra cost. Sources and measurements: [docs/spec/hook-sources.md](docs/spec/hook-sources.md).
|
|
479
597
|
|
|
480
|
-
`hook.missing-script` and `mcp.command-missing` are deliberately conservative.
|
|
598
|
+
`claude.hook.missing-script` and `mcp.command-missing` are deliberately conservative.
|
|
481
599
|
A value is only resolved when it is path-like and the answer is certain; shell
|
|
482
600
|
programs (`a && b`, `$(...)`, pipes), bare PATH binaries (`npx`, `uvx`), and
|
|
483
601
|
unresolved env vars are skipped. A false "broken" on a working `npx` server
|
|
484
602
|
would be worse than silence. `node -e "<code>"` is never treated as a path.
|
|
485
|
-
Only `$CLAUDE_PROJECT_DIR
|
|
486
|
-
|
|
487
|
-
shaped entry will start
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
603
|
+
Only `$CLAUDE_PROJECT_DIR`, `$COMMANDCODE_PROJECT_DIR`, and `$COMMANDCODE_CWD`
|
|
604
|
+
are expanded — other variables are left alone rather than guessed at. Schema
|
|
605
|
+
checks judge misconfiguration, not whether a correctly shaped entry will start
|
|
606
|
+
at runtime.
|
|
607
|
+
|
|
608
|
+
Spec-required checks are written against published pages, not against what
|
|
609
|
+
happens to appear in real projects. Two that were written the other way round
|
|
610
|
+
shipped as false positives — a nine-name hook-event list where the spec has 31,
|
|
611
|
+
and a `name` must equal the directory rule that Claude's spec explicitly
|
|
612
|
+
contradicts. Both are gone. Heuristics that remain (`skill.duplicate-description`,
|
|
613
|
+
the installer lock checks, and the size budgets other than `budget.claude-md`
|
|
614
|
+
and `skill.description-budget`) are labeled in the registry and stay at `info`.
|
|
615
|
+
|
|
616
|
+
Assumptions are recorded in **[docs/spec/](docs/spec/)** with source URL, read
|
|
617
|
+
date, and provenance. When adding a spec-required check, add its spec line there
|
|
618
|
+
first.
|
|
498
619
|
|
|
499
620
|
## Known limits
|
|
500
621
|
|
|
@@ -509,11 +630,28 @@ When adding a check, add its spec line there first.
|
|
|
509
630
|
- **Runtime split.** Source development and the composite Action use Bun; the
|
|
510
631
|
published `dist/cli.js` runs on Node 20.11+ or Bun. The source entrypoint uses
|
|
511
632
|
Bun's `import.meta.dir` / `import.meta.main` and is not the Node entrypoint.
|
|
512
|
-
- **
|
|
633
|
+
- **Command Code local MCP is unread.** `~/.commandcode/projects/{project}/mcp.json`
|
|
634
|
+
is keyed by a working-directory slug that the sessions page does not publish.
|
|
635
|
+
Until it does, that path is skipped rather than guessed. Bundled Command Code
|
|
636
|
+
skills and `--skill` launch flags are not project files and are not scanned.
|
|
637
|
+
`mods.paths` are inventoried; the TypeScript is never executed or imported.
|
|
638
|
+
`auth.json` and `mcp-tokens.json` are never opened. Command Code project
|
|
639
|
+
root is the git root (or cwd outside a git repo); a child `.cursor` does
|
|
640
|
+
not hide `<git-root>/.commandcode/`. Spec/runtime Command Code checks skip
|
|
641
|
+
shadowed lower-precedence definitions; secrets still inspect every readable
|
|
642
|
+
MCP file.
|
|
643
|
+
- **Three Claude hook locations are unread.** `~/.claude/settings.json`, managed policy
|
|
513
644
|
settings, and installed marketplace plugins under `~/.claude/plugins` all sit
|
|
514
645
|
outside the scanned project, and the docs say a plugin's install directory
|
|
515
646
|
changes on every update. A hook registered in one of those, pointing at a
|
|
516
647
|
missing script, is not reported.
|
|
648
|
+
- **Copilot CLI policy hooks are unread.** `/etc/github-copilot/policy.d` and the
|
|
649
|
+
Windows policy directory are machine-wide admin files. User
|
|
650
|
+
`~/.copilot/hooks` is scanned only under `--global`. Inline Copilot
|
|
651
|
+
`.github/copilot/settings.json` hooks are unread.
|
|
652
|
+
- **Gemini user settings are unread.** `~/.gemini/settings.json` is outside a
|
|
653
|
+
normal project scan. Windsurf's official MCP page is global-only and is not
|
|
654
|
+
scanned.
|
|
517
655
|
- **Bounded reads.** A `SKILL.md` is read to 64 KB and a policy file to 100 KB,
|
|
518
656
|
so `skill.broken-reference` and `policyLines` see only that much of a larger
|
|
519
657
|
file. This is reported — `scan.truncated`, at `info` — rather than left
|
|
@@ -521,6 +659,28 @@ When adding a check, add its spec line there first.
|
|
|
521
659
|
that read it see all of it. Reporting the cap as `config.unreadable` at
|
|
522
660
|
severity error is a defect this tool shipped and [plan 019](plans/019-findings-say-true-things.md) fixed.
|
|
523
661
|
|
|
662
|
+
## Releases
|
|
663
|
+
|
|
664
|
+
Notes for every published version are in [CHANGELOG.md](CHANGELOG.md).
|
|
665
|
+
GitHub already has pages for [v0.1.0](https://github.com/SimaAlexandru99/agentscan/releases/tag/v0.1.0)
|
|
666
|
+
and [v0.4.0](https://github.com/SimaAlexandru99/agentscan/releases/tag/v0.4.0).
|
|
667
|
+
|
|
668
|
+
**1.1.0** (31 August 2026) adds a native Command Code provider and finishes the
|
|
669
|
+
correctness pass across registered checks: hook schema profiles, Agent Skills
|
|
670
|
+
optional fields, Claude listing budget, Codex chain knobs, and Copilot CLI
|
|
671
|
+
`version: 1` files. Still offline on `check`. Shared `.mcp.json` is `mcp-json`
|
|
672
|
+
(Claude and Command Code), not Claude-only — that MCP hotfix is **1.0.1** in
|
|
673
|
+
the changelog and ships inside this package version.
|
|
674
|
+
|
|
675
|
+
**1.0.0** (30 August 2026) is the first stable release: 59 checks, 345 tests,
|
|
676
|
+
still offline on `check`. It adds portable Agent Skills (including Cursor and
|
|
677
|
+
Codex skill trees), MCP profiles for VS Code / Cursor / Codex / Antigravity /
|
|
678
|
+
Gemini / OpenCode / Continue, nested `AGENTS.md`, walk-up agents, VS Code
|
|
679
|
+
hooks, and platform-aware launch checks — a `windows:` script is not
|
|
680
|
+
POSIX-joined when the scanner runs on Linux. Old 0.7 rule ids still work in
|
|
681
|
+
`ignoreRules` and `explain`. 0.9.0 was planned as a separate train and shipped
|
|
682
|
+
inside this release.
|
|
683
|
+
|
|
524
684
|
## Development
|
|
525
685
|
|
|
526
686
|
```bash
|
|
@@ -545,6 +705,8 @@ Before publishing a release:
|
|
|
545
705
|
2. Run `bun run build`, then verify `node dist/cli.js --version` prints it.
|
|
546
706
|
3. Run `bun run typecheck`, `bun test`, and `bun run spec:check`.
|
|
547
707
|
4. Refresh the test-count badge and version examples above from that release run.
|
|
708
|
+
5. Add a section to [CHANGELOG.md](CHANGELOG.md) for the version, then publish
|
|
709
|
+
the GitHub release from that text.
|
|
548
710
|
|
|
549
711
|
## Design docs
|
|
550
712
|
|