@codename_inc/spectre 3.7.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/LICENSE +21 -0
- package/README.md +411 -0
- package/bin/spectre.js +8 -0
- package/package.json +23 -0
- package/plugins/spectre/.claude-plugin/plugin.json +5 -0
- package/plugins/spectre/agents/analyst.md +122 -0
- package/plugins/spectre/agents/dev.md +70 -0
- package/plugins/spectre/agents/finder.md +105 -0
- package/plugins/spectre/agents/patterns.md +207 -0
- package/plugins/spectre/agents/reviewer.md +128 -0
- package/plugins/spectre/agents/sync.md +151 -0
- package/plugins/spectre/agents/tester.md +209 -0
- package/plugins/spectre/agents/web-research.md +109 -0
- package/plugins/spectre/commands/architecture_review.md +120 -0
- package/plugins/spectre/commands/clean.md +313 -0
- package/plugins/spectre/commands/code_review.md +408 -0
- package/plugins/spectre/commands/create_plan.md +117 -0
- package/plugins/spectre/commands/create_tasks.md +374 -0
- package/plugins/spectre/commands/create_test_guide.md +120 -0
- package/plugins/spectre/commands/evaluate.md +50 -0
- package/plugins/spectre/commands/execute.md +87 -0
- package/plugins/spectre/commands/fix.md +61 -0
- package/plugins/spectre/commands/forget.md +58 -0
- package/plugins/spectre/commands/handoff.md +161 -0
- package/plugins/spectre/commands/kickoff.md +115 -0
- package/plugins/spectre/commands/learn.md +15 -0
- package/plugins/spectre/commands/plan.md +170 -0
- package/plugins/spectre/commands/plan_review.md +33 -0
- package/plugins/spectre/commands/quick_dev.md +101 -0
- package/plugins/spectre/commands/rebase.md +73 -0
- package/plugins/spectre/commands/recall.md +5 -0
- package/plugins/spectre/commands/research.md +159 -0
- package/plugins/spectre/commands/scope.md +119 -0
- package/plugins/spectre/commands/ship.md +172 -0
- package/plugins/spectre/commands/sweep.md +82 -0
- package/plugins/spectre/commands/test.md +380 -0
- package/plugins/spectre/commands/ux_spec.md +91 -0
- package/plugins/spectre/commands/validate.md +343 -0
- package/plugins/spectre/hooks/hooks.json +34 -0
- package/plugins/spectre/hooks/scripts/bootstrap.cjs +99 -0
- package/plugins/spectre/hooks/scripts/handoff-resume.cjs +410 -0
- package/plugins/spectre/hooks/scripts/lib.cjs +83 -0
- package/plugins/spectre/hooks/scripts/load-knowledge.cjs +120 -0
- package/plugins/spectre/hooks/scripts/precompact-warning.cjs +19 -0
- package/plugins/spectre/hooks/scripts/register_learning.cjs +144 -0
- package/plugins/spectre/hooks/scripts/test_bootstrap.cjs +84 -0
- package/plugins/spectre/hooks/scripts/test_handoff-resume.cjs +858 -0
- package/plugins/spectre/hooks/scripts/test_load-knowledge.cjs +285 -0
- package/plugins/spectre/hooks/scripts/test_register-learning.cjs +146 -0
- package/plugins/spectre/skills/spectre-apply/SKILL.md +189 -0
- package/plugins/spectre/skills/spectre-guide/SKILL.md +358 -0
- package/plugins/spectre/skills/spectre-learn/SKILL.md +635 -0
- package/plugins/spectre/skills/spectre-learn/references/recall-template.md +31 -0
- package/plugins/spectre/skills/spectre-tdd/SKILL.md +111 -0
- package/src/config.test.js +134 -0
- package/src/install.test.js +273 -0
- package/src/lib/config.js +516 -0
- package/src/lib/constants.js +60 -0
- package/src/lib/doctor.js +168 -0
- package/src/lib/install.js +482 -0
- package/src/lib/knowledge.js +217 -0
- package/src/lib/paths.js +98 -0
- package/src/lib/project.js +473 -0
- package/src/main.js +150 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { projectPaths, spectrePluginRoot, ensureDir } from './paths.js';
|
|
4
|
+
|
|
5
|
+
function stripFrontmatter(content) {
|
|
6
|
+
if (!content.startsWith('---\n')) {
|
|
7
|
+
return content.trim();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const end = content.indexOf('\n---\n', 4);
|
|
11
|
+
if (end === -1) {
|
|
12
|
+
return content.trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return content.slice(end + 5).trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function rewriteProjectSkillPaths(content) {
|
|
19
|
+
return content.replaceAll('.claude/skills/', '.agents/skills/');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function rewriteCodexCommandRefs(content) {
|
|
23
|
+
return content.replaceAll('/spectre:', 'spectre-');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function markUserInvocable(content, value = true) {
|
|
27
|
+
if (!content.startsWith('---\n')) {
|
|
28
|
+
return content;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const end = content.indexOf('\n---\n', 4);
|
|
32
|
+
if (end === -1) {
|
|
33
|
+
return content;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const frontmatter = content.slice(4, end).trimEnd();
|
|
37
|
+
const body = content.slice(end + 5);
|
|
38
|
+
const nextFrontmatter = frontmatter.includes('\nuser-invocable:')
|
|
39
|
+
? frontmatter.replace(/\nuser-invocable:\s*(true|false)/, `\nuser-invocable: ${value}`)
|
|
40
|
+
: `${frontmatter}\nuser-invocable: ${value}`;
|
|
41
|
+
|
|
42
|
+
return `---\n${nextFrontmatter}\n---\n${body}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function codexPathConvention(content) {
|
|
46
|
+
return content.replace(
|
|
47
|
+
/Resolution order:[\s\S]*?Do NOT use `git rev-parse --show-toplevel` or any git command to resolve this path\./,
|
|
48
|
+
[
|
|
49
|
+
'Resolution rule:',
|
|
50
|
+
'- Use the current working directory (`$PWD`) as `{{project_root}}`.',
|
|
51
|
+
'',
|
|
52
|
+
'Do NOT use `git rev-parse --show-toplevel` or any git command to resolve this path.'
|
|
53
|
+
].join('\n')
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function codexLearnIntro(content) {
|
|
58
|
+
return content.replace(
|
|
59
|
+
"You capture durable project knowledge into Skills that Claude Code loads on-demand.",
|
|
60
|
+
'You capture durable project knowledge into Skills that Codex loads on-demand.'
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeSkillMarkdown(content) {
|
|
65
|
+
return content.replace(/\n{3,}/g, '\n\n').trimEnd();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function pluginSkillPath(skillName) {
|
|
69
|
+
return path.join(spectrePluginRoot(), 'skills', skillName, 'SKILL.md');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function recallTemplatePath() {
|
|
73
|
+
return path.join(spectrePluginRoot(), 'skills', 'spectre-learn', 'references', 'recall-template.md');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function pluginSkillContent(skillName) {
|
|
77
|
+
return fs.readFileSync(pluginSkillPath(skillName), 'utf8');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function codexSharedSkillContent(skillName) {
|
|
81
|
+
if (skillName === 'spectre-apply') {
|
|
82
|
+
return `${normalizeSkillMarkdown(rewriteCodexCommandRefs(rewriteProjectSkillPaths(pluginSkillContent(skillName))))}\n`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (skillName === 'spectre-learn') {
|
|
86
|
+
return `${normalizeSkillMarkdown(markUserInvocable(rewriteCodexCommandRefs(codexPathConvention(codexLearnIntro(rewriteProjectSkillPaths(pluginSkillContent(skillName)))))))}\n`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function knowledgeRegistryHeader() {
|
|
93
|
+
return [
|
|
94
|
+
'# SPECTRE Knowledge Registry',
|
|
95
|
+
'# Format: skill-name|category|triggers|description',
|
|
96
|
+
''
|
|
97
|
+
].join('\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function countKnowledgeEntries(registryContent) {
|
|
101
|
+
const lines = registryContent ? registryContent.split('\n') : [];
|
|
102
|
+
let count = 0;
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
if (line.trim() && line.includes('|') && !line.startsWith('#')) {
|
|
105
|
+
count += 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return count;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function readKnowledgeRegistry(projectDir) {
|
|
112
|
+
const paths = projectPaths(projectDir);
|
|
113
|
+
const registryContent = fs.existsSync(paths.knowledgeRegistryPath)
|
|
114
|
+
? fs.readFileSync(paths.knowledgeRegistryPath, 'utf8').trim()
|
|
115
|
+
: '';
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
registryContent,
|
|
119
|
+
entryCount: countKnowledgeEntries(registryContent),
|
|
120
|
+
registryPath: paths.knowledgeRegistryPath
|
|
121
|
+
};
|
|
122
|
+
}
|
|
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
|
+
export function generateRecallSkillContent(projectDir) {
|
|
153
|
+
const { registryContent } = readKnowledgeRegistry(projectDir);
|
|
154
|
+
const template = fs.readFileSync(recallTemplatePath(), 'utf8');
|
|
155
|
+
return `${markUserInvocable(rewriteCodexCommandRefs(template.replace('{{REGISTRY}}', registryContent.trim())))}\n`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function ensureKnowledgeFiles(projectDir) {
|
|
159
|
+
const paths = projectPaths(projectDir);
|
|
160
|
+
ensureDir(paths.projectSkillsDir);
|
|
161
|
+
ensureDir(paths.recallReferencesDir);
|
|
162
|
+
|
|
163
|
+
if (!fs.existsSync(paths.knowledgeRegistryPath)) {
|
|
164
|
+
fs.writeFileSync(paths.knowledgeRegistryPath, `${knowledgeRegistryHeader()}\n`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
fs.writeFileSync(paths.recallSkillPath, generateRecallSkillContent(projectDir));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function buildKnowledgeOverrideBody(projectDir) {
|
|
171
|
+
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`
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return normalizeSkillMarkdown(applyContent);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function updateKnowledgeRegistry(projectDir, { skillName, category, triggers, description }) {
|
|
182
|
+
ensureKnowledgeFiles(projectDir);
|
|
183
|
+
const paths = projectPaths(projectDir);
|
|
184
|
+
const entry = `${skillName}|${category}|${triggers}|${description}`;
|
|
185
|
+
const entryPrefix = `${skillName}|`;
|
|
186
|
+
const existing = fs.readFileSync(paths.knowledgeRegistryPath, 'utf8').trim();
|
|
187
|
+
const lines = existing ? existing.split('\n') : knowledgeRegistryHeader().split('\n');
|
|
188
|
+
const updatedLines = [];
|
|
189
|
+
let replaced = false;
|
|
190
|
+
|
|
191
|
+
for (const line of lines) {
|
|
192
|
+
if (line.startsWith(entryPrefix)) {
|
|
193
|
+
updatedLines.push(entry);
|
|
194
|
+
replaced = true;
|
|
195
|
+
} else {
|
|
196
|
+
updatedLines.push(line);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!replaced) {
|
|
201
|
+
updatedLines.push(entry);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
fs.writeFileSync(paths.knowledgeRegistryPath, `${updatedLines.join('\n').trimEnd()}\n`);
|
|
205
|
+
fs.writeFileSync(paths.recallSkillPath, generateRecallSkillContent(projectDir));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function knowledgeStatusMessage(projectDir) {
|
|
209
|
+
ensureKnowledgeFiles(projectDir);
|
|
210
|
+
const { entryCount } = readKnowledgeRegistry(projectDir);
|
|
211
|
+
|
|
212
|
+
if (entryCount === 0) {
|
|
213
|
+
return '👻 spectre: ready — capture knowledge with spectre-learn';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return `👻 spectre: ${entryCount} knowledge skills available`;
|
|
217
|
+
}
|
package/src/lib/paths.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
export function repoRoot() {
|
|
10
|
+
return path.resolve(__dirname, '..', '..');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function spectrePluginRoot() {
|
|
14
|
+
return path.join(repoRoot(), 'plugins', 'spectre');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resolveCodexHome() {
|
|
18
|
+
const envHome = process.env.CODEX_HOME;
|
|
19
|
+
if (envHome) {
|
|
20
|
+
return envHome;
|
|
21
|
+
}
|
|
22
|
+
return path.join(os.homedir(), '.codex');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function codexConfigPath() {
|
|
26
|
+
return path.join(resolveCodexHome(), 'config.toml');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function codexHooksConfigPath() {
|
|
30
|
+
return path.join(resolveCodexHome(), 'hooks.json');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function codexPromptsDir() {
|
|
34
|
+
return path.join(resolveCodexHome(), 'prompts');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function codexSkillsDir() {
|
|
38
|
+
return path.join(resolveCodexHome(), 'skills');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function codexRuntimeRoot() {
|
|
42
|
+
return path.join(resolveCodexHome(), 'spectre');
|
|
43
|
+
}
|
|
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
|
+
export function runtimeAgentsDir() {
|
|
58
|
+
return path.join(codexRuntimeRoot(), 'agents');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function runtimeToolsDir() {
|
|
62
|
+
return path.join(codexRuntimeRoot(), 'tools');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function runtimeHooksDir() {
|
|
66
|
+
return path.join(codexRuntimeRoot(), 'hooks');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function projectCodexHome(projectDir) {
|
|
70
|
+
return path.join(projectDir, '.codex');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function projectPaths(projectDir) {
|
|
74
|
+
const projectSkillsDir = path.join(projectDir, '.agents', 'skills');
|
|
75
|
+
const recallSkillDir = path.join(projectSkillsDir, 'spectre-recall');
|
|
76
|
+
const recallReferencesDir = path.join(recallSkillDir, 'references');
|
|
77
|
+
return {
|
|
78
|
+
projectDir,
|
|
79
|
+
projectCodexHome: projectCodexHome(projectDir),
|
|
80
|
+
projectCodexConfigPath: path.join(projectCodexHome(projectDir), 'config.toml'),
|
|
81
|
+
projectSpectreBinDir: path.join(projectDir, '.spectre', 'bin'),
|
|
82
|
+
spectreDir: path.join(projectDir, '.spectre'),
|
|
83
|
+
manifestPath: path.join(projectDir, '.spectre', 'manifest.json'),
|
|
84
|
+
rootAgentsPath: path.join(projectDir, 'AGENTS.md'),
|
|
85
|
+
overrideAgentsPath: path.join(projectDir, 'AGENTS.override.md'),
|
|
86
|
+
projectSkillsDir,
|
|
87
|
+
recallSkillDir,
|
|
88
|
+
recallSkillPath: path.join(recallSkillDir, 'SKILL.md'),
|
|
89
|
+
recallReferencesDir,
|
|
90
|
+
knowledgeRegistryPath: path.join(recallReferencesDir, 'registry.toon'),
|
|
91
|
+
sessionSkillDir: path.join(projectDir, '.agents', 'skills', 'spectre-session'),
|
|
92
|
+
sessionSkillPath: path.join(projectDir, '.agents', 'skills', 'spectre-session', 'SKILL.md')
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function ensureDir(dirPath) {
|
|
97
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
98
|
+
}
|