@automagik/genie 0.260202.1901 → 0.260203.135
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.beads/issues.jsonl +9 -0
- package/.claude/skills/brainstorm/SKILL.md +53 -0
- package/.claude/skills/genie-base/SKILL.md +66 -0
- package/.claude/skills/genie-base/assets/workspace/AGENTS.md +191 -0
- package/.claude/skills/genie-base/assets/workspace/ENVIRONMENT.md +18 -0
- package/.claude/skills/genie-base/assets/workspace/HEARTBEAT.md +4 -0
- package/.claude/skills/genie-base/assets/workspace/IDENTITY.md +17 -0
- package/.claude/skills/genie-base/assets/workspace/MEMORY.md +16 -0
- package/.claude/skills/genie-base/assets/workspace/ROLE.md +14 -0
- package/.claude/skills/genie-base/assets/workspace/SOUL.md +36 -0
- package/.claude/skills/genie-base/assets/workspace/TOOLS.md +25 -0
- package/.claude/skills/genie-base/assets/workspace/USER.md +13 -0
- package/.claude/skills/genie-base/assets/workspace/memory/2026-01-30.md +6 -0
- package/.claude/skills/genie-base/assets/workspace/memory/2026-01-31.md +16 -0
- package/.claude/skills/genie-base/assets/workspace/memory/882c22be-9710-41c1-91f8-ed82947ef6ce.txt +1 -0
- package/.claude/skills/genie-base/scripts/install-workspace.sh +107 -0
- package/.claude/skills/genie-base/scripts/sanity-sweep.sh +60 -0
- package/.claude/skills/genie-blank-init/SKILL.md +37 -0
- package/.claude/skills/genie-blank-init/assets/BOOTSTRAP.md +44 -0
- package/.claude/skills/genie-blank-init/assets/IDENTITY.md +9 -0
- package/.claude/skills/genie-blank-init/assets/SOUL.md +10 -0
- package/.claude/skills/genie-blank-init/assets/USER.md +9 -0
- package/.claude/skills/genie-blank-init/scripts/apply-blank-init.sh +117 -0
- package/.claude/skills/genie-forge/SKILL.md +171 -0
- package/.claude/skills/genie-plan-review/CLAUDE.md +11 -0
- package/.claude/skills/genie-plan-review/SKILL.md +53 -0
- package/.claude/skills/genie-review/SKILL.md +171 -0
- package/.claude/skills/genie-wish/SKILL.md +141 -0
- package/.claude-plugin/marketplace.json +18 -0
- package/.genie/.gitkeep +3 -0
- package/.genie/backlog/hooks-v2.md +82 -0
- package/.genie/wishes/upgrade-brainstorm-handoff/wish.md +124 -0
- package/.gitattributes +1 -1
- package/AGENTS.md +35 -0
- package/README.md +10 -5
- package/bun.lock +55 -0
- package/dist/claudio.js +1 -1
- package/dist/genie.js +1 -1
- package/dist/term.js +108 -85
- package/docs/CO-ORCHESTRATION-GUIDE.md +375 -0
- package/package.json +5 -1
- package/plugin/.claude-plugin/plugin.json +18 -0
- package/plugin/README.md +120 -0
- package/plugin/agents/implementor.md +92 -0
- package/plugin/agents/quality-reviewer.md +113 -0
- package/plugin/agents/spec-reviewer.md +90 -0
- package/plugin/hooks/hooks.json +3 -0
- package/plugin/hooks/postInstall.sh +10 -0
- package/plugin/references/review-criteria.md +72 -0
- package/plugin/references/wish-template.md +92 -0
- package/plugin/scripts/genie.cjs +141 -0
- package/plugin/scripts/smart-install.js +308 -0
- package/plugin/scripts/src/install-genie-cli.sh +120 -0
- package/plugin/scripts/src/validate-completion.ts +142 -0
- package/plugin/scripts/src/validate-wish.ts +137 -0
- package/plugin/scripts/term.cjs +231 -0
- package/plugin/scripts/validate-completion.cjs +16 -0
- package/plugin/scripts/validate-wish.cjs +17 -0
- package/plugin/scripts/worker-service.cjs +28 -0
- package/plugin/skills/brainstorm/SKILL.md +106 -0
- package/plugin/skills/forge/SKILL.md +171 -0
- package/plugin/skills/genie-base/SKILL.md +99 -0
- package/plugin/skills/genie-base/assets/workspace/AGENTS.md +191 -0
- package/plugin/skills/genie-base/assets/workspace/ENVIRONMENT.md +18 -0
- package/plugin/skills/genie-base/assets/workspace/HEARTBEAT.md +4 -0
- package/plugin/skills/genie-base/assets/workspace/IDENTITY.md +17 -0
- package/plugin/skills/genie-base/assets/workspace/MEMORY.md +16 -0
- package/plugin/skills/genie-base/assets/workspace/ROLE.md +14 -0
- package/plugin/skills/genie-base/assets/workspace/SOUL.md +36 -0
- package/plugin/skills/genie-base/assets/workspace/TOOLS.md +25 -0
- package/plugin/skills/genie-base/assets/workspace/USER.md +13 -0
- package/plugin/skills/genie-base/scripts/install-workspace.sh +107 -0
- package/plugin/skills/genie-base/scripts/sanity-sweep.sh +60 -0
- package/plugin/skills/genie-blank-init/SKILL.md +73 -0
- package/plugin/skills/genie-blank-init/assets/BOOTSTRAP.md +44 -0
- package/plugin/skills/genie-blank-init/assets/IDENTITY.md +9 -0
- package/plugin/skills/genie-blank-init/assets/SOUL.md +10 -0
- package/plugin/skills/genie-blank-init/assets/USER.md +9 -0
- package/plugin/skills/genie-blank-init/scripts/apply-blank-init.sh +117 -0
- package/plugin/skills/genie-cli-dev/CLAUDE.md +19 -0
- package/plugin/skills/genie-cli-dev/SKILL.md +295 -0
- package/plugin/skills/plan-review/SKILL.md +101 -0
- package/plugin/skills/review/SKILL.md +221 -0
- package/plugin/skills/wish/SKILL.md +110 -0
- package/plugin/skills/work-orchestration/SKILL.md +116 -0
- package/scripts/build.js +132 -0
- package/scripts/smart-install.js +308 -0
- package/scripts/sync.js +134 -0
- package/src/lib/beads-registry.ts +49 -0
- package/src/lib/orchestrator/event-monitor.ts +2 -0
- package/src/lib/skill-loader.ts +215 -0
- package/src/lib/tmux.ts +19 -14
- package/src/lib/version.ts +1 -1
- package/src/lib/worker-registry.ts +10 -0
- package/src/services/worker-service.ts +351 -0
- package/src/term-commands/close.ts +14 -4
- package/src/term-commands/create.ts +95 -0
- package/src/term-commands/kill.ts +15 -4
- package/src/term-commands/orchestrate.ts +3 -2
- package/src/term-commands/send.ts +43 -15
- package/src/term-commands/spawn.ts +446 -0
- package/src/term-commands/split.ts +14 -3
- package/src/term-commands/work.ts +217 -57
- package/src/term.ts +81 -6
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Smart Install Script for automagik-genie
|
|
4
|
+
*
|
|
5
|
+
* Ensures required dependencies are installed:
|
|
6
|
+
* - Bun runtime (auto-installs if missing)
|
|
7
|
+
* - tmux (guides user if missing - can't auto-install)
|
|
8
|
+
* - beads (auto-installs if missing via npm)
|
|
9
|
+
*
|
|
10
|
+
* Also handles:
|
|
11
|
+
* - Dependency installation when version changes
|
|
12
|
+
* - CLI symlink creation for agent access
|
|
13
|
+
* - Version marker management
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, symlinkSync, unlinkSync } from 'fs';
|
|
16
|
+
import { execSync, spawnSync } from 'child_process';
|
|
17
|
+
import { join } from 'path';
|
|
18
|
+
import { homedir } from 'os';
|
|
19
|
+
|
|
20
|
+
const ROOT = process.env.CLAUDE_PLUGIN_ROOT || join(homedir(), '.claude', 'plugins', 'automagik-genie');
|
|
21
|
+
const GENIE_DIR = join(homedir(), '.genie');
|
|
22
|
+
const MARKER = join(GENIE_DIR, '.install-version');
|
|
23
|
+
const BIN_DIR = join(homedir(), '.local', 'bin');
|
|
24
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
25
|
+
|
|
26
|
+
// Common installation paths (handles fresh installs before PATH reload)
|
|
27
|
+
const BUN_COMMON_PATHS = IS_WINDOWS
|
|
28
|
+
? [join(homedir(), '.bun', 'bin', 'bun.exe')]
|
|
29
|
+
: [join(homedir(), '.bun', 'bin', 'bun'), '/usr/local/bin/bun', '/opt/homebrew/bin/bun'];
|
|
30
|
+
|
|
31
|
+
const BEADS_COMMON_PATHS = IS_WINDOWS
|
|
32
|
+
? [join(homedir(), 'AppData', 'Roaming', 'npm', 'bd.cmd')]
|
|
33
|
+
: [join(homedir(), '.local', 'bin', 'bd'), '/usr/local/bin/bd', '/opt/homebrew/bin/bd'];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get the Bun executable path
|
|
37
|
+
*/
|
|
38
|
+
function getBunPath() {
|
|
39
|
+
// Try PATH first
|
|
40
|
+
try {
|
|
41
|
+
const result = spawnSync('bun', ['--version'], {
|
|
42
|
+
encoding: 'utf-8',
|
|
43
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
44
|
+
shell: IS_WINDOWS
|
|
45
|
+
});
|
|
46
|
+
if (result.status === 0) return 'bun';
|
|
47
|
+
} catch {
|
|
48
|
+
// Not in PATH
|
|
49
|
+
}
|
|
50
|
+
return BUN_COMMON_PATHS.find(existsSync) || null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isBunInstalled() {
|
|
54
|
+
return getBunPath() !== null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getBunVersion() {
|
|
58
|
+
const bunPath = getBunPath();
|
|
59
|
+
if (!bunPath) return null;
|
|
60
|
+
try {
|
|
61
|
+
const result = spawnSync(bunPath, ['--version'], {
|
|
62
|
+
encoding: 'utf-8',
|
|
63
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
64
|
+
shell: IS_WINDOWS
|
|
65
|
+
});
|
|
66
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Install Bun automatically
|
|
74
|
+
*/
|
|
75
|
+
function installBun() {
|
|
76
|
+
console.error('Installing Bun runtime...');
|
|
77
|
+
try {
|
|
78
|
+
if (IS_WINDOWS) {
|
|
79
|
+
execSync('powershell -c "irm bun.sh/install.ps1 | iex"', { stdio: 'inherit', shell: true });
|
|
80
|
+
} else {
|
|
81
|
+
execSync('curl -fsSL https://bun.sh/install | bash', { stdio: 'inherit', shell: true });
|
|
82
|
+
}
|
|
83
|
+
if (!isBunInstalled()) {
|
|
84
|
+
throw new Error('Bun installation completed but binary not found. Please restart your terminal.');
|
|
85
|
+
}
|
|
86
|
+
console.error(`Bun ${getBunVersion()} installed`);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Failed to install Bun. Please install manually:');
|
|
89
|
+
if (IS_WINDOWS) {
|
|
90
|
+
console.error(' winget install Oven-sh.Bun');
|
|
91
|
+
} else {
|
|
92
|
+
console.error(' curl -fsSL https://bun.sh/install | bash');
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check if tmux is installed
|
|
100
|
+
*/
|
|
101
|
+
function isTmuxInstalled() {
|
|
102
|
+
try {
|
|
103
|
+
const result = spawnSync('tmux', ['-V'], {
|
|
104
|
+
encoding: 'utf-8',
|
|
105
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
106
|
+
shell: IS_WINDOWS
|
|
107
|
+
});
|
|
108
|
+
return result.status === 0;
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get tmux version
|
|
116
|
+
*/
|
|
117
|
+
function getTmuxVersion() {
|
|
118
|
+
try {
|
|
119
|
+
const result = spawnSync('tmux', ['-V'], {
|
|
120
|
+
encoding: 'utf-8',
|
|
121
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
122
|
+
shell: IS_WINDOWS
|
|
123
|
+
});
|
|
124
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
125
|
+
} catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Check if beads (bd) is installed
|
|
132
|
+
*/
|
|
133
|
+
function getBeadsPath() {
|
|
134
|
+
try {
|
|
135
|
+
const result = spawnSync('bd', ['--version'], {
|
|
136
|
+
encoding: 'utf-8',
|
|
137
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
138
|
+
shell: IS_WINDOWS
|
|
139
|
+
});
|
|
140
|
+
if (result.status === 0) return 'bd';
|
|
141
|
+
} catch {
|
|
142
|
+
// Not in PATH
|
|
143
|
+
}
|
|
144
|
+
return BEADS_COMMON_PATHS.find(existsSync) || null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isBeadsInstalled() {
|
|
148
|
+
return getBeadsPath() !== null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Install beads via npm
|
|
153
|
+
*/
|
|
154
|
+
function installBeads() {
|
|
155
|
+
console.error('Installing beads (bd)...');
|
|
156
|
+
try {
|
|
157
|
+
execSync('npm install -g @anthropic-ai/bd', { stdio: 'inherit', shell: true });
|
|
158
|
+
if (!isBeadsInstalled()) {
|
|
159
|
+
throw new Error('beads installation completed but bd not found.');
|
|
160
|
+
}
|
|
161
|
+
console.error('beads installed');
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error('Failed to install beads. Please install manually:');
|
|
164
|
+
console.error(' npm install -g @anthropic-ai/bd');
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Check if dependencies need to be installed
|
|
171
|
+
*/
|
|
172
|
+
function needsInstall() {
|
|
173
|
+
if (!existsSync(join(ROOT, 'node_modules'))) return true;
|
|
174
|
+
if (!existsSync(join(ROOT, 'package.json'))) return false; // No package.json = no deps needed
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'));
|
|
178
|
+
if (!existsSync(MARKER)) return true;
|
|
179
|
+
const marker = JSON.parse(readFileSync(MARKER, 'utf-8'));
|
|
180
|
+
return pkg.version !== marker.version || getBunVersion() !== marker.bun;
|
|
181
|
+
} catch {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Install dependencies using Bun
|
|
188
|
+
*/
|
|
189
|
+
function installDeps() {
|
|
190
|
+
const bunPath = getBunPath();
|
|
191
|
+
if (!bunPath) {
|
|
192
|
+
throw new Error('Bun executable not found');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.error('Installing dependencies...');
|
|
196
|
+
|
|
197
|
+
// Ensure .genie directory exists
|
|
198
|
+
if (!existsSync(GENIE_DIR)) {
|
|
199
|
+
mkdirSync(GENIE_DIR, { recursive: true });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const bunCmd = IS_WINDOWS && bunPath.includes(' ') ? `"${bunPath}"` : bunPath;
|
|
203
|
+
execSync(`${bunCmd} install`, { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
|
|
204
|
+
|
|
205
|
+
// Write version marker
|
|
206
|
+
let version = 'unknown';
|
|
207
|
+
try {
|
|
208
|
+
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'));
|
|
209
|
+
version = pkg.version;
|
|
210
|
+
} catch {
|
|
211
|
+
// Ignore
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
writeFileSync(MARKER, JSON.stringify({
|
|
215
|
+
version,
|
|
216
|
+
bun: getBunVersion(),
|
|
217
|
+
tmux: getTmuxVersion(),
|
|
218
|
+
installedAt: new Date().toISOString()
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Create CLI symlinks for agent access
|
|
224
|
+
*/
|
|
225
|
+
function createCliSymlinks() {
|
|
226
|
+
// Ensure bin directory exists
|
|
227
|
+
if (!existsSync(BIN_DIR)) {
|
|
228
|
+
mkdirSync(BIN_DIR, { recursive: true });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const links = [
|
|
232
|
+
{ name: 'genie', target: join(ROOT, 'scripts', 'genie.cjs') },
|
|
233
|
+
{ name: 'term', target: join(ROOT, 'scripts', 'term.cjs') }
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
for (const { name, target } of links) {
|
|
237
|
+
const linkPath = join(BIN_DIR, name);
|
|
238
|
+
|
|
239
|
+
// Skip if target doesn't exist
|
|
240
|
+
if (!existsSync(target)) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Remove existing symlink if it exists
|
|
245
|
+
try {
|
|
246
|
+
if (existsSync(linkPath)) {
|
|
247
|
+
unlinkSync(linkPath);
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
// Ignore errors
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Create symlink
|
|
254
|
+
try {
|
|
255
|
+
symlinkSync(target, linkPath);
|
|
256
|
+
console.error(`Created symlink: ${linkPath} -> ${target}`);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error(`Warning: Could not create symlink for ${name}: ${error.message}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Main execution
|
|
264
|
+
try {
|
|
265
|
+
// 1. Check/install Bun
|
|
266
|
+
if (!isBunInstalled()) {
|
|
267
|
+
installBun();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 2. Check tmux - REQUIRED, but can't auto-install
|
|
271
|
+
if (!isTmuxInstalled()) {
|
|
272
|
+
console.error('');
|
|
273
|
+
console.error('ERROR: tmux is required but not installed.');
|
|
274
|
+
console.error('');
|
|
275
|
+
console.error('Please install tmux manually:');
|
|
276
|
+
if (process.platform === 'darwin') {
|
|
277
|
+
console.error(' brew install tmux');
|
|
278
|
+
} else if (process.platform === 'linux') {
|
|
279
|
+
console.error(' sudo apt install tmux # Debian/Ubuntu');
|
|
280
|
+
console.error(' sudo dnf install tmux # Fedora/RHEL');
|
|
281
|
+
console.error(' sudo pacman -S tmux # Arch');
|
|
282
|
+
} else if (IS_WINDOWS) {
|
|
283
|
+
console.error(' WSL is required for tmux on Windows');
|
|
284
|
+
console.error(' Inside WSL: sudo apt install tmux');
|
|
285
|
+
}
|
|
286
|
+
console.error('');
|
|
287
|
+
console.error('Then restart Claude Code.');
|
|
288
|
+
process.exit(2); // Exit code 2 = blocking error for Claude to process
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 3. Check/install beads
|
|
292
|
+
if (!isBeadsInstalled()) {
|
|
293
|
+
installBeads();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// 4. Install dependencies if needed
|
|
297
|
+
if (needsInstall()) {
|
|
298
|
+
installDeps();
|
|
299
|
+
console.error('Dependencies installed');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 5. Create CLI symlinks
|
|
303
|
+
createCliSymlinks();
|
|
304
|
+
|
|
305
|
+
} catch (e) {
|
|
306
|
+
console.error('Installation failed:', e.message);
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# Install genie-cli globally via npm/bun
|
|
6
|
+
# This script is included with the automagik-genie plugin
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
usage() {
|
|
10
|
+
cat <<'EOF'
|
|
11
|
+
Usage:
|
|
12
|
+
install-genie-cli.sh [--global | --local]
|
|
13
|
+
|
|
14
|
+
Installs genie-cli, the companion CLI for the automagik-genie plugin.
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--global Install globally to ~/.local/bin (default)
|
|
18
|
+
--local Install to current directory's node_modules
|
|
19
|
+
|
|
20
|
+
The global install creates a symlink at ~/.local/bin/genie
|
|
21
|
+
that points to the installed genie-cli binary.
|
|
22
|
+
EOF
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
INSTALL_MODE="global"
|
|
26
|
+
|
|
27
|
+
while [[ $# -gt 0 ]]; do
|
|
28
|
+
case "$1" in
|
|
29
|
+
--global)
|
|
30
|
+
INSTALL_MODE="global"; shift ;;
|
|
31
|
+
--local)
|
|
32
|
+
INSTALL_MODE="local"; shift ;;
|
|
33
|
+
-h|--help)
|
|
34
|
+
usage; exit 0 ;;
|
|
35
|
+
*)
|
|
36
|
+
echo "Unknown arg: $1" >&2
|
|
37
|
+
usage
|
|
38
|
+
exit 2
|
|
39
|
+
;;
|
|
40
|
+
esac
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
# Determine plugin root (relative to this script)
|
|
44
|
+
PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
45
|
+
GENIE_CLI_ROOT="$(cd "$PLUGIN_ROOT/.." && pwd)"
|
|
46
|
+
|
|
47
|
+
echo "Installing genie-cli from: $GENIE_CLI_ROOT"
|
|
48
|
+
|
|
49
|
+
# Check for package manager
|
|
50
|
+
if command -v bun &>/dev/null; then
|
|
51
|
+
PKG_MGR="bun"
|
|
52
|
+
elif command -v npm &>/dev/null; then
|
|
53
|
+
PKG_MGR="npm"
|
|
54
|
+
else
|
|
55
|
+
echo "Error: Neither bun nor npm found. Install one first." >&2
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
echo "Using package manager: $PKG_MGR"
|
|
60
|
+
|
|
61
|
+
if [[ "$INSTALL_MODE" == "global" ]]; then
|
|
62
|
+
# Global install approach: link from ~/.local/bin
|
|
63
|
+
mkdir -p ~/.local/bin
|
|
64
|
+
|
|
65
|
+
# Install dependencies in genie-cli directory
|
|
66
|
+
echo "Installing dependencies..."
|
|
67
|
+
cd "$GENIE_CLI_ROOT"
|
|
68
|
+
$PKG_MGR install
|
|
69
|
+
|
|
70
|
+
# Create symlink
|
|
71
|
+
GENIE_BIN="$GENIE_CLI_ROOT/src/cli.ts"
|
|
72
|
+
if [[ -f "$GENIE_BIN" ]]; then
|
|
73
|
+
# For bun, we can use the TypeScript directly
|
|
74
|
+
if [[ "$PKG_MGR" == "bun" ]]; then
|
|
75
|
+
# Create a wrapper script
|
|
76
|
+
cat > ~/.local/bin/genie <<EOF
|
|
77
|
+
#!/usr/bin/env bash
|
|
78
|
+
exec bun "$GENIE_BIN" "\$@"
|
|
79
|
+
EOF
|
|
80
|
+
chmod +x ~/.local/bin/genie
|
|
81
|
+
echo "Created wrapper at ~/.local/bin/genie"
|
|
82
|
+
else
|
|
83
|
+
# For npm, need to build first
|
|
84
|
+
echo "Building genie-cli..."
|
|
85
|
+
$PKG_MGR run build 2>/dev/null || true
|
|
86
|
+
if [[ -f "$GENIE_CLI_ROOT/dist/cli.js" ]]; then
|
|
87
|
+
ln -sf "$GENIE_CLI_ROOT/dist/cli.js" ~/.local/bin/genie
|
|
88
|
+
chmod +x ~/.local/bin/genie
|
|
89
|
+
echo "Linked to ~/.local/bin/genie"
|
|
90
|
+
else
|
|
91
|
+
echo "Warning: Build failed, using TypeScript source with bun" >&2
|
|
92
|
+
cat > ~/.local/bin/genie <<EOF
|
|
93
|
+
#!/usr/bin/env bash
|
|
94
|
+
exec bun "$GENIE_BIN" "\$@"
|
|
95
|
+
EOF
|
|
96
|
+
chmod +x ~/.local/bin/genie
|
|
97
|
+
fi
|
|
98
|
+
fi
|
|
99
|
+
else
|
|
100
|
+
echo "Error: genie-cli binary not found at $GENIE_BIN" >&2
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
echo ""
|
|
105
|
+
echo "genie-cli installed successfully!"
|
|
106
|
+
echo ""
|
|
107
|
+
echo "Make sure ~/.local/bin is in your PATH:"
|
|
108
|
+
echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
|
|
109
|
+
echo ""
|
|
110
|
+
echo "Then run: genie --help"
|
|
111
|
+
|
|
112
|
+
else
|
|
113
|
+
# Local install
|
|
114
|
+
echo "Installing genie-cli locally..."
|
|
115
|
+
cd "$GENIE_CLI_ROOT"
|
|
116
|
+
$PKG_MGR install
|
|
117
|
+
echo ""
|
|
118
|
+
echo "genie-cli installed locally."
|
|
119
|
+
echo "Run with: $PKG_MGR run genie --help"
|
|
120
|
+
fi
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Validate forge completion before session ends.
|
|
4
|
+
* Used by Stop hook to check if all tasks are complete.
|
|
5
|
+
*
|
|
6
|
+
* Usage: bun validate-completion.ts --session <session-id>
|
|
7
|
+
* bun validate-completion.ts --help
|
|
8
|
+
*
|
|
9
|
+
* This script is advisory-only (exits 0) but logs warnings if:
|
|
10
|
+
* - Active wish exists with incomplete tasks
|
|
11
|
+
* - Tasks are marked BLOCKED
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { parseArgs } from "util";
|
|
15
|
+
import { readdirSync, readFileSync, existsSync } from "fs";
|
|
16
|
+
import { join } from "path";
|
|
17
|
+
|
|
18
|
+
const { values } = parseArgs({
|
|
19
|
+
args: Bun.argv.slice(2),
|
|
20
|
+
options: {
|
|
21
|
+
session: { type: "string", short: "s" },
|
|
22
|
+
help: { type: "boolean", short: "h" },
|
|
23
|
+
},
|
|
24
|
+
strict: true,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (values.help) {
|
|
28
|
+
console.log(`
|
|
29
|
+
validate-completion.ts - Check forge completion status
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
bun validate-completion.ts --session <session-id>
|
|
33
|
+
bun validate-completion.ts --help
|
|
34
|
+
|
|
35
|
+
Options:
|
|
36
|
+
-s, --session Session ID (currently unused, for future integration)
|
|
37
|
+
-h, --help Show this help message
|
|
38
|
+
|
|
39
|
+
This script checks for incomplete work and logs warnings.
|
|
40
|
+
It always exits 0 (advisory only).
|
|
41
|
+
`);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface WishStatus {
|
|
46
|
+
slug: string;
|
|
47
|
+
status: string;
|
|
48
|
+
incompleteTasks: number;
|
|
49
|
+
blockedTasks: number;
|
|
50
|
+
totalTasks: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function findWishes(baseDir: string): WishStatus[] {
|
|
54
|
+
const wishesDir = join(baseDir, ".genie", "wishes");
|
|
55
|
+
const results: WishStatus[] = [];
|
|
56
|
+
|
|
57
|
+
if (!existsSync(wishesDir)) {
|
|
58
|
+
return results;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const slugs = readdirSync(wishesDir, { withFileTypes: true })
|
|
63
|
+
.filter((d) => d.isDirectory())
|
|
64
|
+
.map((d) => d.name);
|
|
65
|
+
|
|
66
|
+
for (const slug of slugs) {
|
|
67
|
+
const wishFile = join(wishesDir, slug, "wish.md");
|
|
68
|
+
if (!existsSync(wishFile)) continue;
|
|
69
|
+
|
|
70
|
+
const content = readFileSync(wishFile, "utf-8");
|
|
71
|
+
|
|
72
|
+
// Extract status
|
|
73
|
+
const statusMatch = content.match(/^\*\*Status:\*\*\s*(\w+)/m);
|
|
74
|
+
const status = statusMatch ? statusMatch[1] : "UNKNOWN";
|
|
75
|
+
|
|
76
|
+
// Skip completed wishes
|
|
77
|
+
if (status === "DONE") continue;
|
|
78
|
+
|
|
79
|
+
// Count execution group tasks
|
|
80
|
+
const groupMatches = content.match(/^###\s+Group\s+[A-Z]:/gm) || [];
|
|
81
|
+
const totalTasks = groupMatches.length;
|
|
82
|
+
|
|
83
|
+
// Count unchecked acceptance criteria (rough proxy for incomplete tasks)
|
|
84
|
+
const uncheckedCriteria = (content.match(/^-\s+\[\s+\]/gm) || []).length;
|
|
85
|
+
const checkedCriteria = (content.match(/^-\s+\[x\]/gim) || []).length;
|
|
86
|
+
|
|
87
|
+
// Check for BLOCKED mentions
|
|
88
|
+
const blockedMentions = (content.match(/BLOCKED/gi) || []).length;
|
|
89
|
+
|
|
90
|
+
results.push({
|
|
91
|
+
slug,
|
|
92
|
+
status,
|
|
93
|
+
incompleteTasks: uncheckedCriteria > 0 ? Math.ceil(uncheckedCriteria / 3) : 0, // Rough estimate
|
|
94
|
+
blockedTasks: blockedMentions > 0 ? 1 : 0,
|
|
95
|
+
totalTasks,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// Silent failure - don't block session end
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return results;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Find wishes from current directory
|
|
106
|
+
const cwd = process.cwd();
|
|
107
|
+
const wishes = findWishes(cwd);
|
|
108
|
+
|
|
109
|
+
// Filter to active wishes (IN_PROGRESS)
|
|
110
|
+
const activeWishes = wishes.filter((w) => w.status === "IN_PROGRESS");
|
|
111
|
+
|
|
112
|
+
if (activeWishes.length === 0) {
|
|
113
|
+
// No active wishes, nothing to report
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Log warnings for incomplete work
|
|
118
|
+
let hasWarnings = false;
|
|
119
|
+
|
|
120
|
+
for (const wish of activeWishes) {
|
|
121
|
+
if (wish.incompleteTasks > 0 || wish.blockedTasks > 0) {
|
|
122
|
+
hasWarnings = true;
|
|
123
|
+
console.log(`\n⚠ Active wish "${wish.slug}" has incomplete work:`);
|
|
124
|
+
|
|
125
|
+
if (wish.incompleteTasks > 0) {
|
|
126
|
+
console.log(` - ~${wish.incompleteTasks} tasks with unchecked criteria`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (wish.blockedTasks > 0) {
|
|
130
|
+
console.log(` - ${wish.blockedTasks} BLOCKED task(s) need attention`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log(` Run /forge to continue or /review to validate.`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (hasWarnings) {
|
|
138
|
+
console.log("");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Always exit 0 (advisory only)
|
|
142
|
+
process.exit(0);
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Validate a wish document structure before writing.
|
|
4
|
+
* Used by PreToolUse hook to catch missing sections.
|
|
5
|
+
*
|
|
6
|
+
* Usage: bun validate-wish.ts --file <path-to-wish.md>
|
|
7
|
+
* bun validate-wish.ts --help
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { parseArgs } from "util";
|
|
11
|
+
import { readFileSync, existsSync } from "fs";
|
|
12
|
+
|
|
13
|
+
const { values } = parseArgs({
|
|
14
|
+
args: Bun.argv.slice(2),
|
|
15
|
+
options: {
|
|
16
|
+
file: { type: "string", short: "f" },
|
|
17
|
+
help: { type: "boolean", short: "h" },
|
|
18
|
+
},
|
|
19
|
+
strict: true,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (values.help) {
|
|
23
|
+
console.log(`
|
|
24
|
+
validate-wish.ts - Validate wish document structure
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
bun validate-wish.ts --file <path-to-wish.md>
|
|
28
|
+
bun validate-wish.ts --help
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
-f, --file Path to wish document to validate
|
|
32
|
+
-h, --help Show this help message
|
|
33
|
+
|
|
34
|
+
Exit codes:
|
|
35
|
+
0 Validation passed
|
|
36
|
+
1 Validation failed (missing required sections)
|
|
37
|
+
2 Invalid arguments or file not found
|
|
38
|
+
`);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!values.file) {
|
|
43
|
+
console.error("Error: --file is required");
|
|
44
|
+
process.exit(2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!existsSync(values.file)) {
|
|
48
|
+
// File doesn't exist yet (being created), skip validation
|
|
49
|
+
console.log("File not found, skipping validation (new file)");
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const content = readFileSync(values.file, "utf-8");
|
|
54
|
+
|
|
55
|
+
interface ValidationResult {
|
|
56
|
+
passed: boolean;
|
|
57
|
+
issues: string[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validateWish(content: string): ValidationResult {
|
|
61
|
+
const issues: string[] = [];
|
|
62
|
+
|
|
63
|
+
// Required sections
|
|
64
|
+
const requiredSections = [
|
|
65
|
+
{ pattern: /^##\s+Summary/m, name: "## Summary" },
|
|
66
|
+
{ pattern: /^##\s+Scope/m, name: "## Scope" },
|
|
67
|
+
{ pattern: /^###\s+IN/m, name: "### IN (under Scope)" },
|
|
68
|
+
{ pattern: /^###\s+OUT/m, name: "### OUT (under Scope)" },
|
|
69
|
+
{ pattern: /^##\s+Success Criteria/m, name: "## Success Criteria" },
|
|
70
|
+
{ pattern: /^##\s+Execution Groups/m, name: "## Execution Groups" },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
for (const { pattern, name } of requiredSections) {
|
|
74
|
+
if (!pattern.test(content)) {
|
|
75
|
+
issues.push(`Missing required section: ${name}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check for at least one execution group
|
|
80
|
+
const groupPattern = /^###\s+Group\s+[A-Z]:/m;
|
|
81
|
+
if (!groupPattern.test(content)) {
|
|
82
|
+
issues.push("Missing execution group (need at least one ### Group X: section)");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check for acceptance criteria in groups
|
|
86
|
+
const groups = content.match(/^###\s+Group\s+[A-Z]:.*/gm) || [];
|
|
87
|
+
if (groups.length > 0) {
|
|
88
|
+
// Check if Acceptance Criteria exists somewhere after Execution Groups
|
|
89
|
+
const execGroupsIndex = content.indexOf("## Execution Groups");
|
|
90
|
+
const afterExecGroups = content.slice(execGroupsIndex);
|
|
91
|
+
|
|
92
|
+
if (!afterExecGroups.includes("**Acceptance Criteria:**")) {
|
|
93
|
+
issues.push("Execution groups should have **Acceptance Criteria:** sections");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!afterExecGroups.includes("**Validation:**")) {
|
|
97
|
+
issues.push("Execution groups should have **Validation:** command sections");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check that OUT scope is not empty
|
|
102
|
+
const outMatch = content.match(/^###\s+OUT\s*\n([\s\S]*?)(?=^##|^###|\n---|\Z)/m);
|
|
103
|
+
if (outMatch) {
|
|
104
|
+
const outContent = outMatch[1].trim();
|
|
105
|
+
// Check if it only has placeholder text or is empty
|
|
106
|
+
if (!outContent || outContent === "-" || outContent.match(/^-\s*$/)) {
|
|
107
|
+
issues.push("OUT scope should not be empty - add explicit exclusions");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check for success criteria checkboxes
|
|
112
|
+
const successSection = content.match(/^##\s+Success Criteria\s*\n([\s\S]*?)(?=^##|\n---|\Z)/m);
|
|
113
|
+
if (successSection) {
|
|
114
|
+
const checkboxes = successSection[1].match(/^-\s+\[\s*\]/gm) || [];
|
|
115
|
+
if (checkboxes.length === 0) {
|
|
116
|
+
issues.push("Success Criteria should have checkbox items (- [ ])");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
passed: issues.length === 0,
|
|
122
|
+
issues,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const result = validateWish(content);
|
|
127
|
+
|
|
128
|
+
if (result.passed) {
|
|
129
|
+
console.log("✓ Wish document validation passed");
|
|
130
|
+
process.exit(0);
|
|
131
|
+
} else {
|
|
132
|
+
console.log("⚠ Wish document validation issues:");
|
|
133
|
+
for (const issue of result.issues) {
|
|
134
|
+
console.log(` - ${issue}`);
|
|
135
|
+
}
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|