organic-growth 3.1.0 → 3.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.
Files changed (3) hide show
  1. package/README.md +5 -2
  2. package/bin/cli.mjs +181 -87
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -26,6 +26,9 @@ bunx organic-growth docs/my-product-spec.md
26
26
 
27
27
  # Force overwrite existing files:
28
28
  bunx organic-growth --force
29
+
30
+ # Upgrade managed files (preserves CLAUDE.md, .mcp.json, etc.):
31
+ bunx organic-growth --upgrade
29
32
  ```
30
33
 
31
34
  This installs `CLAUDE.md` at your project root and a `.claude/` directory with agents and commands. No runtime dependencies.
@@ -110,7 +113,7 @@ Properties are not test cases or user stories. A test says "when I do X, Y happe
110
113
 
111
114
  Properties **accumulate** across stages. Stage 3 must still satisfy the properties from stages 1 and 2. They are permanent commitments, not checkboxes to discard. This is what prevents regressions as the feature grows.
112
115
 
113
- The gardener agent handles the full property format — categories, failure analysis, dependency tracking. See the [example growth plan](docs/example-growth-plan.md) for what this looks like in practice.
116
+ The gardener agent handles the full property format — categories, failure analysis, dependency tracking. See the [example growth plan](.organic-growth/example-growth-plan.md) for what this looks like in practice.
114
117
 
115
118
  ## After Install
116
119
 
@@ -118,7 +121,7 @@ The gardener agent handles the full property format — categories, failure anal
118
121
  2. Fill in Quality Tools section with your project's lint/test commands
119
122
  3. Start building with `/grow`
120
123
 
121
- See the [example growth plan](docs/example-growth-plan.md) to see properties, stages, and accumulation in action.
124
+ See the [example growth plan](.organic-growth/example-growth-plan.md) to see properties, stages, and accumulation in action.
122
125
 
123
126
  ## Releases
124
127
 
package/bin/cli.mjs CHANGED
@@ -129,6 +129,7 @@ function printHelp() {
129
129
  log('');
130
130
  log(`${CYAN}Options:${RESET}`);
131
131
  log(` -f, --force Overwrite existing files without prompting`);
132
+ log(` --upgrade Update managed files while preserving user customizations`);
132
133
  log(` --migrate Move legacy docs/growth and docs/product-dna.md to .organic-growth/`);
133
134
  log(` -h, --help Show this help message`);
134
135
  log(` -v, --version Show version number`);
@@ -141,6 +142,7 @@ function printHelp() {
141
142
  log(` npx organic-growth Install Claude Code templates`);
142
143
  log(` npx organic-growth --opencode Install opencode templates`);
143
144
  log(` npx organic-growth --force Install templates (overwrite existing)`);
145
+ log(` npx organic-growth --upgrade Update managed files, keep user customizations`);
144
146
  log(` npx organic-growth --migrate Migrate legacy docs/ state into .organic-growth/`);
145
147
  log(` npx organic-growth spec.md Install templates + copy DNA document`);
146
148
  log('');
@@ -160,126 +162,218 @@ async function install() {
160
162
  }
161
163
 
162
164
  const force = args.includes('--force') || args.includes('-f');
165
+ const upgrade = args.includes('--upgrade');
163
166
  const migrate = args.includes('--migrate');
164
167
  const isOpencode = args.includes('--opencode');
165
168
  const dna = args.find(a => !a.startsWith('-') && a.endsWith('.md'));
166
169
 
170
+ // --upgrade and --force are mutually exclusive
171
+ if (upgrade && force) {
172
+ console.error('Error: --upgrade and --force are mutually exclusive. Use --upgrade to update managed files while preserving user customizations, or --force to overwrite everything.');
173
+ process.exit(1);
174
+ }
175
+
176
+ // User-customized files that --upgrade should never overwrite or create
177
+ const USER_FILES = new Set(['CLAUDE.md', 'AGENTS.md', '.mcp.json', 'opencode.json']);
178
+
179
+ function isUserFile(filePath) {
180
+ // Check if the file's basename (top-level name) is in the user files set
181
+ return USER_FILES.has(filePath);
182
+ }
183
+
167
184
  log('');
168
- if (isOpencode) {
185
+ if (upgrade) {
186
+ log(`${GREEN}🌱 Organic Growth${RESET} — upgrading managed files`);
187
+ } else if (isOpencode) {
169
188
  log(`${GREEN}🌱 Organic Growth${RESET} — opencode setup for incremental development`);
170
189
  } else {
171
190
  log(`${GREEN}🌱 Organic Growth${RESET} — Claude Code setup for incremental development`);
172
191
  }
173
192
  log('');
174
193
 
175
- const templatesDir = isOpencode ? TEMPLATES_OPENCODE_DIR : TEMPLATES_DIR;
176
- const files = getAllFiles(templatesDir);
177
- const created = [];
178
- const skipped = [];
194
+ if (upgrade) {
195
+ // Read existing version for display
196
+ const versionFilePath = join(TARGET_DIR, '.organic-growth', '.version');
197
+ const fromVersion = existsSync(versionFilePath)
198
+ ? readFileSync(versionFilePath, 'utf8').trim()
199
+ : 'unknown';
200
+ const toVersion = readVersion();
179
201
 
180
- for (const file of files) {
181
- const src = join(templatesDir, file);
182
- const dest = join(TARGET_DIR, file);
183
- const destDir = dirname(dest);
202
+ const templatesDir = isOpencode ? TEMPLATES_OPENCODE_DIR : TEMPLATES_DIR;
203
+ const files = getAllFiles(templatesDir);
204
+ const updated = [];
205
+ const skippedUser = [];
184
206
 
185
- if (!existsSync(destDir)) {
186
- mkdirSync(destDir, { recursive: true });
187
- }
207
+ for (const file of files) {
208
+ const src = join(templatesDir, file);
209
+ const dest = join(TARGET_DIR, file);
210
+ const destDir = dirname(dest);
188
211
 
189
- if (existsSync(dest) && !force) {
190
- if (!process.stdin.isTTY) {
191
- skipped.push(file);
212
+ if (isUserFile(file)) {
213
+ // User-customized files: skip if they exist, don't create if missing
214
+ if (existsSync(dest)) {
215
+ skippedUser.push(file);
216
+ }
217
+ // If it doesn't exist, we intentionally do NOT create it (P8)
192
218
  continue;
193
219
  }
194
- const answer = await ask(`${YELLOW}!${RESET} ${file} already exists. Overwrite? [y/N] `);
195
- if (answer !== 'y' && answer !== 'yes') {
196
- skipped.push(file);
197
- continue;
220
+
221
+ // Managed file: overwrite (or create if new)
222
+ if (!existsSync(destDir)) {
223
+ mkdirSync(destDir, { recursive: true });
198
224
  }
225
+ copyFileSync(src, dest);
226
+ updated.push(file);
199
227
  }
200
228
 
201
- copyFileSync(src, dest);
202
- created.push(file);
203
- }
229
+ // Display upgrade results
230
+ info(`Upgrading from ${CYAN}${fromVersion}${RESET} to ${CYAN}${toVersion}${RESET}`);
231
+ log('');
204
232
 
205
- // Create .organic-growth/growth/ directory
206
- const growthDir = join(TARGET_DIR, '.organic-growth', 'growth');
207
- if (!existsSync(growthDir)) {
208
- mkdirSync(growthDir, { recursive: true });
209
- created.push('.organic-growth/growth/');
210
- }
233
+ if (updated.length > 0) {
234
+ log(`${GREEN}Updated:${RESET}`);
235
+ for (const f of updated) {
236
+ log(` ${DIM}${f}${RESET}`);
237
+ }
238
+ }
239
+ if (skippedUser.length > 0) {
240
+ log(`${YELLOW}Skipped (user customized):${RESET}`);
241
+ for (const f of skippedUser) {
242
+ log(` ${DIM}${f}${RESET}`);
243
+ }
244
+ }
211
245
 
212
- // Handle DNA document
213
- if (dna) {
214
- const dnaSource = resolve(TARGET_DIR, dna);
215
- if (existsSync(dnaSource)) {
216
- const dnaDest = join(TARGET_DIR, '.organic-growth', 'product-dna.md');
217
- mkdirSync(dirname(dnaDest), { recursive: true });
218
- copyFileSync(dnaSource, dnaDest);
219
- success(`Product DNA copied from ${dna}`);
220
- } else {
221
- warn(`DNA file not found: ${dna}`);
246
+ // Write version file after all managed files are updated
247
+ const ogDir = join(TARGET_DIR, '.organic-growth');
248
+ if (!existsSync(ogDir)) {
249
+ mkdirSync(ogDir, { recursive: true });
222
250
  }
223
- }
251
+ writeFileSync(join(ogDir, '.version'), toVersion);
224
252
 
225
- if (migrate) {
226
- const migrated = migrateLegacyState(TARGET_DIR);
227
- if (migrated.length > 0) {
228
- log('');
229
- log(`${GREEN}Migrated:${RESET}`);
230
- for (const step of migrated) {
231
- log(` ${DIM}${step}${RESET}`);
253
+ log('');
254
+ log(`${GREEN}Done!${RESET} ${updated.length} updated, ${skippedUser.length} skipped.`);
255
+ log('');
256
+ } else {
257
+ // Normal install flow
258
+ const templatesDir = isOpencode ? TEMPLATES_OPENCODE_DIR : TEMPLATES_DIR;
259
+ const files = getAllFiles(templatesDir);
260
+ const created = [];
261
+ const skipped = [];
262
+
263
+ for (const file of files) {
264
+ const src = join(templatesDir, file);
265
+ const dest = join(TARGET_DIR, file);
266
+ const destDir = dirname(dest);
267
+
268
+ if (!existsSync(destDir)) {
269
+ mkdirSync(destDir, { recursive: true });
232
270
  }
233
- } else {
234
- info('No legacy docs/ state found to migrate');
235
- }
236
- }
237
271
 
238
- log('');
239
- if (created.length > 0) {
240
- log(`${GREEN}Installed:${RESET}`);
241
- for (const f of created) {
242
- log(` ${DIM}${f}${RESET}`);
272
+ if (existsSync(dest) && !force) {
273
+ if (!process.stdin.isTTY) {
274
+ skipped.push(file);
275
+ continue;
276
+ }
277
+ const answer = await ask(`${YELLOW}!${RESET} ${file} already exists. Overwrite? [y/N] `);
278
+ if (answer !== 'y' && answer !== 'yes') {
279
+ skipped.push(file);
280
+ continue;
281
+ }
282
+ }
283
+
284
+ copyFileSync(src, dest);
285
+ created.push(file);
243
286
  }
244
- }
245
- if (skipped.length > 0) {
246
- log(`${YELLOW}Skipped (already exist):${RESET}`);
247
- for (const f of skipped) {
248
- log(` ${DIM}${f}${RESET}`);
287
+
288
+ // Create .organic-growth/growth/ directory
289
+ const growthDir = join(TARGET_DIR, '.organic-growth', 'growth');
290
+ if (!existsSync(growthDir)) {
291
+ mkdirSync(growthDir, { recursive: true });
292
+ created.push('.organic-growth/growth/');
249
293
  }
250
- }
251
294
 
252
- log('');
253
- log(`${GREEN}Done!${RESET} Next steps:`);
254
- log('');
255
- if (isOpencode) {
295
+ // Handle DNA document
256
296
  if (dna) {
257
- info(`Run ${CYAN}/seed .organic-growth/product-dna.md${RESET} to bootstrap from your DNA document`);
258
- } else {
259
- info(`Run ${CYAN}/seed${RESET} to bootstrap a new project (interview mode)`);
260
- info(`Or: ${CYAN}/seed path/to/product-doc.md${RESET} if you have a product document`);
297
+ const dnaSource = resolve(TARGET_DIR, dna);
298
+ if (existsSync(dnaSource)) {
299
+ const dnaDest = join(TARGET_DIR, '.organic-growth', 'product-dna.md');
300
+ mkdirSync(dirname(dnaDest), { recursive: true });
301
+ copyFileSync(dnaSource, dnaDest);
302
+ success(`Product DNA copied from ${dna}`);
303
+ } else {
304
+ warn(`DNA file not found: ${dna}`);
305
+ }
261
306
  }
262
- info(`Edit ${CYAN}AGENTS.md${RESET} to fill in your tech stack and quality tools`);
263
- } else {
264
- if (dna) {
265
- info(`Run ${CYAN}/seed .organic-growth/product-dna.md${RESET} to bootstrap from your DNA document`);
307
+
308
+ if (migrate) {
309
+ const migrated = migrateLegacyState(TARGET_DIR);
310
+ if (migrated.length > 0) {
311
+ log('');
312
+ log(`${GREEN}Migrated:${RESET}`);
313
+ for (const step of migrated) {
314
+ log(` ${DIM}${step}${RESET}`);
315
+ }
316
+ } else {
317
+ info('No legacy docs/ state found to migrate');
318
+ }
319
+ }
320
+
321
+ log('');
322
+ if (created.length > 0) {
323
+ log(`${GREEN}Installed:${RESET}`);
324
+ for (const f of created) {
325
+ log(` ${DIM}${f}${RESET}`);
326
+ }
327
+ }
328
+ if (skipped.length > 0) {
329
+ log(`${YELLOW}Skipped (already exist):${RESET}`);
330
+ for (const f of skipped) {
331
+ log(` ${DIM}${f}${RESET}`);
332
+ }
333
+ }
334
+
335
+ // Write version file after all templates and DNA are handled
336
+ const versionFilePath = join(TARGET_DIR, '.organic-growth', '.version');
337
+ const ogDir = dirname(versionFilePath);
338
+ if (!existsSync(ogDir)) {
339
+ mkdirSync(ogDir, { recursive: true });
340
+ }
341
+ writeFileSync(versionFilePath, readVersion());
342
+
343
+ log('');
344
+ log(`${GREEN}Done!${RESET} Next steps:`);
345
+ log('');
346
+ if (isOpencode) {
347
+ if (dna) {
348
+ info(`Run ${CYAN}/seed .organic-growth/product-dna.md${RESET} to bootstrap from your DNA document`);
349
+ } else {
350
+ info(`Run ${CYAN}/seed${RESET} to bootstrap a new project (interview mode)`);
351
+ info(`Or: ${CYAN}/seed path/to/product-doc.md${RESET} if you have a product document`);
352
+ }
353
+ info(`Edit ${CYAN}AGENTS.md${RESET} to fill in your tech stack and quality tools`);
266
354
  } else {
267
- info(`Run ${CYAN}/seed${RESET} to bootstrap a new project (interview mode)`);
268
- info(`Or: ${CYAN}/seed path/to/product-doc.md${RESET} if you have a product document`);
355
+ if (dna) {
356
+ info(`Run ${CYAN}/seed .organic-growth/product-dna.md${RESET} to bootstrap from your DNA document`);
357
+ } else {
358
+ info(`Run ${CYAN}/seed${RESET} to bootstrap a new project (interview mode)`);
359
+ info(`Or: ${CYAN}/seed path/to/product-doc.md${RESET} if you have a product document`);
360
+ }
361
+ info(`Edit ${CYAN}CLAUDE.md${RESET} to fill in your tech stack and quality tools`);
269
362
  }
270
- info(`Edit ${CYAN}CLAUDE.md${RESET} to fill in your tech stack and quality tools`);
271
- }
272
- log('');
273
- log(`${DIM}Commands available after setup:${RESET}`);
274
- log(` ${CYAN}/seed${RESET} bootstrap project (interview or DNA document)`);
275
- log(` ${CYAN}/grow${RESET} — plan and start a new feature`);
276
- log(` ${CYAN}/map${RESET} view or adjust the system growth map`);
277
- log(` ${CYAN}/next${RESET} implement the next growth stage`);
278
- log(` ${CYAN}/next-automatic${RESET} run multiple stages automatically`);
279
- log(` ${CYAN}/replan${RESET} — re-evaluate when things change`);
280
- log(` ${CYAN}/review${RESET} — deep quality review of recent stages`);
363
+ log('');
364
+ log(`${DIM}Commands available after setup:${RESET}`);
365
+ log(` ${CYAN}/seed${RESET} — bootstrap project (interview or DNA document)`);
366
+ log(` ${CYAN}/grow${RESET} — plan and start a new feature`);
367
+ log(` ${CYAN}/map${RESET} view or adjust the system growth map`);
368
+ log(` ${CYAN}/next${RESET} — implement the next growth stage`);
369
+ log(` ${CYAN}/next-automatic${RESET} run multiple stages automatically`);
370
+ log(` ${CYAN}/replan${RESET} re-evaluate when things change`);
371
+ log(` ${CYAN}/review${RESET} deep quality review of recent stages`);
281
372
 
282
- log('');
373
+ log('');
374
+ log(`${DIM}To upgrade later: npx organic-growth --upgrade${RESET}`);
375
+ log('');
376
+ }
283
377
  }
284
378
 
285
379
  install().catch(err => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "organic-growth",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Claude Code and opencode setup for incremental software development — grow features in natural stages",
5
5
  "bin": {
6
6
  "organic-growth": "bin/cli.mjs"