@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,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* register_learning.cjs
|
|
6
|
+
*
|
|
7
|
+
* Registers a spectre learning and manages the project-level recall skill.
|
|
8
|
+
*
|
|
9
|
+
* Responsibilities:
|
|
10
|
+
* 1. Create/update registry at .claude/skills/spectre-recall/references/registry.toon
|
|
11
|
+
* 2. Read recall-template.md from plugin
|
|
12
|
+
* 3. Generate .claude/skills/spectre-recall/SKILL.md with embedded registry
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* node register_learning.cjs \
|
|
16
|
+
* --project-root "/path/to/project" \
|
|
17
|
+
* --skill-name "feature-my-feature" \
|
|
18
|
+
* --category "feature" \
|
|
19
|
+
* --triggers "keyword1, keyword2" \
|
|
20
|
+
* --description "Use when doing X or Y"
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
|
|
26
|
+
function getRegistryHeader() {
|
|
27
|
+
return [
|
|
28
|
+
'# SPECTRE Knowledge Registry',
|
|
29
|
+
'# Format: skill-name|category|triggers|description',
|
|
30
|
+
''
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function updateRegistry(registryPath, entry, skillName) {
|
|
35
|
+
const entryPrefix = skillName + '|';
|
|
36
|
+
let lines;
|
|
37
|
+
|
|
38
|
+
if (fs.existsSync(registryPath)) {
|
|
39
|
+
const content = fs.readFileSync(registryPath, 'utf8').trim();
|
|
40
|
+
lines = content ? content.split('\n') : [];
|
|
41
|
+
} else {
|
|
42
|
+
lines = getRegistryHeader();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let entryExists = false;
|
|
46
|
+
const updatedLines = [];
|
|
47
|
+
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
if (line.startsWith(entryPrefix)) {
|
|
50
|
+
updatedLines.push(entry);
|
|
51
|
+
entryExists = true;
|
|
52
|
+
} else {
|
|
53
|
+
updatedLines.push(line);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!entryExists) {
|
|
58
|
+
updatedLines.push(entry);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let content = updatedLines.join('\n');
|
|
62
|
+
if (!content.endsWith('\n')) {
|
|
63
|
+
content += '\n';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fs.writeFileSync(registryPath, content);
|
|
67
|
+
return content;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function generateFindSkill(findSkillPath, templatePath, registryContent) {
|
|
71
|
+
if (!fs.existsSync(templatePath)) {
|
|
72
|
+
process.stderr.write(`Warning: Template not found at ${templatePath}\n`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const template = fs.readFileSync(templatePath, 'utf8');
|
|
77
|
+
const skillContent = template.replace('{{REGISTRY}}', registryContent.trim());
|
|
78
|
+
|
|
79
|
+
fs.mkdirSync(path.dirname(findSkillPath), { recursive: true });
|
|
80
|
+
fs.writeFileSync(findSkillPath, skillContent);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseArgs(argv) {
|
|
84
|
+
const args = {};
|
|
85
|
+
const flags = ['--project-root', '--skill-name', '--category', '--triggers', '--description'];
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < argv.length; i++) {
|
|
88
|
+
if (flags.includes(argv[i]) && i + 1 < argv.length) {
|
|
89
|
+
// Convert --project-root to projectRoot
|
|
90
|
+
const key = argv[i].slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
91
|
+
args[key] = argv[i + 1];
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return args;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function main() {
|
|
100
|
+
const args = parseArgs(process.argv.slice(2));
|
|
101
|
+
|
|
102
|
+
const required = ['projectRoot', 'skillName', 'category', 'triggers', 'description'];
|
|
103
|
+
for (const key of required) {
|
|
104
|
+
if (!args[key]) {
|
|
105
|
+
process.stderr.write(`Error: missing required argument --${key.replace(/[A-Z]/g, c => '-' + c.toLowerCase())}\n`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const projectRoot = args.projectRoot;
|
|
111
|
+
|
|
112
|
+
// New paths: registry lives inside spectre-recall skill
|
|
113
|
+
const recallDir = path.join(projectRoot, '.claude', 'skills', 'spectre-recall');
|
|
114
|
+
const registryDir = path.join(recallDir, 'references');
|
|
115
|
+
const registryPath = path.join(registryDir, 'registry.toon');
|
|
116
|
+
const recallSkillPath = path.join(recallDir, 'SKILL.md');
|
|
117
|
+
|
|
118
|
+
// Template is in the plugin — resolve via env var (hooks) or __filename (manual invocation)
|
|
119
|
+
let pluginRoot;
|
|
120
|
+
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
121
|
+
pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
|
|
122
|
+
} else {
|
|
123
|
+
// Fallback: resolve relative to this script
|
|
124
|
+
// Script is at: <plugin_root>/hooks/scripts/register_learning.cjs
|
|
125
|
+
pluginRoot = path.resolve(__dirname, '..', '..');
|
|
126
|
+
}
|
|
127
|
+
const templatePath = path.join(pluginRoot, 'skills', 'spectre-learn', 'references', 'recall-template.md');
|
|
128
|
+
|
|
129
|
+
// Ensure directories exist
|
|
130
|
+
fs.mkdirSync(registryDir, { recursive: true });
|
|
131
|
+
|
|
132
|
+
// Build the registry entry
|
|
133
|
+
const entry = `${args.skillName}|${args.category}|${args.triggers}|${args.description}`;
|
|
134
|
+
|
|
135
|
+
// Update registry and get full content
|
|
136
|
+
const registryContent = updateRegistry(registryPath, entry, args.skillName);
|
|
137
|
+
|
|
138
|
+
// Generate recall skill with embedded registry
|
|
139
|
+
generateFindSkill(recallSkillPath, templatePath, registryContent);
|
|
140
|
+
|
|
141
|
+
process.stdout.write(`Registered: ${entry}\n`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
main();
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { describe, it } = require('node:test');
|
|
5
|
+
const assert = require('node:assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const { cleanupStalePaths, STALE_PATHS } = require('./bootstrap.cjs');
|
|
10
|
+
|
|
11
|
+
function createTempDir() {
|
|
12
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'spectre-bootstrap-test-'));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('bootstrap', () => {
|
|
16
|
+
it('STALE_PATHS is a non-empty array', () => {
|
|
17
|
+
assert.ok(Array.isArray(STALE_PATHS));
|
|
18
|
+
assert.ok(STALE_PATHS.length > 0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('removes stale files that exist', () => {
|
|
22
|
+
const tmpDir = createTempDir();
|
|
23
|
+
try {
|
|
24
|
+
// Create a fake stale file
|
|
25
|
+
const scriptsDir = path.join(tmpDir, 'hooks', 'scripts');
|
|
26
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
27
|
+
const staleFile = path.join(scriptsDir, 'capture-todos.py');
|
|
28
|
+
fs.writeFileSync(staleFile, '# old python script');
|
|
29
|
+
|
|
30
|
+
const removed = cleanupStalePaths(tmpDir);
|
|
31
|
+
|
|
32
|
+
assert.ok(removed >= 1, `Expected at least 1 removal, got ${removed}`);
|
|
33
|
+
assert.ok(!fs.existsSync(staleFile), 'Stale file should be deleted');
|
|
34
|
+
} finally {
|
|
35
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('removes stale directories that exist', () => {
|
|
40
|
+
const tmpDir = createTempDir();
|
|
41
|
+
try {
|
|
42
|
+
// Create a fake stale directory
|
|
43
|
+
const staleDir = path.join(tmpDir, 'skills', 'spectre-next-steps');
|
|
44
|
+
fs.mkdirSync(staleDir, { recursive: true });
|
|
45
|
+
fs.writeFileSync(path.join(staleDir, 'SKILL.md'), '# old skill');
|
|
46
|
+
|
|
47
|
+
const removed = cleanupStalePaths(tmpDir);
|
|
48
|
+
|
|
49
|
+
assert.ok(removed >= 1, `Expected at least 1 removal, got ${removed}`);
|
|
50
|
+
assert.ok(!fs.existsSync(staleDir), 'Stale directory should be deleted');
|
|
51
|
+
} finally {
|
|
52
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('returns 0 when no stale files exist', () => {
|
|
57
|
+
const tmpDir = createTempDir();
|
|
58
|
+
try {
|
|
59
|
+
const removed = cleanupStalePaths(tmpDir);
|
|
60
|
+
assert.strictEqual(removed, 0);
|
|
61
|
+
} finally {
|
|
62
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('handles mixed: some files exist, some do not', () => {
|
|
67
|
+
const tmpDir = createTempDir();
|
|
68
|
+
try {
|
|
69
|
+
// Only create 2 of the stale files
|
|
70
|
+
const scriptsDir = path.join(tmpDir, 'hooks', 'scripts');
|
|
71
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
72
|
+
fs.writeFileSync(path.join(scriptsDir, 'handoff-resume.py'), '# old');
|
|
73
|
+
fs.writeFileSync(path.join(scriptsDir, 'load-knowledge.py'), '# old');
|
|
74
|
+
|
|
75
|
+
const removed = cleanupStalePaths(tmpDir);
|
|
76
|
+
|
|
77
|
+
assert.strictEqual(removed, 2);
|
|
78
|
+
assert.ok(!fs.existsSync(path.join(scriptsDir, 'handoff-resume.py')));
|
|
79
|
+
assert.ok(!fs.existsSync(path.join(scriptsDir, 'load-knowledge.py')));
|
|
80
|
+
} finally {
|
|
81
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|