@mmerterden/multi-agent-pipeline 16.23.0 → 16.25.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 +46 -0
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/design-check/SKILL.md +12 -3
- package/pipeline/multi-agent-refs/channels/jira.md +7 -0
- package/pipeline/multi-agent-refs/channels/pr.md +16 -3
- package/pipeline/multi-agent-refs/features/design-conformance.md +312 -0
- package/pipeline/multi-agent-refs/features/visual-evidence.md +172 -0
- package/pipeline/multi-agent-refs/generate-issue.md +3 -3
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +2 -0
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +4 -0
- package/pipeline/multi-agent-refs/phases/phase-5-test.md +1 -0
- package/pipeline/multi-agent-refs/phases/phase-6-commit.md +1 -1
- package/pipeline/schemas/agent-state.schema.json +268 -41
- package/pipeline/schemas/prefs.schema.json +80 -19
- package/pipeline/schemas/token-budget.json +2 -2
- package/pipeline/scripts/capture-evidence.sh +187 -0
- package/pipeline/scripts/jira-attach.sh +85 -0
- package/pipeline/skills/.skill-manifest.json +2 -2
- package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,52 @@ Internal file-layout changes that don't affect the slash-command surface are sti
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [16.25.0] - 2026-09-09
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **`features/design-conformance.md` - the component walk, ported as doctrine rather than as a tool.** An upstream corporate screen-QA skill landed with a catalog worth having, and the pipeline already owns this problem in `/multi-agent:design-check`. Copying the skill would have produced a second design auditor that drifts from the first, so what came across is the part that is portable: the catalog and the three mechanisms that turn "I looked at it" into a countable result.
|
|
24
|
+
|
|
25
|
+
**Enumerate first.** Build the component inventory once, before any comparison, with elements nested inside buttons, fields and cards exploded into their own rows - an inventory built by kind drops a button silently, and with it the button height and its label. Then fill every cell: a blank one is a fail-to-verify, never a pass.
|
|
26
|
+
|
|
27
|
+
**Measure, never read the token.** A `[MEASURE]` item compares the design against measured pixels or the resolved code value, never against the design tool's node box or the implementation's own layout token. The token is the thing under test; comparing it against itself is how a 48pt field ships against a 56pt design with every check green.
|
|
28
|
+
|
|
29
|
+
**Converge per component.** One pass catches roughly one defect on a component - the wrong font, and not that same label's wrong colour and inset. Each component is re-checked until two consecutive attempts find nothing new, capped at attempt 8 with a `did-not-converge` warning. The screen is done when every component converged, not when the walk reached the bottom.
|
|
30
|
+
|
|
31
|
+
Plus the four scope tags (`[MEASURE]` / `[CAPTURE+]` / `[A11Y]` / `[DYNAMIC]`, and a `[DYNAMIC]` item may never be silently passed), the 30 rule groups, and the bidirectional presence gate - which finds not only what the design has and the implementation lacks, but the neutral decoration the implementation adds that a one-directional colour comparison cannot see.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **`design-check` no longer halts on a module without mock support.** The mock gate gains a snapshot lane: a screen that can be rendered from its own view model is auditable even when nothing can toggle a variant at runtime. The trade is stated rather than hidden - snapshots reach modules mocks cannot, and cannot flip an in-app variant - and a declined or impossible snapshot still halts rather than fabricating a comparison.
|
|
36
|
+
|
|
37
|
+
- **The report says what it did not check.** The per-variant record gains `walk`: how many components were inventoried, how many converged, which ones hit the cap, and every `[DYNAMIC]` / `[CAPTURE+]` item that was not run, with its reason. An empty not-verified list on a run that never captured a dark or mirrored pass is a false clean, so the skip is listed instead.
|
|
38
|
+
|
|
39
|
+
- **`derivedSkillSources` records what was reviewed and refused, not only what was taken.** New `derivationNote`, `excluded` and `notDerived` fields. `tools/openapi-regen` is marked deliberately-not-derived at 0.45.0 with its reason: the skill's value is being a driver around three named scripts in one repo, so renaming its brand words leaves a skill pointing at scripts nobody else has. Without that record, every drift review rediscovers the same candidate and re-argues it.
|
|
40
|
+
|
|
41
|
+
- **`smoke-design-conformance.sh`** - 26 checks. Half of them guard the doctrine against being trimmed back into a glance; the other half name the project assets that must never arrive: a vendored colour catalog carrying cabin-class names, a typography catalog carrying licensed font families, product screen names, upstream script names. Those leak by content and carry no brand word, so a gate that greps for a company name would pass them.
|
|
42
|
+
|
|
43
|
+
## [16.24.0] - 2026-09-09
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **A UI fix now carries its picture.** Three changed files in a diff do not tell a reviewer what was wrong, and they do not tell a tester what to look for. When `taskType` is `bugfix` and a UI file changed, the run produces a before/after pair; when the work is design (a `component` task, a Figma reference, or a UI file that did not exist before), it produces the after. The verdict comes from `taskType` plus the changed-file list, mechanically, not from the model's reading of the task - the UI-file test is per stack and written down.
|
|
48
|
+
|
|
49
|
+
**Before is the reporter's screenshot, or nothing.** Rebuilding the pre-fix state to photograph it costs a second build and a second launch on every UI bug, to recreate evidence the reporter usually attached already. So Phase 0 keeps the issue's own image attachments and that is the whole of it; no image on the ticket is a recorded gap, not a search.
|
|
50
|
+
|
|
51
|
+
**After is captured in Phase 3, not Phase 5.** Phase 5 is the natural home - the simulator is already up - and it is dropped by every `autopilot` and `--local` entry, so a capture living only there produces nothing for exactly the runs nobody watched. It happens instead right after the build+test gate goes green, which is in every mode's phase set. `capture-evidence.sh` owns the naming, cleans the status bar (two shots of one screen otherwise differ by the clock, which makes every "after" look like a change) and downscales to 1242px.
|
|
52
|
+
|
|
53
|
+
- **The flow video, in three tiers, so no repo is excluded by not having a test target.** Tier 1 runs the repo's own XCUITest/Espresso target when it can be seeded with test data - the most faithful record, and it is a test that already runs in CI. Tier 2 drives the flow with `agent_run_steps` and records with `ios_record_video` / `android_record_screen`, needing nothing but a launchable build. Tier 3 is the honest skip for a library or a backend, with the reason recorded. The flow itself comes from the ticket when someone wrote one there, and from the Phase 5 test scenarios otherwise: human-written beats generated, the same rule the before follows.
|
|
54
|
+
|
|
55
|
+
- **Where they land.** The artefacts are Jira attachments. The Phase 7 comment renders them _inside_ its existing sections rather than adding a fourth - the section order is fixed and the contract says so - with the before/after thumbnails under the work summary and the flow video under the scenario it demonstrates. The PR body gains a `visuals` section that names the filenames and the ticket, deliberately not the URLs: a Jira attachment URL is auth-gated and renders as a broken image for anyone reading the PR outside a Jira session.
|
|
56
|
+
|
|
57
|
+
- **Degrading quality is allowed; dropping the artefact is not.** Jira's ceiling is an instance setting, so it is a preference (`visualEvidence.maxAttachmentMb`, default 10). Over it, a video re-encodes to 720p and a screenshot to JPEG q80; still over, the video becomes a four-frame contact sheet that says why. Phase 6 Step 3 blocks when a required artefact is neither attached nor explained - the gate is against silence, not against an honest "the ticket carries no image".
|
|
58
|
+
|
|
59
|
+
- **`smoke-visual-evidence.sh`** - 32 checks, most of them about stated absence being as required as stated presence: both scripts refuse bad input rather than guessing, the size ladder never returns an empty path, all four phases act, both renderers know where pictures go, the schemas declare the fields, and no ref re-implements the attachment POST.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- **One Jira uploader instead of two.** `generate-issue.md` carried its own attachment `curl`; visual evidence would have been the second copy, and two copies is two places for `X-Atlassian-Token: no-check` to go missing - which fails as a 403 that reads like an auth problem. Both now call `jira-attach.sh`, which resolves host and token from prefs, keeps the token out of argv, and prints `<filename>\t<url>` per file.
|
|
64
|
+
|
|
19
65
|
## [16.23.0] - 2026-09-09
|
|
20
66
|
|
|
21
67
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmerterden/multi-agent-pipeline",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.25.0",
|
|
4
4
|
"description": "8-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI and Codex CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -50,7 +50,7 @@ Phase 4: Report → coverage gate + assemble + export HTML + PDF (+ Conf
|
|
|
50
50
|
|
|
51
51
|
- **iOS**: Xcode + an available Simulator. **Android**: Android SDK + a running emulator / device.
|
|
52
52
|
- **MCP**: the `multi-agent-toolkit` MCP server (>= 2.8.0, for `design_scenario_inventory`, the `design_report` coverage gate, and `design_visual_compare` region alignment). The Figma MCP (`mcp__claude_ai_Figma__*`) must be authenticated - the user supplies the Figma URL.
|
|
53
|
-
- The selected module must
|
|
53
|
+
- The selected module must be auditable: a **mock mode** (preferred - it can toggle variants) or a **snapshot capture** of the screen's view model. Phase 0 decides which. Neither → the command halts.
|
|
54
54
|
|
|
55
55
|
## Phase Tracker Contract (mandatory)
|
|
56
56
|
|
|
@@ -112,7 +112,7 @@ for l in sys.stdin:
|
|
|
112
112
|
extra_keys: <design-check-config mock.keys, if any> })
|
|
113
113
|
```
|
|
114
114
|
Branch on `supported`:
|
|
115
|
-
- **`false`** → **
|
|
115
|
+
- **`false`** → try the **snapshot lane** before halting: a screen renderable from its own view model (filled, flow-advanced, no backend) is auditable without a runtime mock switch. Ask via `AskUserQuestion`, persist `state.designCheck.captureLane = "snapshot"`. State the trade: snapshots reach modules mocks cannot and cannot flip an in-app variant, so the target set is whatever the view model can be built into. Declined or not renderable → **HALT** with `reason`. Never fabricate a comparison.
|
|
116
116
|
- **`"debug-only"`** → surface an `AskUserQuestion` warning: mocks are compiled into the Debug build via `#if DEBUG` DI, so **variants cannot be toggled at launch** - only the default Debug state is comparable. Options (label + description in `outputLanguage`): option 1 semantically "Continue" - audit the single default Debug state only; option 2 semantically "Cancel" - stop.
|
|
117
117
|
- **`true`** → continue. Persist `state.designCheck.mock = <detect result>` (mechanism, activation, variantsHint, evidence).
|
|
118
118
|
5. **SCENARIO INVENTORY (this is the audit's target set)**:
|
|
@@ -170,6 +170,10 @@ Persist the variant list + spec + PNG paths to `state.designCheck.variants[]`.
|
|
|
170
170
|
|
|
171
171
|
**This phase iterates `state.designCheck.scope.targetIds` - a finite list decided in Phase 0. It ends when every id is resolved, not when the UI stops offering new taps.** Tap-walking alone cannot reach a state that needs a different launch argument, a different scenario case, or a typed scenario code, which is exactly how a module's error / edge states go missing.
|
|
172
172
|
|
|
173
|
+
### 3.0 What every capture is compared against
|
|
174
|
+
|
|
175
|
+
**Read `$HOME/.claude/multi-agent-refs/features/design-conformance.md` before the first compare.** It owns the per-component catalog (30 groups), the scope tags, and the three rules that decide whether this phase produced a result or an impression: enumerate first (a blank cell is a fail-to-verify), measure never the token, and converge per component (two consecutive clean attempts, cap 8). The screen is done when every component converged - not when the walk reached the bottom.
|
|
176
|
+
|
|
173
177
|
### 3.1 Drive the inventory's plan, batch by batch
|
|
174
178
|
|
|
175
179
|
**Follow `inventory.plan`, filtered to the scoped ids.** Each batch is one launch: the batch's relaunch target opens a screen, and every in-app target for that same screen is then flipped while the app is already sitting there. Driving the plan is what makes full coverage affordable - target-by-target relaunching is what made earlier runs give up a third of the way in.
|
|
@@ -260,8 +264,13 @@ Before leaving this phase, compare `covered.length + skipped.length` against `sc
|
|
|
260
264
|
truncatedInventory: <inventory.truncated>,
|
|
261
265
|
floor: <config coverage.floor, optional> },
|
|
262
266
|
variants: [ { name, figmaNodeId, perceptualPct, passed, compareSize, liveSize,
|
|
263
|
-
images, findings, fixPrompt, componentRefs
|
|
267
|
+
images, findings, fixPrompt, componentRefs,
|
|
268
|
+
walk: { components: <inventoried>, converged: <count>,
|
|
269
|
+
didNotConverge: [ { component, attempts } ],
|
|
270
|
+
notVerified: [ { item, tag, reason } ] } } ] }
|
|
264
271
|
```
|
|
272
|
+
|
|
273
|
+
`walk` separates a checked screen from a viewed one. An empty `notVerified` on a run that never captured a dark or mirrored pass is a false clean - list the skip. Field definitions: `features/design-conformance.md`.
|
|
265
274
|
Pass **ids**, not counts: the engine then names each unaccounted target in the report instead of printing an anonymous tally. `compareSize` and `liveSize` come straight from the `design_visual_compare` result.
|
|
266
275
|
3. **Render + export**:
|
|
267
276
|
```
|
|
@@ -20,6 +20,13 @@ Every Jira comment posted by this adapter follows the same section order. Sectio
|
|
|
20
20
|
|
|
21
21
|
**`summary`** - 2-5 sentences in `outputLanguage`. What changed, why, and the user-visible impact. No "we", no marketing tone. Past tense (the work is done at the time the comment goes up).
|
|
22
22
|
|
|
23
|
+
**Visual evidence inside these two sections.** When `state.visualEvidence` carries artefacts, they render INSIDE `summary` and `test_scenarios` - never as a fourth section, which the fixed section order forbids. Upload first (`jira-attach.sh <issue> <file>...`), then reference by the returned filename:
|
|
24
|
+
|
|
25
|
+
- `summary`, after its sentences: one line naming the pair in `outputLanguage` (`Düzeltme öncesi / Düzeltme sonrası`), then the thumbnails on the next line - `!<file>-before.png|thumbnail! !<file>-after.png|thumbnail!`.
|
|
26
|
+
- `test_scenarios`, under the scenario the recording demonstrates: `!<file>-flow.mp4!` plus one line stating the tier used.
|
|
27
|
+
|
|
28
|
+
A `gaps[]` entry prints its reason on the line where the artefact would have been (`Düzeltme öncesi: ticket'ta görsel yok`). Never an empty thumbnail, never a silent omission. Contract: `$HOME/.claude/multi-agent-refs/features/visual-evidence.md`.
|
|
29
|
+
|
|
23
30
|
**`test_scenarios`** - Given/When/Then numbered list. One scenario per acceptance criterion. The heading and scenario text are rendered in `outputLanguage` at write-time; the template itself (this file) shows the English skeleton:
|
|
24
31
|
|
|
25
32
|
```markdown
|
|
@@ -14,9 +14,10 @@ The PR description targets code reviewers - it stays technical. Every adapter
|
|
|
14
14
|
| 2 | `changes` | `## Değişiklikler` | `## Changes` | always |
|
|
15
15
|
| 3 | `architecture` | `## Mimari Kararlar` | `## Architecture Decisions` | when a non-trivial design choice was made |
|
|
16
16
|
| 4 | `verification` | `## Doğrulama` | `## Verification` | always |
|
|
17
|
-
| 5 | `
|
|
18
|
-
| 6 | `
|
|
19
|
-
| 7 | `
|
|
17
|
+
| 5 | `visuals` | `## Görsel Kanıt` | `## Visual Evidence` | when `state.visualEvidence.required` |
|
|
18
|
+
| 6 | `risk` | `## Risk ve Güvenlik` | `## Risk and Security` | when `state.diffRisk.signals` carries a high-stakes signal (`security_path`, `migration`, `public_api`, `no_test_change`, `test_lines_removed`) |
|
|
19
|
+
| 7 | `dependencies` | `## Bağımlılıklar` | `## Dependencies` | when deps added/removed/bumped |
|
|
20
|
+
| 8 | `related` | `## İlgili` | `## Related` | always (Jira/issue ref; never `Closes/Fixes`) |
|
|
20
21
|
|
|
21
22
|
### Section content rules
|
|
22
23
|
|
|
@@ -62,6 +63,18 @@ Multi-repo PRs (one PR per repo) emit verification commands for that repo's stac
|
|
|
62
63
|
- Rollback: feature flag <name> | git revert <sha> | none, and why
|
|
63
64
|
```
|
|
64
65
|
|
|
66
|
+
**`visuals`** - only when `state.visualEvidence.required`. The images live on the Jira issue as attachments; this section exists so a reviewer opening the PR knows they are there and what each one shows. Filenames, not raw URLs - a Jira attachment URL is auth-gated and renders as a broken image for anyone reading the PR outside a Jira session.
|
|
67
|
+
|
|
68
|
+
```markdown
|
|
69
|
+
## Visual Evidence
|
|
70
|
+
|
|
71
|
+
- Before: `<before-filename>` (attached to PROJ-XXXXX)
|
|
72
|
+
- After: `<after-filename>` (attached to PROJ-XXXXX)
|
|
73
|
+
- Flow video: `<flow-filename>`, tier <N> (attached to PROJ-XXXXX)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Every `state.visualEvidence.gaps[]` entry becomes its own line with the reason instead of a filename (`- Before: none - the ticket carries no image attachment`). Phase 6 Step 3 blocks on a required artefact that is neither listed nor explained. Contract: `$HOME/.claude/multi-agent-refs/features/visual-evidence.md`.
|
|
77
|
+
|
|
65
78
|
**`dependencies`** - only when `Package.swift` / `Podfile` / `build.gradle` / `package.json` changed. Each entry: `package@old → new - reason`.
|
|
66
79
|
|
|
67
80
|
**`related`** - flat list, plain text. Examples:
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# Design conformance - the component walk, and why a glance is not a pass
|
|
2
|
+
|
|
3
|
+
A design audit that reads a screen top to bottom and reports what looks wrong
|
|
4
|
+
finds about one defect per element: the wrong font on a label, and not that same
|
|
5
|
+
label's wrong colour and wrong inset. This file is the catalog, and the three
|
|
6
|
+
mechanisms that turn "I looked at it" into a countable result.
|
|
7
|
+
|
|
8
|
+
Derived generically from an upstream corporate screen-QA tool (recorded in
|
|
9
|
+
`derivedSkillSources`). The doctrine is portable. Its vendored design-token
|
|
10
|
+
catalogs and product examples are not, and were not taken - section 7 says what
|
|
11
|
+
replaces them.
|
|
12
|
+
|
|
13
|
+
Consumers: `/multi-agent:design-check` (the runner), Phase 4 review when a UI
|
|
14
|
+
diff is under review, and `features/visual-evidence.md` when a capture has to
|
|
15
|
+
prove a fix. Gate: `smoke-design-conformance.sh`.
|
|
16
|
+
|
|
17
|
+
## 1. Enumerate first, then fill every cell
|
|
18
|
+
|
|
19
|
+
Do **not** walk by finding, and do not walk by headline. Build the inventory
|
|
20
|
+
once, before any comparison:
|
|
21
|
+
|
|
22
|
+
1. Enumerate every visible element of the design frame - every text, icon,
|
|
23
|
+
image, field, **button**, container. An element nested inside a button, field
|
|
24
|
+
or card is **its own row**, marked as nested (`> in <Button>`), never folded
|
|
25
|
+
into its parent.
|
|
26
|
+
2. For each row, pre-fill the design side of every rule that applies and leave
|
|
27
|
+
the implementation side blank.
|
|
28
|
+
3. Fill every blank. **A blank cell is a fail-to-verify, not a pass**, and it is
|
|
29
|
+
reported as one.
|
|
30
|
+
|
|
31
|
+
The inventory is the coverage guarantee; the rule groups in section 5 are the
|
|
32
|
+
per-cell recipe. A truncated inventory makes the denominator wrong, so it is
|
|
33
|
+
reported as incomplete rather than as a clean percentage of a partial set.
|
|
34
|
+
|
|
35
|
+
## 2. Measure, never read the token
|
|
36
|
+
|
|
37
|
+
Every `[MEASURE]` item compares the design value against **measured pixels** in
|
|
38
|
+
the render, or against the implementation's resolved value in code. Never
|
|
39
|
+
against:
|
|
40
|
+
|
|
41
|
+
- the design tool's node box, which includes invisible padding, or
|
|
42
|
+
- the implementation's own layout token (a spacing or size constant, a
|
|
43
|
+
`.frame(height:)`, a Compose `Modifier.height`).
|
|
44
|
+
|
|
45
|
+
The token is the thing under test. Measuring it against itself is how a 48pt
|
|
46
|
+
field ships against a 56pt design with every check green. Button and field
|
|
47
|
+
**height** are the two a token lies about most often, because internal padding
|
|
48
|
+
and safe-area insets are not in it.
|
|
49
|
+
|
|
50
|
+
## 3. Adaptive per-component convergence
|
|
51
|
+
|
|
52
|
+
One pass over a component catches roughly one defect on it. So each component is
|
|
53
|
+
re-checked until **two consecutive attempts find nothing new on it**.
|
|
54
|
+
|
|
55
|
+
- Soft cap: reaching attempt 8 without two consecutive clean attempts stops that
|
|
56
|
+
component with a `did-not-converge` warning. There is never a ninth attempt.
|
|
57
|
+
- A component with a font, a margin and a colour defect needs about three
|
|
58
|
+
finding attempts plus two clean ones. A correct component needs the two clean
|
|
59
|
+
attempts and nothing more.
|
|
60
|
+
- The screen is done when **every** inventoried component has reached two
|
|
61
|
+
consecutive clean attempts or its cap with a warning. Not when the walk
|
|
62
|
+
reached the bottom.
|
|
63
|
+
|
|
64
|
+
On a fixing run, each attempt that found something is fixed, re-captured, and
|
|
65
|
+
its changes appended to one accumulating record, so the final artefact shows
|
|
66
|
+
what each attempt changed rather than one undifferentiated diff.
|
|
67
|
+
|
|
68
|
+
## 4. Scope tags - how an item is verified
|
|
69
|
+
|
|
70
|
+
| Tag | Meaning |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `[MEASURE]` | Verified in the primary capture: design value against measured pixels or resolved code value (section 2) |
|
|
73
|
+
| `[CAPTURE+]` | Needs an extra capture pass - a second appearance, a mirrored layout, another width, another state. Skipping one is allowed only if the skip is logged |
|
|
74
|
+
| `[A11Y]` | Accessibility lane: the platform accessibility tree (labels, element frames) plus contrast math |
|
|
75
|
+
| `[DYNAMIC]` | Behavioural or temporal; a static capture cannot show it. Verify by driving the device, or report `not verified (dynamic)`. **Never silently pass a `[DYNAMIC]` item** |
|
|
76
|
+
|
|
77
|
+
## 5. The catalog
|
|
78
|
+
|
|
79
|
+
Run every applicable item for **every** component, then every gap through
|
|
80
|
+
group 2, then the whole-screen passes (17, 21, 22, 24, 25). A check that was not
|
|
81
|
+
run is a fail-to-verify.
|
|
82
|
+
|
|
83
|
+
**1. Layout** `[MEASURE]` - x relative to container (never absolute y: the status
|
|
84
|
+
bar offsets it), y relative to an in-content span, L/R/T/B margin to parent,
|
|
85
|
+
centring, baseline alignment across a row, shared leading edge for siblings,
|
|
86
|
+
label-left/value-right rows aligned at both ends, safe-area and system-bar
|
|
87
|
+
clearance.
|
|
88
|
+
|
|
89
|
+
> **Horizontal-inset lane** - the most-skipped one. For every element record
|
|
90
|
+
> **both** sides of the leading inset: the design value and the measured
|
|
91
|
+
> implementation value. One side filled is a fail-to-verify. Then two cheap
|
|
92
|
+
> catches that need no per-element math: (a) **leading rail** - every loose,
|
|
93
|
+
> non-carded text in a vertical group must share one leading x; if the
|
|
94
|
+
> implementation puts a subtitle at the card's outer edge while the design insets
|
|
95
|
+
> it, the rail is broken; (b) **sibling consistency** - when sibling screens
|
|
96
|
+
> exist, diff the same element's inset across them; the outlier is usually the
|
|
97
|
+
> bug. A different text-wrap point is a symptom of a different margin, not only
|
|
98
|
+
> of a different font.
|
|
99
|
+
|
|
100
|
+
**2. Spacing** `[MEASURE]` - outer margins, inner padding, screen edge margin
|
|
101
|
+
(L=R symmetric), text-to-icon and icon-to-button and card-to-card and
|
|
102
|
+
section-to-section gaps, list/grid/row spacing, the vertical gap **between**
|
|
103
|
+
adjacent components (`next.y - (prev.y + prev.h)` against the stack spacing), and
|
|
104
|
+
every gap resolving to a spacing token - an off-scale value is a deviation.
|
|
105
|
+
|
|
106
|
+
**3. Size** `[MEASURE]` - width and height against the design (measured), min and
|
|
107
|
+
max honoured, `[CAPTURE+]` responsive widths (group 17), `[A11Y]` interactive tap
|
|
108
|
+
target at least 44x44pt on iOS / 48x48dp on Android, read from the accessibility
|
|
109
|
+
tree rather than assumed.
|
|
110
|
+
|
|
111
|
+
**4. Typography** `[MEASURE]` - family, size, weight, style, letter spacing, line
|
|
112
|
+
height, paragraph spacing, alignment, text transform, text decoration, foreground
|
|
113
|
+
colour, and truncation: ellipsis position, wrap and line limit matching the
|
|
114
|
+
design.
|
|
115
|
+
|
|
116
|
+
**5. Colours** `[MEASURE]` - background colour and opacity, gradient direction and
|
|
117
|
+
stops, blur effects, foreground and secondary text, disabled and placeholder,
|
|
118
|
+
border and divider and icon tint, brand accent and the status set
|
|
119
|
+
(success/error/warning/info), field prefix or affix colour, and no hardcoded
|
|
120
|
+
value: every colour resolves to a design token.
|
|
121
|
+
|
|
122
|
+
**6. Border** `[MEASURE]` - width, corner radius (scalar and per-corner), style
|
|
123
|
+
(solid vs dashed) and stroke alignment, colour and opacity.
|
|
124
|
+
|
|
125
|
+
**7. Shadow / elevation** `[MEASURE]` - present or not, colour and opacity, blur
|
|
126
|
+
radius, spread, x/y offset, and parity between the iOS shadow and the Android
|
|
127
|
+
elevation for the same surface.
|
|
128
|
+
|
|
129
|
+
**8. Shape** `[MEASURE]` - the drawn shape matches: radius about half the height
|
|
130
|
+
means a pill; radius about half the smaller side with a square box means a
|
|
131
|
+
circle.
|
|
132
|
+
|
|
133
|
+
**9. Icons** `[MEASURE]` - correct icon and variant, **size measured from the
|
|
134
|
+
rendered bounding box in both images**, never the node box or a size token,
|
|
135
|
+
weight (line vs filled), colour and tint, padding and alignment and icon-to-text
|
|
136
|
+
spacing, rotation and mirroring, rendering mode (a multicolour icon rendered as a
|
|
137
|
+
single-tint template is a defect), and vector crispness.
|
|
138
|
+
|
|
139
|
+
**10. Images** `[MEASURE]` - width and height, aspect ratio preserved, crop mode
|
|
140
|
+
(fill vs fit), no distortion, no unintended blur and sufficient resolution,
|
|
141
|
+
`[CAPTURE+]` placeholder and loading state.
|
|
142
|
+
|
|
143
|
+
**11. Buttons** `[MEASURE]` - width and **measured height** (from the rendered
|
|
144
|
+
fill rect, never a frame token), L/R/T/B spacing with L=R symmetry, the inner
|
|
145
|
+
label's font/weight/colour/alignment **as its own row**, the inner icon
|
|
146
|
+
(presence, size, colour, side, spacing), centring, fill and border and text
|
|
147
|
+
colour for the current state, `[CAPTURE+]` the state set (group 21).
|
|
148
|
+
|
|
149
|
+
> A button instance matches no text, icon or field kind, so an inventory built by
|
|
150
|
+
> kind drops it silently - and with it the button height and its nested label.
|
|
151
|
+
> The button is its own row and its children are exploded into their own rows.
|
|
152
|
+
|
|
153
|
+
**12. Input fields** `[MEASURE]` - **measured height** (the rendered field box,
|
|
154
|
+
never a frame or size token, because internal padding is not in the token),
|
|
155
|
+
width, border, radius, placeholder text and its colour and alignment, background
|
|
156
|
+
shade (read-only vs editable), prefix or affix and its colour, cursor and
|
|
157
|
+
selection colour, character counter behaviour, `[CAPTURE+]` focus/error/disabled/
|
|
158
|
+
filled states with the error message's colour, position and icon, `[DYNAMIC]`
|
|
159
|
+
keyboard type.
|
|
160
|
+
|
|
161
|
+
**13. Card** `[MEASURE]` - padding, radius, background, border, shadow, and
|
|
162
|
+
content grouping: **sample the pixel behind every text block** - is it inside a
|
|
163
|
+
card or bare on the page background? A design block in a card that the
|
|
164
|
+
implementation renders bare (or the reverse) is a wrong-container finding
|
|
165
|
+
(group 27) and is invisible to the text checks alone. Diff the design's container
|
|
166
|
+
list one-to-one against the implementation's card wrappers; the implementation
|
|
167
|
+
side is code-verifiable (inside a card helper vs placed bare in a stack with
|
|
168
|
+
padding). Never infer this from a glance.
|
|
169
|
+
|
|
170
|
+
**14. Lists** `[MEASURE]` - item height and internal padding, divider colour and
|
|
171
|
+
thickness and insets, inter-item spacing, section header grouping and order,
|
|
172
|
+
`[DYNAMIC]` scroll behaviour and lazy loading.
|
|
173
|
+
|
|
174
|
+
**15. Navigation / header** `[MEASURE]` - bar height, title text and alignment,
|
|
175
|
+
back affordance present and correct and mirrored under RTL, right-side actions
|
|
176
|
+
present and ordered, bottom navigation items with icons, labels and selected
|
|
177
|
+
state, `[DYNAMIC]` transition style.
|
|
178
|
+
|
|
179
|
+
**16. Scroll** - `[DYNAMIC]` smooth scroll, bounce, overscroll, sticky header,
|
|
180
|
+
scroll indicator; `[MEASURE]` the pinned header or footer position, which is the
|
|
181
|
+
static part a capture can check.
|
|
182
|
+
|
|
183
|
+
**17. Responsive** `[CAPTURE+]` - small, medium and large phone widths, tablet,
|
|
184
|
+
landscape. No overflow, clipping or distortion at any of them. Re-capture per
|
|
185
|
+
width rather than reasoning about it.
|
|
186
|
+
|
|
187
|
+
**18. Alignment** `[MEASURE]` - horizontal, vertical, and baseline across a row.
|
|
188
|
+
|
|
189
|
+
**19. Animation and motion** `[DYNAMIC]` - duration, curve, delay,
|
|
190
|
+
fade/scale/slide/rotation, loading and skeleton animation, haptics. Verify by
|
|
191
|
+
recording the device or report `not verified (dynamic)`.
|
|
192
|
+
|
|
193
|
+
**20. Visibility** `[CAPTURE+]` - hidden vs visible renders correctly; collapse
|
|
194
|
+
and expand states captured separately.
|
|
195
|
+
|
|
196
|
+
**21. States** `[CAPTURE+]` per interactive component - default, pressed, focus,
|
|
197
|
+
selected, active, disabled (colour, opacity, and genuinely not tappable),
|
|
198
|
+
loading, empty, error, success.
|
|
199
|
+
|
|
200
|
+
> **State fidelity gate**: the captured implementation must be in the exact state
|
|
201
|
+
> the design frame depicts - the same toggle position, the same filled or empty
|
|
202
|
+
> content, the same validation result. A capture in the wrong state is redone,
|
|
203
|
+
> not compared.
|
|
204
|
+
|
|
205
|
+
**22. Accessibility** `[A11Y]` - contrast at least 4.5:1 computed from sampled
|
|
206
|
+
colours, tap target at least 44x44pt / 48x48dp from the accessibility tree,
|
|
207
|
+
screen-reader label present and meaningful plus a meaningful hint, correct
|
|
208
|
+
traits, an accessibility identifier on every interactive element, and a logical
|
|
209
|
+
focus order. Whether to also audit scaled text sizes depends on the project: a
|
|
210
|
+
design system with fixed, non-scaling typography makes a forced-scale render
|
|
211
|
+
meaningless noise, so state which of the two the project is instead of assuming.
|
|
212
|
+
|
|
213
|
+
**23. Platform** - iOS: safe area, notch and Dynamic Island clearance, home
|
|
214
|
+
indicator, native navigation behaviour. Android: Material compliance, status and
|
|
215
|
+
navigation bar treatment, ripple.
|
|
216
|
+
|
|
217
|
+
**24. Dark appearance** `[CAPTURE+]` - capture a second image in the dark
|
|
218
|
+
appearance and re-run groups 3 to 9 against the dark design frame. Background,
|
|
219
|
+
text, icon, border, shadow, divider and gradient all adapt; no colour stuck at
|
|
220
|
+
its light value; contrast preserved.
|
|
221
|
+
|
|
222
|
+
**25. RTL and localization** - `[CAPTURE+]` a mirrored-locale capture: layout
|
|
223
|
+
mirrored, leading and trailing swapped, directional icons flipped; `[MEASURE]` no
|
|
224
|
+
hardcoded strings, every user-visible string from a localization key; a raw or
|
|
225
|
+
undefined key rendered on screen (the design shows copy, the implementation shows
|
|
226
|
+
`Screen.SomeKey`) reported in its own localization section; `[CAPTURE+]` a
|
|
227
|
+
long-language pass with no clipping. Compare the string's *presence and shape*,
|
|
228
|
+
not a translation of the same phrase against itself.
|
|
229
|
+
|
|
230
|
+
**26. Content and data formatting** `[MEASURE]` - number grouping, currency symbol
|
|
231
|
+
and position, date format, pluralization, and a long dynamic value that does not
|
|
232
|
+
overflow. The value itself (a name, a number, a masked field) is not under test.
|
|
233
|
+
|
|
234
|
+
**27. Component presence and order - hard gate** - the group the whole walk
|
|
235
|
+
exists for.
|
|
236
|
+
|
|
237
|
+
> Mechanical, not a glance. Number every element of the design list - text **and**
|
|
238
|
+
> every icon, avatar, badge, **and every button** - and for each write its
|
|
239
|
+
> implementation counterpart or `MISSING`. Report the count explicitly: "N design
|
|
240
|
+
> components -> N implementation counterparts". "Complete" may not be concluded
|
|
241
|
+
> without that list. A button and the label nested inside it are two presence
|
|
242
|
+
> checks, not one.
|
|
243
|
+
>
|
|
244
|
+
> - **Icons are the most-missed presence class.** For every icon in the design
|
|
245
|
+
> list, locate it in the render by measuring its coloured bounding box. A design
|
|
246
|
+
> icon with roughly no matching pixels is a missing-element finding.
|
|
247
|
+
> - **Recurse into nested blocks.** The gate applies to rows inside cards - masked
|
|
248
|
+
> rows, list rows, key/value rows - not only to top-level sections. "That card
|
|
249
|
+
> looks right" is not a pass.
|
|
250
|
+
> - **Bidirectional, and code-side too.** Diff design-to-implementation
|
|
251
|
+
> (`MISSING`) *and* implementation-to-design (`EXTRA`). The most-missed extra is
|
|
252
|
+
> a neutral decoration the implementation adds - a trailing chevron, an
|
|
253
|
+
> underline, a strikethrough - which a colour-based, one-directional comparison
|
|
254
|
+
> cannot see. Diff the implementation's explicit decoration modifiers against
|
|
255
|
+
> the design's own decoration nodes, independently of the string.
|
|
256
|
+
> - **A localization gap does not short-circuit this gate.** Raw keys are one
|
|
257
|
+
> finding; presence, icon, decoration and structure checks still run, because
|
|
258
|
+
> they read code and spec rather than the rendered string.
|
|
259
|
+
|
|
260
|
+
Findings: `MISSING` · `EXTRA` · `MISSING-BETWEEN` (dropped from the middle of the
|
|
261
|
+
sequence) · `REORDERED` · `WRONG-STATE` · `WRONG-CONTAINER` (present but grouped
|
|
262
|
+
differently, loose vs carded).
|
|
263
|
+
|
|
264
|
+
**28. Keyboard and input behaviour** `[DYNAMIC]` - keyboard avoidance with the
|
|
265
|
+
active field not covered, dismiss behaviour, and return/next moving to the
|
|
266
|
+
correct field.
|
|
267
|
+
|
|
268
|
+
**29. Analytics** - out of visual scope; checked only when the spec requires it.
|
|
269
|
+
|
|
270
|
+
**30. Cross-check matrix** - per component confirm x, y, w, h, the four margins,
|
|
271
|
+
the four paddings, font family/size/weight, line height, letter spacing, text
|
|
272
|
+
colour, background colour, border width/radius/colour, shadow, opacity, icon
|
|
273
|
+
size/colour/position, image size/crop, divider, alignment, responsive, state,
|
|
274
|
+
animation, safe area, and overflow. **Mark only deviations** on the image;
|
|
275
|
+
matches belong in the report text.
|
|
276
|
+
|
|
277
|
+
## 6. Output
|
|
278
|
+
|
|
279
|
+
Mark only problems on the image, with thin lines. Everything else goes in the
|
|
280
|
+
report, whose section keys are stable and whose prose follows `outputLanguage`:
|
|
281
|
+
|
|
282
|
+
| Key | Content |
|
|
283
|
+
|---|---|
|
|
284
|
+
| `deviations` | Numbered, each one a design value against the measured or coded implementation value |
|
|
285
|
+
| `localization` | Raw keys, missing keys, hardcoded strings |
|
|
286
|
+
| `matches` | What was checked and agreed |
|
|
287
|
+
| `ignored` | What was deliberately out of scope, with the reason |
|
|
288
|
+
| `not_verified` | **Every** `[DYNAMIC]` and `[CAPTURE+]` item that was not run, so the coverage gap is explicit rather than absent |
|
|
289
|
+
|
|
290
|
+
Each finding is `[what] + [why] + [how]` with `file:line` wherever the
|
|
291
|
+
implementation side is code-resolvable. A finding without a file reference is
|
|
292
|
+
still a finding; a finding without a "how" is a complaint.
|
|
293
|
+
|
|
294
|
+
## 7. What a token catalog is, and why none ships here
|
|
295
|
+
|
|
296
|
+
Comparing typography and colour exactly needs the project's own catalogs - the
|
|
297
|
+
resolved name, size, weight and line height of each typography style, and the
|
|
298
|
+
resolved value of each colour token. Those are **project assets**, not pipeline
|
|
299
|
+
content: they carry brand colours, licensed font names and product vocabulary.
|
|
300
|
+
|
|
301
|
+
So this contract defines the *interface*, never the data. The runner resolves
|
|
302
|
+
catalogs in this order and states which one it used:
|
|
303
|
+
|
|
304
|
+
1. A path in preferences (`designCheck.tokenCatalogs`), when the project exports
|
|
305
|
+
them.
|
|
306
|
+
2. Generated from the repo's own token definitions at run time.
|
|
307
|
+
3. Neither available -> typography and colour items degrade from `[MEASURE]` to
|
|
308
|
+
sampled-pixel comparison, and the report says so. They are not silently
|
|
309
|
+
passed.
|
|
310
|
+
|
|
311
|
+
A vendored catalog inside the pipeline would be one project's design system
|
|
312
|
+
shipped to every other project: wrong for all of them, and stale for its owner.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Visual evidence - before/after screenshots and the UI flow video
|
|
2
|
+
|
|
3
|
+
A UI fix that reads as three changed files in a diff is not reviewable. The
|
|
4
|
+
reviewer cannot see what was wrong, and the tester cannot see what to look for.
|
|
5
|
+
This contract makes the pipeline carry the picture: the state the reporter saw,
|
|
6
|
+
the state the fix produces, and where possible a recording of the flow running.
|
|
7
|
+
|
|
8
|
+
The artefacts live as **Jira attachments** and are referenced from two places -
|
|
9
|
+
the Phase 7 Jira comment, where the picture belongs next to the work summary and
|
|
10
|
+
the test scenarios, and the PR body, which tells the reviewer they exist.
|
|
11
|
+
|
|
12
|
+
Consumers: Phase 3 (capture), Phase 5 (video, preferred host), Phase 6 (blocker),
|
|
13
|
+
`channels/jira.md` and `channels/pr.md` (render). Gate: `smoke-visual-evidence.sh`.
|
|
14
|
+
|
|
15
|
+
## 1. When it is required
|
|
16
|
+
|
|
17
|
+
Decided mechanically from `taskType` plus the changed-file list, never from the
|
|
18
|
+
model's reading of the task. A file counts as UI by stack:
|
|
19
|
+
|
|
20
|
+
| Stack | UI file test |
|
|
21
|
+
|---|---|
|
|
22
|
+
| iOS | `.swift` declaring `: View`, `: ViewController`, or under a `Views/` path |
|
|
23
|
+
| Android | `.kt` with `@Composable`, or declaring `: Activity` / `: Fragment` |
|
|
24
|
+
| Web | `.tsx` / `.jsx` / `.vue` / `.svelte` |
|
|
25
|
+
|
|
26
|
+
| Case | Before | After | Video |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| `taskType` is `bugfix` AND a UI file changed | required | required | when a tier allows |
|
|
29
|
+
| Design work: `taskType` is `component`, or Figma was referenced, or a UI file was **added** | not applicable | required | when a tier allows |
|
|
30
|
+
| Anything else | - | - | - |
|
|
31
|
+
|
|
32
|
+
`state.visualEvidence.required` records the verdict and which rule produced it.
|
|
33
|
+
Nothing about this section is optional-by-omission: when it is required and an
|
|
34
|
+
artefact is absent, the absence is written down with its reason (section 5).
|
|
35
|
+
|
|
36
|
+
## 2. Before - the reporter's screenshot, or nothing
|
|
37
|
+
|
|
38
|
+
**The pipeline does not build the old state to photograph it.** Reproducing a
|
|
39
|
+
pre-fix screen costs a second build and a second launch on every UI bug, to
|
|
40
|
+
recreate evidence the reporter has usually already attached.
|
|
41
|
+
|
|
42
|
+
So: Phase 0 already fetches the issue. Any image attachment on it (`.png`,
|
|
43
|
+
`.jpg`, `.jpeg`, `.gif`, `.heic`) is downloaded to `$WORKTREE/.pipeline/evidence/`
|
|
44
|
+
and recorded as `state.visualEvidence.before[]`. More than one is kept - a bug
|
|
45
|
+
reported on two platforms has two.
|
|
46
|
+
|
|
47
|
+
No image on the ticket means no "before". Record the gap
|
|
48
|
+
(`before_missing: "ticket carries no image attachment"`) and continue. The
|
|
49
|
+
section still renders, saying so.
|
|
50
|
+
|
|
51
|
+
## 3. After - Phase 3, not Phase 5
|
|
52
|
+
|
|
53
|
+
Phase 5 is the natural home: the simulator is already up. It is also **dropped by
|
|
54
|
+
every `autopilot` and `--local` entry** (`phase-5-test.md` TLDR), so a capture
|
|
55
|
+
that lives only there produces nothing for unattended runs - which are exactly
|
|
56
|
+
the runs where nobody watched the screen.
|
|
57
|
+
|
|
58
|
+
The capture therefore happens in **Phase 3, after the build+test gate passes**,
|
|
59
|
+
which is in every mode's phase set. Phase 5 may add richer evidence on top; it is
|
|
60
|
+
never the only source.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bash $HOME/.claude/scripts/capture-evidence.sh after \
|
|
64
|
+
--task "$TASK_ID" --platform "$PLATFORM" --label "<screen-slug>"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The script boots the device if needed, launches the built app, cleans the status
|
|
68
|
+
bar (`ios_status_bar preset:clean` / the Android equivalent) so the frame carries
|
|
69
|
+
no clock, carrier or battery noise, captures at device resolution, downscales to
|
|
70
|
+
at most 1242px wide, and writes
|
|
71
|
+
`$WORKTREE/.pipeline/evidence/<TASK_ID>-<label>-after.png`.
|
|
72
|
+
|
|
73
|
+
A clean status bar is not cosmetic: without it two captures of the same screen
|
|
74
|
+
differ by the clock, which makes every "after" look like a change.
|
|
75
|
+
|
|
76
|
+
## 4. Video - three tiers, resolved once per repo
|
|
77
|
+
|
|
78
|
+
The flow source, in order: a UI test flow written on the ticket wins; otherwise
|
|
79
|
+
the Phase 5 test scenarios are the flow. Human-written beats generated, the same
|
|
80
|
+
rule the "before" follows.
|
|
81
|
+
|
|
82
|
+
The tier is probed once and stored in `state.visualEvidence.videoTier`:
|
|
83
|
+
|
|
84
|
+
| Tier | Condition | Mechanism |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| 1 | The repo has a UI test target (XCUITest / Espresso) **and** it can be seeded with test data | Run that test, record the screen for its duration. Most faithful, and it is a test that already runs in CI |
|
|
87
|
+
| 2 | A runnable build exists | Drive the flow with `mcp__multi-agent-toolkit__agent_run_steps` and record with `ios_record_video` / `android_record_screen`. **No test target needed** |
|
|
88
|
+
| 3 | No runnable build (library / SPM package / backend) | Skip, with the reason recorded |
|
|
89
|
+
|
|
90
|
+
Tier 2 is why "can every repo do this" answers yes in practice: any app that can
|
|
91
|
+
be launched can be driven. A repo without a UI test target loses fidelity, not
|
|
92
|
+
the recording.
|
|
93
|
+
|
|
94
|
+
Preferred host is **Phase 5** - the device is already up and a human is present
|
|
95
|
+
to confirm the flow is the right one. In a mode without Phase 5, tier 2 runs at
|
|
96
|
+
the end of Phase 3 instead. Tier 1 always runs where the test runs.
|
|
97
|
+
|
|
98
|
+
Duration is capped by `visualEvidence.maxVideoSeconds` (default 60), read via
|
|
99
|
+
`capture-evidence.sh limits` so the number lives in one place rather than in two
|
|
100
|
+
documents. A flow that needs longer is not a review artefact, it is a debugging
|
|
101
|
+
session.
|
|
102
|
+
|
|
103
|
+
`visualEvidence.enabled` turns the whole feature off - capture, upload, both
|
|
104
|
+
render sections and the Phase 6 blocker with it.
|
|
105
|
+
|
|
106
|
+
## 5. Size, and what happens when it does not fit
|
|
107
|
+
|
|
108
|
+
Jira's attachment ceiling is an instance setting, so it is a preference:
|
|
109
|
+
`visualEvidence.maxAttachmentMb` (default `10`).
|
|
110
|
+
|
|
111
|
+
Order of attempts, each one recorded:
|
|
112
|
+
|
|
113
|
+
1. Upload as captured.
|
|
114
|
+
2. On `413` or a local size overrun: re-encode. Video drops to 720p and a lower
|
|
115
|
+
bitrate; PNG converts to JPEG at quality 80. **Reducing quality is allowed;
|
|
116
|
+
dropping the artefact is not.**
|
|
117
|
+
3. Still over: replace the video with a four-frame contact sheet (start, two
|
|
118
|
+
midpoints, end) as a single PNG, and say in the caption that the recording
|
|
119
|
+
exceeded the limit.
|
|
120
|
+
|
|
121
|
+
Never silently attach nothing.
|
|
122
|
+
|
|
123
|
+
## 6. Rendering
|
|
124
|
+
|
|
125
|
+
### Jira comment (`channels/jira.md`)
|
|
126
|
+
|
|
127
|
+
The comment's section order is fixed and nothing may be inserted between the
|
|
128
|
+
three sections. The evidence goes **inside** two of them:
|
|
129
|
+
|
|
130
|
+
- `summary` - after the summary sentences, the before/after pair as a Jira wiki
|
|
131
|
+
thumbnail row: `!<TASK>-<label>-before.png|thumbnail! !<TASK>-<label>-after.png|thumbnail!`
|
|
132
|
+
preceded by one line naming which is which in `outputLanguage`
|
|
133
|
+
(`Düzeltme öncesi / Düzeltme sonrası`).
|
|
134
|
+
- `test_scenarios` - the flow video attached under the scenario it demonstrates:
|
|
135
|
+
`!<TASK>-flow.mp4!`, one line stating the tier used.
|
|
136
|
+
|
|
137
|
+
Missing artefacts print their reason on the same line, never an empty frame.
|
|
138
|
+
|
|
139
|
+
### PR body (`channels/pr.md`)
|
|
140
|
+
|
|
141
|
+
A `visuals` section whose job is to tell the reviewer the evidence exists and
|
|
142
|
+
where it lives - the images themselves stay on the ticket:
|
|
143
|
+
|
|
144
|
+
```markdown
|
|
145
|
+
## Görsel Kanıt
|
|
146
|
+
|
|
147
|
+
- Düzeltme öncesi: <before-filename> (PROJ-123 ekinde)
|
|
148
|
+
- Düzeltme sonrası: <after-filename> (PROJ-123 ekinde)
|
|
149
|
+
- Akış videosu: <flow-filename>, tier <N> (PROJ-123 ekinde)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 7. Blocker
|
|
153
|
+
|
|
154
|
+
When section 1 says required and `state.visualEvidence` carries neither an
|
|
155
|
+
artefact nor a recorded reason for its absence, **Phase 6 Step 3 blocks** - the
|
|
156
|
+
same shape as the `risk` section blocker. A recorded reason is enough to pass:
|
|
157
|
+
the gate is against silence, not against an honest "no image on the ticket".
|
|
158
|
+
|
|
159
|
+
## 8. State
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
"visualEvidence": {
|
|
163
|
+
"required": true,
|
|
164
|
+
"requiredBy": "bugfix + ui-file-changed",
|
|
165
|
+
"platform": "ios",
|
|
166
|
+
"before": [{"file": "...", "source": "ticket", "jiraFilename": "...", "url": "..."}],
|
|
167
|
+
"after": [{"file": "...", "capturedAt": "phase-3", "jiraFilename": "...", "url": "..."}],
|
|
168
|
+
"videoTier": 2,
|
|
169
|
+
"video": {"file": "...", "seconds": 41, "jiraFilename": "...", "url": "..."},
|
|
170
|
+
"gaps": [{"what": "before", "reason": "ticket carries no image attachment"}]
|
|
171
|
+
}
|
|
172
|
+
```
|