@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.
- package/README.md +6 -7
- package/package.json +3 -2
- package/plugins/spectre/.claude-plugin/plugin.json +1 -1
- package/plugins/spectre/bin/spectre-register +5 -0
- package/plugins/spectre/hooks/hooks.json +3 -14
- package/plugins/spectre/hooks/scripts/bootstrap.mjs +98 -0
- package/plugins/spectre/hooks/scripts/handoff-resume.mjs +404 -0
- package/plugins/spectre/hooks/scripts/lib.mjs +82 -0
- package/plugins/spectre/hooks/scripts/load-knowledge.mjs +189 -0
- package/plugins/spectre/hooks/scripts/register_learning.mjs +264 -0
- package/plugins/spectre/hooks/scripts/{test_bootstrap.cjs → test_bootstrap.mjs} +12 -7
- package/plugins/spectre/hooks/scripts/{test_handoff-resume.cjs → test_handoff-resume.mjs} +13 -11
- package/plugins/spectre/hooks/scripts/{test_load-knowledge.cjs → test_load-knowledge.mjs} +103 -22
- package/plugins/spectre/hooks/scripts/test_register-learning.mjs +335 -0
- package/plugins/spectre/skills/apply/SKILL.md +87 -0
- package/plugins/spectre/{commands/architecture_review.md → skills/architecture_review/SKILL.md} +9 -0
- package/plugins/spectre/{commands/clean.md → skills/clean/SKILL.md} +9 -0
- package/plugins/spectre/{commands/code_review.md → skills/code_review/SKILL.md} +9 -0
- package/plugins/spectre/{commands/create_plan.md → skills/create_plan/SKILL.md} +9 -0
- package/plugins/spectre/{commands/create_tasks.md → skills/create_tasks/SKILL.md} +9 -0
- package/plugins/spectre/{commands/create_test_guide.md → skills/create_test_guide/SKILL.md} +9 -0
- package/plugins/spectre/{commands/evaluate.md → skills/evaluate/SKILL.md} +11 -2
- package/plugins/spectre/{commands/execute.md → skills/execute/SKILL.md} +12 -3
- package/plugins/spectre/{commands/fix.md → skills/fix/SKILL.md} +9 -0
- package/plugins/spectre/{commands/forget.md → skills/forget/SKILL.md} +9 -0
- package/plugins/spectre/skills/{spectre-guide → guide}/SKILL.md +6 -5
- package/plugins/spectre/{commands/handoff.md → skills/handoff/SKILL.md} +9 -0
- package/plugins/spectre/{commands/kickoff.md → skills/kickoff/SKILL.md} +9 -0
- package/plugins/spectre/skills/{spectre-learn → learn}/SKILL.md +19 -59
- package/plugins/spectre/skills/learn/references/recall-template.md +34 -0
- package/plugins/spectre/{commands/plan.md → skills/plan/SKILL.md} +66 -25
- package/plugins/spectre/{commands/plan_review.md → skills/plan_review/SKILL.md} +9 -0
- package/plugins/spectre/skills/prototype/SKILL.md +314 -0
- package/plugins/spectre/{commands/quick_dev.md → skills/quick_dev/SKILL.md} +9 -0
- package/plugins/spectre/{commands/rebase.md → skills/rebase/SKILL.md} +9 -0
- package/plugins/spectre/skills/recall/SKILL.md +17 -0
- package/plugins/spectre/{commands/research.md → skills/research/SKILL.md} +9 -0
- package/plugins/spectre/skills/scope/SKILL.md +174 -0
- package/plugins/spectre/{commands/ship.md → skills/ship/SKILL.md} +9 -0
- package/plugins/spectre/{commands/sweep.md → skills/sweep/SKILL.md} +9 -0
- package/plugins/spectre/skills/tdd/SKILL.md +111 -0
- package/plugins/spectre/{commands/test.md → skills/test/SKILL.md} +9 -0
- package/plugins/spectre/skills/ux/SKILL.md +121 -0
- package/plugins/spectre/{commands/validate.md → skills/validate/SKILL.md} +9 -0
- package/plugins/spectre-codex/agents/analyst.toml +117 -0
- package/plugins/spectre-codex/agents/dev.toml +65 -0
- package/plugins/spectre-codex/agents/finder.toml +101 -0
- package/plugins/spectre-codex/agents/patterns.toml +203 -0
- package/plugins/spectre-codex/agents/reviewer.toml +123 -0
- package/plugins/spectre-codex/agents/sync.toml +146 -0
- package/plugins/spectre-codex/agents/tester.toml +205 -0
- package/plugins/spectre-codex/agents/web-research.toml +104 -0
- package/plugins/spectre-codex/hooks/hooks.json +23 -0
- package/plugins/{spectre/hooks/scripts/bootstrap.cjs → spectre-codex/hooks/scripts/bootstrap.mjs} +15 -16
- package/plugins/{spectre/hooks/scripts/handoff-resume.cjs → spectre-codex/hooks/scripts/handoff-resume.mjs} +21 -27
- package/plugins/{spectre/hooks/scripts/lib.cjs → spectre-codex/hooks/scripts/lib.mjs} +3 -4
- package/plugins/spectre-codex/hooks/scripts/load-knowledge.mjs +189 -0
- package/plugins/spectre-codex/hooks/scripts/register_learning.mjs +264 -0
- package/plugins/spectre-codex/skills/apply/SKILL.md +87 -0
- package/plugins/spectre-codex/skills/architecture_review/SKILL.md +129 -0
- package/plugins/spectre-codex/skills/clean/SKILL.md +322 -0
- package/plugins/spectre-codex/skills/code_review/SKILL.md +417 -0
- package/plugins/spectre-codex/skills/create_plan/SKILL.md +126 -0
- package/plugins/spectre-codex/skills/create_tasks/SKILL.md +383 -0
- package/plugins/spectre-codex/skills/create_test_guide/SKILL.md +129 -0
- package/plugins/spectre-codex/skills/evaluate/SKILL.md +59 -0
- package/plugins/spectre-codex/skills/execute/SKILL.md +96 -0
- package/plugins/spectre-codex/skills/fix/SKILL.md +70 -0
- package/plugins/spectre-codex/skills/forget/SKILL.md +67 -0
- package/plugins/spectre-codex/skills/guide/SKILL.md +359 -0
- package/plugins/spectre-codex/skills/handoff/SKILL.md +170 -0
- package/plugins/spectre-codex/skills/kickoff/SKILL.md +124 -0
- package/plugins/spectre-codex/skills/learn/SKILL.md +595 -0
- package/plugins/{spectre/skills/spectre-learn → spectre-codex/skills/learn}/references/recall-template.md +4 -1
- package/plugins/spectre-codex/skills/plan/SKILL.md +211 -0
- package/plugins/spectre-codex/skills/plan_review/SKILL.md +42 -0
- package/plugins/spectre-codex/skills/prototype/SKILL.md +314 -0
- package/plugins/spectre-codex/skills/quick_dev/SKILL.md +110 -0
- package/plugins/spectre-codex/skills/rebase/SKILL.md +82 -0
- package/plugins/spectre-codex/skills/recall/SKILL.md +17 -0
- package/plugins/spectre-codex/skills/research/SKILL.md +168 -0
- package/plugins/spectre-codex/skills/scope/SKILL.md +174 -0
- package/plugins/spectre-codex/skills/ship/SKILL.md +181 -0
- package/plugins/spectre-codex/skills/sweep/SKILL.md +91 -0
- package/plugins/{spectre/skills/spectre-tdd → spectre-codex/skills/tdd}/SKILL.md +1 -1
- package/plugins/spectre-codex/skills/test/SKILL.md +389 -0
- package/plugins/spectre-codex/skills/ux/SKILL.md +121 -0
- package/plugins/spectre-codex/skills/validate/SKILL.md +352 -0
- package/src/config.test.js +6 -5
- package/src/install.test.js +100 -11
- package/src/lib/config.js +107 -54
- package/src/lib/constants.js +17 -23
- package/src/lib/doctor.js +19 -22
- package/src/lib/install.js +98 -313
- package/src/lib/knowledge.js +7 -37
- package/src/lib/paths.js +0 -12
- package/src/pack.test.js +87 -0
- package/plugins/spectre/commands/learn.md +0 -15
- package/plugins/spectre/commands/recall.md +0 -5
- package/plugins/spectre/commands/scope.md +0 -119
- package/plugins/spectre/commands/ux_spec.md +0 -91
- package/plugins/spectre/hooks/scripts/load-knowledge.cjs +0 -120
- package/plugins/spectre/hooks/scripts/precompact-warning.cjs +0 -19
- package/plugins/spectre/hooks/scripts/register_learning.cjs +0 -144
- package/plugins/spectre/hooks/scripts/test_register-learning.cjs +0 -146
- package/plugins/spectre/skills/spectre-apply/SKILL.md +0 -189
package/src/lib/install.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
44
|
-
return
|
|
35
|
+
function generatedCodexRoot() {
|
|
36
|
+
return path.join(repoRoot(), 'plugins', 'spectre-codex');
|
|
45
37
|
}
|
|
46
38
|
|
|
47
|
-
function
|
|
48
|
-
return
|
|
39
|
+
function generatedCodexSkillsDir() {
|
|
40
|
+
return path.join(generatedCodexRoot(), 'skills');
|
|
49
41
|
}
|
|
50
42
|
|
|
51
|
-
function
|
|
52
|
-
return
|
|
43
|
+
function generatedCodexAgentsDir() {
|
|
44
|
+
return path.join(generatedCodexRoot(), 'agents');
|
|
53
45
|
}
|
|
54
46
|
|
|
55
|
-
function
|
|
56
|
-
return
|
|
47
|
+
function generatedCodexHooksDir() {
|
|
48
|
+
return path.join(generatedCodexRoot(), 'hooks');
|
|
57
49
|
}
|
|
58
50
|
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
51
|
+
function generatedCodexHooksConfigPath() {
|
|
52
|
+
return path.join(generatedCodexHooksDir(), 'hooks.json');
|
|
53
|
+
}
|
|
63
54
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
81
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
112
|
-
|
|
101
|
+
function removeLegacyPrefixedSkillDirs() {
|
|
102
|
+
const skillsRoot = codexSkillsDir();
|
|
103
|
+
if (!fs.existsSync(skillsRoot)) {
|
|
104
|
+
return;
|
|
113
105
|
}
|
|
114
106
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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(/(?:<|<)ARGUMENTS(?:>|>)\s*\$ARGUMENTS\s*(?:<\/|<\/)ARGUMENTS(?:>|>)/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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
const
|
|
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
|
|
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
|
|
311
|
-
for (const fileName of [`spectre:${
|
|
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
|
-
|
|
367
|
-
|
|
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
|
-
|
|
384
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
470
|
-
const skillDir = path.join(codexSkillsDir(),
|
|
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
|
}
|
package/src/lib/knowledge.js
CHANGED
|
@@ -20,7 +20,7 @@ function rewriteProjectSkillPaths(content) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function rewriteCodexCommandRefs(content) {
|
|
23
|
-
return content.replaceAll('/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', '
|
|
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 === '
|
|
81
|
+
if (skillName === 'apply') {
|
|
82
82
|
return `${normalizeSkillMarkdown(rewriteCodexCommandRefs(rewriteProjectSkillPaths(pluginSkillContent(skillName))))}\n`;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
if (skillName === '
|
|
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
|
|
173
|
-
|
|
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
|
|
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
|
}
|