@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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +411 -0
  3. package/bin/spectre.js +8 -0
  4. package/package.json +23 -0
  5. package/plugins/spectre/.claude-plugin/plugin.json +5 -0
  6. package/plugins/spectre/agents/analyst.md +122 -0
  7. package/plugins/spectre/agents/dev.md +70 -0
  8. package/plugins/spectre/agents/finder.md +105 -0
  9. package/plugins/spectre/agents/patterns.md +207 -0
  10. package/plugins/spectre/agents/reviewer.md +128 -0
  11. package/plugins/spectre/agents/sync.md +151 -0
  12. package/plugins/spectre/agents/tester.md +209 -0
  13. package/plugins/spectre/agents/web-research.md +109 -0
  14. package/plugins/spectre/commands/architecture_review.md +120 -0
  15. package/plugins/spectre/commands/clean.md +313 -0
  16. package/plugins/spectre/commands/code_review.md +408 -0
  17. package/plugins/spectre/commands/create_plan.md +117 -0
  18. package/plugins/spectre/commands/create_tasks.md +374 -0
  19. package/plugins/spectre/commands/create_test_guide.md +120 -0
  20. package/plugins/spectre/commands/evaluate.md +50 -0
  21. package/plugins/spectre/commands/execute.md +87 -0
  22. package/plugins/spectre/commands/fix.md +61 -0
  23. package/plugins/spectre/commands/forget.md +58 -0
  24. package/plugins/spectre/commands/handoff.md +161 -0
  25. package/plugins/spectre/commands/kickoff.md +115 -0
  26. package/plugins/spectre/commands/learn.md +15 -0
  27. package/plugins/spectre/commands/plan.md +170 -0
  28. package/plugins/spectre/commands/plan_review.md +33 -0
  29. package/plugins/spectre/commands/quick_dev.md +101 -0
  30. package/plugins/spectre/commands/rebase.md +73 -0
  31. package/plugins/spectre/commands/recall.md +5 -0
  32. package/plugins/spectre/commands/research.md +159 -0
  33. package/plugins/spectre/commands/scope.md +119 -0
  34. package/plugins/spectre/commands/ship.md +172 -0
  35. package/plugins/spectre/commands/sweep.md +82 -0
  36. package/plugins/spectre/commands/test.md +380 -0
  37. package/plugins/spectre/commands/ux_spec.md +91 -0
  38. package/plugins/spectre/commands/validate.md +343 -0
  39. package/plugins/spectre/hooks/hooks.json +34 -0
  40. package/plugins/spectre/hooks/scripts/bootstrap.cjs +99 -0
  41. package/plugins/spectre/hooks/scripts/handoff-resume.cjs +410 -0
  42. package/plugins/spectre/hooks/scripts/lib.cjs +83 -0
  43. package/plugins/spectre/hooks/scripts/load-knowledge.cjs +120 -0
  44. package/plugins/spectre/hooks/scripts/precompact-warning.cjs +19 -0
  45. package/plugins/spectre/hooks/scripts/register_learning.cjs +144 -0
  46. package/plugins/spectre/hooks/scripts/test_bootstrap.cjs +84 -0
  47. package/plugins/spectre/hooks/scripts/test_handoff-resume.cjs +858 -0
  48. package/plugins/spectre/hooks/scripts/test_load-knowledge.cjs +285 -0
  49. package/plugins/spectre/hooks/scripts/test_register-learning.cjs +146 -0
  50. package/plugins/spectre/skills/spectre-apply/SKILL.md +189 -0
  51. package/plugins/spectre/skills/spectre-guide/SKILL.md +358 -0
  52. package/plugins/spectre/skills/spectre-learn/SKILL.md +635 -0
  53. package/plugins/spectre/skills/spectre-learn/references/recall-template.md +31 -0
  54. package/plugins/spectre/skills/spectre-tdd/SKILL.md +111 -0
  55. package/src/config.test.js +134 -0
  56. package/src/install.test.js +273 -0
  57. package/src/lib/config.js +516 -0
  58. package/src/lib/constants.js +60 -0
  59. package/src/lib/doctor.js +168 -0
  60. package/src/lib/install.js +482 -0
  61. package/src/lib/knowledge.js +217 -0
  62. package/src/lib/paths.js +98 -0
  63. package/src/lib/project.js +473 -0
  64. 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
+ });