@mmerterden/multi-agent-pipeline 16.29.0 → 16.30.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 +44 -0
- package/docs/features.md +14 -0
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/design-check/SKILL.md +6 -5
- package/pipeline/commands/multi-agent/help/SKILL.md +13 -12
- package/pipeline/commands/multi-agent/manual-test/SKILL.md +1 -1
- package/pipeline/multi-agent-refs/channels/pr.md +37 -1
- package/pipeline/multi-agent-refs/features/visual-evidence.md +103 -20
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +29 -0
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +13 -1
- package/pipeline/multi-agent-refs/phases/phase-5-test.md +11 -1
- package/pipeline/multi-agent-refs/phases/phase-6-commit.md +23 -0
- package/pipeline/schemas/agent-state.schema.json +79 -1
- package/pipeline/schemas/prefs.schema.json +20 -1
- package/pipeline/schemas/token-budget.json +10 -10
- package/pipeline/scripts/capture-evidence.sh +170 -5
- package/pipeline/scripts/evidence-gate.mjs +31 -2
- package/pipeline/scripts/probe-evidence-capability.sh +250 -0
- package/pipeline/scripts/run-ui-tests.sh +380 -0
- package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,50 @@ Internal file-layout changes that don't affect the slash-command surface are sti
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
+
## [16.30.0] - 2026-09-12
|
|
18
|
+
|
|
19
|
+
The flow video existed as a contract with no recorder, no UI test ever ran, and the PR carried filenames where a reviewer needed a picture. This release makes the evidence real and asks the user what it is worth paying for.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **`capture-evidence.sh video start|stop`** - the recorder the contract has described since 16.24.0 and nothing implemented. iOS records through `simctl io recordVideo --codec h264` (hevc does not play in the Jira attachment preview or several browsers) and stops on SIGINT, which is the only signal that makes simctl close the container; a TERM leaves an mp4 every player refuses. Android uses `screenrecord` with its own 180s ceiling, which the script clamps to and reports, because one preference honoured on one platform and silently halved on the other is worse than a stated limit. Shell rather than MCP, like the still capture beside it: a host with no toolkit MCP registered still produces evidence.
|
|
24
|
+
|
|
25
|
+
**Both recorders encode on change**, so a flow over a screen that never moved is a valid two-frame file a fraction of a second long. That file is kept - it is not broken - but `stop` says on stderr that nothing moved, and the caller records it as a gap rather than presenting a still as a flow. Nothing asserts the duration against wall clock anywhere, because that check fails a correct capture of a static screen.
|
|
26
|
+
|
|
27
|
+
- **`run-ui-tests.sh`** - finds the repo's own UI test target, picks the tests covering the changed files, and runs them. Exit 3 (target present, nothing matches) and exit 4 (no target) are reasons to fall to the next video tier; only exit 1 is a red test, and the log is the evidence, since a runner that died before reaching the tests also exits non-zero.
|
|
28
|
+
|
|
29
|
+
**The signal is `XCUIApplication`, not a folder named `*UITests`.** In the reference iOS app 477 files sit under such a path and exactly 2 drive the UI; the other 475 are snapshot tests that render a view and compare pixels without launching the app. Recording video around one of those produces a still frame and files it as a flow. And a real app has many candidates - 17 UI test directories there, 8 instrumentation modules in the Android app - so detection reports the whole set and lets the changed files choose. Taking the first off a `find` is a guess wearing a measurement's clothes.
|
|
30
|
+
|
|
31
|
+
Detection reads the filesystem and never calls `xcodebuild -list`, which resolves the SPM graph first and took 72 seconds on the reference workspace. It also found a target the `xcodebuild` path had missed entirely. The scheme, which only a run needs, is resolved in `run` behind a timeout.
|
|
32
|
+
|
|
33
|
+
- **`probe-evidence-capability.sh`** - measures before the user is asked. Offering "unit + UI test with a screen recording" and discovering afterwards that there is no target or no booted device spends the user's answer on something that was never going to happen. Every absent value carries its reason, because `no booted simulator, but one is available to boot` and `no iOS simulator available on this machine` close the same menu row and ask for completely different things. A value it could not measure is `null`, never `false`: with no `adb` on the PATH, "no device attached" is a negative nobody looked for and reads exactly like one somebody checked.
|
|
34
|
+
|
|
35
|
+
- **Test depth is a question** (Phase 0 Step 7.7): unit only, unit plus the UI test with a recording, or unit plus an MCP-driven flow. The options are built from the probe; a closed one keeps its row and states why, and when every option but the first is closed nothing is asked at all and `testDepthSource` records `forced` - a one-option question has no answer in it. Asked at intake rather than in Phase 5 because Phase 5 is absent from four of the eight modes, and a question asked where it cannot be reached is a question nobody answers. `prefs.global.testDepth.default` serves both autopilot entries.
|
|
36
|
+
|
|
37
|
+
- **The tier is re-checked at capture time.** A simulator booted at intake can be gone by the time the build goes green, so Phase 3 re-measures the device row alone and records the transition it came down from.
|
|
38
|
+
|
|
39
|
+
- **`state.visualEvidence.host`, and a PR that shows the picture.** Resolved in Phase 6: Jira takes both stills and video as attachments; with no Jira the stills go to an orphan `evidence/<task-id>` branch and the PR body embeds them. There is no GitHub API that attaches a file to an issue or a PR - the web uploader needs a browser session - so a branch is the only mechanism a script has that neither touches the PR diff nor publishes a release. **A private repo gets a blob permalink rather than an embed**: GitHub renders markdown images through a proxy that has no credentials for a private repo, and a broken image reads as missing evidence. Video stays Jira-only, and on a GitHub run none is recorded at all rather than paying UI-test minutes for a download nobody opens.
|
|
40
|
+
|
|
41
|
+
- **The probe's output contract**, settled by its own review. `--json-out <path>` writes the JSON the state records while stdout stays `KEY=VALUE`, so one run serves both the shell that builds the menu and the state that keeps the measurement; two runs meant two repo scans and two chances to disagree. Every value is **shell-quoted**, because Phase 0 evals that output and the reasons are prose: unquoted, `EVIDENCE_DEVICE_REASON=no booted simulator, but one is available to boot` makes `eval` run `booted` as a command and keep only the first word. That bug is invisible on any machine where the reasons happen to come back empty, which is the machine it was first tested on. `--only device` skips the detection scan for the Phase 3 re-check, and reports tier 1 as **unknown** rather than closed there - a tier that was never probed is not a tier that was ruled out, which is the same rule the probe states about every other field.
|
|
42
|
+
|
|
43
|
+
- **`smoke-ui-test-runner.sh`** (24 assertions) and **`smoke-evidence-probe.sh`** (33). The first caught a real bug while being written: `run` keyed its "no target" exit off the single chosen target, which is deliberately empty while several candidates exist, so "this repo has nothing to record" and "nothing covers what changed" collapsed into one code.
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- **`smoke-no-mcp-in-dev-phases.sh` failed the pipeline's own prescribed calls.** It rejected every `mcpCalls[]` entry at phase >= 2 regardless of tool, while the rule it enforces (Locked 30, `rules/figma-pipeline.md`, `phase-3-dev.md` item 7) names `mcp__claude_ai_Figma__*` and nothing else, because what it protects is a single source of design truth, not MCP as a transport. `phase-3-dev.md` builds with `ios_xcodebuild` and `phase-5-test.md` audits with the accessibility tools, both at phase >= 2 and neither reading a design; the first person to record telemetry honestly is the one the gate would have failed. Now filtered on the tool name, with three assertions covering both directions.
|
|
48
|
+
|
|
49
|
+
- **The design-check MCP probe had never run.** `design-check/SKILL.md` read `mcpServers[...].args[0]` as a script path and `cd`'d to its parent. The registration is the npx form, so `args[0]` is `-y`, the `cd` failed every time, and a gate that exists to catch a stale tool list was inert. It now launches the server exactly as it is registered, which is what `doctor.mjs` already did.
|
|
50
|
+
|
|
51
|
+
- **`evidence-gate.mjs` never read the `screenshot` field** it has carried in the manual-test document shape since the gate shipped, so a criterion with `"screenshot": null` passed as a verified manual test on a UI change - the one case the picture was added for. New `--require-screenshot` flag, set by Phase 5 when visual evidence is required: a passing criterion must name a file that is actually on disk, since a path pointing nowhere is not evidence.
|
|
52
|
+
|
|
53
|
+
- **The derived `multi-agent-manual-test` skill had no evidence gate at all**, while its command sibling had one. Two copies of a command that disagree about whether a claim needs proof is the drift `skill-siblings.mjs` exists to surface.
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- `features/visual-evidence.md` section 4 rewritten around probe -> question -> run, plus a new host section carrying the two GitHub constraints that shape the PR rendering.
|
|
58
|
+
- Four phase docs gained a step, so `token-budget.json` moves with them: phase 0 (probe + depth question), 3 and 5 (recording), 6 (host resolution). The docs were compressed first and the new numbers are the measured residual, not headroom - these files are read into context on every run, so the budget is the place that cost is recorded rather than absorbed.
|
|
59
|
+
- `smoke-visual-evidence.sh` now records two seconds on a booted device and verifies the h264 file instead of grepping this repo's own markdown for the words "Tier 1" and `agent_run_steps`. A gate that asserts what we print proves nothing. Where no device is present the skip is printed as its own assertion, because `run-smokes.mjs` counts a silent suite as zero assertions and treats it as inert. It also holds the host to its two constraints: the public shape must embed an image, and the private shape must **not** - an embedded raw URL from a private repo renders broken for every reader, which looks like missing evidence rather than a link nobody clicked.
|
|
60
|
+
|
|
17
61
|
## [16.29.0] - 2026-09-11
|
|
18
62
|
|
|
19
63
|
### Added
|
package/docs/features.md
CHANGED
|
@@ -271,6 +271,20 @@ Signals + weights: `security_path` ×3, `migration` ×4, `public_api` ×2, `no_t
|
|
|
271
271
|
|
|
272
272
|
`pipeline/scripts/test-gap-scan.mjs` runs at Phase 5 Step 0. Walks the diff for newly added public symbols and reports those with no paired test. Stack-specific rules ship for iOS, Android, Python, Node.js. iOS Views and Android `@Composable` symbols default to `important`; other public API additions to `suggestion`. Optional gating via `prefs.testGap.blockingThreshold` - when set, the report becomes a Phase 4 rework finding once `important + blocking` count exceeds the threshold.
|
|
273
273
|
|
|
274
|
+
### Visual Evidence (UI changes)
|
|
275
|
+
|
|
276
|
+
A UI change carries its own picture. `state.visualEvidence.required` is decided mechanically from `taskType` plus the changed-file list, never from a reading of the task.
|
|
277
|
+
|
|
278
|
+
**Stills.** The "before" is the reporter's own ticket attachment, harvested in Phase 0; the pipeline never rebuilds the old state to photograph it. The "after" is captured in Phase 3 right after the build goes green, not Phase 5, which autopilot and both local modes drop. `capture-evidence.sh` cleans the status bar and downscales to 1242px so two captures of one screen differ by the change and not by the clock.
|
|
279
|
+
|
|
280
|
+
**The flow video rides on a test run.** `probe-evidence-capability.sh` measures the UI test target, the tests matching this change, the device, the recorder and the MCP registration; Phase 0 Step 7.7 then asks the depth with the options built from that measurement, and a closed option keeps its row and states why. Tier 1 runs the repo's own UI test and records around it, tier 2 drives the flow through `agent_run_steps`, tier 3 records nothing and says so. The tier is re-checked before the recording starts, because a simulator booted at intake can be gone by Phase 3.
|
|
281
|
+
|
|
282
|
+
UI test detection keys on `XCUIApplication` rather than on a folder named `*UITests`: in a real app the overwhelming majority of files under such a path are snapshot tests, which never launch the app and would produce a still frame filed as a flow.
|
|
283
|
+
|
|
284
|
+
**Where it lands.** Jira takes both stills and video as attachments. With no Jira the stills go to an orphan `evidence/<task-id>` branch and the PR body embeds them, or links them with a blob permalink when the repo is private (GitHub's image proxy has no credentials for a private repo, and a broken image reads as missing evidence). Phase 6 blocks when a required artefact is neither published nor explained; the gate is against silence, not against an honest "the ticket carries no image".
|
|
285
|
+
|
|
286
|
+
Toggle via `prefs.global.visualEvidence.enabled` (default ON), `visualEvidence.githubHost`, `visualEvidence.maxAttachmentMb`, `visualEvidence.maxVideoSeconds`, `prefs.global.testDepth.default`.
|
|
287
|
+
|
|
274
288
|
### Triage Memory
|
|
275
289
|
|
|
276
290
|
Per-repo append-only JSONL corpus at `~/.claude/memory/multi-agent/<repo-slug>/triage-corpus.jsonl`. Phase 7 ingests every triage output (idempotent), Phase 1 enriches the analysis with similar past tasks, Phase 4 triage attaches prior-art hits to each raw finding with an explicit bias hedge. Token-overlap recall, zero deps, Node-18-compatible. `/multi-agent:search "<text>" --semantic` routes the query to the corpus instead of agent-log grep. Toggle via `prefs.global.priorArtEnrichment.enabled` (default ON).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmerterden/multi-agent-pipeline",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.30.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",
|
|
@@ -79,23 +79,24 @@ Every Phase 0 / Phase 2 decision uses a native `AskUserQuestion` picker (numbere
|
|
|
79
79
|
|
|
80
80
|
a. **Session** - `mcp__multi-agent-toolkit__design_scenario_inventory` must be present in the tools available to you, and `mcp__multi-agent-toolkit__design_visual_compare` must accept `live_region`. Absent → the session is bound to a stale process.
|
|
81
81
|
|
|
82
|
-
b. **Disk** - probe
|
|
82
|
+
b. **Disk** - probe what a fresh connection WOULD serve. Launch it as registered; under npx `args[0]` is `-y`, not a path.
|
|
83
83
|
```bash
|
|
84
|
-
|
|
84
|
+
MA_MCP_CMD=$(python3 -c "import json,os,shlex;e=json.load(open(os.path.expanduser('~/.claude.json')))['mcpServers']['multi-agent-toolkit'];print(shlex.join([e['command']]+e.get('args',[])))")
|
|
85
85
|
printf '%s\n%s\n' \
|
|
86
86
|
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \
|
|
87
87
|
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
|
|
88
|
-
|
|
|
88
|
+
| eval "$MA_MCP_CMD" 2>/dev/null | python3 -c "
|
|
89
89
|
import sys,json
|
|
90
90
|
for l in sys.stdin:
|
|
91
91
|
try: m=json.loads(l)
|
|
92
92
|
except: continue
|
|
93
|
-
if m.get('id')==1: print('version',
|
|
93
|
+
if m.get('id')==1: print('version',m['result']['serverInfo']['version'])
|
|
94
94
|
if m.get('id')==2:
|
|
95
95
|
n=[t['name'] for t in m['result']['tools']]
|
|
96
|
-
print('tools',
|
|
96
|
+
print('tools',len(n),'inventory','design_scenario_inventory' in n)"
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
|
|
99
100
|
Branch on the two results:
|
|
100
101
|
- **disk OK, session missing the tool** → **HALT** and tell the user to reconnect: `/mcp` → `multi-agent-toolkit` → Reconnect. Note the tool-count tell (a stale build advertises one fewer `design_*` tool). If a reconnect does not take, stale server processes may be lingering - `pgrep -f multi-agent-toolkit-mcp/index.js` with `ps -o lstart=` shows their start times, and any that predate the code's mtime cannot serve the current tools.
|
|
101
102
|
- **disk itself stale** (older version, or `inventory False`) → **HALT**: the checkout needs updating before a reconnect can help.
|
|
@@ -243,24 +243,25 @@ Key Features:
|
|
|
243
243
|
Multi-Platform iOS, Android, Backend, Web - auto-detected
|
|
244
244
|
Build Queue xcodebuild/Gradle serialized with lock file - parallel safe
|
|
245
245
|
Stack Aware Auto-detects iOS/Android/Python/Node/Go/Docker, uses the enabled stack plugin's skills
|
|
246
|
-
Det. Gates Build + lint + test + secret scan
|
|
247
|
-
Multi-Repo Per-repo worktrees
|
|
248
|
-
Identity Routing Git identity
|
|
249
|
-
Issue Safety Never auto-closes issues (4 approvals
|
|
250
|
-
Store Compliance /multi-agent:test "store-ready"
|
|
251
|
-
|
|
246
|
+
Det. Gates Build + lint + test + secret scan before AI review
|
|
247
|
+
Multi-Repo Per-repo worktrees + identity, integration build before commit
|
|
248
|
+
Identity Routing Git identity picked from the repo origin URL (corporate vs personal)
|
|
249
|
+
Issue Safety Never auto-closes issues (4 approvals, GitHub + Jira)
|
|
250
|
+
Store Compliance /multi-agent:test "store-ready" - 18-rule iOS audit (ITMS, Privacy Manifest,
|
|
251
|
+
signing, debug leaks, IPv6, SDK list) + 21-rule Android audit
|
|
252
252
|
Bilingual EN + TR - outputLanguage toggles assistant explanations; promptLanguage is locked en
|
|
253
253
|
|
|
254
254
|
Quality & Telemetry (advisory, on by default - flip prefs.global.* to disable):
|
|
255
255
|
|
|
256
256
|
Diff Risk Score Phase 4 Step 1.75 ranks files before reviewer dispatch (security paths,
|
|
257
|
-
|
|
257
|
+
migrations, no-test-change, complexity) - heuristic, sub-second
|
|
258
258
|
Test Gap Report Phase 5 Step 0 surfaces public symbols added in this branch with no paired test
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
259
|
+
Visual Evidence before/after stills + flow video on UI changes; Step 7.7 asks the depth
|
|
260
|
+
Cost Breakdown Phase 7 appends per-phase tokens + estimated USD to agent-log.md
|
|
261
|
+
Triage Memory Phase 7 ingests accepted/deferred/rejected findings into a repo corpus
|
|
262
|
+
Prior-Art Lookup Phase 1 + Phase 4 query the corpus for similar findings, inject as context
|
|
263
|
+
Per-Persona Dispatch reads `preferredModel` from the persona file; override per call via
|
|
264
|
+
PHASE_MODEL_OVERRIDE; ladder fable -> opus -> sonnet -> haiku
|
|
264
265
|
|
|
265
266
|
------------------------------------------------------------
|
|
266
267
|
|
|
@@ -51,5 +51,5 @@ Lets you switch to the task branch for manual testing in Xcode before the PR is
|
|
|
51
51
|
```json
|
|
52
52
|
{"criteria":[{"spec":"<quote>","source":"analysis 15.2 | plan task 3 | user","observed":"<what was seen>","verdict":"pass|fail|not-tested","reason":"<required when not-tested>","screenshot":"<path or null>"}],"verdict":"passed|failed"}
|
|
53
53
|
```
|
|
54
|
-
then run `node $HOME/.claude/scripts/evidence-gate.mjs --claim manual --status passed --evidence "$WORKTREE/.pipeline/manual-test.json"
|
|
54
|
+
then run `node $HOME/.claude/scripts/evidence-gate.mjs --claim manual --status passed --evidence "$WORKTREE/.pipeline/manual-test.json"`, adding `--require-screenshot` when `state.visualEvidence.required` is true (a passing criterion then has to name a screenshot that is actually on disk). Exit 1 means the "ok" is not accepted: name the criterion that is missing evidence and wait for the next reply. Exit 0 → `phase-tracker.sh update 5 completed` + `phase-tracker.sh meta 5 Result "local test passed (user)"`, recreate the worktree, continue to Phase 6. Full contract: `$HOME/.claude/multi-agent-refs/phases/phase-5-test.md` step 5.
|
|
55
55
|
- **Fix needed** → `phase-tracker.sh now 5 "applying fix: <summary>"`, recreate the worktree, apply the fix
|
|
@@ -63,7 +63,9 @@ Multi-repo PRs (one PR per repo) emit verification commands for that repo's stac
|
|
|
63
63
|
- Rollback: feature flag <name> | git revert <sha> | none, and why
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
**`visuals`** - only when `state.visualEvidence.required`.
|
|
66
|
+
**`visuals`** - only when `state.visualEvidence.required`. What this section can show depends on where the artefacts are hosted, which Phase 6 resolves into `state.visualEvidence.host`. Render the form for that host and no other.
|
|
67
|
+
|
|
68
|
+
**`host: jira`.** Filenames, never URLs. A Jira attachment URL is auth-gated and renders as a broken image for anyone reading the PR outside a Jira session, and a broken image is worse than a filename because it looks like the evidence is missing.
|
|
67
69
|
|
|
68
70
|
```markdown
|
|
69
71
|
## Visual Evidence
|
|
@@ -73,6 +75,40 @@ Multi-repo PRs (one PR per repo) emit verification commands for that repo's stac
|
|
|
73
75
|
- Flow video: `<flow-filename>`, tier <N> (attached to PROJ-XXXXX)
|
|
74
76
|
```
|
|
75
77
|
|
|
78
|
+
**`host: github-public`.** The stills are on the `evidence/<task-id>` branch, so they embed and the reviewer sees them without leaving the PR:
|
|
79
|
+
|
|
80
|
+
```markdown
|
|
81
|
+
## Visual Evidence
|
|
82
|
+
|
|
83
|
+
**Before**
|
|
84
|
+
|
|
85
|
+

|
|
86
|
+
|
|
87
|
+
**After**
|
|
88
|
+
|
|
89
|
+

|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**`host: github-private`.** Same branch, but a link rather than an embed. GitHub renders markdown images through its own proxy, which has no credentials for a private repo, so an embedded raw URL renders broken for every reader including the author. A blob link opens the image for anyone who can already see the repo:
|
|
93
|
+
|
|
94
|
+
```markdown
|
|
95
|
+
## Visual Evidence
|
|
96
|
+
|
|
97
|
+
- Before: [<before-filename>](https://github.com/<owner>/<repo>/blob/evidence/<task-id>/<before-filename>)
|
|
98
|
+
- After: [<after-filename>](https://github.com/<owner>/<repo>/blob/evidence/<task-id>/<after-filename>)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**`host: none`.** Filenames plus the artefact directory, and the reason there is no host:
|
|
102
|
+
|
|
103
|
+
```markdown
|
|
104
|
+
## Visual Evidence
|
|
105
|
+
|
|
106
|
+
- After: `<after-filename>` (run artefacts: `<artifactsPath>`)
|
|
107
|
+
- Not published: <hostReason>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Video is Jira-only.** On a GitHub-hosted run no recording is made and none is published: an mp4 behind a blob link is a download, not something a reviewer opens mid-review, and paying for a recording nobody watches is worse than saying plainly that there is none. The gap line carries that reason.
|
|
111
|
+
|
|
76
112
|
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
113
|
|
|
78
114
|
**`dependencies`** - only when `Package.swift` / `Podfile` / `build.gradle` / `package.json` changed. Each entry: `package@old → new - reason`.
|
|
@@ -78,32 +78,86 @@ at most 1242px wide, and writes
|
|
|
78
78
|
A clean status bar is not cosmetic: without it two captures of the same screen
|
|
79
79
|
differ by the clock, which makes every "after" look like a change.
|
|
80
80
|
|
|
81
|
-
## 4. Video -
|
|
81
|
+
## 4. Video - the recording rides on a test run
|
|
82
82
|
|
|
83
|
-
The flow
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
The flow video is not recorded on its own. It wraps something that drives the
|
|
84
|
+
screen, and what drives it is chosen by the user at intake, because running a UI
|
|
85
|
+
suite costs minutes and that is their time to spend.
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
### 4.1 Probe first
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
`probe-evidence-capability.sh` measures, before the question is asked, what this
|
|
90
|
+
machine and this repo can actually do: the UI test target, the tests matching
|
|
91
|
+
this change, the device, the recorder CLI, and whether the toolkit MCP is
|
|
92
|
+
registered. Detection is delegated to `run-ui-tests.sh detect`, which is also
|
|
93
|
+
what runs the tests, so there is one implementation of the answer.
|
|
94
|
+
|
|
95
|
+
Two rules the probe keeps, and the reason for each:
|
|
96
|
+
|
|
97
|
+
- **Absence carries a reason.** `no booted simulator, but one is available to
|
|
98
|
+
boot` and `no iOS simulator available on this machine` close the same menu row
|
|
99
|
+
and ask the user for completely different things.
|
|
100
|
+
- **Unmeasurable is `null`, never `false`.** With no `adb` on the PATH the probe
|
|
101
|
+
cannot see whether a device is attached; reporting that as "no device" is a
|
|
102
|
+
negative nobody looked for, and it reads exactly like one somebody checked.
|
|
94
103
|
|
|
95
|
-
|
|
96
|
-
be launched can be driven. A repo without a UI test target loses fidelity, not
|
|
97
|
-
the recording.
|
|
104
|
+
### 4.2 Then the question
|
|
98
105
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
Phase 0 Step 7.7 asks the depth, with the options built from the probe. A closed
|
|
107
|
+
option stays on the list carrying its reason; when every option but the first is
|
|
108
|
+
closed, nothing is asked and `testDepthSource` records `forced`. The full menu
|
|
109
|
+
rules are in `phases/phase-0-init.md`.
|
|
102
110
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
### 4.3 Then the recording
|
|
112
|
+
|
|
113
|
+
| `testDepth` | Tier | What drives the screen |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `unit+ui` | 1 | The repo's own UI test, selected by the changed files. Most faithful, and it is a test that already runs in CI |
|
|
116
|
+
| `unit+mcp` | 2 | `mcp__multi-agent-toolkit__agent_run_steps` drives the flow. No test target needed |
|
|
117
|
+
| `unit` | 3 | Nothing. No recording, and the reason is the user's own answer |
|
|
118
|
+
|
|
119
|
+
The recorder itself is `capture-evidence.sh video start|stop`, which writes
|
|
120
|
+
`<task-id>[-<label>]-flow.mp4` into the evidence directory. The label is optional
|
|
121
|
+
because one recording per task is the common case, and both renderers cite the
|
|
122
|
+
unlabelled form. It is shell rather than MCP for three reasons: the sibling still capture already shells out,
|
|
123
|
+
a host with no toolkit MCP registered still produces evidence, and Phase 3 and
|
|
124
|
+
Phase 5 are exactly where an MCP call is contested.
|
|
125
|
+
|
|
126
|
+
**iOS UI test targets are not named, they are detected.** A path containing
|
|
127
|
+
`UITests` is not the signal: in the reference app 477 files sit under such a path
|
|
128
|
+
and exactly 2 drive the UI, the rest being snapshot tests that render a view and
|
|
129
|
+
compare pixels without ever launching the app. The signal is `XCUIApplication`,
|
|
130
|
+
the only API that drives another process's interface, which is precisely the
|
|
131
|
+
precondition a flow recording has. On Android the signal is the `androidTest`
|
|
132
|
+
source set, which is instrumentation by definition.
|
|
133
|
+
|
|
134
|
+
**A real app has many candidates** - 17 UI test directories on the reference iOS
|
|
135
|
+
app, 8 instrumentation modules on the Android one. Detection reports the whole
|
|
136
|
+
set and lets the changed files choose; taking the first off a `find` is a guess
|
|
137
|
+
wearing a measurement's clothes.
|
|
138
|
+
|
|
139
|
+
### 4.4 Re-check before recording
|
|
140
|
+
|
|
141
|
+
The probe runs at intake and the recording happens in Phase 3. A simulator booted
|
|
142
|
+
then can be gone by the time the build goes green, so Phase 3 re-measures the
|
|
143
|
+
device row alone and downgrades the tier if it has to, recording the transition
|
|
144
|
+
(`tier 1 -> 3: simulator no longer booted`). A tier taken from a stale
|
|
145
|
+
measurement is a promise the run cannot keep.
|
|
146
|
+
|
|
147
|
+
### 4.5 Duration, and the recording that shows nothing
|
|
148
|
+
|
|
149
|
+
The cap is `visualEvidence.maxVideoSeconds` (default 60), read through
|
|
150
|
+
`capture-evidence.sh limits` so one value serves the phase doc and the recorder.
|
|
151
|
+
Android's `screenrecord` has its own ceiling of 180 seconds that no setting can
|
|
152
|
+
lift, so the script clamps to it and says when it did: one preference honoured on
|
|
153
|
+
one platform and silently halved on the other is worse than a stated limit.
|
|
154
|
+
|
|
155
|
+
Both recorders encode on change. A flow over a screen that never moved therefore
|
|
156
|
+
produces a valid two-frame mp4 a fraction of a second long - not a broken file,
|
|
157
|
+
but not evidence of a flow either. `video stop` says so on stderr when the result
|
|
158
|
+
is under a second, and the caller records that as a gap reason. The duration is
|
|
159
|
+
never asserted against wall clock anywhere, because doing so fails a correct
|
|
160
|
+
capture of a static screen.
|
|
107
161
|
|
|
108
162
|
`visualEvidence.enabled` turns the whole feature off - capture, upload, both
|
|
109
163
|
render sections and the Phase 6 blocker with it.
|
|
@@ -125,6 +179,35 @@ Order of attempts, each one recorded:
|
|
|
125
179
|
|
|
126
180
|
Never silently attach nothing.
|
|
127
181
|
|
|
182
|
+
## 5b. Where the artefacts live - the host
|
|
183
|
+
|
|
184
|
+
Resolved in Phase 6 Step 2.9, recorded as `state.visualEvidence.host`:
|
|
185
|
+
|
|
186
|
+
| Order | Host | Condition | Stills | Video |
|
|
187
|
+
|---|---|---|---|---|
|
|
188
|
+
| 1 | `jira` | `jiraId` present | Jira attachments | Jira attachment |
|
|
189
|
+
| 2 | `github-public` | no Jira, GitHub remote, public repo | `evidence/<task-id>` orphan branch, embedded in the PR body | none |
|
|
190
|
+
| 3 | `github-private` | no Jira, GitHub remote, private repo | same branch, blob permalink in the PR body | none |
|
|
191
|
+
| 4 | `none` | anything else, or `githubHost: off` | not published, gap recorded | none |
|
|
192
|
+
|
|
193
|
+
**GitHub cannot be given a file.** There is no API that attaches an image to an
|
|
194
|
+
issue or a pull request; the web uploader posts to an endpoint that needs a
|
|
195
|
+
browser session, so no token can drive it. The PR body can only point at
|
|
196
|
+
something already hosted, and an orphan branch is the one mechanism a script has
|
|
197
|
+
that neither touches the PR diff nor publishes a release.
|
|
198
|
+
|
|
199
|
+
**A private repo cannot show an inline image.** GitHub renders markdown images
|
|
200
|
+
through its own proxy, which carries no credentials for a private repo, so an
|
|
201
|
+
embedded `raw.githubusercontent.com` URL renders broken for every reader
|
|
202
|
+
including the author. That is worse than a link, because a broken image looks
|
|
203
|
+
like missing evidence. The private variant therefore links rather than embeds.
|
|
204
|
+
|
|
205
|
+
**Video is Jira-only, by decision.** On a GitHub-hosted run none is recorded at
|
|
206
|
+
all. An mp4 behind a blob link is a download rather than something a reviewer
|
|
207
|
+
opens mid-review, and paying minutes of UI-test time for a recording nobody
|
|
208
|
+
watches is worse than saying plainly that there is none. The gap line carries
|
|
209
|
+
that reason.
|
|
210
|
+
|
|
128
211
|
## 6. Rendering
|
|
129
212
|
|
|
130
213
|
### Jira comment (`channels/jira.md`)
|
|
@@ -589,6 +589,35 @@ Persist `state.baseline.tests` with `command`, `capturedAt`, `logPath` and exact
|
|
|
589
589
|
|
|
590
590
|
Log: `Phase 0 Step 7.6: test baseline = {green|red|unknown} ({N} pre-existing failures)`
|
|
591
591
|
|
|
592
|
+
#### Step 7.7 - Evidence capability, then test depth
|
|
593
|
+
|
|
594
|
+
Probe, then ask, then run. Skipped unless `state.visualEvidence.required` and `visualEvidence.enabled` is not `false`. Reasoning: `features/visual-evidence.md` section 4.
|
|
595
|
+
|
|
596
|
+
```bash
|
|
597
|
+
eval "$(bash $HOME/.claude/scripts/probe-evidence-capability.sh \
|
|
598
|
+
--platform "$PLATFORM" --repo "$WORKTREE" --changed "$CHANGED_CSV" \
|
|
599
|
+
--json-out "$WORKTREE/.pipeline/evidence-capability.json")"
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
One run, both forms: stdout is `EVIDENCE_*` (shell-quoted, so the eval is safe), and the same measurement lands as JSON.
|
|
603
|
+
|
|
604
|
+
Persist that file as `state.evidenceCapability`, then build the menu from it, never from a reading of the repo: `1. Sadece unit test` / `2. Unit + UI test, ekran kaydiyla` (tier 1) / `3. Unit + MCP ile akis kaydi` (tier 2).
|
|
605
|
+
|
|
606
|
+
A closed option keeps its row and prints the probe's reason verbatim. No `uiTestTargets` closes 2; `mcp` false closes 3; a missing `device` or `recorder` closes both. Targets present with no `matchingTests` leaves 2 open, warning that the whole UI suite will run. **When only option 1 is open, do not ask**: `testDepth = unit`, `testDepthSource = forced`, and the tier 3 gap is written.
|
|
607
|
+
|
|
608
|
+
Pass the default as the **1-based index**, never the label (`rules.md`: labels render in `outputLanguage`, so a label default matches nothing on a `tr` run and `ask-choice.sh` takes option 1 on a non-TTY):
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
DEPTH_DEFAULT_INDEX=1
|
|
612
|
+
[ "$EVIDENCE_TIER1" = "open" ] && [ -n "$EVIDENCE_MATCHING_TESTS" ] && DEPTH_DEFAULT_INDEX=2
|
|
613
|
+
[ "$DEPTH_DEFAULT_INDEX" = "1" ] && [ "$EVIDENCE_TIER2" = "open" ] && DEPTH_DEFAULT_INDEX=3
|
|
614
|
+
ASK_CHOICE_DEFAULT="$DEPTH_DEFAULT_INDEX" $HOME/.claude/lib/ask-choice.sh ...
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
Asked by `/multi-agent` and `:local`; autopilot reads `prefs.global.testDepth.default` and degrades to the best open option. Asked here, not Phase 5, which four of the eight modes drop.
|
|
618
|
+
|
|
619
|
+
Log: `Phase 0 Step 7.7: testDepth = {unit|unit+ui|unit+mcp} (source {user|autopilot|default|forced}), tier1/tier2 = {open|closed}`
|
|
620
|
+
|
|
592
621
|
#### Step 8 - Clarification (opt-in, runs AFTER maturity, BEFORE Phase 1)
|
|
593
622
|
|
|
594
623
|
**Gated by `prefs.global.clarifyAmbiguous.enabled`** (default: `false`). When enabled and `state.maturity.status != "blocker"`:
|
|
@@ -236,7 +236,19 @@ Gated by `prefs.global.devCritic.enabled` (default: `false`). When enabled, afte
|
|
|
236
236
|
|
|
237
237
|
#### Step 3.55 - Visual evidence capture (UI changes only)
|
|
238
238
|
|
|
239
|
-
When `state.visualEvidence.required`, capture
|
|
239
|
+
When `state.visualEvidence.required`, capture with the build that just went green. Here, not Phase 5, which every autopilot and `--local` entry drops. Exit 4 anywhere below is a gap, not a failure. Contract: `features/visual-evidence.md`.
|
|
240
|
+
|
|
241
|
+
1. **Still**: `$HOME/.claude/scripts/capture-evidence.sh after --task "$TASK_ID" --platform "$PLATFORM" --label <slug>`.
|
|
242
|
+
2. **Re-check the device.** `evidenceCapability` was measured at intake; a simulator booted then can be gone now. Re-measure the one volatile row with `$HOME/.claude/scripts/probe-evidence-capability.sh --platform "$PLATFORM" --repo "$WORKTREE" --only device`, which skips the repo scan the full probe does; gone means fall to the next open tier and write `visualEvidence.videoTierReason` as the transition (`tier 1 -> 3: simulator no longer booted`).
|
|
243
|
+
3. **Recording**, by `state.testDepth`:
|
|
244
|
+
- `unit+ui`: `video start` -> `$HOME/.claude/scripts/run-ui-tests.sh run --platform "$PLATFORM" --repo "$WORKTREE" --changed "$CHANGED_CSV"` -> `video stop`. Runner exit 3 (no matching test) or 4 (no target) -> stop, discard, fall to tier 2. Exit 1 is a red UI test: keep the recording, it shows the failure.
|
|
245
|
+
- `unit+mcp`: `video start` -> drive with `mcp__multi-agent-toolkit__agent_run_steps` -> `video stop`. The only MCP-dependent path; MCP absent -> tier 3 gap.
|
|
246
|
+
- `unit`: no recording; the gap reason is the user's own answer.
|
|
247
|
+
4. **Fit**: `$HOME/.claude/scripts/capture-evidence.sh fit --file <path>` per artefact. Quality degrades, the artefact is never dropped.
|
|
248
|
+
|
|
249
|
+
`video stop` warns when the recording is under a second: both recorders encode on change, so a screen that never moved yields a valid two-frame file. Keep it, record the warning as a gap reason, never present a still as a flow.
|
|
250
|
+
|
|
251
|
+
Persist `state.uiTest` and the artefact entries. A UI test that ran is subject to the default-FAIL rule like the build: run `evidence-gate.mjs --claim test --status passed --evidence "$WORKTREE/.pipeline/ui-test.log"` first, because a runner that died before reaching the tests also exits non-zero.
|
|
240
252
|
|
|
241
253
|
#### Step 3.6 - Code-simplifier pass (required diff shrink, before Phase 4 handoff)
|
|
242
254
|
|
|
@@ -112,6 +112,8 @@ Tier 1 / Tier 2 records print `screenshotUrl` from the captured evidence (Tier 2
|
|
|
112
112
|
```bash
|
|
113
113
|
node $HOME/.claude/scripts/evidence-gate.mjs --claim manual --status passed --evidence "$WORKTREE/.pipeline/manual-test.json"
|
|
114
114
|
```
|
|
115
|
+
Add `--require-screenshot` when `state.visualEvidence.required` is true: a passing criterion then has to name a file that exists, because a `screenshot` key pointing nowhere is not evidence.
|
|
116
|
+
|
|
115
117
|
Exit 1 means the "ok" is not accepted: tell the user which criterion is missing evidence (a `fail` verdict, or `not-tested` without a reason) and wait for the next reply. Exit 0 marks Phase 5 completed with `Result "local test passed (user)"`. The "fix: ..." path below is unchanged.
|
|
116
118
|
6. If fix needed:
|
|
117
119
|
- Branch already has WIP commit (from step 2) - changes are safe
|
|
@@ -138,7 +140,7 @@ Before or during user testing, run device-level audits via Bash if user requests
|
|
|
138
140
|
|
|
139
141
|
| Check | When | Command |
|
|
140
142
|
| ------------------- | ----------------- | ----------------------------------------- |
|
|
141
|
-
| UI flow video | `state.visualEvidence.required`
|
|
143
|
+
| UI flow video | `state.visualEvidence.required` AND Phase 3 recorded none | `capture-evidence.sh video start` -> drive the flow -> `video stop` -> `fit`. See below |
|
|
142
144
|
| Accessibility audit | UI changes | `mcp__multi-agent-toolkit__{ios,android}_accessibility_audit` |
|
|
143
145
|
| Biometric test | Auth flow changes | ios: `mcp__multi-agent-toolkit__ios_biometric` (android: manual) |
|
|
144
146
|
| Launch time | Perf-sensitive changes | ios: app-launch instrument · android: `mcp__multi-agent-toolkit__android_launch_time` |
|
|
@@ -146,6 +148,14 @@ Before or during user testing, run device-level audits via Bash if user requests
|
|
|
146
148
|
| Snapshot regression | Component / pixel-stable UI changes | ios: `mcp__multi-agent-toolkit__ios_visual_diff` · android: `mcp__multi-agent-toolkit__android_screenshot` + compare |
|
|
147
149
|
| Store screenshots | `taskType === screenshot` | ios: `ios_status_bar({preset: "clean"})` · android: `android_screenshot` |
|
|
148
150
|
|
|
151
|
+
##### UI flow video, when Phase 3 produced none
|
|
152
|
+
|
|
153
|
+
Phase 3 Step 3.55 is the primary host and runs in every mode. Phase 5 is the richer one where it exists: the device is up and a person is watching, so the flow is one somebody confirmed. It adds, never replaces.
|
|
154
|
+
|
|
155
|
+
Run only when `visualEvidence.required` and `visualEvidence.video.file` is empty: re-check the device with `$HOME/.claude/scripts/probe-evidence-capability.sh --only device`, then `$HOME/.claude/scripts/capture-evidence.sh video start` -> drive the flow (`run-ui-tests.sh run`, or the Phase 5 scenarios by hand) -> `video stop` -> `fit`. The cap is `visualEvidence.maxVideoSeconds`, read through `capture-evidence.sh limits` so one value serves both. Update `videoTier` and `videoTierReason` with what ran.
|
|
156
|
+
|
|
157
|
+
When the intake answer was `unit` there is no recording here either: overriding it in a phase the user may not be watching makes the question decorative.
|
|
158
|
+
|
|
149
159
|
Results included in Phase 7 report. MCP tools preferred when available - concise structured output, lower token cost.
|
|
150
160
|
|
|
151
161
|
**Snapshot regression flow (optional):** when the task changes a stable component, capture a screenshot before the change (baseline) and after (current), then call `ios_visual_diff({baseline, current, max_diff_pct: 1.0})`. Threshold can be relaxed for animated / non-deterministic regions - keep `max_diff_pct ≤ 1.0` for static layouts.
|
|
@@ -130,6 +130,29 @@ Branch **deterministically**, no implicit fallback. Read `agent-state.json` and
|
|
|
130
130
|
11. **NEVER close or resolve the issue** - neither GitHub Issue nor Jira. Issues require team review (4 approvals) before closing. Only post a comment with commit/PR URLs.
|
|
131
131
|
12. Log: "Phase 6: Commit {sha} - PR #{number}, worktree {removed|kept: <reason>}"
|
|
132
132
|
|
|
133
|
+
#### Step 2.9 - Resolve the evidence host (UI changes only)
|
|
134
|
+
|
|
135
|
+
Runs when `state.visualEvidence.required`, before the PR body is written, because the body renders a different shape per host. Record `state.visualEvidence.host` and `hostReason`:
|
|
136
|
+
|
|
137
|
+
1. **`jira`** - `state.jiraId` present. Upload stills and video with `jira-attach.sh "$JIRA_ID" <files>`; it prints `<filename>\t<url>` per file into `before[]` / `after[]` / `video`.
|
|
138
|
+
2. **`github-public` / `github-private`** - no Jira, `visualEvidence.githubHost` is `branch`, remote is GitHub. Push the stills to the orphan branch below, then pick the variant from `gh repo view --json isPrivate`.
|
|
139
|
+
3. **`none`** - anything else, including `githubHost: off`. Publish nothing, record the reason, let the `gaps[]` rule carry it.
|
|
140
|
+
|
|
141
|
+
No API attaches a file to a GitHub issue or PR (the web uploader needs a browser session), so the PR body can only point at something already hosted. An orphan branch is the one mechanism a script has that neither touches the PR diff nor publishes a release.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
EVB="evidence/$TASK_ID"
|
|
145
|
+
git -C "$WORKTREE" worktree add --detach "$TMP_EV" 2>/dev/null
|
|
146
|
+
git -C "$TMP_EV" checkout --orphan "$EVB" && git -C "$TMP_EV" rm -rf . >/dev/null 2>&1 || true
|
|
147
|
+
cp "$WORKTREE"/.pipeline/evidence/*.png "$TMP_EV"/ 2>/dev/null || true
|
|
148
|
+
git -C "$TMP_EV" add -A && git -C "$TMP_EV" commit -m "evidence: $TASK_ID"
|
|
149
|
+
git -C "$TMP_EV" push -u origin "$EVB"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Stills only. On a GitHub-hosted run no video is recorded (Step 3.55), and an mp4 behind a blob link is a download rather than something a reviewer opens. A failed push is not a phase failure: drop to `host: none` with the error as `hostReason`.
|
|
153
|
+
|
|
154
|
+
Log: `Phase 6 Step 2.9: evidence host = {jira|github-public|github-private|none} ({reason})`
|
|
155
|
+
|
|
133
156
|
#### Step 3 - PR Description (technical detail for reviewers)
|
|
134
157
|
|
|
135
158
|
**Section set + markup dialect: `channels/pr.md` - read it first.** Phase 7 channels replaces this body with that section set, so build to it. Required reading: `payload-contracts.md`.
|
|
@@ -1122,6 +1122,71 @@
|
|
|
1122
1122
|
"type": ["string", "null"],
|
|
1123
1123
|
"description": "Directory the worktree's artefacts were salvaged into before removal (agent-state, phase-tracker, triage-output, .pipeline/, build+test logs, review diff). Phase 7 and :resume read from here when worktreePath is gone."
|
|
1124
1124
|
},
|
|
1125
|
+
"testDepth": {
|
|
1126
|
+
"type": ["string", "null"],
|
|
1127
|
+
"enum": ["unit", "unit+ui", "unit+mcp", null],
|
|
1128
|
+
"description": "How far the run tests, answered at intake because Phase 5 is absent from four of the eight modes and a question asked where it cannot be reached is a question nobody answers. `unit+ui` runs the repo's own UI test and records the screen around it; `unit+mcp` drives the flow through the toolkit MCP instead. The options offered are built from evidenceCapability, never from the model's reading of the repo."
|
|
1129
|
+
},
|
|
1130
|
+
"testDepthSource": {
|
|
1131
|
+
"type": ["string", "null"],
|
|
1132
|
+
"enum": ["user", "autopilot", "default", "forced", null],
|
|
1133
|
+
"description": "Who chose. `forced` means only one option was open, so nothing was asked - recorded rather than passed off as the user's answer."
|
|
1134
|
+
},
|
|
1135
|
+
"evidenceCapability": {
|
|
1136
|
+
"type": ["object", "null"],
|
|
1137
|
+
"additionalProperties": true,
|
|
1138
|
+
"description": "What this machine and this repo can actually produce, measured by probe-evidence-capability.sh BEFORE the test-depth question. Every absent value carries its reason, so a closed option can say why instead of vanishing from the menu; a value that could not be measured is null with a reason, never false, because a probe that did not look and a probe that found nothing are different facts. Contract: multi-agent-refs/features/visual-evidence.md.",
|
|
1139
|
+
"properties": {
|
|
1140
|
+
"platform": { "type": "string", "enum": ["ios", "android", "web", "other"] },
|
|
1141
|
+
"uiTestTarget": {
|
|
1142
|
+
"type": ["string", "null"],
|
|
1143
|
+
"description": "The single chosen target, empty while several candidates exist and no match picks one."
|
|
1144
|
+
},
|
|
1145
|
+
"uiTestTargets": {
|
|
1146
|
+
"type": "array",
|
|
1147
|
+
"items": { "type": "string" },
|
|
1148
|
+
"description": "Every candidate. A real app has many: the reference iOS app has one XCUITest bundle among 477 files that merely sit under a *UITests path, and the reference Android app has eight instrumentation source sets."
|
|
1149
|
+
},
|
|
1150
|
+
"uiTestTargetReason": { "type": ["string", "null"] },
|
|
1151
|
+
"matchingTests": {
|
|
1152
|
+
"type": "array",
|
|
1153
|
+
"items": { "type": "string" },
|
|
1154
|
+
"description": "Tests that mention a changed file's name. A heuristic, and treated as one: an empty set falls to the next tier rather than concluding the screen is untested."
|
|
1155
|
+
},
|
|
1156
|
+
"matchingTestsReason": { "type": ["string", "null"] },
|
|
1157
|
+
"device": { "type": ["string", "null"] },
|
|
1158
|
+
"deviceReason": {
|
|
1159
|
+
"type": ["string", "null"],
|
|
1160
|
+
"description": "'no booted simulator, but one is available to boot' and 'no iOS simulator available on this machine' are different problems with different fixes, and the user can act on only one of them."
|
|
1161
|
+
},
|
|
1162
|
+
"recorder": { "type": ["boolean", "null"] },
|
|
1163
|
+
"recorderReason": { "type": ["string", "null"] },
|
|
1164
|
+
"mcp": { "type": ["boolean", "null"] },
|
|
1165
|
+
"mcpReason": { "type": ["string", "null"] },
|
|
1166
|
+
"tier1": {
|
|
1167
|
+
"type": "string",
|
|
1168
|
+
"enum": ["open", "closed", "unknown"],
|
|
1169
|
+
"description": "Whether the depth menu may offer tier 1. `unknown` means the target was not probed (a --only device re-check), which is not the same as closed and must not be rendered as one."
|
|
1170
|
+
},
|
|
1171
|
+
"tier2": { "type": "string", "enum": ["open", "closed", "unknown"] }
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
1174
|
+
"uiTest": {
|
|
1175
|
+
"type": ["object", "null"],
|
|
1176
|
+
"additionalProperties": true,
|
|
1177
|
+
"description": "The UI test run that produced the tier 1 recording. Subject to the same default-FAIL rule as the build: a zero exit code alone is not a pass, the log is the evidence, and evidence-gate.mjs reads it.",
|
|
1178
|
+
"properties": {
|
|
1179
|
+
"ran": { "type": "boolean" },
|
|
1180
|
+
"target": { "type": ["string", "null"] },
|
|
1181
|
+
"selected": { "type": "array", "items": { "type": "string" } },
|
|
1182
|
+
"status": { "type": ["string", "null"], "enum": ["passed", "failed", "not-run", null] },
|
|
1183
|
+
"notRunReason": {
|
|
1184
|
+
"type": ["string", "null"],
|
|
1185
|
+
"description": "Why it did not run: no target, no matching test, no device. Each is a reason to fall to the next video tier, never a phase failure."
|
|
1186
|
+
},
|
|
1187
|
+
"logPath": { "type": ["string", "null"] }
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1125
1190
|
"visualEvidence": {
|
|
1126
1191
|
"type": ["object", "null"],
|
|
1127
1192
|
"additionalProperties": false,
|
|
@@ -1198,10 +1263,23 @@
|
|
|
1198
1263
|
},
|
|
1199
1264
|
"description": "Captured in Phase 3 after the build+test gate, because Phase 5 is dropped by every autopilot and --local entry."
|
|
1200
1265
|
},
|
|
1266
|
+
"host": {
|
|
1267
|
+
"type": ["string", "null"],
|
|
1268
|
+
"enum": ["jira", "github-public", "github-private", "none", null],
|
|
1269
|
+
"description": "Where the artefacts are published, resolved in Phase 6. `jira` attaches both stills and video. `github-public` pushes the stills to the evidence branch and embeds them in the PR body. `github-private` pushes the same stills but the PR carries a blob permalink instead of an inline image, because GitHub's image proxy cannot fetch a private repo's raw URL and an embedded one renders broken for every reader. `none` publishes nothing and records the gap. Video is Jira-only by decision: without an attachment host there is nothing a recording can be attached to."
|
|
1270
|
+
},
|
|
1271
|
+
"hostReason": {
|
|
1272
|
+
"type": ["string", "null"],
|
|
1273
|
+
"description": "Why this host and not the one above it in the order. A host of `none` with no reason is the silence the Phase 6 blocker exists to catch."
|
|
1274
|
+
},
|
|
1201
1275
|
"videoTier": {
|
|
1202
1276
|
"type": ["integer", "null"],
|
|
1203
1277
|
"enum": [1, 2, 3, null],
|
|
1204
|
-
"description": "1 = the repo's own UI test target drove the flow, 2 = MCP-driven flow, 3 = no
|
|
1278
|
+
"description": "1 = the repo's own UI test target drove the flow, 2 = MCP-driven flow, 3 = no recording. Resolved from the capability probe, then RE-CHECKED at capture time: a device booted at intake can be gone by Phase 3, and a tier recorded from a stale measurement is a promise the run cannot keep."
|
|
1279
|
+
},
|
|
1280
|
+
"videoTierReason": {
|
|
1281
|
+
"type": ["string", "null"],
|
|
1282
|
+
"description": "Which rule produced the tier, and the tier it came down from when it was downgraded at capture time, e.g. 'tier 1 -> 2: simulator no longer booted'."
|
|
1205
1283
|
},
|
|
1206
1284
|
"video": {
|
|
1207
1285
|
"type": "object",
|