@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.
Files changed (419) hide show
  1. package/README.md +125 -374
  2. package/dist/index.js +43 -756
  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/archive.js +0 -500
  183. package/dist/commands/burnup.js +0 -198
  184. package/dist/commands/diagnose.js +0 -525
  185. package/dist/commands/evaluate.js +0 -314
  186. package/dist/commands/gates.js +0 -149
  187. package/dist/commands/init.js +0 -857
  188. package/dist/commands/iterate.js +0 -417
  189. package/dist/commands/mode.js +0 -269
  190. package/dist/commands/parallel.js +0 -242
  191. package/dist/commands/plan.js +0 -438
  192. package/dist/commands/provenance.js +0 -1143
  193. package/dist/commands/quality-monitor.js +0 -284
  194. package/dist/commands/scope.js +0 -264
  195. package/dist/commands/session.js +0 -312
  196. package/dist/commands/sidecar.js +0 -74
  197. package/dist/commands/specs.js +0 -1448
  198. package/dist/commands/status.js +0 -1151
  199. package/dist/commands/templates.js +0 -237
  200. package/dist/commands/tool.js +0 -136
  201. package/dist/commands/tutorial.js +0 -480
  202. package/dist/commands/validate.js +0 -357
  203. package/dist/commands/verify-acs.js +0 -443
  204. package/dist/commands/waivers.js +0 -599
  205. package/dist/commands/workflow.js +0 -243
  206. package/dist/commands/worktree.js +0 -386
  207. package/dist/config/lite-scope.js +0 -158
  208. package/dist/config/modes.js +0 -347
  209. package/dist/constants/spec-types.js +0 -65
  210. package/dist/gates/budget-limit.js +0 -121
  211. package/dist/gates/feedback.js +0 -260
  212. package/dist/gates/format.js +0 -179
  213. package/dist/gates/god-object.js +0 -117
  214. package/dist/gates/pipeline.js +0 -167
  215. package/dist/gates/scope-boundary.js +0 -93
  216. package/dist/gates/spec-completeness.js +0 -109
  217. package/dist/gates/todo-detection.js +0 -205
  218. package/dist/generators/jest-config-generator.js +0 -242
  219. package/dist/generators/working-spec.js +0 -237
  220. package/dist/minimal-cli.js +0 -88
  221. package/dist/parallel/parallel-manager.js +0 -433
  222. package/dist/policy/PolicyManager.js +0 -465
  223. package/dist/scaffold/claude-hooks.js +0 -443
  224. package/dist/scaffold/cursor-hooks.js +0 -177
  225. package/dist/scaffold/git-hooks.js +0 -928
  226. package/dist/scaffold/index.js +0 -794
  227. package/dist/session/session-manager.js +0 -653
  228. package/dist/sidecars/index.js +0 -33
  229. package/dist/sidecars/listeners.js +0 -40
  230. package/dist/sidecars/provenance-summary.js +0 -238
  231. package/dist/sidecars/quality-gaps.js +0 -258
  232. package/dist/sidecars/schema.js +0 -149
  233. package/dist/sidecars/spec-drift.js +0 -151
  234. package/dist/sidecars/waiver-draft.js +0 -176
  235. package/dist/spec/SpecFileManager.js +0 -419
  236. package/dist/templates/.caws/schemas/policy.schema.json +0 -112
  237. package/dist/templates/.caws/schemas/scope.schema.json +0 -52
  238. package/dist/templates/.caws/schemas/waivers.schema.json +0 -106
  239. package/dist/templates/.caws/schemas/working-spec.schema.json +0 -340
  240. package/dist/templates/.caws/schemas/worktrees.schema.json +0 -38
  241. package/dist/templates/.caws/templates/working-spec.template.yml +0 -80
  242. package/dist/templates/.caws/tools/README.md +0 -18
  243. package/dist/templates/.caws/tools/scope-guard.js +0 -203
  244. package/dist/templates/.caws/tools-allow.json +0 -331
  245. package/dist/templates/.caws/waivers.yml +0 -19
  246. package/dist/templates/.claude/README.md +0 -190
  247. package/dist/templates/.claude/hooks/audit.sh +0 -121
  248. package/dist/templates/.claude/hooks/block-dangerous.sh +0 -203
  249. package/dist/templates/.claude/hooks/classify_command.py +0 -592
  250. package/dist/templates/.claude/hooks/doc-frontmatter-check.sh +0 -173
  251. package/dist/templates/.claude/hooks/lite-sprawl-check.sh +0 -145
  252. package/dist/templates/.claude/hooks/naming-check.sh +0 -100
  253. package/dist/templates/.claude/hooks/protected-paths.sh +0 -39
  254. package/dist/templates/.claude/hooks/quality-check.sh +0 -81
  255. package/dist/templates/.claude/hooks/scan-secrets.sh +0 -85
  256. package/dist/templates/.claude/hooks/scope-guard.sh +0 -381
  257. package/dist/templates/.claude/hooks/session-caws-status.sh +0 -117
  258. package/dist/templates/.claude/hooks/session-log.sh +0 -634
  259. package/dist/templates/.claude/hooks/simplification-guard.sh +0 -92
  260. package/dist/templates/.claude/hooks/stop-worktree-check.sh +0 -46
  261. package/dist/templates/.claude/hooks/test_classify_command.py +0 -370
  262. package/dist/templates/.claude/hooks/test_wrapper_smoke.sh +0 -96
  263. package/dist/templates/.claude/hooks/validate-spec.sh +0 -76
  264. package/dist/templates/.claude/hooks/worktree-guard.sh +0 -220
  265. package/dist/templates/.claude/hooks/worktree-write-guard.sh +0 -190
  266. package/dist/templates/.claude/rules/git-safety.md +0 -26
  267. package/dist/templates/.claude/rules/worktree-isolation.md +0 -83
  268. package/dist/templates/.claude/settings.json +0 -141
  269. package/dist/templates/.cursor/README.md +0 -299
  270. package/dist/templates/.cursor/hooks/audit.sh +0 -55
  271. package/dist/templates/.cursor/hooks/block-dangerous.sh +0 -84
  272. package/dist/templates/.cursor/hooks/caws-quality-check.sh +0 -52
  273. package/dist/templates/.cursor/hooks/caws-scope-guard.sh +0 -130
  274. package/dist/templates/.cursor/hooks/format.sh +0 -38
  275. package/dist/templates/.cursor/hooks/naming-check.sh +0 -64
  276. package/dist/templates/.cursor/hooks/scan-secrets.sh +0 -51
  277. package/dist/templates/.cursor/hooks/scope-guard.sh +0 -52
  278. package/dist/templates/.cursor/hooks/session-log.sh +0 -924
  279. package/dist/templates/.cursor/hooks/validate-spec.sh +0 -83
  280. package/dist/templates/.cursor/hooks.json +0 -76
  281. package/dist/templates/.cursor/rules/00-claims-verification.mdc +0 -144
  282. package/dist/templates/.cursor/rules/01-working-style.mdc +0 -50
  283. package/dist/templates/.cursor/rules/02-quality-gates.mdc +0 -368
  284. package/dist/templates/.cursor/rules/03-naming-and-refactor.mdc +0 -33
  285. package/dist/templates/.cursor/rules/04-logging-language-style.mdc +0 -23
  286. package/dist/templates/.cursor/rules/05-safe-defaults-guards.mdc +0 -23
  287. package/dist/templates/.cursor/rules/06-typescript-conventions.mdc +0 -36
  288. package/dist/templates/.cursor/rules/07-process-ops.mdc +0 -20
  289. package/dist/templates/.cursor/rules/08-solid-and-architecture.mdc +0 -16
  290. package/dist/templates/.cursor/rules/09-docstrings.mdc +0 -89
  291. package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +0 -385
  292. package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +0 -381
  293. package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +0 -516
  294. package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +0 -578
  295. package/dist/templates/.cursor/rules/README.md +0 -148
  296. package/dist/templates/.github/copilot-instructions.md +0 -82
  297. package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +0 -5
  298. package/dist/templates/.idea/runConfigurations/CAWS_Validate.xml +0 -5
  299. package/dist/templates/.junie/guidelines.md +0 -73
  300. package/dist/templates/.vscode/launch.json +0 -17
  301. package/dist/templates/.vscode/settings.json +0 -95
  302. package/dist/templates/.windsurf/rules/caws-quality-standards.md +0 -54
  303. package/dist/templates/.windsurf/workflows/caws-guided-development.md +0 -92
  304. package/dist/templates/CLAUDE.md +0 -174
  305. package/dist/templates/COMMIT_CONVENTIONS.md +0 -86
  306. package/dist/templates/OIDC_SETUP.md +0 -300
  307. package/dist/templates/agents.md +0 -145
  308. package/dist/templates/codemod/README.md +0 -1
  309. package/dist/templates/codemod/test.js +0 -93
  310. package/dist/templates/docs/README.md +0 -151
  311. package/dist/templates/scripts/new_feature.sh +0 -80
  312. package/dist/templates/scripts/quality-gates/check-god-objects.js +0 -146
  313. package/dist/templates/scripts/quality-gates/run-quality-gates.js +0 -50
  314. package/dist/templates/scripts/v3/analysis/todo_analyzer.py +0 -1997
  315. package/dist/test-analysis.js +0 -786
  316. package/dist/tool-interface.js +0 -314
  317. package/dist/tool-loader.js +0 -303
  318. package/dist/tool-validator.js +0 -393
  319. package/dist/utils/agent-session.js +0 -202
  320. package/dist/utils/async-utils.js +0 -188
  321. package/dist/utils/command-wrapper.js +0 -200
  322. package/dist/utils/event-log.js +0 -584
  323. package/dist/utils/event-renderer.js +0 -521
  324. package/dist/utils/finalization.js +0 -230
  325. package/dist/utils/git-lock.js +0 -119
  326. package/dist/utils/gitignore-updater.js +0 -158
  327. package/dist/utils/ide-detection.js +0 -133
  328. package/dist/utils/lifecycle-events.js +0 -94
  329. package/dist/utils/project-analysis.js +0 -367
  330. package/dist/utils/promise-utils.js +0 -72
  331. package/dist/utils/quality-gates-errors.js +0 -520
  332. package/dist/utils/quality-gates-utils.js +0 -387
  333. package/dist/utils/schema-validator.js +0 -50
  334. package/dist/utils/spec-resolver.js +0 -711
  335. package/dist/utils/typescript-detector.js +0 -369
  336. package/dist/utils/working-state.js +0 -530
  337. package/dist/utils/yaml-validation.js +0 -156
  338. package/dist/validation/spec-validation.js +0 -921
  339. package/dist/waivers-manager.js +0 -732
  340. package/dist/worktree/worktree-manager.js +0 -1374
  341. package/templates/.caws/schemas/policy.schema.json +0 -112
  342. package/templates/.caws/schemas/scope.schema.json +0 -52
  343. package/templates/.caws/schemas/waivers.schema.json +0 -106
  344. package/templates/.caws/schemas/working-spec.schema.json +0 -340
  345. package/templates/.caws/schemas/worktrees.schema.json +0 -38
  346. package/templates/.caws/templates/working-spec.template.yml +0 -80
  347. package/templates/.caws/tools/README.md +0 -18
  348. package/templates/.caws/tools/scope-guard.js +0 -203
  349. package/templates/.caws/tools-allow.json +0 -331
  350. package/templates/.caws/waivers.yml +0 -19
  351. package/templates/.claude/README.md +0 -190
  352. package/templates/.claude/hooks/audit.sh +0 -121
  353. package/templates/.claude/hooks/block-dangerous.sh +0 -203
  354. package/templates/.claude/hooks/classify_command.py +0 -592
  355. package/templates/.claude/hooks/doc-frontmatter-check.sh +0 -173
  356. package/templates/.claude/hooks/lite-sprawl-check.sh +0 -145
  357. package/templates/.claude/hooks/naming-check.sh +0 -100
  358. package/templates/.claude/hooks/protected-paths.sh +0 -39
  359. package/templates/.claude/hooks/quality-check.sh +0 -81
  360. package/templates/.claude/hooks/scan-secrets.sh +0 -85
  361. package/templates/.claude/hooks/scope-guard.sh +0 -381
  362. package/templates/.claude/hooks/session-caws-status.sh +0 -117
  363. package/templates/.claude/hooks/session-log.sh +0 -634
  364. package/templates/.claude/hooks/simplification-guard.sh +0 -92
  365. package/templates/.claude/hooks/stop-worktree-check.sh +0 -46
  366. package/templates/.claude/hooks/test_classify_command.py +0 -370
  367. package/templates/.claude/hooks/test_wrapper_smoke.sh +0 -96
  368. package/templates/.claude/hooks/validate-spec.sh +0 -76
  369. package/templates/.claude/hooks/worktree-guard.sh +0 -220
  370. package/templates/.claude/hooks/worktree-write-guard.sh +0 -190
  371. package/templates/.claude/rules/git-safety.md +0 -26
  372. package/templates/.claude/rules/worktree-isolation.md +0 -83
  373. package/templates/.claude/settings.json +0 -141
  374. package/templates/.cursor/README.md +0 -299
  375. package/templates/.cursor/hooks/audit.sh +0 -55
  376. package/templates/.cursor/hooks/block-dangerous.sh +0 -84
  377. package/templates/.cursor/hooks/caws-quality-check.sh +0 -52
  378. package/templates/.cursor/hooks/caws-scope-guard.sh +0 -130
  379. package/templates/.cursor/hooks/format.sh +0 -38
  380. package/templates/.cursor/hooks/naming-check.sh +0 -64
  381. package/templates/.cursor/hooks/scan-secrets.sh +0 -51
  382. package/templates/.cursor/hooks/scope-guard.sh +0 -52
  383. package/templates/.cursor/hooks/session-log.sh +0 -924
  384. package/templates/.cursor/hooks/validate-spec.sh +0 -83
  385. package/templates/.cursor/hooks.json +0 -76
  386. package/templates/.cursor/rules/00-claims-verification.mdc +0 -144
  387. package/templates/.cursor/rules/01-working-style.mdc +0 -50
  388. package/templates/.cursor/rules/02-quality-gates.mdc +0 -368
  389. package/templates/.cursor/rules/03-naming-and-refactor.mdc +0 -33
  390. package/templates/.cursor/rules/04-logging-language-style.mdc +0 -23
  391. package/templates/.cursor/rules/05-safe-defaults-guards.mdc +0 -23
  392. package/templates/.cursor/rules/06-typescript-conventions.mdc +0 -36
  393. package/templates/.cursor/rules/07-process-ops.mdc +0 -20
  394. package/templates/.cursor/rules/08-solid-and-architecture.mdc +0 -16
  395. package/templates/.cursor/rules/09-docstrings.mdc +0 -89
  396. package/templates/.cursor/rules/10-documentation-quality-standards.mdc +0 -385
  397. package/templates/.cursor/rules/11-scope-management-waivers.mdc +0 -381
  398. package/templates/.cursor/rules/12-implementation-completeness.mdc +0 -516
  399. package/templates/.cursor/rules/13-language-agnostic-standards.mdc +0 -578
  400. package/templates/.cursor/rules/README.md +0 -148
  401. package/templates/.github/copilot-instructions.md +0 -82
  402. package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +0 -5
  403. package/templates/.idea/runConfigurations/CAWS_Validate.xml +0 -5
  404. package/templates/.junie/guidelines.md +0 -73
  405. package/templates/.vscode/launch.json +0 -17
  406. package/templates/.vscode/settings.json +0 -95
  407. package/templates/.windsurf/rules/caws-quality-standards.md +0 -54
  408. package/templates/.windsurf/workflows/caws-guided-development.md +0 -92
  409. package/templates/CLAUDE.md +0 -174
  410. package/templates/COMMIT_CONVENTIONS.md +0 -86
  411. package/templates/OIDC_SETUP.md +0 -300
  412. package/templates/agents.md +0 -145
  413. package/templates/codemod/README.md +0 -1
  414. package/templates/codemod/test.js +0 -93
  415. package/templates/docs/README.md +0 -151
  416. package/templates/scripts/new_feature.sh +0 -80
  417. package/templates/scripts/quality-gates/check-god-objects.js +0 -146
  418. package/templates/scripts/quality-gates/run-quality-gates.js +0 -50
  419. 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
- };