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.
- package/.claudecode-lint.defaults.yaml +27 -1
- package/README.md +24 -6
- package/contracts/agent-frontmatter.schema.json +287 -0
- package/contracts/command-frontmatter.schema.json +219 -0
- package/contracts/hooks.schema.json +275 -0
- package/contracts/mcp.schema.json +302 -0
- package/contracts/settings.schema.json +2495 -0
- package/contracts/skill-frontmatter.schema.json +378 -0
- package/dist/contracts.js +77 -1
- package/dist/discovery.js +26 -2
- package/dist/fixers/settings-json.js +7 -6
- package/dist/index.js +23 -7
- package/dist/linters/agent-md.js +71 -8
- package/dist/linters/claude-md.js +15 -3
- package/dist/linters/command-md.js +67 -5
- package/dist/linters/hooks-json.js +24 -0
- package/dist/linters/mcp-json.js +33 -2
- package/dist/linters/settings-json.js +316 -29
- package/dist/linters/skill-md.js +142 -11
- package/dist/plugin-schema.js +19 -0
- package/dist/utils/effort.js +29 -0
- package/dist/utils/frontmatter-keys.js +67 -0
- package/package.json +9 -3
|
@@ -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.
|
|
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`,
|
|
257
|
-
* plugin install cache's
|
|
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
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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("--
|
|
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(",")
|