mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 22:26:38 +03:00
Live: polling rework, source locks, preflight scaffolding, Vue previews
Carved out of #371, minus progressive publication. Everything here works against real project source the way main's Live already does: the agent writes variants into the file the browser loaded, HMR fires, Accept promotes and carbonizes. Nothing is staged anywhere. Poll lanes. Events now carry an explicit priority: accept/discard/exit ahead of manual_edit_apply/steer/carbonize_cleanup ahead of generate. A long generate can no longer sit in front of the Accept the user just clicked. leaseEvent claims its lease before awaiting, so a slow prepare cannot hand the same event to two pollers. Source locks. A per-file mutex around every accept and discard path, keyed on a digest of the absolute path. Staleness is decided by owner-pid liveness rather than mtime, so a wedged lock clears when its owner dies instead of after an arbitrary timeout, and a slow-but-live accept is never stolen from. Only the owning process can release a lock. Preflight scaffolding. The server runs live-wrap (or live-insert) before the poll returns and hands the result back as event.scaffold. That walk is measured at ~7.6s on a large repo; moving it off the agent's critical path removes a deterministic tool round trip without touching the generated design. Falls back cleanly to the agent running the helper itself. Vue previews. previewMode: "vue-component" for Nuxt/Vue targets, matching the existing Svelte component path: variants compile as real SFCs from a dev-only directory so the route is never rewritten during generation, and Vite mounts them without invalidating page state. Accept is the only route write. Includes a Vue attr tokenizer that normalizes shorthand bindings (@x, :x, #x) to their canonical forms. Accept hardening. Every thrown failure now returns mode: 'error' rather than an ambiguous unhandled result, so a real failure is never classified as a deliberate manual handoff and silently dropped. The marker search skips node_modules/.git/dist/build/.impeccable. Shared CLI arg parsing extracted to scripts/lib/cli-args.mjs. Assisted-by: Claude Code
This commit is contained in:
@@ -13,8 +13,6 @@
|
||||
* 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.
|
||||
@@ -536,13 +534,12 @@ 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 = String(args[i] || '');
|
||||
const arg = args[i];
|
||||
if (arg === '--shared') {
|
||||
shared = true;
|
||||
} else if (arg === '--local') {
|
||||
@@ -553,20 +550,8 @@ function parseIgnoreValueArgs(args) {
|
||||
chunks.push(args[++i]);
|
||||
}
|
||||
reason = chunks.join(' ').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 if (String(arg).startsWith('--reason=')) {
|
||||
reason = String(arg).slice('--reason='.length).trim();
|
||||
} else {
|
||||
positionals.push(arg);
|
||||
}
|
||||
@@ -576,7 +561,6 @@ function parseIgnoreValueArgs(args) {
|
||||
return {
|
||||
rule: String(rule || '').trim().toLowerCase(),
|
||||
value: normalizeIgnoreValue(valueParts.join(' ')),
|
||||
files: Array.from(new Set(files.filter(Boolean))),
|
||||
shared,
|
||||
local,
|
||||
reason,
|
||||
@@ -593,19 +577,10 @@ 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 }));
|
||||
// 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);
|
||||
const key = `${parsed.rule}\0${parsed.value}`;
|
||||
const existing = config.ignoreValues.find((entry) => `${entry.rule}\0${entry.value}` === key);
|
||||
|
||||
if (existing) {
|
||||
if (parsed.reason) existing.reason = parsed.reason;
|
||||
@@ -613,17 +588,15 @@ 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';
|
||||
const scopeSuffix = parsed.files.length ? ` scoped to ${parsed.files.join(', ')}` : '';
|
||||
return `Added ${parsed.rule}=${parsed.value}${scopeSuffix} to ${scope} (${path.relative(cwd, target) || target}).`;
|
||||
return `Added ${parsed.rule}=${parsed.value} to ${scope} (${path.relative(cwd, target) || target}).`;
|
||||
}
|
||||
|
||||
function reset(cwd) {
|
||||
|
||||
Reference in New Issue
Block a user