agentleFS
ice-lfernandesice-lfernandes/claude-spring-architect/.claude/skills/graphql-api-architect/SKILL.md

graphql-api-architect

Skill11 starsChanged 7 days agoApache-2.0View on GitHub

Designs the inbound GraphQL adapter of an already-modeled use case — schema-first, Spring for GraphQL on the servlet stack — into the `31-graphql.md` partial: the root fields and the input port each calls (the same port a REST adapter calls), types and nullability, mutation inputs and payloads with `userErrors`, Relay connections with a page ceiling, `@BatchMapping` child fields and the batch ports they ask for, the error map, field-level access, personal data per output field, and — on the project's first GraphQL case — the depth, complexity, timeout, batch-size, introspection, GraphiQL and CORS setup, plus a quota per caller or a concurrency cap when the use case states one. Use when the request involves GraphQL, a schema or `.graphqls` file, a query or mutation, a resolver, `@QueryMapping`, `@BatchMapping`, DataLoader, N+1 in GraphQL, a connection or cursor, GraphQL errors, or a rate limit on the GraphQL endpoint. Piece of the `/new-feature` pipeline: requires `10-domain.md` and a `00-use-case.md` whose `Trigger` names a GraphQL adapter in the given folder — and stops without them.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Fix pagination — § 4: Every list that grows is a connection, forward only unless a client pages backwards.

    line 169
  2. Very specific:

    Fix the batch loads — § 5: Every child field that costs I/O, the batch port it needs — keys in, map out — and what a missing key answers.

    line 172
  3. Very specific:

    Personal data — § 8: Every output field walked through @.claude/rules/logging.md § Masking candidates.

    line 202
  4. Very specific:

    Never a version from memory (@CLAUDE.md invariant 8).

    line 221

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Available specs

!find "${CLAUDE_PROJECT_DIR:-.}/docs/use-cases" -mindepth 1 -maxdepth 1 -type d -name 'UC-*' 2>/dev/null | sort

Empty above → none yet, run /use-case-design first. (find, not an ls glob: under zsh an unmatched glob aborts the command before any fallback runs.)

Target

$ARGUMENTS


GraphQL API Architect

Designs how a use case is exposed over GraphQL: which root fields exist and which input port each calls, what the schema promises and what it may answer null, how a mutation reports what the client can correct, how a list that grows is paged, which child fields load in batches and through which port, what each failure becomes, which field needs its own access check, and — once per project — the limits every operation runs under. What domain-modeling left as an input port, this skill gives a GraphQL shape, next to or instead of the REST shape rest-api-architect gives it.

Entry rule: no GraphQL trigger, no GraphQL design. This skill reads docs/use-cases/UC-NNN-<slug>/00-use-case.md and 10-domain.md and runs only when the Trigger row names inbound GraphQL adapter or inbound REST and GraphQL adapters. Without 10-domain.md, stop and say to run /domain-modeling; with a trigger that names no GraphQL adapter, stop and say so — the protocol is the use case's choice, never this skill's.

Scope rule: queries and mutations over HTTP, nothing else. A case that needs a subscription, a file upload, federation, a repository exposed as a resolver, generated types, or the reactive stack is outside this skill (@.claude/rules/api-graphql.md, the scope paragraph, and references/pitfalls.md § Out of scope). Stop, name which of them the case needs, and record it — the user decides between narrowing the case with use-case-design and deferring it. An operation callable without authentication is the same: the endpoint stays authenticated, and opening it is a design decision outside this run.

Exit rule: it doesn't write code. It emits 31-graphql.md. The schema file, the controller, the records, the resolver and the configuration come from the executor agent, which reads the partial and the templates/ exemplars.

Rule rule: the rules don't live here. Protocol choice, boundary, schema, batching, pagination, errors, access, demand limits, evolution, observability and tests are @.claude/rules/api-graphql.md, which cites @.claude/rules/authorization.md, @.claude/rules/error-handling.md and @.claude/rules/personal-data.md. This skill applies and cites them; it doesn't reproduce them.

How it's invoked

Two paths: /graphql-api-architect by hand, or chained by /new-feature as step 3g, right after the REST step and before security. That's why it does not carry disable-model-invocation — a skill the model can't see is a skill the orchestrator can't call (@.claude/decisions/0007-pipeline-skills-invocation.md). The guard against firing out of order is the entry rule above.

Why this is a skill and not a subagent

Form 1, motivated by axis 1 of the designer's interview: a solution-design step that picks the inbound protocol per case is planned, and without a GraphQL designer that choice had no path and fell back to REST by omission; the maintainer chose to design it ahead of a run that shows the defect, on the pitfalls the official documentation and the library's source record (references/pitfalls.md) — seven of them reproduced only by running the exemplars. The closest rejected form was a GraphQL mode of rest-api-architect: its interview reaches verbs, statuses and Idempotency-Key, not nullability, batching, connections or demand limits, and its norm's territory cannot reach a GraphQL package. A subagent fails the counter-test on all three points: the interview is the task, the reference fits in references/ and templates/, and the partial is short.

Pinned to opus: the partial is what the executor implements verbatim, and the pin holding for the rest of the turn keeps /new-feature on the model that designed it (@.claude/decisions/0081-skill-model-required-per-class.md). Design record: @.claude/decisions/0146-graphql-api-architect-skill.md (this repository only).

Boundary with neighboring skills

PieceDecidesNever decides
use-case-designThe trigger — REST, GraphQL or both — and the Access rowThe schema
domain-modelingThe input ports, their read models, the exceptions and errorCodesAnything about GraphQL
rest-api-architectThe REST operations over the same ports, when the trigger names bothThe GraphQL shape
this skillRoot fields and their ports, types and nullability, inputs, payloads and userErrors, connections, batch loads and the batch ports they ask for, the error map, personal data per output field, the project's GraphQL setup, GraphQL dependencies and configurationThe ports, the access rules, the batch query's statement, the tests' code
security-architectOne access row per root field and per sensitive field, the meta-annotation each uses, the endpoint in the filter chainWhich fields exist
persistence-architectThe batch port's statement and its index, the order's indexThe batch port's existence (§ 5 asks for it)
test-architectWhich test proves each case of § 10, and the schema snapshot test—

Doesn't collide with gof-design-patterns: that skill owns the catalog, this one decides which row shapes the GraphQL layer of the case being designed (step 12b).

Procedure

  1. Read the specs. 00-use-case.md (trigger, Access, Components table, response shape), 10-domain.md (input ports and their signatures, read models, exception table with errorCode), and — when present — 30-rest.md (the REST operations over the same ports). Apply the entry rule and the scope rule. List the operations the case needs, each with the input port it calls.

    A port that returns an entity, or a read model a field would complete lazily, is a divergence, not something to work around here: 10-domain.md is domain-modeling's to change, and the operation stays undesigned until the port returns a complete read model.

  2. Survey — every answer the project already gave is inherited, never re-asked.

    # Boot line and whether Spring for GraphQL is already in the build
    grep -m1 -A1 "<artifactId>spring-boot-starter-parent" pom.xml
    grep -nE "spring-boot-starter-graphql" pom.xml
    # GraphQL code and schema files already in the project
    grep -rlnE "@(QueryMapping|MutationMapping|SchemaMapping|BatchMapping)" --include='*.java' src/main 2>/dev/null
    find src/main/resources/graphql -name '*.graphqls' 2>/dev/null
    # Decisions earlier use cases recorded — the project setup lives in the first one's § 6
    find docs/use-cases -name '31-graphql.md' 2>/dev/null
    # The project's admission setup — a REST case may have written it before any GraphQL case
    grep -rln "AdmissionProperties" --include='*.java' src/ 2>/dev/null

    And the active blueprint's packages.map entry ending in .graphql — the package every controller, record and the wiring go to. No such entry → stop and say which line is missing from the blueprint, instead of choosing a package inside this use case.

    The templates' Boot-side packages (GraphQlSourceBuilderCustomizer, @GraphQlTest) are Boot 4's. On a Boot 3 project the design is the same; record the line in the partial's header so the executor reads the Boot 3 names from the build, never from the template.

    FactInherited fromAsked only when
    Project setup — limits, introspection, GraphiQL, CORSThe first 31-graphql.md § 6No earlier 31-graphql.md — first GraphQL case of the project
    A type an earlier case declared (Order, Money)The schema file and its 31-graphql.md § 2Never — extended additively, never redefined
    Project-wide pieces (GraphQlLimits, GraphQlConfig, GraphQlExceptionResolver)Already in the GraphQL packageFirst GraphQL case
  3. Interview — only what steps 1 and 2 did not settle. AskUserQuestion, at most 4 questions per call and never fewer than 2 real options per question; an axis with one sensible answer is decided and recorded, not asked (@CLAUDE.md § Known pitfalls). Every option states its cost.

    AxisDecidesSkip when
    Which fields and children the client reads — the screens or callers, and what each selects§ 2, and which children are batch loadsThe response shape of 00-use-case.md already lists them
    Does the list grow without bound§ 4 — a connection, and its orderNever a plain list for a growing set
    Each output field that matches a masking candidate — full, reduced or a reference, and who reads it§ 8, and a field-level checkNo field matches
    Does an input update part of an object — may the client clear a field§ 3 — ArgumentValue per fieldThe case creates or acts, never edits fields
    The project setup — internal or public clients, the origins a browser calls from, the slowest port§ 6Inherited
  4. Fix the operations and the schema — § 1 and § 2. One root field per operation, each calling an input port; a query never mutates, a mutation takes one input and returns one payload. Types, fields and nullability per @.claude/rules/api-graphql.md § Schema, names per @.claude/rules/naming.md; a type an earlier case declared is extended, never redeclared. Form: templates/schema.graphqls.example and templates/GraphQlController.java.example.
  5. Fix inputs and payloads — § 3. Shape validation on the input record; every errorCode the use case can raise that the client can correct goes to userErrors, with the input path it concerns. A partial update binds each optional field with ArgumentValue.
  6. Fix pagination — § 4. Every list that grows is a connection, forward only unless a client pages backwards; the order ends in a unique key; the default and maximum page come from § 6.
  7. Fix the batch loads — § 5. Every child field that costs I/O, the batch port it needs — keys in, map out — and what a missing key answers. A batch port 10-domain.md does not declare is a requirement in § 9, never invented as if it existed.
  8. Decide or inherit the project setup — § 6. First GraphQL case: every limit of @.claude/rules/api-graphql.md § Demand limits with its value and its reason — form templates/GraphQlConfig.java.example and templates/application-graphql.yml.example. Later cases: inherited from UC-NNN, and a change only with a reason and a row in ## Impact on approved use cases.

8b. Admission — § 6's Admission rows. Only when 00-use-case.md's Admission row is not none; no row, the rows read none. A limit is never proposed (@.claude/rules/inbound-admission.md § When a limit exists). The project's admission setup — where the quota is enforced, replicas, the caller's key, the 503 wait — is asked exactly as @.claude/skills/rest-api-architect/SKILL.md step 7c asks it, when step 2 found no AdmissionProperties; found, or written by a REST case's 30-rest.md § 8, it is inherited from UC-NNN. Then the case's limits:

LimitWhereWritten
QuotaThe whole GraphQL endpoint, per caller, before the document runs — never per field or per costThe graphql entry of app.admission.quotas, stated and per-replica. The first case to state one sets it; a later case's different number is a CHANGE with a reason, since every operation shares it. The 429 is HTTP, Problem Details, before any GraphQL response — a row in § 7 and a case in § 10
CapThe root field's controller method, @Bulkhead(name = "inbound-<slug>")Size below the scarcest resource the case holds, and why; the SERVICE_BUSY row in § 7 and its case in § 10

The pieces are @.claude/skills/rest-api-architect/templates/AdmissionConfig.java.example and application-admission.yml.example, in the GraphQL package's admission subpackage when the project has no REST adapter.

  1. Fix the error map — § 7. Each exception of 10-domain.md the operations can raise, where it lands (userErrors or top-level), its classification and errorCode, per @.claude/rules/api-graphql.md § Errors. Form: templates/GraphQlExceptionResolver.java.example.
  2. Personal data — § 8. Every output field walked through @.claude/rules/logging.md § Masking candidates; each match gets the recorded decision @.claude/rules/personal-data.md § In transit requires. A field in clear for a narrower audience than the operation's needs a field-level check — a row for security-architect in § 9.
  3. Requirements to other partials — § 9. The batch ports to 10-domain.md; one statement per batch port and the order's index to 20-persistence.md; one access row per root field and per sensitive field to 32-security.md. Each a row, never an edit of another partial.
  4. Test cases, dependencies and configuration — § 10, § 11, § 12. § 10 lists every case @.claude/rules/api-graphql.md § Tests names, by level — slice, HTTP through the real chain, schema snapshot. § 11: the Initializr's graphql dependency and its test starter, version empty — managed by the Boot parent; read the id against the project's Boot when the build lacks it:

    curl -s "https://start.spring.io/dependencies?bootVersion=<boot from step 2>" \
      -H 'Accept: application/vnd.initializr.v2.2+json' | grep -o '"graphql":{[^}]*}'

    Never a version from memory (@CLAUDE.md invariant 8); no network → ask. The executor may write pom.xml for exactly these rows. § 12 lists every property, its value and its owner.

12b. Decide the design patterns of this layer. Run @.claude/skills/gof-design-patterns/SKILL.md § Design-time use over the controllers, records and mappers this case adds. The answer goes into ## Design patterns — none when nothing matches, and absence is not none.

  1. Write the partial. docs/use-cases/UC-NNN-<slug>/31-graphql.md, from templates/graphql-spec.md.example. Thirteen numbered blocks plus ## Design patterns, all mandatory, each written as none when there is nothing — absence is not none.
  2. Report and stop. Path of the file written; each root field with its port; every connection with its ceiling; every batch load and the port it asks for; every field of § 8 in clear with its receiver; § 6 decided or inherited; each § 9 requirement. A port returning an entity, a growing list without a connection, or a child field with I/O and no batch is a finding. Don't invoke anyone else: the next pipeline step reads § 9.

What the partial contains

BlockFixes
1 · OperationsOne row per root field: kind, input port, return, REST counterpart
2 · SchemaTypes, fields, nullability and why, the read model behind each
3 · Inputs and payloadsInput fields and shape validation, payload, userErrors codes
4 · PaginationPer connection: directions, order with its unique key, default and maximum page, the cursor's keys
5 · Batch loadsPer child field: the batch port (a requirement when new), key, missing-key answer
6 · Project setupEvery limit with its value and reason — or inherited from UC-NNN — and the Admission rows: none, or the setup and this case's quota and cap (step 8b)
7 · Error mapSituation, where it lands, classification, errorCode
8 · Personal dataPer matching output field: form, receiver and reason
9 · Requirements to other partialsBatch ports, statements and indexes, access rows
10 · Contract test casesEvery case, by level
11 · Declared dependenciesThe starter and its test starter
12 · ConfigurationEvery property, its value, its owner
13 · DeferredSame shape as every partial. none when nothing was deferred
Design patternsEach pattern the controllers, records or mappers adopt — step 12b

Plus ## Impact on approved use cases and ## Implementation order, as in every partial.

The exemplars in templates/ are reference for form, not files to copy. Each was compiled and run against the classpath the Initializr resolved on the day it was written:

The mapper comes in two exemplars — OrderGraphQlMapper inside GraphQlController.java.example, and GraphQlMapStructMapper.java.example beside it — because the mechanism is the project's, not this partial's: the partial says what maps to what, and the executor writes it in the mechanism the root CLAUDE.md's **Mappers: paragraph names (@.claude/rules/mapping.md).

ExemplarShape of
templates/schema.graphqls.exampleThe schema file: nullability, a connection by suffix, a mutation input and payload, UserError
templates/GraphQlController.java.exampleThe input ports and read models it calls, the adapter records, the mapper, @QueryMapping, a connection through ScrollSubrange and Window, @MutationMapping with userErrors, two @BatchMapping, a guarded @SchemaMapping
templates/GraphQlMapStructMapper.java.exampleThe same read model → schema type mapper as a MapStruct interface, for a project whose mechanism is MapStruct: generated records, a Money from two source fields through a @Named method
templates/GraphQlConfig.java.exampleGraphQlLimits, depth, complexity, timeout, batch size, the schema inspection that fails the startup
templates/GraphQlExceptionResolver.java.exampleEach family to a classification and errorCode; null for the rest
templates/application-graphql.yml.exampleIntrospection and GraphiQL by profile, CORS, app.graphql.*
templates/graphql-spec.md.exampleThe partial

The test shapes are test-architect's: GraphQlControllerTest.java.example, GraphQlHttpIT.java.example, GraphQlSchemaCompatibilityTest.java.example.

Contract

Class: design — the territory is skill_classes.design in @.claude/schemas/extensions.json, and ArchHook.java guard enforces it. Writes inside the use case folder and nothing else.

Reads docs/use-cases/UC-NNN-<slug>/00-use-case.md (mandatory), 10-domain.md (mandatory), 30-rest.md (when present), the 31-graphql.md of earlier cases, pom.xml, the schema files under src/main/resources/graphql/, the active blueprint's packages.map, the Initializr's metadata, @.claude/rules/api-graphql.md, @.claude/rules/authorization.md, @.claude/rules/error-handling.md, @.claude/rules/personal-data.md, @.claude/rules/logging.md, @.claude/rules/naming.md, @.claude/rules/architecture-ddd.md, and this skill's references/ — plus, with an Admission row, @.claude/rules/inbound-admission.md and the partial that wrote the admission setup.

Writes docs/use-cases/UC-NNN-<slug>/31-graphql.md. Nothing else.

Owns the root fields and the port each calls, the schema's types and nullability, inputs, payloads and userErrors, connections and their ceilings, batch loads and the batch ports they ask for, the GraphQL error map, the personal-data decision per output field, the project's GraphQL setup, the GraphQL endpoint's quota and each root field's cap, and GraphQL dependencies and configuration.

Does not decide the input ports, their read models or the exceptions (10-domain.md), the trigger or the Access row (00-use-case.md), the REST operations (30-rest.md), the access rule of each field and the filter chain (32-security.md), the batch statements and indexes (20-persistence.md), or the tests (40-tests.md). Does not write Java, schema files, pom.xml or application.yml — the executor does, from this partial. Doesn't touch .claude/rules/**.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.