@codename_inc/spectre 3.7.0 → 5.0.0

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.
Files changed (106) hide show
  1. package/README.md +6 -7
  2. package/package.json +3 -2
  3. package/plugins/spectre/.claude-plugin/plugin.json +1 -1
  4. package/plugins/spectre/bin/spectre-register +5 -0
  5. package/plugins/spectre/hooks/hooks.json +3 -14
  6. package/plugins/spectre/hooks/scripts/bootstrap.mjs +98 -0
  7. package/plugins/spectre/hooks/scripts/handoff-resume.mjs +404 -0
  8. package/plugins/spectre/hooks/scripts/lib.mjs +82 -0
  9. package/plugins/spectre/hooks/scripts/load-knowledge.mjs +189 -0
  10. package/plugins/spectre/hooks/scripts/register_learning.mjs +264 -0
  11. package/plugins/spectre/hooks/scripts/{test_bootstrap.cjs → test_bootstrap.mjs} +12 -7
  12. package/plugins/spectre/hooks/scripts/{test_handoff-resume.cjs → test_handoff-resume.mjs} +13 -11
  13. package/plugins/spectre/hooks/scripts/{test_load-knowledge.cjs → test_load-knowledge.mjs} +103 -22
  14. package/plugins/spectre/hooks/scripts/test_register-learning.mjs +335 -0
  15. package/plugins/spectre/skills/apply/SKILL.md +87 -0
  16. package/plugins/spectre/{commands/architecture_review.md → skills/architecture_review/SKILL.md} +9 -0
  17. package/plugins/spectre/{commands/clean.md → skills/clean/SKILL.md} +9 -0
  18. package/plugins/spectre/{commands/code_review.md → skills/code_review/SKILL.md} +9 -0
  19. package/plugins/spectre/{commands/create_plan.md → skills/create_plan/SKILL.md} +9 -0
  20. package/plugins/spectre/{commands/create_tasks.md → skills/create_tasks/SKILL.md} +9 -0
  21. package/plugins/spectre/{commands/create_test_guide.md → skills/create_test_guide/SKILL.md} +9 -0
  22. package/plugins/spectre/{commands/evaluate.md → skills/evaluate/SKILL.md} +11 -2
  23. package/plugins/spectre/{commands/execute.md → skills/execute/SKILL.md} +12 -3
  24. package/plugins/spectre/{commands/fix.md → skills/fix/SKILL.md} +9 -0
  25. package/plugins/spectre/{commands/forget.md → skills/forget/SKILL.md} +9 -0
  26. package/plugins/spectre/skills/{spectre-guide → guide}/SKILL.md +6 -5
  27. package/plugins/spectre/{commands/handoff.md → skills/handoff/SKILL.md} +9 -0
  28. package/plugins/spectre/{commands/kickoff.md → skills/kickoff/SKILL.md} +9 -0
  29. package/plugins/spectre/skills/{spectre-learn → learn}/SKILL.md +19 -59
  30. package/plugins/spectre/skills/learn/references/recall-template.md +34 -0
  31. package/plugins/spectre/{commands/plan.md → skills/plan/SKILL.md} +66 -25
  32. package/plugins/spectre/{commands/plan_review.md → skills/plan_review/SKILL.md} +9 -0
  33. package/plugins/spectre/skills/prototype/SKILL.md +314 -0
  34. package/plugins/spectre/{commands/quick_dev.md → skills/quick_dev/SKILL.md} +9 -0
  35. package/plugins/spectre/{commands/rebase.md → skills/rebase/SKILL.md} +9 -0
  36. package/plugins/spectre/skills/recall/SKILL.md +17 -0
  37. package/plugins/spectre/{commands/research.md → skills/research/SKILL.md} +9 -0
  38. package/plugins/spectre/skills/scope/SKILL.md +174 -0
  39. package/plugins/spectre/{commands/ship.md → skills/ship/SKILL.md} +9 -0
  40. package/plugins/spectre/{commands/sweep.md → skills/sweep/SKILL.md} +9 -0
  41. package/plugins/spectre/skills/tdd/SKILL.md +111 -0
  42. package/plugins/spectre/{commands/test.md → skills/test/SKILL.md} +9 -0
  43. package/plugins/spectre/skills/ux/SKILL.md +121 -0
  44. package/plugins/spectre/{commands/validate.md → skills/validate/SKILL.md} +9 -0
  45. package/plugins/spectre-codex/agents/analyst.toml +117 -0
  46. package/plugins/spectre-codex/agents/dev.toml +65 -0
  47. package/plugins/spectre-codex/agents/finder.toml +101 -0
  48. package/plugins/spectre-codex/agents/patterns.toml +203 -0
  49. package/plugins/spectre-codex/agents/reviewer.toml +123 -0
  50. package/plugins/spectre-codex/agents/sync.toml +146 -0
  51. package/plugins/spectre-codex/agents/tester.toml +205 -0
  52. package/plugins/spectre-codex/agents/web-research.toml +104 -0
  53. package/plugins/spectre-codex/hooks/hooks.json +23 -0
  54. package/plugins/{spectre/hooks/scripts/bootstrap.cjs → spectre-codex/hooks/scripts/bootstrap.mjs} +15 -16
  55. package/plugins/{spectre/hooks/scripts/handoff-resume.cjs → spectre-codex/hooks/scripts/handoff-resume.mjs} +21 -27
  56. package/plugins/{spectre/hooks/scripts/lib.cjs → spectre-codex/hooks/scripts/lib.mjs} +3 -4
  57. package/plugins/spectre-codex/hooks/scripts/load-knowledge.mjs +189 -0
  58. package/plugins/spectre-codex/hooks/scripts/register_learning.mjs +264 -0
  59. package/plugins/spectre-codex/skills/apply/SKILL.md +87 -0
  60. package/plugins/spectre-codex/skills/architecture_review/SKILL.md +129 -0
  61. package/plugins/spectre-codex/skills/clean/SKILL.md +322 -0
  62. package/plugins/spectre-codex/skills/code_review/SKILL.md +417 -0
  63. package/plugins/spectre-codex/skills/create_plan/SKILL.md +126 -0
  64. package/plugins/spectre-codex/skills/create_tasks/SKILL.md +383 -0
  65. package/plugins/spectre-codex/skills/create_test_guide/SKILL.md +129 -0
  66. package/plugins/spectre-codex/skills/evaluate/SKILL.md +59 -0
  67. package/plugins/spectre-codex/skills/execute/SKILL.md +96 -0
  68. package/plugins/spectre-codex/skills/fix/SKILL.md +70 -0
  69. package/plugins/spectre-codex/skills/forget/SKILL.md +67 -0
  70. package/plugins/spectre-codex/skills/guide/SKILL.md +359 -0
  71. package/plugins/spectre-codex/skills/handoff/SKILL.md +170 -0
  72. package/plugins/spectre-codex/skills/kickoff/SKILL.md +124 -0
  73. package/plugins/spectre-codex/skills/learn/SKILL.md +595 -0
  74. package/plugins/{spectre/skills/spectre-learn → spectre-codex/skills/learn}/references/recall-template.md +4 -1
  75. package/plugins/spectre-codex/skills/plan/SKILL.md +211 -0
  76. package/plugins/spectre-codex/skills/plan_review/SKILL.md +42 -0
  77. package/plugins/spectre-codex/skills/prototype/SKILL.md +314 -0
  78. package/plugins/spectre-codex/skills/quick_dev/SKILL.md +110 -0
  79. package/plugins/spectre-codex/skills/rebase/SKILL.md +82 -0
  80. package/plugins/spectre-codex/skills/recall/SKILL.md +17 -0
  81. package/plugins/spectre-codex/skills/research/SKILL.md +168 -0
  82. package/plugins/spectre-codex/skills/scope/SKILL.md +174 -0
  83. package/plugins/spectre-codex/skills/ship/SKILL.md +181 -0
  84. package/plugins/spectre-codex/skills/sweep/SKILL.md +91 -0
  85. package/plugins/{spectre/skills/spectre-tdd → spectre-codex/skills/tdd}/SKILL.md +1 -1
  86. package/plugins/spectre-codex/skills/test/SKILL.md +389 -0
  87. package/plugins/spectre-codex/skills/ux/SKILL.md +121 -0
  88. package/plugins/spectre-codex/skills/validate/SKILL.md +352 -0
  89. package/src/config.test.js +6 -5
  90. package/src/install.test.js +100 -11
  91. package/src/lib/config.js +107 -54
  92. package/src/lib/constants.js +17 -23
  93. package/src/lib/doctor.js +19 -22
  94. package/src/lib/install.js +98 -313
  95. package/src/lib/knowledge.js +7 -37
  96. package/src/lib/paths.js +0 -12
  97. package/src/pack.test.js +87 -0
  98. package/plugins/spectre/commands/learn.md +0 -15
  99. package/plugins/spectre/commands/recall.md +0 -5
  100. package/plugins/spectre/commands/scope.md +0 -119
  101. package/plugins/spectre/commands/ux_spec.md +0 -91
  102. package/plugins/spectre/hooks/scripts/load-knowledge.cjs +0 -120
  103. package/plugins/spectre/hooks/scripts/precompact-warning.cjs +0 -19
  104. package/plugins/spectre/hooks/scripts/register_learning.cjs +0 -144
  105. package/plugins/spectre/hooks/scripts/test_register-learning.cjs +0 -146
  106. package/plugins/spectre/skills/spectre-apply/SKILL.md +0 -189
package/src/lib/config.js CHANGED
@@ -279,57 +279,103 @@ function writeHooksConfig(hooksPath, config) {
279
279
  fs.writeFileSync(hooksPath, `${JSON.stringify(nextConfig, null, 2)}\n`);
280
280
  }
281
281
 
282
- function spectreSessionStartCommand(runtimeRoot) {
283
- return `node ${shellQuote(path.join(runtimeRoot, 'hooks', 'session-start.mjs'))}`;
284
- }
285
-
286
- function isSpectreSessionStartHook(hook) {
282
+ function isSpectreHook(hook) {
287
283
  return hook
288
284
  && typeof hook === 'object'
289
285
  && hook.type === 'command'
290
286
  && typeof hook.command === 'string'
291
- && hook.command.includes('spectre/hooks/session-start.mjs');
287
+ && (
288
+ hook.command.includes('spectre/hooks/session-start.mjs')
289
+ || hook.command.includes('spectre/hooks/scripts/')
290
+ );
292
291
  }
293
292
 
294
- function upsertSpectreSessionStartHook(runtimeRoot) {
295
- const { hooksPath, config } = readHooksConfig();
296
- const hooks = { ...(config.hooks ?? {}) };
297
- const groups = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
298
- const nextGroups = [];
293
+ function materializeHookCommand(command, runtimeRoot) {
294
+ return command
295
+ .replaceAll('${CODEX_HOME}/spectre', runtimeRoot)
296
+ .replaceAll('${CODEX_HOME}\\spectre', runtimeRoot)
297
+ .replace(/^node\s+(.+)$/, (_match, scriptPath) => `node ${shellQuote(scriptPath)}`);
298
+ }
299
+
300
+ function materializeGeneratedHooks(generatedHooks, runtimeRoot) {
301
+ const materialized = {};
299
302
 
300
- for (const group of groups) {
301
- if (!group || typeof group !== 'object' || Array.isArray(group)) {
302
- nextGroups.push(group);
303
+ for (const [eventName, groups] of Object.entries(generatedHooks ?? {})) {
304
+ if (!Array.isArray(groups)) {
303
305
  continue;
304
306
  }
305
307
 
306
- const hookList = Array.isArray(group.hooks) ? group.hooks.filter(hook => !isSpectreSessionStartHook(hook)) : [];
307
- if (hookList.length > 0) {
308
- nextGroups.push({
308
+ materialized[eventName] = groups.map(group => {
309
+ if (!group || typeof group !== 'object' || Array.isArray(group)) {
310
+ return group;
311
+ }
312
+
313
+ const hookList = Array.isArray(group.hooks) ? group.hooks.map(hook => {
314
+ if (!hook || typeof hook !== 'object' || Array.isArray(hook)) {
315
+ return hook;
316
+ }
317
+ if (hook.type !== 'command' || typeof hook.command !== 'string') {
318
+ return hook;
319
+ }
320
+
321
+ return {
322
+ ...hook,
323
+ command: materializeHookCommand(hook.command, runtimeRoot)
324
+ };
325
+ }) : group.hooks;
326
+
327
+ return {
309
328
  ...group,
310
329
  hooks: hookList
311
- });
312
- }
330
+ };
331
+ });
313
332
  }
314
333
 
315
- nextGroups.push({
316
- hooks: [
317
- {
318
- type: 'command',
319
- command: spectreSessionStartCommand(runtimeRoot),
320
- statusMessage: 'Spectre: loading session context'
334
+ return materialized;
335
+ }
336
+
337
+ function upsertSpectreHooks(runtimeRoot, generatedHooks) {
338
+ const { hooksPath, config } = readHooksConfig();
339
+ const hooks = { ...(config.hooks ?? {}) };
340
+ const materializedHooks = materializeGeneratedHooks(generatedHooks, runtimeRoot);
341
+
342
+ for (const eventName of new Set([...Object.keys(hooks), ...Object.keys(materializedHooks)])) {
343
+ const groups = Array.isArray(hooks[eventName]) ? hooks[eventName] : [];
344
+ const nextGroups = [];
345
+
346
+ for (const group of groups) {
347
+ if (!group || typeof group !== 'object' || Array.isArray(group)) {
348
+ nextGroups.push(group);
349
+ continue;
321
350
  }
322
- ]
323
- });
324
351
 
325
- hooks.SessionStart = nextGroups;
352
+ const hookList = Array.isArray(group.hooks) ? group.hooks.filter(hook => !isSpectreHook(hook)) : [];
353
+ if (hookList.length > 0) {
354
+ nextGroups.push({
355
+ ...group,
356
+ hooks: hookList
357
+ });
358
+ }
359
+ }
360
+
361
+ if (Array.isArray(materializedHooks[eventName])) {
362
+ nextGroups.push(...materializedHooks[eventName]);
363
+ }
364
+
365
+ if (nextGroups.length > 0) {
366
+ hooks[eventName] = nextGroups;
367
+ } else {
368
+ delete hooks[eventName];
369
+ }
370
+ }
371
+
326
372
  writeHooksConfig(hooksPath, {
327
373
  ...config,
328
374
  hooks
329
375
  });
330
376
  }
331
377
 
332
- function removeSpectreSessionStartHook() {
378
+ function removeSpectreHooks() {
333
379
  const hooksPath = codexHooksConfigPath();
334
380
  if (!fs.existsSync(hooksPath)) {
335
381
  return false;
@@ -337,35 +383,40 @@ function removeSpectreSessionStartHook() {
337
383
 
338
384
  const { config } = readHooksConfig();
339
385
  const hooks = { ...(config.hooks ?? {}) };
340
- const groups = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
341
386
  let removed = false;
342
- const nextGroups = [];
343
387
 
344
- for (const group of groups) {
345
- if (!group || typeof group !== 'object' || Array.isArray(group)) {
346
- nextGroups.push(group);
388
+ for (const [eventName, groups] of Object.entries(hooks)) {
389
+ if (!Array.isArray(groups)) {
347
390
  continue;
348
391
  }
349
392
 
350
- const originalHooks = Array.isArray(group.hooks) ? group.hooks : [];
351
- const filteredHooks = originalHooks.filter(hook => {
352
- const shouldRemove = isSpectreSessionStartHook(hook);
353
- removed ||= shouldRemove;
354
- return !shouldRemove;
355
- });
393
+ const nextGroups = [];
394
+ for (const group of groups) {
395
+ if (!group || typeof group !== 'object' || Array.isArray(group)) {
396
+ nextGroups.push(group);
397
+ continue;
398
+ }
356
399
 
357
- if (filteredHooks.length > 0) {
358
- nextGroups.push({
359
- ...group,
360
- hooks: filteredHooks
400
+ const originalHooks = Array.isArray(group.hooks) ? group.hooks : [];
401
+ const filteredHooks = originalHooks.filter(hook => {
402
+ const shouldRemove = isSpectreHook(hook);
403
+ removed ||= shouldRemove;
404
+ return !shouldRemove;
361
405
  });
406
+
407
+ if (filteredHooks.length > 0) {
408
+ nextGroups.push({
409
+ ...group,
410
+ hooks: filteredHooks
411
+ });
412
+ }
362
413
  }
363
- }
364
414
 
365
- if (nextGroups.length > 0) {
366
- hooks.SessionStart = nextGroups;
367
- } else {
368
- delete hooks.SessionStart;
415
+ if (nextGroups.length > 0) {
416
+ hooks[eventName] = nextGroups;
417
+ } else {
418
+ delete hooks[eventName];
419
+ }
369
420
  }
370
421
 
371
422
  writeHooksConfig(hooksPath, {
@@ -448,7 +499,7 @@ export function removeProjectSkillsConfigured(projectDir) {
448
499
  writeConfig(configPath, content);
449
500
  }
450
501
 
451
- export function ensureSpectreHooksConfigured(runtimeRoot, agents) {
502
+ export function ensureSpectreHooksConfigured(runtimeRoot, agents, generatedHooks = {}) {
452
503
  const { configPath, content: initialContent } = readConfig();
453
504
  let content = initialContent;
454
505
 
@@ -457,7 +508,8 @@ export function ensureSpectreHooksConfigured(runtimeRoot, agents) {
457
508
  content = removeScalarKey(content, 'hooks', 'session_start');
458
509
  content = removeEntryFromArrayLine(content, 'hooks.blocking', 'pre_session_start', preSessionEntry);
459
510
  content = upsertRootScalarKey(content, 'suppress_unstable_features_warning', 'true');
460
- content = upsertScalarKey(content, 'features', 'codex_hooks', 'true');
511
+ content = removeScalarKey(content, 'features', 'codex_hooks');
512
+ content = upsertScalarKey(content, 'features', 'hooks', 'true');
461
513
  content = upsertScalarKey(content, 'features', 'skills', 'true');
462
514
  content = upsertScalarKey(content, 'features', 'multi_agent', 'true');
463
515
  content = removeEmptyTable(content, 'hooks');
@@ -479,7 +531,7 @@ export function ensureSpectreHooksConfigured(runtimeRoot, agents) {
479
531
  }
480
532
 
481
533
  writeConfig(configPath, content);
482
- upsertSpectreSessionStartHook(runtimeRoot);
534
+ upsertSpectreHooks(runtimeRoot, generatedHooks);
483
535
  }
484
536
 
485
537
  export function removeSpectreHooksConfigured(runtimeRoot, agents) {
@@ -491,7 +543,7 @@ export function removeSpectreHooksConfigured(runtimeRoot, agents) {
491
543
  let content = fs.readFileSync(configPath, 'utf8');
492
544
  const preSessionEntry = `{ command = ["node", "${escapeTomlString(path.join(runtimeRoot, 'hooks', 'pre-session-start.mjs'))}"] }`;
493
545
 
494
- const removedSessionStartHook = removeSpectreSessionStartHook();
546
+ const removedSpectreHooks = removeSpectreHooks();
495
547
 
496
548
  content = removeScalarKey(content, 'hooks', 'session_start');
497
549
  content = removeEntryFromArrayLine(content, 'hooks.blocking', 'pre_session_start', preSessionEntry);
@@ -502,7 +554,8 @@ export function removeSpectreHooksConfigured(runtimeRoot, agents) {
502
554
  content = removeTable(content, `agents.spectre_${agent.id}`);
503
555
  }
504
556
 
505
- if (removedSessionStartHook && !hasRemainingHookDefinitions()) {
557
+ if (removedSpectreHooks && !hasRemainingHookDefinitions()) {
558
+ content = removeScalarKey(content, 'features', 'hooks');
506
559
  content = removeScalarKey(content, 'features', 'codex_hooks');
507
560
  }
508
561
 
@@ -12,28 +12,14 @@ export const SESSION_OVERRIDE_END = '<!-- spectre-session:end -->';
12
12
  export const KNOWLEDGE_OVERRIDE_START = '<!-- spectre-knowledge:start -->';
13
13
  export const KNOWLEDGE_OVERRIDE_END = '<!-- spectre-knowledge:end -->';
14
14
 
15
- export function listSpectreCommands() {
16
- const commandsDir = path.join(spectrePluginRoot(), 'commands');
17
- return fs.readdirSync(commandsDir)
18
- .filter(name => name.endsWith('.md'))
19
- .map(name => path.basename(name, '.md'))
15
+ export function listSpectreSkills() {
16
+ const skillsDir = path.join(spectrePluginRoot(), 'skills');
17
+ return fs.readdirSync(skillsDir, { withFileTypes: true })
18
+ .filter(entry => entry.isDirectory() && fs.existsSync(path.join(skillsDir, entry.name, 'SKILL.md')))
19
+ .map(entry => entry.name)
20
20
  .sort();
21
21
  }
22
22
 
23
- export function codexCommandSkillName(commandName) {
24
- if (commandName === 'learn') {
25
- return 'spectre-learn';
26
- }
27
- if (commandName === 'recall') {
28
- return 'spectre-recall';
29
- }
30
- return `spectre-${commandName}`;
31
- }
32
-
33
- export function listCodexWorkflowCommands() {
34
- return listSpectreCommands().filter(commandName => !['learn', 'recall'].includes(commandName));
35
- }
36
-
37
23
  export function listSpectreAgents() {
38
24
  const agentsDir = path.join(spectrePluginRoot(), 'agents');
39
25
  return fs.readdirSync(agentsDir)
@@ -43,10 +29,18 @@ export function listSpectreAgents() {
43
29
  }
44
30
 
45
31
  export const SHARED_SKILLS = [
46
- 'spectre-apply',
47
- 'spectre-guide',
48
- 'spectre-learn',
49
- 'spectre-tdd'
32
+ 'apply',
33
+ 'guide',
34
+ 'learn',
35
+ 'tdd'
36
+ ];
37
+
38
+ export const WORKFLOW_PROBE_SKILLS = [
39
+ 'scope',
40
+ 'plan',
41
+ 'execute',
42
+ 'clean',
43
+ 'test'
50
44
  ];
51
45
 
52
46
  export function repoMetadata() {
package/src/lib/doctor.js CHANGED
@@ -2,18 +2,17 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { execFileSync } from 'child_process';
4
4
  import {
5
- codexCommandSkillName,
6
- listCodexWorkflowCommands,
7
5
  listSpectreAgents,
8
- listSpectreCommands,
9
- MIN_CODEX_VERSION
6
+ listSpectreSkills,
7
+ MIN_CODEX_VERSION,
8
+ SHARED_SKILLS,
9
+ WORKFLOW_PROBE_SKILLS
10
10
  } from './constants.js';
11
11
  import {
12
12
  codexConfigPath,
13
13
  codexHooksConfigPath,
14
14
  codexRuntimeRoot,
15
15
  codexSkillsDir,
16
- projectPaths,
17
16
  resolveCodexHome
18
17
  } from './paths.js';
19
18
 
@@ -55,7 +54,10 @@ function sessionStartHookConfigured() {
55
54
  Array.isArray(group?.hooks) && group.hooks.some(hook =>
56
55
  hook?.type === 'command'
57
56
  && typeof hook.command === 'string'
58
- && hook.command.includes('spectre/hooks/session-start.mjs')
57
+ && (
58
+ hook.command.includes('spectre/hooks/session-start.mjs')
59
+ || hook.command.includes('spectre/hooks/scripts/load-knowledge.mjs')
60
+ )
59
61
  )
60
62
  );
61
63
 
@@ -68,12 +70,7 @@ function sessionStartHookConfigured() {
68
70
  }
69
71
  }
70
72
 
71
- function commandSkillPath(projectDir, commandName) {
72
- const skillName = codexCommandSkillName(commandName);
73
- if (skillName === 'spectre-recall') {
74
- return projectPaths(projectDir).recallSkillPath;
75
- }
76
-
73
+ function skillPath(skillName) {
77
74
  return path.join(codexSkillsDir(), skillName, 'SKILL.md');
78
75
  }
79
76
 
@@ -98,7 +95,7 @@ export function runDoctor({ verifyHooks = false, json = false, projectDir = proc
98
95
  hooks: {
99
96
  verifyRequested: verifyHooks,
100
97
  sessionStartConfigured: false,
101
- codexHooksEnabled: false,
98
+ hooksFeatureEnabled: false,
102
99
  hiddenContextInjection: 'unconfigured',
103
100
  hooksConfigPath: codexHooksConfigPath(),
104
101
  hooksConfigPresent: fs.existsSync(codexHooksConfigPath())
@@ -114,12 +111,12 @@ export function runDoctor({ verifyHooks = false, json = false, projectDir = proc
114
111
 
115
112
  if (fs.existsSync(codexConfigPath())) {
116
113
  const config = fs.readFileSync(codexConfigPath(), 'utf8');
117
- result.hooks.codexHooksEnabled = config.includes('codex_hooks = true');
114
+ result.hooks.hooksFeatureEnabled = /^hooks\s*=\s*true\s*$/m.test(config);
118
115
  result.hooks.sessionStartConfigured = hookConfigStatus.configured;
119
116
  if (hookConfigStatus.error) {
120
117
  result.hooks.configError = hookConfigStatus.error;
121
118
  }
122
- if (result.hooks.sessionStartConfigured && result.hooks.codexHooksEnabled) {
119
+ if (result.hooks.sessionStartConfigured && result.hooks.hooksFeatureEnabled) {
123
120
  result.hooks.hiddenContextInjection = 'agents_override_managed_block';
124
121
  } else if (result.hooks.sessionStartConfigured) {
125
122
  result.hooks.hiddenContextInjection = 'configured_but_feature_disabled';
@@ -130,13 +127,13 @@ export function runDoctor({ verifyHooks = false, json = false, projectDir = proc
130
127
  result.capabilities.multiAgentEnabled = config.includes('multi_agent = true');
131
128
  }
132
129
 
133
- const commandSkillFiles = listSpectreCommands().map(name => commandSkillPath(projectDir, name));
134
- result.capabilities.workflowSkillsInstalled = listCodexWorkflowCommands()
135
- .some(name => fs.existsSync(path.join(codexSkillsDir(), codexCommandSkillName(name), 'SKILL.md')));
136
- result.capabilities.exactWorkflowSkillsInstalled = commandSkillFiles.every(filePath => fs.existsSync(filePath));
130
+ const expectedSkillFiles = listSpectreSkills().map(name => skillPath(name));
131
+ result.capabilities.workflowSkillsInstalled = WORKFLOW_PROBE_SKILLS
132
+ .some(name => fs.existsSync(skillPath(name)));
133
+ result.capabilities.exactWorkflowSkillsInstalled = expectedSkillFiles.every(filePath => fs.existsSync(filePath));
137
134
 
138
- result.capabilities.sharedSkillsInstalled = ['spectre-apply', 'spectre-guide', 'spectre-learn', 'spectre-tdd']
139
- .every(skill => fs.existsSync(path.join(codexSkillsDir(), skill, 'SKILL.md')));
135
+ result.capabilities.sharedSkillsInstalled = SHARED_SKILLS
136
+ .every(skill => fs.existsSync(skillPath(skill)));
140
137
 
141
138
  if (verifyHooks) {
142
139
  result.hooks.manualVerification = 'Use an interactive Codex session to verify SessionStart context injection. `codex exec` is not treated as authoritative for this hook lifecycle.';
@@ -154,7 +151,7 @@ export function runDoctor({ verifyHooks = false, json = false, projectDir = proc
154
151
  process.stdout.write(`Runtime present: ${result.installed.runtimeDir ? 'yes' : 'no'}\n`);
155
152
  process.stdout.write(`session_start hook configured: ${result.hooks.sessionStartConfigured ? 'yes' : 'no'}\n`);
156
153
  process.stdout.write(`hooks.json present: ${result.hooks.hooksConfigPresent ? 'yes' : 'no'}\n`);
157
- process.stdout.write(`Experimental codex_hooks enabled: ${result.hooks.codexHooksEnabled ? 'yes' : 'no'}\n`);
154
+ process.stdout.write(`Hooks feature enabled: ${result.hooks.hooksFeatureEnabled ? 'yes' : 'no'}\n`);
158
155
  process.stdout.write(`Hidden context injection: ${result.hooks.hiddenContextInjection}\n`);
159
156
  if (result.hooks.configError) {
160
157
  process.stdout.write(`Hook config error: ${result.hooks.configError}\n`);