@santi020k/eslint-config-basic 1.6.0 → 2.0.0-beta.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/CHANGELOG.md +165 -1
- package/README.md +4 -22
- package/dist/agent-skill-generator.d.ts +42 -20
- package/dist/agent-skill-generator.js +375 -198
- package/dist/cli.d.ts +22 -0
- package/dist/cli.js +603 -38
- package/dist/compose.d.ts +4 -2
- package/dist/compose.js +19 -8
- package/dist/frameworks.d.ts +58 -0
- package/dist/frameworks.js +106 -0
- package/dist/index.d.ts +15 -7
- package/dist/index.js +755 -104
- package/dist/integrations.d.ts +1 -0
- package/dist/integrations.js +5 -0
- package/dist/lazy.d.ts +3 -0
- package/dist/lazy.js +19 -0
- package/dist/resolvers.d.ts +5 -2
- package/dist/resolvers.js +60 -23
- package/package.json +47 -64
- package/dist/optionals.d.ts +0 -13
- package/dist/optionals.js +0 -71
|
@@ -4,53 +4,81 @@ import { pathToFileURL } from "node:url";
|
|
|
4
4
|
import { detectProjectOptions } from "./index.js";
|
|
5
5
|
const AGENT_TARGETS = [
|
|
6
6
|
{
|
|
7
|
+
format: "frontmatter",
|
|
7
8
|
label: ".agent (generic skill format)",
|
|
8
9
|
markerFolder: ".agent",
|
|
9
|
-
skillSubdir: "skills",
|
|
10
10
|
skillFile: "eslint-standards.md",
|
|
11
|
-
|
|
11
|
+
skillSubdir: "skills"
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
|
+
format: "frontmatter",
|
|
14
15
|
label: ".agents (generic skill format)",
|
|
15
16
|
markerFolder: ".agents",
|
|
16
|
-
skillSubdir: "skills",
|
|
17
17
|
skillFile: "eslint-standards.md",
|
|
18
|
-
|
|
18
|
+
skillSubdir: "skills"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
|
+
format: "plain",
|
|
21
22
|
label: "Claude Code (.claude/commands)",
|
|
22
23
|
markerFolder: ".claude",
|
|
23
|
-
skillSubdir: "commands",
|
|
24
24
|
skillFile: "eslint.md",
|
|
25
|
-
|
|
25
|
+
skillSubdir: "commands"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
|
+
format: "cursor",
|
|
28
29
|
label: "Cursor (.cursor/rules)",
|
|
29
30
|
markerFolder: ".cursor",
|
|
30
|
-
skillSubdir: "rules",
|
|
31
31
|
skillFile: "eslint-standards.mdc",
|
|
32
|
-
|
|
32
|
+
skillSubdir: "rules"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
+
format: "frontmatter",
|
|
35
36
|
label: "Windsurf (.windsurf/rules)",
|
|
36
37
|
markerFolder: ".windsurf",
|
|
37
|
-
skillSubdir: "rules",
|
|
38
38
|
skillFile: "eslint-standards.md",
|
|
39
|
-
|
|
39
|
+
skillSubdir: "rules"
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
|
+
format: "plain",
|
|
42
43
|
label: "Copilot (.copilot)",
|
|
43
44
|
markerFolder: ".copilot",
|
|
44
|
-
skillSubdir: "instructions",
|
|
45
45
|
skillFile: "eslint-standards.md",
|
|
46
|
-
|
|
46
|
+
skillSubdir: "instructions"
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
|
+
format: "plain",
|
|
49
50
|
label: "Aider (.aider)",
|
|
50
51
|
markerFolder: ".aider",
|
|
51
|
-
skillSubdir: ".",
|
|
52
52
|
skillFile: "eslint-standards.md",
|
|
53
|
-
|
|
53
|
+
skillSubdir: "."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
format: "plain",
|
|
57
|
+
label: "Gemini (.gemini)",
|
|
58
|
+
markerFolder: ".gemini",
|
|
59
|
+
skillFile: "styleguide.md",
|
|
60
|
+
skillSubdir: "."
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
format: "plain",
|
|
64
|
+
label: "Cline (.clinerules)",
|
|
65
|
+
markerFolder: ".clinerules",
|
|
66
|
+
skillFile: "eslint-standards.md",
|
|
67
|
+
skillSubdir: "."
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
format: "plain",
|
|
71
|
+
label: "Roo Code (.roo/rules)",
|
|
72
|
+
markerFolder: ".roo",
|
|
73
|
+
skillFile: "eslint-standards.md",
|
|
74
|
+
skillSubdir: "rules"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
format: "kiro",
|
|
78
|
+
label: "Kiro (.kiro/steering)",
|
|
79
|
+
markerFolder: ".kiro",
|
|
80
|
+
skillFile: "eslint-standards.md",
|
|
81
|
+
skillSubdir: "steering"
|
|
54
82
|
}
|
|
55
83
|
];
|
|
56
84
|
const FEATURE_MAP = [
|
|
@@ -68,85 +96,141 @@ const FEATURE_MAP = [
|
|
|
68
96
|
["eslint-config-hono/", "frameworks", "Hono"],
|
|
69
97
|
["eslint-config-expo/", "frameworks", "Expo"],
|
|
70
98
|
["eslint-config-qwik/", "frameworks", "Qwik"],
|
|
99
|
+
["eslint-config-react-router/", "frameworks", "React Router"],
|
|
71
100
|
["eslint-config-remix/", "frameworks", "Remix"],
|
|
72
101
|
["eslint-config-solid/", "frameworks", "SolidJS"],
|
|
102
|
+
["eslint-config-nuxt/", "frameworks", "Nuxt"],
|
|
103
|
+
["eslint-config-lit/", "frameworks", "Lit"],
|
|
104
|
+
["eslint-config-tanstack-start/", "frameworks", "TanStack Start"],
|
|
105
|
+
["eslint-config-preact/", "frameworks", "Preact"],
|
|
106
|
+
["eslint-config-slidev/", "frameworks", "Slidev"],
|
|
107
|
+
["eslint-config-vite/", "frameworks", "Vite"],
|
|
73
108
|
// Testing
|
|
74
|
-
["
|
|
75
|
-
["
|
|
76
|
-
["
|
|
77
|
-
["
|
|
78
|
-
["
|
|
109
|
+
["integrations/vitest", "testing", "Vitest"],
|
|
110
|
+
["integrations/jest", "testing", "Jest"],
|
|
111
|
+
["eslint-config-integrations/jest-dom", "testing", "Jest DOM"],
|
|
112
|
+
["integrations/playwright", "testing", "Playwright"],
|
|
113
|
+
["integrations/cypress", "testing", "Cypress"],
|
|
114
|
+
["integrations/testing-library", "testing", "Testing Library"],
|
|
79
115
|
// Tools
|
|
80
116
|
["eslint-config/prettier", "tools", "Prettier"],
|
|
81
|
-
["
|
|
82
|
-
["eslint-config-
|
|
83
|
-
["
|
|
117
|
+
["integrations/cspell", "tools", "CSpell"],
|
|
118
|
+
["eslint-config-integrations/jsdoc", "tools", "JSDoc"],
|
|
119
|
+
["integrations/swagger", "tools", "Swagger"],
|
|
120
|
+
["eslint-config-integrations/command", "tools", "Command"],
|
|
121
|
+
["eslint-config-integrations/docker", "tools", "Docker"],
|
|
122
|
+
["eslint-config-integrations/github-actions", "tools", "GitHub Actions"],
|
|
123
|
+
["eslint-config-integrations/nx", "tools", "Nx"],
|
|
124
|
+
["integrations/pnpm/", "tools", "pnpm"],
|
|
84
125
|
// Libraries
|
|
126
|
+
["eslint-config-integrations/ai-sdk", "libraries", "AI SDK"],
|
|
127
|
+
["eslint-config-integrations/mcp", "libraries", "MCP"],
|
|
128
|
+
["eslint-config-integrations/mastra", "libraries", "Mastra"],
|
|
129
|
+
["eslint-config-integrations/openai-agents", "libraries", "OpenAI Agents"],
|
|
130
|
+
["eslint-config-integrations/langchain", "libraries", "LangChain"],
|
|
131
|
+
["eslint-config-integrations/llamaindex", "libraries", "LlamaIndex"],
|
|
85
132
|
["santi020k/tailwind/", "libraries", "Tailwind CSS"],
|
|
86
|
-
["
|
|
87
|
-
["
|
|
88
|
-
["
|
|
89
|
-
["eslint-config-
|
|
90
|
-
["eslint-config-
|
|
133
|
+
["integrations/i18next", "libraries", "i18next"],
|
|
134
|
+
["integrations/stencil", "libraries", "Stencil"],
|
|
135
|
+
["storybook:recommended", "libraries", "Storybook"],
|
|
136
|
+
["eslint-config-integrations/tanstack-query", "libraries", "TanStack Query"],
|
|
137
|
+
["eslint-config-integrations/tanstack-router", "libraries", "TanStack Router"],
|
|
138
|
+
["eslint-config-integrations/autogen", "libraries", "Autogen"],
|
|
139
|
+
["eslint-config-integrations/google-genai", "libraries", "Google GenAI"],
|
|
140
|
+
["eslint-config-integrations/drizzle", "libraries", "Drizzle ORM"],
|
|
141
|
+
["eslint-config-integrations/prisma", "libraries", "Prisma"],
|
|
142
|
+
["eslint-config-integrations/typeorm", "libraries", "TypeORM"],
|
|
143
|
+
["eslint-config-integrations/mikro-orm", "libraries", "MikroORM"],
|
|
144
|
+
["eslint-config-integrations/sequelize", "libraries", "Sequelize"],
|
|
145
|
+
["eslint-config-integrations/zod", "libraries", "Zod"],
|
|
146
|
+
["eslint-config-turbo/", "libraries", "Turbo"],
|
|
91
147
|
// Formats
|
|
92
|
-
["
|
|
93
|
-
["
|
|
94
|
-
["
|
|
95
|
-
["
|
|
148
|
+
["integrations/css/", "formats", "CSS"],
|
|
149
|
+
["integrations/html/", "formats", "HTML"],
|
|
150
|
+
["eslint-config-integrations/package-json", "formats", "Package JSON"],
|
|
151
|
+
["integrations/graphql/", "formats", "GraphQL"],
|
|
152
|
+
["integrations/yaml/", "formats", "YAML"],
|
|
153
|
+
["integrations/jsonc/", "formats", "JSONC"],
|
|
154
|
+
["integrations/markdown", "formats", "Markdown"],
|
|
96
155
|
["eslint-config-mdx/", "formats", "MDX"],
|
|
97
|
-
["
|
|
156
|
+
["integrations/toml/", "formats", "TOML"],
|
|
98
157
|
// Extensions
|
|
99
158
|
["eslint-config/unicorn", "extensions", "Unicorn"],
|
|
100
159
|
["eslint-config/sonarjs", "extensions", "SonarJS"],
|
|
101
|
-
["eslint-config-
|
|
102
|
-
["eslint-config-
|
|
103
|
-
["
|
|
104
|
-
["eslint-config/best-practices", "extensions", "Best Practices"]
|
|
160
|
+
["eslint-config-integrations/security", "extensions", "Security"],
|
|
161
|
+
["eslint-config-integrations/perfectionist", "extensions", "Perfectionist"],
|
|
162
|
+
["integrations/regexp", "extensions", "Regexp"],
|
|
163
|
+
["eslint-config/best-practices", "extensions", "Best Practices"],
|
|
164
|
+
["eslint-config-integrations/a11y/", "extensions", "Accessibility (a11y)"],
|
|
165
|
+
["eslint-config-integrations/biome", "extensions", "Biome"],
|
|
166
|
+
["eslint-config-integrations/import-boundaries", "extensions", "Import Boundaries"],
|
|
167
|
+
["integrations/compat/", "extensions", "Browser Compat"],
|
|
168
|
+
["integrations/de-morgan/", "extensions", "De Morgan"],
|
|
169
|
+
["integrations/depend/", "extensions", "Depend"],
|
|
170
|
+
["integrations/node/", "extensions", "Node.js"],
|
|
171
|
+
["eslint-config-integrations/no-only-tests", "extensions", "No Only Tests"],
|
|
172
|
+
["integrations/oxlint", "extensions", "Oxlint"]
|
|
105
173
|
];
|
|
106
|
-
const DETECTED_FRAMEWORK_LABELS =
|
|
107
|
-
angular
|
|
108
|
-
astro
|
|
109
|
-
expo
|
|
110
|
-
hono
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
"
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
174
|
+
const DETECTED_FRAMEWORK_LABELS = /* @__PURE__ */ new Map([
|
|
175
|
+
["angular", "Angular"],
|
|
176
|
+
["astro", "Astro"],
|
|
177
|
+
["expo", "Expo"],
|
|
178
|
+
["hono", "Hono"],
|
|
179
|
+
["lit", "Lit"],
|
|
180
|
+
["nest", "NestJS"],
|
|
181
|
+
["next", "Next.js"],
|
|
182
|
+
["nuxt", "Nuxt"],
|
|
183
|
+
["preact", "Preact"],
|
|
184
|
+
["qwik", "Qwik"],
|
|
185
|
+
["react", "React"],
|
|
186
|
+
["react-router", "React Router"],
|
|
187
|
+
["remix", "Remix"],
|
|
188
|
+
["slidev", "Slidev"],
|
|
189
|
+
["solid", "SolidJS"],
|
|
190
|
+
["svelte", "Svelte"],
|
|
191
|
+
["tanstack-start", "TanStack Start"],
|
|
192
|
+
["vite", "Vite"],
|
|
193
|
+
["vue", "Vue"]
|
|
194
|
+
]);
|
|
195
|
+
const FEATURE_LABELS = /* @__PURE__ */ new Map([
|
|
196
|
+
["best-practices", "Best Practices"],
|
|
197
|
+
["compat", "Browser Compat"],
|
|
198
|
+
["cspell", "CSpell"],
|
|
199
|
+
["css", "CSS"],
|
|
200
|
+
["cypress", "Cypress"],
|
|
201
|
+
["de-morgan", "De Morgan"],
|
|
202
|
+
["depend", "Depend"],
|
|
203
|
+
["graphql", "GraphQL"],
|
|
204
|
+
["html", "HTML"],
|
|
205
|
+
["i18next", "i18next"],
|
|
206
|
+
["jest", "Jest"],
|
|
207
|
+
["jsdoc", "JSDoc"],
|
|
208
|
+
["jsonc", "JSONC"],
|
|
209
|
+
["markdown", "Markdown"],
|
|
210
|
+
["mdx", "MDX"],
|
|
211
|
+
["node", "Node.js"],
|
|
212
|
+
["oxlint", "Oxlint"],
|
|
213
|
+
["perfectionist", "Perfectionist"],
|
|
214
|
+
["playwright", "Playwright"],
|
|
215
|
+
["pnpm", "pnpm"],
|
|
216
|
+
["prettier", "Prettier"],
|
|
217
|
+
["regexp", "Regexp"],
|
|
218
|
+
["security", "Security"],
|
|
219
|
+
["sonarjs", "SonarJS"],
|
|
220
|
+
["stencil", "Stencil"],
|
|
221
|
+
["storybook", "Storybook"],
|
|
222
|
+
["swagger", "Swagger"],
|
|
223
|
+
["tailwind", "Tailwind CSS"],
|
|
224
|
+
["tanstack-query", "TanStack Query"],
|
|
225
|
+
["tanstack-router", "TanStack Router"],
|
|
226
|
+
["testing-library", "Testing Library"],
|
|
227
|
+
["toml", "TOML"],
|
|
228
|
+
["unicorn", "Unicorn"],
|
|
229
|
+
["vitest", "Vitest"],
|
|
230
|
+
["yaml", "YAML"]
|
|
231
|
+
]);
|
|
232
|
+
const toFeatureLabel = (value) => FEATURE_LABELS.get(value) ?? value;
|
|
233
|
+
const extractRuleNamespaces = (rules) => Object.keys(rules).filter((key) => key.includes("/")).map((key) => key.slice(0, key.indexOf("/")));
|
|
150
234
|
const collectTokens = (configs) => {
|
|
151
235
|
const tokens = [];
|
|
152
236
|
for (const entry of configs) {
|
|
@@ -159,27 +243,50 @@ const collectTokens = (configs) => {
|
|
|
159
243
|
tokens.push(...Object.keys(cfg.plugins));
|
|
160
244
|
}
|
|
161
245
|
if (cfg.rules && typeof cfg.rules === "object") {
|
|
162
|
-
|
|
163
|
-
const slash = key.indexOf("/");
|
|
164
|
-
if (slash > -1) tokens.push(key.slice(0, slash));
|
|
165
|
-
}
|
|
246
|
+
tokens.push(...extractRuleNamespaces(cfg.rules));
|
|
166
247
|
}
|
|
167
248
|
}
|
|
168
249
|
return tokens;
|
|
169
250
|
};
|
|
251
|
+
const recordFeature = (features, category, label) => {
|
|
252
|
+
if (category === "typescript") {
|
|
253
|
+
features.typescript = true;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
switch (category) {
|
|
257
|
+
case "extensions":
|
|
258
|
+
features.extensions.push(label);
|
|
259
|
+
break;
|
|
260
|
+
case "formats":
|
|
261
|
+
features.formats.push(label);
|
|
262
|
+
break;
|
|
263
|
+
case "frameworks":
|
|
264
|
+
features.frameworks.push(label);
|
|
265
|
+
break;
|
|
266
|
+
case "libraries":
|
|
267
|
+
features.libraries.push(label);
|
|
268
|
+
break;
|
|
269
|
+
case "testing":
|
|
270
|
+
features.testing.push(label);
|
|
271
|
+
break;
|
|
272
|
+
case "tools":
|
|
273
|
+
features.tools.push(label);
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
170
277
|
const extractFeatures = (configs, lintCommand, configFile) => {
|
|
171
278
|
const tokens = collectTokens(configs);
|
|
172
279
|
const features = {
|
|
173
|
-
|
|
280
|
+
configFile,
|
|
281
|
+
extensions: [],
|
|
282
|
+
formats: [],
|
|
174
283
|
frameworks: [],
|
|
175
|
-
testing: [],
|
|
176
|
-
tools: [],
|
|
177
284
|
libraries: [],
|
|
178
|
-
formats: [],
|
|
179
|
-
extensions: [],
|
|
180
285
|
lintCommand,
|
|
181
|
-
|
|
182
|
-
|
|
286
|
+
source: "config-file",
|
|
287
|
+
testing: [],
|
|
288
|
+
tools: [],
|
|
289
|
+
typescript: false
|
|
183
290
|
};
|
|
184
291
|
const seen = /* @__PURE__ */ new Set();
|
|
185
292
|
for (const [pattern, category, label] of FEATURE_MAP) {
|
|
@@ -187,11 +294,7 @@ const extractFeatures = (configs, lintCommand, configFile) => {
|
|
|
187
294
|
const matched = tokens.some((t) => t.includes(pattern));
|
|
188
295
|
if (!matched) continue;
|
|
189
296
|
seen.add(label);
|
|
190
|
-
|
|
191
|
-
features.typescript = true;
|
|
192
|
-
} else {
|
|
193
|
-
features[category].push(label);
|
|
194
|
-
}
|
|
297
|
+
recordFeature(features, category, label);
|
|
195
298
|
}
|
|
196
299
|
return features;
|
|
197
300
|
};
|
|
@@ -243,42 +346,36 @@ const analyzeEslintConfig = async (cwd) => {
|
|
|
243
346
|
const featuresFromDetection = (cwd) => {
|
|
244
347
|
const opts = detectProjectOptions(cwd);
|
|
245
348
|
const lintCommand = detectLintCommand(cwd);
|
|
246
|
-
const frameworks = (opts.detectedFrameworks ?? []).map((f) => DETECTED_FRAMEWORK_LABELS
|
|
349
|
+
const frameworks = (opts.detectedFrameworks ?? []).map((f) => DETECTED_FRAMEWORK_LABELS.get(f) ?? f);
|
|
247
350
|
return {
|
|
248
|
-
|
|
351
|
+
configFile: null,
|
|
352
|
+
extensions: (opts.extensions ?? []).map(toFeatureLabel),
|
|
353
|
+
formats: (opts.formats ?? []).map(toFeatureLabel),
|
|
249
354
|
frameworks,
|
|
250
|
-
testing: (opts.testing ?? []).map(toFeatureLabel),
|
|
251
|
-
tools: (opts.tools ?? []).map(toFeatureLabel),
|
|
252
355
|
libraries: (opts.libraries ?? []).map(toFeatureLabel),
|
|
253
|
-
formats: (opts.formats ?? []).map(toFeatureLabel),
|
|
254
|
-
extensions: (opts.extensions ?? []).map(toFeatureLabel),
|
|
255
356
|
lintCommand,
|
|
256
|
-
|
|
257
|
-
|
|
357
|
+
source: "detection-fallback",
|
|
358
|
+
testing: (opts.testing ?? []).map(toFeatureLabel),
|
|
359
|
+
tools: (opts.tools ?? []).map(toFeatureLabel),
|
|
360
|
+
typescript: opts.typescript === true
|
|
258
361
|
};
|
|
259
362
|
};
|
|
260
|
-
const
|
|
261
|
-
const {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
if (frameworks.length > 0)
|
|
265
|
-
if (testing.length > 0)
|
|
266
|
-
if (tools.length > 0)
|
|
267
|
-
if (libraries.length > 0)
|
|
268
|
-
if (formats.length > 0)
|
|
269
|
-
if (extensions.length > 0)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
- Use \`import type\` for type-only imports
|
|
275
|
-
- Avoid \`any\` \u2014 prefer \`unknown\` with type guards
|
|
276
|
-
- Prefer \`const\` over \`let\` whenever the binding is not reassigned
|
|
277
|
-
- Return types must be explicit on exported functions
|
|
278
|
-
` : "";
|
|
279
|
-
const frameworkSections = [];
|
|
363
|
+
const buildSummaryLines = (features) => {
|
|
364
|
+
const { extensions, formats, frameworks, libraries, testing, tools, typescript } = features;
|
|
365
|
+
const lines = [];
|
|
366
|
+
lines.push(`- **TypeScript**: ${typescript ? "enabled" : "disabled"}`);
|
|
367
|
+
if (frameworks.length > 0) lines.push(`- **Frameworks**: ${frameworks.join(", ")}`);
|
|
368
|
+
if (testing.length > 0) lines.push(`- **Testing**: ${testing.join(", ")}`);
|
|
369
|
+
if (tools.length > 0) lines.push(`- **Tools**: ${tools.join(", ")}`);
|
|
370
|
+
if (libraries.length > 0) lines.push(`- **Libraries**: ${libraries.join(", ")}`);
|
|
371
|
+
if (formats.length > 0) lines.push(`- **Formats**: ${formats.join(", ")}`);
|
|
372
|
+
if (extensions.length > 0) lines.push(`- **Extensions**: ${extensions.join(", ")}`);
|
|
373
|
+
return lines;
|
|
374
|
+
};
|
|
375
|
+
const buildFrameworkSections = (frameworks) => {
|
|
376
|
+
const sections = [];
|
|
280
377
|
if (frameworks.some((f) => f === "React" || f === "Next.js")) {
|
|
281
|
-
|
|
378
|
+
sections.push(`
|
|
282
379
|
### React / JSX
|
|
283
380
|
|
|
284
381
|
- Prefer function components over class components
|
|
@@ -288,7 +385,7 @@ const generateSkillContent = (features, format) => {
|
|
|
288
385
|
`);
|
|
289
386
|
}
|
|
290
387
|
if (frameworks.includes("Next.js")) {
|
|
291
|
-
|
|
388
|
+
sections.push(`
|
|
292
389
|
### Next.js
|
|
293
390
|
|
|
294
391
|
- Follow App Router conventions (Server Components by default, \`"use client"\` when needed)
|
|
@@ -297,7 +394,7 @@ const generateSkillContent = (features, format) => {
|
|
|
297
394
|
`);
|
|
298
395
|
}
|
|
299
396
|
if (frameworks.includes("Vue")) {
|
|
300
|
-
|
|
397
|
+
sections.push(`
|
|
301
398
|
### Vue
|
|
302
399
|
|
|
303
400
|
- Prefer Composition API (\`<script setup>\`) over Options API
|
|
@@ -305,14 +402,14 @@ const generateSkillContent = (features, format) => {
|
|
|
305
402
|
`);
|
|
306
403
|
}
|
|
307
404
|
if (frameworks.includes("Svelte")) {
|
|
308
|
-
|
|
405
|
+
sections.push(`
|
|
309
406
|
### Svelte
|
|
310
407
|
|
|
311
408
|
- Virtual \`*.svelte/*.ts\` files are handled by the ESLint config \u2014 do not manually adjust \`allowDefaultProject\`
|
|
312
409
|
`);
|
|
313
410
|
}
|
|
314
411
|
if (frameworks.includes("Astro")) {
|
|
315
|
-
|
|
412
|
+
sections.push(`
|
|
316
413
|
### Astro
|
|
317
414
|
|
|
318
415
|
- Virtual \`*.astro/*.ts\` files are handled by the ESLint config
|
|
@@ -320,13 +417,67 @@ const generateSkillContent = (features, format) => {
|
|
|
320
417
|
`);
|
|
321
418
|
}
|
|
322
419
|
if (frameworks.includes("Angular")) {
|
|
323
|
-
|
|
420
|
+
sections.push(`
|
|
324
421
|
### Angular
|
|
325
422
|
|
|
326
423
|
- Follow the Angular style guide component, directive, and service naming conventions
|
|
327
424
|
- Use standalone components by default; avoid NgModule unless required
|
|
328
425
|
`);
|
|
329
426
|
}
|
|
427
|
+
return sections;
|
|
428
|
+
};
|
|
429
|
+
const wrapWithFormat = (body, format) => {
|
|
430
|
+
if (format === "frontmatter") {
|
|
431
|
+
return `---
|
|
432
|
+
name: eslint-standards
|
|
433
|
+
description: >
|
|
434
|
+
Code quality standards enforced by @santi020k/eslint-config-basic. Follow these
|
|
435
|
+
conventions whenever writing or editing code in this project.
|
|
436
|
+
trigger: always_on
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
${body}`;
|
|
440
|
+
}
|
|
441
|
+
if (format === "cursor") {
|
|
442
|
+
return `---
|
|
443
|
+
description: >
|
|
444
|
+
ESLint code standards for this project. Apply these conventions when writing
|
|
445
|
+
or editing any source file.
|
|
446
|
+
globs:
|
|
447
|
+
- "**/*.ts"
|
|
448
|
+
- "**/*.tsx"
|
|
449
|
+
- "**/*.js"
|
|
450
|
+
- "**/*.jsx"
|
|
451
|
+
- "**/*.vue"
|
|
452
|
+
- "**/*.svelte"
|
|
453
|
+
- "**/*.astro"
|
|
454
|
+
alwaysApply: true
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
${body}`;
|
|
458
|
+
}
|
|
459
|
+
if (format === "kiro") {
|
|
460
|
+
return `---
|
|
461
|
+
inclusion: always
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
${body}`;
|
|
465
|
+
}
|
|
466
|
+
return body;
|
|
467
|
+
};
|
|
468
|
+
const generateSkillContent = (features, format) => {
|
|
469
|
+
const { frameworks, libraries, lintCommand, testing, tools, typescript } = features;
|
|
470
|
+
const summaryLines = buildSummaryLines(features);
|
|
471
|
+
const tsSection = typescript ? `
|
|
472
|
+
### TypeScript
|
|
473
|
+
|
|
474
|
+
- Use explicit type annotations on all exported functions and values
|
|
475
|
+
- Use \`import type\` for type-only imports
|
|
476
|
+
- Avoid \`any\` \u2014 prefer \`unknown\` with type guards
|
|
477
|
+
- Prefer \`const\` over \`let\` whenever the binding is not reassigned
|
|
478
|
+
- Return types must be explicit on exported functions
|
|
479
|
+
` : "";
|
|
480
|
+
const frameworkSections = buildFrameworkSections(frameworks);
|
|
330
481
|
const testingSections = [];
|
|
331
482
|
if (testing.some((t) => t === "Vitest" || t === "Jest")) {
|
|
332
483
|
testingSections.push(`
|
|
@@ -364,7 +515,8 @@ const generateSkillContent = (features, format) => {
|
|
|
364
515
|
}
|
|
365
516
|
const body = `# ESLint Code Standards
|
|
366
517
|
|
|
367
|
-
This project uses [\`@santi020k/eslint-config-basic\`](https://github.com/santi020k/eslint-config-basic) \u2014
|
|
518
|
+
This project uses [\`@santi020k/eslint-config-basic\`](https://github.com/santi020k/eslint-config-basic) \u2014
|
|
519
|
+
a composable ESLint 10+ Flat Config package.
|
|
368
520
|
|
|
369
521
|
**Always run \`${lintCommand}\` to validate your changes before finishing any task.**
|
|
370
522
|
|
|
@@ -376,7 +528,7 @@ ${summaryLines.join("\n")}
|
|
|
376
528
|
${tsSection}${frameworkSections.join("")}${testingSections.join("")}${toolSections.join("")}
|
|
377
529
|
### General
|
|
378
530
|
|
|
379
|
-
- ESLint
|
|
531
|
+
- ESLint 10 **Flat Config** format only \u2014 no \`.eslintrc\` files
|
|
380
532
|
- Use \`.js\` extensions on relative imports (ESM requirement)
|
|
381
533
|
- Use \`type\` imports for type-only values; regular imports for runtime values
|
|
382
534
|
|
|
@@ -392,36 +544,7 @@ If the command is unavailable, fall back to:
|
|
|
392
544
|
npx eslint .
|
|
393
545
|
\`\`\`
|
|
394
546
|
`;
|
|
395
|
-
|
|
396
|
-
return `---
|
|
397
|
-
name: eslint-standards
|
|
398
|
-
description: >
|
|
399
|
-
Code quality standards enforced by @santi020k/eslint-config-basic. Follow these
|
|
400
|
-
conventions whenever writing or editing code in this project.
|
|
401
|
-
trigger: always_on
|
|
402
|
-
---
|
|
403
|
-
|
|
404
|
-
${body}`;
|
|
405
|
-
}
|
|
406
|
-
if (format === "cursor") {
|
|
407
|
-
return `---
|
|
408
|
-
description: >
|
|
409
|
-
ESLint code standards for this project. Apply these conventions when writing
|
|
410
|
-
or editing any source file.
|
|
411
|
-
globs:
|
|
412
|
-
- "**/*.ts"
|
|
413
|
-
- "**/*.tsx"
|
|
414
|
-
- "**/*.js"
|
|
415
|
-
- "**/*.jsx"
|
|
416
|
-
- "**/*.vue"
|
|
417
|
-
- "**/*.svelte"
|
|
418
|
-
- "**/*.astro"
|
|
419
|
-
alwaysApply: true
|
|
420
|
-
---
|
|
421
|
-
|
|
422
|
-
${body}`;
|
|
423
|
-
}
|
|
424
|
-
return body;
|
|
547
|
+
return wrapWithFormat(body, format);
|
|
425
548
|
};
|
|
426
549
|
const writeSkillFile = (filePath, content, force) => {
|
|
427
550
|
if (existsSync(filePath) && !force) return false;
|
|
@@ -430,70 +553,107 @@ const writeSkillFile = (filePath, content, force) => {
|
|
|
430
553
|
return true;
|
|
431
554
|
};
|
|
432
555
|
const COPILOT_INSTRUCTIONS_PATH = ".github/copilot-instructions.md";
|
|
556
|
+
const AGENTS_MD_PATH = "AGENTS.md";
|
|
433
557
|
const COPILOT_SECTION_START = "<!-- eslint-standards:start -->";
|
|
434
558
|
const COPILOT_SECTION_END = "<!-- eslint-standards:end -->";
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
559
|
+
const buildGuardedSection = (body) => `${COPILOT_SECTION_START}
|
|
560
|
+
${body}
|
|
561
|
+
${COPILOT_SECTION_END}`;
|
|
562
|
+
const handleGuardedSectionFile = (filePath, body, force, check, createIfMissing = false) => {
|
|
563
|
+
const section = buildGuardedSection(body);
|
|
564
|
+
if (!existsSync(filePath)) {
|
|
565
|
+
if (!createIfMissing || check) return null;
|
|
566
|
+
writeFileSync(filePath, `# Agent instructions
|
|
567
|
+
|
|
568
|
+
${section}
|
|
569
|
+
`, "utf-8");
|
|
570
|
+
return "written";
|
|
571
|
+
}
|
|
438
572
|
const existing = readFileSync(filePath, "utf-8");
|
|
573
|
+
if (check) {
|
|
574
|
+
return existing.includes(section) ? "skipped" : "stale";
|
|
575
|
+
}
|
|
439
576
|
if (existing.includes(COPILOT_SECTION_START)) {
|
|
440
577
|
if (!force) return "skipped";
|
|
441
|
-
const updated = existing.
|
|
578
|
+
const updated = existing.replaceAll(
|
|
442
579
|
new RegExp(`${COPILOT_SECTION_START}[\\s\\S]*?${COPILOT_SECTION_END}`, "g"),
|
|
443
|
-
|
|
444
|
-
${body}
|
|
445
|
-
${COPILOT_SECTION_END}`
|
|
580
|
+
section
|
|
446
581
|
);
|
|
447
582
|
writeFileSync(filePath, updated, "utf-8");
|
|
448
583
|
return "written";
|
|
449
584
|
}
|
|
450
|
-
writeFileSync(
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
${COPILOT_SECTION_START}
|
|
454
|
-
${body}
|
|
455
|
-
${COPILOT_SECTION_END}
|
|
456
|
-
`,
|
|
457
|
-
"utf-8"
|
|
458
|
-
);
|
|
585
|
+
writeFileSync(filePath, `${existing}
|
|
586
|
+
${section}
|
|
587
|
+
`, "utf-8");
|
|
459
588
|
return "written";
|
|
460
589
|
};
|
|
590
|
+
const resolveSkillOptions = (opts) => ({
|
|
591
|
+
check: opts.check ?? false,
|
|
592
|
+
createAgentsMd: opts.createAgentsMd ?? false,
|
|
593
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
594
|
+
force: opts.force ?? false
|
|
595
|
+
});
|
|
461
596
|
const generateAgentSkills = async (opts = {}) => {
|
|
462
|
-
const
|
|
463
|
-
const force = opts.force ?? false;
|
|
597
|
+
const { check, createAgentsMd, cwd, force } = resolveSkillOptions(opts);
|
|
464
598
|
const written = [];
|
|
465
599
|
const skipped = [];
|
|
600
|
+
const stale = [];
|
|
466
601
|
const features = await analyzeEslintConfig(cwd) ?? featuresFromDetection(cwd);
|
|
467
602
|
const plainBody = generateSkillContent(features, "plain");
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
603
|
+
const recordGuardedResult = (filePath, result) => {
|
|
604
|
+
switch (result) {
|
|
605
|
+
case "skipped":
|
|
606
|
+
skipped.push(filePath);
|
|
607
|
+
break;
|
|
608
|
+
case "stale":
|
|
609
|
+
stale.push(filePath);
|
|
610
|
+
break;
|
|
611
|
+
case "written":
|
|
612
|
+
written.push(filePath);
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
const copilotPath = join(cwd, COPILOT_INSTRUCTIONS_PATH);
|
|
617
|
+
recordGuardedResult(copilotPath, handleGuardedSectionFile(copilotPath, plainBody, force, check));
|
|
618
|
+
const agentsMdPath = join(cwd, AGENTS_MD_PATH);
|
|
619
|
+
recordGuardedResult(agentsMdPath, handleGuardedSectionFile(agentsMdPath, plainBody, force, check, createAgentsMd));
|
|
471
620
|
for (const target of AGENT_TARGETS) {
|
|
472
621
|
const agentFolder = join(cwd, target.markerFolder);
|
|
473
622
|
if (!existsSync(agentFolder)) continue;
|
|
474
623
|
const subdir = target.skillSubdir === "." ? agentFolder : join(agentFolder, target.skillSubdir);
|
|
475
624
|
const filePath = join(subdir, target.skillFile);
|
|
476
625
|
const content = generateSkillContent(features, target.format);
|
|
626
|
+
if (check) {
|
|
627
|
+
const upToDate = existsSync(filePath) && readFileSync(filePath, "utf-8") === content;
|
|
628
|
+
if (upToDate) skipped.push(filePath);
|
|
629
|
+
else stale.push(filePath);
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
477
632
|
const didWrite = writeSkillFile(filePath, content, force);
|
|
478
633
|
if (didWrite) written.push(filePath);
|
|
479
634
|
else skipped.push(filePath);
|
|
480
635
|
}
|
|
481
|
-
return {
|
|
636
|
+
return { skipped, stale, written };
|
|
482
637
|
};
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
)
|
|
496
|
-
|
|
638
|
+
const resolveGenerateSkillFlags = (flags) => ({
|
|
639
|
+
check: flags.check ?? false,
|
|
640
|
+
createAgentsMd: flags.createAgentsMd ?? false
|
|
641
|
+
});
|
|
642
|
+
const outputGenerateSkillResults = (check, result, cwd) => {
|
|
643
|
+
if (check) {
|
|
644
|
+
for (const file of result.skipped) {
|
|
645
|
+
console.log(`\u2705 Up to date: ${file.replace(cwd + "/", "")}`);
|
|
646
|
+
}
|
|
647
|
+
for (const file of result.stale) {
|
|
648
|
+
console.log(`\u274C Stale or missing: ${file.replace(cwd + "/", "")}`);
|
|
649
|
+
}
|
|
650
|
+
if (result.stale.length > 0) {
|
|
651
|
+
console.log(`
|
|
652
|
+
\u26A0\uFE0F ${result.stale.length} skill file(s) are out of date. Run \`generate-skill --force\` to regenerate them.`);
|
|
653
|
+
process.exitCode = 1;
|
|
654
|
+
} else {
|
|
655
|
+
console.log("\n\u{1F389} All agent skill files are up to date!");
|
|
656
|
+
}
|
|
497
657
|
return;
|
|
498
658
|
}
|
|
499
659
|
for (const file of result.written) {
|
|
@@ -511,6 +671,23 @@ const handleGenerateSkill = async (cwd = process.cwd(), force = false) => {
|
|
|
511
671
|
console.log("\n\u{1F4A1} Tip: run with --force to overwrite existing skill files.");
|
|
512
672
|
}
|
|
513
673
|
};
|
|
674
|
+
const handleGenerateSkill = async (cwd = process.cwd(), force = false, flags = {}) => {
|
|
675
|
+
const { check, createAgentsMd } = resolveGenerateSkillFlags(flags);
|
|
676
|
+
console.log(check ? "\u{1F50D} Checking AI agent skill files..." : "\u{1F50D} Scanning for AI agent folders...");
|
|
677
|
+
const configFile = findEslintConfig(cwd);
|
|
678
|
+
if (configFile) {
|
|
679
|
+
console.log(`\u{1F4C4} Reading config from: ${configFile.replace(cwd + "/", "")}`);
|
|
680
|
+
} else {
|
|
681
|
+
console.log("\u26A0\uFE0F No eslint.config.js found \u2014 falling back to package.json detection.");
|
|
682
|
+
}
|
|
683
|
+
const result = await generateAgentSkills({ check, createAgentsMd, cwd, force });
|
|
684
|
+
if (result.written.length === 0 && result.skipped.length === 0 && result.stale.length === 0) {
|
|
685
|
+
console.log("\n\u26A0\uFE0F No agent folders found (.agent, .agents, .claude, .cursor, .windsurf, .copilot, .aider, .gemini, .clinerules, .roo, .kiro) and no AGENTS.md.");
|
|
686
|
+
console.log(" Create one of those folders first (or re-run with --create to scaffold AGENTS.md).");
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
outputGenerateSkillResults(check, result, cwd);
|
|
690
|
+
};
|
|
514
691
|
export {
|
|
515
692
|
AGENT_TARGETS,
|
|
516
693
|
analyzeEslintConfig,
|