start-vibing-stacks 1.2.0 → 1.3.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.
- package/dist/index.js +16 -3
- package/dist/setup.js +97 -1
- package/package.json +1 -1
- package/stacks/_shared/hooks/run-hook.sh +19 -0
- package/stacks/_shared/hooks/run-hook.ts +90 -0
package/dist/index.js
CHANGED
|
@@ -238,10 +238,23 @@ async function main() {
|
|
|
238
238
|
console.log(chalk.green.bold(' 🎸 Ready to vibe!'));
|
|
239
239
|
console.log('');
|
|
240
240
|
if (!FLAGS.noClaude) {
|
|
241
|
-
console.log(chalk.dim(' Launching Claude Code
|
|
242
|
-
|
|
241
|
+
console.log(chalk.dim(' Launching Claude Code...\n'));
|
|
242
|
+
const { execSync: run } = await import('child_process');
|
|
243
|
+
try {
|
|
244
|
+
run('claude --dangerously-skip-permissions', {
|
|
245
|
+
cwd: projectDir,
|
|
246
|
+
stdio: 'inherit',
|
|
247
|
+
env: { ...process.env },
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
console.log('');
|
|
252
|
+
ui.info('Claude Code session ended. Run again with: claude --dangerously-skip-permissions');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
console.log(chalk.dim(' Run: claude --dangerously-skip-permissions\n'));
|
|
243
257
|
}
|
|
244
|
-
console.log('');
|
|
245
258
|
}
|
|
246
259
|
main().catch((err) => {
|
|
247
260
|
ui.error(`Unexpected error: ${err}`);
|
package/dist/setup.js
CHANGED
|
@@ -134,8 +134,18 @@ export async function setupProject(projectDir, config, options = {}) {
|
|
|
134
134
|
if (existsSync(sharedCommandsDir)) {
|
|
135
135
|
copyDirRecursive(sharedCommandsDir, join(claudeDir, 'commands'), options.force);
|
|
136
136
|
}
|
|
137
|
-
// 13. Write settings.json for Claude Code
|
|
137
|
+
// 13. Write settings.json for Claude Code (complete config)
|
|
138
138
|
const settings = {
|
|
139
|
+
model: 'sonnet',
|
|
140
|
+
max_tokens: 8192,
|
|
141
|
+
max_turns: 100,
|
|
142
|
+
enableAllProjectMcpServers: true,
|
|
143
|
+
context: {
|
|
144
|
+
compaction_threshold: 0.85,
|
|
145
|
+
enable_compaction: true,
|
|
146
|
+
enable_semantic_search: true,
|
|
147
|
+
memory_files: ['.claude/CLAUDE.md', 'CLAUDE.md'],
|
|
148
|
+
},
|
|
139
149
|
permissions: {
|
|
140
150
|
allow: [
|
|
141
151
|
'Bash(*)',
|
|
@@ -149,7 +159,93 @@ export async function setupProject(projectDir, config, options = {}) {
|
|
|
149
159
|
],
|
|
150
160
|
deny: [],
|
|
151
161
|
},
|
|
162
|
+
hooks: {
|
|
163
|
+
UserPromptSubmit: [
|
|
164
|
+
{
|
|
165
|
+
matcher: '',
|
|
166
|
+
hooks: [
|
|
167
|
+
{
|
|
168
|
+
type: 'command',
|
|
169
|
+
command: 'npx tsx .claude/hooks/run-hook.ts user-prompt-submit',
|
|
170
|
+
timeout: 10,
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
Stop: [
|
|
176
|
+
{
|
|
177
|
+
hooks: [
|
|
178
|
+
{
|
|
179
|
+
type: 'command',
|
|
180
|
+
command: 'npx tsx .claude/hooks/run-hook.ts stop-validator',
|
|
181
|
+
timeout: 30,
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
agents: {
|
|
188
|
+
'research-web': {
|
|
189
|
+
description: 'Researches best practices before implementation',
|
|
190
|
+
},
|
|
191
|
+
'documenter': {
|
|
192
|
+
description: 'Creates and maintains domain documentation',
|
|
193
|
+
},
|
|
194
|
+
'domain-updater': {
|
|
195
|
+
description: 'Records session learnings in domain docs',
|
|
196
|
+
},
|
|
197
|
+
'commit-manager': {
|
|
198
|
+
description: 'Manages commits and git workflow',
|
|
199
|
+
},
|
|
200
|
+
'claude-md-compactor': {
|
|
201
|
+
description: 'Compacts CLAUDE.md when it exceeds 40k chars',
|
|
202
|
+
},
|
|
203
|
+
'tester': {
|
|
204
|
+
description: 'Creates tests using the stack-appropriate framework',
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
workflow: {
|
|
208
|
+
default_flow: [
|
|
209
|
+
'research-web',
|
|
210
|
+
'tester',
|
|
211
|
+
'documenter',
|
|
212
|
+
'domain-updater',
|
|
213
|
+
'commit-manager',
|
|
214
|
+
],
|
|
215
|
+
config_flow: ['domain-updater', 'commit-manager'],
|
|
216
|
+
},
|
|
217
|
+
rules: {
|
|
218
|
+
research: {
|
|
219
|
+
required_before_implementation: true,
|
|
220
|
+
must_cite_sources: true,
|
|
221
|
+
},
|
|
222
|
+
security: {
|
|
223
|
+
sanitize_all_responses: true,
|
|
224
|
+
owasp_validation_required: true,
|
|
225
|
+
},
|
|
226
|
+
testing: {
|
|
227
|
+
unit_tests_required: true,
|
|
228
|
+
edge_cases_research_required: true,
|
|
229
|
+
},
|
|
230
|
+
documentation: {
|
|
231
|
+
flow_docs_required: true,
|
|
232
|
+
research_must_be_documented: true,
|
|
233
|
+
},
|
|
234
|
+
},
|
|
152
235
|
};
|
|
236
|
+
// Add stack-specific quality gates
|
|
237
|
+
const stackCfg = loadStackConfig(config.stack);
|
|
238
|
+
if (stackCfg) {
|
|
239
|
+
const gates = {};
|
|
240
|
+
for (const gate of stackCfg.qualityGates) {
|
|
241
|
+
gates[gate.name.toLowerCase().replace(/\s+/g, '_')] = {
|
|
242
|
+
command: gate.command,
|
|
243
|
+
required: gate.required,
|
|
244
|
+
blocking: gate.required,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
settings.quality_gates = gates;
|
|
248
|
+
}
|
|
153
249
|
writeFileSync(join(claudeDir, 'settings.json'), JSON.stringify(settings, null, '\t'));
|
|
154
250
|
spinner.succeed(`Setup complete: ${agentCount} agents, ${sharedSkillCount + stackSkillCount} skills, ${hookCount} hooks`);
|
|
155
251
|
return true;
|
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Universal Hook Runner — Start Vibing Stacks
|
|
3
|
+
# Tries: bun → npx tsx
|
|
4
|
+
|
|
5
|
+
HOOK_NAME="$1"
|
|
6
|
+
HOOKS_DIR="$(dirname "$0")"
|
|
7
|
+
|
|
8
|
+
command_exists() { command -v "$1" >/dev/null 2>&1; }
|
|
9
|
+
|
|
10
|
+
if command_exists bun && [ -f "$HOOKS_DIR/$HOOK_NAME.ts" ]; then
|
|
11
|
+
exec bun "$HOOKS_DIR/$HOOK_NAME.ts"
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
if command_exists npx && [ -f "$HOOKS_DIR/$HOOK_NAME.ts" ]; then
|
|
15
|
+
exec npx tsx "$HOOKS_DIR/$HOOK_NAME.ts"
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
echo '{"decision":"approve","continue":true,"reason":"No runtime available"}'
|
|
19
|
+
exit 0
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Universal Hook Runner — Start Vibing Stacks
|
|
4
|
+
*
|
|
5
|
+
* Runtime fallbacks: bun → npx tsx
|
|
6
|
+
* Usage: npx tsx run-hook.ts <hook-name>
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { spawnSync } from 'child_process';
|
|
10
|
+
import { existsSync } from 'fs';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
|
|
14
|
+
const HOOKS_DIR = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
function checkRuntime(cmd: string): boolean {
|
|
17
|
+
try {
|
|
18
|
+
const result = spawnSync(cmd, ['--version'], {
|
|
19
|
+
stdio: 'pipe', shell: true, timeout: 5000,
|
|
20
|
+
});
|
|
21
|
+
return result.status === 0;
|
|
22
|
+
} catch { return false; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function runWithRuntime(cmd: string, args: string[], input: string) {
|
|
26
|
+
try {
|
|
27
|
+
const result = spawnSync(cmd, args, {
|
|
28
|
+
input, shell: true, stdio: ['pipe', 'pipe', 'pipe'],
|
|
29
|
+
timeout: 30000, encoding: 'utf8',
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
exitCode: result.status ?? 1,
|
|
33
|
+
output: result.stdout?.toString() || '',
|
|
34
|
+
error: result.stderr?.toString() || undefined,
|
|
35
|
+
};
|
|
36
|
+
} catch (err) {
|
|
37
|
+
return { exitCode: 1, output: '', error: String(err) };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function readStdin(timeoutMs: number): Promise<string> {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
const timeout = setTimeout(() => { process.stdin.destroy(); resolve('{}'); }, timeoutMs);
|
|
44
|
+
let data = '';
|
|
45
|
+
process.stdin.setEncoding('utf8');
|
|
46
|
+
process.stdin.on('data', (chunk: string) => { data += chunk; });
|
|
47
|
+
process.stdin.on('end', () => { clearTimeout(timeout); resolve(data || '{}'); });
|
|
48
|
+
process.stdin.on('error', () => { clearTimeout(timeout); resolve('{}'); });
|
|
49
|
+
if (process.stdin.readableEnded) { clearTimeout(timeout); resolve('{}'); }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function main(): Promise<void> {
|
|
54
|
+
const hookName = process.argv[2];
|
|
55
|
+
if (!hookName) {
|
|
56
|
+
console.error('[run-hook] Usage: npx tsx run-hook.ts <hook-name>');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const tsPath = join(HOOKS_DIR, `${hookName}.ts`);
|
|
61
|
+
if (!existsSync(tsPath)) {
|
|
62
|
+
console.error(`[run-hook] Hook not found: ${tsPath}`);
|
|
63
|
+
process.stdout.write(JSON.stringify({ decision: 'approve', continue: true, reason: 'Hook not found' }));
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const stdinData = await readStdin(2000);
|
|
68
|
+
const runtimes = [
|
|
69
|
+
{ name: 'bun', cmd: 'bun' },
|
|
70
|
+
{ name: 'npx-tsx', cmd: 'npx tsx' },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
for (const runtime of runtimes) {
|
|
74
|
+
if (!checkRuntime(runtime.cmd.split(' ')[0])) continue;
|
|
75
|
+
|
|
76
|
+
const result = runWithRuntime(runtime.cmd, [tsPath], stdinData);
|
|
77
|
+
process.stdout.write(result.output);
|
|
78
|
+
if (result.error) process.stderr.write(result.error);
|
|
79
|
+
process.exit(result.exitCode);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.error('[run-hook] No runtime available');
|
|
83
|
+
process.stdout.write(JSON.stringify({ decision: 'approve', continue: true, reason: 'No runtime available' }));
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
main().catch(() => {
|
|
88
|
+
process.stdout.write(JSON.stringify({ decision: 'approve', continue: true, reason: 'Hook error' }));
|
|
89
|
+
process.exit(0);
|
|
90
|
+
});
|