@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
package/README.md CHANGED
@@ -12,8 +12,8 @@ The CAWS CLI serves as the central control point for:
12
12
  - **Quality Validation**: Run comprehensive validation against working specifications
13
13
  - **Agent Integration**: Programmatic APIs for AI agents to evaluate and guide development
14
14
  - **Waiver Management**: Fast-lane escape hatches with full audit trails
15
- - **CI/CD Optimization**: Pipeline generation and optimization tools
16
- - **Experimental Features**: Dry-run capabilities for experimental functionality
15
+ - **Quality Gates**: v2 pipeline with configurable gate modules
16
+ - **Session Management**: Track and manage agent work sessions
17
17
 
18
18
  ## Installation
19
19
 
@@ -86,40 +86,82 @@ caws waivers list
86
86
  caws waivers revoke WV-0001
87
87
  ```
88
88
 
89
- ### CI/CD Optimization
89
+ ### Quality Gates
90
90
 
91
91
  ```bash
92
- # Analyze project for CI/CD optimizations
93
- caws cicd analyze
92
+ # Run quality gates v2 pipeline
93
+ caws gates run
94
94
 
95
- # Generate optimized GitHub Actions workflow
96
- caws cicd generate github --output .github/workflows/caws-gates.yml
95
+ # Run legacy quality gates
96
+ caws quality-gates
97
+ ```
98
+
99
+ ### Worktree Management
100
+
101
+ ```bash
102
+ # Create an isolated worktree for parallel agent work
103
+ caws worktree create <name>
104
+
105
+ # List active worktrees
106
+ caws worktree list
107
+
108
+ # Merge a completed worktree back to base
109
+ caws worktree merge <name>
97
110
 
98
- # Smart test selection based on changes
99
- caws cicd test-selection --from-commit HEAD~1
111
+ # Destroy a worktree
112
+ caws worktree destroy <name>
113
+
114
+ # Bind a spec to a worktree (fixes authoritative scope mode)
115
+ caws worktree bind <spec-id>
116
+
117
+ # Repair registry inconsistencies
118
+ caws worktree repair
119
+ ```
120
+
121
+ ### Scope Management
122
+
123
+ ```bash
124
+ # Inspect effective scope boundaries, mode, and binding health
125
+ caws scope show
100
126
  ```
101
127
 
102
- ### Experimental Features
128
+ ### Session Management
103
129
 
104
130
  ```bash
105
- # Dry-run validation without side effects
106
- caws experimental --dry-run validate .caws/working-spec.yaml
131
+ # Start a tracked session
132
+ caws session start
107
133
 
108
- # Experimental quality gates
109
- caws experimental quality-gates .caws/working-spec.yaml --parallel-execution
134
+ # Create a session checkpoint
135
+ caws session checkpoint
136
+
137
+ # End a session
138
+ caws session end
139
+
140
+ # List past sessions
141
+ caws session list
110
142
  ```
111
143
 
112
- ### Tool Management
144
+ ### Spec Management
113
145
 
114
146
  ```bash
115
- # List available CAWS tools
116
- caws tools list
147
+ # List all specs (project + feature)
148
+ caws specs list
149
+
150
+ # Create a feature spec
151
+ caws specs create FEAT-001 --type feature --title "description"
117
152
 
118
- # Execute specific tool
119
- caws tools run validate
153
+ # Show a spec
154
+ caws specs show FEAT-001
120
155
 
121
- # Manage tool configurations
122
- caws tools --help
156
+ # Check for scope conflicts between specs
157
+ caws specs conflicts
158
+ ```
159
+
160
+ ### Tool Management
161
+
162
+ ```bash
163
+ # Run the CAWS tool interface
164
+ caws tool
123
165
  ```
124
166
 
125
167
  ## Architecture
@@ -131,7 +173,7 @@ caws-cli/
131
173
  ├── src/
132
174
  │ ├── index.js # Main CLI entry point
133
175
  │ ├── waivers-manager.js # Waiver system implementation
134
- │ ├── cicd-optimizer.js # CI/CD optimization logic
176
+ │ ├── quality-gates/ # v2 gate modules
135
177
  │ └── tool-loader.js # Dynamic tool loading system
136
178
  ├── templates/ # Project templates
137
179
  └── dist/ # Compiled output
@@ -142,7 +184,7 @@ caws-cli/
142
184
  - **Command Parser**: Commander.js-based CLI with subcommands
143
185
  - **Tool System**: Dynamic loading of quality gate tools
144
186
  - **Waiver Manager**: Fast-lane escape hatch management
145
- - **CI/CD Optimizer**: Pipeline analysis and generation
187
+ - **Quality Gates v2**: Modular gate pipeline with configurable modules
146
188
  - **Agent Interface**: JSON APIs for programmatic agent integration
147
189
 
148
190
  ## Integration with CAWS Ecosystem
@@ -157,25 +199,22 @@ caws-cli/
157
199
  │ │
158
200
  └───────────────────────┘
159
201
 
160
- ┌─────────────────┐
161
- │ caws-mcp-server │
162
- │ (Agent Bridge) │
163
- └─────────────────┘
164
202
  ```
165
203
 
166
204
  - **caws-template**: Provides the tools and configurations that CLI manages
167
- - **caws-mcp-server**: Exposes CLI functionality to AI agents via MCP protocol
168
205
 
169
206
  ### Quality Gates Integration
170
207
 
171
- The CLI automatically executes quality gates defined in the template:
208
+ The v2 quality gates pipeline (`caws gates run`) executes modular gate checks:
172
209
 
173
- 1. **Spec Validation**: Validates working specifications against schema
210
+ 1. **Spec Validation**: Validates working specifications against schema (mode, blast_radius, rollback SLO)
174
211
  2. **Security Scanning**: Runs security checks and secret detection
175
- 3. **Code Quality**: Executes linting, type checking, and formatting
176
- 4. **Test Execution**: Runs unit, integration, and contract tests
212
+ 3. **Scope Enforcement**: Verifies changes stay within spec-defined boundaries
213
+ 4. **Test & Coverage**: Runs tests and validates coverage thresholds per risk tier
177
214
  5. **Performance Checks**: Validates performance budgets and metrics
178
215
 
216
+ Gates can be configured per-spec with `mode` (block/warn/skip) and custom `thresholds` in policy.yaml.
217
+
179
218
  ### Agent Workflow Integration
180
219
 
181
220
  The CLI provides structured APIs for agents:
@@ -156,6 +156,168 @@ function getDefaultPolicy() {
156
156
  };
157
157
  }
158
158
 
159
+ /**
160
+ * Load policy.yaml synchronously for contexts that can't go async.
161
+ * Falls back to the bundled default policy when the file is absent or invalid.
162
+ * NOTE: bypasses PolicyManager's TTL cache by design — callers that need
163
+ * caching should use the async `deriveBudget` path.
164
+ * @param {string} projectRoot - Project root directory
165
+ * @returns {Object} Policy object (validated, with _isDefault flag if fallback used)
166
+ */
167
+ function loadPolicySync(projectRoot) {
168
+ const policyPath = path.join(projectRoot, '.caws', 'policy.yaml');
169
+ if (!fs.existsSync(policyPath)) {
170
+ return { ...getDefaultPolicy(), _isDefault: true };
171
+ }
172
+
173
+ let policyContent;
174
+ try {
175
+ policyContent = yaml.load(fs.readFileSync(policyPath, 'utf-8'));
176
+ } catch (error) {
177
+ console.warn(`Could not parse policy.yaml (${error.message}); using defaults`);
178
+ return { ...getDefaultPolicy(), _isDefault: true };
179
+ }
180
+
181
+ if (!policyContent || typeof policyContent !== 'object') {
182
+ return { ...getDefaultPolicy(), _isDefault: true };
183
+ }
184
+
185
+ try {
186
+ validatePolicy(policyContent);
187
+ } catch (error) {
188
+ // Policy file exists but is structurally invalid — surface as warning and
189
+ // fall back to defaults so validation can continue. The PolicyManager
190
+ // async path uses console.warn for the same shape.
191
+ console.warn(`Policy has structure violations (${error.message}); using defaults`);
192
+ return { ...getDefaultPolicy(), _isDefault: true };
193
+ }
194
+
195
+ return policyContent;
196
+ }
197
+
198
+ /**
199
+ * Normalize spec.risk_tier to a canonical lookup key.
200
+ * Accepts numeric tier (2), numeric-string ("2"), or "T2"/"t2" forms.
201
+ * Returns the numeric tier (2) when the input is recognizable, otherwise
202
+ * returns the original value so downstream "missing tier" logic can report it.
203
+ * @param {*} riskTier - spec.risk_tier
204
+ * @returns {number|*} numeric tier or original value
205
+ */
206
+ function normalizeRiskTier(riskTier) {
207
+ if (typeof riskTier === 'number') {
208
+ return riskTier;
209
+ }
210
+ if (typeof riskTier === 'string') {
211
+ const match = riskTier.match(/^T?(\d)$/i);
212
+ if (match) {
213
+ return parseInt(match[1], 10);
214
+ }
215
+ }
216
+ return riskTier;
217
+ }
218
+
219
+ /**
220
+ * Look up a tier in policy.risk_tiers, tolerant of numeric vs string keys.
221
+ * policy.yaml serializes tier keys as strings ("1", "2", "3") while specs
222
+ * may use numeric risk_tier. Check both representations.
223
+ * @param {Object} policy - Policy object with risk_tiers map
224
+ * @param {number|string} tier - Normalized tier key
225
+ * @returns {Object|undefined} Tier budget config or undefined if missing
226
+ */
227
+ function lookupTierBudget(policy, tier) {
228
+ if (!policy || !policy.risk_tiers) {
229
+ return undefined;
230
+ }
231
+ return policy.risk_tiers[tier] ?? policy.risk_tiers[String(tier)];
232
+ }
233
+
234
+ /**
235
+ * Build a derived-budget result from a spec, policy, and optional project root.
236
+ * Shared by both `deriveBudget` (async) and `deriveBudgetSync`. Pure function
237
+ * over already-loaded policy — no I/O.
238
+ *
239
+ * Baseline resolution order (per CAWSFIX-07 A1/A2):
240
+ * 1. If spec.change_budget has numeric max_files and max_loc, use it.
241
+ * Legacy specs still in the tree may carry change_budget; CAWSFIX-03
242
+ * forbade it in the schema but not the runtime, so we honor it when
243
+ * present.
244
+ * 2. Otherwise, fall back to policy.risk_tiers[spec.risk_tier].
245
+ *
246
+ * Throws a named-tier error (A3) if the tier isn't present in policy and no
247
+ * spec-level change_budget is available.
248
+ *
249
+ * @param {Object} spec - Working spec
250
+ * @param {Object} policy - Loaded policy object
251
+ * @param {string} projectRoot - Project root (for waiver loading)
252
+ * @returns {Object} { baseline, effective, waivers_applied, derived_at }
253
+ */
254
+ function applyBudgetDerivation(spec, policy, projectRoot) {
255
+ const riskTier = normalizeRiskTier(spec.risk_tier);
256
+ const tierBudget = lookupTierBudget(policy, riskTier);
257
+ const specBudget = spec && spec.change_budget;
258
+ const hasSpecBudget =
259
+ specBudget &&
260
+ typeof specBudget.max_files === 'number' &&
261
+ typeof specBudget.max_loc === 'number';
262
+
263
+ let baseline;
264
+ if (hasSpecBudget) {
265
+ baseline = {
266
+ max_files: specBudget.max_files,
267
+ max_loc: specBudget.max_loc,
268
+ };
269
+ } else if (tierBudget) {
270
+ baseline = {
271
+ max_files: tierBudget.max_files,
272
+ max_loc: tierBudget.max_loc,
273
+ };
274
+ } else {
275
+ const available = policy && policy.risk_tiers ? Object.keys(policy.risk_tiers).join(', ') : 'none';
276
+ throw new Error(
277
+ `Risk tier ${spec.risk_tier} not defined in policy.yaml\n` +
278
+ `Policy only defines tiers: ${available}\n` +
279
+ `Valid tiers are: 1 (critical), 2 (standard), 3 (low-risk)` +
280
+ (typeof spec.risk_tier === 'string'
281
+ ? `\nHint: use numeric risk_tier (e.g., 2) instead of "${spec.risk_tier}"`
282
+ : '')
283
+ );
284
+ }
285
+
286
+ let effectiveBudget = { ...baseline };
287
+
288
+ if (spec.waiver_ids && Array.isArray(spec.waiver_ids)) {
289
+ for (const waiverId of spec.waiver_ids) {
290
+ const waiver = loadWaiver(waiverId, projectRoot);
291
+ if (waiver && waiver.status === 'active' && isWaiverValid(waiver)) {
292
+ if (!waiver.gates || !waiver.gates.includes('budget_limit')) {
293
+ console.warn(
294
+ `\nWaiver ${waiverId} does not cover 'budget_limit' gate\n` +
295
+ ` Current gates: [${waiver.gates ? waiver.gates.join(', ') : 'none'}]\n` +
296
+ ` Add 'budget_limit' to gates array to apply to budget violations\n`
297
+ );
298
+ continue;
299
+ }
300
+
301
+ if (waiver.delta) {
302
+ if (waiver.delta.max_files) {
303
+ effectiveBudget.max_files += waiver.delta.max_files;
304
+ }
305
+ if (waiver.delta.max_loc) {
306
+ effectiveBudget.max_loc += waiver.delta.max_loc;
307
+ }
308
+ }
309
+ }
310
+ }
311
+ }
312
+
313
+ return {
314
+ baseline,
315
+ effective: effectiveBudget,
316
+ waivers_applied: spec.waiver_ids || [],
317
+ derived_at: new Date().toISOString(),
318
+ };
319
+ }
320
+
159
321
  /**
160
322
  * Derive budget for a working spec based on policy and waivers
161
323
  * Enhanced to use PolicyManager for caching
@@ -204,70 +366,32 @@ async function deriveBudget(spec, projectRoot = process.cwd(), options = {}) {
204
366
  }
205
367
  }
206
368
 
207
- // Normalize risk_tier: accept "T1"/"T2"/"T3" strings and convert to numeric
208
- let riskTier = spec.risk_tier;
209
- if (typeof riskTier === 'string') {
210
- const match = riskTier.match(/^T?(\d)$/i);
211
- if (match) {
212
- riskTier = parseInt(match[1], 10);
213
- }
214
- }
215
-
216
- // Check if risk tier exists in policy
217
- if (!policy.risk_tiers[riskTier]) {
218
- throw new Error(
219
- `Risk tier ${spec.risk_tier} not defined in policy.yaml\n` +
220
- `Policy only defines tiers: ${Object.keys(policy.risk_tiers).join(', ')}\n` +
221
- `Valid tiers are: 1 (critical), 2 (standard), 3 (low-risk)` +
222
- (typeof spec.risk_tier === 'string'
223
- ? `\nHint: use numeric risk_tier (e.g., 2) instead of "${spec.risk_tier}"`
224
- : '')
225
- );
226
- }
227
-
228
- const tierBudget = policy.risk_tiers[riskTier];
229
- const baseline = {
230
- max_files: tierBudget.max_files,
231
- max_loc: tierBudget.max_loc,
232
- };
233
-
234
- // Start with baseline budget
235
- let effectiveBudget = { ...baseline };
236
-
237
- // Apply waivers if any
238
- if (spec.waiver_ids && Array.isArray(spec.waiver_ids)) {
239
- for (const waiverId of spec.waiver_ids) {
240
- const waiver = loadWaiver(waiverId, projectRoot);
241
- if (waiver && waiver.status === 'active' && isWaiverValid(waiver)) {
242
- // Validate waiver covers budget_limit gate
243
- if (!waiver.gates || !waiver.gates.includes('budget_limit')) {
244
- console.warn(
245
- `\nWaiver ${waiverId} does not cover 'budget_limit' gate\n` +
246
- ` Current gates: [${waiver.gates ? waiver.gates.join(', ') : 'none'}]\n` +
247
- ` Add 'budget_limit' to gates array to apply to budget violations\n`
248
- );
249
- continue;
250
- }
251
-
252
- // Apply additive deltas
253
- if (waiver.delta) {
254
- if (waiver.delta.max_files) {
255
- effectiveBudget.max_files += waiver.delta.max_files;
256
- }
257
- if (waiver.delta.max_loc) {
258
- effectiveBudget.max_loc += waiver.delta.max_loc;
259
- }
260
- }
261
- }
262
- }
263
- }
369
+ return applyBudgetDerivation(spec, policy, projectRoot);
370
+ } catch (error) {
371
+ throw new Error(`Budget derivation failed: ${error.message}`);
372
+ }
373
+ }
264
374
 
265
- return {
266
- baseline,
267
- effective: effectiveBudget,
268
- waivers_applied: spec.waiver_ids || [],
269
- derived_at: new Date().toISOString(),
270
- };
375
+ /**
376
+ * Synchronous version of deriveBudget for callers that cannot go async.
377
+ * Uses `loadPolicySync` (no PolicyManager caching) and otherwise shares
378
+ * the same derivation semantics as the async variant.
379
+ *
380
+ * Added for CAWSFIX-07: the legacy synchronous call site in
381
+ * `validation/spec-validation.js` was passing the un-awaited Promise from
382
+ * `deriveBudget` into `checkBudgetCompliance`, which then read
383
+ * `derivedBudget.effective.max_files` on an undefined `.effective` —
384
+ * producing the "Cannot read properties of undefined (reading 'max_files')"
385
+ * warning on every schema-compliant spec.
386
+ *
387
+ * @param {Object} spec - Working spec object
388
+ * @param {string} projectRoot - Project root directory
389
+ * @returns {Object} Derived budget with baseline and effective limits
390
+ */
391
+ function deriveBudgetSync(spec, projectRoot = process.cwd()) {
392
+ try {
393
+ const policy = loadPolicySync(projectRoot);
394
+ return applyBudgetDerivation(spec, policy, projectRoot);
271
395
  } catch (error) {
272
396
  throw new Error(`Budget derivation failed: ${error.message}`);
273
397
  }
@@ -278,15 +402,25 @@ async function deriveBudget(spec, projectRoot = process.cwd(), options = {}) {
278
402
  * @param {Object} waiver - Waiver document to validate
279
403
  * @throws {Error} If waiver structure is invalid
280
404
  */
281
- function validateWaiverStructure(waiver) {
282
- const requiredFields = ['id', 'title', 'reason', 'status', 'gates', 'expires_at', 'approvers'];
405
+ const WAIVER_REQUIRED_FIELDS = [
406
+ 'id',
407
+ 'applies_to',
408
+ 'gates',
409
+ 'delta',
410
+ 'reason_code',
411
+ 'expires_at',
412
+ 'risk_owner',
413
+ 'approvers',
414
+ 'status',
415
+ ];
283
416
 
417
+ function validateWaiverStructure(waiver) {
284
418
  // Check all required fields present
285
- for (const field of requiredFields) {
419
+ for (const field of WAIVER_REQUIRED_FIELDS) {
286
420
  if (!(field in waiver)) {
287
421
  throw new Error(
288
422
  `Waiver missing required field: ${field}\n` +
289
- `Required fields: ${requiredFields.join(', ')}\n` +
423
+ `Required fields: ${WAIVER_REQUIRED_FIELDS.join(', ')}\n` +
290
424
  `Fix the waiver file at .caws/waivers/${waiver.id || 'unknown'}.yaml`
291
425
  );
292
426
  }
@@ -302,8 +436,8 @@ function validateWaiverStructure(waiver) {
302
436
  );
303
437
  }
304
438
 
305
- // Validate status
306
- const validStatuses = ['active', 'expired', 'revoked'];
439
+ // Validate status (proposed is valid per schema but not applied by derivation)
440
+ const validStatuses = ['proposed', 'active', 'expired', 'revoked'];
307
441
  if (!validStatuses.includes(waiver.status)) {
308
442
  throw new Error(
309
443
  `Invalid waiver status: ${waiver.status}\n` +
@@ -322,15 +456,25 @@ function validateWaiverStructure(waiver) {
322
456
  );
323
457
  }
324
458
 
325
- // Validate approvers is array
459
+ // Validate approvers is array of {handle, approved_at?} objects
326
460
  if (!Array.isArray(waiver.approvers) || waiver.approvers.length === 0) {
327
461
  throw new Error(
328
462
  `Invalid waiver approvers: ${JSON.stringify(waiver.approvers)}\n` +
329
- 'approvers must be a non-empty array of approver names/emails\n' +
330
- 'Example: approvers: ["tech-lead@company.com"]\n' +
463
+ 'approvers must be a non-empty array of objects with a `handle` field\n' +
464
+ 'Example: approvers: [{ handle: "tech-lead", approved_at: "2025-01-01T00:00:00Z" }]\n' +
331
465
  `Fix the approvers field in .caws/waivers/${waiver.id}.yaml`
332
466
  );
333
467
  }
468
+ for (const approver of waiver.approvers) {
469
+ if (typeof approver !== 'object' || approver === null || typeof approver.handle !== 'string') {
470
+ throw new Error(
471
+ `Invalid waiver approver entry: ${JSON.stringify(approver)}\n` +
472
+ 'Each approver must be an object with a required `handle` field (string).\n' +
473
+ 'Expected shape: { handle: "github-or-email", approved_at: "ISO-8601" }\n' +
474
+ `Fix the approvers field in .caws/waivers/${waiver.id}.yaml`
475
+ );
476
+ }
477
+ }
334
478
 
335
479
  // Validate expires_at is valid date string
336
480
  const expiryDate = new Date(waiver.expires_at);
@@ -451,8 +595,9 @@ function isWaiverValid(waiver, policy = null) {
451
595
  }
452
596
  }
453
597
 
454
- // Check required fields
455
- if (!waiver.id || !waiver.title || !waiver.gates) {
598
+ // Shallow sanity check. Full schema conformance is enforced by
599
+ // validateWaiverStructure at load time (see loadWaiver).
600
+ if (!waiver.id || !waiver.gates) {
456
601
  console.warn(`Waiver ${waiver.id || 'unknown'} missing required fields`);
457
602
  return false;
458
603
  }
@@ -592,6 +737,7 @@ function generateBurnupReport(derivedBudget, currentStats) {
592
737
 
593
738
  module.exports = {
594
739
  deriveBudget,
740
+ deriveBudgetSync,
595
741
  loadWaiver,
596
742
  isWaiverValid,
597
743
  checkBudgetCompliance,
@@ -601,4 +747,5 @@ module.exports = {
601
747
  validatePolicy,
602
748
  getDefaultPolicy,
603
749
  validateWaiverStructure,
750
+ WAIVER_REQUIRED_FIELDS,
604
751
  };