claude-dev-env 8.78.2 → 8.78.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.
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
import { test } from 'node:test';
|
|
2
2
|
import { strict as assert } from 'node:assert';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
3
4
|
|
|
4
|
-
import { mergeHooksIntoSettings, pruneManagedHooksFromSettings } from './install.mjs';
|
|
5
|
+
import { isManagedPromptHook, mergeHooksIntoSettings, pruneManagedHooksFromSettings } from './install.mjs';
|
|
6
|
+
|
|
7
|
+
const SHIPPED_HOOKS_CONFIG = JSON.parse(readFileSync(new URL('../hooks/hooks.json', import.meta.url), 'utf8'));
|
|
5
8
|
|
|
6
9
|
const REPLY_MATCHER = 'mcp__hearthbot__reply';
|
|
7
|
-
const
|
|
10
|
+
const EXAMPLE_HOME = '/home/example';
|
|
8
11
|
const USER_PROMPT_HOOK = { type: 'prompt', prompt: 'User check. $ARGUMENTS', statusMessage: 'my own check' };
|
|
9
12
|
|
|
13
|
+
function modelHookEntries(hooksConfig) {
|
|
14
|
+
return Object.entries(hooksConfig.hooks).flatMap(([eventName, matcherGroups]) => matcherGroups.flatMap(
|
|
15
|
+
eachGroup => (eachGroup.hooks ?? [])
|
|
16
|
+
.filter(eachHook => typeof eachHook.command !== 'string')
|
|
17
|
+
.map(eachHook => ({ eventName, matcher: eachGroup.matcher ?? '', hook: eachHook })),
|
|
18
|
+
));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function shippedReplyJudgeEntries() {
|
|
22
|
+
return modelHookEntries(SHIPPED_HOOKS_CONFIG).filter(
|
|
23
|
+
eachEntry => eachEntry.eventName === 'PreToolUse' && eachEntry.matcher.split('|').includes(REPLY_MATCHER),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const [SHIPPED_REPLY_JUDGE_ENTRY] = shippedReplyJudgeEntries();
|
|
28
|
+
assert.ok(SHIPPED_REPLY_JUDGE_ENTRY, 'the shipped hooks.json needs a claim judge on reply');
|
|
29
|
+
const MANAGED_STATUS = SHIPPED_REPLY_JUDGE_ENTRY.hook.statusMessage;
|
|
30
|
+
|
|
31
|
+
function mergeInto(settings, hooksConfig) {
|
|
32
|
+
mergeHooksIntoSettings(settings, hooksConfig, `${EXAMPLE_HOME}/.claude`, 'python3', null, EXAMPLE_HOME);
|
|
33
|
+
}
|
|
34
|
+
|
|
10
35
|
function hooksConfigWithPrompt(promptText) {
|
|
11
36
|
return {
|
|
12
37
|
hooks: {
|
|
@@ -23,14 +48,14 @@ function hooksConfigWithPrompt(promptText) {
|
|
|
23
48
|
function replyHooks(settings) {
|
|
24
49
|
return (settings.hooks?.PreToolUse ?? [])
|
|
25
50
|
.filter(eachGroup => eachGroup.matcher === REPLY_MATCHER)
|
|
26
|
-
.flatMap(eachGroup => eachGroup.hooks);
|
|
51
|
+
.flatMap(eachGroup => eachGroup.hooks ?? []);
|
|
27
52
|
}
|
|
28
53
|
|
|
29
54
|
test('should keep one managed prompt hook across reinstalls and carry the new prompt text', () => {
|
|
30
55
|
const settings = { hooks: { PreToolUse: [{ matcher: REPLY_MATCHER, hooks: [USER_PROMPT_HOOK] }] } };
|
|
31
56
|
|
|
32
|
-
|
|
33
|
-
|
|
57
|
+
mergeInto(settings, hooksConfigWithPrompt('First judge. $ARGUMENTS'));
|
|
58
|
+
mergeInto(settings, hooksConfigWithPrompt('Second judge. $ARGUMENTS'));
|
|
34
59
|
|
|
35
60
|
assert.deepEqual(
|
|
36
61
|
replyHooks(settings).map(eachHook => eachHook.prompt),
|
|
@@ -40,9 +65,41 @@ test('should keep one managed prompt hook across reinstalls and carry the new pr
|
|
|
40
65
|
|
|
41
66
|
test('should remove the managed prompt hook on uninstall and keep the user prompt hook', () => {
|
|
42
67
|
const settings = { hooks: { PreToolUse: [{ matcher: REPLY_MATCHER, hooks: [USER_PROMPT_HOOK] }] } };
|
|
43
|
-
|
|
68
|
+
mergeInto(settings, hooksConfigWithPrompt('Judge. $ARGUMENTS'));
|
|
44
69
|
|
|
45
70
|
pruneManagedHooksFromSettings(settings, new Set());
|
|
46
71
|
|
|
47
72
|
assert.deepEqual(replyHooks(settings), [USER_PROMPT_HOOK]);
|
|
48
73
|
});
|
|
74
|
+
|
|
75
|
+
test('should mark the shipped claim judge on reply, and any other shipped model hook, as managed', () => {
|
|
76
|
+
assert.ok(modelHookEntries(SHIPPED_HOOKS_CONFIG).every(eachEntry => isManagedPromptHook(eachEntry.hook)));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('should keep the shipped claim judge once, under its own event and matcher, after two installs', () => {
|
|
80
|
+
const settings = {};
|
|
81
|
+
mergeInto(settings, structuredClone(SHIPPED_HOOKS_CONFIG));
|
|
82
|
+
mergeInto(settings, structuredClone(SHIPPED_HOOKS_CONFIG));
|
|
83
|
+
|
|
84
|
+
assert.deepEqual(modelHookEntries(settings), modelHookEntries(SHIPPED_HOOKS_CONFIG));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('should drop a managed prompt hook left under a retired matcher of an event the shipped config lists', () => {
|
|
88
|
+
const staleMatcher = 'mcp__hearthbot__retired';
|
|
89
|
+
const judgeOnlyMatcher = 'mcp__hearthbot__retired_alone';
|
|
90
|
+
const oldJudge = { type: 'prompt', prompt: 'Old judge. $ARGUMENTS', statusMessage: MANAGED_STATUS };
|
|
91
|
+
const settings = {
|
|
92
|
+
hooks: {
|
|
93
|
+
PreToolUse: [
|
|
94
|
+
{ matcher: staleMatcher, hooks: [oldJudge, USER_PROMPT_HOOK] },
|
|
95
|
+
{ matcher: judgeOnlyMatcher, hooks: [oldJudge] },
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
mergeInto(settings, structuredClone(SHIPPED_HOOKS_CONFIG));
|
|
101
|
+
|
|
102
|
+
const staleGroup = settings.hooks.PreToolUse.find(eachGroup => eachGroup.matcher === staleMatcher);
|
|
103
|
+
assert.deepEqual(staleGroup.hooks, [USER_PROMPT_HOOK]);
|
|
104
|
+
assert.equal(settings.hooks.PreToolUse.some(eachGroup => eachGroup.matcher === judgeOnlyMatcher), false);
|
|
105
|
+
});
|