@chimix/agentscan 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +105 -53
  2. package/dist/cli.js +1081 -782
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -5,8 +5,8 @@
5
5
  </p>
6
6
 
7
7
  <p align="center">
8
- <img src="https://img.shields.io/badge/checks-23-111111?style=flat-square" alt="23 checks">
9
- <img src="https://img.shields.io/badge/tests-183%20passing-111111?style=flat-square" alt="183 tests">
8
+ <img src="https://img.shields.io/badge/checks-27-111111?style=flat-square" alt="27 checks">
9
+ <img src="https://img.shields.io/badge/tests-235%20passing-111111?style=flat-square" alt="235 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">
@@ -15,7 +15,7 @@
15
15
  </p>
16
16
 
17
17
  <p align="center">
18
- <strong>23 checks &middot; 3.4k lines &middot; 0 network calls &middot; every check sourced to a published spec line</strong><br>
18
+ <strong>27 checks &middot; 4.2k lines &middot; 0 network calls &middot; every check sourced to a published spec line</strong><br>
19
19
  <sub>Alpha. An earlier build reported 37 findings across 17 real projects of which <strong>25 were false</strong> — two checks had been written from what real projects looked like instead of from the spec. Both were deleted, and every check that survived is recorded in <a href="docs/spec/">docs/spec/</a> with the URL it came from and the date it was read. That story is the reason this tool exists in its current shape.</sub>
20
20
  </p>
21
21
 
@@ -47,12 +47,11 @@ think you have has silently not existed for six weeks.
47
47
  ```console
48
48
  $ agentscan check
49
49
 
50
- WARN hook:PreToolUse:.claude/hooks/guard-destructive-bash.js
51
- rule:hook.missing-script
52
- PreToolUse hook points at a script that does not exist
53
- evidence: hook PreToolUse @ …/.claude/settings.json
50
+ ERROR rule:hook.missing-script
51
+ PreToolUse hook points at a script that does not exist: .claude/hooks/guard-destructive-bash.js
52
+ PreToolUse @ .claude/settings.json · .claude/hooks/guard-destructive-bash.js
54
53
 
55
- Summary: 6 warn · 4 info hidden (--verbose) · score 40/100
54
+ Summary: 1 error · score 90/100
56
55
  ```
57
56
 
58
57
  That is the whole category: **a config file asserting something that is not true
@@ -65,7 +64,7 @@ No AI, no network, no heuristics. Read the config, read the disk, compare:
65
64
  ```
66
65
  1. Discover .claude/ .agents/ .mcp.json AGENTS.md skills-lock.json
67
66
  2. Extract immutable facts — never re-read during checking
68
- 3. Check 23 checks, each against one published spec line
67
+ 3. Check 27 checks, each against one published spec line
69
68
  4. Report text · --json · --output prompt (handoff for a fixing agent)
70
69
  ```
71
70
 
@@ -84,8 +83,16 @@ A check here has to point at a spec line. If it cannot, it does not ship.
84
83
 
85
84
  ## Try it in 30 seconds
86
85
 
87
- Run it against any project. It reads that project, writes nothing, and never
88
- leaves your machine:
86
+ No project handy? Run the built-in demo — it builds a throwaway fixture with the
87
+ killer case (a `PreToolUse` hook pointing at a missing script), prints the human
88
+ report, and deletes the fixture:
89
+
90
+ ```bash
91
+ npx @chimix/agentscan@latest demo
92
+ ```
93
+
94
+ Against a real project, it reads that tree, writes nothing, and never leaves
95
+ your machine:
89
96
 
90
97
  ```bash
91
98
  cd ~/your-project
@@ -104,8 +111,9 @@ bun add -d @chimix/agentscan # then: bunx agentscan check
104
111
  ```
105
112
 
106
113
  Point it at a project that actually has agent config — a `.claude/` directory,
107
- an `.mcp.json`, an `AGENTS.md`. On a project with none of those it will
108
- correctly find nothing and say so.
114
+ an `.mcp.json`, an `AGENTS.md`. A directory with agent config and no
115
+ `package.json` is still scannable. On a directory with neither it exits with a
116
+ clear error.
109
117
 
110
118
  ```bash
111
119
  # what it could possibly report, before you run it
@@ -148,10 +156,11 @@ bunx agentscan check # text report (cwd)
148
156
  bunx agentscan check ./my-app # explicit root
149
157
  bunx agentscan check --json # machine-readable
150
158
  bunx agentscan check --quiet # summary only
151
- bunx agentscan check --verbose # include KEEP and info-severity findings
159
+ bunx agentscan check --verbose # include KEEP, info findings, and each finding's id
152
160
  bunx agentscan check --fail-on warning
153
161
  bunx agentscan check --global # also scan global skill dirs
154
162
 
163
+ bunx agentscan demo # one-shot fixture (no project required)
155
164
  bunx agentscan explain <findingId> # detail one finding
156
165
  bunx agentscan rules # every check + rule id that can fire
157
166
  bunx agentscan init # write .agentscanrc.json
@@ -166,7 +175,7 @@ Flags for `check`:
166
175
  | `--copy` | Also copy the report to the system clipboard |
167
176
  | `--no-color` | Never colour, even on a terminal (`NO_COLOR=1` does the same) |
168
177
  | `--quiet` | Summary line only |
169
- | `--verbose` | Show KEEP + info-severity findings |
178
+ | `--verbose` | Show KEEP + info-severity findings, and print each finding's id |
170
179
  | `--fail-on <level>` | `never` (default) · `warning` · `error` |
171
180
  | `--fail-under <0-100>` | Fail when the score drops below this floor |
172
181
  | `--global` | Also scan `~/.claude/skills` and `~/.codex/skills` (see below) |
@@ -177,23 +186,28 @@ Flags for `check`:
177
186
  ## Sample output
178
187
 
179
188
  ```text
180
- ┌─────┐ 40/100 broken · touchagency
181
- │ ✕ ✕ │ ██████████░░░░░░░░░░░░░░░
182
- │ ⌒ │ 6 warn · 4 info hidden (--verbose)
189
+ ┌─────┐ 34/100 broken · touchagency
190
+ │ ✕ ✕ │ █████████░░░░░░░░░░░░░░░░
191
+ │ ⌒ │ 6 error · 2 warning · 11 info hidden (--verbose)
183
192
  └─────┘
184
193
 
185
- agentscan v0.4.0 — touchagency
194
+ agentscan v0.6.0 — touchagency
186
195
 
187
- Scanned: 46 deps · 54 skills · 1 mcp · 2 agents · packageManager=bun
196
+ Scanned: 46 deps · 108 skills · 1 mcp · 2 agents · packageManager=bun
188
197
 
189
- WARN rule:hook.missing-script ×6
190
- PreToolUse hook points at a script that does not exist
198
+ ERROR rule:hook.missing-script ×6
199
+ Hook points at a script that does not exist
191
200
  PostToolUse @ .claude/settings.json · .claude/hooks/notify-related-tests.js
192
201
  PreToolUse @ .claude/settings.json · .claude/hooks/guard-destructive-bash.js
193
202
  PreToolUse @ .claude/settings.json · .claude/hooks/protect-artists-json.js
194
203
  PreToolUse @ .claude/settings.json · .claude/hooks/protect-env.js
195
204
 
196
- Summary: 6 warn · 4 info hidden (--verbose) · score 40/100 broken
205
+ WARN rule:agent.invalid-name ×2
206
+ 2 findings
207
+ .claude/agents/marketing-seo-specialist.md
208
+ .claude/agents/testing-accessibility-auditor.md
209
+
210
+ Summary: 6 error · 2 warning · 11 info hidden (--verbose) · score 34/100 broken
197
211
  ```
198
212
 
199
213
  On a terminal the header is coloured green, yellow or red and the face tracks
@@ -201,14 +215,15 @@ the score. Redirect or pipe it and you get exactly the text above, with no
201
215
  escape sequences — the header box is dropped too, so `--json`, `--output
202
216
  prompt`, CI logs and `--copy` are unchanged by any of this.
203
217
 
204
- The `Stack:` line is orientation only — which project, how big. The summary lists
205
- only actions that actually occurred; a clean project prints `Summary: no findings`.
218
+ The `Scanned:` line is orientation only — which project, how big. The summary
219
+ lists severities (error / warning / info); a clean project prints
220
+ `Summary: no findings`. Score and `--fail-on` still use severity under the hood.
206
221
 
207
222
  JSON shape (abridged):
208
223
 
209
224
  ```json
210
225
  {
211
- "version": "0.1.0",
226
+ "version": "0.6.0",
212
227
  "root": "/path/to/project",
213
228
  "factsSummary": {
214
229
  "packageManager": "bun",
@@ -379,8 +394,9 @@ This is the only place agentscan starts a subprocess, and it runs only under
379
394
 
380
395
  `ignoreRules` disables a check project-wide, which is too blunt when a single
381
396
  hook uses an exotic launcher. `ignoreFindings` takes exact finding ids — the
382
- same strings `agentscan explain` accepts, so the value you already have from
383
- reading the report is the value you paste:
397
+ same strings `agentscan explain` accepts. `check --verbose` prints the id under
398
+ each finding, and `--json` carries it as `.findings[].id`, so the value you copy
399
+ out of the report is the value you paste:
384
400
 
385
401
  ```json
386
402
  { "ignoreFindings": ["hook.missing-script:hook:PreToolUse:./bin/wrapper"] }
@@ -391,21 +407,26 @@ reading the report is the value you paste:
391
407
  Every check lives in `src/checks/` and runs on every `check`. `agentscan rules`
392
408
  lists all of them with their ids; `agentscan explain <id>` details any finding.
393
409
 
394
- Most validate one discovered item against its own file on disk. The five
395
- `budget.*` / `policy.*` entries at the bottom judge aggregate size instead —
396
- they are all **info**, they are sourced to `docs/spec/thresholds.md`, and they
397
- are retuned through `thresholds` in `.agentscanrc.json` rather than switched
398
- off.
410
+ Most validate one discovered item against its own file on disk. The four
411
+ `budget.*` entries at the bottom judge aggregate size instead, as does
412
+ `skill.description-budget` — all five are **info**, all five are sourced to
413
+ `docs/spec/thresholds.md`, and all five are retuned through `thresholds` in
414
+ `.agentscanrc.json` rather than switched off.
399
415
 
400
416
  | id | Severity | Catches |
401
417
  |----|----------|---------|
402
418
  | `config.unreadable` | error | A config file that is not valid JSON, so whatever it declares is silently not in effect |
419
+ | `scan.truncated` | info | A file past the scan cap, so the checks that read its body saw only a prefix — about this tool's reach, not about your project |
403
420
  | `hook.missing-script` | error | A registered hook whose script does not exist — it never runs |
404
421
  | `hook.unknown-event` | error | A hook registered under an event name that is never dispatched |
405
- | `agent.missing-frontmatter` | warning | An agent definition with no `---` block |
406
- | `agent.missing-description` | info | Agent frontmatter has no `description`, so it will not be dispatched on its own |
407
- | `mcp.no-launch` | error | An MCP server with neither `command` nor `url`; its tools are never available |
408
- | `mcp.url-without-type` | error | A remote MCP server with a `url` but no `type` — read as stdio, fails, and is skipped |
422
+ | `agent.missing-frontmatter` | error | An agent definition with no `---` block |
423
+ | `agent.missing-description` | error | Agent frontmatter has no `description` |
424
+ | `agent.missing-name` | error | Agent frontmatter has no `name` |
425
+ | `agent.invalid-name` | warning | Agent name is not lowercase letters, numbers, and hyphens |
426
+ | `agent.duplicate-name` | error | Multiple agent files declare the same name |
427
+ | `mcp.no-launch` | error | An MCP server with neither `command` nor `url` — unlaunchable by schema |
428
+ | `mcp.command-missing` | error | An MCP `command` that is a path-like value whose file does not exist on disk |
429
+ | `mcp.url-without-type` | error | A remote MCP server with a `url` but no `type` — read as stdio, misconfigured by schema, and skipped |
409
430
  | `mcp.hardcoded-secret` | error | A token-shaped literal in MCP config (the value is never echoed back) |
410
431
  | `mcp.literal-env` | warning | Long literal `env` values that should be `${VAR}` |
411
432
  | `skill.missing-skill-md` | warning | A directory under a skill path with no `SKILL.md` |
@@ -417,11 +438,14 @@ off.
417
438
  | `skill.not-in-lock` | info | A skill on disk that the lockfile does not track — local and unpinned |
418
439
  | `skill.description-budget` | info | Skill names + descriptions exceed the startup character budget |
419
440
  | `skill.no-lockfile` | info | Skills present with no lockfile at all (only with `requireLock`) |
441
+ | `budget.agents-md` | info | `AGENTS.md` past the point where added lines stop helping (>150) |
442
+ | `budget.claude-md` | info | `CLAUDE.md` past the instruction count a model reliably follows (>200) |
443
+ | `budget.agents` | info | More agent definitions than a focused set (>8 in `.claude/agents`) |
444
+ | `budget.mcp` | info | More MCP servers than the point where tool selection degrades (>5) |
420
445
 
421
446
  Agent definitions are checked for structure only. An agent's frontmatter `name`
422
- is a display name — 16 of 34 real files declare one that differs from the
423
- filename, and nothing keys on the filename — so it is deliberately not compared,
424
- and a test guards against re-adding that check. Model identifiers and tool names
447
+ is not compared to the filename; it must simply be a valid lowercase identifier
448
+ and unique. Model identifiers and tool names
425
449
  are likewise not validated: both would need a hardcoded list, which is the shape
426
450
  that already shipped a false error. See [plans/003](plans/003-validate-agent-definitions.md).
427
451
 
@@ -433,13 +457,14 @@ references measured across 17 projects, 1645 resolved skill-relative and 12 only
433
457
  at the root. Fenced code blocks are stripped first: a path in an example is
434
458
  illustration, not a pointer.
435
459
 
436
- `hook.missing-script` is deliberately conservative. A command is only resolved
437
- when it is a single invocation with a path-like argument; shell programs
438
- (`a && b`, `$(...)`, pipes) are skipped, because a hook written as
439
- `[ ! -f x ] || node x` already handles the missing file and flagging it would be
440
- a false positive. `node -e "<code>"` is never treated as a path. Only
441
- `$CLAUDE_PROJECT_DIR` is expanded — other variables are left alone rather than
442
- guessed at.
460
+ `hook.missing-script` and `mcp.command-missing` are deliberately conservative.
461
+ A value is only resolved when it is path-like and the answer is certain; shell
462
+ programs (`a && b`, `$(...)`, pipes), bare PATH binaries (`npx`, `uvx`), and
463
+ unresolved env vars are skipped. A false "broken" on a working `npx` server
464
+ would be worse than silence. `node -e "<code>"` is never treated as a path.
465
+ Only `$CLAUDE_PROJECT_DIR` is expanded — other variables are left alone rather
466
+ than guessed at. Schema checks judge misconfiguration, not whether a correctly
467
+ shaped entry will start at runtime.
443
468
 
444
469
  Checks are written against the published specs, not against what happens to
445
470
  appear in real projects. Two that were written the other way round shipped as
@@ -461,8 +486,15 @@ When adding a check, add its spec line there first.
461
486
  the `computedHash` in `skills-lock.json` is the obvious `refresh` source; the
462
487
  hash algorithm used by the installing tool is not documented here, and it is
463
488
  not reproducible from `SKILL.md` bytes alone, so it is not implemented.
464
- - **Bun only.** `import.meta.dir` / `import.meta.main`; it will not run on Node.
465
- - Policy files are read up to 100 KB, so `policyLines` undercounts past that.
489
+ - **Runtime split.** Source development and the composite Action use Bun; the
490
+ published `dist/cli.js` runs on Node 20.11+ or Bun. The source entrypoint uses
491
+ Bun's `import.meta.dir` / `import.meta.main` and is not the Node entrypoint.
492
+ - **Bounded reads.** A `SKILL.md` is read to 64 KB and a policy file to 100 KB,
493
+ so `skill.broken-reference` and `policyLines` see only that much of a larger
494
+ file. This is reported — `scan.truncated`, at `info` — rather than left
495
+ silent, and it says nothing about the file being valid: it is, and the tools
496
+ that read it see all of it. Reporting the cap as `config.unreadable` at
497
+ severity error is a defect this tool shipped and [plan 019](plans/019-findings-say-true-things.md) fixed.
466
498
 
467
499
  ## Development
468
500
 
@@ -480,14 +512,34 @@ bun run src/cli.ts check tests/fixtures/lock-drift
480
512
  | `test` | `bun test` |
481
513
  | `check` | `bun run src/cli.ts check` |
482
514
 
515
+ ### Release checklist
516
+
517
+ Before publishing a release:
518
+
519
+ 1. Confirm `package.json` and `src/version.ts` contain the same version.
520
+ 2. Run `bun run build`, then verify `node dist/cli.js --version` prints it.
521
+ 3. Run `bun run typecheck`, `bun test`, and `bun run spec:check`.
522
+ 4. Refresh the test-count badge and version examples above from that release run.
523
+
483
524
  ## Design docs
484
525
 
485
526
  **This README is the source of truth for current behavior.** The documents under
486
527
  `docs/superpowers/specs/` are the original design and are **superseded** —
487
- several decisions changed while building: Bun-only runtime, the dep-to-skill map
488
- and its "orphan" heuristic dropped in favour of `skills-lock.json`, budget
489
- checks added, structural checks added, and the YAML rule engine deleted
490
- (`plans/010`). Read them as history, not as behavior.
528
+ several decisions changed while building: the original Bun-only runtime claim was
529
+ superseded by a Node-compatible published bundle, the dep-to-skill map and its
530
+ "orphan" heuristic dropped in favour of `skills-lock.json`, budget checks added,
531
+ structural checks added, and the YAML rule engine deleted (`plans/010`). Read
532
+ them as history, not as behavior.
533
+
534
+ ## Skill
535
+
536
+ A Cursor / Agent Skills file lives at [`skills/agentscan/SKILL.md`](skills/agentscan/SKILL.md). Copy the folder so the agent runs the audit before it edits hooks or claims a guard is in place:
537
+
538
+ ```bash
539
+ cp -R skills/agentscan .cursor/skills/agentscan
540
+ # or: .agents/skills/agentscan
541
+ # or: .claude/skills/agentscan
542
+ ```
491
543
 
492
544
  ## License
493
545