Linters read the code your agent writes. This reads the agent itself — skills, skills-lock.json, hooks, MCP servers, agent definitions, policy files.
The failure it exists for
You registered a PreToolUse hook to stop destructive shell commands:
// .claude/settings.json
{ "hooks": { "PreToolUse": [{ "hooks": [
{ "type": "command", "command": ".claude/hooks/guard-destructive-bash.js" }
] }] } }
Someone deleted the script six weeks ago.
$ ls .claude/hooks/guard-destructive-bash.js
ls: cannot access '.claude/hooks/guard-destructive-bash.js': No such file or directory
Your agent starts normally. No warning, no error, no log line. The guard you
think you have has silently not existed for six weeks.
$ agentscan check
ERROR rule:claude.hook.missing-script
PreToolUse hook points at a script that does not exist: .claude/hooks/guard-destructive-bash.js
PreToolUse @ .claude/settings.json · .claude/hooks/guard-destructive-bash.js
Summary: 1 error · score 90/100
That is the whole category: a config file asserting something that is not true
of the filesystem, where nothing else in your stack will ever tell you.
How it works
No AI, no network on check. Read the config, read the disk, compare:
1. Discover .claude/ .commandcode/ .grok/ .agents/ .vscode/ .cursor/ .codex/ .gemini/ .github/ .continue/ AGENTS.md skills-lock.json
2. Extract immutable facts — never re-read during checking
3. Check 118 checks, each labeled spec-required, vendor-recommendation,
security, internal-consistency, or heuristic
4. Report text · --json · --output prompt (handoff for a fixing agent)
Same tree in, same findings out, every time. It never writes to the tree it
scans and never opens a socket.
What it will not do
It will not compare a Claude-native skill's frontmatter name to its directory
(portable Agent Skills under .agents/skills and Cursor .cursor/skills still
require that match), and it will not validate model ids or MCP tool names.
Those last two need a hardcoded list of valid values, with everything absent
from the list reported as broken. That is exactly how the 25 false findings
happened: a hook-event list with 9 names when the spec has 31, calling working
hooks dead at severity error.
A spec-required check has to point at a published line. If it cannot, it does
not ship as an error. Size opinions and installer policy remain as info
heuristics and are labeled that way in the registry.
Try it in 30 seconds
No project handy? Run the built-in demo — it builds a throwaway fixture with the
killer case (a PreToolUse hook pointing at a missing script), prints the human
report, and deletes the fixture:
npx @chimix/agentscan@latest demo
Against a real project, it reads that tree, writes nothing, and never leaves
your machine:
cd ~/your-project
npx @chimix/agentscan@latest
Works on Node 20.11+ or Bun — the published bin is a single bundled file with
no dependencies to install. bunx --bun @chimix/agentscan is the same thing on
Bun.
The package is scoped @chimix/agentscan because npm rejects the bare name as
too close to an unrelated agent-scan. The command you type stays agentscan.
bun add -d @chimix/agentscan # then: bunx agentscan check
Point it at a project that actually has agent config — a .claude/,
.commandcode/, or .grok/ directory, an .mcp.json, an AGENTS.md. A directory with
agent config and no package.json is still scannable. On a directory with
neither it exits with a clear error.
# what it could possibly report, before you run it
bunx @chimix/agentscan rules
# the interesting bits it hides by default
bunx @chimix/agentscan check ~/your-project --verbose
# why one specific finding fired
bunx @chimix/agentscan explain claude.hook.missing-script:hook:PreToolUse:./x.js ~/your-project
check never opens a socket and never writes to the tree it scans. Contributing
needs Bun 1.4+ — the version CI pins and every gate is verified on; the
text bun.lock this repo ships cannot be read by 1.1 at all. The repo runs
TypeScript directly and bun run build
bundles it for Node.
In CI
The Action runs from its own checkout, so it uses the ref you pin rather than
whatever is on npm:
- uses: SimaAlexandru99/agentscan@v1
with:
fail-on: error
From a checkout
git clone --depth=1 https://github.com/SimaAlexandru99/agentscan
cd agentscan && bun install
bun run src/cli.ts check ~/your-project
Usage
bunx agentscan check # text report (cwd)
bunx agentscan check ./my-app # explicit root
bunx agentscan check --json # machine-readable
bunx agentscan check --quiet # summary only
bunx agentscan check --verbose # include KEEP, info findings, and each finding's id
bunx agentscan check --fail-on warning
bunx agentscan check --global # also scan global skill dirs
bunx agentscan demo # one-shot fixture (no project required)
bunx agentscan explain <findingId> # detail one finding
bunx agentscan rules # every check + rule id that can fire
bunx agentscan init # write .agentscanrc.json
Flags for check:
| Flag | Meaning |
|---|
--json | JSON report (alias for --output json) |
--output <format> | human (default) · json · prompt |
--copy | Also copy the report to the system clipboard |
--no-color | Never colour, even on a terminal (NO_COLOR=1 does the same) |
--quiet | Summary line only |
--verbose | Show KEEP + info-severity findings, and print each finding's id |
--fail-on <level> | never (default) · warning · error |
--fail-under <0-100> | Fail when the score drops below this floor |
--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.json (user + matching local mcpServers), Copilot $COPILOT_HOME / ~/.copilot/hooks and settings.json, Codex $CODEX_HOME / ~/.codex/config.toml, Grok $GROK_HOME / ~/.grok (hooks + config.toml), Command Code user MCP/agents/memory, and Windsurf ~/.codeium/windsurf MCP + global_rules.md + hooks.json (see below) |
--config <path> | Config file path |
v1 does not write the tree — no apply, no skill delete/install. Findings may suggest shell commands; you run them yourself.
Sample output
┌─────┐ 34/100 broken · touchagency
│ ✕ ✕ │ █████████░░░░░░░░░░░░░░░░
│ ⌒ │ 6 error · 2 warning · 11 info hidden (--verbose)
└─────┘
agentscan v1.4.0 — touchagency
Scanned: 46 deps · 108 skills · 1 mcp · 2 agents · packageManager=bun
ERROR rule:claude.hook.missing-script ×6
Hook points at a script that does not exist
PostToolUse @ .claude/settings.json · .claude/hooks/notify-related-tests.js
PreToolUse @ .claude/settings.json · .claude/hooks/guard-destructive-bash.js
PreToolUse @ .claude/settings.json · .claude/hooks/protect-artists-json.js
PreToolUse @ .claude/settings.json · .claude/hooks/protect-env.js
WARN rule:claude.agent.invalid-name ×2
2 findings
.claude/agents/marketing-seo-specialist.md
.claude/agents/testing-accessibility-auditor.md
Summary: 6 error · 2 warning · 11 info hidden (--verbose) · score 34/100 broken
On a terminal the header is coloured green, yellow or red and the face tracks
the score. Redirect or pipe it and you get exactly the text above, with no
escape sequences — the header box is dropped too, so --json, --output prompt, CI logs and --copy are unchanged by any of this.
The Scanned: line is orientation only — which project, how big. The summary
lists severities (error / warning / info); a clean project prints
Summary: no findings. Score and --fail-on still use severity under the hood.
JSON shape (abridged):
{
"version": "1.4.0",
"root": "/path/to/project",
"factsSummary": {
"packageManager": "bun",
"depCount": 1,
"skillCount": 1,
"globalSkillCount": 0
},
"findings": [
{
"id": "claude.hook.missing-script:hook:PreToolUse:.claude/hooks/protect-env.js",
"ruleId": "claude.hook.missing-script",
"action": "warn",
"severity": "error",
"subject": "hook:PreToolUse:.claude/hooks/protect-env.js",
"message": "PreToolUse hook points at a script that does not exist: .claude/hooks/protect-env.js",
"reason": "The hook is registered but its script is missing, so it never runs.",
"evidence": [{ "kind": "script", "value": ".claude/hooks/protect-env.js" }],
"suggest": "Restore the script or remove the hook"
}
]
}
Same tree → same sorted findings, with stable unique ids.
--global
Adds user skill dirs ($CLAUDE_CONFIG_DIR/skills or ~/.claude/skills, ~/.codex/skills,
~/.commandcode/skills, ~/.agents/skills, $GROK_HOME/skills or
~/.grok/skills), Claude user settings / agents / CLAUDE.md / rules /
commands and ~/.claude.json (top-level user mcpServers plus local
projects.<scanned-path>.mcpServers), Copilot ~/.copilot/hooks, Codex $CODEX_HOME /
~/.codex (config.toml MCP plus AGENTS.override.md / AGENTS.md),
Grok $GROK_HOME / ~/.grok (hooks + config.toml), Command Code user
agents / MCP / memory, and Windsurf ~/.codeium/windsurf/mcp_config.json,
memories/global_rules.md, hooks.json, and skills to the structural checks: a SKILL.md that
is malformed is malformed wherever it lives, and those findings carry a
source: global evidence entry so you can tell them apart. Codex and
Command Code skills use the Agent Skills contract (name and description
required). Grok skills do not — name and description are optional. It
never opens auth.json, mcp-tokens.json, Codex auth.json, or Grok
auth.json / mcp_credentials.json. A ~/.claude.json without user or
matching local mcpServers is skipped, not reported as unreadable.
CLAUDE_CONFIG_DIR relocates the ~/.claude directory; ~/.claude.json
stays in the home directory.
Lockfile checks stay project-scoped — a project lockfile cannot pin a skill that
lives in your home directory, so reporting one as "not in the lockfile" could
only ever be wrong. In a monorepo, each skills-lock.json governs only the
skills under its directory (nearest lock wins). A skill with no ancestor
lockfile is not compared to a distant lock.
includeGlobal: true in .agentscanrc.json does the same thing without the
flag.
Score
score = max(0, 100 - 10 × errors - 3 × warnings)
That is the whole formula. Count two lines of the report and you can recompute
it. The comparable tools both score 0-100 and neither states its formula in
prose: shadscan's category weights are readable in its MIT source but not
documented, and react-doctor computes the score by POSTing your diagnostics to
react.doctor/api/score, so it cannot be recomputed offline at all. A number
whose derivation cannot be inspected is the false precision a score is supposed
to replace.
Deduction, not coverage. The obvious model — checks passed over checks run —
was measured against 17 real projects and rejected. Every one scored between
97.7% and 100%, so it discriminated nothing, and it inverted severity: the
denominator grows with the size of your config while the defects do not, so a
project with 85 skills and one warning outscored a project with 10 skills and
the same one warning. Deduction has no denominator. Measured on the same 17
projects it spans 40 to 100, and the 85-skill project scores 97 while a
54-skill one with six broken hooks scores 40.
Info findings cost nothing. They are budgets and hygiene notes, and they do
grow with project size — charging for them would reintroduce the inversion.
The score is a summary, not a verdict. --fail-on error is still the sharper
CI gate: it says which kind of problem fails the build, where --fail-under
only says how many points of unspecified trouble is too much.
Exit codes
| Code | When |
|---|
0 | OK, or findings below --fail-on threshold (default never always 0 for findings) |
1 | Findings at/above --fail-on, or score below --fail-under |
2 | Usage / config / load error |
CI
- uses: SimaAlexandru99/agentscan@v1
with:
fail-on: error # never | warning | error
output: human # human | json | prompt
The action installs Bun, runs the scan, and exposes the report as the report
output so a later step can post it. Or run it directly:
- name: agentscan
run: bunx agentscan check --fail-on error
Or with a local checkout of this repo:
bun run src/cli.ts check --fail-on warning --json
Default failOn is never so local runs stay non-blocking until you opt in.
Config
Optional .agentscanrc.json (create with agentscan init):
{
"skillPaths": [".agents/skills", ".claude/skills", "skills", ".cursor/skills", ".codex/skills", ".commandcode/skills", ".grok/skills", ".windsurf/skills"],
"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"],
"policyFiles": ["AGENTS.md", "CLAUDE.md"],
"ignoreSkills": [],
"ignoreRules": [],
"ignoreFindings": [],
"failOn": "never",
"includeGlobal": false,
"requireLock": false,
"thresholds": {
"skillListingChars": 8000,
"skillListingMaxDescChars": 1536,
"mcp": 5,
"agentsMdLines": 150,
"claudeMdLines": 200,
"agents": 8
}
}
Budget rules are info (hidden unless --verbose) so they do not flood default text or CI with --fail-on warning.
Handing findings to an agent
agentscan check --output prompt
A paste-ready markdown handoff: each item carries the message, why it matters,
the suggested fix, its evidence, and its finding id. Info-severity findings are
left out — budgets and hygiene notes are for a maintainer to weigh, not work
items for an executor.
Getting the report out of the terminal
Saving and piping are the shell's job, and it already does them:
agentscan check --output prompt > handoff.md # save
agentscan check --output prompt | claude # send to another tool
agentscan check --json | jq '.findings[]' # filter
The clipboard is the one thing no shell does portably, so that one is a flag:
agentscan check --output prompt --copy
--copy tries wl-copy, xclip, xsel, pbcopy, clip.exe in turn and uses
the first that succeeds — being installed is not the same as working, and on a
headless WSL shell the first two are present and both fail. The report still
goes to stdout; the "copied" note goes to stderr, so --copy composes with
redirection. A clipboard that cannot be reached prints why and does not change
the exit code.
This is the only place agentscan starts a subprocess, and it runs only under
--copy. It writes to the clipboard and nowhere else.
Suppressing one finding
ignoreRules disables a check project-wide, which is too blunt when a single
hook uses an exotic launcher. ignoreFindings takes exact finding ids — the
same strings agentscan explain accepts. check --verbose prints the id under
each finding, and --json carries it as .findings[].id, so the value you copy
out of the report is the value you paste:
{ "ignoreFindings": ["claude.hook.missing-script:hook:PreToolUse:./bin/wrapper"] }
What it checks
Every check lives in src/checks/ and runs on every check. agentscan rules
lists all of them with their ids; agentscan explain <id> details any finding.
Most validate one discovered item against its own file on disk. Budget and
hygiene checks are info (hidden unless --verbose).
Every rule states where its assertion comes from, and every report says so:
a provenance label (spec-required, vendor-recommendation, security,
internal-consistency, or heuristic) and either the vendor page it was read
from, with the date, or an explicit note that no vendor page states it.
ERROR rule:cursor.hook.unknown-event
"bogusEventName" is not a hook event that gets dispatched
bogusEventName @ .cursor/hooks.json · sessionStart, sessionEnd, ...
spec-required · cursor.com/docs/hooks · read 2026-09-03
ERROR rule:cursor.hook.missing-script
Hook points at a script that does not exist
afterFileEdit @ .cursor/hooks.json · ./hooks/format.sh
internal-consistency · no vendor page · agentscan inference
agentscan rules lists every rule with its provenance and source;
agentscan rules --json adds the full URL and the docs/spec capture holding
the quoted lines; agentscan explain <id> prints both for one finding, and
--json carries them on every finding. Every cited page is one
bun run spec:check re-fetches and hash-compares, so a vendor rewriting it
fails CI instead of quietly turning a quoted rule into a false positive.
| id | Severity | Provenance | Source | Catches |
|---|
config.unreadable | error | internal-consistency | parser result | A config file that is not valid JSON, so whatever it declares is silently not in effect |
scan.truncated | info | internal-consistency | scan cap | 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 |
claude.hook.unknown-event | error | spec-required | code.claude.com/docs/en/hooks | A Claude hook registered under an event name that is never dispatched |
claude.hook.missing-script | error | internal-consistency | agentscan inference | A registered Claude command hook whose script does not exist — it never runs |
vscode.hook.unknown-event | error | spec-required | code.visualstudio.com/docs/agents/reference/hooks-reference | A VS Code hook registered under an event name that is never dispatched |
vscode.hook.missing-script | error | internal-consistency | agentscan inference | A VS Code command hook whose script does not exist |
claude.hook.invalid-group | error | internal-consistency | code.claude.com/docs/en/hooks | A Claude hook matcher group with no nested hooks array (flat handler arrays are invalid) |
claude.hook.command-without-command | error | spec-required | code.claude.com/docs/en/hooks | A Claude type: command hook with no |
Coverage in 1.4.0
Each cell is one coverage dimension. There is no full / partial label: a
documented global or static location that is not opened stays unread.
Dimensions:
- project discovery — documented in-repo files the scanner opens
- global discovery — documented user / machine locations;
--global is named when that is the only path that opens them
- schema — spec-required (and labeled vendor-recommendation) field checks
- precedence — documented merge / override / shadowing
- conformance — official-shaped fixture or equivalent negative tests
| Ecosystem | project discovery | global discovery | schema | precedence | conformance |
|---|
| 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 |
| AGENTS.md | nested walk-up | n/a | no required fields | nearest-wins | tests |
| 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.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 |
| 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 |
| Codex | .codex/config.toml, .codex/skills, AGENTS chain | --global $CODEX_HOME / ~/.codex/config.toml MCP; AGENTS.override.md then ; unread system, managed, requirements, profiles, plugins, trust |
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. Windsurf workspace rules use a 12,000-character budget (windsurf.rule.too-large); do not apply the Cursor 500-line check. Portable AGENTS.md / agents.md stay sourceProvider: unknown — this scanner does not claim exclusive Windsurf ownership.
Old 0.7 rule ids still work in ignoreRules and explain (for example hook.missing-script aliases claude.hook.missing-script).
Agent definitions are checked for structure only. A Claude agent's frontmatter
name is not compared to the filename; it must be lowercase letters and
hyphens and unique. Command Code supplies name from the filename when
frontmatter omits it, and does not emit missing-name or missing-description.
Model identifiers and tool names are likewise not validated: both would need a
hardcoded list, which is the shape that already shipped a false error. See
plans/003.
skill.broken-reference reads the body, not just the frontmatter. It looks for
paths under the conventional bundled directories (scripts/, references/,
assets/, templates/, examples/). Agent Skills references resolve from the
skill root only. Claude native skills also try the repo root — of 1674
references measured across 17 projects, 1645 resolved skill-relative and 12 only
at the root. Fenced code blocks are stripped first: a path in an example is
illustration, not a pointer.
Where it looks for hooks
The Claude hooks reference lists seven places a hook can be registered. agentscan
reads the project settings files, in-tree plugin and command-file hooks,
~/.claude/settings.json (or $CLAUDE_CONFIG_DIR/settings.json)
under --global, plus VS Code workspace hooks and Command Code settings hooks:
| Registered in | Script paths resolve against |
|---|
.claude/settings.json · .claude/settings.local.json | project root, ${CLAUDE_PROJECT_DIR} |
~/.claude/settings.json (or $CLAUDE_CONFIG_DIR/settings.json) under --global | scanned project root, ${CLAUDE_PROJECT_DIR} |
A plugin's hooks/hooks.json (a directory with .claude-plugin/plugin.json, or the scan root itself) | the plugin root, ${CLAUDE_PLUGIN_ROOT} |
SKILL.md frontmatter (including in-tree plugin skills) | the skill's own directory, then the project root |
.claude/commands/*.md frontmatter | the command file's directory, then the project root |
.claude/agents/*.md frontmatter | the agent file's directory, then the project root |
.github/hooks/*.json without version: 1 (native VS Code) | project root and the hooks directory |
.github/hooks/*.json with version: 1 (Copilot CLI) | project root, cwd, host bash / powershell / command |
.github/copilot/settings.json · .github/copilot/settings.local.json (Copilot CLI inline) | project root and the settings directory; always copilot-cli |
~/.copilot/hooks and ~/.copilot/settings.json (or $COPILOT_HOME/…) under --global | same Copilot CLI / native VS Code split on hook files; settings hooks are always copilot-cli |
.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 |
.grok/hooks/*.json | project root and the hooks directory |
$GROK_HOME/hooks or ~/.grok/hooks under |
${CLAUDE_PLUGIN_ROOT} is expanded only for a hook that came from a plugin.
In a settings file it names nothing, so the path is skipped rather than guessed
at — measured across 17 installed plugins, 31 of 33 plugin hook commands use it
and none of them are settings hooks. claude.hook.unknown-event covers all four sites
at no extra cost. Sources and measurements: docs/spec/hook-sources.md.
claude.hook.missing-script and mcp.command-missing are deliberately conservative.
A value is only resolved when it is path-like and the answer is certain; shell
programs (a && b, $(...), pipes), bare PATH binaries (npx, uvx), and
unresolved env vars are skipped. A false "broken" on a working npx server
would be worse than silence. node -e "<code>" is never treated as a path.
Only $CLAUDE_PROJECT_DIR, $COMMANDCODE_PROJECT_DIR, and $COMMANDCODE_CWD
are expanded — other variables are left alone rather than guessed at. Schema
checks judge misconfiguration, not whether a correctly shaped entry will start
at runtime.
Spec-required checks are written against published pages, not against what
happens to appear in real projects. Two that were written the other way round
shipped as false positives — a nine-name hook-event list where the spec has 31,
and a name must equal the directory rule that Claude's spec explicitly
contradicts. Both are gone. Heuristics that remain (skill.duplicate-description,
the installer lock checks, and the size budgets other than budget.claude-md
and skill.description-budget) are labeled in the registry and stay at info.
Assumptions are recorded in docs/spec/ with source URL, read
date, and provenance. When adding a spec-required check, add its spec line there
first.
Known limits
- No dependency→skill knowledge. Nothing here says "you have
next, you
should add a next-* skill". That needed a hand-maintained registry, and the
version that existed fired on none of the 17 projects measured, so it was
removed. Skill provenance comes from skills-lock.json instead.
- No rule produces
refresh or keep. Comparing a skill's content against
the computedHash in skills-lock.json is the obvious refresh source; the
hash algorithm used by the installing tool is not documented here, and it is
not reproducible from SKILL.md bytes alone, so it is not implemented.
- Runtime split. Source development and the composite Action use Bun; the
published
dist/cli.js runs on Node 20.11+ or Bun. The source entrypoint uses
Bun's import.meta.dir / import.meta.main and is not the Node entrypoint.
- Grok compatibility and unpublished surfaces are unread. Managed
(
~/.grok/managed_config.toml, /etc/grok/managed_config.toml) and
requirements TOML are not opened. Plugins, marketplaces, personas, and
[skills] paths (no quoted resolution base) stay unread. .grok/agents/
is unread because the page names the directory but not a filename pattern
or required fields. Claude / Cursor / .mcp.json files keep their own
schemas — this scanner does not add consumedBy: grok to them. Grok’s
“skip gitignored instruction files” is unread: agentscan does not honor
.gitignore. auth.json and mcp_credentials.json are never opened.
Spec/runtime Grok MCP checks skip shadowed same-name servers; secrets
still inspect every readable MCP file.
- Codex admin and profile layers are unread. System
/etc/codex/config.toml,
managed preferences, requirements.toml, $CODEX_HOME/<name>.config.toml
profiles, plugin MCP, and trust_level are not opened. User
$CODEX_HOME/config.toml (or ~/.codex/config.toml) MCP is scanned only
under --global. auth.json is never opened. Same-name user and project
MCP servers are both inventoried — the published page does not quote a
replace-entirely rule. User-file project_doc_* knobs are not applied.
- Command Code local MCP is unread.
~/.commandcode/projects/{project}/mcp.json
is keyed by a working-directory slug that the sessions page does not publish.
Until it does, that path is skipped rather than guessed. Bundled Command Code
skills and launch flags are not project files and are not scanned.
are inventoried; the TypeScript is never executed or imported.
and are never opened. Command Code project
root is the git root (or cwd outside a git repo); a child does
not hide . Spec/runtime Command Code checks skip
shadowed lower-precedence definitions; secrets still inspect every readable
MCP file.
Releases
Notes for every published version are in CHANGELOG.md.
GitHub already has pages for v0.1.0
and v0.4.0.
1.4.0 (2 September 2026) closes two coverage gaps and ships the
2026-09-02 re-verification: Copilot CLI inline hooks in
.github/copilot/settings.json / settings.local.json and --global
$COPILOT_HOME / ~/.copilot/settings.json; mcp.literal-credential on
MCP headers / http_headers / auth (field paths only). Also the
seven false-positive corrections, spec content hashes, verbatim
conformance fixtures, and Windsurf Cascade hooks / skills from #13.
103 checks, 532 tests. Policy hooks, plugin hooks.json, and
.claude/settings.json as Copilot stay unread.
1.3.0 (31 August 2026) adds native Windsurf / Cascade coverage: workspace
.devin/rules and .windsurf/rules, portable agents.md, and --global
mcp_config.json plus global_rules.md. Devin CLI MCP, auto memories,
system rules, and a project MCP path stay unread.
1.2.1 (31 August 2026) opens Codex user MCP ($CODEX_HOME/config.toml
or ~/.codex/config.toml) under --global. Same-name user and project
servers are both inventoried. System, managed, requirements, profiles,
plugins, and trust_level stay unread. auth.json is never opened.
1.2.0 (31 August 2026) adds a native Grok Build provider: project
.grok/* plus $GROK_HOME / ~/.grok/* under --global. Compatibility
with Claude or Cursor is not schema identity. Agents, managed/requirements
TOML, plugins, [skills] paths, and .gitignore skip stay unread.
1.1.0 (31 August 2026) adds a native Command Code provider and finishes the
correctness pass across registered checks: hook schema profiles, Agent Skills
optional fields, Claude listing budget, Codex chain knobs, and Copilot CLI
version: 1 files. Still offline on check. Shared .mcp.json is mcp-json
(Claude and Command Code), not Claude-only — that MCP hotfix is 1.0.1 in
the changelog and ships inside this package version.
1.0.0 (30 August 2026) is the first stable release: 59 checks, 345 tests,
still offline on check. It adds portable Agent Skills (including Cursor and
Codex skill trees), MCP profiles for VS Code / Cursor / Codex / Antigravity /
Gemini / OpenCode / Continue, nested AGENTS.md, walk-up agents, VS Code
hooks, and platform-aware launch checks — a windows: script is not
POSIX-joined when the scanner runs on Linux. Old 0.7 rule ids still work in
ignoreRules and explain. 0.9.0 was planned as a separate train and shipped
inside this release.
Development
bun install
bun run typecheck
bun test
bun run src/cli.ts check tests/fixtures/lock-drift
| Script | Command |
|---|
agentscan | bun run src/cli.ts |
typecheck | tsc --noEmit |
test | bun test |
check | bun run src/cli.ts check |
Release checklist
Before publishing a release:
- Confirm
package.json and src/version.ts contain the same version.
- Run
bun run build, then verify node dist/cli.js --version prints it.
- Run
bun run typecheck, bun test, and bun run spec:check. If a page
hash changed, re-read that page, update its docs/spec/ capture and the
conformance fixture, then bun run spec:record.
- Refresh the test-count badge and version examples above from that release run.
- Add a section to CHANGELOG.md for the version, then publish
the GitHub release from that text.
Design docs
This README is the source of truth for current behavior. The documents under
docs/superpowers/specs/ are the original design and are superseded —
several decisions changed while building: the original Bun-only runtime claim was
superseded by a Node-compatible published bundle, the dep-to-skill map and its
"orphan" heuristic dropped in favour of skills-lock.json, budget checks added,
structural checks added, and the YAML rule engine deleted (plans/010). Read
them as history, not as behavior.
Skill
A Cursor / Agent Skills file lives at skills/agentscan/SKILL.md. Copy the folder so the agent runs the audit before it edits hooks or claims a guard is in place:
cp -R skills/agentscan .cursor/skills/agentscan
# or: .agents/skills/agentscan
# or: .claude/skills/agentscan
License
MIT