@prodara/compiler 0.1.0 → 0.1.2

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/dist/cli/main.js CHANGED
@@ -6,6 +6,7 @@ import { Command } from 'commander';
6
6
  import { readFileSync, writeFileSync, mkdirSync, existsSync, watch } from 'node:fs';
7
7
  import { resolve, dirname, join, relative } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
+ import { execSync } from 'node:child_process';
9
10
  import { compile } from './compile.js';
10
11
  import { runPipeline } from '../orchestrator/pipeline.js';
11
12
  import { formatDiagnosticsJson, formatDiagnosticsHuman } from '../diagnostics/reporter.js';
@@ -19,6 +20,7 @@ import { getStarterTemplate, listStarterTemplates } from './starters.js';
19
20
  import { explainNode, collectAllNodeIds, formatExplanation, getDiagnosticInfo } from './explain.js';
20
21
  import { registerShutdownHandlers, setActiveRoot, clearActiveRoot } from './lifecycle.js';
21
22
  import { generateSlashCommands, writeSlashCommands, isValidAgentId, listSupportedAgents, getAgentConfig } from './agent-setup.js';
23
+ import { banner, success, error as uiError, warn as uiWarn, info as uiInfo, phaseIcon, box, table, dim, bold, isInteractive, createSpinner } from './ui.js';
22
24
  import { createProposal, listProposals, applyProposal, archiveProposal } from '../proposal/proposal.js';
23
25
  import { generateChecklist, formatChecklistHuman } from './checklist.js';
24
26
  import { analyzeGraph, formatAnalysisHuman } from './analyze.js';
@@ -53,7 +55,8 @@ export function createProgram() {
53
55
  .option('--auto-commit', 'Auto-commit changes after successful build')
54
56
  .option('--notify', 'Send desktop notification on build completion')
55
57
  .option('--party', 'Run multi-agent party mode review')
56
- .action((path, opts) => {
58
+ .option('--dry-run', 'Show implementation tasks without executing')
59
+ .action(async (path, opts) => {
57
60
  const root = resolve(path);
58
61
  registerShutdownHandlers();
59
62
  setActiveRoot(root);
@@ -64,26 +67,38 @@ export function createProgram() {
64
67
  headless: opts.headless ?? false,
65
68
  noImplement: opts.implement === false,
66
69
  noReview: opts.review === false,
70
+ dryRun: opts.dryRun ?? false,
67
71
  onProgress: (phase, index, total) => {
68
72
  if (opts.format !== 'json') {
69
- process.stderr.write(`[${index + 1}/${total}] ${phase}...\n`);
73
+ process.stderr.write(`${uiInfo(`[${index + 1}/${total}] ${phase}...`)}\n`);
70
74
  }
71
75
  },
72
76
  };
73
- const result = runPipeline(root, config, pipelineOpts);
77
+ if (opts.format !== 'json') {
78
+ process.stdout.write(banner('Prodara Build') + '\n\n');
79
+ }
80
+ const result = await runPipeline(root, config, pipelineOpts);
74
81
  if (opts.format === 'json') {
75
82
  process.stdout.write(JSON.stringify({
76
83
  status: result.status,
77
84
  phases: result.phases.map((p) => ({ phase: p.phase, status: p.status, detail: p.detail, duration_ms: p.duration_ms })),
78
85
  duration_ms: result.duration_ms,
86
+ implementResult: result.implementResult ?? undefined,
79
87
  }, null, 2) + '\n');
80
88
  }
81
89
  else {
82
- for (const p of result.phases) {
83
- const icon = p.status === 'ok' ? '✓' : p.status === 'warn' ? '⚠' : p.status === 'error' ? '✗' : '○';
84
- process.stdout.write(` ${icon} ${p.phase}: ${p.detail}\n`);
90
+ const phaseRows = result.phases.map(p => [
91
+ `${phaseIcon(p.status)} ${p.phase}`,
92
+ p.detail,
93
+ dim(`${p.duration_ms}ms`),
94
+ ]);
95
+ process.stdout.write(table(['Phase', 'Detail', 'Time'], phaseRows) + '\n\n');
96
+ if (result.status === 'success') {
97
+ process.stdout.write(box('Build Result', [success(`Build completed in ${result.duration_ms}ms`)]) + '\n');
98
+ }
99
+ else {
100
+ process.stdout.write(box('Build Result', [uiError(`Build failed (${result.duration_ms}ms)`)]) + '\n');
85
101
  }
86
- process.stdout.write(`\nBuild ${result.status} (${result.duration_ms}ms)\n`);
87
102
  }
88
103
  clearActiveRoot();
89
104
  // Auto-commit after successful build
@@ -91,7 +106,7 @@ export function createProgram() {
91
106
  /* v8 ignore next -- graph always exists when status is success */
92
107
  const hash = autoCommit(root, result.graph?.product.name ?? 'unknown', result.phases.length);
93
108
  if (hash && opts.format !== 'json') {
94
- process.stdout.write(`Auto-committed: ${hash}\n`);
109
+ process.stdout.write(success(`Auto-committed: ${hash}`) + '\n');
95
110
  }
96
111
  }
97
112
  // Desktop notification
@@ -114,17 +129,45 @@ export function createProgram() {
114
129
  .option('--template <template>', 'Starter template: minimal | saas | marketplace | internal-tool | api')
115
130
  .option('--ai <agent>', 'Configure AI agent slash commands (e.g. copilot, claude, cursor)')
116
131
  .option('--ai-commands-dir <dir>', 'Custom directory for slash commands (use with --ai generic)')
117
- .action((path, opts) => {
132
+ .option('--skip-install', 'Skip automatic npm init and compiler installation')
133
+ .action(async (path, opts) => {
118
134
  const root = resolve(path);
119
135
  const appPrd = join(root, 'app.prd');
120
136
  const configFile = join(root, CONFIG_FILENAME);
121
137
  const prodaraDir = join(root, '.prodara');
122
138
  if (existsSync(appPrd)) {
123
- process.stderr.write(`Already initialized: ${appPrd} exists\n`);
139
+ process.stderr.write(uiError(`Already initialized: ${appPrd} exists`) + '\n');
124
140
  process.exitCode = 1;
125
141
  return;
126
142
  }
143
+ process.stdout.write(banner('Prodara Init') + '\n\n');
127
144
  mkdirSync(root, { recursive: true });
145
+ // Ensure npm is initialized and compiler is installed
146
+ if (!opts.skipInstall) {
147
+ const pkgJsonPath = join(root, 'package.json');
148
+ if (!existsSync(pkgJsonPath)) {
149
+ const spinner = createSpinner('Initializing npm project...').start();
150
+ try {
151
+ execSync('npm init -y', { cwd: root, stdio: 'pipe' });
152
+ spinner.succeed('Created package.json');
153
+ }
154
+ catch {
155
+ spinner.fail('Failed to initialize npm project');
156
+ process.stderr.write(uiError('Run `npm init` manually.') + '\n');
157
+ process.exitCode = 1;
158
+ return;
159
+ }
160
+ }
161
+ const installSpinner = createSpinner('Installing @prodara/compiler...').start();
162
+ try {
163
+ execSync('npm install --save-dev @prodara/compiler', { cwd: root, stdio: 'pipe' });
164
+ installSpinner.succeed('Installed @prodara/compiler');
165
+ }
166
+ catch {
167
+ installSpinner.fail('Failed to install @prodara/compiler');
168
+ process.stderr.write(uiWarn('Install it manually: npm install --save-dev @prodara/compiler') + '\n');
169
+ }
170
+ }
128
171
  mkdirSync(prodaraDir, { recursive: true });
129
172
  mkdirSync(join(prodaraDir, 'runs'), { recursive: true });
130
173
  mkdirSync(join(prodaraDir, 'reviewers'), { recursive: true });
@@ -132,8 +175,8 @@ export function createProgram() {
132
175
  const tmpl = getStarterTemplate(opts.template, opts.name);
133
176
  if (!tmpl) {
134
177
  const available = listStarterTemplates().map(t => t.name).join(', ');
135
- process.stderr.write(`Unknown template: ${opts.template}\n`);
136
- process.stderr.write(`Available templates: ${available}\n`);
178
+ process.stderr.write(uiError(`Unknown template: ${opts.template}`) + '\n');
179
+ process.stderr.write(uiInfo(`Available templates: ${available}`) + '\n');
137
180
  process.exitCode = 1;
138
181
  return;
139
182
  }
@@ -142,9 +185,9 @@ export function createProgram() {
142
185
  mkdirSync(dirname(filePath), { recursive: true });
143
186
  writeFileSync(filePath, f.content, 'utf-8');
144
187
  }
145
- process.stdout.write(`Initialized Prodara project in ${root} (template: ${tmpl.name})\n`);
188
+ process.stdout.write(success(`Initialized Prodara project (template: ${tmpl.name})`) + '\n');
146
189
  for (const f of tmpl.files) {
147
- process.stdout.write(` Created ${f.path}\n`);
190
+ process.stdout.write(` ${success(f.path)}\n`);
148
191
  }
149
192
  }
150
193
  else {
@@ -164,8 +207,7 @@ module core {
164
207
 
165
208
  }
166
209
  `, 'utf-8');
167
- process.stdout.write(`Initialized Prodara project in ${root}\n`);
168
- process.stdout.write(` Created app.prd\n`);
210
+ process.stdout.write(success('Created app.prd') + '\n');
169
211
  }
170
212
  writeFileSync(configFile, JSON.stringify({
171
213
  phases: {},
@@ -174,20 +216,39 @@ module core {
174
216
  validation: { lint: null, typecheck: null, test: null, build: null },
175
217
  audit: { enabled: true, path: '.prodara/runs/' },
176
218
  }, null, 2) + '\n', 'utf-8');
177
- process.stdout.write(` Created ${CONFIG_FILENAME}\n`);
178
- process.stdout.write(` Created .prodara/\n`);
179
- // Slash command generation
219
+ process.stdout.write(success(`Created ${CONFIG_FILENAME}`) + '\n');
220
+ process.stdout.write(success('Created .prodara/') + '\n');
221
+ // Slash command generation — interactive prompt if --ai not provided
222
+ let agentId = null;
180
223
  if (opts.ai) {
181
224
  if (!isValidAgentId(opts.ai)) {
182
225
  const supported = listSupportedAgents().join(', ');
183
- process.stderr.write(`Unknown AI agent: ${opts.ai}\n`);
184
- process.stderr.write(`Supported agents: ${supported}\n`);
226
+ process.stderr.write(uiError(`Unknown AI agent: ${opts.ai}`) + '\n');
227
+ process.stderr.write(uiInfo(`Supported agents: ${supported}`) + '\n');
185
228
  process.exitCode = 1;
186
229
  return;
187
230
  }
188
- const agentId = opts.ai;
231
+ agentId = opts.ai;
232
+ }
233
+ else if (isInteractive()) {
234
+ const { select } = await import('@inquirer/prompts');
235
+ const agents = listSupportedAgents();
236
+ const popular = ['copilot', 'claude', 'cursor', 'gemini'];
237
+ const others = agents.filter(a => !popular.includes(a) && a !== 'generic');
238
+ const choices = [
239
+ ...popular.map(a => ({ name: a.charAt(0).toUpperCase() + a.slice(1), value: a })),
240
+ ...others.map(a => ({ name: a.charAt(0).toUpperCase() + a.slice(1), value: a })),
241
+ { name: 'Generic', value: 'generic' },
242
+ { name: 'Skip (no agent setup)', value: 'skip' },
243
+ ];
244
+ const selected = await select({ message: 'Which AI agent do you use?', choices });
245
+ if (selected !== 'skip') {
246
+ agentId = selected;
247
+ }
248
+ }
249
+ if (agentId) {
189
250
  if (agentId === 'generic' && !opts.aiCommandsDir) {
190
- process.stderr.write(`--ai generic requires --ai-commands-dir <dir>\n`);
251
+ process.stderr.write(uiError('--ai generic requires --ai-commands-dir <dir>') + '\n');
191
252
  process.exitCode = 1;
192
253
  return;
193
254
  }
@@ -196,13 +257,17 @@ module core {
196
257
  const agentConfig = getAgentConfig(agentId);
197
258
  /* v8 ignore next -- triple fallback for command dir */
198
259
  const commandsDir = opts.aiCommandsDir ?? agentConfig?.commandsDir ?? '.ai/commands';
199
- process.stdout.write(` Created slash commands in ${commandsDir}/\n`);
260
+ process.stdout.write(success(`Created slash commands in ${commandsDir}/`) + '\n');
200
261
  for (const cmd of commands) {
201
262
  const rel = relative(root, cmd.path);
202
- process.stdout.write(` ${rel}\n`);
263
+ process.stdout.write(` ${dim(rel)}\n`);
203
264
  }
204
265
  }
205
- process.stdout.write(`\nRun \`prodara build\` to compile.\n`);
266
+ process.stdout.write('\n' + box('Next Steps', [
267
+ `${bold('cd')} ${root === resolve('.') ? '.' : root}`,
268
+ `${bold('prodara build')} — compile and validate your spec`,
269
+ `${bold('prodara doctor')} — check your installation`,
270
+ ]) + '\n');
206
271
  process.exitCode = 0;
207
272
  });
208
273
  // -----------------------------------------------------------------------
@@ -217,6 +282,9 @@ module core {
217
282
  const root = resolve(path);
218
283
  const result = compile(root, { stopAfter: 'validate' });
219
284
  outputDiagnostics(result.diagnostics, opts.format);
285
+ if (!result.diagnostics.hasErrors && opts.format !== 'json') {
286
+ process.stdout.write(success('No errors found') + '\n');
287
+ }
220
288
  process.exitCode = result.diagnostics.hasErrors ? 1 : 0;
221
289
  });
222
290
  // -----------------------------------------------------------------------
@@ -240,7 +308,7 @@ module core {
240
308
  if (opts.output) {
241
309
  writeFileSync(resolve(opts.output), result.graphJson, 'utf-8');
242
310
  if (opts.format !== 'json') {
243
- process.stderr.write(`Graph written to ${opts.output}\n`);
311
+ process.stderr.write(success(`Graph written to ${opts.output}`) + '\n');
244
312
  }
245
313
  }
246
314
  else {
@@ -292,7 +360,7 @@ module core {
292
360
  const r = result.testResults;
293
361
  process.stdout.write(`Tests: ${r.totalPassed} passed, ${r.totalFailed} failed\n`);
294
362
  for (const t of r.results) {
295
- const icon = t.passed ? '' : '';
363
+ const icon = t.passed ? phaseIcon('ok') : phaseIcon('error');
296
364
  process.stdout.write(` ${icon} ${t.name}\n`);
297
365
  for (const f of t.failures) {
298
366
  process.stdout.write(` ${f}\n`);
@@ -328,7 +396,7 @@ module core {
328
396
  if (opts.semantic) {
329
397
  const prevGraph = readPreviousGraph(root);
330
398
  if (!prevGraph) {
331
- process.stdout.write('No previous graph found — nothing to diff.\n');
399
+ process.stdout.write(uiInfo('No previous graph found — nothing to diff.') + '\n');
332
400
  process.exitCode = 0;
333
401
  return;
334
402
  }
@@ -370,12 +438,14 @@ module core {
370
438
  .action((path) => {
371
439
  const root = resolve(path);
372
440
  const files = discoverFiles(root);
373
- process.stdout.write(`Prodara Compiler v${pkg.version}\n`);
374
- process.stdout.write(`Node.js ${process.version}\n`);
375
- process.stdout.write(`Workspace: ${root}\n`);
376
- process.stdout.write(`Found ${files.length} .prd file(s)\n`);
441
+ process.stdout.write(box('Prodara Doctor', [
442
+ `Compiler ${bold(`v${pkg.version}`)}`,
443
+ `Node.js ${bold(process.version)}`,
444
+ `Workspace ${root}`,
445
+ `Found ${bold(String(files.length))} .prd file(s)`,
446
+ ]) + '\n');
377
447
  for (const f of files) {
378
- process.stdout.write(` ${f}\n`);
448
+ process.stdout.write(` ${dim(f)}\n`);
379
449
  }
380
450
  process.exitCode = 0;
381
451
  });
@@ -416,7 +486,7 @@ module core {
416
486
  const result = compile(root, { stopAfter: 'test' });
417
487
  outputDiagnostics(result.diagnostics, opts.format);
418
488
  if (!result.diagnostics.hasErrors) {
419
- process.stdout.write('Build OK\n');
489
+ process.stdout.write(success('Build OK') + '\n');
420
490
  }
421
491
  };
422
492
  // Initial build
@@ -463,12 +533,12 @@ module core {
463
533
  }
464
534
  const explanation = explainNode(result.graph, nodeId);
465
535
  if (!explanation) {
466
- process.stderr.write(`Node not found: ${nodeId}\n`);
536
+ process.stderr.write(uiError(`Node not found: ${nodeId}`) + '\n');
467
537
  const allIds = collectAllNodeIds(result.graph);
468
538
  if (allIds.size > 0) {
469
539
  const matches = [...allIds].filter(id => id.includes(/* v8 ignore next -- pop() on non-empty array */ nodeId.split('.').pop() ?? ''));
470
540
  if (matches.length > 0) {
471
- process.stderr.write(`Did you mean: ${matches.slice(0, 5).join(', ')}?\n`);
541
+ process.stderr.write(uiInfo(`Did you mean: ${matches.slice(0, 5).join(', ')}?`) + '\n');
472
542
  }
473
543
  }
474
544
  process.exitCode = 1;
@@ -492,11 +562,11 @@ module core {
492
562
  .action((code) => {
493
563
  const info = getDiagnosticInfo(code);
494
564
  if (!info) {
495
- process.stderr.write(`Unknown diagnostic code: ${code}\n`);
565
+ process.stderr.write(uiError(`Unknown diagnostic code: ${code}`) + '\n');
496
566
  process.exitCode = 1;
497
567
  return;
498
568
  }
499
- process.stdout.write(`${code}: ${info.title}\n`);
569
+ process.stdout.write(`${bold(code)}: ${info.title}\n`);
500
570
  process.stdout.write(` Phase: ${info.phase}\n`);
501
571
  process.stdout.write(` Severity: ${info.severity}\n`);
502
572
  process.stdout.write(` ${info.description}\n`);
@@ -514,15 +584,15 @@ module core {
514
584
  const root = resolve(path);
515
585
  try {
516
586
  const proposal = createProposal(description, root);
517
- process.stdout.write(`Created change proposal: ${proposal.name}\n`);
518
- process.stdout.write(` Directory: ${relative(root, proposal.path)}/\n`);
587
+ process.stdout.write(success(`Created change proposal: ${proposal.name}`) + '\n');
588
+ process.stdout.write(` Directory: ${dim(relative(root, proposal.path))}/\n`);
519
589
  process.stdout.write(` delta.prd: Edit to define specification changes\n`);
520
590
  process.stdout.write(` proposal.md: Document motivation and scope\n`);
521
591
  process.stdout.write(` tasks.md: Track implementation tasks\n`);
522
592
  process.exitCode = 0;
523
593
  }
524
594
  catch (e) {
525
- process.stderr.write(`${e.message}\n`);
595
+ process.stderr.write(uiError(e.message) + '\n');
526
596
  process.exitCode = 1;
527
597
  }
528
598
  });
@@ -541,11 +611,11 @@ module core {
541
611
  process.stdout.write(JSON.stringify(proposals, null, 2) + '\n');
542
612
  }
543
613
  else if (proposals.length === 0) {
544
- process.stdout.write('No active change proposals.\n');
545
- process.stdout.write('Run `prodara propose "<description>"` to create one.\n');
614
+ process.stdout.write(uiInfo('No active change proposals.') + '\n');
615
+ process.stdout.write(dim('Run `prodara propose "<description>"` to create one.') + '\n');
546
616
  }
547
617
  else {
548
- process.stdout.write(`Active change proposals (${proposals.length}):\n\n`);
618
+ process.stdout.write(bold(`Active change proposals (${proposals.length}):`) + '\n\n');
549
619
  for (const p of proposals) {
550
620
  /* v8 ignore next -- proposal status ternary */
551
621
  const icon = p.status === 'proposed' ? '○' : p.status === 'in-progress' ? '◐' : '●';
@@ -571,7 +641,7 @@ module core {
571
641
  const proposal = applyProposal(change, root);
572
642
  const deltaPath = join(proposal.path, 'delta.prd');
573
643
  if (!existsSync(deltaPath)) {
574
- process.stderr.write(`No delta.prd found in proposal: ${change}\n`);
644
+ process.stderr.write(uiError(`No delta.prd found in proposal: ${change}`) + '\n');
575
645
  process.exitCode = 1;
576
646
  return;
577
647
  }
@@ -583,8 +653,8 @@ module core {
583
653
  process.exitCode = 1;
584
654
  return;
585
655
  }
586
- process.stdout.write(`Applied change proposal: ${proposal.name}\n`);
587
- process.stdout.write(` Status: ${proposal.status}\n`);
656
+ process.stdout.write(success(`Applied change proposal: ${proposal.name}`) + '\n');
657
+ process.stdout.write(` Status: ${dim(proposal.status)}\n`);
588
658
  if (result.graph) {
589
659
  const prevGraph = readPreviousGraph(root);
590
660
  if (prevGraph) {
@@ -600,7 +670,7 @@ module core {
600
670
  process.exitCode = 0;
601
671
  }
602
672
  catch (e) {
603
- process.stderr.write(`${e.message}\n`);
673
+ process.stderr.write(uiError(e.message) + '\n');
604
674
  process.exitCode = 1;
605
675
  }
606
676
  });
@@ -616,12 +686,12 @@ module core {
616
686
  const root = resolve(path);
617
687
  try {
618
688
  const proposal = archiveProposal(change, root);
619
- process.stdout.write(`Archived change proposal: ${proposal.name}\n`);
620
- process.stdout.write(` Moved to: ${relative(root, proposal.path)}/\n`);
689
+ process.stdout.write(success(`Archived change proposal: ${proposal.name}`) + '\n');
690
+ process.stdout.write(` Moved to: ${dim(relative(root, proposal.path))}/\n`);
621
691
  process.exitCode = 0;
622
692
  }
623
693
  catch (e) {
624
- process.stderr.write(`${e.message}\n`);
694
+ process.stderr.write(uiError(e.message) + '\n');
625
695
  process.exitCode = 1;
626
696
  }
627
697
  });
@@ -654,14 +724,14 @@ module core {
654
724
  const resolved = autoResolveClarifications(clarifyResult.data.questions, config.phases.clarify.ambiguityThreshold, result.graph);
655
725
  questions = resolved.needsInput;
656
726
  if (opts.format !== 'json') {
657
- process.stdout.write(`Auto-resolved ${resolved.autoResolved.length} question(s)\n\n`);
727
+ process.stdout.write(success(`Auto-resolved ${resolved.autoResolved.length} question(s)`) + '\n\n');
658
728
  }
659
729
  }
660
730
  if (opts.format === 'json') {
661
731
  const output = JSON.stringify({ questions, total: questions.length }, null, 2);
662
732
  if (opts.output) {
663
733
  writeFileSync(resolve(opts.output), output + '\n', 'utf-8');
664
- process.stdout.write(`Questions written to ${opts.output}\n`);
734
+ process.stdout.write(success(`Questions written to ${opts.output}`) + '\n');
665
735
  }
666
736
  else {
667
737
  process.stdout.write(output + '\n');
@@ -669,10 +739,10 @@ module core {
669
739
  }
670
740
  else {
671
741
  if (questions.length === 0) {
672
- process.stdout.write('No ambiguities found in the specification.\n');
742
+ process.stdout.write(success('No ambiguities found in the specification.') + '\n');
673
743
  }
674
744
  else {
675
- process.stdout.write(`Found ${questions.length} ambiguity question(s):\n\n`);
745
+ process.stdout.write(bold(`Found ${questions.length} ambiguity question(s):`) + '\n\n');
676
746
  for (let i = 0; i < questions.length; i++) {
677
747
  const q = questions[i];
678
748
  process.stdout.write(` ${i + 1}. [${q.category}] ${q.question}\n`);
@@ -683,7 +753,7 @@ module core {
683
753
  if (opts.output) {
684
754
  const output = questions.map((q, i) => `${i + 1}. [${q.category}] ${q.question}`).join('\n');
685
755
  writeFileSync(resolve(opts.output), output + '\n', 'utf-8');
686
- process.stdout.write(`Questions written to ${opts.output}\n`);
756
+ process.stdout.write(success(`Questions written to ${opts.output}`) + '\n');
687
757
  }
688
758
  }
689
759
  process.exitCode = 0;
@@ -712,7 +782,7 @@ module core {
712
782
  const output = JSON.stringify(checklist, null, 2);
713
783
  if (opts.output) {
714
784
  writeFileSync(resolve(opts.output), output + '\n', 'utf-8');
715
- process.stdout.write(`Checklist written to ${opts.output}\n`);
785
+ process.stdout.write(success(`Checklist written to ${opts.output}`) + '\n');
716
786
  }
717
787
  else {
718
788
  process.stdout.write(output + '\n');
@@ -722,7 +792,7 @@ module core {
722
792
  const output = formatChecklistHuman(checklist);
723
793
  if (opts.output) {
724
794
  writeFileSync(resolve(opts.output), output + '\n', 'utf-8');
725
- process.stdout.write(`Checklist written to ${opts.output}\n`);
795
+ process.stdout.write(success(`Checklist written to ${opts.output}`) + '\n');
726
796
  }
727
797
  else {
728
798
  process.stdout.write(output + '\n');
@@ -797,7 +867,7 @@ module core {
797
867
  const output = JSON.stringify(data, null, 2);
798
868
  if (opts.output) {
799
869
  writeFileSync(resolve(opts.output), output + '\n', 'utf-8');
800
- process.stdout.write(`Onboarding doc written to ${opts.output}\n`);
870
+ process.stdout.write(success(`Onboarding doc written to ${opts.output}`) + '\n');
801
871
  }
802
872
  else {
803
873
  process.stdout.write(output + '\n');
@@ -841,7 +911,7 @@ module core {
841
911
  const output = lines.join('\n');
842
912
  if (opts.output) {
843
913
  writeFileSync(resolve(opts.output), output + '\n', 'utf-8');
844
- process.stdout.write(`Onboarding doc written to ${opts.output}\n`);
914
+ process.stdout.write(success(`Onboarding doc written to ${opts.output}`) + '\n');
845
915
  }
846
916
  else {
847
917
  process.stdout.write(output + '\n');
@@ -878,13 +948,13 @@ module core {
878
948
  process.stdout.write(JSON.stringify(records, null, 2) + '\n');
879
949
  }
880
950
  else if (records.length === 0) {
881
- process.stdout.write('No build history recorded.\n');
882
- process.stdout.write('Run `prodara build` to create a build record.\n');
951
+ process.stdout.write(uiInfo('No build history recorded.') + '\n');
952
+ process.stdout.write(dim('Run `prodara build` to create a build record.') + '\n');
883
953
  }
884
954
  else {
885
- process.stdout.write(`Build History (last ${records.length}):\n\n`);
955
+ process.stdout.write(bold(`Build History (last ${records.length}):`) + '\n\n');
886
956
  for (const r of records) {
887
- const icon = r.outcome === 'success' ? '' : '';
957
+ const icon = r.outcome === 'success' ? phaseIcon('ok') : phaseIcon('error');
888
958
  const date = new Date(r.timestamp).toLocaleString();
889
959
  const phaseSummary = r.phases.map(p => `${p.name}:${p.status}`).join(' ');
890
960
  process.stdout.write(` ${icon} ${date} — ${r.outcome}\n`);
@@ -910,10 +980,10 @@ module core {
910
980
  const root = resolve(path);
911
981
  const extensions = listInstalledExtensions(root);
912
982
  if (extensions.length === 0) {
913
- process.stdout.write('No extensions installed.\n');
983
+ process.stdout.write(uiInfo('No extensions installed.') + '\n');
914
984
  }
915
985
  else {
916
- process.stdout.write(`Installed extensions (${extensions.length}):\n\n`);
986
+ process.stdout.write(bold(`Installed extensions (${extensions.length}):`) + '\n\n');
917
987
  for (const ext of extensions) {
918
988
  const caps = ext.manifest.capabilities.map(c => c.kind).join(', ');
919
989
  process.stdout.write(` ${ext.manifest.name}@${ext.manifest.version} — ${ext.manifest.description}\n`);
@@ -931,7 +1001,7 @@ module core {
931
1001
  const root = resolve(path);
932
1002
  const manifest = JSON.parse(readFileSync(resolve(manifestPath), 'utf-8'));
933
1003
  const installed = installExtension(root, manifest.name, manifest);
934
- process.stdout.write(`Installed extension "${manifest.name}" at ${installed.path}\n`);
1004
+ process.stdout.write(success(`Installed extension "${manifest.name}" at ${installed.path}`) + '\n');
935
1005
  process.exitCode = 0;
936
1006
  });
937
1007
  ext
@@ -942,7 +1012,7 @@ module core {
942
1012
  .action((name, path) => {
943
1013
  const root = resolve(path);
944
1014
  removeExtension(root, name);
945
- process.stdout.write(`Removed extension "${name}".\n`);
1015
+ process.stdout.write(success(`Removed extension "${name}".`) + '\n');
946
1016
  process.exitCode = 0;
947
1017
  });
948
1018
  // ---------------------------------------------------------------------------
@@ -959,10 +1029,10 @@ module core {
959
1029
  const root = resolve(path);
960
1030
  const presets = loadPresets(root);
961
1031
  if (presets.length === 0) {
962
- process.stdout.write('No presets installed.\n');
1032
+ process.stdout.write(uiInfo('No presets installed.') + '\n');
963
1033
  }
964
1034
  else {
965
- process.stdout.write(`Installed presets (${presets.length}):\n\n`);
1035
+ process.stdout.write(bold(`Installed presets (${presets.length}):`) + '\n\n');
966
1036
  for (const p of presets) {
967
1037
  process.stdout.write(` ${p.manifest.name}@${p.manifest.version} — ${p.manifest.description}\n`);
968
1038
  process.stdout.write(` Priority: ${p.priority}\n\n`);
@@ -979,7 +1049,7 @@ module core {
979
1049
  const root = resolve(path);
980
1050
  const manifest = JSON.parse(readFileSync(resolve(manifestPath), 'utf-8'));
981
1051
  const installed = installPreset(root, manifest.name, manifest);
982
- process.stdout.write(`Installed preset "${manifest.name}" at ${installed.path}\n`);
1052
+ process.stdout.write(success(`Installed preset "${manifest.name}" at ${installed.path}`) + '\n');
983
1053
  process.exitCode = 0;
984
1054
  });
985
1055
  pst
@@ -990,7 +1060,7 @@ module core {
990
1060
  .action((name, path) => {
991
1061
  const root = resolve(path);
992
1062
  removePreset(root, name);
993
- process.stdout.write(`Removed preset "${name}".\n`);
1063
+ process.stdout.write(success(`Removed preset "${name}".`) + '\n');
994
1064
  process.exitCode = 0;
995
1065
  });
996
1066
  // ---------------------------------------------------------------------------
@@ -1015,7 +1085,7 @@ module core {
1015
1085
  : config.docs;
1016
1086
  const files = generateDocs(result.graph, docsConfig, root);
1017
1087
  writeDocs(files, docsConfig.outputDir, root);
1018
- process.stdout.write(`Generated ${files.length} doc file(s) in ${docsConfig.outputDir}/\n`);
1088
+ process.stdout.write(success(`Generated ${files.length} doc file(s) in ${docsConfig.outputDir}/`) + '\n');
1019
1089
  process.exitCode = 0;
1020
1090
  });
1021
1091
  // ---------------------------------------------------------------------------