---
description: "Audit an iOS module against the shared coding-standard registry (99 stable-ID rules), produce a remediation plan, then hand off to /multi-agent. Use for a standards pass on a module, or when a review needs rule IDs rather than opinions."
description-tr: "Bir iOS modulunu paylasilan kodlama-standardi registry'sine (99 sabit-ID kural) gore denetler, duzeltme plani cikarir, sonra /multi-agent'e devreder. Bir modulde standart gecisi icin, ya da bir review'un gorus yerine kural ID'si istedigi durumda kullan."
argument-hint: "[module name or path]"
parameters: [{"name":"target","kind":"path"}]
gui: form
destructive: false
confirm: none
allowed-tools: Skill, Bash, Read, Edit, Write, AskUserQuestion
---

# multi-agent ios-coding-standard  -  Module audit → plan → dev handoff

> **Pickers follow** `$HOME/.claude/multi-agent-refs/picker-contract.md`: never a one-option call, and branch on the option selected, not on its text.

**Input**: $ARGUMENTS  -  optionally a module name or path. When absent, Phase 1 discovers and asks.

This routine is the **procedure**. The rules live in the `ai-ios-toolkit:ios-coding-standard` skill, whose registry is
`references/rules.yml` and whose teaching doc is `references/STANDARD.md`. Load that
skill first; Claude Code loads it from the iOS stack plugin, Copilot/Codex from their local copy, so
there is one registry rather than a copy per CLI. Never restate a rule here  -  cite its ID. A rule that is not in the
registry is not a rule; if the audit needs one, propose it as `status: proposed` and say so.

Read-only + planning. This routine never edits source  -  the dev pipeline does.

**Goal.** A developer new to the repo can open any file in the module and understand it without a
guided tour, and nothing sensitive leaks on the way. Rank every finding by "does fixing this
shorten the time to first productive PR, or close a real risk?"

Render assistant-facing prose in `outputLanguage`. External payloads (branch names, plan file,
dev task) stay English.

---

## Phase 1  -  Discover and pick the module

1. Repo root via `git rev-parse --show-toplevel`.
2. **Discover module roots**  -  any directory with a `Package.swift`, a `Sources/` subtree or an
   `.xcodeproj`. Sweep the container dirs that exist (`Domains/`, `Packages/`, `Modules/`,
   `Features/`, `Core/`, `Common/`, top-level module dirs) one level deep, then two if empty.
3. Record per module: name · path · Swift files · lines · governance docs present · SPM targets.
   Keep this **module registry** for the whole run  -  `MOD-*` greps every import against it.
   Classify each module's role, because the role decides which dependency edges are legal:
   **feature** · **core/common** · **seam** (cross-module contracts/bridges/navigation) ·
   **composition root** (legitimately knows every module).
4. If `$ARGUMENTS` resolves to a module, skip the prompt. Otherwise `AskUserQuestion`
   (single-select, `outputLanguage`), documented modules first, each option showing file/line
   counts and `docs: yes/no`. If the list exceeds the question limit, group the smallest into an
   "other" option and ask again  -  never truncate silently.
5. Scope = the whole module's sources across all its targets, unless the user narrows it.

## Phase 2  -  Resolve what applies to THIS module

### 2a. The registry is the standard  -  in-module prose docs are NOT consulted

The skill's `references/rules.yml` (+ a project `modules/<Module>.yml` overlay) is the single source of truth. **Do not read, cite, or
derive rules from a module's own `*-CLAUDE.md`, `docs/` set or any other in-repo prose.** They are
being retired precisely so there is one place a rule can live; consulting them re-creates the
split this command exists to remove. If such a file exists and contradicts the registry, that is a
finding against the file, not against the code.

The only in-repo inputs are **code and manifests**: sources, tests, `Package.swift`, `Info.plist`,
the specs the generator reads, and the module registry from Phase 1.

### 2b. Per-module overlay, then inference

1. **`modules/<Module>.yml`** beside the project's own config carries what code cannot state: the vocabulary
   bindings, the module's role, prohibitions that currently have **zero** instances, deliberate
   carve-outs, name locks, and the real verification command. Registry-backed, so its findings may
   be `blocking`.
   *Why prohibitions need to be written down:* inference reads dominant patterns, and a rule
   obeyed everywhere has no counter-example to infer from. A ban at 100% compliance is invisible
   to a scan  -  it survives only if the overlay states it.
2. **Repo-level rules** (root `CLAUDE.md`, contributing docs) apply to every module.
3. **The module's own dominant pattern is its de-facto convention.** Count variants per dimension;
   the minority instances are the finding. Internal consistency beats conformity to a sibling.
4. No majority (even split, single instance) → not a finding. Record an Open Question.

Findings from step 3 are tagged `inferred` and capped at **suggestion**. Only registry- or
overlay-backed rules may be `blocking`.

**When the overlay is missing**, generate a draft from code evidence, print it for confirmation,
and mark every slot it could not derive  -  a frozen UI target, a name lock, a carve-out and an
operational constraint all look like ordinary code from the outside. Do not guess them; list them
as Open Questions.

### 2c. Vocabulary

Bind each slot to a real symbol by grepping the module, then the reference module. **An unbound
slot disables its rules**  -  never invent a name, never import a sibling's.

`HandlerName` · `EventParam` · `CoordinatorType` · `ResultType` / `ErrorType` / `ErrorFactory` ·
`DIResolver` / `DIConfigurator` · `RequestModelSuffix` / `ResponseModelSuffix` / `TransportSuffix` ·
`MapperShape` · `SafeEnumProtocol` / `UnknownCase` · `CopySurface` · `AnalyticsSurface` ·
`A11yIdentifierSource` · `GeneratedServiceRoot` · `SharedComponentsDir` / `SharedEntitiesDir` ·
`MicroComponentLibrary` · `FrozenUITarget` · `DesignTokenNamespaces` · `CalendarDayHelper` ·
`LoggerAPI` · `FileHeaderShape` · `ScreenRoot` · `ScreenRoleSuffixes` · `AsyncStyle` ·
`CompositionRoot` · `SeamLayer` · `ModuleEntrySurface` · `ConcurrencyPosture` · `LintToolchain` ·
`CredentialStore` (the module's Keychain wrapper, if any).

Print the resolved table before scanning so a wrong binding is caught early.

### 2d. Sensitive-data inventory  -  required before any SEC rule runs

The `SEC-*` rules are written against the **data classes** in `references/rules.yml →
sensitive_data_classes`, never against one module's field names. Resolve the module's concrete
instances of each class: grep entities, request/response models, storage calls and analytics
events for the values the module actually handles, and map each to its class.

Produce the inventory as a table  -  **class · concrete symbols · where it enters · required
lifetime (transient / survives-restart / survives-reinstall) · where it is actually stored ·
where it is logged · where it leaves (network, analytics, pasteboard, another module)**.

The **lifetime column is the one that decides SEC-01**, and it is filled by walking
`references/rules.yml → persistence_decision`, not by looking at what the code currently does. Compare it
against the storage column and report both mismatches:
- required transient but persisted anywhere (including the Keychain) → **over-persistence**;
- required to survive a restart but stored outside the Keychain → **under-protection**.

Then generate the SEC lint patterns *from this inventory*, so the checks match this module's real
vocabulary. A class with no instance here yields no finding; a symbol you cannot classify, or one
whose required lifetime nobody can state, is an Open Question, not a silent pass.

This table is a deliverable in its own right, independent of whether any violation is found.

## Phase 3  -  Scan

Apply the rules in `references/rules.yml` that survived Phase 2 binding, plus the module's own `validation`
gate. Tag every finding: **rule ID · severity · source (`registry` / `doc` / `inferred`) ·
onboarding impact (high/medium/low)**.

### Coverage gate  -  on disk, not in memory

Before scanning, write `.<module>-audit-coverage.tsv` to the scratch dir: one row per target
(every screen under `ScreenRoot`, every shared component, every entity, every file in every
target) with columns `path · status · rules-applied`. Mark rows as you go.

- Each target is **audited** or **explicitly skipped with a reason** (generated, fixture,
  vendored). No third state.
- **The run is incomplete while any row is unmarked**  -  and the file, not a claim, is the proof.
  Chunk the module if it is too large for one pass; the checklist survives a context reset.
- No sampling. A partial audit reported as complete certifies the unscanned screens as clean.

### Grep hygiene

Registry `mechanism` patterns are line-based and match doc comments. Filter
`^[[:space:]]*//`  -  BSD grep does not understand `\s`, so a `\s*//` filter silently lets `/// ...`
through  -  then open each surviving hit and confirm it is real code. Report pre-filter and
post-verification counts separately; a table padded with comment matches destroys trust.

### Judgement rules need evidence, not assertion

For any rule marked `enforcement: judgement`, a finding must carry the measurement its `check`
names  -  a reference count, a call-site count, a consumer count, a file list. **No count, no
finding.** This is what keeps a subjective rule from becoming an opinion.

Every judgement rule has a worked ✗/✓ pair in `EXAMPLES.md`, keyed by ID. Cite it in the finding's
proposed fix rather than re-describing the shape  -  the developer applying the fix should be
reading the same picture the audit used.

### Module-specific overrides

The module's own docs win over the registry, including where they contradict a sibling module.
Never apply one module's dialect to another. List every override honoured, so the reader sees why
a registry rule was not raised.

## Phase 4  -  Produce the plan

No findings → report compliant and stop. Otherwise write
`<ModulePath>/ios-coding-standard-plan-<module>.md` (or the repo's scratch/docs dir if the module
tree must stay clean), containing, in order:

1. **Coverage ledger**  -  total targets · audited · skipped with reasons. Not 100% accounted for
   means the plan is not deliverable.
2. **Summary**  -  counts by severity and source, resolved vocabulary, branch to create.
3. **Sensitive-data inventory** (Phase 2d)  -  first, because it is the highest-risk content.
4. **Screen × role matrix** `[STRUCT-02]`  -  screens as rows, manifest roles as columns, cells
   `✓ / missing / misplaced`. The structural map a newcomer reads first.
5. **Type placement table** `[STRUCT-05]`  -  type · location · consumer count · correct tier ·
   move required. Paired with the nested-type list `[STRUCT-01]`: owner · nested type · kind ·
   reference count · verdict.
6. **Visibility report** `[VIS-01, VIS-02]`  -  over-exposed declarations per file, non-final
   classes with no subclass.
7. **Boundary report** `[MOD-*]`  -  import graph (allowed vs forbidden, with coupling depth) ·
   manifest graph (declared vs legal for the role, plus dead edges) · **removability delta**
   ("removing X touches N files: ...") · inbound test (tests compile with no sibling present).
8. **Testability seam report** `[TEST-*]`  -  types reaching for the environment, logic requiring a
   view to execute, doubles whose signatures have drifted.
9. **Change-cost probe** `[FLEX-*]`  -  pick two plausible upcoming changes (a new variant of an
   existing screen; a new field on a shared entity) and state what each costs in files today.
10. **Findings table**  -  rule ID · `file:line` · current state · proposed fix · severity · source ·
    onboarding impact.
11. **Per-file work**  -  grouped per file, blocking → important → suggestion; within a severity,
    highest onboarding impact first.
12. **Behaviour-preservation guard**  -  splitting files, adding MARKs, extracting extensions and
    moving types must not change behaviour. Anything requiring a behaviour change (a missing
    analytics event, a wrong result type, a timezone fix, a storage migration) goes in a
    **separate group**, reviewed and tested on its own, never inside a mechanical split commit.
13. **Tooling delegation & the ratchet**  -  the section that decides whether any of this survives.
    - Classify each applied rule by its registry `enforcement`. For every `lint` / `format` /
      `scan` rule, state the concrete mechanism and whether that toolchain exists in the repo
      (`LintToolchain`). **When it does not exist, saying so is a finding**, ranked above most
      individual violations it would have caught.
    - Recommend **baseline-and-ratchet, never big-bang**: grandfather existing violations, surface
      only new ones, drain the backlog behind a green build. Order the plan the same way  -  stop
      the bleeding, then clean up.
    - Custom lint rule identifiers **are** the registry IDs (`sec_01_no_plaintext_persistence`,
      `mod_01_no_sibling_import`) so a violation points straight at the rule entry.
    - **Linting is local-only by decision**  -  the skill's `references/lint-local.sh` with `references/swiftlint.draft.yml`, config
      and baseline kept outside the repository; no committed config, no build phase, no CI job.
      State this honestly in the plan rather than describing a gate that does not exist: a
      pre-PR habit catches less than a wall, so the residual risk is real and belongs in the
      report. Adopting it project-wide remains an open decision  -  record it as one so it stays
      visible instead of quietly lapsing.
    - Never imply the audit substitutes for the linter, or the linter for a gate. The audit finds
      the debt once; the linter keeps a developer honest; only a gate stops it returning.
14. **Numeric exit criteria**  -  target and today's measured value side by side: lint violations
    per 1k lines (<1) · public-surface ratio, public declarations ÷ externally consumed symbols
    (≈1) · forbidden cross-module imports (0) · removability delta · files over threshold and
    files >120 lines with no sectioning (0) · screens missing a manifest role (0) · escape-hatch
    count `[CONC-03]` (flat or falling) · exception-marker count (flat or falling) · dead code (0)
    · screens with logic and no mirrored tests (0) · unclassified sensitive symbols (0).
15. **Exception ledger**  -  every `// standard:exception(<ID>)` in the module: rule · reason ·
    expiry · expired yes/no.
16. **Open Questions**  -  Phase 2b dimensions with no majority, and unclassifiable symbols.
17. **Suggested commit slicing**  -  one commit per file-group / concern.
18. **Scope guard**  -  only this module is touched; definition of done is the module's own
    verification path.

Then write a **separate one-page onboarding summary** beside the plan, aimed at a developer
joining next week rather than at whoever does the remediation: the ten always/never lines
specialised to this module, the screen file manifest, the placement ladder, the sensitive-data
classes in play, and the verification command. Nobody learns a codebase from a 40-finding audit -
this page is the actual deliverable, and the audit is how you earn the right to write it
accurately. Derive it from `references/STANDARD.md section 0`, do not re-invent it.

Show a concise version of the plan to the user too.

## Phase 5  -  Hand off

`AskUserQuestion` (single-select, `outputLanguage`):

| Option | What it does |
|---|---|
| `/multi-agent` | Opens a worktree; fixes on a fresh branch + PR |

Both run the whole pipeline; a standards sweep is usually already scoped, so Phase 1 has little to say and its document is short.

Invoke the chosen command with the plan file as the task input, branching off the repo's main
development branch (e.g. `chore/<module>-coding-standard`). The dev pipeline applies the fixes,
verifies, and commits only when asked. This routine ends at the handoff.

## Notes

- **Verification reality is per-module  -  establish it, do not assume it.** Some targets cannot be
  compiled from the CLI (asset symbol generation, UIKit-dependent packages, a dependency failing
  earlier in the graph); there, verification is Xcode and a manifest dump validates the manifest
  only. Never trust a piped build's exit code  -  a pipe reports the tail's status. Record the
  module's actual verification command in the plan.
- One module per run. Several modules means several runs and several plans, never one
  cross-module churn commit.
- If the audit wants a rule the registry lacks, add it to the skill's `references/rules.yml` as `status: proposed` with a
  rationale and surface it in Open Questions. Do not enforce an unregistered rule.
- No AI attribution anywhere (code, commits, PRs). Author = git identity.
