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