@ghl-ai/aw 0.1.37-beta.66 → 0.1.37-beta.68
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.
- package/commands/doctor.mjs +139 -6
- package/commands/init.mjs +1 -0
- package/integrate.mjs +2 -0
- package/package.json +1 -1
- package/startup.mjs +264 -54
package/commands/doctor.mjs
CHANGED
|
@@ -31,7 +31,21 @@ const CLAUDE_WINDOWS_HOOK_COMMAND = '"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd"
|
|
|
31
31
|
const REPO_CLAUDE_SESSION_START_COMMAND = '"$CLAUDE_PROJECT_DIR"/hooks/aw-session-start';
|
|
32
32
|
const CODEX_HOOK_MATCHER = 'startup|resume';
|
|
33
33
|
const CODEX_HOOK_COMMAND = 'bash "$HOME/.codex/hooks/aw-session-start.sh"';
|
|
34
|
+
const CODEX_PROMPT_HOOK_COMMAND = 'bash "$HOME/.codex/hooks/aw-user-prompt-submit.sh"';
|
|
35
|
+
const CODEX_PRE_TOOL_HOOK_COMMAND = 'bash "$HOME/.codex/hooks/aw-pre-tool-use.sh"';
|
|
36
|
+
const CODEX_POST_TOOL_HOOK_COMMAND = 'bash "$HOME/.codex/hooks/aw-post-tool-use.sh"';
|
|
37
|
+
const CODEX_STOP_HOOK_COMMAND = 'bash "$HOME/.codex/hooks/aw-stop.sh"';
|
|
34
38
|
const CURSOR_HOOK_COMMAND = 'node .cursor/hooks/session-start.js';
|
|
39
|
+
const CURSOR_BEFORE_PROMPT_COMMAND = 'node .cursor/hooks/before-submit-prompt.js';
|
|
40
|
+
|
|
41
|
+
function hasManagedHookCommand(entry, command) {
|
|
42
|
+
return Array.isArray(entry?.hooks)
|
|
43
|
+
&& entry.hooks.some(hook => hook?.type === 'command' && hook?.command === command);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function formatMissingParts(parts) {
|
|
47
|
+
return parts.length > 0 ? parts.join(', ') : 'none';
|
|
48
|
+
}
|
|
35
49
|
|
|
36
50
|
function projectRelinkFix(homeDir, cwd, targetDescription) {
|
|
37
51
|
return cwd !== homeDir
|
|
@@ -245,6 +259,110 @@ function hasManagedCursorSessionStart(filePath) {
|
|
|
245
259
|
);
|
|
246
260
|
}
|
|
247
261
|
|
|
262
|
+
function getClaudeHomeHookCoverage(homeDir) {
|
|
263
|
+
const hooksPath = join(homeDir, '.claude', 'hooks', 'hooks.json');
|
|
264
|
+
if (!existsSync(hooksPath)) {
|
|
265
|
+
return { ok: false, missingPhases: ['SessionStart', 'UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'Stop'] };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const hooksConfig = readJson(hooksPath, {});
|
|
269
|
+
const sessionStartStatus = getClaudeHomeSessionStartStatus(homeDir);
|
|
270
|
+
const phases = {
|
|
271
|
+
SessionStart: sessionStartStatus.present && sessionStartStatus.ok && sessionStartStatus.reason !== 'missing',
|
|
272
|
+
UserPromptSubmit: Array.isArray(hooksConfig?.hooks?.UserPromptSubmit)
|
|
273
|
+
&& hooksConfig.hooks.UserPromptSubmit.some(entry =>
|
|
274
|
+
Array.isArray(entry?.hooks)
|
|
275
|
+
&& entry.hooks.some(hook => String(hook?.command || '').includes('session-start-rules-context.sh'))
|
|
276
|
+
),
|
|
277
|
+
PreToolUse: Array.isArray(hooksConfig?.hooks?.PreToolUse) && hooksConfig.hooks.PreToolUse.length > 0,
|
|
278
|
+
PostToolUse: Array.isArray(hooksConfig?.hooks?.PostToolUse) && hooksConfig.hooks.PostToolUse.length > 0,
|
|
279
|
+
Stop: Array.isArray(hooksConfig?.hooks?.Stop) && hooksConfig.hooks.Stop.length > 0,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const missingPhases = Object.entries(phases)
|
|
283
|
+
.filter(([, present]) => !present)
|
|
284
|
+
.map(([phase]) => phase);
|
|
285
|
+
|
|
286
|
+
return { ok: missingPhases.length === 0, missingPhases };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function getCodexHomeHookCoverage(homeDir) {
|
|
290
|
+
const hooksPath = join(homeDir, '.codex', 'hooks.json');
|
|
291
|
+
const hooksConfig = readJson(hooksPath, {});
|
|
292
|
+
const phases = {
|
|
293
|
+
SessionStart: Array.isArray(hooksConfig?.hooks?.SessionStart)
|
|
294
|
+
&& hooksConfig.hooks.SessionStart.some(entry =>
|
|
295
|
+
entry?.matcher === CODEX_HOOK_MATCHER
|
|
296
|
+
&& hasManagedHookCommand(entry, CODEX_HOOK_COMMAND)
|
|
297
|
+
),
|
|
298
|
+
UserPromptSubmit: Array.isArray(hooksConfig?.hooks?.UserPromptSubmit)
|
|
299
|
+
&& hooksConfig.hooks.UserPromptSubmit.some(entry => hasManagedHookCommand(entry, CODEX_PROMPT_HOOK_COMMAND)),
|
|
300
|
+
PreToolUse: Array.isArray(hooksConfig?.hooks?.PreToolUse)
|
|
301
|
+
&& hooksConfig.hooks.PreToolUse.some(entry => entry?.matcher === '*' && hasManagedHookCommand(entry, CODEX_PRE_TOOL_HOOK_COMMAND)),
|
|
302
|
+
PostToolUse: Array.isArray(hooksConfig?.hooks?.PostToolUse)
|
|
303
|
+
&& hooksConfig.hooks.PostToolUse.some(entry => entry?.matcher === '*' && hasManagedHookCommand(entry, CODEX_POST_TOOL_HOOK_COMMAND)),
|
|
304
|
+
Stop: Array.isArray(hooksConfig?.hooks?.Stop)
|
|
305
|
+
&& hooksConfig.hooks.Stop.some(entry => hasManagedHookCommand(entry, CODEX_STOP_HOOK_COMMAND)),
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const scripts = {
|
|
309
|
+
SessionStart: existsSync(join(homeDir, '.codex', 'hooks', 'aw-session-start.sh')),
|
|
310
|
+
UserPromptSubmit: existsSync(join(homeDir, '.codex', 'hooks', 'aw-user-prompt-submit.sh')),
|
|
311
|
+
PreToolUse: existsSync(join(homeDir, '.codex', 'hooks', 'aw-pre-tool-use.sh')),
|
|
312
|
+
PostToolUse: existsSync(join(homeDir, '.codex', 'hooks', 'aw-post-tool-use.sh')),
|
|
313
|
+
Stop: existsSync(join(homeDir, '.codex', 'hooks', 'aw-stop.sh')),
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const missingPhases = Object.entries(phases)
|
|
317
|
+
.filter(([, present]) => !present)
|
|
318
|
+
.map(([phase]) => phase);
|
|
319
|
+
const missingScripts = Object.entries(scripts)
|
|
320
|
+
.filter(([, present]) => !present)
|
|
321
|
+
.map(([phase]) => phase);
|
|
322
|
+
|
|
323
|
+
return { ok: missingPhases.length === 0 && missingScripts.length === 0, missingPhases, missingScripts };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function getCursorHomeHookCoverage(homeDir) {
|
|
327
|
+
const hooksPath = join(homeDir, '.cursor', 'hooks.json');
|
|
328
|
+
const hooksConfig = readJson(hooksPath, {});
|
|
329
|
+
const phases = {
|
|
330
|
+
SessionStart: Array.isArray(hooksConfig?.hooks?.sessionStart) && hooksConfig.hooks.sessionStart.length > 0,
|
|
331
|
+
UserPromptSubmit: Array.isArray(hooksConfig?.hooks?.beforeSubmitPrompt) && hooksConfig.hooks.beforeSubmitPrompt.length > 0,
|
|
332
|
+
PreToolUse: Array.isArray(hooksConfig?.hooks?.beforeShellExecution)
|
|
333
|
+
&& hooksConfig.hooks.beforeShellExecution.length > 0
|
|
334
|
+
&& Array.isArray(hooksConfig?.hooks?.beforeMCPExecution)
|
|
335
|
+
&& hooksConfig.hooks.beforeMCPExecution.length > 0,
|
|
336
|
+
PostToolUse: Array.isArray(hooksConfig?.hooks?.afterShellExecution)
|
|
337
|
+
&& hooksConfig.hooks.afterShellExecution.length > 0
|
|
338
|
+
&& Array.isArray(hooksConfig?.hooks?.afterFileEdit)
|
|
339
|
+
&& hooksConfig.hooks.afterFileEdit.length > 0
|
|
340
|
+
&& Array.isArray(hooksConfig?.hooks?.afterMCPExecution)
|
|
341
|
+
&& hooksConfig.hooks.afterMCPExecution.length > 0,
|
|
342
|
+
Stop: Array.isArray(hooksConfig?.hooks?.stop) && hooksConfig.hooks.stop.length > 0,
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const scripts = {
|
|
346
|
+
SessionStart: existsSync(join(homeDir, '.cursor', 'hooks', 'session-start.js')),
|
|
347
|
+
UserPromptSubmit: existsSync(join(homeDir, '.cursor', 'hooks', 'before-submit-prompt.js')),
|
|
348
|
+
PreToolUse: existsSync(join(homeDir, '.cursor', 'hooks', 'before-shell-execution.js'))
|
|
349
|
+
&& existsSync(join(homeDir, '.cursor', 'hooks', 'before-mcp-execution.js')),
|
|
350
|
+
PostToolUse: existsSync(join(homeDir, '.cursor', 'hooks', 'after-shell-execution.js'))
|
|
351
|
+
&& existsSync(join(homeDir, '.cursor', 'hooks', 'after-file-edit.js'))
|
|
352
|
+
&& existsSync(join(homeDir, '.cursor', 'hooks', 'after-mcp-execution.js')),
|
|
353
|
+
Stop: existsSync(join(homeDir, '.cursor', 'hooks', 'stop.js')),
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const missingPhases = Object.entries(phases)
|
|
357
|
+
.filter(([, present]) => !present)
|
|
358
|
+
.map(([phase]) => phase);
|
|
359
|
+
const missingScripts = Object.entries(scripts)
|
|
360
|
+
.filter(([, present]) => !present)
|
|
361
|
+
.map(([phase]) => phase);
|
|
362
|
+
|
|
363
|
+
return { ok: missingPhases.length === 0 && missingScripts.length === 0, missingPhases, missingScripts };
|
|
364
|
+
}
|
|
365
|
+
|
|
248
366
|
function getClaudePluginSessionStartStatus(pluginRoot) {
|
|
249
367
|
const hooksConfig = readJson(join(pluginRoot, 'hooks', 'hooks.json'), {});
|
|
250
368
|
if (!Array.isArray(hooksConfig?.hooks?.SessionStart)) {
|
|
@@ -556,6 +674,7 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
556
674
|
|
|
557
675
|
const claudeLegacyHooks = parseLegacyClaudeHookTargets(claudeSettings?.hooks?.SessionStart || []);
|
|
558
676
|
const claudeHomeSessionStartStatus = getClaudeHomeSessionStartStatus(homeDir);
|
|
677
|
+
const claudeHomeHookCoverage = getClaudeHomeHookCoverage(homeDir);
|
|
559
678
|
if (claudeLegacyHooks.length > 0) {
|
|
560
679
|
const brokenTargets = claudeLegacyHooks.filter(target => !target.path || !existsSync(target.path));
|
|
561
680
|
checks.push(makeCheck(
|
|
@@ -583,6 +702,18 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
583
702
|
checks.push(makeCheck('claude-session-start', 'Claude session-start hook', 'pass', 'No stale Claude SessionStart override detected'));
|
|
584
703
|
}
|
|
585
704
|
|
|
705
|
+
checks.push(
|
|
706
|
+
claudeHomeHookCoverage.ok
|
|
707
|
+
? makeCheck('claude-home-hooks', 'Claude home hook phases', 'pass', 'Claude home hooks cover SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, and Stop')
|
|
708
|
+
: makeCheck(
|
|
709
|
+
'claude-home-hooks',
|
|
710
|
+
'Claude home hook phases',
|
|
711
|
+
'fail',
|
|
712
|
+
`Claude home hooks are missing core phases: ${formatMissingParts(claudeHomeHookCoverage.missingPhases)}`,
|
|
713
|
+
'Run `aw init` to refresh ~/.claude/hooks/hooks.json with the full home-level AW hook phase set.',
|
|
714
|
+
),
|
|
715
|
+
);
|
|
716
|
+
|
|
586
717
|
const claudeInstallStatePath = join(homeDir, '.claude', 'ecc', 'install-state.json');
|
|
587
718
|
checks.push(existsSync(claudeInstallStatePath)
|
|
588
719
|
? makeCheck('claude-install-state', 'Claude install state', 'pass', 'Claude install-state file is present')
|
|
@@ -675,14 +806,15 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
675
806
|
const codexConfigPath = join(homeDir, '.codex', 'config.toml');
|
|
676
807
|
const codexHooksPath = join(homeDir, '.codex', 'hooks.json');
|
|
677
808
|
const codexRuntimePath = join(homeDir, '.codex', 'hooks', 'aw-session-start.sh');
|
|
678
|
-
const
|
|
809
|
+
const codexHomeHookCoverage = getCodexHomeHookCoverage(homeDir);
|
|
810
|
+
const codexHealthy = startup.codexHooksEnabled && codexHomeHookCoverage.ok;
|
|
679
811
|
checks.push(codexHealthy
|
|
680
|
-
? makeCheck('codex-routing', 'Codex routing', 'pass', 'Codex hooks,
|
|
812
|
+
? makeCheck('codex-routing', 'Codex routing', 'pass', 'Codex home hooks cover SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, and Stop')
|
|
681
813
|
: makeCheck(
|
|
682
814
|
'codex-routing',
|
|
683
815
|
'Codex routing',
|
|
684
816
|
startup.mode === 'disabled' ? 'warn' : 'fail',
|
|
685
|
-
`Codex routing incomplete (codex_hooks=${parseCodexHooksFile(codexConfigPath)},
|
|
817
|
+
`Codex routing incomplete (codex_hooks=${parseCodexHooksFile(codexConfigPath)}, missing phases=${formatMissingParts(codexHomeHookCoverage.missingPhases)}, missing scripts=${formatMissingParts(codexHomeHookCoverage.missingScripts)})`,
|
|
686
818
|
'Run `aw routing enable` or `aw init` to restore Codex startup wiring.',
|
|
687
819
|
),
|
|
688
820
|
);
|
|
@@ -766,14 +898,15 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
766
898
|
|
|
767
899
|
const cursorHooksPath = join(homeDir, '.cursor', 'hooks.json');
|
|
768
900
|
const cursorRuntimePath = join(homeDir, '.cursor', 'hooks', 'session-start.js');
|
|
769
|
-
const
|
|
901
|
+
const cursorHomeHookCoverage = getCursorHomeHookCoverage(homeDir);
|
|
902
|
+
const cursorHealthy = cursorHomeHookCoverage.ok;
|
|
770
903
|
checks.push(cursorHealthy
|
|
771
|
-
? makeCheck('cursor-routing', 'Cursor routing', 'pass', 'Cursor sessionStart
|
|
904
|
+
? makeCheck('cursor-routing', 'Cursor routing', 'pass', 'Cursor home hooks cover sessionStart, beforeSubmitPrompt, pre-tool, post-tool, and stop phases')
|
|
772
905
|
: makeCheck(
|
|
773
906
|
'cursor-routing',
|
|
774
907
|
'Cursor routing',
|
|
775
908
|
startup.mode === 'disabled' ? 'warn' : 'fail',
|
|
776
|
-
`Cursor routing incomplete (
|
|
909
|
+
`Cursor routing incomplete (missing phases=${formatMissingParts(cursorHomeHookCoverage.missingPhases)}, missing scripts=${formatMissingParts(cursorHomeHookCoverage.missingScripts)})`,
|
|
777
910
|
'Run `aw routing enable` or `aw init` to restore Cursor startup wiring.',
|
|
778
911
|
),
|
|
779
912
|
);
|
package/commands/init.mjs
CHANGED
|
@@ -277,6 +277,7 @@ export async function initCommand(args) {
|
|
|
277
277
|
ensureAwRuntimeHook(HOME);
|
|
278
278
|
syncHomeAndProjectInstructions(cwd, freshCfg?.namespace || team);
|
|
279
279
|
await setupMcp(HOME, freshCfg?.namespace || team, { silent });
|
|
280
|
+
applyStoredStartupPreferences(HOME);
|
|
280
281
|
const removedLegacyStartupFiles = cwd !== HOME ? removeWorkspaceHookDefaults(cwd) : [];
|
|
281
282
|
installGlobalHooks();
|
|
282
283
|
|
package/integrate.mjs
CHANGED
|
@@ -106,6 +106,8 @@ function shouldResetHomeInstructionFile(content, file) {
|
|
|
106
106
|
: [
|
|
107
107
|
'# AGENTS.md — ',
|
|
108
108
|
'# ECC for Codex CLI',
|
|
109
|
+
'# AW SDLC Repo Instructions',
|
|
110
|
+
'Use the repo-local AW SDLC files as the source of truth for routing and stage behavior.',
|
|
109
111
|
'<!-- BEGIN ECC -->',
|
|
110
112
|
];
|
|
111
113
|
|
package/package.json
CHANGED
package/startup.mjs
CHANGED
|
@@ -11,6 +11,14 @@ const CODEX_HOOK_MATCHER = 'startup|resume';
|
|
|
11
11
|
const CODEX_HOOK_COMMAND = 'bash "$HOME/.codex/hooks/aw-session-start.sh"';
|
|
12
12
|
const CODEX_HOOK_STATUS = 'Loading AW router';
|
|
13
13
|
const CODEX_HOOK_MARKER = '# aw-managed: codex-global-session-start';
|
|
14
|
+
const CODEX_PROMPT_HOOK_COMMAND = 'bash "$HOME/.codex/hooks/aw-user-prompt-submit.sh"';
|
|
15
|
+
const CODEX_PRE_TOOL_HOOK_COMMAND = 'bash "$HOME/.codex/hooks/aw-pre-tool-use.sh"';
|
|
16
|
+
const CODEX_POST_TOOL_HOOK_COMMAND = 'bash "$HOME/.codex/hooks/aw-post-tool-use.sh"';
|
|
17
|
+
const CODEX_STOP_HOOK_COMMAND = 'bash "$HOME/.codex/hooks/aw-stop.sh"';
|
|
18
|
+
const CODEX_PROMPT_HOOK_MARKER = '# aw-managed: codex-global-user-prompt-submit';
|
|
19
|
+
const CODEX_PRE_TOOL_HOOK_MARKER = '# aw-managed: codex-global-pre-tool-use';
|
|
20
|
+
const CODEX_POST_TOOL_HOOK_MARKER = '# aw-managed: codex-global-post-tool-use';
|
|
21
|
+
const CODEX_STOP_HOOK_MARKER = '# aw-managed: codex-global-stop';
|
|
14
22
|
const CURSOR_SESSION_START_COMMAND = 'node .cursor/hooks/session-start.js';
|
|
15
23
|
const CURSOR_SESSION_START_DESCRIPTION = 'Load previous context and detect environment';
|
|
16
24
|
const REPO_CURSOR_SESSION_START_COMMAND = 'bash "$(git rev-parse --show-toplevel)/hooks/aw-session-start"';
|
|
@@ -40,6 +48,32 @@ JSON_CONTEXT=$(printf '%s' "\$CONTEXT" | python3 -c 'import json, sys; print(jso
|
|
|
40
48
|
echo "{\\"hookSpecificOutput\\":{\\"hookEventName\\":\\"SessionStart\\",\\"additionalContext\\":\${JSON_CONTEXT}}}"
|
|
41
49
|
`;
|
|
42
50
|
|
|
51
|
+
const CODEX_PROMPT_HOOK_SCRIPT = `#!/usr/bin/env bash
|
|
52
|
+
${CODEX_PROMPT_HOOK_MARKER}
|
|
53
|
+
set -euo pipefail
|
|
54
|
+
|
|
55
|
+
TARGET="$HOME/.aw-ecc/scripts/hooks/session-start-rules-context.sh"
|
|
56
|
+
if [[ -f "$TARGET" ]]; then
|
|
57
|
+
exec bash "$TARGET"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
exit 0
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
function buildCodexLifecycleNoopScript(marker, label) {
|
|
64
|
+
return `#!/usr/bin/env bash
|
|
65
|
+
${marker}
|
|
66
|
+
set -euo pipefail
|
|
67
|
+
|
|
68
|
+
# Reserved AW ${label} phase for Codex home routing.
|
|
69
|
+
exit 0
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const CODEX_PRE_TOOL_HOOK_SCRIPT = buildCodexLifecycleNoopScript(CODEX_PRE_TOOL_HOOK_MARKER, 'PreToolUse');
|
|
74
|
+
const CODEX_POST_TOOL_HOOK_SCRIPT = buildCodexLifecycleNoopScript(CODEX_POST_TOOL_HOOK_MARKER, 'PostToolUse');
|
|
75
|
+
const CODEX_STOP_HOOK_SCRIPT = buildCodexLifecycleNoopScript(CODEX_STOP_HOOK_MARKER, 'Stop');
|
|
76
|
+
|
|
43
77
|
function startupPrefsPath(homeDir = homedir()) {
|
|
44
78
|
return join(homeDir, '.aw', STARTUP_PREFS_FILENAME);
|
|
45
79
|
}
|
|
@@ -52,6 +86,22 @@ function codexHookScriptPath(homeDir = homedir()) {
|
|
|
52
86
|
return join(homeDir, '.codex', 'hooks', 'aw-session-start.sh');
|
|
53
87
|
}
|
|
54
88
|
|
|
89
|
+
function codexPromptHookScriptPath(homeDir = homedir()) {
|
|
90
|
+
return join(homeDir, '.codex', 'hooks', 'aw-user-prompt-submit.sh');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function codexPreToolHookScriptPath(homeDir = homedir()) {
|
|
94
|
+
return join(homeDir, '.codex', 'hooks', 'aw-pre-tool-use.sh');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function codexPostToolHookScriptPath(homeDir = homedir()) {
|
|
98
|
+
return join(homeDir, '.codex', 'hooks', 'aw-post-tool-use.sh');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function codexStopHookScriptPath(homeDir = homedir()) {
|
|
102
|
+
return join(homeDir, '.codex', 'hooks', 'aw-stop.sh');
|
|
103
|
+
}
|
|
104
|
+
|
|
55
105
|
function resolveRegistryRoot(homeDir = homedir()) {
|
|
56
106
|
return [
|
|
57
107
|
join(homeDir, '.aw_registry'),
|
|
@@ -77,6 +127,27 @@ function writeJson(filePath, value) {
|
|
|
77
127
|
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
78
128
|
}
|
|
79
129
|
|
|
130
|
+
function ensureManagedFile(filePath, content, updatedFiles) {
|
|
131
|
+
const current = existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
|
|
132
|
+
if (!existsSync(filePath) || current !== content) {
|
|
133
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
134
|
+
writeFileSync(filePath, content);
|
|
135
|
+
updatedFiles.push(filePath);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function removeManagedFile(filePath, marker, updatedFiles) {
|
|
140
|
+
if (!existsSync(filePath)) return;
|
|
141
|
+
try {
|
|
142
|
+
const current = readFileSync(filePath, 'utf8');
|
|
143
|
+
if (!current.includes(marker)) return;
|
|
144
|
+
rmSync(filePath, { force: true });
|
|
145
|
+
updatedFiles.push(filePath);
|
|
146
|
+
} catch {
|
|
147
|
+
/* best effort */
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
80
151
|
function isObject(value) {
|
|
81
152
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
82
153
|
}
|
|
@@ -121,22 +192,43 @@ function isLegacyClaudeSessionStartEntry(entry) {
|
|
|
121
192
|
);
|
|
122
193
|
}
|
|
123
194
|
|
|
124
|
-
function
|
|
125
|
-
return entry?.
|
|
126
|
-
&& Array.isArray(entry?.hooks)
|
|
195
|
+
function hasCommandHook(entry, command) {
|
|
196
|
+
return Array.isArray(entry?.hooks)
|
|
127
197
|
&& entry.hooks.some(hook =>
|
|
128
198
|
hook?.type === 'command'
|
|
129
|
-
&& hook?.command ===
|
|
199
|
+
&& hook?.command === command
|
|
130
200
|
);
|
|
131
201
|
}
|
|
132
202
|
|
|
203
|
+
function isManagedCodexSessionStartEntry(entry) {
|
|
204
|
+
return entry?.matcher === CODEX_HOOK_MATCHER && hasCommandHook(entry, CODEX_HOOK_COMMAND);
|
|
205
|
+
}
|
|
206
|
+
|
|
133
207
|
function isLegacyCodexSessionStartEntry(entry) {
|
|
134
208
|
return Array.isArray(entry?.hooks)
|
|
135
|
-
&& entry.hooks.some(hook =>
|
|
136
|
-
hook?.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
209
|
+
&& entry.hooks.some(hook => {
|
|
210
|
+
const command = String(hook?.command || '');
|
|
211
|
+
return (
|
|
212
|
+
command.includes('.aw_registry/platform/core/skills/using-aw-skills/hooks/session-start.sh')
|
|
213
|
+
|| (command.includes('.codex/hooks/aw-session-start.sh') && entry?.matcher !== CODEX_HOOK_MATCHER)
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function isManagedCodexPromptSubmitEntry(entry) {
|
|
219
|
+
return hasCommandHook(entry, CODEX_PROMPT_HOOK_COMMAND);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function isManagedCodexPreToolUseEntry(entry) {
|
|
223
|
+
return entry?.matcher === '*' && hasCommandHook(entry, CODEX_PRE_TOOL_HOOK_COMMAND);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isManagedCodexPostToolUseEntry(entry) {
|
|
227
|
+
return entry?.matcher === '*' && hasCommandHook(entry, CODEX_POST_TOOL_HOOK_COMMAND);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function isManagedCodexStopEntry(entry) {
|
|
231
|
+
return hasCommandHook(entry, CODEX_STOP_HOOK_COMMAND);
|
|
140
232
|
}
|
|
141
233
|
|
|
142
234
|
function disableClaudeStartup(homeDir = homedir()) {
|
|
@@ -270,13 +362,11 @@ function enableCodexStartup(homeDir = homedir()) {
|
|
|
270
362
|
updatedFiles.push(configPath);
|
|
271
363
|
}
|
|
272
364
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
updatedFiles.push(hookScriptPath);
|
|
279
|
-
}
|
|
365
|
+
ensureManagedFile(codexHookScriptPath(homeDir), CODEX_HOOK_SCRIPT, updatedFiles);
|
|
366
|
+
ensureManagedFile(codexPromptHookScriptPath(homeDir), CODEX_PROMPT_HOOK_SCRIPT, updatedFiles);
|
|
367
|
+
ensureManagedFile(codexPreToolHookScriptPath(homeDir), CODEX_PRE_TOOL_HOOK_SCRIPT, updatedFiles);
|
|
368
|
+
ensureManagedFile(codexPostToolHookScriptPath(homeDir), CODEX_POST_TOOL_HOOK_SCRIPT, updatedFiles);
|
|
369
|
+
ensureManagedFile(codexStopHookScriptPath(homeDir), CODEX_STOP_HOOK_SCRIPT, updatedFiles);
|
|
280
370
|
|
|
281
371
|
const hooksPath = join(homeDir, '.codex', 'hooks.json');
|
|
282
372
|
const config = readJson(hooksPath, {});
|
|
@@ -285,24 +375,98 @@ function enableCodexStartup(homeDir = homedir()) {
|
|
|
285
375
|
config.hooks = {};
|
|
286
376
|
}
|
|
287
377
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
378
|
+
let hooksChanged = false;
|
|
379
|
+
|
|
380
|
+
const currentSessionStart = Array.isArray(config.hooks.SessionStart) ? config.hooks.SessionStart : [];
|
|
381
|
+
const nextSessionStart = [
|
|
382
|
+
...currentSessionStart.filter(entry => !isLegacyCodexSessionStartEntry(entry) && !isManagedCodexSessionStartEntry(entry)),
|
|
383
|
+
{
|
|
384
|
+
matcher: CODEX_HOOK_MATCHER,
|
|
385
|
+
hooks: [
|
|
386
|
+
{
|
|
387
|
+
type: 'command',
|
|
388
|
+
command: CODEX_HOOK_COMMAND,
|
|
389
|
+
statusMessage: CODEX_HOOK_STATUS,
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
},
|
|
393
|
+
];
|
|
394
|
+
if (JSON.stringify(nextSessionStart) !== JSON.stringify(currentSessionStart)) {
|
|
395
|
+
config.hooks.SessionStart = nextSessionStart;
|
|
396
|
+
hooksChanged = true;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const currentPromptSubmit = Array.isArray(config.hooks.UserPromptSubmit) ? config.hooks.UserPromptSubmit : [];
|
|
400
|
+
const nextPromptSubmit = [
|
|
401
|
+
...currentPromptSubmit.filter(entry => !isManagedCodexPromptSubmitEntry(entry)),
|
|
402
|
+
{
|
|
403
|
+
hooks: [
|
|
404
|
+
{
|
|
405
|
+
type: 'command',
|
|
406
|
+
command: CODEX_PROMPT_HOOK_COMMAND,
|
|
407
|
+
},
|
|
408
|
+
],
|
|
409
|
+
},
|
|
410
|
+
];
|
|
411
|
+
if (JSON.stringify(nextPromptSubmit) !== JSON.stringify(currentPromptSubmit)) {
|
|
412
|
+
config.hooks.UserPromptSubmit = nextPromptSubmit;
|
|
413
|
+
hooksChanged = true;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const currentPreToolUse = Array.isArray(config.hooks.PreToolUse) ? config.hooks.PreToolUse : [];
|
|
417
|
+
const nextPreToolUse = [
|
|
418
|
+
...currentPreToolUse.filter(entry => !isManagedCodexPreToolUseEntry(entry)),
|
|
419
|
+
{
|
|
420
|
+
matcher: '*',
|
|
421
|
+
hooks: [
|
|
422
|
+
{
|
|
423
|
+
type: 'command',
|
|
424
|
+
command: CODEX_PRE_TOOL_HOOK_COMMAND,
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
},
|
|
428
|
+
];
|
|
429
|
+
if (JSON.stringify(nextPreToolUse) !== JSON.stringify(currentPreToolUse)) {
|
|
430
|
+
config.hooks.PreToolUse = nextPreToolUse;
|
|
431
|
+
hooksChanged = true;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const currentPostToolUse = Array.isArray(config.hooks.PostToolUse) ? config.hooks.PostToolUse : [];
|
|
435
|
+
const nextPostToolUse = [
|
|
436
|
+
...currentPostToolUse.filter(entry => !isManagedCodexPostToolUseEntry(entry)),
|
|
437
|
+
{
|
|
438
|
+
matcher: '*',
|
|
439
|
+
hooks: [
|
|
440
|
+
{
|
|
441
|
+
type: 'command',
|
|
442
|
+
command: CODEX_POST_TOOL_HOOK_COMMAND,
|
|
443
|
+
},
|
|
444
|
+
],
|
|
445
|
+
},
|
|
446
|
+
];
|
|
447
|
+
if (JSON.stringify(nextPostToolUse) !== JSON.stringify(currentPostToolUse)) {
|
|
448
|
+
config.hooks.PostToolUse = nextPostToolUse;
|
|
449
|
+
hooksChanged = true;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const currentStop = Array.isArray(config.hooks.Stop) ? config.hooks.Stop : [];
|
|
453
|
+
const nextStop = [
|
|
454
|
+
...currentStop.filter(entry => !isManagedCodexStopEntry(entry)),
|
|
455
|
+
{
|
|
456
|
+
hooks: [
|
|
457
|
+
{
|
|
458
|
+
type: 'command',
|
|
459
|
+
command: CODEX_STOP_HOOK_COMMAND,
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
},
|
|
463
|
+
];
|
|
464
|
+
if (JSON.stringify(nextStop) !== JSON.stringify(currentStop)) {
|
|
465
|
+
config.hooks.Stop = nextStop;
|
|
466
|
+
hooksChanged = true;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (hooksChanged) {
|
|
306
470
|
writeJson(hooksPath, config);
|
|
307
471
|
updatedFiles.push(hooksPath);
|
|
308
472
|
}
|
|
@@ -312,38 +476,84 @@ function enableCodexStartup(homeDir = homedir()) {
|
|
|
312
476
|
|
|
313
477
|
function disableCodexStartup(homeDir = homedir()) {
|
|
314
478
|
const updatedFiles = [];
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
rmSync(hookScriptPath, { force: true });
|
|
321
|
-
updatedFiles.push(hookScriptPath);
|
|
322
|
-
}
|
|
323
|
-
} catch {
|
|
324
|
-
/* best effort */
|
|
325
|
-
}
|
|
326
|
-
}
|
|
479
|
+
removeManagedFile(codexHookScriptPath(homeDir), CODEX_HOOK_MARKER, updatedFiles);
|
|
480
|
+
removeManagedFile(codexPromptHookScriptPath(homeDir), CODEX_PROMPT_HOOK_MARKER, updatedFiles);
|
|
481
|
+
removeManagedFile(codexPreToolHookScriptPath(homeDir), CODEX_PRE_TOOL_HOOK_MARKER, updatedFiles);
|
|
482
|
+
removeManagedFile(codexPostToolHookScriptPath(homeDir), CODEX_POST_TOOL_HOOK_MARKER, updatedFiles);
|
|
483
|
+
removeManagedFile(codexStopHookScriptPath(homeDir), CODEX_STOP_HOOK_MARKER, updatedFiles);
|
|
327
484
|
|
|
328
485
|
const hooksPath = join(homeDir, '.codex', 'hooks.json');
|
|
329
486
|
if (!existsSync(hooksPath)) return updatedFiles;
|
|
330
487
|
|
|
331
488
|
const config = readJson(hooksPath, {});
|
|
332
|
-
if (!isObject(config.hooks)
|
|
489
|
+
if (!isObject(config.hooks)) {
|
|
333
490
|
return updatedFiles;
|
|
334
491
|
}
|
|
335
492
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
493
|
+
let hooksChanged = false;
|
|
494
|
+
|
|
495
|
+
if (Array.isArray(config.hooks.SessionStart)) {
|
|
496
|
+
const filtered = config.hooks.SessionStart.filter(entry => !isManagedCodexSessionStartEntry(entry) && !isLegacyCodexSessionStartEntry(entry));
|
|
497
|
+
if (filtered.length !== config.hooks.SessionStart.length) {
|
|
498
|
+
hooksChanged = true;
|
|
499
|
+
if (filtered.length > 0) {
|
|
500
|
+
config.hooks.SessionStart = filtered;
|
|
501
|
+
} else {
|
|
502
|
+
delete config.hooks.SessionStart;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
339
505
|
}
|
|
340
506
|
|
|
341
|
-
if (
|
|
342
|
-
config.hooks.
|
|
343
|
-
|
|
344
|
-
|
|
507
|
+
if (Array.isArray(config.hooks.UserPromptSubmit)) {
|
|
508
|
+
const filtered = config.hooks.UserPromptSubmit.filter(entry => !isManagedCodexPromptSubmitEntry(entry));
|
|
509
|
+
if (filtered.length !== config.hooks.UserPromptSubmit.length) {
|
|
510
|
+
hooksChanged = true;
|
|
511
|
+
if (filtered.length > 0) {
|
|
512
|
+
config.hooks.UserPromptSubmit = filtered;
|
|
513
|
+
} else {
|
|
514
|
+
delete config.hooks.UserPromptSubmit;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
345
517
|
}
|
|
346
518
|
|
|
519
|
+
if (Array.isArray(config.hooks.PreToolUse)) {
|
|
520
|
+
const filtered = config.hooks.PreToolUse.filter(entry => !isManagedCodexPreToolUseEntry(entry));
|
|
521
|
+
if (filtered.length !== config.hooks.PreToolUse.length) {
|
|
522
|
+
hooksChanged = true;
|
|
523
|
+
if (filtered.length > 0) {
|
|
524
|
+
config.hooks.PreToolUse = filtered;
|
|
525
|
+
} else {
|
|
526
|
+
delete config.hooks.PreToolUse;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (Array.isArray(config.hooks.PostToolUse)) {
|
|
532
|
+
const filtered = config.hooks.PostToolUse.filter(entry => !isManagedCodexPostToolUseEntry(entry));
|
|
533
|
+
if (filtered.length !== config.hooks.PostToolUse.length) {
|
|
534
|
+
hooksChanged = true;
|
|
535
|
+
if (filtered.length > 0) {
|
|
536
|
+
config.hooks.PostToolUse = filtered;
|
|
537
|
+
} else {
|
|
538
|
+
delete config.hooks.PostToolUse;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (Array.isArray(config.hooks.Stop)) {
|
|
544
|
+
const filtered = config.hooks.Stop.filter(entry => !isManagedCodexStopEntry(entry));
|
|
545
|
+
if (filtered.length !== config.hooks.Stop.length) {
|
|
546
|
+
hooksChanged = true;
|
|
547
|
+
if (filtered.length > 0) {
|
|
548
|
+
config.hooks.Stop = filtered;
|
|
549
|
+
} else {
|
|
550
|
+
delete config.hooks.Stop;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (!hooksChanged) return updatedFiles;
|
|
556
|
+
|
|
347
557
|
if (isEmptyObject(config.hooks)) {
|
|
348
558
|
delete config.hooks;
|
|
349
559
|
}
|