@paths.design/caws-cli 10.0.1 → 10.2.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/README.md +13 -5
- package/dist/budget-derivation.js +221 -74
- package/dist/commands/agents.js +124 -0
- package/dist/commands/evaluate.js +26 -12
- package/dist/commands/gates.js +31 -4
- package/dist/commands/init.js +7 -4
- package/dist/commands/iterate.js +7 -3
- package/dist/commands/scope.js +264 -0
- package/dist/commands/sidecar.js +6 -3
- package/dist/commands/specs.js +359 -4
- package/dist/commands/status.js +29 -4
- package/dist/commands/templates.js +0 -8
- package/dist/commands/validate.js +34 -13
- package/dist/commands/verify-acs.js +25 -10
- package/dist/commands/waivers.js +147 -5
- package/dist/commands/worktree.js +200 -4
- package/dist/gates/budget-limit.js +6 -1
- package/dist/gates/scope-boundary.js +26 -7
- package/dist/gates/spec-completeness.js +8 -1
- package/dist/index.js +56 -0
- package/dist/policy/PolicyManager.js +14 -7
- package/dist/session/session-manager.js +34 -0
- package/dist/templates/.caws/schemas/policy.schema.json +101 -34
- package/dist/templates/.caws/schemas/scope.schema.json +3 -3
- package/dist/templates/.caws/schemas/waivers.schema.json +91 -21
- package/dist/templates/.caws/schemas/working-spec.schema.json +253 -89
- package/dist/templates/.caws/templates/working-spec.template.yml +3 -1
- package/dist/templates/.caws/tools/scope-guard.js +66 -15
- package/dist/templates/.claude/README.md +1 -1
- package/dist/templates/.claude/hooks/protected-paths.sh +39 -0
- package/dist/templates/.claude/hooks/scope-guard.sh +106 -27
- package/dist/templates/.claude/hooks/worktree-write-guard.sh +96 -3
- package/dist/templates/.claude/rules/worktree-isolation.md +21 -3
- package/dist/templates/.claude/settings.json +5 -0
- package/dist/templates/CLAUDE.md +56 -0
- package/dist/templates/agents.md +47 -0
- package/dist/utils/agent-display.js +210 -0
- package/dist/utils/agent-session.js +142 -0
- package/dist/utils/event-log.js +584 -0
- package/dist/utils/event-renderer.js +521 -0
- package/dist/utils/schema-validator.js +10 -2
- package/dist/utils/working-state.js +25 -0
- package/dist/validation/spec-validation.js +102 -9
- package/dist/waivers-manager.js +84 -0
- package/dist/worktree/worktree-manager.js +593 -26
- package/package.json +5 -4
- package/templates/.caws/schemas/policy.schema.json +101 -34
- package/templates/.caws/schemas/scope.schema.json +3 -3
- package/templates/.caws/schemas/waivers.schema.json +91 -21
- package/templates/.caws/schemas/working-spec.schema.json +253 -89
- package/templates/.caws/templates/working-spec.template.yml +3 -1
- package/templates/.caws/tools/scope-guard.js +66 -15
- package/templates/.claude/README.md +1 -1
- package/templates/.claude/hooks/protected-paths.sh +39 -0
- package/templates/.claude/hooks/scope-guard.sh +106 -27
- package/templates/.claude/hooks/worktree-write-guard.sh +96 -3
- package/templates/.claude/rules/worktree-isolation.md +21 -3
- package/templates/.claude/settings.json +5 -0
- package/templates/CLAUDE.md +56 -0
- package/templates/agents.md +47 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CAWS Scope CLI Command
|
|
3
|
+
* Inspects and displays effective scope boundaries for the current context
|
|
4
|
+
* @author @darianrosebrook
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
const yaml = require('js-yaml');
|
|
11
|
+
const {
|
|
12
|
+
getRepoRoot,
|
|
13
|
+
loadRegistry,
|
|
14
|
+
findFeatureSpecPath,
|
|
15
|
+
WORKTREES_DIR,
|
|
16
|
+
} = require('../worktree/worktree-manager');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Handle scope subcommands
|
|
20
|
+
* @param {string} subcommand - Subcommand name
|
|
21
|
+
* @param {Object} options - Command options
|
|
22
|
+
*/
|
|
23
|
+
async function scopeCommand(subcommand, options = {}) {
|
|
24
|
+
try {
|
|
25
|
+
switch (subcommand) {
|
|
26
|
+
case 'show':
|
|
27
|
+
return handleShow(options);
|
|
28
|
+
default:
|
|
29
|
+
console.error(chalk.red(`Unknown scope subcommand: ${subcommand}`));
|
|
30
|
+
console.log(chalk.blue('Available: show'));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(chalk.red(`${error.message}`));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Detect if current working directory is inside a worktree
|
|
41
|
+
* @param {string} root - Repository root
|
|
42
|
+
* @returns {{ inWorktree: boolean, worktreeName: string|null }}
|
|
43
|
+
*/
|
|
44
|
+
function detectWorktreeContext(root) {
|
|
45
|
+
const cwd = process.cwd();
|
|
46
|
+
const worktreesBase = path.join(root, WORKTREES_DIR);
|
|
47
|
+
|
|
48
|
+
if (!cwd.startsWith(worktreesBase + path.sep) && cwd !== worktreesBase) {
|
|
49
|
+
return { inWorktree: false, worktreeName: null };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Extract worktree name: first path segment after the worktrees dir
|
|
53
|
+
const relative = path.relative(worktreesBase, cwd);
|
|
54
|
+
const worktreeName = relative.split(path.sep)[0];
|
|
55
|
+
|
|
56
|
+
if (!worktreeName) {
|
|
57
|
+
return { inWorktree: false, worktreeName: null };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { inWorktree: true, worktreeName };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Load a spec file and return its parsed contents
|
|
65
|
+
* @param {string} specPath - Absolute path to spec YAML
|
|
66
|
+
* @returns {Object|null}
|
|
67
|
+
*/
|
|
68
|
+
function loadSpec(specPath) {
|
|
69
|
+
try {
|
|
70
|
+
if (!fs.existsSync(specPath)) return null;
|
|
71
|
+
const content = fs.readFileSync(specPath, 'utf8');
|
|
72
|
+
return yaml.load(content);
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find all active spec files in .caws/specs/
|
|
80
|
+
* @param {string} root - Repository root
|
|
81
|
+
* @returns {Array<{ id: string, path: string, data: Object }>}
|
|
82
|
+
*/
|
|
83
|
+
function findAllActiveSpecs(root) {
|
|
84
|
+
const specsDir = path.join(root, '.caws', 'specs');
|
|
85
|
+
if (!fs.existsSync(specsDir)) return [];
|
|
86
|
+
|
|
87
|
+
const files = fs.readdirSync(specsDir).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
88
|
+
const specs = [];
|
|
89
|
+
|
|
90
|
+
for (const file of files) {
|
|
91
|
+
const specPath = path.join(specsDir, file);
|
|
92
|
+
const data = loadSpec(specPath);
|
|
93
|
+
if (!data) continue;
|
|
94
|
+
|
|
95
|
+
// Skip closed/archived specs
|
|
96
|
+
const status = (data.status || '').toLowerCase();
|
|
97
|
+
if (status === 'closed' || status === 'archived') continue;
|
|
98
|
+
|
|
99
|
+
const id = path.basename(file, path.extname(file));
|
|
100
|
+
specs.push({ id, path: specPath, data });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return specs;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Print scope patterns for a spec
|
|
108
|
+
* @param {Object} data - Parsed spec YAML
|
|
109
|
+
* @param {string} indent - Indentation prefix
|
|
110
|
+
*/
|
|
111
|
+
function printScopePatterns(data, indent = ' ') {
|
|
112
|
+
const scope = data.scope || {};
|
|
113
|
+
const scopeIn = scope.in || scope.include || [];
|
|
114
|
+
const scopeOut = scope.out || scope.exclude || [];
|
|
115
|
+
|
|
116
|
+
if (scopeIn.length > 0) {
|
|
117
|
+
console.log(chalk.green(`${indent}scope.in:`));
|
|
118
|
+
for (const pattern of scopeIn) {
|
|
119
|
+
console.log(chalk.gray(`${indent} - ${pattern}`));
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
console.log(chalk.yellow(`${indent}scope.in: (none defined)`));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (scopeOut.length > 0) {
|
|
126
|
+
console.log(chalk.red(`${indent}scope.out:`));
|
|
127
|
+
for (const pattern of scopeOut) {
|
|
128
|
+
console.log(chalk.gray(`${indent} - ${pattern}`));
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
console.log(chalk.gray(`${indent}scope.out: (none)`));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Handle the 'show' subcommand
|
|
137
|
+
* @param {Object} options - Command options
|
|
138
|
+
*/
|
|
139
|
+
function handleShow(_options) {
|
|
140
|
+
const root = getRepoRoot();
|
|
141
|
+
const { inWorktree, worktreeName } = detectWorktreeContext(root);
|
|
142
|
+
|
|
143
|
+
console.log(chalk.bold.cyan('CAWS Scope Inspector'));
|
|
144
|
+
console.log(chalk.cyan('='.repeat(50)));
|
|
145
|
+
console.log('');
|
|
146
|
+
|
|
147
|
+
if (inWorktree) {
|
|
148
|
+
return handleAuthoritativeMode(root, worktreeName);
|
|
149
|
+
} else {
|
|
150
|
+
return handleUnionMode(root);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Handle authoritative mode: agent is inside a worktree with a bound spec
|
|
156
|
+
* @param {string} root - Repository root
|
|
157
|
+
* @param {string} worktreeName - Name of the worktree
|
|
158
|
+
*/
|
|
159
|
+
function handleAuthoritativeMode(root, worktreeName) {
|
|
160
|
+
console.log(chalk.white(`Worktree: ${chalk.bold(worktreeName)}`));
|
|
161
|
+
|
|
162
|
+
const registry = loadRegistry(root);
|
|
163
|
+
const entry = registry.worktrees ? registry.worktrees[worktreeName] : null;
|
|
164
|
+
|
|
165
|
+
if (!entry) {
|
|
166
|
+
console.log(chalk.red(`Worktree '${worktreeName}' not found in registry.`));
|
|
167
|
+
console.log(chalk.yellow('The scope guard is operating in union mode (all active specs).'));
|
|
168
|
+
return handleUnionMode(root);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const specId = entry.specId;
|
|
172
|
+
|
|
173
|
+
if (!specId) {
|
|
174
|
+
console.log(chalk.yellow('Mode: union (no spec bound to this worktree)'));
|
|
175
|
+
console.log('');
|
|
176
|
+
console.log(chalk.yellow('This worktree has no spec binding. The scope guard checks'));
|
|
177
|
+
console.log(chalk.yellow('against the union of all active specs.'));
|
|
178
|
+
console.log('');
|
|
179
|
+
console.log(chalk.blue('To bind a spec: caws worktree bind <spec-id>'));
|
|
180
|
+
console.log('');
|
|
181
|
+
return handleUnionMode(root);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Load the spec
|
|
185
|
+
const specPath = findFeatureSpecPath(root, specId);
|
|
186
|
+
if (!specPath) {
|
|
187
|
+
console.log(chalk.red(`Bound spec '${specId}' not found on disk.`));
|
|
188
|
+
console.log(chalk.yellow('Fix: recreate the spec or rebind with a valid spec ID.'));
|
|
189
|
+
console.log(chalk.blue(` caws worktree bind <valid-spec-id>`));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const specData = loadSpec(specPath);
|
|
194
|
+
if (!specData) {
|
|
195
|
+
console.log(chalk.red(`Failed to parse spec file: ${specPath}`));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log(chalk.green(`Mode: authoritative (single bound spec)`));
|
|
200
|
+
console.log(chalk.white(`Spec: ${chalk.bold(specId)}`));
|
|
201
|
+
if (specData.title) {
|
|
202
|
+
console.log(chalk.gray(`Title: ${specData.title}`));
|
|
203
|
+
}
|
|
204
|
+
console.log('');
|
|
205
|
+
|
|
206
|
+
// Print scope patterns
|
|
207
|
+
printScopePatterns(specData);
|
|
208
|
+
console.log('');
|
|
209
|
+
|
|
210
|
+
// Check binding health: mutual reference
|
|
211
|
+
const specWorktreeRef = specData.worktree || null;
|
|
212
|
+
const registrySpecRef = specId;
|
|
213
|
+
|
|
214
|
+
if (specWorktreeRef !== worktreeName) {
|
|
215
|
+
console.log(chalk.yellow('Binding health: BROKEN'));
|
|
216
|
+
console.log(chalk.yellow(` Registry points to spec '${registrySpecRef}'`));
|
|
217
|
+
console.log(chalk.yellow(` Spec 'worktree' field: ${specWorktreeRef || '(missing)'} (expected: ${worktreeName})`));
|
|
218
|
+
console.log('');
|
|
219
|
+
console.log(chalk.blue(`Fix: caws worktree bind ${specId}`));
|
|
220
|
+
} else {
|
|
221
|
+
console.log(chalk.green('Binding health: OK'));
|
|
222
|
+
console.log(chalk.gray(` Registry -> spec: ${registrySpecRef}`));
|
|
223
|
+
console.log(chalk.gray(` Spec -> worktree: ${specWorktreeRef}`));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Handle union mode: no worktree or no spec binding
|
|
229
|
+
* @param {string} root - Repository root
|
|
230
|
+
*/
|
|
231
|
+
function handleUnionMode(root) {
|
|
232
|
+
const specs = findAllActiveSpecs(root);
|
|
233
|
+
|
|
234
|
+
if (specs.length === 0) {
|
|
235
|
+
console.log(chalk.gray('Mode: union (no active specs found)'));
|
|
236
|
+
console.log('');
|
|
237
|
+
console.log(chalk.gray('No active feature specs in .caws/specs/.'));
|
|
238
|
+
console.log(chalk.gray('The scope guard has no patterns to enforce.'));
|
|
239
|
+
console.log('');
|
|
240
|
+
console.log(chalk.blue('Create a spec: caws specs create <id> --title "description"'));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
console.log(chalk.white('Mode: union (checking all active specs)'));
|
|
245
|
+
console.log(chalk.gray(`Active specs: ${specs.length}`));
|
|
246
|
+
console.log('');
|
|
247
|
+
|
|
248
|
+
for (const spec of specs) {
|
|
249
|
+
const statusLabel = spec.data.status || 'draft';
|
|
250
|
+
console.log(chalk.white(` ${chalk.bold(spec.id)} [${statusLabel}]`));
|
|
251
|
+
if (spec.data.title) {
|
|
252
|
+
console.log(chalk.gray(` Title: ${spec.data.title}`));
|
|
253
|
+
}
|
|
254
|
+
printScopePatterns(spec.data, ' ');
|
|
255
|
+
|
|
256
|
+
// Check if this spec has a worktree binding
|
|
257
|
+
if (spec.data.worktree) {
|
|
258
|
+
console.log(chalk.gray(` worktree: ${spec.data.worktree}`));
|
|
259
|
+
}
|
|
260
|
+
console.log('');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
module.exports = { scopeCommand };
|
package/dist/commands/sidecar.js
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
const chalk = require('chalk');
|
|
9
9
|
const { resolveSpec } = require('../utils/spec-resolver');
|
|
10
|
-
|
|
10
|
+
// EVLOG-002 Phase 2 read flip: sidecars read state from the event log via the
|
|
11
|
+
// pure renderer. loadStateFromEvents matches loadState's null contract exactly,
|
|
12
|
+
// so the existing "state may be null — sidecars handle that" behavior stays.
|
|
13
|
+
const { loadStateFromEvents } = require('../utils/event-renderer');
|
|
11
14
|
const { commandWrapper } = require('../utils/command-wrapper');
|
|
12
15
|
const { SIDECARS, formatSidecarText } = require('../sidecars');
|
|
13
16
|
|
|
@@ -45,8 +48,8 @@ async function sidecarCommand(subcommand, options = {}) {
|
|
|
45
48
|
process.exit(1);
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
// Load working state (may be null — sidecars handle that)
|
|
49
|
-
const state =
|
|
51
|
+
// Load working state (may be null — sidecars handle that; EVLOG-002: from event log)
|
|
52
|
+
const state = loadStateFromEvents(spec.id);
|
|
50
53
|
|
|
51
54
|
// Build sidecar-specific options
|
|
52
55
|
const sidecarOptions = {};
|