fix(observer): hook-resolver — split combined matchers (Edit|Write)
Final-review followup. .claude/settings.json uses regex-style combined matchers like "Edit|Write"; transcript writes per-tool PreToolUse:Edit. Split on | when building map so per-tool counts resolve. Also sync spec doc loadHookMap -> buildHookMap (impl name). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Brain Status (auto-generated)
|
||||
|
||||
Last updated: 2026-05-23T10:39:02.076Z
|
||||
Last updated: 2026-05-23T10:45:47.776Z
|
||||
|
||||
| Контролёр | Состояние | Детали |
|
||||
|---|---|---|
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
**`tools/observer-hook-resolver.mjs`** (новый, ~80 LoC, pure)
|
||||
|
||||
```js
|
||||
export function loadHookMap({ projectSettings, userSettings } = {})
|
||||
export function buildHookMap({ projectSettings, userSettings } = {})
|
||||
// Map<matcher, string[]>
|
||||
// matcher: "PreToolUse:Bash" | "UserPromptSubmit" | "SessionStart:startup" | ...
|
||||
// value: ["tools/observer-stop-hook.mjs", "inline:claude-md-guard-7f3a", ...]
|
||||
@@ -117,7 +117,7 @@ Stop-hook → parser.parseTranscript(transcriptText)
|
||||
├─ collectToolUse → skills[], counts, errors
|
||||
├─ extractProcessEvents
|
||||
│ ├─ hookCounts (matcher) ← из attachment.hookName
|
||||
│ ├─ resolveScriptCounts(hookCounts, hookResolver.loadHookMap())
|
||||
│ ├─ resolveScriptCounts(hookCounts, hookResolver.buildHookMap())
|
||||
│ └─ event hook_fired = { counts, scripts, errors }
|
||||
└─ primary_rationale
|
||||
└─ recommended_node = skills.length === 0
|
||||
|
||||
@@ -57,14 +57,21 @@ export function buildHookMap(projectSettings = {}, userSettings = {}) {
|
||||
if (!Array.isArray(entries)) continue;
|
||||
for (const entry of entries) {
|
||||
if (!entry || typeof entry !== 'object') continue;
|
||||
const matcher = entry.matcher ? `${event}:${entry.matcher}` : event;
|
||||
const scripts = Array.isArray(entry.hooks) ? entry.hooks : [];
|
||||
const existing = map.get(matcher) || [];
|
||||
const scriptNames = [];
|
||||
for (const h of scripts) {
|
||||
if (!h || h.type !== 'command') continue;
|
||||
existing.push(extractScriptName(h.command));
|
||||
scriptNames.push(extractScriptName(h.command));
|
||||
}
|
||||
if (scriptNames.length === 0) continue;
|
||||
const matcherKeys = entry.matcher
|
||||
? String(entry.matcher).split('|').map((t) => `${event}:${t.trim()}`).filter(Boolean)
|
||||
: [event];
|
||||
for (const matcher of matcherKeys) {
|
||||
const existing = map.get(matcher) || [];
|
||||
existing.push(...scriptNames);
|
||||
map.set(matcher, existing);
|
||||
}
|
||||
map.set(matcher, existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,33 @@ describe('buildHookMap', () => {
|
||||
const map = buildHookMap(project, user);
|
||||
expect(map.get('PreToolUse:Bash')).toEqual(['tools/a.mjs', 'tools/b.mjs']);
|
||||
});
|
||||
|
||||
it('splits combined matcher "Edit|Write" into two map entries', () => {
|
||||
const settings = {
|
||||
hooks: {
|
||||
PreToolUse: [
|
||||
{ matcher: 'Edit|Write', hooks: [{ type: 'command', command: 'node tools/guard.mjs' }] },
|
||||
],
|
||||
},
|
||||
};
|
||||
const map = buildHookMap(settings);
|
||||
expect(map.get('PreToolUse:Edit')).toEqual(['tools/guard.mjs']);
|
||||
expect(map.get('PreToolUse:Write')).toEqual(['tools/guard.mjs']);
|
||||
expect(map.get('PreToolUse:Edit|Write')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('trims whitespace around matchers split on |', () => {
|
||||
const settings = {
|
||||
hooks: {
|
||||
PreToolUse: [
|
||||
{ matcher: 'Edit | Write', hooks: [{ type: 'command', command: 'node tools/g.mjs' }] },
|
||||
],
|
||||
},
|
||||
};
|
||||
const map = buildHookMap(settings);
|
||||
expect(map.get('PreToolUse:Edit')).toEqual(['tools/g.mjs']);
|
||||
expect(map.get('PreToolUse:Write')).toEqual(['tools/g.mjs']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveScriptCounts', () => {
|
||||
|
||||
Reference in New Issue
Block a user