@paths.design/caws-cli 8.2.1 → 8.2.3
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/budget-derivation.js +10 -10
- package/dist/commands/archive.js +22 -22
- package/dist/commands/burnup.js +7 -7
- package/dist/commands/diagnose.js +25 -25
- package/dist/commands/evaluate.js +20 -20
- package/dist/commands/init.js +71 -72
- package/dist/commands/iterate.js +21 -21
- package/dist/commands/mode.js +11 -11
- package/dist/commands/plan.js +5 -5
- package/dist/commands/provenance.js +86 -86
- package/dist/commands/quality-gates.js +3 -3
- package/dist/commands/quality-monitor.js +17 -17
- package/dist/commands/session.js +312 -0
- package/dist/commands/specs.js +44 -44
- package/dist/commands/status.js +43 -43
- package/dist/commands/templates.js +14 -14
- package/dist/commands/tool.js +1 -1
- package/dist/commands/troubleshoot.js +11 -11
- package/dist/commands/tutorial.js +119 -119
- package/dist/commands/validate.js +6 -6
- package/dist/commands/waivers.js +93 -60
- package/dist/commands/workflow.js +17 -17
- package/dist/commands/worktree.js +13 -13
- package/dist/config/index.js +5 -5
- package/dist/config/modes.js +7 -7
- package/dist/constants/spec-types.js +5 -5
- package/dist/error-handler.js +4 -4
- package/dist/generators/jest-config-generator.js +3 -3
- package/dist/generators/working-spec.js +4 -4
- package/dist/index.js +79 -27
- package/dist/minimal-cli.js +9 -9
- package/dist/policy/PolicyManager.js +1 -1
- package/dist/scaffold/claude-hooks.js +7 -7
- package/dist/scaffold/cursor-hooks.js +8 -8
- package/dist/scaffold/git-hooks.js +152 -152
- package/dist/scaffold/index.js +48 -48
- package/dist/session/session-manager.js +548 -0
- package/dist/test-analysis.js +20 -20
- package/dist/utils/command-wrapper.js +8 -8
- package/dist/utils/detection.js +7 -7
- package/dist/utils/finalization.js +21 -21
- package/dist/utils/git-lock.js +3 -3
- package/dist/utils/gitignore-updater.js +1 -1
- package/dist/utils/project-analysis.js +7 -7
- package/dist/utils/quality-gates-utils.js +35 -35
- package/dist/utils/spec-resolver.js +8 -8
- package/dist/utils/typescript-detector.js +5 -5
- package/dist/utils/yaml-validation.js +1 -1
- package/dist/validation/spec-validation.js +4 -4
- package/dist/worktree/worktree-manager.js +11 -5
- 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 };
|
package/dist/commands/specs.js
CHANGED
|
@@ -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(
|
|
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('
|
|
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(
|
|
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('
|
|
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('
|
|
171
|
+
console.log(chalk.yellow('Overriding existing spec...'));
|
|
172
172
|
}
|
|
173
173
|
} else {
|
|
174
|
-
console.error(chalk.red(
|
|
174
|
+
console.error(chalk.red(`Spec '${id}' already exists.`));
|
|
175
175
|
console.error(
|
|
176
|
-
chalk.yellow('
|
|
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('
|
|
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
|
-
'
|
|
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(`\
|
|
402
|
-
console.log(chalk.gray('
|
|
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('
|
|
502
|
+
console.log(chalk.green('Merge completed:'));
|
|
503
503
|
if (mergeLog.length > 0) {
|
|
504
504
|
mergeLog.forEach((change) => {
|
|
505
|
-
console.log(chalk.gray(`
|
|
505
|
+
console.log(chalk.gray(` - ${change}`));
|
|
506
506
|
});
|
|
507
507
|
} else {
|
|
508
|
-
console.log(chalk.gray('
|
|
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('\
|
|
545
|
-
console.log(chalk.cyan('
|
|
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('
|
|
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(`\
|
|
597
|
-
console.log(chalk.cyan('
|
|
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('
|
|
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(`
|
|
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('
|
|
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(`\
|
|
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('
|
|
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(`\
|
|
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(
|
|
687
|
+
console.log(chalk.yellow(`${errorMsg}`));
|
|
688
688
|
throw new Error(errorMsg);
|
|
689
689
|
} else {
|
|
690
|
-
console.log(chalk.blue(`\
|
|
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(`
|
|
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(`
|
|
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(`\
|
|
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('\
|
|
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('\
|
|
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('\
|
|
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('
|
|
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('
|
|
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(
|
|
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('
|
|
874
|
+
console.log(chalk.green('No scope conflicts detected'));
|
|
875
875
|
} else {
|
|
876
876
|
console.log(
|
|
877
877
|
chalk.yellow(
|
|
878
|
-
|
|
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('\
|
|
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(
|
|
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(
|
|
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(
|
|
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('\
|
|
1006
|
-
console.log(chalk.cyan('
|
|
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}`);
|