@mmerterden/multi-agent-pipeline 20.15.1 → 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.
Files changed (67) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +6 -5
  3. package/README.tr.md +6 -5
  4. package/docs/architecture.md +3 -3
  5. package/docs/ecosystem.md +5 -5
  6. package/docs/facts.json +14 -12
  7. package/docs/features.md +9 -1
  8. package/install/catalog-history.json +1 -1
  9. package/install/claude.mjs +40 -1
  10. package/install/codex.mjs +4 -3
  11. package/manifest.json +70 -51
  12. package/package.json +1 -1
  13. package/pipeline/commands/multi-agent/bug-bash/SKILL.md +154 -0
  14. package/pipeline/commands/multi-agent/review/SKILL.md +46 -1
  15. package/pipeline/commands/multi-agent/setup/SKILL.md +8 -6
  16. package/pipeline/commands/multi-agent/sync/SKILL.md +4 -3
  17. package/pipeline/commands/multi-agent/update/SKILL.md +5 -1
  18. package/pipeline/commands/sim-test.md +71 -6
  19. package/pipeline/lib/plugin-skill-roots.mjs +80 -0
  20. package/pipeline/lib/usage-lifecycle.mjs +83 -0
  21. package/pipeline/multi-agent-refs/cross-cli-contract.md +20 -4
  22. package/pipeline/multi-agent-refs/features/assertion-judge.md +76 -0
  23. package/pipeline/multi-agent-refs/features/exploratory-testing.md +140 -0
  24. package/pipeline/multi-agent-refs/features/review-decision.md +2 -1
  25. package/pipeline/multi-agent-refs/features/skill-conformance.md +1 -1
  26. package/pipeline/multi-agent-refs/features/usage-reporting.md +59 -5
  27. package/pipeline/multi-agent-refs/features/valven.md +110 -0
  28. package/pipeline/multi-agent-refs/help/en.md +2 -0
  29. package/pipeline/multi-agent-refs/help/tr.md +2 -0
  30. package/pipeline/multi-agent-refs/phases/phase-0-init.md +2 -1
  31. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +2 -0
  32. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +2 -4
  33. package/pipeline/multi-agent-refs/phases/phase-3-review.md +5 -5
  34. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +3 -2
  35. package/pipeline/schemas/agent-state.schema.json +16 -0
  36. package/pipeline/schemas/bug-bash-verification.schema.json +41 -0
  37. package/pipeline/schemas/explore-run.schema.json +108 -0
  38. package/pipeline/schemas/prefs.schema.json +21 -0
  39. package/pipeline/scripts/_stack-routing.mjs +2 -0
  40. package/pipeline/scripts/bug-bash.mjs +379 -0
  41. package/pipeline/scripts/explore-findings.mjs +410 -0
  42. package/pipeline/scripts/pr-timeline.mjs +135 -0
  43. package/pipeline/scripts/pre-commit-check.sh +4 -2
  44. package/pipeline/scripts/review-decision-gate.mjs +1 -1
  45. package/pipeline/scripts/skill-conformance.mjs +17 -71
  46. package/pipeline/scripts/smoke-cross-cli-behavior.sh +1 -1
  47. package/pipeline/scripts/uninstall.mjs +28 -3
  48. package/pipeline/scripts/usage-identity.mjs +36 -10
  49. package/pipeline/scripts/usage-register.mjs +95 -8
  50. package/pipeline/scripts/usage-report.mjs +52 -2
  51. package/pipeline/scripts/valven-gate.mjs +1069 -0
  52. package/pipeline/scripts/valven-port.mjs +169 -0
  53. package/pipeline/scripts/valven-split.mjs +341 -0
  54. package/pipeline/scripts/verify-citations.mjs +14 -1
  55. package/pipeline/skills/.skill-manifest.json +18 -6
  56. package/pipeline/skills/.skills-index.json +126 -71
  57. package/pipeline/skills/shared/README.md +8 -5
  58. package/pipeline/skills/shared/core/multi-agent-bug-bash/SKILL.md +150 -0
  59. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +2 -0
  60. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +5 -1
  61. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +4 -3
  62. package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +4 -1
  63. package/pipeline/skills/shared/external/e2e-testing/SKILL.md +124 -0
  64. package/pipeline/skills/shared/external/e2e-testing/references/document-fixtures.md +65 -0
  65. package/pipeline/skills/shared/external/valven-rules/SKILL.md +93 -0
  66. package/pipeline/skills/shared/external/valven-rules/references/rules.yml +186 -0
  67. package/pipeline/skills/skills-index.md +76 -71
@@ -182,6 +182,51 @@ Persist `agent-state.review.moduleGuides = [<repo-relative paths>]` and inject i
182
182
 
183
183
  Scope note: a guide governs only files under its own directory - a guide found under one module must not be applied to a sibling module's changes in the same PR.
184
184
 
185
+ ### 2c. Valven delivery rules - required on every input
186
+
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
+
189
+ ```bash
190
+ VG_FILE="/tmp/multi-agent-review-${TASK_ID}-valven.json"
191
+ VG_ARGS=(--mode review --out "$VG_FILE")
192
+ case "$kind/$provider" in
193
+ branch/local)
194
+ VG_ARGS+=(--repo . --base origin/develop --head "$ref") ;;
195
+ pr/github)
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
+ jq -r '.body // ""' "/tmp/multi-agent-review-${TASK_ID}-pr.json" > "/tmp/multi-agent-review-${TASK_ID}-body.md"
199
+ jq -r '.commits[] | "\(.authoredDate)\t\(.messageHeadline)"' "/tmp/multi-agent-review-${TASK_ID}-pr.json" > "/tmp/multi-agent-review-${TASK_ID}-commits.txt"
200
+ VG_ARGS+=(--branch "$(jq -r .headRefName "/tmp/multi-agent-review-${TASK_ID}-pr.json")"
201
+ --pr-body-file "/tmp/multi-agent-review-${TASK_ID}-body.md"
202
+ --commits-file "/tmp/multi-agent-review-${TASK_ID}-commits.txt"
203
+ --pr-meta-file "/tmp/multi-agent-review-${TASK_ID}-pr.json") ;;
204
+ pr/bitbucket-server)
205
+ # Same credentials as Step 2. PR: fromRef.displayId + description; commits: values[].message.
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:
208
+ VG_ARGS+=(--branch "$BB_FROM_REF" --pr-body-file "/tmp/multi-agent-review-${TASK_ID}-body.md"
209
+ --commits-file "/tmp/multi-agent-review-${TASK_ID}-commits.txt"
210
+ --pr-meta-file "/tmp/multi-agent-review-${TASK_ID}-meta.json") ;;
211
+ esac
212
+ ```
213
+
214
+ For PR input: when the cwd is a checkout of the same repo, `git fetch origin "<headRef>"` (this moves no HEAD) and add `--repo . --base "origin/<baseRef>" --head FETCH_HEAD`, so sizes and the rework ratio come from history. Otherwise add `--diff-file "$DIFF_FILE"`; the rework rule then reports `not-measured`, which is stated in the summary rather than read as a pass.
215
+
216
+ ```bash
217
+ node "$HOME/.claude/scripts/valven-gate.mjs" "${VG_ARGS[@]}" >/dev/null
218
+ VG_RC=$? # 0 clean, 1 findings, 2 could not run
219
+ ```
220
+
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.
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.
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.
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`.
227
+
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).
229
+
185
230
  ### 3. Launch parallel reviewers - host-CLI dependent
186
231
 
187
232
  Every host runs three reviewers; only the second slot differs, because GPT-5.4 exists on Copilot and Codex but not on Claude Code, where Opus fills it.
@@ -218,7 +263,7 @@ Never dispatch a Fable reviewer or a Fable triage on the model list below alone.
218
263
 
219
264
  The rung switch and its resolution: `$HOME/.claude/multi-agent-refs/features/model-fallback.md`.
220
265
 
221
- Each reviewer receives the diff, the module review guides from Step 2b (when any were found), plus the standard reviewer system prompt (see `$HOME/.claude/multi-agent-refs/phases/phase-3-review.md` for the prompt contract). Output: structured `findings[]` per reviewer.
266
+ Each reviewer receives the diff, the module review guides from Step 2b (when any were found), the Valven judgement rules from Step 2c, plus the standard reviewer system prompt (see `$HOME/.claude/multi-agent-refs/phases/phase-3-review.md` for the prompt contract). Output: structured `findings[]` per reviewer.
222
267
 
223
268
  ### 4. Store-compliance cross-reference
224
269
 
@@ -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. This call is why the step exists:
158
- registration used to happen only inside `/multi-agent:update`, so a user who
159
- installed, ran setup and never ran update never registered and never reported -
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 + 63 sub-command skills)
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
- **63 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
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: [{Critical/Major/Minor}] {title}
304
+ ### BUG-1: [{severity word} {issue|warning}] {title}
250
305
 
251
306
  - **Screen**: {which screen}
252
307
  - **Category**: Layout / Dark Mode / Typography / Navigation
253
- - **Steps**: 1. Open app -> 2. Tap {X} -> 3. Observe {issue}
308
+ - **Repro**: 1. Open app -> 2. Tap {X} -> 3. Observe {issue}
254
309
  - **Expected**: {correct behavior}
255
- - **Actual**: {what's wrong}
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
- - Critical: {N} | Major: {N} | Minor: {N}
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 Minor bugs remain, and READY only when there are zero Critical / Major bugs and every planned screen was visited.
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 (63 commands)](#1-command-inventory-63-commands)
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 (63 commands)
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.