@paths.design/caws-cli 8.2.3 → 9.0.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.
- package/dist/commands/parallel.js +238 -0
- package/dist/commands/specs.js +55 -2
- package/dist/commands/status.js +13 -3
- package/dist/constants/spec-types.js +25 -2
- package/dist/index.js +43 -2
- package/dist/parallel/parallel-manager.js +443 -0
- package/dist/scaffold/claude-hooks.js +54 -1
- package/dist/scaffold/git-hooks.js +188 -14
- package/dist/session/session-manager.js +14 -0
- package/dist/templates/.claude/hooks/scope-guard.sh +67 -21
- package/dist/templates/.claude/hooks/session-caws-status.sh +117 -0
- package/dist/templates/.claude/hooks/stop-worktree-check.sh +46 -0
- package/dist/templates/.claude/hooks/worktree-guard.sh +207 -0
- package/dist/templates/.claude/hooks/worktree-write-guard.sh +84 -0
- package/dist/templates/.claude/rules/git-safety.md +26 -0
- package/dist/templates/.claude/rules/worktree-isolation.md +51 -0
- package/dist/templates/.claude/settings.json +15 -0
- package/dist/validation/spec-validation.js +16 -0
- package/dist/worktree/worktree-manager.js +31 -21
- package/package.json +1 -1
- package/templates/.claude/hooks/scope-guard.sh +67 -21
- package/templates/.claude/hooks/session-caws-status.sh +117 -0
- package/templates/.claude/hooks/stop-worktree-check.sh +46 -0
- package/templates/.claude/hooks/worktree-guard.sh +207 -0
- package/templates/.claude/hooks/worktree-write-guard.sh +84 -0
- package/templates/.claude/rules/git-safety.md +26 -0
- package/templates/.claude/rules/worktree-isolation.md +51 -0
- package/templates/.claude/settings.json +15 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CAWS Parallel CLI Command
|
|
3
|
+
* Orchestrates parallel multi-agent workspaces
|
|
4
|
+
* @author @darianrosebrook
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const {
|
|
10
|
+
loadPlan,
|
|
11
|
+
setupParallel,
|
|
12
|
+
getParallelStatus,
|
|
13
|
+
mergeParallel,
|
|
14
|
+
teardownParallel,
|
|
15
|
+
} = require('../parallel/parallel-manager');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Handle parallel subcommands
|
|
19
|
+
* @param {string} subcommand - Subcommand name
|
|
20
|
+
* @param {Object} options - Command options
|
|
21
|
+
*/
|
|
22
|
+
async function parallelCommand(subcommand, options = {}) {
|
|
23
|
+
try {
|
|
24
|
+
switch (subcommand) {
|
|
25
|
+
case 'setup':
|
|
26
|
+
return handleSetup(options);
|
|
27
|
+
case 'status':
|
|
28
|
+
return handleStatus();
|
|
29
|
+
case 'merge':
|
|
30
|
+
return handleMerge(options);
|
|
31
|
+
case 'teardown':
|
|
32
|
+
return handleTeardown(options);
|
|
33
|
+
default:
|
|
34
|
+
console.error(chalk.red(`Unknown parallel subcommand: ${subcommand}`));
|
|
35
|
+
console.log(chalk.blue('Available: setup, status, merge, teardown'));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error(chalk.red(`${error.message}`));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function handleSetup(options) {
|
|
45
|
+
const { planFile, baseBranch } = options;
|
|
46
|
+
|
|
47
|
+
if (!planFile) {
|
|
48
|
+
console.error(chalk.red('Plan file is required'));
|
|
49
|
+
console.log(chalk.blue('Usage: caws parallel setup <plan-file> [--base-branch <branch>]'));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const planPath = path.resolve(planFile);
|
|
54
|
+
console.log(chalk.cyan(`Loading plan: ${planFile}`));
|
|
55
|
+
|
|
56
|
+
const plan = loadPlan(planPath);
|
|
57
|
+
|
|
58
|
+
// Allow CLI --base-branch to override plan file
|
|
59
|
+
if (baseBranch) {
|
|
60
|
+
plan.baseBranch = baseBranch;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(chalk.cyan(`Setting up ${plan.agents.length} parallel worktree(s)...`));
|
|
64
|
+
const results = setupParallel(plan);
|
|
65
|
+
|
|
66
|
+
console.log(chalk.green(`\nParallel workspace created`));
|
|
67
|
+
console.log(chalk.gray(` Base branch: ${results[0] ? results[0].baseBranch : plan.baseBranch || 'main'}`));
|
|
68
|
+
console.log(chalk.gray(` Strategy: ${plan.mergeStrategy}`));
|
|
69
|
+
console.log('');
|
|
70
|
+
|
|
71
|
+
// Print table
|
|
72
|
+
console.log(
|
|
73
|
+
chalk.bold(
|
|
74
|
+
'Agent'.padEnd(20) +
|
|
75
|
+
'Branch'.padEnd(25) +
|
|
76
|
+
'Scope'
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
console.log(chalk.gray('-'.repeat(70)));
|
|
80
|
+
|
|
81
|
+
for (const entry of results) {
|
|
82
|
+
console.log(
|
|
83
|
+
entry.name.padEnd(20) +
|
|
84
|
+
chalk.cyan(entry.branch.padEnd(25)) +
|
|
85
|
+
chalk.gray(entry.scope || '(all)')
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log('');
|
|
90
|
+
console.log(chalk.blue('Direct each agent to its worktree:'));
|
|
91
|
+
for (const entry of results) {
|
|
92
|
+
console.log(chalk.gray(` ${entry.name}: cd ${entry.path}`));
|
|
93
|
+
}
|
|
94
|
+
console.log('');
|
|
95
|
+
console.log(chalk.blue('Monitor progress: caws parallel status'));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handleStatus() {
|
|
99
|
+
const status = getParallelStatus();
|
|
100
|
+
|
|
101
|
+
if (!status) {
|
|
102
|
+
console.log(chalk.gray('No active parallel run.'));
|
|
103
|
+
console.log(chalk.blue('Start one with: caws parallel setup <plan-file>'));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log(chalk.bold.cyan('CAWS Parallel Status'));
|
|
108
|
+
console.log(chalk.cyan('='.repeat(70)));
|
|
109
|
+
console.log(chalk.gray(` Base branch: ${status.baseBranch}`));
|
|
110
|
+
console.log(chalk.gray(` Strategy: ${status.mergeStrategy}`));
|
|
111
|
+
console.log(chalk.gray(` Created: ${status.createdAt}`));
|
|
112
|
+
console.log('');
|
|
113
|
+
|
|
114
|
+
// Agent table
|
|
115
|
+
console.log(
|
|
116
|
+
chalk.bold(
|
|
117
|
+
'Agent'.padEnd(18) +
|
|
118
|
+
'Status'.padEnd(10) +
|
|
119
|
+
'Branch'.padEnd(22) +
|
|
120
|
+
'Commits'.padEnd(9) +
|
|
121
|
+
'Dirty'.padEnd(7) +
|
|
122
|
+
'Scope'
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
console.log(chalk.gray('-'.repeat(80)));
|
|
126
|
+
|
|
127
|
+
for (const agent of status.agents) {
|
|
128
|
+
const statusColor =
|
|
129
|
+
agent.status === 'active'
|
|
130
|
+
? chalk.green
|
|
131
|
+
: agent.status === 'missing'
|
|
132
|
+
? chalk.red
|
|
133
|
+
: chalk.yellow;
|
|
134
|
+
|
|
135
|
+
console.log(
|
|
136
|
+
agent.name.padEnd(18) +
|
|
137
|
+
statusColor(agent.status.padEnd(10)) +
|
|
138
|
+
agent.branch.padEnd(22) +
|
|
139
|
+
String(agent.commitCount).padEnd(9) +
|
|
140
|
+
(agent.dirty ? chalk.yellow('yes') : chalk.gray('no')).padEnd(7 + 10) + // +10 for chalk color codes
|
|
141
|
+
chalk.gray(agent.scope || '(all)')
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Show conflicts
|
|
146
|
+
if (status.conflicts.length > 0) {
|
|
147
|
+
console.log('');
|
|
148
|
+
console.log(chalk.yellow(`WARNING: ${status.conflicts.length} file-level conflict(s) detected:`));
|
|
149
|
+
for (const conflict of status.conflicts) {
|
|
150
|
+
console.log(chalk.yellow(` ${conflict.file} -- modified by: ${conflict.agents.join(', ')}`));
|
|
151
|
+
}
|
|
152
|
+
console.log(chalk.blue(' These files were modified by multiple agents and may cause merge conflicts.'));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log('');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function handleMerge(options) {
|
|
159
|
+
const { strategy, dryRun, force } = options;
|
|
160
|
+
|
|
161
|
+
if (dryRun) {
|
|
162
|
+
console.log(chalk.cyan('Dry run: previewing merge...'));
|
|
163
|
+
} else {
|
|
164
|
+
console.log(chalk.cyan('Merging parallel branches back to base...'));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = mergeParallel({ strategy, dryRun, force });
|
|
168
|
+
|
|
169
|
+
// Show conflicts
|
|
170
|
+
if (result.conflicts.length > 0) {
|
|
171
|
+
console.log(chalk.yellow(`\n${result.conflicts.length} file-level conflict(s) detected:`));
|
|
172
|
+
for (const conflict of result.conflicts) {
|
|
173
|
+
console.log(chalk.yellow(` ${conflict.file} -- ${conflict.agents.join(', ')}`));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!force && !dryRun) {
|
|
177
|
+
console.log('');
|
|
178
|
+
console.log(chalk.red('Merge aborted due to conflicts.'));
|
|
179
|
+
console.log(chalk.blue(' Review conflicts, then: caws parallel merge --force'));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (dryRun) {
|
|
185
|
+
console.log(chalk.green(`\nWould merge ${result.merged.length} branch(es):`));
|
|
186
|
+
for (const name of result.merged) {
|
|
187
|
+
console.log(chalk.gray(` - ${name}`));
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (result.merged.length > 0) {
|
|
193
|
+
console.log(chalk.green(`\nMerged ${result.merged.length} branch(es):`));
|
|
194
|
+
for (const name of result.merged) {
|
|
195
|
+
console.log(chalk.gray(` - ${name}`));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (result.failed.length > 0) {
|
|
200
|
+
console.log(chalk.red(`\nFailed to merge ${result.failed.length} branch(es):`));
|
|
201
|
+
for (const fail of result.failed) {
|
|
202
|
+
console.log(chalk.red(` - ${fail.name}: ${fail.error}`));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (result.merged.length > 0 && result.failed.length === 0) {
|
|
207
|
+
console.log('');
|
|
208
|
+
console.log(chalk.blue('Clean up with: caws parallel teardown --delete-branches'));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function handleTeardown(options) {
|
|
213
|
+
const { deleteBranches, force } = options;
|
|
214
|
+
|
|
215
|
+
console.log(chalk.cyan('Tearing down parallel worktrees...'));
|
|
216
|
+
const result = teardownParallel({ deleteBranches, force });
|
|
217
|
+
|
|
218
|
+
if (result.destroyed.length > 0) {
|
|
219
|
+
console.log(chalk.green(`Destroyed ${result.destroyed.length} worktree(s):`));
|
|
220
|
+
for (const name of result.destroyed) {
|
|
221
|
+
console.log(chalk.gray(` - ${name}`));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (result.failed.length > 0) {
|
|
226
|
+
console.log(chalk.red(`Failed to destroy ${result.failed.length} worktree(s):`));
|
|
227
|
+
for (const fail of result.failed) {
|
|
228
|
+
console.log(chalk.red(` - ${fail.name}: ${fail.error}`));
|
|
229
|
+
}
|
|
230
|
+
console.log(chalk.blue(' Use --force to override'));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (deleteBranches) {
|
|
234
|
+
console.log(chalk.gray(' Branches also deleted'));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
module.exports = { parallelCommand };
|
package/dist/commands/specs.js
CHANGED
|
@@ -363,6 +363,16 @@ async function updateSpec(id, updates = {}) {
|
|
|
363
363
|
return false;
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
// Validate status if being updated
|
|
367
|
+
if (updates.status) {
|
|
368
|
+
const { SPEC_STATUSES } = require('../constants/spec-types');
|
|
369
|
+
if (!SPEC_STATUSES[updates.status]) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
`Invalid status '${updates.status}'. Valid values: ${Object.keys(SPEC_STATUSES).join(', ')}`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
366
376
|
// Apply updates
|
|
367
377
|
const updatedSpec = {
|
|
368
378
|
...spec,
|
|
@@ -536,6 +546,30 @@ async function deleteSpec(id) {
|
|
|
536
546
|
return true;
|
|
537
547
|
}
|
|
538
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Close a spec (sets status to 'closed', removing scope enforcement).
|
|
551
|
+
* @param {string} id - Spec identifier
|
|
552
|
+
* @returns {Promise<boolean>} Success status
|
|
553
|
+
*/
|
|
554
|
+
async function closeSpec(id) {
|
|
555
|
+
const spec = await loadSpec(id);
|
|
556
|
+
if (!spec) {
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const currentStatus = spec.status || 'draft';
|
|
561
|
+
if (currentStatus === 'closed') {
|
|
562
|
+
console.log(chalk.yellow(`Spec '${id}' is already closed.`));
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
if (currentStatus === 'archived') {
|
|
566
|
+
console.log(chalk.yellow(`Spec '${id}' is archived and cannot be closed.`));
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return await updateSpec(id, { status: 'closed' });
|
|
571
|
+
}
|
|
572
|
+
|
|
539
573
|
/**
|
|
540
574
|
* Display specs in a formatted table
|
|
541
575
|
* @param {Array} specs - Array of spec objects
|
|
@@ -554,7 +588,7 @@ function displaySpecsTable(specs) {
|
|
|
554
588
|
console.log(chalk.gray('-'.repeat(80)));
|
|
555
589
|
|
|
556
590
|
// Sort specs by type and status priority
|
|
557
|
-
const statusPriority = { active: 0, draft: 1, completed: 2,
|
|
591
|
+
const statusPriority = { active: 0, draft: 1, completed: 2, closed: 3, archived: 4 };
|
|
558
592
|
const sortedSpecs = specs.sort((a, b) => {
|
|
559
593
|
const typeDiff = a.type.localeCompare(b.type);
|
|
560
594
|
if (typeDiff !== 0) return typeDiff;
|
|
@@ -1001,6 +1035,24 @@ async function specsCommand(action, options = {}) {
|
|
|
1001
1035
|
});
|
|
1002
1036
|
}
|
|
1003
1037
|
|
|
1038
|
+
case 'close': {
|
|
1039
|
+
if (!options.id) {
|
|
1040
|
+
throw new Error('Spec ID is required. Usage: caws specs close <id>');
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const closed = await closeSpec(options.id);
|
|
1044
|
+
if (!closed) {
|
|
1045
|
+
throw new Error(`Could not close spec '${options.id}'`);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
console.log(chalk.green(`Closed spec: ${options.id} -- scope restrictions removed`));
|
|
1049
|
+
|
|
1050
|
+
return outputResult({
|
|
1051
|
+
command: 'specs close',
|
|
1052
|
+
spec: options.id,
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1004
1056
|
case 'types': {
|
|
1005
1057
|
console.log(chalk.bold.cyan('\nAvailable Spec Types'));
|
|
1006
1058
|
console.log(chalk.cyan('==============================================\n'));
|
|
@@ -1019,7 +1071,7 @@ async function specsCommand(action, options = {}) {
|
|
|
1019
1071
|
|
|
1020
1072
|
default:
|
|
1021
1073
|
throw new Error(
|
|
1022
|
-
`Unknown specs action: ${action}. Use: list, create, show, update, delete, conflicts, migrate, types`
|
|
1074
|
+
`Unknown specs action: ${action}. Use: list, create, show, update, delete, close, conflicts, migrate, types`
|
|
1023
1075
|
);
|
|
1024
1076
|
}
|
|
1025
1077
|
},
|
|
@@ -1037,6 +1089,7 @@ module.exports = {
|
|
|
1037
1089
|
loadSpec,
|
|
1038
1090
|
updateSpec,
|
|
1039
1091
|
deleteSpec,
|
|
1092
|
+
closeSpec,
|
|
1040
1093
|
displaySpecsTable,
|
|
1041
1094
|
displaySpecDetails,
|
|
1042
1095
|
askConflictResolution,
|
package/dist/commands/status.js
CHANGED
|
@@ -510,9 +510,10 @@ async function displayVisualStatus(data, currentMode) {
|
|
|
510
510
|
console.log(chalk.green(`Specs System (${specs.length} specs)`));
|
|
511
511
|
|
|
512
512
|
// Show active specs first
|
|
513
|
-
const activeSpecs = specs.filter((s) => s.status === 'active');
|
|
513
|
+
const activeSpecs = specs.filter((s) => s.status === 'active' || s.status === 'in_progress');
|
|
514
514
|
const draftSpecs = specs.filter((s) => s.status === 'draft');
|
|
515
515
|
const completedSpecs = specs.filter((s) => s.status === 'completed');
|
|
516
|
+
const closedSpecs = specs.filter((s) => s.status === 'closed' || s.status === 'archived');
|
|
516
517
|
|
|
517
518
|
if (activeSpecs.length > 0) {
|
|
518
519
|
console.log(
|
|
@@ -543,10 +544,19 @@ async function displayVisualStatus(data, currentMode) {
|
|
|
543
544
|
});
|
|
544
545
|
}
|
|
545
546
|
}
|
|
547
|
+
if (closedSpecs.length > 0) {
|
|
548
|
+
console.log(
|
|
549
|
+
chalk.gray(
|
|
550
|
+
` Closed: ${closedSpecs.length} spec${closedSpecs.length > 1 ? 's' : ''}`
|
|
551
|
+
)
|
|
552
|
+
);
|
|
553
|
+
}
|
|
546
554
|
|
|
547
|
-
// Overall specs progress
|
|
555
|
+
// Overall specs progress (completed + closed both count as done)
|
|
548
556
|
const totalSpecs = specs.length;
|
|
549
|
-
const completedSpecsCount = specs.filter((s) =>
|
|
557
|
+
const completedSpecsCount = specs.filter((s) =>
|
|
558
|
+
s.status === 'completed' || s.status === 'closed' || s.status === 'archived'
|
|
559
|
+
).length;
|
|
550
560
|
const activeSpecsCount = specs.filter((s) => s.status === 'active').length;
|
|
551
561
|
const progressPercentage =
|
|
552
562
|
totalSpecs > 0 ? Math.round((completedSpecsCount / totalSpecs) * 100) : 0;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Spec Types Constants
|
|
3
|
-
* Defines spec types and their metadata for consistent display
|
|
2
|
+
* @fileoverview Spec Types and Status Constants
|
|
3
|
+
* Defines spec types, statuses, and their metadata for consistent display
|
|
4
4
|
* @author @darianrosebrook
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -37,6 +37,29 @@ const SPEC_TYPES = {
|
|
|
37
37
|
},
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Spec statuses and lifecycle metadata.
|
|
42
|
+
* Terminal statuses mean the spec is done — its scope restrictions
|
|
43
|
+
* should NOT be enforced by the scope guard.
|
|
44
|
+
*/
|
|
45
|
+
const SPEC_STATUSES = {
|
|
46
|
+
draft: { label: 'Draft', color: chalk.yellow, terminal: false },
|
|
47
|
+
active: { label: 'Active', color: chalk.green, terminal: false },
|
|
48
|
+
in_progress: { label: 'In Progress', color: chalk.green, terminal: false },
|
|
49
|
+
completed: { label: 'Completed', color: chalk.blue, terminal: true },
|
|
50
|
+
closed: { label: 'Closed', color: chalk.gray, terminal: true },
|
|
51
|
+
archived: { label: 'Archived', color: chalk.gray, terminal: true },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Status keys that indicate a spec is done (scope no longer enforced).
|
|
56
|
+
*/
|
|
57
|
+
const TERMINAL_STATUSES = Object.entries(SPEC_STATUSES)
|
|
58
|
+
.filter(([, v]) => v.terminal)
|
|
59
|
+
.map(([k]) => k);
|
|
60
|
+
|
|
40
61
|
module.exports = {
|
|
41
62
|
SPEC_TYPES,
|
|
63
|
+
SPEC_STATUSES,
|
|
64
|
+
TERMINAL_STATUSES,
|
|
42
65
|
};
|
package/dist/index.js
CHANGED
|
@@ -51,6 +51,7 @@ const { tutorialCommand } = require('./commands/tutorial');
|
|
|
51
51
|
const { planCommand } = require('./commands/plan');
|
|
52
52
|
const { worktreeCommand } = require('./commands/worktree');
|
|
53
53
|
const { sessionCommand } = require('./commands/session');
|
|
54
|
+
const { parallelCommand } = require('./commands/parallel');
|
|
54
55
|
|
|
55
56
|
// Import scaffold functionality
|
|
56
57
|
const { scaffoldProject, setScaffoldDependencies } = require('./scaffold');
|
|
@@ -263,7 +264,7 @@ specsCmd
|
|
|
263
264
|
specsCmd
|
|
264
265
|
.command('update <id>')
|
|
265
266
|
.description('Update spec properties')
|
|
266
|
-
.option('-s, --status <status>', 'Spec status (draft, active, completed)')
|
|
267
|
+
.option('-s, --status <status>', 'Spec status (draft, active, in_progress, completed, closed, archived)')
|
|
267
268
|
.option('--title <title>', 'Spec title')
|
|
268
269
|
.option('--description <desc>', 'Spec description')
|
|
269
270
|
.action((id, options) => specsCommand('update', { id, ...options }));
|
|
@@ -273,6 +274,11 @@ specsCmd
|
|
|
273
274
|
.description('Delete a spec')
|
|
274
275
|
.action((id) => specsCommand('delete', { id }));
|
|
275
276
|
|
|
277
|
+
specsCmd
|
|
278
|
+
.command('close <id>')
|
|
279
|
+
.description('Close a completed spec (removes scope enforcement)')
|
|
280
|
+
.action((id) => specsCommand('close', { id }));
|
|
281
|
+
|
|
276
282
|
specsCmd
|
|
277
283
|
.command('conflicts')
|
|
278
284
|
.description('Check for scope conflicts between specs')
|
|
@@ -429,6 +435,37 @@ sessionCmd
|
|
|
429
435
|
.description('Show session briefing for hooks/startup')
|
|
430
436
|
.action(() => sessionCommand('briefing'));
|
|
431
437
|
|
|
438
|
+
// Parallel command group
|
|
439
|
+
const parallelCmd = program
|
|
440
|
+
.command('parallel')
|
|
441
|
+
.description('Orchestrate parallel multi-agent workspaces');
|
|
442
|
+
|
|
443
|
+
parallelCmd
|
|
444
|
+
.command('setup <plan-file>')
|
|
445
|
+
.description('Create worktrees and sessions from a plan file')
|
|
446
|
+
.option('--base-branch <branch>', 'Base branch for all worktrees')
|
|
447
|
+
.action((planFile, options) => parallelCommand('setup', { planFile, ...options }));
|
|
448
|
+
|
|
449
|
+
parallelCmd
|
|
450
|
+
.command('status')
|
|
451
|
+
.description('Show all active parallel worktrees and sessions')
|
|
452
|
+
.action(() => parallelCommand('status'));
|
|
453
|
+
|
|
454
|
+
parallelCmd
|
|
455
|
+
.command('merge')
|
|
456
|
+
.description('Merge all parallel branches back to base')
|
|
457
|
+
.option('--strategy <strategy>', 'Merge strategy: rebase, merge, or squash', 'merge')
|
|
458
|
+
.option('--dry-run', 'Preview merge without executing', false)
|
|
459
|
+
.option('--force', 'Force merge even with detected conflicts', false)
|
|
460
|
+
.action((options) => parallelCommand('merge', options));
|
|
461
|
+
|
|
462
|
+
parallelCmd
|
|
463
|
+
.command('teardown')
|
|
464
|
+
.description('Destroy all parallel worktrees')
|
|
465
|
+
.option('--delete-branches', 'Also delete associated branches', false)
|
|
466
|
+
.option('--force', 'Force removal even if worktrees are dirty', false)
|
|
467
|
+
.action((options) => parallelCommand('teardown', options));
|
|
468
|
+
|
|
432
469
|
// Templates command
|
|
433
470
|
program
|
|
434
471
|
.command('templates [subcommand]')
|
|
@@ -699,6 +736,7 @@ program.exitOverride((err) => {
|
|
|
699
736
|
'tool',
|
|
700
737
|
'worktree',
|
|
701
738
|
'session',
|
|
739
|
+
'parallel',
|
|
702
740
|
];
|
|
703
741
|
const similar = findSimilarCommand(commandName, validCommands);
|
|
704
742
|
|
|
@@ -792,6 +830,9 @@ if (require.main === module) {
|
|
|
792
830
|
'hooks',
|
|
793
831
|
'burnup',
|
|
794
832
|
'tool',
|
|
833
|
+
'worktree',
|
|
834
|
+
'session',
|
|
835
|
+
'parallel',
|
|
795
836
|
];
|
|
796
837
|
const similar = findSimilarCommand(commandName, validCommands);
|
|
797
838
|
|
|
@@ -802,7 +843,7 @@ if (require.main === module) {
|
|
|
802
843
|
}
|
|
803
844
|
|
|
804
845
|
console.error(
|
|
805
|
-
chalk.yellow('Available commands: init, validate, scaffold, provenance, hooks')
|
|
846
|
+
chalk.yellow('Available commands: init, validate, scaffold, provenance, hooks, parallel')
|
|
806
847
|
);
|
|
807
848
|
console.error(chalk.yellow('Try: caws --help for full command list'));
|
|
808
849
|
console.error(
|