@codename_inc/spectre 3.7.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +3 -4
  2. package/package.json +3 -2
  3. package/plugins/spectre/.claude-plugin/plugin.json +1 -1
  4. package/plugins/spectre/bin/spectre-register +5 -0
  5. package/plugins/spectre/hooks/hooks.json +3 -14
  6. package/plugins/spectre/hooks/scripts/bootstrap.mjs +98 -0
  7. package/plugins/spectre/hooks/scripts/handoff-resume.mjs +404 -0
  8. package/plugins/spectre/hooks/scripts/lib.mjs +82 -0
  9. package/plugins/spectre/hooks/scripts/load-knowledge.mjs +189 -0
  10. package/plugins/spectre/hooks/scripts/register_learning.mjs +264 -0
  11. package/plugins/spectre/hooks/scripts/{test_bootstrap.cjs → test_bootstrap.mjs} +12 -7
  12. package/plugins/spectre/hooks/scripts/{test_handoff-resume.cjs → test_handoff-resume.mjs} +13 -11
  13. package/plugins/spectre/hooks/scripts/{test_load-knowledge.cjs → test_load-knowledge.mjs} +103 -22
  14. package/plugins/spectre/hooks/scripts/test_register-learning.mjs +335 -0
  15. package/plugins/spectre/skills/apply/SKILL.md +87 -0
  16. package/plugins/spectre/{commands/architecture_review.md → skills/architecture_review/SKILL.md} +9 -0
  17. package/plugins/spectre/{commands/clean.md → skills/clean/SKILL.md} +9 -0
  18. package/plugins/spectre/{commands/code_review.md → skills/code_review/SKILL.md} +9 -0
  19. package/plugins/spectre/{commands/create_plan.md → skills/create_plan/SKILL.md} +9 -0
  20. package/plugins/spectre/{commands/create_tasks.md → skills/create_tasks/SKILL.md} +9 -0
  21. package/plugins/spectre/{commands/create_test_guide.md → skills/create_test_guide/SKILL.md} +9 -0
  22. package/plugins/spectre/{commands/evaluate.md → skills/evaluate/SKILL.md} +11 -2
  23. package/plugins/spectre/{commands/execute.md → skills/execute/SKILL.md} +12 -3
  24. package/plugins/spectre/{commands/fix.md → skills/fix/SKILL.md} +9 -0
  25. package/plugins/spectre/{commands/forget.md → skills/forget/SKILL.md} +9 -0
  26. package/plugins/spectre/skills/{spectre-guide → guide}/SKILL.md +2 -1
  27. package/plugins/spectre/{commands/handoff.md → skills/handoff/SKILL.md} +9 -0
  28. package/plugins/spectre/{commands/kickoff.md → skills/kickoff/SKILL.md} +9 -0
  29. package/plugins/spectre/skills/{spectre-learn → learn}/SKILL.md +19 -59
  30. package/plugins/spectre/skills/learn/references/recall-template.md +34 -0
  31. package/plugins/spectre/{commands/plan.md → skills/plan/SKILL.md} +66 -25
  32. package/plugins/spectre/{commands/plan_review.md → skills/plan_review/SKILL.md} +9 -0
  33. package/plugins/spectre/{commands/quick_dev.md → skills/quick_dev/SKILL.md} +9 -0
  34. package/plugins/spectre/{commands/rebase.md → skills/rebase/SKILL.md} +9 -0
  35. package/plugins/spectre/skills/recall/SKILL.md +17 -0
  36. package/plugins/spectre/{commands/research.md → skills/research/SKILL.md} +9 -0
  37. package/plugins/spectre/{commands/scope.md → skills/scope/SKILL.md} +9 -0
  38. package/plugins/spectre/{commands/ship.md → skills/ship/SKILL.md} +9 -0
  39. package/plugins/spectre/{commands/sweep.md → skills/sweep/SKILL.md} +9 -0
  40. package/plugins/spectre/skills/tdd/SKILL.md +111 -0
  41. package/plugins/spectre/{commands/test.md → skills/test/SKILL.md} +9 -0
  42. package/plugins/spectre/{commands/ux_spec.md → skills/ux_spec/SKILL.md} +9 -0
  43. package/plugins/spectre/{commands/validate.md → skills/validate/SKILL.md} +9 -0
  44. package/plugins/spectre-codex/agents/analyst.toml +117 -0
  45. package/plugins/spectre-codex/agents/dev.toml +65 -0
  46. package/plugins/spectre-codex/agents/finder.toml +101 -0
  47. package/plugins/spectre-codex/agents/patterns.toml +203 -0
  48. package/plugins/spectre-codex/agents/reviewer.toml +123 -0
  49. package/plugins/spectre-codex/agents/sync.toml +146 -0
  50. package/plugins/spectre-codex/agents/tester.toml +205 -0
  51. package/plugins/spectre-codex/agents/web-research.toml +104 -0
  52. package/plugins/spectre-codex/hooks/hooks.json +23 -0
  53. package/plugins/{spectre/hooks/scripts/bootstrap.cjs → spectre-codex/hooks/scripts/bootstrap.mjs} +15 -16
  54. package/plugins/{spectre/hooks/scripts/handoff-resume.cjs → spectre-codex/hooks/scripts/handoff-resume.mjs} +21 -27
  55. package/plugins/{spectre/hooks/scripts/lib.cjs → spectre-codex/hooks/scripts/lib.mjs} +3 -4
  56. package/plugins/spectre-codex/hooks/scripts/load-knowledge.mjs +189 -0
  57. package/plugins/spectre-codex/hooks/scripts/register_learning.mjs +264 -0
  58. package/plugins/spectre-codex/skills/apply/SKILL.md +87 -0
  59. package/plugins/spectre-codex/skills/architecture_review/SKILL.md +129 -0
  60. package/plugins/spectre-codex/skills/clean/SKILL.md +322 -0
  61. package/plugins/spectre-codex/skills/code_review/SKILL.md +417 -0
  62. package/plugins/spectre-codex/skills/create_plan/SKILL.md +126 -0
  63. package/plugins/spectre-codex/skills/create_tasks/SKILL.md +383 -0
  64. package/plugins/spectre-codex/skills/create_test_guide/SKILL.md +129 -0
  65. package/plugins/spectre-codex/skills/evaluate/SKILL.md +59 -0
  66. package/plugins/spectre-codex/skills/execute/SKILL.md +96 -0
  67. package/plugins/spectre-codex/skills/fix/SKILL.md +70 -0
  68. package/plugins/spectre-codex/skills/forget/SKILL.md +67 -0
  69. package/plugins/spectre-codex/skills/guide/SKILL.md +359 -0
  70. package/plugins/spectre-codex/skills/handoff/SKILL.md +170 -0
  71. package/plugins/spectre-codex/skills/kickoff/SKILL.md +124 -0
  72. package/plugins/spectre-codex/skills/learn/SKILL.md +595 -0
  73. package/plugins/{spectre/skills/spectre-learn → spectre-codex/skills/learn}/references/recall-template.md +4 -1
  74. package/plugins/spectre-codex/skills/plan/SKILL.md +211 -0
  75. package/plugins/spectre-codex/skills/plan_review/SKILL.md +42 -0
  76. package/plugins/spectre-codex/skills/quick_dev/SKILL.md +110 -0
  77. package/plugins/spectre-codex/skills/rebase/SKILL.md +82 -0
  78. package/plugins/spectre-codex/skills/recall/SKILL.md +17 -0
  79. package/plugins/spectre-codex/skills/research/SKILL.md +168 -0
  80. package/plugins/spectre-codex/skills/scope/SKILL.md +128 -0
  81. package/plugins/spectre-codex/skills/ship/SKILL.md +181 -0
  82. package/plugins/spectre-codex/skills/sweep/SKILL.md +91 -0
  83. package/plugins/{spectre/skills/spectre-tdd → spectre-codex/skills/tdd}/SKILL.md +1 -1
  84. package/plugins/spectre-codex/skills/test/SKILL.md +389 -0
  85. package/plugins/spectre-codex/skills/ux_spec/SKILL.md +100 -0
  86. package/plugins/spectre-codex/skills/validate/SKILL.md +352 -0
  87. package/src/config.test.js +6 -5
  88. package/src/install.test.js +100 -11
  89. package/src/lib/config.js +107 -54
  90. package/src/lib/constants.js +17 -23
  91. package/src/lib/doctor.js +19 -22
  92. package/src/lib/install.js +98 -313
  93. package/src/lib/knowledge.js +7 -37
  94. package/src/lib/paths.js +0 -12
  95. package/src/pack.test.js +87 -0
  96. package/plugins/spectre/commands/learn.md +0 -15
  97. package/plugins/spectre/commands/recall.md +0 -5
  98. package/plugins/spectre/hooks/scripts/load-knowledge.cjs +0 -120
  99. package/plugins/spectre/hooks/scripts/precompact-warning.cjs +0 -19
  100. package/plugins/spectre/hooks/scripts/register_learning.cjs +0 -144
  101. package/plugins/spectre/hooks/scripts/test_register-learning.cjs +0 -146
  102. package/plugins/spectre/skills/spectre-apply/SKILL.md +0 -189
@@ -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,5 +0,0 @@
1
- # /recall - Search Project Knowledge
2
-
3
- Load the `spectre-recall` skill and follow its instructions.
4
-
5
- **Search query**: $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
- });