autoworkflow 3.1.5 → 3.5.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 (123) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +26 -0
  3. package/.claude/commands/build.md +39 -0
  4. package/.claude/commands/commit.md +25 -0
  5. package/.claude/commands/fix.md +23 -0
  6. package/.claude/commands/plan.md +18 -0
  7. package/.claude/commands/suggest.md +23 -0
  8. package/.claude/commands/verify.md +18 -0
  9. package/.claude/hooks/post-bash-router.sh +20 -0
  10. package/.claude/hooks/post-commit.sh +140 -0
  11. package/.claude/hooks/pre-edit.sh +129 -0
  12. package/.claude/hooks/session-check.sh +79 -0
  13. package/.claude/settings.json +40 -6
  14. package/.claude/settings.local.json +3 -1
  15. package/.claude/skills/actix.md +337 -0
  16. package/.claude/skills/alembic.md +504 -0
  17. package/.claude/skills/angular.md +237 -0
  18. package/.claude/skills/api-design.md +187 -0
  19. package/.claude/skills/aspnet-core.md +377 -0
  20. package/.claude/skills/astro.md +245 -0
  21. package/.claude/skills/auth-clerk.md +327 -0
  22. package/.claude/skills/auth-firebase.md +367 -0
  23. package/.claude/skills/auth-nextauth.md +359 -0
  24. package/.claude/skills/auth-supabase.md +368 -0
  25. package/.claude/skills/axum.md +386 -0
  26. package/.claude/skills/blazor.md +456 -0
  27. package/.claude/skills/chi.md +348 -0
  28. package/.claude/skills/code-review.md +133 -0
  29. package/.claude/skills/csharp.md +296 -0
  30. package/.claude/skills/css-modules.md +325 -0
  31. package/.claude/skills/cypress.md +343 -0
  32. package/.claude/skills/debugging.md +133 -0
  33. package/.claude/skills/diesel.md +392 -0
  34. package/.claude/skills/django.md +301 -0
  35. package/.claude/skills/docker.md +319 -0
  36. package/.claude/skills/doctrine.md +473 -0
  37. package/.claude/skills/documentation.md +182 -0
  38. package/.claude/skills/dotnet.md +409 -0
  39. package/.claude/skills/drizzle.md +293 -0
  40. package/.claude/skills/echo.md +321 -0
  41. package/.claude/skills/eloquent.md +256 -0
  42. package/.claude/skills/emotion.md +426 -0
  43. package/.claude/skills/entity-framework.md +370 -0
  44. package/.claude/skills/express.md +316 -0
  45. package/.claude/skills/fastapi.md +329 -0
  46. package/.claude/skills/fastify.md +299 -0
  47. package/.claude/skills/fiber.md +315 -0
  48. package/.claude/skills/flask.md +322 -0
  49. package/.claude/skills/gin.md +342 -0
  50. package/.claude/skills/git.md +116 -0
  51. package/.claude/skills/github-actions.md +353 -0
  52. package/.claude/skills/go.md +377 -0
  53. package/.claude/skills/gorm.md +409 -0
  54. package/.claude/skills/graphql.md +478 -0
  55. package/.claude/skills/hibernate.md +379 -0
  56. package/.claude/skills/hono.md +306 -0
  57. package/.claude/skills/java.md +400 -0
  58. package/.claude/skills/jest.md +313 -0
  59. package/.claude/skills/jpa.md +282 -0
  60. package/.claude/skills/kotlin.md +347 -0
  61. package/.claude/skills/kubernetes.md +363 -0
  62. package/.claude/skills/laravel.md +414 -0
  63. package/.claude/skills/mcp-browser.md +320 -0
  64. package/.claude/skills/mcp-database.md +219 -0
  65. package/.claude/skills/mcp-fetch.md +241 -0
  66. package/.claude/skills/mcp-filesystem.md +204 -0
  67. package/.claude/skills/mcp-github.md +217 -0
  68. package/.claude/skills/mcp-memory.md +240 -0
  69. package/.claude/skills/mcp-search.md +218 -0
  70. package/.claude/skills/mcp-slack.md +262 -0
  71. package/.claude/skills/micronaut.md +388 -0
  72. package/.claude/skills/mongodb.md +319 -0
  73. package/.claude/skills/mongoose.md +355 -0
  74. package/.claude/skills/mysql.md +281 -0
  75. package/.claude/skills/nestjs.md +335 -0
  76. package/.claude/skills/nextjs-app-router.md +260 -0
  77. package/.claude/skills/nextjs-pages.md +172 -0
  78. package/.claude/skills/nuxt.md +202 -0
  79. package/.claude/skills/openapi.md +489 -0
  80. package/.claude/skills/performance.md +199 -0
  81. package/.claude/skills/php.md +398 -0
  82. package/.claude/skills/playwright.md +371 -0
  83. package/.claude/skills/postgresql.md +257 -0
  84. package/.claude/skills/prisma.md +293 -0
  85. package/.claude/skills/pydantic.md +304 -0
  86. package/.claude/skills/pytest.md +313 -0
  87. package/.claude/skills/python.md +272 -0
  88. package/.claude/skills/quarkus.md +377 -0
  89. package/.claude/skills/react.md +230 -0
  90. package/.claude/skills/redis.md +391 -0
  91. package/.claude/skills/refactoring.md +143 -0
  92. package/.claude/skills/remix.md +246 -0
  93. package/.claude/skills/rest-api.md +490 -0
  94. package/.claude/skills/rocket.md +366 -0
  95. package/.claude/skills/rust.md +341 -0
  96. package/.claude/skills/sass.md +380 -0
  97. package/.claude/skills/sea-orm.md +382 -0
  98. package/.claude/skills/security.md +167 -0
  99. package/.claude/skills/sequelize.md +395 -0
  100. package/.claude/skills/spring-boot.md +416 -0
  101. package/.claude/skills/sqlalchemy.md +269 -0
  102. package/.claude/skills/sqlx-rust.md +408 -0
  103. package/.claude/skills/state-jotai.md +346 -0
  104. package/.claude/skills/state-mobx.md +353 -0
  105. package/.claude/skills/state-pinia.md +431 -0
  106. package/.claude/skills/state-redux.md +337 -0
  107. package/.claude/skills/state-tanstack-query.md +434 -0
  108. package/.claude/skills/state-zustand.md +340 -0
  109. package/.claude/skills/styled-components.md +403 -0
  110. package/.claude/skills/svelte.md +238 -0
  111. package/.claude/skills/sveltekit.md +207 -0
  112. package/.claude/skills/symfony.md +437 -0
  113. package/.claude/skills/tailwind.md +279 -0
  114. package/.claude/skills/terraform.md +394 -0
  115. package/.claude/skills/testing-library.md +371 -0
  116. package/.claude/skills/trpc.md +426 -0
  117. package/.claude/skills/typeorm.md +368 -0
  118. package/.claude/skills/vitest.md +330 -0
  119. package/.claude/skills/vue.md +202 -0
  120. package/.claude/skills/warp.md +365 -0
  121. package/README.md +135 -52
  122. package/package.json +1 -1
  123. package/system/triggers.md +152 -11
@@ -0,0 +1,478 @@
1
+ # GraphQL Skill
2
+
3
+ ## Schema Design
4
+ \`\`\`graphql
5
+ # schema.graphql
6
+
7
+ # Scalars
8
+ scalar DateTime
9
+ scalar JSON
10
+
11
+ # Enums
12
+ enum Role {
13
+ USER
14
+ ADMIN
15
+ MODERATOR
16
+ }
17
+
18
+ enum PostStatus {
19
+ DRAFT
20
+ PUBLISHED
21
+ ARCHIVED
22
+ }
23
+
24
+ # Types
25
+ type User {
26
+ id: ID!
27
+ email: String!
28
+ name: String
29
+ role: Role!
30
+ posts(first: Int, after: String): PostConnection!
31
+ createdAt: DateTime!
32
+ updatedAt: DateTime!
33
+ }
34
+
35
+ type Post {
36
+ id: ID!
37
+ title: String!
38
+ content: String!
39
+ status: PostStatus!
40
+ author: User!
41
+ comments(first: Int, after: String): CommentConnection!
42
+ tags: [String!]!
43
+ publishedAt: DateTime
44
+ createdAt: DateTime!
45
+ }
46
+
47
+ type Comment {
48
+ id: ID!
49
+ content: String!
50
+ author: User!
51
+ post: Post!
52
+ createdAt: DateTime!
53
+ }
54
+
55
+ # Pagination (Relay-style)
56
+ type PageInfo {
57
+ hasNextPage: Boolean!
58
+ hasPreviousPage: Boolean!
59
+ startCursor: String
60
+ endCursor: String
61
+ }
62
+
63
+ type PostEdge {
64
+ node: Post!
65
+ cursor: String!
66
+ }
67
+
68
+ type PostConnection {
69
+ edges: [PostEdge!]!
70
+ pageInfo: PageInfo!
71
+ totalCount: Int!
72
+ }
73
+
74
+ # Inputs
75
+ input CreateUserInput {
76
+ email: String!
77
+ name: String!
78
+ password: String!
79
+ }
80
+
81
+ input CreatePostInput {
82
+ title: String!
83
+ content: String!
84
+ status: PostStatus = DRAFT
85
+ tags: [String!]
86
+ }
87
+
88
+ input UpdatePostInput {
89
+ title: String
90
+ content: String
91
+ status: PostStatus
92
+ tags: [String!]
93
+ }
94
+
95
+ input PostFilterInput {
96
+ status: PostStatus
97
+ authorId: ID
98
+ search: String
99
+ }
100
+
101
+ # Queries
102
+ type Query {
103
+ # Single resources
104
+ user(id: ID!): User
105
+ post(id: ID!): Post
106
+ me: User
107
+
108
+ # Collections with filtering & pagination
109
+ users(first: Int, after: String): UserConnection!
110
+ posts(
111
+ first: Int
112
+ after: String
113
+ filter: PostFilterInput
114
+ ): PostConnection!
115
+ }
116
+
117
+ # Mutations
118
+ type Mutation {
119
+ # User mutations
120
+ createUser(input: CreateUserInput!): CreateUserPayload!
121
+ updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
122
+ deleteUser(id: ID!): DeleteUserPayload!
123
+
124
+ # Post mutations
125
+ createPost(input: CreatePostInput!): CreatePostPayload!
126
+ updatePost(id: ID!, input: UpdatePostInput!): UpdatePostPayload!
127
+ deletePost(id: ID!): DeletePostPayload!
128
+ publishPost(id: ID!): PublishPostPayload!
129
+ }
130
+
131
+ # Mutation Payloads
132
+ type CreatePostPayload {
133
+ post: Post
134
+ errors: [Error!]
135
+ }
136
+
137
+ type Error {
138
+ field: String
139
+ message: String!
140
+ }
141
+
142
+ # Subscriptions
143
+ type Subscription {
144
+ postCreated: Post!
145
+ postUpdated(id: ID!): Post!
146
+ commentAdded(postId: ID!): Comment!
147
+ }
148
+ \`\`\`
149
+
150
+ ## Resolvers (Apollo Server)
151
+ \`\`\`typescript
152
+ // resolvers.ts
153
+ import { Resolvers } from './generated/graphql';
154
+ import DataLoader from 'dataloader';
155
+
156
+ export const resolvers: Resolvers = {
157
+ Query: {
158
+ user: async (_, { id }, { prisma }) => {
159
+ return prisma.user.findUnique({ where: { id } });
160
+ },
161
+
162
+ me: async (_, __, { user }) => {
163
+ if (!user) throw new AuthenticationError('Not authenticated');
164
+ return user;
165
+ },
166
+
167
+ posts: async (_, { first = 10, after, filter }, { prisma }) => {
168
+ const where = {
169
+ ...(filter?.status && { status: filter.status }),
170
+ ...(filter?.authorId && { authorId: filter.authorId }),
171
+ ...(filter?.search && {
172
+ OR: [
173
+ { title: { contains: filter.search } },
174
+ { content: { contains: filter.search } },
175
+ ],
176
+ }),
177
+ };
178
+
179
+ const posts = await prisma.post.findMany({
180
+ where,
181
+ take: first + 1,
182
+ ...(after && { cursor: { id: after }, skip: 1 }),
183
+ orderBy: { createdAt: 'desc' },
184
+ });
185
+
186
+ const hasNextPage = posts.length > first;
187
+ const edges = posts.slice(0, first).map((post) => ({
188
+ node: post,
189
+ cursor: post.id,
190
+ }));
191
+
192
+ return {
193
+ edges,
194
+ pageInfo: {
195
+ hasNextPage,
196
+ hasPreviousPage: !!after,
197
+ startCursor: edges[0]?.cursor,
198
+ endCursor: edges[edges.length - 1]?.cursor,
199
+ },
200
+ totalCount: await prisma.post.count({ where }),
201
+ };
202
+ },
203
+ },
204
+
205
+ Mutation: {
206
+ createPost: async (_, { input }, { user, prisma }) => {
207
+ if (!user) {
208
+ return { post: null, errors: [{ message: 'Not authenticated' }] };
209
+ }
210
+
211
+ try {
212
+ const post = await prisma.post.create({
213
+ data: { ...input, authorId: user.id },
214
+ });
215
+ return { post, errors: null };
216
+ } catch (error) {
217
+ return { post: null, errors: [{ message: 'Failed to create post' }] };
218
+ }
219
+ },
220
+
221
+ publishPost: async (_, { id }, { user, prisma }) => {
222
+ const post = await prisma.post.findUnique({ where: { id } });
223
+
224
+ if (!post) {
225
+ return { post: null, errors: [{ message: 'Post not found' }] };
226
+ }
227
+
228
+ if (post.authorId !== user.id) {
229
+ return { post: null, errors: [{ message: 'Not authorized' }] };
230
+ }
231
+
232
+ const updated = await prisma.post.update({
233
+ where: { id },
234
+ data: { status: 'PUBLISHED', publishedAt: new Date() },
235
+ });
236
+
237
+ return { post: updated, errors: null };
238
+ },
239
+ },
240
+
241
+ // Field resolvers
242
+ User: {
243
+ posts: async (user, { first, after }, { prisma }) => {
244
+ // Delegate to Query.posts with filter
245
+ return resolvers.Query.posts(
246
+ null,
247
+ { first, after, filter: { authorId: user.id } },
248
+ { prisma }
249
+ );
250
+ },
251
+ },
252
+
253
+ Post: {
254
+ author: async (post, _, { loaders }) => {
255
+ // Use DataLoader to batch & cache
256
+ return loaders.userLoader.load(post.authorId);
257
+ },
258
+ },
259
+
260
+ Subscription: {
261
+ postCreated: {
262
+ subscribe: (_, __, { pubsub }) => {
263
+ return pubsub.asyncIterator(['POST_CREATED']);
264
+ },
265
+ },
266
+ },
267
+ };
268
+ \`\`\`
269
+
270
+ ## DataLoader (N+1 Prevention)
271
+ \`\`\`typescript
272
+ // loaders.ts
273
+ import DataLoader from 'dataloader';
274
+ import { PrismaClient, User } from '@prisma/client';
275
+
276
+ export function createLoaders(prisma: PrismaClient) {
277
+ return {
278
+ userLoader: new DataLoader<string, User>(async (ids) => {
279
+ const users = await prisma.user.findMany({
280
+ where: { id: { in: [...ids] } },
281
+ });
282
+
283
+ const userMap = new Map(users.map((u) => [u.id, u]));
284
+ return ids.map((id) => userMap.get(id) || null);
285
+ }),
286
+
287
+ postsByAuthorLoader: new DataLoader<string, Post[]>(async (authorIds) => {
288
+ const posts = await prisma.post.findMany({
289
+ where: { authorId: { in: [...authorIds] } },
290
+ });
291
+
292
+ const postsByAuthor = new Map<string, Post[]>();
293
+ posts.forEach((post) => {
294
+ const existing = postsByAuthor.get(post.authorId) || [];
295
+ postsByAuthor.set(post.authorId, [...existing, post]);
296
+ });
297
+
298
+ return authorIds.map((id) => postsByAuthor.get(id) || []);
299
+ }),
300
+ };
301
+ }
302
+
303
+ // Context setup
304
+ const server = new ApolloServer({
305
+ typeDefs,
306
+ resolvers,
307
+ context: ({ req }) => ({
308
+ prisma,
309
+ user: req.user,
310
+ loaders: createLoaders(prisma),
311
+ }),
312
+ });
313
+ \`\`\`
314
+
315
+ ## Client Usage (Apollo Client)
316
+ \`\`\`typescript
317
+ // apollo-client.ts
318
+ import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
319
+ import { setContext } from '@apollo/client/link/context';
320
+
321
+ const httpLink = createHttpLink({
322
+ uri: '/api/graphql',
323
+ });
324
+
325
+ const authLink = setContext((_, { headers }) => {
326
+ const token = localStorage.getItem('token');
327
+ return {
328
+ headers: {
329
+ ...headers,
330
+ authorization: token ? \`Bearer \${token}\` : '',
331
+ },
332
+ };
333
+ });
334
+
335
+ export const client = new ApolloClient({
336
+ link: authLink.concat(httpLink),
337
+ cache: new InMemoryCache({
338
+ typePolicies: {
339
+ Query: {
340
+ fields: {
341
+ posts: {
342
+ keyArgs: ['filter'],
343
+ merge(existing, incoming, { args }) {
344
+ if (!args?.after) return incoming;
345
+ return {
346
+ ...incoming,
347
+ edges: [...(existing?.edges || []), ...incoming.edges],
348
+ };
349
+ },
350
+ },
351
+ },
352
+ },
353
+ },
354
+ }),
355
+ });
356
+
357
+ // hooks/usePosts.ts
358
+ import { gql, useQuery } from '@apollo/client';
359
+
360
+ const GET_POSTS = gql\`
361
+ query GetPosts($first: Int, $after: String, $filter: PostFilterInput) {
362
+ posts(first: $first, after: $after, filter: $filter) {
363
+ edges {
364
+ node {
365
+ id
366
+ title
367
+ status
368
+ author {
369
+ id
370
+ name
371
+ }
372
+ }
373
+ cursor
374
+ }
375
+ pageInfo {
376
+ hasNextPage
377
+ endCursor
378
+ }
379
+ }
380
+ }
381
+ \`;
382
+
383
+ export function usePosts(filter?: PostFilterInput) {
384
+ const { data, loading, error, fetchMore } = useQuery(GET_POSTS, {
385
+ variables: { first: 10, filter },
386
+ });
387
+
388
+ const loadMore = () => {
389
+ if (!data?.posts.pageInfo.hasNextPage) return;
390
+
391
+ fetchMore({
392
+ variables: {
393
+ after: data.posts.pageInfo.endCursor,
394
+ },
395
+ });
396
+ };
397
+
398
+ return { posts: data?.posts.edges, loading, error, loadMore };
399
+ }
400
+
401
+ // Mutation hook
402
+ const CREATE_POST = gql\`
403
+ mutation CreatePost($input: CreatePostInput!) {
404
+ createPost(input: $input) {
405
+ post {
406
+ id
407
+ title
408
+ }
409
+ errors {
410
+ field
411
+ message
412
+ }
413
+ }
414
+ }
415
+ \`;
416
+
417
+ export function useCreatePost() {
418
+ const [createPost, { loading }] = useMutation(CREATE_POST, {
419
+ update(cache, { data }) {
420
+ if (data?.createPost.post) {
421
+ cache.modify({
422
+ fields: {
423
+ posts(existing = { edges: [] }) {
424
+ const newEdge = {
425
+ __typename: 'PostEdge',
426
+ node: data.createPost.post,
427
+ cursor: data.createPost.post.id,
428
+ };
429
+ return {
430
+ ...existing,
431
+ edges: [newEdge, ...existing.edges],
432
+ };
433
+ },
434
+ },
435
+ });
436
+ }
437
+ },
438
+ });
439
+
440
+ return { createPost, loading };
441
+ }
442
+ \`\`\`
443
+
444
+ ## Error Handling
445
+ \`\`\`typescript
446
+ // Custom errors
447
+ import { GraphQLError } from 'graphql';
448
+
449
+ throw new GraphQLError('Not authenticated', {
450
+ extensions: {
451
+ code: 'UNAUTHENTICATED',
452
+ http: { status: 401 },
453
+ },
454
+ });
455
+
456
+ throw new GraphQLError('Resource not found', {
457
+ extensions: {
458
+ code: 'NOT_FOUND',
459
+ http: { status: 404 },
460
+ },
461
+ });
462
+ \`\`\`
463
+
464
+ ## ❌ DON'T
465
+ - Return null without proper error handling
466
+ - Skip DataLoader for relationship fields
467
+ - Expose sensitive fields without authorization
468
+ - Use deeply nested queries without limits
469
+ - Forget input validation
470
+
471
+ ## ✅ DO
472
+ - Use Relay-style pagination for lists
473
+ - Implement DataLoader for all relationships
474
+ - Return error payloads from mutations
475
+ - Add query complexity limits
476
+ - Use code generation for types
477
+ - Implement field-level authorization
478
+ - Cache and batch with DataLoader