solid2-agent-kit 0.4.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lightsound
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # solid2-agent-kit
2
+
3
+ Teach AI coding agents to write correct **Solid 2.0** — not React, and not Solid 1.x.
4
+
5
+ LLMs are heavily biased toward React when writing TSX, and their Solid knowledge is mostly
6
+ Solid 1.x, which is incompatible with Solid 2.0. This kit installs layered guardrails into a
7
+ project so agents stop destructuring props, reaching for `useEffect`/`createResource`, or
8
+ writing `className` — and start writing idiomatic Solid 2.0.
9
+
10
+ Supported agents: **Cursor** and **Claude Code**.
11
+
12
+ ## What it installs
13
+
14
+ | Layer | Cursor | Claude Code | Role |
15
+ |---|---|---|---|
16
+ | Always-applied hard rules | `.cursor/rules/solid-2.mdc` (glob-attached to `*.tsx`/`*.jsx`) | managed block in `CLAUDE.md` | 23 hard rules + banned Solid 1.x API table + eslint-plugin-solid lint-heritage table. Injected without the agent having to decide to read anything |
17
+ | Agent skill | `.cursor/skills/solid-2/` | `.claude/skills/solid-2/` | Execution-model primer, decision tables, canonical patterns, a “still runs / write the other form” table, review checklist, official-doc URL index |
18
+ | Shared agent context | managed block in `AGENTS.md` | same (Claude Code users can reference it from `CLAUDE.md`) | Core principles and pointers, always in context |
19
+ | Mechanical gate | `solid2-kit check` | same | Whole-file regex guard over `src/**/*.{ts,tsx}`: fails on props destructuring (including multi-line signatures), `{ ...props }` rest copies, `{list().map(...)}` in JSX, React imports/hooks/JSX props/`React.lazy`, Solid 1.x imports/APIs/components/JSX namespaces, `vite-plugin-solid`, old-router `<Route>`/`<HashRouter>`, `MetaProvider`, `Context.Provider`, camelCase style keys, `key` props, `value()!` hand-narrowing, hand-rolled loading signals, and `=== undefined` readiness branches |
20
+
21
+ Managed blocks are delimited with `<!-- solid2-agent-kit:*:start/end -->` markers; everything
22
+ outside them is yours. Kit-owned files (`solid-2.mdc`, the skill directories) are overwritten
23
+ on sync.
24
+
25
+ ## Usage
26
+
27
+ GitHub is the canonical distribution channel (the kit is intentionally not published to
28
+ npm; consumers pin a commit through their lockfile):
29
+
30
+ ```sh
31
+ # install guidance for both Cursor and Claude Code (default)
32
+ npx github:lightsound/solid2-agent-kit init
33
+
34
+ # one tool only
35
+ npx github:lightsound/solid2-agent-kit init --cursor
36
+ npx github:lightsound/solid2-agent-kit init --claude
37
+
38
+ # run the mechanical pattern gate (default source dir: src)
39
+ npx github:lightsound/solid2-agent-kit check
40
+ npx github:lightsound/solid2-agent-kit check --dir app
41
+
42
+ # pull in kit updates later (idempotent; replaces managed blocks and kit-owned files)
43
+ npx github:lightsound/solid2-agent-kit sync
44
+ ```
45
+
46
+ So that agents (and CI) can run the gate by name, add the kit as a devDependency and wire a
47
+ script in the consuming project's `package.json` (with a local install, `npx`/`bunx`
48
+ resolve the project-local bin, so the bare name is safe there):
49
+
50
+ ```json
51
+ {
52
+ "devDependencies": { "solid2-agent-kit": "github:lightsound/solid2-agent-kit" },
53
+ "scripts": { "lint:solid": "solid2-agent-kit check" }
54
+ }
55
+ ```
56
+
57
+ Update later with `bun update solid2-agent-kit` (or the npm/pnpm equivalent) followed by
58
+ `npx solid2-agent-kit sync`. Outside a project that has the kit installed, always use the
59
+ `npx github:...` form — the bare package name is not claimed on the npm registry.
60
+
61
+ ## Why not eslint-plugin-solid?
62
+
63
+ `eslint-plugin-solid` was built for Solid 1.x. Its analyzer predates the Solid 2 model and
64
+ flags correct Solid 2 idioms — two-phase `createEffect(compute, apply)`, writable derivations
65
+ (`createSignal(fn)`), draft store setters — steering agents back toward 1.x patterns. This
66
+ kit carries over the plugin's still-valid intents (`no-destructure`,
67
+ `components-return-once`, `reactivity`, `prefer-for`, `prefer-show`, `no-innerhtml`,
68
+ `style-prop`, ...) as documented rules plus a regex gate, without the 1.x false positives.
69
+
70
+ ## Docs-drift CI
71
+
72
+ Solid 2.0 is young and its docs move. A weekly GitHub Actions job
73
+ (`.github/workflows/docs-drift.yml`) fetches the official docs corpus
74
+ ([llms-full.txt](https://v2-rebuild--solid-docs-v2.netlify.app/llms-full.txt)) and verifies
75
+ that every API the kit recommends still exists: `create*` primitives are auto-extracted from
76
+ the kit content, the rest come from an explicit list in `scripts/check-docs-drift.mjs`.
77
+ Matching requires code-like contexts (backticked, import/JSX position, or a call), so
78
+ common-word APIs such as `merge` or `action` cannot pass on prose alone. If Solid renames or
79
+ removes an API, the job fails and the kit content gets fixed before it teaches agents stale
80
+ names. The same workflow runs `tests/check.test.mjs` so the mechanical gate's new patterns
81
+ keep matching.
82
+
83
+ Note: the canonical docs site https://v2.solidjs.com/ sits behind bot protection, so the kit
84
+ (and the guidance it installs) points agents at the official markdown mirror
85
+ `https://v2-rebuild--solid-docs-v2.netlify.app` (`llms.txt` index; every page served as
86
+ markdown with an `.md` suffix).
87
+
88
+ ## Layout
89
+
90
+ ```
91
+ bin/solid2-kit.mjs CLI: init / sync / check
92
+ files/shared/rules-body.md hard rules (rendered into .mdc and CLAUDE.md)
93
+ files/shared/agents-section.md AGENTS.md managed-block content
94
+ files/skills/solid-2/ SKILL.md + references/official-docs.md
95
+ scripts/check-docs-drift.mjs weekly docs cross-check
96
+ tests/ mechanical-gate fixtures (`npm test`)
97
+ ```
98
+
99
+ ## License
100
+
101
+ MIT
@@ -0,0 +1,368 @@
1
+ #!/usr/bin/env node
2
+ // solid2-agent-kit CLI — installs and maintains Solid 2.0 guidance for AI
3
+ // coding agents (Cursor and Claude Code), and runs a mechanical pattern gate.
4
+ //
5
+ // solid2-kit init [--cursor] [--claude] [--target <dir>]
6
+ // solid2-kit sync [--cursor] [--claude] [--target <dir>] (alias of init)
7
+ // solid2-kit check [--dir <srcdir>] [--target <dir>]
8
+ //
9
+ // `init`/`sync` are idempotent: kit-owned files are overwritten and managed
10
+ // blocks in AGENTS.md / CLAUDE.md are replaced in place.
11
+
12
+ import {
13
+ cpSync,
14
+ existsSync,
15
+ mkdirSync,
16
+ readFileSync,
17
+ readdirSync,
18
+ rmSync,
19
+ writeFileSync,
20
+ } from 'node:fs';
21
+ import { dirname, join, relative, resolve } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+
24
+ const KIT_ROOT = fileURLToPath(new URL('..', import.meta.url));
25
+ const VERSION = JSON.parse(readFileSync(join(KIT_ROOT, 'package.json'), 'utf8')).version;
26
+
27
+ const args = process.argv.slice(2);
28
+ const command = args[0];
29
+
30
+ function flagValue(name, fallback) {
31
+ const index = args.indexOf(name);
32
+ return index === -1 || index + 1 >= args.length ? fallback : args[index + 1];
33
+ }
34
+
35
+ // --- init / sync ------------------------------------------------------------
36
+
37
+ function countOccurrences(haystack, needle) {
38
+ return haystack.split(needle).length - 1;
39
+ }
40
+
41
+ function upsertManagedBlock(filePath, blockId, body, createPreamble) {
42
+ const start = `<!-- solid2-agent-kit:${blockId}:start -->`;
43
+ const end = `<!-- solid2-agent-kit:${blockId}:end -->`;
44
+ const note = `<!-- Managed by solid2-agent-kit v${VERSION}. Do not edit inside this block; run \`solid2-kit sync\` to update. -->`;
45
+ const block = `${start}\n${note}\n\n${body.trim()}\n\n${end}`;
46
+
47
+ let content = existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
48
+ const starts = countOccurrences(content, start);
49
+ const ends = countOccurrences(content, end);
50
+
51
+ if (starts === 1 && ends === 1) {
52
+ // Function replacement: a plain string would have `$&`/`$'`-style
53
+ // sequences in the body interpreted as replacement patterns.
54
+ content = content.replace(new RegExp(`${start}[\\s\\S]*?${end}`), () => block);
55
+ } else if (starts === 0 && ends === 0) {
56
+ const base = content.trim() ? content.trimEnd() : (createPreamble ?? '').trimEnd();
57
+ content = `${base}${base ? '\n\n' : ''}${block}\n`;
58
+ } else {
59
+ // Malformed markers (a stray delete or a duplicated block). Touching the
60
+ // file could swallow user content between mismatched markers, so leave
61
+ // it alone and ask for a manual fix.
62
+ console.warn(
63
+ `solid2-kit — warning: malformed managed-block markers in ${filePath} (${starts} start / ${ends} end for "${blockId}"). File left untouched; repair the markers and re-run sync.`,
64
+ );
65
+ return null;
66
+ }
67
+ writeFileSync(filePath, content);
68
+ return filePath;
69
+ }
70
+
71
+ function writeKitOwnedFile(filePath, body) {
72
+ mkdirSync(dirname(filePath), { recursive: true });
73
+ writeFileSync(filePath, body);
74
+ return filePath;
75
+ }
76
+
77
+ function copySkill(targetSkillDir) {
78
+ // The skill directory is kit-owned: remove it first so files deleted in
79
+ // newer kit versions do not linger after a sync.
80
+ rmSync(targetSkillDir, { recursive: true, force: true });
81
+ mkdirSync(targetSkillDir, { recursive: true });
82
+ cpSync(join(KIT_ROOT, 'files/skills/solid-2'), targetSkillDir, { recursive: true });
83
+ return targetSkillDir;
84
+ }
85
+
86
+ function init() {
87
+ const target = resolve(flagValue('--target', '.'));
88
+ const onlyCursor = args.includes('--cursor') && !args.includes('--claude');
89
+ const onlyClaude = args.includes('--claude') && !args.includes('--cursor');
90
+ const wantCursor = !onlyClaude;
91
+ const wantClaude = !onlyCursor;
92
+
93
+ const rulesBody = readFileSync(join(KIT_ROOT, 'files/shared/rules-body.md'), 'utf8');
94
+ const agentsSection = readFileSync(join(KIT_ROOT, 'files/shared/agents-section.md'), 'utf8');
95
+ const written = [];
96
+
97
+ if (wantCursor) {
98
+ const mdc = [
99
+ '---',
100
+ 'description: Solid 2.0 JSX and reactivity rules — prevents React and Solid 1.x patterns in TSX files',
101
+ 'globs: **/*.tsx,**/*.jsx',
102
+ 'alwaysApply: false',
103
+ '---',
104
+ '',
105
+ `<!-- Generated by solid2-agent-kit v${VERSION}. Do not edit; run \`solid2-kit sync\` to update. -->`,
106
+ '',
107
+ rulesBody.trim(),
108
+ '',
109
+ ].join('\n');
110
+ written.push(writeKitOwnedFile(join(target, '.cursor/rules/solid-2.mdc'), mdc));
111
+ written.push(copySkill(join(target, '.cursor/skills/solid-2')));
112
+ }
113
+
114
+ if (wantClaude) {
115
+ written.push(copySkill(join(target, '.claude/skills/solid-2')));
116
+ // Claude Code has no glob-attached rules mechanism; CLAUDE.md is always
117
+ // loaded, so the full rules body lives there in a managed block.
118
+ written.push(
119
+ upsertManagedBlock(
120
+ join(target, 'CLAUDE.md'),
121
+ 'solid-rules',
122
+ rulesBody,
123
+ '# CLAUDE.md\n\nProject guidance for Claude Code.',
124
+ ),
125
+ );
126
+ }
127
+
128
+ written.push(
129
+ upsertManagedBlock(
130
+ join(target, 'AGENTS.md'),
131
+ 'agents-section',
132
+ agentsSection,
133
+ '# AGENTS.md\n\nProject-specific context for coding agents.',
134
+ ),
135
+ );
136
+
137
+ console.log(`solid2-agent-kit v${VERSION} — installed for ${[wantCursor && 'Cursor', wantClaude && 'Claude Code'].filter(Boolean).join(' + ')}:`);
138
+ for (const path of written.filter(Boolean)) console.log(` ${relative(target, path) || '.'}`);
139
+ }
140
+
141
+ // --- check ------------------------------------------------------------------
142
+
143
+ const CHECKS = [
144
+ {
145
+ id: 'props-destructure-param',
146
+ pattern: /function\s+[A-Z]\w*\s*\(\s*\{|(?:const|let)\s+[A-Z]\w*\s*=\s*(?:async\s*)?\(\s*\{/g,
147
+ message:
148
+ 'Destructured component props read getters once at setup and break reactivity. Take a single `props` parameter and read `props.x` inside JSX, memos, or effect computes.',
149
+ },
150
+ {
151
+ id: 'props-destructure-body',
152
+ pattern: /\}\s*=\s*props\b/g,
153
+ message:
154
+ 'Destructuring `props` freezes the values. Keep `props.x` property accesses; name derivations instead: `const x = () => props.x`.',
155
+ },
156
+ {
157
+ id: 'react-import',
158
+ pattern: /from\s+['"]react(?:-dom)?(?:['"]|\/)/g,
159
+ message: 'React import in a Solid 2.0 codebase.',
160
+ },
161
+ {
162
+ id: 'solid1-import-path',
163
+ pattern: /from\s+['"]solid-js\/(?:store|web|h|html|universal|jsx-runtime|jsx-dev-runtime)['"]/g,
164
+ message:
165
+ 'Solid 1.x import path. Stores/merge/omit come from "solid-js"; render/hydrate/Portal/Dynamic come from "@solidjs/web".',
166
+ },
167
+ {
168
+ id: 'react-jsx-prop',
169
+ pattern: /\b(?:className|htmlFor)=/g,
170
+ message: 'React JSX prop. Use `class` / `for`.',
171
+ },
172
+ {
173
+ id: 'solid1-classlist',
174
+ pattern: /\bclassList=/g,
175
+ message: 'Solid 1.x `classList` was removed. Use the object/array form of `class`.',
176
+ },
177
+ {
178
+ id: 'react-hook',
179
+ pattern:
180
+ /(?<![.\w])use(?:State|Effect|Memo|Ref|Callback|Reducer|LayoutEffect|Transition|SyncExternalStore|ImperativeHandle|Id|DeferredValue|InsertionEffect)\s*\(/g,
181
+ message: 'React hook. See the primitive mapping in the solid-2 rules.',
182
+ },
183
+ {
184
+ id: 'solid1-api',
185
+ pattern:
186
+ /(?<![.\w])(?:createResource|onMount|createMutable|modifyMutable|mergeProps|splitProps|produce|unwrap|createComputed|createSelector|createDeferred|startTransition|batch|onError|catchError|createDynamic|renderToStringAsync|clearDelegatedEvents)\s*\(/g,
187
+ message:
188
+ 'Removed Solid 1.x API. Replacements: async createMemo, onSettled, createStore drafts, merge/omit/snapshot, automatic batching, Errored, dynamic(), renderToStream.',
189
+ },
190
+ {
191
+ id: 'solid1-component',
192
+ pattern: /<(?:Suspense|ErrorBoundary|SuspenseList|Index)[\s/>]/g,
193
+ message: 'Solid 1.x component. Use <Loading>, <Errored>, <Reveal>, <For keyed={false}>.',
194
+ },
195
+ {
196
+ id: 'context-provider',
197
+ pattern: /<\w+\.Provider\b/g,
198
+ message: 'Solid 2 contexts are their own provider: `<MyContext value={...}>`.',
199
+ },
200
+ {
201
+ // Matches a camelCase key in property position (right after `{{` or after a
202
+ // comma). Ternary branches inside values (`a ? fooBar : baz`) never sit in
203
+ // property position, so they are not flagged.
204
+ id: 'style-camelcase',
205
+ pattern: /style=\{\{\s*(?:[^{}]*,\s*)?[a-z]+[A-Z]\w*\s*:/g,
206
+ message:
207
+ 'camelCase style key. Solid style objects use CSS property names: `"background-color"` (and no automatic px).',
208
+ },
209
+ {
210
+ id: 'react-key-prop',
211
+ pattern: /\skey=\{/g,
212
+ message: 'React `key` prop has no meaning in Solid. Row identity belongs on <For keyed={...}>.',
213
+ },
214
+ {
215
+ // `{todos().map((t) => <Row />)}` renders then recreates every row.
216
+ // `children().toArray().map` does not match (method is toArray, not a bare accessor).
217
+ id: 'jsx-accessor-map',
218
+ pattern: /\{\s*[A-Za-z_$][\w$]*\(\)\.map\s*\(/g,
219
+ message:
220
+ '`.map()` over a reactive accessor in JSX recreates every row. Use <For each={...}> (and a key function for server/refetched rows).',
221
+ },
222
+ {
223
+ id: 'for-each-map',
224
+ pattern: /\beach=\{\s*[^}]*\.map\s*\(/g,
225
+ message:
226
+ '<For each={list().map(...)}> still rebuilds the array every run. Derive the list first, then pass that list to each={...}.',
227
+ },
228
+ {
229
+ id: 'props-rest-copy',
230
+ pattern: /=\s*\{\s*\.\.\.\s*props\b/g,
231
+ message:
232
+ '`{ ...props }` is a snapshot. Forward leftover props with omit(props, "key") (a reactive proxy), then JSX-spread that.',
233
+ },
234
+ {
235
+ // A non-null assertion on a zero-arg call is almost always a reactive read
236
+ // narrowed by hand (`error()!.message`). Common zero-arg stdlib methods
237
+ // that legitimately pair with `!` are excluded.
238
+ id: 'accessor-non-null',
239
+ pattern: /(?<!\.pop|\.shift|\.next)\(\)!/g,
240
+ message:
241
+ 'Non-null assertion on a zero-arg call. Narrow reactive reads with <Show when={...}> and its function child (narrowed accessor); use an explicit guard variable for non-reactive calls.',
242
+ },
243
+ {
244
+ id: 'manual-loading-signal',
245
+ pattern: /\[\s*(?:is)?[Ll]oading\s*,|\bset(?:Is)?Loading\s*\(/g,
246
+ message:
247
+ 'Hand-rolled loading state. First-load UI belongs to a <Loading> fallback, refetch indicators to isPending(), in-flight values to latest(). Model the async work as a computation (promise / async iterable), not signals.',
248
+ },
249
+ {
250
+ id: 'react-lazy',
251
+ pattern: /\bReact\.lazy\s*\(/g,
252
+ message: 'React.lazy. Use `lazy(() => import("./X"))` from "solid-js" and read it under <Loading>.',
253
+ },
254
+ {
255
+ id: 'lazy-then-wrapper',
256
+ pattern: /\blazy\(\s*\(\)[\s\S]{0,160}?\.then\s*\(/g,
257
+ message:
258
+ 'lazy().then((m) => ({ default: m.X })) breaks hydration. Pass { export: "X" } as the second argument: lazy(() => import("./mod"), { export: "X" }).',
259
+ },
260
+ {
261
+ id: 'dangerously-set-inner-html',
262
+ pattern: /\bdangerouslySetInnerHTML=/g,
263
+ message: 'React innerHTML prop. Use innerHTML={sanitizedHtml()} from Solid, never combined with JSX children.',
264
+ },
265
+ {
266
+ id: 'setter-as-handler',
267
+ pattern: /\bon[A-Z][A-Za-z]*=\{\s*set[A-Z]\w*\s*\}/g,
268
+ message:
269
+ 'Passing a setter as an event handler writes the event object. Wrap it: onClick={() => setCount((c) => c + 1)}.',
270
+ },
271
+ {
272
+ id: 'vite-plugin-solid',
273
+ pattern: /from\s+['"]vite-plugin-solid['"]/g,
274
+ message: 'Solid 1.x Vite plugin. Import `solid` from "@solidjs/vite-plugin".',
275
+ },
276
+ {
277
+ id: 'solid1-jsx-namespace',
278
+ pattern: /\b(?:use|on|oncapture|attr|bool):[A-Za-z][\w-]*=/g,
279
+ message:
280
+ 'Solid 1.x JSX namespace. Use ref callbacks (and directive factories), camelCase event props, and standard attributes.',
281
+ },
282
+ {
283
+ id: 'solid1-router',
284
+ pattern: /<(?:HashRouter|MemoryRouter|Route|Navigate)\b/g,
285
+ message:
286
+ 'Solid Router 0.x/1.x JSX. Define routes with createRouter({ routes }) and plain <a href={Router.paths...}>.',
287
+ },
288
+ {
289
+ id: 'meta-provider',
290
+ pattern: /<MetaProvider\b/g,
291
+ message: 'Solid Meta 1.x has no provider. Render <Title>/<Meta>/<Link> from "@solidjs/meta" anywhere.',
292
+ },
293
+ {
294
+ // A zero-arg call compared against undefined/null is a manual async
295
+ // readiness branch (`data() === undefined ? ... : ...`).
296
+ id: 'async-undefined-check',
297
+ pattern: /\(\)\s*[!=]==?\s*(?:undefined|null)\b|\b(?:undefined|null)\s*[!=]==?\s*\w+\(\)/g,
298
+ message:
299
+ 'Readiness branch on a zero-arg call. Read async values under <Loading> (first load) / isPending() (refetch) and let errors reach <Errored>. For non-reactive utils, assign to a variable and test that.',
300
+ },
301
+ ];
302
+
303
+ // .ts / .tsx sources, excluding .d.ts declaration files.
304
+ const SOURCE_FILE = /(?<!\.d)\.tsx?$/;
305
+
306
+ function* walk(dir) {
307
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
308
+ const path = join(dir, entry.name);
309
+ if (entry.isDirectory()) yield* walk(path);
310
+ else if (SOURCE_FILE.test(entry.name)) yield path;
311
+ }
312
+ }
313
+
314
+ function check() {
315
+ const target = resolve(flagValue('--target', '.'));
316
+ const srcDir = resolve(target, flagValue('--dir', 'src'));
317
+ if (!existsSync(srcDir)) {
318
+ console.error(`solid2-kit check — source directory not found: ${srcDir} (use --dir)`);
319
+ process.exit(2);
320
+ }
321
+
322
+ let findings = 0;
323
+ let files = 0;
324
+ for (const file of walk(srcDir)) {
325
+ files += 1;
326
+ const content = readFileSync(file, 'utf8');
327
+ const lines = content.split('\n');
328
+ for (const rule of CHECKS) {
329
+ for (const match of content.matchAll(rule.pattern)) {
330
+ findings += 1;
331
+ const lineNumber = content.slice(0, match.index).split('\n').length;
332
+ console.error(
333
+ `${relative(target, file)}:${lineNumber} [${rule.id}] ${rule.message}\n > ${lines[lineNumber - 1].trim()}`,
334
+ );
335
+ }
336
+ }
337
+ }
338
+
339
+ if (findings > 0) {
340
+ console.error(`\nsolid2-kit check — ${findings} finding(s) in ${files} file(s).`);
341
+ process.exit(1);
342
+ }
343
+ console.log(`solid2-kit check — OK (${files} files scanned).`);
344
+ }
345
+
346
+ // --- entry ------------------------------------------------------------------
347
+
348
+ switch (command) {
349
+ case 'init':
350
+ case 'sync':
351
+ init();
352
+ break;
353
+ case 'check':
354
+ check();
355
+ break;
356
+ default:
357
+ console.log(
358
+ [
359
+ `solid2-agent-kit v${VERSION}`,
360
+ '',
361
+ 'Usage:',
362
+ ' solid2-kit init [--cursor] [--claude] [--target <dir>] install/update guidance (default: both tools)',
363
+ ' solid2-kit sync [--cursor] [--claude] [--target <dir>] alias of init (idempotent)',
364
+ ' solid2-kit check [--dir <srcdir>] [--target <dir>] mechanical React/Solid 1.x pattern gate (default dir: src)',
365
+ ].join('\n'),
366
+ );
367
+ process.exit(command ? 2 : 0);
368
+ }
@@ -0,0 +1,6 @@
1
+ ## Solid 2.0 (not React, not Solid 1.x)
2
+
3
+ - All TSX in this project is Solid 2.0. Components run **once**; reactivity flows through signals/stores to JSX. React patterns (props destructuring, `className`, per-keystroke `onChange`, state-synced-by-effect, `.map()` lists) and Solid 1.x APIs (`createResource`, `onMount`, `solid-js/store`, `Suspense`, path setters) are bugs here.
4
+ - Before writing TSX, read the `solid-2` skill (`.cursor/skills/solid-2/SKILL.md` for Cursor, `.claude/skills/solid-2/SKILL.md` for Claude Code): patterns, decision tables, official doc URLs. Hard rules auto-attach from `.cursor/rules/solid-2.mdc` in Cursor and live in a managed block in `CLAUDE.md` for Claude Code.
5
+ - After editing TSX, run the `solid2-kit check` mechanical gate — it fails on React/Solid 1.x tokens and props destructuring. Use the project's `lint:solid` script if one exists; otherwise `npx solid2-agent-kit check` (with the kit as a devDependency) or `npx github:lightsound/solid2-agent-kit check`. Do not install `eslint-plugin-solid` (built for Solid 1.x; misreads Solid 2 idioms).
6
+ - Solid 2.0 shipped recently and breaks with 1.x — never trust pre-2.0 Solid docs, tutorials, or training knowledge. Verify APIs against https://v2-rebuild--solid-docs-v2.netlify.app/llms.txt (markdown mirror of https://v2.solidjs.com/, which blocks non-browser fetchers). If the project has `@solidjs/router`, server functions, or `@solidjs/meta`, use those official pages (see the skill's official-docs index) — not React Router, Next.js, SolidStart 1.x, or Solid Router 0.x/1.x JSX `<Route>`/`<A>`.