@specmarket/cli 0.0.4 → 0.0.6

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 (39) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-MS2DYACY.js → chunk-OTXWWFAO.js} +42 -3
  3. package/dist/chunk-OTXWWFAO.js.map +1 -0
  4. package/dist/{config-R5KWZSJP.js → config-5JMI3YAR.js} +2 -2
  5. package/dist/index.js +1945 -252
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/commands/comment.test.ts +211 -0
  9. package/src/commands/comment.ts +176 -0
  10. package/src/commands/fork.test.ts +163 -0
  11. package/src/commands/info.test.ts +192 -0
  12. package/src/commands/info.ts +66 -2
  13. package/src/commands/init.test.ts +245 -0
  14. package/src/commands/init.ts +359 -25
  15. package/src/commands/issues.test.ts +382 -0
  16. package/src/commands/issues.ts +436 -0
  17. package/src/commands/login.test.ts +99 -0
  18. package/src/commands/login.ts +2 -6
  19. package/src/commands/logout.test.ts +54 -0
  20. package/src/commands/publish.test.ts +159 -0
  21. package/src/commands/publish.ts +1 -0
  22. package/src/commands/report.test.ts +181 -0
  23. package/src/commands/run.test.ts +419 -0
  24. package/src/commands/run.ts +71 -3
  25. package/src/commands/search.test.ts +147 -0
  26. package/src/commands/validate.test.ts +206 -2
  27. package/src/commands/validate.ts +315 -192
  28. package/src/commands/whoami.test.ts +106 -0
  29. package/src/index.ts +6 -0
  30. package/src/lib/convex-client.ts +6 -2
  31. package/src/lib/format-detection.test.ts +223 -0
  32. package/src/lib/format-detection.ts +172 -0
  33. package/src/lib/meta-instructions.test.ts +340 -0
  34. package/src/lib/meta-instructions.ts +562 -0
  35. package/src/lib/ralph-loop.test.ts +404 -0
  36. package/src/lib/ralph-loop.ts +501 -95
  37. package/src/lib/telemetry.ts +7 -1
  38. package/dist/chunk-MS2DYACY.js.map +0 -1
  39. /package/dist/{config-R5KWZSJP.js.map → config-5JMI3YAR.js.map} +0 -0
@@ -1,13 +1,54 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import ora from 'ora';
4
- import { mkdir, writeFile } from 'fs/promises';
5
- import { join, resolve } from 'path';
6
- import { EXIT_CODES } from '@specmarket/shared';
4
+ import { mkdir, writeFile, readdir } from 'fs/promises';
5
+ import { join, resolve, basename } from 'path';
6
+ import { EXIT_CODES, SIDECAR_FILENAME } from '@specmarket/shared';
7
7
  import createDebug from 'debug';
8
+ import { detectSpecFormat, fileExists, directoryExists } from '../lib/format-detection.js';
8
9
 
9
10
  const debug = createDebug('specmarket:cli');
10
11
 
12
+ type MetadataData = {
13
+ displayName: string;
14
+ description: string;
15
+ replacesSaas?: string;
16
+ outputType: string;
17
+ primaryStack: string;
18
+ };
19
+
20
+ /** Build specmarket.yaml for any spec format (single source of truth for validate/publish) */
21
+ function buildSpecmarketYaml(data: MetadataData & { specFormat: string }): string {
22
+ return `# SpecMarket metadata (required for validate/publish)
23
+ # Single source of truth for format and marketplace fields.
24
+ # Your existing spec files (Spec Kit, BMAD, Ralph, etc.) are not modified.
25
+
26
+ spec_format: ${data.specFormat}
27
+ display_name: "${data.displayName.replace(/"/g, '\\"')}"
28
+ description: "${data.description.replace(/"/g, '\\"')}"
29
+ output_type: ${data.outputType}
30
+ primary_stack: ${data.primaryStack}
31
+ ${data.replacesSaas ? `replaces_saas: "${data.replacesSaas.replace(/"/g, '\\"')}"` : '# replaces_saas: "ProductName"'}
32
+ # replaces_pricing: "$0-16/mo"
33
+ tags: []
34
+ estimated_tokens: 50000
35
+ estimated_cost_usd: 2.50
36
+ estimated_time_minutes: 30
37
+ `;
38
+ }
39
+
40
+ /** specmarket.yaml for new SpecMarket (classic) specs */
41
+ const SPECMARKET_YAML_TEMPLATE = (data: {
42
+ displayName: string;
43
+ replacesSaas?: string;
44
+ outputType: string;
45
+ primaryStack: string;
46
+ }) => buildSpecmarketYaml({
47
+ ...data,
48
+ specFormat: 'specmarket',
49
+ description: `A ${data.outputType} spec${data.replacesSaas ? ` that replaces ${data.replacesSaas}` : ''}.`,
50
+ });
51
+
11
52
  const SPEC_YAML_TEMPLATE = (data: {
12
53
  name: string;
13
54
  displayName: string;
@@ -26,7 +67,7 @@ ${data.replacesSaas ? `replaces_saas: "${data.replacesSaas}"` : '# replaces_saas
26
67
  output_type: ${data.outputType}
27
68
  primary_stack: ${data.primaryStack}
28
69
  version: "1.0.0"
29
- runner: claude-code
70
+ runner: claude
30
71
  min_model: "claude-opus-4-5"
31
72
 
32
73
  estimated_tokens: 50000
@@ -71,9 +112,9 @@ Read the requirements in SPEC.md and implement the application step by step.
71
112
  ## Process
72
113
 
73
114
  1. Read SPEC.md completely before writing any code
74
- 2. Check fix_plan.md for outstanding items
115
+ 2. Check TASKS.md for outstanding items
75
116
  3. Implement features, run tests, iterate
76
- 4. Update fix_plan.md as you complete items
117
+ 4. Update TASKS.md as you complete items
77
118
  5. Verify SUCCESS_CRITERIA.md criteria are met
78
119
 
79
120
  ## Rules
@@ -81,7 +122,7 @@ Read the requirements in SPEC.md and implement the application step by step.
81
122
  - Follow stdlib/STACK.md for technology choices
82
123
  - Write tests for all business logic
83
124
  - Do not skip steps or take shortcuts
84
- - Update fix_plan.md after each significant change
125
+ - Update TASKS.md after each significant change
85
126
  `;
86
127
 
87
128
  const SPEC_MD_TEMPLATE = (data: { displayName: string }) => `# ${data.displayName} — Specification
@@ -144,12 +185,12 @@ ${primaryStack}
144
185
  - Playwright for E2E (optional)
145
186
  `;
146
187
 
147
- const FIX_PLAN_TEMPLATE = (displayName: string) => `# Fix Plan
188
+ const TASKS_MD_TEMPLATE = (displayName: string) => `# Tasks
148
189
 
149
190
  > This file tracks outstanding work. Update it after each change.
150
- > Empty = implementation complete.
191
+ > All items checked = implementation complete.
151
192
 
152
- ## ${displayName} — Initial Implementation
193
+ ## Phase 1: ${displayName} — Initial Implementation
153
194
 
154
195
  - [ ] Set up project structure and dependencies
155
196
  - [ ] Implement core data model
@@ -158,14 +199,278 @@ const FIX_PLAN_TEMPLATE = (displayName: string) => `# Fix Plan
158
199
  - [ ] Implement UI/interface
159
200
  - [ ] Write integration tests
160
201
  - [ ] Update README.md
202
+
203
+ ## Discovered Issues
161
204
  `;
162
205
 
206
+ /** Prompt for marketplace metadata only (used when adding sidecar to existing spec) */
207
+ async function promptMetadataOnly(defaultDisplayName?: string): Promise<MetadataData> {
208
+ const { default: inquirer } = await import('inquirer');
209
+ const answers = await inquirer.prompt<{
210
+ displayName: string;
211
+ description: string;
212
+ replacesSaas: string;
213
+ outputType: string;
214
+ primaryStack: string;
215
+ }>([
216
+ {
217
+ type: 'input',
218
+ name: 'displayName',
219
+ message: 'Display name for the marketplace:',
220
+ default: defaultDisplayName ?? 'My Spec',
221
+ },
222
+ {
223
+ type: 'input',
224
+ name: 'description',
225
+ message: 'Short description (min 10 characters):',
226
+ default: 'A spec ready to validate and publish on SpecMarket.',
227
+ validate: (v: string) => (v.length >= 10 ? true : 'Description must be at least 10 characters'),
228
+ },
229
+ {
230
+ type: 'input',
231
+ name: 'replacesSaas',
232
+ message: 'What SaaS product does this replace? (optional, Enter to skip):',
233
+ default: '',
234
+ },
235
+ {
236
+ type: 'list',
237
+ name: 'outputType',
238
+ message: 'Output type:',
239
+ choices: [
240
+ { name: 'Web Application', value: 'web-app' },
241
+ { name: 'CLI Tool', value: 'cli-tool' },
242
+ { name: 'API Service', value: 'api-service' },
243
+ { name: 'Library/Package', value: 'library' },
244
+ { name: 'Mobile App', value: 'mobile-app' },
245
+ ],
246
+ },
247
+ {
248
+ type: 'list',
249
+ name: 'primaryStack',
250
+ message: 'Primary stack:',
251
+ choices: [
252
+ { name: 'Next.js + TypeScript', value: 'nextjs-typescript' },
253
+ { name: 'Astro + TypeScript', value: 'astro-typescript' },
254
+ { name: 'Python + FastAPI', value: 'python-fastapi' },
255
+ { name: 'Go', value: 'go' },
256
+ { name: 'Rust', value: 'rust' },
257
+ { name: 'Other', value: 'other' },
258
+ ],
259
+ },
260
+ ]);
261
+ return {
262
+ displayName: answers.displayName,
263
+ description: answers.description,
264
+ replacesSaas: answers.replacesSaas || undefined,
265
+ outputType: answers.outputType,
266
+ primaryStack: answers.primaryStack,
267
+ };
268
+ }
269
+
270
+ /** Returns true if directory exists and has at least one file (not just subdirs). */
271
+ async function dirHasFiles(dir: string): Promise<boolean> {
272
+ try {
273
+ const entries = await readdir(dir, { withFileTypes: true });
274
+ return entries.some((e) => e.isFile());
275
+ } catch {
276
+ return false;
277
+ }
278
+ }
279
+
163
280
  export async function handleInit(opts: {
164
281
  name?: string;
165
282
  path?: string;
283
+ from?: string;
166
284
  }): Promise<void> {
167
285
  const { default: inquirer } = await import('inquirer');
168
286
 
287
+ // --from: strict import-only mode. Errors if directory doesn't exist or has no spec files.
288
+ if (opts.from !== undefined && opts.from !== '') {
289
+ const targetDir = resolve(opts.from);
290
+
291
+ const dirExists = await directoryExists(targetDir);
292
+ if (!dirExists) {
293
+ console.error(chalk.red(`Directory not found: ${targetDir}`));
294
+ console.error(chalk.gray('--from requires an existing spec directory. Run specmarket init (no flags) to create a new spec from scratch.'));
295
+ process.exit(EXIT_CODES.INVALID_SPEC);
296
+ }
297
+
298
+ const hasAnyFiles = await dirHasFiles(targetDir);
299
+ if (!hasAnyFiles) {
300
+ console.error(chalk.red(`Directory is empty: ${targetDir}`));
301
+ console.error(chalk.gray('--from requires a directory with spec files (Spec Kit, BMAD, Ralph, or custom markdown). Run specmarket init to create a new spec.'));
302
+ process.exit(EXIT_CODES.INVALID_SPEC);
303
+ }
304
+
305
+ const sidecarPath = join(targetDir, SIDECAR_FILENAME);
306
+ if (await fileExists(sidecarPath)) {
307
+ console.log(chalk.yellow(`${SIDECAR_FILENAME} already exists in this directory.`));
308
+ console.log(chalk.gray('Run specmarket validate to check your spec, then specmarket publish to publish.'));
309
+ return;
310
+ }
311
+
312
+ const detection = await detectSpecFormat(targetDir);
313
+ const formatLabel =
314
+ detection.format === 'specmarket'
315
+ ? 'SpecMarket (spec.yaml + PROMPT.md + …)'
316
+ : detection.format === 'speckit'
317
+ ? 'Spec Kit'
318
+ : detection.format === 'bmad'
319
+ ? 'BMAD'
320
+ : detection.format === 'ralph'
321
+ ? 'Ralph'
322
+ : 'custom markdown';
323
+ console.log(chalk.gray(`Detected ${formatLabel} spec. Adding SpecMarket metadata only; your files will not be modified.`));
324
+ console.log('');
325
+
326
+ const metadata = await promptMetadataOnly(basename(targetDir));
327
+ const yaml = buildSpecmarketYaml({ ...metadata, specFormat: detection.format });
328
+ await writeFile(sidecarPath, yaml);
329
+
330
+ console.log('');
331
+ console.log(chalk.green(`Added ${SIDECAR_FILENAME} to ${targetDir}`));
332
+ console.log('');
333
+ console.log(chalk.bold('Next steps:'));
334
+ console.log(` 1. Run ${chalk.cyan('specmarket validate')} to check your spec`);
335
+ console.log(` 2. Run ${chalk.cyan('specmarket publish')} to publish to the marketplace`);
336
+ console.log(` 3. Run ${chalk.cyan('specmarket run')} to execute the spec locally`);
337
+ return;
338
+ }
339
+
340
+ // When -p/--path is set, we're initializing in an existing directory (safe add-sidecar flow)
341
+ if (opts.path !== undefined && opts.path !== '') {
342
+ const targetDir = resolve(opts.path);
343
+ await mkdir(targetDir, { recursive: true });
344
+
345
+ const sidecarPath = join(targetDir, SIDECAR_FILENAME);
346
+ if (await fileExists(sidecarPath)) {
347
+ console.log(chalk.yellow(`${SIDECAR_FILENAME} already exists in this directory.`));
348
+ console.log(chalk.gray('Run specmarket validate to check your spec, then specmarket publish to publish.'));
349
+ return;
350
+ }
351
+
352
+ const detection = await detectSpecFormat(targetDir);
353
+ const hasAnyFiles = await dirHasFiles(targetDir);
354
+
355
+ if (hasAnyFiles && detection.format !== 'custom') {
356
+ // Recognized format (specmarket, speckit, bmad, ralph): add sidecar only, no overwrites
357
+ const formatLabel =
358
+ detection.format === 'specmarket'
359
+ ? 'SpecMarket (spec.yaml + PROMPT.md + …)'
360
+ : detection.format === 'speckit'
361
+ ? 'Spec Kit'
362
+ : detection.format === 'bmad'
363
+ ? 'BMAD'
364
+ : detection.format === 'ralph'
365
+ ? 'Ralph'
366
+ : detection.format;
367
+ console.log(chalk.gray(`Detected ${formatLabel} spec. Adding SpecMarket metadata only; your files will not be modified.`));
368
+ console.log('');
369
+
370
+ const metadata = await promptMetadataOnly(basename(targetDir));
371
+ const yaml = buildSpecmarketYaml({
372
+ ...metadata,
373
+ specFormat: detection.format,
374
+ });
375
+ await writeFile(sidecarPath, yaml);
376
+
377
+ console.log('');
378
+ console.log(chalk.green(`Added ${SIDECAR_FILENAME} to ${targetDir}`));
379
+ console.log('');
380
+ console.log(chalk.bold('Next steps:'));
381
+ console.log(` 1. Run ${chalk.cyan('specmarket validate')} to check your spec`);
382
+ console.log(` 2. Run ${chalk.cyan('specmarket publish')} to publish to the marketplace`);
383
+ console.log(` 3. Run ${chalk.cyan('specmarket run')} to execute the spec locally`);
384
+ return;
385
+ }
386
+
387
+ if (hasAnyFiles && detection.format === 'custom') {
388
+ // Custom (e.g. random .md): still add sidecar so they can validate/publish
389
+ console.log(chalk.gray('Detected markdown spec. Adding SpecMarket metadata only; your files will not be modified.'));
390
+ console.log('');
391
+ const metadata = await promptMetadataOnly(basename(targetDir));
392
+ const yaml = buildSpecmarketYaml({
393
+ ...metadata,
394
+ specFormat: 'custom',
395
+ });
396
+ await writeFile(sidecarPath, yaml);
397
+ console.log('');
398
+ console.log(chalk.green(`Added ${SIDECAR_FILENAME} to ${targetDir}`));
399
+ console.log('');
400
+ console.log(chalk.bold('Next steps:'));
401
+ console.log(` 1. Run ${chalk.cyan('specmarket validate')} to check your spec`);
402
+ console.log(` 2. Run ${chalk.cyan('specmarket publish')} to publish to the marketplace`);
403
+ return;
404
+ }
405
+
406
+ // Empty directory: offer to create from scratch
407
+ console.log(chalk.gray('Directory is empty or has no recognized spec format.'));
408
+ const { createNew } = await inquirer.prompt<{ createNew: boolean }>([
409
+ {
410
+ type: 'confirm',
411
+ name: 'createNew',
412
+ message: 'Create a new SpecMarket spec from scratch here?',
413
+ default: true,
414
+ },
415
+ ]);
416
+ if (!createNew) {
417
+ console.log(chalk.gray('Exiting. Add spec files (e.g. Spec Kit, BMAD) and run specmarket init -p . again.'));
418
+ return;
419
+ }
420
+ const spinner = ora(`Creating spec at ${targetDir}...`).start();
421
+ const fullAnswers = await inquirer.prompt<{
422
+ name: string;
423
+ displayName: string;
424
+ replacesSaas: string;
425
+ outputType: string;
426
+ primaryStack: string;
427
+ }>([
428
+ { type: 'input', name: 'name', message: 'Spec name (lowercase, hyphens only):', default: opts.name ?? (basename(targetDir) || 'my-spec'), validate: (v: string) => /^[a-z0-9-]+$/.test(v) || 'Must be lowercase alphanumeric with hyphens' },
429
+ { type: 'input', name: 'displayName', message: 'Display name:', default: (a: { name: string }) => a.name.split('-').map((w) => w[0]?.toUpperCase() + w.slice(1)).join(' ') },
430
+ { type: 'input', name: 'replacesSaas', message: 'What SaaS product does this replace? (optional):', default: '' },
431
+ { type: 'list', name: 'outputType', message: 'Output type:', choices: [
432
+ { name: 'Web Application', value: 'web-app' },
433
+ { name: 'CLI Tool', value: 'cli-tool' },
434
+ { name: 'API Service', value: 'api-service' },
435
+ { name: 'Library/Package', value: 'library' },
436
+ { name: 'Mobile App', value: 'mobile-app' },
437
+ ]},
438
+ { type: 'list', name: 'primaryStack', message: 'Primary stack:', choices: [
439
+ { name: 'Next.js + TypeScript', value: 'nextjs-typescript' },
440
+ { name: 'Astro + TypeScript', value: 'astro-typescript' },
441
+ { name: 'Python + FastAPI', value: 'python-fastapi' },
442
+ { name: 'Go', value: 'go' },
443
+ { name: 'Rust', value: 'rust' },
444
+ { name: 'Other', value: 'other' },
445
+ ]},
446
+ ]);
447
+ const data = {
448
+ name: fullAnswers.name,
449
+ displayName: fullAnswers.displayName,
450
+ replacesSaas: fullAnswers.replacesSaas || undefined,
451
+ outputType: fullAnswers.outputType,
452
+ primaryStack: fullAnswers.primaryStack,
453
+ };
454
+ await mkdir(join(targetDir, 'stdlib'), { recursive: true });
455
+ await Promise.all([
456
+ writeFile(sidecarPath, SPECMARKET_YAML_TEMPLATE(data)),
457
+ writeFile(join(targetDir, 'spec.yaml'), SPEC_YAML_TEMPLATE(data)),
458
+ writeFile(join(targetDir, 'PROMPT.md'), PROMPT_MD_TEMPLATE(data)),
459
+ writeFile(join(targetDir, 'SPEC.md'), SPEC_MD_TEMPLATE(data)),
460
+ writeFile(join(targetDir, 'SUCCESS_CRITERIA.md'), SUCCESS_CRITERIA_TEMPLATE),
461
+ writeFile(join(targetDir, 'stdlib', 'STACK.md'), STACK_MD_TEMPLATE(fullAnswers.primaryStack)),
462
+ writeFile(join(targetDir, 'TASKS.md'), TASKS_MD_TEMPLATE(fullAnswers.displayName)),
463
+ ]);
464
+ spinner.succeed(chalk.green(`Spec created at ${targetDir}`));
465
+ console.log('');
466
+ console.log(chalk.bold('Next steps:'));
467
+ console.log(` 1. Edit ${chalk.cyan('SPEC.md')} with your application requirements`);
468
+ console.log(` 2. Edit ${chalk.cyan('SUCCESS_CRITERIA.md')} with pass/fail criteria`);
469
+ console.log(` 3. Run ${chalk.cyan('specmarket validate')} then ${chalk.cyan('specmarket run')}`);
470
+ return;
471
+ }
472
+
473
+ // No path: create new directory (or add sidecar if that directory already exists with content)
169
474
  const answers = await inquirer.prompt<{
170
475
  name: string;
171
476
  displayName: string;
@@ -185,11 +490,8 @@ export async function handleInit(opts: {
185
490
  type: 'input',
186
491
  name: 'displayName',
187
492
  message: 'Display name:',
188
- default: (answers: { name: string }) =>
189
- answers.name
190
- .split('-')
191
- .map((w) => w[0]?.toUpperCase() + w.slice(1))
192
- .join(' '),
493
+ default: (ans: { name: string }) =>
494
+ ans.name.split('-').map((w) => w[0]?.toUpperCase() + w.slice(1)).join(' '),
193
495
  },
194
496
  {
195
497
  type: 'input',
@@ -224,14 +526,44 @@ export async function handleInit(opts: {
224
526
  },
225
527
  ]);
226
528
 
227
- const targetDir = resolve(opts.path ?? answers.name);
529
+ const targetDir = resolve(answers.name);
228
530
  const spinner = ora(`Creating spec directory at ${targetDir}...`).start();
229
531
 
230
532
  try {
231
- // Create directory structure
232
533
  await mkdir(targetDir, { recursive: true });
233
- await mkdir(join(targetDir, 'stdlib'), { recursive: true });
234
534
 
535
+ const sidecarPath = join(targetDir, SIDECAR_FILENAME);
536
+ if (await fileExists(sidecarPath)) {
537
+ spinner.stop();
538
+ console.log(chalk.yellow(`${SIDECAR_FILENAME} already exists in ${targetDir}.`));
539
+ console.log(chalk.gray('Run specmarket validate to check your spec, then specmarket publish to publish.'));
540
+ return;
541
+ }
542
+
543
+ const hasFiles = await dirHasFiles(targetDir);
544
+ if (hasFiles) {
545
+ const detection = await detectSpecFormat(targetDir);
546
+ spinner.stop();
547
+ console.log(chalk.gray(`Directory already has files (detected: ${detection.format}). Adding ${SIDECAR_FILENAME} only; no files overwritten.`));
548
+ const metadata: MetadataData = {
549
+ displayName: answers.displayName,
550
+ description: `A ${answers.outputType} spec${answers.replacesSaas ? ` that replaces ${answers.replacesSaas}` : ''}.`,
551
+ replacesSaas: answers.replacesSaas || undefined,
552
+ outputType: answers.outputType,
553
+ primaryStack: answers.primaryStack,
554
+ };
555
+ const yaml = buildSpecmarketYaml({ ...metadata, specFormat: detection.format });
556
+ await writeFile(sidecarPath, yaml);
557
+ console.log('');
558
+ console.log(chalk.green(`Added ${SIDECAR_FILENAME} to ${targetDir}`));
559
+ console.log('');
560
+ console.log(chalk.bold('Next steps:'));
561
+ console.log(` 1. Run ${chalk.cyan('specmarket validate')} to check your spec`);
562
+ console.log(` 2. Run ${chalk.cyan('specmarket publish')} to publish to the marketplace`);
563
+ return;
564
+ }
565
+
566
+ await mkdir(join(targetDir, 'stdlib'), { recursive: true });
235
567
  const data = {
236
568
  name: answers.name,
237
569
  displayName: answers.displayName,
@@ -240,25 +572,24 @@ export async function handleInit(opts: {
240
572
  primaryStack: answers.primaryStack,
241
573
  };
242
574
 
243
- // Write all template files
244
575
  await Promise.all([
576
+ writeFile(sidecarPath, SPECMARKET_YAML_TEMPLATE(data)),
245
577
  writeFile(join(targetDir, 'spec.yaml'), SPEC_YAML_TEMPLATE(data)),
246
578
  writeFile(join(targetDir, 'PROMPT.md'), PROMPT_MD_TEMPLATE(data)),
247
579
  writeFile(join(targetDir, 'SPEC.md'), SPEC_MD_TEMPLATE(data)),
248
580
  writeFile(join(targetDir, 'SUCCESS_CRITERIA.md'), SUCCESS_CRITERIA_TEMPLATE),
249
581
  writeFile(join(targetDir, 'stdlib', 'STACK.md'), STACK_MD_TEMPLATE(answers.primaryStack)),
250
- writeFile(join(targetDir, 'fix_plan.md'), FIX_PLAN_TEMPLATE(answers.displayName)),
582
+ writeFile(join(targetDir, 'TASKS.md'), TASKS_MD_TEMPLATE(answers.displayName)),
251
583
  ]);
252
584
 
253
585
  spinner.succeed(chalk.green(`Spec created at ${targetDir}`));
254
-
255
586
  console.log('');
256
587
  console.log(chalk.bold('Next steps:'));
257
588
  console.log(` 1. ${chalk.cyan(`cd ${answers.name}`)}`);
258
589
  console.log(` 2. Edit ${chalk.cyan('SPEC.md')} with your application requirements`);
259
590
  console.log(` 3. Edit ${chalk.cyan('SUCCESS_CRITERIA.md')} with specific pass/fail criteria`);
260
- console.log(` 4. Run ${chalk.cyan(`specmarket validate ${answers.name}`)} to check your spec`);
261
- console.log(` 5. Run ${chalk.cyan(`specmarket run ${answers.name}`)} to execute the spec`);
591
+ console.log(` 4. Run ${chalk.cyan('specmarket validate')} to check your spec`);
592
+ console.log(` 5. Run ${chalk.cyan('specmarket run')} to execute the spec`);
262
593
  } catch (err) {
263
594
  spinner.fail(chalk.red(`Failed to create spec: ${(err as Error).message}`));
264
595
  throw err;
@@ -267,9 +598,12 @@ export async function handleInit(opts: {
267
598
 
268
599
  export function createInitCommand(): Command {
269
600
  return new Command('init')
270
- .description('Create a new spec directory with template files')
601
+ .description(
602
+ 'Create a new SpecMarket spec or add specmarket.yaml to an existing spec (Spec Kit, BMAD, Ralph). Use -p . to init in current directory without overwriting existing files.'
603
+ )
271
604
  .option('-n, --name <name>', 'Spec name (skip prompt)')
272
- .option('-p, --path <path>', 'Target directory path (defaults to spec name)')
605
+ .option('-p, --path <path>', 'Target directory (e.g. . or ./my-spec). When set, detects existing format and adds only specmarket.yaml if present; no files overwritten.')
606
+ .option('--from <path>', 'Import an existing spec directory (Spec Kit, BMAD, Ralph, or custom markdown). Detects format and adds specmarket.yaml metadata sidecar without modifying original files. Errors if the directory is missing or empty.')
273
607
  .action(async (opts) => {
274
608
  try {
275
609
  await handleInit(opts);