@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.
Files changed (4) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +54 -22
  3. package/dist/cli.js +1017 -265
  4. 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-143-111111?style=flat-square" alt="143 checks">
9
- <img src="https://img.shields.io/badge/tests-647%20passing-111111?style=flat-square" alt="647 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.0.0-111111?style=flat-square" alt="Changelog"></a>
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>143 checks &middot; offline &middot; 0 network calls on <code>check</code> &middot; provenance on every rule</strong><br>
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 &middot; offline &middot; 0 network calls on <code>check</code> &middot; 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 143 checks, each labeled spec-required, vendor-recommendation,
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`, `~/.codex/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) |
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`, `~/.codex/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", ".codex/skills", ".commandcode/skills", ".grok/skills", ".windsurf/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 paragraph |
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` | warning/error | spec-required | [code.claude.com/docs/en/sub-agents](https://code.claude.com/docs/en/sub-agents) | Agent frontmatter name is not a valid identifier |
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` | warning | spec-required | [docs.windsurf.com/windsurf/cascade/memories](https://docs.windsurf.com/windsurf/cascade/memories) | Windsurf workspace rule is missing frontmatter 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` | error | spec-required | [docs.devin.ai/desktop/cascade/hooks](https://docs.devin.ai/desktop/cascade/hooks) | Windsurf hook registered under an event name that is never dispatched |
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`, `~/.codex/skills` | required `name` / `description` plus optional `compatibility` / `metadata` / `allowed-tools`; 500-line info | n/a | `agent-skills` fixture |
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; `.codex/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 |
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 `~/.cursor/skills/` until a page quotes that directory; 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 |
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`, `.codex/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`