@caddis/cli 0.2.6 → 0.4.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 (265) hide show
  1. package/bundles/antigravity-plugin/agents/claude-md-curator.md +1 -1
  2. package/bundles/antigravity-plugin/agents/knowledge-transfer.md +17 -32
  3. package/bundles/antigravity-plugin/caddis_statusline.py +825 -0
  4. package/bundles/antigravity-plugin/claude-md/folder-agents.md.tmpl +21 -0
  5. package/bundles/antigravity-plugin/guard_agy.py +60 -9
  6. package/bundles/antigravity-plugin/plugin.json +1 -1
  7. package/bundles/antigravity-plugin/scripts/caddis_gate.py +524 -0
  8. package/bundles/antigravity-plugin/scripts/caddis_inventory.py +439 -0
  9. package/bundles/antigravity-plugin/scripts/caddis_migrate_dir.py +270 -0
  10. package/bundles/antigravity-plugin/scripts/caddis_spawn.py +300 -0
  11. package/bundles/antigravity-plugin/scripts/caddis_statusline.py +825 -0
  12. package/bundles/antigravity-plugin/scripts/caddis_tidy.py +366 -0
  13. package/bundles/antigravity-plugin/scripts/check_doc_coverage.py +924 -0
  14. package/bundles/antigravity-plugin/scripts/claude-deepseek.ps1 +4 -0
  15. package/bundles/antigravity-plugin/scripts/claude-deepseek.sh +3 -0
  16. package/bundles/antigravity-plugin/scripts/claude-glm.ps1 +4 -0
  17. package/bundles/antigravity-plugin/scripts/claude-glm.sh +3 -0
  18. package/bundles/antigravity-plugin/scripts/claude-oss.ps1 +64 -0
  19. package/bundles/antigravity-plugin/scripts/claude-oss.sh +60 -0
  20. package/bundles/antigravity-plugin/scripts/claudster_config.py +121 -0
  21. package/bundles/antigravity-plugin/scripts/claudster_doctor.py +356 -0
  22. package/bundles/antigravity-plugin/scripts/claudster_init.py +400 -0
  23. package/bundles/antigravity-plugin/scripts/claudster_migrate_rules.py +300 -0
  24. package/bundles/antigravity-plugin/scripts/enforcement_inventory.py +261 -0
  25. package/bundles/antigravity-plugin/scripts/hook_log.py +156 -0
  26. package/bundles/antigravity-plugin/scripts/new-machine.ps1 +170 -0
  27. package/bundles/antigravity-plugin/scripts/oss_ask.py +98 -0
  28. package/bundles/antigravity-plugin/scripts/oss_model.py +174 -0
  29. package/bundles/antigravity-plugin/scripts/oss_review.py +718 -0
  30. package/bundles/antigravity-plugin/scripts/setup_project_ai.py +1432 -0
  31. package/bundles/antigravity-plugin/scripts/usage_review.py +1323 -0
  32. package/bundles/antigravity-plugin/session_end_agy.py +52 -8
  33. package/bundles/antigravity-plugin/skills/_registry.md +1 -0
  34. package/bundles/antigravity-plugin/skills/advisory-hub/SKILL.md +44 -0
  35. package/bundles/antigravity-plugin/skills/code-review/SKILL.md +11 -0
  36. package/bundles/antigravity-plugin/skills/fastapi-dev/SKILL.md +1 -1
  37. package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +21 -0
  38. package/bundles/antigravity-plugin/skills/gate-review/SKILL.md +11 -0
  39. package/bundles/antigravity-plugin/skills/implement/SKILL.md +36 -0
  40. package/bundles/antigravity-plugin/skills/security-review/SKILL.md +9 -0
  41. package/bundles/antigravity-plugin/skills/ship/SKILL.md +1 -1
  42. package/bundles/antigravity-plugin/skills/ship-merge/SKILL.md +1 -1
  43. package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +1 -1
  44. package/bundles/antigravity-plugin/skills/statusline/SKILL.md +105 -0
  45. package/bundles/antigravity-plugin/skills/use-model/SKILL.md +15 -3
  46. package/bundles/antigravity-plugin/skills/validate-phase/SKILL.md +31 -0
  47. package/bundles/antigravity-plugin/warm_start_agy.py +117 -11
  48. package/bundles/antigravity-plugin-extras/plugin.json +1 -1
  49. package/bundles/antigravity-plugin-extras/skills/architecture-design/SKILL.md +1 -1
  50. package/bundles/antigravity-plugin-extras/skills/caching-patterns/SKILL.md +1 -1
  51. package/bundles/antigravity-plugin-extras/skills/webapp-development/SKILL.md +1 -1
  52. package/bundles/codex/.codex/config.toml.example +36 -0
  53. package/bundles/codex/.codex/skills/coding/api-design/SKILL.md +249 -0
  54. package/bundles/codex/.codex/skills/coding/backend-development/SKILL.md +305 -0
  55. package/bundles/codex/.codex/skills/coding/code-review/SKILL.md +140 -0
  56. package/bundles/codex/.codex/skills/coding/codebase-audit/SKILL.md +204 -0
  57. package/bundles/codex/.codex/skills/coding/dashboard-perf-sweep/SKILL.md +132 -0
  58. package/bundles/codex/.codex/skills/coding/dashboard-perf-sweep/scripts/perf_sweep.py +249 -0
  59. package/bundles/codex/.codex/skills/coding/error-handling/SKILL.md +260 -0
  60. package/bundles/codex/.codex/skills/coding/fastapi-dev/SKILL.md +300 -0
  61. package/bundles/codex/.codex/skills/coding/javascript-typescript/SKILL.md +142 -0
  62. package/bundles/codex/.codex/skills/coding/python/SKILL.md +388 -0
  63. package/bundles/codex/.codex/skills/coding/refactoring/SKILL.md +486 -0
  64. package/bundles/codex/.codex/skills/coding/security-review/SKILL.md +205 -0
  65. package/bundles/codex/.codex/skills/coding/sql/SKILL.md +321 -0
  66. package/bundles/codex/.codex/skills/data/database-design/SKILL.md +177 -0
  67. package/bundles/codex/.codex/skills/data/db-diagram/SKILL.md +148 -0
  68. package/bundles/codex/.codex/skills/data/db-diagram/scripts/sql_to_graph.py +1212 -0
  69. package/bundles/codex/.codex/skills/devops/ci-cd-pipeline/SKILL.md +315 -0
  70. package/bundles/codex/.codex/skills/devops/gh-cli/SKILL.md +195 -0
  71. package/bundles/codex/.codex/skills/devops/git-commit/SKILL.md +285 -0
  72. package/bundles/codex/.codex/skills/devops/using-git-worktrees/SKILL.md +217 -0
  73. package/bundles/codex/.codex/skills/devops/windows-deployment/SKILL.md +880 -0
  74. package/bundles/codex/.codex/skills/docs/code-documentation/SKILL.md +271 -0
  75. package/bundles/codex/.codex/skills/docs/explainer-doc/SKILL.md +97 -0
  76. package/bundles/codex/.codex/skills/docs/technical-writing/SKILL.md +286 -0
  77. package/bundles/codex/.codex/skills/docs/writing-plans/SKILL.md +387 -0
  78. package/bundles/codex/.codex/skills/frontend/css-architecture/SKILL.md +305 -0
  79. package/bundles/codex/.codex/skills/frontend/css-architecture/references/RESPONSIVE-DESIGN.md +604 -0
  80. package/bundles/codex/.codex/skills/frontend/frontend-design/SKILL.md +193 -0
  81. package/bundles/codex/.codex/skills/frontend/frontend-design/references/my-tech-stack.md +127 -0
  82. package/bundles/codex/.codex/skills/frontend/mockup/SKILL.md +242 -0
  83. package/bundles/codex/.codex/skills/frontend/react-best-practices/SKILL.md +269 -0
  84. package/bundles/codex/.codex/skills/frontend/react-dev/README.md +404 -0
  85. package/bundles/codex/.codex/skills/frontend/react-dev/SKILL.md +459 -0
  86. package/bundles/codex/.codex/skills/frontend/react-dev/examples/generic-components.md +579 -0
  87. package/bundles/codex/.codex/skills/frontend/react-dev/examples/server-components.md +579 -0
  88. package/bundles/codex/.codex/skills/frontend/react-dev/references/event-handlers.md +574 -0
  89. package/bundles/codex/.codex/skills/frontend/react-dev/references/hooks.md +456 -0
  90. package/bundles/codex/.codex/skills/frontend/react-dev/references/react-19-patterns.md +638 -0
  91. package/bundles/codex/.codex/skills/frontend/react-dev/references/react-router.md +1002 -0
  92. package/bundles/codex/.codex/skills/frontend/react-dev/references/tanstack-router.md +587 -0
  93. package/bundles/codex/.codex/skills/frontend/ui-review/SKILL.md +176 -0
  94. package/bundles/codex/.codex/skills/frontend/ui-review/references/framework-fixes.md +471 -0
  95. package/bundles/codex/.codex/skills/frontend/ui-review/references/visual-checklist.md +236 -0
  96. package/bundles/codex/.codex/skills/frontend/warm-editorial-ui/DESIGN_TOKENS.md +487 -0
  97. package/bundles/codex/.codex/skills/frontend/warm-editorial-ui/IMPLEMENTATION_GUIDE.md +177 -0
  98. package/bundles/codex/.codex/skills/frontend/warm-editorial-ui/SKILL.md +732 -0
  99. package/bundles/codex/.codex/skills/media/draw-io/SKILL.md +162 -0
  100. package/bundles/codex/.codex/skills/media/draw-io/references/aws-icons.md +677 -0
  101. package/bundles/codex/.codex/skills/media/draw-io/references/layout-guidelines.md +142 -0
  102. package/bundles/codex/.codex/skills/media/draw-io/references/troubleshooting.md +118 -0
  103. package/bundles/codex/.codex/skills/media/draw-io/references/workflows.md +103 -0
  104. package/bundles/codex/.codex/skills/media/draw-io/scripts/convert-drawio-to-png.sh +25 -0
  105. package/bundles/codex/.codex/skills/media/draw-io/scripts/find_aws_icon.py +79 -0
  106. package/bundles/codex/.codex/skills/media/mermaid-diagrams/SKILL.md +292 -0
  107. package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/ENHANCEMENTS.md +264 -0
  108. package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/MERMAID-SUMMARY.md +137 -0
  109. package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/advanced-features.md +556 -0
  110. package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/architecture-diagrams.md +192 -0
  111. package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/c4-diagrams.md +410 -0
  112. package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/class-diagrams.md +361 -0
  113. package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/erd-diagrams.md +510 -0
  114. package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/flowcharts.md +450 -0
  115. package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/sequence-diagrams.md +394 -0
  116. package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/troubleshooting.md +335 -0
  117. package/bundles/codex/.codex/skills/media/mermaid-diagrams/references/workflows.md +418 -0
  118. package/bundles/codex/.codex/skills/media/particle-art/SKILL.md +243 -0
  119. package/bundles/codex/.codex/skills/media/particle-art/references/canvas-utils.ts +171 -0
  120. package/bundles/codex/.codex/skills/media/particle-art/references/dot-field.template.tsx +203 -0
  121. package/bundles/codex/.codex/skills/media/particle-art/references/flow-field.template.tsx +263 -0
  122. package/bundles/codex/.codex/skills/media/particle-art/references/node-shape.template.tsx +261 -0
  123. package/bundles/codex/.codex/skills/media/particle-art/references/shape-sampler.ts +281 -0
  124. package/bundles/codex/.codex/skills/media/particle-art/references/stipple-morph.template.tsx +167 -0
  125. package/bundles/codex/.codex/skills/media/particle-art/references/stipple.template.tsx +175 -0
  126. package/bundles/codex/.codex/skills/media/particle-art/references/trail-ghost.template.tsx +266 -0
  127. package/bundles/codex/.codex/skills/media/particle-art/references/usage-examples.md +320 -0
  128. package/bundles/codex/.codex/skills/testing/playwright/API_REFERENCE.md +653 -0
  129. package/bundles/codex/.codex/skills/testing/playwright/SKILL.md +454 -0
  130. package/bundles/codex/.codex/skills/testing/playwright/lib/helpers.js +441 -0
  131. package/bundles/codex/.codex/skills/testing/playwright/package.json +26 -0
  132. package/bundles/codex/.codex/skills/testing/playwright/run.js +228 -0
  133. package/bundles/codex/.codex/skills/testing/tdd-workflow/SKILL.md +188 -0
  134. package/bundles/codex/.codex/skills/testing/test-strategy/SKILL.md +155 -0
  135. package/bundles/codex/.codex/skills/testing/webapp-testing/LICENSE.txt +202 -0
  136. package/bundles/codex/.codex/skills/testing/webapp-testing/SKILL.md +97 -0
  137. package/bundles/codex/.codex/skills/testing/webapp-testing/examples/console_logging.py +35 -0
  138. package/bundles/codex/.codex/skills/testing/webapp-testing/examples/element_discovery.py +40 -0
  139. package/bundles/codex/.codex/skills/testing/webapp-testing/examples/static_html_automation.py +33 -0
  140. package/bundles/codex/.codex/skills/testing/webapp-testing/scripts/with_server.py +106 -0
  141. package/bundles/codex/.codex/skills/workflow/advisory-hub/SKILL.md +227 -0
  142. package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/context-template.md +214 -0
  143. package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/hub-handbook.md +233 -0
  144. package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/report-contract.md +94 -0
  145. package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/succession.md +118 -0
  146. package/bundles/codex/.codex/skills/workflow/best-practices/SKILL.md +500 -0
  147. package/bundles/codex/.codex/skills/workflow/best-practices/agents/best-practices-referencer.md +263 -0
  148. package/bundles/codex/.codex/skills/workflow/best-practices/agents/codebase-context-builder.md +326 -0
  149. package/bundles/codex/.codex/skills/workflow/best-practices/agents/task-intent-analyzer.md +245 -0
  150. package/bundles/codex/.codex/skills/workflow/best-practices/references/anti-patterns.md +571 -0
  151. package/bundles/codex/.codex/skills/workflow/best-practices/references/before-after-examples.md +1114 -0
  152. package/bundles/codex/.codex/skills/workflow/best-practices/references/best-practices-guide.md +513 -0
  153. package/bundles/codex/.codex/skills/workflow/best-practices/references/common-workflows.md +692 -0
  154. package/bundles/codex/.codex/skills/workflow/best-practices/references/prompt-patterns.md +547 -0
  155. package/bundles/codex/.codex/skills/workflow/brainstorming/SKILL.md +57 -0
  156. package/bundles/codex/.codex/skills/workflow/context-curator/SKILL.md +157 -0
  157. package/bundles/codex/.codex/skills/workflow/golden-plan/SKILL.md +586 -0
  158. package/bundles/codex/.codex/skills/workflow/preflight/SKILL.md +440 -0
  159. package/bundles/codex/.codex/skills/workflow/skill-creator/LICENSE.txt +202 -0
  160. package/bundles/codex/.codex/skills/workflow/skill-creator/SKILL.md +491 -0
  161. package/bundles/codex/.codex/skills/workflow/skill-creator/agents/analyzer.md +274 -0
  162. package/bundles/codex/.codex/skills/workflow/skill-creator/agents/comparator.md +202 -0
  163. package/bundles/codex/.codex/skills/workflow/skill-creator/agents/grader.md +223 -0
  164. package/bundles/codex/.codex/skills/workflow/skill-creator/assets/eval_review.html +146 -0
  165. package/bundles/codex/.codex/skills/workflow/skill-creator/eval-viewer/generate_review.py +471 -0
  166. package/bundles/codex/.codex/skills/workflow/skill-creator/eval-viewer/viewer.html +1325 -0
  167. package/bundles/codex/.codex/skills/workflow/skill-creator/references/schemas.md +430 -0
  168. package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/__init__.py +0 -0
  169. package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/aggregate_benchmark.py +401 -0
  170. package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/generate_report.py +326 -0
  171. package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/improve_description.py +247 -0
  172. package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/package_skill.py +136 -0
  173. package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/quick_validate.py +103 -0
  174. package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/run_eval.py +310 -0
  175. package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/run_loop.py +328 -0
  176. package/bundles/codex/.codex/skills/workflow/skill-creator/scripts/utils.py +47 -0
  177. package/bundles/codex/AGENTS.md +146 -0
  178. package/bundles/manifest.json +4 -3
  179. package/dist/claude-deepseek.js +544 -0
  180. package/dist/claude-glm.js +544 -0
  181. package/dist/claude-oss.js +544 -0
  182. package/dist/cli.js +542 -90
  183. package/package.json +5 -2
  184. package/bundles/antigravity-plugin/statusline-command-agy.sh +0 -91
  185. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +0 -93
  186. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  187. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  188. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +0 -93
  189. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  190. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +0 -93
  191. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  192. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  193. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +0 -93
  194. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  195. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  196. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  197. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +0 -93
  198. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  199. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +0 -93
  200. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
  201. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +0 -94
  202. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  203. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  204. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +0 -93
  205. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  206. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +0 -93
  207. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
  208. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  209. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +0 -93
  210. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  211. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  212. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  213. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  214. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  215. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  216. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  217. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  218. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +0 -93
  219. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  220. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  221. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  222. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +0 -93
  223. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
  224. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  225. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +0 -93
  226. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  227. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
  228. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
  229. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-OFL.txt +0 -93
  230. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +0 -93
  231. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  232. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
  233. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  234. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
  235. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-OFL.txt +0 -93
  236. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
  237. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  238. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +0 -93
  239. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  240. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +0 -93
  241. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  242. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
  243. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +0 -93
  244. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
  245. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  246. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +0 -93
  247. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +0 -93
  248. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  249. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  250. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +0 -93
  251. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  252. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +0 -93
  253. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  254. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  255. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +0 -93
  256. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
  257. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +0 -93
  258. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
  259. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  260. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  261. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  262. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +0 -93
  263. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  264. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +0 -93
  265. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
@@ -0,0 +1,300 @@
1
+ """claudster_migrate_rules — migrate a repo's rules hierarchy to AGENTS.md-canonical + CLAUDE.md shims.
2
+
3
+ `AGENTS.md` is the canonical rules file every agent reads (Claude Code, Codex CLI, Antigravity); each
4
+ `CLAUDE.md` becomes a thin `@AGENTS.md` import shim. This tool migrates an existing repo — root pair +
5
+ every subfolder `CLAUDE.md` — to that layout, safely and idempotently.
6
+
7
+ Behavior (per target repo):
8
+ • ROOT: CLAUDE.md's (fuller, possibly enriched) content becomes the canonical AGENTS.md, re-headed
9
+ agent-neutrally; CLAUDE.md becomes the shim. An existing AGENTS.md that is the old caddis mirror
10
+ (a subset of CLAUDE.md) is replaced. If the existing AGENTS.md carries content CLAUDE.md LACKS
11
+ (project enrichments on both sides), it is a CONFLICT — the root pair is skipped and reported
12
+ (exit 1); a human resolves the few by hand-merge, then re-runs to confirm.
13
+ • SUBFOLDERS: each <dir>/CLAUDE.md is `git mv`'d to <dir>/AGENTS.md (content verbatim) + a <dir>/CLAUDE.md shim.
14
+ • Idempotent (a re-run is a no-op); refuses a dirty tree (unless --allow-dirty); `--dry-run` is the
15
+ default (`--apply` executes); `git mv` when tracked; never touches archived `done/`-style files.
16
+ • --check (fork detector): flags any CLAUDE.md that is NOT a shim beside an AGENTS.md (drifted shim),
17
+ and any bare CLAUDE.md with no sibling AGENTS.md (a hand-created fork). Exit 1 on findings.
18
+
19
+ Usage:
20
+ python scripts/claudster_migrate_rules.py [target] # dry-run (default)
21
+ python scripts/claudster_migrate_rules.py [target] --apply # execute
22
+ python scripts/claudster_migrate_rules.py [target] --check # fork detector (gate)
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import argparse
27
+ import os
28
+ import subprocess
29
+ import sys
30
+ from pathlib import Path
31
+
32
+ # Reuse the harness templates + folder shim so the migration output matches what the generator emits.
33
+ try:
34
+ from setup_project_ai import HARNESS_DIR, SUBFOLDER_SHIM, render as _render
35
+ except Exception: # pragma: no cover — standalone fallback if setup_project_ai isn't importable
36
+ HARNESS_DIR = None
37
+ SUBFOLDER_SHIM = ("# Folder conventions — canonical in AGENTS.md (imported below; Claude Code inlines it)\n"
38
+ "@AGENTS.md\n")
39
+
40
+ def _render(text, mapping): # type: ignore
41
+ for k, v in mapping.items():
42
+ text = text.replace("{{" + k + "}}", v)
43
+ return text
44
+
45
+ # Directories we never migrate into (vendored/generated + archived rules).
46
+ SKIP_DIRS = {".git", "node_modules", ".venv", "venv", "__pycache__", ".mypy_cache", ".ruff_cache",
47
+ ".pytest_cache", "dist", "build", ".tanstack", "done", "archive", "archived"}
48
+
49
+ # Section headings whose bodies are pure template boilerplate — their wording differs between the old
50
+ # root.md.tmpl and agents.md.tmpl, so they are excluded from the divergence check (only PROJECT content
51
+ # is compared). "What this project is" is intentionally NOT here — its content IS compared.
52
+ _BOILERPLATE_HEADING_KEYS = (
53
+ "the laws", "the harness", "development harness", "context discipline", "conventions",
54
+ "where things live", "doc discipline", "resuming a session", "resources", "why subagents",
55
+ )
56
+
57
+ _CANONICAL_PREAMBLE = (
58
+ "> **This is the canonical rules file.** Every coding agent reads it — Claude Code (via the one-line\n"
59
+ "> `@AGENTS.md` import in `CLAUDE.md`), OpenAI Codex CLI, and Antigravity (`agy`). `CLAUDE.md` is a\n"
60
+ "> thin `@AGENTS.md` import shim; keep durable project rules HERE, never in the shim."
61
+ )
62
+
63
+
64
+ # ── git helpers ──────────────────────────────────────────────────────────────
65
+ def _git(target: Path, *args: str) -> subprocess.CompletedProcess:
66
+ return subprocess.run(["git", *args], cwd=str(target), capture_output=True, text=True)
67
+
68
+
69
+ def _in_git_repo(target: Path) -> bool:
70
+ r = _git(target, "rev-parse", "--is-inside-work-tree")
71
+ return r.returncode == 0 and r.stdout.strip() == "true"
72
+
73
+
74
+ def _is_dirty(target: Path) -> bool:
75
+ return bool(_git(target, "status", "--porcelain").stdout.strip())
76
+
77
+
78
+ def _is_tracked(target: Path, rel: str) -> bool:
79
+ return bool(_git(target, "ls-files", "--error-unmatch", rel).returncode == 0)
80
+
81
+
82
+ # ── content helpers ──────────────────────────────────────────────────────────
83
+ def is_shim(text: str) -> bool:
84
+ """A CLAUDE.md is a shim iff it carries a bare `@AGENTS.md` import line."""
85
+ return any(ln.strip() == "@AGENTS.md" for ln in text.splitlines())
86
+
87
+
88
+ def _project_name(claude_text: str, target: Path) -> str:
89
+ for ln in claude_text.splitlines():
90
+ if ln.startswith("# "):
91
+ title = ln[2:].strip()
92
+ # "<name> — Project Memory (root)" -> "<name>"
93
+ for sep in (" — ", " - ", " –– "):
94
+ if sep in title:
95
+ return title.split(sep, 1)[0].strip()
96
+ return title
97
+ return target.name
98
+
99
+
100
+ def _meaningful(text: str) -> set[str]:
101
+ """Normalized set of content lines (md markers/whitespace stripped), for subset comparison."""
102
+ out: set[str] = set()
103
+ for ln in text.splitlines():
104
+ s = ln.strip().lstrip("#>-*").strip()
105
+ # drop an ordered-list prefix like "1. "
106
+ while s[:1].isdigit():
107
+ s = s[1:]
108
+ s = s.lstrip(". ").strip()
109
+ if len(s) >= 8:
110
+ out.add(s.lower())
111
+ return out
112
+
113
+
114
+ def _checkable_lines(agents_text: str) -> set[str]:
115
+ """AGENTS.md's PROJECT content — meaningful lines from non-boilerplate-heading sections (and the
116
+ preamble is dropped). Compared against CLAUDE.md to detect divergence."""
117
+ lines = agents_text.splitlines()
118
+ # find section spans by "## " headings
119
+ heads = [i for i, l in enumerate(lines) if l.startswith("## ")]
120
+ out: set[str] = set()
121
+ for k, start in enumerate(heads):
122
+ heading = lines[start][3:].strip().lower()
123
+ if any(key in heading for key in _BOILERPLATE_HEADING_KEYS):
124
+ continue
125
+ end = heads[k + 1] if k + 1 < len(heads) else len(lines)
126
+ out |= _meaningful("\n".join(lines[start:end]))
127
+ return out
128
+
129
+
130
+ def root_conflict(claude_text: str, agents_text: str) -> list[str]:
131
+ """Return AGENTS.md project lines that CLAUDE.md lacks (the enrichment residue). Non-empty => the
132
+ two files diverged and must not be auto-merged."""
133
+ residue = _checkable_lines(agents_text) - _meaningful(claude_text)
134
+ return sorted(residue)
135
+
136
+
137
+ def reheaded_agents(claude_text: str, name: str) -> str:
138
+ """Canonical AGENTS.md from CLAUDE.md content: replace the shim/mirror preamble (everything before
139
+ the first `## ` heading) with a canonical header + preamble; keep the body (enrichments) verbatim."""
140
+ lines = claude_text.splitlines()
141
+ idx = next((i for i, l in enumerate(lines) if l.startswith("## ")), None)
142
+ body = "\n".join(lines[idx:]) if idx is not None else claude_text
143
+ header = f"# {name} — Project Memory (canonical rules)\n\n{_CANONICAL_PREAMBLE}\n\n"
144
+ text = header + body
145
+ return text if text.endswith("\n") else text + "\n"
146
+
147
+
148
+ def root_shim(name: str) -> str:
149
+ """The root CLAUDE.md shim — the harness root.md.tmpl rendered (reused, so no drift)."""
150
+ if HARNESS_DIR is not None:
151
+ tmpl = HARNESS_DIR / "claude-md" / "root.md.tmpl"
152
+ if tmpl.is_file():
153
+ return _render(tmpl.read_text(encoding="utf-8"), {"PROJECT_NAME": name})
154
+ return (f"# {name} — Project Memory\n\n@AGENTS.md\n\n"
155
+ "> **Canonical rules live in `AGENTS.md`** (imported above — Claude Code inlines it). This\n"
156
+ "> shim adds only Claude-native conveniences; durable rules go in `AGENTS.md`.\n")
157
+
158
+
159
+ # ── migration steps ──────────────────────────────────────────────────────────
160
+ def _write(target: Path, rel: str, content: str, apply: bool, report: list[str], verb: str):
161
+ report.append(f"{verb}: {rel}")
162
+ if apply:
163
+ dest = target / rel
164
+ dest.parent.mkdir(parents=True, exist_ok=True)
165
+ dest.write_text(content, encoding="utf-8")
166
+
167
+
168
+ def _move(target: Path, src_rel: str, dst_rel: str, apply: bool, report: list[str]):
169
+ report.append(f"move: {src_rel} -> {dst_rel}")
170
+ if not apply:
171
+ return
172
+ if _is_tracked(target, src_rel):
173
+ r = _git(target, "mv", src_rel, dst_rel)
174
+ if r.returncode == 0:
175
+ return
176
+ # untracked (or git mv failed): plain rename
177
+ (target / dst_rel).parent.mkdir(parents=True, exist_ok=True)
178
+ os.replace(target / src_rel, target / dst_rel)
179
+
180
+
181
+ def migrate_root(target: Path, apply: bool, report: list[str]) -> int:
182
+ """Migrate the root pair. Returns 1 on conflict (root skipped), else 0."""
183
+ claude = target / "CLAUDE.md"
184
+ agents = target / "AGENTS.md"
185
+ if not claude.exists() and not agents.exists():
186
+ report.append("root: no CLAUDE.md/AGENTS.md — nothing to migrate")
187
+ return 0
188
+ claude_text = claude.read_text(encoding="utf-8") if claude.exists() else ""
189
+ if claude.exists() and is_shim(claude_text) and agents.exists():
190
+ report.append("root: already migrated (CLAUDE.md is a shim) — no-op")
191
+ return 0
192
+ if not claude.exists():
193
+ report.append("root: AGENTS.md present, no CLAUDE.md — leaving as-is (already canonical or hand-authored)")
194
+ return 0
195
+ name = _project_name(claude_text, target)
196
+ if agents.exists():
197
+ conflict = root_conflict(claude_text, agents.read_text(encoding="utf-8"))
198
+ if conflict:
199
+ report.append("root: CONFLICT — AGENTS.md carries content CLAUDE.md lacks; skipping root pair. "
200
+ "Hand-merge into CLAUDE.md, then re-run. Divergent line(s): "
201
+ + "; ".join(conflict[:5]) + (" …" if len(conflict) > 5 else ""))
202
+ return 1
203
+ # safe: CLAUDE.md content -> canonical AGENTS.md; CLAUDE.md -> shim
204
+ _write(target, "AGENTS.md", reheaded_agents(claude_text, name), apply, report,
205
+ "rewrite (canonical)" if agents.exists() else "create (canonical)")
206
+ _write(target, "CLAUDE.md", root_shim(name), apply, report, "shim")
207
+ return 0
208
+
209
+
210
+ def _iter_subfolder_claude(target: Path):
211
+ for dirpath, dirnames, filenames in os.walk(target):
212
+ dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
213
+ d = Path(dirpath)
214
+ if d == target:
215
+ continue # root handled separately
216
+ if "CLAUDE.md" in filenames:
217
+ yield d
218
+
219
+
220
+ def migrate_subfolders(target: Path, apply: bool, report: list[str]) -> None:
221
+ for d in _iter_subfolder_claude(target):
222
+ rel = d.relative_to(target).as_posix()
223
+ claude_text = (d / "CLAUDE.md").read_text(encoding="utf-8")
224
+ if is_shim(claude_text) and (d / "AGENTS.md").exists():
225
+ report.append(f"{rel}/: already migrated — no-op")
226
+ continue
227
+ if (d / "AGENTS.md").exists():
228
+ report.append(f"{rel}/: CONFLICT — both AGENTS.md and a non-shim CLAUDE.md present; skipped")
229
+ continue
230
+ _move(target, f"{rel}/CLAUDE.md", f"{rel}/AGENTS.md", apply, report)
231
+ _write(target, f"{rel}/CLAUDE.md", SUBFOLDER_SHIM, apply, report, "shim")
232
+
233
+
234
+ def check_forks(target: Path) -> list[str]:
235
+ """Fork detector: non-shim CLAUDE.md beside an AGENTS.md, or a bare CLAUDE.md with no sibling AGENTS.md."""
236
+ findings: list[str] = []
237
+ for dirpath, dirnames, filenames in os.walk(target):
238
+ dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
239
+ if "CLAUDE.md" not in filenames:
240
+ continue
241
+ d = Path(dirpath)
242
+ rel = (d / "CLAUDE.md").relative_to(target).as_posix()
243
+ text = (d / "CLAUDE.md").read_text(encoding="utf-8", errors="replace")
244
+ if "AGENTS.md" in filenames:
245
+ if not is_shim(text):
246
+ findings.append(f"{rel}: CLAUDE.md is not a shim but a sibling AGENTS.md exists (drifted shim)")
247
+ else:
248
+ findings.append(f"{rel}: CLAUDE.md has no sibling AGENTS.md (hand-created fork — run /caddis:add-rules semantics)")
249
+ return findings
250
+
251
+
252
+ # ── main ─────────────────────────────────────────────────────────────────────
253
+ def main(argv: list[str] | None = None) -> int:
254
+ for _stream in (sys.stdout, sys.stderr): # Windows cp1252 can't encode em-dashes in our messages
255
+ if hasattr(_stream, "reconfigure"):
256
+ _stream.reconfigure(encoding="utf-8")
257
+ ap = argparse.ArgumentParser(description="Migrate a repo to AGENTS.md-canonical rules + CLAUDE.md shims.")
258
+ ap.add_argument("target", nargs="?", default=".", type=Path)
259
+ ap.add_argument("--apply", action="store_true", help="Execute (default is --dry-run).")
260
+ ap.add_argument("--allow-dirty", action="store_true", help="Proceed even if the working tree is dirty.")
261
+ ap.add_argument("--check", action="store_true", help="Fork detector only (gate mode); exit 1 on findings.")
262
+ args = ap.parse_args(argv)
263
+
264
+ target = args.target.resolve()
265
+ if not target.is_dir():
266
+ print(f"ERROR: target not a directory: {target}", file=sys.stderr)
267
+ return 2
268
+
269
+ if args.check:
270
+ findings = check_forks(target)
271
+ for f in findings:
272
+ print(f" fork: {f}")
273
+ if findings:
274
+ print(f"\n{len(findings)} rules-file fork(s) found.")
275
+ return 1
276
+ print("No rules-file forks — CLAUDE.md shims are clean.")
277
+ return 0
278
+
279
+ apply = args.apply
280
+ if not _in_git_repo(target):
281
+ print(f"ERROR: {target} is not a git repository (rules migration uses git mv).", file=sys.stderr)
282
+ return 2
283
+ if apply and _is_dirty(target) and not args.allow_dirty:
284
+ print("ERROR: working tree is dirty — commit/stash first, or pass --allow-dirty.", file=sys.stderr)
285
+ return 2
286
+
287
+ print(f"=== caddis-migrate-rules -> {target} {'(APPLY)' if apply else '(dry-run)'}")
288
+ report: list[str] = []
289
+ rc = migrate_root(target, apply, report)
290
+ migrate_subfolders(target, apply, report)
291
+ for line in report:
292
+ print(f" {line}")
293
+ if not apply:
294
+ print("\n(dry-run — no files changed; pass --apply to execute)")
295
+ print(f"\nresult: {'CONFLICT (root skipped)' if rc else 'ok'}")
296
+ return rc
297
+
298
+
299
+ if __name__ == "__main__":
300
+ raise SystemExit(main())
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env python3
2
+ """Inventory caddis's load-bearing rules, and measure how many are actually enforced.
3
+
4
+ WHY THIS EXISTS
5
+ ---------------
6
+ Three independent audits reached the same conclusion in different words: strip the prose from
7
+ this plugin and the machine content is very small. Nearly every "MUST" / "NEVER" / "STOP" is a
8
+ sentence in a markdown file that a model may or may not honour. Those degrade silently under
9
+ model change, context pressure, a cheaper lane, or a distracted session — and they read as
10
+ guarantees, which is worse than reading as advice.
11
+
12
+ The things that actually held under pressure were the ones with exit codes: pytest,
13
+ validate_pool, the hooks, oss_review's exit 2.
14
+
15
+ So this script measures the ratio of rules that a machine can fail on, to rules that only a
16
+ model can choose to honour. That number is the health metric for the plugin. It is expected to
17
+ start LOW; the point is that it is visible and moves in one direction.
18
+
19
+ THE ANTI-FLATTERY RULE
20
+ ----------------------
21
+ The classification is DERIVED FROM THE LINT TESTS, never asserted by hand. A rule counts as
22
+ `machine` only when a test that targets its file asserts a literal string contained in that
23
+ rule's text — i.e. deleting the rule would turn a test red. Everything else is `advisory`.
24
+
25
+ This matters because the first version of this script DID use a hand-written registry, and it
26
+ was wrong in both directions: a global substring match credited an unrelated skill's "ALWAYS ask
27
+ via AskUserQuestion" to the headless-mode test, while genuinely-covered rules went uncounted
28
+ because their sentence lacked the registry keyword. A metric you can talk yourself into is not a
29
+ metric. Deriving it from the tests also means it moves on its own the moment someone adds or
30
+ removes a lint, which is the only way a health number stays honest.
31
+
32
+ Known limit, stated rather than hidden: matching is per-line, so a rule spanning two lines can be
33
+ missed, and a long asserted literal can match a neighbouring sentence. Treat the ratio as an
34
+ order-of-magnitude signal and a direction of travel, not a precise score.
35
+
36
+ Usage:
37
+ python scripts/enforcement_inventory.py # summary + ratio
38
+ python scripts/enforcement_inventory.py --list # every rule, classified
39
+ python scripts/enforcement_inventory.py --advisory # only the unenforced ones
40
+ """
41
+ from __future__ import annotations
42
+
43
+ import argparse
44
+ import re
45
+ import sys
46
+ from dataclasses import dataclass
47
+ from pathlib import Path
48
+
49
+ if hasattr(sys.stdout, "reconfigure"):
50
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
51
+
52
+ REPO = Path(__file__).resolve().parents[1]
53
+ SCAN_ROOTS = [REPO / "claude-harness" / "commands", REPO / ".github" / "skills"]
54
+
55
+ # An imperative that makes a promise. Ordered longest-first so MUST NOT wins over MUST.
56
+ IMPERATIVE = re.compile(
57
+ r"\b(MUST NOT|MUST|NEVER|ALWAYS|REQUIRED|STOP|DO NOT|Do not)\b"
58
+ )
59
+
60
+ # ── Deriving enforcement FROM THE TESTS, not from a hand-written claim ──────
61
+ # An earlier version used a hand-maintained registry of rule->enforcer. It was wrong in both
62
+ # directions: a global substring match credited brand-design's "ALWAYS ask via AskUserQuestion"
63
+ # to the headless-mode test, which has nothing to do with it, while genuinely-covered rules went
64
+ # uncounted because their sentence lacked the registry's keyword.
65
+ #
66
+ # So the classifier now READS THE LINT TESTS. For each test module it works out which source
67
+ # files that module targets (from its module-level Path constants) and which literal strings it
68
+ # asserts on. A rule is `machine` when a test targeting ITS file asserts a literal contained in
69
+ # its text. Nothing is claimed that a test does not actually check, and the metric updates itself
70
+ # the moment someone adds or deletes a lint - which is the property a health metric must have.
71
+ LINT_TEST_DIRS = [REPO / "scripts" / "tests", REPO / "claude-harness" / "hooks" / "tests"]
72
+
73
+ # Literals shorter than this match too much to mean anything.
74
+ MIN_LITERAL = 10
75
+
76
+
77
+ def _test_modules() -> list[tuple[set[str], set[str]]]:
78
+ """(source files this test targets, literals it asserts) for every lint test module."""
79
+ import ast
80
+ out = []
81
+ for d in LINT_TEST_DIRS:
82
+ if not d.exists():
83
+ continue
84
+ for f in sorted(d.glob("test_*.py")):
85
+ if f.name == "test_enforcement_inventory.py":
86
+ continue # measures the metric; must not feed it
87
+ try:
88
+ tree = ast.parse(f.read_text(encoding="utf-8", errors="ignore"))
89
+ except SyntaxError:
90
+ continue
91
+ # ONLY literals inside an `assert` count. Scanning every string constant swept in
92
+ # docstrings and fixture data, and this module's own test docstrings describe the very
93
+ # rules being measured - so prose about a rule was being counted as enforcement OF that
94
+ # rule. The metric was measuring how much we had WRITTEN about enforcement.
95
+ literals: set[str] = set()
96
+ for node in ast.walk(tree):
97
+ if not isinstance(node, ast.Assert):
98
+ continue
99
+ for sub in ast.walk(node):
100
+ if isinstance(sub, ast.Constant) and isinstance(sub.value, str):
101
+ v = sub.value.strip()
102
+ if len(v) >= MIN_LITERAL:
103
+ literals.add(v)
104
+ # TARGETS come from ALL string constants, not just asserts: a module names the file
105
+ # it lints in a module-level Path constant (`_IMPLEMENT = ... / "implement.md"`), which
106
+ # is not an assert. Deriving both from the assert set emptied every target and drove
107
+ # the ratio to a false 0.0% - caught by this module's own plausibility test.
108
+ targets = {
109
+ v.strip() for node in ast.walk(tree)
110
+ if isinstance(node, ast.Constant) and isinstance(node.value, str)
111
+ for v in [node.value] if v.strip().endswith(".md")
112
+ }
113
+ out.append((targets, literals))
114
+ return out
115
+
116
+
117
+ _MODULES = _test_modules()
118
+
119
+
120
+ def _enforcer_for(rel_path: str, text: str) -> str | None:
121
+ """The lint that would fail if this rule were deleted, or None."""
122
+ fname = rel_path.rsplit("/", 1)[-1]
123
+ for targets, literals in _MODULES:
124
+ if not any(t == fname or t.endswith(fname) for t in targets):
125
+ continue
126
+ for lit in literals:
127
+ if lit.lower() in text.lower():
128
+ return "lint"
129
+ return None
130
+
131
+
132
+ # ── Gates: rules enforced by an executable, not merely by a content lint ────
133
+ # The first version counted only lint tests, so a rule backed by a REAL gate scored the same as
134
+ # one backed by nothing - the metric was blind to the strongest form of enforcement and would
135
+ # have shown no movement for the phase that introduced it. A rule counts as gate-enforced when
136
+ # its file invokes caddis_gate.py AND the rule concerns one of the gate's actual subcommands.
137
+ # The subcommand must exist in the script: a claim nobody implements is exactly what this file
138
+ # refuses to reward.
139
+ GATE_SCRIPT = REPO / "scripts" / "caddis_gate.py"
140
+ GATE_CONCERNS = {
141
+ "lane-check": ("lane", "spawn", "launch command", "--print", "-p"),
142
+ "verdict-gate": ("verdict", "advisory-hub", "blocked-pending-hub", "accept-degraded"),
143
+ "tracker-vs-git": ("tracker",),
144
+ }
145
+
146
+
147
+ def _live_gates() -> dict[str, tuple[str, ...]]:
148
+ """Subcommands the gate script actually implements. Verified, never assumed."""
149
+ if not GATE_SCRIPT.is_file():
150
+ return {}
151
+ src = GATE_SCRIPT.read_text(encoding="utf-8", errors="ignore")
152
+ return {k: v for k, v in GATE_CONCERNS.items() if f'"{k}"' in src or f"'{k}'" in src}
153
+
154
+
155
+ _GATES = _live_gates()
156
+
157
+
158
+ def _gate_for(file_text: str, text: str) -> str | None:
159
+ if "caddis_gate.py" not in file_text:
160
+ return None
161
+ low = text.lower()
162
+ for sub, needles in _GATES.items():
163
+ if any(n.lower() in low for n in needles):
164
+ return sub
165
+ return None
166
+
167
+
168
+ @dataclass
169
+ class Rule:
170
+ path: str
171
+ line: int
172
+ marker: str
173
+ text: str
174
+ enforcer: str | None = None
175
+
176
+ @property
177
+ def machine(self) -> bool:
178
+ return self.enforcer is not None
179
+
180
+
181
+ def _sentence(line: str) -> str:
182
+ return " ".join(line.split())[:150]
183
+
184
+
185
+ def collect(roots: list[Path] | None = None) -> list[Rule]:
186
+ rules: list[Rule] = []
187
+ for root in (roots or SCAN_ROOTS):
188
+ if not root.exists():
189
+ continue
190
+ for f in sorted(root.rglob("*.md")):
191
+ try:
192
+ lines = f.read_text(encoding="utf-8", errors="ignore").splitlines()
193
+ except Exception:
194
+ continue
195
+ # Fall back to the absolute path for roots outside the repo, so collect() can be
196
+ # pointed at a fixture directory. A metric script that cannot be tested on a fixture
197
+ # can only be verified by eyeballing its output, which is how metrics start lying.
198
+ whole = "\n".join(lines)
199
+ try:
200
+ rel = str(f.relative_to(REPO)).replace("\\", "/")
201
+ except ValueError:
202
+ rel = str(f).replace("\\", "/")
203
+ for i, line in enumerate(lines, 1):
204
+ m = IMPERATIVE.search(line)
205
+ if not m:
206
+ continue
207
+ text = _sentence(line)
208
+ enf = _enforcer_for(rel, text) or _gate_for(whole, text)
209
+ rules.append(Rule(rel, i, m.group(1), text, enf))
210
+ return rules
211
+
212
+
213
+ def main(argv: list[str] | None = None) -> int:
214
+ ap = argparse.ArgumentParser(description="caddis enforcement inventory")
215
+ ap.add_argument("--list", action="store_true", help="print every rule, classified")
216
+ ap.add_argument("--advisory", action="store_true", help="print only the unenforced rules")
217
+ args = ap.parse_args(argv)
218
+
219
+ rules = collect()
220
+ machine = [r for r in rules if r.machine]
221
+ advisory = [r for r in rules if not r.machine]
222
+ total = len(rules)
223
+ ratio = (len(machine) / total) if total else 0.0
224
+
225
+ if args.list or args.advisory:
226
+ for r in rules:
227
+ if args.advisory and r.machine:
228
+ continue
229
+ tag = f"machine <- {r.enforcer}" if r.machine else "advisory"
230
+ print(f" [{tag}] {r.path}:{r.line} {r.marker}")
231
+ print(f" {r.text}")
232
+ print()
233
+
234
+ # Report the SPLIT, not just the headline. A single blended number is actively misleading:
235
+ # it is dominated by the ~235 skill rules, which are guidance ("prefer this", "consider that")
236
+ # and SHOULD be advisory - turning them into exit codes would be theatre. The number that
237
+ # matters is commands, because that is the execution path where a broken guarantee costs you.
238
+ cmd = [r for r in rules if "/commands/" in r.path]
239
+ skl = [r for r in rules if "/commands/" not in r.path]
240
+ def _pct(group):
241
+ return (sum(1 for r in group if r.machine) / len(group)) if group else 0.0
242
+
243
+ print("=" * 62)
244
+ print(f" load-bearing rules found : {total}")
245
+ print(f" machine-enforced : {len(machine)}")
246
+ print(f" advisory only : {len(advisory)}")
247
+ print("-" * 62)
248
+ print(f" COMMANDS (execution path): {sum(1 for r in cmd if r.machine)}/{len(cmd)} "
249
+ f"= {_pct(cmd):.1%} <- the figure that matters")
250
+ print(f" SKILLS (guidance) : {sum(1 for r in skl if r.machine)}/{len(skl)} "
251
+ f"= {_pct(skl):.1%} (advisory is correct here)")
252
+ print(f" blended : {ratio:.1%}")
253
+ print("=" * 62)
254
+ print(" A low number is not a bug report - it is the starting point. Advisory rules are")
255
+ print(" legitimate; the danger is an advisory rule that READS like a guarantee. Move the")
256
+ print(" load-bearing ones to exit codes, and label the rest honestly.")
257
+ return 0
258
+
259
+
260
+ if __name__ == "__main__":
261
+ raise SystemExit(main())