@ghl-ai/aw 0.1.39-beta.8 → 0.1.39

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/startup.mjs ADDED
@@ -0,0 +1,562 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+
5
+ import {
6
+ CODEX_HOME_HOOK_MATCHER,
7
+ codexHomeHookScriptPath,
8
+ getCodexHomePhaseDefinitions,
9
+ isManagedCodexHomeEntry,
10
+ } from './hooks/codex-home.mjs';
11
+
12
+ const STARTUP_PREFS_FILENAME = 'startup-preferences.json';
13
+ const ENABLED_MODE = 'enabled';
14
+ const DISABLED_MODE = 'disabled';
15
+
16
+ const CLAUDE_DISABLE_DESCRIPTION = 'AW-managed override: disable automatic AW session routing';
17
+ const CURSOR_SESSION_START_COMMAND = 'node .cursor/hooks/session-start.js';
18
+ const CURSOR_SESSION_START_DESCRIPTION = 'Load previous context and detect environment';
19
+ const REPO_CURSOR_SESSION_START_COMMAND = 'bash "$(git rev-parse --show-toplevel)/hooks/aw-session-start"';
20
+ const REPO_CLAUDE_SESSION_START_COMMAND = '"$CLAUDE_PROJECT_DIR"/hooks/aw-session-start';
21
+ const CODEX_HOME_PHASE_DEFINITIONS = getCodexHomePhaseDefinitions();
22
+ const CODEX_HOME_PHASE_BY_NAME = new Map(
23
+ CODEX_HOME_PHASE_DEFINITIONS.map(definition => [definition.phase, definition]),
24
+ );
25
+ const CODEX_SESSION_START_DEFINITION = CODEX_HOME_PHASE_BY_NAME.get('SessionStart');
26
+ const CODEX_PROMPT_DEFINITION = CODEX_HOME_PHASE_BY_NAME.get('UserPromptSubmit');
27
+ const CODEX_PRE_TOOL_USE_DEFINITION = CODEX_HOME_PHASE_BY_NAME.get('PreToolUse');
28
+ const CODEX_POST_TOOL_USE_DEFINITION = CODEX_HOME_PHASE_BY_NAME.get('PostToolUse');
29
+ const CODEX_STOP_DEFINITION = CODEX_HOME_PHASE_BY_NAME.get('Stop');
30
+ const CODEX_HOOK_COMMAND = CODEX_SESSION_START_DEFINITION.command;
31
+ const CODEX_HOOK_STATUS = CODEX_SESSION_START_DEFINITION.entry.hooks[0].statusMessage;
32
+ const CODEX_PROMPT_HOOK_COMMAND = CODEX_PROMPT_DEFINITION.command;
33
+ const CODEX_PRE_TOOL_HOOK_COMMAND = CODEX_PRE_TOOL_USE_DEFINITION.command;
34
+ const CODEX_POST_TOOL_HOOK_COMMAND = CODEX_POST_TOOL_USE_DEFINITION.command;
35
+ const CODEX_STOP_HOOK_COMMAND = CODEX_STOP_DEFINITION.command;
36
+
37
+ function startupPrefsPath(homeDir = homedir()) {
38
+ return join(homeDir, '.aw', STARTUP_PREFS_FILENAME);
39
+ }
40
+
41
+ function codexConfigPath(homeDir = homedir()) {
42
+ return join(homeDir, '.codex', 'config.toml');
43
+ }
44
+
45
+ function codexHookScriptPath(homeDir = homedir()) {
46
+ return codexHomeHookScriptPath(homeDir, CODEX_SESSION_START_DEFINITION);
47
+ }
48
+
49
+ function resolveRegistryRoot(homeDir = homedir()) {
50
+ return [
51
+ join(homeDir, '.aw_registry'),
52
+ join(homeDir, '.aw', '.aw_registry'),
53
+ ].find(existsSync) || null;
54
+ }
55
+
56
+ function awRuntimeHookSourcePath(homeDir = homedir()) {
57
+ return join(homeDir, '.aw-ecc', 'skills', 'using-aw-skills', 'hooks', 'session-start.sh');
58
+ }
59
+
60
+ function readJson(filePath, fallback = {}) {
61
+ if (!existsSync(filePath)) return fallback;
62
+ try {
63
+ return JSON.parse(readFileSync(filePath, 'utf8'));
64
+ } catch {
65
+ return fallback;
66
+ }
67
+ }
68
+
69
+ function writeJson(filePath, value) {
70
+ mkdirSync(dirname(filePath), { recursive: true });
71
+ writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
72
+ }
73
+
74
+ function ensureManagedFile(filePath, content, updatedFiles) {
75
+ const current = existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
76
+ if (!existsSync(filePath) || current !== content) {
77
+ mkdirSync(dirname(filePath), { recursive: true });
78
+ writeFileSync(filePath, content);
79
+ updatedFiles.push(filePath);
80
+ }
81
+ }
82
+
83
+ function removeManagedFile(filePath, marker, updatedFiles) {
84
+ if (!existsSync(filePath)) return;
85
+ try {
86
+ const current = readFileSync(filePath, 'utf8');
87
+ if (!current.includes(marker)) return;
88
+ rmSync(filePath, { force: true });
89
+ updatedFiles.push(filePath);
90
+ } catch {
91
+ /* best effort */
92
+ }
93
+ }
94
+
95
+ function isObject(value) {
96
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
97
+ }
98
+
99
+ function isEmptyObject(value) {
100
+ return isObject(value) && Object.keys(value).length === 0;
101
+ }
102
+
103
+ function pruneEmptySettingsFile(filePath, config) {
104
+ if (!existsSync(filePath)) return false;
105
+ if (!isEmptyObject(config)) return false;
106
+ rmSync(filePath, { force: true });
107
+ return true;
108
+ }
109
+
110
+ function ensureHooksObject(config) {
111
+ if (!isObject(config.hooks)) {
112
+ config.hooks = {};
113
+ }
114
+ }
115
+
116
+ function isManagedClaudeSessionStartOverride(entry) {
117
+ return entry?.description === CLAUDE_DISABLE_DESCRIPTION;
118
+ }
119
+
120
+ function hasClaudePluginCache(homeDir = homedir()) {
121
+ const cacheRoot = join(homeDir, '.claude', 'plugins', 'cache', 'aw-marketplace', 'aw');
122
+ if (!existsSync(cacheRoot)) return false;
123
+ try {
124
+ return readdirSync(cacheRoot, { withFileTypes: true }).some(entry => entry.isDirectory());
125
+ } catch {
126
+ return false;
127
+ }
128
+ }
129
+
130
+ function isLegacyClaudeSessionStartEntry(entry) {
131
+ return Array.isArray(entry?.hooks)
132
+ && entry.hooks.some(hook =>
133
+ hook?.type === 'command'
134
+ && typeof hook?.command === 'string'
135
+ && hook.command.includes('.aw_registry/platform/core/skills/using-aw-skills/hooks/session-start.sh')
136
+ );
137
+ }
138
+
139
+ function hasCommandHook(entry, command) {
140
+ return Array.isArray(entry?.hooks)
141
+ && entry.hooks.some(hook =>
142
+ hook?.type === 'command'
143
+ && hook?.command === command
144
+ );
145
+ }
146
+
147
+ function isManagedCodexSessionStartEntry(entry) {
148
+ return isManagedCodexHomeEntry(entry, CODEX_SESSION_START_DEFINITION);
149
+ }
150
+
151
+ function isLegacyCodexSessionStartEntry(entry) {
152
+ return Array.isArray(entry?.hooks)
153
+ && entry.hooks.some(hook => {
154
+ const command = String(hook?.command || '');
155
+ return (
156
+ command.includes('.aw_registry/platform/core/skills/using-aw-skills/hooks/session-start.sh')
157
+ || (command.includes('.codex/hooks/aw-session-start.sh') && entry?.matcher !== CODEX_HOME_HOOK_MATCHER)
158
+ );
159
+ });
160
+ }
161
+
162
+ function isManagedCodexPromptSubmitEntry(entry) {
163
+ return isManagedCodexHomeEntry(entry, CODEX_PROMPT_DEFINITION);
164
+ }
165
+
166
+ function isManagedCodexPreToolUseEntry(entry) {
167
+ return isManagedCodexHomeEntry(entry, CODEX_PRE_TOOL_USE_DEFINITION);
168
+ }
169
+
170
+ function isManagedCodexPostToolUseEntry(entry) {
171
+ return isManagedCodexHomeEntry(entry, CODEX_POST_TOOL_USE_DEFINITION);
172
+ }
173
+
174
+ function isManagedCodexStopEntry(entry) {
175
+ return isManagedCodexHomeEntry(entry, CODEX_STOP_DEFINITION);
176
+ }
177
+
178
+ function disableClaudeStartup(homeDir = homedir()) {
179
+ const settingsPath = join(homeDir, '.claude', 'settings.json');
180
+ const config = readJson(settingsPath, {});
181
+ ensureHooksObject(config);
182
+
183
+ const current = Array.isArray(config.hooks.SessionStart) ? config.hooks.SessionStart : [];
184
+ const next = [
185
+ {
186
+ matcher: '*',
187
+ hooks: [],
188
+ description: CLAUDE_DISABLE_DESCRIPTION,
189
+ },
190
+ ...current.filter(entry => !isManagedClaudeSessionStartOverride(entry) && !isLegacyClaudeSessionStartEntry(entry)),
191
+ ];
192
+
193
+ if (JSON.stringify(current) === JSON.stringify(next)) {
194
+ return [];
195
+ }
196
+
197
+ config.hooks.SessionStart = next;
198
+ writeJson(settingsPath, config);
199
+ return [settingsPath];
200
+ }
201
+
202
+ function enableClaudeStartup(homeDir = homedir()) {
203
+ const settingsPath = join(homeDir, '.claude', 'settings.json');
204
+ if (!existsSync(settingsPath)) return [];
205
+
206
+ const config = readJson(settingsPath, {});
207
+ if (!isObject(config.hooks) || !Array.isArray(config.hooks.SessionStart)) {
208
+ return [];
209
+ }
210
+
211
+ const filtered = config.hooks.SessionStart.filter(
212
+ entry => !isManagedClaudeSessionStartOverride(entry) && !isLegacyClaudeSessionStartEntry(entry),
213
+ );
214
+ if (filtered.length === config.hooks.SessionStart.length) {
215
+ return [];
216
+ }
217
+
218
+ if (filtered.length > 0) {
219
+ config.hooks.SessionStart = filtered;
220
+ } else {
221
+ delete config.hooks.SessionStart;
222
+ }
223
+
224
+ if (isEmptyObject(config.hooks)) {
225
+ delete config.hooks;
226
+ }
227
+
228
+ if (!pruneEmptySettingsFile(settingsPath, config)) {
229
+ writeJson(settingsPath, config);
230
+ }
231
+
232
+ return [settingsPath];
233
+ }
234
+
235
+ function hasCodexSessionStartScript(homeDir = homedir()) {
236
+ return existsSync(codexHookScriptPath(homeDir));
237
+ }
238
+
239
+ export function ensureAwRuntimeHook(homeDir = homedir()) {
240
+ const sourcePath = awRuntimeHookSourcePath(homeDir);
241
+ const registryRoot = resolveRegistryRoot(homeDir);
242
+ if (!existsSync(sourcePath) || !registryRoot) return [];
243
+
244
+ const destinationPath = join(
245
+ registryRoot,
246
+ 'platform',
247
+ 'core',
248
+ 'skills',
249
+ 'using-aw-skills',
250
+ 'hooks',
251
+ 'session-start.sh',
252
+ );
253
+ const sourceContent = readFileSync(sourcePath, 'utf8');
254
+ const existingContent = existsSync(destinationPath) ? readFileSync(destinationPath, 'utf8') : null;
255
+ if (existingContent === sourceContent) return [];
256
+
257
+ mkdirSync(dirname(destinationPath), { recursive: true });
258
+ writeFileSync(destinationPath, sourceContent);
259
+ try { chmodSync(destinationPath, 0o755); } catch { /* best effort */ }
260
+ return [destinationPath];
261
+ }
262
+
263
+ function hasCodexHooksEnabled(homeDir = homedir()) {
264
+ const configPath = codexConfigPath(homeDir);
265
+ if (!existsSync(configPath)) return false;
266
+
267
+ try {
268
+ return /^\s*codex_hooks\s*=\s*true\b/m.test(readFileSync(configPath, 'utf8'));
269
+ } catch {
270
+ return false;
271
+ }
272
+ }
273
+
274
+ function ensureCodexHookEnabled(content) {
275
+ if (/^\s*codex_hooks\s*=\s*true\b/m.test(content)) {
276
+ return content;
277
+ }
278
+
279
+ if (/^\s*codex_hooks\s*=\s*false\b/m.test(content)) {
280
+ return content.replace(/^\s*codex_hooks\s*=\s*false\b.*$/m, 'codex_hooks = true');
281
+ }
282
+
283
+ const featuresHeaderMatch = content.match(/^\[features\]\s*$/m);
284
+ if (featuresHeaderMatch && featuresHeaderMatch.index !== undefined) {
285
+ const insertAt = featuresHeaderMatch.index + featuresHeaderMatch[0].length;
286
+ return `${content.slice(0, insertAt)}\ncodex_hooks = true${content.slice(insertAt)}`;
287
+ }
288
+
289
+ const trimmed = content.trimEnd();
290
+ if (trimmed.length === 0) {
291
+ return '[features]\ncodex_hooks = true\n';
292
+ }
293
+
294
+ return `${trimmed}\n\n[features]\ncodex_hooks = true\n`;
295
+ }
296
+
297
+ function enableCodexStartup(homeDir = homedir()) {
298
+ const updatedFiles = [];
299
+ const configPath = codexConfigPath(homeDir);
300
+ const currentConfig = existsSync(configPath) ? readFileSync(configPath, 'utf8') : '';
301
+ const nextConfig = ensureCodexHookEnabled(currentConfig);
302
+
303
+ if (!existsSync(configPath) || nextConfig !== currentConfig) {
304
+ mkdirSync(dirname(configPath), { recursive: true });
305
+ writeFileSync(configPath, nextConfig);
306
+ updatedFiles.push(configPath);
307
+ }
308
+
309
+ for (const definition of CODEX_HOME_PHASE_DEFINITIONS) {
310
+ ensureManagedFile(codexHomeHookScriptPath(homeDir, definition), definition.scriptContent, updatedFiles);
311
+ }
312
+
313
+ const hooksPath = join(homeDir, '.codex', 'hooks.json');
314
+ const config = readJson(hooksPath, {});
315
+
316
+ if (!isObject(config.hooks)) {
317
+ config.hooks = {};
318
+ }
319
+
320
+ let hooksChanged = false;
321
+
322
+ for (const definition of CODEX_HOME_PHASE_DEFINITIONS) {
323
+ const currentEntries = Array.isArray(config.hooks[definition.hookConfigKey]) ? config.hooks[definition.hookConfigKey] : [];
324
+ const nextEntries = [
325
+ ...currentEntries.filter(entry => !isManagedCodexHomeEntry(entry, definition)
326
+ && !(definition.phase === 'SessionStart' && isLegacyCodexSessionStartEntry(entry))),
327
+ definition.entry,
328
+ ];
329
+
330
+ if (JSON.stringify(nextEntries) !== JSON.stringify(currentEntries)) {
331
+ config.hooks[definition.hookConfigKey] = nextEntries;
332
+ hooksChanged = true;
333
+ }
334
+ }
335
+
336
+ if (hooksChanged) {
337
+ writeJson(hooksPath, config);
338
+ updatedFiles.push(hooksPath);
339
+ }
340
+
341
+ return updatedFiles;
342
+ }
343
+
344
+ function disableCodexStartup(homeDir = homedir()) {
345
+ const updatedFiles = [];
346
+ for (const definition of CODEX_HOME_PHASE_DEFINITIONS) {
347
+ removeManagedFile(codexHomeHookScriptPath(homeDir, definition), definition.scriptMarker, updatedFiles);
348
+ }
349
+
350
+ const hooksPath = join(homeDir, '.codex', 'hooks.json');
351
+ if (!existsSync(hooksPath)) return updatedFiles;
352
+
353
+ const config = readJson(hooksPath, {});
354
+ if (!isObject(config.hooks)) {
355
+ return updatedFiles;
356
+ }
357
+
358
+ let hooksChanged = false;
359
+
360
+ for (const definition of CODEX_HOME_PHASE_DEFINITIONS) {
361
+ const currentEntries = Array.isArray(config.hooks[definition.hookConfigKey]) ? config.hooks[definition.hookConfigKey] : null;
362
+ if (!currentEntries) continue;
363
+
364
+ const filtered = currentEntries.filter(entry => !isManagedCodexHomeEntry(entry, definition)
365
+ && !(definition.phase === 'SessionStart' && isLegacyCodexSessionStartEntry(entry)));
366
+ if (filtered.length !== currentEntries.length) {
367
+ hooksChanged = true;
368
+ if (filtered.length > 0) {
369
+ config.hooks[definition.hookConfigKey] = filtered;
370
+ } else {
371
+ delete config.hooks[definition.hookConfigKey];
372
+ }
373
+ }
374
+ }
375
+
376
+ if (!hooksChanged) return updatedFiles;
377
+
378
+ if (isEmptyObject(config.hooks)) {
379
+ delete config.hooks;
380
+ }
381
+
382
+ if (!pruneEmptySettingsFile(hooksPath, config)) {
383
+ writeJson(hooksPath, config);
384
+ }
385
+
386
+ updatedFiles.push(hooksPath);
387
+ return updatedFiles;
388
+ }
389
+
390
+ function isManagedCursorSessionStartEntry(entry) {
391
+ const command = String(entry?.command || '');
392
+ return command === CURSOR_SESSION_START_COMMAND || command.endsWith('.cursor/hooks/session-start.js');
393
+ }
394
+
395
+ function hasCursorSessionStartScript(homeDir = homedir()) {
396
+ return existsSync(join(homeDir, '.cursor', 'hooks', 'session-start.js'));
397
+ }
398
+
399
+ function disableCursorStartup(homeDir = homedir()) {
400
+ const hooksPath = join(homeDir, '.cursor', 'hooks.json');
401
+ if (!existsSync(hooksPath)) return [];
402
+
403
+ const config = readJson(hooksPath, {});
404
+ if (!isObject(config.hooks) || !Array.isArray(config.hooks.sessionStart)) {
405
+ return [];
406
+ }
407
+
408
+ const filtered = config.hooks.sessionStart.filter(entry => !isManagedCursorSessionStartEntry(entry));
409
+ if (filtered.length === config.hooks.sessionStart.length) {
410
+ return [];
411
+ }
412
+
413
+ if (filtered.length > 0) {
414
+ config.hooks.sessionStart = filtered;
415
+ } else {
416
+ delete config.hooks.sessionStart;
417
+ }
418
+
419
+ if (isEmptyObject(config.hooks)) {
420
+ delete config.hooks;
421
+ }
422
+
423
+ if (config.version === undefined) {
424
+ config.version = 1;
425
+ }
426
+
427
+ if (!pruneEmptySettingsFile(hooksPath, config)) {
428
+ writeJson(hooksPath, config);
429
+ }
430
+
431
+ return [hooksPath];
432
+ }
433
+
434
+ function enableCursorStartup(homeDir = homedir()) {
435
+ if (!hasCursorSessionStartScript(homeDir)) return [];
436
+
437
+ const hooksPath = join(homeDir, '.cursor', 'hooks.json');
438
+ const config = readJson(hooksPath, {});
439
+
440
+ if (!isObject(config.hooks)) {
441
+ config.hooks = {};
442
+ }
443
+
444
+ const current = Array.isArray(config.hooks.sessionStart) ? config.hooks.sessionStart : [];
445
+ if (current.length > 0) {
446
+ return [];
447
+ }
448
+
449
+ config.version = Number.isInteger(config.version) ? config.version : 1;
450
+ config.hooks.sessionStart = [
451
+ {
452
+ command: CURSOR_SESSION_START_COMMAND,
453
+ event: 'sessionStart',
454
+ description: CURSOR_SESSION_START_DESCRIPTION,
455
+ },
456
+ ];
457
+
458
+ writeJson(hooksPath, config);
459
+ return [hooksPath];
460
+ }
461
+
462
+ export function loadStartupPreferences(homeDir = homedir()) {
463
+ const prefs = readJson(startupPrefsPath(homeDir), {});
464
+ return {
465
+ mode: prefs.mode === DISABLED_MODE ? DISABLED_MODE : ENABLED_MODE,
466
+ updatedAt: typeof prefs.updatedAt === 'string' ? prefs.updatedAt : null,
467
+ };
468
+ }
469
+
470
+ export function saveStartupPreferences(mode, homeDir = homedir()) {
471
+ const normalizedMode = mode === DISABLED_MODE ? DISABLED_MODE : ENABLED_MODE;
472
+ const next = {
473
+ mode: normalizedMode,
474
+ updatedAt: new Date().toISOString(),
475
+ };
476
+ writeJson(startupPrefsPath(homeDir), next);
477
+ return next;
478
+ }
479
+
480
+ export function clearStartupPreferences(homeDir = homedir()) {
481
+ const prefsPath = startupPrefsPath(homeDir);
482
+ if (!existsSync(prefsPath)) return false;
483
+ rmSync(prefsPath, { force: true });
484
+ return true;
485
+ }
486
+
487
+ export function applyGlobalStartupMode(mode, homeDir = homedir()) {
488
+ const normalizedMode = mode === DISABLED_MODE ? DISABLED_MODE : ENABLED_MODE;
489
+ const updatedFiles = [];
490
+
491
+ if (normalizedMode === DISABLED_MODE) {
492
+ updatedFiles.push(...disableClaudeStartup(homeDir));
493
+ updatedFiles.push(...disableCodexStartup(homeDir));
494
+ updatedFiles.push(...disableCursorStartup(homeDir));
495
+ } else {
496
+ updatedFiles.push(...enableClaudeStartup(homeDir));
497
+ updatedFiles.push(...enableCodexStartup(homeDir));
498
+ updatedFiles.push(...enableCursorStartup(homeDir));
499
+ }
500
+
501
+ return [...new Set(updatedFiles)];
502
+ }
503
+
504
+ export function applyStoredStartupPreferences(homeDir = homedir()) {
505
+ return applyGlobalStartupMode(loadStartupPreferences(homeDir).mode, homeDir);
506
+ }
507
+
508
+ export function getStartupStatus(homeDir = homedir()) {
509
+ const prefs = loadStartupPreferences(homeDir);
510
+ const claudeSettingsPath = join(homeDir, '.claude', 'settings.json');
511
+ const codexHooksPath = join(homeDir, '.codex', 'hooks.json');
512
+ const cursorHooksPath = join(homeDir, '.cursor', 'hooks.json');
513
+ const claudeSettings = readJson(claudeSettingsPath, {});
514
+ const codexHooks = readJson(codexHooksPath, {});
515
+ const cursorHooks = readJson(cursorHooksPath, {});
516
+
517
+ return {
518
+ ...prefs,
519
+ preferencesPath: startupPrefsPath(homeDir),
520
+ claudePluginEnabled: claudeSettings?.enabledPlugins?.['aw@aw-marketplace'] === true,
521
+ claudePluginInstalled: hasClaudePluginCache(homeDir),
522
+ claudeDisabled: Array.isArray(claudeSettings?.hooks?.SessionStart) &&
523
+ claudeSettings.hooks.SessionStart.some(isManagedClaudeSessionStartOverride),
524
+ claudeLegacySessionStartPresent: Array.isArray(claudeSettings?.hooks?.SessionStart) &&
525
+ claudeSettings.hooks.SessionStart.some(isLegacyClaudeSessionStartEntry),
526
+ codexHooksEnabled: hasCodexHooksEnabled(homeDir),
527
+ codexSessionStartPresent: Array.isArray(codexHooks?.hooks?.SessionStart) &&
528
+ codexHooks.hooks.SessionStart.some(isManagedCodexSessionStartEntry),
529
+ codexSessionStartScriptInstalled: hasCodexSessionStartScript(homeDir),
530
+ cursorSessionStartPresent: Array.isArray(cursorHooks?.hooks?.sessionStart) &&
531
+ cursorHooks.hooks.sessionStart.length > 0,
532
+ cursorSessionStartScriptInstalled: hasCursorSessionStartScript(homeDir),
533
+ };
534
+ }
535
+
536
+ export function hasLegacyRepoStartupDefaults(cwd) {
537
+ const codexHooksPath = join(cwd, '.codex', 'hooks.json');
538
+ const cursorHooksPath = join(cwd, '.cursor', 'hooks.json');
539
+ const claudeSettingsPath = join(cwd, '.claude', 'settings.json');
540
+ const wrapperPath = join(cwd, 'hooks', 'aw-session-start');
541
+
542
+ const codexHooks = readJson(codexHooksPath, {});
543
+ const cursorHooks = readJson(cursorHooksPath, {});
544
+ const claudeSettings = readJson(claudeSettingsPath, {});
545
+
546
+ const codexSessionStart = Array.isArray(codexHooks?.hooks?.SessionStart)
547
+ && codexHooks.hooks.SessionStart.some(entry =>
548
+ Array.isArray(entry?.hooks)
549
+ && entry.hooks.some(hook => String(hook?.command || '').includes('hooks/aw-session-start'))
550
+ );
551
+
552
+ const cursorSessionStart = Array.isArray(cursorHooks?.hooks?.sessionStart)
553
+ && cursorHooks.hooks.sessionStart.some(entry => String(entry?.command || '') === REPO_CURSOR_SESSION_START_COMMAND);
554
+
555
+ const claudeSessionStart = Array.isArray(claudeSettings?.hooks?.SessionStart)
556
+ && claudeSettings.hooks.SessionStart.some(entry =>
557
+ Array.isArray(entry?.hooks)
558
+ && entry.hooks.some(hook => String(hook?.command || '') === REPO_CLAUDE_SESSION_START_COMMAND)
559
+ );
560
+
561
+ return existsSync(wrapperPath) || codexSessionStart || cursorSessionStart || claudeSessionStart;
562
+ }