@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.
- package/README.md +3 -4
- 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 +2 -1
- 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/{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/{commands/scope.md → skills/scope/SKILL.md} +9 -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/{commands/ux_spec.md → skills/ux_spec/SKILL.md} +9 -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/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 +128 -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_spec/SKILL.md +100 -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/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/pack.test.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { execFileSync } from 'child_process';
|
|
7
|
+
|
|
8
|
+
function makeTempDir(prefix) {
|
|
9
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function exec(command, args, options = {}) {
|
|
13
|
+
return execFileSync(command, args, {
|
|
14
|
+
encoding: 'utf8',
|
|
15
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
16
|
+
...options
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function collectFiles(root) {
|
|
21
|
+
const files = [];
|
|
22
|
+
if (!fs.existsSync(root)) return files;
|
|
23
|
+
|
|
24
|
+
function walk(dir) {
|
|
25
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
26
|
+
const fullPath = path.join(dir, entry.name);
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
walk(fullPath);
|
|
29
|
+
} else if (entry.isFile()) {
|
|
30
|
+
files.push(fullPath);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
walk(root);
|
|
36
|
+
return files;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
test('packed npm artifact installs Codex assets from generated tree', { concurrency: false }, () => {
|
|
40
|
+
const repoRoot = path.resolve('.');
|
|
41
|
+
const packDir = makeTempDir('spectre-pack-');
|
|
42
|
+
const projectDir = makeTempDir('spectre-pack-install-');
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const packOutput = exec('npm', ['pack', '--pack-destination', packDir], { cwd: repoRoot }).trim();
|
|
46
|
+
const tarball = path.join(packDir, packOutput.split('\n').at(-1));
|
|
47
|
+
|
|
48
|
+
exec('npm', ['init', '-y'], { cwd: projectDir });
|
|
49
|
+
exec('npm', ['install', '--ignore-scripts', '--no-audit', '--no-fund', tarball], { cwd: projectDir });
|
|
50
|
+
exec('git', ['init', '-b', 'main'], { cwd: projectDir });
|
|
51
|
+
|
|
52
|
+
const env = { ...process.env };
|
|
53
|
+
delete env.CODEX_HOME;
|
|
54
|
+
|
|
55
|
+
const unscopedHelp = exec('npx', ['spectre', 'help'], {
|
|
56
|
+
cwd: projectDir,
|
|
57
|
+
env
|
|
58
|
+
});
|
|
59
|
+
assert.match(unscopedHelp, /spectre install codex/);
|
|
60
|
+
|
|
61
|
+
exec('npx', ['@codename_inc/spectre', 'install', 'codex', '--scope', 'project', '--project-dir', projectDir], {
|
|
62
|
+
cwd: projectDir,
|
|
63
|
+
env
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const codexHome = path.join(projectDir, '.codex');
|
|
67
|
+
assert.ok(fs.existsSync(path.join(codexHome, 'skills', 'plan', 'SKILL.md')));
|
|
68
|
+
assert.ok(fs.existsSync(path.join(codexHome, 'spectre', 'agents', 'dev.toml')));
|
|
69
|
+
assert.ok(fs.existsSync(path.join(codexHome, 'spectre', 'hooks', 'scripts', 'load-knowledge.mjs')));
|
|
70
|
+
assert.ok(!fs.existsSync(path.join(codexHome, 'spectre', 'hooks', 'session-start.mjs')));
|
|
71
|
+
|
|
72
|
+
const hooksConfig = JSON.parse(fs.readFileSync(path.join(codexHome, 'hooks.json'), 'utf8'));
|
|
73
|
+
assert.ok(hooksConfig.hooks.SessionStart.some(group =>
|
|
74
|
+
Array.isArray(group.hooks) && group.hooks.some(hook => hook.command.includes('spectre/hooks/scripts/load-knowledge.mjs'))
|
|
75
|
+
));
|
|
76
|
+
|
|
77
|
+
const runtimeFiles = collectFiles(path.join(codexHome, 'spectre'));
|
|
78
|
+
for (const filePath of runtimeFiles) {
|
|
79
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
80
|
+
assert.doesNotMatch(content, /file:\/\//, `${filePath} should not contain package-cache file:// imports`);
|
|
81
|
+
assert.doesNotMatch(content, new RegExp(repoRoot.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), `${filePath} should not reference the repo checkout`);
|
|
82
|
+
}
|
|
83
|
+
} finally {
|
|
84
|
+
fs.rmSync(packDir, { recursive: true, force: true });
|
|
85
|
+
fs.rmSync(projectDir, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "\ud83d\udc7b | Capture knowledge for future sessions"
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# /learn - Capture Project Knowledge
|
|
6
|
-
|
|
7
|
-
Load the `spectre-learn` skill via `Skill(spectre-learn)` and **execute its workflow step-by-step**.
|
|
8
|
-
|
|
9
|
-
<CRITICAL>
|
|
10
|
-
This skill supersedes auto-memory. Do NOT write to MEMORY.md or any auto-memory directory. The skill defines the exclusive storage location, format, and registration workflow for captured knowledge.
|
|
11
|
-
|
|
12
|
-
Treat the skill output as a binding directive, not informational context.
|
|
13
|
-
</CRITICAL>
|
|
14
|
-
|
|
15
|
-
**Topic**: $ARGUMENTS
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* load-knowledge.cjs
|
|
6
|
-
*
|
|
7
|
-
* SessionStart hook that injects the apply skill content with embedded registry
|
|
8
|
-
* directly into Claude's context.
|
|
9
|
-
*
|
|
10
|
-
* Reads:
|
|
11
|
-
* - Apply skill from plugin: skills/spectre-apply/SKILL.md
|
|
12
|
-
* - Registry from project: .claude/skills/spectre-recall/references/registry.toon
|
|
13
|
-
*
|
|
14
|
-
* Combines them by replacing the Registry Location section with actual registry content.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const fs = require('fs');
|
|
18
|
-
const path = require('path');
|
|
19
|
-
|
|
20
|
-
function countRegistryEntries(lines) {
|
|
21
|
-
let count = 0;
|
|
22
|
-
for (const line of lines) {
|
|
23
|
-
if (line.trim() && line.includes('|') && !line.startsWith('#')) {
|
|
24
|
-
count++;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return count;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function stripFrontmatter(content) {
|
|
31
|
-
if (content.startsWith('---')) {
|
|
32
|
-
const end = content.indexOf('---', 3);
|
|
33
|
-
if (end !== -1) {
|
|
34
|
-
return content.slice(end + 3).trim();
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return content;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function main() {
|
|
41
|
-
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
42
|
-
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || '';
|
|
43
|
-
|
|
44
|
-
const applySkillPath = path.join(pluginRoot, 'skills', 'spectre-apply', 'SKILL.md');
|
|
45
|
-
|
|
46
|
-
if (!fs.existsSync(applySkillPath)) {
|
|
47
|
-
process.exit(0);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Paths - check new name first, fall back to old names for migration
|
|
51
|
-
let registryPath = path.join(projectDir, '.claude', 'skills', 'spectre-recall', 'references', 'registry.toon');
|
|
52
|
-
const oldRegistryPath = path.join(projectDir, '.claude', 'skills', 'spectre-find', 'references', 'registry.toon');
|
|
53
|
-
|
|
54
|
-
// Support old "spectre-find" path for projects that haven't migrated
|
|
55
|
-
if (!fs.existsSync(registryPath) && fs.existsSync(oldRegistryPath)) {
|
|
56
|
-
registryPath = oldRegistryPath;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Read registry if it exists
|
|
60
|
-
let registryContent = '';
|
|
61
|
-
let entryCount = 0;
|
|
62
|
-
if (fs.existsSync(registryPath)) {
|
|
63
|
-
registryContent = fs.readFileSync(registryPath, 'utf8').trim();
|
|
64
|
-
const lines = registryContent ? registryContent.split('\n') : [];
|
|
65
|
-
entryCount = countRegistryEntries(lines);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Read apply skill and strip frontmatter
|
|
69
|
-
let applyContent = fs.readFileSync(applySkillPath, 'utf8');
|
|
70
|
-
applyContent = stripFrontmatter(applyContent);
|
|
71
|
-
|
|
72
|
-
// Replace the Registry Location section with embedded registry or empty notice
|
|
73
|
-
let registrySection;
|
|
74
|
-
if (entryCount > 0) {
|
|
75
|
-
registrySection =
|
|
76
|
-
'## Registry\n\n' +
|
|
77
|
-
'**Format**: `skill-name|category|triggers|description`\n\n' +
|
|
78
|
-
'```\n' +
|
|
79
|
-
registryContent + '\n' +
|
|
80
|
-
'```\n\n' +
|
|
81
|
-
'Each entry corresponds to a skill that can be loaded via `Skill({skill-name})`\n\n' +
|
|
82
|
-
'**Categories:** feature, gotchas, patterns, decisions, procedures, integration, performance, testing, ux, strategy';
|
|
83
|
-
} else {
|
|
84
|
-
registrySection =
|
|
85
|
-
'## Registry\n\n' +
|
|
86
|
-
'No knowledge has been captured for this project yet. The behavioral rules in this document still apply.\n\n' +
|
|
87
|
-
'To capture knowledge from this session, use `/spectre:learn` after completing significant work.\n\n' +
|
|
88
|
-
'**Categories:** feature, gotchas, patterns, decisions, procedures, integration, performance, testing, ux, strategy';
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Replace the Registry Location section
|
|
92
|
-
applyContent = applyContent.replace(
|
|
93
|
-
/## Registry Location[\s\S]*?(?=## Workflow)/,
|
|
94
|
-
registrySection + '\n\n'
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
// Build final context
|
|
98
|
-
const context = `<spectre-knowledge>\n${applyContent}\n</spectre-knowledge>`;
|
|
99
|
-
|
|
100
|
-
// Visible notice
|
|
101
|
-
let visibleNotice;
|
|
102
|
-
if (entryCount > 0) {
|
|
103
|
-
visibleNotice = `\ud83d\udc7b spectre: ${entryCount} knowledge skills available`;
|
|
104
|
-
} else {
|
|
105
|
-
visibleNotice = '\ud83d\udc7b spectre: ready \u2014 capture knowledge with /spectre:learn';
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const output = {
|
|
109
|
-
systemMessage: visibleNotice,
|
|
110
|
-
hookSpecificOutput: {
|
|
111
|
-
hookEventName: 'SessionStart',
|
|
112
|
-
additionalContext: context
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
process.stdout.write(JSON.stringify(output) + '\n');
|
|
117
|
-
process.exit(0);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
main();
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* precompact-warning.cjs
|
|
6
|
-
*
|
|
7
|
-
* PreCompact hook that suggests using /spectre:handoff + /clear
|
|
8
|
-
* instead of auto-compact for better context continuity.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const output = {
|
|
12
|
-
systemMessage:
|
|
13
|
-
'\u26a0\ufe0f Auto-compact can cause context loss. ' +
|
|
14
|
-
'For full continuity: /spectre:handoff \u2192 /clear \u2192 new session. ' +
|
|
15
|
-
'Consider disabling auto-compact in /config.'
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
process.stdout.write(JSON.stringify(output) + '\n');
|
|
19
|
-
process.exit(0);
|
|
@@ -1,144 +0,0 @@
|
|
|
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();
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Tests for register_learning.cjs
|
|
6
|
-
*
|
|
7
|
-
* Run with: node --test plugins/spectre/hooks/scripts/test_register-learning.cjs
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const { describe, it } = require('node:test');
|
|
11
|
-
const assert = require('node:assert/strict');
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
const path = require('path');
|
|
14
|
-
const os = require('os');
|
|
15
|
-
const { execFileSync } = require('child_process');
|
|
16
|
-
|
|
17
|
-
const SCRIPT_PATH = path.join(__dirname, 'register_learning.cjs');
|
|
18
|
-
|
|
19
|
-
function createTmpDir() {
|
|
20
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), 'spectre-rl-'));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function cleanup(dir) {
|
|
24
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function runScript(args, opts) {
|
|
28
|
-
opts = opts || {};
|
|
29
|
-
const env = Object.assign({}, process.env);
|
|
30
|
-
if (opts.pluginRoot) {
|
|
31
|
-
env.CLAUDE_PLUGIN_ROOT = opts.pluginRoot;
|
|
32
|
-
} else {
|
|
33
|
-
delete env.CLAUDE_PLUGIN_ROOT;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const stdout = execFileSync(process.execPath, [SCRIPT_PATH, ...args], {
|
|
38
|
-
env,
|
|
39
|
-
timeout: 10000,
|
|
40
|
-
encoding: 'utf8'
|
|
41
|
-
});
|
|
42
|
-
return { stdout, exitCode: 0 };
|
|
43
|
-
} catch (err) {
|
|
44
|
-
return { stdout: err.stdout || '', stderr: err.stderr || '', exitCode: err.status };
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
describe('register_learning', () => {
|
|
49
|
-
it('creates new registry with entry', () => {
|
|
50
|
-
const tmp = createTmpDir();
|
|
51
|
-
try {
|
|
52
|
-
const result = runScript([
|
|
53
|
-
'--project-root', tmp,
|
|
54
|
-
'--skill-name', 'feature-auth',
|
|
55
|
-
'--category', 'feature',
|
|
56
|
-
'--triggers', 'auth, login',
|
|
57
|
-
'--description', 'Use when working on authentication'
|
|
58
|
-
]);
|
|
59
|
-
|
|
60
|
-
assert.equal(result.exitCode, 0);
|
|
61
|
-
assert.ok(result.stdout.includes('Registered:'));
|
|
62
|
-
|
|
63
|
-
const registryPath = path.join(tmp, '.claude', 'skills', 'spectre-recall', 'references', 'registry.toon');
|
|
64
|
-
assert.ok(fs.existsSync(registryPath));
|
|
65
|
-
|
|
66
|
-
const content = fs.readFileSync(registryPath, 'utf8');
|
|
67
|
-
assert.ok(content.includes('# SPECTRE Knowledge Registry'));
|
|
68
|
-
assert.ok(content.includes('feature-auth|feature|auth, login|Use when working on authentication'));
|
|
69
|
-
} finally {
|
|
70
|
-
cleanup(tmp);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('updates existing entry by skill name', () => {
|
|
75
|
-
const tmp = createTmpDir();
|
|
76
|
-
try {
|
|
77
|
-
// First registration
|
|
78
|
-
runScript([
|
|
79
|
-
'--project-root', tmp,
|
|
80
|
-
'--skill-name', 'feature-auth',
|
|
81
|
-
'--category', 'feature',
|
|
82
|
-
'--triggers', 'auth',
|
|
83
|
-
'--description', 'Old description'
|
|
84
|
-
]);
|
|
85
|
-
|
|
86
|
-
// Second registration with same skill name
|
|
87
|
-
runScript([
|
|
88
|
-
'--project-root', tmp,
|
|
89
|
-
'--skill-name', 'feature-auth',
|
|
90
|
-
'--category', 'feature',
|
|
91
|
-
'--triggers', 'auth, login, oauth',
|
|
92
|
-
'--description', 'Updated description'
|
|
93
|
-
]);
|
|
94
|
-
|
|
95
|
-
const registryPath = path.join(tmp, '.claude', 'skills', 'spectre-recall', 'references', 'registry.toon');
|
|
96
|
-
const content = fs.readFileSync(registryPath, 'utf8');
|
|
97
|
-
|
|
98
|
-
// Should have the updated entry, not the old one
|
|
99
|
-
assert.ok(content.includes('Updated description'));
|
|
100
|
-
assert.ok(!content.includes('Old description'));
|
|
101
|
-
|
|
102
|
-
// Should only have one entry for feature-auth
|
|
103
|
-
const entries = content.split('\n').filter(l => l.startsWith('feature-auth|'));
|
|
104
|
-
assert.equal(entries.length, 1);
|
|
105
|
-
} finally {
|
|
106
|
-
cleanup(tmp);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('generates recall skill with template', () => {
|
|
111
|
-
const tmp = createTmpDir();
|
|
112
|
-
const pluginRoot = path.join(tmp, 'plugin');
|
|
113
|
-
|
|
114
|
-
// Create template
|
|
115
|
-
const templateDir = path.join(pluginRoot, 'skills', 'spectre-learn', 'references');
|
|
116
|
-
fs.mkdirSync(templateDir, { recursive: true });
|
|
117
|
-
fs.writeFileSync(
|
|
118
|
-
path.join(templateDir, 'recall-template.md'),
|
|
119
|
-
'# Recall Skill\n\nRegistry:\n{{REGISTRY}}\n\nEnd.\n'
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
runScript([
|
|
124
|
-
'--project-root', tmp,
|
|
125
|
-
'--skill-name', 'feature-test',
|
|
126
|
-
'--category', 'feature',
|
|
127
|
-
'--triggers', 'test',
|
|
128
|
-
'--description', 'Test skill'
|
|
129
|
-
], { pluginRoot });
|
|
130
|
-
|
|
131
|
-
const skillPath = path.join(tmp, '.claude', 'skills', 'spectre-recall', 'SKILL.md');
|
|
132
|
-
assert.ok(fs.existsSync(skillPath));
|
|
133
|
-
|
|
134
|
-
const content = fs.readFileSync(skillPath, 'utf8');
|
|
135
|
-
assert.ok(content.includes('# Recall Skill'));
|
|
136
|
-
assert.ok(content.includes('feature-test|feature|test|Test skill'));
|
|
137
|
-
} finally {
|
|
138
|
-
cleanup(tmp);
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('fails with missing required arguments', () => {
|
|
143
|
-
const result = runScript(['--project-root', '/tmp/fake']);
|
|
144
|
-
assert.notEqual(result.exitCode, 0);
|
|
145
|
-
});
|
|
146
|
-
});
|