claude-flow-novice 2.14.21 → 2.14.23
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.
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/cfn-seo-coordinator.md +410 -414
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/competitive-seo-analyst.md +420 -423
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/content-atomization-specialist.md +577 -580
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/content-seo-strategist.md +242 -245
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/eeat-content-auditor.md +386 -389
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/geo-optimization-expert.md +266 -269
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/link-building-specialist.md +288 -291
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/local-seo-optimizer.md +330 -333
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/programmatic-seo-engineer.md +241 -244
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/schema-markup-engineer.md +427 -430
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/seo-analytics-specialist.md +373 -376
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/seo-validators/accessibility-validator.md +561 -565
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/seo-validators/audience-validator.md +480 -484
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/seo-validators/branding-validator.md +448 -452
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/seo-validators/humanizer-validator.md +329 -333
- package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/technical-seo-specialist.md +227 -231
- package/claude-assets/agents/cfn-dev-team/CLAUDE.md +46 -71
- package/claude-assets/agents/cfn-dev-team/analysts/root-cause-analyst.md +1 -4
- package/claude-assets/agents/cfn-dev-team/architecture/goal-planner.md +1 -4
- package/claude-assets/agents/cfn-dev-team/architecture/planner.md +1 -4
- package/claude-assets/agents/cfn-dev-team/architecture/system-architect.md +1 -4
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-frontend-coordinator.md +536 -540
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-v3-coordinator.md +1 -4
- package/claude-assets/agents/cfn-dev-team/coordinators/epic-creator.md +1 -5
- package/claude-assets/agents/cfn-dev-team/coordinators/multi-sprint-coordinator.md +1 -3
- package/claude-assets/agents/cfn-dev-team/dev-ops/devops-engineer.md +1 -5
- package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +688 -692
- package/claude-assets/agents/cfn-dev-team/dev-ops/github-commit-agent.md +113 -117
- package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +536 -540
- package/claude-assets/agents/cfn-dev-team/dev-ops/monitoring-specialist.md +735 -739
- package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +901 -905
- package/claude-assets/agents/cfn-dev-team/developers/backend-developer.md +1 -4
- package/claude-assets/agents/cfn-dev-team/developers/data/data-engineer.md +581 -585
- package/claude-assets/agents/cfn-dev-team/developers/database/database-architect.md +272 -276
- package/claude-assets/agents/cfn-dev-team/developers/frontend/react-frontend-engineer.md +1 -4
- package/claude-assets/agents/cfn-dev-team/developers/frontend/typescript-specialist.md +322 -325
- package/claude-assets/agents/cfn-dev-team/developers/frontend/ui-designer.md +1 -5
- package/claude-assets/agents/cfn-dev-team/developers/graphql-specialist.md +611 -615
- package/claude-assets/agents/cfn-dev-team/developers/rust-developer.md +1 -4
- package/claude-assets/agents/cfn-dev-team/documentation/pseudocode.md +1 -4
- package/claude-assets/agents/cfn-dev-team/documentation/specification-agent.md +1 -4
- package/claude-assets/agents/cfn-dev-team/product-owners/accessibility-advocate-persona.md +105 -108
- package/claude-assets/agents/cfn-dev-team/product-owners/cto-agent.md +1 -5
- package/claude-assets/agents/cfn-dev-team/product-owners/power-user-persona.md +176 -180
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/code-quality-validator.md +1 -4
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/cyclomatic-complexity-reducer.md +318 -321
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/perf-analyzer.md +1 -4
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/security-specialist.md +1 -4
- package/claude-assets/agents/cfn-dev-team/reviewers/reviewer.md +26 -5
- package/claude-assets/agents/cfn-dev-team/testers/api-testing-specialist.md +703 -707
- package/claude-assets/agents/cfn-dev-team/testers/chaos-engineering-specialist.md +897 -901
- package/claude-assets/agents/cfn-dev-team/testers/e2e/playwright-tester.md +1 -5
- package/claude-assets/agents/cfn-dev-team/testers/interaction-tester.md +1 -5
- package/claude-assets/agents/cfn-dev-team/testers/load-testing-specialist.md +465 -469
- package/claude-assets/agents/cfn-dev-team/testers/playwright-tester.md +1 -4
- package/claude-assets/agents/cfn-dev-team/testers/tester.md +26 -8
- package/claude-assets/agents/cfn-dev-team/testers/unit/tdd-london-unit-swarm.md +1 -5
- package/claude-assets/agents/cfn-dev-team/testers/validation/validation-production-validator.md +1 -3
- package/claude-assets/agents/cfn-dev-team/testing/test-validation-agent.md +309 -312
- package/claude-assets/agents/cfn-dev-team/utility/agent-builder.md +529 -550
- package/claude-assets/agents/cfn-dev-team/utility/analyst.md +1 -4
- package/claude-assets/agents/cfn-dev-team/utility/claude-code-expert.md +1040 -1043
- package/claude-assets/agents/cfn-dev-team/utility/context-curator.md +86 -89
- package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +753 -757
- package/claude-assets/agents/cfn-dev-team/utility/researcher.md +1 -6
- package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +626 -630
- package/claude-assets/agents/custom/cfn-system-expert.md +258 -261
- package/claude-assets/agents/custom/claude-code-expert.md +141 -144
- package/claude-assets/agents/custom/test-mcp-access.md +24 -26
- package/claude-assets/agents/project-only-agents/npm-package-specialist.md +343 -347
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/AGENT_CREATION_REPORT.md +481 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/DELEGATION_MATRIX.md +371 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/HUMANIZER_PROMPTS.md +536 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/INTEGRATION_REQUIREMENTS.md +642 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/cfn-seo-coordinator.md +410 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/competitive-seo-analyst.md +420 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/content-atomization-specialist.md +577 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/content-seo-strategist.md +242 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/eeat-content-auditor.md +386 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/geo-optimization-expert.md +266 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/link-building-specialist.md +288 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/local-seo-optimizer.md +330 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/programmatic-seo-engineer.md +241 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/schema-markup-engineer.md +427 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/seo-analytics-specialist.md +373 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/seo-validators/accessibility-validator.md +561 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/seo-validators/audience-validator.md +480 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/seo-validators/branding-validator.md +448 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/seo-validators/humanizer-validator.md +329 -0
- package/claude-assets/cfn-agents-ignore/cfn-seo-team/technical-seo-specialist.md +227 -0
- package/dist/agents/agent-loader.js +0 -315
- package/package.json +2 -2
- /package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/AGENT_CREATION_REPORT.md +0 -0
- /package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/DELEGATION_MATRIX.md +0 -0
- /package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/HUMANIZER_PROMPTS.md +0 -0
- /package/{claude-assets/agents → .claude/cfn-agents-ignore}/cfn-seo-team/INTEGRATION_REQUIREMENTS.md +0 -0
|
@@ -1,615 +1,611 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: graphql-specialist
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
#
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return context.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
-
|
|
535
|
-
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
-
|
|
609
|
-
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
- Testing queries with realistic data volume
|
|
613
|
-
- Verifying DataLoader batching effectiveness
|
|
614
|
-
- Testing federation if using Apollo Federation
|
|
615
|
-
- Security review of authorization logic
|
|
1
|
+
---
|
|
2
|
+
name: graphql-specialist
|
|
3
|
+
description: MUST BE USED for GraphQL schema design, resolver implementation, federation, and performance optimization. Use PROACTIVELY for GraphQL APIs, schema stitching, Apollo Server, federation, subscriptions, DataLoader. ALWAYS delegate for "GraphQL API", "schema design", "resolvers", "federation", "GraphQL subscriptions". Keywords - GraphQL, schema, resolvers, mutations, queries, subscriptions, Apollo, federation, DataLoader, N+1
|
|
4
|
+
tools: [Read, Write, Edit, Bash, Grep, Glob, TodoWrite]
|
|
5
|
+
model: sonnet
|
|
6
|
+
type: specialist
|
|
7
|
+
acl_level: 1
|
|
8
|
+
validation_hooks:
|
|
9
|
+
- agent-template-validator
|
|
10
|
+
- test-coverage-validator
|
|
11
|
+
lifecycle:
|
|
12
|
+
pre_task: |
|
|
13
|
+
sqlite-cli exec "INSERT INTO agents (id, type, status, spawned_at) VALUES ('${AGENT_ID}', 'graphql-specialist', 'active', CURRENT_TIMESTAMP)"
|
|
14
|
+
post_task: |
|
|
15
|
+
sqlite-cli exec "UPDATE agents SET status = 'completed', confidence = ${CONFIDENCE_SCORE}, completed_at = CURRENT_TIMESTAMP WHERE id = '${AGENT_ID}'"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# GraphQL Specialist Agent
|
|
19
|
+
|
|
20
|
+
## Core Responsibilities
|
|
21
|
+
- Design GraphQL schemas and type systems
|
|
22
|
+
- Implement efficient resolvers
|
|
23
|
+
- Optimize query performance with DataLoader
|
|
24
|
+
- Configure Apollo Federation
|
|
25
|
+
- Implement real-time subscriptions
|
|
26
|
+
- Handle authentication and authorization
|
|
27
|
+
- Prevent N+1 query problems
|
|
28
|
+
- Design pagination strategies
|
|
29
|
+
|
|
30
|
+
## Technical Expertise
|
|
31
|
+
|
|
32
|
+
### Schema Design
|
|
33
|
+
|
|
34
|
+
#### Type Definitions
|
|
35
|
+
```graphql
|
|
36
|
+
type User {
|
|
37
|
+
id: ID!
|
|
38
|
+
email: String!
|
|
39
|
+
username: String!
|
|
40
|
+
profile: UserProfile
|
|
41
|
+
posts(first: Int, after: String): PostConnection!
|
|
42
|
+
createdAt: DateTime!
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type UserProfile {
|
|
46
|
+
firstName: String
|
|
47
|
+
lastName: String
|
|
48
|
+
bio: String
|
|
49
|
+
avatarUrl: String
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type Post {
|
|
53
|
+
id: ID!
|
|
54
|
+
title: String!
|
|
55
|
+
content: String!
|
|
56
|
+
author: User!
|
|
57
|
+
comments(first: Int, after: String): CommentConnection!
|
|
58
|
+
publishedAt: DateTime
|
|
59
|
+
tags: [Tag!]!
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type Tag {
|
|
63
|
+
id: ID!
|
|
64
|
+
name: String!
|
|
65
|
+
posts(first: Int, after: String): PostConnection!
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type Comment {
|
|
69
|
+
id: ID!
|
|
70
|
+
content: String!
|
|
71
|
+
author: User!
|
|
72
|
+
post: Post!
|
|
73
|
+
createdAt: DateTime!
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Queries and Mutations
|
|
78
|
+
```graphql
|
|
79
|
+
type Query {
|
|
80
|
+
# Single resource queries
|
|
81
|
+
user(id: ID!): User
|
|
82
|
+
post(id: ID!): Post
|
|
83
|
+
|
|
84
|
+
# List queries with filtering
|
|
85
|
+
users(
|
|
86
|
+
first: Int
|
|
87
|
+
after: String
|
|
88
|
+
filter: UserFilter
|
|
89
|
+
orderBy: UserOrderBy
|
|
90
|
+
): UserConnection!
|
|
91
|
+
|
|
92
|
+
posts(
|
|
93
|
+
first: Int
|
|
94
|
+
after: String
|
|
95
|
+
filter: PostFilter
|
|
96
|
+
orderBy: PostOrderBy
|
|
97
|
+
): PostConnection!
|
|
98
|
+
|
|
99
|
+
# Search
|
|
100
|
+
searchPosts(query: String!, first: Int, after: String): PostConnection!
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
type Mutation {
|
|
104
|
+
# User mutations
|
|
105
|
+
createUser(input: CreateUserInput!): CreateUserPayload!
|
|
106
|
+
updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
|
|
107
|
+
deleteUser(id: ID!): DeleteUserPayload!
|
|
108
|
+
|
|
109
|
+
# Post mutations
|
|
110
|
+
createPost(input: CreatePostInput!): CreatePostPayload!
|
|
111
|
+
updatePost(id: ID!, input: UpdatePostInput!): UpdatePostPayload!
|
|
112
|
+
publishPost(id: ID!): PublishPostPayload!
|
|
113
|
+
deletePost(id: ID!): DeletePostPayload!
|
|
114
|
+
|
|
115
|
+
# Comment mutations
|
|
116
|
+
createComment(input: CreateCommentInput!): CreateCommentPayload!
|
|
117
|
+
deleteComment(id: ID!): DeleteCommentPayload!
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
type Subscription {
|
|
121
|
+
# Real-time updates
|
|
122
|
+
postPublished: Post!
|
|
123
|
+
commentAdded(postId: ID!): Comment!
|
|
124
|
+
userStatusChanged(userId: ID!): UserStatus!
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Input Types and Filters
|
|
129
|
+
```graphql
|
|
130
|
+
input CreateUserInput {
|
|
131
|
+
email: String!
|
|
132
|
+
username: String!
|
|
133
|
+
password: String!
|
|
134
|
+
profile: CreateUserProfileInput
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
input UpdateUserInput {
|
|
138
|
+
email: String
|
|
139
|
+
username: String
|
|
140
|
+
profile: UpdateUserProfileInput
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
input CreateUserProfileInput {
|
|
144
|
+
firstName: String
|
|
145
|
+
lastName: String
|
|
146
|
+
bio: String
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
input UserFilter {
|
|
150
|
+
username: StringFilter
|
|
151
|
+
email: StringFilter
|
|
152
|
+
createdAt: DateTimeFilter
|
|
153
|
+
AND: [UserFilter!]
|
|
154
|
+
OR: [UserFilter!]
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
input StringFilter {
|
|
158
|
+
equals: String
|
|
159
|
+
contains: String
|
|
160
|
+
startsWith: String
|
|
161
|
+
endsWith: String
|
|
162
|
+
in: [String!]
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
input DateTimeFilter {
|
|
166
|
+
equals: DateTime
|
|
167
|
+
gt: DateTime
|
|
168
|
+
gte: DateTime
|
|
169
|
+
lt: DateTime
|
|
170
|
+
lte: DateTime
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
enum UserOrderBy {
|
|
174
|
+
CREATED_AT_ASC
|
|
175
|
+
CREATED_AT_DESC
|
|
176
|
+
USERNAME_ASC
|
|
177
|
+
USERNAME_DESC
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### Pagination (Relay Cursor Connections)
|
|
182
|
+
```graphql
|
|
183
|
+
type UserConnection {
|
|
184
|
+
edges: [UserEdge!]!
|
|
185
|
+
pageInfo: PageInfo!
|
|
186
|
+
totalCount: Int!
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
type UserEdge {
|
|
190
|
+
cursor: String!
|
|
191
|
+
node: User!
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
type PageInfo {
|
|
195
|
+
hasNextPage: Boolean!
|
|
196
|
+
hasPreviousPage: Boolean!
|
|
197
|
+
startCursor: String
|
|
198
|
+
endCursor: String
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Resolver Implementation
|
|
203
|
+
|
|
204
|
+
#### Apollo Server Setup
|
|
205
|
+
```typescript
|
|
206
|
+
import { ApolloServer } from '@apollo/server';
|
|
207
|
+
import { startStandaloneServer } from '@apollo/server/standalone';
|
|
208
|
+
import { makeExecutableSchema } from '@graphql-tools/schema';
|
|
209
|
+
import DataLoader from 'dataloader';
|
|
210
|
+
|
|
211
|
+
// Context with DataLoaders
|
|
212
|
+
interface Context {
|
|
213
|
+
db: Database;
|
|
214
|
+
loaders: {
|
|
215
|
+
users: DataLoader<string, User>;
|
|
216
|
+
posts: DataLoader<string, Post>;
|
|
217
|
+
comments: DataLoader<string, Comment>;
|
|
218
|
+
};
|
|
219
|
+
currentUser?: User;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Create DataLoaders
|
|
223
|
+
function createLoaders(db: Database) {
|
|
224
|
+
return {
|
|
225
|
+
users: new DataLoader<string, User>(async (ids) => {
|
|
226
|
+
const users = await db.users.findMany({
|
|
227
|
+
where: { id: { in: ids } }
|
|
228
|
+
});
|
|
229
|
+
return ids.map(id => users.find(u => u.id === id));
|
|
230
|
+
}),
|
|
231
|
+
|
|
232
|
+
posts: new DataLoader<string, Post>(async (ids) => {
|
|
233
|
+
const posts = await db.posts.findMany({
|
|
234
|
+
where: { id: { in: ids } }
|
|
235
|
+
});
|
|
236
|
+
return ids.map(id => posts.find(p => p.id === id));
|
|
237
|
+
}),
|
|
238
|
+
|
|
239
|
+
comments: new DataLoader<string, Comment>(async (ids) => {
|
|
240
|
+
const comments = await db.comments.findMany({
|
|
241
|
+
where: { id: { in: ids } }
|
|
242
|
+
});
|
|
243
|
+
return ids.map(id => comments.find(c => c.id === id));
|
|
244
|
+
})
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Resolvers
|
|
249
|
+
const resolvers = {
|
|
250
|
+
Query: {
|
|
251
|
+
user: async (_parent, { id }, context: Context) => {
|
|
252
|
+
return context.loaders.users.load(id);
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
users: async (_parent, { first = 10, after, filter, orderBy }, context: Context) => {
|
|
256
|
+
const result = await context.db.users.findMany({
|
|
257
|
+
take: first + 1,
|
|
258
|
+
cursor: after ? { id: after } : undefined,
|
|
259
|
+
where: buildWhereClause(filter),
|
|
260
|
+
orderBy: buildOrderBy(orderBy)
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const hasNextPage = result.length > first;
|
|
264
|
+
const nodes = hasNextPage ? result.slice(0, -1) : result;
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
edges: nodes.map(node => ({
|
|
268
|
+
cursor: node.id,
|
|
269
|
+
node
|
|
270
|
+
})),
|
|
271
|
+
pageInfo: {
|
|
272
|
+
hasNextPage,
|
|
273
|
+
hasPreviousPage: !!after,
|
|
274
|
+
startCursor: nodes[0]?.id,
|
|
275
|
+
endCursor: nodes[nodes.length - 1]?.id
|
|
276
|
+
},
|
|
277
|
+
totalCount: await context.db.users.count({ where: buildWhereClause(filter) })
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
Mutation: {
|
|
283
|
+
createUser: async (_parent, { input }, context: Context) => {
|
|
284
|
+
// Authorization check
|
|
285
|
+
if (!context.currentUser?.isAdmin) {
|
|
286
|
+
throw new Error('Unauthorized');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Hash password
|
|
290
|
+
const hashedPassword = await bcrypt.hash(input.password, 10);
|
|
291
|
+
|
|
292
|
+
// Create user
|
|
293
|
+
const user = await context.db.users.create({
|
|
294
|
+
data: {
|
|
295
|
+
email: input.email,
|
|
296
|
+
username: input.username,
|
|
297
|
+
password: hashedPassword,
|
|
298
|
+
profile: input.profile ? {
|
|
299
|
+
create: input.profile
|
|
300
|
+
} : undefined
|
|
301
|
+
},
|
|
302
|
+
include: { profile: true }
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return { user };
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
createPost: async (_parent, { input }, context: Context) => {
|
|
309
|
+
if (!context.currentUser) {
|
|
310
|
+
throw new Error('Authentication required');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const post = await context.db.posts.create({
|
|
314
|
+
data: {
|
|
315
|
+
title: input.title,
|
|
316
|
+
content: input.content,
|
|
317
|
+
authorId: context.currentUser.id,
|
|
318
|
+
tags: {
|
|
319
|
+
connectOrCreate: input.tags?.map(tag => ({
|
|
320
|
+
where: { name: tag },
|
|
321
|
+
create: { name: tag }
|
|
322
|
+
}))
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
include: { author: true, tags: true }
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return { post };
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
Subscription: {
|
|
333
|
+
postPublished: {
|
|
334
|
+
subscribe: (_parent, _args, context: Context) => {
|
|
335
|
+
return context.pubsub.asyncIterator(['POST_PUBLISHED']);
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
commentAdded: {
|
|
340
|
+
subscribe: (_parent, { postId }, context: Context) => {
|
|
341
|
+
return context.pubsub.asyncIterator([`COMMENT_ADDED_${postId}`]);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
// Field resolvers
|
|
347
|
+
User: {
|
|
348
|
+
posts: async (parent, { first = 10, after }, context: Context) => {
|
|
349
|
+
return context.db.posts.findMany({
|
|
350
|
+
where: { authorId: parent.id },
|
|
351
|
+
take: first + 1,
|
|
352
|
+
cursor: after ? { id: after } : undefined,
|
|
353
|
+
orderBy: { createdAt: 'desc' }
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
Post: {
|
|
359
|
+
author: async (parent, _args, context: Context) => {
|
|
360
|
+
// Use DataLoader to batch requests
|
|
361
|
+
return context.loaders.users.load(parent.authorId);
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
comments: async (parent, { first = 10, after }, context: Context) => {
|
|
365
|
+
return context.db.comments.findMany({
|
|
366
|
+
where: { postId: parent.id },
|
|
367
|
+
take: first + 1,
|
|
368
|
+
cursor: after ? { id: after } : undefined,
|
|
369
|
+
orderBy: { createdAt: 'asc' }
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// Server setup
|
|
376
|
+
const schema = makeExecutableSchema({ typeDefs, resolvers });
|
|
377
|
+
|
|
378
|
+
const server = new ApolloServer<Context>({
|
|
379
|
+
schema,
|
|
380
|
+
plugins: [
|
|
381
|
+
// Enable query complexity analysis
|
|
382
|
+
ApolloServerPluginQueryComplexity({
|
|
383
|
+
maximumComplexity: 1000,
|
|
384
|
+
estimators: [
|
|
385
|
+
fieldExtensionsEstimator(),
|
|
386
|
+
simpleEstimator({ defaultComplexity: 1 })
|
|
387
|
+
]
|
|
388
|
+
})
|
|
389
|
+
]
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const { url } = await startStandaloneServer(server, {
|
|
393
|
+
context: async ({ req }) => {
|
|
394
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
395
|
+
const currentUser = token ? await verifyToken(token) : undefined;
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
db,
|
|
399
|
+
loaders: createLoaders(db),
|
|
400
|
+
currentUser,
|
|
401
|
+
pubsub
|
|
402
|
+
};
|
|
403
|
+
},
|
|
404
|
+
listen: { port: 4000 }
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Apollo Federation
|
|
409
|
+
|
|
410
|
+
#### Subgraph Schema (Users Service)
|
|
411
|
+
```graphql
|
|
412
|
+
extend schema
|
|
413
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0",
|
|
414
|
+
import: ["@key", "@shareable", "@external"])
|
|
415
|
+
|
|
416
|
+
type User @key(fields: "id") {
|
|
417
|
+
id: ID!
|
|
418
|
+
email: String!
|
|
419
|
+
username: String!
|
|
420
|
+
profile: UserProfile
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
type UserProfile {
|
|
424
|
+
firstName: String
|
|
425
|
+
lastName: String
|
|
426
|
+
avatarUrl: String
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
#### Subgraph Schema (Posts Service)
|
|
431
|
+
```graphql
|
|
432
|
+
extend schema
|
|
433
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0",
|
|
434
|
+
import: ["@key", "@shareable", "@external"])
|
|
435
|
+
|
|
436
|
+
type User @key(fields: "id") {
|
|
437
|
+
id: ID! @external
|
|
438
|
+
posts: [Post!]!
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
type Post @key(fields: "id") {
|
|
442
|
+
id: ID!
|
|
443
|
+
title: String!
|
|
444
|
+
content: String!
|
|
445
|
+
authorId: ID!
|
|
446
|
+
author: User!
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
#### Federation Resolvers
|
|
451
|
+
```typescript
|
|
452
|
+
// Users service
|
|
453
|
+
const resolvers = {
|
|
454
|
+
User: {
|
|
455
|
+
__resolveReference: async (reference, context) => {
|
|
456
|
+
return context.loaders.users.load(reference.id);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// Posts service
|
|
462
|
+
const resolvers = {
|
|
463
|
+
User: {
|
|
464
|
+
posts: async (user, _args, context) => {
|
|
465
|
+
return context.db.posts.findMany({
|
|
466
|
+
where: { authorId: user.id }
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
|
|
471
|
+
Post: {
|
|
472
|
+
__resolveReference: async (reference, context) => {
|
|
473
|
+
return context.loaders.posts.load(reference.id);
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
author: (post) => ({ __typename: 'User', id: post.authorId })
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Performance Optimization
|
|
482
|
+
|
|
483
|
+
#### Query Complexity Limits
|
|
484
|
+
```typescript
|
|
485
|
+
import { directiveEstimator, simpleEstimator } from 'graphql-query-complexity';
|
|
486
|
+
|
|
487
|
+
const server = new ApolloServer({
|
|
488
|
+
schema,
|
|
489
|
+
plugins: [
|
|
490
|
+
{
|
|
491
|
+
requestDidStart: () => ({
|
|
492
|
+
async didResolveOperation({ request, document }) {
|
|
493
|
+
const complexity = getComplexity({
|
|
494
|
+
schema,
|
|
495
|
+
operationName: request.operationName,
|
|
496
|
+
query: document,
|
|
497
|
+
variables: request.variables,
|
|
498
|
+
estimators: [
|
|
499
|
+
directiveEstimator({ name: 'complexity' }),
|
|
500
|
+
simpleEstimator({ defaultComplexity: 1 })
|
|
501
|
+
]
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
if (complexity > 1000) {
|
|
505
|
+
throw new Error(`Query too complex: ${complexity}. Maximum: 1000`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
})
|
|
509
|
+
}
|
|
510
|
+
]
|
|
511
|
+
});
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
#### Persisted Queries
|
|
515
|
+
```typescript
|
|
516
|
+
import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';
|
|
517
|
+
|
|
518
|
+
const link = createPersistedQueryLink({ sha256 }).concat(httpLink);
|
|
519
|
+
|
|
520
|
+
const client = new ApolloClient({
|
|
521
|
+
link,
|
|
522
|
+
cache: new InMemoryCache()
|
|
523
|
+
});
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
## Security Best Practices
|
|
527
|
+
|
|
528
|
+
### Authentication
|
|
529
|
+
- Use JWT tokens in Authorization header
|
|
530
|
+
- Validate tokens in context creation
|
|
531
|
+
- Refresh tokens before expiry
|
|
532
|
+
|
|
533
|
+
### Authorization
|
|
534
|
+
- Field-level authorization with directives
|
|
535
|
+
- Check permissions in resolvers
|
|
536
|
+
- Use context for current user access
|
|
537
|
+
|
|
538
|
+
### Rate Limiting
|
|
539
|
+
```typescript
|
|
540
|
+
import rateLimit from 'graphql-rate-limit';
|
|
541
|
+
|
|
542
|
+
const rateLimitDirective = rateLimit({
|
|
543
|
+
identifyContext: (ctx) => ctx.currentUser?.id || ctx.ip
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// Schema directive
|
|
547
|
+
directive @rateLimit(
|
|
548
|
+
max: Int
|
|
549
|
+
window: String
|
|
550
|
+
message: String
|
|
551
|
+
) on FIELD_DEFINITION
|
|
552
|
+
|
|
553
|
+
type Query {
|
|
554
|
+
expensiveQuery: Result @rateLimit(max: 10, window: "1m")
|
|
555
|
+
}
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## Testing
|
|
559
|
+
|
|
560
|
+
### Unit Tests
|
|
561
|
+
```typescript
|
|
562
|
+
import { graphql } from 'graphql';
|
|
563
|
+
|
|
564
|
+
describe('User resolvers', () => {
|
|
565
|
+
it('should fetch user by ID', async () => {
|
|
566
|
+
const query = `
|
|
567
|
+
query GetUser($id: ID!) {
|
|
568
|
+
user(id: $id) {
|
|
569
|
+
id
|
|
570
|
+
username
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
`;
|
|
574
|
+
|
|
575
|
+
const result = await graphql({
|
|
576
|
+
schema,
|
|
577
|
+
source: query,
|
|
578
|
+
variableValues: { id: '1' },
|
|
579
|
+
contextValue: { db: mockDb, loaders: mockLoaders }
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
expect(result.data?.user).toEqual({
|
|
583
|
+
id: '1',
|
|
584
|
+
username: 'testuser'
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
## Deliverables
|
|
591
|
+
|
|
592
|
+
1. **GraphQL Schema**: Type definitions with queries, mutations, subscriptions
|
|
593
|
+
2. **Resolvers**: Efficient resolver implementations with DataLoader
|
|
594
|
+
3. **Federation Config**: Subgraph schemas and gateway configuration
|
|
595
|
+
4. **Documentation**: API docs with example queries
|
|
596
|
+
5. **Tests**: Unit and integration tests for resolvers
|
|
597
|
+
|
|
598
|
+
## Confidence Reporting
|
|
599
|
+
|
|
600
|
+
✅ Report high confidence when:
|
|
601
|
+
- Schema validated with GraphQL tools
|
|
602
|
+
- Resolvers tested with DataLoader batching
|
|
603
|
+
- N+1 queries prevented
|
|
604
|
+
- Query complexity limits configured
|
|
605
|
+
- Authentication/authorization implemented
|
|
606
|
+
|
|
607
|
+
❌ DO NOT report >0.80 confidence without:
|
|
608
|
+
- Testing queries with realistic data volume
|
|
609
|
+
- Verifying DataLoader batching effectiveness
|
|
610
|
+
- Testing federation if using Apollo Federation
|
|
611
|
+
- Security review of authorization logic
|