odd-studio 2.0.0 → 2.1.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/bin/odd-studio.js CHANGED
@@ -62,7 +62,7 @@ program
62
62
  console.log(chalk.bold(` Setting up: ${chalk.cyan(resolvedName)}\n`));
63
63
 
64
64
  // 1. Scaffold project structure
65
- print.step(1, 3, 'Creating project structure...');
65
+ print.step(1, 4, 'Creating project structure...');
66
66
  const spinner1 = ora({ text: '', indent: 4 }).start();
67
67
  try {
68
68
  await scaffoldProject(targetDir, resolvedName);
@@ -76,7 +76,7 @@ program
76
76
 
77
77
  // 2. Install /odd skill
78
78
  if (!options.skipSkill) {
79
- print.step(2, 3, 'Installing /odd skill into Claude Code...');
79
+ print.step(2, 4, 'Installing /odd skill into Claude Code...');
80
80
  const spinner2 = ora({ text: '', indent: 4 }).start();
81
81
  try {
82
82
  const result = await installSkill(PACKAGE_ROOT);
@@ -88,13 +88,13 @@ program
88
88
  print.info('Manual install: copy ' + chalk.dim('skill/') + ' to ' + chalk.dim('~/.claude/skills/odd/'));
89
89
  }
90
90
  } else {
91
- print.step(2, 3, 'Skipping skill install (--skip-skill)');
91
+ print.step(2, 4, 'Skipping skill install (--skip-skill)');
92
92
  print.warn('Remember to install the skill manually for /odd to work in Claude Code.');
93
93
  }
94
94
 
95
95
  // 3. Setup hooks
96
96
  if (!options.skipHooks) {
97
- print.step(3, 3, 'Installing safety hooks into Claude Code settings...');
97
+ print.step(3, 4, 'Installing safety hooks into Claude Code settings...');
98
98
  const spinner3 = ora({ text: '', indent: 4 }).start();
99
99
  try {
100
100
  const result = await setupHooks(PACKAGE_ROOT);
@@ -106,10 +106,29 @@ program
106
106
  print.info('Manual install: add entries from ' + chalk.dim('hooks/') + ' to ~/.claude/settings.json');
107
107
  }
108
108
  } else {
109
- print.step(3, 3, 'Skipping hooks (--skip-hooks)');
109
+ print.step(3, 4, 'Skipping hooks (--skip-hooks)');
110
110
  print.warn('Safety hooks not installed. Git guardrails and quality gates will not run.');
111
111
  }
112
112
 
113
+ // 4. Configure ruflo MCP server (cross-session memory)
114
+ print.step(4, 4, 'Configuring ruflo memory server...');
115
+ const spinner4 = ora({ text: '', indent: 4 }).start();
116
+ try {
117
+ const { default: setupMcp } = await import('../scripts/setup-mcp.js');
118
+ const mcpResult = await setupMcp();
119
+ spinner4.stop();
120
+ if (mcpResult.mcpJsonUpdated || mcpResult.settingsUpdated) {
121
+ print.ok('Ruflo MCP server configured — cross-session memory enabled');
122
+ } else {
123
+ print.ok('Ruflo MCP server already configured');
124
+ }
125
+ } catch (e) {
126
+ spinner4.stop();
127
+ print.warn('Could not configure ruflo automatically: ' + e.message);
128
+ print.info('Manual setup: add ruflo to ~/.mcp.json and enable in ~/.claude/settings.json');
129
+ print.info('See: https://github.com/ruvnet/ruflo for installation instructions');
130
+ }
131
+
113
132
  // ── Done ──
114
133
  print.blank();
115
134
  console.log(chalk.bold.green(' ✓ ODD Studio is ready.\n'));
@@ -188,6 +207,15 @@ program
188
207
  s2.fail('Hooks update failed: ' + e.message);
189
208
  }
190
209
 
210
+ const s3 = ora({ text: 'Checking ruflo MCP configuration...', indent: 4 }).start();
211
+ try {
212
+ const { default: setupMcp } = await import('../scripts/setup-mcp.js');
213
+ const mcpResult = await setupMcp();
214
+ s3.succeed(mcpResult.mcpJsonUpdated || mcpResult.settingsUpdated ? 'Ruflo MCP configured' : 'Ruflo MCP already configured');
215
+ } catch (e) {
216
+ s3.fail('Ruflo MCP check failed: ' + e.message);
217
+ }
218
+
191
219
  print.blank();
192
220
  print.ok('Upgrade complete.');
193
221
  print.blank();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "odd-studio",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Outcome-Driven Development for Claude Code — a planning and build harness for domain experts building serious software with AI.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -13,16 +13,19 @@ if (!isGlobal && !isNpx) {
13
13
  process.exit(0);
14
14
  }
15
15
 
16
- import('./install-skill.js')
17
- .then(({ default: installSkill }) => {
18
- const pkg = new URL('..', import.meta.url).pathname;
19
- return installSkill(pkg.replace(/\/$/, ''));
20
- })
16
+ const pkg = new URL('..', import.meta.url).pathname.replace(/\/$/, '');
17
+
18
+ Promise.all([
19
+ import('./install-skill.js').then(({ default: installSkill }) => installSkill(pkg)),
20
+ import('./setup-hooks.js').then(({ default: setupHooks }) => setupHooks(pkg)),
21
+ import('./setup-mcp.js').then(({ default: setupMcp }) => setupMcp()),
22
+ ])
21
23
  .then(() => {
22
- console.log('✓ ODD Studio: /odd skill installed into Claude Code');
24
+ console.log('✓ ODD Studio: /odd skill, safety hooks, and ruflo memory installed into Claude Code');
25
+ console.log(' Run: npx odd-studio init [project-name] to scaffold your first project.');
23
26
  })
24
27
  .catch((e) => {
25
28
  // Non-fatal — user can run odd-studio init manually
26
- console.log('⚠ ODD Studio: Could not auto-install skill (' + e.message + ')');
29
+ console.log('⚠ ODD Studio: Could not complete setup (' + e.message + ')');
27
30
  console.log(' Run: npx odd-studio init to complete setup.');
28
31
  });
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import os from 'os';
5
+
6
+ const MCP_JSON_PATH = path.join(os.homedir(), '.mcp.json');
7
+ const SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
8
+
9
+ const RUFLO_SERVER_ENTRY = {
10
+ type: 'stdio',
11
+ command: 'npx',
12
+ args: ['ruflo@latest', 'mcp', 'start'],
13
+ env: {},
14
+ };
15
+
16
+ /**
17
+ * Configures the ruflo MCP server so that cross-session memory works.
18
+ *
19
+ * Two writes required:
20
+ * 1. ~/.mcp.json — defines the server (npx ruflo@latest mcp start)
21
+ * 2. ~/.claude/settings.json — adds "ruflo" to enabledMcpjsonServers
22
+ *
23
+ * Both are idempotent: existing entries are left untouched.
24
+ */
25
+ export default async function setupMcp() {
26
+ const result = { mcpJsonUpdated: false, settingsUpdated: false };
27
+
28
+ // ── 1. Add ruflo to ~/.mcp.json ────────────────────────────────────────────
29
+ let mcpConfig = { mcpServers: {} };
30
+ if (fs.existsSync(MCP_JSON_PATH)) {
31
+ try {
32
+ mcpConfig = await fs.readJson(MCP_JSON_PATH);
33
+ } catch {
34
+ // Corrupt file — start fresh but preserve any valid content
35
+ mcpConfig = { mcpServers: {} };
36
+ }
37
+ }
38
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
39
+
40
+ if (!mcpConfig.mcpServers.ruflo) {
41
+ mcpConfig.mcpServers.ruflo = RUFLO_SERVER_ENTRY;
42
+ await fs.writeJson(MCP_JSON_PATH, mcpConfig, { spaces: 2 });
43
+ result.mcpJsonUpdated = true;
44
+ }
45
+
46
+ // ── 2. Enable ruflo in ~/.claude/settings.json ─────────────────────────────
47
+ await fs.ensureDir(path.join(os.homedir(), '.claude'));
48
+ let settings = {};
49
+ if (fs.existsSync(SETTINGS_PATH)) {
50
+ try {
51
+ settings = await fs.readJson(SETTINGS_PATH);
52
+ } catch {
53
+ settings = {};
54
+ }
55
+ }
56
+
57
+ if (!Array.isArray(settings.enabledMcpjsonServers)) {
58
+ settings.enabledMcpjsonServers = [];
59
+ }
60
+
61
+ if (!settings.enabledMcpjsonServers.includes('ruflo')) {
62
+ settings.enabledMcpjsonServers.push('ruflo');
63
+ await fs.writeJson(SETTINGS_PATH, settings, { spaces: 2 });
64
+ result.settingsUpdated = true;
65
+ }
66
+
67
+ return result;
68
+ }