claudecode-linter 2.1.144 → 2.1.148-patch.2

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.
@@ -0,0 +1,378 @@
1
+ {
2
+ "extractedFromClaudeCodeVersion": "2.1.146",
3
+ "extractedAt": "2026-05-22T08:49:59.762Z",
4
+ "schema": {
5
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
6
+ "title": "Claude Code SKILL.md frontmatter",
7
+ "type": "object",
8
+ "properties": {
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
+ ],
24
+ "description": "Display name. Defaults to the filename without extension."
25
+ },
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
+ ],
41
+ "description": "One-line summary shown in listings and the Skill tool."
42
+ },
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
+ ],
58
+ "description": "Model override (`haiku`, `sonnet`, `opus`, or a full ID). Use `inherit` to match the parent conversation."
59
+ },
60
+ "allowed-tools": {
61
+ "anyOf": [
62
+ {
63
+ "anyOf": [
64
+ {
65
+ "type": "string"
66
+ },
67
+ {
68
+ "type": "number"
69
+ },
70
+ {
71
+ "type": "boolean"
72
+ },
73
+ {
74
+ "type": "null"
75
+ }
76
+ ]
77
+ },
78
+ {
79
+ "type": "array",
80
+ "items": {
81
+ "type": "string"
82
+ }
83
+ }
84
+ ],
85
+ "description": "Tools available to the model while this file is active. Comma-separated string or YAML list."
86
+ },
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
+ ],
102
+ "description": "Placeholder text shown after the slash command name."
103
+ },
104
+ "arguments": {
105
+ "anyOf": [
106
+ {
107
+ "anyOf": [
108
+ {
109
+ "type": "string"
110
+ },
111
+ {
112
+ "type": "number"
113
+ },
114
+ {
115
+ "type": "boolean"
116
+ },
117
+ {
118
+ "type": "null"
119
+ }
120
+ ]
121
+ },
122
+ {
123
+ "type": "array",
124
+ "items": {
125
+ "type": "string"
126
+ }
127
+ }
128
+ ],
129
+ "description": "@internal — typed variant of argument-hint; argument-hint is the documented form"
130
+ },
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
+ ],
146
+ "description": "If true, the model cannot invoke this via the Skill tool; only users can type the slash command."
147
+ },
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
+ ],
163
+ "description": "If false, hides the slash command from users; only the model can invoke it via the Skill tool."
164
+ },
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
+ ],
180
+ "description": "Thinking effort for the model: `low`, `medium`, `high`, `max`, or an integer."
181
+ },
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
+ ],
197
+ "description": "Shell for `!`-command blocks: `bash` or `powershell`. Defaults to bash regardless of platform."
198
+ },
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
+ ],
214
+ "description": "@internal — bookkeeping, not surfaced to users"
215
+ },
216
+ "when_to_use": {
217
+ "anyOf": [
218
+ {
219
+ "type": "string"
220
+ },
221
+ {
222
+ "type": "number"
223
+ },
224
+ {
225
+ "type": "boolean"
226
+ },
227
+ {
228
+ "type": "null"
229
+ }
230
+ ],
231
+ "description": "Guidance for when the model should reach for this skill. Becomes part of the tool description."
232
+ },
233
+ "paths": {
234
+ "anyOf": [
235
+ {
236
+ "anyOf": [
237
+ {
238
+ "type": "string"
239
+ },
240
+ {
241
+ "type": "number"
242
+ },
243
+ {
244
+ "type": "boolean"
245
+ },
246
+ {
247
+ "type": "null"
248
+ }
249
+ ]
250
+ },
251
+ {
252
+ "type": "array",
253
+ "items": {
254
+ "type": "string"
255
+ }
256
+ }
257
+ ],
258
+ "description": "Glob patterns this skill applies to. The skill only loads when the model touches matching files."
259
+ },
260
+ "hooks": {
261
+ "description": "Hooks registered while this skill is active. Same shape as settings.json `hooks`."
262
+ },
263
+ "context": {
264
+ "enum": [
265
+ "inline",
266
+ "fork",
267
+ null
268
+ ],
269
+ "description": "Where the skill runs: `inline` expands into the current conversation; `fork` spawns a subagent."
270
+ },
271
+ "agent": {
272
+ "anyOf": [
273
+ {
274
+ "type": "string"
275
+ },
276
+ {
277
+ "type": "number"
278
+ },
279
+ {
280
+ "type": "boolean"
281
+ },
282
+ {
283
+ "type": "null"
284
+ }
285
+ ],
286
+ "description": "Agent type to spawn when `context: fork`."
287
+ },
288
+ "fallback": {
289
+ "anyOf": [
290
+ {
291
+ "type": "string"
292
+ },
293
+ {
294
+ "type": "number"
295
+ },
296
+ {
297
+ "type": "boolean"
298
+ },
299
+ {
300
+ "type": "null"
301
+ }
302
+ ],
303
+ "description": "@internal — interim defense-in-depth for thin-pointer skill stubs. If true, this skill yields to a same-suffix plugin or MCP skill (`<plugin>:<name>` / `<server>:<name>`) when one is loaded. Stubs carrying this should be deleted once their canonical plugin/MCP skill ships, not maintained."
304
+ },
305
+ "created_by": {
306
+ "anyOf": [
307
+ {
308
+ "type": "string"
309
+ },
310
+ {
311
+ "type": "number"
312
+ },
313
+ {
314
+ "type": "boolean"
315
+ },
316
+ {
317
+ "type": "null"
318
+ }
319
+ ],
320
+ "description": "@internal — provenance marker (e.g. dream-proposal)"
321
+ },
322
+ "improved_by": {
323
+ "anyOf": [
324
+ {
325
+ "type": "string"
326
+ },
327
+ {
328
+ "type": "number"
329
+ },
330
+ {
331
+ "type": "boolean"
332
+ },
333
+ {
334
+ "type": "null"
335
+ }
336
+ ],
337
+ "description": "@internal — provenance marker (e.g. dream-proposal)"
338
+ },
339
+ "mcpServers": {
340
+ "description": "@internal"
341
+ },
342
+ "lspServers": {
343
+ "description": "@internal"
344
+ },
345
+ "agents": {
346
+ "description": "@internal"
347
+ },
348
+ "outputStyles": {
349
+ "description": "@internal"
350
+ },
351
+ "themes": {
352
+ "description": "@internal"
353
+ },
354
+ "workflows": {
355
+ "description": "@internal"
356
+ },
357
+ "channels": {
358
+ "description": "@internal"
359
+ },
360
+ "monitors": {
361
+ "description": "@internal"
362
+ },
363
+ "settings": {
364
+ "description": "@internal"
365
+ },
366
+ "experimental": {
367
+ "description": "@internal"
368
+ },
369
+ "dependencies": {
370
+ "description": "@internal"
371
+ },
372
+ "metadata": {
373
+ "description": "@internal"
374
+ }
375
+ },
376
+ "description": "Claude Code SKILL.md YAML frontmatter. Validates the structure of known fields; the object is intentionally permissive — unknown frontmatter keys are not schema errors (Claude Code still loads the skill), they are reported by the advisory skill-md/no-unknown-frontmatter rule."
377
+ }
378
+ }
package/dist/contracts.js CHANGED
@@ -1,11 +1,14 @@
1
1
  // Auto-generated from contracts/claude-code-contracts.json
2
- // Claude Code v2.1.144 — extracted 2026-05-19T01:06:35.112Z
2
+ // Claude Code v2.1.148 — extracted 2026-05-22T07:13:45.210Z
3
3
  // Do not edit manually. Run: npm run generate-contracts
4
4
  export const TOOLS = new Set([
5
5
  "Agent",
6
6
  "AskUserQuestion",
7
7
  "Bash",
8
8
  "Config",
9
+ "CronCreate",
10
+ "CronDelete",
11
+ "CronList",
9
12
  "Edit",
10
13
  "EnterPlanMode",
11
14
  "EnterWorktree",
@@ -16,10 +19,13 @@ export const TOOLS = new Set([
16
19
  "LSP",
17
20
  "ListMcpResources",
18
21
  "Mcp",
22
+ "Monitor",
19
23
  "NotebookEdit",
20
24
  "NotebookRead",
25
+ "PushNotification",
21
26
  "Read",
22
27
  "ReadMcpResource",
28
+ "RemoteTrigger",
23
29
  "SendMessage",
24
30
  "Skill",
25
31
  "SubscribeMcpResource",
@@ -164,6 +170,7 @@ export const MCP_SERVER_FIELDS = new Set([
164
170
  "headersHelper",
165
171
  "oauth",
166
172
  "role",
173
+ "timeout",
167
174
  "type",
168
175
  "url",
169
176
  ]);
@@ -316,7 +323,63 @@ export const SETTINGS_USER_FIELDS = new Set([
316
323
  "wslInheritsWindowsSettings",
317
324
  ]);
318
325
  export const SETTINGS_PROJECT_FIELDS = new Set([
326
+ "hooks",
319
327
  "permissions",
328
+ "sandbox",
329
+ ]);
330
+ // Allowed sub-keys of the settings `permissions` object.
331
+ export const PERMISSIONS_FIELDS = new Set([
332
+ "additionalDirectories",
333
+ "allow",
334
+ "ask",
335
+ "defaultMode",
336
+ "deny",
337
+ "disableAutoMode",
338
+ "disableBypassPermissionsMode",
339
+ ]);
340
+ // Allowed sub-keys of the settings `sandbox` object.
341
+ export const SANDBOX_FIELDS = new Set([
342
+ "allowUnsandboxedCommands",
343
+ "autoAllowBashIfSandboxed",
344
+ "bwrapPath",
345
+ "enableWeakerNestedSandbox",
346
+ "enableWeakerNetworkIsolation",
347
+ "enabled",
348
+ "excludedCommands",
349
+ "failIfUnavailable",
350
+ "filesystem",
351
+ "ignoreViolations",
352
+ "network",
353
+ "ripgrep",
354
+ ]);
355
+ // Allowed sub-keys of `sandbox.network` and `sandbox.filesystem`.
356
+ export const SANDBOX_NETWORK_FIELDS = new Set([
357
+ "allowAllUnixSockets",
358
+ "allowLocalBinding",
359
+ "allowMachLookup",
360
+ "allowManagedDomainsOnly",
361
+ "allowUnixSockets",
362
+ "allowedDomains",
363
+ "deniedDomains",
364
+ "httpProxyPort",
365
+ "socksProxyPort",
366
+ "tlsTerminate",
367
+ ]);
368
+ export const SANDBOX_FILESYSTEM_FIELDS = new Set([
369
+ "allowManagedReadPathsOnly",
370
+ "allowRead",
371
+ "allowWrite",
372
+ "denyRead",
373
+ "denyWrite",
374
+ ]);
375
+ // Valid values for `permissions.defaultMode`.
376
+ export const PERMISSION_MODES = new Set([
377
+ "acceptEdits",
378
+ "auto",
379
+ "bypassPermissions",
380
+ "default",
381
+ "dontAsk",
382
+ "plan",
320
383
  ]);
321
384
  // Hand-curated denylist of tools declared in agent frontmatter that never
322
385
  // reach plugin-defined subagents at runtime. Source: tracked upstream bugs
@@ -326,4 +389,17 @@ export const PLUGIN_SUBAGENT_BLOCKED_TOOLS = new Set([
326
389
  "Glob",
327
390
  "Grep",
328
391
  ]);
392
+ // Hand-curated named values for the frontmatter `effort` field. The Zod
393
+ // schema types `effort` as a permissive scalar; the field's own describe()
394
+ // string in the Claude Code bundle reads: "Thinking effort for the model:
395
+ // `low`, `medium`, `high`, `max`, or an integer." — so a string `effort`
396
+ // must be one of these, and a numeric `effort` must be an integer. (The
397
+ // runtime effortLevel enum also has `xhigh`, but the frontmatter describe
398
+ // string deliberately omits it; we follow the frontmatter contract.)
399
+ export const EFFORT_LEVELS = new Set([
400
+ "low",
401
+ "medium",
402
+ "high",
403
+ "max",
404
+ ]);
329
405
  //# sourceMappingURL=contracts.js.map
package/dist/discovery.js CHANGED
@@ -79,6 +79,25 @@ export function discoverArtifacts(targetPath, options) {
79
79
  }
80
80
  return artifacts;
81
81
  }
82
+ /**
83
+ * Detect which Claude Code artifact types are present under the given paths.
84
+ * Returns the distinct types, sorted; excludes the `misplaced-file` diagnostic
85
+ * category (it marks a misplacement, not a kind of artifact a project owns).
86
+ * An empty result means the path holds no recognizable Claude Code artifacts.
87
+ *
88
+ * Powers the `--detect` CLI mode: a generic git hook can call it to decide
89
+ * whether a repository is a Claude Code plugin / config tree before linting.
90
+ */
91
+ export function detectArtifactTypes(paths, ignore = []) {
92
+ const found = new Set();
93
+ for (const targetPath of paths) {
94
+ for (const a of discoverArtifacts(targetPath, { ignore })) {
95
+ if (a.artifactType !== "misplaced-file")
96
+ found.add(a.artifactType);
97
+ }
98
+ }
99
+ return [...found].sort();
100
+ }
82
101
  function detectScope(filePath) {
83
102
  const resolved = resolve(filePath);
84
103
  // Inside ~/.claude/ itself (not a subdirectory project)
@@ -253,8 +272,9 @@ function discoverInDirectory(dir) {
253
272
  * sitting at a non-canonical location is returned as a
254
273
  * `misplaced-file` artifact for the misplaced-file linter to flag.
255
274
  *
256
- * Ignores typical noise dirs (`node_modules`, `.git`, `dist`, the
257
- * plugin install cache's `.in_use` / `.orphaned_at` markers).
275
+ * Ignores typical noise dirs (`node_modules`, `.git`, `dist`,
276
+ * `.claude/worktrees/` worktree copies, the plugin install cache's
277
+ * `.in_use` / `.orphaned_at` markers).
258
278
  */
259
279
  function findMisplacedFiles(pluginRoot) {
260
280
  const out = [];
@@ -270,6 +290,10 @@ function findMisplacedFiles(pluginRoot) {
270
290
  ignore: [
271
291
  "**/node_modules/**",
272
292
  "**/.git/**",
293
+ // `.claude/worktrees/` holds transient git-worktree copies
294
+ // (Claude Code's own EnterWorktree). Linting them re-reports
295
+ // every artifact once per worktree — pure noise.
296
+ "**/.claude/worktrees/**",
273
297
  "**/dist/**",
274
298
  "**/.in_use/**",
275
299
  "**/.orphaned_at/**",
@@ -1,6 +1,8 @@
1
1
  import { formatJson } from "../utils/prettier.js";
2
2
  const TOP_LEVEL_KEY_ORDER = [
3
3
  "permissions",
4
+ "sandbox",
5
+ "hooks",
4
6
  "env",
5
7
  "plugins",
6
8
  "skipDangerousModePermissionPrompt",
@@ -30,15 +32,14 @@ export const settingsJsonFixer = {
30
32
  ordered[key] = parsed[key];
31
33
  }
32
34
  }
33
- // Sort permissions.allow and permissions.deny alphabetically
35
+ // Sort the permissions.allow / deny / ask rule arrays alphabetically
34
36
  const permissions = ordered["permissions"];
35
37
  if (typeof permissions === "object" && permissions !== null && !Array.isArray(permissions)) {
36
38
  const perms = permissions;
37
- if (Array.isArray(perms["allow"])) {
38
- perms["allow"] = [...perms["allow"]].sort();
39
- }
40
- if (Array.isArray(perms["deny"])) {
41
- perms["deny"] = [...perms["deny"]].sort();
39
+ for (const list of ["allow", "deny", "ask"]) {
40
+ if (Array.isArray(perms[list])) {
41
+ perms[list] = [...perms[list]].sort();
42
+ }
42
43
  }
43
44
  }
44
45
  return formatJson(JSON.stringify(ordered));
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url";
5
5
  import sade from "sade";
6
6
  import pc from "picocolors";
7
7
  import { loadConfig, mergeCliRules } from "./config.js";
8
- import { discoverArtifacts } from "./discovery.js";
8
+ import { discoverArtifacts, detectArtifactTypes } from "./discovery.js";
9
9
  import { formatHuman } from "./formatters/human.js";
10
10
  import { formatJson } from "./formatters/json.js";
11
11
  import { pluginJsonLinter } from "./linters/plugin-json.js";
@@ -110,19 +110,20 @@ const pkgVersion = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.met
110
110
  sade("claudecode-linter", true)
111
111
  .version(pkgVersion)
112
112
  .describe("Linter for Claude Code plugin artifacts")
113
- .option("--lint", "Lint artifacts and report issues (default)")
114
- .option("--fix", "Auto-fix lint violations, then report remaining issues")
115
- .option("--format", "Format all artifacts for consistent style (no lint output)")
113
+ .option("--lint", "Lint artifacts and report issues (default)", false)
114
+ .option("--fix", "Auto-fix lint violations, then report remaining issues", false)
115
+ .option("--format", "Format all artifacts for consistent style (no lint output)", false)
116
116
  .option("--output", "Output format: human | json", "human")
117
117
  .option("--config", "Config file path")
118
118
  .option("--scope", "Filter by scope: user | project | subdirectory")
119
119
  .option("--ignore", "Comma-separated glob patterns to ignore")
120
- .option("--quiet", "Only show errors")
120
+ .option("--quiet", "Only show errors", false)
121
121
  .option("--enable", "Comma-separated rule IDs to enable")
122
122
  .option("--disable", "Comma-separated rule IDs to disable")
123
123
  .option("--rule", "Run only this single rule ID")
124
- .option("--list-rules", "Print all rules with their default severity and exit")
125
- .option("--fix-dry-run", "Run fixers but print diff instead of writing")
124
+ .option("--list-rules", "Print all rules with their default severity and exit", false)
125
+ .option("--detect", "Print detected Claude Code artifact type(s), one per line; exit 0 if any, 1 if none", false)
126
+ .option("--fix-dry-run", "Run fixers but print diff instead of writing", false)
126
127
  .option("--init", "Copy default config to path (default: current directory)")
127
128
  .action(async (opts) => {
128
129
  // sade accepts variadic positional via opts._; default to ["."] when empty.
@@ -153,6 +154,21 @@ sade("claudecode-linter", true)
153
154
  }
154
155
  process.exit(0);
155
156
  }
157
+ if (opts.detect) {
158
+ const ignoreD = opts.ignore
159
+ ?.split(",")
160
+ .map((p) => p.trim())
161
+ .filter(Boolean) ?? [];
162
+ const types = detectArtifactTypes(paths, ignoreD);
163
+ if (opts.output === "json") {
164
+ process.stdout.write(`${JSON.stringify(types)}\n`);
165
+ }
166
+ else {
167
+ for (const t of types)
168
+ process.stdout.write(`${t}\n`);
169
+ }
170
+ process.exit(types.length > 0 ? 0 : 1);
171
+ }
156
172
  const enableList = opts.enable
157
173
  ? opts.enable
158
174
  .split(",")