@mmerterden/multi-agent-pipeline 13.5.0 → 14.0.0

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.
Files changed (119) hide show
  1. package/CHANGELOG.md +243 -0
  2. package/README.md +3 -3
  3. package/docs/features.md +1 -1
  4. package/install/_common.mjs +73 -0
  5. package/install/_mcp-register.mjs +70 -31
  6. package/install/_plugin-skills.mjs +73 -14
  7. package/install/claude.mjs +28 -4
  8. package/install/codex.mjs +33 -2
  9. package/install/copilot.mjs +145 -9
  10. package/install/index.mjs +10 -6
  11. package/install/templates/copilot-instructions.md +1 -1
  12. package/package.json +1 -1
  13. package/pipeline/agents/code-reviewer.md +58 -1
  14. package/pipeline/commands/multi-agent/SKILL.md +7 -5
  15. package/pipeline/commands/multi-agent/analysis/SKILL.md +7 -7
  16. package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +1 -1
  17. package/pipeline/commands/multi-agent/build-optimize/SKILL.md +7 -7
  18. package/pipeline/commands/multi-agent/channels/SKILL.md +5 -5
  19. package/pipeline/commands/multi-agent/dev/SKILL.md +23 -18
  20. package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +19 -13
  21. package/pipeline/commands/multi-agent/dev-local/SKILL.md +14 -12
  22. package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +17 -12
  23. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
  24. package/pipeline/commands/multi-agent/help/SKILL.md +4 -4
  25. package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +2 -2
  26. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +4 -4
  27. package/pipeline/commands/multi-agent/resume/SKILL.md +1 -1
  28. package/pipeline/commands/multi-agent/review/SKILL.md +5 -5
  29. package/pipeline/commands/multi-agent/scan/SKILL.md +1 -1
  30. package/pipeline/commands/multi-agent/search/SKILL.md +1 -1
  31. package/pipeline/commands/multi-agent/setup/SKILL.md +6 -6
  32. package/pipeline/commands/multi-agent/{finish → ship}/SKILL.md +12 -12
  33. package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +1 -1
  34. package/pipeline/commands/multi-agent/update/SKILL.md +5 -2
  35. package/pipeline/commands/sim-test.md +2 -2
  36. package/pipeline/lib/credential-store-resolver.sh +16 -0
  37. package/pipeline/lib/credential-store.sh +47 -4
  38. package/pipeline/lib/fetch-figma-annotations.sh +26 -28
  39. package/pipeline/lib/figma-screenshot.sh +28 -39
  40. package/pipeline/lib/figma-token.sh +63 -0
  41. package/pipeline/multi-agent-refs/analysis-template.md +1 -1
  42. package/pipeline/multi-agent-refs/android-guide.md +1 -1
  43. package/pipeline/multi-agent-refs/channels/issue-comment.md +1 -1
  44. package/pipeline/multi-agent-refs/component-dispatch.md +2 -2
  45. package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
  46. package/pipeline/multi-agent-refs/features/dev-critic.md +2 -2
  47. package/pipeline/multi-agent-refs/features/model-fallback.md +35 -2
  48. package/pipeline/multi-agent-refs/features/plan-todos.md +1 -1
  49. package/pipeline/multi-agent-refs/features/repo-map.md +1 -1
  50. package/pipeline/multi-agent-refs/features/review-multi-repo.md +3 -3
  51. package/pipeline/multi-agent-refs/features/shadow-git.md +1 -1
  52. package/pipeline/multi-agent-refs/features/skill-conformance.md +116 -0
  53. package/pipeline/multi-agent-refs/features/verify-by-test.md +1 -1
  54. package/pipeline/multi-agent-refs/generate-issue.md +1 -1
  55. package/pipeline/multi-agent-refs/multi-repo-integration-build.md +1 -1
  56. package/pipeline/multi-agent-refs/phases/log-format.md +4 -4
  57. package/pipeline/multi-agent-refs/phases/modes.md +7 -7
  58. package/pipeline/multi-agent-refs/phases/phase-0-init.md +13 -11
  59. package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +17 -15
  60. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +7 -7
  61. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +28 -13
  62. package/pipeline/multi-agent-refs/phases/phase-4-review.md +90 -58
  63. package/pipeline/multi-agent-refs/phases/phase-5-test.md +7 -7
  64. package/pipeline/multi-agent-refs/phases/phase-6-commit.md +8 -8
  65. package/pipeline/multi-agent-refs/phases/phase-7-report.md +8 -8
  66. package/pipeline/multi-agent-refs/phases.md +13 -13
  67. package/pipeline/multi-agent-refs/progress-contract.md +2 -2
  68. package/pipeline/multi-agent-refs/rules.md +7 -5
  69. package/pipeline/multi-agent-refs/swiftui-guide.md +1 -1
  70. package/pipeline/multi-agent-refs/tracker-contract.md +16 -15
  71. package/pipeline/preferences-template.json +7 -1
  72. package/pipeline/rules/figma-pipeline.md +2 -2
  73. package/pipeline/schemas/agent-state.schema.json +333 -79
  74. package/pipeline/schemas/criteria-manifest.schema.json +228 -0
  75. package/pipeline/schemas/migrations/prefs-2.4.0-to-2.5.0.mjs +64 -0
  76. package/pipeline/schemas/prefs.schema.json +118 -262
  77. package/pipeline/schemas/reviewer-output.schema.json +48 -3
  78. package/pipeline/schemas/token-budget.json +34 -10
  79. package/pipeline/schemas/triage-output.schema.json +112 -27
  80. package/pipeline/scripts/cost-table.json +7 -4
  81. package/pipeline/scripts/gc-worktrees.sh +1 -1
  82. package/pipeline/scripts/gen-mode-dispatch.mjs +6 -6
  83. package/pipeline/scripts/match-skills.mjs +37 -4
  84. package/pipeline/scripts/migrate-prefs.mjs +88 -17
  85. package/pipeline/scripts/phase-tracker.sh +14 -3
  86. package/pipeline/scripts/pre-commit-check.sh +49 -2
  87. package/pipeline/scripts/skill-conformance.mjs +960 -0
  88. package/pipeline/scripts/smoke-schema-validation.sh +17 -4
  89. package/pipeline/scripts/uninstall.mjs +35 -9
  90. package/pipeline/scripts/validate-reviewer.mjs +108 -1
  91. package/pipeline/skills/.skill-manifest.json +1 -1
  92. package/pipeline/skills/.skills-index.json +36 -9
  93. package/pipeline/skills/shared/README.md +15 -12
  94. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +1 -0
  95. package/pipeline/skills/shared/core/apple-archive-compliance/references/rules.yml +167 -0
  96. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +1 -0
  97. package/pipeline/skills/shared/core/google-play-compliance/references/rules.yml +184 -0
  98. package/pipeline/skills/shared/core/multi-agent/SKILL.md +10 -10
  99. package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +4 -4
  100. package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +3 -3
  101. package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +2 -2
  102. package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
  103. package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +6 -5
  104. package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +7 -6
  105. package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +4 -3
  106. package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +2 -1
  107. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -2
  108. package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +1 -1
  109. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +4 -4
  110. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +5 -5
  111. package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +1 -1
  112. package/pipeline/skills/shared/core/multi-agent-search/SKILL.md +1 -1
  113. package/pipeline/skills/shared/core/{multi-agent-finish → multi-agent-ship}/SKILL.md +8 -8
  114. package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +44 -5
  115. package/pipeline/skills/shared/external/ios-coding-standard/modules/_TEMPLATE.yml +82 -0
  116. package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +169 -10
  117. package/pipeline/skills/shared/external/ios-coding-standard/references/lint-local.sh +13 -1
  118. package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +335 -16
  119. package/pipeline/skills/skills-index.md +11 -8
@@ -1,9 +1,33 @@
1
- version: 1.0.0
2
- updated: 2026-07-26
1
+ version: 1.1.0
2
+ updated: 2026-07-29
3
3
  owner: iOS platform
4
4
  description: >
5
- The rule registry. STANDARD.md teaches these to a newcomer with examples; SKILL.md audits
6
- against them. IDs are stable and never renumbered - a rule is retired by status, not deletion.
5
+ The rule registry. STANDARD.md teaches these to a newcomer with examples; EXAMPLES.md carries a
6
+ worked ✗/✓ pair per judgement rule; SKILL.md audits against them. IDs are stable and never
7
+ renumbered - a rule is retired by status, not deletion.
8
+
9
+ # What these rules may be applied to. A consumer resolves this BEFORE selecting rules,
10
+ # and records how many it dropped and why. Without it every rule below would be applied
11
+ # to every changed file, which on an Objective-C or UIKit diff manufactures findings and
12
+ # buries the real ones - worse than declaring no coverage at all.
13
+ #
14
+ # Per-rule `scope:` narrows this further and never widens it. A rule with no `scope:`
15
+ # inherits the block below.
16
+ scope:
17
+ languages: [swift]
18
+ paths:
19
+ - "**/*.swift"
20
+ excludePaths:
21
+ - "**/*.generated.swift"
22
+ - "**/Generated/**"
23
+ - "**/*.pb.swift"
24
+ # Named so a consumer can report the gap rather than silently covering nothing:
25
+ # these languages appear in iOS repos and this registry does NOT speak for them.
26
+ notCovered:
27
+ objective-c: "*.m / *.mm / *.h - no ObjC rules exist here; report as a coverage gap, never apply Swift rules to them"
28
+ uikit: "UIKit view controllers are Swift, so file-level rules apply, but the UI-* family assumes SwiftUI view construction and is scoped out below"
29
+ c-cpp: "*.c / *.cc / *.hpp - out of scope"
30
+
7
31
  severity_levels: [blocking, important, suggestion]
8
32
  enforcement_kinds:
9
33
  format: the formatter owns it; not reviewed by humans
@@ -12,6 +36,44 @@ enforcement_kinds:
12
36
  judgement: requires a human or an audit run
13
37
  exception_marker: "// standard:exception(<RULE-ID>) <reason> <expiry:YYYY-MM-DD>"
14
38
 
39
+ module_overlay_slots:
40
+ description: >
41
+ Some rules govern a CHOICE rather than a defect: two shapes are each internally coherent, the
42
+ cost is only in mixing them, and picking one is the module's call. Writing one of them into the
43
+ shared registry turns every module that chose the other into a hundred findings - which is a
44
+ migration proposal wearing a standards pass. Those rules bind to a slot here instead. The
45
+ module's `modules/<Module>.yml` overlay binds it; an UNBOUND slot disables its rules, and the
46
+ audit says so in the plan rather than defaulting to one dialect silently.
47
+ A slot is only legitimate when both values are genuinely defensible. `UI-04` is the
48
+ counter-example: a module with no copy surface has not chosen a different dialect, it is
49
+ missing the surface, so that rule is not slotted.
50
+ slots:
51
+ - id: ServiceNamingScheme
52
+ governs: [SVC-07]
53
+ values:
54
+ send-path: >
55
+ a method wrapping one service call is named after the endpoint path, prefixed to say a
56
+ request leaves the device. One spelling on both sides of the wire; a grep from the
57
+ endpoint reaches every layer.
58
+ domain-verb: >
59
+ the method is named after what the domain asks for. Reads more naturally at the call site
60
+ and survives an endpoint being renamed, at the cost of the endpoint-to-code grep.
61
+ note: >
62
+ Module-wide, never per screen. Changing the bound value is a rename migration with its own
63
+ ticket, not a review comment.
64
+ - id: NavExitShape
65
+ governs: [NAME-01]
66
+ values:
67
+ coordinator-event: a typed event enum per screen plus a handler alias the coordinator applies.
68
+ output-closure: an output enum per screen delivered through a closure the factory injects.
69
+ note: Both enumerate every exit in one place, which is the property NAME-01 actually protects.
70
+ - id: ComponentsDir
71
+ governs: [READ-04b]
72
+ values:
73
+ Presentation/Components: the conventional spelling.
74
+ Presentation/Subviews: an equally clear alternative in use.
75
+ note: The name is free; using two of them in one module is not.
76
+
15
77
  persistence_decision:
16
78
  description: >
17
79
  Answer this BEFORE reaching for storage. Keychain is the answer to "where does a persisted
@@ -171,8 +233,11 @@ rules:
171
233
  check: >
172
234
  A `private var x: some View` inside a scene is a component in disguise. If it renders a
173
235
  THING - a switcher, a chip row, a banner, a bar, a legend, a card - extract it to its own
174
- file under the screen's `Presentation/Components/`, as a `struct` that takes DATA and
236
+ file under the screen's component directory (the overlay's `ComponentsDir`, conventionally
237
+ `Presentation/Components/`), as a `struct` that takes DATA and
175
238
  CALLBACKS, never the view model, and give it a `#Preview`.
239
+ The directory's NAME is a dialect choice and not a finding on its own; using two names for
240
+ it within one module is.
176
241
  Taking data instead of the view model is what makes the preview possible at all: a
177
242
  component holding a view model needs the DI container a canvas preview never configures,
178
243
  and the workaround (a preview-only fixture type shipped in production sources) is itself
@@ -201,6 +266,12 @@ rules:
201
266
  Not this rule: building a design-system `Configuration` value. Moving those to the view
202
267
  model drags UI types across the boundary; they belong inside the component that renders
203
268
  them (READ-04b), which is where they disappear once the component takes data.
269
+ The view model is where such a rule GOES BY DEFAULT, and for a single-screen rule that is
270
+ the end of it. When the same question turns out to be several screens', SVC-08's ladder
271
+ decides between the entity and a named rule namespace (RULE-01); moving it out of the view
272
+ layer is this rule, choosing its final home is that one.
273
+ Also not this rule: reading the load state's own shape (NAME-06). `state.isLoading` decides
274
+ nothing - it reports what the enum already says.
204
275
 
205
276
  - id: READ-04d
206
277
  title: A pure transform is a shared helper, not a method on the view
@@ -321,11 +392,24 @@ rules:
321
392
 
322
393
  # ── NAME ──────────────────────────────────────────────────────────────────
323
394
  - id: NAME-01
324
- title: One name per role - the navigation handler is never called something else
395
+ title: One name per role - a screen's navigation exit has one spelling module-wide
325
396
  severity: blocking
326
397
  enforcement: lint
327
- mechanism: 'custom regex scoped to Scene/ViewModel: \boutput\b as a handler property or parameter'
398
+ mechanism: >
399
+ custom regex scoped to Scene/ViewModel, generated from the module's bound `NavExitShape`:
400
+ any navigation-exit property or parameter whose name does not match the bound spelling.
328
401
  rationale: readability
402
+ applies_when: >
403
+ the module's overlay binds `NavExitShape`. Unbound, this rule is DISABLED - a regex for one
404
+ spelling would report every module that chose the other one, which is a dialect difference,
405
+ not a defect.
406
+ check: >
407
+ What this rule protects is that a reader finds a screen's exits under ONE name across the
408
+ module, not that a particular name wins. Both shapes in use are legitimate: a typed
409
+ `CoordinatorEvent` enum with a handler alias, or an `Output` enum with a closure. Each
410
+ enumerates every navigation exit in one place, which is the property that matters.
411
+ The finding is a module that uses both, or a screen whose exits are spread across an enum
412
+ and loose ad-hoc callbacks. Report the spelling per screen and the count of each.
329
413
 
330
414
  - id: NAME-02
331
415
  title: Our models use RequestModel/ResponseModel; transport suffixes stop at the data layer
@@ -349,6 +433,16 @@ rules:
349
433
  check: >
350
434
  An unrecognised server value must land on a known-unknown case rather than failing or
351
435
  silently carrying an arbitrary string into the UI.
436
+ **When the generator declares the same value set several times** - one inline enum per
437
+ response payload, because that is what the schema says - do not pick one of them as the
438
+ domain type and do not add a `switch` per mapper. Declare the domain enum once and bridge
439
+ through the shared raw code, in an extension on the domain type
440
+ (`extension X { init?(wireCode: String) }`) that lives beside it. Every mapper then maps
441
+ through one initialiser, the unknown value lands in one place, and adding a case is one edit
442
+ rather than one per payload. The bridge is a lowering, so it stays a mapper concern and does
443
+ not put the generated types in front of the view model (SVC-04).
444
+ Report per value set: how many generated declarations exist, how many hand-written mappings
445
+ of it the module has, and whether the unknown value is handled the same way in each.
352
446
 
353
447
  - id: NAME-05
354
448
  title: Screen behaviour is driven by an enum state, not by a spread of booleans
@@ -372,6 +466,36 @@ rules:
372
466
  keeps a single source of truth, whereas a stored copy drifts from the inputs it mirrors.
373
467
  Two independent booleans that never interact are fine; do not enum-ify for its own sake.
374
468
 
469
+ - id: NAME-06
470
+ title: The screen's load state is one enum with its payload attached, and the absent case is explained
471
+ severity: important
472
+ enforcement: judgement
473
+ rationale: readability
474
+ check: >
475
+ NAME-05 says a screen's behaviour is an enum rather than a spread of booleans. This rule is
476
+ about the LOAD state specifically - the one every screen with a service call has - and about
477
+ the shape, because the shape is what stops the booleans growing back.
478
+ - **The payload hangs off the case, not beside it.** `case loaded(Content)` where `Content`
479
+ is its own struct. The alternative - an `isLoading` flag next to an optional `data` next to
480
+ an optional `errorMessage` - permits `isLoading == true` with data present and an error
481
+ set, a combination nothing in the type rejects and every reader has to reason about.
482
+ - **The enum answers questions; the view does not destructure it.** Accessors on the enum
483
+ (`var isLoading`, `var <entity>`) keep `if case let` out of the view body. These are not
484
+ business rules (READ-04c) - they are reads of the state's own shape.
485
+ - **A case deliberately NOT modelled is stated in the file, with its reason.** "There is no
486
+ `empty` case: a successful response always represents a populated record; an empty payload
487
+ arrives as a not-found error." Without that line the next reader cannot tell a considered
488
+ omission from an oversight, and adds the case defensively.
489
+ - **A secondary source that may fail is an optional slot inside the payload, not a second
490
+ state.** When a screen's primary data loads but a supporting fetch fails, the failure
491
+ belongs in `Content` as an optional the view simply does not render - not as a screen-level
492
+ error that discards the data that did arrive. Say so at the property: what nil means and
493
+ why it is non-fatal. Promoting a non-critical failure to a screen error is the finding, and
494
+ so is silently defaulting it to an empty value, which makes "absent" and "empty" the same.
495
+ **The measurement:** count the mutually-dependent state properties the view reads to decide
496
+ what to render. Three or more that must be read together, or any pair whose illegal
497
+ combination the type permits, is the finding.
498
+
375
499
  - id: STRUCT-07
376
500
  title: A scene is one type - no inner view struct wrapping it
377
501
  severity: important
@@ -394,6 +518,17 @@ rules:
394
518
  `XUseCase.swift` holds the protocol; `XUseCaseLive.swift` sits next to it. Same for a
395
519
  repository. A reader opening the protocol sees the contract without scrolling past an
396
520
  implementation, and the implementation file is where the collaborators are declared.
521
+ **The split earns its keep when there is an implementation to scroll past.** A repository, or
522
+ any implementation with a body - request construction, mapping, cache reads, error
523
+ projection - is split: that is the case the rule is for. A use case that is a pure
524
+ pass-through, whose whole body forwards one call to one collaborator, may keep the protocol,
525
+ the live type and its null object (TEST-08) in one file: there is no contract to scroll past,
526
+ and splitting a dozen of those produces three dozen files whose names differ by a suffix.
527
+ Decide by whether the implementation has anything a reader must skip, never by the layer's
528
+ name - and apply one answer across the module, because the cost of this rule is a reader
529
+ guessing which file a type is in.
530
+ A pass-through use case is worth a second look for a different reason: SVC-01 asks what it
531
+ adds over calling the repository directly. That is a separate finding from this one.
397
532
 
398
533
  - id: STRUCT-06
399
534
  title: A validation rule is a shared type until measurement says otherwise
@@ -450,10 +585,29 @@ rules:
450
585
  rationale: flexibility
451
586
 
452
587
  - id: SVC-05
453
- title: One result and error family per module, built through one factory
588
+ title: One failure vocabulary per module, built through one factory - outcomes may be screen-scoped
454
589
  severity: blocking
455
590
  enforcement: judgement
456
591
  rationale: testability
592
+ check: >
593
+ Two things used to be one rule here, and conflating them pushed modules toward the wrong fix
594
+ in both directions. Separate them:
595
+ **The failure vocabulary is module-scoped.** The set of ways a call can fail - offline,
596
+ timeout, unauthorised, not-found, conflict, server, unknown - is one type the whole module
597
+ shares, produced by one factory that projects the transport error onto it. This is the
598
+ blocking half. The tell that it was skipped: the same offline / timeout / server triple
599
+ hand-written into a dozen screen-local types, each classifying the transport error again, so
600
+ a new failure kind means a dozen edits and the classifications drift apart. Count the
601
+ declarations of the same failure kind across the module; more than one home for the same
602
+ concept is the finding.
603
+ **The outcome type may be screen-scoped.** SVC-09 allows a screen to declare its own closed
604
+ outcome enum, whose success cases carry that screen's destinations. That is not a second
605
+ error family and does not violate this rule - provided the failure cases PROJECT FROM the
606
+ module vocabulary rather than redeclaring it. A screen-scoped outcome that reaches for the
607
+ transport error directly, or invents its own failure spelling, is the violation; one that
608
+ names the shared kinds is correct.
609
+ The distinction in one line: **how a call can fail is a module fact; what a screen does next
610
+ is a screen fact.**
457
611
 
458
612
  - id: SVC-06
459
613
  title: Spec discipline - nothing hand-written around the generator, no endpoint in two specs
@@ -467,6 +621,12 @@ rules:
467
621
  enforcement: lint
468
622
  mechanism: custom regex - non-`send` funcs in repository protocols surface for an explicit marker
469
623
  rationale: readability
624
+ applies_when: >
625
+ the module's overlay binds `ServiceNamingScheme: send-path`. Unbound, or bound to
626
+ `domain-verb`, this rule is DISABLED for the module - see module_overlay_slots. The scheme is
627
+ a module-wide decision because its whole value is one spelling on both sides of the wire;
628
+ what the rule then enforces is internal consistency with the declared scheme, never
629
+ conformity to a sibling module's choice.
470
630
  check: >
471
631
  `send` + the endpoint path, segments in their own order, camel-cased, path parameters
472
632
  dropped: `check-open-status` → `sendCheckOpenStatus`, `order/items/save` →
@@ -505,10 +665,12 @@ rules:
505
665
  mechanism: custom regex - arithmetic and conditional expressions inside Mapper paths
506
666
  rationale: testability
507
667
  check: >
508
- There are exactly two homes for a business rule: the **view model** that owns the
509
- behaviour, or a **computed property on the struct the rule is about** (under a
510
- `// MARK: - Derived`), when more than one screen asks the same question.
511
- Every other layer is transport. A mapper, a `UseCaseLive`, a repository, a DTO mirror, a
668
+ There are exactly three homes for a business rule, and RULE-01 carries the ladder that
669
+ picks between them: the **view model** that owns the behaviour; a **computed property on
670
+ the struct the rule is about** (under a `// MARK: - Derived`), when more than one screen
671
+ asks the same question of data the struct already holds; or a **named rule namespace**
672
+ (RULE-01), when several screens share a rule that is screen policy rather than entity
673
+ state. Every other layer is transport. A mapper, a `UseCaseLive`, a repository, a DTO mirror, a
512
674
  coordinator, a data source - each moves values between two shapes and decides nothing. A
513
675
  `UseCaseLive` in particular reads as a tempting home because it already knows the domain
514
676
  vocabulary: it does not get one. Its body is call → map → return, plus cache read/write;
@@ -556,6 +718,9 @@ rules:
556
718
  produce, because the state is computed from the same inputs in both.
557
719
  - **In the view model** - when the question is that one screen's, or when the answer needs
558
720
  anything the entity does not hold (localized copy, a feature flag, live user state).
721
+ - **In a named rule namespace** (RULE-01) - when several screens share the rule but it is
722
+ screen policy rather than entity state, so putting it on the entity would make a data
723
+ carrier answer a question about presentation. RULE-01 carries the selection ladder.
559
724
  Never in the mapper, and never as a stored field the mapper fills, because a stored field
560
725
  is indistinguishable from a wire value at every call site that reads it.
561
726
 
@@ -568,6 +733,75 @@ rules:
568
733
  emits or drops a field, an aggregation whose result is a screen state
569
734
  (`contains { ... } ? .invalid : .valid`), and any localized string.
570
735
 
736
+ - id: SVC-09
737
+ title: A service outcome is a closed enum - a failure is a value, not control flow
738
+ severity: important
739
+ enforcement: judgement
740
+ rationale: testability
741
+ check: >
742
+ A boundary method returns ONE type that enumerates every outcome the caller must handle,
743
+ and the caller `switch`es over it exhaustively. The compiler then answers "is any outcome
744
+ unhandled?", which no `catch` can. SVC-01 already bans `throws` alongside a result family;
745
+ this rule says what the family looks like.
746
+ Three properties make it work, and each is a separate finding when missing:
747
+ - **Total.** Success, business failure, and every transport failure the screen renders
748
+ differently are cases of the same type. A method that returns a result AND throws has two
749
+ outcome channels, so no `switch` is exhaustive.
750
+ - **Projected, not leaked.** The transport error family (the networking package's own error
751
+ type) is projected onto the screen's outcome inside the data layer, through one shared
752
+ projection helper rather than a per-repository `switch`. The transport type never appears
753
+ in a Domain or Presentation signature (FLEX-05). A repository that returns the transport
754
+ error verbatim has moved the classification into every caller.
755
+ - **Named after the outcome, not the payload.** A success case may carry a DESTINATION
756
+ rather than data - a redirect the backend chose, resolved from the wire value into a case
757
+ (NAME-04) so no raw string reaches the view model. That is the shape's main advantage over
758
+ a bare `Result<Payload, Error>`, and it is why the two coexist.
759
+ Where the error spine lives is SVC-05's question, not this one: the outcome enum is
760
+ screen-scoped, the failure vocabulary it projects from is module-scoped. Duplicating the
761
+ same offline / timeout / server triple into each screen's enum is the SVC-05 finding.
762
+ **The measurement, for a finding to be one:** per boundary method, the outcome-channel count
763
+ (a result type plus a `throws` is two), the number of call sites that re-classify a transport
764
+ error, and whether any non-exhaustive `switch` over the result exists. No count, no finding.
765
+
766
+ # ── RULE - business-rule surface ──────────────────────────────────────────
767
+ - id: RULE-01
768
+ title: A shared business rule lives in a named rule namespace, as a pure static, traceable to its source
769
+ severity: important
770
+ enforcement: judgement
771
+ rationale: testability
772
+ check: >
773
+ SVC-08 names three homes for a business rule. The ladder that picks between them, cheapest
774
+ rung first:
775
+ 1. **One screen asks it** -> the view model. Stop here. Do not create a namespace for one
776
+ caller; a namespace with a single consumer is over-hoisting (STRUCT-05).
777
+ 2. **Several screens ask it, of data the entity already carries** -> a computed property on
778
+ the entity, under `// MARK: - Derived`. This is also what keeps test doubles honest: a
779
+ double can no longer hand-set a state the real payload could not produce.
780
+ 3. **Several screens ask it, but the answer is screen POLICY rather than entity state** ->
781
+ a named rule namespace. Putting it on the entity would make a data carrier answer a
782
+ question about presentation; leaving it in one view model hides it from the others.
783
+ The namespace's shape, and each property is what makes it auditable:
784
+ - A **caseless enum** (never a struct with an initialiser, never a class), so it cannot be
785
+ instantiated or hold state.
786
+ - **Static, pure functions.** Inputs in, value out. No stored state, no I/O, no logging, no
787
+ localized copy. A rule that needs the environment takes it as a parameter - a clock as a
788
+ defaulted parameter is the accepted seam, and it is what keeps TEST-01 satisfied without
789
+ an injected container for a value transform.
790
+ - It lives in the module's **domain layer**, beside the entities it reads, not beside the
791
+ scene that needed it first.
792
+ - It is **not** a formatter, a mapper, or a validator. A value transform is READ-04d, a
793
+ shape lowering is SVC-08, a form rule is STRUCT-06. A namespace that accumulates all four
794
+ is a Utils bucket with a better name (STRUCT-04).
795
+ **Traceability is part of the rule, not a nicety.** Each function names the requirement it
796
+ enforces - the spec clause, business-rule ID, or ticket - in its doc comment, and the test
797
+ that covers it repeats that same token in its name. One grep then reaches requirement, code
798
+ and test. This is also the rule's own measurement: a namespace whose functions cite nothing
799
+ cannot be checked against anything, and a cited rule with no test of the same token is an
800
+ untested requirement, which is the finding.
801
+ **What the doc comment is for.** Recording the decision - which input the rule keys off and
802
+ why the obvious alternative was rejected - is exactly the case READ-02 protects; it is not
803
+ trimmed. This rule does not require such a comment, it requires the citation.
804
+
571
805
  - id: PLAT-01
572
806
  title: Platform affordances go through the app's own wrapper, not the OS API
573
807
  severity: blocking
@@ -704,12 +938,16 @@ rules:
704
938
 
705
939
  # ── UI ────────────────────────────────────────────────────────────────────
706
940
  - id: UI-01
941
+ scope: { frameworks: [swiftui], paths: ["**/*View.swift", "**/*Screen.swift", "**/*Scene.swift", "**/*Cell.swift", "**/*Configuration.swift", "**/*+Modifiers.swift"] }
942
+ scope_reason: SwiftUI view composition - a UIKit view controller hierarchy expresses reuse differently
707
943
  title: A screen's composite view is never consumed by another screen
708
944
  severity: blocking
709
945
  enforcement: judgement
710
946
  rationale: flexibility
711
947
 
712
948
  - id: UI-02
949
+ scope: { frameworks: [swiftui], paths: ["**/*View.swift", "**/*Screen.swift", "**/*Scene.swift", "**/*Cell.swift", "**/*Configuration.swift", "**/*+Modifiers.swift"] }
950
+ scope_reason: SwiftUI view construction
713
951
  title: No new component is added to a UI target the module marks as frozen
714
952
  severity: important
715
953
  enforcement: lint
@@ -717,12 +955,16 @@ rules:
717
955
  rationale: flexibility
718
956
 
719
957
  - id: UI-03
958
+ scope: { frameworks: [swiftui], paths: ["**/*View.swift", "**/*Screen.swift", "**/*Scene.swift", "**/*Cell.swift", "**/*Configuration.swift", "**/*+Modifiers.swift"] }
959
+ scope_reason: SwiftUI view construction
720
960
  title: Every screen with user actions has its analytics surface; no direct generated-event calls
721
961
  severity: important
722
962
  enforcement: judgement
723
963
  rationale: readability
724
964
 
725
965
  - id: UI-04
966
+ scope: { frameworks: [swiftui], paths: ["**/*View.swift", "**/*Screen.swift", "**/*Scene.swift", "**/*Cell.swift", "**/*Configuration.swift", "**/*+Modifiers.swift"] }
967
+ scope_reason: SwiftUI copy surface (LocalizedText); UIKit modules localise through a different seam
726
968
  title: All user copy goes through the screen's copy surface
727
969
  severity: important
728
970
  enforcement: lint
@@ -844,11 +1086,24 @@ rules:
844
1086
  rationale: testability
845
1087
 
846
1088
  - id: TEST-04
847
- title: Test doubles follow one named taxonomy - stub, spy, fake, mock
1089
+ title: Test doubles follow one named taxonomy - stub, spy, fake, mock, builder
848
1090
  severity: suggestion
849
1091
  enforcement: judgement
850
1092
  rationale: testability
851
- check: One kind per file, name states the kind, signature parity with the real type (SVC-02).
1093
+ check: >
1094
+ One kind per file, name states the kind, signature parity with the real type (SVC-02). All of
1095
+ them live in the test target (TEST-08).
1096
+ **The builder is the fifth kind and the one most often missing.** Where tests need many
1097
+ near-identical entities differing in one field, the alternative to a builder is a wall of
1098
+ full initialiser calls - so a test's actual subject drowns in fifty lines of scaffolding, and
1099
+ a new field on the entity breaks every test that never cared about it. A builder is a base
1100
+ value plus chainable single-field overrides returning a copy
1101
+ (`.<baseCase>().with<Field>(...)`), so each test states ONLY what it varies and reads as its
1102
+ own precondition. Two properties keep it honest: it produces the real type (never a
1103
+ parallel test-only mirror, which drifts), and each override sets one field, so a reader can
1104
+ tell what a test depends on from its chain.
1105
+ The finding is not "no builder exists" - it is a test file where the setup of a case is
1106
+ longer than its assertion, repeated. Report the ratio, not the absence.
852
1107
 
853
1108
  - id: TEST-05
854
1109
  title: Async behaviour is testable - no unstructured task in logic, no sleep, injected clock
@@ -870,6 +1125,41 @@ rules:
870
1125
  enforcement: scan
871
1126
  rationale: testability
872
1127
 
1128
+ - id: TEST-08
1129
+ title: A null object may ship in production sources; a test double may not
1130
+ severity: important
1131
+ enforcement: lint
1132
+ mechanism: >
1133
+ custom regex - a type whose name marks it as a double (Mock / Stub / Spy / Fake / Fixture
1134
+ prefix or suffix) declared under the module's production source root rather than its test
1135
+ target. Whether a given production type is a null object or a disguised double is
1136
+ JUDGEMENT: the name is the signal a regex can see, the payload is not.
1137
+ rationale: testability
1138
+ check: >
1139
+ Two kinds of type conform to a protocol without doing the real work, and only one of them
1140
+ belongs in production sources.
1141
+ **A null object does.** It satisfies the protocol by doing nothing and returning the empty
1142
+ answer - no event sent, `nil` returned, an empty list. It is the honest default for a
1143
+ dependency the caller has legitimately not wired: a defaulted initialiser parameter, a
1144
+ canvas preview, a screen whose optional slot is not configured yet. It carries no data, so
1145
+ it cannot drift from the real payload, and it makes the seam visible in the DI graph instead
1146
+ of forcing an optional dependency on every call site. Name it so the reader knows what it is
1147
+ (a `Noop`-style prefix, consistently across the module) and let it be found by grep.
1148
+ **A test double does not.** A type that carries canned payloads - a mock repository
1149
+ returning a hand-built response, a fixture of sample records - ships that data to users,
1150
+ grows in the store binary, and can be resolved by accident because nothing but the name
1151
+ says it is not real. It belongs in the test target, where the compiler keeps it out of the
1152
+ app. Where a canned payload is genuinely needed AT RUNTIME (a demo build, an offline
1153
+ preview mode), that is a debug affordance and SEC-09 governs it: the question is what
1154
+ removes it from the store build, and "it is only referenced by the mock path" is not an
1155
+ answer.
1156
+ Distinguish by payload, never by name: a `Mock` that returns nothing is a null object with
1157
+ the wrong name (rename it), and a `Noop` that returns three sample bookings is a double in
1158
+ the wrong target (move it).
1159
+ **The measurement:** per module, the count of double-named types under the production source
1160
+ root, split into carries-payload and returns-empty. The first number is the finding; the
1161
+ second is a naming fix.
1162
+
873
1163
  # ── FLEX - intra-module flexibility ───────────────────────────────────────
874
1164
  - id: FLEX-01
875
1165
  title: Layers meet through protocols; no concrete cross-layer type in a signature
@@ -965,11 +1255,30 @@ rules:
965
1255
  pasteboard writes explicit and expiring; nothing sensitive cached to disk by default.
966
1256
 
967
1257
  - id: SEC-06
968
- title: Analytics and crash payloads are redacted
1258
+ title: Analytics and crash payloads are redacted, through a named helper, with a test that proves it
969
1259
  severity: blocking
970
1260
  enforcement: judgement
971
1261
  rationale: security
972
- check: Cross-check every analytics event's parameter list, user properties, breadcrumbs and non-fatal payloads for PII.
1262
+ check: >
1263
+ Cross-check every analytics event's parameter list, user properties, breadcrumbs and
1264
+ non-fatal payloads against the module's sensitive-data inventory. A class marked
1265
+ `loggable: never` must not appear at all; one marked `hashed-or-truncated-only` appears only
1266
+ in its reduced form.
1267
+ Redaction that lives at the call site is not redaction - it is a habit, and it fails on the
1268
+ event somebody adds next month. Three properties, each a separate finding:
1269
+ - **One named helper does the reduction.** A hashing or truncating function with a name,
1270
+ callable from anywhere the value is emitted, so there is one implementation to review and
1271
+ one thing to grep. Not an inline expression repeated per event.
1272
+ - **The event surface carries only the reduced form.** The typed event (UI-03) declares the
1273
+ parameter as the hash or the truncation, never the raw value with a comment asking callers
1274
+ to reduce it first. A parameter that CAN hold the raw value eventually does.
1275
+ - **The redaction has a test.** At minimum: the output is deterministic for the same input,
1276
+ differs for different inputs, and does not contain the input. A redaction nobody tested is
1277
+ a claim; these three assertions are cheap and turn it into a property. This is the rule's
1278
+ measurement - an untested reduction helper is the finding even when the code is correct.
1279
+ Truncation needs one extra check that hashing does not: that what remains cannot identify
1280
+ the subject on its own, and cannot be joined against another field in the same event to do
1281
+ so. Two separately-harmless truncations in one payload are not harmless.
973
1282
 
974
1283
  - id: SEC-07
975
1284
  title: Permissions are least-privilege with accurate purpose strings
@@ -1103,6 +1412,8 @@ rules:
1103
1412
 
1104
1413
  # ── A11Y ──────────────────────────────────────────────────────────────────
1105
1414
  - id: A11Y-01
1415
+ scope: { frameworks: [swiftui], paths: ["**/*View.swift", "**/*Screen.swift", "**/*Scene.swift", "**/*Cell.swift", "**/*Configuration.swift", "**/*+Modifiers.swift"] }
1416
+ scope_reason: SwiftUI accessibility modifiers
1106
1417
  title: An identifier from the shared source on every interactive element
1107
1418
  severity: important
1108
1419
  enforcement: lint
@@ -1110,18 +1421,24 @@ rules:
1110
1421
  rationale: testability
1111
1422
 
1112
1423
  - id: A11Y-02
1424
+ scope: { frameworks: [swiftui], paths: ["**/*View.swift", "**/*Screen.swift", "**/*Scene.swift", "**/*Cell.swift", "**/*Configuration.swift", "**/*+Modifiers.swift"] }
1425
+ scope_reason: SwiftUI accessibility modifiers
1113
1426
  title: Localized VoiceOver label, plus a hint where the action is not obvious
1114
1427
  severity: important
1115
1428
  enforcement: judgement
1116
1429
  rationale: accessibility
1117
1430
 
1118
1431
  - id: A11Y-03
1432
+ scope: { frameworks: [swiftui], paths: ["**/*View.swift", "**/*Screen.swift", "**/*Scene.swift", "**/*Cell.swift", "**/*Configuration.swift", "**/*+Modifiers.swift"] }
1433
+ scope_reason: SwiftUI accessibility modifiers
1119
1434
  title: Minimum 44x44 tap target; grouped content exposes one meaningful element
1120
1435
  severity: important
1121
1436
  enforcement: judgement
1122
1437
  rationale: accessibility
1123
1438
 
1124
1439
  - id: A11Y-04
1440
+ scope: { frameworks: [swiftui], paths: ["**/*View.swift", "**/*Screen.swift", "**/*Scene.swift", "**/*Cell.swift", "**/*Configuration.swift", "**/*+Modifiers.swift"] }
1441
+ scope_reason: SwiftUI accessibility modifiers
1125
1442
  title: Dynamic Type does not break layout at the largest accessibility sizes
1126
1443
  severity: important
1127
1444
  enforcement: judgement
@@ -1129,6 +1446,8 @@ rules:
1129
1446
  check: No fixed-height container holding scalable text.
1130
1447
 
1131
1448
  - id: A11Y-05
1449
+ scope: { frameworks: [swiftui], paths: ["**/*View.swift", "**/*Screen.swift", "**/*Scene.swift", "**/*Cell.swift", "**/*Configuration.swift", "**/*+Modifiers.swift"] }
1450
+ scope_reason: SwiftUI accessibility modifiers
1132
1451
  title: RTL mirrors correctly; no leading/trailing hardcoded as left/right
1133
1452
  severity: important
1134
1453
  enforcement: lint
@@ -3,7 +3,7 @@
3
3
  > Auto-generated by `pipeline/scripts/build-skills-index.mjs` - do not hand-edit.
4
4
  > Regenerate with `node pipeline/scripts/build-skills-index.mjs`.
5
5
 
6
- **193 skills** across 2 groups.
6
+ **196 skills** across 2 groups.
7
7
 
8
8
  | Group | Name | Platform | Description |
9
9
  |-------|------|----------|-------------|
@@ -75,12 +75,13 @@
75
75
  | external | `html-semantic` | - | Semantic HTML5: elements, forms with validation and accessibility, SEO best practices, structured data, Open Graph, Twitter Cards. Use when |
76
76
  | external | `humanizer` | - | \| |
77
77
  | external | `ios-accessibility` | - | Implements, reviews, or improves accessibility in iOS/macOS apps with SwiftUI, UIKit, and AppKit. Use when adding VoiceOver, Voice Control, |
78
+ | external | `ios-coding-standard` | - | The iOS coding-standard rule registry: 99 stable-ID rules across readability, security, service layer, business rules, concurrency, testing, |
78
79
  | external | `ios-debugger-agent` | - | Debug the current iOS project on a booted simulator with XcodeBuildMCP. Use when an iOS bug has to be reproduced and diagnosed on a booted s |
79
80
  | external | `ios-developer` | - | Develop native iOS applications with Swift/SwiftUI. Masters iOS 18, SwiftUI, UIKit integration, Core Data, networking, and App Store optimiz |
80
81
  | external | `ios-localization` | - | Implement, review, or improve localization and internationalization in iOS/macOS apps - String Catalogs (.xcstrings), generated localizabl |
81
82
  | external | `ios-networking` | - | Build, review, or improve networking code in iOS/macOS apps using URLSession with async/await, structured concurrency, and modern Swift patt |
82
83
  | external | `ios-security` | - | Secure iOS apps with Keychain Services, CryptoKit encryption, biometric authentication (Face ID, Touch ID), Secure Enclave key storage, LACo |
83
- | external | `ios-simulator` | - | Manages iOS Simulator devices and tests app behavior using xcrun simctl. Covers device lifecycle (create, boot, shutdown, erase, delete), ap |
84
+ | external | `ios-simulator` | - | Manages iOS Simulator devices and tests app behaviour with xcrun simctl: device lifecycle, app install and launch, push notification and loc |
84
85
  | external | `kotlin-coroutines-expert` | - | Expert patterns for Kotlin Coroutines and Flow, covering structured concurrency, error handling, and testing. Use when writing or reviewing |
85
86
  | external | `live-activities` | - | Implement, review, or improve Live Activities and Dynamic Island experiences in iOS apps using ActivityKit. Use when building real-time upda |
86
87
  | external | `macos-menubar-tuist-app` | - | Build, refactor, or review SwiftUI macOS menubar apps that use Tuist. Use when building, refactoring or reviewing a SwiftUI macOS menubar ap |
@@ -96,15 +97,16 @@
96
97
  | core | `multi-agent-channels` | - | Multi-channel reporter - Jira/Confluence/Wiki/PR description. Multi-select channels + content, humanizer pass, reviewer-preserving Bitbuck |
97
98
  | core | `multi-agent-create-jira` | - | Create a standards-compliant Jira issue (Task / Bug / Story): asks the type, mines project conventions, drafts from a standard template with |
98
99
  | core | `multi-agent-design-check` | - | Mock-mode vs Figma design audit (iOS / Android, local-only). Pick repo + module, gate on mock support, enumerate every state driver into a c |
99
- | core | `multi-agent-dev` | - | Fast development mode: Init → Dev (Opus) → Test → Commit → Report. Analysis, planning, and review phases are skipped. Use when the work is a |
100
- | core | `multi-agent-dev-autopilot` | - | Fastest mode: Dev (Opus) plus Autopilot. Init → Dev → Commit → Report with zero confirmations. Use when a change is well understood and shou |
101
- | core | `multi-agent-dev-local` | - | Fast mode + local - Init → Dev(Opus) → Commit → Report, no worktree. Use when a change should be developed on the current branch without c |
100
+ | core | `multi-agent-dev` | - | Fast development mode: Init → Dev (Opus) → Review → Test → Commit → Report. Analysis and planning are skipped, review is not. Use when the w |
101
+ | core | `multi-agent-dev-autopilot` | - | Fastest mode: Dev (Opus) plus Autopilot. Init → Dev → Review → Commit → Report with zero confirmations. Review still runs and auto-fixes blo |
102
+ | core | `multi-agent-dev-local` | - | Fast mode + local - Init → Dev(Opus) → Review → Commit → Report, no worktree. Use when a change should be developed and reviewed on the cu |
102
103
  | core | `multi-agent-dev-local-autopilot` | - | Fastest + local - Dev(Opus) + autopilot, no worktree, zero interaction. Use when a change should be developed on the current branch with n |
103
104
  | core | `multi-agent-diff-explain` | - | Map Phase 4 triage findings to branch diff lines. Read-only post-hoc command, used after review to answer 'which finding lines up with which |
104
- | core | `multi-agent-finish` | - | Continue already-done LOCAL work through the pipeline tail: Review → Build+Test → Commit/PR → Report (technical analysis + Jira test-scenari |
105
+ | core | `multi-agent-ship` | - | Continue already-done LOCAL work through the pipeline tail: Review → Build+Test → Commit/PR → Report (technical analysis + Jira test-scenari |
105
106
  | core | `multi-agent-forget` | - | Remove a saved /multi-agent routine (created by /multi-agent:save): deletes its local-only command and its registry entry. Asks which one an |
106
107
  | core | `multi-agent-garbage-collect` | - | Sweep leftover /tmp scratch (picker state, review diffs, channel payloads, analysis drafts) from past runs. Dry-run first; confirms before d |
107
108
  | core | `multi-agent-help` | - | Multi-agent pipeline usage guide - renders in EN or TR per prefs.global.outputLanguage (falls back to promptLanguage for backward compatib |
109
+ | core | `multi-agent-ios-coding-standard` | - | Audit an iOS module against the shared coding-standard registry (99 stable-ID rules), produce a remediation plan, then hand off to dev/dev-l |
108
110
  | core | `multi-agent-issue` | - | List unassigned GitHub issues, pick one, auto-assign, and launch the multi-agent pipeline. Use when a GitHub issue should be picked up and s |
109
111
  | core | `multi-agent-jira` | - | List open Jira issues, pick one, and launch the multi-agent pipeline. Use when a Jira issue should be picked up and started without knowing |
110
112
  | core | `multi-agent-kill` | - | Stop the given task, then remove its worktree and branch. Asks for confirmation. Use when a running or stuck task should be stopped and its |
@@ -129,6 +131,7 @@
129
131
  | core | `multi-agent-status` | - | Show every multi-agent task's ID, phase, branch, and status. Use when asked what is running, or for an overview of every task. |
130
132
  | core | `multi-agent-sync` | - | One-shot sync of the entire multi-agent ecosystem: Claude Code, Copilot CLI, pipeline repo, website, and the dev-toolkit MCP server. Use whe |
131
133
  | core | `multi-agent-test` | - | UI Bug Hunter - iOS Simulator (simctl) + Android Emulator (adb). Auto-detects platform. Screenshot + tap + analyze on the booted device. T |
134
+ | core | `multi-agent-testflight-validation` | - | Pre-submission validation for a TestFlight / App Store build (iOS, local-only). Three gates: static archive audit, Apple's own `altool --val |
132
135
  | core | `multi-agent-uninstall` | - | Uninstall the pipeline from Claude Code + Copilot CLI. Keychain access tokens are always left untouched; --all-data also clears pipeline set |
133
136
  | core | `multi-agent-update` | - | Update the pipeline to the latest version: git pull, install, migrate. Use when the installed pipeline is behind and should be brought to th |
134
137
  | external | `musickit-audio` | - | Integrate Apple Music playback, catalog search, and Now Playing metadata using MusicKit and MediaPlayer. Use when adding music search, Apple |
@@ -155,7 +158,7 @@
155
158
  | external | `speech-recognition` | - | Transcribe speech to text using Apple's Speech framework. Use when implementing live microphone transcription with AVAudioEngine, recognizin |
156
159
  | external | `spm-build-analysis` | - | Analyze Swift Package Manager dependencies, package plugins, module variants, and CI-oriented build overhead that slow Xcode builds. Use whe |
157
160
  | external | `storekit` | - | Implement, review, or improve in-app purchases and subscriptions using StoreKit 2. Use when building paywalls with SubscriptionStoreView or |
158
- | external | `swift-api-design-guidelines` | - | Apply Swift API Design Guidelines to name, label, and document Swift APIs. Covers argument label rules (prepositional phrase rule, grammatic |
161
+ | external | `swift-api-design-guidelines` | - | Swift API Design Guidelines: clarity at the point of use, naming for readability over brevity, argument labels and prepositional phrases, me |
159
162
  | external | `swift-architecture` | - | Select, implement, or migrate between app architecture patterns for Apple platform apps. Use when choosing between MV (Model-View with @Obse |
160
163
  | external | `swift-charts` | - | Implement, review, or improve data visualizations using Swift Charts. Use when building bar, line, area, point, pie, donut, or iOS 26 3D cha |
161
164
  | external | `swift-codable` | - | Implement Swift Codable models for JSON and property-list encoding and decoding with JSONDecoder, JSONEncoder, CodingKeys, and custom init(f |
@@ -169,7 +172,7 @@
169
172
  | external | `swift-testing-pro` | - | Writes, reviews, and improves Swift Testing code using modern APIs and best practices. Use when reading, writing, or reviewing projects that |
170
173
  | external | `swiftdata` | - | Implement, review, or improve data persistence using SwiftData. Use when defining @Model classes with @Attribute, @Relationship, @Transient, |
171
174
  | external | `swiftdata-pro` | - | Writes, reviews, and improves SwiftData code using modern APIs and best practices. Use when reading, writing, or reviewing projects that use |
172
- | external | `swiftlint` | - | Configures and enforces SwiftLint in Swift projects using build tool plugins, run scripts, and CI. Covers .swiftlint.yml configuration, disa |
175
+ | external | `swiftlint` | - | Configures and enforces SwiftLint via build tool plugins, run scripts and CI: .swiftlint.yml rule sets, analyzer rules, baselines, autocorre |
173
176
  | external | `swiftui-animation` | - | Implement, review, or improve SwiftUI animations and transitions. Use when adding explicit animations with withAnimation, configuring implic |
174
177
  | external | `swiftui-expert-skill` | - | Write, review, or improve SwiftUI code following best practices for state management, view composition, performance, macOS-specific APIs, an |
175
178
  | external | `swiftui-gestures` | - | Implement, review, or improve SwiftUI gesture handling. Use when adding tap, long press, drag, magnify, or rotate gestures, composing gestur |