claudecode-linter 2.1.148 → 2.1.150-patch.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 (37) hide show
  1. package/.claudecode-lint.defaults.yaml +21 -2
  2. package/README.md +90 -3
  3. package/contracts/agent-frontmatter.schema.json +142 -2
  4. package/contracts/command-frontmatter.schema.json +128 -2
  5. package/contracts/hooks.schema.json +275 -0
  6. package/contracts/lsp.schema.json +2 -2
  7. package/contracts/mcp.schema.json +326 -0
  8. package/contracts/monitors.schema.json +3 -3
  9. package/contracts/plugin.schema.json +672 -22
  10. package/contracts/schemastore/keybindings.schema.json +179 -0
  11. package/contracts/schemastore/manifest.json +25 -0
  12. package/contracts/schemastore/marketplace.schema.json +2067 -0
  13. package/contracts/schemastore/plugin-manifest.schema.json +1834 -0
  14. package/contracts/schemastore/settings.schema.json +3062 -0
  15. package/contracts/settings.schema.json +333 -4
  16. package/contracts/skill-frontmatter.schema.json +198 -2
  17. package/dist/config.js +13 -8
  18. package/dist/contracts.js +20 -1
  19. package/dist/discovery.js +23 -0
  20. package/dist/formatters/human.js +3 -2
  21. package/dist/index.js +80 -12
  22. package/dist/linters/agent-md.js +44 -7
  23. package/dist/linters/command-md.js +37 -1
  24. package/dist/linters/hooks-json.js +24 -0
  25. package/dist/linters/keybindings-json.js +53 -0
  26. package/dist/linters/marketplace-json.js +55 -0
  27. package/dist/linters/mcp-json.js +24 -0
  28. package/dist/linters/settings-json.js +42 -15
  29. package/dist/linters/skill-md.js +48 -1
  30. package/dist/plugin-schema.js +96 -39
  31. package/dist/utils/asset-path.js +56 -0
  32. package/dist/utils/effort.js +29 -0
  33. package/dist/utils/frontmatter-keys.js +32 -3
  34. package/dist/utils/frontmatter.js +1 -1
  35. package/dist/utils/safe-write.js +36 -0
  36. package/dist/utils/terminal.js +18 -0
  37. package/package.json +6 -2
@@ -32,6 +32,10 @@ rules:
32
32
  skill-md/description-max-length: error
33
33
  skill-md/description-no-angle-brackets: error
34
34
  skill-md/description-trigger-phrases: warning
35
+ skill-md/model-valid: warning
36
+ skill-md/effort-valid: warning
37
+ skill-md/allowed-tools-valid: warning
38
+ skill-md/frontmatter-field-type: warning
35
39
  skill-md/no-unknown-frontmatter: warning
36
40
  skill-md/body-word-count: warning
37
41
  skill-md/body-has-headers: info
@@ -45,21 +49,36 @@ rules:
45
49
  agent-md/description-routing-guidance: warning
46
50
  agent-md/model-required: error
47
51
  agent-md/model-valid: warning
48
- agent-md/color-required: error
52
+ agent-md/permission-mode-valid: warning
53
+ agent-md/effort-valid: warning
54
+ agent-md/max-turns-valid: warning
49
55
  agent-md/color-valid: warning
50
56
  agent-md/system-prompt-present: error
51
57
  agent-md/system-prompt-length: warning
52
58
  agent-md/system-prompt-second-person: info
53
59
 
60
+ # ── marketplace.json (schemastore-only) ──────────────────
61
+ marketplace-json/valid-json: error
62
+ marketplace-json/schema-valid: error
63
+
64
+ # ── keybindings.json (schemastore-only) ──────────────────
65
+ keybindings-json/valid-json: error
66
+ keybindings-json/schema-valid: error
67
+
54
68
  # ── Command .md ──────────────────────────────────────────
55
69
  command-md/valid-frontmatter: error
56
70
  command-md/schema-valid: error
57
71
  command-md/description-required: error
72
+ command-md/name-format: warning
73
+ command-md/model-valid: warning
74
+ command-md/effort-valid: warning
75
+ command-md/frontmatter-field-type: warning
58
76
  command-md/allowed-tools-valid: warning
59
77
  command-md/body-present: warning
60
78
 
61
79
  # ── hooks.json ───────────────────────────────────────────
62
80
  hooks-json/valid-json: error
81
+ hooks-json/schema-valid: error
63
82
  hooks-json/root-hooks-key: error
64
83
  hooks-json/valid-event-names: error
65
84
  hooks-json/hook-type-required: error
@@ -71,7 +90,6 @@ rules:
71
90
 
72
91
  # ── settings.json ────────────────────────────────────────
73
92
  settings-json/valid-json: error
74
- settings-json/scope-file-name: error
75
93
  settings-json/scope-field: warning
76
94
  settings-json/no-unknown-fields: warning
77
95
  settings-json/permissions-object: error
@@ -98,6 +116,7 @@ rules:
98
116
  # ── mcp.json ─────────────────────────────────────────────
99
117
  mcp-json/scope-file-name: warning
100
118
  mcp-json/valid-json: error
119
+ mcp-json/schema-valid: error
101
120
  mcp-json/servers-required: error
102
121
  mcp-json/servers-object: error
103
122
  mcp-json/server-name-kebab: info
package/README.md CHANGED
@@ -23,6 +23,17 @@ Or run directly:
23
23
  npx claudecode-linter ~/projects/my-plugin/
24
24
  ```
25
25
 
26
+ Or build it from a clone of this repository — there is no `dist/` until you build:
27
+
28
+ ```bash
29
+ git clone https://github.com/retif/claudecode-linter
30
+ cd claudecode-linter
31
+ npm ci && npm run build # install dependencies, compile to dist/
32
+ node dist/index.js path/to/plugin/
33
+ ```
34
+
35
+ Commands elsewhere in this README are written as `claudecode-linter …` (the global / `npx` install); from a clone, that is `node dist/index.js …`.
36
+
26
37
  ## Usage
27
38
 
28
39
  ### Lint
@@ -130,9 +141,9 @@ No issues found.
130
141
  | Type | Files | Rules |
131
142
  |------|-------|-------|
132
143
  | plugin-json | `.claude-plugin/plugin.json` | 13 |
133
- | skill-md | `skills/*/SKILL.md` | 12 |
134
- | agent-md | `agents/*.md` | 17 |
135
- | command-md | `commands/*.md` | 6 |
144
+ | skill-md | `skills/*/SKILL.md` | 16 |
145
+ | agent-md | `agents/*.md` | 20 |
146
+ | command-md | `commands/*.md` | 10 |
136
147
  | hooks-json | `hooks/hooks.json` | 9 |
137
148
  | settings-json | `.claude-plugin/settings.json` | 25 |
138
149
  | mcp-json | `.claude-plugin/mcp.json` | 16 |
@@ -211,6 +222,82 @@ Formatting is powered by [prettier](https://prettier.io/) for consistent JSON an
211
222
  | 1 | Lint errors found |
212
223
  | 2 | Fatal error |
213
224
 
225
+ ## Running on untrusted plugins
226
+
227
+ claudecode-linter is a **static analyzer** — it parses and validates the artifacts it inspects, it never executes them. There is no `eval`, no `child_process`, no declared hooks are run, and no MCP servers are spawned. Linting **trusted** code needs no special isolation.
228
+
229
+ For **untrusted** plugins — especially with `--fix`, which writes files back to disk — run the linter sandboxed. claudecode-linter is verified to run correctly fully confined: no network, a read-only root filesystem, all Linux capabilities dropped, `no-new-privileges`, a non-root UID, and only the target directory mounted.
230
+
231
+ ### The Docker image
232
+
233
+ Two multi-arch (`linux/amd64`, `linux/arm64`) images are published to the GitHub Container Registry — two separate packages, each with its own `:latest` rolling tag and `:<version>` tag:
234
+
235
+ | Image | Built from | Notes |
236
+ |-------|-----------|-------|
237
+ | `ghcr.io/retif/node-claudecode-linter` | `Dockerfile` — `node:24-alpine` | default |
238
+ | `ghcr.io/retif/bun-claudecode-linter` | `Dockerfile.compile` — `bun build --compile` single executable | smaller (~44 MB compressed) |
239
+
240
+ **Pull a published image:**
241
+
242
+ ```bash
243
+ docker pull ghcr.io/retif/node-claudecode-linter # default (node:24-alpine)
244
+ docker pull ghcr.io/retif/bun-claudecode-linter # smaller (bun --compile)
245
+ ```
246
+
247
+ **Or build it locally** from a checkout of this repo:
248
+
249
+ ```bash
250
+ docker build -t node-claudecode-linter . # default (Dockerfile)
251
+ docker build -f Dockerfile.compile -t bun-claudecode-linter . # smaller variant
252
+ ```
253
+
254
+ Both images behave identically. The `docker run` recipes below use `ghcr.io/retif/node-claudecode-linter`; substitute `ghcr.io/retif/bun-claudecode-linter` or a locally-built tag as you prefer.
255
+
256
+ ### Sandboxed invocation
257
+
258
+ **Docker — read-only lint:**
259
+
260
+ ```bash
261
+ docker run --rm --network none --read-only --tmpfs /tmp \
262
+ --user "$(id -u):$(id -g)" --cap-drop ALL --security-opt no-new-privileges \
263
+ -v "$PWD":/work:ro -w /work ghcr.io/retif/node-claudecode-linter /work
264
+ ```
265
+
266
+ **Docker — `--fix`:** the mount must be read-write so fixes can be written back. Otherwise identical, plus the `--fix` flag:
267
+
268
+ ```bash
269
+ docker run --rm --network none --read-only --tmpfs /tmp \
270
+ --user "$(id -u):$(id -g)" --cap-drop ALL --security-opt no-new-privileges \
271
+ -v "$PWD":/work -w /work ghcr.io/retif/node-claudecode-linter --fix /work
272
+ ```
273
+
274
+ All four recipes here are verified. On Linux without Docker, [bubblewrap](https://github.com/containers/bubblewrap) (`bwrap`) gives the equivalent boundary: `--unshare-all` cuts network (confirmed: `ECONNREFUSED` inside the sandbox), and nothing is writable except — for `--fix` — the target directory (confirmed: a write outside it is refused).
275
+
276
+ **bwrap — read-only (lint):**
277
+
278
+ ```bash
279
+ bwrap \
280
+ --ro-bind / / --dev /dev --proc /proc --tmpfs /tmp \
281
+ --unshare-all --die-with-parent \
282
+ --chdir "$PWD" \
283
+ claudecode-linter "$PWD"
284
+ ```
285
+
286
+ **bwrap — read-write (`--fix`):** the later `--bind` overrides the read-only root for just the target directory:
287
+
288
+ ```bash
289
+ bwrap \
290
+ --ro-bind / / --bind "$PWD" "$PWD" \
291
+ --dev /dev --proc /proc --tmpfs /tmp \
292
+ --unshare-all --die-with-parent \
293
+ --chdir "$PWD" \
294
+ claudecode-linter --fix "$PWD"
295
+ ```
296
+
297
+ `--ro-bind / /` can be replaced with explicit per-path `--ro-bind` entries (e.g. just `/usr`, `/nix`, and the target) for least-read-authority.
298
+
299
+ See [`SECURITY.md`](SECURITY.md) for the full security model, the audited input-handling hardening, and how to report a vulnerability.
300
+
214
301
  ## Versioning
215
302
 
216
303
  This linter's version tracks the Claude Code version it was extracted from:
@@ -1,18 +1,60 @@
1
1
  {
2
- "extractedFromClaudeCodeVersion": "2.1.146",
3
- "extractedAt": "2026-05-21T21:04:24.810Z",
2
+ "extractedFromClaudeCodeVersion": "2.1.150",
3
+ "extractedAt": "2026-05-23T18:30:08.382Z",
4
4
  "schema": {
5
5
  "$schema": "https://json-schema.org/draft/2020-12/schema",
6
6
  "title": "Claude Code agent .md frontmatter",
7
7
  "type": "object",
8
8
  "properties": {
9
9
  "name": {
10
+ "anyOf": [
11
+ {
12
+ "type": "string"
13
+ },
14
+ {
15
+ "type": "number"
16
+ },
17
+ {
18
+ "type": "boolean"
19
+ },
20
+ {
21
+ "type": "null"
22
+ }
23
+ ],
10
24
  "description": "Agent identifier. Required — this is how the Agent tool and `--agent` flag address it."
11
25
  },
12
26
  "description": {
27
+ "anyOf": [
28
+ {
29
+ "type": "string"
30
+ },
31
+ {
32
+ "type": "number"
33
+ },
34
+ {
35
+ "type": "boolean"
36
+ },
37
+ {
38
+ "type": "null"
39
+ }
40
+ ],
13
41
  "description": "When to use this agent. Required — shown in the Agent tool listing."
14
42
  },
15
43
  "model": {
44
+ "anyOf": [
45
+ {
46
+ "type": "string"
47
+ },
48
+ {
49
+ "type": "number"
50
+ },
51
+ {
52
+ "type": "boolean"
53
+ },
54
+ {
55
+ "type": "null"
56
+ }
57
+ ],
16
58
  "description": "Model override for this agent. Use `inherit` to match the spawning conversation."
17
59
  },
18
60
  "tools": {
@@ -70,12 +112,54 @@
70
112
  "description": "Tools removed from the default set. Ignored if `tools` is set."
71
113
  },
72
114
  "color": {
115
+ "anyOf": [
116
+ {
117
+ "type": "string"
118
+ },
119
+ {
120
+ "type": "number"
121
+ },
122
+ {
123
+ "type": "boolean"
124
+ },
125
+ {
126
+ "type": "null"
127
+ }
128
+ ],
73
129
  "description": "@internal — display color in the agents UI"
74
130
  },
75
131
  "effort": {
132
+ "anyOf": [
133
+ {
134
+ "type": "string"
135
+ },
136
+ {
137
+ "type": "number"
138
+ },
139
+ {
140
+ "type": "boolean"
141
+ },
142
+ {
143
+ "type": "null"
144
+ }
145
+ ],
76
146
  "description": "Thinking effort: `low`, `medium`, `high`, `max`, or an integer."
77
147
  },
78
148
  "permissionMode": {
149
+ "anyOf": [
150
+ {
151
+ "type": "string"
152
+ },
153
+ {
154
+ "type": "number"
155
+ },
156
+ {
157
+ "type": "boolean"
158
+ },
159
+ {
160
+ "type": "null"
161
+ }
162
+ ],
79
163
  "description": "Permission mode the agent runs in."
80
164
  },
81
165
  "mcpServers": {
@@ -126,15 +210,71 @@
126
210
  "description": "Skills preloaded for this agent."
127
211
  },
128
212
  "initialPrompt": {
213
+ "anyOf": [
214
+ {
215
+ "type": "string"
216
+ },
217
+ {
218
+ "type": "number"
219
+ },
220
+ {
221
+ "type": "boolean"
222
+ },
223
+ {
224
+ "type": "null"
225
+ }
226
+ ],
129
227
  "description": "Auto-submitted first message when this agent runs as the main session (via `--agent` or settings). Not read when spawned as a subagent."
130
228
  },
131
229
  "memory": {
230
+ "anyOf": [
231
+ {
232
+ "type": "string"
233
+ },
234
+ {
235
+ "type": "number"
236
+ },
237
+ {
238
+ "type": "boolean"
239
+ },
240
+ {
241
+ "type": "null"
242
+ }
243
+ ],
132
244
  "description": "Memory scope: `user`, `project`, or `local`."
133
245
  },
134
246
  "background": {
247
+ "anyOf": [
248
+ {
249
+ "type": "string"
250
+ },
251
+ {
252
+ "type": "number"
253
+ },
254
+ {
255
+ "type": "boolean"
256
+ },
257
+ {
258
+ "type": "null"
259
+ }
260
+ ],
135
261
  "description": "If true, the agent runs in the background by default."
136
262
  },
137
263
  "isolation": {
264
+ "anyOf": [
265
+ {
266
+ "type": "string"
267
+ },
268
+ {
269
+ "type": "number"
270
+ },
271
+ {
272
+ "type": "boolean"
273
+ },
274
+ {
275
+ "type": "null"
276
+ }
277
+ ],
138
278
  "description": "Filesystem isolation: `worktree` runs in a temporary git worktree."
139
279
  }
140
280
  },
@@ -1,18 +1,60 @@
1
1
  {
2
- "extractedFromClaudeCodeVersion": "2.1.146",
3
- "extractedAt": "2026-05-21T21:04:24.812Z",
2
+ "extractedFromClaudeCodeVersion": "2.1.150",
3
+ "extractedAt": "2026-05-23T18:30:08.384Z",
4
4
  "schema": {
5
5
  "$schema": "https://json-schema.org/draft/2020-12/schema",
6
6
  "title": "Claude Code command .md frontmatter",
7
7
  "type": "object",
8
8
  "properties": {
9
9
  "name": {
10
+ "anyOf": [
11
+ {
12
+ "type": "string"
13
+ },
14
+ {
15
+ "type": "number"
16
+ },
17
+ {
18
+ "type": "boolean"
19
+ },
20
+ {
21
+ "type": "null"
22
+ }
23
+ ],
10
24
  "description": "Display name. Defaults to the filename without extension."
11
25
  },
12
26
  "description": {
27
+ "anyOf": [
28
+ {
29
+ "type": "string"
30
+ },
31
+ {
32
+ "type": "number"
33
+ },
34
+ {
35
+ "type": "boolean"
36
+ },
37
+ {
38
+ "type": "null"
39
+ }
40
+ ],
13
41
  "description": "One-line summary shown in listings and the Skill tool."
14
42
  },
15
43
  "model": {
44
+ "anyOf": [
45
+ {
46
+ "type": "string"
47
+ },
48
+ {
49
+ "type": "number"
50
+ },
51
+ {
52
+ "type": "boolean"
53
+ },
54
+ {
55
+ "type": "null"
56
+ }
57
+ ],
16
58
  "description": "Model override (`haiku`, `sonnet`, `opus`, or a full ID). Use `inherit` to match the parent conversation."
17
59
  },
18
60
  "allowed-tools": {
@@ -43,6 +85,20 @@
43
85
  "description": "Tools available to the model while this file is active. Comma-separated string or YAML list."
44
86
  },
45
87
  "argument-hint": {
88
+ "anyOf": [
89
+ {
90
+ "type": "string"
91
+ },
92
+ {
93
+ "type": "number"
94
+ },
95
+ {
96
+ "type": "boolean"
97
+ },
98
+ {
99
+ "type": "null"
100
+ }
101
+ ],
46
102
  "description": "Placeholder text shown after the slash command name."
47
103
  },
48
104
  "arguments": {
@@ -73,18 +129,88 @@
73
129
  "description": "@internal — typed variant of argument-hint; argument-hint is the documented form"
74
130
  },
75
131
  "disable-model-invocation": {
132
+ "anyOf": [
133
+ {
134
+ "type": "string"
135
+ },
136
+ {
137
+ "type": "number"
138
+ },
139
+ {
140
+ "type": "boolean"
141
+ },
142
+ {
143
+ "type": "null"
144
+ }
145
+ ],
76
146
  "description": "If true, the model cannot invoke this via the Skill tool; only users can type the slash command."
77
147
  },
78
148
  "user-invocable": {
149
+ "anyOf": [
150
+ {
151
+ "type": "string"
152
+ },
153
+ {
154
+ "type": "number"
155
+ },
156
+ {
157
+ "type": "boolean"
158
+ },
159
+ {
160
+ "type": "null"
161
+ }
162
+ ],
79
163
  "description": "If false, hides the slash command from users; only the model can invoke it via the Skill tool."
80
164
  },
81
165
  "effort": {
166
+ "anyOf": [
167
+ {
168
+ "type": "string"
169
+ },
170
+ {
171
+ "type": "number"
172
+ },
173
+ {
174
+ "type": "boolean"
175
+ },
176
+ {
177
+ "type": "null"
178
+ }
179
+ ],
82
180
  "description": "Thinking effort for the model: `low`, `medium`, `high`, `max`, or an integer."
83
181
  },
84
182
  "shell": {
183
+ "anyOf": [
184
+ {
185
+ "type": "string"
186
+ },
187
+ {
188
+ "type": "number"
189
+ },
190
+ {
191
+ "type": "boolean"
192
+ },
193
+ {
194
+ "type": "null"
195
+ }
196
+ ],
85
197
  "description": "Shell for `!`-command blocks: `bash` or `powershell`. Defaults to bash regardless of platform."
86
198
  },
87
199
  "version": {
200
+ "anyOf": [
201
+ {
202
+ "type": "string"
203
+ },
204
+ {
205
+ "type": "number"
206
+ },
207
+ {
208
+ "type": "boolean"
209
+ },
210
+ {
211
+ "type": "null"
212
+ }
213
+ ],
88
214
  "description": "@internal — bookkeeping, not surfaced to users"
89
215
  }
90
216
  },