@paths.design/caws-cli 8.2.1 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/budget-derivation.js +10 -10
  2. package/dist/commands/archive.js +22 -22
  3. package/dist/commands/burnup.js +7 -7
  4. package/dist/commands/diagnose.js +25 -25
  5. package/dist/commands/evaluate.js +20 -20
  6. package/dist/commands/init.js +71 -72
  7. package/dist/commands/iterate.js +21 -21
  8. package/dist/commands/mode.js +11 -11
  9. package/dist/commands/plan.js +5 -5
  10. package/dist/commands/provenance.js +86 -86
  11. package/dist/commands/quality-gates.js +3 -3
  12. package/dist/commands/quality-monitor.js +17 -17
  13. package/dist/commands/session.js +312 -0
  14. package/dist/commands/specs.js +44 -44
  15. package/dist/commands/status.js +43 -43
  16. package/dist/commands/templates.js +14 -14
  17. package/dist/commands/tool.js +1 -1
  18. package/dist/commands/troubleshoot.js +11 -11
  19. package/dist/commands/tutorial.js +119 -119
  20. package/dist/commands/validate.js +6 -6
  21. package/dist/commands/waivers.js +93 -60
  22. package/dist/commands/workflow.js +17 -17
  23. package/dist/commands/worktree.js +13 -13
  24. package/dist/config/index.js +5 -5
  25. package/dist/config/modes.js +7 -7
  26. package/dist/constants/spec-types.js +5 -5
  27. package/dist/error-handler.js +4 -4
  28. package/dist/generators/jest-config-generator.js +3 -3
  29. package/dist/generators/working-spec.js +4 -4
  30. package/dist/index.js +79 -27
  31. package/dist/minimal-cli.js +9 -9
  32. package/dist/policy/PolicyManager.js +1 -1
  33. package/dist/scaffold/claude-hooks.js +7 -7
  34. package/dist/scaffold/cursor-hooks.js +8 -8
  35. package/dist/scaffold/git-hooks.js +152 -152
  36. package/dist/scaffold/index.js +48 -48
  37. package/dist/session/session-manager.js +548 -0
  38. package/dist/test-analysis.js +20 -20
  39. package/dist/utils/command-wrapper.js +8 -8
  40. package/dist/utils/detection.js +7 -7
  41. package/dist/utils/finalization.js +21 -21
  42. package/dist/utils/git-lock.js +3 -3
  43. package/dist/utils/gitignore-updater.js +1 -1
  44. package/dist/utils/project-analysis.js +7 -7
  45. package/dist/utils/quality-gates-utils.js +35 -35
  46. package/dist/utils/spec-resolver.js +8 -8
  47. package/dist/utils/typescript-detector.js +5 -5
  48. package/dist/utils/yaml-validation.js +1 -1
  49. package/dist/validation/spec-validation.js +4 -4
  50. package/dist/worktree/worktree-manager.js +11 -5
  51. package/package.json +1 -1
@@ -0,0 +1,312 @@
1
+ /**
2
+ * @fileoverview CAWS Session CLI Command
3
+ * Manages session lifecycle and capsule persistence for multi-agent coordination.
4
+ * @author @darianrosebrook
5
+ */
6
+
7
+ const chalk = require('chalk');
8
+ const {
9
+ startSession,
10
+ checkpointSession,
11
+ endSession,
12
+ listSessions,
13
+ showSession,
14
+ getBriefing,
15
+ } = require('../session/session-manager');
16
+
17
+ /**
18
+ * Handle session subcommands
19
+ * @param {string} subcommand - Subcommand name
20
+ * @param {Object} options - Command options
21
+ */
22
+ async function sessionCommand(subcommand, options = {}) {
23
+ try {
24
+ switch (subcommand) {
25
+ case 'start':
26
+ return handleStart(options);
27
+ case 'checkpoint':
28
+ return handleCheckpoint(options);
29
+ case 'end':
30
+ return handleEnd(options);
31
+ case 'list':
32
+ return handleList(options);
33
+ case 'show':
34
+ return handleShow(options);
35
+ case 'briefing':
36
+ return handleBriefing();
37
+ default:
38
+ console.error(chalk.red(`Unknown session subcommand: ${subcommand}`));
39
+ console.log(chalk.blue('Available: start, checkpoint, end, list, show, briefing'));
40
+ process.exit(1);
41
+ }
42
+ } catch (error) {
43
+ console.error(chalk.red(error.message));
44
+ process.exit(1);
45
+ }
46
+ }
47
+
48
+ function handleStart(options) {
49
+ const { role, specId, scope, intent } = options;
50
+
51
+ let allowedGlobs = [];
52
+ let forbiddenGlobs = [];
53
+ if (scope) {
54
+ allowedGlobs = scope.split(',').map((s) => s.trim());
55
+ }
56
+
57
+ console.log(chalk.cyan('Starting CAWS session...'));
58
+
59
+ const capsule = startSession({
60
+ role,
61
+ specId,
62
+ allowedGlobs,
63
+ forbiddenGlobs,
64
+ intent,
65
+ });
66
+
67
+ console.log(chalk.green('Session started'));
68
+ console.log(chalk.gray(` ID: ${capsule.session_id}`));
69
+ console.log(chalk.gray(` Role: ${capsule.role}`));
70
+ console.log(chalk.gray(` Baseline: ${capsule.base_state.branch} @ ${capsule.base_state.head_rev}`));
71
+ if (capsule.spec_id) console.log(chalk.gray(` Spec: ${capsule.spec_id}`));
72
+ if (capsule.base_state.workspace_fingerprint.dirty) {
73
+ console.log(
74
+ chalk.yellow(
75
+ ` Warning: Dirty tree: ${capsule.base_state.workspace_fingerprint.paths_touched.length} file(s) uncommitted`
76
+ )
77
+ );
78
+ }
79
+ if (capsule.work_summary.intent) {
80
+ console.log(chalk.gray(` Intent: ${capsule.work_summary.intent}`));
81
+ }
82
+ }
83
+
84
+ function handleCheckpoint(options) {
85
+ const { sessionId, intent } = options;
86
+
87
+ // Parse JSON fields if provided
88
+ let testsRun = [];
89
+ let knownIssues = [];
90
+ let pathsTouched = [];
91
+
92
+ if (options.tests) {
93
+ try {
94
+ testsRun = JSON.parse(options.tests);
95
+ } catch {
96
+ console.error(chalk.red('--tests must be valid JSON array'));
97
+ process.exit(1);
98
+ }
99
+ }
100
+
101
+ if (options.issues) {
102
+ try {
103
+ knownIssues = JSON.parse(options.issues);
104
+ } catch {
105
+ console.error(chalk.red('--issues must be valid JSON array'));
106
+ process.exit(1);
107
+ }
108
+ }
109
+
110
+ if (options.paths) {
111
+ pathsTouched = options.paths.split(',').map((p) => p.trim());
112
+ }
113
+
114
+ const capsule = checkpointSession({
115
+ sessionId,
116
+ intent,
117
+ pathsTouched: pathsTouched.length > 0 ? pathsTouched : undefined,
118
+ testsRun: testsRun.length > 0 ? testsRun : undefined,
119
+ knownIssues: knownIssues.length > 0 ? knownIssues : undefined,
120
+ });
121
+
122
+ console.log(chalk.green('Checkpoint recorded'));
123
+ console.log(chalk.gray(` Session: ${capsule.session_id}`));
124
+ console.log(chalk.gray(` Commits: ${capsule.work_summary.commits.length}`));
125
+ console.log(chalk.gray(` Files: ${capsule.work_summary.paths_touched.length}`));
126
+ console.log(chalk.gray(` Tests: ${capsule.verification.tests_run.length} recorded`));
127
+ }
128
+
129
+ function handleEnd(options) {
130
+ const { sessionId } = options;
131
+
132
+ let nextActions = [];
133
+ let riskNotes = [];
134
+
135
+ if (options.nextActions) {
136
+ nextActions = options.nextActions.split('|').map((a) => a.trim());
137
+ }
138
+ if (options.riskNotes) {
139
+ riskNotes = options.riskNotes.split('|').map((r) => r.trim());
140
+ }
141
+
142
+ const capsule = endSession({
143
+ sessionId,
144
+ nextActions: nextActions.length > 0 ? nextActions : undefined,
145
+ riskNotes: riskNotes.length > 0 ? riskNotes : undefined,
146
+ });
147
+
148
+ console.log(chalk.green('Session ended'));
149
+ console.log(chalk.gray(` ID: ${capsule.session_id}`));
150
+ console.log(chalk.gray(` Duration: ${formatDuration(capsule.started_at, capsule.ended_at)}`));
151
+ console.log(chalk.gray(` Commits: ${capsule.work_summary.commits.length}`));
152
+ console.log(chalk.gray(` Files: ${capsule.work_summary.paths_touched.length}`));
153
+
154
+ if (capsule.handoff.next_actions.length > 0) {
155
+ console.log(chalk.cyan('\n Handoff:'));
156
+ for (const action of capsule.handoff.next_actions) {
157
+ console.log(chalk.gray(` - ${action}`));
158
+ }
159
+ }
160
+
161
+ if (capsule.known_issues.length > 0) {
162
+ console.log(chalk.yellow('\n Known issues:'));
163
+ for (const issue of capsule.known_issues) {
164
+ console.log(chalk.gray(` [${issue.type}] ${issue.description}`));
165
+ }
166
+ }
167
+ }
168
+
169
+ function handleList(options) {
170
+ const entries = listSessions({
171
+ status: options.status,
172
+ limit: options.limit ? parseInt(options.limit, 10) : undefined,
173
+ });
174
+
175
+ if (entries.length === 0) {
176
+ console.log(chalk.gray('No sessions found.'));
177
+ console.log(chalk.blue('Start one with: caws session start'));
178
+ return;
179
+ }
180
+
181
+ console.log(chalk.bold.cyan('CAWS Sessions'));
182
+ console.log(chalk.cyan('='.repeat(90)));
183
+ console.log(
184
+ chalk.bold(
185
+ 'Status'.padEnd(12) +
186
+ 'Role'.padEnd(12) +
187
+ 'Branch'.padEnd(16) +
188
+ 'Rev'.padEnd(10) +
189
+ 'Spec'.padEnd(14) +
190
+ 'Started'
191
+ )
192
+ );
193
+ console.log(chalk.gray('-'.repeat(90)));
194
+
195
+ for (const entry of entries) {
196
+ const statusColor =
197
+ entry.status === 'active' ? chalk.green : chalk.gray;
198
+
199
+ const started = new Date(entry.started_at).toLocaleString();
200
+
201
+ console.log(
202
+ statusColor(entry.status.padEnd(12)) +
203
+ (entry.role || 'worker').padEnd(12) +
204
+ (entry.branch || '-').padEnd(16) +
205
+ (entry.head_rev || '-').padEnd(10) +
206
+ (entry.spec_id || '-').padEnd(14) +
207
+ started
208
+ );
209
+ }
210
+ console.log('');
211
+ }
212
+
213
+ function handleShow(options) {
214
+ const sessionId = options.id || 'latest';
215
+ const capsule = showSession(sessionId);
216
+
217
+ if (options.json) {
218
+ console.log(JSON.stringify(capsule, null, 2));
219
+ return;
220
+ }
221
+
222
+ console.log(chalk.bold.cyan(`Session: ${capsule.session_id}`));
223
+ console.log(chalk.cyan('='.repeat(70)));
224
+
225
+ // Identity
226
+ console.log(chalk.bold('\nIdentity'));
227
+ console.log(chalk.gray(` Project: ${capsule.project}`));
228
+ console.log(chalk.gray(` Skein: ${capsule.skein_id}`));
229
+ console.log(chalk.gray(` Role: ${capsule.role}`));
230
+ if (capsule.spec_id) console.log(chalk.gray(` Spec: ${capsule.spec_id}`));
231
+
232
+ // Base state
233
+ console.log(chalk.bold('\nBaseline'));
234
+ console.log(chalk.gray(` Rev: ${capsule.base_state.head_rev}`));
235
+ console.log(chalk.gray(` Branch: ${capsule.base_state.branch}`));
236
+ console.log(
237
+ chalk.gray(
238
+ ` Dirty: ${capsule.base_state.workspace_fingerprint.dirty ? 'yes' : 'no'} (${capsule.base_state.workspace_fingerprint.paths_touched.length} files)`
239
+ )
240
+ );
241
+
242
+ // Work summary
243
+ console.log(chalk.bold('\nWork Summary'));
244
+ if (capsule.work_summary.intent) {
245
+ console.log(chalk.gray(` Intent: ${capsule.work_summary.intent}`));
246
+ }
247
+ console.log(chalk.gray(` Files: ${capsule.work_summary.paths_touched.length}`));
248
+ console.log(chalk.gray(` Commits: ${capsule.work_summary.commits.length}`));
249
+ if (capsule.work_summary.commits.length > 0) {
250
+ for (const c of capsule.work_summary.commits) {
251
+ console.log(chalk.gray(` ${c.rev} @ ${c.checkpoint_at}`));
252
+ }
253
+ }
254
+
255
+ // Verification
256
+ if (capsule.verification.tests_run.length > 0) {
257
+ console.log(chalk.bold('\nVerification'));
258
+ for (const t of capsule.verification.tests_run) {
259
+ const icon = t.status === 'pass' ? '[PASS]' : '[FAIL]';
260
+ console.log(chalk.gray(` ${icon} ${t.name}: ${t.status}`));
261
+ }
262
+ }
263
+
264
+ // Known issues
265
+ if (capsule.known_issues.length > 0) {
266
+ console.log(chalk.bold('\nKnown Issues'));
267
+ for (const issue of capsule.known_issues) {
268
+ console.log(chalk.yellow(` [${issue.type}] ${issue.description}`));
269
+ }
270
+ }
271
+
272
+ // Handoff
273
+ if (capsule.handoff.next_actions.length > 0 || capsule.handoff.risk_notes.length > 0) {
274
+ console.log(chalk.bold('\nHandoff'));
275
+ for (const action of capsule.handoff.next_actions) {
276
+ console.log(chalk.cyan(` - ${action}`));
277
+ }
278
+ for (const note of capsule.handoff.risk_notes) {
279
+ console.log(chalk.yellow(` Warning: ${note}`));
280
+ }
281
+ }
282
+
283
+ // Timing
284
+ console.log(chalk.bold('\nTiming'));
285
+ console.log(chalk.gray(` Started: ${capsule.started_at}`));
286
+ if (capsule.ended_at) {
287
+ console.log(chalk.gray(` Ended: ${capsule.ended_at}`));
288
+ console.log(chalk.gray(` Duration: ${formatDuration(capsule.started_at, capsule.ended_at)}`));
289
+ } else {
290
+ console.log(chalk.green(' Status: ACTIVE'));
291
+ }
292
+
293
+ console.log('');
294
+ }
295
+
296
+ function handleBriefing() {
297
+ console.log(getBriefing());
298
+ }
299
+
300
+ /**
301
+ * Format duration between two ISO timestamps
302
+ */
303
+ function formatDuration(start, end) {
304
+ const ms = new Date(end) - new Date(start);
305
+ const minutes = Math.floor(ms / 60000);
306
+ const hours = Math.floor(minutes / 60);
307
+ const mins = minutes % 60;
308
+ if (hours > 0) return `${hours}h ${mins}m`;
309
+ return `${mins}m`;
310
+ }
311
+
312
+ module.exports = { sessionCommand };
@@ -129,7 +129,7 @@ async function createSpec(id, options = {}) {
129
129
 
130
130
  if (specExists && !force) {
131
131
  if (interactive) {
132
- console.log(chalk.yellow(`⚠️ Spec '${id}' already exists.`));
132
+ console.log(chalk.yellow(`Spec '${id}' already exists.`));
133
133
  console.log(chalk.gray(` Path: ${existingSpecPath}`));
134
134
 
135
135
  // Load existing spec to show details
@@ -151,7 +151,7 @@ async function createSpec(id, options = {}) {
151
151
  answer = await askConflictResolution();
152
152
 
153
153
  if (answer === 'cancel') {
154
- console.log(chalk.blue('ℹ️ Spec creation canceled.'));
154
+ console.log(chalk.blue('Spec creation canceled.'));
155
155
  return null;
156
156
  } else if (answer === 'rename') {
157
157
  // Generate new name with valid PREFIX-NUMBER format
@@ -161,19 +161,19 @@ async function createSpec(id, options = {}) {
161
161
  // Generate sequential number based on timestamp
162
162
  const number = Date.now().toString().slice(-6); // Last 6 digits of timestamp
163
163
  const newId = `${prefix}-${number}`;
164
- console.log(chalk.blue(`📝 Creating spec with new name: ${newId}`));
164
+ console.log(chalk.blue(`Creating spec with new name: ${newId}`));
165
165
  return await createSpec(newId, { ...options, interactive: false });
166
166
  } else if (answer === 'merge') {
167
167
  // Merge new spec data with existing spec
168
- console.log(chalk.blue('🔄 Merging with existing spec...'));
168
+ console.log(chalk.blue('Merging with existing spec...'));
169
169
  return await mergeSpec(id, options);
170
170
  } else if (answer === 'override') {
171
- console.log(chalk.yellow('⚠️ Overriding existing spec...'));
171
+ console.log(chalk.yellow('Overriding existing spec...'));
172
172
  }
173
173
  } else {
174
- console.error(chalk.red(`❌ Spec '${id}' already exists.`));
174
+ console.error(chalk.red(`Spec '${id}' already exists.`));
175
175
  console.error(
176
- chalk.yellow('💡 Use --force to override, or --interactive for conflict resolution.')
176
+ chalk.yellow('Use --force to override, or --interactive for conflict resolution.')
177
177
  );
178
178
  throw new Error(`Spec '${id}' already exists. Use --force to override.`);
179
179
  }
@@ -181,7 +181,7 @@ async function createSpec(id, options = {}) {
181
181
 
182
182
  // If we got here via override choice, proceed with creation
183
183
  if (specExists && (force || answer === 'override')) {
184
- console.log(chalk.yellow('⚠️ Overriding existing spec...'));
184
+ console.log(chalk.yellow('Overriding existing spec...'));
185
185
  }
186
186
 
187
187
  // Ensure specs directory exists
@@ -298,7 +298,7 @@ async function createSpec(id, options = {}) {
298
298
  if (error.message.includes('YAMLException') || error.message.includes('yaml')) {
299
299
  throw new Error(
300
300
  `Failed to create valid spec: YAML syntax error. ${error.message}\n` +
301
- '💡 Consider using the interactive mode: caws specs create <id> --interactive'
301
+ 'Consider using the interactive mode: caws specs create <id> --interactive'
302
302
  );
303
303
  }
304
304
  throw error;
@@ -398,8 +398,8 @@ async function mergeSpec(id, options = {}) {
398
398
  throw new Error(`Spec '${id}' not found`);
399
399
  }
400
400
 
401
- console.log(chalk.blue(`\n📋 Merging into existing spec: ${id}`));
402
- console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
401
+ console.log(chalk.blue(`\nMerging into existing spec: ${id}`));
402
+ console.log(chalk.gray('==============================================\n'));
403
403
 
404
404
  // Show existing spec summary
405
405
  console.log(chalk.gray(`Existing spec:`));
@@ -499,13 +499,13 @@ async function mergeSpec(id, options = {}) {
499
499
  await updateSpec(id, mergedSpec);
500
500
 
501
501
  // Display merge results
502
- console.log(chalk.green('Merge completed:'));
502
+ console.log(chalk.green('Merge completed:'));
503
503
  if (mergeLog.length > 0) {
504
504
  mergeLog.forEach((change) => {
505
- console.log(chalk.gray(` ${change}`));
505
+ console.log(chalk.gray(` - ${change}`));
506
506
  });
507
507
  } else {
508
- console.log(chalk.gray(' No changes needed (specs were identical)'));
508
+ console.log(chalk.gray(' - No changes needed (specs were identical)'));
509
509
  }
510
510
  console.log('');
511
511
 
@@ -541,8 +541,8 @@ async function deleteSpec(id) {
541
541
  * @param {Array} specs - Array of spec objects
542
542
  */
543
543
  function displaySpecsTable(specs) {
544
- console.log(chalk.bold.cyan('\n📋 CAWS Specs'));
545
- console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
544
+ console.log(chalk.bold.cyan('\nCAWS Specs'));
545
+ console.log(chalk.cyan('==============================================\n'));
546
546
 
547
547
  if (specs.length === 0) {
548
548
  console.log(chalk.gray(' No specs found. Create one with: caws specs create <id>'));
@@ -551,7 +551,7 @@ function displaySpecsTable(specs) {
551
551
 
552
552
  // Header
553
553
  console.log(chalk.bold('ID'.padEnd(15) + 'Type'.padEnd(10) + 'Status'.padEnd(12) + 'Title'));
554
- console.log(chalk.gray(''.repeat(80)));
554
+ console.log(chalk.gray('-'.repeat(80)));
555
555
 
556
556
  // Sort specs by type and status priority
557
557
  const statusPriority = { active: 0, draft: 1, completed: 2, archived: 3 };
@@ -593,8 +593,8 @@ function displaySpecDetails(spec) {
593
593
  const specType = SPEC_TYPES[spec.type] || SPEC_TYPES.feature;
594
594
  const typeColor = specType.color;
595
595
 
596
- console.log(chalk.bold.cyan(`\n📋 Spec Details: ${spec.id}`));
597
- console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
596
+ console.log(chalk.bold.cyan(`\nSpec Details: ${spec.id}`));
597
+ console.log(chalk.cyan('==============================================\n'));
598
598
 
599
599
  console.log(`${specType.icon} ${typeColor(spec.type.toUpperCase())} - ${spec.title}`);
600
600
  console.log(
@@ -610,7 +610,7 @@ function displaySpecDetails(spec) {
610
610
  if (spec.acceptance_criteria && spec.acceptance_criteria.length > 0) {
611
611
  console.log(chalk.gray(`\n Acceptance Criteria (${spec.acceptance_criteria.length}):`));
612
612
  spec.acceptance_criteria.forEach((criterion, index) => {
613
- const status = criterion.completed ? chalk.green('') : chalk.red('');
613
+ const status = criterion.completed ? chalk.green('[done]') : chalk.red('[ ]');
614
614
  console.log(
615
615
  chalk.gray(` ${status} ${criterion.description || criterion.title || `A${index + 1}`}`)
616
616
  );
@@ -620,7 +620,7 @@ function displaySpecDetails(spec) {
620
620
  if (spec.contracts && spec.contracts.length > 0) {
621
621
  console.log(chalk.gray(`\n Contracts (${spec.contracts.length}):`));
622
622
  spec.contracts.forEach((contract) => {
623
- console.log(chalk.gray(` 📄 ${contract.type}: ${contract.path}`));
623
+ console.log(chalk.gray(` ${contract.type}: ${contract.path}`));
624
624
  });
625
625
  }
626
626
 
@@ -645,7 +645,7 @@ async function migrateFromLegacy(options = {}, createSpecFn = createSpec) {
645
645
  throw new Error('No legacy working-spec.yaml found to migrate');
646
646
  }
647
647
 
648
- console.log(chalk.blue('🔄 Migrating from legacy single-spec to multi-spec...'));
648
+ console.log(chalk.blue('Migrating from legacy single-spec to multi-spec...'));
649
649
 
650
650
  const legacyContent = await fs.readFile(legacyPath, 'utf8');
651
651
  const legacySpec = yaml.load(legacyContent);
@@ -661,7 +661,7 @@ async function migrateFromLegacy(options = {}, createSpecFn = createSpec) {
661
661
  // Suggest feature breakdown based on acceptance criteria
662
662
  const features = suggestFeatureBreakdown(legacySpec);
663
663
 
664
- console.log(chalk.green(`\n✅ Found ${features.length} potential features to extract:`));
664
+ console.log(chalk.green(`\nFound ${features.length} potential features to extract:`));
665
665
  features.forEach((feature, index) => {
666
666
  console.log(chalk.yellow(` ${index + 1}. ${feature.id} - ${feature.title}`));
667
667
  console.log(chalk.gray(` Scope: ${feature.scope.in.join(', ')}`));
@@ -673,10 +673,10 @@ async function migrateFromLegacy(options = {}, createSpecFn = createSpec) {
673
673
  if (options.interactive) {
674
674
  selectedFeatures = await selectFeaturesInteractively(features);
675
675
  if (selectedFeatures.length === 0) {
676
- console.log(chalk.yellow('⚠️ No features selected. Migration cancelled.'));
676
+ console.log(chalk.yellow('No features selected. Migration cancelled.'));
677
677
  return { migrated: 0, total: features.length, createdSpecs: [], legacySpec: legacySpec.id };
678
678
  }
679
- console.log(chalk.blue(`\n📋 Migrating ${selectedFeatures.length} selected features`));
679
+ console.log(chalk.blue(`\nMigrating ${selectedFeatures.length} selected features`));
680
680
  }
681
681
 
682
682
  if (options.features && options.features.length > 0) {
@@ -684,10 +684,10 @@ async function migrateFromLegacy(options = {}, createSpecFn = createSpec) {
684
684
  selectedFeatures = features.filter((f) => options.features.includes(f.id));
685
685
  if (selectedFeatures.length === 0) {
686
686
  const errorMsg = `No features found matching: ${options.features.join(', ')}. Available features: ${features.map((f) => f.id).join(', ')}`;
687
- console.log(chalk.yellow(`⚠️ ${errorMsg}`));
687
+ console.log(chalk.yellow(`${errorMsg}`));
688
688
  throw new Error(errorMsg);
689
689
  } else {
690
- console.log(chalk.blue(`\n📋 Migrating selected features: ${options.features.join(', ')}`));
690
+ console.log(chalk.blue(`\nMigrating selected features: ${options.features.join(', ')}`));
691
691
  }
692
692
  }
693
693
 
@@ -714,10 +714,10 @@ async function migrateFromLegacy(options = {}, createSpecFn = createSpec) {
714
714
  });
715
715
 
716
716
  createdSpecs.push(specId);
717
- console.log(chalk.green(` Created spec: ${specId}`));
717
+ console.log(chalk.green(` Created spec: ${specId}`));
718
718
  } catch (error) {
719
719
  // Log full error details for debugging
720
- console.log(chalk.red(` Failed to create spec ${feature.id}: ${error.message}`));
720
+ console.log(chalk.red(` Failed to create spec ${feature.id}: ${error.message}`));
721
721
  if (process.env.DEBUG_MIGRATION) {
722
722
  console.log(chalk.gray(` Error details: ${error.stack}`));
723
723
  }
@@ -725,11 +725,11 @@ async function migrateFromLegacy(options = {}, createSpecFn = createSpec) {
725
725
  }
726
726
 
727
727
  console.log(
728
- chalk.green(`\n🎉 Migration completed! Created ${createdSpecs.length} feature specs.`)
728
+ chalk.green(`\nMigration completed! Created ${createdSpecs.length} feature specs.`)
729
729
  );
730
730
 
731
731
  if (createdSpecs.length > 0) {
732
- console.log(chalk.blue('\n💡 Next steps:'));
732
+ console.log(chalk.blue('\nNext steps:'));
733
733
  console.log(chalk.gray(' 1. Review and customize each feature spec'));
734
734
  console.log(chalk.gray(' 2. Update agents to use --spec-id <feature-id>'));
735
735
  console.log(chalk.gray(' 3. Consider archiving legacy working-spec.yaml when ready'));
@@ -756,7 +756,7 @@ async function selectFeaturesInteractively(features) {
756
756
  output: process.stdout,
757
757
  });
758
758
 
759
- console.log(chalk.cyan('\n📋 Select features to migrate:\n'));
759
+ console.log(chalk.cyan('\nSelect features to migrate:\n'));
760
760
  features.forEach((f, i) => {
761
761
  const scope = f.scope?.in?.join(', ') || 'N/A';
762
762
  console.log(` ${chalk.yellow(i + 1)}. ${chalk.bold(f.id || f.name)} - ${f.title || f.description}`);
@@ -799,7 +799,7 @@ async function selectFeaturesInteractively(features) {
799
799
  async function askConflictResolution() {
800
800
  const readline = require('readline');
801
801
 
802
- console.log(chalk.blue('\n🔄 Conflict Resolution Options:'));
802
+ console.log(chalk.blue('\nConflict Resolution Options:'));
803
803
  console.log(chalk.gray(" 1. Cancel - Don't create the spec"));
804
804
  console.log(chalk.gray(' 2. Rename - Create with auto-generated name'));
805
805
  console.log(chalk.gray(' 3. Merge - Merge with existing spec (not implemented)'));
@@ -825,7 +825,7 @@ async function askConflictResolution() {
825
825
  } else if (trimmed === '4' || trimmed === 'override') {
826
826
  return 'override';
827
827
  } else {
828
- console.log(chalk.red('Invalid choice. Defaulting to cancel.'));
828
+ console.log(chalk.red('Invalid choice. Defaulting to cancel.'));
829
829
  return 'cancel';
830
830
  }
831
831
  } finally {
@@ -859,7 +859,7 @@ async function specsCommand(action, options = {}) {
859
859
  const specIds = Object.keys(registry.specs ?? {});
860
860
 
861
861
  if (specIds.length < 2) {
862
- console.log(chalk.blue('ℹ️ No scope conflicts possible with fewer than 2 specs'));
862
+ console.log(chalk.blue('No scope conflicts possible with fewer than 2 specs'));
863
863
  return outputResult({
864
864
  command: 'specs conflicts',
865
865
  conflictCount: 0,
@@ -867,15 +867,15 @@ async function specsCommand(action, options = {}) {
867
867
  });
868
868
  }
869
869
 
870
- console.log(chalk.blue(`🔍 Checking scope conflicts between ${specIds.length} specs...`));
870
+ console.log(chalk.blue(`Checking scope conflicts between ${specIds.length} specs...`));
871
871
  const conflicts = await checkScopeConflicts(specIds);
872
872
 
873
873
  if (conflicts.length === 0) {
874
- console.log(chalk.green('No scope conflicts detected'));
874
+ console.log(chalk.green('No scope conflicts detected'));
875
875
  } else {
876
876
  console.log(
877
877
  chalk.yellow(
878
- `⚠️ Found ${conflicts.length} scope conflict${conflicts.length > 1 ? 's' : ''}:`
878
+ `Found ${conflicts.length} scope conflict${conflicts.length > 1 ? 's' : ''}:`
879
879
  )
880
880
  );
881
881
  conflicts.forEach((conflict) => {
@@ -885,7 +885,7 @@ async function specsCommand(action, options = {}) {
885
885
  });
886
886
  });
887
887
  console.log(
888
- chalk.blue('\n💡 Tip: Use non-overlapping scope.in paths to avoid conflicts')
888
+ chalk.blue('\nTip: Use non-overlapping scope.in paths to avoid conflicts')
889
889
  );
890
890
  }
891
891
 
@@ -932,7 +932,7 @@ async function specsCommand(action, options = {}) {
932
932
  });
933
933
  }
934
934
 
935
- console.log(chalk.green(`✅ Created spec: ${newSpec.id}`));
935
+ console.log(chalk.green(`Created spec: ${newSpec.id}`));
936
936
  displaySpecDetails(newSpec);
937
937
 
938
938
  return outputResult({
@@ -974,7 +974,7 @@ async function specsCommand(action, options = {}) {
974
974
  throw new Error(`Spec '${options.id}' not found`);
975
975
  }
976
976
 
977
- console.log(chalk.green(`✅ Updated spec: ${options.id}`));
977
+ console.log(chalk.green(`Updated spec: ${options.id}`));
978
978
 
979
979
  return outputResult({
980
980
  command: 'specs update',
@@ -993,7 +993,7 @@ async function specsCommand(action, options = {}) {
993
993
  throw new Error(`Spec '${options.id}' not found`);
994
994
  }
995
995
 
996
- console.log(chalk.green(`✅ Deleted spec: ${options.id}`));
996
+ console.log(chalk.green(`Deleted spec: ${options.id}`));
997
997
 
998
998
  return outputResult({
999
999
  command: 'specs delete',
@@ -1002,8 +1002,8 @@ async function specsCommand(action, options = {}) {
1002
1002
  }
1003
1003
 
1004
1004
  case 'types': {
1005
- console.log(chalk.bold.cyan('\n📋 Available Spec Types'));
1006
- console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
1005
+ console.log(chalk.bold.cyan('\nAvailable Spec Types'));
1006
+ console.log(chalk.cyan('==============================================\n'));
1007
1007
 
1008
1008
  Object.entries(SPEC_TYPES).forEach(([type, info]) => {
1009
1009
  console.log(`${info.icon} ${info.color(type.padEnd(10))} - ${info.description}`);