@mmerterden/multi-agent-pipeline 20.16.0 → 20.17.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 +30 -0
- package/README.md +6 -5
- package/README.tr.md +6 -5
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +14 -12
- package/docs/features.md +9 -1
- package/install/catalog-history.json +1 -1
- package/install/claude.mjs +41 -6
- package/install/codex.mjs +3 -3
- package/manifest.json +62 -49
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/bug-bash/SKILL.md +154 -0
- package/pipeline/commands/multi-agent/review/SKILL.md +13 -9
- package/pipeline/commands/multi-agent/setup/SKILL.md +8 -6
- package/pipeline/commands/multi-agent/sync/SKILL.md +4 -3
- package/pipeline/commands/multi-agent/update/SKILL.md +5 -1
- package/pipeline/commands/sim-test.md +71 -6
- package/pipeline/lib/plugin-skill-roots.mjs +80 -0
- package/pipeline/lib/usage-lifecycle.mjs +83 -0
- package/pipeline/multi-agent-refs/cross-cli-contract.md +20 -4
- package/pipeline/multi-agent-refs/features/assertion-judge.md +76 -0
- package/pipeline/multi-agent-refs/features/exploratory-testing.md +140 -0
- package/pipeline/multi-agent-refs/features/skill-conformance.md +1 -1
- package/pipeline/multi-agent-refs/features/usage-reporting.md +59 -5
- package/pipeline/multi-agent-refs/features/valven.md +30 -3
- package/pipeline/multi-agent-refs/help/en.md +2 -0
- package/pipeline/multi-agent-refs/help/tr.md +2 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +2 -2
- package/pipeline/schemas/bug-bash-verification.schema.json +41 -0
- package/pipeline/schemas/explore-run.schema.json +108 -0
- package/pipeline/schemas/prefs.schema.json +1 -1
- package/pipeline/scripts/_stack-routing.mjs +2 -0
- package/pipeline/scripts/bug-bash.mjs +379 -0
- package/pipeline/scripts/explore-findings.mjs +410 -0
- package/pipeline/scripts/gen-skills-index.mjs +1 -2
- package/pipeline/scripts/pr-timeline.mjs +135 -0
- package/pipeline/scripts/pre-commit-check.sh +4 -2
- package/pipeline/scripts/skill-conformance.mjs +17 -71
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +1 -1
- package/pipeline/scripts/uninstall.mjs +29 -8
- package/pipeline/scripts/usage-identity.mjs +36 -10
- package/pipeline/scripts/usage-register.mjs +95 -8
- package/pipeline/scripts/usage-report.mjs +52 -2
- package/pipeline/scripts/valven-gate.mjs +292 -11
- package/pipeline/skills/.skill-manifest.json +18 -10
- package/pipeline/skills/.skills-index.json +28 -6
- package/pipeline/skills/shared/README.md +6 -4
- package/pipeline/skills/shared/core/multi-agent-bug-bash/SKILL.md +150 -0
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +5 -1
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +4 -3
- package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +4 -1
- package/pipeline/skills/shared/external/e2e-testing/SKILL.md +124 -0
- package/pipeline/skills/shared/external/e2e-testing/references/document-fixtures.md +65 -0
- package/pipeline/skills/shared/external/valven-rules/SKILL.md +93 -0
- package/pipeline/skills/shared/{core → external}/valven-rules/references/rules.yml +52 -6
- package/pipeline/skills/skills-index.md +4 -2
- package/pipeline/skills/shared/core/valven-rules/SKILL.md +0 -62
|
@@ -184,7 +184,7 @@ Scope note: a guide governs only files under its own directory - a guide found
|
|
|
184
184
|
|
|
185
185
|
### 2c. Valven delivery rules - required on every input
|
|
186
186
|
|
|
187
|
-
The `valven-rules` registry (
|
|
187
|
+
The `valven-rules` registry (`ai-common-toolkit`) is applied to every review, in `review` mode. Collect what the gate needs, then run it:
|
|
188
188
|
|
|
189
189
|
```bash
|
|
190
190
|
VG_FILE="/tmp/multi-agent-review-${TASK_ID}-valven.json"
|
|
@@ -193,18 +193,21 @@ case "$kind/$provider" in
|
|
|
193
193
|
branch/local)
|
|
194
194
|
VG_ARGS+=(--repo . --base origin/develop --head "$ref") ;;
|
|
195
195
|
pr/github)
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
node "$HOME/.claude/scripts/pr-timeline.mjs" --repo "$org_repo" --pr "$pr_number" \
|
|
197
|
+
--out "/tmp/multi-agent-review-${TASK_ID}-pr.json" # gh pr view + ready time + bot-typed reviews
|
|
198
198
|
jq -r '.body // ""' "/tmp/multi-agent-review-${TASK_ID}-pr.json" > "/tmp/multi-agent-review-${TASK_ID}-body.md"
|
|
199
199
|
jq -r '.commits[] | "\(.authoredDate)\t\(.messageHeadline)"' "/tmp/multi-agent-review-${TASK_ID}-pr.json" > "/tmp/multi-agent-review-${TASK_ID}-commits.txt"
|
|
200
200
|
VG_ARGS+=(--branch "$(jq -r .headRefName "/tmp/multi-agent-review-${TASK_ID}-pr.json")"
|
|
201
201
|
--pr-body-file "/tmp/multi-agent-review-${TASK_ID}-body.md"
|
|
202
|
-
--commits-file "/tmp/multi-agent-review-${TASK_ID}-commits.txt"
|
|
202
|
+
--commits-file "/tmp/multi-agent-review-${TASK_ID}-commits.txt"
|
|
203
|
+
--pr-meta-file "/tmp/multi-agent-review-${TASK_ID}-pr.json") ;;
|
|
203
204
|
pr/bitbucket-server)
|
|
204
205
|
# Same credentials as Step 2. PR: fromRef.displayId + description; commits: values[].message.
|
|
205
|
-
# Write the description to ...-body.md
|
|
206
|
+
# Write the description to ...-body.md, one "<ISO authorTimestamp>\t<first message line>" per commit to ...-commits.txt,
|
|
207
|
+
# and the timeline to ...-meta.json (features/valven.md, "Development Behaviors"). Then:
|
|
206
208
|
VG_ARGS+=(--branch "$BB_FROM_REF" --pr-body-file "/tmp/multi-agent-review-${TASK_ID}-body.md"
|
|
207
|
-
--commits-file "/tmp/multi-agent-review-${TASK_ID}-commits.txt"
|
|
209
|
+
--commits-file "/tmp/multi-agent-review-${TASK_ID}-commits.txt"
|
|
210
|
+
--pr-meta-file "/tmp/multi-agent-review-${TASK_ID}-meta.json") ;;
|
|
208
211
|
esac
|
|
209
212
|
```
|
|
210
213
|
|
|
@@ -217,11 +220,12 @@ VG_RC=$? # 0 clean, 1 findings, 2 could not run
|
|
|
217
220
|
|
|
218
221
|
- Exit 2: print the stderr line and continue the review with `Valven: not measured (<reason>)` in the Step 6 summary. Never print it as passed.
|
|
219
222
|
- The gate's `findings[]` join the reviewer findings at Step 5 with `tag: valven`. They are measurements, so triage accepts them unless the task is exempt; an accepted `important` or `blocking` one makes the Step 7 decision NEEDS WORK.
|
|
220
|
-
- When `splitRequired` is true, run `node "$HOME/.claude/scripts/valven-split.mjs" --repo . --base "origin/<baseRef>" --head <head>` (plan only, no `--apply`) where a checkout exists, and append the slice table (branch, lines, files) to the `VLV-SIZE-01` finding's `fix
|
|
221
|
-
- When `VLV-AGE-01`, `VLV-BEHIND-01` or `VLV-TRACE-03` fires and a checkout exists,
|
|
223
|
+
- When `splitRequired` is true, run `node "$HOME/.claude/scripts/valven-split.mjs" --repo . --base "origin/<baseRef>" --head <head>` (plan only, no `--apply`) where a checkout exists, and append the slice table (branch, lines, files) to the `VLV-SIZE-01` finding's `fix`.
|
|
224
|
+
- When `VLV-AGE-01`, `VLV-BEHIND-01` or `VLV-TRACE-03` fires and a checkout exists, put the `valven-port.mjs` plan (same base and head, no `--apply`) in the finding's `fix` as `$HOME/.claude/multi-agent-refs/features/valven.md`, "Old work", lists it.
|
|
222
225
|
- The registry's judgement rules (`rules:` block: `VLV-SCOPE-02`, `VLV-PRDESC-02`) go into every reviewer prompt in Step 3, with the PR body when one exists. Each reviewer returns a verdict per ID.
|
|
226
|
+
- `VLV-CYCLE-01..03` are `suggestion` findings with a Valven score and never set NEEDS WORK alone; `VLV-PICKUP-01` is `important` for no reviewer, a `suggestion` for an old draft. Branch input reports them `not-measured`.
|
|
223
227
|
|
|
224
|
-
Step 6 adds one line under the table: `Valven: <n> finding(s) · <changedLines> lines / <fileCount> files · split: <yes N slices|no
|
|
228
|
+
Step 6 adds one line under the table: `Valven: <n> finding(s) · <changedLines> lines / <fileCount> files · split: <yes N slices|no>`, plus for a PR `Valven scores: churn · coding · pickup · review` from `scores` (`-` when absent).
|
|
225
229
|
|
|
226
230
|
### 3. Launch parallel reviewers - host-CLI dependent
|
|
227
231
|
|
|
@@ -150,15 +150,17 @@ pbcopy < /dev/null
|
|
|
150
150
|
Then, **whether they pasted or skipped**, run:
|
|
151
151
|
|
|
152
152
|
```bash
|
|
153
|
-
node "$HOME/.claude/scripts/usage-register.mjs"
|
|
153
|
+
node "$HOME/.claude/scripts/usage-register.mjs" --json
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
+
If its JSON line says `"needsPersonalAccount":true`, offer to tie this machine to
|
|
157
|
+
the person's own GitHub account once (the "Personal account" step in
|
|
158
|
+
`$HOME/.claude/multi-agent-refs/features/usage-reporting.md`).
|
|
159
|
+
|
|
156
160
|
It maps whatever token exists and switches reporting on, and requests a
|
|
157
|
-
per-machine write-only one when none resolves
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
which reads in the panel exactly like nobody using the pipeline. What is sent,
|
|
161
|
-
what never is, and `usageLog.optOut`:
|
|
161
|
+
per-machine write-only one when none resolves, so a machine that runs setup and
|
|
162
|
+
never runs update still reports. What is sent, what never is, and
|
|
163
|
+
`usageLog.optOut`:
|
|
162
164
|
`$HOME/.claude/multi-agent-refs/features/usage-reporting.md`.
|
|
163
165
|
|
|
164
166
|
### Auto-learned fields (no setup step needed)
|
|
@@ -73,7 +73,7 @@ Run every step automatically:
|
|
|
73
73
|
```
|
|
74
74
|
Step 0: DOCTOR doctor.mjs - exit 2 or 4 stops the sync
|
|
75
75
|
Step 1.5: DETECT Compare timestamps, find stale targets
|
|
76
|
-
Step 2: COPILOT Claude Code -> Copilot CLI (instructions +
|
|
76
|
+
Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 64 sub-command skills)
|
|
77
77
|
Step 2b: CODEX Claude Code -> Codex CLI (1 router skill + sub-command specs as refs + agent TOML)
|
|
78
78
|
Step 3: REPO Claude Code -> pipeline repo (genericized, personal data scrub, bash -n on all sh)
|
|
79
79
|
Step 3c: PLUGINS pipeline shared/external -> multi-agent-plugins marketplace (rebuild knowledge/,
|
|
@@ -131,6 +131,7 @@ Step 0 gate rules and why: `features/doctor.md`.
|
|
|
131
131
|
- `~/.claude/CLAUDE.md`, `~/.claude/rules/`, `~/.claude/knowledge/`
|
|
132
132
|
- `~/.claude/scripts/` - EXCEPT `pre-commit-check.sh`, `agent-guard.sh`, `agent-guard.py`, and `build-stack-plugins.mjs` (generic, synced)
|
|
133
133
|
- `~/.claude/settings.json`
|
|
134
|
+
- `~/.claude/multi-agent-refs/registries/` - the installer copies gate registries there from `pipeline/skills/shared/external`; the repo already holds the source
|
|
134
135
|
- **Any `~/.claude/commands/multi-agent/*/SKILL.md` whose frontmatter has `local-only: true`** - these are user/repo-specific alias wrappers that delegate to a private marketplace plugin's skills exposed as `multi-agent:<name>`. Syncing them would leak the private plugin/skill names into the public pipeline. Skip them at copy time; the backstop below proves none reached `pipeline/commands/`.
|
|
135
136
|
```bash
|
|
136
137
|
grep -rl "^local-only: true" ~/multi-agent-pipeline/pipeline/commands/ 2>/dev/null \
|
|
@@ -240,11 +241,11 @@ same specs as reference files rather than as peer skills, via Step 2b - see
|
|
|
240
241
|
|-------------|-------------|
|
|
241
242
|
| `~/.claude/commands/multi-agent/{cmd}/SKILL.md` | `~/.copilot/skills/multi-agent-{cmd}/SKILL.md` |
|
|
242
243
|
|
|
243
|
-
**
|
|
244
|
+
**64 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
|
|
244
245
|
|
|
245
246
|
```
|
|
246
247
|
analysis, analysis-jira, analysis-resolve, autopilot, autopilot-off, autopilot-on,
|
|
247
|
-
autopilot-status, build-optimize, channels, complaint-analysis, create-jira,
|
|
248
|
+
autopilot-status, bug-bash, build-optimize, channels, complaint-analysis, create-jira,
|
|
248
249
|
design-check, diff-explain, doctor, estimate, feedback, forget, garbage-collect, graph, help,
|
|
249
250
|
ios-coding-standard, issue, jira, kill, language, log,
|
|
250
251
|
manual-test, model, prune-logs, prune-prompts, purge, refactor, research, resume, review, review-analysis, review-issue, review-jira, route-off, route-on, route-status,
|
|
@@ -126,7 +126,7 @@ and every later block that needs them starts by sourcing it.
|
|
|
126
126
|
whichever of the three runs first.
|
|
127
127
|
|
|
128
128
|
```bash
|
|
129
|
-
node "$HOME/.claude/scripts/usage-register.mjs"
|
|
129
|
+
node "$HOME/.claude/scripts/usage-register.mjs" --json
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
It requests a per-machine **write-only** token when none resolves, stores it
|
|
@@ -135,6 +135,10 @@ and every later block that needs them starts by sourcing it.
|
|
|
135
135
|
reporting off with one status line - never an error. Contract and the data it
|
|
136
136
|
sends: `$HOME/.claude/multi-agent-refs/features/usage-reporting.md`.
|
|
137
137
|
|
|
138
|
+
If its JSON line says `"needsPersonalAccount":true`, offer to tie this machine to
|
|
139
|
+
the person's own GitHub account once (the "Personal account" step in
|
|
140
|
+
`$HOME/.claude/multi-agent-refs/features/usage-reporting.md`).
|
|
141
|
+
|
|
138
142
|
6. **Show the new version and its changes** (from the packaged CHANGELOG - there is no git history on this channel):
|
|
139
143
|
```bash
|
|
140
144
|
. "${TMPDIR:-/tmp}/multi-agent-update.env" || { echo "Run step 1 first."; exit 1; }
|
|
@@ -67,6 +67,29 @@ is the iOS-pinned alias of that same command.
|
|
|
67
67
|
|
|
68
68
|
## Flow
|
|
69
69
|
|
|
70
|
+
Every run follows the exploratory-testing contract: findings shape, budget,
|
|
71
|
+
stop rules, and blocked steps kept apart from product failures. Read it once
|
|
72
|
+
before Step 1:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
$HOME/.claude/multi-agent-refs/features/exploratory-testing.md
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Step 0 - Budget and run file
|
|
79
|
+
|
|
80
|
+
Open the run file at `$HOME/.claude/logs/sim-test/{bundle_id}/{timestamp}/run.json`
|
|
81
|
+
with the goal (the scenario, or "full sweep"), the platform and the budget
|
|
82
|
+
(defaults 8 steps, 10 minutes, 150000 tokens; the user may name others). Append
|
|
83
|
+
each step and each finding as it happens. Before every new step:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
node "$HOME/.claude/scripts/explore-findings.mjs" check --run "<run.json>"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`stop: true` ends exploration with that reason (`step-limit`, `time`, `tokens`,
|
|
90
|
+
or `stuck` after three consecutive blocked steps that reported nothing) and
|
|
91
|
+
goes straight to the crash sweep and Step 6. A failed step does not stop the run.
|
|
92
|
+
|
|
70
93
|
### Step 1 - Device & App Discovery
|
|
71
94
|
|
|
72
95
|
```
|
|
@@ -90,7 +113,16 @@ Call: ios_status_bar (time: "09:41", battery_level: 100)
|
|
|
90
113
|
|
|
91
114
|
### Step 3 - Systematic Screen Exploration
|
|
92
115
|
|
|
93
|
-
For each screen in the app, read before you look, and batch what you can
|
|
116
|
+
For each screen in the app, read before you look, and batch what you can.
|
|
117
|
+
|
|
118
|
+
**Locators before coordinates.** With `@mmerterden/multi-agent-toolkit-mcp` ≥
|
|
119
|
+
v3.19.0, resolve elements with `ui_locate` (accessibility id, then role plus
|
|
120
|
+
label, then visible text) and tap with `ios_tap_element` / `android_tap_element`;
|
|
121
|
+
assert exact outcomes with `expect_text` / `expect_element` / `expect_url` steps
|
|
122
|
+
inside `agent_run_steps`; type credentials with `secret_ref`, never the value.
|
|
123
|
+
`LOCATOR_NOT_FOUND` / `LOCATOR_AMBIGUOUS` block the step as automation, not
|
|
124
|
+
product. On an older toolkit, the frame-centre taps below are the fallback; say
|
|
125
|
+
which one ran in the report.
|
|
94
126
|
|
|
95
127
|
```
|
|
96
128
|
Call: ios_get_ui_tree (text: labels, frames, traits; ~0.1 s, no image)
|
|
@@ -213,6 +245,17 @@ Call: ios_list_crashes (app: <process name>, since_min: <minutes since Step 1 la
|
|
|
213
245
|
|
|
214
246
|
### Step 5 - Bug Detection (on EVERY screenshot)
|
|
215
247
|
|
|
248
|
+
Exact claims (a total, a label, a route, an element present) are checked with an
|
|
249
|
+
expect step or the UI tree. A visual or semantic claim that becomes a finding,
|
|
250
|
+
or decides a step, goes to a fresh judge subagent that sees only the assertion
|
|
251
|
+
and the current screenshot / UI tree, never this run's history:
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
$HOME/.claude/multi-agent-refs/features/assertion-judge.md
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
`inconclusive` never passes. The checked verdict goes on the finding as `judge`.
|
|
258
|
+
|
|
216
259
|
When analyzing each screenshot, check for:
|
|
217
260
|
|
|
218
261
|
| Category | What to Look For |
|
|
@@ -228,6 +271,18 @@ When analyzing each screenshot, check for:
|
|
|
228
271
|
|
|
229
272
|
### Step 6 - Report
|
|
230
273
|
|
|
274
|
+
Render the findings block from the run file first; its verdict, issues,
|
|
275
|
+
warnings and blocked section are the source for everything below:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
node "$HOME/.claude/scripts/explore-findings.mjs" report --run "<run.json>"
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Every bug below is a finding from the run file: `expected`, `observed`, `repro`,
|
|
282
|
+
`severity` 1-5 (5 critical, 4 high, 3 medium, 2 low, 1 trivial), `kind`
|
|
283
|
+
`issue` or `warning`, and `evidence`. A finding the script listed under
|
|
284
|
+
"failed the contract" is fixed or dropped, never reported as a bug.
|
|
285
|
+
|
|
231
286
|
Create tasks for each bug found:
|
|
232
287
|
|
|
233
288
|
```
|
|
@@ -246,18 +301,25 @@ Then output full report:
|
|
|
246
301
|
|
|
247
302
|
## Bugs Found ({N} total)
|
|
248
303
|
|
|
249
|
-
### BUG-1: [{
|
|
304
|
+
### BUG-1: [{severity word} {issue|warning}] {title}
|
|
250
305
|
|
|
251
306
|
- **Screen**: {which screen}
|
|
252
307
|
- **Category**: Layout / Dark Mode / Typography / Navigation
|
|
253
|
-
- **
|
|
308
|
+
- **Repro**: 1. Open app -> 2. Tap {X} -> 3. Observe {issue}
|
|
254
309
|
- **Expected**: {correct behavior}
|
|
255
|
-
- **
|
|
310
|
+
- **Observed**: {what's wrong}
|
|
311
|
+
- **Judge**: {passed / failed ASSERTION_FAILED / failed ASSERTION_INCONCLUSIVE, when a judge decided it}
|
|
256
312
|
- **Spec**: "<quoted acceptance criterion from the analysis doc Section 15 / 20, or: no spec, model expectation>" (<source>)
|
|
257
313
|
- **Evidence**: before=<png path> after=<png path>
|
|
258
314
|
|
|
259
315
|
### BUG-2: ...
|
|
260
316
|
|
|
317
|
+
## Blocked ({N}) - not product failures
|
|
318
|
+
|
|
319
|
+
| Step | Cause | Code | Detail |
|
|
320
|
+
| ---- | ----- | ---- | ------ |
|
|
321
|
+
| 4 | credentials | CREDENTIAL_MISSING | no test account for checkout |
|
|
322
|
+
|
|
261
323
|
## Screens Visited ({N})
|
|
262
324
|
|
|
263
325
|
| # | Screen | Light | Dark | Large Text | Bugs | Evidence |
|
|
@@ -271,13 +333,16 @@ Then output full report:
|
|
|
271
333
|
- Screens tested: {N}
|
|
272
334
|
- Clean: {N}
|
|
273
335
|
- With bugs: {N}
|
|
274
|
-
-
|
|
336
|
+
- Verdict: {passed / failed / blocked} · ended: {finished / step-limit / time / tokens / stuck}
|
|
337
|
+
- Budget used: {steps}/{max} steps · {min}/{max} min · {tokens}/{max} tokens
|
|
338
|
+
- Critical: {N} | High: {N} | Medium: {N} | Low: {N} | Trivial: {N}
|
|
339
|
+
- Blocked steps: {N} (environment {N} · credentials {N} · seed data {N} · automation {N})
|
|
275
340
|
- Accessibility audit (accessibility scenario): {N} audited, {N} not audited (reason per screen), deep pass: {ran / skipped: reason}
|
|
276
341
|
- Crashes (full scenario): {N} during this run
|
|
277
342
|
- Production readiness: FAILED | NEEDS WORK | READY
|
|
278
343
|
```
|
|
279
344
|
|
|
280
|
-
`Evidence` in a bug block is the captures already written to files in Step 4: a tap-driven finding needs both `before` and `after`, a static finding (layout, contrast, dark mode, large text) needs `after` only. The `Evidence` column in Screens Visited is the count of screenshot files written for that screen. Production readiness defaults to FAILED; it is NEEDS WORK when only
|
|
345
|
+
`Evidence` in a bug block is the captures already written to files in Step 4: a tap-driven finding needs both `before` and `after`, a static finding (layout, contrast, dark mode, large text) needs `after` only. The `Evidence` column in Screens Visited is the count of screenshot files written for that screen. Production readiness defaults to FAILED; it is NEEDS WORK when only low / trivial findings remain, and READY only when there are no findings of severity 3 or above and every planned screen was visited. A blocked run is never READY: the screens it could not reach were not tested.
|
|
281
346
|
|
|
282
347
|
Save to: `$HOME/.claude/logs/sim-test/{bundle_id}/{timestamp}.md`
|
|
283
348
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file plugin-skill-roots.mjs - the `skills/` roots of the marketplace stack
|
|
3
|
+
* plugins on this machine.
|
|
4
|
+
*
|
|
5
|
+
* On Claude Code the stack skills do not land in ~/.claude/skills - they are
|
|
6
|
+
* loaded from the multi-agent-plugins marketplace. Registries a gate depends on
|
|
7
|
+
* (`ios-coding-standard`, `ios-module-structure`, `valven-rules`) live in a
|
|
8
|
+
* plugin's knowledge/ tree, so without these roots a gate reads green over
|
|
9
|
+
* criteria that were never loaded. Local checkout is preferred over the plugin
|
|
10
|
+
* cache for the same reason install/_plugin-skills.mjs prefers it: the checkout
|
|
11
|
+
* is what /multi-agent:sync rebuilds, the cache only advances on marketplace
|
|
12
|
+
* refresh. Cache dirs are version-named; newest wins.
|
|
13
|
+
*
|
|
14
|
+
* @module pipeline/lib/plugin-skill-roots
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
|
|
21
|
+
export const PLUGIN_MARKETPLACE = "multi-agent-plugins";
|
|
22
|
+
|
|
23
|
+
export const STACK_PLUGIN_NAMES = Object.freeze([
|
|
24
|
+
"ai-ios-toolkit",
|
|
25
|
+
"ai-android-toolkit",
|
|
26
|
+
"ai-frontend-toolkit",
|
|
27
|
+
"ai-backend-toolkit",
|
|
28
|
+
"ai-common-toolkit",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
/** Numeric semver order, falling back to string order for non-numeric parts. */
|
|
32
|
+
export function semverCompare(a, b) {
|
|
33
|
+
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
34
|
+
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
|
35
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
36
|
+
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
37
|
+
if (d !== 0) return d;
|
|
38
|
+
}
|
|
39
|
+
return a.localeCompare(b);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isDir(p) {
|
|
43
|
+
try {
|
|
44
|
+
return statSync(p).isDirectory();
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function listDirs(p) {
|
|
51
|
+
try {
|
|
52
|
+
return readdirSync(p).filter((n) => isDir(join(p, n)));
|
|
53
|
+
} catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Every `skills/` root a plugin may be loaded from, best first: the marketplace
|
|
60
|
+
* checkout, then each cached version, newest first. A caller looking for one
|
|
61
|
+
* file walks this list, so a checkout or a cache that predates the file does not
|
|
62
|
+
* hide a version that has it.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} [home]
|
|
65
|
+
* @param {readonly string[]} [names]
|
|
66
|
+
* @returns {string[]}
|
|
67
|
+
*/
|
|
68
|
+
export function pluginSkillRootCandidates(home = homedir(), names = STACK_PLUGIN_NAMES) {
|
|
69
|
+
const roots = [];
|
|
70
|
+
for (const name of names) {
|
|
71
|
+
const checkout = join(home, PLUGIN_MARKETPLACE, "plugins", name, "skills");
|
|
72
|
+
if (existsSync(checkout)) roots.push(checkout);
|
|
73
|
+
const cacheRoot = join(home, ".claude", "plugins", "cache", PLUGIN_MARKETPLACE, name);
|
|
74
|
+
for (const v of listDirs(cacheRoot).sort(semverCompare).reverse()) {
|
|
75
|
+
const p = join(cacheRoot, v, "skills");
|
|
76
|
+
if (existsSync(p)) roots.push(p);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return roots;
|
|
80
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usage-lifecycle.mjs - install and update records for operational reporting.
|
|
3
|
+
*
|
|
4
|
+
* Derived, never stored by the installer: install.js lays down files and
|
|
5
|
+
* nothing else. The version stamp it writes (`.pipeline-version`) says which
|
|
6
|
+
* version is installed and, by its modification time, since when; prefs keep the
|
|
7
|
+
* last version reported (`usageLog.reportedVersion`). When the two differ,
|
|
8
|
+
* `usage-report.mjs` sends one record and moves `reportedVersion` forward once
|
|
9
|
+
* the server accepted it.
|
|
10
|
+
*
|
|
11
|
+
* A record carries `ev` (`install` or `update`), `v`, `vf` (the version it
|
|
12
|
+
* replaced, null when unknown), `hs` (the host trees at that version) and `t`
|
|
13
|
+
* (the stamp time). Its id is derived from the version and the stamp time, so a
|
|
14
|
+
* resend replaces the record rather than adding one.
|
|
15
|
+
*
|
|
16
|
+
* @module pipeline/lib/usage-lifecycle
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readFileSync, statSync } from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
|
|
22
|
+
const VERSION_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
23
|
+
const HOSTS = ["claude", "copilot", "codex"];
|
|
24
|
+
|
|
25
|
+
function version(v) {
|
|
26
|
+
const s = typeof v === "string" ? v.trim() : "";
|
|
27
|
+
return VERSION_RE.test(s) ? s : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readStamp(home, host) {
|
|
31
|
+
const path = join(home, `.${host}`, ".pipeline-version");
|
|
32
|
+
try {
|
|
33
|
+
const v = version(readFileSync(path, "utf8"));
|
|
34
|
+
return v ? { host, v, mtimeMs: statSync(path).mtimeMs } : null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The installed version, the time its stamp was written and every host tree at
|
|
42
|
+
* that version; Claude Code's stamp first. Null when no stamp is readable.
|
|
43
|
+
*/
|
|
44
|
+
export function installedVersion(home) {
|
|
45
|
+
const stamps = HOSTS.map((h) => readStamp(home, h)).filter(Boolean);
|
|
46
|
+
if (!stamps.length) return null;
|
|
47
|
+
const first = stamps[0];
|
|
48
|
+
return {
|
|
49
|
+
v: first.v,
|
|
50
|
+
at: new Date(Math.floor(first.mtimeMs)).toISOString(),
|
|
51
|
+
hosts: stamps.filter((s) => s.v === first.v).map((s) => s.host),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The record for an installed version not reported yet, or null.
|
|
57
|
+
*
|
|
58
|
+
* Without a reported version the machine's registration decides: registered
|
|
59
|
+
* after the stamp means this version was its first install; registered before
|
|
60
|
+
* it (or by a version that did not record when) means an update from a version
|
|
61
|
+
* nobody reported.
|
|
62
|
+
*/
|
|
63
|
+
export function lifecycleEvent({ installed, usageLog = {} }) {
|
|
64
|
+
if (!installed?.v) return null;
|
|
65
|
+
const reported = version(usageLog.reportedVersion);
|
|
66
|
+
if (reported === installed.v) return null;
|
|
67
|
+
let ev = "update";
|
|
68
|
+
if (!reported) {
|
|
69
|
+
const registeredAt = Date.parse(usageLog.registeredAt || "");
|
|
70
|
+
const registeredEarlier =
|
|
71
|
+
Boolean(usageLog.registeredAs) &&
|
|
72
|
+
(Number.isNaN(registeredAt) || registeredAt < Date.parse(installed.at));
|
|
73
|
+
if (!registeredEarlier) ev = "install";
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
id: `lc-${installed.v}-${Date.parse(installed.at)}`,
|
|
77
|
+
ev,
|
|
78
|
+
v: installed.v,
|
|
79
|
+
vf: reported,
|
|
80
|
+
hs: installed.hosts,
|
|
81
|
+
t: installed.at,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Cross-CLI Contract (Claude Code · Copilot CLI · Codex CLI)
|
|
2
2
|
|
|
3
3
|
<!-- toc -->
|
|
4
|
-
- [1. Command Inventory (
|
|
4
|
+
- [1. Command Inventory (64 commands)](#1-command-inventory-64-commands)
|
|
5
5
|
- [2. Canonical Placeholder Vocabulary](#2-canonical-placeholder-vocabulary)
|
|
6
6
|
- [2.6 Intentional structural divergence - thin dispatcher vs inlined orchestrator](#26-intentional-structural-divergence---thin-dispatcher-vs-inlined-orchestrator)
|
|
7
7
|
- [2.7 One command, three different meanings: `/multi-agent:model`](#27-one-command-three-different-meanings-multi-agentmodel)
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
## 1. Command Inventory (
|
|
23
|
+
## 1. Command Inventory (64 commands)
|
|
24
24
|
|
|
25
25
|
```
|
|
26
26
|
analysis, analysis-jira, analysis-resolve, autopilot, autopilot-off,
|
|
27
|
-
autopilot-on, autopilot-status, build-optimize, channels, complaint-analysis,
|
|
27
|
+
autopilot-on, autopilot-status, bug-bash, build-optimize, channels, complaint-analysis,
|
|
28
28
|
create-jira, design-check, diff-explain, doctor, estimate, feedback, forget,
|
|
29
29
|
garbage-collect, graph, help, ios-coding-standard, issue, jira, kill,
|
|
30
30
|
language, log, manual-test, model, prune-logs,
|
|
@@ -42,7 +42,7 @@ Categories:
|
|
|
42
42
|
- **Issue generator** (one-shot, no worktree, asks type Task/Bug/Story, hard approval gate before create): `create-jira`
|
|
43
43
|
- **Pipeline entries**: `autopilot` (plus the bare `/multi-agent` in the dispatcher). The phase set is a property of the command, and every mode runs its whole set.
|
|
44
44
|
- **Tail path** (the pipeline tail over work already on a branch, no run behind it): `resume`
|
|
45
|
-
- **Ops commands** (one-shot, no worktree): `status`, `log`, `kill`, `steer`, `purge`, `uninstall`, `resume`, `review`, `review-jira`, `review-issue`, `analysis`, `analysis-resolve`, `research`, `scaffold`, `complaint-analysis`, `build-optimize`, `channels`, `scan`, `search`, `diff-explain`, `garbage-collect`, `graph`, `prune-logs`, `prune-prompts`, `serve`
|
|
45
|
+
- **Ops commands** (one-shot, no worktree): `status`, `log`, `kill`, `steer`, `purge`, `uninstall`, `resume`, `review`, `review-jira`, `review-issue`, `analysis`, `analysis-resolve`, `research`, `scaffold`, `complaint-analysis`, `bug-bash`, `build-optimize`, `channels`, `scan`, `search`, `diff-explain`, `garbage-collect`, `graph`, `prune-logs`, `prune-prompts`, `serve`
|
|
46
46
|
- **Local audits** (worktree only to build; no commit, push, PR or channels): `design-check`, `testflight-validation`, `ios-coding-standard`. `testflight-validation` additionally never invokes `altool --upload-app` - a validation run must not be able to ship a build by accident.
|
|
47
47
|
- **Meta-ops**: `setup`, `sync`, `update`, `help`, `refactor`, `test`, `stack`, `manual-test`, `language`
|
|
48
48
|
- **Routines** (user-defined routine registry; the routines they create are local-only and never synced): `save`, `routines`, `forget`
|
|
@@ -96,6 +96,22 @@ Both skills are wired to 4 consumers: `/multi-agent:test "store-ready"` (primary
|
|
|
96
96
|
|
|
97
97
|
Superseded by section 1.1: Claude Code resolves component dispatch to the marketplace plugin (dual-name `create-component`/`create-ui-component`; full contract in `$HOME/.claude/multi-agent-refs/component-dispatch.md`); Copilot CLI receives the enabled plugin's authored skills through `install/copilot.mjs` (section 1.1), not a standalone `figma-*` tree - the installer prunes those. No shared filesystem-path routing table or figma-skill frontmatter/inventory parity remains under enforcement.
|
|
98
98
|
|
|
99
|
+
### 1.4 End-to-end testing tools
|
|
100
|
+
|
|
101
|
+
`/multi-agent:test` (`commands/sim-test.md`), `/multi-agent:bug-bash` and the
|
|
102
|
+
`e2e-testing` knowledge skill (ai-common-toolkit) call these toolkit tools when
|
|
103
|
+
the server has them, and fall back to UI-tree reads and frame-centre taps when
|
|
104
|
+
it does not. Contracts: `features/exploratory-testing.md`,
|
|
105
|
+
`features/assertion-judge.md`.
|
|
106
|
+
|
|
107
|
+
| Capability | Tools / fields | Minimum |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| Element locators | `ui_locate`, `ios_tap_element`, `android_tap_element`; codes `LOCATOR_NOT_FOUND`, `LOCATOR_AMBIGUOUS` | `@mmerterden/multi-agent-toolkit-mcp` ≥ v3.19.0 |
|
|
110
|
+
| Assertion steps and verdicts | `agent_run_steps` steps `expect_text`, `expect_element`, `expect_url`; `verdict: passed / failed / blocked` with `ASSERTION_FAILED`, `CREDENTIAL_MISSING`, `ENVIRONMENT_UNAVAILABLE`, `SEED_DATA_MISSING`, `BUDGET_EXCEEDED`, `REPLAY_DIVERGED`; failure `trace.md` | ≥ v3.19.0 |
|
|
111
|
+
| Secrets by reference | `secret_ref` on the type tools | ≥ v3.19.0 |
|
|
112
|
+
| Parallel web sessions, replay | `session` on the web tools; `save_as` / `replay` | ≥ v3.19.0 |
|
|
113
|
+
| Travel document fixtures | `document_generate`, `document_mrz_build` | ≥ v3.19.0 |
|
|
114
|
+
|
|
99
115
|
---
|
|
100
116
|
|
|
101
117
|
## 2. Canonical Placeholder Vocabulary
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Assertion judge - a fresh reader decides what the screen shows
|
|
2
|
+
|
|
3
|
+
A visual or semantic assertion ("the error message tells the user what to do",
|
|
4
|
+
"no text is clipped", "the selected plan is highlighted") cannot be decided by
|
|
5
|
+
an exact comparison. It is decided by a judge: a fresh subagent that sees the
|
|
6
|
+
assertion and the current evidence, and nothing about how the run got there.
|
|
7
|
+
|
|
8
|
+
Consumers: `/multi-agent:test` (`commands/sim-test.md` Step 5),
|
|
9
|
+
`/multi-agent:bug-bash` (charters and verification). Script:
|
|
10
|
+
`$HOME/.claude/scripts/explore-findings.mjs judge-packet` / `judge-verdict`.
|
|
11
|
+
|
|
12
|
+
## 1. Why the judge is separate
|
|
13
|
+
|
|
14
|
+
The agent that drove the app knows what it meant to do, what it already
|
|
15
|
+
believes, and what it was told about when a step counts as done. Every one of
|
|
16
|
+
those lowers the bar for "the screen shows it". A judge that reads only the
|
|
17
|
+
statement and the evidence answers the question that was asked. The actor's
|
|
18
|
+
transcript, its step summaries, earlier findings and its instructions never
|
|
19
|
+
reach the judge.
|
|
20
|
+
|
|
21
|
+
What does reach it is the app's own vocabulary, when the assertion depends on
|
|
22
|
+
it ("plans are called tiers"): a sentence that would still be true for a
|
|
23
|
+
different tester on the same app.
|
|
24
|
+
|
|
25
|
+
## 2. When to use it
|
|
26
|
+
|
|
27
|
+
| Assertion | Decided by |
|
|
28
|
+
|---|---|
|
|
29
|
+
| exact text, count, total, route, element present | an expect step (`expect_text`, `expect_element`, `expect_url`) or a UI-tree read; no judge |
|
|
30
|
+
| layout, contrast, clipping, overlap, an image's content | judge, with a screenshot |
|
|
31
|
+
| copy meaning ("explains the error"), state ("the right tab is selected") | judge, with the screenshot and the UI tree |
|
|
32
|
+
|
|
33
|
+
If an exact check can decide it, it is not a judge question.
|
|
34
|
+
|
|
35
|
+
## 3. Flow
|
|
36
|
+
|
|
37
|
+
1. Capture the evidence now, for this assertion: a screenshot file and, when
|
|
38
|
+
useful, the UI tree saved to a file. Evidence from an earlier step is not
|
|
39
|
+
current.
|
|
40
|
+
2. Build the packet:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
node "$HOME/.claude/scripts/explore-findings.mjs" judge-packet \
|
|
44
|
+
--assertion "<one sentence>" \
|
|
45
|
+
--evidence screenshot=<png> --evidence ui-tree=<json> \
|
|
46
|
+
[--vocabulary "<app terms>"] > <dir>/judge-packet.json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The script refuses any field that describes the run.
|
|
50
|
+
3. Dispatch one fresh subagent (Agent tool, no conversation context carried
|
|
51
|
+
over; Copilot CLI: a new task). Its whole prompt is the packet: the
|
|
52
|
+
`instructions` field, the assertion, the evidence paths to open, and the
|
|
53
|
+
vocabulary when present.
|
|
54
|
+
4. Save its JSON answer to `<dir>/judge-verdict.json` and check it:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
node "$HOME/.claude/scripts/explore-findings.mjs" judge-verdict \
|
|
58
|
+
--packet <dir>/judge-packet.json --verdict <dir>/judge-verdict.json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 4. Verdicts
|
|
62
|
+
|
|
63
|
+
| Judge answer | Result |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `passed`, citing evidence from the packet | passed |
|
|
66
|
+
| `failed`, citing evidence from the packet | failed, `ASSERTION_FAILED` |
|
|
67
|
+
| `inconclusive` | failed, `ASSERTION_INCONCLUSIVE` |
|
|
68
|
+
| a verdict citing a path not in the packet, no reason, or no citation | failed, `ASSERTION_INCONCLUSIVE` |
|
|
69
|
+
|
|
70
|
+
Inconclusive is never a pass. It means the evidence did not show enough:
|
|
71
|
+
capture a better screenshot (scroll, wait for the content) and judge again once,
|
|
72
|
+
or record the step as failed with that code. The checked result goes on the
|
|
73
|
+
finding as `judge: {verdict, code, reason}`.
|
|
74
|
+
|
|
75
|
+
The actor never overrides a judge verdict. When it disagrees, it captures new
|
|
76
|
+
evidence and asks a new judge; it does not argue with the old one.
|