wogiflow 1.0.11 → 1.0.13
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/.workflow/specs/architecture.md.template +24 -0
- package/.workflow/specs/stack.md.template +33 -0
- package/.workflow/specs/testing.md.template +36 -0
- package/README.md +90 -1
- package/lib/unified-wizard.js +569 -30
- package/package.json +1 -1
- package/scripts/MEMORY-ARCHITECTURE.md +150 -0
- package/scripts/flow +20 -19
- package/scripts/flow-auto-context.js +97 -3
- package/scripts/flow-conflict-resolver.js +735 -0
- package/scripts/flow-context-gatherer.js +520 -0
- package/scripts/flow-context-monitor.js +148 -19
- package/scripts/flow-damage-control.js +5 -1
- package/scripts/flow-export-profile +168 -1
- package/scripts/flow-import-profile +257 -6
- package/scripts/flow-instruction-richness.js +182 -18
- package/scripts/flow-knowledge-router.js +2 -0
- package/scripts/flow-knowledge-sync.js +2 -0
- package/scripts/{flow-transcript-chunking.js → flow-long-input-chunking.js} +4 -2
- package/scripts/{flow-transcript-parsing.js → flow-long-input-parsing.js} +35 -0
- package/scripts/{flow-transcript-stories.js → flow-long-input-stories.js} +86 -38
- package/scripts/{flow-transcript-digest.js → flow-long-input.js} +231 -15
- package/scripts/flow-memory-db.js +386 -1
- package/scripts/flow-memory-sync.js +2 -0
- package/scripts/flow-model-adapter.js +53 -29
- package/scripts/flow-model-router.js +246 -1
- package/scripts/flow-morning.js +94 -0
- package/scripts/flow-onboard +223 -10
- package/scripts/flow-orchestrate-validation.js +539 -0
- package/scripts/flow-orchestrate.js +16 -507
- package/scripts/flow-pattern-extractor.js +1265 -0
- package/scripts/flow-prompt-composer.js +222 -2
- package/scripts/flow-quality-guard.js +594 -0
- package/scripts/flow-section-index.js +713 -0
- package/scripts/flow-section-resolver.js +484 -0
- package/scripts/flow-session-end.js +188 -2
- package/scripts/flow-skill-create.js +19 -3
- package/scripts/flow-skill-matcher.js +122 -7
- package/scripts/flow-statusline-setup.js +218 -0
- package/scripts/flow-step-review.js +19 -0
- package/scripts/flow-tech-debt.js +734 -0
- package/scripts/flow-utils.js +2 -0
- package/scripts/hooks/core/long-input-gate.js +293 -0
- package/scripts/flow-parallel-detector.js +0 -399
- package/scripts/flow-parallel-dispatch.js +0 -987
- /package/scripts/{flow-transcript-language.js → flow-long-input-language.js} +0 -0
package/lib/unified-wizard.js
CHANGED
|
@@ -91,10 +91,15 @@ class UnifiedWizard {
|
|
|
91
91
|
this.rl = null;
|
|
92
92
|
this.config = {
|
|
93
93
|
projectName: '',
|
|
94
|
-
|
|
94
|
+
description: '',
|
|
95
95
|
cli: 'claude',
|
|
96
|
+
projectState: '',
|
|
97
|
+
goals: [],
|
|
98
|
+
documentation: [], // Array of {type, inputType, content, summary}
|
|
99
|
+
planningDocs: [], // Roadmap, issue tracker, etc.
|
|
100
|
+
// Legacy fields for backward compatibility
|
|
101
|
+
projectType: '',
|
|
96
102
|
strictMode: true,
|
|
97
|
-
importPath: null,
|
|
98
103
|
stackSelections: null,
|
|
99
104
|
scanFindings: null
|
|
100
105
|
};
|
|
@@ -103,6 +108,7 @@ class UnifiedWizard {
|
|
|
103
108
|
|
|
104
109
|
/**
|
|
105
110
|
* Run the unified wizard
|
|
111
|
+
* New flow: CLI first → basics → docs → state → goals → planning → summary → AI handoff
|
|
106
112
|
*/
|
|
107
113
|
async run() {
|
|
108
114
|
this.rl = readline.createInterface({
|
|
@@ -114,44 +120,33 @@ class UnifiedWizard {
|
|
|
114
120
|
// Step 1: Welcome
|
|
115
121
|
this.printWelcome();
|
|
116
122
|
|
|
117
|
-
// Step 2:
|
|
118
|
-
await this.
|
|
123
|
+
// Step 2: CLI selection (FIRST)
|
|
124
|
+
await this.askCLI();
|
|
119
125
|
|
|
120
|
-
// Step 3:
|
|
121
|
-
await this.
|
|
126
|
+
// Step 3: Project name
|
|
127
|
+
await this.askProjectName();
|
|
122
128
|
|
|
123
|
-
// Step 4:
|
|
124
|
-
|
|
125
|
-
if (imported) {
|
|
126
|
-
await this.finalizeSetup();
|
|
127
|
-
return this.config;
|
|
128
|
-
}
|
|
129
|
+
// Step 4: Description (short or documentation)
|
|
130
|
+
await this.askDescription();
|
|
129
131
|
|
|
130
|
-
// Step 5:
|
|
131
|
-
|
|
132
|
-
await this.runStackWizard();
|
|
133
|
-
} else {
|
|
134
|
-
await this.runProjectScanner();
|
|
135
|
-
}
|
|
132
|
+
// Step 5: Project state
|
|
133
|
+
await this.askProjectState();
|
|
136
134
|
|
|
137
|
-
// Step 6:
|
|
138
|
-
await this.
|
|
135
|
+
// Step 6: Goals (multi-select)
|
|
136
|
+
await this.askGoals();
|
|
139
137
|
|
|
140
|
-
// Step 7:
|
|
141
|
-
await this.
|
|
138
|
+
// Step 7: Planning documents
|
|
139
|
+
await this.askPlanningDocs();
|
|
142
140
|
|
|
143
141
|
// Step 8: Show summary and confirm
|
|
144
|
-
const confirmed = await this.
|
|
142
|
+
const confirmed = await this.showSummary();
|
|
145
143
|
if (!confirmed) {
|
|
146
|
-
console.log(c('yellow', '\nSetup cancelled. Run `flow
|
|
144
|
+
console.log(c('yellow', '\nSetup cancelled. Run `npx flow onboard` to try again.\n'));
|
|
147
145
|
return null;
|
|
148
146
|
}
|
|
149
147
|
|
|
150
|
-
// Step 9: Create
|
|
151
|
-
await this.
|
|
152
|
-
|
|
153
|
-
// Step 10: Success
|
|
154
|
-
this.printSuccess();
|
|
148
|
+
// Step 9: Create structure and handoff to AI
|
|
149
|
+
await this.finalizeAndHandoff();
|
|
155
150
|
|
|
156
151
|
return this.config;
|
|
157
152
|
|
|
@@ -315,10 +310,301 @@ class UnifiedWizard {
|
|
|
315
310
|
});
|
|
316
311
|
}
|
|
317
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Single-select with arrow key navigation
|
|
315
|
+
* @param {string} question - The question to ask
|
|
316
|
+
* @param {Array} options - Array of {key, label, description?} objects
|
|
317
|
+
* @param {string} defaultKey - Default selected key
|
|
318
|
+
* @returns {Promise<string>} Selected key
|
|
319
|
+
*/
|
|
320
|
+
askSingleSelect(question, options, defaultKey = null) {
|
|
321
|
+
return new Promise((resolve) => {
|
|
322
|
+
// Fallback if no TTY
|
|
323
|
+
if (!process.stdin.isTTY || !process.stdin.setRawMode) {
|
|
324
|
+
console.log(`\n${question}\n`);
|
|
325
|
+
options.forEach((opt, i) => {
|
|
326
|
+
const marker = opt.key === defaultKey ? '>' : ' ';
|
|
327
|
+
console.log(` ${marker} (${i + 1}) ${opt.label}${opt.description ? ` - ${opt.description}` : ''}`);
|
|
328
|
+
});
|
|
329
|
+
return this.ask('Your choice (number)', '1').then(answer => {
|
|
330
|
+
const num = parseInt(answer, 10);
|
|
331
|
+
if (num >= 1 && num <= options.length) {
|
|
332
|
+
resolve(options[num - 1].key);
|
|
333
|
+
} else {
|
|
334
|
+
resolve(defaultKey || options[0].key);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let selectedIndex = defaultKey ? options.findIndex(o => o.key === defaultKey) : 0;
|
|
340
|
+
if (selectedIndex < 0) selectedIndex = 0;
|
|
341
|
+
|
|
342
|
+
const render = () => {
|
|
343
|
+
// Move cursor up to redraw (except first render)
|
|
344
|
+
process.stdout.write(`\x1b[${options.length}A\x1b[J`);
|
|
345
|
+
options.forEach((opt, i) => {
|
|
346
|
+
const selected = i === selectedIndex;
|
|
347
|
+
const marker = selected ? c('cyan', '❯') : ' ';
|
|
348
|
+
const radio = selected ? c('green', '●') : '○';
|
|
349
|
+
const label = selected ? c('bold', opt.label) : opt.label;
|
|
350
|
+
const desc = opt.description ? c('dim', ` - ${opt.description}`) : '';
|
|
351
|
+
console.log(` ${marker} ${radio} ${label}${desc}`);
|
|
352
|
+
});
|
|
353
|
+
process.stdout.write(c('dim', '\n [↑↓ to move, Enter to select]'));
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
console.log(`\n${question}\n`);
|
|
357
|
+
// Initial render (print blank lines first)
|
|
358
|
+
options.forEach(() => console.log(''));
|
|
359
|
+
console.log('');
|
|
360
|
+
render();
|
|
361
|
+
|
|
362
|
+
this.rl.pause();
|
|
363
|
+
process.stdin.setRawMode(true);
|
|
364
|
+
process.stdin.resume();
|
|
365
|
+
|
|
366
|
+
const cleanup = () => {
|
|
367
|
+
process.stdin.setRawMode(false);
|
|
368
|
+
process.stdin.removeListener('data', onKey);
|
|
369
|
+
this.rl.resume();
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const onKey = (key) => {
|
|
373
|
+
const seq = key.toString();
|
|
374
|
+
|
|
375
|
+
// Arrow up
|
|
376
|
+
if (seq === '\x1b[A' || seq === 'k') {
|
|
377
|
+
selectedIndex = (selectedIndex - 1 + options.length) % options.length;
|
|
378
|
+
render();
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Arrow down
|
|
383
|
+
if (seq === '\x1b[B' || seq === 'j') {
|
|
384
|
+
selectedIndex = (selectedIndex + 1) % options.length;
|
|
385
|
+
render();
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Enter
|
|
390
|
+
if (seq === '\r' || seq === '\n') {
|
|
391
|
+
cleanup();
|
|
392
|
+
process.stdout.write('\x1b[K\n'); // Clear hint line
|
|
393
|
+
resolve(options[selectedIndex].key);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Ctrl+C
|
|
398
|
+
if (seq === '\x03') {
|
|
399
|
+
cleanup();
|
|
400
|
+
process.stdout.write('\n');
|
|
401
|
+
process.exit();
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
process.stdin.on('data', onKey);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Multi-select with checkbox toggle (space to toggle, enter to confirm)
|
|
411
|
+
* @param {string} question - The question to ask
|
|
412
|
+
* @param {Array} options - Array of {key, label, description?, default?} objects
|
|
413
|
+
* @returns {Promise<string[]>} Array of selected keys
|
|
414
|
+
*/
|
|
415
|
+
askMultiSelect(question, options) {
|
|
416
|
+
return new Promise((resolve) => {
|
|
417
|
+
// Fallback if no TTY
|
|
418
|
+
if (!process.stdin.isTTY || !process.stdin.setRawMode) {
|
|
419
|
+
console.log(`\n${question}\n`);
|
|
420
|
+
options.forEach((opt, i) => {
|
|
421
|
+
const checked = opt.default ? '[x]' : '[ ]';
|
|
422
|
+
console.log(` ${checked} (${i + 1}) ${opt.label}${opt.description ? ` - ${opt.description}` : ''}`);
|
|
423
|
+
});
|
|
424
|
+
return this.ask('Select (comma-separated numbers)', '').then(answer => {
|
|
425
|
+
if (!answer) {
|
|
426
|
+
resolve(options.filter(o => o.default).map(o => o.key));
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const nums = answer.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
|
|
430
|
+
resolve(nums.filter(n => n >= 1 && n <= options.length).map(n => options[n - 1].key));
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
let cursorIndex = 0;
|
|
435
|
+
const selected = new Set(options.filter(o => o.default).map(o => o.key));
|
|
436
|
+
|
|
437
|
+
const render = () => {
|
|
438
|
+
// Move cursor up to redraw
|
|
439
|
+
process.stdout.write(`\x1b[${options.length}A\x1b[J`);
|
|
440
|
+
options.forEach((opt, i) => {
|
|
441
|
+
const isCursor = i === cursorIndex;
|
|
442
|
+
const isSelected = selected.has(opt.key);
|
|
443
|
+
const cursor = isCursor ? c('cyan', '❯') : ' ';
|
|
444
|
+
const checkbox = isSelected ? c('green', '☑') : '☐';
|
|
445
|
+
const label = isCursor ? c('bold', opt.label) : opt.label;
|
|
446
|
+
const desc = opt.description ? c('dim', ` - ${opt.description}`) : '';
|
|
447
|
+
console.log(` ${cursor} ${checkbox} ${label}${desc}`);
|
|
448
|
+
});
|
|
449
|
+
process.stdout.write(c('dim', '\n [↑↓ move, Space toggle, Enter done]'));
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
console.log(`\n${question}\n`);
|
|
453
|
+
// Initial render
|
|
454
|
+
options.forEach(() => console.log(''));
|
|
455
|
+
console.log('');
|
|
456
|
+
render();
|
|
457
|
+
|
|
458
|
+
this.rl.pause();
|
|
459
|
+
process.stdin.setRawMode(true);
|
|
460
|
+
process.stdin.resume();
|
|
461
|
+
|
|
462
|
+
const cleanup = () => {
|
|
463
|
+
process.stdin.setRawMode(false);
|
|
464
|
+
process.stdin.removeListener('data', onKey);
|
|
465
|
+
this.rl.resume();
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const onKey = (key) => {
|
|
469
|
+
const seq = key.toString();
|
|
470
|
+
|
|
471
|
+
// Arrow up
|
|
472
|
+
if (seq === '\x1b[A' || seq === 'k') {
|
|
473
|
+
cursorIndex = (cursorIndex - 1 + options.length) % options.length;
|
|
474
|
+
render();
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Arrow down
|
|
479
|
+
if (seq === '\x1b[B' || seq === 'j') {
|
|
480
|
+
cursorIndex = (cursorIndex + 1) % options.length;
|
|
481
|
+
render();
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Space - toggle
|
|
486
|
+
if (seq === ' ') {
|
|
487
|
+
const key = options[cursorIndex].key;
|
|
488
|
+
if (selected.has(key)) {
|
|
489
|
+
selected.delete(key);
|
|
490
|
+
} else {
|
|
491
|
+
selected.add(key);
|
|
492
|
+
}
|
|
493
|
+
render();
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Enter - confirm
|
|
498
|
+
if (seq === '\r' || seq === '\n') {
|
|
499
|
+
cleanup();
|
|
500
|
+
process.stdout.write('\x1b[K\n');
|
|
501
|
+
resolve(Array.from(selected));
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Ctrl+C
|
|
506
|
+
if (seq === '\x03') {
|
|
507
|
+
cleanup();
|
|
508
|
+
process.stdout.write('\n');
|
|
509
|
+
process.exit();
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
process.stdin.on('data', onKey);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Ask for document input - either paste content or link to file
|
|
519
|
+
* @param {string} docType - Name of the document type (e.g., "PRD", "README")
|
|
520
|
+
* @returns {Promise<{type: 'paste'|'link', content: string, summary: string}>}
|
|
521
|
+
*/
|
|
522
|
+
async askDocumentInput(docType) {
|
|
523
|
+
// First ask: paste or link?
|
|
524
|
+
const inputType = await this.askSingleSelect(
|
|
525
|
+
`How do you want to provide the ${docType}?`,
|
|
526
|
+
[
|
|
527
|
+
{ key: 'paste', label: 'Paste content', description: 'Paste the document content directly' },
|
|
528
|
+
{ key: 'link', label: 'Link to file', description: 'Provide a file path' }
|
|
529
|
+
],
|
|
530
|
+
'paste'
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
if (inputType === 'link') {
|
|
534
|
+
const filePath = await this.ask(`File path for ${docType}`);
|
|
535
|
+
if (!filePath) {
|
|
536
|
+
return { type: 'link', content: '', summary: '[no file provided]' };
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Validate file exists
|
|
540
|
+
const resolvedPath = path.resolve(this.projectRoot, filePath);
|
|
541
|
+
if (!isPathWithinProject(resolvedPath, this.projectRoot)) {
|
|
542
|
+
console.log(c('yellow', ' Path must be within project directory'));
|
|
543
|
+
return { type: 'link', content: '', summary: '[invalid path]' };
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
try {
|
|
547
|
+
fs.accessSync(resolvedPath, fs.constants.R_OK);
|
|
548
|
+
return { type: 'link', content: resolvedPath, summary: `[linked - ${filePath}]` };
|
|
549
|
+
} catch {
|
|
550
|
+
console.log(c('yellow', ' File not found or not readable'));
|
|
551
|
+
return { type: 'link', content: '', summary: '[file not found]' };
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Paste mode - multi-line input
|
|
556
|
+
console.log(c('dim', `\nPaste ${docType} content (press Enter twice on empty line when done):\n`));
|
|
557
|
+
|
|
558
|
+
return new Promise((resolve) => {
|
|
559
|
+
const lines = [];
|
|
560
|
+
let emptyLineCount = 0;
|
|
561
|
+
|
|
562
|
+
const lineHandler = (line) => {
|
|
563
|
+
if (line === '') {
|
|
564
|
+
emptyLineCount++;
|
|
565
|
+
if (emptyLineCount >= 2) {
|
|
566
|
+
this.rl.removeListener('line', lineHandler);
|
|
567
|
+
const content = lines.join('\n');
|
|
568
|
+
const lineCount = lines.length;
|
|
569
|
+
const summary = `[pasted - ${lineCount} line${lineCount !== 1 ? 's' : ''}]`;
|
|
570
|
+
console.log(c('green', ` ${summary}\n`));
|
|
571
|
+
resolve({ type: 'paste', content, summary });
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
} else {
|
|
575
|
+
emptyLineCount = 0;
|
|
576
|
+
}
|
|
577
|
+
lines.push(line);
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
this.rl.on('line', lineHandler);
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
318
584
|
// ============================================
|
|
319
|
-
// STEP IMPLEMENTATIONS
|
|
585
|
+
// STEP IMPLEMENTATIONS (New Flow)
|
|
320
586
|
// ============================================
|
|
321
587
|
|
|
588
|
+
/**
|
|
589
|
+
* Step 2: Ask CLI selection (FIRST question)
|
|
590
|
+
*/
|
|
591
|
+
async askCLI() {
|
|
592
|
+
const cliOptions = Object.entries(SUPPORTED_CLIS).map(([key, value]) => ({
|
|
593
|
+
key,
|
|
594
|
+
label: value.name,
|
|
595
|
+
description: value.description
|
|
596
|
+
}));
|
|
597
|
+
|
|
598
|
+
this.config.cli = await this.askSingleSelect(
|
|
599
|
+
'Which AI CLI are you using?',
|
|
600
|
+
cliOptions,
|
|
601
|
+
'claude'
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Step 3: Ask project name
|
|
607
|
+
*/
|
|
322
608
|
async askProjectName() {
|
|
323
609
|
// Try to detect from package.json
|
|
324
610
|
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
@@ -341,6 +627,259 @@ class UnifiedWizard {
|
|
|
341
627
|
this.config.projectName = await this.askWithPlaceholder('Project name', detectedName);
|
|
342
628
|
}
|
|
343
629
|
|
|
630
|
+
/**
|
|
631
|
+
* Step 4: Ask for description (short or documentation)
|
|
632
|
+
*/
|
|
633
|
+
async askDescription() {
|
|
634
|
+
const hasDocsChoice = await this.askSingleSelect(
|
|
635
|
+
'How would you like to describe your project?',
|
|
636
|
+
[
|
|
637
|
+
{ key: 'short', label: 'Short description', description: '1-2 sentences' },
|
|
638
|
+
{ key: 'docs', label: 'I have documentation', description: 'PRD, README, specs, etc.' }
|
|
639
|
+
],
|
|
640
|
+
'short'
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
if (hasDocsChoice === 'short') {
|
|
644
|
+
this.config.description = await this.ask('What does this project do? (1-2 sentences)');
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Documentation flow
|
|
649
|
+
const docTypes = await this.askMultiSelect(
|
|
650
|
+
'What documentation do you have?',
|
|
651
|
+
[
|
|
652
|
+
{ key: 'prd', label: 'PRD / Product Spec' },
|
|
653
|
+
{ key: 'readme', label: 'README' },
|
|
654
|
+
{ key: 'architecture', label: 'Architecture docs' },
|
|
655
|
+
{ key: 'api', label: 'API documentation' },
|
|
656
|
+
{ key: 'other', label: 'Other' }
|
|
657
|
+
]
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
if (docTypes.length === 0) {
|
|
661
|
+
// Fallback to short description
|
|
662
|
+
this.config.description = await this.ask('What does this project do? (1-2 sentences)');
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// For each selected doc type, get input
|
|
667
|
+
const docTypeNames = {
|
|
668
|
+
prd: 'PRD / Product Spec',
|
|
669
|
+
readme: 'README',
|
|
670
|
+
architecture: 'Architecture docs',
|
|
671
|
+
api: 'API documentation',
|
|
672
|
+
other: 'Other documentation'
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
for (const docType of docTypes) {
|
|
676
|
+
const docName = docTypeNames[docType] || docType;
|
|
677
|
+
const input = await this.askDocumentInput(docName);
|
|
678
|
+
this.config.documentation.push({
|
|
679
|
+
type: docType,
|
|
680
|
+
name: docName,
|
|
681
|
+
inputType: input.type,
|
|
682
|
+
content: input.content,
|
|
683
|
+
summary: input.summary
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Step 5: Ask project state
|
|
690
|
+
*/
|
|
691
|
+
async askProjectState() {
|
|
692
|
+
this.config.projectState = await this.askSingleSelect(
|
|
693
|
+
"What's your project's current state?",
|
|
694
|
+
[
|
|
695
|
+
{ key: 'new', label: 'New / early development', description: 'Just starting out' },
|
|
696
|
+
{ key: 'mvp', label: 'MVP / working prototype', description: 'Core features working' },
|
|
697
|
+
{ key: 'production', label: 'Production with users', description: 'Live and serving users' },
|
|
698
|
+
{ key: 'maintenance', label: 'Maintenance mode', description: 'Stable, minimal changes' }
|
|
699
|
+
],
|
|
700
|
+
'mvp'
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Step 6: Ask goals (multi-select)
|
|
706
|
+
*/
|
|
707
|
+
async askGoals() {
|
|
708
|
+
this.config.goals = await this.askMultiSelect(
|
|
709
|
+
'What are you trying to accomplish with AI assistance?',
|
|
710
|
+
[
|
|
711
|
+
{ key: 'features', label: 'Add new features', default: true },
|
|
712
|
+
{ key: 'bugs', label: 'Fix bugs', default: true },
|
|
713
|
+
{ key: 'refactor', label: 'Refactor / improve code quality' },
|
|
714
|
+
{ key: 'tests', label: 'Add tests' },
|
|
715
|
+
{ key: 'docs', label: 'Documentation' },
|
|
716
|
+
{ key: 'performance', label: 'Performance optimization' },
|
|
717
|
+
{ key: 'security', label: 'Security improvements' }
|
|
718
|
+
]
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Step 7: Ask about planning documents
|
|
724
|
+
*/
|
|
725
|
+
async askPlanningDocs() {
|
|
726
|
+
const planningTypes = await this.askMultiSelect(
|
|
727
|
+
'Do you have any existing planning documents the AI should analyze?',
|
|
728
|
+
[
|
|
729
|
+
{ key: 'roadmap', label: 'Roadmap / backlog' },
|
|
730
|
+
{ key: 'issues', label: 'Issue tracker export' },
|
|
731
|
+
{ key: 'techdebt', label: 'Technical debt notes' },
|
|
732
|
+
{ key: 'none', label: 'None of these', default: true }
|
|
733
|
+
]
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
// Filter out 'none' and store
|
|
737
|
+
this.config.planningDocs = planningTypes.filter(t => t !== 'none');
|
|
738
|
+
|
|
739
|
+
if (this.config.planningDocs.length > 0) {
|
|
740
|
+
console.log(c('dim', '\n These will be analyzed when the AI scans your project.\n'));
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Step 8: Show summary
|
|
746
|
+
*/
|
|
747
|
+
async showSummary() {
|
|
748
|
+
const cli = SUPPORTED_CLIS[this.config.cli];
|
|
749
|
+
|
|
750
|
+
console.log('\n' + c('cyan', '═'.repeat(60)));
|
|
751
|
+
console.log(c('cyan', ' Setup Summary'));
|
|
752
|
+
console.log(c('cyan', '═'.repeat(60)) + '\n');
|
|
753
|
+
|
|
754
|
+
console.log(` Project: ${c('bold', this.config.projectName)}`);
|
|
755
|
+
console.log(` CLI: ${c('bold', cli.name)}`);
|
|
756
|
+
console.log(` State: ${c('bold', this.config.projectState || 'Not specified')}`);
|
|
757
|
+
|
|
758
|
+
if (this.config.goals.length > 0) {
|
|
759
|
+
console.log(` Goals: ${c('bold', this.config.goals.join(', '))}`);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (this.config.documentation.length > 0) {
|
|
763
|
+
console.log(` Documentation: ${c('bold', this.config.documentation.length + ' file(s)')}`);
|
|
764
|
+
this.config.documentation.forEach(doc => {
|
|
765
|
+
console.log(` - ${doc.name}: ${c('dim', doc.summary)}`);
|
|
766
|
+
});
|
|
767
|
+
} else if (this.config.description) {
|
|
768
|
+
console.log(` Description: ${c('dim', this.config.description.substring(0, 50) + (this.config.description.length > 50 ? '...' : ''))}`);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
console.log('\n' + c('bold', 'Ready to let the AI analyze your project?'));
|
|
772
|
+
console.log(c('dim', 'This will create the workflow structure and prepare for AI analysis.\n'));
|
|
773
|
+
|
|
774
|
+
return await this.askYesNo('Proceed?', true);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Step 9: Finalize and handoff to AI
|
|
779
|
+
*/
|
|
780
|
+
async finalizeAndHandoff() {
|
|
781
|
+
console.log(c('dim', '\nCreating project structure...\n'));
|
|
782
|
+
|
|
783
|
+
// Create minimal structure
|
|
784
|
+
await this.createWorkflowStructure();
|
|
785
|
+
await this.createCLIConfig();
|
|
786
|
+
|
|
787
|
+
// Save onboarding data for AI to read
|
|
788
|
+
await this.saveOnboardingData();
|
|
789
|
+
|
|
790
|
+
// Determine if running inside AI session
|
|
791
|
+
const inSession = this.detectAISession();
|
|
792
|
+
|
|
793
|
+
if (inSession) {
|
|
794
|
+
// Already in AI session - output trigger for AI to continue
|
|
795
|
+
this.printAITrigger();
|
|
796
|
+
} else {
|
|
797
|
+
// Not in session - show instructions
|
|
798
|
+
this.printHandoffInstructions();
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Detect if running inside an AI CLI session
|
|
804
|
+
*/
|
|
805
|
+
detectAISession() {
|
|
806
|
+
// Check for common environment indicators
|
|
807
|
+
return !!(
|
|
808
|
+
process.env.CLAUDE_CODE ||
|
|
809
|
+
process.env.ANTHROPIC_API_KEY ||
|
|
810
|
+
process.env.GEMINI_CLI ||
|
|
811
|
+
process.env.OPENCODE_SESSION ||
|
|
812
|
+
// Check if parent process looks like an AI CLI
|
|
813
|
+
(process.env._ && (
|
|
814
|
+
process.env._.includes('claude') ||
|
|
815
|
+
process.env._.includes('gemini') ||
|
|
816
|
+
process.env._.includes('opencode')
|
|
817
|
+
))
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Print trigger for AI to continue (when in session)
|
|
823
|
+
*/
|
|
824
|
+
printAITrigger() {
|
|
825
|
+
console.log('\n' + c('green', '═'.repeat(60)));
|
|
826
|
+
console.log(c('green', ' ✅ Structure created!'));
|
|
827
|
+
console.log(c('green', '═'.repeat(60)) + '\n');
|
|
828
|
+
|
|
829
|
+
console.log('The AI can now analyze your project.\n');
|
|
830
|
+
console.log(c('yellow', 'AI: Please run /wogi-onboard to complete the setup by:'));
|
|
831
|
+
console.log(' • Scanning the codebase for patterns and structure');
|
|
832
|
+
console.log(' • Detecting frameworks, testing, and coding styles');
|
|
833
|
+
console.log(' • Generating component map and architecture docs');
|
|
834
|
+
console.log(' • Creating coding rules based on detected patterns\n');
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Print handoff instructions (when not in session)
|
|
839
|
+
*/
|
|
840
|
+
printHandoffInstructions() {
|
|
841
|
+
const cli = SUPPORTED_CLIS[this.config.cli];
|
|
842
|
+
const cliCommand = this.config.cli; // 'claude', 'gemini', 'opencode'
|
|
843
|
+
|
|
844
|
+
console.log('\n' + c('cyan', '╔════════════════════════════════════════════════════════════╗'));
|
|
845
|
+
console.log(c('cyan', '║') + ' ' + c('bold', 'Almost done! Start your AI CLI to complete setup.') + ' ' + c('cyan', '║'));
|
|
846
|
+
console.log(c('cyan', '╚════════════════════════════════════════════════════════════╝') + '\n');
|
|
847
|
+
|
|
848
|
+
console.log(` Run: ${c('yellow', cliCommand)}\n`);
|
|
849
|
+
|
|
850
|
+
console.log(' The AI will:');
|
|
851
|
+
console.log(' • Scan your codebase for patterns');
|
|
852
|
+
console.log(' • Detect frameworks and coding styles');
|
|
853
|
+
console.log(' • Generate component map and architecture docs');
|
|
854
|
+
console.log(' • Ask clarifying questions if needed\n');
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Save onboarding data for AI to read
|
|
859
|
+
*/
|
|
860
|
+
async saveOnboardingData() {
|
|
861
|
+
const onboardingData = {
|
|
862
|
+
version: PACKAGE_VERSION,
|
|
863
|
+
timestamp: new Date().toISOString(),
|
|
864
|
+
projectName: this.config.projectName,
|
|
865
|
+
description: this.config.description,
|
|
866
|
+
cli: this.config.cli,
|
|
867
|
+
projectState: this.config.projectState,
|
|
868
|
+
goals: this.config.goals,
|
|
869
|
+
documentation: this.config.documentation,
|
|
870
|
+
planningDocs: this.config.planningDocs,
|
|
871
|
+
status: 'pending_ai_analysis'
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
const onboardingPath = path.join(this.projectRoot, '.workflow', 'state', 'onboarding.json');
|
|
875
|
+
fs.writeFileSync(onboardingPath, JSON.stringify(onboardingData, null, 2));
|
|
876
|
+
console.log(' ' + c('green', '✓') + ' Saved onboarding data for AI analysis');
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// ============================================
|
|
880
|
+
// LEGACY METHODS (kept for backward compatibility)
|
|
881
|
+
// ============================================
|
|
882
|
+
|
|
344
883
|
async askProjectType() {
|
|
345
884
|
const choice = await this.askChoice(
|
|
346
885
|
'Is this a new project or an existing codebase?',
|