@paths.design/caws-cli 9.3.2 → 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.
Files changed (286) hide show
  1. package/README.md +71 -32
  2. package/dist/budget-derivation.js +221 -74
  3. package/dist/commands/archive.js +67 -28
  4. package/dist/commands/burnup.js +20 -11
  5. package/dist/commands/diagnose.js +34 -22
  6. package/dist/commands/evaluate.js +41 -15
  7. package/dist/commands/gates.js +149 -0
  8. package/dist/commands/init.js +150 -19
  9. package/dist/commands/iterate.js +81 -4
  10. package/dist/commands/parallel.js +4 -0
  11. package/dist/commands/plan.js +9 -19
  12. package/dist/commands/provenance.js +53 -17
  13. package/dist/commands/quality-monitor.js +64 -45
  14. package/dist/commands/scope.js +264 -0
  15. package/dist/commands/sidecar.js +74 -0
  16. package/dist/commands/specs.js +381 -45
  17. package/dist/commands/status.js +117 -9
  18. package/dist/commands/templates.js +0 -8
  19. package/dist/commands/tutorial.js +10 -9
  20. package/dist/commands/validate.js +70 -6
  21. package/dist/commands/verify-acs.js +48 -76
  22. package/dist/commands/waivers.js +212 -13
  23. package/dist/commands/worktree.js +131 -26
  24. package/dist/error-handler.js +2 -13
  25. package/dist/gates/budget-limit.js +121 -0
  26. package/dist/gates/feedback.js +260 -0
  27. package/dist/gates/format.js +179 -0
  28. package/dist/gates/god-object.js +117 -0
  29. package/dist/gates/pipeline.js +167 -0
  30. package/dist/gates/scope-boundary.js +93 -0
  31. package/dist/gates/spec-completeness.js +109 -0
  32. package/dist/gates/todo-detection.js +205 -0
  33. package/dist/index.js +157 -151
  34. package/dist/parallel/parallel-manager.js +3 -3
  35. package/dist/policy/PolicyManager.js +51 -17
  36. package/dist/scaffold/claude-hooks.js +24 -1
  37. package/dist/scaffold/git-hooks.js +45 -102
  38. package/dist/scaffold/index.js +4 -3
  39. package/dist/session/session-manager.js +105 -14
  40. package/dist/sidecars/index.js +33 -0
  41. package/dist/sidecars/listeners.js +40 -0
  42. package/dist/sidecars/provenance-summary.js +238 -0
  43. package/dist/sidecars/quality-gaps.js +258 -0
  44. package/dist/sidecars/schema.js +149 -0
  45. package/dist/sidecars/spec-drift.js +151 -0
  46. package/dist/sidecars/waiver-draft.js +176 -0
  47. package/dist/templates/.caws/schemas/policy.schema.json +112 -0
  48. package/dist/templates/.caws/schemas/scope.schema.json +3 -3
  49. package/dist/templates/.caws/schemas/waivers.schema.json +96 -20
  50. package/dist/templates/.caws/schemas/working-spec.schema.json +264 -57
  51. package/dist/templates/.caws/schemas/worktrees.schema.json +3 -1
  52. package/dist/templates/.caws/templates/working-spec.template.yml +10 -4
  53. package/dist/templates/.caws/tools/scope-guard.js +66 -15
  54. package/dist/templates/.claude/README.md +1 -1
  55. package/dist/templates/.claude/hooks/audit.sh +0 -0
  56. package/dist/templates/.claude/hooks/block-dangerous.sh +52 -11
  57. package/dist/templates/.claude/hooks/classify_command.py +592 -0
  58. package/dist/templates/.claude/hooks/doc-frontmatter-check.sh +173 -0
  59. package/dist/templates/.claude/hooks/protected-paths.sh +39 -0
  60. package/dist/templates/.claude/hooks/quality-check.sh +23 -10
  61. package/dist/templates/.claude/hooks/scope-guard.sh +136 -55
  62. package/dist/templates/.claude/hooks/session-caws-status.sh +2 -2
  63. package/dist/templates/.claude/hooks/session-log.sh +76 -3
  64. package/dist/templates/.claude/hooks/stop-worktree-check.sh +1 -1
  65. package/dist/templates/.claude/hooks/test_classify_command.py +370 -0
  66. package/dist/templates/.claude/hooks/test_wrapper_smoke.sh +96 -0
  67. package/dist/templates/.claude/hooks/worktree-guard.sh +2 -2
  68. package/dist/templates/.claude/hooks/worktree-write-guard.sh +97 -4
  69. package/dist/templates/.claude/settings.json +31 -0
  70. package/dist/templates/.cursor/hooks/caws-quality-check.sh +4 -4
  71. package/dist/templates/.cursor/hooks/caws-scope-guard.sh +1 -1
  72. package/dist/templates/.cursor/hooks/session-log.sh +924 -0
  73. package/dist/templates/.cursor/hooks.json +25 -0
  74. package/dist/templates/.cursor/rules/02-quality-gates.mdc +3 -5
  75. package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +6 -11
  76. package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +14 -18
  77. package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +4 -4
  78. package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +3 -13
  79. package/dist/templates/.github/copilot-instructions.md +5 -5
  80. package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +1 -1
  81. package/dist/templates/.junie/guidelines.md +2 -2
  82. package/dist/templates/.vscode/settings.json +3 -1
  83. package/dist/templates/.windsurf/rules/caws-quality-standards.md +2 -2
  84. package/dist/templates/.windsurf/workflows/caws-guided-development.md +3 -3
  85. package/dist/templates/CLAUDE.md +77 -8
  86. package/dist/templates/agents.md +50 -9
  87. package/dist/templates/docs/README.md +8 -7
  88. package/dist/templates/scripts/new_feature.sh +80 -0
  89. package/dist/test-analysis.js +43 -30
  90. package/dist/tool-loader.js +1 -1
  91. package/dist/utils/agent-session.js +202 -0
  92. package/dist/utils/detection.js +8 -2
  93. package/dist/utils/event-log.js +584 -0
  94. package/dist/utils/event-renderer.js +521 -0
  95. package/dist/utils/finalization.js +7 -6
  96. package/dist/utils/gitignore-updater.js +3 -0
  97. package/dist/utils/lifecycle-events.js +94 -0
  98. package/dist/utils/quality-gates-utils.js +29 -44
  99. package/dist/utils/schema-validator.js +50 -0
  100. package/dist/utils/spec-resolver.js +93 -21
  101. package/dist/utils/working-state.js +530 -0
  102. package/dist/validation/spec-validation.js +191 -31
  103. package/dist/waivers-manager.js +144 -6
  104. package/dist/worktree/worktree-manager.js +598 -95
  105. package/package.json +9 -8
  106. package/templates/.caws/schemas/policy.schema.json +112 -0
  107. package/templates/.caws/schemas/scope.schema.json +3 -3
  108. package/templates/.caws/schemas/waivers.schema.json +96 -20
  109. package/templates/.caws/schemas/working-spec.schema.json +264 -57
  110. package/templates/.caws/schemas/worktrees.schema.json +3 -1
  111. package/templates/.caws/templates/working-spec.template.yml +10 -4
  112. package/templates/.caws/tools/scope-guard.js +66 -15
  113. package/templates/.claude/README.md +1 -1
  114. package/templates/.claude/hooks/block-dangerous.sh +52 -11
  115. package/templates/.claude/hooks/classify_command.py +592 -0
  116. package/templates/.claude/hooks/doc-frontmatter-check.sh +173 -0
  117. package/templates/.claude/hooks/protected-paths.sh +39 -0
  118. package/templates/.claude/hooks/quality-check.sh +23 -10
  119. package/templates/.claude/hooks/scope-guard.sh +136 -55
  120. package/templates/.claude/hooks/session-caws-status.sh +2 -2
  121. package/templates/.claude/hooks/session-log.sh +76 -3
  122. package/templates/.claude/hooks/stop-worktree-check.sh +1 -1
  123. package/templates/.claude/hooks/test_classify_command.py +370 -0
  124. package/templates/.claude/hooks/test_wrapper_smoke.sh +96 -0
  125. package/templates/.claude/hooks/worktree-guard.sh +2 -2
  126. package/templates/.claude/hooks/worktree-write-guard.sh +97 -4
  127. package/templates/.claude/settings.json +31 -0
  128. package/templates/.cursor/hooks/caws-quality-check.sh +4 -4
  129. package/templates/.cursor/hooks/caws-scope-guard.sh +1 -1
  130. package/templates/.cursor/hooks/session-log.sh +924 -0
  131. package/templates/.cursor/hooks.json +25 -0
  132. package/templates/.cursor/rules/02-quality-gates.mdc +3 -5
  133. package/templates/.cursor/rules/10-documentation-quality-standards.mdc +6 -11
  134. package/templates/.cursor/rules/11-scope-management-waivers.mdc +14 -18
  135. package/templates/.cursor/rules/12-implementation-completeness.mdc +4 -4
  136. package/templates/.cursor/rules/13-language-agnostic-standards.mdc +3 -13
  137. package/templates/.github/copilot-instructions.md +5 -5
  138. package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +1 -1
  139. package/templates/.junie/guidelines.md +2 -2
  140. package/templates/.vscode/settings.json +3 -1
  141. package/templates/.windsurf/rules/caws-quality-standards.md +2 -2
  142. package/templates/.windsurf/workflows/caws-guided-development.md +3 -3
  143. package/templates/CLAUDE.md +77 -8
  144. package/templates/{AGENTS.md → agents.md} +50 -9
  145. package/templates/docs/README.md +8 -7
  146. package/templates/scripts/new_feature.sh +80 -0
  147. package/dist/budget-derivation.d.ts +0 -74
  148. package/dist/budget-derivation.d.ts.map +0 -1
  149. package/dist/cicd-optimizer.d.ts +0 -142
  150. package/dist/cicd-optimizer.d.ts.map +0 -1
  151. package/dist/commands/archive.d.ts +0 -51
  152. package/dist/commands/archive.d.ts.map +0 -1
  153. package/dist/commands/burnup.d.ts +0 -6
  154. package/dist/commands/burnup.d.ts.map +0 -1
  155. package/dist/commands/diagnose.d.ts +0 -52
  156. package/dist/commands/diagnose.d.ts.map +0 -1
  157. package/dist/commands/evaluate.d.ts +0 -8
  158. package/dist/commands/evaluate.d.ts.map +0 -1
  159. package/dist/commands/init.d.ts +0 -5
  160. package/dist/commands/init.d.ts.map +0 -1
  161. package/dist/commands/iterate.d.ts +0 -8
  162. package/dist/commands/iterate.d.ts.map +0 -1
  163. package/dist/commands/mode.d.ts +0 -25
  164. package/dist/commands/mode.d.ts.map +0 -1
  165. package/dist/commands/parallel.d.ts +0 -7
  166. package/dist/commands/parallel.d.ts.map +0 -1
  167. package/dist/commands/plan.d.ts +0 -49
  168. package/dist/commands/plan.d.ts.map +0 -1
  169. package/dist/commands/provenance.d.ts +0 -32
  170. package/dist/commands/provenance.d.ts.map +0 -1
  171. package/dist/commands/quality-gates.d.ts +0 -6
  172. package/dist/commands/quality-gates.d.ts.map +0 -1
  173. package/dist/commands/quality-gates.js +0 -444
  174. package/dist/commands/quality-monitor.d.ts +0 -17
  175. package/dist/commands/quality-monitor.d.ts.map +0 -1
  176. package/dist/commands/session.d.ts +0 -7
  177. package/dist/commands/session.d.ts.map +0 -1
  178. package/dist/commands/specs.d.ts +0 -77
  179. package/dist/commands/specs.d.ts.map +0 -1
  180. package/dist/commands/status.d.ts +0 -44
  181. package/dist/commands/status.d.ts.map +0 -1
  182. package/dist/commands/templates.d.ts +0 -74
  183. package/dist/commands/templates.d.ts.map +0 -1
  184. package/dist/commands/tool.d.ts +0 -13
  185. package/dist/commands/tool.d.ts.map +0 -1
  186. package/dist/commands/troubleshoot.d.ts +0 -8
  187. package/dist/commands/troubleshoot.d.ts.map +0 -1
  188. package/dist/commands/troubleshoot.js +0 -104
  189. package/dist/commands/tutorial.d.ts +0 -55
  190. package/dist/commands/tutorial.d.ts.map +0 -1
  191. package/dist/commands/validate.d.ts +0 -15
  192. package/dist/commands/validate.d.ts.map +0 -1
  193. package/dist/commands/waivers.d.ts +0 -8
  194. package/dist/commands/waivers.d.ts.map +0 -1
  195. package/dist/commands/workflow.d.ts +0 -85
  196. package/dist/commands/workflow.d.ts.map +0 -1
  197. package/dist/commands/worktree.d.ts +0 -7
  198. package/dist/commands/worktree.d.ts.map +0 -1
  199. package/dist/config/index.d.ts +0 -29
  200. package/dist/config/index.d.ts.map +0 -1
  201. package/dist/config/lite-scope.d.ts +0 -33
  202. package/dist/config/lite-scope.d.ts.map +0 -1
  203. package/dist/config/modes.d.ts +0 -264
  204. package/dist/config/modes.d.ts.map +0 -1
  205. package/dist/constants/spec-types.d.ts +0 -93
  206. package/dist/constants/spec-types.d.ts.map +0 -1
  207. package/dist/error-handler.d.ts +0 -151
  208. package/dist/error-handler.d.ts.map +0 -1
  209. package/dist/generators/jest-config-generator.d.ts +0 -32
  210. package/dist/generators/jest-config-generator.d.ts.map +0 -1
  211. package/dist/generators/jest-config.d.ts +0 -32
  212. package/dist/generators/jest-config.d.ts.map +0 -1
  213. package/dist/generators/jest-config.js +0 -242
  214. package/dist/generators/working-spec.d.ts +0 -13
  215. package/dist/generators/working-spec.d.ts.map +0 -1
  216. package/dist/index-new.d.ts +0 -5
  217. package/dist/index-new.d.ts.map +0 -1
  218. package/dist/index-new.js +0 -317
  219. package/dist/index.d.ts +0 -5
  220. package/dist/index.d.ts.map +0 -1
  221. package/dist/index.js.backup +0 -4711
  222. package/dist/minimal-cli.d.ts +0 -3
  223. package/dist/minimal-cli.d.ts.map +0 -1
  224. package/dist/parallel/parallel-manager.d.ts +0 -67
  225. package/dist/parallel/parallel-manager.d.ts.map +0 -1
  226. package/dist/policy/PolicyManager.d.ts +0 -104
  227. package/dist/policy/PolicyManager.d.ts.map +0 -1
  228. package/dist/scaffold/claude-hooks.d.ts +0 -28
  229. package/dist/scaffold/claude-hooks.d.ts.map +0 -1
  230. package/dist/scaffold/cursor-hooks.d.ts +0 -7
  231. package/dist/scaffold/cursor-hooks.d.ts.map +0 -1
  232. package/dist/scaffold/git-hooks.d.ts +0 -38
  233. package/dist/scaffold/git-hooks.d.ts.map +0 -1
  234. package/dist/scaffold/index.d.ts +0 -17
  235. package/dist/scaffold/index.d.ts.map +0 -1
  236. package/dist/session/session-manager.d.ts +0 -94
  237. package/dist/session/session-manager.d.ts.map +0 -1
  238. package/dist/spec/SpecFileManager.d.ts +0 -146
  239. package/dist/spec/SpecFileManager.d.ts.map +0 -1
  240. package/dist/templates/.cursor/hooks/caws-tool-validation.sh +0 -121
  241. package/dist/templates/.github/copilot/instructions.md +0 -311
  242. package/dist/test-analysis.d.ts +0 -231
  243. package/dist/test-analysis.d.ts.map +0 -1
  244. package/dist/tool-interface.d.ts +0 -236
  245. package/dist/tool-interface.d.ts.map +0 -1
  246. package/dist/tool-loader.d.ts +0 -77
  247. package/dist/tool-loader.d.ts.map +0 -1
  248. package/dist/tool-validator.d.ts +0 -72
  249. package/dist/tool-validator.d.ts.map +0 -1
  250. package/dist/utils/async-utils.d.ts +0 -73
  251. package/dist/utils/async-utils.d.ts.map +0 -1
  252. package/dist/utils/command-wrapper.d.ts +0 -66
  253. package/dist/utils/command-wrapper.d.ts.map +0 -1
  254. package/dist/utils/detection.d.ts +0 -14
  255. package/dist/utils/detection.d.ts.map +0 -1
  256. package/dist/utils/error-categories.d.ts +0 -52
  257. package/dist/utils/error-categories.d.ts.map +0 -1
  258. package/dist/utils/finalization.d.ts +0 -17
  259. package/dist/utils/finalization.d.ts.map +0 -1
  260. package/dist/utils/git-lock.d.ts +0 -13
  261. package/dist/utils/git-lock.d.ts.map +0 -1
  262. package/dist/utils/gitignore-updater.d.ts +0 -39
  263. package/dist/utils/gitignore-updater.d.ts.map +0 -1
  264. package/dist/utils/ide-detection.d.ts +0 -89
  265. package/dist/utils/ide-detection.d.ts.map +0 -1
  266. package/dist/utils/project-analysis.d.ts +0 -34
  267. package/dist/utils/project-analysis.d.ts.map +0 -1
  268. package/dist/utils/promise-utils.d.ts +0 -30
  269. package/dist/utils/promise-utils.d.ts.map +0 -1
  270. package/dist/utils/quality-gates-utils.d.ts +0 -49
  271. package/dist/utils/quality-gates-utils.d.ts.map +0 -1
  272. package/dist/utils/quality-gates.d.ts +0 -49
  273. package/dist/utils/quality-gates.d.ts.map +0 -1
  274. package/dist/utils/quality-gates.js +0 -402
  275. package/dist/utils/spec-resolver.d.ts +0 -80
  276. package/dist/utils/spec-resolver.d.ts.map +0 -1
  277. package/dist/utils/typescript-detector.d.ts +0 -66
  278. package/dist/utils/typescript-detector.d.ts.map +0 -1
  279. package/dist/utils/yaml-validation.d.ts +0 -32
  280. package/dist/utils/yaml-validation.d.ts.map +0 -1
  281. package/dist/validation/spec-validation.d.ts +0 -43
  282. package/dist/validation/spec-validation.d.ts.map +0 -1
  283. package/dist/waivers-manager.d.ts +0 -167
  284. package/dist/waivers-manager.d.ts.map +0 -1
  285. package/dist/worktree/worktree-manager.d.ts +0 -54
  286. package/dist/worktree/worktree-manager.d.ts.map +0 -1
@@ -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,119 +15,326 @@
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
- "risk_tier": { "type": ["integer", "string"], "enum": [1, 2, 3, "1", "2", "3"] },
22
- "mode": { "type": "string", "enum": ["feature", "refactor", "fix", "doc", "chore"] },
24
+ "id": {
25
+ "type": "string",
26
+ "pattern": "^[A-Z][A-Z0-9]*(-[A-Z0-9]+)*-\\d+$",
27
+ "description": "Unique identifier for the change. Format: uppercase/digit PREFIX segments separated by dashes, final segment is one+ digits (e.g. CAWSFIX-16, P03-TRUTH-001, ALG-001A-HARDEN-01). Matches SPEC_ID_PATTERN in spec-validation.js (CAWSFIX-21 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
+ },
47
+ "status": {
48
+ "type": "string",
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."
95
+ },
23
96
  "waiver_ids": {
24
97
  "type": "array",
25
- "items": { "type": "string", "pattern": "^WV-\\d{4}$" },
98
+ "items": {
99
+ "type": "string",
100
+ "pattern": "^WV-\\d{4}$"
101
+ },
26
102
  "description": "IDs of active waivers applying to this spec"
27
103
  },
28
104
  "blast_radius": {
29
105
  "type": "object",
30
- "required": ["modules"],
106
+ "required": [
107
+ "modules",
108
+ "data_migration"
109
+ ],
31
110
  "properties": {
32
- "modules": { "type": "array", "items": { "type": "string" } },
33
- "data_migration": { "type": "boolean" }
34
- }
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')"
35
129
  },
36
- "operational_rollback_slo": { "type": "string" },
37
130
  "scope": {
38
131
  "type": "object",
39
- "required": ["in", "out"],
132
+ "required": [
133
+ "in"
134
+ ],
40
135
  "properties": {
41
- "in": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
42
- "out": { "type": "array", "items": { "type": "string" } }
43
- }
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
153
+ },
154
+ "invariants": {
155
+ "type": "array",
156
+ "items": {
157
+ "type": "string"
158
+ },
159
+ "minItems": 1,
160
+ "description": "Properties that must hold across the change (non-empty)"
44
161
  },
45
- "invariants": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
46
162
  "acceptance": {
47
163
  "type": "array",
48
164
  "minItems": 1,
165
+ "description": "Given/When/Then acceptance criteria (non-empty)",
49
166
  "items": {
50
167
  "type": "object",
51
- "required": ["id", "given", "when", "then"],
168
+ "required": [
169
+ "id",
170
+ "given",
171
+ "when",
172
+ "then"
173
+ ],
52
174
  "properties": {
53
- "id": { "type": "string", "pattern": "^A\\d+$" },
54
- "given": { "type": "string" },
55
- "when": { "type": "string" },
56
- "then": { "type": "string" }
57
- }
175
+ "id": {
176
+ "type": "string",
177
+ "minLength": 1
178
+ },
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
190
+ }
191
+ },
192
+ "additionalProperties": true
58
193
  }
59
194
  },
60
195
  "non_functional": {
61
196
  "type": "object",
197
+ "required": [
198
+ "a11y",
199
+ "perf",
200
+ "security"
201
+ ],
62
202
  "properties": {
63
- "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
+ },
64
210
  "perf": {
65
211
  "type": "object",
66
- "properties": {
67
- "api_p95_ms": { "type": "integer", "minimum": 1 },
68
- "lcp_ms": { "type": "integer", "minimum": 1 }
69
- },
70
- "additionalProperties": false
212
+ "description": "Performance requirements (e.g. api_p95_ms, lcp_ms)"
71
213
  },
72
- "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
+ }
73
221
  },
74
- "additionalProperties": false
222
+ "additionalProperties": true
75
223
  },
76
224
  "contracts": {
77
225
  "type": "array",
78
- "minItems": 1,
226
+ "description": "API contracts. Empty array is permitted; CAWSFIX-06 will warn (not fail) when mode=feature with empty contracts.",
79
227
  "items": {
80
228
  "type": "object",
81
- "required": ["type", "path"],
229
+ "required": [
230
+ "type",
231
+ "path"
232
+ ],
82
233
  "properties": {
83
- "type": { "type": "string", "enum": ["openapi", "graphql", "proto", "pact"] },
84
- "path": { "type": "string" }
85
- }
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
86
255
  }
87
256
  },
88
257
  "observability": {
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"
270
+ }
271
+ },
272
+ "experimental_mode": {
89
273
  "type": "object",
274
+ "required": [
275
+ "enabled",
276
+ "rationale",
277
+ "expires_at"
278
+ ],
90
279
  "properties": {
91
- "logs": { "type": "array", "items": { "type": "string" } },
92
- "metrics": { "type": "array", "items": { "type": "string" } },
93
- "traces": { "type": "array", "items": { "type": "string" } }
280
+ "enabled": {
281
+ "type": "boolean"
282
+ },
283
+ "rationale": {
284
+ "type": "string"
285
+ },
286
+ "expires_at": {
287
+ "type": "string"
288
+ }
94
289
  }
95
290
  },
96
- "migrations": { "type": "array", "items": { "type": "string" } },
97
- "rollback": { "type": "array", "items": { "type": "string" } },
98
- "experiment_mode": {
99
- "type": "boolean",
100
- "description": "Enables experimental mode with reduced requirements"
101
- },
102
291
  "timeboxed_hours": {
103
292
  "type": "integer",
104
- "minimum": 1,
105
- "description": "Time limit for experimental features in hours"
293
+ "minimum": 1
106
294
  },
107
295
  "human_override": {
108
296
  "type": "object",
297
+ "required": [
298
+ "approved_by",
299
+ "reason"
300
+ ],
109
301
  "properties": {
110
- "approved_by": { "type": "string" },
111
- "reason": { "type": "string" },
302
+ "approved_by": {
303
+ "type": "string"
304
+ },
305
+ "reason": {
306
+ "type": "string"
307
+ },
112
308
  "waived_requirements": {
113
309
  "type": "array",
114
310
  "items": {
115
- "type": "string",
116
- "enum": ["mutation_testing", "contract_tests", "coverage", "manual_review"]
311
+ "type": "string"
117
312
  }
118
313
  },
119
- "expiry_date": { "type": "string", "format": "date-time" }
120
- },
121
- "required": ["approved_by", "reason"]
314
+ "expiry_date": {
315
+ "type": "string"
316
+ }
317
+ }
122
318
  },
123
319
  "ai_assessment": {
124
320
  "type": "object",
125
321
  "properties": {
126
- "confidence_level": { "type": "integer", "minimum": 1, "maximum": 10 },
127
- "uncertainty_areas": { "type": "array", "items": { "type": "string" } },
128
- "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
+ }
129
336
  }
130
337
  }
131
338
  },
132
- "additionalProperties": false
339
+ "additionalProperties": true
133
340
  }
@@ -21,11 +21,13 @@
21
21
  "baseBranch": { "type": "string" },
22
22
  "scope": { "type": ["string", "null"] },
23
23
  "specId": { "type": ["string", "null"] },
24
+ "owner": { "type": ["string", "null"], "description": "CLAUDE_SESSION_ID of the creating agent" },
24
25
  "createdAt": { "type": "string", "format": "date-time" },
25
26
  "destroyedAt": { "type": "string", "format": "date-time" },
27
+ "autoRegistered": { "type": "boolean" },
26
28
  "status": {
27
29
  "type": "string",
28
- "enum": ["active", "orphaned", "missing", "destroyed"]
30
+ "enum": ["fresh", "active", "merged", "orphaned", "missing", "stale-merged", "destroyed"]
29
31
  }
30
32
  },
31
33
  "additionalProperties": false
@@ -1,6 +1,13 @@
1
1
  id: '{{FEATURE_ID}}'
2
2
  title: '{{FEATURE_TITLE}}'
3
- risk_tier: { { TIER } }
3
+ # Recommended when the spec belongs to a CAWS worktree:
4
+ # worktree: '{{WORKTREE_NAME}}'
5
+ risk_tier: {{TIER}}
6
+ mode: feature
7
+ blast_radius:
8
+ modules: []
9
+ data_migration: false
10
+ operational_rollback_slo: "30m"
4
11
  scope:
5
12
  in:
6
13
  - '{{SCOPE_ITEM_1}}'
@@ -37,8 +44,8 @@ non_functional:
37
44
  a11y:
38
45
  - '{{ACCESSIBILITY_REQUIREMENT}}'
39
46
  perf:
40
- api_p95_ms: { { PERF_BUDGET } }
41
- lcp_ms: { { LCP_BUDGET } }
47
+ api_p95_ms: {{PERF_BUDGET}}
48
+ lcp_ms: {{LCP_BUDGET}}
42
49
  security:
43
50
  - '{{SECURITY_REQUIREMENT}}'
44
51
  contracts:
@@ -71,4 +78,3 @@ rollback:
71
78
  # reason: "Urgent production fix - bypassing mutation tests for immediate deployment"
72
79
  # waived_requirements: ["mutation_testing", "manual_review"]
73
80
  # expiry_date: "2025-10-01T00:00:00Z"
74
-
@@ -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
 
@@ -1,23 +1,70 @@
1
1
  #!/bin/bash
2
- # CAWS Dangerous Command Blocker for Claude Code
3
- # Blocks potentially destructive shell commands
2
+ # CAWS Command Safety Gate for Claude Code
3
+ # Delegates to classify_command.py for robust command parsing and classification.
4
+ # Falls back to bash pattern matching if Python is unavailable.
5
+ #
6
+ # The Python classifier handles:
7
+ # - Heredoc-aware parsing (won't false-positive on quoted dangerous commands)
8
+ # - Quoted-region stripping (echo "git reset --hard" is safe)
9
+ # - Pipeline-aware dangers (curl | sh)
10
+ # - Context-aware rm classification (safe prefixes vs dangerous targets)
11
+ # - Proper shell segmentation (&&, ||, ;, |)
12
+ #
4
13
  # @author @darianrosebrook
5
14
 
6
15
  set -euo pipefail
7
16
 
17
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+
8
19
  # Read JSON input from Claude Code
9
20
  INPUT=$(cat)
10
21
 
11
22
  # Extract tool info
12
- TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
13
- COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
23
+ TOOL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool_name // ""')
24
+ COMMAND=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // ""')
14
25
 
15
26
  # Only check Bash tool
16
27
  if [[ "$TOOL_NAME" != "Bash" ]] || [[ -z "$COMMAND" ]]; then
17
28
  exit 0
18
29
  fi
19
30
 
20
- # Dangerous command patterns
31
+ # --- Try Python classifier first (preferred) ---
32
+ CLASSIFIER="$SCRIPT_DIR/classify_command.py"
33
+ if [[ -f "$CLASSIFIER" ]] && command -v python3 >/dev/null 2>&1; then
34
+ REPO_ROOT="${CLAUDE_PROJECT_DIR:-.}"
35
+ CLASSIFIER_STDERR=$(mktemp)
36
+ RESULT=$(printf '%s' "$COMMAND" | python3 "$CLASSIFIER" \
37
+ --repo-root "$REPO_ROOT" \
38
+ --home "$HOME" \
39
+ --cwd "$(pwd)" 2>"$CLASSIFIER_STDERR") || {
40
+ DIAG=$(head -c 200 "$CLASSIFIER_STDERR" 2>/dev/null || true)
41
+ rm -f "$CLASSIFIER_STDERR"
42
+ RESULT="{\"decision\":\"ask\",\"reason\":\"command classifier failed: ${DIAG:-unknown error}\"}"
43
+ }
44
+ rm -f "$CLASSIFIER_STDERR"
45
+
46
+ DECISION=$(printf '%s' "$RESULT" | jq -r '.decision // "ask"')
47
+ REASON=$(printf '%s' "$RESULT" | jq -r '.reason // "unknown"')
48
+
49
+ case "$DECISION" in
50
+ allow)
51
+ exit 0
52
+ ;;
53
+ deny)
54
+ echo "BLOCKED: $REASON" >&2
55
+ echo "Command was: $COMMAND" >&2
56
+ exit 2
57
+ ;;
58
+ ask)
59
+ echo "WARNING: $REASON" >&2
60
+ echo "Command was: $COMMAND" >&2
61
+ exit 1
62
+ ;;
63
+ esac
64
+ fi
65
+
66
+ # --- Fallback: bash pattern matching (less precise, no heredoc/quote awareness) ---
67
+
21
68
  DANGEROUS_PATTERNS=(
22
69
  # Destructive file operations
23
70
  'rm -rf /'
@@ -96,7 +143,6 @@ for pattern in "${DANGEROUS_PATTERNS[@]}"; do
96
143
  # Allow git rebase/cherry-pick only when no worktrees are active
97
144
  if [[ "$pattern" == *"git rebase"* ]] || [[ "$pattern" == *"git cherry-pick"* ]]; then
98
145
  PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
99
- # Resolve to main repo root if we're in a worktree
100
146
  if command -v git >/dev/null 2>&1; then
101
147
  GIT_COMMON=$(cd "$PROJECT_DIR" && git rev-parse --git-common-dir 2>/dev/null || echo "")
102
148
  if [[ -n "$GIT_COMMON" ]] && [[ "$GIT_COMMON" != ".git" ]]; then
@@ -116,7 +162,6 @@ for pattern in "${DANGEROUS_PATTERNS[@]}"; do
116
162
  } catch(e) { console.log(0); }
117
163
  " 2>/dev/null || echo "0")
118
164
  if [[ "$ACTIVE_COUNT" -gt 0 ]]; then
119
- # Extract the specific git subcommand for the message
120
165
  GIT_SUBCMD="git operation"
121
166
  [[ "$pattern" == *"git rebase"* ]] && GIT_SUBCMD="git rebase"
122
167
  [[ "$pattern" == *"git cherry-pick"* ]] && GIT_SUBCMD="git cherry-pick"
@@ -126,7 +171,6 @@ for pattern in "${DANGEROUS_PATTERNS[@]}"; do
126
171
  exit 2
127
172
  fi
128
173
  fi
129
- # No active worktrees — allow
130
174
  continue
131
175
  fi
132
176
 
@@ -142,11 +186,8 @@ for pattern in "${DANGEROUS_PATTERNS[@]}"; do
142
186
  fi
143
187
  fi
144
188
 
145
- # Output to stderr for Claude to see
146
189
  echo "BLOCKED: Command matches dangerous pattern: $pattern" >&2
147
190
  echo "Command was: $COMMAND" >&2
148
-
149
- # Exit code 2 blocks the tool and shows stderr to Claude
150
191
  exit 2
151
192
  fi
152
193
  done