@codename_inc/spectre 3.7.0 → 4.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 (102) hide show
  1. package/README.md +3 -4
  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 +2 -1
  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/{commands/quick_dev.md → skills/quick_dev/SKILL.md} +9 -0
  34. package/plugins/spectre/{commands/rebase.md → skills/rebase/SKILL.md} +9 -0
  35. package/plugins/spectre/skills/recall/SKILL.md +17 -0
  36. package/plugins/spectre/{commands/research.md → skills/research/SKILL.md} +9 -0
  37. package/plugins/spectre/{commands/scope.md → skills/scope/SKILL.md} +9 -0
  38. package/plugins/spectre/{commands/ship.md → skills/ship/SKILL.md} +9 -0
  39. package/plugins/spectre/{commands/sweep.md → skills/sweep/SKILL.md} +9 -0
  40. package/plugins/spectre/skills/tdd/SKILL.md +111 -0
  41. package/plugins/spectre/{commands/test.md → skills/test/SKILL.md} +9 -0
  42. package/plugins/spectre/{commands/ux_spec.md → skills/ux_spec/SKILL.md} +9 -0
  43. package/plugins/spectre/{commands/validate.md → skills/validate/SKILL.md} +9 -0
  44. package/plugins/spectre-codex/agents/analyst.toml +117 -0
  45. package/plugins/spectre-codex/agents/dev.toml +65 -0
  46. package/plugins/spectre-codex/agents/finder.toml +101 -0
  47. package/plugins/spectre-codex/agents/patterns.toml +203 -0
  48. package/plugins/spectre-codex/agents/reviewer.toml +123 -0
  49. package/plugins/spectre-codex/agents/sync.toml +146 -0
  50. package/plugins/spectre-codex/agents/tester.toml +205 -0
  51. package/plugins/spectre-codex/agents/web-research.toml +104 -0
  52. package/plugins/spectre-codex/hooks/hooks.json +23 -0
  53. package/plugins/{spectre/hooks/scripts/bootstrap.cjs → spectre-codex/hooks/scripts/bootstrap.mjs} +15 -16
  54. package/plugins/{spectre/hooks/scripts/handoff-resume.cjs → spectre-codex/hooks/scripts/handoff-resume.mjs} +21 -27
  55. package/plugins/{spectre/hooks/scripts/lib.cjs → spectre-codex/hooks/scripts/lib.mjs} +3 -4
  56. package/plugins/spectre-codex/hooks/scripts/load-knowledge.mjs +189 -0
  57. package/plugins/spectre-codex/hooks/scripts/register_learning.mjs +264 -0
  58. package/plugins/spectre-codex/skills/apply/SKILL.md +87 -0
  59. package/plugins/spectre-codex/skills/architecture_review/SKILL.md +129 -0
  60. package/plugins/spectre-codex/skills/clean/SKILL.md +322 -0
  61. package/plugins/spectre-codex/skills/code_review/SKILL.md +417 -0
  62. package/plugins/spectre-codex/skills/create_plan/SKILL.md +126 -0
  63. package/plugins/spectre-codex/skills/create_tasks/SKILL.md +383 -0
  64. package/plugins/spectre-codex/skills/create_test_guide/SKILL.md +129 -0
  65. package/plugins/spectre-codex/skills/evaluate/SKILL.md +59 -0
  66. package/plugins/spectre-codex/skills/execute/SKILL.md +96 -0
  67. package/plugins/spectre-codex/skills/fix/SKILL.md +70 -0
  68. package/plugins/spectre-codex/skills/forget/SKILL.md +67 -0
  69. package/plugins/spectre-codex/skills/guide/SKILL.md +359 -0
  70. package/plugins/spectre-codex/skills/handoff/SKILL.md +170 -0
  71. package/plugins/spectre-codex/skills/kickoff/SKILL.md +124 -0
  72. package/plugins/spectre-codex/skills/learn/SKILL.md +595 -0
  73. package/plugins/{spectre/skills/spectre-learn → spectre-codex/skills/learn}/references/recall-template.md +4 -1
  74. package/plugins/spectre-codex/skills/plan/SKILL.md +211 -0
  75. package/plugins/spectre-codex/skills/plan_review/SKILL.md +42 -0
  76. package/plugins/spectre-codex/skills/quick_dev/SKILL.md +110 -0
  77. package/plugins/spectre-codex/skills/rebase/SKILL.md +82 -0
  78. package/plugins/spectre-codex/skills/recall/SKILL.md +17 -0
  79. package/plugins/spectre-codex/skills/research/SKILL.md +168 -0
  80. package/plugins/spectre-codex/skills/scope/SKILL.md +128 -0
  81. package/plugins/spectre-codex/skills/ship/SKILL.md +181 -0
  82. package/plugins/spectre-codex/skills/sweep/SKILL.md +91 -0
  83. package/plugins/{spectre/skills/spectre-tdd → spectre-codex/skills/tdd}/SKILL.md +1 -1
  84. package/plugins/spectre-codex/skills/test/SKILL.md +389 -0
  85. package/plugins/spectre-codex/skills/ux_spec/SKILL.md +100 -0
  86. package/plugins/spectre-codex/skills/validate/SKILL.md +352 -0
  87. package/src/config.test.js +6 -5
  88. package/src/install.test.js +100 -11
  89. package/src/lib/config.js +107 -54
  90. package/src/lib/constants.js +17 -23
  91. package/src/lib/doctor.js +19 -22
  92. package/src/lib/install.js +98 -313
  93. package/src/lib/knowledge.js +7 -37
  94. package/src/lib/paths.js +0 -12
  95. package/src/pack.test.js +87 -0
  96. package/plugins/spectre/commands/learn.md +0 -15
  97. package/plugins/spectre/commands/recall.md +0 -5
  98. package/plugins/spectre/hooks/scripts/load-knowledge.cjs +0 -120
  99. package/plugins/spectre/hooks/scripts/precompact-warning.cjs +0 -19
  100. package/plugins/spectre/hooks/scripts/register_learning.cjs +0 -144
  101. package/plugins/spectre/hooks/scripts/test_register-learning.cjs +0 -146
  102. package/plugins/spectre/skills/spectre-apply/SKILL.md +0 -189
@@ -1,11 +1,8 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { pathToFileURL } from 'url';
4
3
  import {
5
- codexCommandSkillName,
6
- listCodexWorkflowCommands,
7
4
  listSpectreAgents,
8
- listSpectreCommands,
5
+ listSpectreSkills,
9
6
  SHARED_SKILLS,
10
7
  repoMetadata
11
8
  } from './constants.js';
@@ -15,7 +12,6 @@ import {
15
12
  removeSpectreHooksConfigured,
16
13
  syncProjectSkillsConfigured
17
14
  } from './config.js';
18
- import { codexSharedSkillContent } from './knowledge.js';
19
15
  import { installProjectFiles, uninstallProjectFiles } from './project.js';
20
16
  import {
21
17
  codexPromptsDir,
@@ -25,11 +21,7 @@ import {
25
21
  repoRoot,
26
22
  runtimeAgentsDir,
27
23
  runtimeHooksDir,
28
- runtimeSourceAgentsDir,
29
- runtimeSourceCommandsDir,
30
- runtimeSourceRoot,
31
24
  runtimeToolsDir,
32
- spectrePluginRoot
33
25
  } from './paths.js';
34
26
 
35
27
  function writeFile(targetPath, content, mode) {
@@ -40,220 +32,106 @@ function writeFile(targetPath, content, mode) {
40
32
  }
41
33
  }
42
34
 
43
- function readMarkdown(filePath) {
44
- return fs.readFileSync(filePath, 'utf8');
35
+ function generatedCodexRoot() {
36
+ return path.join(repoRoot(), 'plugins', 'spectre-codex');
45
37
  }
46
38
 
47
- function escapeTomlMultilineString(value) {
48
- return value.replaceAll('"""', '\\"""');
39
+ function generatedCodexSkillsDir() {
40
+ return path.join(generatedCodexRoot(), 'skills');
49
41
  }
50
42
 
51
- function escapeTomlBasicString(value) {
52
- return JSON.stringify(value);
43
+ function generatedCodexAgentsDir() {
44
+ return path.join(generatedCodexRoot(), 'agents');
53
45
  }
54
46
 
55
- function escapeYamlDoubleQuotedString(value) {
56
- return JSON.stringify(value);
47
+ function generatedCodexHooksDir() {
48
+ return path.join(generatedCodexRoot(), 'hooks');
57
49
  }
58
50
 
59
- function splitFrontmatter(content) {
60
- if (!content.startsWith('---\n')) {
61
- return { frontmatter: '', body: content.trim() };
62
- }
51
+ function generatedCodexHooksConfigPath() {
52
+ return path.join(generatedCodexHooksDir(), 'hooks.json');
53
+ }
63
54
 
64
- const end = content.indexOf('\n---\n', 4);
65
- if (end === -1) {
66
- return { frontmatter: '', body: content.trim() };
55
+ function generatedCodexHooksConfig() {
56
+ const hooksPath = generatedCodexHooksConfigPath();
57
+ if (!fs.existsSync(hooksPath)) {
58
+ throw new Error(`Missing generated Codex hooks config: ${hooksPath}. Run npm run sync-codex first.`);
67
59
  }
68
60
 
69
- return {
70
- frontmatter: content.slice(4, end).trim(),
71
- body: content.slice(end + 5).trim()
72
- };
73
- }
74
-
75
- function frontmatterValue(frontmatter, key) {
76
- const match = frontmatter.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
77
- return match ? match[1].trim().replace(/^["']|["']$/g, '') : '';
61
+ const parsed = JSON.parse(fs.readFileSync(hooksPath, 'utf8'));
62
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || !parsed.hooks) {
63
+ throw new Error(`Malformed generated Codex hooks config: ${hooksPath}`);
64
+ }
65
+ return parsed.hooks;
78
66
  }
79
67
 
80
- function commandLabel(commandName) {
81
- return `spectre-${commandName}`;
82
- }
68
+ function listGeneratedCodexSkills({ required = false } = {}) {
69
+ const skillsDir = generatedCodexSkillsDir();
70
+ if (!fs.existsSync(skillsDir)) {
71
+ if (required) {
72
+ throw new Error(`Missing generated Codex skills directory: ${skillsDir}. Run npm run sync-codex first.`);
73
+ }
74
+ return [];
75
+ }
83
76
 
84
- function commandSourceLabel(commandName) {
85
- return `/spectre:${commandName}`;
77
+ return fs.readdirSync(skillsDir, { withFileTypes: true })
78
+ .filter(entry => entry.isDirectory() && fs.existsSync(path.join(skillsDir, entry.name, 'SKILL.md')))
79
+ .map(entry => entry.name)
80
+ .sort();
86
81
  }
87
82
 
88
- function copySourceAssets() {
89
- const pluginRoot = spectrePluginRoot();
90
- ensureDir(runtimeSourceRoot());
91
- fs.cpSync(path.join(pluginRoot, 'commands'), runtimeSourceCommandsDir(), { recursive: true });
92
- fs.cpSync(path.join(pluginRoot, 'agents'), runtimeSourceAgentsDir(), { recursive: true });
83
+ function managedCodexSkillNames({ requireGenerated = false } = {}) {
84
+ return Array.from(new Set([
85
+ ...SHARED_SKILLS,
86
+ ...listSpectreSkills(),
87
+ ...listGeneratedCodexSkills({ required: requireGenerated })
88
+ ])).sort();
93
89
  }
94
90
 
95
- function sharedSkillContent(skillName) {
96
- const codexSkill = codexSharedSkillContent(skillName);
97
- if (codexSkill) {
98
- return codexSkill;
91
+ function replaceDirectory(sourceDir, targetDir) {
92
+ if (!fs.existsSync(sourceDir)) {
93
+ throw new Error(`Missing generated Codex asset directory: ${sourceDir}. Run npm run sync-codex first.`);
99
94
  }
100
95
 
101
- if (skillName === 'spectre-guide') {
102
- return `---
103
- name: ${escapeYamlDoubleQuotedString('spectre-guide')}
104
- description: ${escapeYamlDoubleQuotedString('Use when suggesting the next Spectre workflow skill or explaining the Spectre workflow.')}
105
- ---
106
-
107
- # Spectre Guide
108
-
109
- Core path: \`spectre-scope\` -> \`spectre-plan\` -> \`spectre-execute\` -> \`spectre-clean\` -> \`spectre-test\` -> \`spectre-rebase\` -> \`spectre-evaluate\`.
96
+ fs.rmSync(targetDir, { recursive: true, force: true });
97
+ ensureDir(path.dirname(targetDir));
98
+ fs.cpSync(sourceDir, targetDir, { recursive: true });
99
+ }
110
100
 
111
- Use \`spectre-handoff\` to save continuity and \`spectre-forget\` to clear it.
112
- `;
101
+ function removeLegacyPrefixedSkillDirs() {
102
+ const skillsRoot = codexSkillsDir();
103
+ if (!fs.existsSync(skillsRoot)) {
104
+ return;
113
105
  }
114
106
 
115
- if (skillName === 'spectre-learn') {
116
- return `---
117
- name: ${escapeYamlDoubleQuotedString('spectre-learn')}
118
- description: ${escapeYamlDoubleQuotedString('Use when capturing durable project knowledge into Codex-native skills.')}
119
- ---
120
-
121
- # Spectre Learn
122
-
123
- Store learned project knowledge under \`.agents/skills/{category}-{slug}/SKILL.md\`.
124
-
125
- Each new learning should:
126
- - include YAML frontmatter with \`name\` and \`description\`
127
- - focus on actionable project knowledge
128
- - be updated when behavior changes
129
- `;
107
+ for (const entry of fs.readdirSync(skillsRoot, { withFileTypes: true })) {
108
+ if (!entry.isDirectory() || !entry.name.startsWith('spectre-')) {
109
+ continue;
110
+ }
111
+ const bareName = entry.name.slice('spectre-'.length);
112
+ if (managedCodexSkillNames().includes(bareName)) {
113
+ fs.rmSync(path.join(skillsRoot, entry.name), { recursive: true, force: true });
114
+ }
130
115
  }
131
-
132
- return `---
133
- name: ${escapeYamlDoubleQuotedString('spectre-tdd')}
134
- description: ${escapeYamlDoubleQuotedString("Use when following Spectre's TDD-first execution style.")}
135
- ---
136
-
137
- # Spectre TDD
138
-
139
- Default cycle:
140
- 1. Write or identify the failing behavior.
141
- 2. Add the smallest test that proves the behavior.
142
- 3. Implement the smallest code change that makes the test pass.
143
- 4. Refactor only after the test passes.
144
- `;
145
116
  }
146
117
 
147
- function installSharedSkills() {
118
+ function installGeneratedCodexSkills() {
119
+ const sourceRoot = generatedCodexSkillsDir();
148
120
  const skillsRoot = codexSkillsDir();
149
121
  ensureDir(skillsRoot);
150
- for (const skillName of SHARED_SKILLS) {
151
- const skillPath = path.join(skillsRoot, skillName, 'SKILL.md');
152
- writeFile(skillPath, sharedSkillContent(skillName));
153
- }
154
- }
155
122
 
156
- function workflowPostStep(commandName, runtimeRoot) {
157
- const refreshCommand = `node "${path.join(runtimeRoot, 'tools', 'refresh-project-context.mjs')}" --project-root "$PWD"`;
158
- const syncCommand = `node "${path.join(runtimeRoot, 'tools', 'sync-session-override.mjs')}" --project-root "$PWD" --source handoff`;
159
- const clearCommand = `node "${path.join(runtimeRoot, 'tools', 'sync-session-override.mjs')}" --project-root "$PWD" --clear`;
160
-
161
- if (commandName === 'learn') {
162
- return `After creating or updating project skills, run:\n\n\`\`\`bash\n${refreshCommand}\n\`\`\`\n`;
163
- }
123
+ removeLegacyPrefixedSkillDirs();
164
124
 
165
- if (commandName === 'handoff') {
166
- return `After saving the handoff, run:\n\n\`\`\`bash\n${syncCommand}\n\`\`\`\n`;
125
+ for (const skillName of managedCodexSkillNames({ requireGenerated: true })) {
126
+ const skillDir = path.join(skillsRoot, skillName);
127
+ if (fs.existsSync(skillDir)) {
128
+ fs.rmSync(skillDir, { recursive: true, force: true });
129
+ }
167
130
  }
168
131
 
169
- if (commandName === 'forget') {
170
- return `After clearing session memory, run:\n\n\`\`\`bash\n${clearCommand}\n\`\`\`\n`;
132
+ for (const skillName of listGeneratedCodexSkills({ required: true })) {
133
+ fs.cpSync(path.join(sourceRoot, skillName), path.join(skillsRoot, skillName), { recursive: true });
171
134
  }
172
-
173
- return 'No extra runtime step is required for this command.\n';
174
- }
175
-
176
- function workflowBody(sourceContent) {
177
- return sourceContent
178
- .replace(/(?:<|&lt;)ARGUMENTS(?:>|&gt;)\s*\$ARGUMENTS\s*(?:<\/|&lt;\/)ARGUMENTS(?:>|&gt;)/g, 'Treat the current user request as the input for this workflow.')
179
- .replace(/\$ARGUMENTS/g, 'the current user request')
180
- .replaceAll('/spectre:', 'spectre-');
181
- }
182
-
183
- function workflowSkill(commandName, runtimeRoot) {
184
- const commandSource = readMarkdown(path.join(runtimeSourceCommandsDir(), `${commandName}.md`)).trim();
185
- const { frontmatter, body } = splitFrontmatter(commandSource);
186
- const description = frontmatterValue(frontmatter, 'description') || `Use when running the Spectre ${commandName} workflow.`;
187
-
188
- return `---
189
- name: ${escapeYamlDoubleQuotedString(codexCommandSkillName(commandName))}
190
- description: ${escapeYamlDoubleQuotedString(description)}
191
- user-invocable: true
192
- ---
193
-
194
- # ${commandLabel(commandName)}
195
-
196
- Use when the user explicitly wants the Spectre \`${commandName}\` workflow, or when another Spectre workflow delegates to it.
197
-
198
- This is the Codex skill replacement for the deprecated custom prompt ${commandSourceLabel(commandName)}.
199
-
200
- ## Input Handling
201
-
202
- Treat the current user request as the input arguments for this workflow.
203
-
204
- ## Required setup
205
-
206
- 1. Read \`AGENTS.md\` if present.
207
- 2. Read \`.spectre/manifest.json\` if present.
208
- 3. Read project skills under \`.agents/skills/\` when their descriptions match the task.
209
- 4. Prefer the installed Spectre subagents when the workflow dispatches specialized roles.
210
-
211
- ## Codex translation layer
212
-
213
- - Treat both \`@spectre:name\` and \`@name\` as the installed Spectre Codex subagent for that role.
214
- - If multi-agent support is available, spawn the relevant subagent(s). If not, execute the same work sequentially yourself and preserve the same artifacts, checks, and completion reports.
215
- - Treat \`Skill(name)\` or skill-tool instructions as: load the named Codex skill from \`.agents/skills/{name}/SKILL.md\` or \`$CODEX_HOME/skills/{name}/SKILL.md\` before continuing.
216
- - Treat nested \`/spectre:other\` references as instructions to execute the installed \`spectre-other\` workflow skill immediately, not as a suggestion to stop and ask the user.
217
- - Ignore Claude plugin, marketplace, model, and tool declarations. Preserve Spectre artifact paths and handoff JSON shapes.
218
-
219
- ## Canonical workflow
220
-
221
- ${workflowBody(body)}
222
-
223
- ## Command-specific post step
224
-
225
- ${workflowPostStep(commandName, runtimeRoot)}
226
- `;
227
- }
228
-
229
- function codexAgentBody(agentName, sourceContent) {
230
- const { frontmatter, body } = splitFrontmatter(sourceContent);
231
- const description = frontmatterValue(frontmatter, 'description') || `${agentName} specialist`;
232
- const instructions = `You are the Codex port of Spectre's \`${agentName}\` subagent.
233
-
234
- ## Role
235
-
236
- ${description}
237
-
238
- ## Operating rules
239
-
240
- - Stay inside this role's scope.
241
- - Preserve Spectre file locations and document contracts.
242
- - If the parent command provided task, scope, or handoff docs, read them before acting.
243
- - Return concrete findings or completion output that the parent workflow can consume directly.
244
-
245
- ## Canonical instructions
246
-
247
- ${body}`;
248
- return {
249
- description,
250
- content: `name = ${escapeTomlBasicString(agentName)}
251
- description = ${escapeTomlBasicString(description)}
252
- developer_instructions = """
253
- ${escapeTomlMultilineString(instructions)}
254
- """
255
- `
256
- };
257
135
  }
258
136
 
259
137
  function agentNicknames(agentName) {
@@ -265,23 +143,28 @@ function agentNicknames(agentName) {
265
143
  ]));
266
144
  }
267
145
 
146
+ function tomlStringField(content, fieldName) {
147
+ const match = content.match(new RegExp(`^${fieldName}\\s*=\\s*(".*")$`, 'm'));
148
+ return match ? JSON.parse(match[1]) : '';
149
+ }
150
+
268
151
  function installAgentConfigs() {
269
152
  const configs = [];
270
- ensureDir(runtimeAgentsDir());
271
- for (const entry of fs.readdirSync(runtimeAgentsDir(), { withFileTypes: true })) {
272
- if (entry.isFile() && entry.name.endsWith('.md')) {
273
- fs.unlinkSync(path.join(runtimeAgentsDir(), entry.name));
153
+ replaceDirectory(generatedCodexAgentsDir(), runtimeAgentsDir());
154
+
155
+ for (const entry of fs.readdirSync(runtimeAgentsDir(), { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
156
+ if (!entry.isFile() || !entry.name.endsWith('.toml')) {
157
+ continue;
274
158
  }
275
- }
276
- for (const agentName of listSpectreAgents()) {
277
- const sourcePath = path.join(runtimeSourceAgentsDir(), `${agentName}.md`);
278
- const agent = codexAgentBody(agentName, readMarkdown(sourcePath));
279
- const configFile = path.join(runtimeAgentsDir(), `${agentName}.toml`);
280
- writeFile(configFile, agent.content);
159
+
160
+ const configFile = path.join(runtimeAgentsDir(), entry.name);
161
+ const content = fs.readFileSync(configFile, 'utf8');
162
+ const agentName = tomlStringField(content, 'name') || path.basename(entry.name, '.toml');
163
+ const description = tomlStringField(content, 'description') || `${agentName} specialist`;
281
164
  configs.push({
282
165
  id: agentName.replace(/-/g, '_'),
283
166
  name: agentName,
284
- description: agent.description,
167
+ description,
285
168
  configFile,
286
169
  nicknames: agentNicknames(agentName)
287
170
  });
@@ -289,26 +172,13 @@ function installAgentConfigs() {
289
172
  return configs;
290
173
  }
291
174
 
292
- function installWorkflowSkills() {
293
- const runtimeRoot = codexRuntimeRoot();
294
- const skillsRoot = codexSkillsDir();
295
- ensureDir(skillsRoot);
296
-
297
- for (const commandName of listCodexWorkflowCommands()) {
298
- writeFile(
299
- path.join(skillsRoot, codexCommandSkillName(commandName), 'SKILL.md'),
300
- workflowSkill(commandName, runtimeRoot)
301
- );
302
- }
303
- }
304
-
305
175
  function cleanupLegacyPrompts() {
306
176
  if (!fs.existsSync(codexPromptsDir())) {
307
177
  return;
308
178
  }
309
179
 
310
- for (const commandName of listSpectreCommands()) {
311
- for (const fileName of [`spectre:${commandName}.md`, `spectre-${commandName}.md`]) {
180
+ for (const skillName of listSpectreSkills()) {
181
+ for (const fileName of [`spectre:${skillName}.md`, `spectre-${skillName}.md`, `${skillName}.md`]) {
312
182
  const filePath = path.join(codexPromptsDir(), fileName);
313
183
  if (fs.existsSync(filePath)) {
314
184
  fs.unlinkSync(filePath);
@@ -317,105 +187,28 @@ function cleanupLegacyPrompts() {
317
187
  }
318
188
  }
319
189
 
320
- function sessionStartHook() {
321
- const projectModuleUrl = pathToFileURL(path.join(repoRoot(), 'src', 'lib', 'project.js')).href;
322
- return `#!/usr/bin/env node
323
- import fs from 'fs';
324
- import path from 'path';
325
-
326
- function readStdin() {
327
- return new Promise((resolve, reject) => {
328
- let input = '';
329
- process.stdin.setEncoding('utf8');
330
- process.stdin.on('data', chunk => { input += chunk; });
331
- process.stdin.on('end', () => resolve(input));
332
- process.stdin.on('error', reject);
333
- });
334
- }
335
-
336
- const input = await readStdin();
337
- let payload = {};
338
- if (input) {
339
- try {
340
- payload = JSON.parse(input);
341
- } catch {
342
- process.exit(0);
343
- }
344
- }
345
- const cwd = payload.cwd || process.cwd();
346
- const manifestPath = path.join(cwd, '.spectre', 'manifest.json');
347
-
348
- if (!fs.existsSync(manifestPath)) {
349
- process.exit(0);
350
- }
351
-
352
- const { buildSessionStartOutput } = await import(${JSON.stringify(projectModuleUrl)});
353
- const output = buildSessionStartOutput(cwd, payload);
354
- if (!output) {
355
- process.exit(0);
356
- }
357
-
358
- process.stdout.write(JSON.stringify(output) + '\\n');
359
- `;
360
- }
361
-
362
190
  function refreshProjectTool() {
363
- const configModuleUrl = pathToFileURL(path.join(repoRoot(), 'src', 'lib', 'config.js')).href;
364
- const projectModuleUrl = pathToFileURL(path.join(repoRoot(), 'src', 'lib', 'project.js')).href;
365
191
  return `#!/usr/bin/env node
366
- const projectRootIndex = process.argv.indexOf('--project-root');
367
- if (projectRootIndex === -1 || !process.argv[projectRootIndex + 1]) {
368
- throw new Error('Missing required --project-root argument');
369
- }
370
-
371
- const projectRoot = process.argv[projectRootIndex + 1];
372
- const { syncProjectSkillsConfigured } = await import(${JSON.stringify(configModuleUrl)});
373
- const { syncKnowledgeOverride } = await import(${JSON.stringify(projectModuleUrl)});
374
- syncProjectSkillsConfigured(projectRoot);
375
- syncKnowledgeOverride(projectRoot);
376
- process.stdout.write('Synced Spectre project skills and knowledge context.\\n');
192
+ process.stderr.write('refresh-project-context is no longer installed as a package-cache wrapper. Run "npx @codename_inc/spectre update codex --scope project --project-dir <path>" to refresh SPECTRE assets.\\n');
193
+ process.exit(1);
377
194
  `;
378
195
  }
379
196
 
380
197
  function syncSessionOverrideTool() {
381
- const projectModuleUrl = pathToFileURL(path.join(repoRoot(), 'src', 'lib', 'project.js')).href;
382
198
  return `#!/usr/bin/env node
383
- const projectRootIndex = process.argv.indexOf('--project-root');
384
- if (projectRootIndex === -1 || !process.argv[projectRootIndex + 1]) {
385
- throw new Error('Missing required --project-root argument');
386
- }
387
-
388
- const projectRoot = process.argv[projectRootIndex + 1];
389
- const clear = process.argv.includes('--clear');
390
- const sourceIndex = process.argv.indexOf('--source');
391
- const source = sourceIndex === -1 ? 'manual' : (process.argv[sourceIndex + 1] || 'manual');
392
-
393
- const { clearSessionOverride, syncKnowledgeOverride, syncSessionOverride } = await import(${JSON.stringify(projectModuleUrl)});
394
-
395
- if (clear) {
396
- clearSessionOverride(projectRoot);
397
- const knowledge = syncKnowledgeOverride(projectRoot);
398
- process.stdout.write(\`Cleared SPECTRE session context. Knowledge status: \${knowledge.knowledgeStatus}.\\n\`);
399
- } else {
400
- const session = syncSessionOverride(projectRoot, { source });
401
- const knowledge = syncKnowledgeOverride(projectRoot);
402
- if (session) {
403
- process.stdout.write(\`Synced SPECTRE context from \${session.handoffPath}. Knowledge status: \${knowledge.knowledgeStatus}.\\n\`);
404
- } else {
405
- process.stdout.write(\`No active handoff found; refreshed knowledge context. Knowledge status: \${knowledge.knowledgeStatus}.\\n\`);
406
- }
407
- }
199
+ process.stderr.write('sync-session-override is no longer installed as a package-cache wrapper. Session context is refreshed by SPECTRE SessionStart hooks.\\n');
200
+ process.exit(1);
408
201
  `;
409
202
  }
410
203
 
411
204
  function installRuntimeScripts() {
412
- const hooksDir = runtimeHooksDir();
413
205
  const toolsDir = runtimeToolsDir();
414
- ensureDir(hooksDir);
415
206
  ensureDir(toolsDir);
207
+ replaceDirectory(generatedCodexHooksDir(), runtimeHooksDir());
416
208
 
417
209
  for (const stalePath of [
418
- path.join(hooksDir, 'pre-session-start.mjs'),
210
+ path.join(runtimeHooksDir(), 'pre-session-start.mjs'),
211
+ path.join(runtimeHooksDir(), 'session-start.mjs'),
419
212
  path.join(toolsDir, 'forget-project-context.mjs')
420
213
  ]) {
421
214
  if (fs.existsSync(stalePath)) {
@@ -423,7 +216,6 @@ function installRuntimeScripts() {
423
216
  }
424
217
  }
425
218
 
426
- writeFile(path.join(hooksDir, 'session-start.mjs'), sessionStartHook(), 0o755);
427
219
  writeFile(path.join(toolsDir, 'refresh-project-context.mjs'), refreshProjectTool(), 0o755);
428
220
  writeFile(path.join(toolsDir, 'sync-session-override.mjs'), syncSessionOverrideTool(), 0o755);
429
221
  }
@@ -431,13 +223,11 @@ function installRuntimeScripts() {
431
223
  export function installCodex({ scope, projectDir }) {
432
224
  const runtimeRoot = codexRuntimeRoot();
433
225
  ensureDir(runtimeRoot);
434
- copySourceAssets();
435
226
  installRuntimeScripts();
436
227
  cleanupLegacyPrompts();
437
- installSharedSkills();
438
- installWorkflowSkills();
228
+ installGeneratedCodexSkills();
439
229
  const agents = installAgentConfigs();
440
- ensureSpectreHooksConfigured(runtimeRoot, agents);
230
+ ensureSpectreHooksConfigured(runtimeRoot, agents, generatedCodexHooksConfig());
441
231
 
442
232
  if (scope === 'project') {
443
233
  installProjectFiles(projectDir, scope);
@@ -459,15 +249,10 @@ export function uninstallCodex({ scope, projectDir }) {
459
249
  fs.rmSync(codexRuntimeRoot(), { recursive: true, force: true });
460
250
  }
461
251
 
462
- for (const skillName of SHARED_SKILLS) {
463
- const skillDir = path.join(codexSkillsDir(), skillName);
464
- if (fs.existsSync(skillDir)) {
465
- fs.rmSync(skillDir, { recursive: true, force: true });
466
- }
467
- }
252
+ removeLegacyPrefixedSkillDirs();
468
253
 
469
- for (const commandName of listCodexWorkflowCommands()) {
470
- const skillDir = path.join(codexSkillsDir(), codexCommandSkillName(commandName));
254
+ for (const skillName of managedCodexSkillNames()) {
255
+ const skillDir = path.join(codexSkillsDir(), skillName);
471
256
  if (fs.existsSync(skillDir)) {
472
257
  fs.rmSync(skillDir, { recursive: true, force: true });
473
258
  }
@@ -20,7 +20,7 @@ function rewriteProjectSkillPaths(content) {
20
20
  }
21
21
 
22
22
  function rewriteCodexCommandRefs(content) {
23
- return content.replaceAll('/spectre:', 'spectre-');
23
+ return content.replaceAll('/spectre:', '');
24
24
  }
25
25
 
26
26
  function markUserInvocable(content, value = true) {
@@ -70,7 +70,7 @@ function pluginSkillPath(skillName) {
70
70
  }
71
71
 
72
72
  function recallTemplatePath() {
73
- return path.join(spectrePluginRoot(), 'skills', 'spectre-learn', 'references', 'recall-template.md');
73
+ return path.join(spectrePluginRoot(), 'skills', 'learn', 'references', 'recall-template.md');
74
74
  }
75
75
 
76
76
  function pluginSkillContent(skillName) {
@@ -78,11 +78,11 @@ function pluginSkillContent(skillName) {
78
78
  }
79
79
 
80
80
  export function codexSharedSkillContent(skillName) {
81
- if (skillName === 'spectre-apply') {
81
+ if (skillName === 'apply') {
82
82
  return `${normalizeSkillMarkdown(rewriteCodexCommandRefs(rewriteProjectSkillPaths(pluginSkillContent(skillName))))}\n`;
83
83
  }
84
84
 
85
- if (skillName === 'spectre-learn') {
85
+ if (skillName === 'learn') {
86
86
  return `${normalizeSkillMarkdown(markUserInvocable(rewriteCodexCommandRefs(codexPathConvention(codexLearnIntro(rewriteProjectSkillPaths(pluginSkillContent(skillName)))))))}\n`;
87
87
  }
88
88
 
@@ -121,34 +121,6 @@ export function readKnowledgeRegistry(projectDir) {
121
121
  };
122
122
  }
123
123
 
124
- function knowledgeRegistrySection(registryContent, entryCount) {
125
- if (entryCount > 0) {
126
- return [
127
- '## Registry',
128
- '',
129
- '**Format**: `skill-name|category|triggers|description`',
130
- '',
131
- '```',
132
- registryContent,
133
- '```',
134
- '',
135
- 'Each entry corresponds to a skill that can be loaded via `Skill({skill-name})`',
136
- '',
137
- '**Categories:** feature, gotchas, patterns, decisions, procedures, integration, performance, testing, ux, strategy'
138
- ].join('\n');
139
- }
140
-
141
- return [
142
- '## Registry',
143
- '',
144
- 'No knowledge has been captured for this project yet. The behavioral rules in this document still apply.',
145
- '',
146
- 'To capture knowledge from this session, use `spectre-learn` after completing significant work.',
147
- '',
148
- '**Categories:** feature, gotchas, patterns, decisions, procedures, integration, performance, testing, ux, strategy'
149
- ].join('\n');
150
- }
151
-
152
124
  export function generateRecallSkillContent(projectDir) {
153
125
  const { registryContent } = readKnowledgeRegistry(projectDir);
154
126
  const template = fs.readFileSync(recallTemplatePath(), 'utf8');
@@ -169,10 +141,8 @@ export function ensureKnowledgeFiles(projectDir) {
169
141
 
170
142
  export function buildKnowledgeOverrideBody(projectDir) {
171
143
  ensureKnowledgeFiles(projectDir);
172
- const { registryContent, entryCount } = readKnowledgeRegistry(projectDir);
173
- const applyContent = stripFrontmatter(rewriteCodexCommandRefs(rewriteProjectSkillPaths(pluginSkillContent('spectre-apply')))).replace(
174
- /## Registry Location[\s\S]*?(?=## Workflow)/,
175
- `${knowledgeRegistrySection(registryContent, entryCount)}\n\n`
144
+ const applyContent = stripFrontmatter(
145
+ rewriteCodexCommandRefs(rewriteProjectSkillPaths(pluginSkillContent('apply')))
176
146
  );
177
147
 
178
148
  return normalizeSkillMarkdown(applyContent);
@@ -210,7 +180,7 @@ export function knowledgeStatusMessage(projectDir) {
210
180
  const { entryCount } = readKnowledgeRegistry(projectDir);
211
181
 
212
182
  if (entryCount === 0) {
213
- return '👻 spectre: ready — capture knowledge with spectre-learn';
183
+ return '👻 spectre: ready — capture knowledge with /spectre:learn';
214
184
  }
215
185
 
216
186
  return `👻 spectre: ${entryCount} knowledge skills available`;
package/src/lib/paths.js CHANGED
@@ -42,18 +42,6 @@ export function codexRuntimeRoot() {
42
42
  return path.join(resolveCodexHome(), 'spectre');
43
43
  }
44
44
 
45
- export function runtimeSourceRoot() {
46
- return path.join(codexRuntimeRoot(), 'source');
47
- }
48
-
49
- export function runtimeSourceCommandsDir() {
50
- return path.join(runtimeSourceRoot(), 'commands');
51
- }
52
-
53
- export function runtimeSourceAgentsDir() {
54
- return path.join(runtimeSourceRoot(), 'agents');
55
- }
56
-
57
45
  export function runtimeAgentsDir() {
58
46
  return path.join(codexRuntimeRoot(), 'agents');
59
47
  }