@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
@@ -0,0 +1,140 @@
1
+ # Exploratory testing - findings, budgets, stop rules
2
+
3
+ <!-- toc -->
4
+ - [1. The run file](#1-the-run-file)
5
+ - [2. A finding](#2-a-finding)
6
+ - [3. Budget and stop rules](#3-budget-and-stop-rules)
7
+ - [4. Product failure or blocked](#4-product-failure-or-blocked)
8
+ - [5. Driving the app: locators and expect steps](#5-driving-the-app-locators-and-expect-steps)
9
+ - [6. Report](#6-report)
10
+ <!-- /toc -->
11
+
12
+ An exploratory run drives a live app toward a goal and reports what looks
13
+ wrong. What it produces is a set of claims, and this contract keeps them
14
+ checkable: every finding carries what was expected, what was seen and how to
15
+ get there again; every run has a budget and stops on its own; a step that could
16
+ not run is reported apart from a product that ran and was wrong.
17
+
18
+ Consumers: `/multi-agent:test` (the UI Bug Hunter, `commands/sim-test.md`),
19
+ `/multi-agent:bug-bash` (one run per charter). Script:
20
+ `$HOME/.claude/scripts/explore-findings.mjs`. Run file:
21
+ `$HOME/.claude/schemas/explore-run.schema.json`. Assertions that need judgement go
22
+ to a fresh judge: `features/assertion-judge.md`.
23
+
24
+ ## 1. The run file
25
+
26
+ Write the run as it goes to `<log dir>/run.json`: `goal`, `platform`, `budget`,
27
+ `usage`, `steps[]`, `findings[]`. One step is one charter-sized move ("change
28
+ the quantity on the first cart row and read the total"), not one tap.
29
+
30
+ ```json
31
+ {"index": 3, "title": "Change quantity", "status": "failed", "code": "ASSERTION_FAILED",
32
+ "detail": "total stayed 12.00", "trace": "<path of the toolkit's trace.md, if any>"}
33
+ ```
34
+
35
+ `status` is `passed | failed | blocked | exhausted`. `code` is the toolkit's
36
+ code when the step did not pass; it decides how the step counts (section 4).
37
+
38
+ ## 2. A finding
39
+
40
+ | Field | Rule |
41
+ |---|---|
42
+ | `kind` | `issue` (functional defect, fails the run) or `warning` (cosmetic, does not) |
43
+ | `severity` | integer 1-5: 1 trivial, 2 low, 3 medium, 4 high, 5 critical |
44
+ | `title`, `screen` | short, and where it was seen |
45
+ | `expected` | what should have happened, from the spec when there is one (`spec` quotes it) |
46
+ | `observed` | what happened instead |
47
+ | `repro` | the steps that reach it from launch, at least one |
48
+ | `evidence` | at least one `{kind, path}`: `screenshot`, `ui-tree`, `video`, `trace`, `log`, `text` |
49
+ | `step` | the step that reported it |
50
+
51
+ Report a finding the moment its evidence is on disk, not at the end. A finding
52
+ that misses a field is listed under "failed the contract" and cannot fail the
53
+ run: `explore-findings.mjs report` holds it out of the verdict.
54
+
55
+ Secrets never appear in a finding. A credential is named, not written:
56
+ `secret_ref:<name>`. The script refuses `password: <value>`, `token=<value>`
57
+ and the like in any finding text.
58
+
59
+ ## 3. Budget and stop rules
60
+
61
+ | Budget | Default | Range |
62
+ |---|---|---|
63
+ | steps | 8 | 1-12 |
64
+ | minutes (wall clock) | 10 | 3-15 |
65
+ | tokens | 150000 | 20000-400000 |
66
+
67
+ Values outside the range are clamped and named in `clamped`. Before each new
68
+ step run:
69
+
70
+ ```bash
71
+ node "$HOME/.claude/scripts/explore-findings.mjs" check --run "<run.json>"
72
+ ```
73
+
74
+ `{"stop": true}` ends the run with that `reason`:
75
+
76
+ - `step-limit`, `time`, `tokens`: the budget is spent.
77
+ - `stuck`: the last three steps were blocked and none of them reported a
78
+ finding. Three blocked steps in a row means the environment, not the app, is
79
+ in the way, and more steps would spend the budget learning nothing.
80
+
81
+ A failed step does not stop the run; only the rules above do.
82
+
83
+ ## 4. Product failure or blocked
84
+
85
+ | Step | Counts as |
86
+ |---|---|
87
+ | `failed` + `ASSERTION_FAILED` / `ASSERTION_INCONCLUSIVE` | product failure |
88
+ | `CREDENTIAL_MISSING` | blocked: credentials |
89
+ | `ENVIRONMENT_UNAVAILABLE` | blocked: environment |
90
+ | `SEED_DATA_MISSING` | blocked: seed data |
91
+ | `LOCATOR_NOT_FOUND`, `LOCATOR_AMBIGUOUS`, `REPLAY_DIVERGED` | blocked: automation |
92
+ | `BUDGET_EXCEEDED` | exhausted (neither) |
93
+ | `failed` with no code | blocked: unknown (never silently a product failure) |
94
+
95
+ A blocked step is a fact about the test setup. It goes in its own report
96
+ section, grouped by cause, so "the login screen is broken" and "the run had no
97
+ test account" are never the same line.
98
+
99
+ Verdict, from `explore-findings.mjs report`:
100
+
101
+ - **failed**: at least one valid `issue`.
102
+ - **blocked**: no step ran, or every step was blocked, and no `issue`.
103
+ - **passed**: steps ran, no `issue`, not every step blocked. Warnings allowed.
104
+
105
+ ## 5. Driving the app: locators and expect steps
106
+
107
+ With `@mmerterden/multi-agent-toolkit-mcp` ≥ v3.19.0, prefer these over
108
+ coordinates; on an older toolkit, fall back to the UI tree plus frame-centre
109
+ taps as before and say so in the report:
110
+
111
+ - **Locate, then act.** `ui_locate` resolves an element by accessibility
112
+ identifier, by role plus label, or by visible text; `ios_tap_element` /
113
+ `android_tap_element` tap what it resolves. Identifier first, role plus label
114
+ second, text last (text changes with locale). `LOCATOR_NOT_FOUND` and
115
+ `LOCATOR_AMBIGUOUS` are automation blocks, not product findings: narrow the
116
+ locator and retry once before recording the step as blocked.
117
+ - **Assert with expect steps.** In `agent_run_steps`, `expect_text`,
118
+ `expect_element` and `expect_url` decide a deterministic check and return
119
+ `verdict: passed | failed | blocked` with a code. Use them for anything
120
+ exact: a total, a label, a route. Only a visual or semantic claim ("the
121
+ layout is not clipped", "the error explains what to do") goes to the judge.
122
+ - **Secrets by reference.** The type tools take `secret_ref` instead of the
123
+ value; the value never enters the transcript, the run file or a finding.
124
+ - **Traces.** A failed `agent_run_steps` writes a `trace.md`; put its path in
125
+ the step's `trace` and in the finding's `evidence` as `kind: "trace"`.
126
+ - **Replay.** `save_as` stores a step sequence that reached a finding, and
127
+ `replay` runs it again. `REPLAY_DIVERGED` means the app no longer follows the
128
+ recorded path: an automation block, and a hint that the screen changed.
129
+ - **Web sessions.** Pass `session` to keep parallel browser sessions apart
130
+ (one per bug-bash charter).
131
+
132
+ Tool inputs are the tool's own schema; the names above are the contract, the
133
+ argument shapes are read from the server.
134
+
135
+ ## 6. Report
136
+
137
+ `explore-findings.mjs report --run <run.json>` prints the verdict, why the run
138
+ ended, the budget used, issues before warnings with the most severe first,
139
+ blocked steps by cause, and the findings that failed the contract. The UI Bug
140
+ Hunter keeps its own screen table and summary around this block.
@@ -102,6 +102,7 @@ result, before Step 3.8:
102
102
  node "$HOME/.claude/scripts/review-decision-gate.mjs" "$TRIAGE_FILE" --state "$STATE_FILE" \
103
103
  --integrity "$WORKTREE/.pipeline/test-integrity.json" \
104
104
  --integrity "$WORKTREE/.pipeline/owned-path.json" \
105
+ --integrity "$WORKTREE/.pipeline/valven-gate.json" \
105
106
  --source "$WORKTREE/.pipeline/security-audit-$ITERATION.json" \
106
107
  --json > "$WORKTREE/.pipeline/review-decision-$ITERATION.json"
107
108
  RD_RC=$?
@@ -113,7 +114,7 @@ nothing); `security-audit-$ITERATION.json` is the file Step 2.7 writes when the
113
114
  audit runs (`security-audit.md`). Both are files and not process substitutions
114
115
  of a shell variable with a `{}` brace default: bash 3.2, the stock macOS
115
116
  `/bin/bash`, expands that to `{\}`, and unparseable input is exit 3.
116
- `owned-path.json` is the file Step 1.761 writes. `--integrity` repeats, one
117
+ `owned-path.json` is the file Step 1.761 writes, `valven-gate.json` the one Step 1.762 writes. `--integrity` repeats, one
117
118
  file per deterministic gate. Both gates always write their file, so a declared
118
119
  `--integrity` file that does not exist is unreadable (exit 3): the gate failed
119
120
  before writing. An empty one means no findings from that gate, and a missing or
@@ -44,7 +44,7 @@ The key sits under `metadata:` because the host skill frontmatter schema has no
44
44
 
45
45
  `skill-conformance.mjs` reads frontmatter across the installed skills trees and loads what declared itself. Nothing in the pipeline names `ios-coding-standard`, `apple-archive-compliance` or any other skill, so a future UIKit, Objective-C, Kotlin or backend registry drops in with zero pipeline change - and a registry that is absent produces a declared coverage gap rather than a silent pass.
46
46
 
47
- **The skills root differs per host**, so discovery is a bounded walk over candidate roots (`<install>/skills`, `<install>/multi-agent-refs/skills` for Codex, `<repo>/pipeline/skills`), installed layouts first. `install/copilot.mjs` copies `scripts/` byte-for-byte with no path rewrite, so a hardcoded `~/.claude/skills` is inert on two of the three hosts. That bug has already shipped here once: dynamic skill loading exited 1 on every real install while passing a smoke that ran from the repo. `skillsRootsSearched` is recorded in the manifest so an empty result is attributable to a root rather than to an absence of registries.
47
+ **The skills root differs per host**, so discovery is a bounded walk over candidate roots and the first registry found under a name wins. On Claude Code the order is each marketplace stack plugin's `skills/` (the checkout, then every cached version, from `lib/plugin-skill-roots.mjs`), `~/.claude/multi-agent-refs/registries` (registries the installer lays down for the pipeline's gates, `valven-rules` among them), `~/.claude/skills`, then `<repo>/pipeline/skills`: a registry that ships in a plugin (`ios-coding-standard`, `valven-rules`) is what Claude Code loads, and a same-named copy an older install left behind must not shadow it. Copilot CLI and Codex load no plugin, so their installed skills dir (`<install>/skills`, `<install>/multi-agent-refs/skills`) comes first and the plugin roots follow. `install/copilot.mjs` copies `scripts/` byte-for-byte with no path rewrite, so a hardcoded `~/.claude/skills` is inert on two of the three hosts. `skillsRootsSearched` is recorded in the manifest so an empty result is attributable to a root rather than to an absence of registries.
48
48
 
49
49
  ## Scope is required, and it is what makes this stack-generic
50
50
 
@@ -57,6 +57,21 @@ progress.
57
57
  | `completed` | Phase 5, after it writes `status: complete` and `finishedAt` to the state; the analysis render step |
58
58
  | `halted` | the halt-visibility step in `phases/operations.md` |
59
59
  | `parked` | the park-visibility step in `phases/operations.md`, after a run writes `awaiting_input` or `waitingFor` |
60
+ | `install`, `update` | registration, setup and update, and every accepted run event, when the installed version was not reported yet (see below) |
61
+
62
+ **Install and update records.** Derived, never written by the installer (see
63
+ section 8): the version stamp `install.js` writes (`.pipeline-version`, Claude
64
+ Code's first) says which version is installed and, by its modification time,
65
+ since when; prefs keep the last version reported (`usageLog.reportedVersion`).
66
+ When they differ, `usage-report.mjs --lifecycle` sends one record: `ev`
67
+ (`update` from the reported version; without one, `install` on a machine that
68
+ registered after the stamp and `update` from an unknown version on one that
69
+ registered before it), `v`, `vf`, `hs` (the host trees at that version) and `t`
70
+ (the stamp time), and moves `reportedVersion` forward once the server accepted
71
+ it. The id is derived from the version and the stamp time, so a resend replaces
72
+ the record. It runs after a successful registration, after setup or update finds
73
+ a token already onboarded, and after every accepted run event. The panel lists
74
+ the records under "Kurulumlar" and keeps them out of the run counts.
60
75
 
61
76
  The panel derives a run's status from the last event it received. A run still
62
77
  `in_progress` with no event for **6 hours** is shown as abandoned; a parked run
@@ -79,16 +94,55 @@ corporate title, and never simply the active `gh` account, which on a machine
79
94
  with a work account and a personal account is often the one without access.
80
95
 
81
96
  The answer is cached in prefs (`usageLog.login`, `loginRepo`, `loginCheckedAt`)
82
- for 7 days, or 1 day when no account had access, so a phase boundary costs no
83
- network call. Refresh it after changing accounts or being granted access:
97
+ for 7 days, or 1 day for a fallback or when no account was found, so a phase
98
+ boundary costs no network call. Refresh it after changing accounts or being granted access:
84
99
 
85
100
  ```bash
86
101
  node "$HOME/.claude/scripts/usage-identity.mjs" --refresh
87
102
  ```
88
103
 
89
- **No account with access, no report.** The emitter sends nothing and writes one
90
- line to stderr (`usage-report: not sent - none of the logged-in GitHub accounts
91
- (...) can read ...`); registration is skipped with the same reason.
104
+ **No account with access: the active account, and the server decides.** A work
105
+ machine often has only an enterprise-managed account logged in to gh, and such an
106
+ account can never be a collaborator of a personal repository. Then the active
107
+ account (else the first) is used as a fallback, marked `usageLog.loginFallback`
108
+ and cached for 1 day so an account with access added later takes over. The
109
+ server accepts it when its suffix is one the operator listed
110
+ (`USAGE_ENTERPRISE_SUFFIXES`) and refuses it with a 403 otherwise.
111
+
112
+ **No gh account at all, no report.** The emitter sends nothing and writes one
113
+ line to stderr (`usage-report: not sent - no GitHub account is logged in ...`);
114
+ registration is skipped with the same reason.
115
+
116
+ **`--quiet` still speaks once.** The Phase 0 backstop discards stdout, so a
117
+ registration that leaves reporting off for a reason the user can fix (a refused
118
+ login, no gh account, an unreachable endpoint) prints one stderr line,
119
+ `multi-agent: usage reporting is off - <reason>`, on every run until it is fixed.
120
+
121
+ ### Personal account
122
+
123
+ A registration is pinned: the server binds the token to the login it verified,
124
+ and the machine keeps that login in `usageLog.registeredAs`. Every later record
125
+ is named after it, with no gh call, and a fallback account never replaces it
126
+ (`decideRegistration`). So the personal account has to be logged in to gh only
127
+ once, at the moment of registration; it can be logged out afterwards.
128
+
129
+ `usage-register.mjs --json` reports `needsPersonalAccount: true` while the
130
+ machine is not pinned to an account that can read the pipeline repository: no
131
+ registration yet, or one made with a fallback account. Setup and update then ask
132
+ once (AskUserQuestion per `$HOME/.claude/multi-agent-refs/picker-contract.md`; label
133
+ `Add personal account (Recommended)` / `Skip`):
134
+
135
+ 1. Note the active account: `gh api user --jq .login`.
136
+ 2. The person runs the browser login for the account that holds access, in their
137
+ own terminal (in Claude Code: `! gh auth login --hostname github.com --web`).
138
+ gh keeps both accounts.
139
+ 3. Switch the work account back, so pull requests keep their author:
140
+ `gh auth switch --hostname github.com --user <the noted account>`.
141
+ 4. `node "$HOME/.claude/scripts/usage-register.mjs" --refresh --json`. It finds
142
+ the account with access, replaces the fallback registration and pins the new
143
+ one.
144
+
145
+ Skip leaves the fallback in place; the next update asks again.
92
146
 
93
147
  ## 4. The server side
94
148
 
@@ -0,0 +1,110 @@
1
+ # Valven delivery rules across the phases
2
+
3
+ <!-- toc -->
4
+ - [Phase 1](#phase-1)
5
+ - [Phase 2](#phase-2)
6
+ - [Phase 3](#phase-3)
7
+ - [Phase 4](#phase-4)
8
+ - [Development Behaviors](#development-behaviors)
9
+ - [Old work](#old-work)
10
+ <!-- /toc -->
11
+
12
+ The `valven-rules` skill holds the rules in `references/rules.yml`; `valven-gate.mjs` measures them and `valven-split.mjs` turns an oversized branch into stacked pull requests. This file is the per-phase contract the phase docs point at.
13
+
14
+ The skill ships in `ai-common-toolkit`, the always-on plugin, so the rules reach everyone who installs the marketplace, with or without the pipeline. Claude Code loads it from the plugin (`ai-common-toolkit:valven-rules`); the installer copies it into the skills tree on Copilot CLI and Codex. The gate reads the registry installed with it, so its evaluators and its rules come from one release: `multi-agent-refs/registries/valven-rules/` on Claude Code (laid down by the installer outside the skills dir, so the host does not load the skill twice), the skills tree on Copilot CLI and Codex, the source in a checkout, and the plugin copies only when none of those exists. `skill-conformance.mjs` finds the judgement rules in the plugin on Claude Code and falls back to the same registries dir. The gate prints the path it used as `rulesPath`; when none is found it exits 2, which every step below treats as "could not measure", never as a pass.
15
+
16
+ ## Phase 1
17
+
18
+ Work is built on one branch and shipped as small pull requests. Each task also gets an estimated line count (added + removed, tests included, generated output excluded), and the plan groups tasks into **PR slices** that each stay under the `VLV-SIZE-01` budget (`prefs.global.valven.thresholds`, default 250 lines and 25 files). Order the slices the way `valven-split.mjs` will cut them: renames and moves first, then data (models, services, repositories), then domain (view models, use cases), then UI, each slice carrying its own tests. Show the slice table in the plan (`slice`, `tasks`, `est. lines`) so the user approves the stack together with the plan. A plan whose total estimate exceeds the budget and shows no slices is incomplete. The estimate is advisory; Phase 4 Step 2.95 measures the real diff and splits by it.
19
+
20
+ ## Phase 2
21
+
22
+ Gate 6 of the exit gate, after the secret scan:
23
+
24
+ ```bash
25
+ node $HOME/.claude/scripts/valven-gate.mjs --mode dev --repo "$WORKTREE" --base "origin/$BASE_BRANCH" --worktree \
26
+ --out "$WORKTREE/.pipeline/valven-dev.json" >/dev/null
27
+ ```
28
+
29
+ **Gate 6** (valven-rules, dev mode). Exit 2 blocks the phase: the rules could not be read or git failed. Exit 1: `VLV-TEST-01` (source changed, no test changed) and `VLV-TRACE-01` (no issue key in the branch) are fixed here before Review. `VLV-SIZE-01`, `VLV-SIZE-02` and `VLV-SCOPE-01` are not fixed by editing: keep building on this branch, set `state.valven.splitRequired = true`, and Phase 4 Step 2.95 splits it into stacked PRs. `VLV-REWORK-01` is reported to the user with the ratio and continues. Contract: the `valven-rules` skill.
30
+
31
+ ## Phase 3
32
+
33
+ Step 1.762, after the owned-path gate. The PR body and the pull request do not exist yet, so `VLV-PRDESC-01`, `VLV-CYCLE-01`, `VLV-CYCLE-02`, `VLV-CYCLE-03` and `VLV-PICKUP-01` report `not-measured`; Phase 4 and `/multi-agent:review` measure them.
34
+
35
+ ```bash
36
+ VG_FILE="$WORKTREE/.pipeline/valven-gate.json"
37
+ node $HOME/.claude/scripts/valven-gate.mjs --mode review --repo "$WORKTREE" --base "origin/$BASE_BRANCH" \
38
+ --out "$VG_FILE" >/dev/null
39
+ VG_RC=$?
40
+ [ "$VG_RC" = "2" ] && HALT "valven gate could not run - see stderr"
41
+ $HOME/.claude/scripts/log-metric.sh "$TASK_ID" 3 review.valven \
42
+ findings="$(jq -r '.findings | length' "$VG_FILE")" split="$(jq -r '.splitRequired // false' "$VG_FILE")"
43
+ ```
44
+
45
+ Exit 1 is findings, not an error: they merge at Step 3.0 with `tag: valven`, and `review-decision-gate.mjs` counts them as gate-backed, so a single-source one is not downgraded. Triage accepts a `criteriaSource: "valven-rules"` finding unless the task is exempt, never rejects it as style, and keeps its `line: 0`, which `post-pr-review.sh` posts as a top-level comment. When `splitRequired` is true the fix is never "edit the code"; Phase 4 Step 2.95 splits the branch. With `prefs.global.valven.enabled = false` the file still lands with `enabled: false` and no findings.
46
+
47
+ ## Phase 4
48
+
49
+ ```bash
50
+ VG_COMMIT="$WORKTREE/.pipeline/valven-commit.json"
51
+ node $HOME/.claude/scripts/valven-gate.mjs --mode commit --repo "$WORKTREE" --base "origin/$BASE_BRANCH" --out "$VG_COMMIT" >/dev/null
52
+ VG_RC=$?
53
+ ```
54
+
55
+ - Exit 2 halts: the gate could not measure, and pushing on an unmeasured branch is the silent pass this step exists to prevent.
56
+ - Exit 1 without `splitRequired`: fix in place. `VLV-COMMIT-01` and `VLV-TRACE-02` are squash and reword of this branch's own commits (`git rebase -i` is not available; use `git reset --soft "$(git merge-base HEAD origin/$BASE_BRANCH)"` and re-commit with the step 6 convention). `VLV-TEST-01` and `VLV-TRACE-01` return to Phase 2. Re-run the gate.
57
+ - `splitRequired: true`: plan the stack and show it.
58
+
59
+ ```bash
60
+ node $HOME/.claude/scripts/valven-split.mjs --repo "$WORKTREE" --base "origin/$BASE_BRANCH" --out "$WORKTREE/.pipeline/valven-split.json"
61
+ ```
62
+
63
+ Render one row per slice (branch, lines, layers, files, `oversized`) and ask (`question` / labels in `outputLanguage`, `header` English `"Split PR"`): option 1 "Split into N stacked PRs" (Recommended), option 2 "Ship as one PR" (the review will mark it NEEDS WORK), option 3 "Pause". Autopilot takes option 1. On option 1 re-run with `--apply`; exit 1 (the stack's top differs from the branch) halts with `drift[]` shown. Then build every slice branch with the Phase 2 Gate 1 command, in order; a slice that fails is folded into the next one: delete the `<branch>-p*` branches (`--apply` refuses to overwrite them), re-run with `--budget` raised to the two slices' combined lines, and build again. The original branch is never modified, so every re-run starts from the same tree. An `oversized` slice is one file larger than the budget: report it, it cannot be split by file.
64
+
65
+ After a split, steps 7 and 8 run once per slice in order: push `<branch>-pK`, open its PR with base `$BASE_BRANCH` for p1 and `<branch>-p(K-1)` for the rest, title suffix `(K/N)`, and a `## Stack` section in each body listing every slice PR with the current one marked. The original branch stays local. Record `state.valven.split = {sliceCount, branches[], prs[]}`.
66
+
67
+ Log: `Phase 4 Step 2.95: valven {pass|fixed|split N|shipped-whole}`
68
+
69
+ The commit-mode report also scores `VLV-CYCLE-01`, from the oldest author date among the commits about to be pushed to now. Phase 4 commits right before the push, so this is what Valven will read from the pushed history, and it is a `suggestion` that never blocks the push. The rule carries information on a pull request whose commits were pushed as they were made, which `/multi-agent:review` measures.
70
+
71
+ ### Opening the pull request
72
+
73
+ Every gate has passed before the push, so the pull request opens ready for review with the default reviewers attached, and the draft/ready prompt recommends READY. A draft delays the reviewers' notification, and `VLV-PICKUP-01` flags a draft older than 3 hours on the next review.
74
+
75
+ ### PR body
76
+
77
+ **Valven body check (required, every PR).** After the body is written to `/tmp/pr-body-$TASK_ID.md` and before the PR is created or updated:
78
+
79
+ ```bash
80
+ node $HOME/.claude/scripts/valven-gate.mjs --mode review --repo "$WORKTREE" --base "origin/$BASE_BRANCH" \
81
+ --pr-body-file "/tmp/pr-body-$TASK_ID.md" --out "$WORKTREE/.pipeline/valven-body.json" >/dev/null
82
+ jq -r '.findings[] | select(.ruleId == "VLV-PRDESC-01") | .issue' "$WORKTREE/.pipeline/valven-body.json"
83
+ ```
84
+
85
+ Any `VLV-PRDESC-01` line blocks creation: add the missing why / how-to-verify text and re-run. Other findings here were already handled at Step 2.95.
86
+
87
+ ## Development Behaviors
88
+
89
+ `VLV-CYCLE-01` (coding: first commit to the pull request opening), `VLV-CYCLE-02` (pickup: ready for review to the first review) and `VLV-CYCLE-03` (review: first review to approval or merge) are scored into Valven's bands, reported as `results[].score` and collected in `scores`; `VLV-REWORK-01` adds the churn score, `100 - churn %`. A duration in the 50 or 0 band is a `suggestion`, because the time has passed and the pull request cannot change it. `VLV-PICKUP-01` is `important` when the pull request has no reviewer, which the author fixes now; a draft older than 3 hours is a `suggestion` (`draft_severity`), because an unattended run opens a draft on purpose and a person decides when it is ready.
90
+
91
+ The durations need the pull request's timeline, passed with `--pr-meta-file`. On GitHub, `pr-timeline.mjs --repo <owner/repo> --pr <N> --out <file>` writes it: `gh pr view --json` (head and base branch, body, commits, author, open, close and merge times, draft state, review requests, reviews) plus two things only GitHub's GraphQL timeline carries, the time a draft was marked ready (`readyAt`) and each review author's type, so a bot such as Copilot's reviewer is not counted as a person. When that query fails the script keeps the `gh pr view` output and says on stderr that pickup then counts from the opening and bots are not recognised; the summary repeats it.
92
+
93
+ A Bitbucket caller writes `{createdAt, readyAt, isDraft, reviewerCount, firstReviewAt, approvedAt, mergedAt, closedAt}` in ISO 8601: `createdDate`, `draft` and the close time of a declined pull request from the pull request, `reviewerCount` the length of `reviewers[]`, `firstReviewAt` the earliest `COMMENTED`, `REVIEWED` or `APPROVED` activity by someone other than the author, `approvedAt` the earliest `APPROVED`; a field it cannot read is null. Bitbucket records no ready-for-review time, so its `readyAt` is null.
94
+
95
+ The gate leaves out reviews by the author, by bots and pending reviews, and a review left before the pull request was ready: pickup and review time start at ready. An unknown reviewer count stays unknown (`VLV-PICKUP-01` reports it `not-measured`, never "no reviewer"), a draft is not asked for a reviewer, and on a merged or closed pull request the clocks stop there and `VLV-PICKUP-01` does not apply. A file that does not parse, or has no valid `createdAt`, is reported on stderr and the timeline rules report `not-measured`; every other rule is still measured.
96
+
97
+ ## Old work
98
+
99
+ `VLV-AGE-01` (a commit older than 14 days, by author date so a rebase does not hide it), `VLV-BEHIND-01` (more than 50 commits behind the base, or a merge-base older than 14 days) and `VLV-TRACE-03` (commits carrying another issue's key) mean the branch carries old or foreign work. The answer is never a rebase of the old history: the change moves.
100
+
101
+ ```bash
102
+ node $HOME/.claude/scripts/valven-port.mjs --repo "$WORKTREE" --base "origin/$BASE_BRANCH" # plan
103
+ node $HOME/.claude/scripts/valven-port.mjs --repo "$WORKTREE" --base "origin/$BASE_BRANCH" --apply # move
104
+ ```
105
+
106
+ `--apply` cuts `<branch>-v2` (next free `-vN`) from the current base tip, applies the net diff with `git apply --3way` as one commit, and returns to the original branch, which it never modifies. Exit 1 is a conflict: the new branch is deleted, `conflicts[]` names the paths, and they are resolved by hand on a fresh branch. `sharedWithBase[]` lists the paths the base also changed; those are the ones to re-test.
107
+
108
+ - **Phase 0.** A task that resolves to an existing branch runs `valven-gate.mjs --mode dev` against it first. An old-work finding means work starts on the ported branch, not on the old one.
109
+ - **Phase 4 Step 2.95.** Old-work findings are handled before the size check: port, re-run the gate on the new branch, then split if a size rule still fails. A `VLV-TRACE-03` commit is removed from the port (`--head` at the commit before it, or a cherry-pick of the rest) and gets its own branch.
110
+ - **Review.** The finding's `fix` is concrete: the `valven-port.mjs` plan (new branch name, commits dropped, `sharedWithBase[]`), then "open the new PR from `<to>`, close this one with a link to it". For `VLV-TRACE-03` it lists the foreign commits. Accepted findings make the review NEEDS WORK.
@@ -186,6 +186,8 @@ UI Testing (standalone - not part of pipeline phases):
186
186
  through three gates per platform, and lives at /multi-agent:store-ready. The old
187
187
  /multi-agent:test "store-ready" [path] tag still works and hands off there.
188
188
 
189
+ /multi-agent:bug-bash [scope] Parallel exploratory sessions by charter and persona, each with a budget; a finding is reported only when a repro test fails for its reason
190
+
189
191
  Uses xcrun simctl / adb (native, no external app needed).
190
192
  Booted simulator/emulator required. Auto-detects bundle ID from project.
191
193
 
@@ -185,6 +185,8 @@ UI Testing (standalone - pipeline fazlarından bağımsız):
185
185
  geçirerek doğrular, iOS + Android, ve /multi-agent:store-ready altında. Eski
186
186
  /multi-agent:test "store-ready" [path] etiketi çalışır ve oraya devreder.
187
187
 
188
+ /multi-agent:bug-bash [kapsam] Charter ve persona başına bütçeli paralel keşif oturumları; bulgu, sebebiyle fail olan bir repro testi varsa raporlanır
189
+
188
190
  xcrun simctl / adb kullanır (harici app gerekmez).
189
191
  Booted simulator/emulator şart. Bundle ID proje'den otomatik algılanır.
190
192
 
@@ -419,6 +419,7 @@ Branch name is deterministic - no user confirmation needed.
419
419
  # 0 = ref exists (collision) · 2 = no matching ref (authoritative "free")
420
420
  # anything else = the probe itself failed; REMOTE_ERR holds why
421
421
  ```
422
+ - Old work is not reused (`features/valven.md`).
422
423
  - `REMOTE_RC` is 0 or 2 → treat as authoritative
423
424
  - `REMOTE_RC` is anything else → the remote answer is **unknown**, not "free". Log
424
425
  `Remote collision probe failed: <REMOTE_ERR>`, fall back to the local check only,
@@ -680,7 +681,7 @@ Phase 0 owns `agent-state.json`. Do not call
680
681
 
681
682
  ```bash
682
683
  node "$HOME/.claude/scripts/phase0-exit-gate.mjs" "$TASK_ID" --input "$ORIGINAL_INPUT"
683
- node "$HOME/.claude/scripts/usage-register.mjs" --quiet >/dev/null 2>&1 || true
684
+ node "$HOME/.claude/scripts/usage-register.mjs" --quiet >/dev/null || true
684
685
  node "$HOME/.claude/scripts/usage-report.mjs" --task-id "$TASK_ID" --phase 0 --event phase >/dev/null 2>&1 || true
685
686
  ```
686
687
 
@@ -326,6 +326,8 @@ For each identified change area:
326
326
 
327
327
  Create tasks using TaskCreate with imperative subject, description (what/which files/expected behavior), and `addBlockedBy` for dependencies.
328
328
 
329
+ **PR slices (required):** `features/valven.md`.
330
+
329
331
  ##### Analysis citation requirement (every UI task)
330
332
 
331
333
  Every UI-touching task in the plan MUST cite:
@@ -349,7 +349,6 @@ bash $HOME/.claude/scripts/phase-tracker.sh tokens 2 <input_count> <output_count
349
349
 
350
350
  Contract and rationale: `progress-contract.md` -> Token telemetry forwarding.
351
351
 
352
-
353
352
  #### Generated trees are not yours to edit
354
353
 
355
354
  Many repos generate part of their source: a service client from an OpenAPI spec, mock
@@ -399,8 +398,6 @@ reach Review at all.
399
398
 
400
399
  #### Step 1 - Deterministic Gates (run BEFORE AI review)
401
400
 
402
- If any gate fails → fix first, don't waste AI tokens reviewing broken code.
403
-
404
401
  ```bash
405
402
  # Gate 0: Owned paths (repo profile), before the build
406
403
  node $HOME/.claude/scripts/owned-path-gate.mjs --repo "$WORKTREE" --base "origin/$BASE_BRANCH" --state "$STATE_FILE"
@@ -411,6 +408,7 @@ node $HOME/.claude/scripts/owned-path-gate.mjs --repo "$WORKTREE" --base "origin
411
408
  <test-command> 2>&1 | tee "{worktreePath}/.test.log"
412
409
  # Gate 4: Secrets - run the scanner against the staged diff
413
410
  bash $HOME/.claude/scripts/pre-commit-check.sh
411
+ # Gate 6: features/valven.md
414
412
  ```
415
413
 
416
414
  **Gate 0** exit 1 blocks the phase: move each change where its `fix` points, revert the owned path, re-run. Exit 2 is a call error, never a pass: an unknown `--base` ref means fetch the base branch. Exit 0 with `skipped: true` gives its reason in `note`; log it. Contract: `features/repo-profile.md`, "Owned-path gate".
@@ -442,7 +440,7 @@ The subtraction never widens: match on identifier only, and when identifiers can
442
440
  **Gate results:**
443
441
 
444
442
  - All pass (including the evidence gate) -> proceed to AI review
445
- - Any fail -> fix immediately, re-run gates (no AI review until clean)
443
+ - Any fail -> fix, re-run gates (no AI review until clean)
446
444
  Log: "Phase 3: Gates - owned:{ok/fail} build:{pass/fail} lint:{pass/fail} test:{pass/fail/inherited-red} secrets:{clean/found} evidence:{ok/unverified}"
447
445
 
448
446
  ##### Gate 5 - Fortify SSC findings (runs when `state.contextLinks[]` contains a `fortify` entry, or when `prefs.global.fortify.alwaysCheck === true`)
@@ -145,7 +145,7 @@ node $HOME/.claude/scripts/owned-path-gate.mjs --repo "$WORKTREE" --base "origin
145
145
  --state "$STATE_FILE" --out "$OP_FILE"
146
146
  ```
147
147
 
148
- `--out` drops the old file; exit 2 writes none. A missing `$OP_FILE` is a gate error, never "no findings" (`review-decision-gate.mjs` exit 3 under the gates). Findings merge at Step 3.0. Contract: `features/repo-profile.md`.
148
+ `--out` drops the old file; exit 2 writes none. A missing `$OP_FILE` is a gate error, never "no findings" (`review-decision-gate.mjs` exit 3 under the gates). Findings merge at Step 3.0. Contract: `features/repo-profile.md`. Step 1.762: `features/valven.md`.
149
149
 
150
150
  #### Step 1.77 - Reviewer scope (cost gate)
151
151
 
@@ -377,15 +377,15 @@ ANON=$(jq -n --argjson r "$REVIEWERS_JSON" --arg t "$TASK_ID" --argjson i "$ITER
377
377
 
378
378
  `$REVIEWERS_JSON` is `state.reviewIterations[i].reviewers`. Findings come back with `foundBy: "Source A|B|C"` and every identity key removed. Persist the map to `state.reviewIterations[i].anonymizationMap` for Phase 5 per-reviewer telemetry, and **never put the map in a prompt**.
379
379
 
380
- Then append the Step 1.76 test-integrity, 1.761 owned-path and 2.7 security-audit findings, so they are adjudicated:
380
+ Then append the deterministic findings (Steps 1.76, 1.761, 1.762, 2.7):
381
381
 
382
382
  ```bash
383
383
  SA_FILE="$WORKTREE/.pipeline/security-audit-$ITERATION.json"
384
- MERGED=$(printf '%s' "$ANON" | cat - "$TI_FILE" "$OP_FILE" "$SA_FILE" 2>/dev/null \
384
+ MERGED=$(printf '%s' "$ANON" | cat - "$TI_FILE" "$OP_FILE" "$VG_FILE" "$SA_FILE" 2>/dev/null \
385
385
  | jq -s '.[0] + ([.[1:][] | .findings // []] | add // [])')
386
386
  ```
387
387
 
388
- Deterministic findings keep their `tag` (`test_integrity`, `owned_path`) and no `foundBy`: a reviewer finding may hallucinate, a gate finding is a fact. Security-audit findings carry their `security` envelope and `foundBy: "security-auditor"`; no audit file contributes nothing.
388
+ Deterministic findings keep their `tag` (`test_integrity`, `owned_path`, `valven`) and no `foundBy`: a reviewer finding may hallucinate, a gate finding is a fact. Security-audit findings carry their `security` envelope and `foundBy: "security-auditor"`; no audit file contributes nothing.
389
389
 
390
390
  ##### 3.1 Short-circuit: no findings
391
391
 
@@ -550,7 +550,7 @@ A triage verdict is judgment; a failing repro test is proof. Runs only when `pre
550
550
 
551
551
  Compressed flow: dispatch ONE verifier agent (model `verifyByTest.model`, default `sonnet`) for up to `maxFindings` (default 3) accepted blocking findings. Per finding it writes ONE minimal repro test and runs ONLY that test (Phase 2 single-test invocation, build lock, log tee'd to `$WORKTREE/.pipeline/verify-<i>.test.log`). Outcomes: test FAILS as predicted -> `confirmed`, finding stays blocking and the test is KEPT in `redTests[]` as the Phase 2 rework RED test; test PASSES on every one of `verifyByTest.repeatCount` runs (default 3) -> `not-reproduced` ONLY if `evidence-gate.mjs --claim test --status passed` exits 0 on each log; a run that disagrees with the others -> `inconclusive` with `flaky: passed k/N`, finding moves to `deferred[]`, test deleted; compile error / timeout / not unit-testable -> `inconclusive`, judgment verdict stands. Stamp findings with `verification` (schema v3.2.0), persist `state.reviewIterations[-1].verifyByTest = {attempted, confirmed, downgraded, inconclusive, redTests[]}`, recompute `approved`, re-run `validate-triage.mjs` under the 3.2.1 gate. Whole step bounded by `stepTimeoutSec` (default 600); on breach or crash remaining findings keep judgment verdicts - never blocks. Telemetry per 3.4: `review.verify_by_test attempted= confirmed= downgraded= inconclusive= duration_ms=`.
552
552
 
553
- **Autopilot decision rule (every round, after 3.7):** run the call in `$HOME/.claude/multi-agent-refs/features/review-decision.md` (Wiring: `--integrity "$TI_FILE" --integrity "$OP_FILE" --source "$SA_FILE"`), merge its JSON into `state.reviewIterations[-1].reviewDecision`, repeat the `cp`. A blocker backed by neither two reviewers nor a failing test becomes important; on exit 1 or 3 run `gate-ledger.mjs park --outcome verification-failed --gate review-decision`.
553
+ **Autopilot decision rule (every round, after 3.7):** run the call in `$HOME/.claude/multi-agent-refs/features/review-decision.md` (Wiring: one `--integrity` per gate file, `--source "$SA_FILE"`), merge its JSON into `state.reviewIterations[-1].reviewDecision`, repeat the `cp`. A blocker backed by neither two reviewers nor a failing test becomes important; on exit 1 or 3 run `gate-ledger.mjs park --outcome verification-failed --gate review-decision`.
554
554
 
555
555
  ##### 3.8 Cross-round delta + circuit-breaker trigger 2 (iteration >= 2)
556
556
 
@@ -20,7 +20,7 @@ Progress emission per `$HOME/.claude/multi-agent-refs/progress-contract.md` -
20
20
  > │
21
21
  > ┌──────────────── PR payload ─────────────────────────┘
22
22
  > │ title + description (rawfile) + reviewers + fromRef + toRef + draft
23
- > └──► POST (Bitbucket) or gh pr create --draft (GitHub) ──► PR open
23
+ > └──► POST (Bitbucket) or gh pr create (GitHub) ──► PR open
24
24
  > ```
25
25
 
26
26
  #### Input contract
@@ -110,6 +110,7 @@ Branch **deterministically**, no implicit fallback. Read `agent-state.json` and
110
110
  - For EACH submodule with changes: stage, commit, push separately
111
111
  - Commit message uses same convention but scope reflects submodule: `{type}({submodule-scope}): {description} [{jiraId}]`
112
112
  6. Commit with convention: `{type}({scope}): {description} [{jiraId}]` or `[#{shortId}]`. When `node $HOME/.claude/scripts/repo-profile.mjs resolve commit.format --repo "$PROJECT_ROOT"` returns `use: true`, its `ticketPosition` places the ticket (`prefix` first, `suffix` last; `none` keeps this default unless confirmed or manual), `ticketStyle` its form and `scopeRequired` the scope. A profile hook with `amends-commit` or `writes-files` rewrites the commit: re-read the SHA after committing ([`features/repo-profile.md`]($HOME/.claude/multi-agent-refs/features/repo-profile.md)). **Local-only repos** (`projects[i].provider == "local"`) have no ticket, and that wins over any profile placement: the subject keeps the conventional prefix with no ticket, and the taskId (`LOCAL-...`) goes in the body footer: `Local-Task: {taskId}`.
113
+ 6a. **Valven (required):** `features/valven.md`.
113
114
  7. Push to remote - **skip per-repo when `projects[i].provider == "local"`** (no `git push`, no upstream config). Log `Phase 4: local-only commit {sha} (no push)` for that repo.
114
115
  8. Ask: "Want to open a Pull Request?"
115
116
  - **Skip the prompt entirely when `state.offlineOnly == true`** (all repos local - no PR target exists). Proceed to Phase 5 with `pr.status = "skipped-offline"` for each project.
@@ -238,7 +239,7 @@ PR author cannot appear in `--reviewer` - strip from list.
238
239
 
239
240
  ##### Draft vs ready-for-review
240
241
 
241
- Before creating PR, ask: "DRAFT or READY? [1/2], default 1" - DRAFT blocks merge/delays notifications, READY notifies reviewers + CI. Skip in `autopilot` → default READY.
242
+ Before creating PR, ask: "READY or DRAFT? [1/2], default `defaultPrMode` or 1" - READY notifies reviewers + CI, DRAFT blocks merge and notifies later. Skip in `autopilot` → READY.
242
243
 
243
244
  - **Bitbucket**: `"draft": true` in payload (DC 8.x+). Promote: `draft: false` PUT. Older: prefix title `[DRAFT]`.
244
245
  - **GitHub**: `--draft`. Promote: `gh pr ready {N}`.
@@ -1476,6 +1476,22 @@
1476
1476
  }
1477
1477
  }
1478
1478
  },
1479
+ "valven": {
1480
+ "type": "object",
1481
+ "additionalProperties": true,
1482
+ "description": "valven-rules delivery gate. Phase 2 Gate 6 sets splitRequired when a size or scope rule fails; Phase 4 Step 2.95 records the stacked PRs it opened.",
1483
+ "properties": {
1484
+ "splitRequired": { "type": "boolean" },
1485
+ "split": {
1486
+ "type": "object",
1487
+ "properties": {
1488
+ "sliceCount": { "type": "integer", "minimum": 1 },
1489
+ "branches": { "type": "array", "items": { "type": "string" } },
1490
+ "prs": { "type": "array", "items": { "type": "string" } }
1491
+ }
1492
+ }
1493
+ }
1494
+ },
1479
1495
  "diffRisk": {
1480
1496
  "type": "object",
1481
1497
  "additionalProperties": true,
@@ -0,0 +1,41 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/bug-bash-verification.schema.json",
4
+ "version": "1.0.0",
5
+ "title": "Multi-Agent Pipeline - bug bash verifications",
6
+ "description": "One entry per merged candidate, written by the verifying subagents of /multi-agent:bug-bash. bug-bash.mjs report confirms a candidate only when its repro test failed with ASSERTION_FAILED on the assertion that encodes it (failedAt equals assertion); any other outcome is listed as rejected with the reason.",
7
+ "type": "array",
8
+ "items": {
9
+ "type": "object",
10
+ "required": ["id", "status"],
11
+ "properties": {
12
+ "id": { "type": "string", "pattern": "^BB-[0-9]+$" },
13
+ "status": { "enum": ["confirmed", "rejected"] },
14
+ "reason": {
15
+ "enum": ["artifact", "environment", "design", "fixture", "did-not-reproduce", "duplicate"],
16
+ "description": "Required when rejected. artifact: the explorer misread the screen; environment: a key, service or limit only the local stack lacks; design: the code, tests or copy say it is intended; fixture: the seed data lacks what real records have."
17
+ },
18
+ "note": { "type": "string" },
19
+ "productionRisk": {
20
+ "type": "boolean",
21
+ "description": "An environment rejection whose failure handling production users would also see."
22
+ },
23
+ "rootCause": { "type": "string", "description": "file:line, when the source was read." },
24
+ "repro": {
25
+ "type": "object",
26
+ "required": ["path", "command", "code", "assertion"],
27
+ "properties": {
28
+ "path": { "type": "string", "minLength": 1 },
29
+ "command": { "type": "string", "minLength": 1 },
30
+ "code": {
31
+ "type": ["string", "null"],
32
+ "description": "ASSERTION_FAILED, PASSED, or the failure code the run produced."
33
+ },
34
+ "assertion": { "type": "string", "minLength": 1 },
35
+ "failedAt": { "type": ["string", "null"] },
36
+ "trace": { "type": "string" }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }