/stitch — Cross-repo stitch layer
Generate a cross-repo binding artifact (STITCH.md) for a product group (backend + one or more frontends). Tables only, not narrative. Drift detection uses CODEMAP endpoint sections by default with explicit opt-in fallback to grep.
Step 0: Load config
<!-- shared-block: group-loader -->Read ~/.claude/aria-knowledge.local.md. Parse YAML frontmatter projects_groups (multi-line YAML block — see CONFIG.md "Skill-only fields" for canonical schema, including the optional stitch_path sub-field and custom-role conventions).
Look up <tag> in projects_list (get project_root) and projects_groups (get role → folder dict).
- If
<tag>missing fromprojects_list: stop with "unknown project tag: <tag>". - If
<tag>inprojects_listbut missing fromprojects_groupsand<project_root>has multiple distinct codebases that must stay in sync (separate repo-marker sub-dirs, OR one repo with a shared-contract source + multiple generated/typed clients — see scan below): trigger auto-propose bootstrap. The git-repo boundary is NOT the signal — a monorepo with acontract/→ios/+android/+backend/seam qualifies just as much as separate repos. - If
<tag>is a single undifferentiated codebase (no separate sub-dirs and no contract→multi-client seam): load<project_root>/CODEMAP.mdonly.
Auto-propose bootstrap (when projects_groups[<tag>] is missing but <project_root> contains multiple sync-bound codebases — separate repo dirs or a contract→clients seam):
- Scan
<project_root>one level deep for sub-directories with repo or contract markers:openapi.{yaml,yml,json}/*.proto/schema.graphql(or a dir namedcontract/contracts/api-spec/proto) →contract(the shared source clients are generated from — its drift is what STITCH tracks)manage.py+settings.py→backend(Django)composer.json+artisan→backend(Laravel)Gemfilewithrails→backend(Rails)package.jsonwithexpress/fastify/nestjs→backend(Node)pyproject.toml/requirements.txtwithfastapi/pydantic→backend(FastAPI)Package.swift/*.xcodeproj/ aniosdir →ios(Swift/SwiftUI)build.gradle{,.kts}with anandroiddir →android(Kotlin/Android)next.config.*→web(Next.js)app.json+expoin package.json →mobile(Expo)package.jsonwithreact(nonext/expo) →web(React SPA)- other
package.json→ prompt user for role name
- Handle role conflicts: if two dirs inferred as same role, prompt user to assign distinct keys (
web,web-admin, etc.). - Propose the group structure to user: sub-repo names, inferred roles, YAML block to insert. Show a preview diff of the change to
~/.claude/aria-knowledge.local.md. - On approval, edit the config file to add the
projects_groups[<tag>]entry, preserving existing fields and YAML structure. - On decline, stop with "proceed after registering group manually".
Resolve each (role, folder) pair to absolute path: <project_root>/<folder>. For each absolute path, read CODEMAP.md if it exists. Read <project_root>/STITCH.md if it exists. Return resolved path map + warnings for any missing CODEMAPs.
For /stitch specifically: the group MUST have ≥2 distinct codebases bound by a shared contract — at least one contract/backend source role + at least one client role that must stay in sync with it. Whether they live in separate git repos or one monorepo is irrelevant — the load-bearing condition is "multiple codebases that drift apart," not "multiple repos." A monorepo's contract/ → ios/+android/+backend/ seam (the dual-native keystone — one OpenAPI/proto/GraphQL source feeding generated clients) is exactly the drift seam STITCH exists to document. Only stop when there's a single undifferentiated codebase with no such seam: "/stitch needs ≥2 contract-bound codebases; this looks like one codebase — use /codemap."
Step 1: Resolve paths & output target
BACKEND_ROOT=<project_root>/<backend folder>(the one role=backend entry)FRONTEND_ROOTS= list of<project_root>/<folder>for all non-backend rolesSTITCH_FILE=<project_root>/STITCH.mdby default. Override: ifprojects_groups[<tag>]contains astitch_pathfield, use that (relative to<project_root>).
For create mode, require BACKEND_ROOT/CODEMAP.md and each frontend_root/CODEMAP.md. If any missing, list what's missing and recommend running /codemap create in each affected repo first.
Step 2: Load template (create mode only)
Start from ${CLAUDE_PLUGIN_ROOT}/template/stitch/STITCH.template.md. Fill Group identity with:
- Group tag
- Backend repo folder name +
git rev-parse HEADif git available - Frontend repo folder names + revisions
- CODEMAP absolute paths for each repo
- Configured
STITCH_FILEpath
Step 3: Build sections 2–5 (create + section modes)
Using the loaded CODEMAPs, populate:
- 2. Auth stitch — token path FE → BE. Source: FE auth slice/hook + BE auth middleware/JWT handler. Table rows: step | location (file) | notes. Mermaid optional, keep minimal.
- 3. Endpoint stitch — union of FE RTK/fetch calls → BE routes. Normalize paths (strip env prefixes, trailing slashes). Table columns: FE hook/client | HTTP method | FE file | Path | BE urls module | View/handler | Permission | Notes.
- 4. Entity stitch — when traceable from CODEMAP model/serializer/type tables. Columns: Domain | FE type/schema | BE serializer | Model | Notes.
- 5. Integration stitch — external services from backend CODEMAP's Integrations section; note FE usage where mentioned. Columns: Service | Env keys | Owner repo | Files | FE usage.
Only populate cells with information that appears in the loaded CODEMAPs. Leave cells blank rather than inventing.
Step 4: Drift log (create + diff modes)
Precedence (check in order):
-
User-provided script — check for
<workspace_root>/analyze-stitch.shor<workspace_root>/analyze-stitch.py. If either exists, invoke with JSON stdin:{"backend_root": "<abs path>", "frontend_roots": ["<abs path>", ...], "group": "<tag>"}Expect JSON stdout:
{"fe_orphans": [{"call": "...", "file": "..."}, ...], "be_orphans": [{"route": "...", "file": "..."}, ...]}Label output section: "Drift source: user script (analyze-stitch.)"*.
-
CODEMAP-based (default expected path) — check both CODEMAPs for required endpoint sections:
- Backend: look for URLConf tree section (match heading like
## N. URLConfor similar). Parse endpoint rows. - Frontend: look for API client / RTK Query / endpoint table section. Parse endpoint definitions.
- If both present → normalize to
method + pathtuples, diff the sets. Label: "Drift source: CODEMAPs (sections: <backend section name>, <frontend section name>)".
- Backend: look for URLConf tree section (match heading like
-
Missing CODEMAP endpoint data — prompt user explicitly (do NOT silently fall through):
STITCH drift detection requires endpoint sections in both CODEMAPs. Currently missing: - <backend_path>/CODEMAP.md: <missing section name> - <frontend_path>/CODEMAP.md: <missing section name> (if applicable) Recommended: run `/codemap section <missing section>` in the affected repo(s) first (better accuracy, self-improving as you maintain CODEMAPs). Fallback: proceed with grep-based drift (coarse — catches presence/absence, misses HTTP methods, dynamic paths, non-REST conventions). Output will be labeled "Drift source: fallback grep." Choose: [C]odemap (stop here, regenerate first) / [G]rep fallback (proceed now) -
On [G]rep fallback — grep FE for
/api/strings (andapi/v1/,apiSlice,fetch(variants), grep backend for route definitions (Djangourls.pypatterns, or equivalent). Compare normalized sets. L