eslint-plugin-kerfjs 4.4.1 → 5.0.0-beta.3
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/README.md +1 -1
- package/docs/rules/ai-assistant-configs.md +4 -3
- package/index.js +1 -1
- package/lib/rules/ai-assistant-configs.js +36 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ npm install
|
|
|
83
83
|
npm test
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
The AST rules' test suites use ESLint's `RuleTester` with `@typescript-eslint/parser`. The `ai-assistant-configs` tests are filesystem-driven — they build temp project roots with fixture `node_modules/kerfjs/ai/` bundles
|
|
86
|
+
The AST rules' test suites use ESLint's `RuleTester` with `@typescript-eslint/parser`. The `ai-assistant-configs` tests are filesystem-driven — they build temp project roots with fixture `node_modules/kerfjs/ai/` bundles, drive the classifier directly, and use the real ESLint API to prove plain lint is read-only while explicit `--fix` performs the external write.
|
|
87
87
|
|
|
88
88
|
## License
|
|
89
89
|
|
|
@@ -18,7 +18,7 @@ The rule resolves `kerfjs/ai/manifest.json` from the consumer's installed `kerfj
|
|
|
18
18
|
A triggered file is then classified into one of three reported states (a fourth, "up-to-date," is silent):
|
|
19
19
|
|
|
20
20
|
- **Missing** — the consumer's `dest` doesn't exist.
|
|
21
|
-
- **Stale** — exists, parses cleanly,
|
|
21
|
+
- **Stale** — exists, parses cleanly, its `kerf-skill-version` line is older than the bundle's, and its canonical-section hash matches that version in the manifest's history.
|
|
22
22
|
- **Forked** — the consumer's file no longer matches the canonical layout: marker is missing, marker appears more than once, `kerf-skill-version` line is missing, or the content above the marker has been edited.
|
|
23
23
|
|
|
24
24
|
## ❌ Reported
|
|
@@ -46,7 +46,7 @@ For **missing** files, `--fix` copies the bundled canonical (`node_modules/kerfj
|
|
|
46
46
|
|
|
47
47
|
For **stale** files, `--fix` replaces only the content **above and including the `KERF-APP-CANONICAL-END` marker**. Everything below the marker — the consumer's append zone — is preserved byte-for-byte. This is the "versioned-section preservation" strategy from the design doc.
|
|
48
48
|
|
|
49
|
-
The `fix()` callback writes to a file OTHER than the linted source — unusual for an ESLint rule. ESLint
|
|
49
|
+
The `fix()` callback writes to a file OTHER than the linted source — unusual for an ESLint rule. ESLint evaluates fix callbacks during ordinary lint too, so the callback gates this external write on an explicit CLI `--fix` flag. Plain `eslint` reports the warning without touching disk; `eslint --fix` opts into installation or update.
|
|
50
50
|
|
|
51
51
|
## ❗ Forked — no auto-fix
|
|
52
52
|
|
|
@@ -55,7 +55,8 @@ If the consumer has edited the file in a way that breaks the canonical/append-zo
|
|
|
55
55
|
- **No marker present.** The file pre-dates the marker convention, the consumer deleted the marker, or the file is a hand-written variant. Auto-fixing would either clobber legitimate customizations or leave the file in an ambiguous state.
|
|
56
56
|
- **Multiple markers.** A well-formed file has exactly one. Auto-fix would have to guess which boundary is real.
|
|
57
57
|
- **No `kerf-skill-version` line.** The staleness signal is missing; we can't tell what's there.
|
|
58
|
-
- **Content above the marker has been edited.** The contract is "above the marker is kerf's; below is yours."
|
|
58
|
+
- **Content above the marker has been edited.** The contract is "above the marker is kerf's; below is yours." The rule checks stale files against the historical canonical hash for their own version, so an edited old section is preserved as a deliberate fork rather than overwritten.
|
|
59
|
+
- **Canonical history unavailable.** An old version absent from the shipped manifest history cannot be proven untouched, so the rule conservatively refuses to overwrite it.
|
|
59
60
|
|
|
60
61
|
Resolution: either restore the canonical layout (move customizations below a freshly-inserted marker, delete extras, re-add the version line) or disable the rule for this project — `'kerfjs/ai-assistant-configs': 'off'` in `eslint.config.js`.
|
|
61
62
|
|
package/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import requireDataKeyInEach from './lib/rules/require-data-key-in-each.js';
|
|
|
8
8
|
import requireDelegateDisposer from './lib/rules/require-delegate-disposer.js';
|
|
9
9
|
|
|
10
10
|
const plugin = {
|
|
11
|
-
meta: { name: 'eslint-plugin-kerfjs', version: '0.
|
|
11
|
+
meta: { name: 'eslint-plugin-kerfjs', version: '5.0.0-beta.3' },
|
|
12
12
|
rules: {
|
|
13
13
|
'no-inline-jsx-event-handlers': noInlineJsxEventHandlers,
|
|
14
14
|
'no-raw-with-dynamic-arg': noRawWithDynamicArg,
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
* `KERF-APP-CANONICAL-END` marker; the consumer's append zone below the
|
|
9
9
|
* marker is preserved byte-for-byte (the "option 2" strategy from KF-217).
|
|
10
10
|
*
|
|
11
|
-
* Unusual for an ESLint rule: the `fix()` callback writes to a file
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Unusual for an ESLint rule: the `fix()` callback writes to a file OTHER than
|
|
12
|
+
* the linted source. ESLint evaluates fix callbacks during ordinary lint too,
|
|
13
|
+
* so the callback must explicitly gate that side effect on the CLI's `--fix`
|
|
14
|
+
* flag.
|
|
14
15
|
*/
|
|
15
16
|
import { createHash } from 'node:crypto';
|
|
16
17
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
@@ -118,12 +119,23 @@ export function classifyFile(file, bundleDir, cwd) {
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
const cmp = compareSemver(consumerVersion, file.version);
|
|
122
|
+
const consumerCanonicalHash = sha256(consumerCanonical);
|
|
121
123
|
if (cmp === 0) {
|
|
122
124
|
// Same version. Above-marker section should match the bundled sha256.
|
|
123
|
-
if (
|
|
125
|
+
if (consumerCanonicalHash === file.sha256) return { state: 'ok' };
|
|
124
126
|
return { state: 'forked', reason: 'content above marker has been edited' };
|
|
125
127
|
}
|
|
126
128
|
if (cmp < 0) {
|
|
129
|
+
const historicalHash = file.history?.[consumerVersion];
|
|
130
|
+
if (!historicalHash) {
|
|
131
|
+
return {
|
|
132
|
+
state: 'forked',
|
|
133
|
+
reason: `canonical hash for version ${consumerVersion} is unavailable`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (consumerCanonicalHash !== historicalHash) {
|
|
137
|
+
return { state: 'forked', reason: 'content above marker has been edited' };
|
|
138
|
+
}
|
|
127
139
|
return {
|
|
128
140
|
state: 'stale',
|
|
129
141
|
consumerVersion,
|
|
@@ -210,9 +222,9 @@ const meta = {
|
|
|
210
222
|
forked:
|
|
211
223
|
'{{tool}} drop-in at `{{dest}}` is forked: {{reason}}. Restore the canonical layout (one `KERF-APP-CANONICAL-END` marker, no edits above it) or disable this rule with `\'kerfjs/ai-assistant-configs\': \'off\'`.',
|
|
212
224
|
},
|
|
213
|
-
// Mark as fixable so
|
|
214
|
-
//
|
|
215
|
-
//
|
|
225
|
+
// Mark as fixable so the CLI exposes this rule under `--fix`. The callback
|
|
226
|
+
// writes to a separate file and returns null, so ESLint applies no edit to
|
|
227
|
+
// the linted source itself — see file header.
|
|
216
228
|
fixable: 'code',
|
|
217
229
|
};
|
|
218
230
|
|
|
@@ -225,10 +237,7 @@ function create(context) {
|
|
|
225
237
|
const claudeEnabled = options.claude !== false;
|
|
226
238
|
const cursorEnabled = options.cursor !== false;
|
|
227
239
|
// ESLint v9: `context.cwd` is a string. ESLint v8: `context.getCwd()`.
|
|
228
|
-
const cwd =
|
|
229
|
-
(typeof context.cwd === 'string' && context.cwd)
|
|
230
|
-
|| (typeof context.getCwd === 'function' && context.getCwd())
|
|
231
|
-
|| process.cwd();
|
|
240
|
+
const cwd = contextCwd(context);
|
|
232
241
|
const checked = runCheck(cwd);
|
|
233
242
|
CACHED_RESULT = checked;
|
|
234
243
|
if (!checked) return;
|
|
@@ -248,7 +257,7 @@ function create(context) {
|
|
|
248
257
|
messageId: 'missing',
|
|
249
258
|
data,
|
|
250
259
|
fix() {
|
|
251
|
-
applyFix(file, checked.bundleDir, cwd, '');
|
|
260
|
+
if (isFixCliRun()) applyFix(file, checked.bundleDir, cwd, '');
|
|
252
261
|
return null;
|
|
253
262
|
},
|
|
254
263
|
});
|
|
@@ -262,7 +271,9 @@ function create(context) {
|
|
|
262
271
|
bundledVersion: result.bundledVersion,
|
|
263
272
|
},
|
|
264
273
|
fix() {
|
|
265
|
-
|
|
274
|
+
if (isFixCliRun()) {
|
|
275
|
+
applyFix(file, checked.bundleDir, cwd, result.appendZone);
|
|
276
|
+
}
|
|
266
277
|
return null;
|
|
267
278
|
},
|
|
268
279
|
});
|
|
@@ -278,4 +289,16 @@ function create(context) {
|
|
|
278
289
|
};
|
|
279
290
|
}
|
|
280
291
|
|
|
292
|
+
function contextCwd(context) {
|
|
293
|
+
return (
|
|
294
|
+
(typeof context.cwd === 'string' && context.cwd)
|
|
295
|
+
|| (typeof context.getCwd === 'function' && context.getCwd())
|
|
296
|
+
|| process.cwd()
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function isFixCliRun() {
|
|
301
|
+
return process.argv.includes('--fix');
|
|
302
|
+
}
|
|
303
|
+
|
|
281
304
|
export default { meta, create };
|