claudecode-linter 2.1.149 → 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.
- package/.claudecode-lint.defaults.yaml +8 -2
- package/contracts/agent-frontmatter.schema.json +2 -2
- package/contracts/command-frontmatter.schema.json +2 -2
- package/contracts/hooks.schema.json +2 -2
- package/contracts/lsp.schema.json +2 -2
- package/contracts/mcp.schema.json +26 -2
- package/contracts/monitors.schema.json +3 -3
- package/contracts/plugin.schema.json +672 -22
- package/contracts/schemastore/keybindings.schema.json +179 -0
- package/contracts/schemastore/manifest.json +25 -0
- package/contracts/schemastore/marketplace.schema.json +2067 -0
- package/contracts/schemastore/plugin-manifest.schema.json +1834 -0
- package/contracts/schemastore/settings.schema.json +3062 -0
- package/contracts/settings.schema.json +49 -3
- package/contracts/skill-frontmatter.schema.json +2 -2
- package/dist/contracts.js +1 -1
- package/dist/discovery.js +23 -0
- package/dist/index.js +8 -0
- package/dist/linters/agent-md.js +16 -7
- package/dist/linters/keybindings-json.js +53 -0
- package/dist/linters/marketplace-json.js +55 -0
- package/dist/linters/settings-json.js +42 -15
- package/dist/linters/skill-md.js +7 -1
- package/dist/plugin-schema.js +89 -41
- package/package.json +4 -2
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extractedFromClaudeCodeVersion": "2.1.
|
|
3
|
-
"extractedAt": "2026-05-
|
|
2
|
+
"extractedFromClaudeCodeVersion": "2.1.150",
|
|
3
|
+
"extractedAt": "2026-05-23T18:30:08.375Z",
|
|
4
4
|
"schema": {
|
|
5
5
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
6
6
|
"title": "Claude Code settings.json",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {
|
|
9
9
|
"$schema": {
|
|
10
|
-
"const": "
|
|
10
|
+
"const": "https://json.schemastore.org/claude-code-settings.json",
|
|
11
11
|
"description": "JSON Schema reference for Claude Code settings"
|
|
12
12
|
},
|
|
13
13
|
"apiKeyHelper": {
|
|
@@ -78,6 +78,48 @@
|
|
|
78
78
|
"type": "boolean",
|
|
79
79
|
"description": "Whether file picker should respect .gitignore files (default: true). Note: .ignore files are always respected."
|
|
80
80
|
},
|
|
81
|
+
"breakReminder": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"properties": {
|
|
84
|
+
"enabled": {
|
|
85
|
+
"type": "boolean",
|
|
86
|
+
"description": "Show a friendly nudge after sustained continuous use (default false). Must be true for the reminder to fire."
|
|
87
|
+
},
|
|
88
|
+
"intervalMinutes": {
|
|
89
|
+
"type": "number",
|
|
90
|
+
"description": "Minutes of continuous use before the reminder fires (default 120). Re-fires every interval until you take a break."
|
|
91
|
+
},
|
|
92
|
+
"breakThresholdMinutes": {
|
|
93
|
+
"type": "number",
|
|
94
|
+
"description": "Minutes of inactivity that count as a break and reset the timer (default 15)"
|
|
95
|
+
},
|
|
96
|
+
"message": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"description": "Custom reminder text. Leave unset for a rotating set of friendly nudges."
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"description": "@internal Opt-in break reminder. When enabled, shows a dismissible nudge after sustained continuous use. Never blocks — just a friendly heads-up."
|
|
102
|
+
},
|
|
103
|
+
"quietHours": {
|
|
104
|
+
"type": "object",
|
|
105
|
+
"properties": {
|
|
106
|
+
"enabled": {
|
|
107
|
+
"type": "boolean",
|
|
108
|
+
"description": "Show a one-time nudge when you start or keep using the CLI inside your quiet-hours window (default false)."
|
|
109
|
+
},
|
|
110
|
+
"start": {
|
|
111
|
+
"type": "string",
|
|
112
|
+
"pattern": "^([01]?\\d|2[0-3]):[0-5]\\d$",
|
|
113
|
+
"description": "Start of the quiet-hours window, 24-hour local time \"HH:MM\"."
|
|
114
|
+
},
|
|
115
|
+
"end": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"pattern": "^([01]?\\d|2[0-3]):[0-5]\\d$",
|
|
118
|
+
"description": "End of the quiet-hours window, 24-hour local time \"HH:MM\". May be earlier than start for an overnight range."
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"description": "@internal Opt-in quiet hours. When enabled, shows a single soft nudge per session while inside the configured local-time window. Never blocks."
|
|
122
|
+
},
|
|
81
123
|
"cleanupPeriodDays": {
|
|
82
124
|
"type": "number",
|
|
83
125
|
"description": "Number of days to retain chat transcripts before automatic cleanup (default: 30). Minimum 1. Use a large value for long retention; use --no-session-persistence to disable transcript writes entirely."
|
|
@@ -568,6 +610,10 @@
|
|
|
568
610
|
"type": "boolean",
|
|
569
611
|
"description": "When true (and set in managed settings), allowedMcpServers is only read from managed settings. deniedMcpServers still merges from all sources, so users can deny servers for themselves. Users can still add their own MCP servers, but only the admin-defined allowlist applies."
|
|
570
612
|
},
|
|
613
|
+
"allowAllClaudeAiMcps": {
|
|
614
|
+
"type": "boolean",
|
|
615
|
+
"description": "When true (and set in managed settings), claude.ai cloud MCP connectors load alongside managed-mcp.json instead of being suppressed by its exclusive-control lockdown. Default off preserves the lockdown. Read from managed settings only."
|
|
616
|
+
},
|
|
571
617
|
"strictPluginOnlyCustomization": {
|
|
572
618
|
"anyOf": [
|
|
573
619
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extractedFromClaudeCodeVersion": "2.1.
|
|
3
|
-
"extractedAt": "2026-05-
|
|
2
|
+
"extractedFromClaudeCodeVersion": "2.1.150",
|
|
3
|
+
"extractedAt": "2026-05-23T18:30:08.380Z",
|
|
4
4
|
"schema": {
|
|
5
5
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
6
6
|
"title": "Claude Code SKILL.md frontmatter",
|
package/dist/contracts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Auto-generated from contracts/claude-code-contracts.json
|
|
2
|
-
// Claude Code v2.1.
|
|
2
|
+
// Claude Code v2.1.150 — extracted 2026-05-23T07:05:23.158Z
|
|
3
3
|
// Do not edit manually. Run: npm run generate-contracts
|
|
4
4
|
export const TOOLS = new Set([
|
|
5
5
|
"Agent",
|
package/dist/discovery.js
CHANGED
|
@@ -205,6 +205,25 @@ function discoverInDirectory(dir) {
|
|
|
205
205
|
for (const f of monitors) {
|
|
206
206
|
artifacts.push({ filePath: f, artifactType: "monitors-json" });
|
|
207
207
|
}
|
|
208
|
+
// .claude-plugin/marketplace.json — schemastore-only artifact.
|
|
209
|
+
const marketplace = join(dir, ".claude-plugin", "marketplace.json");
|
|
210
|
+
if (existsSync(marketplace)) {
|
|
211
|
+
artifacts.push({ filePath: marketplace, artifactType: "marketplace-json" });
|
|
212
|
+
}
|
|
213
|
+
// keybindings.json — usually at ~/.claude/keybindings.json (user scope),
|
|
214
|
+
// but also picked up at project root if present. schemastore-only artifact.
|
|
215
|
+
for (const candidate of [
|
|
216
|
+
join(dir, "keybindings.json"),
|
|
217
|
+
join(dir, ".claude", "keybindings.json"),
|
|
218
|
+
]) {
|
|
219
|
+
if (existsSync(candidate)) {
|
|
220
|
+
artifacts.push({
|
|
221
|
+
filePath: candidate,
|
|
222
|
+
artifactType: "keybindings-json",
|
|
223
|
+
scope: detectScope(candidate),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
208
227
|
// Claude config files — settings
|
|
209
228
|
for (const name of ["settings.json", "settings.local.json"]) {
|
|
210
229
|
// Direct in dir (handles both ~/.claude/settings.json and project root)
|
|
@@ -316,6 +335,10 @@ function classifyFile(filePath) {
|
|
|
316
335
|
const parent = basename(dirname(filePath));
|
|
317
336
|
if (name === "plugin.json" && parent === ".claude-plugin")
|
|
318
337
|
return "plugin-json";
|
|
338
|
+
if (name === "marketplace.json" && parent === ".claude-plugin")
|
|
339
|
+
return "marketplace-json";
|
|
340
|
+
if (name === "keybindings.json")
|
|
341
|
+
return "keybindings-json";
|
|
319
342
|
if (name === "SKILL.md")
|
|
320
343
|
return "skill-md";
|
|
321
344
|
if (name === "hooks.json" && parent === "hooks")
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,8 @@ import { mcpJsonLinter } from "./linters/mcp-json.js";
|
|
|
21
21
|
import { claudeMdLinter } from "./linters/claude-md.js";
|
|
22
22
|
import { lspJsonLinter } from "./linters/lsp-json.js";
|
|
23
23
|
import { monitorsJsonLinter } from "./linters/monitors-json.js";
|
|
24
|
+
import { marketplaceJsonLinter } from "./linters/marketplace-json.js";
|
|
25
|
+
import { keybindingsJsonLinter } from "./linters/keybindings-json.js";
|
|
24
26
|
import { misplacedFileLinter, MISPLACED_FILE_RULES, } from "./linters/misplaced-file.js";
|
|
25
27
|
import { pluginJsonFixer } from "./fixers/plugin-json.js";
|
|
26
28
|
import { frontmatterFixer } from "./fixers/frontmatter.js";
|
|
@@ -38,6 +40,8 @@ import { MCP_JSON_RULES } from "./linters/mcp-json.js";
|
|
|
38
40
|
import { CLAUDE_MD_RULES } from "./linters/claude-md.js";
|
|
39
41
|
import { LSP_JSON_RULES } from "./linters/lsp-json.js";
|
|
40
42
|
import { MONITORS_JSON_RULES } from "./linters/monitors-json.js";
|
|
43
|
+
import { MARKETPLACE_JSON_RULES } from "./linters/marketplace-json.js";
|
|
44
|
+
import { KEYBINDINGS_JSON_RULES } from "./linters/keybindings-json.js";
|
|
41
45
|
const LINTERS = {
|
|
42
46
|
"plugin-json": pluginJsonLinter,
|
|
43
47
|
"skill-md": skillMdLinter,
|
|
@@ -49,6 +53,8 @@ const LINTERS = {
|
|
|
49
53
|
"claude-md": claudeMdLinter,
|
|
50
54
|
"lsp-json": lspJsonLinter,
|
|
51
55
|
"monitors-json": monitorsJsonLinter,
|
|
56
|
+
"marketplace-json": marketplaceJsonLinter,
|
|
57
|
+
"keybindings-json": keybindingsJsonLinter,
|
|
52
58
|
"misplaced-file": misplacedFileLinter,
|
|
53
59
|
};
|
|
54
60
|
const FIXERS = {
|
|
@@ -72,6 +78,8 @@ const ALL_RULES = [
|
|
|
72
78
|
...CLAUDE_MD_RULES,
|
|
73
79
|
...LSP_JSON_RULES,
|
|
74
80
|
...MONITORS_JSON_RULES,
|
|
81
|
+
...MARKETPLACE_JSON_RULES,
|
|
82
|
+
...KEYBINDINGS_JSON_RULES,
|
|
75
83
|
...MISPLACED_FILE_RULES,
|
|
76
84
|
];
|
|
77
85
|
/**
|
package/dist/linters/agent-md.js
CHANGED
|
@@ -69,7 +69,9 @@ const RULES = [
|
|
|
69
69
|
{ id: "agent-md/permission-mode-valid", defaultSeverity: "warning" },
|
|
70
70
|
{ id: "agent-md/effort-valid", defaultSeverity: "warning" },
|
|
71
71
|
{ id: "agent-md/max-turns-valid", defaultSeverity: "warning" },
|
|
72
|
-
|
|
72
|
+
// agent-md/color-required removed in gitea#3 — Claude Code marks `color`
|
|
73
|
+
// as `.optional()` and `@internal`. The remaining `color-valid` rule still
|
|
74
|
+
// enforces the value set when an author opts in.
|
|
73
75
|
{ id: "agent-md/color-valid", defaultSeverity: "warning" },
|
|
74
76
|
{ id: "agent-md/system-prompt-present", defaultSeverity: "error" },
|
|
75
77
|
{ id: "agent-md/system-prompt-length", defaultSeverity: "warning" },
|
|
@@ -186,12 +188,19 @@ export const agentMdLinter = {
|
|
|
186
188
|
push(diag(config, filePath, "agent-md/max-turns-valid", "warning", `"maxTurns" must be a positive integer (got ${JSON.stringify(mt)})`));
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
|
-
// color
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
// color — optional per Claude Code's agent frontmatter Zod schema
|
|
192
|
+
// (`color: hW().optional().describe("@internal — display color in the
|
|
193
|
+
// agents UI")`). Only validate the value if the user set one; never
|
|
194
|
+
// require it. The `color-required` rule (gitea#3) is kept on disk for
|
|
195
|
+
// users who explicitly opted in via .claudecode-lint.yaml, but defaults
|
|
196
|
+
// to off because requiring an @internal display field misled users.
|
|
197
|
+
if ("color" in fm.data) {
|
|
198
|
+
if (typeof fm.data.color !== "string") {
|
|
199
|
+
push(diag(config, filePath, "agent-md/color-valid", "warning", '"color" must be a string when set'));
|
|
200
|
+
}
|
|
201
|
+
else if (!AGENT_COLORS.has(fm.data.color)) {
|
|
202
|
+
push(diag(config, filePath, "agent-md/color-valid", "warning", `"color" must be one of: ${[...AGENT_COLORS].join(", ")} (got "${fm.data.color}")`));
|
|
203
|
+
}
|
|
195
204
|
}
|
|
196
205
|
// Frontmatter keys: only flag cross-artifact misplacement. A key valid
|
|
197
206
|
// for a *different* markdown artifact (e.g. a skill-only key on an
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { formatAjvError, loadKeybindingsSchema, summarizeErrors, } from "../plugin-schema.js";
|
|
2
|
+
import { isRuleEnabled, getRuleSeverity } from "../types.js";
|
|
3
|
+
export const KEYBINDINGS_JSON_RULES = [
|
|
4
|
+
{ id: "keybindings-json/valid-json", defaultSeverity: "error" },
|
|
5
|
+
{ id: "keybindings-json/schema-valid", defaultSeverity: "error" },
|
|
6
|
+
];
|
|
7
|
+
/**
|
|
8
|
+
* Validate `~/.claude/keybindings.json` (or per-project `keybindings.json`)
|
|
9
|
+
* against the schemastore.org curated schema. As with marketplace.json,
|
|
10
|
+
* there is no Zod source for keybindings in the Claude Code bundle —
|
|
11
|
+
* schemastore is the sole authoritative shape we have.
|
|
12
|
+
*/
|
|
13
|
+
export const keybindingsJsonLinter = {
|
|
14
|
+
artifactType: "keybindings-json",
|
|
15
|
+
lint(filePath, content, config) {
|
|
16
|
+
const diagnostics = [];
|
|
17
|
+
const push = (d) => {
|
|
18
|
+
if (d)
|
|
19
|
+
diagnostics.push(d);
|
|
20
|
+
};
|
|
21
|
+
let parsed;
|
|
22
|
+
try {
|
|
23
|
+
parsed = JSON.parse(content);
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
push(diag(config, filePath, "keybindings-json/valid-json", "error", `Invalid JSON: ${e.message}`));
|
|
27
|
+
return diagnostics;
|
|
28
|
+
}
|
|
29
|
+
if (isRuleEnabled(config, "keybindings-json/schema-valid")) {
|
|
30
|
+
const compiled = loadKeybindingsSchema();
|
|
31
|
+
if (compiled) {
|
|
32
|
+
const ok = compiled.validate(parsed);
|
|
33
|
+
if (!ok && compiled.validate.errors) {
|
|
34
|
+
for (const err of summarizeErrors(compiled.validate.errors)) {
|
|
35
|
+
push(diag(config, filePath, "keybindings-json/schema-valid", "error", formatAjvError(err)));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return diagnostics;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
function diag(config, filePath, ruleId, defaultSeverity, message) {
|
|
44
|
+
if (!isRuleEnabled(config, ruleId))
|
|
45
|
+
return null;
|
|
46
|
+
return {
|
|
47
|
+
rule: ruleId,
|
|
48
|
+
severity: getRuleSeverity(config, ruleId, defaultSeverity),
|
|
49
|
+
message,
|
|
50
|
+
file: filePath,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=keybindings-json.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { formatAjvError, loadMarketplaceSchema, summarizeErrors, } from "../plugin-schema.js";
|
|
2
|
+
import { isRuleEnabled, getRuleSeverity } from "../types.js";
|
|
3
|
+
export const MARKETPLACE_JSON_RULES = [
|
|
4
|
+
{ id: "marketplace-json/valid-json", defaultSeverity: "error" },
|
|
5
|
+
{ id: "marketplace-json/schema-valid", defaultSeverity: "error" },
|
|
6
|
+
];
|
|
7
|
+
/**
|
|
8
|
+
* Validate `.claude-plugin/marketplace.json` against the schemastore.org
|
|
9
|
+
* curated schema. There's no Zod source for marketplace.json in the Claude
|
|
10
|
+
* Code bundle — schemastore is the sole authoritative shape we have.
|
|
11
|
+
*
|
|
12
|
+
* If the schemastore bundle isn't shipped with this install (unlikely;
|
|
13
|
+
* package.json includes it), the schema check silently skips.
|
|
14
|
+
*/
|
|
15
|
+
export const marketplaceJsonLinter = {
|
|
16
|
+
artifactType: "marketplace-json",
|
|
17
|
+
lint(filePath, content, config) {
|
|
18
|
+
const diagnostics = [];
|
|
19
|
+
const push = (d) => {
|
|
20
|
+
if (d)
|
|
21
|
+
diagnostics.push(d);
|
|
22
|
+
};
|
|
23
|
+
let parsed;
|
|
24
|
+
try {
|
|
25
|
+
parsed = JSON.parse(content);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
push(diag(config, filePath, "marketplace-json/valid-json", "error", `Invalid JSON: ${e.message}`));
|
|
29
|
+
return diagnostics;
|
|
30
|
+
}
|
|
31
|
+
if (isRuleEnabled(config, "marketplace-json/schema-valid")) {
|
|
32
|
+
const compiled = loadMarketplaceSchema();
|
|
33
|
+
if (compiled) {
|
|
34
|
+
const ok = compiled.validate(parsed);
|
|
35
|
+
if (!ok && compiled.validate.errors) {
|
|
36
|
+
for (const err of summarizeErrors(compiled.validate.errors)) {
|
|
37
|
+
push(diag(config, filePath, "marketplace-json/schema-valid", "error", formatAjvError(err)));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return diagnostics;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
function diag(config, filePath, ruleId, defaultSeverity, message) {
|
|
46
|
+
if (!isRuleEnabled(config, ruleId))
|
|
47
|
+
return null;
|
|
48
|
+
return {
|
|
49
|
+
rule: ruleId,
|
|
50
|
+
severity: getRuleSeverity(config, ruleId, defaultSeverity),
|
|
51
|
+
message,
|
|
52
|
+
file: filePath,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=marketplace-json.js.map
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { basename } from "node:path";
|
|
2
|
-
import { SETTINGS_USER_FIELDS,
|
|
2
|
+
import { SETTINGS_USER_FIELDS, TOOLS, PERMISSIONS_FIELDS, PERMISSION_MODES, SANDBOX_FIELDS, SANDBOX_NETWORK_FIELDS, SANDBOX_FILESYSTEM_FIELDS, } from "../contracts.js";
|
|
3
3
|
import { formatAjvError, loadSettingsSchema, summarizeErrors, } from "../plugin-schema.js";
|
|
4
4
|
import { isRuleEnabled, getRuleSeverity } from "../types.js";
|
|
5
5
|
const RULES = [
|
|
6
6
|
{ id: "settings-json/valid-json", defaultSeverity: "error" },
|
|
7
7
|
{ id: "settings-json/schema-valid", defaultSeverity: "error" },
|
|
8
|
-
|
|
8
|
+
// settings-json/scope-file-name removed in gitea#4 — .claude/settings.json
|
|
9
|
+
// is a valid project-shared settings source. See misplaced-file/* for
|
|
10
|
+
// wrong-path warnings.
|
|
9
11
|
{ id: "settings-json/scope-field", defaultSeverity: "warning" },
|
|
10
12
|
{ id: "settings-json/no-unknown-fields", defaultSeverity: "warning" },
|
|
11
13
|
{ id: "settings-json/permissions-object", defaultSeverity: "error" },
|
|
@@ -242,22 +244,47 @@ export const settingsJsonLinter = {
|
|
|
242
244
|
}
|
|
243
245
|
}
|
|
244
246
|
}
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
//
|
|
247
|
+
// gitea#4: `.claude/settings.json` is a first-class project settings source
|
|
248
|
+
// (`projectSettings`, committed/shared) distinct from `.claude/settings.local.json`
|
|
249
|
+
// (`localSettings`, gitignored). Claude Code's bundle defines both:
|
|
250
|
+
// `case "projectSettings": return Rk.join(".claude","settings.json")`
|
|
251
|
+
// `case "localSettings": return "project, gitignored"`
|
|
252
|
+
// The rule no longer fires for plain project-level settings.json; the
|
|
253
|
+
// `misplaced-file/canonical-location` rule covers files at the wrong path.
|
|
254
|
+
// Determine which fields are valid for this scope. gitea#2: the legacy
|
|
255
|
+
// SETTINGS_PROJECT_FIELDS whitelist is too narrow — Claude Code accepts
|
|
256
|
+
// most user-level fields at project scope too. We now treat all known
|
|
257
|
+
// user fields as valid at project scope, except a small denylist of
|
|
258
|
+
// genuinely user-only fields (auth helpers, plugin enablement, dangerous
|
|
259
|
+
// mode), which still warn via `settings-json/scope-field`.
|
|
260
|
+
const knownFields = SETTINGS_USER_FIELDS;
|
|
261
|
+
// Fields that genuinely only take effect at user scope. Project-scope
|
|
262
|
+
// versions are silently ignored by Claude Code. Source: bundle inspection
|
|
263
|
+
// (auth helpers run from user creds; enabledPlugins / skip-permission
|
|
264
|
+
// flags can't be enabled by a checked-in repo for security reasons).
|
|
265
|
+
const USER_ONLY_FIELDS = new Set([
|
|
266
|
+
"apiKeyHelper",
|
|
267
|
+
"awsAuthRefresh",
|
|
268
|
+
"awsCredentialExport",
|
|
269
|
+
"gcpAuthRefresh",
|
|
270
|
+
"proxyAuthHelper",
|
|
271
|
+
"policyHelper",
|
|
272
|
+
"enabledPlugins",
|
|
273
|
+
"skipDangerousModePermissionPrompt",
|
|
274
|
+
"forceLoginMethod",
|
|
275
|
+
"forceLoginOrgUUID",
|
|
276
|
+
]);
|
|
277
|
+
// unknown / misplaced top-level fields
|
|
252
278
|
for (const key of Object.keys(parsed)) {
|
|
253
279
|
if (!knownFields.has(key)) {
|
|
254
280
|
const p = findKeyPosition(content, key);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
281
|
+
push(diag(config, filePath, "settings-json/no-unknown-fields", "warning", `Unknown top-level field "${key}"`, p?.line, p?.column));
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
// gitea#2: only the genuinely user-only denylist warns at project scope.
|
|
285
|
+
if (USER_ONLY_FIELDS.has(key) && scope && scope !== "user") {
|
|
286
|
+
const p = findKeyPosition(content, key);
|
|
287
|
+
push(diag(config, filePath, "settings-json/scope-field", "warning", `"${key}" only takes effect at user level (~/.claude/settings.json); it is ignored in project settings`, p?.line, p?.column));
|
|
261
288
|
}
|
|
262
289
|
}
|
|
263
290
|
// Validate the sub-keys of a nested object against a known-field set and a
|
package/dist/linters/skill-md.js
CHANGED
|
@@ -52,7 +52,13 @@ function hasTriggerSignal(desc) {
|
|
|
52
52
|
// ("Trigger on …", "Trigger when/whenever/if …", "Triggers: …", "TRIGGER
|
|
53
53
|
// when:") — not when "trigger" merely appears as a noun (e.g. the phrase
|
|
54
54
|
// "no trigger phrases").
|
|
55
|
-
|
|
55
|
+
// gitea#5: widened to accept more applicability phrasings observed in the
|
|
56
|
+
// wild — "Loaded automatically when …", "when a turn touches …",
|
|
57
|
+
// "applies to …", "for memory operations", "Memory protocol for the X
|
|
58
|
+
// MCP" (a noun-opener that names the domain). The runtime makes
|
|
59
|
+
// `description` optional, so this rule is advisory; aim for low false
|
|
60
|
+
// positive rate rather than complete coverage.
|
|
61
|
+
const triggerPhrases = /\btrigger(s)?\b\s*(on|when|whenever|if|upon|for)\b|\btriggers?\s*:|\buse (this skill |it )?(when|whenever|for|to|on|if)\b|\bshould be used (when|whenever|for|to|if)\b|\b(applies|invoke|reach for|call this|run this|use this|loaded|loads|reaches for|fires) (when|whenever|if|to|for|automatically|on)\b|\bwhen (the user|you|asked|working|debugging|diagnosing|investigating|reviewing|writing|building|creating|editing|setting up|deploying|the task|a request|a question|a message|a turn|the turn|the session|the conversation|the model|you need|there|something|anything)\b|\bwhen [a-z]+ing\b|\bfor (debugging|diagnosing|tasks|requests|questions|when|any|memory|recall|search|saves?|the )\b|\bprotocol for\b|\barchivist for\b|\bhandler for\b|\bauto(matic|-?)\s+(save|load|invocation)\b/i;
|
|
56
62
|
if (triggerPhrases.test(d))
|
|
57
63
|
return true;
|
|
58
64
|
// First "sentence-ish" chunk — used to detect imperative / gerund openers.
|
package/dist/plugin-schema.js
CHANGED
|
@@ -25,36 +25,87 @@ function getAjv() {
|
|
|
25
25
|
addFormats(ajvShared);
|
|
26
26
|
return ajvShared;
|
|
27
27
|
}
|
|
28
|
-
function
|
|
29
|
-
if (compiledCache.has(fileName))
|
|
30
|
-
return compiledCache.get(fileName) ?? null;
|
|
28
|
+
function tryReadFile(relPath) {
|
|
31
29
|
const candidates = [
|
|
32
|
-
...assetCandidates(import.meta.url, ["..",
|
|
33
|
-
...assetCandidates(import.meta.url, ["..", "..",
|
|
30
|
+
...assetCandidates(import.meta.url, ["..", ...relPath]),
|
|
31
|
+
...assetCandidates(import.meta.url, ["..", "..", ...relPath]),
|
|
34
32
|
];
|
|
35
|
-
let raw = null;
|
|
36
33
|
for (const p of candidates) {
|
|
37
34
|
try {
|
|
38
|
-
|
|
39
|
-
break;
|
|
35
|
+
return readFileSync(p, "utf8");
|
|
40
36
|
}
|
|
41
37
|
catch {
|
|
42
38
|
// try next
|
|
43
39
|
}
|
|
44
40
|
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Compile a schema document, handling both wrapper shapes:
|
|
45
|
+
* - extracted (`{ extractedFromClaudeCodeVersion, schema: {...} }`)
|
|
46
|
+
* - bare JSON Schema (the schemastore.org files, no envelope)
|
|
47
|
+
*/
|
|
48
|
+
function compileSchemaDoc(raw, sourceLabel) {
|
|
49
|
+
const parsed = JSON.parse(raw);
|
|
50
|
+
let schema;
|
|
51
|
+
let version;
|
|
52
|
+
if ("extractedFromClaudeCodeVersion" in parsed &&
|
|
53
|
+
typeof parsed.extractedFromClaudeCodeVersion === "string" &&
|
|
54
|
+
"schema" in parsed &&
|
|
55
|
+
typeof parsed.schema === "object" &&
|
|
56
|
+
parsed.schema !== null) {
|
|
57
|
+
schema = parsed.schema;
|
|
58
|
+
version = parsed.extractedFromClaudeCodeVersion;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Bare schemastore document. Strip `$id` (Ajv registers it globally
|
|
62
|
+
// and trips `$id already exists` on a second compile) and any
|
|
63
|
+
// `$schema` meta-pointer Ajv2020 can't resolve (draft-07 etc.).
|
|
64
|
+
const { $id: _id, $schema: _meta, ...rest } = parsed;
|
|
65
|
+
void _id;
|
|
66
|
+
void _meta;
|
|
67
|
+
schema = rest;
|
|
68
|
+
version = sourceLabel;
|
|
69
|
+
}
|
|
70
|
+
const propSrc = schema.properties ?? {};
|
|
71
|
+
return {
|
|
72
|
+
validate: getAjv().compile(schema),
|
|
73
|
+
extractedFromVersion: version,
|
|
74
|
+
knownFields: new Set(Object.keys(propSrc)),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function loadCompiledSchema(fileName) {
|
|
78
|
+
if (compiledCache.has(fileName))
|
|
79
|
+
return compiledCache.get(fileName) ?? null;
|
|
80
|
+
const raw = tryReadFile(["contracts", fileName]);
|
|
45
81
|
if (!raw) {
|
|
46
82
|
compiledCache.set(fileName, null);
|
|
47
83
|
return null;
|
|
48
84
|
}
|
|
49
|
-
const
|
|
50
|
-
const compiled = {
|
|
51
|
-
validate: getAjv().compile(wrapped.schema),
|
|
52
|
-
extractedFromVersion: wrapped.extractedFromClaudeCodeVersion,
|
|
53
|
-
knownFields: new Set(Object.keys(wrapped.schema.properties ?? {})),
|
|
54
|
-
};
|
|
85
|
+
const compiled = compileSchemaDoc(raw, fileName);
|
|
55
86
|
compiledCache.set(fileName, compiled);
|
|
56
87
|
return compiled;
|
|
57
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Load a schemastore.org-curated schema from `contracts/schemastore/`.
|
|
91
|
+
* Schemastore is preferred for top-level artifact validation because it's
|
|
92
|
+
* Anthropic's own published source of truth and stays stable across Claude
|
|
93
|
+
* Code minifier rotations. The Zod-extracted schemas (`loadCompiledSchema`)
|
|
94
|
+
* remain authoritative for sub-shapes schemastore doesn't enumerate.
|
|
95
|
+
*/
|
|
96
|
+
function loadSchemastoreSchema(fileName) {
|
|
97
|
+
const cacheKey = `schemastore/${fileName}`;
|
|
98
|
+
if (compiledCache.has(cacheKey))
|
|
99
|
+
return compiledCache.get(cacheKey) ?? null;
|
|
100
|
+
const raw = tryReadFile(["contracts", "schemastore", fileName]);
|
|
101
|
+
if (!raw) {
|
|
102
|
+
compiledCache.set(cacheKey, null);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const compiled = compileSchemaDoc(raw, `schemastore:${fileName}`);
|
|
106
|
+
compiledCache.set(cacheKey, compiled);
|
|
107
|
+
return compiled;
|
|
108
|
+
}
|
|
58
109
|
export function loadLspSchema() {
|
|
59
110
|
return loadCompiledSchema("lsp.schema.json");
|
|
60
111
|
}
|
|
@@ -62,8 +113,24 @@ export function loadMonitorsSchema() {
|
|
|
62
113
|
return loadCompiledSchema("monitors.schema.json");
|
|
63
114
|
}
|
|
64
115
|
export function loadSettingsSchema() {
|
|
116
|
+
// gitea#6: prefer the Zod-extracted schema (runtime truth, e.g.
|
|
117
|
+
// `disableAutoMode` is `boolean` in 2.1.150) over schemastore (which
|
|
118
|
+
// still lists it as a string literal — out of date). Schemastore is
|
|
119
|
+
// only used for artifacts with no Zod source (marketplace, keybindings).
|
|
65
120
|
return loadCompiledSchema("settings.schema.json");
|
|
66
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Marketplace and keybindings schemas come exclusively from schemastore —
|
|
124
|
+
* there is no Zod source for them in the Claude Code bundle. Returns null
|
|
125
|
+
* if the user's install was shipped without the schemastore bundle (rare;
|
|
126
|
+
* the package.json includes them in `files`).
|
|
127
|
+
*/
|
|
128
|
+
export function loadMarketplaceSchema() {
|
|
129
|
+
return loadSchemastoreSchema("marketplace.schema.json");
|
|
130
|
+
}
|
|
131
|
+
export function loadKeybindingsSchema() {
|
|
132
|
+
return loadSchemastoreSchema("keybindings.schema.json");
|
|
133
|
+
}
|
|
67
134
|
export function loadMcpJsonSchema() {
|
|
68
135
|
return loadCompiledSchema("mcp.schema.json");
|
|
69
136
|
}
|
|
@@ -82,36 +149,17 @@ export function loadCommandFrontmatterSchema() {
|
|
|
82
149
|
export function loadPluginSchema() {
|
|
83
150
|
if (cached)
|
|
84
151
|
return cached;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"..",
|
|
90
|
-
"contracts",
|
|
91
|
-
"plugin.schema.json",
|
|
92
|
-
]),
|
|
93
|
-
];
|
|
94
|
-
let raw = null;
|
|
95
|
-
for (const p of candidates) {
|
|
96
|
-
try {
|
|
97
|
-
raw = readFileSync(p, "utf8");
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
// try next
|
|
102
|
-
}
|
|
103
|
-
}
|
|
152
|
+
// gitea#6: prefer the Zod-extracted plugin.schema.json (runtime truth);
|
|
153
|
+
// schemastore copy stays committed at `contracts/schemastore/` for
|
|
154
|
+
// reference and for the marketplace/keybindings artifacts only.
|
|
155
|
+
const raw = tryReadFile(["contracts", "plugin.schema.json"]);
|
|
104
156
|
if (!raw)
|
|
105
157
|
return null;
|
|
106
|
-
const
|
|
107
|
-
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
108
|
-
addFormats(ajv);
|
|
109
|
-
const validate = ajv.compile(wrapped.schema);
|
|
110
|
-
const knownFields = new Set(Object.keys(wrapped.schema.properties ?? {}));
|
|
158
|
+
const compiled = compileSchemaDoc(raw, "plugin.schema.json");
|
|
111
159
|
cached = {
|
|
112
|
-
validate,
|
|
113
|
-
extractedFromVersion:
|
|
114
|
-
knownFields,
|
|
160
|
+
validate: compiled.validate,
|
|
161
|
+
extractedFromVersion: compiled.extractedFromVersion,
|
|
162
|
+
knownFields: compiled.knownFields,
|
|
115
163
|
};
|
|
116
164
|
return cached;
|
|
117
165
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudecode-linter",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.150-patch.1",
|
|
4
4
|
"description": "Standalone linter for Claude Code plugins and configuration files",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
"deps:update": "ncu -u",
|
|
18
18
|
"knip": "knip --exclude types",
|
|
19
19
|
"check-deps": "tsx scripts/check-deps.ts",
|
|
20
|
-
"extract-contracts": "tsx scripts/extract-contracts.ts && tsx scripts/extract-plugin-schema.ts",
|
|
20
|
+
"extract-contracts": "tsx scripts/extract-contracts.ts && tsx scripts/extract-plugin-schema.ts && tsx scripts/fetch-schemastore.ts",
|
|
21
21
|
"extract-plugin-schema": "tsx scripts/extract-plugin-schema.ts",
|
|
22
|
+
"fetch-schemastore": "tsx scripts/fetch-schemastore.ts",
|
|
22
23
|
"generate-contracts": "tsx scripts/generate-contracts.ts"
|
|
23
24
|
},
|
|
24
25
|
"files": [
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
"contracts/command-frontmatter.schema.json",
|
|
33
34
|
"contracts/mcp.schema.json",
|
|
34
35
|
"contracts/hooks.schema.json",
|
|
36
|
+
"contracts/schemastore/*.json",
|
|
35
37
|
".claudecode-lint.defaults.yaml",
|
|
36
38
|
"README.md"
|
|
37
39
|
],
|