@paths.design/caws-cli 10.1.0 → 11.0.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 +125 -374
- package/dist/index.js +43 -756
- package/dist/shell/binding/resolve-binding.d.ts +4 -0
- package/dist/shell/binding/resolve-binding.d.ts.map +1 -0
- package/dist/shell/binding/resolve-binding.js +228 -0
- package/dist/shell/binding/resolve-binding.js.map +1 -0
- package/dist/shell/binding/types.d.ts +42 -0
- package/dist/shell/binding/types.d.ts.map +1 -0
- package/dist/shell/binding/types.js +21 -0
- package/dist/shell/binding/types.js.map +1 -0
- package/dist/shell/commands/claim.d.ts +14 -0
- package/dist/shell/commands/claim.d.ts.map +1 -0
- package/dist/shell/commands/claim.js +197 -0
- package/dist/shell/commands/claim.js.map +1 -0
- package/dist/shell/commands/doctor.d.ts +13 -0
- package/dist/shell/commands/doctor.d.ts.map +1 -0
- package/dist/shell/commands/doctor.js +97 -0
- package/dist/shell/commands/doctor.js.map +1 -0
- package/dist/shell/commands/evidence.d.ts +28 -0
- package/dist/shell/commands/evidence.d.ts.map +1 -0
- package/dist/shell/commands/evidence.js +166 -0
- package/dist/shell/commands/evidence.js.map +1 -0
- package/dist/shell/commands/gates.d.ts +19 -0
- package/dist/shell/commands/gates.d.ts.map +1 -0
- package/dist/shell/commands/gates.js +181 -0
- package/dist/shell/commands/gates.js.map +1 -0
- package/dist/shell/commands/init.d.ts +8 -0
- package/dist/shell/commands/init.d.ts.map +1 -0
- package/dist/shell/commands/init.js +64 -0
- package/dist/shell/commands/init.js.map +1 -0
- package/dist/shell/commands/scope.d.ts +11 -0
- package/dist/shell/commands/scope.d.ts.map +1 -0
- package/dist/shell/commands/scope.js +92 -0
- package/dist/shell/commands/scope.js.map +1 -0
- package/dist/shell/commands/status.d.ts +15 -0
- package/dist/shell/commands/status.d.ts.map +1 -0
- package/dist/shell/commands/status.js +106 -0
- package/dist/shell/commands/status.js.map +1 -0
- package/dist/shell/commands/waiver.d.ts +38 -0
- package/dist/shell/commands/waiver.d.ts.map +1 -0
- package/dist/shell/commands/waiver.js +240 -0
- package/dist/shell/commands/waiver.js.map +1 -0
- package/dist/shell/gates/disposition.d.ts +23 -0
- package/dist/shell/gates/disposition.d.ts.map +1 -0
- package/dist/shell/gates/disposition.js +87 -0
- package/dist/shell/gates/disposition.js.map +1 -0
- package/dist/shell/gates/gate-result-contract.d.ts +39 -0
- package/dist/shell/gates/gate-result-contract.d.ts.map +1 -0
- package/dist/shell/gates/gate-result-contract.js +150 -0
- package/dist/shell/gates/gate-result-contract.js.map +1 -0
- package/dist/shell/gates/quality-gates-adapter.d.ts +55 -0
- package/dist/shell/gates/quality-gates-adapter.d.ts.map +1 -0
- package/dist/shell/gates/quality-gates-adapter.js +161 -0
- package/dist/shell/gates/quality-gates-adapter.js.map +1 -0
- package/dist/shell/gates/waiver-filter.d.ts +58 -0
- package/dist/shell/gates/waiver-filter.d.ts.map +1 -0
- package/dist/shell/gates/waiver-filter.js +119 -0
- package/dist/shell/gates/waiver-filter.js.map +1 -0
- package/dist/shell/index.d.ts +50 -0
- package/dist/shell/index.d.ts.map +1 -0
- package/dist/shell/index.js +73 -0
- package/dist/shell/index.js.map +1 -0
- package/dist/shell/register.d.ts +11 -0
- package/dist/shell/register.d.ts.map +1 -0
- package/dist/shell/register.js +274 -0
- package/dist/shell/register.js.map +1 -0
- package/dist/shell/render/claim.d.ts +22 -0
- package/dist/shell/render/claim.d.ts.map +1 -0
- package/dist/shell/render/claim.js +75 -0
- package/dist/shell/render/claim.js.map +1 -0
- package/dist/shell/render/decision.d.ts +15 -0
- package/dist/shell/render/decision.d.ts.map +1 -0
- package/dist/shell/render/decision.js +66 -0
- package/dist/shell/render/decision.js.map +1 -0
- package/dist/shell/render/diagnostic.d.ts +19 -0
- package/dist/shell/render/diagnostic.d.ts.map +1 -0
- package/dist/shell/render/diagnostic.js +76 -0
- package/dist/shell/render/diagnostic.js.map +1 -0
- package/dist/shell/render/finding.d.ts +15 -0
- package/dist/shell/render/finding.d.ts.map +1 -0
- package/dist/shell/render/finding.js +57 -0
- package/dist/shell/render/finding.js.map +1 -0
- package/dist/shell/render/gates.d.ts +3 -0
- package/dist/shell/render/gates.d.ts.map +1 -0
- package/dist/shell/render/gates.js +56 -0
- package/dist/shell/render/gates.js.map +1 -0
- package/dist/shell/render/init.d.ts +11 -0
- package/dist/shell/render/init.d.ts.map +1 -0
- package/dist/shell/render/init.js +32 -0
- package/dist/shell/render/init.js.map +1 -0
- package/dist/shell/render/status.d.ts +26 -0
- package/dist/shell/render/status.d.ts.map +1 -0
- package/dist/shell/render/status.js +143 -0
- package/dist/shell/render/status.js.map +1 -0
- package/dist/shell/render/waiver.d.ts +21 -0
- package/dist/shell/render/waiver.d.ts.map +1 -0
- package/dist/shell/render/waiver.js +94 -0
- package/dist/shell/render/waiver.js.map +1 -0
- package/dist/shell/rules.d.ts +37 -0
- package/dist/shell/rules.d.ts.map +1 -0
- package/dist/shell/rules.js +51 -0
- package/dist/shell/rules.js.map +1 -0
- package/dist/shell/session/actor.d.ts +14 -0
- package/dist/shell/session/actor.d.ts.map +1 -0
- package/dist/shell/session/actor.js +34 -0
- package/dist/shell/session/actor.js.map +1 -0
- package/dist/shell/session/resolve-session.d.ts +5 -0
- package/dist/shell/session/resolve-session.d.ts.map +1 -0
- package/dist/shell/session/resolve-session.js +239 -0
- package/dist/shell/session/resolve-session.js.map +1 -0
- package/dist/shell/session/types.d.ts +56 -0
- package/dist/shell/session/types.d.ts.map +1 -0
- package/dist/shell/session/types.js +15 -0
- package/dist/shell/session/types.js.map +1 -0
- package/dist/store/agents-store.d.ts +3 -0
- package/dist/store/agents-store.d.ts.map +1 -0
- package/dist/store/agents-store.js +63 -0
- package/dist/store/agents-store.js.map +1 -0
- package/dist/store/apply-patch.d.ts +16 -0
- package/dist/store/apply-patch.d.ts.map +1 -0
- package/dist/store/apply-patch.js +191 -0
- package/dist/store/apply-patch.js.map +1 -0
- package/dist/store/atomic-write.d.ts +16 -0
- package/dist/store/atomic-write.d.ts.map +1 -0
- package/dist/store/atomic-write.js +132 -0
- package/dist/store/atomic-write.js.map +1 -0
- package/dist/store/doctor-snapshot.d.ts +20 -0
- package/dist/store/doctor-snapshot.d.ts.map +1 -0
- package/dist/store/doctor-snapshot.js +176 -0
- package/dist/store/doctor-snapshot.js.map +1 -0
- package/dist/store/events-store.d.ts +33 -0
- package/dist/store/events-store.d.ts.map +1 -0
- package/dist/store/events-store.js +297 -0
- package/dist/store/events-store.js.map +1 -0
- package/dist/store/index.d.ts +21 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +47 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/init-store.d.ts +21 -0
- package/dist/store/init-store.d.ts.map +1 -0
- package/dist/store/init-store.js +295 -0
- package/dist/store/init-store.js.map +1 -0
- package/dist/store/json-store.d.ts +3 -0
- package/dist/store/json-store.d.ts.map +1 -0
- package/dist/store/json-store.js +65 -0
- package/dist/store/json-store.js.map +1 -0
- package/dist/store/policy-store.d.ts +3 -0
- package/dist/store/policy-store.d.ts.map +1 -0
- package/dist/store/policy-store.js +65 -0
- package/dist/store/policy-store.js.map +1 -0
- package/dist/store/repo-root.d.ts +46 -0
- package/dist/store/repo-root.d.ts.map +1 -0
- package/dist/store/repo-root.js +145 -0
- package/dist/store/repo-root.js.map +1 -0
- package/dist/store/rules.d.ts +53 -0
- package/dist/store/rules.d.ts.map +1 -0
- package/dist/store/rules.js +78 -0
- package/dist/store/rules.js.map +1 -0
- package/dist/store/specs-store.d.ts +3 -0
- package/dist/store/specs-store.d.ts.map +1 -0
- package/dist/store/specs-store.js +131 -0
- package/dist/store/specs-store.js.map +1 -0
- package/dist/store/types.d.ts +84 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/types.js +14 -0
- package/dist/store/types.js.map +1 -0
- package/dist/store/waivers-store.d.ts +25 -0
- package/dist/store/waivers-store.d.ts.map +1 -0
- package/dist/store/waivers-store.js +232 -0
- package/dist/store/waivers-store.js.map +1 -0
- package/dist/store/worktrees-store.d.ts +3 -0
- package/dist/store/worktrees-store.d.ts.map +1 -0
- package/dist/store/worktrees-store.js +62 -0
- package/dist/store/worktrees-store.js.map +1 -0
- package/dist/store/yaml-store.d.ts +9 -0
- package/dist/store/yaml-store.d.ts.map +1 -0
- package/dist/store/yaml-store.js +121 -0
- package/dist/store/yaml-store.js.map +1 -0
- package/package.json +15 -13
- package/dist/budget-derivation.js +0 -751
- package/dist/cicd-optimizer.js +0 -504
- package/dist/commands/archive.js +0 -500
- package/dist/commands/burnup.js +0 -198
- package/dist/commands/diagnose.js +0 -525
- package/dist/commands/evaluate.js +0 -314
- package/dist/commands/gates.js +0 -149
- package/dist/commands/init.js +0 -857
- package/dist/commands/iterate.js +0 -417
- package/dist/commands/mode.js +0 -269
- package/dist/commands/parallel.js +0 -242
- package/dist/commands/plan.js +0 -438
- package/dist/commands/provenance.js +0 -1143
- package/dist/commands/quality-monitor.js +0 -284
- package/dist/commands/scope.js +0 -264
- package/dist/commands/session.js +0 -312
- package/dist/commands/sidecar.js +0 -74
- package/dist/commands/specs.js +0 -1448
- package/dist/commands/status.js +0 -1151
- package/dist/commands/templates.js +0 -237
- package/dist/commands/tool.js +0 -136
- package/dist/commands/tutorial.js +0 -480
- package/dist/commands/validate.js +0 -357
- package/dist/commands/verify-acs.js +0 -443
- package/dist/commands/waivers.js +0 -599
- package/dist/commands/workflow.js +0 -243
- package/dist/commands/worktree.js +0 -386
- package/dist/config/lite-scope.js +0 -158
- package/dist/config/modes.js +0 -347
- package/dist/constants/spec-types.js +0 -65
- package/dist/gates/budget-limit.js +0 -121
- package/dist/gates/feedback.js +0 -260
- package/dist/gates/format.js +0 -179
- package/dist/gates/god-object.js +0 -117
- package/dist/gates/pipeline.js +0 -167
- package/dist/gates/scope-boundary.js +0 -93
- package/dist/gates/spec-completeness.js +0 -109
- package/dist/gates/todo-detection.js +0 -205
- package/dist/generators/jest-config-generator.js +0 -242
- package/dist/generators/working-spec.js +0 -237
- package/dist/minimal-cli.js +0 -88
- package/dist/parallel/parallel-manager.js +0 -433
- package/dist/policy/PolicyManager.js +0 -465
- package/dist/scaffold/claude-hooks.js +0 -443
- package/dist/scaffold/cursor-hooks.js +0 -177
- package/dist/scaffold/git-hooks.js +0 -928
- package/dist/scaffold/index.js +0 -794
- package/dist/session/session-manager.js +0 -653
- package/dist/sidecars/index.js +0 -33
- package/dist/sidecars/listeners.js +0 -40
- package/dist/sidecars/provenance-summary.js +0 -238
- package/dist/sidecars/quality-gaps.js +0 -258
- package/dist/sidecars/schema.js +0 -149
- package/dist/sidecars/spec-drift.js +0 -151
- package/dist/sidecars/waiver-draft.js +0 -176
- package/dist/spec/SpecFileManager.js +0 -419
- package/dist/templates/.caws/schemas/policy.schema.json +0 -112
- package/dist/templates/.caws/schemas/scope.schema.json +0 -52
- package/dist/templates/.caws/schemas/waivers.schema.json +0 -106
- package/dist/templates/.caws/schemas/working-spec.schema.json +0 -340
- package/dist/templates/.caws/schemas/worktrees.schema.json +0 -38
- package/dist/templates/.caws/templates/working-spec.template.yml +0 -80
- package/dist/templates/.caws/tools/README.md +0 -18
- package/dist/templates/.caws/tools/scope-guard.js +0 -203
- package/dist/templates/.caws/tools-allow.json +0 -331
- package/dist/templates/.caws/waivers.yml +0 -19
- package/dist/templates/.claude/README.md +0 -190
- package/dist/templates/.claude/hooks/audit.sh +0 -121
- package/dist/templates/.claude/hooks/block-dangerous.sh +0 -203
- package/dist/templates/.claude/hooks/classify_command.py +0 -592
- package/dist/templates/.claude/hooks/doc-frontmatter-check.sh +0 -173
- package/dist/templates/.claude/hooks/lite-sprawl-check.sh +0 -145
- package/dist/templates/.claude/hooks/naming-check.sh +0 -100
- package/dist/templates/.claude/hooks/protected-paths.sh +0 -39
- package/dist/templates/.claude/hooks/quality-check.sh +0 -81
- package/dist/templates/.claude/hooks/scan-secrets.sh +0 -85
- package/dist/templates/.claude/hooks/scope-guard.sh +0 -381
- package/dist/templates/.claude/hooks/session-caws-status.sh +0 -117
- package/dist/templates/.claude/hooks/session-log.sh +0 -634
- package/dist/templates/.claude/hooks/simplification-guard.sh +0 -92
- package/dist/templates/.claude/hooks/stop-worktree-check.sh +0 -46
- package/dist/templates/.claude/hooks/test_classify_command.py +0 -370
- package/dist/templates/.claude/hooks/test_wrapper_smoke.sh +0 -96
- package/dist/templates/.claude/hooks/validate-spec.sh +0 -76
- package/dist/templates/.claude/hooks/worktree-guard.sh +0 -220
- package/dist/templates/.claude/hooks/worktree-write-guard.sh +0 -190
- package/dist/templates/.claude/rules/git-safety.md +0 -26
- package/dist/templates/.claude/rules/worktree-isolation.md +0 -83
- package/dist/templates/.claude/settings.json +0 -141
- package/dist/templates/.cursor/README.md +0 -299
- package/dist/templates/.cursor/hooks/audit.sh +0 -55
- package/dist/templates/.cursor/hooks/block-dangerous.sh +0 -84
- package/dist/templates/.cursor/hooks/caws-quality-check.sh +0 -52
- package/dist/templates/.cursor/hooks/caws-scope-guard.sh +0 -130
- package/dist/templates/.cursor/hooks/format.sh +0 -38
- package/dist/templates/.cursor/hooks/naming-check.sh +0 -64
- package/dist/templates/.cursor/hooks/scan-secrets.sh +0 -51
- package/dist/templates/.cursor/hooks/scope-guard.sh +0 -52
- package/dist/templates/.cursor/hooks/session-log.sh +0 -924
- package/dist/templates/.cursor/hooks/validate-spec.sh +0 -83
- package/dist/templates/.cursor/hooks.json +0 -76
- package/dist/templates/.cursor/rules/00-claims-verification.mdc +0 -144
- package/dist/templates/.cursor/rules/01-working-style.mdc +0 -50
- package/dist/templates/.cursor/rules/02-quality-gates.mdc +0 -368
- package/dist/templates/.cursor/rules/03-naming-and-refactor.mdc +0 -33
- package/dist/templates/.cursor/rules/04-logging-language-style.mdc +0 -23
- package/dist/templates/.cursor/rules/05-safe-defaults-guards.mdc +0 -23
- package/dist/templates/.cursor/rules/06-typescript-conventions.mdc +0 -36
- package/dist/templates/.cursor/rules/07-process-ops.mdc +0 -20
- package/dist/templates/.cursor/rules/08-solid-and-architecture.mdc +0 -16
- package/dist/templates/.cursor/rules/09-docstrings.mdc +0 -89
- package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +0 -385
- package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +0 -381
- package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +0 -516
- package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +0 -578
- package/dist/templates/.cursor/rules/README.md +0 -148
- package/dist/templates/.github/copilot-instructions.md +0 -82
- package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +0 -5
- package/dist/templates/.idea/runConfigurations/CAWS_Validate.xml +0 -5
- package/dist/templates/.junie/guidelines.md +0 -73
- package/dist/templates/.vscode/launch.json +0 -17
- package/dist/templates/.vscode/settings.json +0 -95
- package/dist/templates/.windsurf/rules/caws-quality-standards.md +0 -54
- package/dist/templates/.windsurf/workflows/caws-guided-development.md +0 -92
- package/dist/templates/CLAUDE.md +0 -174
- package/dist/templates/COMMIT_CONVENTIONS.md +0 -86
- package/dist/templates/OIDC_SETUP.md +0 -300
- package/dist/templates/agents.md +0 -145
- package/dist/templates/codemod/README.md +0 -1
- package/dist/templates/codemod/test.js +0 -93
- package/dist/templates/docs/README.md +0 -151
- package/dist/templates/scripts/new_feature.sh +0 -80
- package/dist/templates/scripts/quality-gates/check-god-objects.js +0 -146
- package/dist/templates/scripts/quality-gates/run-quality-gates.js +0 -50
- package/dist/templates/scripts/v3/analysis/todo_analyzer.py +0 -1997
- package/dist/test-analysis.js +0 -786
- package/dist/tool-interface.js +0 -314
- package/dist/tool-loader.js +0 -303
- package/dist/tool-validator.js +0 -393
- package/dist/utils/agent-session.js +0 -202
- package/dist/utils/async-utils.js +0 -188
- package/dist/utils/command-wrapper.js +0 -200
- package/dist/utils/event-log.js +0 -584
- package/dist/utils/event-renderer.js +0 -521
- package/dist/utils/finalization.js +0 -230
- package/dist/utils/git-lock.js +0 -119
- package/dist/utils/gitignore-updater.js +0 -158
- package/dist/utils/ide-detection.js +0 -133
- package/dist/utils/lifecycle-events.js +0 -94
- package/dist/utils/project-analysis.js +0 -367
- package/dist/utils/promise-utils.js +0 -72
- package/dist/utils/quality-gates-errors.js +0 -520
- package/dist/utils/quality-gates-utils.js +0 -387
- package/dist/utils/schema-validator.js +0 -50
- package/dist/utils/spec-resolver.js +0 -711
- package/dist/utils/typescript-detector.js +0 -369
- package/dist/utils/working-state.js +0 -530
- package/dist/utils/yaml-validation.js +0 -156
- package/dist/validation/spec-validation.js +0 -921
- package/dist/waivers-manager.js +0 -732
- package/dist/worktree/worktree-manager.js +0 -1374
- package/templates/.caws/schemas/policy.schema.json +0 -112
- package/templates/.caws/schemas/scope.schema.json +0 -52
- package/templates/.caws/schemas/waivers.schema.json +0 -106
- package/templates/.caws/schemas/working-spec.schema.json +0 -340
- package/templates/.caws/schemas/worktrees.schema.json +0 -38
- package/templates/.caws/templates/working-spec.template.yml +0 -80
- package/templates/.caws/tools/README.md +0 -18
- package/templates/.caws/tools/scope-guard.js +0 -203
- package/templates/.caws/tools-allow.json +0 -331
- package/templates/.caws/waivers.yml +0 -19
- package/templates/.claude/README.md +0 -190
- package/templates/.claude/hooks/audit.sh +0 -121
- package/templates/.claude/hooks/block-dangerous.sh +0 -203
- package/templates/.claude/hooks/classify_command.py +0 -592
- package/templates/.claude/hooks/doc-frontmatter-check.sh +0 -173
- package/templates/.claude/hooks/lite-sprawl-check.sh +0 -145
- package/templates/.claude/hooks/naming-check.sh +0 -100
- package/templates/.claude/hooks/protected-paths.sh +0 -39
- package/templates/.claude/hooks/quality-check.sh +0 -81
- package/templates/.claude/hooks/scan-secrets.sh +0 -85
- package/templates/.claude/hooks/scope-guard.sh +0 -381
- package/templates/.claude/hooks/session-caws-status.sh +0 -117
- package/templates/.claude/hooks/session-log.sh +0 -634
- package/templates/.claude/hooks/simplification-guard.sh +0 -92
- package/templates/.claude/hooks/stop-worktree-check.sh +0 -46
- package/templates/.claude/hooks/test_classify_command.py +0 -370
- package/templates/.claude/hooks/test_wrapper_smoke.sh +0 -96
- package/templates/.claude/hooks/validate-spec.sh +0 -76
- package/templates/.claude/hooks/worktree-guard.sh +0 -220
- package/templates/.claude/hooks/worktree-write-guard.sh +0 -190
- package/templates/.claude/rules/git-safety.md +0 -26
- package/templates/.claude/rules/worktree-isolation.md +0 -83
- package/templates/.claude/settings.json +0 -141
- package/templates/.cursor/README.md +0 -299
- package/templates/.cursor/hooks/audit.sh +0 -55
- package/templates/.cursor/hooks/block-dangerous.sh +0 -84
- package/templates/.cursor/hooks/caws-quality-check.sh +0 -52
- package/templates/.cursor/hooks/caws-scope-guard.sh +0 -130
- package/templates/.cursor/hooks/format.sh +0 -38
- package/templates/.cursor/hooks/naming-check.sh +0 -64
- package/templates/.cursor/hooks/scan-secrets.sh +0 -51
- package/templates/.cursor/hooks/scope-guard.sh +0 -52
- package/templates/.cursor/hooks/session-log.sh +0 -924
- package/templates/.cursor/hooks/validate-spec.sh +0 -83
- package/templates/.cursor/hooks.json +0 -76
- package/templates/.cursor/rules/00-claims-verification.mdc +0 -144
- package/templates/.cursor/rules/01-working-style.mdc +0 -50
- package/templates/.cursor/rules/02-quality-gates.mdc +0 -368
- package/templates/.cursor/rules/03-naming-and-refactor.mdc +0 -33
- package/templates/.cursor/rules/04-logging-language-style.mdc +0 -23
- package/templates/.cursor/rules/05-safe-defaults-guards.mdc +0 -23
- package/templates/.cursor/rules/06-typescript-conventions.mdc +0 -36
- package/templates/.cursor/rules/07-process-ops.mdc +0 -20
- package/templates/.cursor/rules/08-solid-and-architecture.mdc +0 -16
- package/templates/.cursor/rules/09-docstrings.mdc +0 -89
- package/templates/.cursor/rules/10-documentation-quality-standards.mdc +0 -385
- package/templates/.cursor/rules/11-scope-management-waivers.mdc +0 -381
- package/templates/.cursor/rules/12-implementation-completeness.mdc +0 -516
- package/templates/.cursor/rules/13-language-agnostic-standards.mdc +0 -578
- package/templates/.cursor/rules/README.md +0 -148
- package/templates/.github/copilot-instructions.md +0 -82
- package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +0 -5
- package/templates/.idea/runConfigurations/CAWS_Validate.xml +0 -5
- package/templates/.junie/guidelines.md +0 -73
- package/templates/.vscode/launch.json +0 -17
- package/templates/.vscode/settings.json +0 -95
- package/templates/.windsurf/rules/caws-quality-standards.md +0 -54
- package/templates/.windsurf/workflows/caws-guided-development.md +0 -92
- package/templates/CLAUDE.md +0 -174
- package/templates/COMMIT_CONVENTIONS.md +0 -86
- package/templates/OIDC_SETUP.md +0 -300
- package/templates/agents.md +0 -145
- package/templates/codemod/README.md +0 -1
- package/templates/codemod/test.js +0 -93
- package/templates/docs/README.md +0 -151
- package/templates/scripts/new_feature.sh +0 -80
- package/templates/scripts/quality-gates/check-god-objects.js +0 -146
- package/templates/scripts/quality-gates/run-quality-gates.js +0 -50
- package/templates/scripts/v3/analysis/todo_analyzer.py +0 -1997
|
@@ -1,751 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Budget Derivation Logic
|
|
3
|
-
* Derives budgets from policy.yaml and applies waivers
|
|
4
|
-
* Enhanced with PolicyManager for caching and improved performance
|
|
5
|
-
* @author @darianrosebrook
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require('fs-extra');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const yaml = require('js-yaml');
|
|
11
|
-
const { defaultPolicyManager } = require('./policy/PolicyManager');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Validate policy structure and content
|
|
15
|
-
* @param {Object} policy - Policy object from policy.yaml
|
|
16
|
-
* @throws {Error} If policy is invalid
|
|
17
|
-
*/
|
|
18
|
-
function validatePolicy(policy) {
|
|
19
|
-
// Validate version
|
|
20
|
-
if (!policy.version) {
|
|
21
|
-
throw new Error(
|
|
22
|
-
'Policy missing version field\n' +
|
|
23
|
-
'Add "version: 1" to .caws/policy.yaml\n' +
|
|
24
|
-
'Run "caws init" to regenerate policy.yaml'
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Validate risk_tiers exists
|
|
29
|
-
if (!policy.risk_tiers) {
|
|
30
|
-
throw new Error(
|
|
31
|
-
'Policy missing risk_tiers configuration\n' +
|
|
32
|
-
'Policy must define risk tiers 1, 2, and 3\n' +
|
|
33
|
-
'Run "caws init" to regenerate policy.yaml'
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Validate each required tier
|
|
38
|
-
for (const tier of [1, 2, 3]) {
|
|
39
|
-
if (!policy.risk_tiers[tier]) {
|
|
40
|
-
throw new Error(
|
|
41
|
-
`Policy missing configuration for risk tier ${tier}\n` +
|
|
42
|
-
`Add risk_tiers.${tier} with max_files and max_loc to .caws/policy.yaml\n` +
|
|
43
|
-
'Run "caws init" to regenerate policy.yaml'
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const tierConfig = policy.risk_tiers[tier];
|
|
48
|
-
|
|
49
|
-
// Validate max_files
|
|
50
|
-
if (!tierConfig.max_files || tierConfig.max_files <= 0) {
|
|
51
|
-
throw new Error(
|
|
52
|
-
`Invalid max_files for tier ${tier}: ${tierConfig.max_files}\n` +
|
|
53
|
-
`max_files must be a positive integer\n` +
|
|
54
|
-
`Fix in .caws/policy.yaml under risk_tiers.${tier}.max_files`
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Validate max_loc
|
|
59
|
-
if (!tierConfig.max_loc || tierConfig.max_loc <= 0) {
|
|
60
|
-
throw new Error(
|
|
61
|
-
`Invalid max_loc for tier ${tier}: ${tierConfig.max_loc}\n` +
|
|
62
|
-
`max_loc must be a positive integer\n` +
|
|
63
|
-
`Fix in .caws/policy.yaml under risk_tiers.${tier}.max_loc`
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Validate thresholds if present
|
|
68
|
-
if (
|
|
69
|
-
tierConfig.coverage_threshold !== undefined &&
|
|
70
|
-
(tierConfig.coverage_threshold < 0 || tierConfig.coverage_threshold > 100)
|
|
71
|
-
) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
`Invalid coverage_threshold for tier ${tier}: ${tierConfig.coverage_threshold}\n` +
|
|
74
|
-
`coverage_threshold must be between 0 and 100\n` +
|
|
75
|
-
`Fix in .caws/policy.yaml under risk_tiers.${tier}.coverage_threshold`
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
tierConfig.mutation_threshold !== undefined &&
|
|
81
|
-
(tierConfig.mutation_threshold < 0 || tierConfig.mutation_threshold > 100)
|
|
82
|
-
) {
|
|
83
|
-
throw new Error(
|
|
84
|
-
`Invalid mutation_threshold for tier ${tier}: ${tierConfig.mutation_threshold}\n` +
|
|
85
|
-
`mutation_threshold must be between 0 and 100\n` +
|
|
86
|
-
`Fix in .caws/policy.yaml under risk_tiers.${tier}.mutation_threshold`
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Validate waiver_approval if present
|
|
92
|
-
if (policy.waiver_approval) {
|
|
93
|
-
if (
|
|
94
|
-
policy.waiver_approval.required_approvers !== undefined &&
|
|
95
|
-
policy.waiver_approval.required_approvers < 0
|
|
96
|
-
) {
|
|
97
|
-
throw new Error(
|
|
98
|
-
`Invalid waiver_approval.required_approvers: ${policy.waiver_approval.required_approvers}\n` +
|
|
99
|
-
'required_approvers must be a non-negative integer'
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
policy.waiver_approval.max_duration_days !== undefined &&
|
|
105
|
-
policy.waiver_approval.max_duration_days <= 0
|
|
106
|
-
) {
|
|
107
|
-
throw new Error(
|
|
108
|
-
`Invalid waiver_approval.max_duration_days: ${policy.waiver_approval.max_duration_days}\n` +
|
|
109
|
-
'max_duration_days must be a positive integer'
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Get default policy as fallback
|
|
117
|
-
* @returns {Object} Default CAWS policy
|
|
118
|
-
*/
|
|
119
|
-
function getDefaultPolicy() {
|
|
120
|
-
return {
|
|
121
|
-
version: 1,
|
|
122
|
-
risk_tiers: {
|
|
123
|
-
1: {
|
|
124
|
-
max_files: 25,
|
|
125
|
-
max_loc: 1000,
|
|
126
|
-
coverage_threshold: 90,
|
|
127
|
-
mutation_threshold: 70,
|
|
128
|
-
contracts_required: true,
|
|
129
|
-
manual_review_required: true,
|
|
130
|
-
description: 'Critical changes requiring manual review',
|
|
131
|
-
},
|
|
132
|
-
2: {
|
|
133
|
-
max_files: 50,
|
|
134
|
-
max_loc: 2000,
|
|
135
|
-
coverage_threshold: 80,
|
|
136
|
-
mutation_threshold: 50,
|
|
137
|
-
contracts_required: true,
|
|
138
|
-
manual_review_required: false,
|
|
139
|
-
description: 'Standard features with automated gates',
|
|
140
|
-
},
|
|
141
|
-
3: {
|
|
142
|
-
max_files: 100,
|
|
143
|
-
max_loc: 5000,
|
|
144
|
-
coverage_threshold: 70,
|
|
145
|
-
mutation_threshold: 30,
|
|
146
|
-
contracts_required: false,
|
|
147
|
-
manual_review_required: false,
|
|
148
|
-
description: 'Low-risk changes with minimal oversight',
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
waiver_approval: {
|
|
152
|
-
required_approvers: 1,
|
|
153
|
-
max_duration_days: 90,
|
|
154
|
-
auto_revoke_expired: true,
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
}
|
|
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
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Derive budget for a working spec based on policy and waivers
|
|
323
|
-
* Enhanced to use PolicyManager for caching
|
|
324
|
-
* @param {Object} spec - Working spec object
|
|
325
|
-
* @param {string} projectRoot - Project root directory
|
|
326
|
-
* @param {Object} options - Derivation options
|
|
327
|
-
* @param {boolean} options.useCache - Use cached policy (default: true)
|
|
328
|
-
* @returns {Object} Derived budget with baseline and effective limits
|
|
329
|
-
*/
|
|
330
|
-
async function deriveBudget(spec, projectRoot = process.cwd(), options = {}) {
|
|
331
|
-
try {
|
|
332
|
-
// Load policy using PolicyManager (with caching)
|
|
333
|
-
const policyResult = await defaultPolicyManager.loadPolicy(projectRoot, {
|
|
334
|
-
useCache: options.useCache !== false,
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
const policy = policyResult;
|
|
338
|
-
|
|
339
|
-
// Check if using default policy
|
|
340
|
-
if (policy._isDefault) {
|
|
341
|
-
const expectedPath = path.join(projectRoot, '.caws', 'policy.yaml');
|
|
342
|
-
const policyExists = fs.existsSync(expectedPath);
|
|
343
|
-
|
|
344
|
-
if (policyExists) {
|
|
345
|
-
console.error(
|
|
346
|
-
'Policy file exists but not loaded: ' +
|
|
347
|
-
expectedPath +
|
|
348
|
-
'\n' +
|
|
349
|
-
' Current working directory: ' +
|
|
350
|
-
process.cwd() +
|
|
351
|
-
'\n' +
|
|
352
|
-
' Project root: ' +
|
|
353
|
-
projectRoot +
|
|
354
|
-
'\n' +
|
|
355
|
-
' Cache status: ' +
|
|
356
|
-
(policy._cacheHit ? 'HIT (may be stale)' : 'MISS') +
|
|
357
|
-
'\n' +
|
|
358
|
-
' This may be a path resolution or caching issue\n'
|
|
359
|
-
);
|
|
360
|
-
} else {
|
|
361
|
-
// Policy.yaml is optional - defaults work fine, so don't warn unnecessarily
|
|
362
|
-
// Only show info message if user explicitly wants to see it
|
|
363
|
-
if (options.showPolicyInfo !== false) {
|
|
364
|
-
// Silent by default - policy.yaml is optional
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return applyBudgetDerivation(spec, policy, projectRoot);
|
|
370
|
-
} catch (error) {
|
|
371
|
-
throw new Error(`Budget derivation failed: ${error.message}`);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
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);
|
|
395
|
-
} catch (error) {
|
|
396
|
-
throw new Error(`Budget derivation failed: ${error.message}`);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Validate waiver document structure
|
|
402
|
-
* @param {Object} waiver - Waiver document to validate
|
|
403
|
-
* @throws {Error} If waiver structure is invalid
|
|
404
|
-
*/
|
|
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
|
-
];
|
|
416
|
-
|
|
417
|
-
function validateWaiverStructure(waiver) {
|
|
418
|
-
// Check all required fields present
|
|
419
|
-
for (const field of WAIVER_REQUIRED_FIELDS) {
|
|
420
|
-
if (!(field in waiver)) {
|
|
421
|
-
throw new Error(
|
|
422
|
-
`Waiver missing required field: ${field}\n` +
|
|
423
|
-
`Required fields: ${WAIVER_REQUIRED_FIELDS.join(', ')}\n` +
|
|
424
|
-
`Fix the waiver file at .caws/waivers/${waiver.id || 'unknown'}.yaml`
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Validate ID format (WV-XXXX)
|
|
430
|
-
if (!/^WV-\d{4}$/.test(waiver.id)) {
|
|
431
|
-
throw new Error(
|
|
432
|
-
`Invalid waiver ID format: ${waiver.id}\n` +
|
|
433
|
-
'Waiver IDs must follow the format: WV-XXXX (e.g., WV-0001)\n' +
|
|
434
|
-
'Where XXXX is a 4-digit number\n' +
|
|
435
|
-
`Fix the id field in .caws/waivers/${waiver.id}.yaml`
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Validate status (proposed is valid per schema but not applied by derivation)
|
|
440
|
-
const validStatuses = ['proposed', 'active', 'expired', 'revoked'];
|
|
441
|
-
if (!validStatuses.includes(waiver.status)) {
|
|
442
|
-
throw new Error(
|
|
443
|
-
`Invalid waiver status: ${waiver.status}\n` +
|
|
444
|
-
`Status must be one of: ${validStatuses.join(', ')}\n` +
|
|
445
|
-
`Fix the status field in .caws/waivers/${waiver.id}.yaml`
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Validate gates is array
|
|
450
|
-
if (!Array.isArray(waiver.gates) || waiver.gates.length === 0) {
|
|
451
|
-
throw new Error(
|
|
452
|
-
`Invalid waiver gates: ${JSON.stringify(waiver.gates)}\n` +
|
|
453
|
-
'gates must be a non-empty array of gate names\n' +
|
|
454
|
-
`Example: gates: ["budget_limit", "coverage_threshold"]\n` +
|
|
455
|
-
`Fix the gates field in .caws/waivers/${waiver.id}.yaml`
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Validate approvers is array of {handle, approved_at?} objects
|
|
460
|
-
if (!Array.isArray(waiver.approvers) || waiver.approvers.length === 0) {
|
|
461
|
-
throw new Error(
|
|
462
|
-
`Invalid waiver approvers: ${JSON.stringify(waiver.approvers)}\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' +
|
|
465
|
-
`Fix the approvers field in .caws/waivers/${waiver.id}.yaml`
|
|
466
|
-
);
|
|
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
|
-
}
|
|
478
|
-
|
|
479
|
-
// Validate expires_at is valid date string
|
|
480
|
-
const expiryDate = new Date(waiver.expires_at);
|
|
481
|
-
if (isNaN(expiryDate.getTime())) {
|
|
482
|
-
throw new Error(
|
|
483
|
-
`Invalid waiver expiry date: ${waiver.expires_at}\n` +
|
|
484
|
-
'expires_at must be a valid ISO 8601 date string\n' +
|
|
485
|
-
'Example: expires_at: "2025-12-31T23:59:59Z"\n' +
|
|
486
|
-
`Fix the expires_at field in .caws/waivers/${waiver.id}.yaml`
|
|
487
|
-
);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// Validate delta if present
|
|
491
|
-
if (waiver.delta) {
|
|
492
|
-
if (waiver.delta.max_files !== undefined && waiver.delta.max_files < 0) {
|
|
493
|
-
throw new Error(
|
|
494
|
-
`Invalid waiver delta.max_files: ${waiver.delta.max_files}\n` +
|
|
495
|
-
'delta.max_files must be a non-negative integer\n' +
|
|
496
|
-
`Fix the delta field in .caws/waivers/${waiver.id}.yaml`
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
if (waiver.delta.max_loc !== undefined && waiver.delta.max_loc < 0) {
|
|
501
|
-
throw new Error(
|
|
502
|
-
`Invalid waiver delta.max_loc: ${waiver.delta.max_loc}\n` +
|
|
503
|
-
'delta.max_loc must be a non-negative integer\n' +
|
|
504
|
-
`Fix the delta field in .caws/waivers/${waiver.id}.yaml`
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
/**
|
|
511
|
-
* Load a waiver by ID
|
|
512
|
-
* Enhanced with structure validation and detailed error reporting
|
|
513
|
-
* @param {string} waiverId - Waiver ID (e.g., WV-0001)
|
|
514
|
-
* @param {string} projectRoot - Project root directory
|
|
515
|
-
* @returns {Object|null} Waiver object or null if not found
|
|
516
|
-
*/
|
|
517
|
-
function loadWaiver(waiverId, projectRoot) {
|
|
518
|
-
try {
|
|
519
|
-
// Validate ID format before attempting to load
|
|
520
|
-
if (!/^WV-\d{4}$/.test(waiverId)) {
|
|
521
|
-
console.error(
|
|
522
|
-
`\nInvalid waiver ID format: ${waiverId}\n` +
|
|
523
|
-
` Waiver IDs must be exactly 4 digits: WV-0001 through WV-9999\n` +
|
|
524
|
-
` Fix waiver_ids in .caws/working-spec.yaml\n`
|
|
525
|
-
);
|
|
526
|
-
return null;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
const waiverPath = path.join(projectRoot, '.caws', 'waivers', `${waiverId}.yaml`);
|
|
530
|
-
if (!fs.existsSync(waiverPath)) {
|
|
531
|
-
console.error(
|
|
532
|
-
`\nWaiver file not found: ${waiverId}\n` +
|
|
533
|
-
` Expected location: ${waiverPath}\n` +
|
|
534
|
-
` Create waiver with: caws waiver create\n`
|
|
535
|
-
);
|
|
536
|
-
return null;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
const waiver = yaml.load(fs.readFileSync(waiverPath, 'utf8'));
|
|
540
|
-
|
|
541
|
-
// Validate waiver structure
|
|
542
|
-
try {
|
|
543
|
-
validateWaiverStructure(waiver);
|
|
544
|
-
} catch (error) {
|
|
545
|
-
console.error(`\nInvalid waiver ${waiverId}: ${error.message}\n`);
|
|
546
|
-
return null;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
return waiver;
|
|
550
|
-
} catch (error) {
|
|
551
|
-
console.error(`\nFailed to load waiver ${waiverId}: ${error.message}\n`);
|
|
552
|
-
return null;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* Check if a waiver is currently valid
|
|
558
|
-
* Enhanced with proper expiry and approval validation
|
|
559
|
-
* @param {Object} waiver - Waiver object
|
|
560
|
-
* @param {Object} policy - Policy configuration (optional)
|
|
561
|
-
* @returns {boolean} Whether waiver is valid and active
|
|
562
|
-
*/
|
|
563
|
-
function isWaiverValid(waiver, policy = null) {
|
|
564
|
-
try {
|
|
565
|
-
// Check status first
|
|
566
|
-
if (waiver.status !== 'active') {
|
|
567
|
-
console.warn(`Waiver ${waiver.id} has status: ${waiver.status}`);
|
|
568
|
-
return false;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// Check if expired
|
|
572
|
-
if (waiver.expires_at) {
|
|
573
|
-
const expiryDate = new Date(waiver.expires_at);
|
|
574
|
-
const now = new Date();
|
|
575
|
-
if (now > expiryDate) {
|
|
576
|
-
console.warn(`Waiver ${waiver.id} expired on ${waiver.expires_at}`);
|
|
577
|
-
return false;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// Check required approvals
|
|
582
|
-
if (!waiver.approvers || waiver.approvers.length === 0) {
|
|
583
|
-
console.warn(`Waiver ${waiver.id} has no approvers`);
|
|
584
|
-
return false;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// Validate minimum approvers if policy provided
|
|
588
|
-
if (policy && policy.waiver_approval && policy.waiver_approval.required_approvers) {
|
|
589
|
-
const minApprovers = policy.waiver_approval.required_approvers;
|
|
590
|
-
if (waiver.approvers.length < minApprovers) {
|
|
591
|
-
console.warn(
|
|
592
|
-
`Waiver ${waiver.id} has ${waiver.approvers.length} approvers, needs ${minApprovers}`
|
|
593
|
-
);
|
|
594
|
-
return false;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// Shallow sanity check. Full schema conformance is enforced by
|
|
599
|
-
// validateWaiverStructure at load time (see loadWaiver).
|
|
600
|
-
if (!waiver.id || !waiver.gates) {
|
|
601
|
-
console.warn(`Waiver ${waiver.id || 'unknown'} missing required fields`);
|
|
602
|
-
return false;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
return true;
|
|
606
|
-
} catch (error) {
|
|
607
|
-
console.warn(`Waiver validation error: ${error.message}`);
|
|
608
|
-
return false;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
* Check if current changes exceed derived budget
|
|
614
|
-
* @param {Object} derivedBudget - Budget from deriveBudget()
|
|
615
|
-
* @param {Object} currentStats - Current change statistics
|
|
616
|
-
* @returns {Object} Budget check result
|
|
617
|
-
*/
|
|
618
|
-
function checkBudgetCompliance(derivedBudget, currentStats) {
|
|
619
|
-
const violations = [];
|
|
620
|
-
|
|
621
|
-
if (currentStats.files_changed > derivedBudget.effective.max_files) {
|
|
622
|
-
violations.push({
|
|
623
|
-
gate: 'budget_limit',
|
|
624
|
-
type: 'max_files',
|
|
625
|
-
current: currentStats.files_changed,
|
|
626
|
-
limit: derivedBudget.effective.max_files,
|
|
627
|
-
baseline: derivedBudget.baseline.max_files,
|
|
628
|
-
message: `File count (${currentStats.files_changed}) exceeds budget (${derivedBudget.effective.max_files})`,
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
if (currentStats.lines_changed > derivedBudget.effective.max_loc) {
|
|
633
|
-
violations.push({
|
|
634
|
-
gate: 'budget_limit',
|
|
635
|
-
type: 'max_loc',
|
|
636
|
-
current: currentStats.lines_changed,
|
|
637
|
-
limit: derivedBudget.effective.max_loc,
|
|
638
|
-
baseline: derivedBudget.baseline.max_loc,
|
|
639
|
-
message: `Lines of code (${currentStats.lines_changed}) exceed budget (${derivedBudget.effective.max_loc})`,
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
return {
|
|
644
|
-
compliant: violations.length === 0,
|
|
645
|
-
violations,
|
|
646
|
-
budget: derivedBudget,
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
/**
|
|
651
|
-
* Calculate budget utilization percentages
|
|
652
|
-
* @param {Object} budgetCompliance - Budget compliance result
|
|
653
|
-
* @returns {Object} Utilization percentages
|
|
654
|
-
*/
|
|
655
|
-
function calculateBudgetUtilization(budgetCompliance) {
|
|
656
|
-
const filesPercent =
|
|
657
|
-
budgetCompliance.budget.effective.max_files > 0
|
|
658
|
-
? (budgetCompliance.budget.baseline.max_files / budgetCompliance.budget.effective.max_files) *
|
|
659
|
-
100
|
|
660
|
-
: 0;
|
|
661
|
-
|
|
662
|
-
const locPercent =
|
|
663
|
-
budgetCompliance.budget.effective.max_loc > 0
|
|
664
|
-
? (budgetCompliance.budget.baseline.max_loc / budgetCompliance.budget.effective.max_loc) * 100
|
|
665
|
-
: 0;
|
|
666
|
-
|
|
667
|
-
return {
|
|
668
|
-
files: Math.round(filesPercent),
|
|
669
|
-
loc: Math.round(locPercent),
|
|
670
|
-
overall: Math.round(Math.max(filesPercent, locPercent)),
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
/**
|
|
675
|
-
* Check if changes are approaching budget limit
|
|
676
|
-
* @param {Object} budgetCompliance - Budget compliance result
|
|
677
|
-
* @param {number} threshold - Warning threshold (default 80)
|
|
678
|
-
* @returns {boolean} Whether approaching limit
|
|
679
|
-
*/
|
|
680
|
-
function isApproachingBudgetLimit(budgetCompliance, threshold = 80) {
|
|
681
|
-
const utilization = calculateBudgetUtilization(budgetCompliance);
|
|
682
|
-
return utilization.overall >= threshold;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
* Generate burn-up report for scope visibility
|
|
687
|
-
* Enhanced with utilization metrics and warnings
|
|
688
|
-
* @param {Object} derivedBudget - Budget from deriveBudget()
|
|
689
|
-
* @param {Object} currentStats - Current change statistics
|
|
690
|
-
* @returns {string} Human-readable burn-up report
|
|
691
|
-
*/
|
|
692
|
-
function generateBurnupReport(derivedBudget, currentStats) {
|
|
693
|
-
const report = [
|
|
694
|
-
'CAWS Budget Burn-up Report',
|
|
695
|
-
'===============================',
|
|
696
|
-
'',
|
|
697
|
-
`Risk Tier: ${currentStats.risk_tier}`,
|
|
698
|
-
`Baseline: ${derivedBudget.baseline.max_files} files, ${derivedBudget.baseline.max_loc} LOC`,
|
|
699
|
-
`Current: ${currentStats.files_changed} files, ${currentStats.lines_changed} LOC`,
|
|
700
|
-
];
|
|
701
|
-
|
|
702
|
-
if (derivedBudget.waivers_applied.length > 0) {
|
|
703
|
-
report.push('');
|
|
704
|
-
report.push(`Waivers Applied: ${derivedBudget.waivers_applied.join(', ')}`);
|
|
705
|
-
report.push(
|
|
706
|
-
`Effective Budget: ${derivedBudget.effective.max_files} files, ${derivedBudget.effective.max_loc} LOC`
|
|
707
|
-
);
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
const filePercent = Math.round(
|
|
711
|
-
(currentStats.files_changed / derivedBudget.effective.max_files) * 100
|
|
712
|
-
);
|
|
713
|
-
const locPercent = Math.round(
|
|
714
|
-
(currentStats.lines_changed / derivedBudget.effective.max_loc) * 100
|
|
715
|
-
);
|
|
716
|
-
|
|
717
|
-
report.push('');
|
|
718
|
-
report.push(
|
|
719
|
-
`File Usage: ${filePercent}% (${currentStats.files_changed}/${derivedBudget.effective.max_files})`
|
|
720
|
-
);
|
|
721
|
-
report.push(
|
|
722
|
-
`LOC Usage: ${locPercent}% (${currentStats.lines_changed}/${derivedBudget.effective.max_loc})`
|
|
723
|
-
);
|
|
724
|
-
|
|
725
|
-
// Add warnings at different thresholds
|
|
726
|
-
const overall = Math.max(filePercent, locPercent);
|
|
727
|
-
if (overall >= 95) {
|
|
728
|
-
report.push('', 'CRITICAL: Budget nearly exhausted!');
|
|
729
|
-
} else if (overall >= 90) {
|
|
730
|
-
report.push('', 'WARNING: Approaching budget limits');
|
|
731
|
-
} else if (overall >= 80) {
|
|
732
|
-
report.push('', 'Notice: 80% of budget used');
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
return report.join('\n');
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
module.exports = {
|
|
739
|
-
deriveBudget,
|
|
740
|
-
deriveBudgetSync,
|
|
741
|
-
loadWaiver,
|
|
742
|
-
isWaiverValid,
|
|
743
|
-
checkBudgetCompliance,
|
|
744
|
-
generateBurnupReport,
|
|
745
|
-
calculateBudgetUtilization,
|
|
746
|
-
isApproachingBudgetLimit,
|
|
747
|
-
validatePolicy,
|
|
748
|
-
getDefaultPolicy,
|
|
749
|
-
validateWaiverStructure,
|
|
750
|
-
WAIVER_REQUIRED_FIELDS,
|
|
751
|
-
};
|