@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.
Files changed (60) hide show
  1. package/README.md +13 -5
  2. package/dist/budget-derivation.js +221 -74
  3. package/dist/commands/agents.js +124 -0
  4. package/dist/commands/evaluate.js +26 -12
  5. package/dist/commands/gates.js +31 -4
  6. package/dist/commands/init.js +7 -4
  7. package/dist/commands/iterate.js +7 -3
  8. package/dist/commands/scope.js +264 -0
  9. package/dist/commands/sidecar.js +6 -3
  10. package/dist/commands/specs.js +359 -4
  11. package/dist/commands/status.js +29 -4
  12. package/dist/commands/templates.js +0 -8
  13. package/dist/commands/validate.js +34 -13
  14. package/dist/commands/verify-acs.js +25 -10
  15. package/dist/commands/waivers.js +147 -5
  16. package/dist/commands/worktree.js +200 -4
  17. package/dist/gates/budget-limit.js +6 -1
  18. package/dist/gates/scope-boundary.js +26 -7
  19. package/dist/gates/spec-completeness.js +8 -1
  20. package/dist/index.js +56 -0
  21. package/dist/policy/PolicyManager.js +14 -7
  22. package/dist/session/session-manager.js +34 -0
  23. package/dist/templates/.caws/schemas/policy.schema.json +101 -34
  24. package/dist/templates/.caws/schemas/scope.schema.json +3 -3
  25. package/dist/templates/.caws/schemas/waivers.schema.json +91 -21
  26. package/dist/templates/.caws/schemas/working-spec.schema.json +253 -89
  27. package/dist/templates/.caws/templates/working-spec.template.yml +3 -1
  28. package/dist/templates/.caws/tools/scope-guard.js +66 -15
  29. package/dist/templates/.claude/README.md +1 -1
  30. package/dist/templates/.claude/hooks/protected-paths.sh +39 -0
  31. package/dist/templates/.claude/hooks/scope-guard.sh +106 -27
  32. package/dist/templates/.claude/hooks/worktree-write-guard.sh +96 -3
  33. package/dist/templates/.claude/rules/worktree-isolation.md +21 -3
  34. package/dist/templates/.claude/settings.json +5 -0
  35. package/dist/templates/CLAUDE.md +56 -0
  36. package/dist/templates/agents.md +47 -0
  37. package/dist/utils/agent-display.js +210 -0
  38. package/dist/utils/agent-session.js +142 -0
  39. package/dist/utils/event-log.js +584 -0
  40. package/dist/utils/event-renderer.js +521 -0
  41. package/dist/utils/schema-validator.js +10 -2
  42. package/dist/utils/working-state.js +25 -0
  43. package/dist/validation/spec-validation.js +102 -9
  44. package/dist/waivers-manager.js +84 -0
  45. package/dist/worktree/worktree-manager.js +593 -26
  46. package/package.json +5 -4
  47. package/templates/.caws/schemas/policy.schema.json +101 -34
  48. package/templates/.caws/schemas/scope.schema.json +3 -3
  49. package/templates/.caws/schemas/waivers.schema.json +91 -21
  50. package/templates/.caws/schemas/working-spec.schema.json +253 -89
  51. package/templates/.caws/templates/working-spec.template.yml +3 -1
  52. package/templates/.caws/tools/scope-guard.js +66 -15
  53. package/templates/.claude/README.md +1 -1
  54. package/templates/.claude/hooks/protected-paths.sh +39 -0
  55. package/templates/.claude/hooks/scope-guard.sh +106 -27
  56. package/templates/.claude/hooks/worktree-write-guard.sh +96 -3
  57. package/templates/.claude/rules/worktree-isolation.md +21 -3
  58. package/templates/.claude/settings.json +5 -0
  59. package/templates/CLAUDE.md +56 -0
  60. package/templates/agents.md +47 -0
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "title": "CAWS Working Spec",
4
4
  "type": "object",
5
5
  "required": [
@@ -15,79 +15,178 @@
15
15
  "non_functional",
16
16
  "contracts"
17
17
  ],
18
+ "not": {
19
+ "required": [
20
+ "change_budget"
21
+ ]
22
+ },
18
23
  "properties": {
19
- "id": { "type": "string", "pattern": "^[A-Z]{2,6}-\\d{3,4}$" },
20
- "title": { "type": "string", "minLength": 10, "maxLength": 200 },
21
- "type": { "type": "string", "enum": ["feature", "fix", "refactor", "chore", "docs"] },
24
+ "id": {
25
+ "type": "string",
26
+ "pattern": "^[A-Z][A-Z0-9]*(-[A-Z0-9]+)*-\\d+[a-z]*$",
27
+ "description": "Unique identifier for the change. Format: uppercase/digit PREFIX segments separated by dashes, final segment is one+ digits with an optional lowercase suffix (e.g. CAWSFIX-16, P03-TRUTH-001, ALG-001A-HARDEN-01, APC-01a). Matches SPEC_ID_PATTERN in spec-validation.js (CAWSFIX-25 alignment)."
28
+ },
29
+ "title": {
30
+ "type": "string",
31
+ "minLength": 10,
32
+ "maxLength": 200,
33
+ "description": "Clear, descriptive title"
34
+ },
35
+ "type": {
36
+ "type": "string",
37
+ "enum": [
38
+ "feature",
39
+ "fix",
40
+ "refactor",
41
+ "chore",
42
+ "doc",
43
+ "docs"
44
+ ],
45
+ "description": "Type of work (informational; `mode` is the enforced gate input)"
46
+ },
22
47
  "status": {
23
48
  "type": "string",
24
- "enum": ["draft", "active", "in_progress", "completed", "closed", "archived"]
49
+ "enum": [
50
+ "draft",
51
+ "active",
52
+ "in_progress",
53
+ "completed",
54
+ "closed",
55
+ "archived"
56
+ ]
57
+ },
58
+ "created_at": {
59
+ "type": "string"
60
+ },
61
+ "updated_at": {
62
+ "type": "string"
63
+ },
64
+ "worktree": {
65
+ "type": "string",
66
+ "description": "CAWS worktree name assigned to this spec"
67
+ },
68
+ "risk_tier": {
69
+ "type": [
70
+ "integer",
71
+ "string"
72
+ ],
73
+ "enum": [
74
+ 1,
75
+ 2,
76
+ 3,
77
+ "1",
78
+ "2",
79
+ "3"
80
+ ],
81
+ "description": "Risk level (1=high, 2=medium, 3=low)"
82
+ },
83
+ "mode": {
84
+ "type": "string",
85
+ "enum": [
86
+ "feature",
87
+ "refactor",
88
+ "fix",
89
+ "doc",
90
+ "docs",
91
+ "chore",
92
+ "development"
93
+ ],
94
+ "description": "Mode of change. `development` is the default used by `caws specs create`; feature/refactor/fix/doc/chore are the classic modes the legacy validator accepted."
25
95
  },
26
- "created_at": { "type": "string" },
27
- "updated_at": { "type": "string" },
28
- "risk_tier": { "type": ["integer", "string"], "enum": [1, 2, 3, "1", "2", "3"] },
29
- "mode": { "type": "string", "enum": ["feature", "refactor", "fix", "doc", "docs", "chore", "development"] },
30
96
  "waiver_ids": {
31
97
  "type": "array",
32
- "items": { "type": "string", "pattern": "^WV-\\d{4}$" },
98
+ "items": {
99
+ "type": "string",
100
+ "pattern": "^WV-\\d{4}$"
101
+ },
33
102
  "description": "IDs of active waivers applying to this spec"
34
103
  },
35
104
  "blast_radius": {
36
105
  "type": "object",
37
- "required": ["modules"],
106
+ "required": [
107
+ "modules",
108
+ "data_migration"
109
+ ],
38
110
  "properties": {
39
- "modules": { "type": "array", "items": { "type": "string" } },
40
- "data_migration": { "type": "boolean" }
41
- }
111
+ "modules": {
112
+ "type": "array",
113
+ "items": {
114
+ "type": "string"
115
+ },
116
+ "description": "List of modules/paths the change touches"
117
+ },
118
+ "data_migration": {
119
+ "type": "boolean",
120
+ "description": "Whether the change requires a data migration"
121
+ }
122
+ },
123
+ "additionalProperties": true
124
+ },
125
+ "operational_rollback_slo": {
126
+ "type": "string",
127
+ "minLength": 1,
128
+ "description": "Time target for operational rollback (e.g. '5m', '30m', '1h')"
42
129
  },
43
- "operational_rollback_slo": { "type": "string" },
44
130
  "scope": {
45
131
  "type": "object",
46
- "required": ["in", "out"],
132
+ "required": [
133
+ "in"
134
+ ],
47
135
  "properties": {
48
- "in": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
49
- "out": { "type": "array", "items": { "type": "string" } }
50
- }
136
+ "in": {
137
+ "type": "array",
138
+ "items": {
139
+ "type": "string"
140
+ },
141
+ "minItems": 1,
142
+ "description": "Files/paths allowed to be edited (non-empty)"
143
+ },
144
+ "out": {
145
+ "type": "array",
146
+ "items": {
147
+ "type": "string"
148
+ },
149
+ "description": "Files/paths explicitly excluded from edits"
150
+ }
151
+ },
152
+ "additionalProperties": true
51
153
  },
52
- "invariants": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
53
- "acceptance": {
154
+ "invariants": {
54
155
  "type": "array",
55
- "minItems": 0,
56
156
  "items": {
57
- "type": "object",
58
- "required": ["id", "given", "when", "then"],
59
- "properties": {
60
- "id": { "type": "string", "pattern": "^A\\d+$" },
61
- "given": { "type": "string" },
62
- "when": { "type": "string" },
63
- "then": { "type": "string" }
64
- }
65
- }
157
+ "type": "string"
158
+ },
159
+ "minItems": 1,
160
+ "description": "Properties that must hold across the change (non-empty)"
66
161
  },
67
- "acceptance_criteria": {
162
+ "acceptance": {
68
163
  "type": "array",
69
- "minItems": 0,
164
+ "minItems": 1,
165
+ "description": "Given/When/Then acceptance criteria (non-empty)",
70
166
  "items": {
71
167
  "type": "object",
72
- "required": ["id"],
168
+ "required": [
169
+ "id",
170
+ "given",
171
+ "when",
172
+ "then"
173
+ ],
73
174
  "properties": {
74
- "id": { "type": "string" },
75
- "description": { "type": "string" },
76
- "completed": { "type": "boolean" },
77
- "test": { "type": "string" },
78
- "test_command": { "type": "string" },
79
- "test_nodeids": {
80
- "type": "array",
81
- "items": { "type": "string" }
175
+ "id": {
176
+ "type": "string",
177
+ "minLength": 1
82
178
  },
83
- "evidence": {
84
- "oneOf": [
85
- { "type": "string" },
86
- {
87
- "type": "array",
88
- "items": { "type": "string" }
89
- }
90
- ]
179
+ "given": {
180
+ "type": "string",
181
+ "minLength": 1
182
+ },
183
+ "when": {
184
+ "type": "string",
185
+ "minLength": 1
186
+ },
187
+ "then": {
188
+ "type": "string",
189
+ "minLength": 1
91
190
  }
92
191
  },
93
192
  "additionalProperties": true
@@ -95,82 +194,147 @@
95
194
  },
96
195
  "non_functional": {
97
196
  "type": "object",
197
+ "required": [
198
+ "a11y",
199
+ "perf",
200
+ "security"
201
+ ],
98
202
  "properties": {
99
- "a11y": { "type": "array", "items": { "type": "string" } },
203
+ "a11y": {
204
+ "type": "array",
205
+ "items": {
206
+ "type": "string"
207
+ },
208
+ "description": "Accessibility requirements (may be empty array if not applicable)"
209
+ },
100
210
  "perf": {
101
211
  "type": "object",
102
- "properties": {
103
- "api_p95_ms": { "type": "integer", "minimum": 1 },
104
- "lcp_ms": { "type": "integer", "minimum": 1 }
105
- },
106
- "additionalProperties": false
212
+ "description": "Performance requirements (e.g. api_p95_ms, lcp_ms)"
107
213
  },
108
- "security": { "type": "array", "items": { "type": "string" } }
214
+ "security": {
215
+ "type": "array",
216
+ "items": {
217
+ "type": "string"
218
+ },
219
+ "description": "Security requirements (may be empty array if not applicable)"
220
+ }
109
221
  },
110
- "additionalProperties": false
222
+ "additionalProperties": true
111
223
  },
112
224
  "contracts": {
113
225
  "type": "array",
226
+ "description": "API contracts. Empty array is permitted; CAWSFIX-06 will warn (not fail) when mode=feature with empty contracts.",
114
227
  "items": {
115
228
  "type": "object",
116
- "required": ["type", "path"],
229
+ "required": [
230
+ "type",
231
+ "path"
232
+ ],
117
233
  "properties": {
118
- "type": { "type": "string", "enum": ["openapi", "graphql", "proto", "pact", "project_setup"] },
119
- "path": { "type": "string" },
120
- "description": { "type": "string" },
121
- "version": { "type": "string" }
122
- }
234
+ "type": {
235
+ "type": "string",
236
+ "enum": [
237
+ "openapi",
238
+ "graphql",
239
+ "proto",
240
+ "pact",
241
+ "project_setup"
242
+ ]
243
+ },
244
+ "path": {
245
+ "type": "string"
246
+ },
247
+ "description": {
248
+ "type": "string"
249
+ },
250
+ "version": {
251
+ "type": "string"
252
+ }
253
+ },
254
+ "additionalProperties": true
123
255
  }
124
256
  },
125
257
  "observability": {
126
- "type": "object",
127
- "properties": {
128
- "logs": { "type": "array", "items": { "type": "string" } },
129
- "metrics": { "type": "array", "items": { "type": "string" } },
130
- "traces": { "type": "array", "items": { "type": "string" } }
258
+ "type": "object"
259
+ },
260
+ "migrations": {
261
+ "type": "array",
262
+ "items": {
263
+ "type": "string"
264
+ }
265
+ },
266
+ "rollback": {
267
+ "type": "array",
268
+ "items": {
269
+ "type": "string"
131
270
  }
132
271
  },
133
- "migrations": { "type": "array", "items": { "type": "string" } },
134
- "rollback": { "type": "array", "items": { "type": "string" } },
135
272
  "experimental_mode": {
136
273
  "type": "object",
137
- "description": "Enables experimental mode with reduced requirements",
138
- "required": ["enabled", "rationale", "expires_at"],
274
+ "required": [
275
+ "enabled",
276
+ "rationale",
277
+ "expires_at"
278
+ ],
139
279
  "properties": {
140
- "enabled": { "type": "boolean" },
141
- "rationale": { "type": "string" },
142
- "expires_at": { "type": "string" }
280
+ "enabled": {
281
+ "type": "boolean"
282
+ },
283
+ "rationale": {
284
+ "type": "string"
285
+ },
286
+ "expires_at": {
287
+ "type": "string"
288
+ }
143
289
  }
144
290
  },
145
291
  "timeboxed_hours": {
146
292
  "type": "integer",
147
- "minimum": 1,
148
- "description": "Time limit for experimental features in hours"
293
+ "minimum": 1
149
294
  },
150
295
  "human_override": {
151
296
  "type": "object",
297
+ "required": [
298
+ "approved_by",
299
+ "reason"
300
+ ],
152
301
  "properties": {
153
- "approved_by": { "type": "string" },
154
- "reason": { "type": "string" },
302
+ "approved_by": {
303
+ "type": "string"
304
+ },
305
+ "reason": {
306
+ "type": "string"
307
+ },
155
308
  "waived_requirements": {
156
309
  "type": "array",
157
310
  "items": {
158
- "type": "string",
159
- "enum": ["mutation_testing", "contract_tests", "coverage", "manual_review"]
311
+ "type": "string"
160
312
  }
161
313
  },
162
- "expiry_date": { "type": "string", "format": "date-time" }
163
- },
164
- "required": ["approved_by", "reason"]
314
+ "expiry_date": {
315
+ "type": "string"
316
+ }
317
+ }
165
318
  },
166
319
  "ai_assessment": {
167
320
  "type": "object",
168
321
  "properties": {
169
- "confidence_level": { "type": "integer", "minimum": 1, "maximum": 10 },
170
- "uncertainty_areas": { "type": "array", "items": { "type": "string" } },
171
- "recommended_pairing": { "type": "boolean" }
322
+ "confidence_level": {
323
+ "type": "integer",
324
+ "minimum": 1,
325
+ "maximum": 10
326
+ },
327
+ "uncertainty_areas": {
328
+ "type": "array",
329
+ "items": {
330
+ "type": "string"
331
+ }
332
+ },
333
+ "recommended_pairing": {
334
+ "type": "boolean"
335
+ }
172
336
  }
173
337
  }
174
338
  },
175
- "additionalProperties": false
339
+ "additionalProperties": true
176
340
  }
@@ -1,9 +1,12 @@
1
1
  id: '{{FEATURE_ID}}'
2
2
  title: '{{FEATURE_TITLE}}'
3
+ # Recommended when the spec belongs to a CAWS worktree:
4
+ # worktree: '{{WORKTREE_NAME}}'
3
5
  risk_tier: {{TIER}}
4
6
  mode: feature
5
7
  blast_radius:
6
8
  modules: []
9
+ data_migration: false
7
10
  operational_rollback_slo: "30m"
8
11
  scope:
9
12
  in:
@@ -75,4 +78,3 @@ rollback:
75
78
  # reason: "Urgent production fix - bypassing mutation tests for immediate deployment"
76
79
  # waived_requirements: ["mutation_testing", "manual_review"]
77
80
  # expiry_date: "2025-10-01T00:00:00Z"
78
-
@@ -70,26 +70,71 @@ function checkFileScope(filePath, projectDir) {
70
70
  return { inScope: true, reason: 'js-yaml not available' };
71
71
  }
72
72
 
73
- const specs = [];
73
+ // --- Authoritative spec detection ---
74
+ // If inside a worktree with a mutual spec binding, only check that spec.
75
+ let authoritativeSpec = null;
76
+ let mode = 'union';
74
77
 
75
- if (fs.existsSync(specFile)) {
76
- try {
77
- const s = yaml.load(fs.readFileSync(specFile, 'utf8'));
78
- if (s && !TERMINAL.has(s.status)) {
79
- specs.push({ source: 'working-spec', spec: s });
80
- }
81
- } catch (_) {}
78
+ const registryPath = path.join(projectDir, '.caws', 'worktrees.json');
79
+ const worktreesBase = path.join(projectDir, '.caws', 'worktrees');
80
+ const cwd = process.cwd();
81
+
82
+ if (cwd.startsWith(worktreesBase + path.sep)) {
83
+ const relative = path.relative(worktreesBase, cwd);
84
+ const worktreeName = relative.split(path.sep)[0];
85
+
86
+ if (worktreeName && fs.existsSync(registryPath)) {
87
+ try {
88
+ const reg = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
89
+ const entry = reg.worktrees && reg.worktrees[worktreeName];
90
+
91
+ if (entry && entry.specId) {
92
+ const specCandidates = [
93
+ path.join(specsDir, entry.specId + '.yaml'),
94
+ path.join(specsDir, entry.specId + '.yml'),
95
+ ];
96
+ for (const candidate of specCandidates) {
97
+ if (fs.existsSync(candidate)) {
98
+ try {
99
+ const s = yaml.load(fs.readFileSync(candidate, 'utf8'));
100
+ if (s && !TERMINAL.has(s.status) && s.worktree === worktreeName) {
101
+ authoritativeSpec = { source: path.basename(candidate), spec: s };
102
+ mode = 'authoritative';
103
+ }
104
+ } catch (_) {}
105
+ break;
106
+ }
107
+ }
108
+ }
109
+ } catch (_) {}
110
+ }
82
111
  }
83
112
 
84
- if (fs.existsSync(specsDir)) {
85
- for (const f of fs.readdirSync(specsDir).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'))) {
113
+ // --- Collect specs based on mode ---
114
+ const specs = [];
115
+
116
+ if (authoritativeSpec) {
117
+ specs.push(authoritativeSpec);
118
+ } else {
119
+ if (fs.existsSync(specFile)) {
86
120
  try {
87
- const s = yaml.load(fs.readFileSync(path.join(specsDir, f), 'utf8'));
121
+ const s = yaml.load(fs.readFileSync(specFile, 'utf8'));
88
122
  if (s && !TERMINAL.has(s.status)) {
89
- specs.push({ source: f, spec: s });
123
+ specs.push({ source: 'working-spec', spec: s });
90
124
  }
91
125
  } catch (_) {}
92
126
  }
127
+
128
+ if (fs.existsSync(specsDir)) {
129
+ for (const f of fs.readdirSync(specsDir).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'))) {
130
+ try {
131
+ const s = yaml.load(fs.readFileSync(path.join(specsDir, f), 'utf8'));
132
+ if (s && !TERMINAL.has(s.status)) {
133
+ specs.push({ source: f, spec: s });
134
+ }
135
+ } catch (_) {}
136
+ }
137
+ }
93
138
  }
94
139
 
95
140
  if (specs.length === 0) {
@@ -100,17 +145,23 @@ function checkFileScope(filePath, projectDir) {
100
145
  for (const { source, spec } of specs) {
101
146
  for (const pattern of (spec.scope?.out || [])) {
102
147
  if (globToRegex(pattern).test(filePath)) {
103
- return { inScope: false, reason: `out-of-scope in ${source} (pattern: ${pattern})` };
148
+ const modeHint = mode === 'union'
149
+ ? '. No authoritative spec bound — checking all active specs. Fix: caws worktree bind <spec-id>'
150
+ : '';
151
+ return { inScope: false, reason: `out-of-scope in ${source} (pattern: ${pattern})${modeHint}` };
104
152
  }
105
153
  }
106
154
  }
107
155
 
108
- // Union all scope.in — must match at least one
156
+ // scope.in — must match at least one
109
157
  const allIn = specs.flatMap(({ spec }) => spec.scope?.in || []);
110
158
  if (allIn.length > 0) {
111
159
  const found = allIn.some(pattern => globToRegex(pattern).test(filePath));
112
160
  if (!found) {
113
- return { inScope: false, reason: 'not in any active spec scope.in' };
161
+ const modeHint = mode === 'union'
162
+ ? '. No authoritative spec bound — checking all active specs. Fix: caws worktree bind <spec-id>'
163
+ : '';
164
+ return { inScope: false, reason: `not in any active spec scope.in${modeHint}` };
114
165
  }
115
166
  }
116
167
 
@@ -38,7 +38,7 @@ Run before Claude executes a tool:
38
38
  |------|---------|---------|
39
39
  | `block-dangerous.sh` | `Bash` | Block destructive shell commands |
40
40
  | `scan-secrets.sh` | `Read` | Warn when reading sensitive files |
41
- | `scope-guard.sh` | `Write\|Edit` | Check scope boundaries before edits |
41
+ | `scope-guard.sh` | `Write\|Edit` | Check scope boundaries before edits (use `caws scope show` to diagnose blocks) |
42
42
 
43
43
  ### PostToolUse Hooks
44
44
 
@@ -0,0 +1,39 @@
1
+ #!/bin/bash
2
+ # CAWS Protected Paths Guard for Claude Code
3
+ # Blocks direct Write/Edit access to guard code and guard state.
4
+ # @author @darianrosebrook
5
+
6
+ set -euo pipefail
7
+
8
+ INPUT=$(cat)
9
+
10
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
11
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
12
+
13
+ case "$TOOL_NAME" in
14
+ Write|Edit) ;;
15
+ *) exit 0 ;;
16
+ esac
17
+
18
+ if [[ -z "$FILE_PATH" ]]; then
19
+ exit 0
20
+ fi
21
+
22
+ # If you are reading this because a write was blocked, do not edit hook files or
23
+ # strike-state files to bypass a guard. Switch into the correct worktree, fix the
24
+ # active spec scope, or ask the user if the guard itself is wrong.
25
+ case "$FILE_PATH" in
26
+ */.claude/hooks/*)
27
+ echo "BLOCKED: $FILE_PATH is protected." >&2
28
+ echo "Ask the user for permission before editing Claude hook scripts." >&2
29
+ exit 1
30
+ ;;
31
+ */.claude/logs/guard-strikes-*.json)
32
+ echo "BLOCKED: $FILE_PATH is protected guard state." >&2
33
+ echo "Do not reset or edit strike counters to bypass enforcement." >&2
34
+ echo "Switch into the correct worktree, update the active CAWS spec scope, or ask the user for direction instead." >&2
35
+ exit 2
36
+ ;;
37
+ esac
38
+
39
+ exit 0