mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-20 01:56:37 +03:00
Let hooks ignore-value scope a rule to files, and stop churning the config
Fallout from suppressing the overlay's font-size findings: the narrowest exception detector.ignoreValues supports was unreachable from the path the hook tells the model to use, so the guidance steered to the blunt instrument instead. - hook-admin's ignore-value now takes --file / --files / --file= / --files=, matching `impeccable ignores add-value`, which already had them. Without it the only file-scoped option was ignore-file, which silences every rule for a path permanently, including rules not yet written. - A bare wildcard value is now refused with a message pointing at either --file or ignore-rule. Previously `ignore-value <rule> "*"` quietly wrote a project-wide suppression from a single file's finding. - ignore-value keyed entries on rule+value only, so a second scope for the same rule overwrote the first instead of coexisting. Key on the file scope too. - An unknown flag folded into the value: `ignore-value overused-font Inter --shard` stored "inter --shard", matched nothing, and reported success. Reject it, as the sibling command does. Config churn: normalizeIgnoreValueEntries runs on every write and emitted keys as rule, value, files, reason, createdAt while the config on disk uses createdAt before reason. Any edit therefore rewrote every untouched entry (35 churned lines for a one-line change). Pin the canonical order in both copies of the normalizer and in ignores.mjs, and add a test that the two copies cannot drift apart. Also point the hook's own footer and reference/hooks.md at the file-scoped form first, and say plainly what ignore-file costs. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@
|
||||
* node hook-admin.mjs ignore-file <glob> # append to ignoreFiles
|
||||
* node hook-admin.mjs ignore-value <rule> <value> # append to shared ignoreValues
|
||||
* node hook-admin.mjs ignore-value <rule> <value> --local
|
||||
* node hook-admin.mjs ignore-value <rule> "*" --file <glob> # rule off in <glob> only
|
||||
* node hook-admin.mjs ignore-value <rule> "*" # refused: scope it or use ignore-rule
|
||||
* node hook-admin.mjs reset # remove all config + cache
|
||||
*
|
||||
* Designed to be invoked by the LLM from the reference/hooks.md flow.
|
||||
@@ -534,12 +536,13 @@ function addIgnoreFile(cwd, glob) {
|
||||
|
||||
function parseIgnoreValueArgs(args) {
|
||||
const positionals = [];
|
||||
const files = [];
|
||||
let shared = false;
|
||||
let local = false;
|
||||
let reason = '';
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
const arg = String(args[i] || '');
|
||||
if (arg === '--shared') {
|
||||
shared = true;
|
||||
} else if (arg === '--local') {
|
||||
@@ -550,8 +553,20 @@ function parseIgnoreValueArgs(args) {
|
||||
chunks.push(args[++i]);
|
||||
}
|
||||
reason = chunks.join(' ').trim();
|
||||
} else if (String(arg).startsWith('--reason=')) {
|
||||
reason = String(arg).slice('--reason='.length).trim();
|
||||
} else if (arg.startsWith('--reason=')) {
|
||||
reason = arg.slice('--reason='.length).trim();
|
||||
} else if (arg === '--file' || arg === '--files') {
|
||||
if (i + 1 >= args.length) throw new Error(`${arg} requires a glob`);
|
||||
files.push(String(args[++i]).trim());
|
||||
} else if (arg.startsWith('--file=')) {
|
||||
files.push(arg.slice('--file='.length).trim());
|
||||
} else if (arg.startsWith('--files=')) {
|
||||
files.push(arg.slice('--files='.length).trim());
|
||||
} else if (arg.startsWith('--')) {
|
||||
// Otherwise a typo folds into the value: `ignore-value overused-font Inter
|
||||
// --shard` stored the value "inter --shard", which matches no finding, and
|
||||
// reported success. Matches `impeccable ignores add-value`.
|
||||
throw new Error(`Unknown ignore-value flag: ${arg}`);
|
||||
} else {
|
||||
positionals.push(arg);
|
||||
}
|
||||
@@ -561,6 +576,7 @@ function parseIgnoreValueArgs(args) {
|
||||
return {
|
||||
rule: String(rule || '').trim().toLowerCase(),
|
||||
value: normalizeIgnoreValue(valueParts.join(' ')),
|
||||
files: Array.from(new Set(files.filter(Boolean))),
|
||||
shared,
|
||||
local,
|
||||
reason,
|
||||
@@ -577,10 +593,19 @@ function addIgnoreValue(cwd, args) {
|
||||
throw new Error('Pass only one scope flag: --shared or --local');
|
||||
}
|
||||
|
||||
// A bare `*` would suppress the rule everywhere, which is ignore-rule's job and
|
||||
// not what a finding in one file justifies. detector.ignoreValues honours a
|
||||
// `files` scope, so require one — matching `impeccable ignores add-value`.
|
||||
if (parsed.value === '*' && parsed.files.length === 0) {
|
||||
throw new Error(`Wildcard value ignores must be scoped with --file <glob>, e.g. ${IMPECCABLE_COMMAND} hooks ignore-value design-system-font-size "*" --file "src/widget.js". To suppress the rule project-wide use ${IMPECCABLE_COMMAND} hooks ignore-rule ${parsed.rule}.`);
|
||||
}
|
||||
|
||||
const local = parsed.local;
|
||||
const config = mergeDetectorConfig(readRawDetectorConfig(cwd, { local }));
|
||||
const key = `${parsed.rule}\0${parsed.value}`;
|
||||
const existing = config.ignoreValues.find((entry) => `${entry.rule}\0${entry.value}` === key);
|
||||
// Key on the file scope too: the same rule/value legitimately appears more than
|
||||
// once with different scopes, and a rule+value-only key overwrote them.
|
||||
const key = ignoreValueEntryKey({ rule: parsed.rule, value: parsed.value, files: parsed.files });
|
||||
const existing = config.ignoreValues.find((entry) => ignoreValueEntryKey(entry) === key);
|
||||
|
||||
if (existing) {
|
||||
if (parsed.reason) existing.reason = parsed.reason;
|
||||
@@ -588,15 +613,17 @@ function addIgnoreValue(cwd, args) {
|
||||
const entry = {
|
||||
rule: parsed.rule,
|
||||
value: parsed.value,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
if (parsed.files.length) entry.files = parsed.files;
|
||||
entry.createdAt = new Date().toISOString();
|
||||
if (parsed.reason) entry.reason = parsed.reason;
|
||||
config.ignoreValues.push(entry);
|
||||
}
|
||||
|
||||
const target = writeDetectorConfig(cwd, config, { local });
|
||||
const scope = local ? 'local detector.ignoreValues' : 'shared detector.ignoreValues';
|
||||
return `Added ${parsed.rule}=${parsed.value} to ${scope} (${path.relative(cwd, target) || target}).`;
|
||||
const scopeSuffix = parsed.files.length ? ` scoped to ${parsed.files.join(', ')}` : '';
|
||||
return `Added ${parsed.rule}=${parsed.value}${scopeSuffix} to ${scope} (${path.relative(cwd, target) || target}).`;
|
||||
}
|
||||
|
||||
function reset(cwd) {
|
||||
|
||||
Reference in New Issue
Block a user