@paths.design/caws-cli 10.2.0 → 11.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/README.md +125 -374
  2. package/dist/index.js +45 -787
  3. package/dist/init/harness-detect.d.ts +18 -0
  4. package/dist/init/harness-detect.d.ts.map +1 -0
  5. package/dist/init/harness-detect.js +90 -0
  6. package/dist/init/harness-detect.js.map +1 -0
  7. package/dist/init/hook-install.d.ts +53 -0
  8. package/dist/init/hook-install.d.ts.map +1 -0
  9. package/dist/init/hook-install.js +421 -0
  10. package/dist/init/hook-install.js.map +1 -0
  11. package/dist/init/hook-packs/manifest-claude-code.d.ts +4 -0
  12. package/dist/init/hook-packs/manifest-claude-code.d.ts.map +1 -0
  13. package/dist/init/hook-packs/manifest-claude-code.js +190 -0
  14. package/dist/init/hook-packs/manifest-claude-code.js.map +1 -0
  15. package/dist/init/hook-packs/register.d.ts +19 -0
  16. package/dist/init/hook-packs/register.d.ts.map +1 -0
  17. package/dist/init/hook-packs/register.js +37 -0
  18. package/dist/init/hook-packs/register.js.map +1 -0
  19. package/dist/init/hook-packs/types.d.ts +123 -0
  20. package/dist/init/hook-packs/types.d.ts.map +1 -0
  21. package/dist/init/hook-packs/types.js +29 -0
  22. package/dist/init/hook-packs/types.js.map +1 -0
  23. package/dist/shell/binding/resolve-binding.d.ts +4 -0
  24. package/dist/shell/binding/resolve-binding.d.ts.map +1 -0
  25. package/dist/shell/binding/resolve-binding.js +228 -0
  26. package/dist/shell/binding/resolve-binding.js.map +1 -0
  27. package/dist/shell/binding/types.d.ts +42 -0
  28. package/dist/shell/binding/types.d.ts.map +1 -0
  29. package/dist/shell/binding/types.js +21 -0
  30. package/dist/shell/binding/types.js.map +1 -0
  31. package/dist/shell/commands/claim.d.ts +14 -0
  32. package/dist/shell/commands/claim.d.ts.map +1 -0
  33. package/dist/shell/commands/claim.js +197 -0
  34. package/dist/shell/commands/claim.js.map +1 -0
  35. package/dist/shell/commands/doctor.d.ts +13 -0
  36. package/dist/shell/commands/doctor.d.ts.map +1 -0
  37. package/dist/shell/commands/doctor.js +97 -0
  38. package/dist/shell/commands/doctor.js.map +1 -0
  39. package/dist/shell/commands/evidence.d.ts +28 -0
  40. package/dist/shell/commands/evidence.d.ts.map +1 -0
  41. package/dist/shell/commands/evidence.js +166 -0
  42. package/dist/shell/commands/evidence.js.map +1 -0
  43. package/dist/shell/commands/gates.d.ts +19 -0
  44. package/dist/shell/commands/gates.d.ts.map +1 -0
  45. package/dist/shell/commands/gates.js +208 -0
  46. package/dist/shell/commands/gates.js.map +1 -0
  47. package/dist/shell/commands/init.d.ts +17 -0
  48. package/dist/shell/commands/init.d.ts.map +1 -0
  49. package/dist/shell/commands/init.js +168 -0
  50. package/dist/shell/commands/init.js.map +1 -0
  51. package/dist/shell/commands/scope.d.ts +11 -0
  52. package/dist/shell/commands/scope.d.ts.map +1 -0
  53. package/dist/shell/commands/scope.js +92 -0
  54. package/dist/shell/commands/scope.js.map +1 -0
  55. package/dist/shell/commands/specs.d.ts +41 -0
  56. package/dist/shell/commands/specs.d.ts.map +1 -0
  57. package/dist/shell/commands/specs.js +264 -0
  58. package/dist/shell/commands/specs.js.map +1 -0
  59. package/dist/shell/commands/status.d.ts +15 -0
  60. package/dist/shell/commands/status.d.ts.map +1 -0
  61. package/dist/shell/commands/status.js +106 -0
  62. package/dist/shell/commands/status.js.map +1 -0
  63. package/dist/shell/commands/waiver.d.ts +38 -0
  64. package/dist/shell/commands/waiver.d.ts.map +1 -0
  65. package/dist/shell/commands/waiver.js +240 -0
  66. package/dist/shell/commands/waiver.js.map +1 -0
  67. package/dist/shell/commands/worktree.d.ts +38 -0
  68. package/dist/shell/commands/worktree.d.ts.map +1 -0
  69. package/dist/shell/commands/worktree.js +286 -0
  70. package/dist/shell/commands/worktree.js.map +1 -0
  71. package/dist/shell/gates/disposition.d.ts +23 -0
  72. package/dist/shell/gates/disposition.d.ts.map +1 -0
  73. package/dist/shell/gates/disposition.js +117 -0
  74. package/dist/shell/gates/disposition.js.map +1 -0
  75. package/dist/shell/gates/gate-result-contract.d.ts +39 -0
  76. package/dist/shell/gates/gate-result-contract.d.ts.map +1 -0
  77. package/dist/shell/gates/gate-result-contract.js +150 -0
  78. package/dist/shell/gates/gate-result-contract.js.map +1 -0
  79. package/dist/shell/gates/local-evaluators/budget-limit.d.ts +24 -0
  80. package/dist/shell/gates/local-evaluators/budget-limit.d.ts.map +1 -0
  81. package/dist/shell/gates/local-evaluators/budget-limit.js +67 -0
  82. package/dist/shell/gates/local-evaluators/budget-limit.js.map +1 -0
  83. package/dist/shell/gates/local-evaluators/diff-helpers.d.ts +25 -0
  84. package/dist/shell/gates/local-evaluators/diff-helpers.d.ts.map +1 -0
  85. package/dist/shell/gates/local-evaluators/diff-helpers.js +74 -0
  86. package/dist/shell/gates/local-evaluators/diff-helpers.js.map +1 -0
  87. package/dist/shell/gates/local-evaluators/index.d.ts +28 -0
  88. package/dist/shell/gates/local-evaluators/index.d.ts.map +1 -0
  89. package/dist/shell/gates/local-evaluators/index.js +67 -0
  90. package/dist/shell/gates/local-evaluators/index.js.map +1 -0
  91. package/dist/shell/gates/local-evaluators/scope-boundary.d.ts +23 -0
  92. package/dist/shell/gates/local-evaluators/scope-boundary.d.ts.map +1 -0
  93. package/dist/shell/gates/local-evaluators/scope-boundary.js +67 -0
  94. package/dist/shell/gates/local-evaluators/scope-boundary.js.map +1 -0
  95. package/dist/shell/gates/local-evaluators/spec-completeness.d.ts +12 -0
  96. package/dist/shell/gates/local-evaluators/spec-completeness.d.ts.map +1 -0
  97. package/dist/shell/gates/local-evaluators/spec-completeness.js +73 -0
  98. package/dist/shell/gates/local-evaluators/spec-completeness.js.map +1 -0
  99. package/dist/shell/gates/quality-gates-adapter.d.ts +55 -0
  100. package/dist/shell/gates/quality-gates-adapter.d.ts.map +1 -0
  101. package/dist/shell/gates/quality-gates-adapter.js +161 -0
  102. package/dist/shell/gates/quality-gates-adapter.js.map +1 -0
  103. package/dist/shell/gates/waiver-filter.d.ts +58 -0
  104. package/dist/shell/gates/waiver-filter.d.ts.map +1 -0
  105. package/dist/shell/gates/waiver-filter.js +119 -0
  106. package/dist/shell/gates/waiver-filter.js.map +1 -0
  107. package/dist/shell/index.d.ts +54 -0
  108. package/dist/shell/index.d.ts.map +1 -0
  109. package/dist/shell/index.js +85 -0
  110. package/dist/shell/index.js.map +1 -0
  111. package/dist/shell/register.d.ts +11 -0
  112. package/dist/shell/register.d.ts.map +1 -0
  113. package/dist/shell/register.js +464 -0
  114. package/dist/shell/register.js.map +1 -0
  115. package/dist/shell/render/claim.d.ts +22 -0
  116. package/dist/shell/render/claim.d.ts.map +1 -0
  117. package/dist/shell/render/claim.js +75 -0
  118. package/dist/shell/render/claim.js.map +1 -0
  119. package/dist/shell/render/decision.d.ts +15 -0
  120. package/dist/shell/render/decision.d.ts.map +1 -0
  121. package/dist/shell/render/decision.js +66 -0
  122. package/dist/shell/render/decision.js.map +1 -0
  123. package/dist/shell/render/diagnostic.d.ts +19 -0
  124. package/dist/shell/render/diagnostic.d.ts.map +1 -0
  125. package/dist/shell/render/diagnostic.js +76 -0
  126. package/dist/shell/render/diagnostic.js.map +1 -0
  127. package/dist/shell/render/finding.d.ts +15 -0
  128. package/dist/shell/render/finding.d.ts.map +1 -0
  129. package/dist/shell/render/finding.js +57 -0
  130. package/dist/shell/render/finding.js.map +1 -0
  131. package/dist/shell/render/gates.d.ts +3 -0
  132. package/dist/shell/render/gates.d.ts.map +1 -0
  133. package/dist/shell/render/gates.js +56 -0
  134. package/dist/shell/render/gates.js.map +1 -0
  135. package/dist/shell/render/init-hook-pack.d.ts +16 -0
  136. package/dist/shell/render/init-hook-pack.d.ts.map +1 -0
  137. package/dist/shell/render/init-hook-pack.js +206 -0
  138. package/dist/shell/render/init-hook-pack.js.map +1 -0
  139. package/dist/shell/render/init.d.ts +11 -0
  140. package/dist/shell/render/init.d.ts.map +1 -0
  141. package/dist/shell/render/init.js +32 -0
  142. package/dist/shell/render/init.js.map +1 -0
  143. package/dist/shell/render/status.d.ts +26 -0
  144. package/dist/shell/render/status.d.ts.map +1 -0
  145. package/dist/shell/render/status.js +143 -0
  146. package/dist/shell/render/status.js.map +1 -0
  147. package/dist/shell/render/waiver.d.ts +21 -0
  148. package/dist/shell/render/waiver.d.ts.map +1 -0
  149. package/dist/shell/render/waiver.js +94 -0
  150. package/dist/shell/render/waiver.js.map +1 -0
  151. package/dist/shell/rules.d.ts +37 -0
  152. package/dist/shell/rules.d.ts.map +1 -0
  153. package/dist/shell/rules.js +51 -0
  154. package/dist/shell/rules.js.map +1 -0
  155. package/dist/shell/session/actor.d.ts +14 -0
  156. package/dist/shell/session/actor.d.ts.map +1 -0
  157. package/dist/shell/session/actor.js +34 -0
  158. package/dist/shell/session/actor.js.map +1 -0
  159. package/dist/shell/session/resolve-session.d.ts +5 -0
  160. package/dist/shell/session/resolve-session.d.ts.map +1 -0
  161. package/dist/shell/session/resolve-session.js +239 -0
  162. package/dist/shell/session/resolve-session.js.map +1 -0
  163. package/dist/shell/session/types.d.ts +56 -0
  164. package/dist/shell/session/types.d.ts.map +1 -0
  165. package/dist/shell/session/types.js +15 -0
  166. package/dist/shell/session/types.js.map +1 -0
  167. package/dist/store/agents-store.d.ts +3 -0
  168. package/dist/store/agents-store.d.ts.map +1 -0
  169. package/dist/store/agents-store.js +63 -0
  170. package/dist/store/agents-store.js.map +1 -0
  171. package/dist/store/apply-patch.d.ts +16 -0
  172. package/dist/store/apply-patch.d.ts.map +1 -0
  173. package/dist/store/apply-patch.js +191 -0
  174. package/dist/store/apply-patch.js.map +1 -0
  175. package/dist/store/atomic-write.d.ts +34 -0
  176. package/dist/store/atomic-write.d.ts.map +1 -0
  177. package/dist/store/atomic-write.js +174 -0
  178. package/dist/store/atomic-write.js.map +1 -0
  179. package/dist/store/doctor-snapshot.d.ts +20 -0
  180. package/dist/store/doctor-snapshot.d.ts.map +1 -0
  181. package/dist/store/doctor-snapshot.js +176 -0
  182. package/dist/store/doctor-snapshot.js.map +1 -0
  183. package/dist/store/events-store.d.ts +33 -0
  184. package/dist/store/events-store.d.ts.map +1 -0
  185. package/dist/store/events-store.js +297 -0
  186. package/dist/store/events-store.js.map +1 -0
  187. package/dist/store/index.d.ts +21 -0
  188. package/dist/store/index.d.ts.map +1 -0
  189. package/dist/store/index.js +47 -0
  190. package/dist/store/index.js.map +1 -0
  191. package/dist/store/init-store.d.ts +21 -0
  192. package/dist/store/init-store.d.ts.map +1 -0
  193. package/dist/store/init-store.js +295 -0
  194. package/dist/store/init-store.js.map +1 -0
  195. package/dist/store/json-store.d.ts +3 -0
  196. package/dist/store/json-store.d.ts.map +1 -0
  197. package/dist/store/json-store.js +65 -0
  198. package/dist/store/json-store.js.map +1 -0
  199. package/dist/store/lifecycle-lock.d.ts +34 -0
  200. package/dist/store/lifecycle-lock.d.ts.map +1 -0
  201. package/dist/store/lifecycle-lock.js +168 -0
  202. package/dist/store/lifecycle-lock.js.map +1 -0
  203. package/dist/store/lifecycle-transaction.d.ts +79 -0
  204. package/dist/store/lifecycle-transaction.d.ts.map +1 -0
  205. package/dist/store/lifecycle-transaction.js +319 -0
  206. package/dist/store/lifecycle-transaction.js.map +1 -0
  207. package/dist/store/policy-store.d.ts +3 -0
  208. package/dist/store/policy-store.d.ts.map +1 -0
  209. package/dist/store/policy-store.js +65 -0
  210. package/dist/store/policy-store.js.map +1 -0
  211. package/dist/store/repo-root.d.ts +46 -0
  212. package/dist/store/repo-root.d.ts.map +1 -0
  213. package/dist/store/repo-root.js +145 -0
  214. package/dist/store/repo-root.js.map +1 -0
  215. package/dist/store/rules.d.ts +69 -0
  216. package/dist/store/rules.d.ts.map +1 -0
  217. package/dist/store/rules.js +95 -0
  218. package/dist/store/rules.js.map +1 -0
  219. package/dist/store/specs-store.d.ts +3 -0
  220. package/dist/store/specs-store.d.ts.map +1 -0
  221. package/dist/store/specs-store.js +131 -0
  222. package/dist/store/specs-store.js.map +1 -0
  223. package/dist/store/specs-writer.d.ts +61 -0
  224. package/dist/store/specs-writer.d.ts.map +1 -0
  225. package/dist/store/specs-writer.js +506 -0
  226. package/dist/store/specs-writer.js.map +1 -0
  227. package/dist/store/types.d.ts +84 -0
  228. package/dist/store/types.d.ts.map +1 -0
  229. package/dist/store/types.js +14 -0
  230. package/dist/store/types.js.map +1 -0
  231. package/dist/store/waivers-store.d.ts +25 -0
  232. package/dist/store/waivers-store.d.ts.map +1 -0
  233. package/dist/store/waivers-store.js +232 -0
  234. package/dist/store/waivers-store.js.map +1 -0
  235. package/dist/store/worktrees-store.d.ts +3 -0
  236. package/dist/store/worktrees-store.d.ts.map +1 -0
  237. package/dist/store/worktrees-store.js +62 -0
  238. package/dist/store/worktrees-store.js.map +1 -0
  239. package/dist/store/worktrees-writer.d.ts +77 -0
  240. package/dist/store/worktrees-writer.d.ts.map +1 -0
  241. package/dist/store/worktrees-writer.js +674 -0
  242. package/dist/store/worktrees-writer.js.map +1 -0
  243. package/dist/store/yaml-patch.d.ts +7 -0
  244. package/dist/store/yaml-patch.d.ts.map +1 -0
  245. package/dist/store/yaml-patch.js +250 -0
  246. package/dist/store/yaml-patch.js.map +1 -0
  247. package/dist/store/yaml-store.d.ts +9 -0
  248. package/dist/store/yaml-store.d.ts.map +1 -0
  249. package/dist/store/yaml-store.js +121 -0
  250. package/dist/store/yaml-store.js.map +1 -0
  251. package/package.json +15 -13
  252. package/dist/budget-derivation.js +0 -751
  253. package/dist/cicd-optimizer.js +0 -504
  254. package/dist/commands/agents.js +0 -124
  255. package/dist/commands/archive.js +0 -500
  256. package/dist/commands/burnup.js +0 -198
  257. package/dist/commands/diagnose.js +0 -525
  258. package/dist/commands/evaluate.js +0 -314
  259. package/dist/commands/gates.js +0 -149
  260. package/dist/commands/init.js +0 -857
  261. package/dist/commands/iterate.js +0 -417
  262. package/dist/commands/mode.js +0 -269
  263. package/dist/commands/parallel.js +0 -242
  264. package/dist/commands/plan.js +0 -438
  265. package/dist/commands/provenance.js +0 -1143
  266. package/dist/commands/quality-monitor.js +0 -284
  267. package/dist/commands/scope.js +0 -264
  268. package/dist/commands/session.js +0 -312
  269. package/dist/commands/sidecar.js +0 -74
  270. package/dist/commands/specs.js +0 -1656
  271. package/dist/commands/status.js +0 -1172
  272. package/dist/commands/templates.js +0 -237
  273. package/dist/commands/tool.js +0 -136
  274. package/dist/commands/tutorial.js +0 -480
  275. package/dist/commands/validate.js +0 -357
  276. package/dist/commands/verify-acs.js +0 -443
  277. package/dist/commands/waivers.js +0 -599
  278. package/dist/commands/workflow.js +0 -243
  279. package/dist/commands/worktree.js +0 -502
  280. package/dist/config/lite-scope.js +0 -158
  281. package/dist/config/modes.js +0 -347
  282. package/dist/constants/spec-types.js +0 -65
  283. package/dist/gates/budget-limit.js +0 -121
  284. package/dist/gates/feedback.js +0 -260
  285. package/dist/gates/format.js +0 -179
  286. package/dist/gates/god-object.js +0 -117
  287. package/dist/gates/pipeline.js +0 -167
  288. package/dist/gates/scope-boundary.js +0 -112
  289. package/dist/gates/spec-completeness.js +0 -109
  290. package/dist/gates/todo-detection.js +0 -205
  291. package/dist/generators/jest-config-generator.js +0 -242
  292. package/dist/generators/working-spec.js +0 -237
  293. package/dist/minimal-cli.js +0 -88
  294. package/dist/parallel/parallel-manager.js +0 -433
  295. package/dist/policy/PolicyManager.js +0 -470
  296. package/dist/scaffold/claude-hooks.js +0 -443
  297. package/dist/scaffold/cursor-hooks.js +0 -177
  298. package/dist/scaffold/git-hooks.js +0 -928
  299. package/dist/scaffold/index.js +0 -794
  300. package/dist/session/session-manager.js +0 -653
  301. package/dist/sidecars/index.js +0 -33
  302. package/dist/sidecars/listeners.js +0 -40
  303. package/dist/sidecars/provenance-summary.js +0 -238
  304. package/dist/sidecars/quality-gaps.js +0 -258
  305. package/dist/sidecars/schema.js +0 -149
  306. package/dist/sidecars/spec-drift.js +0 -151
  307. package/dist/sidecars/waiver-draft.js +0 -176
  308. package/dist/spec/SpecFileManager.js +0 -419
  309. package/dist/templates/.caws/schemas/policy.schema.json +0 -117
  310. package/dist/templates/.caws/schemas/scope.schema.json +0 -52
  311. package/dist/templates/.caws/schemas/waivers.schema.json +0 -106
  312. package/dist/templates/.caws/schemas/working-spec.schema.json +0 -340
  313. package/dist/templates/.caws/schemas/worktrees.schema.json +0 -38
  314. package/dist/templates/.caws/templates/working-spec.template.yml +0 -80
  315. package/dist/templates/.caws/tools/README.md +0 -18
  316. package/dist/templates/.caws/tools/scope-guard.js +0 -203
  317. package/dist/templates/.caws/tools-allow.json +0 -331
  318. package/dist/templates/.caws/waivers.yml +0 -19
  319. package/dist/templates/.claude/README.md +0 -190
  320. package/dist/templates/.claude/hooks/audit.sh +0 -121
  321. package/dist/templates/.claude/hooks/block-dangerous.sh +0 -203
  322. package/dist/templates/.claude/hooks/classify_command.py +0 -592
  323. package/dist/templates/.claude/hooks/doc-frontmatter-check.sh +0 -173
  324. package/dist/templates/.claude/hooks/lite-sprawl-check.sh +0 -145
  325. package/dist/templates/.claude/hooks/naming-check.sh +0 -100
  326. package/dist/templates/.claude/hooks/protected-paths.sh +0 -39
  327. package/dist/templates/.claude/hooks/quality-check.sh +0 -81
  328. package/dist/templates/.claude/hooks/scan-secrets.sh +0 -85
  329. package/dist/templates/.claude/hooks/scope-guard.sh +0 -381
  330. package/dist/templates/.claude/hooks/session-caws-status.sh +0 -117
  331. package/dist/templates/.claude/hooks/session-log.sh +0 -634
  332. package/dist/templates/.claude/hooks/simplification-guard.sh +0 -92
  333. package/dist/templates/.claude/hooks/stop-worktree-check.sh +0 -46
  334. package/dist/templates/.claude/hooks/test_classify_command.py +0 -370
  335. package/dist/templates/.claude/hooks/test_wrapper_smoke.sh +0 -96
  336. package/dist/templates/.claude/hooks/validate-spec.sh +0 -76
  337. package/dist/templates/.claude/hooks/worktree-guard.sh +0 -220
  338. package/dist/templates/.claude/hooks/worktree-write-guard.sh +0 -190
  339. package/dist/templates/.claude/rules/git-safety.md +0 -26
  340. package/dist/templates/.claude/rules/worktree-isolation.md +0 -101
  341. package/dist/templates/.claude/settings.json +0 -141
  342. package/dist/templates/.cursor/README.md +0 -299
  343. package/dist/templates/.cursor/hooks/audit.sh +0 -55
  344. package/dist/templates/.cursor/hooks/block-dangerous.sh +0 -84
  345. package/dist/templates/.cursor/hooks/caws-quality-check.sh +0 -52
  346. package/dist/templates/.cursor/hooks/caws-scope-guard.sh +0 -130
  347. package/dist/templates/.cursor/hooks/format.sh +0 -38
  348. package/dist/templates/.cursor/hooks/naming-check.sh +0 -64
  349. package/dist/templates/.cursor/hooks/scan-secrets.sh +0 -51
  350. package/dist/templates/.cursor/hooks/scope-guard.sh +0 -52
  351. package/dist/templates/.cursor/hooks/session-log.sh +0 -924
  352. package/dist/templates/.cursor/hooks/validate-spec.sh +0 -83
  353. package/dist/templates/.cursor/hooks.json +0 -76
  354. package/dist/templates/.cursor/rules/00-claims-verification.mdc +0 -144
  355. package/dist/templates/.cursor/rules/01-working-style.mdc +0 -50
  356. package/dist/templates/.cursor/rules/02-quality-gates.mdc +0 -368
  357. package/dist/templates/.cursor/rules/03-naming-and-refactor.mdc +0 -33
  358. package/dist/templates/.cursor/rules/04-logging-language-style.mdc +0 -23
  359. package/dist/templates/.cursor/rules/05-safe-defaults-guards.mdc +0 -23
  360. package/dist/templates/.cursor/rules/06-typescript-conventions.mdc +0 -36
  361. package/dist/templates/.cursor/rules/07-process-ops.mdc +0 -20
  362. package/dist/templates/.cursor/rules/08-solid-and-architecture.mdc +0 -16
  363. package/dist/templates/.cursor/rules/09-docstrings.mdc +0 -89
  364. package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +0 -385
  365. package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +0 -381
  366. package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +0 -516
  367. package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +0 -578
  368. package/dist/templates/.cursor/rules/README.md +0 -148
  369. package/dist/templates/.github/copilot-instructions.md +0 -82
  370. package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +0 -5
  371. package/dist/templates/.idea/runConfigurations/CAWS_Validate.xml +0 -5
  372. package/dist/templates/.junie/guidelines.md +0 -73
  373. package/dist/templates/.vscode/launch.json +0 -17
  374. package/dist/templates/.vscode/settings.json +0 -95
  375. package/dist/templates/.windsurf/rules/caws-quality-standards.md +0 -54
  376. package/dist/templates/.windsurf/workflows/caws-guided-development.md +0 -92
  377. package/dist/templates/CLAUDE.md +0 -196
  378. package/dist/templates/COMMIT_CONVENTIONS.md +0 -86
  379. package/dist/templates/OIDC_SETUP.md +0 -300
  380. package/dist/templates/agents.md +0 -171
  381. package/dist/templates/codemod/README.md +0 -1
  382. package/dist/templates/codemod/test.js +0 -93
  383. package/dist/templates/docs/README.md +0 -151
  384. package/dist/templates/scripts/new_feature.sh +0 -80
  385. package/dist/templates/scripts/quality-gates/check-god-objects.js +0 -146
  386. package/dist/templates/scripts/quality-gates/run-quality-gates.js +0 -50
  387. package/dist/templates/scripts/v3/analysis/todo_analyzer.py +0 -1997
  388. package/dist/test-analysis.js +0 -786
  389. package/dist/tool-interface.js +0 -314
  390. package/dist/tool-loader.js +0 -303
  391. package/dist/tool-validator.js +0 -393
  392. package/dist/utils/agent-display.js +0 -210
  393. package/dist/utils/agent-session.js +0 -344
  394. package/dist/utils/async-utils.js +0 -188
  395. package/dist/utils/command-wrapper.js +0 -200
  396. package/dist/utils/event-log.js +0 -584
  397. package/dist/utils/event-renderer.js +0 -521
  398. package/dist/utils/finalization.js +0 -230
  399. package/dist/utils/git-lock.js +0 -119
  400. package/dist/utils/gitignore-updater.js +0 -158
  401. package/dist/utils/ide-detection.js +0 -133
  402. package/dist/utils/lifecycle-events.js +0 -94
  403. package/dist/utils/project-analysis.js +0 -367
  404. package/dist/utils/promise-utils.js +0 -72
  405. package/dist/utils/quality-gates-errors.js +0 -520
  406. package/dist/utils/quality-gates-utils.js +0 -387
  407. package/dist/utils/schema-validator.js +0 -50
  408. package/dist/utils/spec-resolver.js +0 -711
  409. package/dist/utils/typescript-detector.js +0 -369
  410. package/dist/utils/working-state.js +0 -530
  411. package/dist/utils/yaml-validation.js +0 -156
  412. package/dist/validation/spec-validation.js +0 -924
  413. package/dist/waivers-manager.js +0 -732
  414. package/dist/worktree/worktree-manager.js +0 -1735
  415. package/templates/.caws/schemas/policy.schema.json +0 -117
  416. package/templates/.caws/schemas/scope.schema.json +0 -52
  417. package/templates/.caws/schemas/waivers.schema.json +0 -106
  418. package/templates/.caws/schemas/working-spec.schema.json +0 -340
  419. package/templates/.caws/schemas/worktrees.schema.json +0 -38
  420. package/templates/.caws/templates/working-spec.template.yml +0 -80
  421. package/templates/.caws/tools/README.md +0 -18
  422. package/templates/.caws/tools/scope-guard.js +0 -203
  423. package/templates/.caws/tools-allow.json +0 -331
  424. package/templates/.caws/waivers.yml +0 -19
  425. package/templates/.claude/README.md +0 -190
  426. package/templates/.claude/hooks/audit.sh +0 -121
  427. package/templates/.claude/hooks/block-dangerous.sh +0 -203
  428. package/templates/.claude/hooks/classify_command.py +0 -592
  429. package/templates/.claude/hooks/doc-frontmatter-check.sh +0 -173
  430. package/templates/.claude/hooks/lite-sprawl-check.sh +0 -145
  431. package/templates/.claude/hooks/naming-check.sh +0 -100
  432. package/templates/.claude/hooks/protected-paths.sh +0 -39
  433. package/templates/.claude/hooks/quality-check.sh +0 -81
  434. package/templates/.claude/hooks/scan-secrets.sh +0 -85
  435. package/templates/.claude/hooks/scope-guard.sh +0 -381
  436. package/templates/.claude/hooks/session-caws-status.sh +0 -117
  437. package/templates/.claude/hooks/session-log.sh +0 -634
  438. package/templates/.claude/hooks/simplification-guard.sh +0 -92
  439. package/templates/.claude/hooks/stop-worktree-check.sh +0 -46
  440. package/templates/.claude/hooks/test_classify_command.py +0 -370
  441. package/templates/.claude/hooks/test_wrapper_smoke.sh +0 -96
  442. package/templates/.claude/hooks/validate-spec.sh +0 -76
  443. package/templates/.claude/hooks/worktree-guard.sh +0 -220
  444. package/templates/.claude/hooks/worktree-write-guard.sh +0 -190
  445. package/templates/.claude/rules/git-safety.md +0 -26
  446. package/templates/.claude/rules/worktree-isolation.md +0 -101
  447. package/templates/.claude/settings.json +0 -141
  448. package/templates/.cursor/README.md +0 -299
  449. package/templates/.cursor/hooks/audit.sh +0 -55
  450. package/templates/.cursor/hooks/block-dangerous.sh +0 -84
  451. package/templates/.cursor/hooks/caws-quality-check.sh +0 -52
  452. package/templates/.cursor/hooks/caws-scope-guard.sh +0 -130
  453. package/templates/.cursor/hooks/format.sh +0 -38
  454. package/templates/.cursor/hooks/naming-check.sh +0 -64
  455. package/templates/.cursor/hooks/scan-secrets.sh +0 -51
  456. package/templates/.cursor/hooks/scope-guard.sh +0 -52
  457. package/templates/.cursor/hooks/session-log.sh +0 -924
  458. package/templates/.cursor/hooks/validate-spec.sh +0 -83
  459. package/templates/.cursor/hooks.json +0 -76
  460. package/templates/.cursor/rules/00-claims-verification.mdc +0 -144
  461. package/templates/.cursor/rules/01-working-style.mdc +0 -50
  462. package/templates/.cursor/rules/02-quality-gates.mdc +0 -368
  463. package/templates/.cursor/rules/03-naming-and-refactor.mdc +0 -33
  464. package/templates/.cursor/rules/04-logging-language-style.mdc +0 -23
  465. package/templates/.cursor/rules/05-safe-defaults-guards.mdc +0 -23
  466. package/templates/.cursor/rules/06-typescript-conventions.mdc +0 -36
  467. package/templates/.cursor/rules/07-process-ops.mdc +0 -20
  468. package/templates/.cursor/rules/08-solid-and-architecture.mdc +0 -16
  469. package/templates/.cursor/rules/09-docstrings.mdc +0 -89
  470. package/templates/.cursor/rules/10-documentation-quality-standards.mdc +0 -385
  471. package/templates/.cursor/rules/11-scope-management-waivers.mdc +0 -381
  472. package/templates/.cursor/rules/12-implementation-completeness.mdc +0 -516
  473. package/templates/.cursor/rules/13-language-agnostic-standards.mdc +0 -578
  474. package/templates/.cursor/rules/README.md +0 -148
  475. package/templates/.github/copilot-instructions.md +0 -82
  476. package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +0 -5
  477. package/templates/.idea/runConfigurations/CAWS_Validate.xml +0 -5
  478. package/templates/.junie/guidelines.md +0 -73
  479. package/templates/.vscode/launch.json +0 -17
  480. package/templates/.vscode/settings.json +0 -95
  481. package/templates/.windsurf/rules/caws-quality-standards.md +0 -54
  482. package/templates/.windsurf/workflows/caws-guided-development.md +0 -92
  483. package/templates/CLAUDE.md +0 -196
  484. package/templates/COMMIT_CONVENTIONS.md +0 -86
  485. package/templates/OIDC_SETUP.md +0 -300
  486. package/templates/agents.md +0 -171
  487. package/templates/codemod/README.md +0 -1
  488. package/templates/codemod/test.js +0 -93
  489. package/templates/docs/README.md +0 -151
  490. package/templates/scripts/new_feature.sh +0 -80
  491. package/templates/scripts/quality-gates/check-god-objects.js +0 -146
  492. package/templates/scripts/quality-gates/run-quality-gates.js +0 -50
  493. package/templates/scripts/v3/analysis/todo_analyzer.py +0 -1997
@@ -1,592 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Command safety classifier for Claude Code PreToolUse hooks.
4
-
5
- Segments shell commands, parses them individually, and classifies each
6
- as allow / confirm / deny based on tiered policy.
7
-
8
- Output: JSON object with keys:
9
- decision: "allow" | "ask" | "deny"
10
- reason: human-readable explanation (empty string for allow)
11
-
12
- Usage:
13
- echo "$COMMAND" | python3 classify_command.py [--repo-root DIR] [--home DIR]
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- import json
19
- import os
20
- import re
21
- import shlex
22
- import sys
23
- from pathlib import Path
24
-
25
-
26
- # ---------------------------------------------------------------------------
27
- # Configuration
28
- # ---------------------------------------------------------------------------
29
-
30
- # Paths that are safe targets for recursive deletion (relative to repo root).
31
- # After normalization, if the resolved path starts with one of these, allow.
32
- SAFE_DELETE_PREFIXES: list[str] = [
33
- "target/",
34
- "tmp/",
35
- ".pytest_cache/",
36
- "node_modules/",
37
- "__pycache__/",
38
- ]
39
-
40
- # Pipeline-aware deny patterns: matched against the FULL raw command string
41
- # BEFORE segmentation. These detect cross-pipeline dangers like curl|sh and
42
- # fork bombs whose syntax spans segment boundaries.
43
- DENY_PIPELINE_PATTERNS: list[tuple[str, str]] = [
44
- # Pipe-to-shell (network exfiltration) — must match across | boundary
45
- (r"\b(curl|wget)\b.*\|\s*(ba)?sh\b", "pipe-to-shell execution"),
46
- # Fork bombs — special syntax that segmentation mangles
47
- (r":\(\)\s*\{.*:\|:.*\}\s*;\s*:", "fork bomb"),
48
- (r"\bwhile\s+true\b.*\bfork\b", "fork loop"),
49
- ]
50
-
51
- # Segment-level regex patterns that are always hard-blocked.
52
- # These are matched against individual parsed command segments, NOT the raw
53
- # command string. Quoted literals in other segments will not trigger them.
54
- DENY_SEGMENT_PATTERNS: list[tuple[str, str]] = [
55
- # System destruction
56
- (r"\bdd\b.*\bif=/dev/(zero|random)\b", "dd with destructive input"),
57
- (r"\bmkfs\.", "filesystem format"),
58
- (r"\bfdisk\b", "disk partitioning"),
59
- (r">\s*/dev/sd", "raw device write"),
60
- # Permission escalation
61
- (r"\bchmod\b.*\+s\b", "setuid/setgid bit"),
62
- # System control
63
- (r"\b(shutdown|reboot)\b", "system shutdown/reboot"),
64
- (r"\binit\s+[06]\b", "system runlevel change"),
65
- ]
66
-
67
- # Segment-level regex patterns that require user confirmation.
68
- CONFIRM_SEGMENT_PATTERNS: list[tuple[str, str]] = [
69
- # Git destructive operations
70
- (r"\bgit\s+reset\s+--hard\b", "git reset --hard"),
71
- (r"\bgit\s+push\s+(-f\b|--force\b|--force-with-lease\b)", "git force push"),
72
- (r"\bgit\s+clean\s+-[a-zA-Z]*f", "git clean with force"),
73
- (r"\bgit\s+checkout\s+\.\s*$", "git checkout . (discard all changes)"),
74
- (r"\bgit\s+restore\s+\.\s*$", "git restore . (discard all changes)"),
75
- (r"\bgit\s+rebase\b", "git rebase (rewrites branch history)"),
76
- (r"\bgit\s+cherry-pick\b", "git cherry-pick (replays commits across branches)"),
77
- # chmod 777
78
- (r"\bchmod\b.*\b777\b", "chmod 777"),
79
- # History manipulation
80
- (r"\bhistory\s+-c\b", "history clear"),
81
- # sudo (not in allowed list)
82
- (r"^sudo\s+(?!npm|yarn|pnpm|brew|apt-get|apt|dnf|yum)", "sudo command"),
83
- # venv creation (sprawl prevention)
84
- (r"\bpython3?\s+-m\s+venv\b", "virtual environment creation"),
85
- (r"\bvirtualenv\s", "virtual environment creation"),
86
- (r"\bconda\s+create\b", "conda environment creation"),
87
- # git init (unless CAWS worktree context)
88
- (r"\bgit\s+init\b", "git init"),
89
- # Credential file reads
90
- (r"\bcat\b.*\.(env|ssh/|aws/)", "credential file read"),
91
- (r"\bcat\b.*/etc/(passwd|shadow)\b", "system credential read"),
92
- (r"\bcat\b.*(id_rsa|credentials)\b", "credential file read"),
93
- ]
94
-
95
-
96
- # ---------------------------------------------------------------------------
97
- # Command segmentation
98
- # ---------------------------------------------------------------------------
99
-
100
- def segment_command(raw: str) -> list[str]:
101
- """Split a shell command string on &&, ||, ;, | operators.
102
-
103
- Respects quoted strings so that e.g. git commit -m "rm -rf /" does not
104
- split inside the quotes. Returns individual command segments with
105
- leading/trailing whitespace stripped.
106
-
107
- This is intentionally conservative: if we cannot parse, we return
108
- the entire string as one segment so it still gets classified.
109
- """
110
- segments: list[str] = []
111
- current: list[str] = []
112
- i = 0
113
- in_single = False
114
- in_double = False
115
- in_heredoc: str | None = None
116
- heredoc_marker: str = ""
117
-
118
- while i < len(raw):
119
- ch = raw[i]
120
-
121
- # ---- heredoc detection ----
122
- # Look for <<EOF or <<'EOF' or <<"EOF" at segment level
123
- if not in_single and not in_double and in_heredoc is None:
124
- if raw[i:i+2] == "<<":
125
- # Extract the delimiter
126
- j = i + 2
127
- while j < len(raw) and raw[j] in (' ', '\t'):
128
- j += 1
129
- # Strip optional quotes around delimiter
130
- quote_char = None
131
- if j < len(raw) and raw[j] in ("'", '"'):
132
- quote_char = raw[j]
133
- j += 1
134
- k = j
135
- while k < len(raw) and raw[k] not in (' ', '\t', '\n', "'", '"', ')'):
136
- k += 1
137
- if k > j:
138
- heredoc_marker = raw[j:k]
139
- in_heredoc = heredoc_marker
140
- # Skip to end of this line
141
- nl = raw.find('\n', i)
142
- if nl >= 0:
143
- current.append(raw[i:nl+1])
144
- i = nl + 1
145
- else:
146
- current.append(raw[i:])
147
- i = len(raw)
148
- continue
149
-
150
- # ---- inside heredoc: scan for closing marker ----
151
- if in_heredoc is not None:
152
- nl = raw.find('\n', i)
153
- if nl < 0:
154
- # No newline found, rest is heredoc content
155
- current.append(raw[i:])
156
- i = len(raw)
157
- continue
158
- line = raw[i:nl]
159
- current.append(raw[i:nl+1])
160
- i = nl + 1
161
- if line.strip() == in_heredoc:
162
- in_heredoc = None
163
- continue
164
-
165
- # ---- quoting ----
166
- if ch == '\\' and not in_single:
167
- current.append(raw[i:i+2])
168
- i += 2
169
- continue
170
- if ch == "'" and not in_double:
171
- in_single = not in_single
172
- current.append(ch)
173
- i += 1
174
- continue
175
- if ch == '"' and not in_single:
176
- in_double = not in_double
177
- current.append(ch)
178
- i += 1
179
- continue
180
-
181
- # ---- segment separators (only outside quotes) ----
182
- if not in_single and not in_double:
183
- # && or ||
184
- if raw[i:i+2] in ('&&', '||'):
185
- seg = ''.join(current).strip()
186
- if seg:
187
- segments.append(seg)
188
- current = []
189
- i += 2
190
- continue
191
- # ; (but not ;;)
192
- if ch == ';' and (i + 1 >= len(raw) or raw[i+1] != ';'):
193
- seg = ''.join(current).strip()
194
- if seg:
195
- segments.append(seg)
196
- current = []
197
- i += 1
198
- continue
199
- # | (but not ||, already handled above)
200
- if ch == '|':
201
- seg = ''.join(current).strip()
202
- if seg:
203
- segments.append(seg)
204
- current = []
205
- i += 1
206
- continue
207
-
208
- current.append(ch)
209
- i += 1
210
-
211
- seg = ''.join(current).strip()
212
- if seg:
213
- segments.append(seg)
214
-
215
- return segments if segments else [raw.strip()]
216
-
217
-
218
- def strip_quotes(s: str) -> str:
219
- """Remove surrounding quotes from a shell token."""
220
- if len(s) >= 2:
221
- if (s[0] == '"' and s[-1] == '"') or (s[0] == "'" and s[-1] == "'"):
222
- return s[1:-1]
223
- return s
224
-
225
-
226
- def extract_command_word(segment: str) -> str:
227
- """Extract the first command word from a segment.
228
-
229
- Strips leading variable assignments (FOO=bar), env prefixes,
230
- and common wrappers like 'time'.
231
- """
232
- try:
233
- tokens = shlex.split(segment)
234
- except ValueError:
235
- # Malformed quoting — return raw first word
236
- return segment.split()[0] if segment.split() else ""
237
-
238
- for tok in tokens:
239
- # Skip variable assignments
240
- if '=' in tok and not tok.startswith('-'):
241
- continue
242
- # Skip common prefixes
243
- if tok in ('env', 'time', 'nice', 'nohup', 'command', 'builtin'):
244
- continue
245
- return tok
246
- return ""
247
-
248
-
249
- # ---------------------------------------------------------------------------
250
- # rm classifier
251
- # ---------------------------------------------------------------------------
252
-
253
- def is_recursive_rm(segment: str) -> tuple[bool, list[str]]:
254
- """Check if a segment is an rm command with recursive flags.
255
-
256
- Returns (is_recursive, [target_paths]).
257
- """
258
- try:
259
- tokens = shlex.split(segment)
260
- except ValueError:
261
- # Cannot parse — be conservative
262
- if re.search(r'\brm\b', segment) and re.search(r'-[a-zA-Z]*r', segment):
263
- return True, []
264
- return False, []
265
-
266
- if not tokens:
267
- return False, []
268
-
269
- # Find the rm command (skip env/time prefixes)
270
- rm_idx = -1
271
- for idx, tok in enumerate(tokens):
272
- if tok in ('env', 'time', 'nice', 'nohup', 'command', 'builtin'):
273
- continue
274
- if '=' in tok and not tok.startswith('-'):
275
- continue
276
- if tok == 'rm':
277
- rm_idx = idx
278
- break
279
-
280
- if rm_idx < 0:
281
- return False, []
282
-
283
- # Check for recursive flag
284
- is_recursive = False
285
- targets: list[str] = []
286
- i = rm_idx + 1
287
- while i < len(tokens):
288
- tok = tokens[i]
289
- if tok == '--':
290
- # Everything after -- is targets
291
- targets.extend(tokens[i+1:])
292
- break
293
- if tok.startswith('-') and not tok.startswith('--'):
294
- if 'r' in tok or 'R' in tok:
295
- is_recursive = True
296
- elif tok.startswith('--'):
297
- if tok == '--recursive':
298
- is_recursive = True
299
- # Other long options: skip
300
- else:
301
- targets.append(tok)
302
- i += 1
303
-
304
- return is_recursive, targets
305
-
306
-
307
- def classify_rm_target(
308
- target: str,
309
- repo_root: Path,
310
- home: Path,
311
- cwd: Path,
312
- ) -> tuple[str, str]:
313
- """Classify a single rm target path.
314
-
315
- Returns ("deny"|"ask"|"allow", reason).
316
- """
317
- # Resolve the target to an absolute path
318
- raw = target.strip()
319
- if not raw:
320
- return "deny", "empty target on recursive delete"
321
-
322
- # Handle glob-like patterns conservatively
323
- if any(c in raw for c in ('*', '?', '[', ']')):
324
- # Check if it is /* or ~/* which are catastrophic
325
- stripped = raw.rstrip('/')
326
- if stripped in ('/*', '~/*', './*'):
327
- return "deny", f"glob expansion at dangerous root: {raw}"
328
- # Other globs: confirm
329
- return "ask", f"recursive delete with glob pattern: {raw}"
330
-
331
- # Resolve path
332
- try:
333
- if raw.startswith('~'):
334
- resolved = (home / raw[2:]).resolve(strict=False) if len(raw) > 1 else home
335
- elif raw.startswith('/'):
336
- resolved = Path(raw).resolve(strict=False)
337
- else:
338
- resolved = (cwd / raw).resolve(strict=False)
339
- except (ValueError, OSError):
340
- return "ask", f"cannot resolve path: {raw}"
341
-
342
- resolved_str = str(resolved)
343
- repo_str = str(repo_root)
344
- home_str = str(home)
345
-
346
- # Hard-block: root, home, repo root
347
- if resolved_str == '/':
348
- return "deny", f"recursive delete targets filesystem root"
349
- if resolved_str == home_str:
350
- return "deny", f"recursive delete targets home directory"
351
- if resolved_str == repo_str:
352
- return "deny", f"recursive delete targets repository root"
353
-
354
- # Check if resolved path is a parent of repo or home (even worse)
355
- if repo_str.startswith(resolved_str + '/'):
356
- return "deny", f"recursive delete targets ancestor of repository: {raw}"
357
- if home_str.startswith(resolved_str + '/'):
358
- return "deny", f"recursive delete targets ancestor of home directory: {raw}"
359
-
360
- # Allow: known safe prefixes (relative to repo root)
361
- try:
362
- rel = resolved.relative_to(repo_root)
363
- rel_str = str(rel) + '/'
364
- for prefix in SAFE_DELETE_PREFIXES:
365
- if rel_str.startswith(prefix):
366
- return "allow", ""
367
- except ValueError:
368
- pass # Not inside repo root
369
-
370
- # Default: confirm
371
- return "ask", f"recursive delete: {raw}"
372
-
373
-
374
- def classify_find_delete(segment: str) -> tuple[str, str] | None:
375
- """Check if segment is a find command with -delete or -exec rm.
376
-
377
- Returns classification tuple or None if not a find-delete.
378
- """
379
- try:
380
- tokens = shlex.split(segment)
381
- except ValueError:
382
- return None
383
-
384
- cmd = extract_command_word(segment)
385
- if cmd != 'find':
386
- return None
387
-
388
- has_delete = '-delete' in tokens
389
- has_exec_rm = False
390
- for i, tok in enumerate(tokens):
391
- if tok == '-exec' and i + 1 < len(tokens) and 'rm' in tokens[i + 1]:
392
- has_exec_rm = True
393
- break
394
-
395
- if not has_delete and not has_exec_rm:
396
- return None
397
-
398
- return "ask", f"find with delete action"
399
-
400
-
401
- def strip_quoted_regions(raw: str) -> str:
402
- """Remove content inside single/double quotes and heredocs.
403
-
404
- Returns only the executable shell surface — quoted literals, heredoc
405
- bodies, and $(...) subshell content embedded in quotes are replaced
406
- with whitespace so that regex patterns only match actual commands.
407
- """
408
- result: list[str] = []
409
- i = 0
410
- in_single = False
411
- in_double = False
412
- in_heredoc: str | None = None
413
-
414
- while i < len(raw):
415
- ch = raw[i]
416
-
417
- # Heredoc detection (outside quotes)
418
- if not in_single and not in_double and in_heredoc is None:
419
- if raw[i:i+2] == "<<":
420
- j = i + 2
421
- while j < len(raw) and raw[j] in (' ', '\t'):
422
- j += 1
423
- if j < len(raw) and raw[j] in ("'", '"'):
424
- j += 1
425
- k = j
426
- while k < len(raw) and raw[k] not in (' ', '\t', '\n', "'", '"', ')'):
427
- k += 1
428
- if k > j:
429
- in_heredoc = raw[j:k]
430
- # Keep the << marker but skip to end of line
431
- result.append(raw[i:i+2])
432
- nl = raw.find('\n', i)
433
- if nl >= 0:
434
- i = nl + 1
435
- else:
436
- i = len(raw)
437
- continue
438
-
439
- # Inside heredoc: skip until closing marker
440
- if in_heredoc is not None:
441
- nl = raw.find('\n', i)
442
- if nl < 0:
443
- i = len(raw)
444
- continue
445
- line = raw[i:nl]
446
- i = nl + 1
447
- if line.strip() == in_heredoc:
448
- in_heredoc = None
449
- else:
450
- result.append(' ') # placeholder
451
- continue
452
-
453
- # Escape handling
454
- if ch == '\\' and not in_single:
455
- result.append(' ')
456
- i += 2
457
- continue
458
-
459
- # Quote tracking
460
- if ch == "'" and not in_double:
461
- if in_single:
462
- in_single = False
463
- else:
464
- in_single = True
465
- i += 1
466
- continue
467
- if ch == '"' and not in_single:
468
- if in_double:
469
- in_double = False
470
- else:
471
- in_double = True
472
- i += 1
473
- continue
474
-
475
- # Inside quotes: replace with space
476
- if in_single or in_double:
477
- result.append(' ')
478
- i += 1
479
- continue
480
-
481
- result.append(ch)
482
- i += 1
483
-
484
- return ''.join(result)
485
-
486
-
487
- # ---------------------------------------------------------------------------
488
- # Main classifier
489
- # ---------------------------------------------------------------------------
490
-
491
- def classify_command(
492
- raw_command: str,
493
- repo_root: Path,
494
- home: Path,
495
- cwd: Path,
496
- caws_worktree: bool = False,
497
- ) -> tuple[str, str]:
498
- """Classify a full command string.
499
-
500
- Returns the most restrictive (decision, reason) across all segments.
501
- Priority: deny > ask > allow.
502
- """
503
- worst_decision = "allow"
504
- worst_reason = ""
505
-
506
- def escalate(decision: str, reason: str) -> None:
507
- nonlocal worst_decision, worst_reason
508
- priority = {"allow": 0, "ask": 1, "deny": 2}
509
- if priority.get(decision, 0) > priority.get(worst_decision, 0):
510
- worst_decision = decision
511
- worst_reason = reason
512
-
513
- # --- Pipeline-aware deny patterns ---
514
- # Strip quoted regions so patterns only match executable shell surface.
515
- # This prevents commit messages, echo arguments, etc. from triggering.
516
- executable_surface = strip_quoted_regions(raw_command)
517
- for pattern, desc in DENY_PIPELINE_PATTERNS:
518
- if re.search(pattern, executable_surface, re.IGNORECASE):
519
- escalate("deny", desc)
520
-
521
- segments = segment_command(raw_command)
522
-
523
- for segment in segments:
524
- # Strip quoted regions for pattern matching so that e.g.
525
- # echo "git reset --hard" does not trigger the git pattern.
526
- # The original segment is still used for rm/find parsing
527
- # (shlex.split handles quotes correctly for argument extraction).
528
- segment_surface = strip_quoted_regions(segment)
529
-
530
- # --- Hard-block patterns (segment-level) ---
531
- for pattern, desc in DENY_SEGMENT_PATTERNS:
532
- if re.search(pattern, segment_surface, re.IGNORECASE):
533
- escalate("deny", desc)
534
-
535
- # --- Confirm patterns (segment-level) ---
536
- for pattern, desc in CONFIRM_SEGMENT_PATTERNS:
537
- if re.search(pattern, segment_surface, re.IGNORECASE):
538
- # Special case: git init in worktree context is allowed
539
- if "git init" in desc and caws_worktree:
540
- continue
541
- escalate("ask", desc)
542
-
543
- # --- rm classifier ---
544
- is_recursive, targets = is_recursive_rm(segment)
545
- if is_recursive:
546
- if not targets:
547
- # Cannot determine targets — be conservative
548
- escalate("ask", "recursive delete with unparseable targets")
549
- else:
550
- for target in targets:
551
- decision, reason = classify_rm_target(
552
- target, repo_root, home, cwd,
553
- )
554
- escalate(decision, reason)
555
-
556
- # --- find -delete classifier ---
557
- find_result = classify_find_delete(segment)
558
- if find_result:
559
- escalate(*find_result)
560
-
561
- return worst_decision, worst_reason
562
-
563
-
564
- # ---------------------------------------------------------------------------
565
- # Entry point
566
- # ---------------------------------------------------------------------------
567
-
568
- def main() -> None:
569
- import argparse
570
-
571
- parser = argparse.ArgumentParser(description="Classify shell command safety")
572
- parser.add_argument("--repo-root", default=os.environ.get("CLAUDE_PROJECT_DIR", "."))
573
- parser.add_argument("--home", default=str(Path.home()))
574
- parser.add_argument("--cwd", default=os.getcwd())
575
- args = parser.parse_args()
576
-
577
- raw_command = sys.stdin.read()
578
-
579
- repo_root = Path(args.repo_root).resolve(strict=False)
580
- home = Path(args.home).resolve(strict=False)
581
- cwd = Path(args.cwd).resolve(strict=False)
582
- caws_worktree = os.environ.get("CAWS_WORKTREE_CONTEXT", "0") == "1"
583
-
584
- decision, reason = classify_command(
585
- raw_command, repo_root, home, cwd, caws_worktree,
586
- )
587
-
588
- json.dump({"decision": decision, "reason": reason}, sys.stdout)
589
-
590
-
591
- if __name__ == "__main__":
592
- main()