Write Path Mapping
ultrathink
Output path directive (canonical — overrides in-body references).
All file outputs from this skill MUST be written under .anthril/reports/write-path-mapping/.
Run mkdir -p .anthril/reports/write-path-mapping before the first Write call.
Primary artefact: .anthril/reports/write-path-mapping/<artefact>.
Do NOT write to the project root or to bare filenames at cwd.
Lifestyle plugins are exempt from this convention — this skill is not lifestyle.
Before You Start
- Locate the target. Use
$ARGUMENTS if provided, otherwise the current working directory. If neither resolves to a real directory, ask the user for the target path before continuing.
- Detect the stack. Run
scripts/detect-stack.sh to identify languages, frameworks, and monorepo layout. This determines which framework matrix to apply in Phase 2.
- Detect the persistence layer. Run
scripts/detect-db.sh to identify Supabase, Prisma, Drizzle, TypeORM, SQLAlchemy, Django ORM, ActiveRecord, Eloquent, Doctrine, Redis, etc. This determines which mutation matrix to apply in Phase 5.
- Check tooling availability. Run
scripts/check-tools.sh. Missing tools degrade depth but never abort the mapping — ripgrep is the only strongly recommended tool.
- Load
.write-path-ignore. If the target contains this file, parse it and treat entries as suppression rules during Phase 8.
- Map project structure. Inventory the codebase excluding
node_modules/, .venv/, venv/, target/, dist/, build/, .next/, .nuxt/, coverage/, .git/, .turbo/.
User Context
$ARGUMENTS
Detected stack: !bash "${CLAUDE_PLUGIN_ROOT}/skills/write-path-mapping/scripts/detect-stack.sh" .
Detected persistence: !bash "${CLAUDE_PLUGIN_ROOT}/skills/write-path-mapping/scripts/detect-db.sh" .
Tool availability: !bash "${CLAUDE_PLUGIN_ROOT}/skills/write-path-mapping/scripts/check-tools.sh" .
Candidate write entries (fast ripgrep seed): !bash "${CLAUDE_PLUGIN_ROOT}/skills/write-path-mapping/scripts/find-write-endpoints.sh" .
Mapping Phases
Execute every phase in order. For each path, record: entry (type, file, line, verb, route, framework), middleware chain, validator, auth layer, handler, persistence targets, fan-out count, downstream effects, risks, verification depth. Use the rubric in ${CLAUDE_PLUGIN_ROOT}/skills/write-path-mapping/reference.md §7 for completeness scoring. Never skip a phase — mark as N/A if genuinely not applicable to the detected stack.
Read-only guarantee. This skill never modifies source files. It emits three new artifacts into the target project:
write-path-map.md — the main report
write-path-map.json — the JSON sidecar
risk-register.md — the standalone risk register
Completeness, not quality. The completeness score measures how thoroughly the skill traced the system, NOT how good the system is. A clean codebase and a messy codebase can both score 100%. System quality is captured separately in the Risk Register.
Phase 1: Discovery & Inventory (context only — no score)
Objective: Build an accurate picture of the codebase and its persistence layer before any mapping begins.
- Read top-level config:
package.json, pyproject.toml / setup.py / requirements*.txt, go.mod, Cargo.toml, pom.xml / build.gradle*, composer.json, Gemfile, *.csproj / *.sln.
- Detect monorepo layout:
pnpm-workspace.yaml, lerna.json, nx.json, turbo.json, Cargo.toml [workspace], go.work, rush.json. Each package becomes a mapping target.
- Run
scripts/extract-schema.sh to collect tables, columns, and schema file inventory.
- Run
scripts/extract-triggers.sh to collect CREATE TRIGGER, CREATE FUNCTION, and CREATE POLICY statements.
- Run
scripts/extract-cron.sh to collect scheduled job sources (BullMQ repeat, pg_cron, Celery beat, Rails whenever, GitHub Actions schedule, Vercel Cron, etc.).
- Run
scripts/extract-queues.sh to collect queue producers and consumers (BullMQ, SQS, Kafka, NATS, Celery, Sidekiq, Supabase queue, Cloudflare Queues).
- Live DB enrichment (optional): run
scripts/live-db-probe.sh. If Supabase MCP is configured or DATABASE_URL is set, enrich the schema data with live pg_policies, pg_trigger, and information_schema queries. When Supabase MCP is available, prefer the mcp__*Supabase__execute_sql (SELECT only) and mcp__*Supabase__list_tables tools over psql. If no live source is available, continue with static data.
- Load
.write-path-ignore if present. Surface unjustified entries and stale patterns as Phase 1 warnings.
This phase produces the context block at the top of the report. No completeness score of its own — phase 1 failures become adjustments to overall completeness.
Phase 2: Entry-Point Discovery
Objective: Find every write entry point in the project.
- Consume the seed list from
scripts/find-write-endpoints.sh (already run in User Context).
- Optionally run
python3 scripts/ast-entrypoints.py for richer AST-level extraction. Its output is merged with the seed list via scripts/normalize-findings.py.
- Classify every candidate using the taxonomy in
reference.md §1.
- MANDATORY sub-agent sweep when >30 candidates. Partition the seed list by top-level domain folder (e.g.
app/, src/api/, supabase/functions/, workers/, each monorepo package) and spawn one Agent(subagent_type=Explore) per domain in parallel (multiple tool calls in one assistant message). Each sub-agent receives the prompt shape from reference.md §9 and returns a JSON array. Merge via scripts/normalize-findings.py.
- Deduplicate by
(file, line, verb).
- Drop entries that are clearly read-only (GET routes, pure-query GraphQL queries, reader RPCs).
Phase completeness = traced_entries / discovered_candidates × 100.
Phase 3: Middleware & Auth Layer Capture
Objective: For every entry, capture the middleware chain and the auth layer.
- For each entry, walk the router registration back to the framework bootstrap and record every
use(), decorator, guard, before_action, Depends(...), middleware alias, or firewall entry. Use the matrix in reference.md §5.
- Resolve framework-specific auth: Next.js
middleware.ts, NestJS @UseGuards(AuthGuard), FastAPI Depends(get_current_user), Django @login_required / permission classes, Rails before_action :authenticate_user!, Laravel ->middleware('auth'), Symfony firewalls.
- Supabase RLS cross-reference. For Supabase projects, run
scripts/rls-policy-check.sh and intersect each write target table with the RLS policies discovered in Phase 1. Tables with no policy covering the relevant role become missing-rls candidates.
- Tentatively flag risks (final resolution in Phase 7):
- Entry with no resolvable auth layer →
unauth-write
- Webhook entry with no signature verification →
unverified-webhook
- Edge function using
SUPABASE_SERVICE_ROLE_KEY to write on behalf of user input → service-role-overreach
- Record the middleware chain in each path's
middleware array.
Phase completeness = entries_with_auth_layer_recorded / entries × 100.
Phase 4: Validator Detection
Objective: Confirm each entry validates its input.
- Detect the project's validator libraries (Zod, Yup, Joi, class-validator, Valibot, ArkType, Pydantic, DRF serializers, Go struct tags, PHP voters, Rails strong parameters).
- For each entry, walk the first N lines of the handler for a
.parse(...), .validate(...), @Body(new ValidationPipe()), class SomeDto, schema=ModelSerializer, request.validate(...), or struct-binding pattern.
- Record the validator in
path.validator = { lib, schema, file, line }. If none is found, tentatively flag missing-validation (confirmed in Phase 7).
- Where possible, cross-reference validator fields against target table columns to detect fields accepted without validation.
Phase completeness = entries_with_validator_recorded_or_confirmed_none / entries × 100.
Phase 5: Handler & Persistence Trace
Objective: For every entry, walk the handler into every persistence target.
For each handler, resolve imports transitively and enumerate every call matching the persistence matrix in reference.md §4. Start with python3 scripts/ast-write-calls.py on the handler file (and the files it imports). Record each target: kind, target, file, line.
Run python3 scripts/transaction-boundary-check.py on the handler file(s) and merge in_transaction flags into the target records.
MANDATORY sub-agent sweep when any of:
- Project has >30 entries total.
- Any single handler reaches 3+ service-layer hops (e.g.
route → service → repo → util).
- The stack is polyglot (JS frontend + Python worker + Go gateway).
Partition handlers into batches of 10 and spawn parallel Agent(subagent_type=Explore) calls. Each sub-agent receives the prompt shape from reference.md §9 Phase 5 and returns JSON per handler matching the path block of templates/paths-schema.json.
Merge all outputs via scripts/normalize-findings.py.
Count fan_out_count = len(persistence_targets) for every path.
Paths with zero targets are reclassified as read-only and dropped from the write map (surfaced in §11 Suppressed Paths).
Phase completeness = entries_with_at_least_one_persistence_target / entries × 100. Depth = % of handlers where every delegate was resolved vs. stopped at dynamic dispatch.
Phase 6: Async / Fan-Out / DB-Side Effects
Objective: Map non-obvious writes that run after the initial handler returns.
- Queue fan-out. For every
queue-publish found in Phase 5, locate the consumer. If not found in the immediate directory, spawn one sub-agent per unresolved queue with the Phase 6 playbook prompt in reference.md §9. Cross-package search includes ../cloudflare-workers/, ../workers/, docker-compose services, and supabase/functions/. Record consumers as secondary entry points and run Phases 2–5 on them.
- Cron / scheduled jobs. Using the
extract-cron.sh output from Phase 1, treat every scheduled job as a secondary entry point and run Phases 2–5 on its handler.
- DB trigger chains. For every table touched in Phase 5, look up triggers from the
extract-triggers.sh output. For each trigger, record its target table(s) as downstream_effects of kind db-trigger. If there are >5 triggers on the project's schema, spawn one sub-agent to walk the full trigger graph.
- Domain events. For every
event-emit found in Phase 5, locate the subscribers. Record as downstream_effects of kind event-subscriber.
- Supabase realtime broadcasts. For every
channel.send({type: 'broadcast'}) found, note the channel as a downstream effect.
- Orphan detection. Queues with producers but no consumer →
orphan-queue-consumer (HIGH). Consumers with no producer → orphan-queue-publish (LOW). Triggers referencing functions that no longer exist → dead-trigger (HIGH).
Phase completeness = resolved_async_targets / discovered_async_targets × 100.
Phase 7: Risk Analysis
Objective: Walk every mapped path through the risk taxonomy and record evidence.
For each path, check every risk subtype from reference.md §6. Record evidence per flagged risk. Apply context-aware severity adjustments:
unauth-write on a path protected by RLS → downgrade to HIGH. Note the RLS mitigation in evidence.
missing-transaction where only one persistence target exists → suppress.
missing-rls confirmed via rls-policy-check.sh → CRITICAL.
cross-tenant-leak detected when a Supabase/ORM write omits workspace_id (or equivalent) on a workspace-scoped table → CRITICAL.
dynamic-dispatch-write → always INFO. The skill cannot evaluate dynamic dispatch; the human reviewer must.
fan-out-write with ≥3 targets but all inside a transaction → INFO. Without a transaction → HIGH.
Deep-dive sub-agents (optional). For any CRITICAL-flagged path with ≥3 middleware layers, spawn one Agent(subagent_type=Explore) to deep-verify the finding before publishing it. The sub-agent confirms whether a compensating control (RLS policy, signature verification, rate limit, tenancy filter) exists elsewhere in the chain and returns {confirmed, evidence, severity_adjustment}.
Phase completeness = paths_with_all_risks_checked / paths × 100.
Phase 8: Reporting
Objective: Produce the final artifact set.
- Merge all findings. Run
python3 scripts/normalize-findings.py one final time to produce the unified write-path-map.json under the target project root.
- Render the main report. Use
templates/output-template.md as the structural skeleton. The report must include:
- Header table (date, stack, persistence, totals, risks by severity, completeness, tier)
- §1 Executive summary (top paths by fan-out, top risks, top data-domain hotspots)
- §2 Stack & persistence
- §3 Write paths by severity (CRITICAL/HIGH/MEDIUM/INFO/OK)
- §4 Write paths by domain
- §5 Per-endpoint detail blocks (top 20, ordered severity then fan-out)
- §6 Data-domain write map (tables, caches, queues, external APIs, file stores)
- §7 Risk register (inline copy)
- §8 Suggested
.write-path-ignore entries
- §9 Visual artifacts (all four Mermaid diagrams)
- §10 JSON sidecar pointer with example
- §11 Suppressed paths
- Render the Mermaid diagrams. Run
python3 scripts/mermaid-render.py write-path-map.json --out diagrams.md and paste the four diagrams into §9 of the main report. All four are required:
- A. System write flowchart
- B. Per-endpoint sequence diagrams (top 20)
- C. Data-domain write map (bipartite)
- D. DB trigger / function graph
- Render the standalone risk register. Use
templates/risk-register-template.md. Write to risk-register.md alongside the main report. Cross-link from §7 of the main report.
- Write artifacts. Using the
Write tool, create write-path-map.md, write-path-map.json, and risk-register.md at the project root (or under .claude/ if the project has one). Never overwrite an existing file without asking first.
- Emit completeness summary. Print the final tier and score to the chat response. Surface any phase that scored <80% as a known gap the user should rerun.
Phase completeness = required_report_sections_rendered / 11 × 100.
Completeness Summary
| Phase |
Focus |
Weight |
| Phase 1 — Discovery |
Schema, triggers, cron, queues, ignore file |
Phase-1 adjustments |
| Phase 2 — Entry points |
Every write entry found |
Coverage |
| Phase 3 — Middleware & auth |
Auth layer recorded per entry |
Depth |
| Phase 4 — Validators |
Input validation recorded |
Depth |
| Phase 5 — Persistence trace |
Targets + transaction state |
Coverage + depth |
| Phase 6 — Async / triggers |
Queue consumers, cron, triggers |
Coverage |
| Phase 7 — Risk analysis |
Every risk subtype walked |
Depth |
| Phase 8 — Reporting |
All required sections rendered |
Depth |
Tiers:
- 95–100 — FULLY MAPPED
- 80–94 — MOSTLY MAPPED (gaps listed)
- 60–79 — PARTIALLY MAPPED
- <60 — INSUFFICIENT — rerun with sub-agents or narrower scope
Important Principles
- The map is a hypothesis backed by trace evidence. Reports describe what the skill observed; they never refactor or delete code.
- This skill never modifies source files. It only emits three new artifacts (
write-path-map.md, write-path-map.json, risk-register.md) at the project root. If those files already exist, ask before overwriting.
- Every mapped path must carry: entry → middleware chain → validator → auth layer → handler → persistence target(s) → downstream effects. A path missing any of these is incomplete.
- Paths without a persistence target are not writes. Drop them or reclassify as reads. Listing GETs in the write map is a bug.
- Completeness measures trace thoroughness, not quality. A 100%-complete map of a broken system is still 100% complete. Quality is captured in the Risk Register.
- DB writes include triggers, functions, policies, and cron jobs. A write path is incomplete until DB-side effects are enumerated via Phase 6.
- Never trust dynamic dispatch silently. Flag
dynamic-dispatch-write wherever a write target is resolved at runtime and leave it as INFO for human review.
- Prefer live DB data when available. Static schema files can be out of sync with the live database. If Supabase MCP or
DATABASE_URL is available, enrich via Phase 1 step 7.
- Respect
.write-path-ignore. Treat entries as load-bearing and surface stale patterns as warnings.
- Sub-agents must be used aggressively. Phase 2 and Phase 5 MUST spawn parallel
Explore agents when the project has >30 candidates or any handler has 3+ service-layer hops. Cutting corners here directly reduces completeness.
- Document tool versions (ripgrep, Python, Node) in the report header so runs are reproducible.
Edge Cases
- Empty / prototype project. If fewer than 10 candidate entries exist, produce a minimal map without spawning sub-agents. Completeness tier "FULLY MAPPED" is achievable on small projects.
- Monorepo. Treat each workspace package as a separate mapping target and produce a per-package map plus a workspace-level rollup. Cross-package writes (a service in package A writing via an import from package B) become fan-out edges.
- Serverless / edge functions. Each function file is an entry point. Cold-start logic (auth clients, DB pool init) counts as middleware.
- GraphQL. Every mutation resolver is an entry point. Subscriptions are NOT writes unless they trigger DB publishes. GraphQL queries are never writes.
- Event-driven / CQRS. Commands are writes. Events emitted from commands are fan-out. Projections that write to read stores are secondary entry points.
- Multi-tenant. Missing
workspace_id / tenant_id filter on a scoped table is cross-tenant-leak CRITICAL. Verify every Supabase/ORM write against the table's column list.
- Background workers. Queue consumers are secondary entry points. Map both the producer and the consumer. Orphans (producer with no consumer, or vice versa) are flagged in Phase 6.
- DB triggers and functions. These are persistence-layer write paths with no application code entry point. Map them in Phase 6 from
extract-triggers.sh. Include them in the DB trigger graph (Diagram D).
- Dynamic SQL. Flag
sqli-risk if the skill detects string interpolation with user input. Flag dynamic-dispatch-write if routing is resolved at runtime. Never attempt to evaluate dynamic SQL — it's the human reviewer's responsibility.
- Generated clients (tRPC codegen, Prisma client, GraphQL codegen). Trace to the generator input (schema, router definition), not the generated output. Add generator directories to the suggested
.write-path-ignore.
- Pure read-only project. If zero write paths are detected, emit a "zero write paths detected" report with only Phase 1 output and exit cleanly. This is a valid outcome, not a failure.
- Unsupported language (Elixir, Haskell, OCaml, Crystal, Zig). Emit "stack not fully supported" in Phase 1 and dump the ripgrep seed list as the map without structural guarantees. Do not fabricate findings.
- Tool failures. If
ast-entrypoints.py or any other helper crashes, record it as a Phase 1 limitation and continue. The skill must never abort on a single-tool failure.
1---2name: write-path-mapping3description: Map the write path of a project across multiple frameworks — entry points, validation, auth, persistence, side-effects. Outputs report, Mermaid diagrams, JSON sidecar. Flags unauth writes, missing RLS, cache gaps. Use for write path, mutation audit, RLS audit.4---5
6# Write Path Mapping
7
8ultrathink
9
10<!-- anthril-output-directive -->
11> **Output path directive (canonical — overrides in-body references).**
12> All file outputs from this skill MUST be written under `.anthril/reports/write-path-mapping/`.
13> Run `mkdir -p .anthril/reports/write-path-mapping` before the first `Write` call.
14> Primary artefact: `.anthril/reports/write-path-mapping/<artefact>`.
15> Do NOT write to the project root or to bare filenames at cwd.
16> Lifestyle plugins are exempt from this convention — this skill is not lifestyle.
17
18## Before You Start
19
201. **Locate the target.** Use `$ARGUMENTS` if provided, otherwise the current working directory. If neither resolves to a real directory, ask the user for the target path before continuing.
212. **Detect the stack.** Run `scripts/detect-stack.sh` to identify languages, frameworks, and monorepo layout. This determines which framework matrix to apply in Phase 2.
223. **Detect the persistence layer.** Run `scripts/detect-db.sh` to identify Supabase, Prisma, Drizzle, TypeORM, SQLAlchemy, Django ORM, ActiveRecord, Eloquent, Doctrine, Redis, etc. This determines which mutation matrix to apply in Phase 5.
234. **Check tooling availability.** Run `scripts/check-tools.sh`. Missing tools degrade depth but never abort the mapping — ripgrep is the only strongly recommended tool.
245. **Load `.write-path-ignore`.** If the target contains this file, parse it and treat entries as suppression rules during Phase 8.
256. **Map project structure.** Inventory the codebase excluding `node_modules/`, `.venv/`, `venv/`, `target/`, `dist/`, `build/`, `.next/`, `.nuxt/`, `coverage/`, `.git/`, `.turbo/`.
26
27## User Context
28
29$ARGUMENTS
30
31Detected stack: !`bash "${CLAUDE_PLUGIN_ROOT}/skills/write-path-mapping/scripts/detect-stack.sh" .`
32
33Detected persistence: !`bash "${CLAUDE_PLUGIN_ROOT}/skills/write-path-mapping/scripts/detect-db.sh" .`
34
35Tool availability: !`bash "${CLAUDE_PLUGIN_ROOT}/skills/write-path-mapping/scripts/check-tools.sh" .`
36
37Candidate write entries (fast ripgrep seed): !`bash "${CLAUDE_PLUGIN_ROOT}/skills/write-path-mapping/scripts/find-write-endpoints.sh" .`
38
39---
40
41## Mapping Phases
42
43Execute every phase in order. For each path, record: entry (type, file, line, verb, route, framework), middleware chain, validator, auth layer, handler, persistence targets, fan-out count, downstream effects, risks, verification depth. Use the rubric in `${CLAUDE_PLUGIN_ROOT}/skills/write-path-mapping/reference.md` §7 for completeness scoring. Never skip a phase — mark as `N/A` if genuinely not applicable to the detected stack.
44
45**Read-only guarantee.** This skill never modifies source files. It emits three new artifacts into the target project:
46
47- `write-path-map.md` — the main report
48- `write-path-map.json` — the JSON sidecar
49- `risk-register.md` — the standalone risk register
50
51**Completeness, not quality.** The completeness score measures how thoroughly the skill traced the system, NOT how good the system is. A clean codebase and a messy codebase can both score 100%. System quality is captured separately in the Risk Register.
52
53---
54
55### Phase 1: Discovery & Inventory (context only — no score)
56
57**Objective:** Build an accurate picture of the codebase and its persistence layer before any mapping begins.
58
591. Read top-level config: `package.json`, `pyproject.toml` / `setup.py` / `requirements*.txt`, `go.mod`, `Cargo.toml`, `pom.xml` / `build.gradle*`, `composer.json`, `Gemfile`, `*.csproj` / `*.sln`.
602. Detect monorepo layout: `pnpm-workspace.yaml`, `lerna.json`, `nx.json`, `turbo.json`, `Cargo.toml [workspace]`, `go.work`, `rush.json`. Each package becomes a mapping target.
613. Run `scripts/extract-schema.sh` to collect tables, columns, and schema file inventory.
624. Run `scripts/extract-triggers.sh` to collect `CREATE TRIGGER`, `CREATE FUNCTION`, and `CREATE POLICY` statements.
635. Run `scripts/extract-cron.sh` to collect scheduled job sources (BullMQ repeat, pg_cron, Celery beat, Rails whenever, GitHub Actions schedule, Vercel Cron, etc.).
646. Run `scripts/extract-queues.sh` to collect queue producers and consumers (BullMQ, SQS, Kafka, NATS, Celery, Sidekiq, Supabase queue, Cloudflare Queues).
657. **Live DB enrichment (optional):** run `scripts/live-db-probe.sh`. If Supabase MCP is configured or `DATABASE_URL` is set, enrich the schema data with live `pg_policies`, `pg_trigger`, and `information_schema` queries. **When Supabase MCP is available, prefer the `mcp__*Supabase__execute_sql` (SELECT only) and `mcp__*Supabase__list_tables` tools over psql.** If no live source is available, continue with static data.
668. Load `.write-path-ignore` if present. Surface unjustified entries and stale patterns as Phase 1 warnings.
67
68This phase produces the context block at the top of the report. No completeness score of its own — phase 1 failures become adjustments to overall completeness.
69
70---
71
72### Phase 2: Entry-Point Discovery
73
74**Objective:** Find every write entry point in the project.
75
761. Consume the seed list from `scripts/find-write-endpoints.sh` (already run in User Context).
772. Optionally run `python3 scripts/ast-entrypoints.py` for richer AST-level extraction. Its output is merged with the seed list via `scripts/normalize-findings.py`.
783. Classify every candidate using the taxonomy in `reference.md` §1.
794. **MANDATORY sub-agent sweep when >30 candidates.** Partition the seed list by top-level domain folder (e.g. `app/`, `src/api/`, `supabase/functions/`, `workers/`, each monorepo package) and spawn one `Agent(subagent_type=Explore)` per domain **in parallel** (multiple tool calls in one assistant message). Each sub-agent receives the prompt shape from `reference.md` §9 and returns a JSON array. Merge via `scripts/normalize-findings.py`.
805. Deduplicate by `(file, line, verb)`.
816. Drop entries that are clearly read-only (GET routes, pure-query GraphQL queries, reader RPCs).
82
83Phase completeness = `traced_entries / discovered_candidates` × 100.
84
85---
86
87### Phase 3: Middleware & Auth Layer Capture
88
89**Objective:** For every entry, capture the middleware chain and the auth layer.
90
911. For each entry, walk the router registration back to the framework bootstrap and record every `use()`, decorator, guard, `before_action`, `Depends(...)`, middleware alias, or firewall entry. Use the matrix in `reference.md` §5.
922. Resolve framework-specific auth: Next.js `middleware.ts`, NestJS `@UseGuards(AuthGuard)`, FastAPI `Depends(get_current_user)`, Django `@login_required` / permission classes, Rails `before_action :authenticate_user!`, Laravel `->middleware('auth')`, Symfony firewalls.
933. **Supabase RLS cross-reference.** For Supabase projects, run `scripts/rls-policy-check.sh` and intersect each write target table with the RLS policies discovered in Phase 1. Tables with no policy covering the relevant role become `missing-rls` candidates.
944. **Tentatively flag risks** (final resolution in Phase 7):
95 - Entry with no resolvable auth layer → `unauth-write`
96 - Webhook entry with no signature verification → `unverified-webhook`
97 - Edge function using `SUPABASE_SERVICE_ROLE_KEY` to write on behalf of user input → `service-role-overreach`
985. Record the middleware chain in each path's `middleware` array.
99
100Phase completeness = `entries_with_auth_layer_recorded / entries` × 100.
101
102---
103
104### Phase 4: Validator Detection
105
106**Objective:** Confirm each entry validates its input.
107
1081. Detect the project's validator libraries (Zod, Yup, Joi, class-validator, Valibot, ArkType, Pydantic, DRF serializers, Go struct tags, PHP voters, Rails strong parameters).
1092. For each entry, walk the first N lines of the handler for a `.parse(...)`, `.validate(...)`, `@Body(new ValidationPipe())`, `class SomeDto`, `schema=ModelSerializer`, `request.validate(...)`, or struct-binding pattern.
1103. Record the validator in `path.validator = { lib, schema, file, line }`. If none is found, tentatively flag `missing-validation` (confirmed in Phase 7).
1114. Where possible, cross-reference validator fields against target table columns to detect fields accepted without validation.
112
113Phase completeness = `entries_with_validator_recorded_or_confirmed_none / entries` × 100.
114
115---
116
117### Phase 5: Handler & Persistence Trace
118
119**Objective:** For every entry, walk the handler into every persistence target.
120
1211. For each handler, resolve imports transitively and enumerate every call matching the persistence matrix in `reference.md` §4. Start with `python3 scripts/ast-write-calls.py` on the handler file (and the files it imports). Record each target: `kind`, `target`, `file`, `line`.
1222. Run `python3 scripts/transaction-boundary-check.py` on the handler file(s) and merge `in_transaction` flags into the target records.
1233. **MANDATORY sub-agent sweep** when any of:
124 - Project has >30 entries total.
125 - Any single handler reaches 3+ service-layer hops (e.g. `route → service → repo → util`).
126 - The stack is polyglot (JS frontend + Python worker + Go gateway).
127
128 Partition handlers into **batches of 10** and spawn parallel `Agent(subagent_type=Explore)` calls. Each sub-agent receives the prompt shape from `reference.md` §9 Phase 5 and returns JSON per handler matching the `path` block of `templates/paths-schema.json`.
129
1304. Merge all outputs via `scripts/normalize-findings.py`.
1315. Count `fan_out_count = len(persistence_targets)` for every path.
1326. Paths with zero targets are reclassified as read-only and dropped from the write map (surfaced in §11 Suppressed Paths).
133
134Phase completeness = `entries_with_at_least_one_persistence_target / entries` × 100. Depth = `% of handlers where every delegate was resolved vs. stopped at dynamic dispatch`.
135
136---
137
138### Phase 6: Async / Fan-Out / DB-Side Effects
139
140**Objective:** Map non-obvious writes that run after the initial handler returns.
141
1421. **Queue fan-out.** For every `queue-publish` found in Phase 5, locate the consumer. If not found in the immediate directory, **spawn one sub-agent per unresolved queue** with the Phase 6 playbook prompt in `reference.md` §9. Cross-package search includes `../cloudflare-workers/`, `../workers/`, docker-compose services, and `supabase/functions/`. Record consumers as secondary entry points and run Phases 2–5 on them.
1432. **Cron / scheduled jobs.** Using the `extract-cron.sh` output from Phase 1, treat every scheduled job as a secondary entry point and run Phases 2–5 on its handler.
1443. **DB trigger chains.** For every table touched in Phase 5, look up triggers from the `extract-triggers.sh` output. For each trigger, record its target table(s) as `downstream_effects` of kind `db-trigger`. If there are >5 triggers on the project's schema, **spawn one sub-agent** to walk the full trigger graph.
1454. **Domain events.** For every `event-emit` found in Phase 5, locate the subscribers. Record as `downstream_effects` of kind `event-subscriber`.
1465. **Supabase realtime broadcasts.** For every `channel.send({type: 'broadcast'})` found, note the channel as a downstream effect.
1476. **Orphan detection.** Queues with producers but no consumer → `orphan-queue-consumer` (HIGH). Consumers with no producer → `orphan-queue-publish` (LOW). Triggers referencing functions that no longer exist → `dead-trigger` (HIGH).
148
149Phase completeness = `resolved_async_targets / discovered_async_targets` × 100.
150
151---
152
153### Phase 7: Risk Analysis
154
155**Objective:** Walk every mapped path through the risk taxonomy and record evidence.
156
157For each path, check every risk subtype from `reference.md` §6. Record evidence per flagged risk. Apply context-aware severity adjustments:
158
159- **`unauth-write` on a path protected by RLS** → downgrade to HIGH. Note the RLS mitigation in evidence.
160- **`missing-transaction` where only one persistence target exists** → suppress.
161- **`missing-rls` confirmed via `rls-policy-check.sh`** → CRITICAL.
162- **`cross-tenant-leak` detected when a Supabase/ORM write omits `workspace_id` (or equivalent) on a workspace-scoped table** → CRITICAL.
163- **`dynamic-dispatch-write`** → always INFO. The skill cannot evaluate dynamic dispatch; the human reviewer must.
164- **`fan-out-write` with ≥3 targets but all inside a transaction** → INFO. Without a transaction → HIGH.
165
166**Deep-dive sub-agents (optional).** For any CRITICAL-flagged path with ≥3 middleware layers, spawn one `Agent(subagent_type=Explore)` to deep-verify the finding before publishing it. The sub-agent confirms whether a compensating control (RLS policy, signature verification, rate limit, tenancy filter) exists elsewhere in the chain and returns `{confirmed, evidence, severity_adjustment}`.
167
168Phase completeness = `paths_with_all_risks_checked / paths` × 100.
169
170---
171
172### Phase 8: Reporting
173
174**Objective:** Produce the final artifact set.
175
1761. **Merge all findings.** Run `python3 scripts/normalize-findings.py` one final time to produce the unified `write-path-map.json` under the target project root.
1772. **Render the main report.** Use `templates/output-template.md` as the structural skeleton. The report must include:
178 - Header table (date, stack, persistence, totals, risks by severity, completeness, tier)
179 - §1 Executive summary (top paths by fan-out, top risks, top data-domain hotspots)
180 - §2 Stack & persistence
181 - §3 Write paths by severity (CRITICAL/HIGH/MEDIUM/INFO/OK)
182 - §4 Write paths by domain
183 - §5 Per-endpoint detail blocks (top 20, ordered severity then fan-out)
184 - §6 Data-domain write map (tables, caches, queues, external APIs, file stores)
185 - §7 Risk register (inline copy)
186 - §8 Suggested `.write-path-ignore` entries
187 - §9 Visual artifacts (all four Mermaid diagrams)
188 - §10 JSON sidecar pointer with example
189 - §11 Suppressed paths
1903. **Render the Mermaid diagrams.** Run `python3 scripts/mermaid-render.py write-path-map.json --out diagrams.md` and paste the four diagrams into §9 of the main report. All four are required:
191 - A. System write flowchart
192 - B. Per-endpoint sequence diagrams (top 20)
193 - C. Data-domain write map (bipartite)
194 - D. DB trigger / function graph
1954. **Render the standalone risk register.** Use `templates/risk-register-template.md`. Write to `risk-register.md` alongside the main report. Cross-link from §7 of the main report.
1965. **Write artifacts.** Using the `Write` tool, create `write-path-map.md`, `write-path-map.json`, and `risk-register.md` at the project root (or under `.claude/` if the project has one). Never overwrite an existing file without asking first.
1976. **Emit completeness summary.** Print the final tier and score to the chat response. Surface any phase that scored <80% as a known gap the user should rerun.
198
199Phase completeness = `required_report_sections_rendered / 11` × 100.
200
201---
202
203## Completeness Summary
204
205| Phase | Focus | Weight |
206|---|---|---|
207| Phase 1 — Discovery | Schema, triggers, cron, queues, ignore file | Phase-1 adjustments |
208| Phase 2 — Entry points | Every write entry found | Coverage |
209| Phase 3 — Middleware & auth | Auth layer recorded per entry | Depth |
210| Phase 4 — Validators | Input validation recorded | Depth |
211| Phase 5 — Persistence trace | Targets + transaction state | Coverage + depth |
212| Phase 6 — Async / triggers | Queue consumers, cron, triggers | Coverage |
213| Phase 7 — Risk analysis | Every risk subtype walked | Depth |
214| Phase 8 — Reporting | All required sections rendered | Depth |
215
216**Tiers:**
217
218- **95–100** — FULLY MAPPED
219- **80–94** — MOSTLY MAPPED (gaps listed)
220- **60–79** — PARTIALLY MAPPED
221- **<60** — INSUFFICIENT — rerun with sub-agents or narrower scope
222
223---
224
225## Important Principles
226
227- **The map is a hypothesis backed by trace evidence.** Reports describe what the skill observed; they never refactor or delete code.
228- **This skill never modifies source files.** It only emits three new artifacts (`write-path-map.md`, `write-path-map.json`, `risk-register.md`) at the project root. If those files already exist, ask before overwriting.
229- **Every mapped path must carry:** entry → middleware chain → validator → auth layer → handler → persistence target(s) → downstream effects. A path missing any of these is incomplete.
230- **Paths without a persistence target are not writes.** Drop them or reclassify as reads. Listing GETs in the write map is a bug.
231- **Completeness measures trace thoroughness, not quality.** A 100%-complete map of a broken system is still 100% complete. Quality is captured in the Risk Register.
232- **DB writes include triggers, functions, policies, and cron jobs.** A write path is incomplete until DB-side effects are enumerated via Phase 6.
233- **Never trust dynamic dispatch silently.** Flag `dynamic-dispatch-write` wherever a write target is resolved at runtime and leave it as INFO for human review.
234- **Prefer live DB data when available.** Static schema files can be out of sync with the live database. If Supabase MCP or `DATABASE_URL` is available, enrich via Phase 1 step 7.
235- **Respect `.write-path-ignore`.** Treat entries as load-bearing and surface stale patterns as warnings.
236- **Sub-agents must be used aggressively.** Phase 2 and Phase 5 MUST spawn parallel `Explore` agents when the project has >30 candidates or any handler has 3+ service-layer hops. Cutting corners here directly reduces completeness.
237- **Document tool versions** (ripgrep, Python, Node) in the report header so runs are reproducible.
238
239---
240
241## Edge Cases
242
2431. **Empty / prototype project.** If fewer than 10 candidate entries exist, produce a minimal map without spawning sub-agents. Completeness tier "FULLY MAPPED" is achievable on small projects.
2442. **Monorepo.** Treat each workspace package as a separate mapping target and produce a per-package map plus a workspace-level rollup. Cross-package writes (a service in package A writing via an import from package B) become fan-out edges.
2453. **Serverless / edge functions.** Each function file is an entry point. Cold-start logic (auth clients, DB pool init) counts as middleware.
2464. **GraphQL.** Every mutation resolver is an entry point. Subscriptions are NOT writes unless they trigger DB publishes. GraphQL queries are never writes.
2475. **Event-driven / CQRS.** Commands are writes. Events emitted from commands are fan-out. Projections that write to read stores are secondary entry points.
2486. **Multi-tenant.** Missing `workspace_id` / `tenant_id` filter on a scoped table is `cross-tenant-leak` CRITICAL. Verify every Supabase/ORM write against the table's column list.
2497. **Background workers.** Queue consumers are secondary entry points. Map both the producer and the consumer. Orphans (producer with no consumer, or vice versa) are flagged in Phase 6.
2508. **DB triggers and functions.** These are persistence-layer write paths with no application code entry point. Map them in Phase 6 from `extract-triggers.sh`. Include them in the DB trigger graph (Diagram D).
2519. **Dynamic SQL.** Flag `sqli-risk` if the skill detects string interpolation with user input. Flag `dynamic-dispatch-write` if routing is resolved at runtime. Never attempt to evaluate dynamic SQL — it's the human reviewer's responsibility.
25210. **Generated clients** (tRPC codegen, Prisma client, GraphQL codegen). Trace to the generator input (schema, router definition), not the generated output. Add generator directories to the suggested `.write-path-ignore`.
25311. **Pure read-only project.** If zero write paths are detected, emit a "zero write paths detected" report with only Phase 1 output and exit cleanly. This is a valid outcome, not a failure.
25412. **Unsupported language** (Elixir, Haskell, OCaml, Crystal, Zig). Emit "stack not fully supported" in Phase 1 and dump the ripgrep seed list as the map without structural guarantees. Do not fabricate findings.
25513. **Tool failures.** If `ast-entrypoints.py` or any other helper crashes, record it as a Phase 1 limitation and continue. The skill must never abort on a single-tool failure.