@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,921 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Working Spec Validation Utilities
|
|
3
|
-
* Functions for validating CAWS working specifications
|
|
4
|
-
* @author @darianrosebrook
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const { deriveBudgetSync, checkBudgetCompliance } = require('../budget-derivation');
|
|
10
|
-
const { execSync } = require('child_process');
|
|
11
|
-
const { createValidator, getSchemaPath } = require('../utils/schema-validator');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* CAWSFIX-10: Canonical regex for valid spec IDs.
|
|
15
|
-
*
|
|
16
|
-
* Accepts:
|
|
17
|
-
* - Single-segment: FEAT-001, EVLOG-002, CAWSFIX-06 (legacy shape)
|
|
18
|
-
* - Multi-segment: P03-IMPL-01, ALG-001A-HARDEN-01, CAWS-FIX-03
|
|
19
|
-
*
|
|
20
|
-
* Rejects:
|
|
21
|
-
* - lowercase (feat-001)
|
|
22
|
-
* - leading digit (01-FEAT)
|
|
23
|
-
* - missing number suffix (FEAT-)
|
|
24
|
-
* - trailing hyphen (FEAT-01-)
|
|
25
|
-
* - leading/double hyphen (--FEAT-01, FEAT--001)
|
|
26
|
-
* - empty string
|
|
27
|
-
*
|
|
28
|
-
* Grammar: [PREFIX](-[SEGMENT])*-NUMBER
|
|
29
|
-
* - PREFIX = [A-Z] followed by zero+ [A-Z0-9]
|
|
30
|
-
* - SEGMENT = one+ [A-Z0-9] (alphanumeric, uppercase only)
|
|
31
|
-
* - NUMBER = one+ digits
|
|
32
|
-
*
|
|
33
|
-
* Defined once per A4 invariant; referenced by both the basic validator
|
|
34
|
-
* (line ~125 pre-fix) and the enhanced validator (line ~307 pre-fix).
|
|
35
|
-
*/
|
|
36
|
-
const SPEC_ID_PATTERN = /^[A-Z][A-Z0-9]*(-[A-Z0-9]+)*-\d+$/;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* User-facing error message for bad spec IDs (CAWSFIX-10 A5).
|
|
40
|
-
* Kept as a module constant so the message stays in sync with the pattern.
|
|
41
|
-
*/
|
|
42
|
-
const SPEC_ID_ERROR_MESSAGE =
|
|
43
|
-
'Project ID should be in format: PREFIX-NUMBER or PREFIX-SEGMENT-NUMBER (e.g., FEAT-001, P03-IMPL-01)';
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Get actual budget statistics from git history
|
|
47
|
-
* Analyzes changes since last tag or initial commit
|
|
48
|
-
* @param {string} specDir - Project directory
|
|
49
|
-
* @returns {Object|null} Budget stats or null on failure
|
|
50
|
-
*/
|
|
51
|
-
function getActualBudgetStats(specDir) {
|
|
52
|
-
const cwd = specDir || process.cwd();
|
|
53
|
-
try {
|
|
54
|
-
// Get base ref (last tag or initial commit)
|
|
55
|
-
let baseRef;
|
|
56
|
-
try {
|
|
57
|
-
baseRef = execSync('git describe --tags --abbrev=0 2>/dev/null', {
|
|
58
|
-
cwd,
|
|
59
|
-
encoding: 'utf8',
|
|
60
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
61
|
-
}).trim();
|
|
62
|
-
} catch {
|
|
63
|
-
// No tags found, use initial commit
|
|
64
|
-
baseRef = execSync('git rev-list --max-parents=0 HEAD', {
|
|
65
|
-
cwd,
|
|
66
|
-
encoding: 'utf8',
|
|
67
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
68
|
-
}).trim();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Count files changed since base ref
|
|
72
|
-
const filesOutput = execSync(`git diff --name-only ${baseRef}..HEAD`, {
|
|
73
|
-
cwd,
|
|
74
|
-
encoding: 'utf8',
|
|
75
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
76
|
-
});
|
|
77
|
-
const files_changed = filesOutput.trim().split('\n').filter(Boolean).length;
|
|
78
|
-
|
|
79
|
-
// Count lines changed (added + removed)
|
|
80
|
-
const numstatOutput = execSync(`git diff --numstat ${baseRef}..HEAD`, {
|
|
81
|
-
cwd,
|
|
82
|
-
encoding: 'utf8',
|
|
83
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
84
|
-
});
|
|
85
|
-
let lines_changed = 0;
|
|
86
|
-
for (const line of numstatOutput.trim().split('\n').filter(Boolean)) {
|
|
87
|
-
const [added, removed] = line.split('\t');
|
|
88
|
-
// Handle binary files (shown as '-')
|
|
89
|
-
const addedNum = added === '-' ? 0 : parseInt(added, 10) || 0;
|
|
90
|
-
const removedNum = removed === '-' ? 0 : parseInt(removed, 10) || 0;
|
|
91
|
-
lines_changed += addedNum + removedNum;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return { files_changed, lines_changed };
|
|
95
|
-
} catch {
|
|
96
|
-
// Git not available or not a repository
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Alias the modern `acceptance_criteria` key into `acceptance` so the semantic
|
|
103
|
-
* validator (which historically keys off `acceptance`) accepts both shapes.
|
|
104
|
-
*
|
|
105
|
-
* Precedence (per CAWSFIX-09 A3 invariant):
|
|
106
|
-
* - If `acceptance` is present (legacy shape: {id,given,when,then}), it wins.
|
|
107
|
-
* - Otherwise `acceptance_criteria` (modern shape: {id,description,test_nodeids,status})
|
|
108
|
-
* is copied into `acceptance`.
|
|
109
|
-
*
|
|
110
|
-
* IMPORTANT: this function mutates the spec in place. The existing validator
|
|
111
|
-
* also mutates in place (risk_tier string→number coercion at line ~141; auto-fix
|
|
112
|
-
* writes via `current[pathParts[...]] = fix.value`). Callers of
|
|
113
|
-
* `validateWorkingSpecWithSuggestions({...}, {autoFix:true})` observe those
|
|
114
|
-
* mutations on the object they passed in — see `Multiple Auto-Fixes` tests.
|
|
115
|
-
* Returning a clone here would silently break that contract.
|
|
116
|
-
*
|
|
117
|
-
* @param {Object} spec - Raw spec object (mutated in place)
|
|
118
|
-
* @returns {Object} Same spec reference
|
|
119
|
-
*/
|
|
120
|
-
function aliasAcceptanceCriteria(spec) {
|
|
121
|
-
if (!spec || typeof spec !== 'object') return spec;
|
|
122
|
-
|
|
123
|
-
const hasLegacy = Array.isArray(spec.acceptance) && spec.acceptance.length > 0;
|
|
124
|
-
const hasModern =
|
|
125
|
-
Array.isArray(spec.acceptance_criteria) && spec.acceptance_criteria.length > 0;
|
|
126
|
-
|
|
127
|
-
// Only alias when: legacy is absent AND modern has content.
|
|
128
|
-
// (Legacy wins when both present; empty modern arrays do not satisfy the
|
|
129
|
-
// required-field check — see edge-case tests in acceptance-criteria-alias.test.js.)
|
|
130
|
-
if (!hasLegacy && hasModern) {
|
|
131
|
-
spec.acceptance = spec.acceptance_criteria;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return spec;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Basic validation of working spec
|
|
139
|
-
* @param {Object} spec - Working spec object
|
|
140
|
-
* @param {Object} options - Validation options
|
|
141
|
-
* @returns {Object} Validation result
|
|
142
|
-
*/
|
|
143
|
-
const validateWorkingSpec = (spec, _options = {}) => {
|
|
144
|
-
try {
|
|
145
|
-
// CAWSFIX-09: Alias `acceptance_criteria` -> `acceptance` before any
|
|
146
|
-
// semantic checks so specs using the modern shape don't trigger
|
|
147
|
-
// "Missing required field: acceptance" false negatives.
|
|
148
|
-
aliasAcceptanceCriteria(spec);
|
|
149
|
-
|
|
150
|
-
// First pass: AJV schema validation (non-blocking — results collected as warnings)
|
|
151
|
-
let schemaWarnings = [];
|
|
152
|
-
try {
|
|
153
|
-
const schemaPath = getSchemaPath('working-spec.schema.json', process.cwd());
|
|
154
|
-
const validate = createValidator(schemaPath);
|
|
155
|
-
const schemaResult = validate(spec);
|
|
156
|
-
if (!schemaResult.valid) {
|
|
157
|
-
schemaWarnings = schemaResult.errors.map(e => ({
|
|
158
|
-
instancePath: e.path,
|
|
159
|
-
message: e.message,
|
|
160
|
-
}));
|
|
161
|
-
}
|
|
162
|
-
} catch (schemaErr) {
|
|
163
|
-
// Schema not available — fall through to semantic validation
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Second pass: semantic checks (authoritative — always runs as fallback)
|
|
167
|
-
|
|
168
|
-
// Check required fields (schema may not be available)
|
|
169
|
-
const requiredFields = [
|
|
170
|
-
'id',
|
|
171
|
-
'title',
|
|
172
|
-
'risk_tier',
|
|
173
|
-
'mode',
|
|
174
|
-
'blast_radius',
|
|
175
|
-
'operational_rollback_slo',
|
|
176
|
-
'scope',
|
|
177
|
-
'invariants',
|
|
178
|
-
'acceptance',
|
|
179
|
-
'non_functional',
|
|
180
|
-
'contracts',
|
|
181
|
-
];
|
|
182
|
-
|
|
183
|
-
for (const field of requiredFields) {
|
|
184
|
-
if (!spec[field]) {
|
|
185
|
-
return {
|
|
186
|
-
valid: false,
|
|
187
|
-
errors: [
|
|
188
|
-
{
|
|
189
|
-
instancePath: `/${field}`,
|
|
190
|
-
message: `Missing required field: ${field}`,
|
|
191
|
-
},
|
|
192
|
-
],
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Validate specific field formats (CAWSFIX-10: DRY regex via SPEC_ID_PATTERN)
|
|
198
|
-
if (!SPEC_ID_PATTERN.test(spec.id)) {
|
|
199
|
-
return {
|
|
200
|
-
valid: false,
|
|
201
|
-
errors: [
|
|
202
|
-
{
|
|
203
|
-
instancePath: '/id',
|
|
204
|
-
message: SPEC_ID_ERROR_MESSAGE,
|
|
205
|
-
},
|
|
206
|
-
],
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Normalize risk_tier: accept "T1"/"T2"/"T3" strings and convert to numeric
|
|
211
|
-
if (spec.risk_tier !== undefined && typeof spec.risk_tier === 'string') {
|
|
212
|
-
const match = spec.risk_tier.match(/^T?(\d)$/i);
|
|
213
|
-
if (match) {
|
|
214
|
-
spec.risk_tier = parseInt(match[1], 10);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Validate status field if present
|
|
219
|
-
if (spec.status) {
|
|
220
|
-
const { SPEC_STATUSES } = require('../constants/spec-types');
|
|
221
|
-
if (!SPEC_STATUSES[spec.status]) {
|
|
222
|
-
return {
|
|
223
|
-
valid: false,
|
|
224
|
-
errors: [
|
|
225
|
-
{
|
|
226
|
-
instancePath: '/status',
|
|
227
|
-
message: `Invalid status '${spec.status}'. Valid values: ${Object.keys(SPEC_STATUSES).join(', ')}`,
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Validate experimental mode
|
|
235
|
-
if (spec.experimental_mode) {
|
|
236
|
-
if (typeof spec.experimental_mode !== 'object') {
|
|
237
|
-
return {
|
|
238
|
-
valid: false,
|
|
239
|
-
errors: [
|
|
240
|
-
{
|
|
241
|
-
instancePath: '/experimental_mode',
|
|
242
|
-
message:
|
|
243
|
-
'Experimental mode must be an object with enabled, rationale, and expires_at fields',
|
|
244
|
-
},
|
|
245
|
-
],
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const requiredExpFields = ['enabled', 'rationale', 'expires_at'];
|
|
250
|
-
for (const field of requiredExpFields) {
|
|
251
|
-
if (!(field in spec.experimental_mode)) {
|
|
252
|
-
return {
|
|
253
|
-
valid: false,
|
|
254
|
-
errors: [
|
|
255
|
-
{
|
|
256
|
-
instancePath: `/experimental_mode/${field}`,
|
|
257
|
-
message: `Missing required experimental mode field: ${field}`,
|
|
258
|
-
},
|
|
259
|
-
],
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (spec.experimental_mode.enabled && spec.risk_tier < 3) {
|
|
265
|
-
return {
|
|
266
|
-
valid: false,
|
|
267
|
-
errors: [
|
|
268
|
-
{
|
|
269
|
-
instancePath: '/experimental_mode',
|
|
270
|
-
message: 'Experimental mode can only be used with Tier 3 (low risk) changes',
|
|
271
|
-
},
|
|
272
|
-
],
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (spec.risk_tier < 1 || spec.risk_tier > 3) {
|
|
278
|
-
return {
|
|
279
|
-
valid: false,
|
|
280
|
-
errors: [
|
|
281
|
-
{
|
|
282
|
-
instancePath: '/risk_tier',
|
|
283
|
-
message: 'Risk tier must be 1, 2, or 3',
|
|
284
|
-
},
|
|
285
|
-
],
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
|
|
290
|
-
return {
|
|
291
|
-
valid: false,
|
|
292
|
-
errors: [
|
|
293
|
-
{
|
|
294
|
-
instancePath: '/scope/in',
|
|
295
|
-
message: 'Scope IN must not be empty',
|
|
296
|
-
},
|
|
297
|
-
],
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return {
|
|
302
|
-
valid: true,
|
|
303
|
-
schemaWarnings: schemaWarnings.length > 0 ? schemaWarnings : undefined,
|
|
304
|
-
};
|
|
305
|
-
} catch (error) {
|
|
306
|
-
return {
|
|
307
|
-
valid: false,
|
|
308
|
-
errors: [
|
|
309
|
-
{
|
|
310
|
-
instancePath: '',
|
|
311
|
-
message: `Validation error: ${error.message}`,
|
|
312
|
-
},
|
|
313
|
-
],
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Enhanced validation with suggestions and auto-fix
|
|
320
|
-
* @param {Object} spec - Working spec object
|
|
321
|
-
* @param {Object} options - Validation options
|
|
322
|
-
* @returns {Object} Enhanced validation result
|
|
323
|
-
*/
|
|
324
|
-
function validateWorkingSpecWithSuggestions(spec, options = {}) {
|
|
325
|
-
const { autoFix = false, checkBudget = false, projectRoot } = options;
|
|
326
|
-
|
|
327
|
-
try {
|
|
328
|
-
// CAWSFIX-09: Alias `acceptance_criteria` -> `acceptance` so the
|
|
329
|
-
// required-field check and the "No acceptance criteria defined" warning
|
|
330
|
-
// recognize the modern shape as valid. Mutates in place to preserve the
|
|
331
|
-
// existing auto-fix contract (callers observe fixes on their object).
|
|
332
|
-
aliasAcceptanceCriteria(spec);
|
|
333
|
-
|
|
334
|
-
let errors = [];
|
|
335
|
-
let warnings = [];
|
|
336
|
-
let fixes = [];
|
|
337
|
-
|
|
338
|
-
// First pass: AJV schema validation (non-blocking — results collected as warnings)
|
|
339
|
-
try {
|
|
340
|
-
const schemaPath = getSchemaPath('working-spec.schema.json', projectRoot || process.cwd());
|
|
341
|
-
const validate = createValidator(schemaPath);
|
|
342
|
-
const schemaResult = validate(spec);
|
|
343
|
-
if (!schemaResult.valid) {
|
|
344
|
-
for (const e of schemaResult.errors) {
|
|
345
|
-
const fieldName = e.path ? e.path.replace(/^\//, '').split('/')[0] : '';
|
|
346
|
-
warnings.push({
|
|
347
|
-
instancePath: e.path,
|
|
348
|
-
message: `Schema: ${e.message}`,
|
|
349
|
-
suggestion: fieldName ? getFieldSuggestion(fieldName, spec) : undefined,
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
} catch (schemaErr) {
|
|
354
|
-
// Schema not available — non-fatal
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Required fields check (authoritative — always runs regardless of schema)
|
|
358
|
-
const requiredFields = [
|
|
359
|
-
'id',
|
|
360
|
-
'title',
|
|
361
|
-
'risk_tier',
|
|
362
|
-
'mode',
|
|
363
|
-
'blast_radius',
|
|
364
|
-
'operational_rollback_slo',
|
|
365
|
-
'scope',
|
|
366
|
-
'invariants',
|
|
367
|
-
'acceptance',
|
|
368
|
-
'non_functional',
|
|
369
|
-
'contracts',
|
|
370
|
-
];
|
|
371
|
-
|
|
372
|
-
for (const field of requiredFields) {
|
|
373
|
-
if (!spec[field]) {
|
|
374
|
-
errors.push({
|
|
375
|
-
instancePath: `/${field}`,
|
|
376
|
-
message: `Missing required field: ${field}`,
|
|
377
|
-
suggestion: getFieldSuggestion(field, spec),
|
|
378
|
-
canAutoFix: canAutoFixField(field, spec),
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Semantic checks that AJV can't express
|
|
384
|
-
|
|
385
|
-
// Validate specific field formats (CAWSFIX-10: DRY regex via SPEC_ID_PATTERN)
|
|
386
|
-
if (spec.id && !SPEC_ID_PATTERN.test(spec.id)) {
|
|
387
|
-
errors.push({
|
|
388
|
-
instancePath: '/id',
|
|
389
|
-
message: SPEC_ID_ERROR_MESSAGE,
|
|
390
|
-
suggestion: 'Use format like: PROJ-001, FEAT-002, P03-IMPL-01, ALG-001A-HARDEN-01',
|
|
391
|
-
canAutoFix: false,
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Validate risk tier with enhanced auto-fix
|
|
396
|
-
if (spec.risk_tier !== undefined && (spec.risk_tier < 1 || spec.risk_tier > 3)) {
|
|
397
|
-
const fixedValue = Math.max(1, Math.min(3, spec.risk_tier || 2));
|
|
398
|
-
errors.push({
|
|
399
|
-
instancePath: '/risk_tier',
|
|
400
|
-
message: 'Risk tier must be 1, 2, or 3',
|
|
401
|
-
suggestion:
|
|
402
|
-
'Tier 1: Critical (auth, billing), Tier 2: Standard (features), Tier 3: Low risk (UI)',
|
|
403
|
-
canAutoFix: true,
|
|
404
|
-
});
|
|
405
|
-
fixes.push({
|
|
406
|
-
field: 'risk_tier',
|
|
407
|
-
value: fixedValue,
|
|
408
|
-
description: `Clamping risk_tier from ${spec.risk_tier} to valid range [1-3]: ${fixedValue}`,
|
|
409
|
-
reason: 'Risk tier out of bounds',
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Auto-fix empty arrays with sensible defaults
|
|
414
|
-
if (!spec.invariants || spec.invariants.length === 0) {
|
|
415
|
-
if (autoFix) {
|
|
416
|
-
fixes.push({
|
|
417
|
-
field: 'invariants',
|
|
418
|
-
value: ['System must remain operational during changes'],
|
|
419
|
-
description: 'Adding default invariant for empty invariants array',
|
|
420
|
-
reason: 'Invariants array was empty',
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (!spec.acceptance || spec.acceptance.length === 0) {
|
|
426
|
-
if (autoFix) {
|
|
427
|
-
fixes.push({
|
|
428
|
-
field: 'acceptance',
|
|
429
|
-
value: [
|
|
430
|
-
{
|
|
431
|
-
id: 'A1',
|
|
432
|
-
given: 'the system is in a valid state',
|
|
433
|
-
when: 'the change is applied',
|
|
434
|
-
then: 'the system remains functional',
|
|
435
|
-
},
|
|
436
|
-
],
|
|
437
|
-
description: 'Adding placeholder acceptance criteria',
|
|
438
|
-
reason: 'Acceptance criteria array was empty',
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Validate scope.out doesn't contain glob patterns
|
|
444
|
-
if (spec.scope && spec.scope.out && Array.isArray(spec.scope.out)) {
|
|
445
|
-
const globPatterns = spec.scope.out.filter(
|
|
446
|
-
(pattern) => pattern.includes('*') || pattern.includes('?')
|
|
447
|
-
);
|
|
448
|
-
if (globPatterns.length > 0) {
|
|
449
|
-
errors.push({
|
|
450
|
-
instancePath: '/scope/out',
|
|
451
|
-
message: `Unsupported glob patterns in scope.out: ${globPatterns.join(', ')}`,
|
|
452
|
-
suggestion:
|
|
453
|
-
'Use directory paths only (e.g., __pycache__/ instead of *.pyc or **/*.pyc). Python cache files are already covered by __pycache__/',
|
|
454
|
-
canAutoFix: true,
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
// Auto-fix: remove glob patterns and keep only directory paths
|
|
458
|
-
if (autoFix) {
|
|
459
|
-
const fixedOut = spec.scope.out
|
|
460
|
-
.filter((pattern) => !pattern.includes('*') && !pattern.includes('?'))
|
|
461
|
-
.map((pattern) => {
|
|
462
|
-
// Ensure directory paths end with /
|
|
463
|
-
if (!pattern.includes('.') && !pattern.endsWith('/')) {
|
|
464
|
-
return pattern + '/';
|
|
465
|
-
}
|
|
466
|
-
return pattern;
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
fixes.push({
|
|
470
|
-
field: 'scope.out',
|
|
471
|
-
value: fixedOut,
|
|
472
|
-
description: `Removed glob patterns from scope.out: ${globPatterns.join(', ')}`,
|
|
473
|
-
reason: 'Glob patterns are not supported in scope.out',
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Auto-fix missing scope.out
|
|
480
|
-
if (spec.scope && !spec.scope.out) {
|
|
481
|
-
fixes.push({
|
|
482
|
-
field: 'scope.out',
|
|
483
|
-
value: ['node_modules/', 'dist/', '.git/'],
|
|
484
|
-
description: 'Adding default exclusions to scope.out',
|
|
485
|
-
reason: 'scope.out was missing',
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Auto-fix missing mode
|
|
490
|
-
if (!spec.mode) {
|
|
491
|
-
fixes.push({
|
|
492
|
-
field: 'mode',
|
|
493
|
-
value: 'feature',
|
|
494
|
-
description: 'Setting default mode to "feature"',
|
|
495
|
-
reason: 'mode field was missing',
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Auto-fix missing blast_radius
|
|
500
|
-
if (!spec.blast_radius) {
|
|
501
|
-
fixes.push({
|
|
502
|
-
field: 'blast_radius',
|
|
503
|
-
value: {
|
|
504
|
-
modules: [],
|
|
505
|
-
data_migration: false,
|
|
506
|
-
},
|
|
507
|
-
description: 'Adding empty blast_radius structure',
|
|
508
|
-
reason: 'blast_radius was missing',
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Auto-fix missing non_functional
|
|
513
|
-
if (!spec.non_functional) {
|
|
514
|
-
fixes.push({
|
|
515
|
-
field: 'non_functional',
|
|
516
|
-
value: {
|
|
517
|
-
a11y: [],
|
|
518
|
-
perf: {},
|
|
519
|
-
security: [],
|
|
520
|
-
},
|
|
521
|
-
description: 'Adding empty non_functional requirements structure',
|
|
522
|
-
reason: 'non_functional was missing',
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// Auto-fix missing contracts
|
|
527
|
-
if (!spec.contracts) {
|
|
528
|
-
fixes.push({
|
|
529
|
-
field: 'contracts',
|
|
530
|
-
value: [],
|
|
531
|
-
description: 'Adding empty contracts array',
|
|
532
|
-
reason: 'contracts field was missing',
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Validate scope.in is not empty
|
|
537
|
-
if (!spec.scope || !spec.scope.in || spec.scope.in.length === 0) {
|
|
538
|
-
errors.push({
|
|
539
|
-
instancePath: '/scope/in',
|
|
540
|
-
message: 'Scope IN must not be empty',
|
|
541
|
-
suggestion: 'Specify directories/files that are included in changes',
|
|
542
|
-
canAutoFix: false,
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Check for common issues
|
|
547
|
-
if (!spec.invariants || spec.invariants.length === 0) {
|
|
548
|
-
warnings.push({
|
|
549
|
-
instancePath: '/invariants',
|
|
550
|
-
message: 'No system invariants defined',
|
|
551
|
-
suggestion: 'Add 1-3 statements about what must always remain true',
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
if (!spec.acceptance || spec.acceptance.length === 0) {
|
|
556
|
-
warnings.push({
|
|
557
|
-
instancePath: '/acceptance',
|
|
558
|
-
message: 'No acceptance criteria defined',
|
|
559
|
-
suggestion: 'Add acceptance criteria in GIVEN/WHEN/THEN format',
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Tier-specific validations
|
|
564
|
-
if (spec.risk_tier === 1 || spec.risk_tier === 2) {
|
|
565
|
-
if (!spec.contracts || spec.contracts.length === 0) {
|
|
566
|
-
const isChoreMode = spec.mode === 'chore';
|
|
567
|
-
const suggestion = isChoreMode
|
|
568
|
-
? 'For infrastructure/setup work, add a minimal project_setup contract or create a waiver'
|
|
569
|
-
: 'Add API contracts (OpenAPI, GraphQL, etc.) or change mode to "chore" for maintenance work';
|
|
570
|
-
|
|
571
|
-
errors.push({
|
|
572
|
-
instancePath: '/contracts',
|
|
573
|
-
message: `Contracts required for Tier ${spec.risk_tier} changes`,
|
|
574
|
-
suggestion: suggestion,
|
|
575
|
-
canAutoFix: false,
|
|
576
|
-
example: isChoreMode
|
|
577
|
-
? {
|
|
578
|
-
contracts: [
|
|
579
|
-
{
|
|
580
|
-
type: 'project_setup',
|
|
581
|
-
path: '.caws/working-spec.yaml',
|
|
582
|
-
description:
|
|
583
|
-
'Project-level CAWS configuration. Feature-specific contracts will be added as features are developed.',
|
|
584
|
-
},
|
|
585
|
-
],
|
|
586
|
-
}
|
|
587
|
-
: {
|
|
588
|
-
contracts: [
|
|
589
|
-
{
|
|
590
|
-
type: 'openapi',
|
|
591
|
-
path: 'docs/api/feature.yaml',
|
|
592
|
-
version: '1.0.0',
|
|
593
|
-
},
|
|
594
|
-
],
|
|
595
|
-
},
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// Tier 1 specific requirements (critical changes)
|
|
601
|
-
if (spec.risk_tier === 1) {
|
|
602
|
-
if (!spec.observability) {
|
|
603
|
-
errors.push({
|
|
604
|
-
instancePath: '/observability',
|
|
605
|
-
message: 'Observability required for Tier 1 changes',
|
|
606
|
-
suggestion: 'Define logging, metrics, and tracing strategy',
|
|
607
|
-
canAutoFix: false,
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
if (!spec.rollback || spec.rollback.length === 0) {
|
|
612
|
-
errors.push({
|
|
613
|
-
instancePath: '/rollback',
|
|
614
|
-
message: 'Rollback procedures required for Tier 1 changes',
|
|
615
|
-
suggestion: 'Document rollback steps and data migration reversal',
|
|
616
|
-
canAutoFix: false,
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
if (
|
|
621
|
-
!spec.non_functional ||
|
|
622
|
-
!spec.non_functional.security ||
|
|
623
|
-
spec.non_functional.security.length === 0
|
|
624
|
-
) {
|
|
625
|
-
errors.push({
|
|
626
|
-
instancePath: '/non_functional/security',
|
|
627
|
-
message: 'Security requirements required for Tier 1 changes',
|
|
628
|
-
suggestion: 'Define authentication, authorization, and data protection requirements',
|
|
629
|
-
canAutoFix: false,
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// Validate rollback format if present (for all tiers)
|
|
635
|
-
if (spec.rollback !== undefined) {
|
|
636
|
-
if (!Array.isArray(spec.rollback)) {
|
|
637
|
-
errors.push({
|
|
638
|
-
instancePath: '/rollback',
|
|
639
|
-
message: 'rollback must be an array of strings',
|
|
640
|
-
suggestion: 'Use format: ["Step 1", "Step 2", "Step 3"]',
|
|
641
|
-
canAutoFix: false,
|
|
642
|
-
});
|
|
643
|
-
} else {
|
|
644
|
-
// Check for duplicates
|
|
645
|
-
const uniqueSteps = [...new Set(spec.rollback)];
|
|
646
|
-
if (uniqueSteps.length !== spec.rollback.length) {
|
|
647
|
-
warnings.push({
|
|
648
|
-
instancePath: '/rollback',
|
|
649
|
-
message: 'Duplicate entries found in rollback array',
|
|
650
|
-
suggestion: 'Remove duplicate entries',
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
if (autoFix) {
|
|
654
|
-
fixes.push({
|
|
655
|
-
field: 'rollback',
|
|
656
|
-
value: uniqueSteps,
|
|
657
|
-
description: 'Removed duplicate rollback entries',
|
|
658
|
-
reason: 'Duplicate entries detected',
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// Validate each entry is a string
|
|
664
|
-
const invalidEntries = spec.rollback.filter((entry) => typeof entry !== 'string');
|
|
665
|
-
if (invalidEntries.length > 0) {
|
|
666
|
-
errors.push({
|
|
667
|
-
instancePath: '/rollback',
|
|
668
|
-
message: `Invalid rollback entries (must be strings): ${invalidEntries.length}`,
|
|
669
|
-
suggestion: 'All rollback entries must be string descriptions',
|
|
670
|
-
canAutoFix: false,
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
// Validate waiver_ids format if present
|
|
677
|
-
if (spec.waiver_ids) {
|
|
678
|
-
if (!Array.isArray(spec.waiver_ids)) {
|
|
679
|
-
errors.push({
|
|
680
|
-
instancePath: '/waiver_ids',
|
|
681
|
-
message: 'waiver_ids must be an array of waiver IDs',
|
|
682
|
-
suggestion: 'Use format: ["WV-0001", "WV-0002"]',
|
|
683
|
-
canAutoFix: false,
|
|
684
|
-
});
|
|
685
|
-
} else {
|
|
686
|
-
for (const waiverId of spec.waiver_ids) {
|
|
687
|
-
if (!/^WV-\d{4}$/.test(waiverId)) {
|
|
688
|
-
errors.push({
|
|
689
|
-
instancePath: '/waiver_ids',
|
|
690
|
-
message: `Invalid waiver ID format: ${waiverId}`,
|
|
691
|
-
suggestion: 'Use format: WV-XXXX where XXXX is exactly 4 digits (e.g., WV-0001)',
|
|
692
|
-
canAutoFix: false,
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// Note: change_budget in specs is informational documentation only.
|
|
700
|
-
// Budget enforcement is derived from policy.yaml risk_tier + waivers.
|
|
701
|
-
// No warning emitted — the field is valid and expected.
|
|
702
|
-
|
|
703
|
-
// Validate scope.json against scope.schema.json if it exists
|
|
704
|
-
if (projectRoot) {
|
|
705
|
-
const scopeJsonPath = path.join(projectRoot, '.caws', 'scope.json');
|
|
706
|
-
if (fs.existsSync(scopeJsonPath)) {
|
|
707
|
-
try {
|
|
708
|
-
const schemaPath = getSchemaPath('scope.schema.json', projectRoot);
|
|
709
|
-
const validate = createValidator(schemaPath);
|
|
710
|
-
const scopeData = JSON.parse(fs.readFileSync(scopeJsonPath, 'utf8'));
|
|
711
|
-
const scopeResult = validate(scopeData);
|
|
712
|
-
if (!scopeResult.valid) {
|
|
713
|
-
for (const err of scopeResult.errors) {
|
|
714
|
-
warnings.push({
|
|
715
|
-
instancePath: `/scope.json${err.path}`,
|
|
716
|
-
message: `scope.json schema violation: ${err.message}`,
|
|
717
|
-
suggestion: 'Fix .caws/scope.json to match scope.schema.json',
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
} catch (schemaErr) {
|
|
722
|
-
// Non-fatal — don't block validation on schema issues
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// Derive and check budget if requested
|
|
728
|
-
//
|
|
729
|
-
// CAWSFIX-07: use `deriveBudgetSync` here. The async `deriveBudget`
|
|
730
|
-
// returns a Promise; this synchronous function previously passed the
|
|
731
|
-
// Promise straight into `checkBudgetCompliance`, which then read
|
|
732
|
-
// `derivedBudget.effective.max_files` on an undefined `.effective` and
|
|
733
|
-
// threw "Cannot read properties of undefined (reading 'max_files')" —
|
|
734
|
-
// surfaced as the "Budget derivation failed" warning on every
|
|
735
|
-
// schema-compliant spec.
|
|
736
|
-
let budgetCheck = null;
|
|
737
|
-
if (checkBudget && projectRoot) {
|
|
738
|
-
try {
|
|
739
|
-
const derivedBudget = deriveBudgetSync(spec, projectRoot);
|
|
740
|
-
|
|
741
|
-
// Get actual stats from git history
|
|
742
|
-
const actualStats = getActualBudgetStats(projectRoot) || {
|
|
743
|
-
files_changed: 0,
|
|
744
|
-
lines_changed: 0,
|
|
745
|
-
};
|
|
746
|
-
actualStats.risk_tier = spec.risk_tier;
|
|
747
|
-
|
|
748
|
-
budgetCheck = checkBudgetCompliance(derivedBudget, actualStats);
|
|
749
|
-
|
|
750
|
-
if (!budgetCheck.compliant) {
|
|
751
|
-
for (const violation of budgetCheck.violations) {
|
|
752
|
-
errors.push({
|
|
753
|
-
instancePath: '/budget',
|
|
754
|
-
message: violation.message,
|
|
755
|
-
suggestion: 'Create a waiver or reduce scope to fit within budget',
|
|
756
|
-
canAutoFix: false,
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// Suggest adding waiver_ids if budget exceeded and none referenced
|
|
761
|
-
if (!spec.waiver_ids || spec.waiver_ids.length === 0) {
|
|
762
|
-
warnings.push({
|
|
763
|
-
instancePath: '/waiver_ids',
|
|
764
|
-
message: 'Budget exceeded but no waivers referenced',
|
|
765
|
-
suggestion:
|
|
766
|
-
'Add waiver_ids: ["WV-0001"] to working spec, then create waiver file with: caws waiver create',
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
} catch (error) {
|
|
771
|
-
warnings.push({
|
|
772
|
-
instancePath: '/budget',
|
|
773
|
-
message: `Budget derivation failed: ${error.message}`,
|
|
774
|
-
suggestion: 'Check that .caws/policy.yaml exists and is valid',
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
// Apply auto-fixes if requested and not in dry-run mode
|
|
780
|
-
const { dryRun = false } = options;
|
|
781
|
-
let appliedFixes = [];
|
|
782
|
-
|
|
783
|
-
if (autoFix && fixes.length > 0) {
|
|
784
|
-
if (dryRun) {
|
|
785
|
-
console.log('Auto-fix preview (dry-run mode):');
|
|
786
|
-
for (const fix of fixes) {
|
|
787
|
-
console.log(` [WOULD FIX] ${fix.field}`);
|
|
788
|
-
console.log(` Description: ${fix.description}`);
|
|
789
|
-
console.log(` Reason: ${fix.reason}`);
|
|
790
|
-
console.log(
|
|
791
|
-
` Value: ${typeof fix.value === 'object' ? JSON.stringify(fix.value) : fix.value}`
|
|
792
|
-
);
|
|
793
|
-
console.log('');
|
|
794
|
-
}
|
|
795
|
-
} else {
|
|
796
|
-
console.log('Applying auto-fixes...');
|
|
797
|
-
for (const fix of fixes) {
|
|
798
|
-
try {
|
|
799
|
-
const pathParts = fix.field.split('.');
|
|
800
|
-
let current = spec;
|
|
801
|
-
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
802
|
-
if (!current[pathParts[i]]) current[pathParts[i]] = {};
|
|
803
|
-
current = current[pathParts[i]];
|
|
804
|
-
}
|
|
805
|
-
current[pathParts[pathParts.length - 1]] = fix.value;
|
|
806
|
-
appliedFixes.push(fix);
|
|
807
|
-
console.log(` Fixed ${fix.field}`);
|
|
808
|
-
console.log(` ${fix.description}`);
|
|
809
|
-
} catch (error) {
|
|
810
|
-
console.warn(` Failed to apply fix for ${fix.field}: ${error.message}`);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// Calculate compliance score (0-1 scale)
|
|
817
|
-
const complianceScore = calculateComplianceScore(errors, warnings);
|
|
818
|
-
|
|
819
|
-
return {
|
|
820
|
-
valid: errors.length === 0,
|
|
821
|
-
errors,
|
|
822
|
-
warnings,
|
|
823
|
-
fixes: fixes.length > 0 ? fixes : undefined,
|
|
824
|
-
appliedFixes: appliedFixes.length > 0 ? appliedFixes : undefined,
|
|
825
|
-
dryRun,
|
|
826
|
-
budget_check: budgetCheck,
|
|
827
|
-
complianceScore,
|
|
828
|
-
};
|
|
829
|
-
} catch (error) {
|
|
830
|
-
return {
|
|
831
|
-
valid: false,
|
|
832
|
-
errors: [
|
|
833
|
-
{
|
|
834
|
-
instancePath: '',
|
|
835
|
-
message: `Validation error: ${error.message}`,
|
|
836
|
-
},
|
|
837
|
-
],
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
/**
|
|
843
|
-
* Calculate compliance score based on errors and warnings
|
|
844
|
-
* Score ranges from 0 (many issues) to 1 (perfect)
|
|
845
|
-
* @param {Array} errors - Validation errors
|
|
846
|
-
* @param {Array} warnings - Validation warnings
|
|
847
|
-
* @returns {number} Compliance score (0-1)
|
|
848
|
-
*/
|
|
849
|
-
function calculateComplianceScore(errors, warnings) {
|
|
850
|
-
// Start at perfect score
|
|
851
|
-
let score = 1.0;
|
|
852
|
-
|
|
853
|
-
// Each error reduces score by 0.2
|
|
854
|
-
score -= errors.length * 0.2;
|
|
855
|
-
|
|
856
|
-
// Each warning reduces score by 0.1
|
|
857
|
-
score -= warnings.length * 0.1;
|
|
858
|
-
|
|
859
|
-
// Floor at 0
|
|
860
|
-
return Math.max(0, score);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
/**
|
|
864
|
-
* Get compliance grade from score
|
|
865
|
-
* @param {number} score - Compliance score (0-1)
|
|
866
|
-
* @returns {string} Grade (A, B, C, D, F)
|
|
867
|
-
*/
|
|
868
|
-
function getComplianceGrade(score) {
|
|
869
|
-
if (score >= 0.9) return 'A';
|
|
870
|
-
if (score >= 0.8) return 'B';
|
|
871
|
-
if (score >= 0.7) return 'C';
|
|
872
|
-
if (score >= 0.6) return 'D';
|
|
873
|
-
return 'F';
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
/**
|
|
877
|
-
* Get suggestion for a missing field
|
|
878
|
-
* @param {string} field - Field name
|
|
879
|
-
* @param {Object} _spec - Spec object (for context)
|
|
880
|
-
* @returns {string} Suggestion text
|
|
881
|
-
*/
|
|
882
|
-
function getFieldSuggestion(field, _spec) {
|
|
883
|
-
const suggestions = {
|
|
884
|
-
id: 'Use format like: PROJ-001, FEAT-002, FIX-003',
|
|
885
|
-
title: 'Add a descriptive project title',
|
|
886
|
-
risk_tier: 'Choose: 1 (critical), 2 (standard), or 3 (low risk)',
|
|
887
|
-
mode: 'Choose: feature, refactor, fix, doc, or chore',
|
|
888
|
-
waiver_ids: 'Reference active waivers by ID (e.g., ["WV-0001"]) if budget exceptions needed',
|
|
889
|
-
blast_radius: 'List affected modules and data migration needs',
|
|
890
|
-
operational_rollback_slo: 'Choose: 1m, 5m, 15m, or 1h',
|
|
891
|
-
scope: "Define what's included (in) and excluded (out) from changes",
|
|
892
|
-
invariants: 'Add 1-3 statements about what must always remain true',
|
|
893
|
-
acceptance: 'Add acceptance criteria in GIVEN/WHEN/THEN format',
|
|
894
|
-
non_functional: 'Define accessibility, performance, and security requirements',
|
|
895
|
-
contracts: 'Specify API contracts (OpenAPI, GraphQL, etc.)',
|
|
896
|
-
};
|
|
897
|
-
return suggestions[field] || `Add the ${field} field`;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
/**
|
|
901
|
-
* Check if a field can be auto-fixed
|
|
902
|
-
* @param {string} field - Field name
|
|
903
|
-
* @param {Object} _spec - Spec object (for context)
|
|
904
|
-
* @returns {boolean} Whether field can be auto-fixed
|
|
905
|
-
*/
|
|
906
|
-
function canAutoFixField(field, _spec) {
|
|
907
|
-
const autoFixable = ['risk_tier'];
|
|
908
|
-
return autoFixable.includes(field);
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
module.exports = {
|
|
912
|
-
validateWorkingSpec,
|
|
913
|
-
validateWorkingSpecWithSuggestions,
|
|
914
|
-
getFieldSuggestion,
|
|
915
|
-
canAutoFixField,
|
|
916
|
-
calculateComplianceScore,
|
|
917
|
-
getComplianceGrade,
|
|
918
|
-
// CAWSFIX-10: exported so init.js and tests reference the same regex
|
|
919
|
-
SPEC_ID_PATTERN,
|
|
920
|
-
SPEC_ID_ERROR_MESSAGE,
|
|
921
|
-
};
|