Remove stale Claude MultiEdit matcher

Claude Code now folds multi-edit behavior into Edit, so keep generated and repaired hook manifests aligned with the current Edit and Write tools. Grok keeps its compatibility matcher unchanged.

AI assistance was used to implement and validate this change.
This commit is contained in:
Paul Bakaus
2026-08-19 09:28:28 -07:00
parent f88b2837a7
commit 7d5c60d291
7 changed files with 16 additions and 11 deletions
+2 -2
View File
@@ -1,9 +1,9 @@
{ {
"description": "Impeccable design detector: immediate-tier checks after Edit/Write/MultiEdit on UI files, full-rule deep pass on Stop.", "description": "Impeccable design detector: immediate-tier checks after Edit/Write on UI files, full-rule deep pass on Stop.",
"hooks": { "hooks": {
"PostToolUse": [ "PostToolUse": [
{ {
"matcher": "Edit|Write|MultiEdit", "matcher": "Edit|Write",
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
+1 -1
View File
@@ -2,7 +2,7 @@
"hooks": { "hooks": {
"PostToolUse": [ "PostToolUse": [
{ {
"matcher": "Edit|Write|MultiEdit", "matcher": "Edit|Write",
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
+3 -3
View File
@@ -137,9 +137,9 @@ const GROK_PROJECT_HOOK = '.grok/skills/impeccable/scripts/hook.mjs';
export function buildClaudeSettingsManifest() { export function buildClaudeSettingsManifest() {
return { return {
description: 'Impeccable design detector: immediate-tier checks after Edit/Write/MultiEdit on UI files, full-rule deep pass on Stop.', description: 'Impeccable design detector: immediate-tier checks after Edit/Write on UI files, full-rule deep pass on Stop.',
hooks: buildClaudeCompatibleHooks( hooks: buildClaudeCompatibleHooks(
'Edit|Write|MultiEdit', 'Edit|Write',
CLAUDE_PROJECT_HOOK, CLAUDE_PROJECT_HOOK,
SYSTEM_MESSAGE_NOTICE, SYSTEM_MESSAGE_NOTICE,
), ),
@@ -155,7 +155,7 @@ export function buildClaudeSettingsManifest() {
export function buildClaudePluginHooksManifest() { export function buildClaudePluginHooksManifest() {
return { return {
hooks: buildClaudeCompatibleHooks( hooks: buildClaudeCompatibleHooks(
'Edit|Write|MultiEdit', 'Edit|Write',
CLAUDE_PLUGIN_HOOK, CLAUDE_PLUGIN_HOOK,
SYSTEM_MESSAGE_NOTICE, SYSTEM_MESSAGE_NOTICE,
), ),
+1 -1
View File
@@ -44,7 +44,7 @@ The first argument is the action. Defaults to `status`.
``` ```
3. If `<action>` is `off`, follow up with a one-line note: "Done. New edits will not trigger the design hook in this project until you run `{{command_prefix}}impeccable hooks on`." 3. If `<action>` is `off`, follow up with a one-line note: "Done. New edits will not trigger the design hook in this project until you run `{{command_prefix}}impeccable hooks on`."
4. If `<action>` is `on`, follow up with: "Done. The design hook will fire after the next Edit/Write/MultiEdit on a UI file." 4. If `<action>` is `on`, follow up with: "Done. The design hook will fire after the next Edit/Write on a UI file."
5. If `<action>` is `ignore-value`, `ignore-file`, or `ignore-rule`, just print the script output. The default scope is shared `.impeccable/config.json`; add `--local` only when the user explicitly asks for a private exception. 5. If `<action>` is `ignore-value`, `ignore-file`, or `ignore-rule`, just print the script output. The default scope is shared `.impeccable/config.json`; add `--local` only when the user explicitly asks for a private exception.
6. If `<action>` is `status`, just print the script output. Do not add commentary unless the user asked a follow-up question. 6. If `<action>` is `status`, just print the script output. Do not add commentary unless the user asked a follow-up question.
+2 -2
View File
@@ -75,11 +75,11 @@ const HOOK_MANIFEST_TARGETS = [
destRel: '.claude/settings.local.json', destRel: '.claude/settings.local.json',
sharedDestRel: '.claude/settings.json', sharedDestRel: '.claude/settings.json',
manifest: () => ({ manifest: () => ({
description: 'Impeccable design detector: immediate-tier checks after Edit/Write/MultiEdit on UI files, full-rule deep pass on Stop.', description: 'Impeccable design detector: immediate-tier checks after Edit/Write on UI files, full-rule deep pass on Stop.',
hooks: { hooks: {
PostToolUse: [ PostToolUse: [
{ {
matcher: 'Edit|Write|MultiEdit', matcher: 'Edit|Write',
hooks: [ hooks: [
{ {
type: 'command', type: 'command',
+3 -2
View File
@@ -72,7 +72,8 @@ describe('hook manifest builders', () => {
const group = manifest.hooks.PostToolUse[0]; const group = manifest.hooks.PostToolUse[0];
const handler = group.hooks[0]; const handler = group.hooks[0];
assert.equal(group.matcher, 'Edit|Write|MultiEdit'); assert.equal(group.matcher, 'Edit|Write');
assert.doesNotMatch(manifest.description, /MultiEdit/);
assert.equal(handler.type, 'command'); assert.equal(handler.type, 'command');
assert.equal(handler.timeout, 5); assert.equal(handler.timeout, 5);
assert.equal(handler.statusMessage, 'Checking UI changes'); assert.equal(handler.statusMessage, 'Checking UI changes');
@@ -356,7 +357,7 @@ describe('generated hook artifacts in repo', () => {
assert.equal(manifest.description, undefined); assert.equal(manifest.description, undefined);
const handler = manifest.hooks.PostToolUse[0].hooks[0]; const handler = manifest.hooks.PostToolUse[0].hooks[0];
assert.equal(manifest.hooks.PostToolUse[0].matcher, 'Edit|Write|MultiEdit'); assert.equal(manifest.hooks.PostToolUse[0].matcher, 'Edit|Write');
expectCommand(handler.command, 'skills/impeccable/scripts/hook.mjs'); expectCommand(handler.command, 'skills/impeccable/scripts/hook.mjs');
// Resolves relative to the installed plugin, not a `.claude/skills/` layout. // Resolves relative to the installed plugin, not a `.claude/skills/` layout.
assert.ok(handler.command.includes('${CLAUDE_PLUGIN_ROOT}'), assert.ok(handler.command.includes('${CLAUDE_PLUGIN_ROOT}'),
+4
View File
@@ -947,6 +947,10 @@ describe('hook-admin.mjs', () => {
// impeccable entry must have been stripped, not accumulated. // impeccable entry must have been stripped, not accumulated.
assert.equal(claude.split('skills/impeccable/scripts/hook.mjs').length - 1, 2); assert.equal(claude.split('skills/impeccable/scripts/hook.mjs').length - 1, 2);
assert.match(claude, /"Stop"/); assert.match(claude, /"Stop"/);
const claudeManifest = JSON.parse(claude);
const impeccableGroup = claudeManifest.hooks.PostToolUse.find((group) =>
group.hooks?.some((hook) => hook.command?.includes('skills/impeccable/scripts/hook.mjs')));
assert.equal(impeccableGroup.matcher, 'Edit|Write');
const codex = fs.readFileSync(path.join(cwd, '.codex', 'hooks.json'), 'utf-8'); const codex = fs.readFileSync(path.join(cwd, '.codex', 'hooks.json'), 'utf-8');
assert.match(codex, /\.agents\/skills\/impeccable\/scripts\/hook\.mjs/); assert.match(codex, /\.agents\/skills\/impeccable\/scripts\/hook\.mjs/);