@paths.design/caws-cli 10.0.1 → 10.1.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/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 +148 -1
- package/dist/commands/status.js +8 -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 +81 -1
- package/dist/gates/budget-limit.js +6 -1
- package/dist/gates/spec-completeness.js +8 -1
- package/dist/index.js +27 -0
- package/dist/policy/PolicyManager.js +9 -7
- package/dist/session/session-manager.js +34 -0
- package/dist/templates/.caws/schemas/policy.schema.json +96 -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/settings.json +5 -0
- package/dist/templates/CLAUDE.md +34 -0
- package/dist/templates/agents.md +21 -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 +99 -9
- package/dist/waivers-manager.js +84 -0
- package/dist/worktree/worktree-manager.js +214 -8
- package/package.json +5 -4
- package/templates/.caws/schemas/policy.schema.json +96 -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/settings.json +5 -0
- package/templates/CLAUDE.md +34 -0
- package/templates/agents.md +21 -0
|
@@ -204,31 +204,83 @@ if command -v node >/dev/null 2>&1; then
|
|
|
204
204
|
process.exit(0);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
|
|
208
|
-
const specs = [];
|
|
207
|
+
const projectDir = '$PROJECT_DIR';
|
|
209
208
|
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
209
|
+
// --- Authoritative spec detection ---
|
|
210
|
+
// If we are inside a worktree with a bound specId, ONLY check that spec.
|
|
211
|
+
// This prevents unrelated specs from blocking writes via broad scope.out.
|
|
212
|
+
let authoritativeSpec = null;
|
|
213
|
+
let mode = 'union';
|
|
214
|
+
|
|
215
|
+
const registryPath = path.join(projectDir, '.caws', 'worktrees.json');
|
|
216
|
+
const cwd = process.cwd();
|
|
217
|
+
const worktreesBase = path.join(projectDir, '.caws', 'worktrees');
|
|
218
|
+
|
|
219
|
+
if (cwd.startsWith(worktreesBase + '/')) {
|
|
220
|
+
const relative = cwd.slice(worktreesBase.length + 1);
|
|
221
|
+
const worktreeName = relative.split('/')[0];
|
|
222
|
+
|
|
223
|
+
if (worktreeName && fs.existsSync(registryPath)) {
|
|
224
|
+
try {
|
|
225
|
+
const reg = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
226
|
+
const entry = reg.worktrees && reg.worktrees[worktreeName];
|
|
227
|
+
|
|
228
|
+
if (entry && entry.specId) {
|
|
229
|
+
// Try to load the bound spec
|
|
230
|
+
const specsDir = '$SPECS_DIR';
|
|
231
|
+
const specCandidates = [
|
|
232
|
+
path.join(specsDir, entry.specId + '.yaml'),
|
|
233
|
+
path.join(specsDir, entry.specId + '.yml'),
|
|
234
|
+
];
|
|
235
|
+
for (const candidate of specCandidates) {
|
|
236
|
+
if (fs.existsSync(candidate)) {
|
|
237
|
+
try {
|
|
238
|
+
const s = yaml.load(fs.readFileSync(candidate, 'utf8'));
|
|
239
|
+
if (s && !TERMINAL.has(s.status)) {
|
|
240
|
+
// Verify mutual binding: spec must also reference this worktree
|
|
241
|
+
if (s.worktree === worktreeName) {
|
|
242
|
+
authoritativeSpec = { source: path.basename(candidate), spec: s };
|
|
243
|
+
mode = 'authoritative';
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} catch (_) {}
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} catch (_) {}
|
|
252
|
+
}
|
|
219
253
|
}
|
|
220
254
|
|
|
221
|
-
//
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
255
|
+
// --- Collect specs based on mode ---
|
|
256
|
+
const specs = [];
|
|
257
|
+
|
|
258
|
+
if (authoritativeSpec) {
|
|
259
|
+
// Authoritative: only the bound spec matters
|
|
260
|
+
specs.push(authoritativeSpec);
|
|
261
|
+
} else {
|
|
262
|
+
// Union: load all active specs
|
|
263
|
+
const mainSpec = '$SPEC_FILE';
|
|
264
|
+
if (fs.existsSync(mainSpec)) {
|
|
225
265
|
try {
|
|
226
|
-
const s = yaml.load(fs.readFileSync(
|
|
266
|
+
const s = yaml.load(fs.readFileSync(mainSpec, 'utf8'));
|
|
227
267
|
if (s && !TERMINAL.has(s.status)) {
|
|
228
|
-
specs.push({ source:
|
|
268
|
+
specs.push({ source: 'working-spec', spec: s });
|
|
229
269
|
}
|
|
230
270
|
} catch (_) {}
|
|
231
271
|
}
|
|
272
|
+
|
|
273
|
+
const specsDir = '$SPECS_DIR';
|
|
274
|
+
if (fs.existsSync(specsDir)) {
|
|
275
|
+
for (const f of fs.readdirSync(specsDir).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'))) {
|
|
276
|
+
try {
|
|
277
|
+
const s = yaml.load(fs.readFileSync(path.join(specsDir, f), 'utf8'));
|
|
278
|
+
if (s && !TERMINAL.has(s.status)) {
|
|
279
|
+
specs.push({ source: f, spec: s });
|
|
280
|
+
}
|
|
281
|
+
} catch (_) {}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
232
284
|
}
|
|
233
285
|
|
|
234
286
|
// No active specs — allow everything
|
|
@@ -237,18 +289,18 @@ if command -v node >/dev/null 2>&1; then
|
|
|
237
289
|
process.exit(0);
|
|
238
290
|
}
|
|
239
291
|
|
|
240
|
-
// Check scope.out
|
|
292
|
+
// Check scope.out — any match blocks
|
|
241
293
|
for (const { source, spec } of specs) {
|
|
242
294
|
for (const pattern of (spec.scope?.out || [])) {
|
|
243
295
|
const regex = globToRegex(pattern);
|
|
244
296
|
if (regex.test(filePath)) {
|
|
245
|
-
console.log('out_of_scope:' + source + ':' + pattern);
|
|
297
|
+
console.log('out_of_scope:' + mode + ':' + source + ':' + pattern);
|
|
246
298
|
process.exit(0);
|
|
247
299
|
}
|
|
248
300
|
}
|
|
249
301
|
}
|
|
250
302
|
|
|
251
|
-
//
|
|
303
|
+
// scope.in — file must match at least one pattern
|
|
252
304
|
const allInScope = specs.flatMap(({ spec }) => spec.scope?.in || []);
|
|
253
305
|
if (allInScope.length > 0) {
|
|
254
306
|
let found = false;
|
|
@@ -260,7 +312,7 @@ if command -v node >/dev/null 2>&1; then
|
|
|
260
312
|
}
|
|
261
313
|
}
|
|
262
314
|
if (!found) {
|
|
263
|
-
console.log('not_in_scope');
|
|
315
|
+
console.log('not_in_scope:' + mode);
|
|
264
316
|
process.exit(0);
|
|
265
317
|
}
|
|
266
318
|
}
|
|
@@ -282,18 +334,45 @@ if command -v node >/dev/null 2>&1; then
|
|
|
282
334
|
|
|
283
335
|
if [[ "$SCOPE_CHECK" == out_of_scope:* ]]; then
|
|
284
336
|
DETAIL="${SCOPE_CHECK#out_of_scope:}"
|
|
285
|
-
|
|
286
|
-
|
|
337
|
+
# Format: mode:source:pattern
|
|
338
|
+
MODE="${DETAIL%%:*}"
|
|
339
|
+
REST="${DETAIL#*:}"
|
|
340
|
+
SOURCE="${REST%%:*}"
|
|
341
|
+
PATTERN="${REST#*:}"
|
|
287
342
|
echo "BLOCKED: $REL_PATH is excluded by scope.out in $SOURCE (pattern: $PATTERN)"
|
|
288
|
-
|
|
289
|
-
|
|
343
|
+
if [[ "$MODE" == "union" ]]; then
|
|
344
|
+
echo " Mode: union (no authoritative spec bound to this worktree)"
|
|
345
|
+
echo " The scope guard is checking ALL active specs because the worktree<->spec"
|
|
346
|
+
echo " binding is missing. An unrelated spec may be blocking this edit."
|
|
347
|
+
echo " Fix: caws worktree bind <your-spec-id>"
|
|
348
|
+
echo " Diagnose: caws scope show"
|
|
349
|
+
else
|
|
350
|
+
echo " Mode: authoritative (checking only your bound spec)"
|
|
351
|
+
echo " To modify scope, update the spec's scope.out field"
|
|
352
|
+
fi
|
|
353
|
+
exit 2
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
if [[ "$SCOPE_CHECK" == not_in_scope:* ]]; then
|
|
357
|
+
MODE="${SCOPE_CHECK#not_in_scope:}"
|
|
358
|
+
echo "BLOCKED: $REL_PATH is not in the defined scope.in of any active spec"
|
|
359
|
+
if [[ "$MODE" == "union" ]]; then
|
|
360
|
+
echo " Mode: union (no authoritative spec bound to this worktree)"
|
|
361
|
+
echo " The scope guard is checking ALL active specs because the worktree<->spec"
|
|
362
|
+
echo " binding is missing. Your file may be in a scope that no spec covers."
|
|
363
|
+
echo " Fix: caws worktree bind <your-spec-id>"
|
|
364
|
+
echo " Diagnose: caws scope show"
|
|
365
|
+
else
|
|
366
|
+
echo " Mode: authoritative (checking only your bound spec)"
|
|
367
|
+
echo " To modify scope, update the spec's scope.in field"
|
|
368
|
+
fi
|
|
290
369
|
exit 2
|
|
291
370
|
fi
|
|
292
371
|
|
|
372
|
+
# Legacy fallback for unqualified not_in_scope (shouldn't happen with updated logic)
|
|
293
373
|
if [[ "$SCOPE_CHECK" == "not_in_scope" ]]; then
|
|
294
374
|
echo "BLOCKED: $REL_PATH is not in the defined scope.in of any active spec"
|
|
295
|
-
echo "
|
|
296
|
-
echo " To modify scope, update the spec's scope.in field"
|
|
375
|
+
echo " Diagnose: caws scope show"
|
|
297
376
|
exit 2
|
|
298
377
|
fi
|
|
299
378
|
fi
|
|
@@ -66,14 +66,107 @@ if [[ "$WT_COUNT" -le 0 ]] 2>/dev/null; then
|
|
|
66
66
|
exit 0
|
|
67
67
|
fi
|
|
68
68
|
|
|
69
|
-
#
|
|
69
|
+
# Main is blocked during active worktree work because shared unstaged state makes
|
|
70
|
+
# agents stash, checkpoint, or explain each other's edits. Keep direct main edits
|
|
71
|
+
# limited to coordination/docs/scratch paths, then use active spec scope below to
|
|
72
|
+
# permit only files no worktree claims.
|
|
70
73
|
if [[ -n "$FILE_PATH" ]]; then
|
|
71
74
|
case "$FILE_PATH" in
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
.caws/*|*/.caws/*) exit 0 ;;
|
|
76
|
+
.claude/*|*/.claude/*) exit 0 ;;
|
|
77
|
+
.gitignore|*/.gitignore) exit 0 ;;
|
|
78
|
+
.tmp/*|*/.tmp/*) exit 0 ;;
|
|
79
|
+
tmp/*|*/tmp/*) exit 0 ;;
|
|
80
|
+
.archive/*|*/.archive/*) exit 0 ;;
|
|
81
|
+
.githooks/*|*/.githooks/*) exit 0 ;;
|
|
82
|
+
.github/*|*/.github/*) exit 0 ;;
|
|
83
|
+
docs/*|*/docs/*) exit 0 ;;
|
|
74
84
|
esac
|
|
75
85
|
fi
|
|
76
86
|
|
|
87
|
+
if [[ -n "$FILE_PATH" ]]; then
|
|
88
|
+
REL_PATH="$FILE_PATH"
|
|
89
|
+
if [[ "$FILE_PATH" == "$PROJECT_DIR"/* ]]; then
|
|
90
|
+
REL_PATH="${FILE_PATH#$PROJECT_DIR/}"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
SPEC_CONTENTION_CHECK=$(PROJECT_DIR="$PROJECT_DIR" CURRENT_BRANCH="$CURRENT_BRANCH" REL_PATH="$REL_PATH" node -e "
|
|
94
|
+
var fs = require('fs');
|
|
95
|
+
var path = require('path');
|
|
96
|
+
var yaml;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
yaml = require('js-yaml');
|
|
100
|
+
} catch (_) {
|
|
101
|
+
console.log('unknown:no-js-yaml');
|
|
102
|
+
process.exit(0);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function globToRegExp(pattern) {
|
|
106
|
+
return new RegExp(String(pattern).replace(/\\*/g, '.*').replace(/\\?/g, '.'));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
var projectDir = process.env.PROJECT_DIR;
|
|
111
|
+
var currentBranch = process.env.CURRENT_BRANCH;
|
|
112
|
+
var relPath = process.env.REL_PATH;
|
|
113
|
+
var registryPath = path.join(projectDir, '.caws', 'worktrees.json');
|
|
114
|
+
var registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
115
|
+
var worktrees = Object.values(registry.worktrees || {}).filter(function(w) {
|
|
116
|
+
return (w.status === 'active' || w.status === 'fresh' || w.status === 'merged') && w.baseBranch === currentBranch;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (worktrees.length === 0) {
|
|
120
|
+
console.log('unknown:no-registry-worktrees');
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (var wi = 0; wi < worktrees.length; wi++) {
|
|
125
|
+
var wt = worktrees[wi];
|
|
126
|
+
if (!wt.specId) {
|
|
127
|
+
console.log('unknown:missing-specId:' + (wt.name || 'unnamed'));
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
var specPath = path.join(projectDir, '.caws', 'specs', wt.specId + '.yaml');
|
|
132
|
+
if (!fs.existsSync(specPath)) {
|
|
133
|
+
specPath = path.join(projectDir, '.caws', 'specs', wt.specId + '.yml');
|
|
134
|
+
}
|
|
135
|
+
if (!fs.existsSync(specPath)) {
|
|
136
|
+
console.log('unknown:missing-spec:' + wt.specId);
|
|
137
|
+
process.exit(0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
var spec = yaml.load(fs.readFileSync(specPath, 'utf8')) || {};
|
|
141
|
+
var scope = spec.scope || {};
|
|
142
|
+
var patterns = []
|
|
143
|
+
.concat(Array.isArray(scope.in) ? scope.in : [])
|
|
144
|
+
.concat(Array.isArray(scope.out) ? scope.out : []);
|
|
145
|
+
|
|
146
|
+
if (patterns.length === 0) {
|
|
147
|
+
console.log('unknown:missing-scope:' + wt.specId);
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (var pi = 0; pi < patterns.length; pi++) {
|
|
152
|
+
if (globToRegExp(patterns[pi]).test(relPath)) {
|
|
153
|
+
console.log('claimed:' + (wt.name || wt.specId) + ':' + patterns[pi]);
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log('clear');
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.log('unknown:' + error.message);
|
|
162
|
+
}
|
|
163
|
+
" 2>/dev/null || echo "unknown:node-error")
|
|
164
|
+
|
|
165
|
+
if [[ "$SPEC_CONTENTION_CHECK" == "clear" ]]; then
|
|
166
|
+
exit 0
|
|
167
|
+
fi
|
|
168
|
+
fi
|
|
169
|
+
|
|
77
170
|
# Allow edits during an active merge (conflict resolution).
|
|
78
171
|
# The worktree-isolation rules explicitly permit merge commits on the base branch.
|
|
79
172
|
# Conflict resolution requires Write/Edit on the conflicted files.
|
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
{
|
|
30
30
|
"matcher": "Write|Edit",
|
|
31
31
|
"hooks": [
|
|
32
|
+
{
|
|
33
|
+
"type": "command",
|
|
34
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protected-paths.sh",
|
|
35
|
+
"timeout": 5
|
|
36
|
+
},
|
|
32
37
|
{
|
|
33
38
|
"type": "command",
|
|
34
39
|
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/worktree-write-guard.sh",
|
package/templates/CLAUDE.md
CHANGED
|
@@ -29,6 +29,9 @@ Before writing code, check the canonical spec for the current feature:
|
|
|
29
29
|
# Create a feature spec for isolated work
|
|
30
30
|
caws specs create FEAT-001 --type feature --title "description"
|
|
31
31
|
|
|
32
|
+
# If you're in a CAWS worktree, the created spec should record it:
|
|
33
|
+
# worktree: <worktree-name>
|
|
34
|
+
|
|
32
35
|
# Validate the feature spec
|
|
33
36
|
caws validate --spec-id FEAT-001
|
|
34
37
|
|
|
@@ -68,6 +71,7 @@ Canonical feature specs live at `.caws/specs/<ID>.yaml` (create with `caws specs
|
|
|
68
71
|
|
|
69
72
|
- **Risk tier**: Quality requirements (T1: critical, T2: standard, T3: low risk)
|
|
70
73
|
- **Mode**: The type of change (`feature`, `refactor`, `fix`, `doc`, `chore`) -- required
|
|
74
|
+
- **Worktree**: The owning CAWS worktree name for this spec (`worktree`) -- recommended for all isolated work
|
|
71
75
|
- **Blast radius**: Which modules are affected (`blast_radius.modules`) -- required
|
|
72
76
|
- **Operational rollback SLO**: Time target for rollback (e.g. `"30m"`) -- required
|
|
73
77
|
- **Scope**: Which files you can edit (`scope.in`) and which are off-limits (`scope.out`)
|
|
@@ -76,6 +80,36 @@ Canonical feature specs live at `.caws/specs/<ID>.yaml` (create with `caws specs
|
|
|
76
80
|
|
|
77
81
|
Always stay within scope boundaries and change budgets.
|
|
78
82
|
|
|
83
|
+
Recommended operating rule: one active feature spec, one active worktree. If a task has a worktree, record that ownership in the spec YAML with `worktree: <name>`.
|
|
84
|
+
|
|
85
|
+
### Scope and Worktree Binding
|
|
86
|
+
|
|
87
|
+
The scope guard enforces file edit boundaries based on your spec's `scope.in` and `scope.out` patterns. **How it enforces depends on whether your worktree is bound to a spec:**
|
|
88
|
+
|
|
89
|
+
- **Authoritative mode** (worktree bound to a spec): Only your spec's scope patterns are checked. Other agents' specs cannot block your edits. This is the correct state.
|
|
90
|
+
- **Union mode** (no binding): The guard checks ALL active specs. Any `scope.out` from any spec can block you, even unrelated ones. This is the common source of "why is spec X blocking me?" confusion.
|
|
91
|
+
|
|
92
|
+
**The mutual binding** requires both sides:
|
|
93
|
+
1. The worktree registry (`.caws/worktrees.json`) must have `specId` pointing to your spec
|
|
94
|
+
2. Your spec (`.caws/specs/<id>.yaml`) must have `worktree: <name>` pointing to your worktree
|
|
95
|
+
|
|
96
|
+
If either side is missing, the guard falls back to union mode.
|
|
97
|
+
|
|
98
|
+
**Quick commands:**
|
|
99
|
+
```bash
|
|
100
|
+
# See your effective scope and binding health
|
|
101
|
+
caws scope show
|
|
102
|
+
|
|
103
|
+
# Fix a broken binding
|
|
104
|
+
caws worktree bind <spec-id>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Recovery checklist** (when the scope guard blocks you unexpectedly):
|
|
108
|
+
1. Run `caws scope show` — check if you're in authoritative or union mode
|
|
109
|
+
2. If union mode: bind your spec with `caws worktree bind <spec-id>`
|
|
110
|
+
3. If authoritative but still blocked: the file is genuinely outside your spec's scope. Update your spec's `scope.in` if the file should be in scope, or request a waiver
|
|
111
|
+
4. Do NOT modify another spec's `scope.out` to unblock yourself — that defeats the isolation
|
|
112
|
+
|
|
79
113
|
> **Budget note**: `change_budget:` in a spec is informational documentation only. CAWS
|
|
80
114
|
> derives the enforced budget from `policy.yaml` keyed on `risk_tier`. The field in the
|
|
81
115
|
> spec is not used by `caws validate` for enforcement.
|
package/templates/agents.md
CHANGED
|
@@ -38,6 +38,27 @@ caws specs create my-feature --type feature --title "My Feature"
|
|
|
38
38
|
caws validate --spec-id my-feature
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## Scope and Worktree Binding
|
|
42
|
+
|
|
43
|
+
The scope guard enforces `scope.in` and `scope.out` from your spec. How it enforces depends on binding:
|
|
44
|
+
|
|
45
|
+
- **Authoritative mode** (worktree bound to a spec): Only your spec's scope is checked. Other agents' specs cannot block you.
|
|
46
|
+
- **Union mode** (no binding): ALL active specs are checked. Any `scope.out` from any spec can block you.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# See your effective scope and binding health
|
|
50
|
+
caws scope show
|
|
51
|
+
|
|
52
|
+
# Fix a broken binding
|
|
53
|
+
caws worktree bind <spec-id>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Recovery** (when blocked unexpectedly):
|
|
57
|
+
1. Run `caws scope show` to check mode and binding health
|
|
58
|
+
2. If union mode: `caws worktree bind <spec-id>`
|
|
59
|
+
3. If authoritative but blocked: update your spec's `scope.in`
|
|
60
|
+
4. Do NOT edit another spec's `scope.out` to unblock yourself
|
|
61
|
+
|
|
41
62
|
## Key Rules
|
|
42
63
|
|
|
43
64
|
1. **Stay in scope** -- only edit files listed in `scope.in`, never touch `scope.out`
|