@faviovazquez/deliberate 0.1.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.0] - 2025-04-03
9
+
10
+ ### Changed
11
+ - Rewrote installer as pure Node.js (no bash dependency required)
12
+ - Blue ASCII "DELIBERATE" banner with professional CLI styling
13
+ - Interactive prompts: platform selection with auto-detection, global/local scope choice
14
+ - Clean ✓ checkmark output with colored status lines
15
+ - Platform flags: --claude, --windsurf, --cursor, --all
16
+ - Scope flags: --global/-g, --local/-l
17
+ - Added --dry-run preview, --uninstall support
18
+ - Moved release.sh to repo root (dev-only, excluded from npm package)
19
+
8
20
  ## [0.1.0] - 2025-04-03
9
21
 
10
22
  ### Added
package/bin/cli.js CHANGED
@@ -2,68 +2,381 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const { execSync, spawn } = require('child_process');
6
- const path = require('path');
7
5
  const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const readline = require('readline');
8
9
 
9
10
  const ROOT = path.resolve(__dirname, '..');
10
- const INSTALL_SCRIPT = path.join(ROOT, 'install.sh');
11
+ const pkg = require(path.join(ROOT, 'package.json'));
11
12
 
13
+ // ─── Colors ────────────────────────────────────────────────────────────────
14
+ const blue = '\x1b[38;5;33m';
15
+ const cyan = '\x1b[36m';
16
+ const green = '\x1b[32m';
17
+ const yellow = '\x1b[33m';
18
+ const dim = '\x1b[2m';
19
+ const bold = '\x1b[1m';
20
+ const reset = '\x1b[0m';
21
+
22
+ // ─── Argument parsing ──────────────────────────────────────────────────────
12
23
  const args = process.argv.slice(2);
24
+ const hasClaude = args.includes('--claude') || args.includes('--claude-code');
25
+ const hasWindsurf = args.includes('--windsurf');
26
+ const hasCursor = args.includes('--cursor');
27
+ const hasAll = args.includes('--all');
28
+ const hasGlobal = args.includes('--global') || args.includes('-g');
29
+ const hasLocal = args.includes('--local') || args.includes('-l');
30
+ const hasDryRun = args.includes('--dry-run');
31
+ const hasHelp = args.includes('--help') || args.includes('-h');
32
+ const hasUninstall = args.includes('--uninstall') || args.includes('-u');
33
+
34
+ let selectedPlatforms = [];
35
+ if (hasAll) selectedPlatforms = ['claude-code', 'windsurf'];
36
+ if (hasClaude) selectedPlatforms.push('claude-code');
37
+ if (hasWindsurf) selectedPlatforms.push('windsurf');
38
+ if (hasCursor) selectedPlatforms.push('cursor');
39
+
40
+ // ─── Banner ────────────────────────────────────────────────────────────────
41
+ const banner = `
42
+ ${blue} ██████╗ ███████╗██╗ ██╗██████╗ ███████╗██████╗ █████╗ ████████╗███████╗
43
+ ██╔══██╗██╔════╝██║ ██║██╔══██╗██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██╔════╝
44
+ ██║ ██║█████╗ ██║ ██║██████╔╝█████╗ ██████╔╝███████║ ██║ █████╗
45
+ ██║ ██║██╔══╝ ██║ ██║██╔══██╗██╔══╝ ██╔══██╗██╔══██║ ██║ ██╔══╝
46
+ ██████╔╝███████╗███████╗██║██████╔╝███████╗██║ ██║██║ ██║ ██║ ███████╗
47
+ ╚═════╝ ╚══════╝╚══════╝╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝${reset}
48
+
49
+ ${dim}Agreement is a bug.${reset}
50
+ ${dim}v${pkg.version} · Multi-agent deliberation for AI coding assistants${reset}
51
+ `;
52
+
53
+ // ─── Help text ─────────────────────────────────────────────────────────────
54
+ const helpText = `
55
+ ${yellow}Usage:${reset} npx @faviovazquez/deliberate [platform] [scope] [options]
56
+
57
+ ${yellow}Platforms:${reset}
58
+ ${cyan}--claude${reset} Claude Code (~/.claude/)
59
+ ${cyan}--windsurf${reset} Windsurf (~/.codeium/windsurf/)
60
+ ${cyan}--cursor${reset} Cursor (.cursor/skills/)
61
+ ${cyan}--all${reset} All detected platforms
13
62
 
14
- function usage() {
15
- console.log(`
16
- deliberate -- Multi-agent deliberation for AI coding assistants
17
- Agreement is a bug.
63
+ ${yellow}Scope:${reset}
64
+ ${cyan}-g, --global${reset} Install to global config directory (recommended)
65
+ ${cyan}-l, --local${reset} Install to current project directory
18
66
 
19
- Usage:
20
- npx @faviovazquez/deliberate Auto-detect platform and install
21
- npx @faviovazquez/deliberate --platform X Install for specific platform
22
- npx @faviovazquez/deliberate --dry-run Preview installation
23
- npx @faviovazquez/deliberate --help Show this help
67
+ ${yellow}Options:${reset}
68
+ ${cyan}--dry-run${reset} Preview without installing
69
+ ${cyan}-u, --uninstall${reset} Remove deliberate files
70
+ ${cyan}-h, --help${reset} Show this help
24
71
 
25
- Platforms: claude-code, windsurf, cursor, all
72
+ ${yellow}Examples:${reset}
73
+ ${dim}# Interactive install (prompts for platform and scope)${reset}
74
+ npx @faviovazquez/deliberate
26
75
 
27
- After installation, open your AI coding assistant and try:
28
- /deliberate "should we migrate from REST to GraphQL?"
76
+ ${dim}# Install for Claude Code globally${reset}
77
+ npx @faviovazquez/deliberate --claude --global
29
78
 
30
- Learn more: https://github.com/FavioVazquez/deliberate
31
- `);
79
+ ${dim}# Install for all platforms globally${reset}
80
+ npx @faviovazquez/deliberate --all --global
81
+
82
+ ${dim}# Preview installation${reset}
83
+ npx @faviovazquez/deliberate --claude --global --dry-run
84
+ `;
85
+
86
+ // ─── Path helpers ──────────────────────────────────────────────────────────
87
+ function getGlobalDir(platform) {
88
+ switch (platform) {
89
+ case 'claude-code': return path.join(os.homedir(), '.claude');
90
+ case 'windsurf': return path.join(os.homedir(), '.codeium', 'windsurf');
91
+ case 'cursor': return path.join(process.cwd(), '.cursor');
92
+ default: return path.join(os.homedir(), '.claude');
93
+ }
32
94
  }
33
95
 
34
- if (args.includes('--help') || args.includes('-h')) {
35
- usage();
36
- process.exit(0);
96
+ function getLocalDir(platform) {
97
+ switch (platform) {
98
+ case 'claude-code': return path.join(process.cwd(), '.claude');
99
+ case 'windsurf': return path.join(process.cwd(), '.windsurf');
100
+ case 'cursor': return path.join(process.cwd(), '.cursor');
101
+ default: return path.join(process.cwd(), '.claude');
102
+ }
37
103
  }
38
104
 
39
- // Forward all arguments to install.sh
40
- console.log('');
41
- console.log(' deliberate installer');
42
- console.log(' Agreement is a bug.');
43
- console.log('');
44
-
45
- // Check for bash
46
- try {
47
- execSync('which bash', { stdio: 'pipe' });
48
- } catch (e) {
49
- console.error('Error: bash is required. Please install bash and try again.');
50
- process.exit(1);
105
+ function getPlatformLabel(platform) {
106
+ const labels = { 'claude-code': 'Claude Code', 'windsurf': 'Windsurf', 'cursor': 'Cursor' };
107
+ return labels[platform] || platform;
51
108
  }
52
109
 
53
- // Make install.sh executable
54
- try {
55
- fs.chmodSync(INSTALL_SCRIPT, '755');
56
- } catch (e) {
57
- // May already be executable
110
+ function tildePath(p) {
111
+ return p.replace(os.homedir(), '~');
58
112
  }
59
113
 
60
- // Run install.sh with forwarded arguments
61
- const installArgs = args.length > 0 ? args : [];
62
- const child = spawn('bash', [INSTALL_SCRIPT, ...installArgs], {
63
- stdio: 'inherit',
64
- cwd: ROOT
65
- });
114
+ // ─── File copy ─────────────────────────────────────────────────────────────
115
+ let fileCount = 0;
116
+
117
+ function copyFile(src, dest) {
118
+ if (hasDryRun) {
119
+ console.log(` ${dim}(dry-run)${reset} ${tildePath(dest)}`);
120
+ return;
121
+ }
122
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
123
+ fs.copyFileSync(src, dest);
124
+ fileCount++;
125
+ }
126
+
127
+ function copyDir(srcDir, destDir) {
128
+ if (!fs.existsSync(srcDir)) return;
129
+ for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
130
+ const src = path.join(srcDir, entry.name);
131
+ const dest = path.join(destDir, entry.name);
132
+ if (entry.isDirectory()) {
133
+ copyDir(src, dest);
134
+ } else {
135
+ copyFile(src, dest);
136
+ }
137
+ }
138
+ }
139
+
140
+ function makeExecutable(filePath) {
141
+ if (hasDryRun || !fs.existsSync(filePath)) return;
142
+ try { fs.chmodSync(filePath, '755'); } catch { /* ignore */ }
143
+ }
144
+
145
+ // ─── Install functions ─────────────────────────────────────────────────────
146
+ function installClaudeCode(isGlobal) {
147
+ const baseDir = isGlobal ? getGlobalDir('claude-code') : getLocalDir('claude-code');
148
+ const agentsDir = path.join(baseDir, 'agents');
149
+ const skillDir = path.join(baseDir, 'skills', 'deliberate');
150
+ const locationLabel = tildePath(baseDir);
151
+
152
+ console.log(`\n Installing for ${cyan}Claude Code${reset} → ${cyan}${locationLabel}${reset}\n`);
153
+
154
+ // Core agents
155
+ const agentsSrc = path.join(ROOT, 'agents');
156
+ for (const f of fs.readdirSync(agentsSrc)) {
157
+ if (!f.endsWith('.md')) continue;
158
+ copyFile(path.join(agentsSrc, f), path.join(agentsDir, `deliberate-${f}`));
159
+ }
160
+ // Specialist agents
161
+ const specSrc = path.join(agentsSrc, 'specialists');
162
+ if (fs.existsSync(specSrc)) {
163
+ for (const f of fs.readdirSync(specSrc)) {
164
+ if (!f.endsWith('.md')) continue;
165
+ copyFile(path.join(specSrc, f), path.join(agentsDir, `deliberate-${f}`));
166
+ }
167
+ }
168
+ console.log(` ${green}✓${reset} Installed 17 agents to ${tildePath(agentsDir)}/`);
169
+
170
+ // Skill protocol
171
+ copyFile(path.join(ROOT, 'SKILL.md'), path.join(skillDir, 'SKILL.md'));
172
+ copyFile(path.join(ROOT, 'BRAINSTORM.md'), path.join(skillDir, 'BRAINSTORM.md'));
173
+ console.log(` ${green}✓${reset} Installed skill protocol to ${tildePath(skillDir)}/`);
174
+
175
+ // Configs
176
+ copyDir(path.join(ROOT, 'configs'), path.join(skillDir, 'configs'));
177
+ console.log(` ${green}✓${reset} Installed configs`);
178
+
179
+ // Scripts
180
+ copyDir(path.join(ROOT, 'scripts'), path.join(skillDir, 'scripts'));
181
+ makeExecutable(path.join(skillDir, 'scripts', 'start-server.sh'));
182
+ makeExecutable(path.join(skillDir, 'scripts', 'stop-server.sh'));
183
+ makeExecutable(path.join(skillDir, 'scripts', 'detect-platform.sh'));
184
+ console.log(` ${green}✓${reset} Installed visual companion scripts`);
185
+
186
+ // Templates
187
+ copyDir(path.join(ROOT, 'templates'), path.join(skillDir, 'templates'));
188
+ console.log(` ${green}✓${reset} Installed output templates`);
189
+
190
+ console.log(`\n ${green}Done!${reset} Open Claude Code and try: ${cyan}/deliberate "your question here"${reset}`);
191
+ }
192
+
193
+ function installWindsurf(isGlobal) {
194
+ const baseDir = isGlobal ? getGlobalDir('windsurf') : getLocalDir('windsurf');
195
+ const skillDir = path.join(baseDir, 'skills', 'deliberate');
196
+ const locationLabel = tildePath(skillDir);
197
+
198
+ console.log(`\n Installing for ${cyan}Windsurf${reset} → ${cyan}${locationLabel}${reset}\n`);
199
+
200
+ // Skill protocol
201
+ copyFile(path.join(ROOT, 'SKILL.md'), path.join(skillDir, 'SKILL.md'));
202
+ copyFile(path.join(ROOT, 'BRAINSTORM.md'), path.join(skillDir, 'BRAINSTORM.md'));
203
+ console.log(` ${green}✓${reset} Installed skill protocol`);
204
+
205
+ // Agents (bundled with skill)
206
+ copyDir(path.join(ROOT, 'agents'), path.join(skillDir, 'agents'));
207
+ console.log(` ${green}✓${reset} Installed 17 agents`);
208
+
209
+ // Configs, Scripts, Templates
210
+ copyDir(path.join(ROOT, 'configs'), path.join(skillDir, 'configs'));
211
+ copyDir(path.join(ROOT, 'scripts'), path.join(skillDir, 'scripts'));
212
+ copyDir(path.join(ROOT, 'templates'), path.join(skillDir, 'templates'));
213
+ makeExecutable(path.join(skillDir, 'scripts', 'start-server.sh'));
214
+ makeExecutable(path.join(skillDir, 'scripts', 'stop-server.sh'));
215
+ makeExecutable(path.join(skillDir, 'scripts', 'detect-platform.sh'));
216
+ console.log(` ${green}✓${reset} Installed configs, scripts, templates`);
66
217
 
67
- child.on('close', (code) => {
68
- process.exit(code || 0);
218
+ console.log(`\n ${green}Done!${reset} Open Windsurf and try: ${cyan}@deliberate${reset} or just ask a complex decision question.`);
219
+ }
220
+
221
+ function installCursor(isGlobal) {
222
+ // Cursor is always workspace-local
223
+ const skillDir = path.join(process.cwd(), '.cursor', 'skills', 'deliberate');
224
+ const locationLabel = tildePath(skillDir);
225
+
226
+ console.log(`\n Installing for ${cyan}Cursor${reset} → ${cyan}${locationLabel}${reset}\n`);
227
+
228
+ copyFile(path.join(ROOT, 'SKILL.md'), path.join(skillDir, 'SKILL.md'));
229
+ copyFile(path.join(ROOT, 'BRAINSTORM.md'), path.join(skillDir, 'BRAINSTORM.md'));
230
+ copyDir(path.join(ROOT, 'agents'), path.join(skillDir, 'agents'));
231
+ copyDir(path.join(ROOT, 'configs'), path.join(skillDir, 'configs'));
232
+ copyDir(path.join(ROOT, 'scripts'), path.join(skillDir, 'scripts'));
233
+ copyDir(path.join(ROOT, 'templates'), path.join(skillDir, 'templates'));
234
+ makeExecutable(path.join(skillDir, 'scripts', 'start-server.sh'));
235
+ makeExecutable(path.join(skillDir, 'scripts', 'stop-server.sh'));
236
+ makeExecutable(path.join(skillDir, 'scripts', 'detect-platform.sh'));
237
+ console.log(` ${green}✓${reset} Installed skill, agents, configs, scripts, templates`);
238
+
239
+ console.log(`\n ${green}Done!${reset} Open Cursor and try: ${cyan}@deliberate${reset} or just ask a complex decision question.`);
240
+ }
241
+
242
+ // ─── Uninstall ─────────────────────────────────────────────────────────────
243
+ function uninstall(platform, isGlobal) {
244
+ const label = getPlatformLabel(platform);
245
+ let removed = 0;
246
+
247
+ if (platform === 'claude-code') {
248
+ const baseDir = isGlobal ? getGlobalDir('claude-code') : getLocalDir('claude-code');
249
+ console.log(`\n Uninstalling from ${cyan}${label}${reset} at ${cyan}${tildePath(baseDir)}${reset}\n`);
250
+
251
+ // Remove agents
252
+ const agentsDir = path.join(baseDir, 'agents');
253
+ if (fs.existsSync(agentsDir)) {
254
+ for (const f of fs.readdirSync(agentsDir)) {
255
+ if (f.startsWith('deliberate-') && f.endsWith('.md')) {
256
+ fs.unlinkSync(path.join(agentsDir, f)); removed++;
257
+ }
258
+ }
259
+ if (removed > 0) console.log(` ${green}✓${reset} Removed ${removed} agent files`);
260
+ }
261
+
262
+ // Remove skill dir
263
+ const skillDir = path.join(baseDir, 'skills', 'deliberate');
264
+ if (fs.existsSync(skillDir)) {
265
+ fs.rmSync(skillDir, { recursive: true });
266
+ console.log(` ${green}✓${reset} Removed skills/deliberate/`);
267
+ removed++;
268
+ }
269
+ } else {
270
+ const baseDir = platform === 'windsurf'
271
+ ? (isGlobal ? getGlobalDir('windsurf') : getLocalDir('windsurf'))
272
+ : path.join(process.cwd(), '.cursor');
273
+ const skillDir = path.join(baseDir, 'skills', 'deliberate');
274
+ console.log(`\n Uninstalling from ${cyan}${label}${reset} at ${cyan}${tildePath(skillDir)}${reset}\n`);
275
+
276
+ if (fs.existsSync(skillDir)) {
277
+ fs.rmSync(skillDir, { recursive: true });
278
+ console.log(` ${green}✓${reset} Removed skills/deliberate/`);
279
+ removed++;
280
+ }
281
+ }
282
+
283
+ if (removed === 0) console.log(` ${yellow}⚠${reset} No deliberate files found.`);
284
+ else console.log(`\n ${green}Done!${reset} deliberate uninstalled from ${label}.`);
285
+ }
286
+
287
+ // ─── Detect platforms ──────────────────────────────────────────────────────
288
+ function detectPlatforms() {
289
+ const detected = [];
290
+ if (fs.existsSync(path.join(os.homedir(), '.claude'))) detected.push('claude-code');
291
+ if (fs.existsSync(path.join(os.homedir(), '.codeium', 'windsurf'))) detected.push('windsurf');
292
+ if (fs.existsSync(path.join(process.cwd(), '.windsurf'))) {
293
+ if (!detected.includes('windsurf')) detected.push('windsurf');
294
+ }
295
+ if (fs.existsSync(path.join(process.cwd(), '.cursor'))) detected.push('cursor');
296
+ return detected;
297
+ }
298
+
299
+ // ─── Interactive prompt ────────────────────────────────────────────────────
300
+ async function promptUser() {
301
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
302
+ const ask = (q) => new Promise(resolve => rl.question(q, resolve));
303
+
304
+ const detected = detectPlatforms();
305
+
306
+ console.log(` ${yellow}Select platform:${reset}`);
307
+ console.log(` 1) Claude Code ${dim}(${detected.includes('claude-code') ? green + 'detected' + reset : dim + 'not detected' + reset}${dim})${reset}`);
308
+ console.log(` 2) Windsurf ${dim}(${detected.includes('windsurf') ? green + 'detected' + reset : dim + 'not detected' + reset}${dim})${reset}`);
309
+ console.log(` 3) Cursor ${dim}(workspace only)${reset}`);
310
+ console.log(` 4) All detected ${dim}(${detected.length > 0 ? detected.map(getPlatformLabel).join(', ') : 'none detected'})${reset}`);
311
+
312
+ const platformChoice = await ask(`\n ${bold}Platform [1-4]:${reset} `);
313
+ const platformMap = {
314
+ '1': ['claude-code'],
315
+ '2': ['windsurf'],
316
+ '3': ['cursor'],
317
+ '4': detected.length > 0 ? detected : ['claude-code'],
318
+ };
319
+ const platforms = platformMap[platformChoice.trim()] || ['claude-code'];
320
+
321
+ // Only ask scope if not cursor-only
322
+ let isGlobal = true;
323
+ const hasCursorOnly = platforms.length === 1 && platforms[0] === 'cursor';
324
+ if (!hasCursorOnly) {
325
+ console.log(`\n ${yellow}Install scope:${reset}`);
326
+ console.log(` 1) ${green}Global${reset} ${dim}(recommended)${reset} — available in all projects`);
327
+ console.log(` 2) Local — current project only`);
328
+ const scopeChoice = await ask(`\n ${bold}Scope [1-2]:${reset} `);
329
+ isGlobal = scopeChoice.trim() !== '2';
330
+ } else {
331
+ isGlobal = false; // Cursor is always workspace-local
332
+ }
333
+
334
+ rl.close();
335
+ return { platforms, isGlobal };
336
+ }
337
+
338
+ // ─── Entry point ───────────────────────────────────────────────────────────
339
+ async function main() {
340
+ console.log(banner);
341
+
342
+ if (hasHelp) { console.log(helpText); process.exit(0); }
343
+
344
+ let platforms = selectedPlatforms;
345
+ let isGlobal = hasGlobal || !hasLocal;
346
+
347
+ if (platforms.length === 0 && !hasUninstall) {
348
+ const result = await promptUser();
349
+ platforms = result.platforms;
350
+ isGlobal = result.isGlobal;
351
+ } else if (platforms.length === 0 && hasUninstall) {
352
+ console.error(` ${yellow}Error:${reset} Specify a platform to uninstall from.`);
353
+ console.error(` Example: npx @faviovazquez/deliberate --claude --global --uninstall`);
354
+ process.exit(1);
355
+ }
356
+
357
+ console.log('');
358
+ for (const platform of platforms) {
359
+ fileCount = 0;
360
+ if (hasUninstall) {
361
+ uninstall(platform, isGlobal);
362
+ } else {
363
+ switch (platform) {
364
+ case 'claude-code': installClaudeCode(isGlobal); break;
365
+ case 'windsurf': installWindsurf(isGlobal); break;
366
+ case 'cursor': installCursor(isGlobal); break;
367
+ default:
368
+ console.error(` ${yellow}Unknown platform:${reset} ${platform}`);
369
+ process.exit(1);
370
+ }
371
+ }
372
+ }
373
+
374
+ if (!hasUninstall) {
375
+ console.log(`\n ${dim}Learn more: https://github.com/FavioVazquez/deliberate${reset}`);
376
+ }
377
+ }
378
+
379
+ main().catch(err => {
380
+ console.error(` Error: ${err.message}`);
381
+ process.exit(1);
69
382
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faviovazquez/deliberate",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Multi-agent deliberation skill for AI coding assistants. Agreement is a bug.",
5
5
  "license": "MIT",
6
6
  "author": "Favio Vazquez",
@@ -1,131 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- # deliberate -- Release script
5
- # Usage: ./scripts/release.sh [patch|minor|major]
6
- #
7
- # This script:
8
- # 1. Bumps the version in package.json
9
- # 2. Updates CHANGELOG.md with the new version header
10
- # 3. Commits the version bump
11
- # 4. Creates a git tag
12
- # 5. Pushes to origin with tags
13
- # 6. Creates a GitHub release
14
- # 7. Publishes to npm
15
-
16
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17
- ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
18
- BUMP_TYPE="${1:-patch}"
19
-
20
- if [[ "$BUMP_TYPE" != "patch" && "$BUMP_TYPE" != "minor" && "$BUMP_TYPE" != "major" ]]; then
21
- echo "Usage: release.sh [patch|minor|major]"
22
- echo " patch: 0.1.0 -> 0.1.1 (bug fixes)"
23
- echo " minor: 0.1.0 -> 0.2.0 (new features, new agents)"
24
- echo " major: 0.1.0 -> 1.0.0 (breaking changes)"
25
- exit 1
26
- fi
27
-
28
- cd "$ROOT_DIR"
29
-
30
- # Pre-flight checks
31
- echo "=== Pre-flight checks ==="
32
-
33
- if [[ -n "$(git status --porcelain)" ]]; then
34
- echo "ERROR: Working directory is not clean. Commit or stash changes first."
35
- git status --short
36
- exit 1
37
- fi
38
-
39
- if ! command -v gh >/dev/null 2>&1; then
40
- echo "ERROR: GitHub CLI (gh) is required. Install: https://cli.github.com"
41
- exit 1
42
- fi
43
-
44
- if ! command -v npm >/dev/null 2>&1; then
45
- echo "ERROR: npm is required."
46
- exit 1
47
- fi
48
-
49
- CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
50
- if [[ "$CURRENT_BRANCH" != "main" ]]; then
51
- echo "WARNING: You are on branch '$CURRENT_BRANCH', not 'main'."
52
- read -p "Continue? [y/N] " confirm
53
- [[ "$confirm" == "y" || "$confirm" == "Y" ]] || exit 1
54
- fi
55
-
56
- # Get current and new version
57
- CURRENT_VERSION=$(node -p "require('./package.json').version")
58
- echo "Current version: $CURRENT_VERSION"
59
-
60
- # Bump version (npm version updates package.json and creates git tag)
61
- NEW_VERSION=$(npm version "$BUMP_TYPE" --no-git-tag-version | sed 's/^v//')
62
- echo "New version: $NEW_VERSION"
63
-
64
- # Update CHANGELOG.md
65
- DATE=$(date +%Y-%m-%d)
66
- CHANGELOG_ENTRY="## [$NEW_VERSION] - $DATE"
67
-
68
- if grep -q "## \[Unreleased\]" CHANGELOG.md 2>/dev/null; then
69
- # Replace [Unreleased] with the new version
70
- sed -i "s/## \[Unreleased\]/$CHANGELOG_ENTRY/" CHANGELOG.md
71
- else
72
- # Insert new version header after the first ## line
73
- sed -i "/^## \[/i\\
74
- \\n$CHANGELOG_ENTRY\\n" CHANGELOG.md
75
- fi
76
-
77
- echo ""
78
- echo "=== CHANGELOG.md updated ==="
79
- echo "Please review and add release notes to CHANGELOG.md before continuing."
80
- echo "The new version header has been added. Add your changes under it."
81
- echo ""
82
- read -p "Open CHANGELOG.md for editing? [Y/n] " edit_changelog
83
- if [[ "$edit_changelog" != "n" && "$edit_changelog" != "N" ]]; then
84
- ${EDITOR:-vi} CHANGELOG.md
85
- fi
86
-
87
- # Commit and tag
88
- echo ""
89
- echo "=== Committing and tagging ==="
90
- git add package.json CHANGELOG.md
91
- git commit -m "release: v$NEW_VERSION"
92
- git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION"
93
-
94
- # Push
95
- echo ""
96
- echo "=== Pushing to origin ==="
97
- git push origin "$CURRENT_BRANCH"
98
- git push origin "v$NEW_VERSION"
99
-
100
- # Create GitHub release
101
- echo ""
102
- echo "=== Creating GitHub release ==="
103
-
104
- # Extract changelog for this version
105
- RELEASE_NOTES=$(awk "/^## \[$NEW_VERSION\]/{found=1; next} /^## \[/{if(found) exit} found{print}" CHANGELOG.md)
106
-
107
- if [[ -z "$RELEASE_NOTES" ]]; then
108
- RELEASE_NOTES="Release v$NEW_VERSION"
109
- fi
110
-
111
- gh release create "v$NEW_VERSION" \
112
- --title "v$NEW_VERSION" \
113
- --notes "$RELEASE_NOTES"
114
-
115
- # Publish to npm
116
- echo ""
117
- echo "=== Publishing to npm ==="
118
- read -p "Publish to npm? [Y/n] " publish
119
- if [[ "$publish" != "n" && "$publish" != "N" ]]; then
120
- npm publish
121
- echo ""
122
- echo "Published! Users can now run: npx deliberate"
123
- else
124
- echo "Skipped npm publish. Run 'npm publish' manually when ready."
125
- fi
126
-
127
- echo ""
128
- echo "=== Release v$NEW_VERSION complete ==="
129
- echo " Git tag: v$NEW_VERSION"
130
- echo " GitHub: https://github.com/FavioVazquez/deliberate/releases/tag/v$NEW_VERSION"
131
- echo " npm: https://www.npmjs.com/package/deliberate"