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.
- package/README.md +5 -2
- package/bin/cli.mjs +181 -87
- 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](
|
|
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](
|
|
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 (
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
const
|
|
202
|
+
const templatesDir = isOpencode ? TEMPLATES_OPENCODE_DIR : TEMPLATES_DIR;
|
|
203
|
+
const files = getAllFiles(templatesDir);
|
|
204
|
+
const updated = [];
|
|
205
|
+
const skippedUser = [];
|
|
184
206
|
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
229
|
+
// Display upgrade results
|
|
230
|
+
info(`Upgrading from ${CYAN}${fromVersion}${RESET} to ${CYAN}${toVersion}${RESET}`);
|
|
231
|
+
log('');
|
|
204
232
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
253
|
-
log(`${GREEN}Done!${RESET} Next steps:`);
|
|
254
|
-
log('');
|
|
255
|
-
if (isOpencode) {
|
|
295
|
+
// Handle DNA document
|
|
256
296
|
if (dna) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
268
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
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