@codename_inc/spectre 3.7.0 → 5.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 (106) hide show
  1. package/README.md +6 -7
  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 +6 -5
  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/skills/prototype/SKILL.md +314 -0
  34. package/plugins/spectre/{commands/quick_dev.md → skills/quick_dev/SKILL.md} +9 -0
  35. package/plugins/spectre/{commands/rebase.md → skills/rebase/SKILL.md} +9 -0
  36. package/plugins/spectre/skills/recall/SKILL.md +17 -0
  37. package/plugins/spectre/{commands/research.md → skills/research/SKILL.md} +9 -0
  38. package/plugins/spectre/skills/scope/SKILL.md +174 -0
  39. package/plugins/spectre/{commands/ship.md → skills/ship/SKILL.md} +9 -0
  40. package/plugins/spectre/{commands/sweep.md → skills/sweep/SKILL.md} +9 -0
  41. package/plugins/spectre/skills/tdd/SKILL.md +111 -0
  42. package/plugins/spectre/{commands/test.md → skills/test/SKILL.md} +9 -0
  43. package/plugins/spectre/skills/ux/SKILL.md +121 -0
  44. package/plugins/spectre/{commands/validate.md → skills/validate/SKILL.md} +9 -0
  45. package/plugins/spectre-codex/agents/analyst.toml +117 -0
  46. package/plugins/spectre-codex/agents/dev.toml +65 -0
  47. package/plugins/spectre-codex/agents/finder.toml +101 -0
  48. package/plugins/spectre-codex/agents/patterns.toml +203 -0
  49. package/plugins/spectre-codex/agents/reviewer.toml +123 -0
  50. package/plugins/spectre-codex/agents/sync.toml +146 -0
  51. package/plugins/spectre-codex/agents/tester.toml +205 -0
  52. package/plugins/spectre-codex/agents/web-research.toml +104 -0
  53. package/plugins/spectre-codex/hooks/hooks.json +23 -0
  54. package/plugins/{spectre/hooks/scripts/bootstrap.cjs → spectre-codex/hooks/scripts/bootstrap.mjs} +15 -16
  55. package/plugins/{spectre/hooks/scripts/handoff-resume.cjs → spectre-codex/hooks/scripts/handoff-resume.mjs} +21 -27
  56. package/plugins/{spectre/hooks/scripts/lib.cjs → spectre-codex/hooks/scripts/lib.mjs} +3 -4
  57. package/plugins/spectre-codex/hooks/scripts/load-knowledge.mjs +189 -0
  58. package/plugins/spectre-codex/hooks/scripts/register_learning.mjs +264 -0
  59. package/plugins/spectre-codex/skills/apply/SKILL.md +87 -0
  60. package/plugins/spectre-codex/skills/architecture_review/SKILL.md +129 -0
  61. package/plugins/spectre-codex/skills/clean/SKILL.md +322 -0
  62. package/plugins/spectre-codex/skills/code_review/SKILL.md +417 -0
  63. package/plugins/spectre-codex/skills/create_plan/SKILL.md +126 -0
  64. package/plugins/spectre-codex/skills/create_tasks/SKILL.md +383 -0
  65. package/plugins/spectre-codex/skills/create_test_guide/SKILL.md +129 -0
  66. package/plugins/spectre-codex/skills/evaluate/SKILL.md +59 -0
  67. package/plugins/spectre-codex/skills/execute/SKILL.md +96 -0
  68. package/plugins/spectre-codex/skills/fix/SKILL.md +70 -0
  69. package/plugins/spectre-codex/skills/forget/SKILL.md +67 -0
  70. package/plugins/spectre-codex/skills/guide/SKILL.md +359 -0
  71. package/plugins/spectre-codex/skills/handoff/SKILL.md +170 -0
  72. package/plugins/spectre-codex/skills/kickoff/SKILL.md +124 -0
  73. package/plugins/spectre-codex/skills/learn/SKILL.md +595 -0
  74. package/plugins/{spectre/skills/spectre-learn → spectre-codex/skills/learn}/references/recall-template.md +4 -1
  75. package/plugins/spectre-codex/skills/plan/SKILL.md +211 -0
  76. package/plugins/spectre-codex/skills/plan_review/SKILL.md +42 -0
  77. package/plugins/spectre-codex/skills/prototype/SKILL.md +314 -0
  78. package/plugins/spectre-codex/skills/quick_dev/SKILL.md +110 -0
  79. package/plugins/spectre-codex/skills/rebase/SKILL.md +82 -0
  80. package/plugins/spectre-codex/skills/recall/SKILL.md +17 -0
  81. package/plugins/spectre-codex/skills/research/SKILL.md +168 -0
  82. package/plugins/spectre-codex/skills/scope/SKILL.md +174 -0
  83. package/plugins/spectre-codex/skills/ship/SKILL.md +181 -0
  84. package/plugins/spectre-codex/skills/sweep/SKILL.md +91 -0
  85. package/plugins/{spectre/skills/spectre-tdd → spectre-codex/skills/tdd}/SKILL.md +1 -1
  86. package/plugins/spectre-codex/skills/test/SKILL.md +389 -0
  87. package/plugins/spectre-codex/skills/ux/SKILL.md +121 -0
  88. package/plugins/spectre-codex/skills/validate/SKILL.md +352 -0
  89. package/src/config.test.js +6 -5
  90. package/src/install.test.js +100 -11
  91. package/src/lib/config.js +107 -54
  92. package/src/lib/constants.js +17 -23
  93. package/src/lib/doctor.js +19 -22
  94. package/src/lib/install.js +98 -313
  95. package/src/lib/knowledge.js +7 -37
  96. package/src/lib/paths.js +0 -12
  97. package/src/pack.test.js +87 -0
  98. package/plugins/spectre/commands/learn.md +0 -15
  99. package/plugins/spectre/commands/recall.md +0 -5
  100. package/plugins/spectre/commands/scope.md +0 -119
  101. package/plugins/spectre/commands/ux_spec.md +0 -91
  102. package/plugins/spectre/hooks/scripts/load-knowledge.cjs +0 -120
  103. package/plugins/spectre/hooks/scripts/precompact-warning.cjs +0 -19
  104. package/plugins/spectre/hooks/scripts/register_learning.cjs +0 -144
  105. package/plugins/spectre/hooks/scripts/test_register-learning.cjs +0 -146
  106. package/plugins/spectre/skills/spectre-apply/SKILL.md +0 -189
@@ -0,0 +1,104 @@
1
+ name = "web_research"
2
+ description = "Do you find yourself desiring information that you don't quite feel well-trained (confident) on? Information that is modern and potentially only discoverable on the web? Use the researcher subagent_type today to find any and all answers to your questions! It will research deeply to figure out and attempt to answer your questions! If you aren't immediately satisfied you can get your money back! (Not really - but you can re-run researcher with an altered prompt in the event you're not satisfied the first time)"
3
+ sandbox_mode = "read-only"
4
+ developer_instructions = """You are an expert web research specialist focused on finding accurate, relevant information from web sources. Your primary tools are WebSearch and WebFetch, which you use to discover and retrieve information based on user queries.
5
+
6
+ ## Core Responsibilities
7
+
8
+ When you receive a research query, you will:
9
+
10
+ 1. **Analyze the Query**: Break down the user's request to identify:
11
+ - Key search terms and concepts
12
+ - Types of sources likely to have answers (documentation, blogs, forums, academic papers)
13
+ - Multiple search angles to ensure comprehensive coverage
14
+
15
+ 2. **Execute Strategic Searches**:
16
+ - Start with broad searches to understand the landscape
17
+ - Refine with specific technical terms and phrases
18
+ - Use multiple search variations to capture different perspectives
19
+ - Include site-specific searches when targeting known authoritative sources (e.g., "site:docs.stripe.com webhook signature")
20
+
21
+ 3. **Fetch and Analyze Content**:
22
+ - Use WebFetch to retrieve full content from promising search results
23
+ - Prioritize official documentation, reputable technical blogs, and authoritative sources
24
+ - Extract specific quotes and sections relevant to the query
25
+ - Note publication dates to ensure currency of information
26
+
27
+ 4. **Synthesize Findings**:
28
+ - Organize information by relevance and authority
29
+ - Include exact quotes with proper attribution
30
+ - Provide direct links to sources
31
+ - Highlight any conflicting information or version-specific details
32
+ - Note any gaps in available information
33
+
34
+ ## Search Strategies
35
+
36
+ ### For API/Library Documentation:
37
+ - Search for official docs first: "[library name] official documentation [specific feature]"
38
+ - Look for changelog or release notes for version-specific information
39
+ - Find code examples in official repositories or trusted tutorials
40
+
41
+ ### For Best Practices:
42
+ - Search for recent articles (include year in search when relevant)
43
+ - Look for content from recognized experts or organizations
44
+ - Cross-reference multiple sources to identify consensus
45
+ - Search for both "best practices" and "anti-patterns" to get full picture
46
+
47
+ ### For Technical Solutions:
48
+ - Use specific error messages or technical terms in quotes
49
+ - Search Stack Overflow and technical forums for real-world solutions
50
+ - Look for GitHub issues and discussions in relevant repositories
51
+ - Find blog posts describing similar implementations
52
+
53
+ ### For Comparisons:
54
+ - Search for "X vs Y" comparisons
55
+ - Look for migration guides between technologies
56
+ - Find benchmarks and performance comparisons
57
+ - Search for decision matrices or evaluation criteria
58
+
59
+ ## Output Format
60
+
61
+ Structure your findings as:
62
+
63
+ ```
64
+ ## Summary
65
+ [Brief overview of key findings]
66
+
67
+ ## Detailed Findings
68
+
69
+ ### [Topic/Source 1]
70
+ **Source**: [Name with link]
71
+ **Relevance**: [Why this source is authoritative/useful]
72
+ **Key Information**:
73
+ - Direct quote or finding (with link to specific section if possible)
74
+ - Another relevant point
75
+
76
+ ### [Topic/Source 2]
77
+ [Continue pattern...]
78
+
79
+ ## Additional Resources
80
+ - [Relevant link 1] - Brief description
81
+ - [Relevant link 2] - Brief description
82
+
83
+ ## Gaps or Limitations
84
+ [Note any information that couldn't be found or requires further investigation]
85
+ ```
86
+
87
+ ## Quality Guidelines
88
+
89
+ - **Accuracy**: Always quote sources accurately and provide direct links
90
+ - **Relevance**: Focus on information that directly addresses the user's query
91
+ - **Currency**: Note publication dates and version information when relevant
92
+ - **Authority**: Prioritize official sources, recognized experts, and peer-reviewed content
93
+ - **Completeness**: Search from multiple angles to ensure comprehensive coverage
94
+ - **Transparency**: Clearly indicate when information is outdated, conflicting, or uncertain
95
+
96
+ ## Search Efficiency
97
+
98
+ - Start with 2-3 well-crafted searches before fetching content
99
+ - Fetch only the most promising 3-5 pages initially
100
+ - If initial results are insufficient, refine search terms and try again
101
+ - Use search operators effectively: quotes for exact phrases, minus for exclusions, site: for specific domains
102
+ - Consider searching in different forms: tutorials, documentation, Q&A sites, and discussion forums
103
+
104
+ Remember: You are the user's expert guide to web information. Be thorough but efficient, always cite your sources, and provide actionable information that directly addresses their needs. Think deeply as you work."""
@@ -0,0 +1,23 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "startup|clear|compact",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node ${CODEX_HOME}/spectre/hooks/scripts/bootstrap.mjs"
10
+ },
11
+ {
12
+ "type": "command",
13
+ "command": "node ${CODEX_HOME}/spectre/hooks/scripts/handoff-resume.mjs"
14
+ },
15
+ {
16
+ "type": "command",
17
+ "command": "node ${CODEX_HOME}/spectre/hooks/scripts/load-knowledge.mjs"
18
+ }
19
+ ]
20
+ }
21
+ ]
22
+ }
23
+ }
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';
3
2
 
4
3
  /**
5
- * bootstrap.cjs
4
+ * bootstrap.mjs
6
5
  *
7
6
  * SessionStart hook that removes stale files from older plugin versions.
8
7
  *
@@ -14,20 +13,25 @@
14
13
  * Paths are relative to CLAUDE_PLUGIN_ROOT.
15
14
  */
16
15
 
17
- const fs = require('fs');
18
- const path = require('path');
19
- const { readStdinWithTimeout } = require('./lib.cjs');
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { readStdinWithTimeout } from './lib.mjs';
20
+
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = path.dirname(__filename);
20
23
 
21
24
  // ──────────────────────────────────────────────────────────────────
22
25
  // Stale paths to remove (relative to CLAUDE_PLUGIN_ROOT)
23
26
  // ──────────────────────────────────────────────────────────────────
24
27
 
25
28
  const STALE_PATHS = [
26
- // Python scripts replaced by .cjs equivalents (v3.x migration)
29
+ // Python scripts replaced by JS hook equivalents (v3.x migration)
27
30
  'hooks/scripts/capture-todos.py',
28
31
  'hooks/scripts/handoff-resume.py',
29
32
  'hooks/scripts/load-knowledge.py',
30
33
  'hooks/scripts/precompact-warning.py',
34
+ 'hooks/scripts/precompact-warning.mjs',
31
35
  'hooks/scripts/register_learning.py',
32
36
  'hooks/scripts/test_handoff_resume.py',
33
37
  'hooks/scripts/test_load_knowledge.py',
@@ -72,11 +76,7 @@ async function main() {
72
76
  // Drain stdin so the hook system doesn't hang
73
77
  await readStdinWithTimeout();
74
78
 
75
- const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
76
- if (!pluginRoot) {
77
- process.stdout.write(JSON.stringify({}) + '\n');
78
- process.exit(0);
79
- }
79
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || path.resolve(__dirname, '..', '..');
80
80
 
81
81
  const removed = cleanupStalePaths(pluginRoot);
82
82
 
@@ -91,9 +91,8 @@ async function main() {
91
91
  process.exit(0);
92
92
  }
93
93
 
94
- // Export for testing
95
- if (typeof module !== 'undefined') {
96
- module.exports = { cleanupStalePaths, STALE_PATHS };
97
- }
94
+ export { cleanupStalePaths, STALE_PATHS };
98
95
 
99
- main();
96
+ if (process.argv[1] && fs.realpathSync(path.resolve(process.argv[1])) === fs.realpathSync(__filename)) {
97
+ main();
98
+ }
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';
3
2
 
4
3
  /**
5
- * handoff-resume.cjs
4
+ * handoff-resume.mjs
6
5
  *
7
6
  * SessionStart hook that injects context from the last /spectre:handoff.
8
7
  * Consolidates the previous session-resume-hook.sh + format-resume-context.py.
@@ -15,17 +14,25 @@
15
14
  * - --bg-copy-refs: Copy plugin references (fork #1)
16
15
  */
17
16
 
18
- const fs = require('fs');
19
- const path = require('path');
20
- const { fork } = require('child_process');
21
- const { readStdinWithTimeout, getGitBranch } = require('./lib.cjs');
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import { fork } from 'node:child_process';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { readStdinWithTimeout, getGitBranch } from './lib.mjs';
22
+
23
+ const __filename = fileURLToPath(import.meta.url);
24
+ const __dirname = path.dirname(__filename);
25
+
26
+ function getPluginRoot() {
27
+ return process.env.CLAUDE_PLUGIN_ROOT || path.resolve(__dirname, '..', '..');
28
+ }
22
29
 
23
30
  // ──────────────────────────────────────────────────────────────────
24
31
  // Plugin reference copying (background fork #1)
25
32
  // ──────────────────────────────────────────────────────────────────
26
33
 
27
34
  function copyPluginReferences() {
28
- const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
35
+ const pluginRoot = getPluginRoot();
29
36
  if (!pluginRoot) return;
30
37
 
31
38
  const referencesSrc = path.join(pluginRoot, 'references');
@@ -198,14 +205,7 @@ function formatContext(data, opts) {
198
205
  const checkboxTree = (beadsAvailable && tasks.length) ? buildCheckboxTree(tasks) : '';
199
206
 
200
207
  // User-visible notice
201
- const asciiBanner = [
202
- '',
203
- '\u2591\u2588\u2580\u2580\u2591\u2588\u2580\u2588\u2591\u2588\u2580\u2580\u2591\u2588\u2580\u2580\u2591\u2580\u2588\u2580\u2591\u2588\u2580\u2584\u2591\u2588\u2580\u2580',
204
- '\u2591\u2580\u2580\u2588\u2591\u2588\u2580\u2580\u2591\u2588\u2580\u2580\u2591\u2588\u2591\u2591\u2591\u2591\u2588\u2591\u2591\u2588\u2580\u2584\u2591\u2588\u2580\u2580',
205
- '\u2591\u2580\u2580\u2580\u2591\u2580\u2591\u2591\u2591\u2580\u2580\u2580\u2591\u2580\u2580\u2580\u2591\u2591\u2580\u2591\u2591\u2580\u2591\u2580\u2591\u2580\u2580\u2580'
206
- ].join('\n');
207
-
208
- const noticeLines = [asciiBanner];
208
+ const noticeLines = ['\ud83d\udc7b SPECTRE'];
209
209
  noticeLines.push(`\n\ud83d\udd04 Session Resumed: ${taskName} | Branch: ${branchName}`);
210
210
 
211
211
  if (goal) {
@@ -353,12 +353,7 @@ async function main() {
353
353
 
354
354
  if (!latestHandoff) {
355
355
  // No session to resume - show welcome banner with tips
356
- const asciiBanner = [
357
- '',
358
- '\u2591\u2588\u2580\u2580\u2591\u2588\u2580\u2588\u2591\u2588\u2580\u2580\u2591\u2588\u2580\u2580\u2591\u2580\u2588\u2580\u2591\u2588\u2580\u2584\u2591\u2588\u2580\u2580',
359
- '\u2591\u2580\u2580\u2588\u2591\u2588\u2580\u2580\u2591\u2588\u2580\u2580\u2591\u2588\u2591\u2591\u2591\u2591\u2588\u2591\u2591\u2588\u2580\u2584\u2591\u2588\u2580\u2580',
360
- '\u2591\u2580\u2580\u2580\u2591\u2580\u2591\u2591\u2591\u2580\u2580\u2580\u2591\u2580\u2580\u2580\u2591\u2591\u2580\u2591\u2591\u2580\u2591\u2580\u2591\u2580\u2580\u2580'
361
- ].join('\n');
356
+ const banner = '\ud83d\udc7b SPECTRE';
362
357
  const tips = [
363
358
  '',
364
359
  'Getting Started with SPECTRE:',
@@ -371,7 +366,7 @@ async function main() {
371
366
  ].join('\n');
372
367
 
373
368
  const welcome = {
374
- systemMessage: asciiBanner + '\n' + tips
369
+ systemMessage: banner + '\n' + tips
375
370
  };
376
371
  process.stdout.write(JSON.stringify(welcome) + '\n');
377
372
  process.exit(0);
@@ -402,9 +397,8 @@ async function main() {
402
397
  process.exit(0);
403
398
  }
404
399
 
405
- // Export for testing
406
- if (typeof module !== 'undefined') {
407
- module.exports = { copyPluginReferences, formatContext, buildCheckboxTree };
408
- }
400
+ export { copyPluginReferences, formatContext, buildCheckboxTree };
409
401
 
410
- main();
402
+ if (process.argv[1] && fs.realpathSync(path.resolve(process.argv[1])) === fs.realpathSync(__filename)) {
403
+ main();
404
+ }
@@ -1,13 +1,12 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';
3
2
 
4
3
  /**
5
- * lib.cjs
4
+ * lib.mjs
6
5
  *
7
6
  * Shared utilities for spectre hook scripts.
8
7
  */
9
8
 
10
- const { execSync } = require('child_process');
9
+ import { execSync } from 'node:child_process';
11
10
 
12
11
  const STDIN_TIMEOUT = 2000; // milliseconds
13
12
 
@@ -80,4 +79,4 @@ function getGitBranch(cwd) {
80
79
  }
81
80
  }
82
81
 
83
- module.exports = { readStdinWithTimeout, getGitBranch, STDIN_TIMEOUT };
82
+ export { readStdinWithTimeout, getGitBranch, STDIN_TIMEOUT };
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * load-knowledge.mjs
5
+ *
6
+ * SessionStart hook that refreshes the managed SPECTRE knowledge block in
7
+ * AGENTS.override.md and returns a short visible status line.
8
+ *
9
+ * Reads:
10
+ * - Apply skill from plugin: skills/apply/SKILL.md
11
+ * - Registry from project: .agents/skills/spectre-recall/references/registry.toon
12
+ */
13
+
14
+ import fs from 'node:fs';
15
+ import path from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = path.dirname(__filename);
20
+
21
+ function getPluginRoot() {
22
+ return process.env.CLAUDE_PLUGIN_ROOT || path.resolve(__dirname, '..', '..');
23
+ }
24
+
25
+ function resolvePluginSkillPath(pluginRoot, skillName, ...parts) {
26
+ const candidates = [
27
+ path.join(pluginRoot, 'skills', skillName, ...parts),
28
+ path.join(pluginRoot, '..', 'skills', skillName, ...parts),
29
+ ];
30
+
31
+ for (const candidate of candidates) {
32
+ if (fs.existsSync(candidate)) {
33
+ return candidate;
34
+ }
35
+ }
36
+
37
+ return candidates[0];
38
+ }
39
+
40
+ function skillBaseDir(projectDir) {
41
+ const agentsDir = path.join(projectDir, '.agents', 'skills');
42
+ if (fs.existsSync(agentsDir)) return agentsDir;
43
+ return path.join(projectDir, '.claude', 'skills');
44
+ }
45
+
46
+ function countRegistryEntries(lines) {
47
+ let count = 0;
48
+ for (const line of lines) {
49
+ if (line.trim() && line.includes('|') && !line.startsWith('#')) {
50
+ count++;
51
+ }
52
+ }
53
+ return count;
54
+ }
55
+
56
+ function stripFrontmatter(content) {
57
+ if (content.startsWith('---')) {
58
+ const end = content.indexOf('---', 3);
59
+ if (end !== -1) {
60
+ return content.slice(end + 3).trim();
61
+ }
62
+ }
63
+ return content;
64
+ }
65
+
66
+ function normalizeOverrideFile(content) {
67
+ return content
68
+ .replace(/\n{3,}/g, '\n\n')
69
+ .trim();
70
+ }
71
+
72
+ function escapeRegExp(value) {
73
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
74
+ }
75
+
76
+ function managedOverridePattern(startMarker, endMarker) {
77
+ return new RegExp(`\\n?${escapeRegExp(startMarker)}[\\s\\S]*?${escapeRegExp(endMarker)}\\n?`, 'm');
78
+ }
79
+
80
+ function writeManagedOverride(overridePath, startMarker, endMarker, bodyContent) {
81
+ const current = fs.existsSync(overridePath) ? fs.readFileSync(overridePath, 'utf8') : '';
82
+ const pattern = managedOverridePattern(startMarker, endMarker);
83
+ const blockContent = `${startMarker}\n${bodyContent}\n${endMarker}`;
84
+ let updated;
85
+
86
+ if (pattern.test(current)) {
87
+ updated = current.replace(pattern, `${blockContent}\n`);
88
+ } else if (current.trim()) {
89
+ updated = `${current.trimEnd()}\n\n${blockContent}\n`;
90
+ } else {
91
+ updated = `${blockContent}\n`;
92
+ }
93
+
94
+ const normalized = normalizeOverrideFile(updated);
95
+ fs.writeFileSync(overridePath, normalized ? `${normalized}\n` : '');
96
+ }
97
+
98
+ function hasProjectKnowledgeSurface(projectDir, registryPath) {
99
+ const overridePath = path.join(projectDir, 'AGENTS.override.md');
100
+ const overrideContent = fs.existsSync(overridePath) ? fs.readFileSync(overridePath, 'utf8') : '';
101
+ return fs.existsSync(path.join(projectDir, '.spectre', 'manifest.json'))
102
+ || fs.existsSync(registryPath)
103
+ || overrideContent.includes('<!-- spectre-knowledge:start -->')
104
+ || overrideContent.includes('<!-- spectre-session:start -->');
105
+ }
106
+
107
+ function buildKnowledgeOverrideBody(applyContent) {
108
+ return [
109
+ '## SPECTRE Knowledge Context',
110
+ '',
111
+ 'This block is managed by SPECTRE and replaced automatically on session start.',
112
+ 'Use it before searching or implementing work in this repository.',
113
+ '',
114
+ applyContent.trim()
115
+ ].join('\n');
116
+ }
117
+
118
+ function main() {
119
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
120
+ const pluginRoot = getPluginRoot();
121
+
122
+ const applySkillPath = resolvePluginSkillPath(pluginRoot, 'apply', 'SKILL.md');
123
+
124
+ if (!fs.existsSync(applySkillPath)) {
125
+ process.exit(0);
126
+ }
127
+
128
+ // Paths - check new name first, fall back to old names for migration
129
+ let registryPath = path.join(skillBaseDir(projectDir), 'spectre-recall', 'references', 'registry.toon');
130
+ const oldRegistryPath = path.join(projectDir, '.claude', 'skills', 'spectre-find', 'references', 'registry.toon');
131
+
132
+ // Support old "spectre-find" path for projects that haven't migrated
133
+ if (!fs.existsSync(registryPath) && fs.existsSync(oldRegistryPath)) {
134
+ registryPath = oldRegistryPath;
135
+ }
136
+
137
+ // Read registry if it exists
138
+ let registryContent = '';
139
+ let entryCount = 0;
140
+ if (fs.existsSync(registryPath)) {
141
+ registryContent = fs.readFileSync(registryPath, 'utf8').trim();
142
+ const lines = registryContent ? registryContent.split('\n') : [];
143
+ entryCount = countRegistryEntries(lines);
144
+ }
145
+
146
+ // Read apply skill and strip frontmatter
147
+ let applyContent = fs.readFileSync(applySkillPath, 'utf8');
148
+ applyContent = stripFrontmatter(applyContent);
149
+ applyContent = applyContent.replaceAll('.claude/skills/', '.agents/skills/').replaceAll('/spectre:', '');
150
+
151
+ if (hasProjectKnowledgeSurface(projectDir, registryPath)) {
152
+ writeManagedOverride(
153
+ path.join(projectDir, 'AGENTS.override.md'),
154
+ '<!-- spectre-knowledge:start -->',
155
+ '<!-- spectre-knowledge:end -->',
156
+ buildKnowledgeOverrideBody(applyContent)
157
+ );
158
+ }
159
+
160
+ // Visible notice
161
+ let visibleNotice;
162
+ if (entryCount > 0) {
163
+ visibleNotice = `\ud83d\udc7b spectre: ${entryCount} knowledge skills available`;
164
+ } else {
165
+ visibleNotice = '\ud83d\udc7b spectre: ready \u2014 capture knowledge with /spectre:learn';
166
+ }
167
+
168
+ const output = {
169
+ systemMessage: visibleNotice,
170
+ hookSpecificOutput: {
171
+ hookEventName: 'SessionStart'
172
+ }
173
+ };
174
+
175
+ process.stdout.write(JSON.stringify(output) + '\n');
176
+ process.exit(0);
177
+ }
178
+
179
+ export {
180
+ buildKnowledgeOverrideBody,
181
+ countRegistryEntries,
182
+ hasProjectKnowledgeSurface,
183
+ resolvePluginSkillPath,
184
+ stripFrontmatter
185
+ };
186
+
187
+ if (process.argv[1] && fs.realpathSync(path.resolve(process.argv[1])) === fs.realpathSync(__filename)) {
188
+ main();
189
+ }