claudecode-linter 2.1.138 → 2.1.140
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 +25 -0
- package/README.md +20 -2
- package/contracts/lsp.schema.json +82 -0
- package/contracts/monitors.schema.json +48 -0
- package/contracts/plugin.schema.json +1319 -0
- package/dist/canonical-paths.js +43 -0
- package/dist/contracts.js +11 -1
- package/dist/discovery.js +69 -1
- package/dist/index.js +35 -21
- package/dist/linters/agent-md.js +106 -6
- package/dist/linters/lsp-json.js +94 -0
- package/dist/linters/misplaced-file.js +45 -0
- package/dist/linters/monitors-json.js +85 -0
- package/dist/linters/plugin-json.js +34 -5
- package/dist/plugin-schema.js +222 -0
- package/package.json +9 -3
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lints standalone `monitors/monitors.json` files (an array of monitor
|
|
3
|
+
* entries). Validates against the schema auto-extracted from Claude Code's
|
|
4
|
+
* runtime parser `vC8().parse(content)` — see scripts/extract-plugin-schema.ts
|
|
5
|
+
* → buildMonitorsSchema().
|
|
6
|
+
*
|
|
7
|
+
* monitors-json/unique-names mirrors Claude Code's refine() check that
|
|
8
|
+
* `Monitor names must be unique within a plugin`. JSON Schema can't express
|
|
9
|
+
* that constraint natively so we enforce it here.
|
|
10
|
+
*/
|
|
11
|
+
import { formatAjvError, loadMonitorsSchema, summarizeErrors, } from "../plugin-schema.js";
|
|
12
|
+
import { isRuleEnabled, getRuleSeverity } from "../types.js";
|
|
13
|
+
const RULES = [
|
|
14
|
+
{ id: "monitors-json/valid-json", defaultSeverity: "error" },
|
|
15
|
+
{ id: "monitors-json/schema-valid", defaultSeverity: "error" },
|
|
16
|
+
{ id: "monitors-json/unique-names", defaultSeverity: "error" },
|
|
17
|
+
];
|
|
18
|
+
function diag(config, filePath, ruleId, defaultSeverity, message, line, column) {
|
|
19
|
+
if (!isRuleEnabled(config, ruleId))
|
|
20
|
+
return null;
|
|
21
|
+
return {
|
|
22
|
+
rule: ruleId,
|
|
23
|
+
severity: getRuleSeverity(config, ruleId, defaultSeverity),
|
|
24
|
+
message,
|
|
25
|
+
file: filePath,
|
|
26
|
+
line,
|
|
27
|
+
column,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export const monitorsJsonLinter = {
|
|
31
|
+
artifactType: "monitors-json",
|
|
32
|
+
lint(filePath, content, config) {
|
|
33
|
+
const diagnostics = [];
|
|
34
|
+
const push = (d) => {
|
|
35
|
+
if (d)
|
|
36
|
+
diagnostics.push(d);
|
|
37
|
+
};
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(content);
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
push(diag(config, filePath, "monitors-json/valid-json", "error", `Invalid JSON: ${e.message}`));
|
|
44
|
+
return diagnostics;
|
|
45
|
+
}
|
|
46
|
+
if (!Array.isArray(parsed)) {
|
|
47
|
+
push(diag(config, filePath, "monitors-json/valid-json", "error", "monitors.json must be a JSON array of monitor entries"));
|
|
48
|
+
return diagnostics;
|
|
49
|
+
}
|
|
50
|
+
// Schema validation
|
|
51
|
+
if (isRuleEnabled(config, "monitors-json/schema-valid")) {
|
|
52
|
+
const ctx = loadMonitorsSchema();
|
|
53
|
+
if (ctx) {
|
|
54
|
+
const ok = ctx.validate(parsed);
|
|
55
|
+
if (!ok && ctx.validate.errors) {
|
|
56
|
+
const filtered = summarizeErrors(ctx.validate.errors);
|
|
57
|
+
for (const err of filtered) {
|
|
58
|
+
push(diag(config, filePath, "monitors-json/schema-valid", "error", formatAjvError(err)));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Unique-name refinement — Claude Code's refine() check.
|
|
64
|
+
if (isRuleEnabled(config, "monitors-json/unique-names")) {
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
const duplicates = new Set();
|
|
67
|
+
for (const entry of parsed) {
|
|
68
|
+
if (entry &&
|
|
69
|
+
typeof entry === "object" &&
|
|
70
|
+
typeof entry.name === "string") {
|
|
71
|
+
const name = entry.name;
|
|
72
|
+
if (seen.has(name))
|
|
73
|
+
duplicates.add(name);
|
|
74
|
+
seen.add(name);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const dup of duplicates) {
|
|
78
|
+
push(diag(config, filePath, "monitors-json/unique-names", "error", `Monitor name "${dup}" is duplicated. Monitor names must be unique within a plugin.`));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return diagnostics;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
export { RULES as MONITORS_JSON_RULES };
|
|
85
|
+
//# sourceMappingURL=monitors-json.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import semver from "semver";
|
|
2
2
|
import { PLUGIN_JSON_FIELDS } from "../contracts.js";
|
|
3
|
+
import { formatAjvError, loadPluginSchema, summarizeErrors, } from "../plugin-schema.js";
|
|
3
4
|
import { isRuleEnabled, getRuleSeverity } from "../types.js";
|
|
4
5
|
import { isKebabCase } from "../utils/kebab-case.js";
|
|
5
6
|
const SPDX_COMMON = new Set([
|
|
@@ -9,6 +10,7 @@ const SPDX_COMMON = new Set([
|
|
|
9
10
|
]);
|
|
10
11
|
const RULES = [
|
|
11
12
|
{ id: "plugin-json/valid-json", defaultSeverity: "error" },
|
|
13
|
+
{ id: "plugin-json/schema-valid", defaultSeverity: "error" },
|
|
12
14
|
{ id: "plugin-json/name-required", defaultSeverity: "error" },
|
|
13
15
|
{ id: "plugin-json/name-kebab-case", defaultSeverity: "error" },
|
|
14
16
|
{ id: "plugin-json/name-length", defaultSeverity: "error" },
|
|
@@ -64,6 +66,27 @@ export const pluginJsonLinter = {
|
|
|
64
66
|
push(diag(config, filePath, "plugin-json/valid-json", "error", "plugin.json must be a JSON object"));
|
|
65
67
|
return diagnostics;
|
|
66
68
|
}
|
|
69
|
+
// schema-valid — defer to extracted JSON Schema for type/enum/nested checks
|
|
70
|
+
// that the per-field rules below don't cover. Existing rules (name-required,
|
|
71
|
+
// version-semver, …) handle the common cases with friendlier messages, so
|
|
72
|
+
// most users will see schema-valid fire only on deeper structural issues
|
|
73
|
+
// (MCP transport unions, userConfig shape, etc.). Skipped silently if the
|
|
74
|
+
// schema bundle isn't shipped with this install.
|
|
75
|
+
if (isRuleEnabled(config, "plugin-json/schema-valid")) {
|
|
76
|
+
const ctx = loadPluginSchema();
|
|
77
|
+
if (ctx) {
|
|
78
|
+
const ok = ctx.validate(parsed);
|
|
79
|
+
if (!ok && ctx.validate.errors) {
|
|
80
|
+
const filtered = summarizeErrors(ctx.validate.errors);
|
|
81
|
+
for (const err of filtered) {
|
|
82
|
+
// First path segment maps to a top-level field we can highlight.
|
|
83
|
+
const firstSeg = err.instancePath.split("/").filter(Boolean)[0];
|
|
84
|
+
const p = firstSeg ? pos(firstSeg) : undefined;
|
|
85
|
+
push(diag(config, filePath, "plugin-json/schema-valid", "error", formatAjvError(err), p?.line, p?.column));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
67
90
|
// name
|
|
68
91
|
if (!("name" in parsed) || typeof parsed.name !== "string" || parsed.name === "") {
|
|
69
92
|
push(diag(config, filePath, "plugin-json/name-required", "error", "\"name\" field is required and must be a non-empty string"));
|
|
@@ -142,11 +165,17 @@ export const pluginJsonLinter = {
|
|
|
142
165
|
}
|
|
143
166
|
}
|
|
144
167
|
}
|
|
145
|
-
// unknown fields
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
168
|
+
// unknown fields — prefer the schema's property set (full + nested-aware)
|
|
169
|
+
// when available; fall back to the contract-extracted PLUGIN_JSON_FIELDS
|
|
170
|
+
// for installs that didn't ship the schema bundle.
|
|
171
|
+
{
|
|
172
|
+
const ctx = loadPluginSchema();
|
|
173
|
+
const known = ctx?.knownFields ?? PLUGIN_JSON_FIELDS;
|
|
174
|
+
for (const key of Object.keys(parsed)) {
|
|
175
|
+
if (!known.has(key)) {
|
|
176
|
+
const p = pos(key);
|
|
177
|
+
push(diag(config, filePath, "plugin-json/no-unknown-fields", "info", `Unknown field "${key}" (known: ${[...known].sort().join(", ")})`, p?.line, p?.column));
|
|
178
|
+
}
|
|
150
179
|
}
|
|
151
180
|
}
|
|
152
181
|
// license
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy loader for the auto-extracted plugin.json JSON Schema. The schema lives
|
|
3
|
+
* at `contracts/plugin.schema.json` (extracted from Claude Code's cli.js by
|
|
4
|
+
* `scripts/extract-plugin-schema.ts`).
|
|
5
|
+
*
|
|
6
|
+
* Resolution: from dist/plugin-schema.js (compiled), step up two directories to
|
|
7
|
+
* reach the package root where `contracts/` lives. Falls back to the package
|
|
8
|
+
* resolver if the file is being run from an alternate layout (e.g. monorepo).
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { dirname, resolve } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
14
|
+
// biome-ignore lint/style/useImportType: runtime import; types only.
|
|
15
|
+
import * as addFormatsNs from "ajv-formats";
|
|
16
|
+
// ajv-formats ships as CJS with a default function export. Under Node16
|
|
17
|
+
// ESM resolution we have to reach in for `.default`.
|
|
18
|
+
const addFormats = addFormatsNs.default;
|
|
19
|
+
let cached = null;
|
|
20
|
+
const compiledCache = new Map();
|
|
21
|
+
let ajvShared = null;
|
|
22
|
+
function getAjv() {
|
|
23
|
+
if (ajvShared)
|
|
24
|
+
return ajvShared;
|
|
25
|
+
ajvShared = new Ajv2020({ allErrors: true, strict: false });
|
|
26
|
+
addFormats(ajvShared);
|
|
27
|
+
return ajvShared;
|
|
28
|
+
}
|
|
29
|
+
function loadCompiledSchema(fileName) {
|
|
30
|
+
if (compiledCache.has(fileName))
|
|
31
|
+
return compiledCache.get(fileName) ?? null;
|
|
32
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const candidates = [
|
|
34
|
+
resolve(here, "..", "contracts", fileName),
|
|
35
|
+
resolve(here, "..", "..", "contracts", fileName),
|
|
36
|
+
];
|
|
37
|
+
let raw = null;
|
|
38
|
+
for (const p of candidates) {
|
|
39
|
+
try {
|
|
40
|
+
raw = readFileSync(p, "utf8");
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// try next
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!raw) {
|
|
48
|
+
compiledCache.set(fileName, null);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const wrapped = JSON.parse(raw);
|
|
52
|
+
const compiled = {
|
|
53
|
+
validate: getAjv().compile(wrapped.schema),
|
|
54
|
+
extractedFromVersion: wrapped.extractedFromClaudeCodeVersion,
|
|
55
|
+
};
|
|
56
|
+
compiledCache.set(fileName, compiled);
|
|
57
|
+
return compiled;
|
|
58
|
+
}
|
|
59
|
+
export function loadLspSchema() {
|
|
60
|
+
return loadCompiledSchema("lsp.schema.json");
|
|
61
|
+
}
|
|
62
|
+
export function loadMonitorsSchema() {
|
|
63
|
+
return loadCompiledSchema("monitors.schema.json");
|
|
64
|
+
}
|
|
65
|
+
export function loadPluginSchema() {
|
|
66
|
+
if (cached)
|
|
67
|
+
return cached;
|
|
68
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
69
|
+
const candidates = [
|
|
70
|
+
resolve(here, "..", "contracts", "plugin.schema.json"),
|
|
71
|
+
resolve(here, "..", "..", "contracts", "plugin.schema.json"),
|
|
72
|
+
];
|
|
73
|
+
let raw = null;
|
|
74
|
+
for (const p of candidates) {
|
|
75
|
+
try {
|
|
76
|
+
raw = readFileSync(p, "utf8");
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// try next
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!raw)
|
|
84
|
+
return null;
|
|
85
|
+
const wrapped = JSON.parse(raw);
|
|
86
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
87
|
+
addFormats(ajv);
|
|
88
|
+
const validate = ajv.compile(wrapped.schema);
|
|
89
|
+
const knownFields = new Set(Object.keys(wrapped.schema.properties ?? {}));
|
|
90
|
+
cached = {
|
|
91
|
+
validate,
|
|
92
|
+
extractedFromVersion: wrapped.extractedFromClaudeCodeVersion,
|
|
93
|
+
knownFields,
|
|
94
|
+
};
|
|
95
|
+
return cached;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Reduce Ajv's anyOf/oneOf branch enumeration to the "closest match" branch.
|
|
99
|
+
*
|
|
100
|
+
* When a value fails an anyOf with N branches, Ajv emits errors for every
|
|
101
|
+
* branch — that's faithful but noisy (14 lines for one malformed MCP server).
|
|
102
|
+
* We use schemaPath to group child errors by branch index and keep only the
|
|
103
|
+
* branch with the fewest errors (the most-likely-intended shape).
|
|
104
|
+
*
|
|
105
|
+
* Nested unions are handled by processing outer unions first: their kept
|
|
106
|
+
* branch's inner anyOf and inner children survive into the second pass, where
|
|
107
|
+
* the inner union is itself summarized.
|
|
108
|
+
*/
|
|
109
|
+
export function summarizeErrors(errors) {
|
|
110
|
+
if (errors.length === 0)
|
|
111
|
+
return errors;
|
|
112
|
+
// Sort unions outermost-first (shorter schemaPath = closer to root).
|
|
113
|
+
const unions = errors
|
|
114
|
+
.filter((e) => e.keyword === "anyOf" || e.keyword === "oneOf")
|
|
115
|
+
.sort((a, b) => a.schemaPath.length - b.schemaPath.length);
|
|
116
|
+
const dropped = new Set();
|
|
117
|
+
for (const union of unions) {
|
|
118
|
+
// Children of this union have schemaPath starting with `<unionPath>/<N>/`.
|
|
119
|
+
// AJV emits schemaPath like "#/properties/mcpServers/anyOf" for the union
|
|
120
|
+
// summary and "#/properties/mcpServers/anyOf/2/required" for branch 2's
|
|
121
|
+
// child errors.
|
|
122
|
+
const prefix = `${union.schemaPath}/`;
|
|
123
|
+
const children = errors.filter((e) => e !== union && e.schemaPath.startsWith(prefix) && !dropped.has(e));
|
|
124
|
+
if (children.length === 0)
|
|
125
|
+
continue;
|
|
126
|
+
// Group by branch index (the segment right after the prefix).
|
|
127
|
+
const byBranch = new Map();
|
|
128
|
+
for (const c of children) {
|
|
129
|
+
const branchIdx = c.schemaPath.slice(prefix.length).split("/", 1)[0];
|
|
130
|
+
const arr = byBranch.get(branchIdx) ?? [];
|
|
131
|
+
arr.push(c);
|
|
132
|
+
byBranch.set(branchIdx, arr);
|
|
133
|
+
}
|
|
134
|
+
// Pick the branch the user got the furthest into. We measure "furthest"
|
|
135
|
+
// as the depth of each branch's *shallowest* error: a branch that failed
|
|
136
|
+
// only deep inside means we matched the outer shape (e.g. value is an
|
|
137
|
+
// object, but a nested transport type is wrong) — strong signal of intent.
|
|
138
|
+
// Counting errors flatly would mislead since nested anyOfs fan out.
|
|
139
|
+
let bestBranch = null;
|
|
140
|
+
let bestDepth = -1;
|
|
141
|
+
for (const [branch, errs] of byBranch) {
|
|
142
|
+
let minDepth = Infinity;
|
|
143
|
+
for (const e of errs) {
|
|
144
|
+
const depth = e.schemaPath
|
|
145
|
+
.slice(prefix.length + branch.length + 1)
|
|
146
|
+
.split("/").length;
|
|
147
|
+
if (depth < minDepth)
|
|
148
|
+
minDepth = depth;
|
|
149
|
+
}
|
|
150
|
+
if (minDepth > bestDepth) {
|
|
151
|
+
bestDepth = minDepth;
|
|
152
|
+
bestBranch = branch;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Drop every child of this union that isn't in the best branch.
|
|
156
|
+
for (const [branch, errs] of byBranch) {
|
|
157
|
+
if (branch === bestBranch)
|
|
158
|
+
continue;
|
|
159
|
+
for (const e of errs)
|
|
160
|
+
dropped.add(e);
|
|
161
|
+
}
|
|
162
|
+
// The anyOf/oneOf parent error itself ("does not match any allowed shape")
|
|
163
|
+
// is redundant once we've kept a branch-specific child error that points
|
|
164
|
+
// to the actual problem. Drop it unless its best branch had nothing left.
|
|
165
|
+
const survivingKept = bestBranch !== null
|
|
166
|
+
? (byBranch.get(bestBranch) ?? []).filter((e) => !dropped.has(e))
|
|
167
|
+
: [];
|
|
168
|
+
if (survivingKept.length > 0)
|
|
169
|
+
dropped.add(union);
|
|
170
|
+
}
|
|
171
|
+
// Final pass: drop the rejected branches and dedupe identical errors.
|
|
172
|
+
const seen = new Set();
|
|
173
|
+
const out = [];
|
|
174
|
+
for (const e of errors) {
|
|
175
|
+
if (dropped.has(e))
|
|
176
|
+
continue;
|
|
177
|
+
const sig = `${e.instancePath}|${e.keyword}|${JSON.stringify(e.params)}`;
|
|
178
|
+
if (seen.has(sig))
|
|
179
|
+
continue;
|
|
180
|
+
seen.add(sig);
|
|
181
|
+
out.push(e);
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
/** Format an Ajv error as a human-readable single-line message. */
|
|
186
|
+
export function formatAjvError(error) {
|
|
187
|
+
const where = error.instancePath || "(root)";
|
|
188
|
+
const params = error.params;
|
|
189
|
+
switch (error.keyword) {
|
|
190
|
+
case "required":
|
|
191
|
+
return `${where}: missing required field "${params.missingProperty}"`;
|
|
192
|
+
case "type":
|
|
193
|
+
return `${where}: must be ${params.type}`;
|
|
194
|
+
case "enum": {
|
|
195
|
+
const allowed = params.allowedValues ?? [];
|
|
196
|
+
return `${where}: must be one of [${allowed
|
|
197
|
+
.map((v) => JSON.stringify(v))
|
|
198
|
+
.join(", ")}]`;
|
|
199
|
+
}
|
|
200
|
+
case "const":
|
|
201
|
+
return `${where}: must equal ${JSON.stringify(params.allowedValue)}`;
|
|
202
|
+
case "additionalProperties":
|
|
203
|
+
return `${where}: unknown property "${params.additionalProperty}"`;
|
|
204
|
+
case "anyOf":
|
|
205
|
+
return `${where}: does not match any allowed shape`;
|
|
206
|
+
case "oneOf":
|
|
207
|
+
return `${where}: does not match exactly one allowed shape`;
|
|
208
|
+
case "pattern":
|
|
209
|
+
return `${where}: does not match pattern ${params.pattern}`;
|
|
210
|
+
case "minLength":
|
|
211
|
+
return `${where}: must be at least ${params.limit} characters`;
|
|
212
|
+
case "maxLength":
|
|
213
|
+
return `${where}: must be at most ${params.limit} characters`;
|
|
214
|
+
case "minItems":
|
|
215
|
+
return `${where}: must have at least ${params.limit} items`;
|
|
216
|
+
case "maxItems":
|
|
217
|
+
return `${where}: must have at most ${params.limit} items`;
|
|
218
|
+
default:
|
|
219
|
+
return `${where}: ${error.message ?? error.keyword}`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=plugin-schema.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudecode-linter",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.140",
|
|
4
4
|
"description": "Standalone linter for Claude Code plugins and configuration files",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,11 +17,15 @@
|
|
|
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",
|
|
20
|
+
"extract-contracts": "tsx scripts/extract-contracts.ts && tsx scripts/extract-plugin-schema.ts",
|
|
21
|
+
"extract-plugin-schema": "tsx scripts/extract-plugin-schema.ts",
|
|
21
22
|
"generate-contracts": "tsx scripts/generate-contracts.ts"
|
|
22
23
|
},
|
|
23
24
|
"files": [
|
|
24
25
|
"dist/**/*.js",
|
|
26
|
+
"contracts/plugin.schema.json",
|
|
27
|
+
"contracts/lsp.schema.json",
|
|
28
|
+
"contracts/monitors.schema.json",
|
|
25
29
|
".claudecode-lint.defaults.yaml",
|
|
26
30
|
"README.md"
|
|
27
31
|
],
|
|
@@ -45,10 +49,12 @@
|
|
|
45
49
|
"node": ">=18"
|
|
46
50
|
},
|
|
47
51
|
"dependencies": {
|
|
48
|
-
"
|
|
52
|
+
"ajv": "^8.20.0",
|
|
53
|
+
"ajv-formats": "^3.0.1",
|
|
49
54
|
"minimatch": "^10.2.4",
|
|
50
55
|
"picocolors": "^1.1.1",
|
|
51
56
|
"prettier": "^3.8.1",
|
|
57
|
+
"sade": "^1.8.1",
|
|
52
58
|
"semver": "^7.7.4",
|
|
53
59
|
"tinyglobby": "^0.2.15",
|
|
54
60
|
"yaml": "^2.8.3"
|