@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.
- package/README.md +71 -32
- package/dist/budget-derivation.js +221 -74
- package/dist/commands/archive.js +67 -28
- package/dist/commands/burnup.js +20 -11
- package/dist/commands/diagnose.js +34 -22
- package/dist/commands/evaluate.js +41 -15
- package/dist/commands/gates.js +149 -0
- package/dist/commands/init.js +150 -19
- package/dist/commands/iterate.js +81 -4
- package/dist/commands/parallel.js +4 -0
- package/dist/commands/plan.js +9 -19
- package/dist/commands/provenance.js +53 -17
- package/dist/commands/quality-monitor.js +64 -45
- package/dist/commands/scope.js +264 -0
- package/dist/commands/sidecar.js +74 -0
- package/dist/commands/specs.js +381 -45
- package/dist/commands/status.js +117 -9
- package/dist/commands/templates.js +0 -8
- package/dist/commands/tutorial.js +10 -9
- package/dist/commands/validate.js +70 -6
- package/dist/commands/verify-acs.js +48 -76
- package/dist/commands/waivers.js +212 -13
- package/dist/commands/worktree.js +131 -26
- package/dist/error-handler.js +2 -13
- package/dist/gates/budget-limit.js +121 -0
- package/dist/gates/feedback.js +260 -0
- package/dist/gates/format.js +179 -0
- package/dist/gates/god-object.js +117 -0
- package/dist/gates/pipeline.js +167 -0
- package/dist/gates/scope-boundary.js +93 -0
- package/dist/gates/spec-completeness.js +109 -0
- package/dist/gates/todo-detection.js +205 -0
- package/dist/index.js +157 -151
- package/dist/parallel/parallel-manager.js +3 -3
- package/dist/policy/PolicyManager.js +51 -17
- package/dist/scaffold/claude-hooks.js +24 -1
- package/dist/scaffold/git-hooks.js +45 -102
- package/dist/scaffold/index.js +4 -3
- package/dist/session/session-manager.js +105 -14
- package/dist/sidecars/index.js +33 -0
- package/dist/sidecars/listeners.js +40 -0
- package/dist/sidecars/provenance-summary.js +238 -0
- package/dist/sidecars/quality-gaps.js +258 -0
- package/dist/sidecars/schema.js +149 -0
- package/dist/sidecars/spec-drift.js +151 -0
- package/dist/sidecars/waiver-draft.js +176 -0
- package/dist/templates/.caws/schemas/policy.schema.json +112 -0
- package/dist/templates/.caws/schemas/scope.schema.json +3 -3
- package/dist/templates/.caws/schemas/waivers.schema.json +96 -20
- package/dist/templates/.caws/schemas/working-spec.schema.json +264 -57
- package/dist/templates/.caws/schemas/worktrees.schema.json +3 -1
- package/dist/templates/.caws/templates/working-spec.template.yml +10 -4
- package/dist/templates/.caws/tools/scope-guard.js +66 -15
- package/dist/templates/.claude/README.md +1 -1
- package/dist/templates/.claude/hooks/audit.sh +0 -0
- package/dist/templates/.claude/hooks/block-dangerous.sh +52 -11
- package/dist/templates/.claude/hooks/classify_command.py +592 -0
- package/dist/templates/.claude/hooks/doc-frontmatter-check.sh +173 -0
- package/dist/templates/.claude/hooks/protected-paths.sh +39 -0
- package/dist/templates/.claude/hooks/quality-check.sh +23 -10
- package/dist/templates/.claude/hooks/scope-guard.sh +136 -55
- package/dist/templates/.claude/hooks/session-caws-status.sh +2 -2
- package/dist/templates/.claude/hooks/session-log.sh +76 -3
- package/dist/templates/.claude/hooks/stop-worktree-check.sh +1 -1
- package/dist/templates/.claude/hooks/test_classify_command.py +370 -0
- package/dist/templates/.claude/hooks/test_wrapper_smoke.sh +96 -0
- package/dist/templates/.claude/hooks/worktree-guard.sh +2 -2
- package/dist/templates/.claude/hooks/worktree-write-guard.sh +97 -4
- package/dist/templates/.claude/settings.json +31 -0
- package/dist/templates/.cursor/hooks/caws-quality-check.sh +4 -4
- package/dist/templates/.cursor/hooks/caws-scope-guard.sh +1 -1
- package/dist/templates/.cursor/hooks/session-log.sh +924 -0
- package/dist/templates/.cursor/hooks.json +25 -0
- package/dist/templates/.cursor/rules/02-quality-gates.mdc +3 -5
- package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +6 -11
- package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +14 -18
- package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +4 -4
- package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +3 -13
- package/dist/templates/.github/copilot-instructions.md +5 -5
- package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +1 -1
- package/dist/templates/.junie/guidelines.md +2 -2
- package/dist/templates/.vscode/settings.json +3 -1
- package/dist/templates/.windsurf/rules/caws-quality-standards.md +2 -2
- package/dist/templates/.windsurf/workflows/caws-guided-development.md +3 -3
- package/dist/templates/CLAUDE.md +77 -8
- package/dist/templates/agents.md +50 -9
- package/dist/templates/docs/README.md +8 -7
- package/dist/templates/scripts/new_feature.sh +80 -0
- package/dist/test-analysis.js +43 -30
- package/dist/tool-loader.js +1 -1
- package/dist/utils/agent-session.js +202 -0
- package/dist/utils/detection.js +8 -2
- package/dist/utils/event-log.js +584 -0
- package/dist/utils/event-renderer.js +521 -0
- package/dist/utils/finalization.js +7 -6
- package/dist/utils/gitignore-updater.js +3 -0
- package/dist/utils/lifecycle-events.js +94 -0
- package/dist/utils/quality-gates-utils.js +29 -44
- package/dist/utils/schema-validator.js +50 -0
- package/dist/utils/spec-resolver.js +93 -21
- package/dist/utils/working-state.js +530 -0
- package/dist/validation/spec-validation.js +191 -31
- package/dist/waivers-manager.js +144 -6
- package/dist/worktree/worktree-manager.js +598 -95
- package/package.json +9 -8
- package/templates/.caws/schemas/policy.schema.json +112 -0
- package/templates/.caws/schemas/scope.schema.json +3 -3
- package/templates/.caws/schemas/waivers.schema.json +96 -20
- package/templates/.caws/schemas/working-spec.schema.json +264 -57
- package/templates/.caws/schemas/worktrees.schema.json +3 -1
- package/templates/.caws/templates/working-spec.template.yml +10 -4
- package/templates/.caws/tools/scope-guard.js +66 -15
- package/templates/.claude/README.md +1 -1
- package/templates/.claude/hooks/block-dangerous.sh +52 -11
- package/templates/.claude/hooks/classify_command.py +592 -0
- package/templates/.claude/hooks/doc-frontmatter-check.sh +173 -0
- package/templates/.claude/hooks/protected-paths.sh +39 -0
- package/templates/.claude/hooks/quality-check.sh +23 -10
- package/templates/.claude/hooks/scope-guard.sh +136 -55
- package/templates/.claude/hooks/session-caws-status.sh +2 -2
- package/templates/.claude/hooks/session-log.sh +76 -3
- package/templates/.claude/hooks/stop-worktree-check.sh +1 -1
- package/templates/.claude/hooks/test_classify_command.py +370 -0
- package/templates/.claude/hooks/test_wrapper_smoke.sh +96 -0
- package/templates/.claude/hooks/worktree-guard.sh +2 -2
- package/templates/.claude/hooks/worktree-write-guard.sh +97 -4
- package/templates/.claude/settings.json +31 -0
- package/templates/.cursor/hooks/caws-quality-check.sh +4 -4
- package/templates/.cursor/hooks/caws-scope-guard.sh +1 -1
- package/templates/.cursor/hooks/session-log.sh +924 -0
- package/templates/.cursor/hooks.json +25 -0
- package/templates/.cursor/rules/02-quality-gates.mdc +3 -5
- package/templates/.cursor/rules/10-documentation-quality-standards.mdc +6 -11
- package/templates/.cursor/rules/11-scope-management-waivers.mdc +14 -18
- package/templates/.cursor/rules/12-implementation-completeness.mdc +4 -4
- package/templates/.cursor/rules/13-language-agnostic-standards.mdc +3 -13
- package/templates/.github/copilot-instructions.md +5 -5
- package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +1 -1
- package/templates/.junie/guidelines.md +2 -2
- package/templates/.vscode/settings.json +3 -1
- package/templates/.windsurf/rules/caws-quality-standards.md +2 -2
- package/templates/.windsurf/workflows/caws-guided-development.md +3 -3
- package/templates/CLAUDE.md +77 -8
- package/templates/{AGENTS.md → agents.md} +50 -9
- package/templates/docs/README.md +8 -7
- package/templates/scripts/new_feature.sh +80 -0
- package/dist/budget-derivation.d.ts +0 -74
- package/dist/budget-derivation.d.ts.map +0 -1
- package/dist/cicd-optimizer.d.ts +0 -142
- package/dist/cicd-optimizer.d.ts.map +0 -1
- package/dist/commands/archive.d.ts +0 -51
- package/dist/commands/archive.d.ts.map +0 -1
- package/dist/commands/burnup.d.ts +0 -6
- package/dist/commands/burnup.d.ts.map +0 -1
- package/dist/commands/diagnose.d.ts +0 -52
- package/dist/commands/diagnose.d.ts.map +0 -1
- package/dist/commands/evaluate.d.ts +0 -8
- package/dist/commands/evaluate.d.ts.map +0 -1
- package/dist/commands/init.d.ts +0 -5
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/iterate.d.ts +0 -8
- package/dist/commands/iterate.d.ts.map +0 -1
- package/dist/commands/mode.d.ts +0 -25
- package/dist/commands/mode.d.ts.map +0 -1
- package/dist/commands/parallel.d.ts +0 -7
- package/dist/commands/parallel.d.ts.map +0 -1
- package/dist/commands/plan.d.ts +0 -49
- package/dist/commands/plan.d.ts.map +0 -1
- package/dist/commands/provenance.d.ts +0 -32
- package/dist/commands/provenance.d.ts.map +0 -1
- package/dist/commands/quality-gates.d.ts +0 -6
- package/dist/commands/quality-gates.d.ts.map +0 -1
- package/dist/commands/quality-gates.js +0 -444
- package/dist/commands/quality-monitor.d.ts +0 -17
- package/dist/commands/quality-monitor.d.ts.map +0 -1
- package/dist/commands/session.d.ts +0 -7
- package/dist/commands/session.d.ts.map +0 -1
- package/dist/commands/specs.d.ts +0 -77
- package/dist/commands/specs.d.ts.map +0 -1
- package/dist/commands/status.d.ts +0 -44
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/templates.d.ts +0 -74
- package/dist/commands/templates.d.ts.map +0 -1
- package/dist/commands/tool.d.ts +0 -13
- package/dist/commands/tool.d.ts.map +0 -1
- package/dist/commands/troubleshoot.d.ts +0 -8
- package/dist/commands/troubleshoot.d.ts.map +0 -1
- package/dist/commands/troubleshoot.js +0 -104
- package/dist/commands/tutorial.d.ts +0 -55
- package/dist/commands/tutorial.d.ts.map +0 -1
- package/dist/commands/validate.d.ts +0 -15
- package/dist/commands/validate.d.ts.map +0 -1
- package/dist/commands/waivers.d.ts +0 -8
- package/dist/commands/waivers.d.ts.map +0 -1
- package/dist/commands/workflow.d.ts +0 -85
- package/dist/commands/workflow.d.ts.map +0 -1
- package/dist/commands/worktree.d.ts +0 -7
- package/dist/commands/worktree.d.ts.map +0 -1
- package/dist/config/index.d.ts +0 -29
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/lite-scope.d.ts +0 -33
- package/dist/config/lite-scope.d.ts.map +0 -1
- package/dist/config/modes.d.ts +0 -264
- package/dist/config/modes.d.ts.map +0 -1
- package/dist/constants/spec-types.d.ts +0 -93
- package/dist/constants/spec-types.d.ts.map +0 -1
- package/dist/error-handler.d.ts +0 -151
- package/dist/error-handler.d.ts.map +0 -1
- package/dist/generators/jest-config-generator.d.ts +0 -32
- package/dist/generators/jest-config-generator.d.ts.map +0 -1
- package/dist/generators/jest-config.d.ts +0 -32
- package/dist/generators/jest-config.d.ts.map +0 -1
- package/dist/generators/jest-config.js +0 -242
- package/dist/generators/working-spec.d.ts +0 -13
- package/dist/generators/working-spec.d.ts.map +0 -1
- package/dist/index-new.d.ts +0 -5
- package/dist/index-new.d.ts.map +0 -1
- package/dist/index-new.js +0 -317
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.backup +0 -4711
- package/dist/minimal-cli.d.ts +0 -3
- package/dist/minimal-cli.d.ts.map +0 -1
- package/dist/parallel/parallel-manager.d.ts +0 -67
- package/dist/parallel/parallel-manager.d.ts.map +0 -1
- package/dist/policy/PolicyManager.d.ts +0 -104
- package/dist/policy/PolicyManager.d.ts.map +0 -1
- package/dist/scaffold/claude-hooks.d.ts +0 -28
- package/dist/scaffold/claude-hooks.d.ts.map +0 -1
- package/dist/scaffold/cursor-hooks.d.ts +0 -7
- package/dist/scaffold/cursor-hooks.d.ts.map +0 -1
- package/dist/scaffold/git-hooks.d.ts +0 -38
- package/dist/scaffold/git-hooks.d.ts.map +0 -1
- package/dist/scaffold/index.d.ts +0 -17
- package/dist/scaffold/index.d.ts.map +0 -1
- package/dist/session/session-manager.d.ts +0 -94
- package/dist/session/session-manager.d.ts.map +0 -1
- package/dist/spec/SpecFileManager.d.ts +0 -146
- package/dist/spec/SpecFileManager.d.ts.map +0 -1
- package/dist/templates/.cursor/hooks/caws-tool-validation.sh +0 -121
- package/dist/templates/.github/copilot/instructions.md +0 -311
- package/dist/test-analysis.d.ts +0 -231
- package/dist/test-analysis.d.ts.map +0 -1
- package/dist/tool-interface.d.ts +0 -236
- package/dist/tool-interface.d.ts.map +0 -1
- package/dist/tool-loader.d.ts +0 -77
- package/dist/tool-loader.d.ts.map +0 -1
- package/dist/tool-validator.d.ts +0 -72
- package/dist/tool-validator.d.ts.map +0 -1
- package/dist/utils/async-utils.d.ts +0 -73
- package/dist/utils/async-utils.d.ts.map +0 -1
- package/dist/utils/command-wrapper.d.ts +0 -66
- package/dist/utils/command-wrapper.d.ts.map +0 -1
- package/dist/utils/detection.d.ts +0 -14
- package/dist/utils/detection.d.ts.map +0 -1
- package/dist/utils/error-categories.d.ts +0 -52
- package/dist/utils/error-categories.d.ts.map +0 -1
- package/dist/utils/finalization.d.ts +0 -17
- package/dist/utils/finalization.d.ts.map +0 -1
- package/dist/utils/git-lock.d.ts +0 -13
- package/dist/utils/git-lock.d.ts.map +0 -1
- package/dist/utils/gitignore-updater.d.ts +0 -39
- package/dist/utils/gitignore-updater.d.ts.map +0 -1
- package/dist/utils/ide-detection.d.ts +0 -89
- package/dist/utils/ide-detection.d.ts.map +0 -1
- package/dist/utils/project-analysis.d.ts +0 -34
- package/dist/utils/project-analysis.d.ts.map +0 -1
- package/dist/utils/promise-utils.d.ts +0 -30
- package/dist/utils/promise-utils.d.ts.map +0 -1
- package/dist/utils/quality-gates-utils.d.ts +0 -49
- package/dist/utils/quality-gates-utils.d.ts.map +0 -1
- package/dist/utils/quality-gates.d.ts +0 -49
- package/dist/utils/quality-gates.d.ts.map +0 -1
- package/dist/utils/quality-gates.js +0 -402
- package/dist/utils/spec-resolver.d.ts +0 -80
- package/dist/utils/spec-resolver.d.ts.map +0 -1
- package/dist/utils/typescript-detector.d.ts +0 -66
- package/dist/utils/typescript-detector.d.ts.map +0 -1
- package/dist/utils/yaml-validation.d.ts +0 -32
- package/dist/utils/yaml-validation.d.ts.map +0 -1
- package/dist/validation/spec-validation.d.ts +0 -43
- package/dist/validation/spec-validation.d.ts.map +0 -1
- package/dist/waivers-manager.d.ts +0 -167
- package/dist/waivers-manager.d.ts.map +0 -1
- package/dist/worktree/worktree-manager.d.ts +0 -54
- 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
|
-
- **
|
|
16
|
-
- **
|
|
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
|
-
###
|
|
89
|
+
### Quality Gates
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
|
-
#
|
|
93
|
-
caws
|
|
92
|
+
# Run quality gates v2 pipeline
|
|
93
|
+
caws gates run
|
|
94
94
|
|
|
95
|
-
#
|
|
96
|
-
caws
|
|
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
|
-
#
|
|
99
|
-
caws
|
|
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
|
-
###
|
|
128
|
+
### Session Management
|
|
103
129
|
|
|
104
130
|
```bash
|
|
105
|
-
#
|
|
106
|
-
caws
|
|
131
|
+
# Start a tracked session
|
|
132
|
+
caws session start
|
|
107
133
|
|
|
108
|
-
#
|
|
109
|
-
caws
|
|
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
|
-
###
|
|
144
|
+
### Spec Management
|
|
113
145
|
|
|
114
146
|
```bash
|
|
115
|
-
# List
|
|
116
|
-
caws
|
|
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
|
-
#
|
|
119
|
-
caws
|
|
153
|
+
# Show a spec
|
|
154
|
+
caws specs show FEAT-001
|
|
120
155
|
|
|
121
|
-
#
|
|
122
|
-
caws
|
|
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
|
-
│ ├──
|
|
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
|
-
- **
|
|
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
|
|
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. **
|
|
176
|
-
4. **Test
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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
|
|
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: ${
|
|
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
|
|
330
|
-
'Example: approvers: ["tech-lead
|
|
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
|
-
//
|
|
455
|
-
|
|
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
|
};
|