@etus/bhono-app 0.1.5 → 0.1.7

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 (300) hide show
  1. package/dist/index.js +0 -0
  2. package/package.json +7 -2
  3. package/templates/base/.claude/commands/check-skill-rules.md +112 -29
  4. package/templates/base/.claude/commands/linear/implement-issue.md +383 -55
  5. package/templates/base/.claude/commands/ship.md +77 -13
  6. package/templates/base/.claude/hooks/package-lock.json +0 -419
  7. package/templates/base/.claude/hooks/skill-activation-prompt.ts +185 -113
  8. package/templates/base/.claude/hooks/skill-tool-guard.sh +6 -0
  9. package/templates/base/.claude/hooks/skill-tool-guard.ts +198 -0
  10. package/templates/base/.claude/scripts/validate-skill-rules.sh +55 -32
  11. package/templates/base/.claude/settings.json +18 -11
  12. package/templates/base/.claude/skills/skill-rules.json +326 -173
  13. package/templates/base/.env.example +3 -0
  14. package/templates/base/CLAUDE.md +5 -5
  15. package/templates/base/README.md +40 -27
  16. package/templates/base/config/eslint.config.js +1 -0
  17. package/templates/base/config/wrangler.json +16 -17
  18. package/templates/base/docs/SETUP-GUIDE.md +566 -0
  19. package/templates/base/docs/app_spec.txt +13 -10
  20. package/templates/base/docs/architecture/README.md +162 -5
  21. package/templates/base/docs/architecture/api-catalog.md +575 -0
  22. package/templates/base/docs/architecture/c4-component.md +309 -0
  23. package/templates/base/docs/architecture/c4-container.md +183 -0
  24. package/templates/base/docs/architecture/c4-context.md +106 -0
  25. package/templates/base/docs/architecture/data-requirements.md +4 -3
  26. package/templates/base/docs/architecture/db-bootstrap.md +39 -0
  27. package/templates/base/docs/architecture/dependencies.md +327 -0
  28. package/templates/base/docs/architecture/drizzle-migration-plan.md +125 -0
  29. package/templates/base/docs/architecture/erd.md +1 -1
  30. package/templates/base/docs/architecture/sql-standards.md +100 -0
  31. package/templates/base/docs/architecture/tech-debt.md +184 -0
  32. package/templates/base/docs/testing.md +36 -29
  33. package/templates/base/package.json +26 -20
  34. package/templates/base/schema.sql +84 -0
  35. package/templates/base/scripts/capture-prod-session.ts +2 -2
  36. package/templates/base/scripts/init.sh +244 -59
  37. package/templates/base/scripts/sync-template.sh +104 -0
  38. package/templates/base/src/client/hooks/use-auth.ts +5 -0
  39. package/templates/base/src/client/routes/_authenticated/dashboard.tsx +1 -1
  40. package/templates/base/src/client/routes/index.tsx +1 -1
  41. package/templates/base/src/server/db/client.ts +3 -5
  42. package/templates/base/src/server/db/records.ts +81 -0
  43. package/templates/base/src/server/db/seed.ts +3 -2
  44. package/templates/base/src/server/db/sql.ts +116 -0
  45. package/templates/base/src/server/index.ts +17 -2
  46. package/templates/base/src/server/lib/audit.ts +74 -26
  47. package/templates/base/src/server/lib/audited-db.ts +219 -109
  48. package/templates/base/src/server/lib/transaction.ts +10 -16
  49. package/templates/base/src/server/middleware/account.ts +9 -16
  50. package/templates/base/src/server/middleware/auth.ts +102 -38
  51. package/templates/base/src/server/middleware/rate-limit.ts +8 -6
  52. package/templates/base/src/server/routes/accounts/handlers.ts +18 -6
  53. package/templates/base/src/server/routes/audits/handlers.ts +3 -1
  54. package/templates/base/src/server/routes/auth/handlers.ts +15 -10
  55. package/templates/base/src/server/routes/auth/test-login.ts +99 -45
  56. package/templates/base/src/server/routes/health/handlers.ts +4 -4
  57. package/templates/base/src/server/routes/index.ts +9 -0
  58. package/templates/base/src/server/routes/invitations/handlers.ts +9 -6
  59. package/templates/base/src/server/routes/openapi.ts +1 -1
  60. package/templates/base/src/server/routes/users/handlers.ts +21 -14
  61. package/templates/base/src/server/services/accounts.ts +242 -217
  62. package/templates/base/src/server/services/audits.ts +114 -61
  63. package/templates/base/src/server/services/auth.ts +310 -180
  64. package/templates/base/src/server/services/invitations.ts +282 -222
  65. package/templates/base/src/server/services/users.ts +383 -293
  66. package/templates/base/src/server/types/index.ts +1 -2
  67. package/templates/base/src/shared/types/api.ts +66 -198
  68. package/templates/base/tests/e2e/auth.setup.ts +1 -1
  69. package/templates/base/{src/server/__tests__/fixtures.ts → tests/fixtures/server.ts} +3 -3
  70. package/templates/base/{src/client/__tests__/setup-browser.ts → tests/helpers/client-setup-browser.ts} +2 -2
  71. package/templates/base/{src/client/__tests__/setup.ts → tests/helpers/client-setup.ts} +1 -1
  72. package/templates/base/{src/client/__tests__/test-utils.tsx → tests/helpers/client-test-utils.tsx} +2 -2
  73. package/templates/base/{src/server/__tests__/setup.ts → tests/helpers/server.ts} +9 -9
  74. package/templates/base/tests/integration/accounts/crud.test.ts +2 -11
  75. package/templates/base/tests/integration/audits/list.test.ts +2 -11
  76. package/templates/base/tests/integration/auth/auth-service.test.ts +1 -10
  77. package/templates/base/tests/integration/auth/invitation-token.test.ts +2 -11
  78. package/templates/base/tests/integration/auth/logout.test.ts +2 -11
  79. package/templates/base/tests/integration/auth/oauth.test.ts +23 -42
  80. package/templates/base/tests/integration/auth/refresh-token.test.ts +1 -9
  81. package/templates/base/tests/integration/auth/session-expiry.test.ts +1 -9
  82. package/templates/base/tests/integration/auth/session.test.ts +2 -11
  83. package/templates/base/tests/integration/auth/super-admin.test.ts +1 -9
  84. package/templates/base/tests/integration/authorization/analytics-role.test.ts +2 -11
  85. package/templates/base/tests/integration/authorization/billing-role.test.ts +2 -11
  86. package/templates/base/tests/integration/authorization/guards-roles.test.ts +1 -9
  87. package/templates/base/tests/integration/authorization/multi-tenancy.test.ts +2 -11
  88. package/templates/base/tests/integration/authorization/roles.test.ts +2 -11
  89. package/templates/base/tests/integration/config/production-behavior.test.ts +2 -11
  90. package/templates/base/tests/integration/health/health.test.ts +25 -44
  91. package/templates/base/tests/integration/invitations/crud.test.ts +2 -11
  92. package/templates/base/tests/integration/invitations/email.test.ts +1 -9
  93. package/templates/base/tests/integration/middleware/auth.test.ts +3 -12
  94. package/templates/base/tests/integration/middleware/request-logger.test.ts +1 -9
  95. package/templates/base/tests/integration/performance/response-times.test.ts +1 -9
  96. package/templates/base/tests/integration/security/cookie-security.test.ts +2 -11
  97. package/templates/base/tests/integration/security/csrf-protection.test.ts +2 -11
  98. package/templates/base/tests/integration/security/log-sanitization.test.ts +1 -9
  99. package/templates/base/tests/integration/security/rate-limiting.test.ts +1 -9
  100. package/templates/base/tests/integration/security/sql-injection.test.ts +7 -18
  101. package/templates/base/tests/integration/security/xss-prevention.test.ts +2 -11
  102. package/templates/base/tests/integration/setup.ts +13 -90
  103. package/templates/base/tests/integration/smoke.test.ts +3 -2
  104. package/templates/base/tests/integration/storage/upload.test.ts +2 -11
  105. package/templates/base/tests/integration/storage/validation.test.ts +2 -11
  106. package/templates/base/tests/integration/users/crud.test.ts +2 -11
  107. package/templates/base/tests/integration/users/list.test.ts +2 -11
  108. package/templates/base/tests/integration/vitest.config.ts +2 -9
  109. package/templates/base/{src/server/__tests__ → tests}/mocks/db.ts +1 -1
  110. package/templates/base/{src/server/__tests__ → tests}/mocks/index.ts +1 -1
  111. package/templates/base/{src/server/__tests__ → tests}/mocks/kv.ts +1 -1
  112. package/templates/base/{src/server/__tests__ → tests}/mocks/r2.ts +1 -1
  113. package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/sidebar.test.tsx +1 -1
  114. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/avatar.test.tsx +1 -1
  115. package/templates/base/{src/client/__tests__ → tests/unit/client/components/ui}/button.test.tsx +1 -1
  116. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/card.test.tsx +1 -1
  117. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/dialog.test.tsx +1 -1
  118. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/input.test.tsx +1 -1
  119. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/loading-skeleton.test.tsx +1 -1
  120. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/skeleton.test.tsx +1 -1
  121. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/sonner.test.tsx +1 -1
  122. package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/tabs.test.tsx +1 -1
  123. package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/account.test.tsx +1 -1
  124. package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/integrations.test.tsx +1 -1
  125. package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/settings.test.tsx +1 -1
  126. package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/team.test.tsx +1 -1
  127. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/authenticated-layout.test.tsx +1 -1
  128. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/dashboard.test.tsx +1 -1
  129. package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/invite.test.tsx +1 -1
  130. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/login.test.tsx +1 -1
  131. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/navigation.test.tsx +1 -1
  132. package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/root-layout.test.tsx +1 -1
  133. package/templates/base/{src/server/auth/__tests__ → tests/unit/server/auth}/guards.test.ts +2 -2
  134. package/templates/base/{src → tests/unit}/server/auth/permissions.test.ts +1 -1
  135. package/templates/base/{src → tests/unit}/server/auth/roles.test.ts +1 -1
  136. package/templates/base/tests/unit/server/db/sql.test.ts +68 -0
  137. package/templates/base/{src → tests/unit}/server/env.test.ts +1 -1
  138. package/templates/base/tests/unit/server/lib/audited-db.test.ts +78 -0
  139. package/templates/base/{src → tests/unit}/server/lib/email.test.ts +1 -1
  140. package/templates/base/{src → tests/unit}/server/lib/errors.test.ts +1 -1
  141. package/templates/base/{src → tests/unit}/server/lib/oauth.test.ts +1 -1
  142. package/templates/base/{src → tests/unit}/server/lib/pagination.test.ts +1 -1
  143. package/templates/base/{src → tests/unit}/server/lib/password.test.ts +1 -1
  144. package/templates/base/{src → tests/unit}/server/lib/providers.test.ts +1 -1
  145. package/templates/base/{src → tests/unit}/server/lib/r2-storage.test.ts +2 -2
  146. package/templates/base/{src → tests/unit}/server/lib/session.test.ts +2 -2
  147. package/templates/base/{src → tests/unit}/server/lib/tokens.test.ts +1 -1
  148. package/templates/base/{src → tests/unit}/server/lib/transaction.test.ts +5 -14
  149. package/templates/base/{src → tests/unit}/server/middleware/account.test.ts +16 -24
  150. package/templates/base/tests/unit/server/middleware/auth.test.ts +647 -0
  151. package/templates/base/{src → tests/unit}/server/middleware/cors.test.ts +1 -1
  152. package/templates/base/{src → tests/unit}/server/middleware/error-handler.test.ts +2 -2
  153. package/templates/base/{src → tests/unit}/server/middleware/rate-limit.test.ts +3 -2
  154. package/templates/base/{src → tests/unit}/server/middleware/request-context.test.ts +1 -1
  155. package/templates/base/{src → tests/unit}/server/middleware/request-logger.test.ts +1 -1
  156. package/templates/base/{src/server/__tests__/mocks/__tests__ → tests/unit/server/mocks}/db.test.ts +1 -1
  157. package/templates/base/{src/server/__tests__/mocks/__tests__ → tests/unit/server/mocks}/kv.test.ts +1 -1
  158. package/templates/base/{src/server/__tests__/mocks/__tests__ → tests/unit/server/mocks}/r2.test.ts +1 -1
  159. package/templates/base/{src/server/routes/accounts/__tests__ → tests/unit/server/routes/accounts}/handlers.test.ts +12 -12
  160. package/templates/base/{src/server/routes/audits/__tests__ → tests/unit/server/routes/audits}/handlers.test.ts +11 -11
  161. package/templates/base/{src/server/routes/auth/__tests__ → tests/unit/server/routes/auth}/handlers.test.ts +124 -13
  162. package/templates/base/{src/server/routes/health/__tests__ → tests/unit/server/routes/health}/handlers.test.ts +27 -23
  163. package/templates/base/{src/server/routes/invitations/__tests__ → tests/unit/server/routes/invitations}/handlers.test.ts +14 -17
  164. package/templates/base/{src/server/routes/storage/__tests__ → tests/unit/server/routes/storage}/handlers.test.ts +6 -6
  165. package/templates/base/{src/server/routes/users/__tests__ → tests/unit/server/routes/users}/handlers.test.ts +81 -17
  166. package/templates/base/tests/unit/server/services/accounts.test.ts +406 -0
  167. package/templates/base/tests/unit/server/services/audits.test.ts +360 -0
  168. package/templates/base/tests/unit/server/services/auth.test.ts +656 -0
  169. package/templates/base/tests/unit/server/services/invitations.test.ts +343 -0
  170. package/templates/base/tests/unit/server/services/users.test.ts +706 -0
  171. package/templates/base/{src/shared/schemas/__tests__ → tests/unit/shared}/schemas.test.ts +1 -1
  172. package/templates/base/tsconfig.json +2 -1
  173. package/templates/base/vite.config.ts +3 -1
  174. package/templates/base/vitest.config.browser.ts +3 -2
  175. package/templates/base/vitest.config.frontend.ts +3 -2
  176. package/templates/base/vitest.config.ts +7 -14
  177. package/templates/base/.claude/settings.local.json +0 -11
  178. package/templates/base/.github/workflows/test.yml +0 -127
  179. package/templates/base/auth-setup-error.png +0 -0
  180. package/templates/base/config/drizzle.config.ts +0 -10
  181. package/templates/base/pnpm-lock.yaml +0 -8175
  182. package/templates/base/src/server/db/schema/accounts.ts +0 -20
  183. package/templates/base/src/server/db/schema/audit-logs.ts +0 -26
  184. package/templates/base/src/server/db/schema/index.ts +0 -7
  185. package/templates/base/src/server/db/schema/invitations.ts +0 -30
  186. package/templates/base/src/server/db/schema/refresh-tokens.ts +0 -22
  187. package/templates/base/src/server/db/schema/user-accounts.ts +0 -25
  188. package/templates/base/src/server/db/schema/users.ts +0 -33
  189. package/templates/base/src/server/lib/audited-db.test.ts +0 -107
  190. package/templates/base/src/server/lib/schema-helpers.ts +0 -16
  191. package/templates/base/src/server/middleware/auth.test.ts +0 -345
  192. package/templates/base/src/server/services/__tests__/accounts.test.ts +0 -764
  193. package/templates/base/src/server/services/__tests__/audits.test.ts +0 -235
  194. package/templates/base/src/server/services/__tests__/auth.test.ts +0 -765
  195. package/templates/base/src/server/services/__tests__/invitations.test.ts +0 -704
  196. package/templates/base/src/server/services/__tests__/users.test.ts +0 -755
  197. package/templates/base/tests/e2e/_auth/.gitkeep +0 -0
  198. package/templates/base/tests/integration/lib/schema-helpers.test.ts +0 -129
  199. package/templates/base/tsconfig.tsbuildinfo +0 -1
  200. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-can-be-collapsed-by-default-1.png +0 -0
  201. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-expands-when-collapsed-and-expand-button-is-clicked-1.png +0 -0
  202. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-handles-logout-button-click-1.png +0 -0
  203. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-handles-navigation-clicks-1.png +0 -0
  204. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-hides-navigation-labels-when-collapsed-1.png +0 -0
  205. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-highlights-active-route-1.png +0 -0
  206. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-renders-sidebar-navigation-items-1.png +0 -0
  207. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-shows-keyboard-shortcut-hint-when-expanded-1.png +0 -0
  208. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-info-when-authenticated-1.png +0 -0
  209. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-initials-in-avatar-fallback-1.png +0 -0
  210. /package/templates/base/{src/client/components/__tests__ → tests/unit/client/components}/error-boundary.test.tsx +0 -0
  211. /package/templates/base/{src/client/components/ui/__tests__ → tests/unit/client/components/ui}/error-fallback.test.tsx +0 -0
  212. /package/templates/base/{src/client/hooks/__tests__ → tests/unit/client/hooks}/use-auth.test.tsx +0 -0
  213. /package/templates/base/{src/client/hooks/__tests__ → tests/unit/client/hooks}/use-theme.test.tsx +0 -0
  214. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-dashboard-stats-cards-1.png +0 -0
  215. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-quick-action-cards-1.png +0 -0
  216. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-recent-activity-section-1.png +0 -0
  217. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-first-name-in-welcome-message-1.png +0 -0
  218. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-information-in-sidebar-1.png +0 -0
  219. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-render-dashboard-when-authenticated-1.png +0 -0
  220. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-show-navigation-sidebar-1.png +0 -0
  221. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-unauthenticated-should-redirect-to-login-when-not-authenticated-1.png +0 -0
  222. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-accept-invitation-button-1.png +0 -0
  223. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-decline-button-linking-to-homepage-1.png +0 -0
  224. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-invitation-details--email--workspace--role--1.png +0 -0
  225. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-have-a-logo-link-to-homepage-1.png +0 -0
  226. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-render-invitation-page-with-inviter-name-and-workspace-1.png +0 -0
  227. /package/templates/base/{src/client/routes/__tests__ → tests/unit/client/routes}/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-show-terms-of-service-and-privacy-policy-links-1.png +0 -0
  228. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-display-Google-OAuth-login-button-1.png +0 -0
  229. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-have-a-link-back-to-home-page-1.png +0 -0
  230. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-content-without-waiting-for-authentication-1.png +0 -0
  231. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-page-at--login-route-1.png +0 -0
  232. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-show-Terms-of-Service-and-Privacy-Policy-links-1.png +0 -0
  233. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/login.test.tsx/Login-Page-should-trigger-OAuth-flow-when-clicking-login-button-1.png +0 -0
  234. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-display-404-text-on-not-found-page-1.png +0 -0
  235. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-have-navigation-options-on-404-page-1.png +0 -0
  236. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-render-404-page-for-unknown-routes-1.png +0 -0
  237. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-account-page-1.png +0 -0
  238. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-integrations-page-1.png +0 -0
  239. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-settings-page-1.png +0 -0
  240. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-team-page-1.png +0 -0
  241. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-display-navigation-links-on-home-page-1.png +0 -0
  242. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-have-correct-link-destinations-on-home-page-1.png +0 -0
  243. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-home-page-without-authentication-1.png +0 -0
  244. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-login-page-without-authentication-1.png +0 -0
  245. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-dashboard-to-login-1.png +0 -0
  246. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-settings-to-login-1.png +0 -0
  247. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-team-to-login-1.png +0 -0
  248. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-render-Active-Sessions-section-with-session-cards-1.png +0 -0
  249. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-render-page-with-correct-title--Account--1.png +0 -0
  250. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-show-API-Access-section-1.png +0 -0
  251. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-show-Connected-Accounts-section-with-Google-connected-1.png +0 -0
  252. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-show-Danger-Zone-section-with-delete-button-1.png +0 -0
  253. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/account.test.tsx/Account-Page-should-show-Security-section-with-Two-Factor-Authentication-1.png +0 -0
  254. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-API-documentation-section-should-display-API-documentation-link-section-1.png +0 -0
  255. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-Create-Webhook-Dialog-should-have-Add-Webhook-trigger-button-1.png +0 -0
  256. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-category-filters-should-display-all-category-buttons-1.png +0 -0
  257. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-all-integration-cards-with-names-1.png +0 -0
  258. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-category-badges-on-cards-1.png +0 -0
  259. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Configure-button-for-connected-integrations-1.png +0 -0
  260. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connect-button-for-not-connected-integrations-1.png +0 -0
  261. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connected-badge-for-connected-integrations-1.png +0 -0
  262. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Available-Integrations-section-1.png +0 -0
  263. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Webhooks-section-1.png +0 -0
  264. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-connected-count-1.png +0 -0
  265. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-page-description-1.png +0 -0
  266. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-search-input-1.png +0 -0
  267. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-render-with-correct-title--Integrations--1.png +0 -0
  268. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-filter-integrations-based-on-search-query-1.png +0 -0
  269. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-search-by-description-1.png +0 -0
  270. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-show-no-results-message-when-search-has-no-matches-1.png +0 -0
  271. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-existing-webhook-1.png +0 -0
  272. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-events-1.png +0 -0
  273. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-last-delivery-info-1.png +0 -0
  274. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-success-status-1.png +0 -0
  275. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-have-Add-Webhook-button-1.png +0 -0
  276. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-connected-accounts-section-with-Google-provider-1.png +0 -0
  277. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-sessions-and-danger-zone-sections-1.png +0 -0
  278. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-all-notification-toggle-options-1.png +0 -0
  279. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-email-notifications-section-1.png +0 -0
  280. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-toggle-switches-for-notification-options-1.png +0 -0
  281. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-have-toggles-checked-by-default-1.png +0 -0
  282. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display--Save-Changes--button-1.png +0 -0
  283. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-personal-information-form-1.png +0 -0
  284. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-profile-picture-section-1.png +0 -0
  285. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-email-in-disabled-email-input-1.png +0 -0
  286. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-initials-in-avatar-1.png +0 -0
  287. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-name-in-the-name-input-1.png +0 -0
  288. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-all-three-tabs-1.png +0 -0
  289. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-page-description-1.png +0 -0
  290. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-render-with-correct-title--Settings--1.png +0 -0
  291. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-show-Profile-tab-as-default-active-tab-1.png +0 -0
  292. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Account-tab-when-clicked-1.png +0 -0
  293. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Notifications-tab-when-clicked-1.png +0 -0
  294. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-display-Active-Members-section-with-member-count-1.png +0 -0
  295. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-display-Pending-Invitations-section-with-invitation-details-1.png +0 -0
  296. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-have-invite-member-button-that-can-be-clicked-1.png +0 -0
  297. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-render-page-with-correct-title-and-description-1.png +0 -0
  298. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-render-search-input-that-filters-team-members-1.png +0 -0
  299. /package/templates/base/{src/client/routes/_authenticated/__tests__ → tests/unit/client/routes/_authenticated}/__screenshots__/team.test.tsx/Team-Page-should-show-current-user-with---you---indicator-and-role-badge-1.png +0 -0
  300. /package/templates/base/{src/client/__tests__ → tests/unit/client}/routes/error-components.test.tsx +0 -0
@@ -1,142 +1,214 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * Skill Activation Prompt Hook (UserPromptSubmit)
4
+ *
5
+ * Analyzes user prompts and suggests relevant skills based on:
6
+ * - Keywords (exact match, case-insensitive)
7
+ * - Intent patterns (regex-based semantic matching)
8
+ *
9
+ * Features:
10
+ * - Skips short prompts (< 15 chars) to avoid noise
11
+ * - Skips slash commands (already invoking a skill)
12
+ * - Groups suggestions by priority level
13
+ * - Returns structured JSON for Claude Code
14
+ */
15
+
2
16
  import { readFileSync } from 'fs';
3
17
  import { join, dirname } from 'path';
4
18
  import { fileURLToPath } from 'url';
5
19
 
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = dirname(__filename);
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+
22
+ // Minimum prompt length to trigger skill detection
23
+ const MIN_PROMPT_LENGTH = 15;
8
24
 
9
25
  interface HookInput {
10
- session_id: string;
11
- transcript_path: string;
12
- cwd: string;
13
- permission_mode: string;
14
- prompt: string;
26
+ session_id: string;
27
+ transcript_path?: string;
28
+ cwd?: string;
29
+ permission_mode?: string;
30
+ prompt: string;
15
31
  }
16
32
 
17
33
  interface PromptTriggers {
18
- keywords?: string[];
19
- intentPatterns?: string[];
34
+ keywords?: string[];
35
+ intentPatterns?: string[];
36
+ }
37
+
38
+ interface ToolGuard {
39
+ tool: string;
40
+ patterns: string[];
20
41
  }
21
42
 
22
43
  interface SkillRule {
23
- type: 'guardrail' | 'domain';
24
- enforcement: 'block' | 'suggest' | 'warn';
25
- priority: 'critical' | 'high' | 'medium' | 'low';
26
- promptTriggers?: PromptTriggers;
44
+ type: 'guardrail' | 'domain';
45
+ enforcement: 'block' | 'suggest' | 'warn';
46
+ priority: 'critical' | 'high' | 'medium' | 'low';
47
+ description?: string;
48
+ promptTriggers?: PromptTriggers;
49
+ toolGuards?: ToolGuard[];
27
50
  }
28
51
 
29
52
  interface SkillRules {
30
- version: string;
31
- skills: Record<string, SkillRule>;
53
+ version: string;
54
+ skills: Record<string, SkillRule>;
32
55
  }
33
56
 
34
57
  interface MatchedSkill {
35
- name: string;
36
- matchType: 'keyword' | 'intent';
37
- config: SkillRule;
58
+ name: string;
59
+ matchType: 'keyword' | 'intent';
60
+ config: SkillRule;
38
61
  }
39
62
 
40
- async function main() {
41
- try {
42
- // Read input from stdin
43
- const input = readFileSync(0, 'utf-8');
44
- const data: HookInput = JSON.parse(input);
63
+ function shouldSkipPrompt(prompt: string): boolean {
64
+ // Skip very short prompts
65
+ if (prompt.length < MIN_PROMPT_LENGTH) {
66
+ return true;
67
+ }
68
+
69
+ // Skip slash commands (user is already invoking a skill)
70
+ if (prompt.trim().startsWith('/')) {
71
+ return true;
72
+ }
73
+
74
+ // Skip common non-task prompts
75
+ const skipPatterns = [
76
+ /^(hi|hello|hey|thanks|thank you|ok|okay|yes|no|sure)[\s!.]*$/i,
77
+ /^(what|how|why|when|where|who|can you|could you|would you).*\?$/i, // Pure questions without action
78
+ ];
79
+
80
+ // Don't skip questions that imply action
81
+ const actionQuestionPatterns = [
82
+ /can you (create|build|make|implement|add|fix|update|deploy)/i,
83
+ /how (do i|to|can i) (create|build|make|implement|add|fix|update|deploy)/i,
84
+ ];
85
+
86
+ const isActionQuestion = actionQuestionPatterns.some(p => p.test(prompt));
87
+ if (isActionQuestion) {
88
+ return false;
89
+ }
90
+
91
+ return skipPatterns.some(p => p.test(prompt.trim()));
92
+ }
45
93
 
46
- // Handle missing or empty prompt
47
- if (!data.prompt) {
48
- process.exit(0);
49
- }
94
+ function findMatches(prompt: string, rules: SkillRules): MatchedSkill[] {
95
+ const matches: MatchedSkill[] = [];
96
+ const promptLower = prompt.toLowerCase();
50
97
 
51
- const prompt = data.prompt.toLowerCase();
52
-
53
- // Load skill rules
54
- // From .claude/hooks, go up one level to .claude, then to skills
55
- const rulesPath = join(__dirname, '..', 'skills', 'skill-rules.json');
56
- const rules: SkillRules = JSON.parse(readFileSync(rulesPath, 'utf-8'));
57
-
58
- const matchedSkills: MatchedSkill[] = [];
59
-
60
- // Check each skill for matches
61
- for (const [skillName, config] of Object.entries(rules.skills)) {
62
- const triggers = config.promptTriggers;
63
- if (!triggers) {
64
- continue;
65
- }
66
-
67
- // Keyword matching
68
- if (triggers.keywords) {
69
- const keywordMatch = triggers.keywords.some(kw =>
70
- prompt.includes(kw.toLowerCase())
71
- );
72
- if (keywordMatch) {
73
- matchedSkills.push({ name: skillName, matchType: 'keyword', config });
74
- continue;
75
- }
76
- }
77
-
78
- // Intent pattern matching
79
- if (triggers.intentPatterns) {
80
- const intentMatch = triggers.intentPatterns.some(pattern => {
81
- const regex = new RegExp(pattern, 'i');
82
- return regex.test(prompt);
83
- });
84
- if (intentMatch) {
85
- matchedSkills.push({ name: skillName, matchType: 'intent', config });
86
- }
87
- }
88
- }
98
+ for (const [skillName, config] of Object.entries(rules.skills)) {
99
+ const triggers = config.promptTriggers;
100
+ if (!triggers) {
101
+ continue;
102
+ }
103
+
104
+ // Keyword matching (exact, case-insensitive)
105
+ if (triggers.keywords) {
106
+ const keywordMatch = triggers.keywords.some(kw =>
107
+ promptLower.includes(kw.toLowerCase())
108
+ );
109
+ if (keywordMatch) {
110
+ matches.push({ name: skillName, matchType: 'keyword', config });
111
+ continue; // Don't check patterns if keyword matched
112
+ }
113
+ }
89
114
 
90
- // Generate output if matches found
91
- if (matchedSkills.length > 0) {
92
- let output = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
93
- output += '🎯 SKILL ACTIVATION CHECK\n';
94
- output += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n';
95
-
96
- // Group by priority
97
- const critical = matchedSkills.filter(s => s.config.priority === 'critical');
98
- const high = matchedSkills.filter(s => s.config.priority === 'high');
99
- const medium = matchedSkills.filter(s => s.config.priority === 'medium');
100
- const low = matchedSkills.filter(s => s.config.priority === 'low');
101
-
102
- if (critical.length > 0) {
103
- output += '⚠️ CRITICAL SKILLS (REQUIRED):\n';
104
- critical.forEach(s => output += ` → ${s.name}\n`);
105
- output += '\n';
106
- }
107
-
108
- if (high.length > 0) {
109
- output += '📚 RECOMMENDED SKILLS:\n';
110
- high.forEach(s => output += ` → ${s.name}\n`);
111
- output += '\n';
112
- }
113
-
114
- if (medium.length > 0) {
115
- output += '💡 SUGGESTED SKILLS:\n';
116
- medium.forEach(s => output += ` → ${s.name}\n`);
117
- output += '\n';
118
- }
119
-
120
- if (low.length > 0) {
121
- output += '📌 OPTIONAL SKILLS:\n';
122
- low.forEach(s => output += ` → ${s.name}\n`);
123
- output += '\n';
124
- }
125
-
126
- output += 'ACTION: Use Skill tool BEFORE responding\n';
127
- output += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
128
-
129
- console.log(output);
115
+ // Intent pattern matching (regex)
116
+ if (triggers.intentPatterns) {
117
+ const intentMatch = triggers.intentPatterns.some(pattern => {
118
+ try {
119
+ const regex = new RegExp(pattern, 'i');
120
+ return regex.test(prompt);
121
+ } catch {
122
+ return false;
130
123
  }
124
+ });
125
+ if (intentMatch) {
126
+ matches.push({ name: skillName, matchType: 'intent', config });
127
+ }
128
+ }
129
+ }
130
+
131
+ return matches;
132
+ }
133
+
134
+ function formatOutput(matches: MatchedSkill[]): string {
135
+ // Group by priority
136
+ const byPriority = {
137
+ critical: matches.filter(s => s.config.priority === 'critical'),
138
+ high: matches.filter(s => s.config.priority === 'high'),
139
+ medium: matches.filter(s => s.config.priority === 'medium'),
140
+ low: matches.filter(s => s.config.priority === 'low'),
141
+ };
142
+
143
+ const lines: string[] = ['🎯 SKILL ACTIVATION'];
131
144
 
132
- process.exit(0);
133
- } catch (err) {
134
- console.error('Error in skill-activation-prompt hook:', err);
135
- process.exit(1);
145
+ if (byPriority.critical.length > 0) {
146
+ lines.push(`⚠️ REQUIRED: ${byPriority.critical.map(s => s.name).join(', ')}`);
147
+ }
148
+
149
+ if (byPriority.high.length > 0) {
150
+ lines.push(`📚 RECOMMENDED: ${byPriority.high.map(s => s.name).join(', ')}`);
151
+ }
152
+
153
+ if (byPriority.medium.length > 0) {
154
+ lines.push(`💡 SUGGESTED: ${byPriority.medium.map(s => s.name).join(', ')}`);
155
+ }
156
+
157
+ if (byPriority.low.length > 0) {
158
+ lines.push(`📌 OPTIONAL: ${byPriority.low.map(s => s.name).join(', ')}`);
159
+ }
160
+
161
+ lines.push('→ Use Skill tool BEFORE responding');
162
+
163
+ return lines.join('\n');
164
+ }
165
+
166
+ function main() {
167
+ try {
168
+ const input = readFileSync(0, 'utf-8');
169
+ const data: HookInput = JSON.parse(input);
170
+ const prompt = data.prompt;
171
+
172
+ // Early exit conditions
173
+ if (shouldSkipPrompt(prompt)) {
174
+ process.exit(0);
136
175
  }
176
+
177
+ // Load skill rules
178
+ const rulesPath = join(__dirname, '..', 'skills', 'skill-rules.json');
179
+ let rules: SkillRules;
180
+
181
+ try {
182
+ rules = JSON.parse(readFileSync(rulesPath, 'utf-8'));
183
+ } catch {
184
+ // Silent exit if rules file not found
185
+ process.exit(0);
186
+ }
187
+
188
+ // Find matching skills
189
+ const matches = findMatches(prompt, rules);
190
+
191
+ // No matches = no output
192
+ if (matches.length === 0) {
193
+ process.exit(0);
194
+ }
195
+
196
+ // Format and output
197
+ const message = formatOutput(matches);
198
+
199
+ const output = {
200
+ hookSpecificOutput: {
201
+ hookEventName: 'UserPromptSubmit',
202
+ additionalContext: `<system-reminder>${message}</system-reminder>`
203
+ }
204
+ };
205
+
206
+ console.log(JSON.stringify(output));
207
+ process.exit(0);
208
+ } catch (err) {
209
+ // Silent failure - don't break user experience
210
+ process.exit(0);
211
+ }
137
212
  }
138
213
 
139
- main().catch(err => {
140
- console.error('Uncaught error:', err);
141
- process.exit(1);
142
- });
214
+ main();
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ cd "$SCRIPT_DIR"
6
+ cat | npx tsx skill-tool-guard.ts
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Skill Tool Guard Hook (PreToolUse)
4
+ *
5
+ * Recommends or blocks tool usage based on skill rules.
6
+ * Reads toolGuards from skill-rules.json to determine which
7
+ * skills should be used before specific tool patterns.
8
+ *
9
+ * Supported tools:
10
+ * - Bash: checks command content
11
+ * - Edit/Write: checks file_path
12
+ * - Read: checks file_path
13
+ * - Glob/Grep: checks pattern/path
14
+ * - Task: checks prompt
15
+ * - WebFetch: checks url
16
+ * - All others: checks JSON stringified input
17
+ */
18
+
19
+ import { readFileSync } from 'fs';
20
+ import { join, dirname } from 'path';
21
+ import { fileURLToPath } from 'url';
22
+
23
+ const __dirname = dirname(fileURLToPath(import.meta.url));
24
+
25
+ interface PreToolUseInput {
26
+ session_id: string;
27
+ tool_name: string;
28
+ tool_input: Record<string, unknown>;
29
+ }
30
+
31
+ interface ToolGuard {
32
+ tool: string;
33
+ patterns: string[];
34
+ }
35
+
36
+ interface SkillRule {
37
+ type: string;
38
+ enforcement: 'suggest' | 'warn' | 'block';
39
+ priority: string;
40
+ description?: string;
41
+ toolGuards?: ToolGuard[];
42
+ }
43
+
44
+ interface SkillRules {
45
+ version: string;
46
+ skills: Record<string, SkillRule>;
47
+ }
48
+
49
+ interface MatchedGuard {
50
+ skillName: string;
51
+ enforcement: string;
52
+ description?: string;
53
+ }
54
+
55
+ /**
56
+ * Extract the content to check based on tool type
57
+ */
58
+ function getContentToCheck(toolName: string, toolInput: Record<string, unknown>): string {
59
+ switch (toolName) {
60
+ case 'Bash':
61
+ return String(toolInput.command || '');
62
+
63
+ case 'Edit':
64
+ case 'Write':
65
+ case 'Read':
66
+ return String(toolInput.file_path || '');
67
+
68
+ case 'Glob':
69
+ return String(toolInput.pattern || '') + ' ' + String(toolInput.path || '');
70
+
71
+ case 'Grep':
72
+ return String(toolInput.pattern || '') + ' ' + String(toolInput.path || '');
73
+
74
+ case 'Task':
75
+ return String(toolInput.prompt || '') + ' ' + String(toolInput.description || '');
76
+
77
+ case 'WebFetch':
78
+ return String(toolInput.url || '');
79
+
80
+ case 'WebSearch':
81
+ return String(toolInput.query || '');
82
+
83
+ default:
84
+ // For unknown tools, stringify the entire input
85
+ return JSON.stringify(toolInput);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Check if a pattern matches the content
91
+ */
92
+ function matchesPattern(content: string, pattern: string): boolean {
93
+ try {
94
+ const regex = new RegExp(pattern, 'i');
95
+ return regex.test(content);
96
+ } catch {
97
+ // If regex fails, try simple includes
98
+ return content.toLowerCase().includes(pattern.toLowerCase());
99
+ }
100
+ }
101
+
102
+ function main() {
103
+ try {
104
+ const input = readFileSync(0, 'utf-8');
105
+ const data: PreToolUseInput = JSON.parse(input);
106
+
107
+ // Load skill rules
108
+ const rulesPath = join(__dirname, '..', 'skills', 'skill-rules.json');
109
+ let rules: SkillRules;
110
+
111
+ try {
112
+ rules = JSON.parse(readFileSync(rulesPath, 'utf-8'));
113
+ } catch {
114
+ process.exit(0);
115
+ }
116
+
117
+ const contentToCheck = getContentToCheck(data.tool_name, data.tool_input);
118
+ const matchedGuards: MatchedGuard[] = [];
119
+
120
+ // Check each skill's toolGuards
121
+ for (const [skillName, config] of Object.entries(rules.skills)) {
122
+ const guards = config.toolGuards;
123
+ if (!guards || guards.length === 0) {
124
+ continue;
125
+ }
126
+
127
+ for (const guard of guards) {
128
+ // Check if tool matches (exact match or wildcard)
129
+ if (guard.tool !== data.tool_name && guard.tool !== '*') {
130
+ continue;
131
+ }
132
+
133
+ // Check if any pattern matches
134
+ const matched = guard.patterns.some(pattern =>
135
+ matchesPattern(contentToCheck, pattern)
136
+ );
137
+
138
+ if (matched) {
139
+ matchedGuards.push({
140
+ skillName,
141
+ enforcement: config.enforcement,
142
+ description: config.description
143
+ });
144
+ break; // Only match once per skill
145
+ }
146
+ }
147
+ }
148
+
149
+ // No matches = no output
150
+ if (matchedGuards.length === 0) {
151
+ process.exit(0);
152
+ }
153
+
154
+ // Group by enforcement level
155
+ const critical = matchedGuards.filter(g => g.enforcement === 'block');
156
+ const warnings = matchedGuards.filter(g => g.enforcement === 'warn');
157
+ const suggestions = matchedGuards.filter(g => g.enforcement === 'suggest');
158
+
159
+ // Build message
160
+ const lines: string[] = [`⚡ TOOL GUARD (${data.tool_name})`];
161
+
162
+ if (critical.length > 0) {
163
+ lines.push(`🚫 BLOCKED: ${critical.map(g => g.skillName).join(', ')}`);
164
+ }
165
+
166
+ if (warnings.length > 0) {
167
+ lines.push(`⚠️ WARNING: ${warnings.map(g => g.skillName).join(', ')}`);
168
+ }
169
+
170
+ if (suggestions.length > 0) {
171
+ lines.push(`💡 CONSIDER: ${suggestions.map(g => g.skillName).join(', ')}`);
172
+ }
173
+
174
+ lines.push('→ Use relevant skill before proceeding');
175
+
176
+ const message = lines.join('\n');
177
+
178
+ // Determine permission decision based on enforcement
179
+ // For now, only 'suggest' - block/warn not implemented yet
180
+ const permissionDecision = critical.length > 0 ? 'block' : 'allow';
181
+
182
+ const output = {
183
+ hookSpecificOutput: {
184
+ hookEventName: 'PreToolUse',
185
+ permissionDecision,
186
+ additionalContext: `<system-reminder>${message}</system-reminder>`
187
+ }
188
+ };
189
+
190
+ console.log(JSON.stringify(output));
191
+ process.exit(0);
192
+ } catch (err) {
193
+ // Silent failure - don't break tool execution
194
+ process.exit(0);
195
+ }
196
+ }
197
+
198
+ main();
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash
2
- # Validates skill-rules.json structure and activation system
3
- # Runs on SessionStart to ensure the skill-rules activation works
2
+ # Validates skill-rules.json structure and hook system
3
+ # Runs on SessionStart to ensure the skill activation works
4
4
 
5
5
  set -e
6
6
 
@@ -13,21 +13,24 @@ RULES_FILE="$CLAUDE_DIR/skills/skill-rules.json"
13
13
 
14
14
  # Collect messages
15
15
  MESSAGES=""
16
+ ERRORS=0
16
17
 
17
18
  # Install dependencies if needed (silent)
18
19
  if [ -f "$HOOKS_DIR/package.json" ] && [ ! -d "$HOOKS_DIR/node_modules" ]; then
19
20
  cd "$HOOKS_DIR" && npm install --silent >/dev/null 2>&1
20
21
  fi
21
22
 
22
- # Make hook executable
23
- if [ -f "$HOOKS_DIR/skill-activation-prompt.sh" ]; then
24
- chmod +x "$HOOKS_DIR/skill-activation-prompt.sh" 2>/dev/null || true
25
- fi
23
+ # Make hooks executable
24
+ for hook in "$HOOKS_DIR"/*.sh; do
25
+ if [ -f "$hook" ]; then
26
+ chmod +x "$hook" 2>/dev/null || true
27
+ fi
28
+ done
26
29
 
27
30
  # Validate skill-rules.json exists
28
31
  if [ ! -f "$RULES_FILE" ]; then
29
32
  MESSAGES+="❌ skill-rules.json not found\n"
30
- MESSAGES+=" Skill activation will not work properly.\n"
33
+ ERRORS=$((ERRORS + 1))
31
34
 
32
35
  jq -n --arg msg "$(echo -e "$MESSAGES")" '{
33
36
  hookSpecificOutput: {
@@ -38,12 +41,12 @@ if [ ! -f "$RULES_FILE" ]; then
38
41
  exit 1
39
42
  fi
40
43
 
41
- # Validate skill-rules.json is valid JSON
44
+ # Validate skill-rules.json is valid JSON and check version
42
45
  if ! command -v jq >/dev/null 2>&1; then
43
- # jq not available, skip JSON validation
44
46
  MESSAGES+="⚠️ jq not found, skipping JSON validation\n"
45
47
  elif ! jq empty "$RULES_FILE" 2>/dev/null; then
46
48
  MESSAGES+="❌ skill-rules.json is not valid JSON\n"
49
+ ERRORS=$((ERRORS + 1))
47
50
 
48
51
  jq -n --arg msg "$(echo -e "$MESSAGES")" '{
49
52
  hookSpecificOutput: {
@@ -52,39 +55,59 @@ elif ! jq empty "$RULES_FILE" 2>/dev/null; then
52
55
  }
53
56
  }'
54
57
  exit 1
58
+ else
59
+ # Check version
60
+ VERSION=$(jq -r '.version // "unknown"' "$RULES_FILE")
61
+ SKILL_COUNT=$(jq '.skills | length' "$RULES_FILE")
62
+ TOOLGUARDS_COUNT=$(jq '[.skills[] | select(.toolGuards != null)] | length' "$RULES_FILE")
55
63
  fi
56
64
 
57
- # Quick test: Run hook with mock data
58
- TEST_INPUT='{"session_id":"test","transcript_path":"/tmp","cwd":".","permission_mode":"auto","prompt":"test fastapi endpoint"}'
65
+ # Test skill-activation-prompt hook
66
+ TEST_PROMPT='{"session_id":"test","transcript_path":"/tmp","cwd":".","permission_mode":"auto","prompt":"analyze architecture of codebase"}'
59
67
  if [ -x "$HOOKS_DIR/skill-activation-prompt.sh" ]; then
60
- if echo "$TEST_INPUT" | "$HOOKS_DIR/skill-activation-prompt.sh" >/dev/null 2>&1; then
61
- MESSAGES+="✅ Skill-rules system validated"
68
+ if echo "$TEST_PROMPT" | "$HOOKS_DIR/skill-activation-prompt.sh" >/dev/null 2>&1; then
69
+ MESSAGES+="✅ skill-activation-prompt hook OK\n"
62
70
  else
63
- MESSAGES+="⚠️ Skill activation hook test failed\n"
64
- MESSAGES+=" Check that dependencies are installed in $HOOKS_DIR\n"
65
-
66
- jq -n --arg msg "$(echo -e "$MESSAGES")" '{
67
- hookSpecificOutput: {
68
- hookEventName: "SessionStart",
69
- additionalContext: $msg
70
- }
71
- }'
72
- exit 1
71
+ MESSAGES+="⚠️ skill-activation-prompt test failed\n"
72
+ ERRORS=$((ERRORS + 1))
73
73
  fi
74
74
  else
75
- MESSAGES+="⚠️ skill-activation-prompt.sh not found or not executable\n"
75
+ MESSAGES+="skill-activation-prompt.sh not found or not executable\n"
76
+ ERRORS=$((ERRORS + 1))
77
+ fi
76
78
 
77
- jq -n --arg msg "$(echo -e "$MESSAGES")" '{
78
- hookSpecificOutput: {
79
- hookEventName: "SessionStart",
80
- additionalContext: $msg
81
- }
82
- }'
83
- exit 1
79
+ # Test skill-tool-guard hook
80
+ TEST_TOOL='{"session_id":"test","tool_name":"Bash","tool_input":{"command":"git push origin main"}}'
81
+ if [ -x "$HOOKS_DIR/skill-tool-guard.sh" ]; then
82
+ if echo "$TEST_TOOL" | "$HOOKS_DIR/skill-tool-guard.sh" >/dev/null 2>&1; then
83
+ MESSAGES+="✅ skill-tool-guard hook OK\n"
84
+ else
85
+ MESSAGES+="⚠️ skill-tool-guard test failed\n"
86
+ ERRORS=$((ERRORS + 1))
87
+ fi
88
+ else
89
+ MESSAGES+="❌ skill-tool-guard.sh not found or not executable\n"
90
+ ERRORS=$((ERRORS + 1))
91
+ fi
92
+
93
+ # Summary
94
+ if [ $ERRORS -eq 0 ]; then
95
+ SUMMARY="✅ Skill-rules system validated (v${VERSION}, ${SKILL_COUNT} skills, ${TOOLGUARDS_COUNT} with toolGuards)"
96
+ else
97
+ SUMMARY="⚠️ Skill-rules validation: ${ERRORS} issue(s) found"
98
+ fi
99
+
100
+ # Build final message
101
+ FINAL_MSG="$SUMMARY"
102
+ if [ -n "$MESSAGES" ]; then
103
+ # Only include details if there were issues
104
+ if [ $ERRORS -gt 0 ]; then
105
+ FINAL_MSG+="\n$(echo -e "$MESSAGES")"
106
+ fi
84
107
  fi
85
108
 
86
109
  # Output success message in Claude Code format with visual reminder
87
- jq -n --arg msg "<important-reminder>IN YOUR FIRST REPLY AFTER SEEING THIS MESSAGE YOU MUST TELL THE USER:\n$(echo -e "$MESSAGES")</important-reminder>" '{
110
+ jq -n --arg msg "<important-reminder>IN YOUR FIRST REPLY AFTER SEEING THIS MESSAGE YOU MUST TELL THE USER:\n${FINAL_MSG}</important-reminder>" '{
88
111
  hookSpecificOutput: {
89
112
  hookEventName: "SessionStart",
90
113
  additionalContext: $msg