@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
package/README.md CHANGED
@@ -35,7 +35,7 @@ That's it. You just start with 1 command to build features.
35
35
  ### Within Codex
36
36
 
37
37
  ```bash
38
- npx spectre install codex
38
+ npx @codename_inc/spectre install codex
39
39
  ```
40
40
 
41
41
  When prompted, choose `project` to install into the current repo's `.codex`, or `user` to install into `~/.codex`.
@@ -47,7 +47,7 @@ If you choose `user`, restart or open your normal Codex session.
47
47
  Then run a Spectre command such as:
48
48
 
49
49
  ```plaintext
50
- spectre-scope
50
+ scope
51
51
  ```
52
52
 
53
53
  Current Codex behavior:
@@ -65,7 +65,7 @@ Session continuity deep dive: [`docs/codex-sessionstart-memory.md`](./docs/codex
65
65
 
66
66
  ## 🔁 How It Works
67
67
 
68
- - run one of the kickoff prompts in Claude Code - `/spectre:scope` is the main command for building new features, but also `/spectre:kickoff` for high ambiguity new features (includes web research), `/spectre:research` for codebase research "how might we build …” style Qs, or `/spectre:ux_spec` to define user flows, components, and layout for a new feature.
68
+ - run one of the kickoff prompts in Claude Code - `/spectre:scope` is the main command for building new features, but also `/spectre:kickoff` for high ambiguity new features (includes web research), `/spectre:research` for codebase research "how might we build …” style Qs, or `/spectre:ux` to define user flows, components, and layout for a new feature.
69
69
 
70
70
  - follow the prompts/instructions to create the related canonical document and Claude Code will suggest the next step in the SPECTRE workflow automatically (e.g., going from `scope` to `plan` to `tasks` and so on)
71
71
 
@@ -281,7 +281,7 @@ Although I do sometimes use @spectre:web-research for web research. It's like mi
281
281
 
282
282
  - start /spectre:scope to get crisp on what's in/out. this is non-negotiable unless the feature is a one line ask.
283
283
 
284
- - if the feature's ux/user flow is unclear to me, or I want to make sure to really nail it, i run /spectre:ux_spec. Its similar to /spectre:scope but focuses on getting clear on the core user flows.
284
+ - if the feature's ux/user flow is unclear to me, or I want to make sure to really nail it, i run /spectre:ux. Its similar to /spectre:scope but focuses on getting clear on the core user flows.
285
285
 
286
286
  - /spectre:plan to build out a well researched technical design or set of tasks
287
287
 
@@ -367,7 +367,7 @@ I use /spectre:fix for pretty much all bugs I run into.
367
367
  | --- | --- |
368
368
  | `/spectre:sweep` | Light cleanup pass — lint, test, descriptive commits |
369
369
  | `/spectre:learn` | Capture knowledge for future sessions |
370
- | `/spectre:ux_spec` | UX specification for UI-heavy features |
370
+ | `/spectre:ux` | UX specification for UI-heavy features |
371
371
  | `/spectre:fix` | Investigate bugs & implement fixes |
372
372
 
373
373
  ## 📁 Repository Structure
@@ -380,10 +380,9 @@ spectre/
380
380
  │ └── spectre/
381
381
  │ ├── .claude-plugin/
382
382
  │ │ └── plugin.json # Plugin manifest
383
- │ ├── commands/ # Slash commands
384
383
  │ ├── agents/ # Subagent definitions
385
384
  │ ├── hooks/ # Session memory hooks
386
- │ └── skills/ # Skills
385
+ │ └── skills/ # Slash workflows + knowledge skills
387
386
  ├── scripts/ # Release & utility scripts
388
387
  └── CLAUDE.md
389
388
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codename_inc/spectre",
3
- "version": "3.7.0",
3
+ "version": "5.0.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "spectre": "./bin/spectre.js"
@@ -13,7 +13,8 @@
13
13
  "LICENSE"
14
14
  ],
15
15
  "scripts": {
16
- "test": "node --test src/**/*.test.js plugins/spectre/hooks/scripts/test_*.cjs",
16
+ "test": "node --test src/**/*.test.js plugins/spectre/hooks/scripts/test_*.mjs scripts/test_sync-codex.cjs",
17
+ "sync-codex": "node scripts/sync-codex.cjs",
17
18
  "tokens": "node scripts/count-tokens.js",
18
19
  "release": "node scripts/release.js"
19
20
  },
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "spectre",
3
- "version": "3.7.0",
3
+ "version": "5.0.0",
4
4
  "description": "Agentic coding workflow with session memory. spectre guides you through Scope, Plan, Execute, Clean, Test, Rebase, and Extract phases."
5
5
  }
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+ # Thin wrapper so skills can call `spectre-register` from Bash.
3
+ # Delegates to the hook registration runtime.
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ exec node "$SCRIPT_DIR/../hooks/scripts/register_learning.mjs" "$@"
@@ -1,31 +1,20 @@
1
1
  {
2
2
  "hooks": {
3
- "PreCompact": [
4
- {
5
- "matcher": "*",
6
- "hooks": [
7
- {
8
- "type": "command",
9
- "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/precompact-warning.cjs"
10
- }
11
- ]
12
- }
13
- ],
14
3
  "SessionStart": [
15
4
  {
16
5
  "matcher": "startup|clear|compact",
17
6
  "hooks": [
18
7
  {
19
8
  "type": "command",
20
- "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/bootstrap.cjs"
9
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/bootstrap.mjs"
21
10
  },
22
11
  {
23
12
  "type": "command",
24
- "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/handoff-resume.cjs"
13
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/handoff-resume.mjs"
25
14
  },
26
15
  {
27
16
  "type": "command",
28
- "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/load-knowledge.cjs"
17
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/load-knowledge.mjs"
29
18
  }
30
19
  ]
31
20
  }
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * bootstrap.mjs
5
+ *
6
+ * SessionStart hook that removes stale files from older plugin versions.
7
+ *
8
+ * When users update the plugin via marketplace, old files that were deleted
9
+ * from the repo may still linger in their cached copy. This script runs on
10
+ * every session start and cleans them up.
11
+ *
12
+ * To add new files to the cleanup list, append to STALE_PATHS below.
13
+ * Paths are relative to CLAUDE_PLUGIN_ROOT.
14
+ */
15
+
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);
23
+
24
+ // ──────────────────────────────────────────────────────────────────
25
+ // Stale paths to remove (relative to CLAUDE_PLUGIN_ROOT)
26
+ // ──────────────────────────────────────────────────────────────────
27
+
28
+ const STALE_PATHS = [
29
+ // Python scripts replaced by JS hook equivalents (v3.x migration)
30
+ 'hooks/scripts/capture-todos.py',
31
+ 'hooks/scripts/handoff-resume.py',
32
+ 'hooks/scripts/load-knowledge.py',
33
+ 'hooks/scripts/precompact-warning.py',
34
+ 'hooks/scripts/precompact-warning.mjs',
35
+ 'hooks/scripts/register_learning.py',
36
+ 'hooks/scripts/test_handoff_resume.py',
37
+ 'hooks/scripts/test_load_knowledge.py',
38
+
39
+ // Old skill directory replaced by spectre-guide
40
+ 'skills/spectre-next-steps',
41
+ ];
42
+
43
+ // ──────────────────────────────────────────────────────────────────
44
+ // Cleanup logic
45
+ // ──────────────────────────────────────────────────────────────────
46
+
47
+ function cleanupStalePaths(pluginRoot) {
48
+ let removed = 0;
49
+
50
+ for (const relPath of STALE_PATHS) {
51
+ const fullPath = path.join(pluginRoot, relPath);
52
+
53
+ try {
54
+ const stat = fs.statSync(fullPath);
55
+
56
+ if (stat.isDirectory()) {
57
+ fs.rmSync(fullPath, { recursive: true, force: true });
58
+ removed++;
59
+ } else {
60
+ fs.unlinkSync(fullPath);
61
+ removed++;
62
+ }
63
+ } catch (_) {
64
+ // File doesn't exist or can't be removed — skip silently
65
+ }
66
+ }
67
+
68
+ return removed;
69
+ }
70
+
71
+ // ──────────────────────────────────────────────────────────────────
72
+ // Main
73
+ // ──────────────────────────────────────────────────────────────────
74
+
75
+ async function main() {
76
+ // Drain stdin so the hook system doesn't hang
77
+ await readStdinWithTimeout();
78
+
79
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || path.resolve(__dirname, '..', '..');
80
+
81
+ const removed = cleanupStalePaths(pluginRoot);
82
+
83
+ if (removed > 0) {
84
+ process.stdout.write(JSON.stringify({
85
+ systemMessage: `bootstrap: cleaned ${removed} stale file${removed > 1 ? 's' : ''} from previous plugin version`
86
+ }) + '\n');
87
+ } else {
88
+ process.stdout.write(JSON.stringify({}) + '\n');
89
+ }
90
+
91
+ process.exit(0);
92
+ }
93
+
94
+ export { cleanupStalePaths, STALE_PATHS };
95
+
96
+ if (process.argv[1] && fs.realpathSync(path.resolve(process.argv[1])) === fs.realpathSync(__filename)) {
97
+ main();
98
+ }
@@ -0,0 +1,404 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * handoff-resume.mjs
5
+ *
6
+ * SessionStart hook that injects context from the last /spectre:handoff.
7
+ * Consolidates the previous session-resume-hook.sh + format-resume-context.py.
8
+ *
9
+ * Outputs JSON for Claude Code hook system:
10
+ * - systemMessage: User-visible notice
11
+ * - hookSpecificOutput.additionalContext: Full session context in <session-context> tags
12
+ *
13
+ * Background modes:
14
+ * - --bg-copy-refs: Copy plugin references (fork #1)
15
+ */
16
+
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
+ }
29
+
30
+ // ──────────────────────────────────────────────────────────────────
31
+ // Plugin reference copying (background fork #1)
32
+ // ──────────────────────────────────────────────────────────────────
33
+
34
+ function copyPluginReferences() {
35
+ const pluginRoot = getPluginRoot();
36
+ if (!pluginRoot) return;
37
+
38
+ const referencesSrc = path.join(pluginRoot, 'references');
39
+ if (!fs.existsSync(referencesSrc)) return;
40
+
41
+ const referencesDst = path.join('.claude', 'spectre');
42
+ fs.mkdirSync(referencesDst, { recursive: true });
43
+
44
+ const files = fs.readdirSync(referencesSrc).filter(f => f.endsWith('.md'));
45
+ for (const file of files) {
46
+ const dst = path.join(referencesDst, file);
47
+ if (!fs.existsSync(dst)) {
48
+ fs.copyFileSync(path.join(referencesSrc, file), dst);
49
+ }
50
+ }
51
+
52
+ // Append to .gitignore if it exists and .claude/ not already ignored
53
+ const gitignorePath = '.gitignore';
54
+ if (fs.existsSync(gitignorePath)) {
55
+ const content = fs.readFileSync(gitignorePath, 'utf8');
56
+ if (!content.includes('.claude/') && !content.includes('.claude/spectre/')) {
57
+ fs.appendFileSync(gitignorePath, '\n# spectre plugin files\n.claude/spectre/\n');
58
+ }
59
+ }
60
+ }
61
+
62
+ // ──────────────────────────────────────────────────────────────────
63
+ // Session discovery
64
+ // ──────────────────────────────────────────────────────────────────
65
+
66
+ function findLatestHandoff(sessionDir) {
67
+ if (!fs.existsSync(sessionDir)) return null;
68
+
69
+ // Only look at top-level files, not in archive/
70
+ const files = fs.readdirSync(sessionDir)
71
+ .filter(f => f.endsWith('_handoff.json'))
72
+ .map(f => ({
73
+ name: f,
74
+ full: path.join(sessionDir, f),
75
+ mtime: fs.statSync(path.join(sessionDir, f)).mtimeMs
76
+ }))
77
+ .sort((a, b) => b.mtime - a.mtime);
78
+
79
+ return files.length > 0 ? files[0].full : null;
80
+ }
81
+
82
+ // ──────────────────────────────────────────────────────────────────
83
+ // Formatting helpers
84
+ // ──────────────────────────────────────────────────────────────────
85
+
86
+ function formatList(items, prefix) {
87
+ prefix = prefix != null ? prefix : '- ';
88
+ if (!items || !items.length) return `${prefix}None`;
89
+ return items.map(item => `${prefix}${item}`).join('\n');
90
+ }
91
+
92
+ function buildCheckboxTree(tasks) {
93
+ if (!tasks || !tasks.length) return 'No tasks found.';
94
+
95
+ const byParent = {};
96
+ for (const task of tasks) {
97
+ const parent = task.parent || null;
98
+ if (!byParent[parent]) byParent[parent] = [];
99
+ byParent[parent].push(task);
100
+ }
101
+
102
+ function renderTask(task, indent) {
103
+ indent = indent || 0;
104
+ const lines = [];
105
+ const prefix = ' '.repeat(indent);
106
+ const checkbox = task.completed ? '[x]' : '[ ]';
107
+ const status = task.status || 'open';
108
+ const title = task.title || 'Untitled';
109
+ const taskId = task.id || 'unknown';
110
+
111
+ if (task.completed) {
112
+ lines.push(`${prefix}- ${checkbox} ${title} (${taskId}) - COMPLETED`);
113
+ } else {
114
+ const cmd = task.resume_command || `bd update ${taskId} --status in_progress`;
115
+ const statusBadge = status !== 'open' ? `[${status}]` : '';
116
+ lines.push(`${prefix}- ${checkbox} ${title} (${taskId}) ${statusBadge} - \`${cmd}\``);
117
+ }
118
+
119
+ // Render children
120
+ const childrenIds = task.children || [];
121
+ if (childrenIds.length) {
122
+ for (const childTask of tasks) {
123
+ if (childrenIds.includes(childTask.id) || childTask.parent === taskId) {
124
+ lines.push(...renderTask(childTask, indent + 1));
125
+ }
126
+ }
127
+ }
128
+
129
+ return lines;
130
+ }
131
+
132
+ // Start with root tasks (no parent or parent is null)
133
+ const rootTasks = (byParent[null] || []).concat(byParent['null'] || []);
134
+
135
+ // If no root tasks found, just list all tasks flat
136
+ const startTasks = rootTasks.length ? rootTasks : tasks;
137
+
138
+ const lines = [];
139
+ const renderedIds = new Set();
140
+
141
+ for (const task of startTasks) {
142
+ if (!renderedIds.has(task.id)) {
143
+ const taskLines = renderTask(task);
144
+ lines.push(...taskLines);
145
+ renderedIds.add(task.id);
146
+ for (const t of tasks) {
147
+ if (t.parent === task.id) {
148
+ renderedIds.add(t.id);
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ return lines.join('\n');
155
+ }
156
+
157
+ // ──────────────────────────────────────────────────────────────────
158
+ // Main context formatter
159
+ // ──────────────────────────────────────────────────────────────────
160
+
161
+ function formatContext(data, opts) {
162
+ opts = opts || {};
163
+ const handoffPath = opts.handoffPath;
164
+
165
+ const taskName = data.task_name || 'unknown';
166
+ const branchName = data.branch_name || 'unknown';
167
+
168
+ // Progress update fields (v1.1 schema)
169
+ const progress = data.progress_update || {};
170
+ const summary = progress.summary || 'No summary available.';
171
+ const goal = progress.goal || '';
172
+ const constraints = progress.constraints || [];
173
+ const decisions = progress.decisions || [];
174
+ const accomplished = progress.accomplished || [];
175
+ const now = progress.now || '';
176
+ const nextSteps = progress.next_steps || [];
177
+ const blockers = progress.blockers || [];
178
+ const openQuestions = progress.open_questions || [];
179
+ const confidence = progress.confidence || 'unknown';
180
+ const risks = progress.risks || [];
181
+
182
+ // Working set (v1.1 schema) - fall back to context.key_files for v1.0
183
+ const workingSet = data.working_set || {};
184
+ let keyFiles = workingSet.key_files || [];
185
+ const activeIds = workingSet.active_ids || [];
186
+ const recentCommands = workingSet.recent_commands || [];
187
+
188
+ // Fall back to old context structure if working_set not present
189
+ if (!keyFiles.length) {
190
+ const ctx = data.context || {};
191
+ keyFiles = ctx.key_files || [];
192
+ }
193
+
194
+ // Beads tasks
195
+ const beads = data.beads || {};
196
+ const beadsAvailable = beads.available != null ? beads.available : true;
197
+ const tasks = beads.tasks || [];
198
+
199
+ // Context
200
+ const context = data.context || {};
201
+ const lastCommit = context.last_commit || 'unknown';
202
+ const wipState = context.wip_state || 'unknown';
203
+
204
+ // Build checkbox tree for beads tasks
205
+ const checkboxTree = (beadsAvailable && tasks.length) ? buildCheckboxTree(tasks) : '';
206
+
207
+ // User-visible notice
208
+ const noticeLines = ['\ud83d\udc7b SPECTRE'];
209
+ noticeLines.push(`\n\ud83d\udd04 Session Resumed: ${taskName} | Branch: ${branchName}`);
210
+
211
+ if (goal) {
212
+ noticeLines.push(`\n\ud83c\udfaf Goal: ${goal}`);
213
+ }
214
+
215
+ noticeLines.push(`\n\ud83d\udcdd Summary: ${summary}`);
216
+
217
+ if (nextSteps.length) {
218
+ noticeLines.push('\n\u27a1\ufe0f Next Steps:');
219
+ for (const step of nextSteps) {
220
+ noticeLines.push(` - ${step}`);
221
+ }
222
+ }
223
+
224
+ if (handoffPath) {
225
+ noticeLines.push(`\n\ud83d\udcc1 Full details: ${handoffPath}`);
226
+ }
227
+
228
+ noticeLines.push('\n\ud83d\udca1 Run /spectre:forget to clear session memory and start fresh.');
229
+
230
+ const visibleNotice = noticeLines.join('\n');
231
+
232
+ // Build the hidden context sections
233
+ const sections = [];
234
+
235
+ sections.push(`# Session Context: ${taskName}`);
236
+
237
+ // Last session summary
238
+ sections.push(`\n## Last Session Summary\n${summary}`);
239
+
240
+ // Goal (if available - v1.1)
241
+ if (goal) {
242
+ sections.push(`\n### Goal\n${goal}`);
243
+ }
244
+
245
+ // Constraints (if available - v1.1)
246
+ if (constraints.length) {
247
+ sections.push(`\n### Constraints\n${formatList(constraints)}`);
248
+ }
249
+
250
+ // What we accomplished
251
+ sections.push(`\n### What We Accomplished\n${formatList(accomplished)}`);
252
+
253
+ // What we were working on (critical for resume - v1.1)
254
+ if (now) {
255
+ sections.push(`\n### Active Work (Resume Here)\n**${now}**`);
256
+ }
257
+
258
+ // What's next
259
+ sections.push(`\n### What's Next\n${formatList(nextSteps)}`);
260
+
261
+ // Blockers
262
+ if (blockers.length) {
263
+ sections.push(`\n### Blockers\n${formatList(blockers)}`);
264
+ }
265
+
266
+ // Open questions (v1.1)
267
+ if (openQuestions.length) {
268
+ sections.push(`\n### Open Questions\n${formatList(openQuestions)}`);
269
+ }
270
+
271
+ // Decisions
272
+ if (decisions.length) {
273
+ sections.push(`\n### Decisions Made\n${formatList(decisions)}`);
274
+ }
275
+
276
+ // Confidence and risks
277
+ const risksStr = risks.length ? formatList(risks, '') : 'None identified';
278
+ sections.push(`\n**Confidence**: ${confidence} | **Risks**: ${risksStr}`);
279
+
280
+ // Working set (v1.1)
281
+ const wsLines = [];
282
+ if (keyFiles.length) {
283
+ wsLines.push(`- **Key Files**: ${keyFiles.join(', ')}`);
284
+ }
285
+ if (activeIds.length) {
286
+ wsLines.push(`- **Active IDs**: ${activeIds.join(', ')}`);
287
+ }
288
+ if (recentCommands.length) {
289
+ wsLines.push(`- **Recent Commands**: ${recentCommands.join(', ')}`);
290
+ }
291
+
292
+ if (wsLines.length) {
293
+ sections.push('\n### Working Set\n' + wsLines.join('\n'));
294
+ }
295
+
296
+ // Context
297
+ sections.push(
298
+ '\n---\n\n' +
299
+ '## Context\n' +
300
+ `- **Branch**: ${branchName}\n` +
301
+ `- **Last Commit**: ${lastCommit}\n` +
302
+ `- **WIP State**: ${wipState}`
303
+ );
304
+
305
+ // Beads tasks (if available)
306
+ if (beadsAvailable && checkboxTree) {
307
+ sections.push(`\n### Beads Tasks\n${checkboxTree}`);
308
+ }
309
+
310
+ const hiddenContext = `<session-context>\n${sections.join('')}\n</session-context>`;
311
+
312
+ return {
313
+ systemMessage: visibleNotice,
314
+ hookSpecificOutput: {
315
+ hookEventName: 'SessionStart',
316
+ additionalContext: hiddenContext
317
+ }
318
+ };
319
+ }
320
+
321
+ // ──────────────────────────────────────────────────────────────────
322
+ // Main entry point
323
+ // ──────────────────────────────────────────────────────────────────
324
+
325
+ async function main() {
326
+ // Handle background fork modes
327
+ if (process.argv[2] === '--bg-copy-refs') {
328
+ try { copyPluginReferences(); } catch (_) {}
329
+ process.exit(0);
330
+ }
331
+
332
+ // Read stdin
333
+ await readStdinWithTimeout();
334
+
335
+ // Fork to copy plugin references in background (non-blocking)
336
+ const copyChild = fork(__filename, ['--bg-copy-refs'], {
337
+ detached: true,
338
+ stdio: 'ignore'
339
+ });
340
+ copyChild.unref();
341
+
342
+ // Get project directory from environment or cwd
343
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
344
+
345
+ // Get branch name
346
+ const branchName = getGitBranch();
347
+
348
+ // Find session logs directory
349
+ const sessionDir = path.join(projectDir, 'docs', 'tasks', branchName, 'session_logs');
350
+
351
+ // Find latest handoff
352
+ const latestHandoff = findLatestHandoff(sessionDir);
353
+
354
+ if (!latestHandoff) {
355
+ // No session to resume - show welcome banner with tips
356
+ const banner = '\ud83d\udc7b SPECTRE';
357
+ const tips = [
358
+ '',
359
+ 'Getting Started with SPECTRE:',
360
+ '',
361
+ '\u2699\ufe0f Tip: Turn off auto-compact via /config \u2014 SPECTRE works best with manual context management',
362
+ '\ud83d\udcbe Use /spectre:handoff when context is getting full but you\'re still going \u2014 saves state for the next session',
363
+ '\ud83e\uddf9 Use /spectre:forget to clear session memory and start fresh',
364
+ '\ud83d\ude80 Use /spectre:scope to start building features with the full SPECTRE workflow',
365
+ '\ud83c\udf93 Use /spectre:learn to create a documentation skill that your Agent will auto-load when relevant.'
366
+ ].join('\n');
367
+
368
+ const welcome = {
369
+ systemMessage: banner + '\n' + tips
370
+ };
371
+ process.stdout.write(JSON.stringify(welcome) + '\n');
372
+ process.exit(0);
373
+ }
374
+
375
+ // Read and parse handoff JSON
376
+ let data;
377
+ try {
378
+ data = JSON.parse(fs.readFileSync(latestHandoff, 'utf8'));
379
+ } catch (_) {
380
+ process.exit(0);
381
+ }
382
+
383
+ // Compute relative path to handoff for user reference
384
+ let handoffRelative;
385
+ try {
386
+ handoffRelative = path.relative(projectDir, latestHandoff);
387
+ } catch (_) {
388
+ handoffRelative = latestHandoff;
389
+ }
390
+
391
+ // Format and output context
392
+ const output = formatContext(data, {
393
+ handoffPath: handoffRelative
394
+ });
395
+ process.stdout.write(JSON.stringify(output) + '\n');
396
+
397
+ process.exit(0);
398
+ }
399
+
400
+ export { copyPluginReferences, formatContext, buildCheckboxTree };
401
+
402
+ if (process.argv[1] && fs.realpathSync(path.resolve(process.argv[1])) === fs.realpathSync(__filename)) {
403
+ main();
404
+ }