voidmachine 0.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -4
  3. package/bin/void-harness.mjs +12 -0
  4. package/bin/void-machine.mjs +12 -0
  5. package/core-assets/.claude-plugin/plugin.json +253 -0
  6. package/core-assets/.gitkeep +0 -0
  7. package/core-assets/PHILOSOPHY.md +218 -0
  8. package/core-assets/PROJECT-DOCTRINE.template.md +14 -0
  9. package/core-assets/adapters/security/manifest.yaml +96 -0
  10. package/core-assets/agents/.gitkeep +0 -0
  11. package/core-assets/agents/accessibility-specialist.md +59 -0
  12. package/core-assets/agents/api-integration-engineer.md +59 -0
  13. package/core-assets/agents/code-explorer.md +94 -0
  14. package/core-assets/agents/code-explorer.source +7 -0
  15. package/core-assets/agents/data-migration-engineer.md +59 -0
  16. package/core-assets/agents/devex-docs-engineer.md +57 -0
  17. package/core-assets/agents/doctrine-critic.md +91 -0
  18. package/core-assets/agents/doctrine-critic.source +11 -0
  19. package/core-assets/agents/domain-architect.md +56 -0
  20. package/core-assets/agents/experience-designer.md +59 -0
  21. package/core-assets/agents/frontend-engineer.md +60 -0
  22. package/core-assets/agents/independent-code-reviewer.md +61 -0
  23. package/core-assets/agents/migration-planner.md +97 -0
  24. package/core-assets/agents/migration-planner.source +7 -0
  25. package/core-assets/agents/observability-sre-engineer.md +59 -0
  26. package/core-assets/agents/pdf-specialist.md +58 -0
  27. package/core-assets/agents/performance-engineer.md +58 -0
  28. package/core-assets/agents/product-challenger.md +55 -0
  29. package/core-assets/agents/security-engineer.md +66 -0
  30. package/core-assets/agents/silent-failure-hunter.md +94 -0
  31. package/core-assets/agents/silent-failure-hunter.source +8 -0
  32. package/core-assets/agents/solution-architect.md +57 -0
  33. package/core-assets/agents/test-qa-engineer.md +60 -0
  34. package/core-assets/agents/type-design-analyzer.md +93 -0
  35. package/core-assets/agents/type-design-analyzer.source +8 -0
  36. package/core-assets/agents/visual-craft-director.md +62 -0
  37. package/core-assets/codex/hooks.json +157 -0
  38. package/core-assets/contract-history/visual-craft-director/v2.yaml +43 -0
  39. package/core-assets/data/catalog.v3.json +10479 -0
  40. package/core-assets/data/certification.json +1800 -0
  41. package/core-assets/data/identity.json +8 -0
  42. package/core-assets/data/model.json +4226 -0
  43. package/core-assets/enforce/ci-enforce.sh +183 -0
  44. package/core-assets/hooks/_checks.sh +145 -0
  45. package/core-assets/hooks/_hooklib.sh +163 -0
  46. package/core-assets/hooks/_syntax-worker.cjs +503 -0
  47. package/core-assets/hooks/_void-hook.mjs +5459 -0
  48. package/core-assets/hooks/activation-meter.sh +13 -0
  49. package/core-assets/hooks/anti-ai-slop-grep.sh +38 -0
  50. package/core-assets/hooks/auto-format.sh +9 -0
  51. package/core-assets/hooks/axe-precommit.sh +35 -0
  52. package/core-assets/hooks/block-dangerous-bash.sh +10 -0
  53. package/core-assets/hooks/block-protected-push.sh +81 -0
  54. package/core-assets/hooks/boundary-direction-check.sh +10 -0
  55. package/core-assets/hooks/control-character-grep.sh +10 -0
  56. package/core-assets/hooks/fowler-refactors.txt +71 -0
  57. package/core-assets/hooks/large-cl-grep.sh +9 -0
  58. package/core-assets/hooks/llm-cost-precommit.sh +55 -0
  59. package/core-assets/hooks/migration-lint.sh +58 -0
  60. package/core-assets/hooks/migration-pr-template.sh +55 -0
  61. package/core-assets/hooks/no-ai-design-slop.sh +10 -0
  62. package/core-assets/hooks/no-any-grep.sh +10 -0
  63. package/core-assets/hooks/no-as-cast-grep.sh +10 -0
  64. package/core-assets/hooks/no-console-log-grep.sh +10 -0
  65. package/core-assets/hooks/no-null-grep.sh +10 -0
  66. package/core-assets/hooks/no-only-no-skip.sh +10 -0
  67. package/core-assets/hooks/outcome-meter.sh +13 -0
  68. package/core-assets/hooks/pre-PR-review-evidence.sh +47 -0
  69. package/core-assets/hooks/protect-sensitive-files.sh +10 -0
  70. package/core-assets/hooks/refactor-named-grep.sh +49 -0
  71. package/core-assets/hooks/secret-in-content.sh +10 -0
  72. package/core-assets/hooks/sessionstart-context.sh +9 -0
  73. package/core-assets/hooks/stop-typecheck.sh +9 -0
  74. package/core-assets/hooks/tdd-guard.md +90 -0
  75. package/core-assets/hooks/tdd-guard.sh +10 -0
  76. package/core-assets/hooks/test-name-lint.sh +10 -0
  77. package/core-assets/hooks/tidying-commit-prefix.sh +37 -0
  78. package/core-assets/hooks/trim-large-output.sh +9 -0
  79. package/core-assets/hooks/tsc-noemit-precommit.sh +22 -0
  80. package/core-assets/hooks/ubiquitous-language-lint.sh +58 -0
  81. package/core-assets/hooks/viewport-screenshot-gate.sh +48 -0
  82. package/core-assets/modules/.gitkeep +0 -0
  83. package/core-assets/packs/pack-mobile/skills/void-eas-build-profile/.source +7 -0
  84. package/core-assets/packs/pack-mobile/skills/void-eas-build-profile/SKILL.md +164 -0
  85. package/core-assets/packs/pack-mobile/skills/void-eas-build-profile/harness.yaml +14 -0
  86. package/core-assets/packs/pack-mobile/skills/void-expo-config-plugins/.source +7 -0
  87. package/core-assets/packs/pack-mobile/skills/void-expo-config-plugins/SKILL.md +178 -0
  88. package/core-assets/packs/pack-mobile/skills/void-expo-config-plugins/harness.yaml +14 -0
  89. package/core-assets/packs/pack-mobile/skills/void-expo-router-pattern/.source +8 -0
  90. package/core-assets/packs/pack-mobile/skills/void-expo-router-pattern/SKILL.md +209 -0
  91. package/core-assets/packs/pack-mobile/skills/void-expo-router-pattern/harness.yaml +14 -0
  92. package/core-assets/packs/pack-mobile/skills/void-ota-update-strategy/.source +7 -0
  93. package/core-assets/packs/pack-mobile/skills/void-ota-update-strategy/SKILL.md +194 -0
  94. package/core-assets/packs/pack-mobile/skills/void-ota-update-strategy/harness.yaml +14 -0
  95. package/core-assets/packs/pack-monorepo/skills/void-dependency-direction/.source +5 -0
  96. package/core-assets/packs/pack-monorepo/skills/void-dependency-direction/SKILL.md +160 -0
  97. package/core-assets/packs/pack-monorepo/skills/void-dependency-direction/harness.yaml +14 -0
  98. package/core-assets/packs/pack-monorepo/skills/void-package-extraction/.source +5 -0
  99. package/core-assets/packs/pack-monorepo/skills/void-package-extraction/SKILL.md +70 -0
  100. package/core-assets/packs/pack-monorepo/skills/void-package-extraction/harness.yaml +14 -0
  101. package/core-assets/packs/pack-monorepo/skills/void-service-package/.source +5 -0
  102. package/core-assets/packs/pack-monorepo/skills/void-service-package/SKILL.md +122 -0
  103. package/core-assets/packs/pack-monorepo/skills/void-service-package/harness.yaml +14 -0
  104. package/core-assets/packs/pack-monorepo/skills/void-turbo-pipeline-tuning/.source +7 -0
  105. package/core-assets/packs/pack-monorepo/skills/void-turbo-pipeline-tuning/SKILL.md +116 -0
  106. package/core-assets/packs/pack-monorepo/skills/void-turbo-pipeline-tuning/harness.yaml +14 -0
  107. package/core-assets/packs/pack-nextjs/skills/void-cache-component-pattern/.source +5 -0
  108. package/core-assets/packs/pack-nextjs/skills/void-cache-component-pattern/SKILL.md +162 -0
  109. package/core-assets/packs/pack-nextjs/skills/void-cache-component-pattern/harness.yaml +14 -0
  110. package/core-assets/packs/pack-nextjs/skills/void-instrumentation-setup/.source +5 -0
  111. package/core-assets/packs/pack-nextjs/skills/void-instrumentation-setup/SKILL.md +161 -0
  112. package/core-assets/packs/pack-nextjs/skills/void-instrumentation-setup/harness.yaml +14 -0
  113. package/core-assets/packs/pack-nextjs/skills/void-loading-error-boundaries/.source +5 -0
  114. package/core-assets/packs/pack-nextjs/skills/void-loading-error-boundaries/SKILL.md +176 -0
  115. package/core-assets/packs/pack-nextjs/skills/void-loading-error-boundaries/harness.yaml +14 -0
  116. package/core-assets/packs/pack-nextjs/skills/void-parallel-routes-slots/.source +5 -0
  117. package/core-assets/packs/pack-nextjs/skills/void-parallel-routes-slots/SKILL.md +129 -0
  118. package/core-assets/packs/pack-nextjs/skills/void-parallel-routes-slots/harness.yaml +14 -0
  119. package/core-assets/packs/pack-nextjs/skills/void-route-group-decision/.source +5 -0
  120. package/core-assets/packs/pack-nextjs/skills/void-route-group-decision/SKILL.md +107 -0
  121. package/core-assets/packs/pack-nextjs/skills/void-route-group-decision/harness.yaml +14 -0
  122. package/core-assets/packs/pack-pwa/skills/void-install-prompt-ux/.source +5 -0
  123. package/core-assets/packs/pack-pwa/skills/void-install-prompt-ux/SKILL.md +153 -0
  124. package/core-assets/packs/pack-pwa/skills/void-install-prompt-ux/harness.yaml +14 -0
  125. package/core-assets/packs/pack-pwa/skills/void-manifest-checklist/.source +5 -0
  126. package/core-assets/packs/pack-pwa/skills/void-manifest-checklist/SKILL.md +204 -0
  127. package/core-assets/packs/pack-pwa/skills/void-manifest-checklist/harness.yaml +14 -0
  128. package/core-assets/packs/pack-pwa/skills/void-offline-first-mutation/.source +8 -0
  129. package/core-assets/packs/pack-pwa/skills/void-offline-first-mutation/SKILL.md +266 -0
  130. package/core-assets/packs/pack-pwa/skills/void-offline-first-mutation/harness.yaml +14 -0
  131. package/core-assets/packs/pack-pwa/skills/void-service-worker-strategy/.source +5 -0
  132. package/core-assets/packs/pack-pwa/skills/void-service-worker-strategy/SKILL.md +178 -0
  133. package/core-assets/packs/pack-pwa/skills/void-service-worker-strategy/harness.yaml +14 -0
  134. package/core-assets/packs/pack-react/skills/void-accessibility-check/.source +8 -0
  135. package/core-assets/packs/pack-react/skills/void-accessibility-check/SKILL.md +136 -0
  136. package/core-assets/packs/pack-react/skills/void-accessibility-check/harness.yaml +14 -0
  137. package/core-assets/packs/pack-react/skills/void-client-vs-server-component/.source +5 -0
  138. package/core-assets/packs/pack-react/skills/void-client-vs-server-component/SKILL.md +116 -0
  139. package/core-assets/packs/pack-react/skills/void-client-vs-server-component/harness.yaml +14 -0
  140. package/core-assets/packs/pack-react/skills/void-form-pattern/.source +7 -0
  141. package/core-assets/packs/pack-react/skills/void-form-pattern/SKILL.md +157 -0
  142. package/core-assets/packs/pack-react/skills/void-form-pattern/harness.yaml +14 -0
  143. package/core-assets/packs/pack-react/skills/void-state-architecture/.source +5 -0
  144. package/core-assets/packs/pack-react/skills/void-state-architecture/SKILL.md +160 -0
  145. package/core-assets/packs/pack-react/skills/void-state-architecture/harness.yaml +14 -0
  146. package/core-assets/packs/pack-server/skills/void-background-job-pattern/.source +6 -0
  147. package/core-assets/packs/pack-server/skills/void-background-job-pattern/SKILL.md +214 -0
  148. package/core-assets/packs/pack-server/skills/void-background-job-pattern/harness.yaml +14 -0
  149. package/core-assets/packs/pack-server/skills/void-drizzle-migration-safe/.source +6 -0
  150. package/core-assets/packs/pack-server/skills/void-drizzle-migration-safe/SKILL.md +168 -0
  151. package/core-assets/packs/pack-server/skills/void-drizzle-migration-safe/harness.yaml +14 -0
  152. package/core-assets/packs/pack-server/skills/void-env-validation/.source +6 -0
  153. package/core-assets/packs/pack-server/skills/void-env-validation/SKILL.md +131 -0
  154. package/core-assets/packs/pack-server/skills/void-env-validation/harness.yaml +14 -0
  155. package/core-assets/packs/pack-server/skills/void-rate-limit-strategy/.source +6 -0
  156. package/core-assets/packs/pack-server/skills/void-rate-limit-strategy/SKILL.md +149 -0
  157. package/core-assets/packs/pack-server/skills/void-rate-limit-strategy/harness.yaml +14 -0
  158. package/core-assets/packs/pack-server/skills/void-server-action/.source +7 -0
  159. package/core-assets/packs/pack-server/skills/void-server-action/SKILL.md +181 -0
  160. package/core-assets/packs/pack-server/skills/void-server-action/harness.yaml +14 -0
  161. package/core-assets/packs/pack-server/skills/void-testing-server-modules/.source +7 -0
  162. package/core-assets/packs/pack-server/skills/void-testing-server-modules/SKILL.md +73 -0
  163. package/core-assets/packs/pack-server/skills/void-testing-server-modules/harness.yaml +14 -0
  164. package/core-assets/packs/pack-server/skills/void-webhook-handler-pattern/.source +7 -0
  165. package/core-assets/packs/pack-server/skills/void-webhook-handler-pattern/SKILL.md +205 -0
  166. package/core-assets/packs/pack-server/skills/void-webhook-handler-pattern/harness.yaml +14 -0
  167. package/core-assets/policies/core.yaml +95 -0
  168. package/core-assets/policies/ui.yaml +23 -0
  169. package/core-assets/profiles/base.yaml +29 -0
  170. package/core-assets/profiles/expo-config.yaml +36 -0
  171. package/core-assets/profiles/expo.yaml +35 -0
  172. package/core-assets/profiles/monorepo.yaml +33 -0
  173. package/core-assets/profiles/nextjs-config.yaml +31 -0
  174. package/core-assets/profiles/nextjs.yaml +31 -0
  175. package/core-assets/profiles/node-server.yaml +31 -0
  176. package/core-assets/profiles/pwa.yaml +33 -0
  177. package/core-assets/profiles/react.yaml +39 -0
  178. package/core-assets/profiles/sql.yaml +39 -0
  179. package/core-assets/profiles/typescript.yaml +39 -0
  180. package/core-assets/skills/.gitkeep +0 -0
  181. package/core-assets/skills/void-accessibility/.source +15 -0
  182. package/core-assets/skills/void-accessibility/SKILL.md +275 -0
  183. package/core-assets/skills/void-accessibility/harness.yaml +17 -0
  184. package/core-assets/skills/void-api-and-interface-design/.source +13 -0
  185. package/core-assets/skills/void-api-and-interface-design/SKILL.md +231 -0
  186. package/core-assets/skills/void-api-and-interface-design/harness.yaml +16 -0
  187. package/core-assets/skills/void-async-safety/.source +13 -0
  188. package/core-assets/skills/void-async-safety/SKILL.md +287 -0
  189. package/core-assets/skills/void-async-safety/harness.yaml +16 -0
  190. package/core-assets/skills/void-audit/.source +15 -0
  191. package/core-assets/skills/void-audit/SKILL.md +83 -0
  192. package/core-assets/skills/void-audit/harness.yaml +14 -0
  193. package/core-assets/skills/void-autopilot/.source +21 -0
  194. package/core-assets/skills/void-autopilot/SKILL.md +358 -0
  195. package/core-assets/skills/void-autopilot/harness.yaml +14 -0
  196. package/core-assets/skills/void-brainstorm/.source +14 -0
  197. package/core-assets/skills/void-brainstorm/SKILL.md +271 -0
  198. package/core-assets/skills/void-brainstorm/harness.yaml +14 -0
  199. package/core-assets/skills/void-checkpoint/.source +39 -0
  200. package/core-assets/skills/void-checkpoint/SKILL.md +227 -0
  201. package/core-assets/skills/void-checkpoint/harness.yaml +14 -0
  202. package/core-assets/skills/void-claude-md/.source +15 -0
  203. package/core-assets/skills/void-claude-md/SKILL.md +180 -0
  204. package/core-assets/skills/void-claude-md/harness.yaml +14 -0
  205. package/core-assets/skills/void-code-review/.source +14 -0
  206. package/core-assets/skills/void-code-review/SKILL.md +232 -0
  207. package/core-assets/skills/void-code-review/harness.yaml +15 -0
  208. package/core-assets/skills/void-commit-discipline/.source +9 -0
  209. package/core-assets/skills/void-commit-discipline/SKILL.md +211 -0
  210. package/core-assets/skills/void-commit-discipline/harness.yaml +15 -0
  211. package/core-assets/skills/void-context/.source +15 -0
  212. package/core-assets/skills/void-context/SKILL.md +167 -0
  213. package/core-assets/skills/void-context/harness.yaml +14 -0
  214. package/core-assets/skills/void-debug/.source +11 -0
  215. package/core-assets/skills/void-debug/SKILL.md +211 -0
  216. package/core-assets/skills/void-debug/harness.yaml +14 -0
  217. package/core-assets/skills/void-decide/.source +9 -0
  218. package/core-assets/skills/void-decide/SKILL.md +147 -0
  219. package/core-assets/skills/void-decide/harness.yaml +14 -0
  220. package/core-assets/skills/void-devex-audit/.source +8 -0
  221. package/core-assets/skills/void-devex-audit/SKILL.md +113 -0
  222. package/core-assets/skills/void-devex-audit/harness.yaml +15 -0
  223. package/core-assets/skills/void-doctor/.source +15 -0
  224. package/core-assets/skills/void-doctor/SKILL.md +83 -0
  225. package/core-assets/skills/void-doctor/harness.yaml +14 -0
  226. package/core-assets/skills/void-domain-driven-design/.source +13 -0
  227. package/core-assets/skills/void-domain-driven-design/SKILL.md +292 -0
  228. package/core-assets/skills/void-domain-driven-design/harness.yaml +15 -0
  229. package/core-assets/skills/void-frontend-design/.source +19 -0
  230. package/core-assets/skills/void-frontend-design/SKILL.md +274 -0
  231. package/core-assets/skills/void-frontend-design/harness.yaml +17 -0
  232. package/core-assets/skills/void-functional/.source +14 -0
  233. package/core-assets/skills/void-functional/SKILL.md +312 -0
  234. package/core-assets/skills/void-functional/harness.yaml +15 -0
  235. package/core-assets/skills/void-graph/.source +15 -0
  236. package/core-assets/skills/void-graph/SKILL.md +80 -0
  237. package/core-assets/skills/void-graph/harness.yaml +14 -0
  238. package/core-assets/skills/void-hexagonal-architecture/.source +12 -0
  239. package/core-assets/skills/void-hexagonal-architecture/SKILL.md +276 -0
  240. package/core-assets/skills/void-hexagonal-architecture/harness.yaml +15 -0
  241. package/core-assets/skills/void-implement/.source +39 -0
  242. package/core-assets/skills/void-implement/SKILL.md +183 -0
  243. package/core-assets/skills/void-implement/harness.yaml +14 -0
  244. package/core-assets/skills/void-learn/.source +13 -0
  245. package/core-assets/skills/void-learn/SKILL.md +226 -0
  246. package/core-assets/skills/void-learn/harness.yaml +14 -0
  247. package/core-assets/skills/void-llm-cost-discipline/.source +13 -0
  248. package/core-assets/skills/void-llm-cost-discipline/SKILL.md +299 -0
  249. package/core-assets/skills/void-llm-cost-discipline/harness.yaml +15 -0
  250. package/core-assets/skills/void-make-pdf/.source +9 -0
  251. package/core-assets/skills/void-make-pdf/SKILL.md +52 -0
  252. package/core-assets/skills/void-make-pdf/harness.yaml +15 -0
  253. package/core-assets/skills/void-merge/.source +8 -0
  254. package/core-assets/skills/void-merge/SKILL.md +111 -0
  255. package/core-assets/skills/void-merge/harness.yaml +14 -0
  256. package/core-assets/skills/void-migrations/.source +13 -0
  257. package/core-assets/skills/void-migrations/SKILL.md +264 -0
  258. package/core-assets/skills/void-migrations/harness.yaml +17 -0
  259. package/core-assets/skills/void-observability/.source +12 -0
  260. package/core-assets/skills/void-observability/SKILL.md +245 -0
  261. package/core-assets/skills/void-observability/harness.yaml +15 -0
  262. package/core-assets/skills/void-plan/.source +15 -0
  263. package/core-assets/skills/void-plan/SKILL.md +239 -0
  264. package/core-assets/skills/void-plan/harness.yaml +14 -0
  265. package/core-assets/skills/void-plan-review/.source +12 -0
  266. package/core-assets/skills/void-plan-review/SKILL.md +128 -0
  267. package/core-assets/skills/void-plan-review/harness.yaml +15 -0
  268. package/core-assets/skills/void-qa/.source +15 -0
  269. package/core-assets/skills/void-qa/SKILL.md +103 -0
  270. package/core-assets/skills/void-qa/harness.yaml +15 -0
  271. package/core-assets/skills/void-refactor/.source +9 -0
  272. package/core-assets/skills/void-refactor/SKILL.md +220 -0
  273. package/core-assets/skills/void-refactor/harness.yaml +15 -0
  274. package/core-assets/skills/void-retrospective/.source +9 -0
  275. package/core-assets/skills/void-retrospective/SKILL.md +71 -0
  276. package/core-assets/skills/void-retrospective/harness.yaml +15 -0
  277. package/core-assets/skills/void-security-audit/.source +12 -0
  278. package/core-assets/skills/void-security-audit/SKILL.md +138 -0
  279. package/core-assets/skills/void-security-audit/harness.yaml +15 -0
  280. package/core-assets/skills/void-security-guidance/.source +12 -0
  281. package/core-assets/skills/void-security-guidance/SKILL.md +255 -0
  282. package/core-assets/skills/void-security-guidance/harness.yaml +15 -0
  283. package/core-assets/skills/void-source-driven-development/.source +12 -0
  284. package/core-assets/skills/void-source-driven-development/SKILL.md +158 -0
  285. package/core-assets/skills/void-source-driven-development/harness.yaml +15 -0
  286. package/core-assets/skills/void-tdd/.source +15 -0
  287. package/core-assets/skills/void-tdd/SKILL.md +394 -0
  288. package/core-assets/skills/void-tdd/harness.yaml +15 -0
  289. package/core-assets/skills/void-testing/.source +19 -0
  290. package/core-assets/skills/void-testing/SKILL.md +301 -0
  291. package/core-assets/skills/void-testing/harness.yaml +17 -0
  292. package/core-assets/skills/void-ticket/.source +18 -0
  293. package/core-assets/skills/void-ticket/SKILL.md +239 -0
  294. package/core-assets/skills/void-ticket/harness.yaml +14 -0
  295. package/core-assets/skills/void-typescript-strict/.source +10 -0
  296. package/core-assets/skills/void-typescript-strict/SKILL.md +294 -0
  297. package/core-assets/skills/void-typescript-strict/harness.yaml +17 -0
  298. package/core-assets/skills/void-ui-review/.source +12 -0
  299. package/core-assets/skills/void-ui-review/SKILL.md +97 -0
  300. package/core-assets/skills/void-ui-review/harness.yaml +15 -0
  301. package/core-assets/skills/void-verify/.source +12 -0
  302. package/core-assets/skills/void-verify/SKILL.md +184 -0
  303. package/core-assets/skills/void-verify/harness.yaml +15 -0
  304. package/core-assets/specialists/accessibility-specialist.source +11 -0
  305. package/core-assets/specialists/accessibility-specialist.yaml +30 -0
  306. package/core-assets/specialists/api-integration-engineer.source +11 -0
  307. package/core-assets/specialists/api-integration-engineer.yaml +30 -0
  308. package/core-assets/specialists/data-migration-engineer.source +11 -0
  309. package/core-assets/specialists/data-migration-engineer.yaml +30 -0
  310. package/core-assets/specialists/devex-docs-engineer.source +11 -0
  311. package/core-assets/specialists/devex-docs-engineer.yaml +29 -0
  312. package/core-assets/specialists/domain-architect.source +11 -0
  313. package/core-assets/specialists/domain-architect.yaml +28 -0
  314. package/core-assets/specialists/experience-designer.source +15 -0
  315. package/core-assets/specialists/experience-designer.yaml +38 -0
  316. package/core-assets/specialists/frontend-engineer.source +11 -0
  317. package/core-assets/specialists/frontend-engineer.yaml +32 -0
  318. package/core-assets/specialists/independent-code-reviewer.source +10 -0
  319. package/core-assets/specialists/independent-code-reviewer.yaml +37 -0
  320. package/core-assets/specialists/migrations.json +15 -0
  321. package/core-assets/specialists/observability-sre-engineer.source +11 -0
  322. package/core-assets/specialists/observability-sre-engineer.yaml +30 -0
  323. package/core-assets/specialists/pdf-specialist.source +11 -0
  324. package/core-assets/specialists/pdf-specialist.yaml +29 -0
  325. package/core-assets/specialists/performance-engineer.source +11 -0
  326. package/core-assets/specialists/performance-engineer.yaml +29 -0
  327. package/core-assets/specialists/product-challenger.source +10 -0
  328. package/core-assets/specialists/product-challenger.yaml +27 -0
  329. package/core-assets/specialists/security-engineer.source +13 -0
  330. package/core-assets/specialists/security-engineer.yaml +41 -0
  331. package/core-assets/specialists/solution-architect.source +13 -0
  332. package/core-assets/specialists/solution-architect.yaml +32 -0
  333. package/core-assets/specialists/test-qa-engineer.source +14 -0
  334. package/core-assets/specialists/test-qa-engineer.yaml +35 -0
  335. package/core-assets/specialists/visual-craft-director.source +17 -0
  336. package/core-assets/specialists/visual-craft-director.yaml +60 -0
  337. package/core-assets/templates/github/dependency-audit.mjs +54 -0
  338. package/core-assets/templates/github/void-dependency-audit.yml +35 -0
  339. package/core-assets/templates/github/void-security.yml +85 -0
  340. package/dist/main.js +57001 -0
  341. package/package.json +66 -4
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env bash
2
+ # ci-enforce — replay the void-machine enforcement floor over a PR diff.
3
+ #
4
+ # Critical path, secret-content, TDD and boundary rules execute through the SAME
5
+ # portable Node bundle as local PreToolUse hooks.
6
+ #
7
+ # FAIL-CLOSED (DEV-393, the #62-64 class): a missing prerequisite or an
8
+ # unresolvable base ref is an explicit RED check, never a silent green.
9
+ #
10
+ # Usage: ci-enforce.sh --base <ref> # ref = the PR base, e.g. origin/main
11
+ # Exit: 0 = clean, 1 = violations found OR fail-closed error.
12
+
13
+ set -uo pipefail
14
+ RUNNER="${BASH_SOURCE[0]%/*}/../hooks/_void-hook.mjs"
15
+
16
+ BASE=""
17
+ while [[ $# -gt 0 ]]; do
18
+ case "$1" in
19
+ --base) BASE="${2:-}"; shift 2 ;;
20
+ -h | --help) printf 'Usage: ci-enforce.sh --base <ref>\n'; exit 0 ;;
21
+ *) printf '::error::void-enforce: unknown argument %s\n' "$1" >&2; exit 1 ;;
22
+ esac
23
+ done
24
+
25
+ fail_closed() {
26
+ printf '::error::void-enforce: %s\n' "$1"
27
+ printf 'void-enforce (fail-closed): %s\n' "$1" >&2
28
+ exit 1
29
+ }
30
+
31
+ command -v git >/dev/null 2>&1 || fail_closed "git not found on runner (cannot compute the diff)"
32
+ NODE_BIN=$(command -v node 2>/dev/null) || fail_closed "Node.js not found on runner (cannot execute the shared floor)"
33
+ [[ -f "$RUNNER" ]] || fail_closed "shared Node enforcement bundle missing at '$RUNNER'"
34
+ [[ -n "$BASE" ]] || fail_closed "no base ref given (pass --base <ref>)"
35
+ git rev-parse --verify "${BASE}^{commit}" >/dev/null 2>&1 \
36
+ || fail_closed "base ref '${BASE}' is unresolvable (checkout with fetch-depth: 0 and fetch the base)"
37
+ # A three-dot diff needs a merge-base. A shallow clone or a rewritten/disjoint
38
+ # base HAS the commit object but shares NO common ancestor: `git diff base...HEAD`
39
+ # then exits 128 and prints nothing — indistinguishable from a clean diff, which
40
+ # would fail OPEN. Verify the merge-base up front so this fails CLOSED instead.
41
+ git merge-base "${BASE}" HEAD >/dev/null 2>&1 \
42
+ || fail_closed "no merge-base between '${BASE}' and HEAD (shallow clone or disjoint history — checkout with fetch-depth: 0)"
43
+
44
+ FAIL=0
45
+
46
+ # Committed, reviewable exemptions: `.github/void-enforce-allow` lists path globs
47
+ # (one per line, # comments) skipped entirely. This is the Action equivalent of
48
+ # the local VOID_MACHINE_ALLOW_SECRET_EDIT override — e.g. a file legitimately
49
+ # NAMED for secrets (the harness's own secret-in-content.sh) that sensitive-path
50
+ # would otherwise flag. A skip is LOGGED, never silent. Loaded once.
51
+ ALLOW_GLOBS=()
52
+ if [[ -f .github/void-enforce-allow ]]; then
53
+ while IFS= read -r line || [[ -n "$line" ]]; do
54
+ line="${line%%#*}" # strip trailing comment
55
+ read -r g <<<"$line" || true # trim surrounding whitespace, take the glob
56
+ [[ -n "${g:-}" ]] && ALLOW_GLOBS+=("$g")
57
+ done <.github/void-enforce-allow
58
+ fi
59
+ _allowlisted() {
60
+ local p="$1" g
61
+ for g in "${ALLOW_GLOBS[@]:-}"; do
62
+ [[ -n "$g" ]] || continue
63
+ # shellcheck disable=SC2053 -- intentional glob match, $g is a pattern
64
+ [[ "$p" == $g ]] && return 0
65
+ done
66
+ return 1
67
+ }
68
+
69
+ # Escape a workflow-command MESSAGE value: % -> %25, CR/LF -> %0D/%0A.
70
+ esc_msg() { printf '%s' "$1" | sed -e 's/%/%25/g' -e 's/\r/%0D/g' | awk 'NR>1{printf "%%0A"} {printf "%s",$0} END{print ""}'; }
71
+ # Escape a PROPERTY value (file/title): also , -> %2C and : -> %3A.
72
+ esc_prop() { printf '%s' "$1" | sed -e 's/%/%25/g' -e 's/:/%3A/g' -e 's/,/%2C/g'; }
73
+
74
+ annotate() { # <level> <file> <line> <message>
75
+ printf '::%s file=%s,line=%s::%s\n' "$1" "$(esc_prop "$2")" "$3" "$(esc_msg "$4")"
76
+ if [[ "$1" == error ]]; then FAIL=1; fi
77
+ }
78
+
79
+ # Added lines of <file> in base...HEAD as `<new-line-number>\t<text>`, one per
80
+ # added line, so a content match maps back to a real line for the annotation.
81
+ # Returns 2 (not empty output) if the per-file git diff itself fails, so the
82
+ # caller can fail CLOSED rather than mistake a git error for "no added lines".
83
+ added_lines() {
84
+ local out
85
+ out=$(git diff "${BASE}"...HEAD -- "$1") || return 2
86
+ printf '%s' "$out" | awk '
87
+ /^@@/ { if (match($0, /\+[0-9]+/)) n = substr($0, RSTART+1, RLENGTH-1) + 0; next }
88
+ /^\+\+\+/ { next }
89
+ /^\+/ { print n "\t" substr($0, 2); n++; next }
90
+ /^-/ { next }
91
+ /^ / { n++; next }
92
+ '
93
+ }
94
+
95
+ # Walk the changed files. Enumerate with -z (NUL-delimited) and
96
+ # core.quotepath=false so paths with non-ASCII bytes, spaces, tabs or newlines
97
+ # arrive RAW: under the default quotepath git octal-escapes and quotes such a
98
+ # path, and the escaped string then matches no file — silently skipping every
99
+ # content check (a leaked secret in `café.ts` would pass GREEN). Captured to a
100
+ # file (a bash variable cannot hold the NUL delimiters) with an exit check so a
101
+ # git error fails CLOSED, not silently clean.
102
+ NAMES=$(mktemp) || fail_closed "cannot create a temp file"
103
+ trap 'rm -f "$NAMES"' EXIT
104
+ git -c core.quotepath=false diff --name-status -z "${BASE}"...HEAD >"$NAMES" \
105
+ || fail_closed "git diff --name-status failed for base '${BASE}'"
106
+
107
+ # -z records: `status\0path\0`, or `R###\0old\0new\0` for renames/copies.
108
+ while IFS= read -r -d '' status; do
109
+ case "$status" in
110
+ R* | C*) IFS= read -r -d '' _src || break; IFS= read -r -d '' path || break ;;
111
+ D*) IFS= read -r -d '' _gone || break; continue ;; # a deletion cannot leak
112
+ *) IFS= read -r -d '' path || break ;;
113
+ esac
114
+ [[ -z "${path:-}" ]] && continue
115
+
116
+ if _allowlisted "$path"; then
117
+ printf 'void-enforce: %s skipped (allowlisted in .github/void-enforce-allow)\n' "$path"
118
+ continue
119
+ fi
120
+
121
+ # 1) Never-edit file (path only): lockfile / key / secret filename / .git.
122
+ if protected=$("$NODE_BIN" "$RUNNER" enforce-ci protected-file "$path" </dev/null 2>&1); then :; else
123
+ # A committed diff cannot prove whether the package manager generated it.
124
+ # Dependency refreshes may leave manifests unchanged; keep content scanning.
125
+ if [[ "$protected" == *": lockfile (regenerate via the package manager, do not hand-edit)" ]]; then
126
+ annotate warning "$path" 1 "lockfile diff requires dependency validation: frozen install, vulnerability audit, tests and review; generation is not verified by this check"
127
+ else
128
+ protected_reason="${protected##*: }"
129
+ annotate error "$path" 1 "protected file: ${protected_reason//$'\n'/ }"
130
+ continue
131
+ fi
132
+ fi
133
+
134
+ # TDD judges final source even when the diff only removes lines.
135
+ if tdd=$("$NODE_BIN" "$RUNNER" enforce-ci tdd-order "$path" </dev/null 2>&1); then
136
+ [[ -z "$tdd" ]] || printf 'void-enforce: %s\n' "${tdd//$'\n'/ }"
137
+ else
138
+ annotate error "$path" 1 "${tdd//$'\n'/ }"
139
+ fi
140
+
141
+ # 2+3) Content checks over the ADDED lines, with real line numbers.
142
+ diff_added=$(added_lines "$path") || fail_closed "git diff failed for '${path}' (cannot scan its content)"
143
+ [[ -z "$diff_added" ]] && continue
144
+ LNOS=()
145
+ while IFS= read -r line_number; do
146
+ LNOS+=("$line_number")
147
+ done < <(printf '%s\n' "$diff_added" | cut -f1)
148
+ added_text=$(printf '%s\n' "$diff_added" | cut -f2-)
149
+
150
+ if hits=$(printf '%s' "$added_text" | "$NODE_BIN" "$RUNNER" enforce-ci secret-content "$path" 2>&1); then :; else
151
+ FOUND=0
152
+ while IFS= read -r hit; do
153
+ [[ "$hit" =~ ^-\ .+:([0-9]+)$ ]] || continue
154
+ FOUND=1
155
+ rel="${BASH_REMATCH[1]}"
156
+ annotate error "$path" "${LNOS[rel - 1]:-1}" "leaked secret (see harness:security-guidance)"
157
+ done <<<"$hits"
158
+ [[ "$FOUND" -eq 1 ]] || annotate error "$path" 1 "secret scan failed closed: ${hits//$'\n'/ }"
159
+ fi
160
+
161
+ if hits=$(printf '%s' "$added_text" | "$NODE_BIN" "$RUNNER" enforce-ci boundary-direction "$path" 2>&1); then :; else
162
+ FOUND=0
163
+ while IFS= read -r hit; do
164
+ [[ "$hit" =~ ^-\ .+:([0-9]+)\ \-\>\ (.+)$ ]] || continue
165
+ FOUND=1
166
+ rel="${BASH_REMATCH[1]}"
167
+ annotate error "$path" "${LNOS[rel - 1]:-1}" "forbidden @repo/* import: ${BASH_REMATCH[2]}"
168
+ done <<<"$hits"
169
+ [[ "$FOUND" -eq 1 ]] || annotate error "$path" 1 "boundary scan failed closed: ${hits//$'\n'/ }"
170
+ fi
171
+ # NB: checks_dangerous_command (the local Bash runtime guard) is deliberately
172
+ # NOT replayed over the diff. A destructive PATTERN committed into a file is a
173
+ # weak signal that self-matches the harness's own detector, security docs, and
174
+ # test fixtures — net-negative false positives for a floor check. Deferred to a
175
+ # follow-up with a proper per-line allow tag (see docs/DECISIONS.md).
176
+ done <"$NAMES"
177
+
178
+ if [[ "$FAIL" -ne 0 ]]; then
179
+ printf 'void-enforce: floor violations found (see annotations above).\n' >&2
180
+ exit 1
181
+ fi
182
+ printf 'void-enforce: clean — no floor violations in the diff.\n'
183
+ exit 0
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env bash
2
+ # _checks.sh — pure detection functions for Void Machine enforcement floor.
3
+ # SOURCED, never executed (leading underscore = library; exempt from the hook
4
+ # size cap, still syntax-checked — see scripts/anti-bloat-check.sh).
5
+ #
6
+ # WHY this file exists (DEV-393): the floor (never-edit files, leaked secrets,
7
+ # @repo/* import direction, catastrophic shell) must be enforced identically by
8
+ # the PreToolUse hooks (Claude runtime, per-edit) AND the CI diff driver
9
+ # (enforce/ci-enforce.sh, per-PR). Extracting the detection here makes it ONE
10
+ # body of logic — the local hook and the server-side Action can never diverge.
11
+ #
12
+ # Contract: every function returns 1 and prints a payload on STDOUT when it
13
+ # detects a violation; returns 0 and prints nothing when the input is clean.
14
+ # Functions never `exit` (they are sourced) and never depend on the caller's
15
+ # stdin plumbing (_hooklib.sh) — path comes as $1, edited content on stdin.
16
+ # Content checks emit `<n>:<line>` (grep -n numbering over the scanned content)
17
+ # so a caller can map a hit back to a real file line for an annotation.
18
+
19
+ # checks_sensitive_path <path>: 1 + reason if the path is a never-edit file
20
+ # (env/secret/key/credential/lockfile/.git), else 0. Case-insensitive: on a
21
+ # case-insensitive FS .ENV and .env are the same file.
22
+ checks_sensitive_path() {
23
+ local path="$1" base lpath
24
+ [[ -z "$path" ]] && return 0
25
+ base=$(basename "$path" | tr '[:upper:]' '[:lower:]')
26
+ lpath=$(printf '%s' "$path" | tr '[:upper:]' '[:lower:]')
27
+ if [[ "$base" =~ ^\.env(\..+)?$ ]] && [[ ! "$base" =~ \.(example|sample|template|dist)$ ]]; then
28
+ printf 'environment file with secrets\n'; return 1; fi
29
+ if [[ "$base" =~ \.(pem|key|p12|pfx|keystore|jks|asc)$ || "$base" =~ ^id_(rsa|ed25519|ecdsa|dsa)$ ]]; then
30
+ printf 'private key / certificate\n'; return 1; fi
31
+ if [[ "$base" =~ (\.npmrc$|\.netrc$|\.pgpass$) ]]; then
32
+ printf 'credential file\n'; return 1; fi
33
+ # The loose secret|credential NAME match is a heuristic; exempt markdown docs.
34
+ # A real credential file is never .md, and content secret-scanning still covers
35
+ # .md, so this only removes false positives (e.g. a decision note whose slug
36
+ # contains "…-byo-credentials.md" is documentation, not a credential file).
37
+ if [[ ! "$base" =~ \.md$ ]] && [[ "$base" =~ (secret|credential) ]]; then
38
+ printf 'credential file\n'; return 1; fi
39
+ case "$base" in
40
+ package-lock.json|pnpm-lock.yaml|yarn.lock|bun.lock|bun.lockb|cargo.lock|poetry.lock|composer.lock)
41
+ printf 'lockfile (regenerate via the package manager, do not hand-edit)\n'; return 1 ;;
42
+ esac
43
+ [[ "$lpath" =~ (^|/)\.git/ ]] && { printf 'internal git metadata\n'; return 1; }
44
+ return 0
45
+ }
46
+
47
+ # checks_boundary_imports <path> (content on stdin): 1 + `<n>:<line>` for each
48
+ # forbidden @repo/* import from packages/<X>/ (X may only import @repo/core or
49
+ # itself). No-ops for apps/, packages/core/, test/generated files — those
50
+ # exemptions are check semantics, so they live here, not in each caller.
51
+ checks_boundary_imports() {
52
+ local file="$1" content pkg re_import violations="" line import target
53
+ content=$(cat)
54
+ [[ "$file" =~ ^packages/[^/]+/ ]] || return 0
55
+ [[ "$file" =~ ^packages/core/ ]] && return 0
56
+ [[ "$file" =~ \.(test|spec)\.(ts|tsx)$|\.d\.ts$|/__generated__/ ]] && return 0
57
+ # The @repo/* direction is a TypeScript-source rule. Docs (.md — e.g. skill
58
+ # examples that show a forbidden `import ... from '@repo/db'` marked ✗) carry
59
+ # such imports as illustrations, never as real edges — only .ts/.tsx count.
60
+ [[ "$file" =~ \.(ts|tsx)$ ]] || return 0
61
+ pkg=$(printf '%s' "$file" | sed -E 's|^packages/([^/]+)/.*|\1|')
62
+ re_import="from[[:space:]]+['\"]@repo/([a-zA-Z0-9-]+)"
63
+ while IFS= read -r line; do
64
+ import=$(printf '%s' "$line" | grep -oE '@repo/[a-zA-Z0-9-]+' | head -1)
65
+ target=$(printf '%s' "$import" | sed 's|@repo/||')
66
+ if [[ -n "$target" && "$target" != "core" && "$target" != "$pkg" ]]; then
67
+ violations="${violations}${line}"$'\n'
68
+ fi
69
+ done < <(printf '%s' "$content" | grep -nE "$re_import" | grep -vE '// *allow-boundary:' || true)
70
+ [[ -n "$violations" ]] && { printf '%s' "$violations"; return 1; }
71
+ return 0
72
+ }
73
+
74
+ # checks_secret_content <path> (content on stdin): 1 + `<n>:<line>` for each line
75
+ # leaking a HIGH-confidence secret. Test-fixture paths are exempt (fake secrets
76
+ # are legitimate there). POSIX ERE only (no grep -P, #64).
77
+ checks_secret_content() {
78
+ local file="$1" content re_hi re_assign n=0 line val hits=""
79
+ content=$(cat)
80
+ [[ "$file" =~ \.(test|spec)\.|/__tests__/|/__fixtures__/|/fixtures/|/__generated__/ ]] && return 0
81
+
82
+ re_hi='AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}' # AWS
83
+ re_hi="$re_hi"'|gh[posru]_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{40,}' # GitHub
84
+ re_hi="$re_hi"'|sk_live_[A-Za-z0-9]{20,}|rk_live_[A-Za-z0-9]{20,}' # Stripe
85
+ re_hi="$re_hi"'|sk-ant-[A-Za-z0-9_-]{40,}|sk-proj-[A-Za-z0-9_-]{40,}' # Anthropic/OpenAI
86
+ re_hi="$re_hi"'|sk-[A-Za-z0-9]{40,}|xox[baprs]-[A-Za-z0-9-]{10,}' # OpenAI/Slack
87
+ re_hi="$re_hi"'|AIza[0-9A-Za-z_-]{35}' # Google API
88
+ re_hi="$re_hi"'|-----BEGIN [A-Z ]*PRIVATE KEY-----' # PEM
89
+ re_assign='(_KEY|_SECRET|_TOKEN|_PASSWORD|_PASSWD|_APIKEY)["'\'' ]*[:=][ ]*["'\'']([A-Za-z0-9+/=_-]{24,})["'\'']'
90
+
91
+ while IFS= read -r line || [[ -n "$line" ]]; do
92
+ n=$((n + 1))
93
+ printf '%s' "$line" | grep -qE 'allow-secret-pattern:' && continue
94
+ if printf '%s' "$line" | grep -qE "$re_hi"; then hits="${hits}${n}:${line}"$'\n'; continue; fi
95
+ printf '%s' "$line" | grep -qiE "$re_assign" || continue
96
+ val=$(printf '%s' "$line" | sed -E "s/.*[:=][ ]*[\"']([A-Za-z0-9+/=_-]{24,})[\"'].*/\1/")
97
+ printf '%s' "$line" | grep -qiE 'process\.env|import\.meta\.env|xxx|changeme|example|redacted|your[-_]|<[a-z]|placeholder|todo' && continue
98
+ [[ "$val" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]] && continue
99
+ [[ "$val" =~ ^[0-9a-fA-F]+$ ]] && continue
100
+ printf '%s' "$val" | grep -qE '[A-Za-z]' && printf '%s' "$val" | grep -qE '[0-9]' || continue
101
+ hits="${hits}${n}:${line}"$'\n'
102
+ done < <(printf '%s' "$content")
103
+
104
+ [[ -n "$hits" ]] && { printf '%s' "$hits"; return 1; }
105
+ return 0
106
+ }
107
+
108
+ # checks_dangerous_command (command text on stdin): 1 + a short label if the
109
+ # text contains a catastrophic shell pattern (recursive root delete, fork bomb,
110
+ # raw-device write, root chmod/chown, force-push, unsafe git flags, DROP/
111
+ # TRUNCATE). Best-effort blocklist, not a complete boundary.
112
+ checks_dangerous_command() {
113
+ local cmd norm
114
+ cmd=$(cat)
115
+ [[ -z "$cmd" ]] && return 0
116
+ norm=$(printf '%s' "$cmd" | tr -d "\"'")
117
+ local HOME_ROOT='(/\*?|~/?\*?|\$HOME/?\*?|\$\{HOME\}/?\*?)'
118
+ local RM_TARGET='('"$HOME_ROOT"'|\./?\*?|\*)'
119
+
120
+ if printf '%s' "$norm" | grep -qE '(^|[;&|[:space:]])rm([[:space:]]+(-[a-zA-Z]+|--[a-z-]+))*[[:space:]]+(-[a-zA-Z]*[rR]|--recursive)' \
121
+ && printf '%s' "$norm" | grep -qE "(^|[;&|[:space:]])rm([[:space:]]+[a-zA-Z-]+)*[[:space:]]+(--[[:space:]]+)?${RM_TARGET}([[:space:]]|$)"; then
122
+ printf 'recursive delete of a root path\n'; return 1; fi
123
+
124
+ printf '%s' "$cmd" | grep -qE ':\(\)[[:space:]]*\{[[:space:]]*:[[:space:]]*\|[[:space:]]*:' \
125
+ && { printf 'fork bomb\n'; return 1; }
126
+
127
+ printf '%s' "$cmd" | grep -qE '\bmkfs(\.[a-z0-9]+)?\b|\bdd\b[^|]*\bof=/dev/|>[[:space:]]*/dev/(sd|nvme|hd|disk)' \
128
+ && { printf 'filesystem / raw-device write\n'; return 1; }
129
+
130
+ if printf '%s' "$norm" | grep -qE 'ch(mod|own)([[:space:]]+[a-zA-Z0-9-]+)*[[:space:]]+(-[a-zA-Z]*R|--recursive)' \
131
+ && printf '%s' "$norm" | grep -qE "[[:space:]](--[[:space:]]+)?${HOME_ROOT}([[:space:]]|$)"; then
132
+ printf 'recursive permission/ownership change on a root path\n'; return 1; fi
133
+
134
+ printf '%s' "$cmd" | grep -qE 'git[[:space:]]+push\b[^&|;]*[[:space:]](--force([^-]|$)|-f([[:space:]]|$))' \
135
+ && { printf 'git push --force (use --force-with-lease)\n'; return 1; }
136
+
137
+ if printf '%s' "$cmd" | grep -qE '\bgit\b([[:space:]]+-[^[:space:]]+)*[[:space:]]+(rebase|am|apply|cherry-pick)\b' \
138
+ && printf '%s' "$cmd" | grep -qE '(--exec([[:space:]=]|$)|--rebase-merges|--strategy-option|--unsafe-paths)'; then
139
+ printf 'git command-execution / unsafe-path flag\n'; return 1; fi
140
+
141
+ printf '%s' "$cmd" | grep -qiE '\b(drop[[:space:]]+(database|table|schema)|truncate[[:space:]]+table)\b' \
142
+ && { printf 'destructive SQL (DROP / TRUNCATE)\n'; return 1; }
143
+
144
+ return 0
145
+ }
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env bash
2
+ # _hooklib.sh — shared helpers for the PreToolUse Edit|Write enforcement hooks.
3
+ # SOURCED, never executed (leading underscore = library; excluded from the
4
+ # hook-size anti-bloat rule, see scripts/anti-bloat-check.sh). Goals (#63):
5
+ # - parse the Claude Code stdin JSON exactly once per hook;
6
+ # - never fail OPEN when jq is missing: scalar fields fall back to pure-bash
7
+ # extraction, and content-scanning hooks fail CLOSED with one explicit
8
+ # message rather than silently skipping enforcement (the audited bug: an
9
+ # unguarded jq call exited 127 under `set -e`, which Claude treats as a
10
+ # non-blocking error, so the whole hook layer went dark without jq);
11
+ # - share the physical-path root normalization introduced for #62.
12
+ # Every symbol is prefixed hooklib_ / _hooklib_.
13
+
14
+ # hooklib_read: consume stdin once. Sets HOOK_INPUT and HOOK_JQ (1 iff jq usable).
15
+ hooklib_read() {
16
+ HOOK_INPUT=$(cat)
17
+ if command -v jq >/dev/null 2>&1; then HOOK_JQ=1; else HOOK_JQ=0; fi
18
+ }
19
+
20
+ # hooklib_str <jq-filter> <bash-key>: extract a JSON scalar string from
21
+ # HOOK_INPUT. jq when available; else a pure-bash regex on the (unescaped)
22
+ # "<bash-key>": "<value>" pair — exact for the flat scalar fields we read.
23
+ hooklib_str() {
24
+ if [[ "${HOOK_JQ:-0}" == 1 ]]; then
25
+ printf '%s' "$HOOK_INPUT" | jq -r "$1 // empty" 2>/dev/null || true
26
+ else
27
+ local re="\"$2\"[[:space:]]*:[[:space:]]*\"([^\"]*)\""
28
+ [[ "$HOOK_INPUT" =~ $re ]] && printf '%s' "${BASH_REMATCH[1]}"
29
+ return 0
30
+ fi
31
+ }
32
+
33
+ hooklib_tool() { hooklib_str '.tool_name' 'tool_name'; }
34
+ hooklib_file() { hooklib_str '.tool_input.file_path' 'file_path'; }
35
+
36
+ # hooklib_content: the edited text (.content for Write, .new_string for Edit).
37
+ # jq gives the exact, unescaped text. Returns 1 (prints nothing) when jq is
38
+ # absent — a pure-bash JSON string-unescape is not reliable enough to scan for
39
+ # forbidden patterns, so the caller must gate on hooklib_require_jq first.
40
+ hooklib_content() {
41
+ [[ "${HOOK_JQ:-0}" == 1 ]] || return 1
42
+ printf '%s' "$HOOK_INPUT" | jq -r '.tool_input.content // .tool_input.new_string // empty' 2>/dev/null || true
43
+ }
44
+
45
+ # hooklib_command: shell command (.command string, or Codex argv array joined).
46
+ hooklib_command() {
47
+ if [[ "${HOOK_JQ:-0}" == 1 ]]; then
48
+ printf '%s' "$HOOK_INPUT" \
49
+ | jq -r 'if (.tool_input.command? | type) == "array" then (.tool_input.command | join(" ")) else (.tool_input.command // empty) end' 2>/dev/null || true
50
+ else
51
+ hooklib_str '.tool_input.command' 'command'
52
+ fi
53
+ }
54
+
55
+ # Framing for the hooklib_edits stream (ASCII RS/US). Edited content routinely
56
+ # contains newlines and tabs, so neither can frame the records.
57
+ _HOOKLIB_RS=$'\036'
58
+ _HOOKLIB_US=$'\037'
59
+
60
+ # _hooklib_patch_text: the raw apply_patch envelope, whichever field a given
61
+ # Codex build carries it in. Same candidate list as the floor's
62
+ # protect-sensitive-files, so the two can never disagree on where a patch lives.
63
+ _hooklib_patch_text() {
64
+ printf '%s' "$HOOK_INPUT" \
65
+ | jq -r '[.tool_input.input, .tool_input.patch, .tool_input.content, .tool_input.command]
66
+ | map(select(. != null))
67
+ | map(if type == "array" then join("\n") else . end)
68
+ | .[]' 2>/dev/null || true
69
+ }
70
+
71
+ # _hooklib_parse_patch: stdin = an apply_patch envelope; stdout = one
72
+ # <path>US<added-content>RS record per Add/Update section. Only ADDED (`+`)
73
+ # lines are collected, so a REMOVED or untouched context line can never trip a
74
+ # forbidden-pattern scan — the Codex equivalent of Claude's `.new_string`. A
75
+ # Delete section yields no record: there is nothing left to scan.
76
+ _hooklib_parse_patch() {
77
+ awk -v USEP="$_HOOKLIB_US" -v RSEP="$_HOOKLIB_RS" '
78
+ function emit() { if (path != "") printf "%s%s%s%s", path, USEP, buf, RSEP }
79
+ /^\*\*\* (Add|Update) File: / { emit(); path = substr($0, index($0, "File: ") + 6); buf = ""; next }
80
+ /^\*\*\* Delete File: / { emit(); path = ""; buf = ""; next }
81
+ /^\*\*\* (Begin|End) Patch/ { next }
82
+ { if (path != "" && substr($0, 1, 1) == "+" && substr($0, 1, 3) != "+++") buf = buf substr($0, 2) "\n" }
83
+ END { emit() }
84
+ '
85
+ }
86
+
87
+ # hooklib_edits: the edits of THIS tool call as a NORMALIZED, runtime-agnostic
88
+ # stream of <path>US<new-content>RS records — one per edited file. It exists so a
89
+ # content-scanning hook iterates instead of assuming Claude's single-file shape:
90
+ # - Claude: Edit|Write -> one record (.tool_input.file_path + .content/.new_string)
91
+ # - Codex: apply_patch -> one record per patched file, carrying its added lines
92
+ # Wiring those hooks for Codex WITHOUT this would fire them against an empty
93
+ # payload: a wired-but-dead hook, which is a silent enforcement hole rather than
94
+ # an honest absence. Requires jq (exact content extraction) — callers gate on
95
+ # hooklib_require_jq first. Read it with:
96
+ # while IFS= read -r -d "$_HOOKLIB_RS" rec; do
97
+ # file="${rec%%"$_HOOKLIB_US"*}"; new="${rec#*"$_HOOKLIB_US"}"
98
+ # done < <(hooklib_edits)
99
+ hooklib_edits() {
100
+ # Without jq: degrade to the pure-bash file_path, emitting one CONTENTLESS
101
+ # record. That keeps the PATH-ONLY hooks (tdd-guard, auto-format) enforcing on
102
+ # the Claude shape exactly as they did before this stream existed. It does not
103
+ # weaken the content-scanning hooks: those call hooklib_require_jq BEFORE the
104
+ # loop and have already exited 2 by the time they would read a record (#63).
105
+ # A Codex patch simply cannot be parsed without jq, so it yields nothing.
106
+ if [[ "${HOOK_JQ:-0}" != 1 ]]; then
107
+ local bash_file
108
+ bash_file=$(hooklib_file)
109
+ [[ -n "$bash_file" ]] && printf '%s%s%s' "$bash_file" "$_HOOKLIB_US" "$_HOOKLIB_RS"
110
+ return 0
111
+ fi
112
+ # A file_path means the single-file runtime shape. The patch parse is then
113
+ # deliberately skipped: file CONTENT that happens to document the apply_patch
114
+ # format (this repo's own docs do) would otherwise forge phantom records.
115
+ if [[ -n "$(hooklib_file)" ]]; then
116
+ printf '%s' "$HOOK_INPUT" \
117
+ | jq -j --arg us "$_HOOKLIB_US" --arg rs "$_HOOKLIB_RS" \
118
+ '.tool_input.file_path + $us + ((.tool_input.content // .tool_input.new_string) // "") + $rs' \
119
+ 2>/dev/null || true
120
+ return 0
121
+ fi
122
+ _hooklib_patch_text | _hooklib_parse_patch
123
+ return 0
124
+ }
125
+
126
+ # hooklib_require_jq [name]: a content-scanning hook cannot verify an edit
127
+ # without jq. Rather than fail OPEN (the #63 bug), block once with an explicit,
128
+ # actionable message. jq is a documented prerequisite (void-machine doctor).
129
+ hooklib_require_jq() {
130
+ [[ "${HOOK_JQ:-0}" == 1 ]] && return 0
131
+ printf '%s: jq is required to inspect edit content but is not installed.\n' "${1:-hook}" >&2
132
+ printf 'Blocking rather than silently skipping enforcement. Install jq\n' >&2
133
+ printf '(e.g. brew install jq) or run: void-machine doctor.\n' >&2
134
+ exit 2
135
+ }
136
+
137
+ # hooklib_root: the project root in physical form (CLAUDE_PROJECT_DIR, else the
138
+ # git toplevel, else pwd). Trailing slash stripped.
139
+ hooklib_root() {
140
+ local r
141
+ r=$(_hooklib_phys "${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}")
142
+ printf '%s' "${r%/}"
143
+ }
144
+
145
+ # hooklib_relpath <path>: strip the project-root prefix so root-anchored globs
146
+ # match the ABSOLUTE paths Claude Code passes (#62). Physical-path compare
147
+ # (logical/physical roots differ under symlinks, e.g. macOS /var -> /private/var).
148
+ hooklib_relpath() {
149
+ local file="$1" root
150
+ [[ "$file" == /* ]] || { printf '%s' "$file"; return 0; }
151
+ root=$(hooklib_root)
152
+ file=$(_hooklib_phys "$file")
153
+ case "$file" in "$root"/*) file="${file#"$root"/}" ;; esac
154
+ printf '%s' "$file"
155
+ }
156
+
157
+ # _hooklib_phys: resolve the longest existing directory prefix to its physical
158
+ # path, then re-append the trailing (possibly non-existent) components.
159
+ _hooklib_phys() {
160
+ local p="$1" t=""
161
+ while [[ ! -d "$p" && "$p" == */* ]]; do t="/${p##*/}$t"; p="${p%/*}"; done
162
+ if [[ -d "$p" ]]; then printf '%s%s' "$(cd "$p" && pwd -P)" "$t"; else printf '%s' "$1"; fi
163
+ }