@mmerterden/multi-agent-pipeline 12.2.0 → 12.3.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.
- package/CHANGELOG.md +52 -0
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/analysis/SKILL.md +68 -32
- package/pipeline/lib/fetch-figma-annotations.sh +237 -0
- package/pipeline/multi-agent-refs/analysis-template.md +233 -123
- package/pipeline/schemas/analysis-spec.schema.json +78 -0
- package/pipeline/schemas/figma-project-config.schema.json +49 -0
- package/pipeline/schemas/prefs.schema.json +22 -0
- package/pipeline/scripts/fixtures/install-layout.tsv +4 -4
- package/pipeline/scripts/smoke-fetchers-offline.sh +41 -0
- package/pipeline/scripts/smoke-validate-analysis-doc.sh +161 -0
- package/pipeline/scripts/validate-analysis-doc.mjs +196 -0
|
@@ -10,7 +10,7 @@ description: "v3 canonical template for /multi-agent:analysis. 23 main sections
|
|
|
10
10
|
|
|
11
11
|
> **Language**: This file is read as a system prompt. Prose stays English. Example tables and headings carry bilingual TR / EN scaffolds; the runtime renderer picks the right column per `outputLanguage`.
|
|
12
12
|
|
|
13
|
-
> **Locked decisions that govern this template**:
|
|
13
|
+
> **Locked decisions that govern this template**: the canonical numbered list lives in `pipeline/commands/multi-agent/analysis/SKILL.md` (cite decisions by label, not by a number duplicated here, to avoid drift). The template's structure is shaped chiefly by: one-feature-per-run, section omission rule, citation discipline (incl. annotation-as-copy), forward-looking spec, humanizer punctuation policy, standards binding, per-platform output split, repo-evidence reuse-first, Figma 3-tier access, Gherkin user stories, Goals + Non-Goals paired, SVG default, Files-to-Add tag, API response variants exhaustive, screenshots embedded, all Figma variants drilled, localization mode (ownership-aware), References at bottom, platform-agnostic + Pass B render, convention extraction, Pass B footnote mandatory, Lite mode, SwiftUI Preview block (iOS), variant usage explicit, analysis self-contained (no MCP downstream), and business-rule to acceptance-criterion to test traceability.
|
|
14
14
|
|
|
15
15
|
## Mode selection - Full vs Lite
|
|
16
16
|
|
|
@@ -45,6 +45,8 @@ feature: <FeatureName>
|
|
|
45
45
|
platform: ios | android | backend | frontend
|
|
46
46
|
language: tr | en
|
|
47
47
|
mode: full | lite
|
|
48
|
+
ui_tests: true | false
|
|
49
|
+
a11y_depth: basic | full
|
|
48
50
|
generated: <ISO 8601 UTC timestamp>
|
|
49
51
|
jira: <JIRA_KEY>-<id>
|
|
50
52
|
branch: <branch-name>
|
|
@@ -58,6 +60,8 @@ template_version: v3
|
|
|
58
60
|
---
|
|
59
61
|
```
|
|
60
62
|
|
|
63
|
+
`ui_tests` and `a11y_depth` record the Phase 0 Step 5a opt-ins (defaults `false` / `basic`) so the coverage choice is auditable and the pre-dispatch validator can enforce it: `ui_tests: true` requires Section 15.6, `a11y_depth: full` requires the Section 16.2 walkthrough.
|
|
64
|
+
|
|
61
65
|
The dev pipeline (`/multi-agent:dev`) reads `platform` to verify file match, `evidence_digest` to detect stale specs, and `mode` to know which section set to expect.
|
|
62
66
|
|
|
63
67
|
## Omission rules
|
|
@@ -71,15 +75,15 @@ Locked 2 - no `TBD` / `Not applicable` placeholder. Sections with zero evidence
|
|
|
71
75
|
| 6 Component Inventory | No Figma URL AND no UI primitive evidence in repo |
|
|
72
76
|
| 7 Design Tokens | Sections 5 and 6 both omitted |
|
|
73
77
|
| 8 Asset Inventory | No new visual asset planned AND no existing asset modified |
|
|
74
|
-
| 10 Localization | No Figma text layer AND no localization key file in repo |
|
|
78
|
+
| 10 Localization | No Figma annotation AND no Figma text layer AND no localization key file in repo |
|
|
75
79
|
| 11 Analytics | No Firebase input AND no analytics call site in repo |
|
|
76
80
|
| 12 Deeplink/Push | No deeplink handler in repo AND no push payload binding AND spec silent |
|
|
77
81
|
| 15 Test Plan | Backend-only run with no contract testing planned (rare) |
|
|
78
82
|
| 16 Accessibility | Backend-only (no UI surface) |
|
|
79
83
|
| 17 Security | No PII / auth / health data touched (rare, document explicit decision) |
|
|
80
|
-
| 18 Rollout | Lite mode |
|
|
81
|
-
| 19 Alternatives |
|
|
82
|
-
| 22 Glossary |
|
|
84
|
+
| 18 Rollout | Lite mode. In Full mode render the compact checklist only (not prose); drop if the pipeline's own commit/PR/rollout phase already covers it and no staged rollout / feature flag applies |
|
|
85
|
+
| 19 Alternatives | Default drop. Render ONLY when a real architectural decision with 2+ genuine options exists; a thin or invented alternatives list is worse than none (a real decision point already lives in Section 20) |
|
|
86
|
+
| 22 Glossary | Default drop. Render ONLY on explicit request or when the doc uses more than 5 domain-specific terms a new reader would not know; otherwise define terms inline |
|
|
83
87
|
| 23 Changelog | Lite mode (optional even in Full for small revisions) |
|
|
84
88
|
|
|
85
89
|
Sections 1, 2, 4, 9, 13, 14, 20, 21 are never omitted.
|
|
@@ -95,10 +99,10 @@ Every Pass B-rendered cell carries a footnote pointing to its evidence source:
|
|
|
95
99
|
Examples:
|
|
96
100
|
|
|
97
101
|
```
|
|
98
|
-
| State holder | `
|
|
102
|
+
| State holder | `UserProfileViewModel` ^[C2 high: 8 örnek; en yakın StandaloneLoginViewModel.swift] |
|
|
99
103
|
| Test naming | `@Test func fetch_validInput_returnsIdle()` ^[C4 medium: 4 mevcut örnek, çoğunluk Swift Testing] |
|
|
100
|
-
| Identifier | `
|
|
101
|
-
| State model | sealed enum `
|
|
104
|
+
| Identifier | `userProfile.continueButton` ^[C5 high: ui-testing-identifiers.json dot notation] |
|
|
105
|
+
| State model | sealed enum `UserProfileUIState` ^[C3 fallback: conventions-defaults.md:C3-iOS, repo evidence none] |
|
|
102
106
|
```
|
|
103
107
|
|
|
104
108
|
Footnote-less cells fail the dispatch gate.
|
|
@@ -132,13 +136,17 @@ Never omitted. Locked 13 - both columns mandatory. A row in only one column is n
|
|
|
132
136
|
## 2. Hedefler ve Karşı Hedefler <!-- TR -->
|
|
133
137
|
## 2. Goals and Non-Goals <!-- EN -->
|
|
134
138
|
|
|
139
|
+
### İş kapsamı / Business scope
|
|
140
|
+
|
|
141
|
+
<1-2 sentences: what this feature covers and its boundary, in domain language. Names the platforms in scope (from the provided repos) and what is explicitly out of scope.>
|
|
142
|
+
|
|
135
143
|
| Hedef / Goal | Karşı Hedef / Non-Goal |
|
|
136
144
|
|---|---|
|
|
137
145
|
| <goal 1 citing source> | <non-goal 1 citing source> |
|
|
138
146
|
| <goal 2> | <non-goal 2> |
|
|
139
147
|
```
|
|
140
148
|
|
|
141
|
-
Each non-goal must say what it does NOT cover, not a vague "out of scope".
|
|
149
|
+
Each non-goal must say what it does NOT cover, not a vague "out of scope" (AI cannot infer exclusion from omission - state it positively). The scope line frames the goals below; detailed rules live in Section 4.4.
|
|
142
150
|
|
|
143
151
|
## 3. Akış Diyagramı / Flow Chart
|
|
144
152
|
|
|
@@ -217,7 +225,7 @@ And <secondary outcome>
|
|
|
217
225
|
Given <precondition>
|
|
218
226
|
But <conflicting state>
|
|
219
227
|
When <action>
|
|
220
|
-
Then <
|
|
228
|
+
Then <business-rule code shown, e.g. ERR-211>
|
|
221
229
|
And <user guidance>
|
|
222
230
|
\`\`\`
|
|
223
231
|
|
|
@@ -228,9 +236,18 @@ Given <precondition>
|
|
|
228
236
|
When <action>
|
|
229
237
|
Then <expected outcome>
|
|
230
238
|
\`\`\`
|
|
239
|
+
|
|
240
|
+
### 4.4 İş Kuralları / Business Rules
|
|
241
|
+
|
|
242
|
+
Locked 31 - the traceability spine. Every business rule is deterministic and testable (same input always yields the same outcome) and carries a stable id `BR-<slug>-NN`. Each rule maps to at least one acceptance criterion written Given / When / Then. In Full mode each acceptance criterion is then realized as unit-test scenarios in Section 15.1; in Lite mode Section 15 is not rendered, so the acceptance criterion stands alone here (it is itself the testable statement) and the 15.1 mapping is deferred to a later Full analysis or to `/multi-agent:dev`. Rules come from the spec / Confluence / Jira / annotations, never invented; a rule with no source cites `[label TBD - see Open Questions]` and adds a Section 20 row.
|
|
243
|
+
|
|
244
|
+
| BR ID | Kural / Rule (deterministic, testable) | Kaynak / Source | Kabul kriteri / Acceptance criterion (Given/When/Then) |
|
|
245
|
+
|---|---|---|---|
|
|
246
|
+
| BR-<slug>-01 | <rule statement> | [Confluence:<pageId>:<heading>] / [Figma annotation <nodeId>] / file:line | Given <state>, When <action>, Then <binary outcome> |
|
|
247
|
+
| BR-<slug>-02 | <rule statement> | <source> | Given ..., When ..., Then ... (+ >=1 failure-mode criterion) |
|
|
231
248
|
```
|
|
232
249
|
|
|
233
|
-
Each scenario cites the matching
|
|
250
|
+
Each scenario in 4.1-4.3 cites the matching `BR-<slug>-NN` id (and, when a server code applies, the Section 9.4 code). Plain-prose user stories without Given/When/Then are rejected by the renderer.
|
|
234
251
|
|
|
235
252
|
## 5. Tasarım Referansı / Design Reference
|
|
236
253
|
|
|
@@ -266,6 +283,24 @@ Token references for each variant (no raw numbers).
|
|
|
266
283
|
Tier in use: <1 (MCP) | 2 (REST) | 3 (user screenshot)>
|
|
267
284
|
Screenshots attached: <count>
|
|
268
285
|
Auth retries used: <count>
|
|
286
|
+
|
|
287
|
+
### 5.4 Yerleşim ve Kaydırma / Layout and Scroll
|
|
288
|
+
|
|
289
|
+
Precise-enough-to-reproduce layout. Spacing/padding are token names (no raw numbers, Locked bound to Section 7). One row per meaningful component or frame region.
|
|
290
|
+
|
|
291
|
+
| Bileşen / Component | Node | Auto Layout (yön/wrap) | Padding (kenar/edge -> token) | Boşluk / Gap (token) | Hizalama / Alignment | Boyut / Sizing (fixed/hug/fill) | Konum / Position (constraint) |
|
|
292
|
+
|---|---|---|---|---|---|---|---|
|
|
293
|
+
| <name> | <nodeId> | vertical | top Spacing.spacing16 / others Spacing.spacing12 | Spacing.spacing8 | leading / center | width fill, height hug | pinned top |
|
|
294
|
+
|
|
295
|
+
**Scroll and container behavior** (a static Figma frame cannot express this - state it explicitly for both the AI and the human):
|
|
296
|
+
|
|
297
|
+
- Kaydırma kabı / Scroll container: <which region scrolls; single vertical scroll vs nested>
|
|
298
|
+
- Sabit / Sticky: <header/footer/CTA pinned while content scrolls, or none>
|
|
299
|
+
- İçerik kenar boşlukları / Content insets: <token or safe-area-relative>
|
|
300
|
+
- Güvenli alan / Safe area: <edges respected: top notch / bottom home indicator / status bar>
|
|
301
|
+
- Klavye / Keyboard avoidance: <inset-adjust vs scroll-into-view vs none>
|
|
302
|
+
|
|
303
|
+
Capture order (tokens never hardcoded): `mcp__claude_ai_Figma__get_metadata` (tree) -> `mcp__claude_ai_Figma__get_variable_defs` (resolve spacing/color/type tokens) -> render -> `mcp__claude_ai_Figma__get_screenshot` (visual check). Run per section/frame for fidelity, not per whole page.
|
|
269
304
|
```
|
|
270
305
|
|
|
271
306
|
Frame gallery images are uploaded to Confluence as multipart attachments and referenced via `<ac:image><ri:attachment ri:filename="..." /></ac:image>` (Locked 17). Section URL drilling enumerates all child frames automatically via `mcp__claude_ai_Figma__get_metadata` or `figma-screenshot.sh --section`.
|
|
@@ -287,6 +322,18 @@ Per-platform projection.
|
|
|
287
322
|
|
|
288
323
|
Locked 11 - existence is resolved against `evidence.codeConnect[]` (the Code Connect index from Phase 1b.1) first: a design `nodeId` (or its `fileKey`) that matches an index entry emits a `reuse` row (`Mevcut/Existing = yes`, pointing at the bound component + path), because figma-to-swiftui already built and bound it. A `nodeId` with no Code Connect match falls back to the Phase 1b `uiComponents` heuristic; only when neither finds a candidate does the row become a new component (`Existing = no`, added to Section 14).
|
|
289
324
|
|
|
325
|
+
**Code Connect snippet is an existence oracle, not spec truth.** The Code Connect index answers "does this component already exist and is it bound", nothing more. A Code Connect snippet's contents are NOT authoritative for copy or for what the new screen needs - they describe what the codebase currently maps. Drift between a snippet and the current design is logged ONCE as a design-system-maintenance note in Section 20 (Open Questions), never as a per-string verification row.
|
|
326
|
+
|
|
327
|
+
### 6.X Varyant / prop kapsamı - Variant / property coverage
|
|
328
|
+
|
|
329
|
+
Completeness audit: every property of a consumed Figma component gets exactly one disposition, so the spec proves it examined the whole component surface. An unmapped remainder is a Section 20 Risk.
|
|
330
|
+
|
|
331
|
+
| Bileşen / Component | Özellik / Property | Karar / Disposition | Not / Note |
|
|
332
|
+
|---|---|---|---|
|
|
333
|
+
| <Name> | <figmaProperty> | implement | <mapped concept / param> |
|
|
334
|
+
| <Name> | <figmaProperty> | skip | <reason not used on this screen> |
|
|
335
|
+
| <Name> | <figmaProperty> | defer | <open question ref> |
|
|
336
|
+
|
|
290
337
|
## 7. Tasarım Token'ları / Design Tokens
|
|
291
338
|
|
|
292
339
|
Per-platform projection. No magic numbers, no raw hex, no raw `.font()`.
|
|
@@ -315,9 +362,20 @@ Per-platform projection. No magic numbers, no raw hex, no raw `.font()`.
|
|
|
315
362
|
|---|---|
|
|
316
363
|
| .h6 | <where> |
|
|
317
364
|
| .body1 | <where> |
|
|
365
|
+
|
|
366
|
+
### 7.4 Karanlık Mod / Dark Mode
|
|
367
|
+
|
|
368
|
+
Rendered when the design ships a dark variant (else drop with note `(N/A: no dark variant)`). Each row is a semantic token PAIR, never a literal hue - the token resolves per scheme. Re-verify contrast in dark; a light-passing pair can fail dark.
|
|
369
|
+
|
|
370
|
+
| Rol / Role | Light token | Dark token | Not / Note (elevation, asset variant, contrast) |
|
|
371
|
+
|---|---|---|---|
|
|
372
|
+
| surface/base | Color.Surface.base | Color.Surface.base (adaptive) | iOS systemBackground; Android tonal elevation (no shadow in dark) |
|
|
373
|
+
| content/primary | Color.Content.primary | Color.Content.primary | contrast >= 4.5:1 rechecked in dark |
|
|
374
|
+
| border/subtle | Color.Border.subtle | Color.Border.subtle | - |
|
|
375
|
+
| <named asset> | icon@light | icon@dark | asset-catalog appearance variant |
|
|
318
376
|
```
|
|
319
377
|
|
|
320
|
-
Per-platform projection translates token names: iOS uses `.Spacing.spacingN` enum; Android uses `MaterialTheme.spacing.medium`
|
|
378
|
+
Per-platform projection translates token names: iOS uses `.Spacing.spacingN` enum + adaptive `Color(.systemBackground)` / asset appearances; Android uses `MaterialTheme.spacing.medium` + Material 3 color roles with tonal elevation in dark; Frontend uses CSS variables `--spacing-md` / `--color-*` per `prefers-color-scheme`; Backend section omitted.
|
|
321
379
|
|
|
322
380
|
## 8. Asset Envanteri / Asset Inventory
|
|
323
381
|
|
|
@@ -379,40 +437,63 @@ List every HTTP status code one by one.
|
|
|
379
437
|
| 422 | business rule violated | `{"code":"FP-031"}` | <UI> |
|
|
380
438
|
| 500 | server error | `{"code":"GEN-500"}` | <UI> |
|
|
381
439
|
|
|
382
|
-
### 9.4
|
|
440
|
+
### 9.4 Business-rule catalog (when applicable)
|
|
383
441
|
|
|
384
442
|
| Kod / Code | Anlam / Meaning | UI konumu / UI location |
|
|
385
443
|
|---|---|---|
|
|
386
444
|
| FP-011 | Invalid format | <screen> |
|
|
387
|
-
|
|
|
445
|
+
| ERR-211 | Empty result set | <screen> |
|
|
388
446
|
```
|
|
389
447
|
|
|
390
448
|
## 10. Lokalizasyon Anahtarları / Localization Keys
|
|
391
449
|
|
|
392
|
-
Per-platform projection. Locked
|
|
450
|
+
Per-platform projection. Locked 20 - shape depends on the project `figma-config` `localization.ownership`. The locale set comes from `localization.locales` (default `tr, en, ar, de, es, fr, it, ru`); never hardcode a locale list.
|
|
451
|
+
|
|
452
|
+
### Mode A - `in-repo` (default): full per-locale grid
|
|
453
|
+
|
|
454
|
+
New keys carry filled cells for every configured locale.
|
|
393
455
|
|
|
394
456
|
```markdown
|
|
395
457
|
## 10. Lokalizasyon Anahtarları <!-- TR -->
|
|
396
458
|
## 10. Localization Keys <!-- EN -->
|
|
397
459
|
|
|
398
|
-
Desteklenen diller: TR, EN, AR, DE, ES, FR,
|
|
460
|
+
Desteklenen diller / Supported locales: <localization.locales, e.g. TR, EN, AR, DE, ES, FR, IT, RU>
|
|
399
461
|
|
|
400
|
-
| Key |
|
|
401
|
-
|
|
402
|
-
| <key 1> | <
|
|
403
|
-
| <key 2> | <
|
|
404
|
-
| <key 3> | <tr> | <en> | [bekleniyor: çeviri ekibi]
|
|
462
|
+
| Key | <loc1> | <loc2> | ... | Source | Status |
|
|
463
|
+
|---|---|---|---|---|---|
|
|
464
|
+
| <key 1> | <val> | <val> | ... | Figma annotation <nodeId> | new |
|
|
465
|
+
| <key 2> | <val> | <val> | ... | repo:Localizable.xcstrings | reuse |
|
|
466
|
+
| <key 3> | <tr> | <en> | [bekleniyor: çeviri ekibi] / [pending: translation team] | Figma annotation <nodeId> | partial |
|
|
405
467
|
```
|
|
406
468
|
|
|
407
|
-
Status values:
|
|
408
|
-
|
|
409
469
|
| Status | Meaning |
|
|
410
470
|
|---|---|
|
|
411
|
-
| reuse | Existing
|
|
412
|
-
| new | New key, all
|
|
413
|
-
| partial |
|
|
471
|
+
| reuse | Existing key, no new translation needed |
|
|
472
|
+
| new | New key, all configured locales must be filled |
|
|
473
|
+
| partial | Base locales filled, rest pending translation team. Dispatch flags `i18n_pending: <count>` as blocker. |
|
|
474
|
+
|
|
475
|
+
### Mode B - `externally-owned`: keys + ownership, values deferred
|
|
476
|
+
|
|
477
|
+
Per-locale values are owned by `localization.authoringPipeline` and are NOT hand-filled here (they would be overwritten by the owning system and were never the analyst's to write). List the key, its status, the copy source, and the `localization.baseLanguage` value only.
|
|
414
478
|
|
|
415
|
-
|
|
479
|
+
```markdown
|
|
480
|
+
## 10. Lokalizasyon Anahtarları <!-- TR -->
|
|
481
|
+
## 10. Localization Keys <!-- EN -->
|
|
482
|
+
|
|
483
|
+
Sahiplik / Ownership: <localization.authoringPipeline> (per-locale values authored there)
|
|
484
|
+
Temel dil / Base language: <localization.baseLanguage>
|
|
485
|
+
|
|
486
|
+
| Key (<keyPattern>) | Durum / Status | Kopya kaynağı / Copy source | Temel değer / Base value | Sahiplik / Ownership |
|
|
487
|
+
|---|---|---|---|---|
|
|
488
|
+
| <Namespace.Case> | new | Figma annotation <nodeId> | <base-language value> | <authoringPipeline> |
|
|
489
|
+
| <Namespace.Case> | reuse | existing catalog | (existing) | <authoringPipeline> |
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Gate (Mode B): the base-language value is present (sourced from the Figma annotation per Locked 3) AND the ownership reference is cited. A key missing its base-language value is a blocker - at runtime a missing base value renders the raw `Namespace.Case` key on screen. Fabricated per-locale values are rejected.
|
|
493
|
+
|
|
494
|
+
Label-authoring contract (Mode B): the analysis names each label and its base copy; it does NOT write per-locale values or hand-edit the owning system's files. A `new` key is a pure addition (auto-mergeable by the authoring pipeline). A `reuse` key that needs a copy change is an EDIT - flag it as review-gated (the owning pipeline requires approval) and never silently overwrite. The base copy always comes from the Figma annotation; when the annotation is missing a language the authoring pipeline expects, emit a Section 20 row rather than inventing it.
|
|
495
|
+
|
|
496
|
+
Per-platform projection (both modes):
|
|
416
497
|
|
|
417
498
|
| Platform | File format | Key convention | Example |
|
|
418
499
|
|---|---|---|---|
|
|
@@ -426,15 +507,16 @@ Per-platform projection:
|
|
|
426
507
|
```markdown
|
|
427
508
|
## 11. Analytics
|
|
428
509
|
|
|
429
|
-
| Event | Parametreler / Params | Tetiklenme / Trigger | Source | Status |
|
|
510
|
+
| Event | Parametreler / Params | Tetiklenme / Trigger (story / rule) | Source | Status |
|
|
430
511
|
|---|---|---|---|---|
|
|
431
|
-
| <event_name> | <param1, param2> |
|
|
432
|
-
| <event_name> | <params> | <
|
|
512
|
+
| <event_name> | <param1, param2> | 4.1 happy path / BR-<slug>-01 | <Firebase schema / repo / Confluence> | new |
|
|
513
|
+
| <event_name> | <params> | <story or BR id> | <source> | reuse |
|
|
433
514
|
|
|
515
|
+
Each event's Trigger cites the user story (Section 4) or business rule (`BR-` id, Section 4.4) it fires on, so the analytics plan traces to behavior (Locked 31).
|
|
434
516
|
PII redaction: <list of fields hashed before telemetry>
|
|
435
517
|
```
|
|
436
518
|
|
|
437
|
-
Locked 11 - direct-match events emit reuse rows. Locked 17 - PII fields (
|
|
519
|
+
Locked 11 - direct-match events emit reuse rows. Locked 17 - PII fields (email, phone, national ID) hashed before emit.
|
|
438
520
|
|
|
439
521
|
## 12. Deeplink ve Push Notification / Deeplink and Push
|
|
440
522
|
|
|
@@ -530,7 +612,7 @@ Locked 21 - platform-agnostic concept layer rendered with repo conventions.
|
|
|
530
612
|
<rendered navigator interface per Pass B>
|
|
531
613
|
\`\`\`
|
|
532
614
|
|
|
533
|
-
### 13.
|
|
615
|
+
### 13.5.1 DI registration
|
|
534
616
|
|
|
535
617
|
\`\`\`<lang>
|
|
536
618
|
<rendered DI register line per Pass B>
|
|
@@ -548,28 +630,28 @@ One preview entry per variant matters for Xcode Canvas and snapshot test alignme
|
|
|
548
630
|
| Default | LTR Dark | dark mode regression check |
|
|
549
631
|
| Default | RTL Light | RTL layout regression |
|
|
550
632
|
| Default | Dynamic Type accessibilityLarge | scaling regression |
|
|
551
|
-
| Error state | LTR Light | empty state,
|
|
633
|
+
| Error state | LTR Light | empty state, error message |
|
|
552
634
|
| Loading state | LTR Light | skeleton or spinner |
|
|
553
|
-
| Edge case (e.g. long copy, single
|
|
635
|
+
| Edge case (e.g. long copy, single item) | LTR Light | optional, when distinct from canonical |
|
|
554
636
|
|
|
555
637
|
Preview snippet (Pass B fills variant list per Phase 1c convention):
|
|
556
638
|
|
|
557
639
|
\`\`\`swift
|
|
558
640
|
#Preview("Default") {
|
|
559
|
-
|
|
641
|
+
UserProfileView(viewModel: .preview(.idle))
|
|
560
642
|
}
|
|
561
643
|
|
|
562
|
-
#Preview("Error -
|
|
563
|
-
|
|
644
|
+
#Preview("Error - ERR-211") {
|
|
645
|
+
UserProfileView(viewModel: .preview(.error(.PP_211)))
|
|
564
646
|
}
|
|
565
647
|
|
|
566
648
|
#Preview("Dark") {
|
|
567
|
-
|
|
649
|
+
UserProfileView(viewModel: .preview(.idle))
|
|
568
650
|
.preferredColorScheme(.dark)
|
|
569
651
|
}
|
|
570
652
|
|
|
571
653
|
#Preview("RTL") {
|
|
572
|
-
|
|
654
|
+
UserProfileView(viewModel: .preview(.idle))
|
|
573
655
|
.environment(\.layoutDirection, .rightToLeft)
|
|
574
656
|
}
|
|
575
657
|
\`\`\`
|
|
@@ -610,11 +692,22 @@ Per-platform projection.
|
|
|
610
692
|
## 15. Test Planı <!-- TR -->
|
|
611
693
|
## 15. Test Plan <!-- EN -->
|
|
612
694
|
|
|
613
|
-
### 15.1 Birim testleri / Unit tests
|
|
695
|
+
### 15.1 Birim testleri / Unit tests (kural bazlı / rule-driven)
|
|
614
696
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
697
|
+
Locked 31 - one sub-table per business rule from Section 4.4. Enumerate the cases: happy, boundary (min/max, off-by-one), error/failure, empty/nil. Each row is Given / When / Then plus a framework-correct test name that traces back to the `BR-` id. Boundary rows collapse into one parameterized test.
|
|
698
|
+
|
|
699
|
+
Framework per platform (from Phase 1c conventions; these are the modern defaults): iOS Swift Testing (`@Test`, `@Suite`, `@Test(arguments:)` for boundary tables, `#expect` / `#require`); Android JUnit5 + MockK (`coEvery` / `coVerify`) + Turbine (`flow.test { awaitItem() }`) + coroutines-test (`runTest`, `StandardTestDispatcher`); Backend pytest (parametrize); Frontend Vitest.
|
|
700
|
+
|
|
701
|
+
**BR-<slug>-01**
|
|
702
|
+
|
|
703
|
+
| Senaryo / Case | Given | When | Then | Test adı / name |
|
|
704
|
+
|---|---|---|---|---|
|
|
705
|
+
| happy | <state> | <action> | <expected> | `func rule_validInput_returnsExpected()` ^[C4 <conf>] |
|
|
706
|
+
| boundary | <edge state> | <action> | <expected> | `@Test(arguments:)` parametrized |
|
|
707
|
+
| error | <failure state> | <action> | <error/emission> | `func rule_failure_emitsError()` |
|
|
708
|
+
| empty/nil | <empty state> | <action> | <default/guard> | `func rule_empty_returnsIdle()` |
|
|
709
|
+
|
|
710
|
+
(Repeat one sub-table per rule. A rule with no unit-test row fails the dispatch gate, Locked 31.)
|
|
618
711
|
|
|
619
712
|
### 15.2 Görsel regresyon / Snapshot tests
|
|
620
713
|
|
|
@@ -636,11 +729,11 @@ Cihaz boyutları / Device sizes: <list>
|
|
|
636
729
|
|
|
637
730
|
### 15.4 Kabul senaryoları / Acceptance scenarios
|
|
638
731
|
|
|
639
|
-
Gherkin scenarios
|
|
732
|
+
Section 4 Gherkin scenarios + their `BR-` ids mapped to test names.
|
|
640
733
|
|
|
641
|
-
| Senaryo |
|
|
642
|
-
|
|
643
|
-
| 4.1 happy path | <
|
|
734
|
+
| Senaryo | BR ID | Test |
|
|
735
|
+
|---|---|---|
|
|
736
|
+
| 4.1 happy path | BR-<slug>-01 | <unit or UI test name> |
|
|
644
737
|
|
|
645
738
|
### 15.5 Performans bütçesi / Performance budget
|
|
646
739
|
|
|
@@ -649,6 +742,16 @@ Gherkin scenarios from Section 4 mapped to UI test names.
|
|
|
649
742
|
| Cold start to ready | <ms> |
|
|
650
743
|
| Scroll FPS | 60 |
|
|
651
744
|
| Memory peak | <MB> |
|
|
745
|
+
|
|
746
|
+
### 15.6 UI test senaryoları / UI test scenarios (OPTIONAL)
|
|
747
|
+
|
|
748
|
+
Rendered ONLY when `state.analysisSpec.options.uiTests == true` (Phase 0 Step 5a opt-in); otherwise omitted. UI tests are the most expensive tier - reserve for high-risk / revenue-connected cross-screen flows, not per-element coverage. Selectors are stable ids (iOS `accessibilityIdentifier`, Android `Modifier.testTag` / `contentDescription`), never localized text, so the flow survives copy/locale changes and an agent can generate it mechanically.
|
|
749
|
+
|
|
750
|
+
| Flow ID | Öncelik / Priority (why it earns a UI test) | Adımlar / Steps | Seçiciler / Selectors | Doğrulamalar / Assertions | Ön koşullar / Preconditions | BR ID |
|
|
751
|
+
|---|---|---|---|---|---|---|
|
|
752
|
+
| UI-<slug>-01 | <high-risk reason> | <ordered user actions> | <accessibilityIdentifier / testTag> | <visible state / navigation / error> | <seeded data / launch args / mocked network> | BR-<slug>-NN |
|
|
753
|
+
|
|
754
|
+
Framework: iOS XCUITest (`waitForExistence`, identifier-driven); Android Compose UI test (`createComposeRule`, `onNodeWithTag`) or Espresso for View-based screens.
|
|
652
755
|
```
|
|
653
756
|
|
|
654
757
|
## 16. Erişilebilirlik / Accessibility
|
|
@@ -659,17 +762,29 @@ Per-platform projection. WCAG 2.2 AA + Apple HIG + Material A11y.
|
|
|
659
762
|
## 16. Erişilebilirlik <!-- TR -->
|
|
660
763
|
## 16. Accessibility <!-- EN -->
|
|
661
764
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
|
665
|
-
|
|
666
|
-
|
|
|
667
|
-
|
|
|
668
|
-
|
|
|
669
|
-
|
|
|
670
|
-
|
|
|
671
|
-
|
|
|
672
|
-
|
|
|
765
|
+
### 16.1 Kontrol listesi / Checklist (always)
|
|
766
|
+
|
|
767
|
+
| Madde / Item | Durum / Status | Notlar / Notes | WCAG 2.2 |
|
|
768
|
+
|---|---|---|---|
|
|
769
|
+
| Unique identifier on every interactive element | required | <per C5 convention> | 4.1.2 |
|
|
770
|
+
| Minimum tap target (iOS 44x44 pt / Android 48x48 dp / Web 44x44 px) | required | exceeds AA | 2.5.8 |
|
|
771
|
+
| Screen reader label (accessibilityLabel / contentDescription / aria-label) | required | | 1.1.1 / 4.1.2 |
|
|
772
|
+
| Hint text (accessibilityHint / stateDescription / aria-description) | optional | | 4.1.2 |
|
|
773
|
+
| Dynamic Type / font scaling to 200% | required | | 1.4.4 |
|
|
774
|
+
| Contrast ratio >= 4.5:1 (text) / 3:1 (large) | required | recheck in dark | 1.4.3 |
|
|
775
|
+
| State expressed beyond colour | required | | 1.4.1 |
|
|
776
|
+
| Reduce motion honored | required | | 2.3.3 |
|
|
777
|
+
| Keyboard navigation (web only) | required | tabindex + focus order | 2.1.1 / 2.4.3 |
|
|
778
|
+
| Voice Over / TalkBack reading order | required | see 16.2 when full depth | 2.4.3 |
|
|
779
|
+
|
|
780
|
+
### 16.2 Ekran okuyucu gezinme / Screen-reader walkthrough (OPTIONAL)
|
|
781
|
+
|
|
782
|
+
Rendered ONLY when `state.analysisSpec.options.a11yDepth == "full"` (Phase 0 Step 5a opt-in); otherwise omitted. An ordered reading-order script that is BOTH the implementation spec and the manual VoiceOver / TalkBack test. One row per focus stop, in traversal order.
|
|
783
|
+
|
|
784
|
+
| # | Odak / Focus target | Seslendirilen / Announced (label + role/trait + value + hint) | Hareket / Gesture | Sonuç / Resulting state |
|
|
785
|
+
|---|---|---|---|---|
|
|
786
|
+
| 1 | <element + selector> | "<label>, <role>, <value>. <hint>" | swipe right | focus moves to 2 |
|
|
787
|
+
| 2 | <element> | "<...>" | double-tap | <navigation / state change> |
|
|
673
788
|
```
|
|
674
789
|
|
|
675
790
|
## 17. Güvenlik ve Gizlilik / Security and Privacy
|
|
@@ -700,17 +815,15 @@ OWASP MASVS quick check:
|
|
|
700
815
|
|
|
701
816
|
## 18. Yayılım Planı / Rollout Plan
|
|
702
817
|
|
|
818
|
+
Compact checklist only (default-drop per the omission table when the pipeline's own commit/PR/rollout phase already covers it). Include a row ONLY when it carries feature-specific information beyond the standard pipeline flow.
|
|
819
|
+
|
|
703
820
|
```markdown
|
|
704
821
|
## 18. Yayılım Planı <!-- TR -->
|
|
705
822
|
## 18. Rollout Plan <!-- EN -->
|
|
706
823
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
| Code review | <number of approvals, who> |
|
|
711
|
-
| QA test | <devices and versions> |
|
|
712
|
-
| Staged rollout | <internal -> beta -> production %> |
|
|
713
|
-
| Rollback | <remote flag name> |
|
|
824
|
+
- Staged rollout: <internal -> beta -> production %, or "standard pipeline flow">
|
|
825
|
+
- Feature flag / rollback: <remote flag name, or "none">
|
|
826
|
+
- QA gate: <feature-specific device/version notes, or "standard">
|
|
714
827
|
```
|
|
715
828
|
|
|
716
829
|
## 19. Değerlendirilen Alternatifler / Alternatives Considered
|
|
@@ -779,13 +892,11 @@ Footer. Optional in Lite mode. Alphabetical.
|
|
|
779
892
|
|
|
780
893
|
| Kısaltma / Abbreviation | Açıklama / Description |
|
|
781
894
|
|---|---|
|
|
782
|
-
| APIS | Advance Passenger Information System |
|
|
783
895
|
| ATS | App Transport Security |
|
|
784
896
|
| BFF | Backend For Frontend |
|
|
785
897
|
| CTA | Call To Action |
|
|
786
|
-
|
|
|
787
|
-
|
|
|
788
|
-
| PNR | Passenger Name Record |
|
|
898
|
+
| DTO | Data Transfer Object |
|
|
899
|
+
| SSOT | Single Source of Truth |
|
|
789
900
|
| WCAG | Web Content Accessibility Guidelines |
|
|
790
901
|
```
|
|
791
902
|
|
|
@@ -817,19 +928,19 @@ Conventions extracted: feature-first + clean arch, `<Feature>ViewModel`, sealed
|
|
|
817
928
|
|
|
818
929
|
```swift
|
|
819
930
|
@MainActor
|
|
820
|
-
final class
|
|
821
|
-
@Published private(set) var state:
|
|
822
|
-
private let fetchUseCase:
|
|
931
|
+
final class UserProfileViewModel: ObservableObject {
|
|
932
|
+
@Published private(set) var state: UserProfileUIState = .loading
|
|
933
|
+
private let fetchUseCase: FetchUserProfileUseCaseProtocol
|
|
823
934
|
|
|
824
|
-
init(fetchUseCase:
|
|
935
|
+
init(fetchUseCase: FetchUserProfileUseCaseProtocol) {
|
|
825
936
|
self.fetchUseCase = fetchUseCase
|
|
826
937
|
}
|
|
827
938
|
|
|
828
939
|
func onAppear() async {
|
|
829
940
|
do {
|
|
830
941
|
let result = try await fetchUseCase.execute()
|
|
831
|
-
state = .idle(
|
|
832
|
-
} catch let error as
|
|
942
|
+
state = .idle(profile: result.profile)
|
|
943
|
+
} catch let error as DomainError {
|
|
833
944
|
state = .error(error.code)
|
|
834
945
|
} catch {
|
|
835
946
|
state = .error(.generic)
|
|
@@ -837,17 +948,17 @@ final class PassengerFlightViewModel: ObservableObject {
|
|
|
837
948
|
}
|
|
838
949
|
}
|
|
839
950
|
|
|
840
|
-
enum
|
|
951
|
+
enum UserProfileUIState {
|
|
841
952
|
case loading
|
|
842
|
-
case idle(
|
|
843
|
-
case error(
|
|
953
|
+
case idle(profile: ProfileEntity)
|
|
954
|
+
case error(DomainErrorCode)
|
|
844
955
|
}
|
|
845
956
|
```
|
|
846
957
|
|
|
847
|
-
Folder: `Domains/
|
|
958
|
+
Folder: `Domains/Profile/Sources/Profile/Screens/ProfileEditor/Presentation/ViewModel/`
|
|
848
959
|
Test name: `@Test func fetch_validInput_returnsIdle()`
|
|
849
|
-
Identifier: `
|
|
850
|
-
Localization key: `
|
|
960
|
+
Identifier: `userProfile.continueButton`
|
|
961
|
+
Localization key: `UserProfile.ContinueButton`
|
|
851
962
|
|
|
852
963
|
## Android render example
|
|
853
964
|
|
|
@@ -855,97 +966,96 @@ Conventions extracted: feature module + clean arch, `<Feature>ViewModel`, sealed
|
|
|
855
966
|
|
|
856
967
|
```kotlin
|
|
857
968
|
@HiltViewModel
|
|
858
|
-
class
|
|
859
|
-
private val fetchUseCase:
|
|
969
|
+
class UserProfileViewModel @Inject constructor(
|
|
970
|
+
private val fetchUseCase: FetchUserProfileUseCase
|
|
860
971
|
) : ViewModel() {
|
|
861
|
-
private val _state = MutableStateFlow<
|
|
862
|
-
val state: StateFlow<
|
|
972
|
+
private val _state = MutableStateFlow<UserProfileUiState>(UserProfileUiState.Loading)
|
|
973
|
+
val state: StateFlow<UserProfileUiState> = _state.asStateFlow()
|
|
863
974
|
|
|
864
975
|
fun onAppear() {
|
|
865
976
|
viewModelScope.launch {
|
|
866
977
|
try {
|
|
867
978
|
val result = fetchUseCase.execute()
|
|
868
|
-
_state.value =
|
|
869
|
-
} catch (e:
|
|
870
|
-
_state.value =
|
|
979
|
+
_state.value = UserProfileUiState.Idle(result.profile)
|
|
980
|
+
} catch (e: DomainException) {
|
|
981
|
+
_state.value = UserProfileUiState.Error(e.code)
|
|
871
982
|
}
|
|
872
983
|
}
|
|
873
984
|
}
|
|
874
985
|
}
|
|
875
986
|
|
|
876
|
-
sealed interface
|
|
877
|
-
data object Loading :
|
|
878
|
-
data class Idle(val
|
|
879
|
-
data class Error(val code:
|
|
987
|
+
sealed interface UserProfileUiState {
|
|
988
|
+
data object Loading : UserProfileUiState
|
|
989
|
+
data class Idle(val profile: Profile) : UserProfileUiState
|
|
990
|
+
data class Error(val code: DomainErrorCode) : UserProfileUiState
|
|
880
991
|
}
|
|
881
992
|
```
|
|
882
993
|
|
|
883
|
-
Folder: `feature/
|
|
994
|
+
Folder: `feature/profile/src/main/java/<package>/userprofile/ui/`
|
|
884
995
|
Test name: `fun fetch_validInput_returnsIdle()`
|
|
885
|
-
testTag: `
|
|
886
|
-
strings.xml: `
|
|
996
|
+
testTag: `userProfileContinueButton`
|
|
997
|
+
strings.xml: `user_profile_continue_button`
|
|
887
998
|
|
|
888
999
|
## Backend render example
|
|
889
1000
|
|
|
890
1001
|
Conventions extracted: layered Python FastAPI, router + service + repository, Pydantic DTO, pytest snake_case, FastAPI Depends.
|
|
891
1002
|
|
|
892
1003
|
```python
|
|
893
|
-
class
|
|
1004
|
+
class UserProfileService:
|
|
894
1005
|
def __init__(
|
|
895
1006
|
self,
|
|
896
|
-
repository:
|
|
897
|
-
validator:
|
|
1007
|
+
repository: UserProfileRepository,
|
|
1008
|
+
validator: ProfileInputValidator,
|
|
898
1009
|
):
|
|
899
1010
|
self.repository = repository
|
|
900
1011
|
self.validator = validator
|
|
901
1012
|
|
|
902
|
-
async def fetch(self, request:
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
return PassengerFlightOut(passengers=passengers, flights=flights)
|
|
1013
|
+
async def fetch(self, request: UserProfileIn) -> UserProfileOut:
|
|
1014
|
+
profile = await self.repository.get_profile(request.user_id)
|
|
1015
|
+
return UserProfileOut(profile=profile)
|
|
906
1016
|
|
|
907
1017
|
|
|
908
|
-
@router.post("/
|
|
909
|
-
async def
|
|
910
|
-
request:
|
|
911
|
-
service:
|
|
912
|
-
) ->
|
|
1018
|
+
@router.post("/update-profile", response_model=UserProfileOut)
|
|
1019
|
+
async def update_profile(
|
|
1020
|
+
request: UserProfileIn,
|
|
1021
|
+
service: UserProfileService = Depends(get_user_profile_service),
|
|
1022
|
+
) -> UserProfileOut:
|
|
913
1023
|
return await service.fetch(request)
|
|
914
1024
|
```
|
|
915
1025
|
|
|
916
|
-
Folder: `app/api/
|
|
917
|
-
Test name: `def
|
|
918
|
-
OpenAPI operationId: `
|
|
1026
|
+
Folder: `app/api/profile/`, `app/services/profile/`, `app/schemas/profile/`
|
|
1027
|
+
Test name: `def test_fetch_valid_input_returns_profile():`
|
|
1028
|
+
OpenAPI operationId: `updateProfile`
|
|
919
1029
|
|
|
920
1030
|
## Frontend render example
|
|
921
1031
|
|
|
922
1032
|
Conventions extracted: Next.js App Router, feature folder, React Query hooks, Zustand stores, Vitest, kebab data-testid, hierarchical dot camelCase i18n.
|
|
923
1033
|
|
|
924
1034
|
```tsx
|
|
925
|
-
type
|
|
1035
|
+
type UserProfileState =
|
|
926
1036
|
| { kind: "loading" }
|
|
927
|
-
| { kind: "idle";
|
|
928
|
-
| { kind: "error"; code:
|
|
1037
|
+
| { kind: "idle"; profile: Profile }
|
|
1038
|
+
| { kind: "error"; code: DomainErrorCode };
|
|
929
1039
|
|
|
930
|
-
export function
|
|
931
|
-
return useQuery<
|
|
932
|
-
queryKey: ["
|
|
933
|
-
queryFn: () =>
|
|
1040
|
+
export function useUserProfile(userId: string) {
|
|
1041
|
+
return useQuery<UserProfileDto, DomainError>({
|
|
1042
|
+
queryKey: ["user-profile", userId],
|
|
1043
|
+
queryFn: () => userProfileApi.fetch({ userId }),
|
|
934
1044
|
});
|
|
935
1045
|
}
|
|
936
1046
|
|
|
937
|
-
export function
|
|
938
|
-
const query =
|
|
1047
|
+
export function UserProfilePage({ userId }: Props) {
|
|
1048
|
+
const query = useUserProfile(userId);
|
|
939
1049
|
if (query.isLoading) return <LoadingState />;
|
|
940
1050
|
if (query.isError) return <ErrorState code={query.error.code} />;
|
|
941
|
-
return <
|
|
1051
|
+
return <UserProfileContent data={query.data} />;
|
|
942
1052
|
}
|
|
943
1053
|
```
|
|
944
1054
|
|
|
945
|
-
Folder: `src/features/
|
|
1055
|
+
Folder: `src/features/profile/user-profile/{components,hooks,api,types}/`
|
|
946
1056
|
Test name: `it('returns idle state for valid input', ...)`
|
|
947
|
-
data-testid: `
|
|
948
|
-
i18n key: `
|
|
1057
|
+
data-testid: `user-profile-continue-button`
|
|
1058
|
+
i18n key: `userProfile.continueButton`
|
|
949
1059
|
|
|
950
1060
|
---
|
|
951
1061
|
|