@chimix/agentscan 2.0.0 → 2.3.1
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 +84 -0
- package/README.md +54 -22
- package/dist/cli.js +1017 -265
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,90 @@ intermediate commit that this train does not cut.
|
|
|
13
13
|
|
|
14
14
|
## Unreleased
|
|
15
15
|
|
|
16
|
+
## 2.3.1 — 2026-11-14
|
|
17
|
+
|
|
18
|
+
- Codex: `[hooks.state]`, the per-hook trust record Codex writes into
|
|
19
|
+
`config.toml`, is no longer read as a hook group (it raised
|
|
20
|
+
`config.unreadable` on every machine that had trusted a hook).
|
|
21
|
+
|
|
22
|
+
## 2.3.0 — 2026-11-14
|
|
23
|
+
|
|
24
|
+
Six more agents, each from a capture of its official docs read on
|
|
25
|
+
2026-11-14 (`docs/spec/`). Anything a capture lists as unverified never
|
|
26
|
+
raises an error. **163 checks**, **694 tests**.
|
|
27
|
+
|
|
28
|
+
- **Claude Desktop:** `claude_desktop_config.json` MCP servers under
|
|
29
|
+
`--global` (macOS, Windows `%APPDATA%\Claude`, Linux); unknown top-level keys
|
|
30
|
+
are ignored. Installed extension manifests
|
|
31
|
+
(`Claude Extensions/<id>/manifest.json`):
|
|
32
|
+
`claude-desktop.extension.missing-field` (warning; `dxt_version` is accepted).
|
|
33
|
+
- **Amp:** `amp.mcpServers` in the nearest `.amp/settings.json` and, under
|
|
34
|
+
`--global`, `~/.config/amp/settings.json`; `~/.config/amp/skills` and
|
|
35
|
+
`~/.config/agents/skills` as Agent Skills.
|
|
36
|
+
- **Zed:** `context_servers` in `.zed/settings.json` and the user settings
|
|
37
|
+
(JSONC). Extension-provided servers and the `{path, args}` command form are
|
|
38
|
+
launchable.
|
|
39
|
+
- **Qwen Code:** `.qwen/settings.json` MCP (`command` / `url` / `httpUrl`) and
|
|
40
|
+
hooks (23 events, `command` / `http` / `prompt`), `.qwen/skills`.
|
|
41
|
+
- **Goose:** `config.yaml` extensions under `--global`; `sse` is flagged
|
|
42
|
+
because Goose does not support it.
|
|
43
|
+
- **Factory Droid:** `.factory/mcp.json`, unwrapped `.factory/hooks.json` (user
|
|
44
|
+
`settings.json` fallback), `.factory/skills`, and custom droids
|
|
45
|
+
(`factory.droid.invalid-name`, `factory.droid.empty-body`).
|
|
46
|
+
|
|
47
|
+
## 2.2.0 — 2026-11-14
|
|
48
|
+
|
|
49
|
+
Aligned with the vendor docs re-read on 2026-11-14: all 46 drifted spec
|
|
50
|
+
captures were re-read, and `spec:check` is clean. **144 checks**,
|
|
51
|
+
**673 tests**.
|
|
52
|
+
|
|
53
|
+
- New `codex.agent.missing-field` (error): a Codex custom agent file
|
|
54
|
+
(`.codex/agents/*.toml`, and `$CODEX_HOME/agents` under `--global`) must
|
|
55
|
+
define string `name`, `description` and `developer_instructions`.
|
|
56
|
+
- Codex skills come from `.agents/skills` (repo walk and `~/.agents/skills`),
|
|
57
|
+
as the Codex skills page documents. `.codex/skills` is no longer scanned.
|
|
58
|
+
- Copilot CLI project MCP: `.github/mcp.json` is read as `copilot-json`.
|
|
59
|
+
- `--global` reads `~/.cursor/skills`; Cline also reads `.clinerules/skills`.
|
|
60
|
+
- Antigravity skills: `name` is optional and defaults to the folder name.
|
|
61
|
+
- Command Code agents accept `permissionMode` `accept-edits` and `yolo`.
|
|
62
|
+
- Claude: an `agent` hook on `PermissionRequest` is incompatible; a skill
|
|
63
|
+
without `description` falls back to the first non-empty line.
|
|
64
|
+
- Kiro accepts the CLI V3 `AgentSpawn` / `agentSpawn` spellings.
|
|
65
|
+
- `windsurf.hook.unknown-event` is a warning: the Cascade event table left the
|
|
66
|
+
live docs (only `post_setup_worktree` is still quoted).
|
|
67
|
+
|
|
68
|
+
## 2.1.0 — 2026-11-14
|
|
69
|
+
|
|
70
|
+
Fewer false findings when the scan root is a home directory or `--global` is
|
|
71
|
+
on, from an audit of four machines (692 findings on one home, about 440 of
|
|
72
|
+
them false or noise). **143 checks**, **656 tests**. No rule id was added or
|
|
73
|
+
removed.
|
|
74
|
+
|
|
75
|
+
- `check --global` with no project in the working directory scans the
|
|
76
|
+
documented global locations instead of exiting 2.
|
|
77
|
+
- `claude.agent.duplicate-name`: `~/.claude/agents` is read once when the scan
|
|
78
|
+
root is home, so one file is no longer reported as its own duplicate.
|
|
79
|
+
- `claude.hook.missing-script`: a quoted script operand such as
|
|
80
|
+
`"${CLAUDE_PLUGIN_ROOT}/scripts/x"` is unquoted before it is resolved.
|
|
81
|
+
- `config.unreadable`: `EACCES` and `EPERM` are an `info` permission note, once
|
|
82
|
+
per path, and podman storage under `~/.local/share/containers` is skipped.
|
|
83
|
+
- `skill.locked-not-installed`: a `skills-lock.json` entry resolves beside the
|
|
84
|
+
lock's own directory, so a lock inside a worktree or dot-directory finds its
|
|
85
|
+
skills.
|
|
86
|
+
- `skill.broken-reference`: a reference into a sibling skill named earlier in
|
|
87
|
+
the same sentence resolves there, also across a line wrap; under `--global`
|
|
88
|
+
a Claude skill's reference also resolves against the repo that owns its
|
|
89
|
+
`.claude/skills`.
|
|
90
|
+
- `claude.agent.invalid-name` follows the current sub-agents page: an error
|
|
91
|
+
only for an empty name, a name over 256 characters, or one containing `:`.
|
|
92
|
+
Digits, capitals and spaces are accepted.
|
|
93
|
+
- `copilot.hook.unknown-event`: a PascalCase twin of a documented camelCase
|
|
94
|
+
event (for example `Notification`) is a `warning` that names the documented
|
|
95
|
+
spelling.
|
|
96
|
+
- `windsurf.rule.missing-trigger` is `info`. Under `--global`, plugin caches,
|
|
97
|
+
marketplaces, backups and installed-plugin copies are skipped for Windsurf
|
|
98
|
+
rules, `budget.*` and `scan.truncated`.
|
|
99
|
+
|
|
16
100
|
## 2.0.0 — 2026-11-12
|
|
17
101
|
|
|
18
102
|
Breaking for configs that still name the 0.7 rule ids or the old threshold
|
package/README.md
CHANGED
|
@@ -5,19 +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-163-111111?style=flat-square" alt="163 checks">
|
|
9
|
+
<img src="https://img.shields.io/badge/tests-695%20passing-111111?style=flat-square" alt="695 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-2.
|
|
15
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-2.3.1-111111?style=flat-square" alt="Changelog"></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
|
-
<strong>
|
|
20
|
-
<sub>An offline linter for Claude Code, Command Code, Grok Build, Windsurf, Kiro, Cline, Roo, Kilo, Junie, portable Agent Skills, nested AGENTS.md, Copilot CLI hooks (including inline settings), and the MCP / hooks / rules surfaces the registry 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>
|
|
19
|
+
<strong>163 checks · offline · 0 network calls on <code>check</code> · provenance on every rule</strong><br>
|
|
20
|
+
<sub>An offline linter for Claude Code, Claude Desktop, Command Code, Grok Build, Windsurf, Kiro, Qwen Code, Factory Droid, Amp, Zed, Goose, Cline, Roo, Kilo, Junie, portable Agent Skills, nested AGENTS.md, Copilot CLI hooks (including inline settings), and the MCP / hooks / rules surfaces the registry 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>
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
23
|
---
|
|
@@ -65,7 +65,7 @@ No AI, no network on `check`. Read the config, read the disk, compare:
|
|
|
65
65
|
```
|
|
66
66
|
1. Discover .claude/ .commandcode/ .grok/ .agents/ .vscode/ .cursor/ .codex/ .gemini/ .github/ .continue/ AGENTS.md skills-lock.json
|
|
67
67
|
2. Extract immutable facts — never re-read during checking
|
|
68
|
-
3. Check
|
|
68
|
+
3. Check 163 checks, each labeled spec-required, vendor-recommendation,
|
|
69
69
|
security, internal-consistency, or heuristic
|
|
70
70
|
4. Report text · --json · --output prompt (handoff for a fixing agent)
|
|
71
71
|
```
|
|
@@ -186,7 +186,7 @@ Flags for `check`:
|
|
|
186
186
|
| `--verbose` | Show info-severity findings, and print each finding's id |
|
|
187
187
|
| `--fail-on <level>` | `never` (default) · `warning` · `error` |
|
|
188
188
|
| `--fail-under <0-100>` | Fail when the score drops below this floor |
|
|
189
|
-
| `--global` | Also scan user skills (`$CLAUDE_CONFIG_DIR/skills` or `~/.claude/skills`, `~/.
|
|
189
|
+
| `--global` | Also scan user skills (`$CLAUDE_CONFIG_DIR/skills` or `~/.claude/skills`, `~/.commandcode/skills`, `~/.agents/skills`, `$GROK_HOME/skills`, `~/.codeium/windsurf/skills`), Claude user settings / agents / `CLAUDE.md` / rules / commands and `$CLAUDE_CONFIG_DIR/.claude.json` or `~/.claude.json` (user + matching local `mcpServers`), Copilot `$COPILOT_HOME` / `~/.copilot/hooks` and `settings.json`, Codex `$CODEX_HOME` / `~/.codex/hooks.json` and `config.toml` (MCP plus inline `[hooks]`), Grok `$GROK_HOME` / `~/.grok` (hooks + `config.toml`), Command Code user MCP/agents/memory, Antigravity `~/.gemini/config/hooks.json`, `rules/`, and `skills/`, plus `~/.gemini/AGENTS.md` and `GEMINI.md`, Kiro `~/.kiro/agents`, and Windsurf `~/.codeium/windsurf` MCP + `global_rules.md` + `hooks.json` (see below) |
|
|
190
190
|
| `--config <path>` | Config file path |
|
|
191
191
|
|
|
192
192
|
**v1 does not write the tree** — no `apply`, no skill delete/install. Findings may *suggest* shell commands; you run them yourself.
|
|
@@ -259,7 +259,7 @@ Same tree → same sorted findings, with stable unique `id`s.
|
|
|
259
259
|
|
|
260
260
|
### `--global`
|
|
261
261
|
|
|
262
|
-
Adds user skill dirs (`$CLAUDE_CONFIG_DIR/skills` or `~/.claude/skills`,
|
|
262
|
+
Adds user skill dirs (`$CLAUDE_CONFIG_DIR/skills` or `~/.claude/skills`,
|
|
263
263
|
`~/.commandcode/skills`, `~/.agents/skills`, `$GROK_HOME/skills` or
|
|
264
264
|
`~/.grok/skills`), Claude user settings / agents / `CLAUDE.md` / rules /
|
|
265
265
|
commands and `~/.claude.json` (top-level user `mcpServers` plus local
|
|
@@ -359,7 +359,7 @@ Optional `.agentscanrc.json` (create with `agentscan init`):
|
|
|
359
359
|
|
|
360
360
|
```json
|
|
361
361
|
{
|
|
362
|
-
"skillPaths": [".agents/skills", ".claude/skills", "skills", ".cursor/skills", ".
|
|
362
|
+
"skillPaths": [".agents/skills", ".claude/skills", "skills", ".cursor/skills", ".commandcode/skills", ".grok/skills", ".windsurf/skills"],
|
|
363
363
|
"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"],
|
|
364
364
|
"policyFiles": ["AGENTS.md", "CLAUDE.md"],
|
|
365
365
|
"ignoreSkills": [],
|
|
@@ -465,7 +465,7 @@ fails CI instead of quietly turning a quoted rule into a false positive.
|
|
|
465
465
|
<!-- rules:start -->
|
|
466
466
|
| id | Severity | Provenance | Source | Catches |
|
|
467
467
|
|----|----------|------------|--------|---------|
|
|
468
|
-
| `config.unreadable` | error | internal-consistency | _parser result_ | Config file is not valid JSON or cannot be read |
|
|
468
|
+
| `config.unreadable` | error/info | internal-consistency | _parser result_ | Config file is not valid JSON or cannot be read |
|
|
469
469
|
| `scan.truncated` | info | internal-consistency | _scan cap_ | File is larger than the scan cap, so body checks read only a prefix of it |
|
|
470
470
|
| `claude.hook.unknown-event` | error | spec-required | [code.claude.com/docs/en/hooks](https://code.claude.com/docs/en/hooks) | Hook registered under an event name that is never dispatched |
|
|
471
471
|
| `claude.hook.missing-script` | error | internal-consistency | _agentscan inference_ | Hook points at a script that does not exist, so it never runs |
|
|
@@ -481,7 +481,7 @@ fails CI instead of quietly turning a quoted rule into a false positive.
|
|
|
481
481
|
| `vscode.hook.invalid-group` | error | internal-consistency | [code.visualstudio.com/docs/agents/reference/hooks-reference](https://code.visualstudio.com/docs/agents/reference/hooks-reference) | VS Code hook group has a matcher but no hooks array |
|
|
482
482
|
| `vscode.hook.command-without-command` | error | spec-required | [code.visualstudio.com/docs/agents/reference/hooks-reference](https://code.visualstudio.com/docs/agents/reference/hooks-reference) | VS Code command hook is missing the command field |
|
|
483
483
|
| `vscode.hook.unknown-handler-type` | error | spec-required | [code.visualstudio.com/docs/agents/reference/hooks-reference](https://code.visualstudio.com/docs/agents/reference/hooks-reference) | VS Code hook uses a handler type other than the required type command |
|
|
484
|
-
| `copilot.hook.unknown-event` | error | spec-required | [docs.github.com/en/copilot/reference/hooks-reference](https://docs.github.com/en/copilot/reference/hooks-reference) | Copilot CLI hook registered under an event name that is never dispatched |
|
|
484
|
+
| `copilot.hook.unknown-event` | error/warning | spec-required | [docs.github.com/en/copilot/reference/hooks-reference](https://docs.github.com/en/copilot/reference/hooks-reference) | Copilot CLI hook registered under an event name that is never dispatched |
|
|
485
485
|
| `copilot.hook.missing-script` | error | internal-consistency | _agentscan inference_ | Copilot CLI hook points at a script that does not exist, so it never runs |
|
|
486
486
|
| `copilot.hook.command-without-command` | error | spec-required | [docs.github.com/en/copilot/reference/hooks-reference](https://docs.github.com/en/copilot/reference/hooks-reference) | Copilot CLI command hook declares none of bash, powershell, command, or exec |
|
|
487
487
|
| `copilot.hook.http-without-url` | error | spec-required | [docs.github.com/en/copilot/reference/hooks-reference](https://docs.github.com/en/copilot/reference/hooks-reference) | Copilot CLI http hook is missing the url field |
|
|
@@ -490,7 +490,7 @@ fails CI instead of quietly turning a quoted rule into a false positive.
|
|
|
490
490
|
| `copilot.hook.incompatible-handler` | error | spec-required | [docs.github.com/en/copilot/reference/hooks-reference](https://docs.github.com/en/copilot/reference/hooks-reference) | Copilot CLI prompt hook is registered on an event other than sessionStart |
|
|
491
491
|
| `skill.missing-skill-md` | warning | internal-consistency | [agentskills.io/specification](https://agentskills.io/specification) | Skill directory has no SKILL.md |
|
|
492
492
|
| `claude.skill.missing-frontmatter` | warning | spec-required | [code.claude.com/docs/en/skills](https://code.claude.com/docs/en/skills) | SKILL.md has no YAML frontmatter block |
|
|
493
|
-
| `claude.skill.missing-description` | info | vendor-recommendation | [code.claude.com/docs/en/skills](https://code.claude.com/docs/en/skills) | SKILL.md frontmatter has no description and no first markdown
|
|
493
|
+
| `claude.skill.missing-description` | info | vendor-recommendation | [code.claude.com/docs/en/skills](https://code.claude.com/docs/en/skills) | SKILL.md frontmatter has no description and no first non-empty markdown line |
|
|
494
494
|
| `agent-skills.skill.missing-frontmatter` | error | spec-required | [agentskills.io/specification](https://agentskills.io/specification) | Portable skill SKILL.md has no YAML frontmatter block |
|
|
495
495
|
| `agent-skills.skill.missing-name` | error | spec-required | [agentskills.io/specification](https://agentskills.io/specification) | Portable skill frontmatter has no required name |
|
|
496
496
|
| `agent-skills.skill.missing-description` | error | spec-required | [agentskills.io/specification](https://agentskills.io/specification) | Portable skill frontmatter has no required description |
|
|
@@ -512,7 +512,7 @@ fails CI instead of quietly turning a quoted rule into a false positive.
|
|
|
512
512
|
| `claude.agent.missing-description` | error | spec-required | [code.claude.com/docs/en/sub-agents](https://code.claude.com/docs/en/sub-agents) | Agent frontmatter has no description |
|
|
513
513
|
| `claude.agent.missing-name` | error | spec-required | [code.claude.com/docs/en/sub-agents](https://code.claude.com/docs/en/sub-agents) | Agent frontmatter has no name |
|
|
514
514
|
| `claude.agent.duplicate-name` | error | spec-required | [code.claude.com/docs/en/sub-agents](https://code.claude.com/docs/en/sub-agents) | Agent names are duplicated |
|
|
515
|
-
| `claude.agent.invalid-name` |
|
|
515
|
+
| `claude.agent.invalid-name` | error | spec-required | [code.claude.com/docs/en/sub-agents](https://code.claude.com/docs/en/sub-agents) | Agent frontmatter name is one Claude Code skips |
|
|
516
516
|
| `claude.mcp.no-launch` | error | spec-required | [code.claude.com/docs/en/mcp](https://code.claude.com/docs/en/mcp) | MCP server declares neither command nor url |
|
|
517
517
|
| `vscode.mcp.no-launch` | error | spec-required | [code.visualstudio.com/docs/agents/reference/mcp-configuration](https://code.visualstudio.com/docs/agents/reference/mcp-configuration) | VS Code MCP server declares neither command nor url |
|
|
518
518
|
| `cursor.mcp.no-launch` | error | spec-required | [cursor.com/docs/context/mcp](https://cursor.com/docs/context/mcp) | Cursor MCP server declares neither command nor url |
|
|
@@ -568,7 +568,7 @@ fails CI instead of quietly turning a quoted rule into a false positive.
|
|
|
568
568
|
| `cursor.rule.too-large` | info | vendor-recommendation | [cursor.com/docs/rules](https://cursor.com/docs/rules) | Cursor project rule exceeds the 500-line recommendation |
|
|
569
569
|
| `windsurf.rule.too-large` | info | vendor-recommendation | [docs.windsurf.com/windsurf/cascade/memories](https://docs.windsurf.com/windsurf/cascade/memories) | Windsurf workspace rule exceeds the 12,000-character limit |
|
|
570
570
|
| `windsurf.rule.global-too-large` | info | vendor-recommendation | [docs.windsurf.com/windsurf/cascade/memories](https://docs.windsurf.com/windsurf/cascade/memories) | Windsurf global_rules.md exceeds the 6,000-character limit |
|
|
571
|
-
| `windsurf.rule.missing-trigger` |
|
|
571
|
+
| `windsurf.rule.missing-trigger` | info | spec-required | [docs.windsurf.com/windsurf/cascade/memories](https://docs.windsurf.com/windsurf/cascade/memories) | Windsurf workspace rule is missing frontmatter trigger |
|
|
572
572
|
| `antigravity.rule.missing-trigger` | warning | spec-required | [antigravity.google/docs/rules](https://antigravity.google/docs/rules) | Antigravity modular rule is missing a frontmatter trigger |
|
|
573
573
|
| `antigravity.rule.invalid-trigger` | warning | spec-required | [antigravity.google/docs/rules](https://antigravity.google/docs/rules) | Antigravity modular rule trigger is not always_on, model_decision, glob, or manual |
|
|
574
574
|
| `antigravity.rule.missing-description` | warning | spec-required | [antigravity.google/docs/rules](https://antigravity.google/docs/rules) | Antigravity model_decision rule is missing a description |
|
|
@@ -576,7 +576,7 @@ fails CI instead of quietly turning a quoted rule into a false positive.
|
|
|
576
576
|
| `antigravity.rule.too-large` | warning | spec-required | [antigravity.google/docs/rules](https://antigravity.google/docs/rules) | Antigravity rule exceeds 24,000 bytes |
|
|
577
577
|
| `windsurf.mcp.no-launch` | error | spec-required | [docs.windsurf.com/windsurf/cascade/mcp](https://docs.windsurf.com/windsurf/cascade/mcp) | Windsurf MCP server declares neither command, serverUrl, nor url |
|
|
578
578
|
| `devin.mcp.no-launch` | error | spec-required | [docs.devin.ai/cli/extensibility/mcp/configuration](https://docs.devin.ai/cli/extensibility/mcp/configuration) | Devin MCP server declares neither command nor url |
|
|
579
|
-
| `windsurf.hook.unknown-event` |
|
|
579
|
+
| `windsurf.hook.unknown-event` | warning | spec-required | [docs.devin.ai/desktop/cascade/hooks](https://docs.devin.ai/desktop/cascade/hooks) | Windsurf hook registered under an event name outside the legacy Cascade event table |
|
|
580
580
|
| `windsurf.hook.missing-script` | error | internal-consistency | _agentscan inference_ | Windsurf hook points at a script that does not exist, so it never runs |
|
|
581
581
|
| `windsurf.hook.command-without-command` | error | spec-required | [docs.devin.ai/desktop/cascade/hooks](https://docs.devin.ai/desktop/cascade/hooks) | Windsurf hook declares neither command nor powershell |
|
|
582
582
|
| `devin.hook.unknown-event` | error | spec-required | [docs.devin.ai/cli/extensibility/hooks/overview](https://docs.devin.ai/cli/extensibility/hooks/overview) | Devin hook registered under an event name that is never dispatched |
|
|
@@ -592,6 +592,7 @@ fails CI instead of quietly turning a quoted rule into a false positive.
|
|
|
592
592
|
| `codex.hook.mcp-tool-without-server-or-tool` | error | spec-required | [learn.chatgpt.com/docs/hooks](https://learn.chatgpt.com/docs/hooks) | Codex MCP tool hook declares no server or tool |
|
|
593
593
|
| `codex.hook.unknown-handler-type` | error | spec-required | [learn.chatgpt.com/docs/hooks](https://learn.chatgpt.com/docs/hooks) | Codex hook handler type is not command, mcp_tool, prompt, or agent |
|
|
594
594
|
| `codex.hook.incompatible-handler` | error | spec-required | [learn.chatgpt.com/docs/hooks](https://learn.chatgpt.com/docs/hooks) | Codex prompt or agent handler is parsed but skipped, or SessionEnd uses mcp_tool |
|
|
595
|
+
| `codex.agent.missing-field` | error | spec-required | [learn.chatgpt.com/docs/agent-configuration/subagents](https://learn.chatgpt.com/docs/agent-configuration/subagents) | Codex custom agent file does not define string name, description, and developer_instructions |
|
|
595
596
|
| `codex.hook.timeout-out-of-bounds` | error | spec-required | [learn.chatgpt.com/docs/hooks](https://learn.chatgpt.com/docs/hooks) | Codex SessionEnd or Interrupt hook timeout is outside 1–3 seconds |
|
|
596
597
|
| `gemini.hook.unknown-event` | error | spec-required | [geminicli.com/docs/hooks](https://geminicli.com/docs/hooks/) | Gemini hook registered under an event that is never dispatched |
|
|
597
598
|
| `gemini.hook.missing-script` | error | internal-consistency | _agentscan inference_ | Gemini hook points at a script that does not exist, so it never runs |
|
|
@@ -608,6 +609,25 @@ fails CI instead of quietly turning a quoted rule into a false positive.
|
|
|
608
609
|
| `kiro.hook.prompt-without-prompt` | error | spec-required | [kiro.dev/docs/hooks](https://kiro.dev/docs/hooks/) | Kiro agent hook is missing the required prompt field |
|
|
609
610
|
| `kiro.hook.unknown-handler-type` | error | spec-required | [kiro.dev/docs/hooks](https://kiro.dev/docs/hooks/) | Kiro hook action type is missing or is not command or agent |
|
|
610
611
|
| `kiro.hook.invalid-group` | error | spec-required | [kiro.dev/docs/hooks](https://kiro.dev/docs/hooks/) | Kiro hook file is missing version v1 or a hooks array |
|
|
612
|
+
| `zed.mcp.no-launch` | error | spec-required | [zed.dev/docs/ai/mcp](https://zed.dev/docs/ai/mcp) | Zed context server declares neither command nor url |
|
|
613
|
+
| `amp.mcp.no-launch` | warning | internal-consistency | _agentscan inference_ | Amp MCP server declares neither command nor url |
|
|
614
|
+
| `claude-desktop.mcp.no-launch` | warning | internal-consistency | _agentscan inference_ | Claude Desktop MCP server declares no command |
|
|
615
|
+
| `claude-desktop.extension.missing-field` | warning | spec-required | [raw.githubusercontent.com/anthropics/mcpb/main/MANIFEST.md](https://raw.githubusercontent.com/anthropics/mcpb/main/MANIFEST.md) | Claude Desktop extension manifest lacks a required field or names an unknown server type |
|
|
616
|
+
| `qwen.mcp.no-launch` | error | spec-required | [qwenlm.github.io/qwen-code-docs/en/users/features/mcp](https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/) | Qwen Code MCP server has neither command, url, nor httpUrl |
|
|
617
|
+
| `qwen.hook.unknown-event` | error | spec-required | [qwenlm.github.io/qwen-code-docs/en/users/features/hooks](https://qwenlm.github.io/qwen-code-docs/en/users/features/hooks/) | Qwen Code hook registered under an event name that is never dispatched |
|
|
618
|
+
| `qwen.hook.missing-script` | error | internal-consistency | _agentscan inference_ | Qwen Code hook points at a script that does not exist, so it never runs |
|
|
619
|
+
| `qwen.hook.command-without-command` | error | spec-required | [qwenlm.github.io/qwen-code-docs/en/users/features/hooks](https://qwenlm.github.io/qwen-code-docs/en/users/features/hooks/) | Qwen Code command hook is missing the command field |
|
|
620
|
+
| `qwen.hook.http-without-url` | error | spec-required | [qwenlm.github.io/qwen-code-docs/en/users/features/hooks](https://qwenlm.github.io/qwen-code-docs/en/users/features/hooks/) | Qwen Code http hook is missing the url field |
|
|
621
|
+
| `qwen.hook.unknown-handler-type` | error | spec-required | [qwenlm.github.io/qwen-code-docs/en/users/features/hooks](https://qwenlm.github.io/qwen-code-docs/en/users/features/hooks/) | Qwen Code hook type is missing or is not command, http, or prompt |
|
|
622
|
+
| `factory.mcp.no-launch` | error | spec-required | [docs.factory.com/harness/mcp](https://docs.factory.com/harness/mcp) | Factory Droid MCP server has neither command nor url |
|
|
623
|
+
| `factory.hook.unknown-event` | error | spec-required | [docs.factory.com/harness/hooks](https://docs.factory.com/harness/hooks) | Factory Droid hook registered under an event name that is never dispatched |
|
|
624
|
+
| `factory.hook.missing-script` | error | internal-consistency | _agentscan inference_ | Factory Droid hook points at a script that does not exist, so it never runs |
|
|
625
|
+
| `factory.hook.invalid-group` | error | spec-required | [docs.factory.com/harness/hooks](https://docs.factory.com/harness/hooks) | Factory Droid hook group has no hooks array |
|
|
626
|
+
| `factory.hook.command-without-command` | error | spec-required | [docs.factory.com/harness/hooks](https://docs.factory.com/harness/hooks) | Factory Droid command hook is missing the command field |
|
|
627
|
+
| `factory.hook.unknown-handler-type` | error | spec-required | [docs.factory.com/harness/hooks](https://docs.factory.com/harness/hooks) | Factory Droid hook type is missing or is not command |
|
|
628
|
+
| `factory.droid.invalid-name` | error | spec-required | [docs.factory.com/harness/subagents](https://docs.factory.com/harness/subagents) | Factory custom droid name is missing or does not match ^[a-z0-9-_]+$ |
|
|
629
|
+
| `factory.droid.empty-body` | error | spec-required | [docs.factory.com/harness/subagents](https://docs.factory.com/harness/subagents) | Factory custom droid has an empty system prompt body |
|
|
630
|
+
| `goose.mcp.no-launch` | error | spec-required | [goose-docs.ai/docs/guides/config-files](https://goose-docs.ai/docs/guides/config-files/) | Goose extension declares type sse, which Goose does not support |
|
|
611
631
|
<!-- rules:end -->
|
|
612
632
|
|
|
613
633
|
### Coverage in 2.0.0
|
|
@@ -625,21 +645,21 @@ Dimensions:
|
|
|
625
645
|
|
|
626
646
|
| Ecosystem | project discovery | global discovery | schema | precedence | conformance |
|
|
627
647
|
|-----------|-------------------|------------------|--------|------------|-------------|
|
|
628
|
-
| Agent Skills | `.agents/skills` (and Cursor / Codex / Command Code skill trees). Default `skills/` uses the Claude schema, not this contract | `--global` `~/.agents/skills
|
|
648
|
+
| Agent Skills | `.agents/skills` (and Cursor / Codex / Command Code skill trees). Default `skills/` uses the Claude schema, not this contract | `--global` `~/.agents/skills` (Codex reads its user skills here) | required `name` / `description` plus optional `compatibility` / `metadata` / `allowed-tools`; 500-line info | n/a | `agent-skills` fixture |
|
|
629
649
|
| AGENTS.md | nested walk-up | n/a | no required fields | nearest-wins | tests |
|
|
630
650
|
| Claude Code | project settings, skills, agents, commands, `CLAUDE.md`, `.mcp.json` / `.claude/mcp.json`; in-tree plugin hooks / skills / agents / commands / `.mcp.json` | `--global` `$CLAUDE_CONFIG_DIR` or `~/.claude` (skills, settings, agents, `CLAUDE.md`, rules, commands) and `$CLAUDE_CONFIG_DIR/.claude.json` or `~/.claude.json` (user + matching local `mcpServers`); unread managed policy, marketplace plugins, other projects' local MCP | 33 events; required handler `type`; MCP reserved names; first-paragraph skill description; listing budget | walk-up `CLAUDE.md` / `.claude/agents` / `.claude/commands`; user+project+local layers inventoried | `claude-json` fixture |
|
|
631
651
|
| 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 |
|
|
632
|
-
| Codex | `.codex/hooks.json` and inline `[hooks]` in `.codex/config.toml` on the ancestor walk; `.
|
|
652
|
+
| Codex | `.codex/hooks.json` and inline `[hooks]` in `.codex/config.toml` on the ancestor walk; skills from `.agents/skills`; AGENTS chain | `--global` `$CODEX_HOME` / `~/.codex` `hooks.json` and `config.toml` (MCP and `[hooks]`); `AGENTS.override.md` then `AGENTS.md`; unread system, managed, requirements, profiles, plugins, trust | 14 hook events; `command` / `mcp_tool`; `prompt` and `agent` parsed but skipped; SessionEnd rejects `mcp_tool`; SessionEnd and Interrupt timeout 1–3; TOML MCP; `project_doc_max_bytes`; Agent Skills | both hook files in a layer are inventoried; trust is not modeled; override > `AGENTS.md` > fallbacks; one file per dir; root→cwd; `project_root_markers`; MCP user+project both inventoried | `codex-hooks`, `codex-toml` fixtures |
|
|
633
653
|
| VS Code | `.github/hooks` without `version: 1` (legacy Local), instruction files, `.github/agents` (frontmatter hooks as vscode-native), `.vscode/mcp.json` `servers` | `--global` `~/.copilot/hooks` without `version: 1`; unread policy dirs; unread marketplace plugin `hooks.json` (install path unpublished) | 8 PascalCase events; command-only. New Copilot hooks are the Copilot row | workspace over user | `vscode-hooks`, `vscode-json` |
|
|
634
|
-
| Copilot CLI | `.github/hooks` with `version: 1`; inline `hooks` in `.github/copilot/settings.json` and `settings.local.json` | `--global` `$COPILOT_HOME` / `~/.copilot/hooks` with `version: 1`, `settings.json`, and `mcp-config.json` (`mcpServers`); unread `/etc/github-copilot/policy.d` | camelCase + PascalCase map; `bash` / `powershell` / `command` / `exec` / `http`; `cwd`; `timeoutSec`; prompt on `sessionStart`; user MCP `command` / `url` | documented sources coexist; policy unread; Copilot also reads `.claude/settings.json` but this scanner lints that file as Claude. Workspace `.mcp.json` stays the shared profile | `copilot-hooks` fixture |
|
|
635
|
-
| Cursor | nested `.cursor/skills`; `.cursor/mcp.json`, `.cursor/hooks.json` and `.cursor/rules` on the ancestor walk only (not descendant package trees from repo root) | `--global` `~/.cursor/hooks.json` and `~/.cursor/mcp.json`; unread MDM/team hooks; unread
|
|
654
|
+
| Copilot CLI | `.github/hooks` with `version: 1`; inline `hooks` in `.github/copilot/settings.json` and `settings.local.json`; `.github/mcp.json` (`mcpServers`) | `--global` `$COPILOT_HOME` / `~/.copilot/hooks` with `version: 1`, `settings.json`, and `mcp-config.json` (`mcpServers`); unread `/etc/github-copilot/policy.d` | camelCase + PascalCase map; `bash` / `powershell` / `command` / `exec` / `http`; `cwd`; `timeoutSec`; prompt on `sessionStart`; user MCP `command` / `url` | documented sources coexist; policy unread; Copilot also reads `.claude/settings.json` but this scanner lints that file as Claude. Workspace `.mcp.json` stays the shared profile | `copilot-hooks` fixture |
|
|
655
|
+
| Cursor | nested `.cursor/skills`; `.cursor/mcp.json`, `.cursor/hooks.json` and `.cursor/rules` on the ancestor walk only (not descendant package trees from repo root) | `--global` `~/.cursor/hooks.json` and `~/.cursor/mcp.json`; `~/.cursor/skills`; unread MDM/team hooks; unread third-party Claude import as Cursor schema | Agent Skills; MCP launch; 500-line rules; 21 hook events, optional `type` (`command` / `prompt`), required `command`. Missing `version` is not a finding | n/a | `cursor-json` fixture |
|
|
636
656
|
| Grok | `.grok/config.toml` walk-up; `.grok/hooks/*.json`; `.grok/skills`; `.grok/rules/*.md`; `Agents.md` / `AGENT.md` | `--global` `$GROK_HOME` or `~/.grok` config/hooks/skills; unread managed, requirements, plugins, `[skills] paths`, agents, credentials | `command` / `url` MCP (no `type`); 14 events; command/http; frontmatter required, name/description optional; no rules cap | closer project wins; project same-name replaces user | `grok-toml` fixture |
|
|
637
657
|
| Antigravity | `.agents/mcp_config.json`; `.agents/hooks.json`, `GEMINI.md`, `.agents/GEMINI.md`, `.agents/rules/*.md`, and legacy `.agent/rules/*.md` on the ancestor walk. Workspace `AGENTS.md` stays the AGENTS.md row. `.agents/skills` stays the Agent Skills row | `--global` `~/.gemini/config` `mcp_config.json`, `hooks.json`, `rules/*.md`, `skills/`, `AGENTS.md`, and `GEMINI.md`, plus `~/.gemini/AGENTS.md` and `GEMINI.md`. Unread plugin hooks, plugin rules, plugin skills, `~/.gemini/antigravity-cli/settings.json`, `~/.gemini/antigravity-cli/skills/`, `~/.gemini/skills/`, and `~/.gemini/antigravity/skills/` | `command` / `serverUrl` (`url` is an alias; `httpUrl` is not a launch field); 6 hook events; direct handlers or matcher groups; `type` defaults to `command`; modular rule `trigger`; 24,000-byte rule limit | n/a | `antigravity-json`, `antigravity-hooks` fixtures |
|
|
638
658
|
| Gemini | `.gemini/settings.json` — MCP and `hooks` | `--global` `~/.gemini/settings.json` (hooks and MCP together); unread `/etc/gemini-cli/settings.json`, extension hooks, and `mcp-oauth-tokens.json` | `command` / `url` / `httpUrl`; underscore-alias warning; 11 hook events, required `type` (`command` only), nested groups, `$GEMINI_PROJECT_DIR` | n/a | `gemini-json` fixture |
|
|
639
659
|
| Windsurf | `.devin/rules/*.md` (preferred), `.windsurf/rules/*.md` (fallback), `.windsurfrules`; `.windsurf/hooks.json`; `.windsurf/skills`; portable `AGENTS.md` / `agents.md` | `--global` `~/.codeium/windsurf/mcp_config.json`, `memories/global_rules.md`, `hooks.json`, and `skills`; unread auto memories, system rules/hooks/skills, JetBrains `~/.codeium/hooks.json` | 12k / 6k character rules; workspace `trigger`; MCP `command` / `serverUrl` / `url` (no `type`); 12 snake_case hook events; `command` / `powershell`; Agent Skills on `.windsurf/skills` | n/a (no project MCP on this row; both rule trees inventoried). Devin CLI hooks and MCP are the next row | `windsurf-rules` fixture |
|
|
640
660
|
| Devin CLI | `.devin/hooks.v1.json`; `"hooks"` in `.devin/config.json` and `config.local.json`; `.devin/mcp_config.json` and `mcp_config.local.json`; `mcpServers` left in those config files; `.devin/skills` | `--global` `~/.config/devin/config.json`, `mcp_config.json`, and `skills`; unread Windows `%APPDATA%\devin` | 8 events (`PostCompaction`, not `PostCompact`); `command` / `prompt`; MCP `command` / `url` (`transport` `http` or `sse` is not a check); Agent Skills | Claude settings Devin also loads stay Claude findings | unit tests |
|
|
641
661
|
| Kiro | `.kiro/hooks/*.json`, `.kiro/skills`, `.kiro/agents/**/*.json` and `*.md` on the ancestor walk | `--global` `~/.kiro/hooks/`, `~/.kiro/skills`, `~/.kiro/agents/` | 13 PascalCase triggers, including `SessionEnd`, `PreCompact`, `PostCompact`; `command` / `agent`; Agent Skills. Agents are inventory only: the name is the relative path without the extension, and no field is required | nearer workspace agent shadows a farther one and a global one with the same name | unit tests |
|
|
642
|
-
| Cline | `.cline/skills` | `--global` `~/.cline/skills`; unread hooks (no quoted event list), unread agents | Agent Skills | n/a | unit tests |
|
|
662
|
+
| Cline | `.cline/skills`, `.clinerules/skills` | `--global` `~/.cline/skills`; unread hooks (no quoted event list), unread agents | Agent Skills | n/a | unit tests |
|
|
643
663
|
| Roo | `.roo/skills` | `--global` `~/.roo/skills` | Agent Skills | n/a | unit tests |
|
|
644
664
|
| Kilo | `.kilo/skills` | `--global` `~/.kilo/skills`; unread extra paths in `kilo.jsonc` | Agent Skills | n/a | unit tests |
|
|
645
665
|
| OpenCode | `opencode.json(c)` V1 and V2 | none | V2 local `command` must be an argv array | n/a | `opencode-json` fixture |
|
|
@@ -784,8 +804,8 @@ first.
|
|
|
784
804
|
because Claude skips them.
|
|
785
805
|
- **Default `skills/` is Claude schema.** A repo-root `skills/` directory is
|
|
786
806
|
discovered, but `name` / directory-match from the Agent Skills spec are not
|
|
787
|
-
applied there. `.agents/skills`, `.cursor/skills`,
|
|
788
|
-
`.commandcode/skills`, `.kiro/skills`, `.cline/skills`, `.roo/skills`,
|
|
807
|
+
applied there. `.agents/skills`, `.cursor/skills`,
|
|
808
|
+
`.commandcode/skills`, `.kiro/skills`, `.cline/skills`, `.clinerules/skills`, `.roo/skills`,
|
|
789
809
|
`.kilo/skills`, and `.junie/skills` use the Agent Skills contract. Junie
|
|
790
810
|
does not require `description` and does not require `name` to match the
|
|
791
811
|
folder.
|
|
@@ -852,6 +872,18 @@ Notes for every published version are in [CHANGELOG.md](CHANGELOG.md).
|
|
|
852
872
|
GitHub already has pages for [v0.1.0](https://github.com/SimaAlexandru99/agentscan/releases/tag/v0.1.0)
|
|
853
873
|
and [v0.4.0](https://github.com/SimaAlexandru99/agentscan/releases/tag/v0.4.0).
|
|
854
874
|
|
|
875
|
+
**2.3.1** (14 November 2026) stops reading Codex's `[hooks.state]` trust
|
|
876
|
+
record as a hook group.
|
|
877
|
+
|
|
878
|
+
**2.3.0** (14 November 2026) adds Claude Desktop, Amp, Zed, Qwen Code,
|
|
879
|
+
Goose and Factory Droid; see the changelog.
|
|
880
|
+
|
|
881
|
+
**2.2.0** (14 November 2026) aligns every surface with the vendor docs
|
|
882
|
+
re-read that day and adds `codex.agent.missing-field`; see the changelog.
|
|
883
|
+
|
|
884
|
+
**2.1.0** (14 November 2026) cuts false findings on home-directory and
|
|
885
|
+
`--global` scans; see the changelog. No rule id changed.
|
|
886
|
+
|
|
855
887
|
**2.0.0** (12 November 2026) is breaking for configs that still name the 0.7
|
|
856
888
|
rule ids or the old threshold keys. `ignoreRules`, `ignoreFindings`, and
|
|
857
889
|
`explain` accept only the printed ids. `thresholds.skillDescriptionBytes`
|