opencode-plugin-flow 4.3.7 → 4.3.9

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/dist/index.js CHANGED
@@ -35,6 +35,12 @@ success | partial | blocked
35
35
  ## Scope
36
36
  <owned slice: path set, module, command, risk lens, route, data range, or question set>
37
37
 
38
+ ## Pass metadata
39
+ - Pass id: <stable pass id from the manifest>
40
+ - Manifest row id: <row id from the manifest>
41
+ - Depends on: <upstream row ids or "none">
42
+ - Write scope: <none | manager-serial | exact-path | isolated-worktree | mixed>
43
+
38
44
  ## Coverage
39
45
  - Expected: <files, ranges, questions, commands, or findings assigned>
40
46
  - Checked: <actual coverage, for example "12/12 files" or "command not run">
@@ -89,6 +95,11 @@ success | partial | blocked
89
95
  ## Scope
90
96
  <claim ids, sources or commands checked, and the acceptance question>
91
97
 
98
+ ## Pass metadata
99
+ - Pass id: <stable pass id from the manifest>
100
+ - Manifest row id: <row id from the manifest>
101
+ - Depends on: <upstream row ids or "none">
102
+
92
103
  ## Verdict per claim
93
104
  - <claim id>: verdict=<supported | partly-supported | unsupported | source-not-found>
94
105
  - claim: <claim text>
@@ -119,6 +130,12 @@ success | partial | blocked
119
130
  ## Scope
120
131
  <isolated worktree or exact path-owned slice>
121
132
 
133
+ ## Pass metadata
134
+ - Pass id: <stable pass id from the manifest>
135
+ - Manifest row id: <row id from the manifest>
136
+ - Depends on: <upstream row ids or "none">
137
+ - Write scope: <exact-path | isolated-worktree>
138
+
122
139
  ## Changed or proposed patch
123
140
  - <path>: <what changed and why>
124
141
 
@@ -145,6 +162,48 @@ live-verified | test-verified | type-check-only | not-verified
145
162
 
146
163
  The manager must inspect and validate any candidate patch before recording Flow
147
164
  completion.
165
+
166
+ ## Manager pass accounting record
167
+
168
+ The manager, not the worker, may carry compact records into
169
+ \`flow_feature_complete.orchestrationPasses\`. Use one record per material pass or
170
+ implementation decision; keep handoffs and long artifacts outside \`.flow/**\`.
171
+ The candidate accounting rules — which \`candidateEligibility\`,
172
+ \`candidateDecision\`, and \`decision\` combinations validate, and what counts as
173
+ candidate execution evidence — live in
174
+ [parallel-orchestration.md](parallel-orchestration.md) under "Implementation
175
+ pass decision"; note \`decision: "parallel"\` is not valid on
176
+ \`implementation-decision\` records.
177
+
178
+ \`\`\`json
179
+ {
180
+ "id": "stable-pass-id",
181
+ "kind": "discovery | audit | review | validation | verification | candidate | implementation-decision",
182
+ "decision": "serial | parallel | candidate-exact-path | candidate-worktree | tournament | skipped",
183
+ "decisionReason": "why this pass shape was chosen",
184
+ "candidateEligibility": "eligible | not_eligible | unknown",
185
+ "candidateDecision": "used | skipped | serial_required",
186
+ "decisionFactors": [
187
+ "shared_state",
188
+ "overlapping_files",
189
+ "small_slice",
190
+ "needs_manager_judgment",
191
+ "independent_surface",
192
+ "validation_available"
193
+ ],
194
+ "modes": ["evidence"],
195
+ "workerCount": 1,
196
+ "candidateWorkerCount": 0,
197
+ "verifierWorkerCount": 0,
198
+ "sliceIds": ["manifest-row-id"],
199
+ "dependsOn": [],
200
+ "writeScope": "none | manager-serial | exact-path | isolated-worktree | mixed",
201
+ "handoffRefs": ["/tmp/flow-handoff.md"],
202
+ "verificationStatus": "not-needed | pending | passed | failed | mixed | downgraded",
203
+ "outcome": "accepted | modified | rejected | partial | not-covered | superseded",
204
+ "synthesisRef": "/tmp/flow-synthesis.md"
205
+ }
206
+ \`\`\`
148
207
  `;
149
208
 
150
209
  // skills/flow/references/parallel-orchestration.md
@@ -197,6 +256,99 @@ Pass notes:
197
256
  or exact non-overlapping path ownership. Patches stay proposals until the
198
257
  manager inspects, merges or rejects, and validates.
199
258
 
259
+ ## Implementation pass decision
260
+
261
+ Before implementing a broad, risky, or multi-target feature, record one manager
262
+ decision. This is required even when the answer is "stay serial"; the point is
263
+ to make the skipped parallelism visible instead of relying on memory.
264
+
265
+ First classify candidate eligibility:
266
+
267
+ - \`eligible\`: at least one slice is independent enough for a candidate worker.
268
+ - \`not_eligible\`: worker isolation would not make the implementation safer or
269
+ cheaper because the slice shares state, files, tests, or one mental model.
270
+ - \`unknown\`: orientation did not produce enough evidence to classify; use this
271
+ only for non-decision pass rows or legacy low-signal records, not for
272
+ \`implementation-decision\` records and not as a substitute for judgment.
273
+
274
+ Then record the candidate decision:
275
+
276
+ - \`used\`: candidate workers were used or a candidate pass carried the work.
277
+ - \`skipped\`: candidates were eligible, but the manager chose serial anyway.
278
+ This is the underused-parallelism signal counted by Flow status; use it only
279
+ on \`kind: "implementation-decision"\` records.
280
+ - \`serial_required\`: candidates were not eligible, so serial work was the
281
+ correct implementation shape. Use it only on \`kind: "implementation-decision"\`
282
+ records.
283
+
284
+ Use one of these decisions:
285
+
286
+ - \`serial\`: the manager implements directly because slices overlap, the next
287
+ edit depends on one shared contract, or prompt/merge overhead would exceed the
288
+ value. Pair unsafe or not-useful worker cases with
289
+ \`candidateEligibility: "not_eligible"\` and
290
+ \`candidateDecision: "serial_required"\`.
291
+ - \`candidate-exact-path\`: one or more candidate workers may edit exact
292
+ non-overlapping paths or modules named by the manager.
293
+ - \`candidate-worktree\`: one or more candidate workers may edit in isolated
294
+ worktrees, then the manager inspects and merges or rejects.
295
+ - \`tournament\`: several isolated candidate implementations compete for the same
296
+ outcome; the manager filters by tests, review, and source inspection before
297
+ accepting one.
298
+ - Candidate-shaped decisions (\`candidate-exact-path\`, \`candidate-worktree\`,
299
+ \`tournament\`) require candidate execution evidence on the same record:
300
+ \`kind: "candidate"\`, \`modes\` includes \`candidate-implementation\`, or
301
+ \`candidateWorkerCount > 0\`. The same evidence rule applies to
302
+ \`candidateDecision: "used"\`. Non-decision candidate rows (for example
303
+ \`kind: "candidate"\`) may omit \`decision\`; \`implementation-decision\` rows must
304
+ always set one, and when \`candidateDecision\` is \`"used"\` that decision must
305
+ be candidate-shaped — never \`serial\`, \`parallel\`, or \`skipped\`.
306
+ - \`parallel\` describes multi-worker read or audit passes (discovery, audit,
307
+ review); it is not a valid \`implementation-decision\` value. Implementation
308
+ decisions use \`serial\`, \`skipped\`, or a candidate-shaped decision.
309
+ - \`skipped\`: candidate workers were eligible but the manager chose serial
310
+ anyway; pair this with \`candidateEligibility: "eligible"\` and
311
+ \`candidateDecision: "skipped"\`. Do not use \`skipped\` for shared fixtures,
312
+ shared API contracts, unclear ownership, or other unsafe worker cases; use
313
+ \`serial\` plus \`serial_required\` for those.
314
+
315
+ Use structured \`decisionFactors\` alongside prose \`decisionReason\`:
316
+ \`shared_state\`, \`overlapping_files\`, \`small_slice\`,
317
+ \`needs_manager_judgment\`, \`independent_surface\`, and
318
+ \`validation_available\`. Serial-required records usually cite
319
+ \`shared_state\`, \`overlapping_files\`, or \`needs_manager_judgment\`; eligible
320
+ records usually cite \`independent_surface\` and \`validation_available\`, with
321
+ \`small_slice\` explaining an eligible-but-skipped choice.
322
+
323
+ ### Worker decision rubric
324
+
325
+ Default to considering candidate workers when:
326
+
327
+ - the plan has three or more features.
328
+ - features touch separate surfaces such as frontend, core, docs, release
329
+ scripts, tests, or bindings.
330
+ - validation can run per slice.
331
+ - the work is mostly additive or localized.
332
+ - the final manager can review, apply, adapt, or reject the result safely.
333
+
334
+ Prefer serial when:
335
+
336
+ - one tight invariant crosses shared files.
337
+ - migrations, persistence, storage, or lifecycle semantics require one mental
338
+ model.
339
+ - tests require iterative local debugging in one checkout.
340
+ - multiple slices would edit the same files or fixtures.
341
+ - the slice is so small that prompt, handoff, merge, and verification overhead
342
+ costs more than direct work.
343
+
344
+ Record the decision in the pass manifest with a stable pass id,
345
+ \`candidateEligibility\`, \`candidateDecision\`, \`decisionFactors\`,
346
+ \`decisionReason\`, \`writeScope\`, expected verification, and where any handoff or
347
+ synthesis artifact will live. If the feature completes, include the compact
348
+ record in the \`orchestrationPasses\` array of the \`flow_feature_complete\`
349
+ payload. The runtime stores only compact accounting; full worker handoffs stay
350
+ in manager-owned scratch files or the conversation.
351
+
200
352
  ## When to stay serial
201
353
 
202
354
  - One file, command, or design question determines the next step.
@@ -272,6 +424,12 @@ schemas, docs, tests, commands, or artifacts to identify real slices. Keep the
272
424
  immediate blocker local: do not delegate the question that determines whether
273
425
  fan-out is even valid.
274
426
 
427
+ Treat orientation as uncertainty reduction. Resolve environment uncertainty by
428
+ inspecting the repo, running cheap commands, or assigning evidence workers; ask
429
+ the user only when the remaining specification uncertainty would make a wrong
430
+ slice expensive to undo. Do not split a vague goal into workers until the
431
+ candidate slices have concrete targets, dependencies, and verification signals.
432
+
275
433
  ## Stage 2 — Slice
276
434
 
277
435
  Split along whichever axis keeps slices independent: modules or path sets,
@@ -279,14 +437,29 @@ route or endpoint groups, risk lenses, command surfaces, data ranges, or claim
279
437
  sets. Each slice needs a one-line scope, expected coverage, and a defined
280
438
  output the manager can check.
281
439
 
440
+ For implementation slices, also name dependencies and write ownership before
441
+ spawning. A real dependency edge means the later slice waits for a verified
442
+ handoff or manager synthesis from the earlier slice; a shared file, fixture,
443
+ schema, or public contract usually means the work should stay serial unless an
444
+ isolated worktree is used. The manifest owns those edges through \`dependsOn\`
445
+ and \`writeScope\`.
446
+
282
447
  ## Stage 3 — Manifest (the pre-fan-out coverage gate)
283
448
 
284
449
  Before spawning, write a pass manifest: one row per slice, plus a totals check.
450
+ Give the pass a stable id so later handoffs, verifier claims, and completion
451
+ payloads can refer to the same work without replaying chat.
285
452
 
286
- | # | Slice scope | Expected coverage | Mode | Verification tier |
287
- | --- | --- | --- | --- | --- |
288
- | 1 | \`src/core/**\` plus its tests | 14 files | \`evidence\` | accept locally |
289
- | 2 | release contract: CI workflows, \`package.json\`, changelog | 6 files | \`evidence\` | verify once |
453
+ | Row id | Slice scope | Expected coverage | Mode | Depends on | Write scope | Verification tier | Handoff ref | Verification status | Synthesis ref |
454
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
455
+ | \`runtime-read\` | \`src/core/**\` plus its tests | 14 files | \`evidence\` | none | none | accept locally | pending | pending | pending |
456
+ | \`release-read\` | release contract: CI workflows, \`package.json\`, changelog | 6 files | \`evidence\` | none | none | verify once | pending | pending | pending |
457
+
458
+ \`writeScope\` values in runtime accounting are \`none\`, \`manager-serial\`,
459
+ \`exact-path\`, \`isolated-worktree\`, or \`mixed\`. Use \`manager-serial\` for a
460
+ recorded serial implementation decision, \`exact-path\` for disjoint candidate
461
+ edits in one checkout, and \`isolated-worktree\` for candidate work that must be
462
+ merged back by the manager.
290
463
 
291
464
  - Count the total work items when countable: files, modules, routes, commands,
292
465
  rows, findings, screenshots, or claims. Confirm slice counts add back to the
@@ -295,11 +468,30 @@ Before spawning, write a pass manifest: one row per slice, plus a totals check.
295
468
  "all changed files plus callers" or "all public commands plus release docs."
296
469
  - Assign each slice's verification tier now (see Stage 6). Deciding where a
297
470
  wrong claim is expensive belongs before handoffs arrive, not after.
471
+ - Record dependency edges now. A row may be spawned only after every \`depends on\`
472
+ row it names has returned a verified handoff or a manager synthesis that
473
+ explicitly settles the dependency.
298
474
  - Fix the slice map centrally before spawning if the gate does not reconcile.
299
475
 
300
476
  The manifest is also the accounting contract for the pass: N rows spawned means
301
477
  N handoffs collected and checked in Stage 5 before anything is synthesized.
302
478
 
479
+ For implementation decisions, add a manifest row even when no worker is spawned:
480
+ \`kind=implementation-decision\`, \`decision=serial\` or \`decision=skipped\`,
481
+ \`candidateEligibility\`, \`candidateDecision\`, \`decisionFactors\`,
482
+ \`workerCount=0\`, \`writeScope=manager-serial\`, and a concrete \`decisionReason\`.
483
+ Use \`decision=serial\` with \`candidateDecision=serial_required\` for ineligible
484
+ worker cases; reserve \`decision=skipped\` for eligible candidate work that the
485
+ manager chose not to delegate. When an implementation-decision row uses
486
+ \`candidateDecision=used\`, it must also record actual candidate execution
487
+ evidence: either \`modes=candidate-implementation\`, or \`candidateWorkerCount > 0\`
488
+ with \`workerCount\` raised to cover it — a \`workerCount=0\` row cannot carry a
489
+ positive \`candidateWorkerCount\`. Neither subtype count may exceed the total:
490
+ \`candidateWorkerCount <= workerCount\` and \`verifierWorkerCount <= workerCount\`
491
+ (a single worker may fill both roles).
492
+ This is how Flow distinguishes deliberate serial work from forgotten candidate
493
+ or verifier passes.
494
+
303
495
  Write the manifest where it survives the pass: the conversation is enough for a
304
496
  single bounded pass, but when a follow-up pass or a session resume is
305
497
  plausible, persist it with the synthesis (Stage 7) so the accounting can be
@@ -312,8 +504,10 @@ Every worker prompt includes:
312
504
  \`\`\`text
313
505
  Overall goal, context only: <goal>
314
506
  Mode: evidence | review | validation | audit | verifier | candidate-implementation
507
+ Pass id and manifest row id: <stable ids from the manifest>
315
508
  Your exact slice: <paths, modules, command, claim ids, risk lens, or worktree>
316
509
  Expected coverage: <count, paths, range, or complete question set>
510
+ Dependencies and write scope: <verified dependencies, if any; none | manager-serial | exact-path | isolated-worktree>
317
511
  Do: <bounded actions>
318
512
  Do not: call state-changing Flow tools, edit .flow/**, own sibling slices, or make the final Flow verdict.
319
513
  Return only the Flow handoff in this exact shape:
@@ -339,6 +533,21 @@ worker that never returns, errors out, returns empty or unstructured output, or
339
533
  reports \`partial\` or \`blocked\` is a hole in the pass, and synthesizing around it
340
534
  silently drops a slice.
341
535
 
536
+ For each row, fill in:
537
+
538
+ - \`handoffRefs\`: worker ids, handoff file paths, command output artifacts, or
539
+ review packet location that the manager can re-open.
540
+ - \`verificationStatus\`: \`not-needed\`, \`pending\`, \`passed\`, \`failed\`, \`mixed\`,
541
+ or \`downgraded\`.
542
+ - \`outcome\`: \`accepted\`, \`modified\`, \`rejected\`, \`partial\`, \`not-covered\`, or
543
+ \`superseded\`.
544
+ - \`synthesisRef\`: the manager-owned synthesis file or plan field that carries
545
+ the accepted result forward.
546
+
547
+ Rows with no worker, such as serial or skipped implementation decisions, still
548
+ need a row id, decision, reason, and outcome. They are not handoffs, but they
549
+ are accounting.
550
+
342
551
  Worker failure ladder:
343
552
 
344
553
  1. Re-spawn once with a narrower slice and a note about what the first attempt
@@ -360,6 +569,12 @@ handoff only after a cheap manager-side pass:
360
569
  - The evidence supports the claim, not just the topic.
361
570
  - Findings stay inside the worker's slice.
362
571
  - Headline counts can be recounted or traced.
572
+ - Dependency claims cite the verified upstream handoff, synthesis, or source
573
+ artifact they depend on.
574
+ - Candidate implementation claims identify whether they came from exact path
575
+ ownership or an isolated worktree, and whether the manager inspected the
576
+ resulting patch. Record the manager result as \`accepted\`, \`modified\`, or
577
+ \`rejected\` where that is the most precise candidate outcome.
363
578
  - Contradictions between workers are either resolved or explicitly marked as
364
579
  contested.
365
580
 
@@ -400,6 +615,11 @@ Apply the manager synthesis barrier before presenting or recording anything:
400
615
  - When workers disagree, inspect the cited artifact or rerun the cited command
401
616
  instead of arbitrating from summaries. Do not average conflicting claims.
402
617
  - Run the strongest practical local check for the deliverable.
618
+ - For broad implementation sessions, use one verifier worker after manager
619
+ synthesis when the risk is medium or high. Ask it whether every planned
620
+ feature landed, worker validation claims are supported, final code matches
621
+ the audit finding, generated bindings/docs/version metadata stayed
622
+ consistent, and changed files have plausible test coverage.
403
623
  - Re-read critical files or docs that will be cited in the final decision.
404
624
  - Move only distilled, evidence-backed claims forward; raw handoffs remain
405
625
  candidate evidence, not a plan, review, completion payload, or final answer.
@@ -424,6 +644,56 @@ Where accepted evidence goes:
424
644
  - Candidate patches are not Flow evidence until the manager inspects, merges or
425
645
  rejects them, and validates the main Flow-managed workspace.
426
646
 
647
+ When completing a feature, include compact pass accounting in
648
+ \`flow_feature_complete.orchestrationPasses\` for any pass or implementation
649
+ decision that materially affected the feature:
650
+
651
+ \`\`\`json
652
+ {
653
+ "id": "feature-id-implementation-decision",
654
+ "kind": "implementation-decision",
655
+ "decision": "serial",
656
+ "decisionReason": "Shared schema and tests made exact path ownership unsafe.",
657
+ "candidateEligibility": "not_eligible",
658
+ "candidateDecision": "serial_required",
659
+ "decisionFactors": ["shared_state", "overlapping_files"],
660
+ "modes": [],
661
+ "workerCount": 0,
662
+ "candidateWorkerCount": 0,
663
+ "verifierWorkerCount": 0,
664
+ "sliceIds": ["manager-implementation"],
665
+ "dependsOn": [],
666
+ "writeScope": "manager-serial",
667
+ "handoffRefs": [],
668
+ "verificationStatus": "not-needed",
669
+ "outcome": "accepted",
670
+ "synthesisRef": "/tmp/flow-pass-synthesis.md"
671
+ }
672
+ \`\`\`
673
+
674
+ For candidate and verifier passes, use \`kind: "candidate"\` or
675
+ \`kind: "verification"\`, list the worker modes used, worker counts, slice ids,
676
+ handoff refs, dependency ids, verification status, and whether the manager
677
+ accepted, rejected, downgraded, or superseded the pass. The runtime aggregates
678
+ these compact records into \`session.budget.orchestration\` and stores them on
679
+ the feature history entry. Do not store full handoffs, long logs, or scratch
680
+ tables in \`.flow/session.json\`.
681
+
682
+ Status accounting distinguishes three cases: \`candidateDecision: "used"\` means
683
+ candidate execution evidence was recorded, \`candidateDecision:
684
+ "serial_required"\` means workers were not safe or useful, and
685
+ \`candidateEligibility: "eligible"\` plus \`candidateDecision: "skipped"\`
686
+ increments \`skippedCandidateDecisionCount\`. These candidate decision counters
687
+ come from \`kind: "implementation-decision"\` records; \`skipped\` and
688
+ \`serial_required\` are not valid on discovery, audit, review, validation,
689
+ verification, or candidate pass rows. \`candidatePassCount\`
690
+ counts actual candidate pass or worker evidence (\`kind: "candidate"\`,
691
+ \`modes\` includes \`candidate-implementation\`, or \`candidateWorkerCount > 0\`) —
692
+ a candidate-shaped decision label without that evidence is rejected, so decision
693
+ labels alone never count. \`verifierPassCount\` similarly counts
694
+ actual verifier pass or worker evidence (\`kind: "verification"\`, \`modes\`
695
+ includes \`verifier\`, or \`verifierWorkerCount > 0\`).
696
+
427
697
  Persist the manifest and the synthesis when another pass may follow or the
428
698
  session is long enough to be compacted or resumed: write the distilled result —
429
699
  the accounted manifest, accepted claims with evidence and confidence, dropped
@@ -442,6 +712,11 @@ Stop after a pass when:
442
712
  - accepted claims are evidenced, scoped, and confidence-labeled.
443
713
  - material single-source, contested, high-stakes, or payload-bound claims have
444
714
  been verified or downgraded.
715
+ - every dependency edge named in the manifest has either a verified upstream
716
+ result or an explicit not-covered outcome.
717
+ - implementation pass decisions are recorded, including skipped candidate
718
+ workers, candidate eligibility, candidate decision, structured factors, and
719
+ the reason eligible workers were skipped.
445
720
  - remaining gaps are explicit and do not block the Flow artifact being produced.
446
721
 
447
722
  Start a bounded follow-up pass only when:
@@ -449,6 +724,9 @@ Start a bounded follow-up pass only when:
449
724
  - the original slice map missed material scope.
450
725
  - workers disagree on a claim that affects the Flow decision.
451
726
  - a high-stakes or payload-bound claim needs verification.
727
+ - a dependency has just become verified and now unlocks a dependent slice.
728
+ - a candidate patch was rejected and an isolated alternative is still cheaper
729
+ than serial repair.
452
730
  - a first pass exposes a narrower implementation or validation slice worth
453
731
  isolating.
454
732
 
@@ -457,6 +735,20 @@ reason, such as a high-stakes verifier check or a newly discovered bounded
457
735
  slice. Do not recurse by default: if a worker says it needs another worker, the
458
736
  manager decides whether that is a follow-up pass and writes the next bounded
459
737
  prompt, starting again from the manifest.
738
+
739
+ ## Worker count defaults
740
+
741
+ Use caps, not a fixed feature limit:
742
+
743
+ - small implementation: zero or one worker.
744
+ - medium independent implementation: two workers.
745
+ - broad audit: three to five workers.
746
+ - broad implementation: two to four candidate workers, only for
747
+ non-overlapping slices.
748
+ - final verifier: one worker when risk is medium or high.
749
+
750
+ The target is not "more workers." The target is explicit accounting: Flow must
751
+ justify not using workers when the work was eligible.
460
752
  `;
461
753
 
462
754
  // skills/flow/references/parallel-pass-example.md
@@ -475,21 +767,24 @@ directory. The manager keeps the middleware local because it is one file and
475
767
  anchors every other judgment.
476
768
 
477
769
  Pass manifest: twelve countable route modules remain after the local check, and
478
- 4 + 3 + 5 adds back to 12 with no overlaps or gaps.
770
+ 4 + 3 + 5 adds back to 12 with no overlaps or gaps. The pass id is
771
+ \`api-error-handling-read\`.
479
772
 
480
- | # | Slice scope | Expected coverage | Mode | Verification tier |
481
- | --- | --- | --- | --- | --- |
482
- | A | auth and account routes | 4/12 modules | \`evidence\` | accept locally |
483
- | B | billing and subscription routes | 3/12 modules | \`review\` | verify once |
484
- | C | remaining content and admin routes | 5/12 modules | \`audit\` | verify once |
773
+ | Row id | Slice scope | Expected coverage | Mode | Depends on | Write scope | Verification tier | Handoff ref | Verification status | Synthesis ref |
774
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
775
+ | \`routes-auth\` | auth and account routes | 4/12 modules | \`evidence\` | none | none | accept locally | pending | pending | pending |
776
+ | \`routes-billing\` | billing and subscription routes | 3/12 modules | \`review\` | none | none | verify once | pending | pending | pending |
777
+ | \`routes-admin\` | remaining content and admin routes | 5/12 modules | \`audit\` | none | none | verify once | pending | pending | pending |
485
778
 
486
779
  Worker prompts:
487
780
 
488
781
  \`\`\`text
489
782
  Overall goal, context only: confirm API error handling is consistent.
490
783
  Mode: evidence
784
+ Pass id and manifest row id: api-error-handling-read / routes-auth
491
785
  Your exact slice: the four auth and account route modules under src/routes/.
492
786
  Expected coverage: 4/4 modules.
787
+ Dependencies and write scope: none; none.
493
788
  Do: report each route's error paths, status codes, and middleware usage with file:line evidence.
494
789
  Do not: call state-changing Flow tools, edit .flow/**, own sibling slices, or make the final Flow verdict.
495
790
  Return only the Flow handoff in this exact shape:
@@ -499,8 +794,10 @@ Return only the Flow handoff in this exact shape:
499
794
  \`\`\`text
500
795
  Overall goal, context only: confirm API error handling is consistent.
501
796
  Mode: review
797
+ Pass id and manifest row id: api-error-handling-read / routes-billing
502
798
  Your exact slice: the three billing and subscription route modules under src/routes/.
503
799
  Expected coverage: 3/3 modules.
800
+ Dependencies and write scope: none; none.
504
801
  Do: separate blocking findings from advisory notes and cite file:line evidence.
505
802
  Do not: call state-changing Flow tools, edit .flow/**, own sibling slices, or make the final Flow verdict.
506
803
  Return only the Flow handoff in this exact shape:
@@ -510,8 +807,10 @@ Return only the Flow handoff in this exact shape:
510
807
  \`\`\`text
511
808
  Overall goal, context only: confirm API error handling is consistent.
512
809
  Mode: audit
810
+ Pass id and manifest row id: api-error-handling-read / routes-admin
513
811
  Your exact slice: the five content and admin route modules under src/routes/.
514
812
  Expected coverage: 5/5 modules.
813
+ Dependencies and write scope: none; none.
515
814
  Do: check each claimed error path against the shared middleware contract and report divergences with evidence.
516
815
  Do not: call state-changing Flow tools, edit .flow/**, own sibling slices, or make the final Flow verdict.
517
816
  Return only the Flow handoff in this exact shape:
@@ -522,6 +821,9 @@ Accounting: three manifest rows spawned means three handoffs collected before
522
821
  synthesis. If slice B returned \`partial\`, the manager would re-spawn it once
523
822
  with a narrower scope, then cover it directly, and as a last resort carry it
524
823
  into the synthesis explicitly as not-covered.
824
+ The manager fills \`handoffRefs\`, \`verificationStatus\`, \`outcome\`, and
825
+ \`synthesisRef\` for each row before any claim becomes a plan decision or review
826
+ payload.
525
827
 
526
828
  Handoff checks: the manager accepts only reports with terminal status, matching
527
829
  coverage counts, concrete file:line evidence, confidence tags, and claims inside
@@ -539,6 +841,12 @@ Final synthesis: the manager re-reads the relevant route and middleware lines,
539
841
  keeps only verified or clearly labeled claims, and records one artifact such as
540
842
  a plan decision, review payload, or docs patch. Raw handoffs and unverified
541
843
  suggestions do not move into the next pass or user-facing answer.
844
+
845
+ If the pass shaped feature execution, the manager records compact accounting in
846
+ \`flow_feature_complete.orchestrationPasses\`, such as pass id
847
+ \`api-error-handling-read\`, kind \`review\`, worker count \`3\`, slice ids
848
+ \`routes-auth\`, \`routes-billing\`, and \`routes-admin\`, verification status
849
+ \`mixed\` or \`passed\`, and a synthesis ref pointing to the manager-owned summary.
542
850
  `;
543
851
 
544
852
  // skills/flow/references/recovery-playbook.md
@@ -822,15 +1130,19 @@ config, or release surfaces in the pass manifest.
822
1130
 
823
1131
  Write the pass manifest and the worker prompts exactly as Stages 3 and 4 of
824
1132
  \`../../flow/references/parallel-orchestration.md\` define them: one manifest row
825
- per slice with expected coverage and a verification tier, and a self-contained
826
- prompt per worker naming the mode (usually \`evidence\`), the exact slice, and
827
- the expected coverage. Discovery-specific rules:
1133
+ per slice with expected coverage, dependencies, write scope, and a verification
1134
+ tier, and a self-contained prompt per worker naming the mode (usually
1135
+ \`evidence\`), the exact slice, and the expected coverage. Discovery-specific
1136
+ rules:
828
1137
 
829
1138
  - Workers are read-only. For validation-oriented discovery, workers may report
830
1139
  commands that should be run, and include raw output only for commands they
831
1140
  actually ran.
832
1141
  - Workers cannot read reference files themselves; paste the matching handoff
833
1142
  template from \`../../flow/references/handoff-format.md\` into the prompt.
1143
+ - If discovery finds later features with disjoint path ownership, preserve that
1144
+ fact in feature \`targets\` and \`dependsOn\` so execution can make an explicit
1145
+ serial or candidate-pass decision instead of rediscovering ownership.
834
1146
 
835
1147
  ## Synthesis
836
1148
 
@@ -1249,6 +1561,11 @@ to execute.
1249
1561
  validation entry should name the expected test level, such as targeted unit,
1250
1562
  integration, browser/e2e, package/build, docs/static, cleanup preservation, or
1251
1563
  broad project gate.
1564
+ - When a feature may benefit from parallel implementation, make \`targets\`
1565
+ precise enough for later ownership decisions: name exact modules, docs,
1566
+ commands, or route groups, and use \`dependsOn\` to preserve prerequisite order.
1567
+ Broad shared-contract work should stay in one feature or an earlier foundation
1568
+ feature so later candidate passes can own disjoint paths safely.
1252
1569
  - Assign \`reviewDepth\` from risk. Use \`detailed\` for persistence, migration,
1253
1570
  concurrency, security, final-delivery-adjacent, or cross-module slices; use
1254
1571
  \`standard\` for normal code changes; reserve \`quick\` for low-risk non-behavioral
@@ -1614,7 +1931,7 @@ Never trim failing output, relabel a failed command as passed, or use "not run"
1614
1931
  `;
1615
1932
 
1616
1933
  // skills/flow-run/SKILL.md
1617
- var SKILL_default6 = '---\nname: flow-run\ndescription: "Use when an approved Flow plan has a feature to implement, validate, or complete in the v4 runtime, and the work is scoped to one active feature. For planning a goal first use flow-plan; for the full goal-to-completion loop or resuming a session use flow."\n---\n\n# Flow Run\n\nUse this skill for implementation after a Flow plan is approved. Work one feature at a time.\n\nIf `flow_run_start` is unavailable, stop and tell the user to check that `opencode-plugin-flow` is loaded in OpenCode.\n\n## Start\n\n- Call `flow_status`.\n- If `flow_status` returns a `session.resumePacket` or\n `session.budget.phaseBoundary`, stop the current autonomous loop and report\n the resume instructions. Only call `flow_run_start` with\n `phaseBoundaryAck: true` at the start of a fresh user invocation that is\n explicitly resuming the Flow session; do not acknowledge a boundary inside\n the same uninterrupted loop that created it.\n- Call `flow_run_start` with no `featureId` unless the user or plan requires a specific runnable feature.\n- Treat the returned feature as the sole scope until it is completed, blocked, or reset.\n- Helper rule: when a named helper skill is unavailable, record the gap and\n keep the corresponding claims conservative instead of simulating its checks.\n- Load `flow-deslop` for cleanup/refactor features.\n- Load `flow-ui-quality` for frontend, UX, responsive, accessibility, or visual work.\n\n## Implement\n\n- Read the feature `targets`, `summary`, `validation`, dependencies, and plan `requirements`/`decisions`.\n- Treat the feature\'s `reviewDepth` as the minimum feature-review depth that\n must be recorded in `flow_feature_complete`.\n- Keep edits scoped to the active feature. If new scope appears, stop and replan or defer it to another feature.\n- Preserve unrelated user changes in the worktree.\n- When a wrong assumption invalidates the feature, use `flow_feature_reset`; do not pile patches onto a bad path.\n- Do not stage, commit, push, amend, rebase, publish, or mutate releases as part\n of feature execution. If the user explicitly asks for commit preparation, load\n `flow-commit` only after `flow_feature_complete` has been recorded, unless the\n user explicitly asks for a WIP commit path. Keep Git boundaries separate from\n Flow state recording.\n\n## Validate\n\n- For complex validation, regression-sensitive changes, browser QA, route QA,\n failure-prone checks, unclear coverage, exploratory QA, or\n `validationRun` summarization, load `flow-test` (helper rule applies).\n- Read `references/validation-rubric.md` before completing.\n- Run the strongest practical checks for the changed behavior.\n- Record concrete command names, status, and observed results. "Tests pass" is not evidence.\n- Non-final features complete with `validationScope: "targeted"`.\n- The final feature must run a broad project-level gate and use `validationScope: "broad"`.\n\nFor broad validation research, risky changes, or unclear coverage, use\n`../flow/references/parallel-orchestration.md` to fan out named Flow workers.\nUse the mode-to-agent mapping in that reference instead of generic subagents.\nWrite its pass manifest before fan-out, paste the matching handoff template\nfrom `../flow/references/handoff-format.md` into every worker prompt, and\napply its verification tiers to the handoffs that come back.\nThey may report command output they actually ran or propose focused checks; the\nmanager decides what is strong enough to record.\n\nFor independent implementation attempts, use candidate workers only with\nexplicit user authorization plus isolated worktrees or exact non-overlapping\npath ownership. Treat their output as candidate patches. The manager inspects,\nmerges, validates, and records Flow state serially.\n\n## Review and complete\n\nBefore `flow_feature_complete`, obtain a `featureReview` payload. Load\n`flow-review`; for read-only subagent reviews, the manager receives the review\npacket and records both `featureReviewDepth` and `featureReview`.\n\nSend reviewers a compact review packet. Do not rely on the accumulated parent\nconversation. Include only:\n\n- active feature id, title, summary, `reviewDepth`, targets, validation, and dependencies\n- relevant plan requirements, decisions, and final review policy\n- changed files and a short diff summary\n- validation evidence with exact commands, status, and observed result\n- targeted paths or risk lenses the reviewer must inspect\n\nIf the review returns `status: "failed"`, do not fix inside the review pass.\nRecord the failed attempt by calling `flow_feature_complete` with the otherwise\nprepared completion payload, the failed `featureReview`, and the attempted\n`featureReviewDepth`; the runtime will reject completion and update the retry\nbudget. Default to stopping and reporting the blocker. When the user already\nauthorized autonomous implementation, make at most one repair and run one retry\nreview. If the retry fails or the runtime reports review retry budget\nexhausted, stop with the blocker.\n\nIf `flow_status` reports `setup.skills` or `flow-review` cannot be loaded, do\nnot record a Flow-gated `featureReview` or `finalReview`. You may perform an\nadvisory review using available context or the bundled review fallback provided\nby plugin config, then complete with `status: "needs_input"` if review evidence\nis required to proceed.\n\nFor the final feature, also obtain a `finalReview` payload whose `reviewDepth` equals the approved plan\'s `finalReviewPolicy`.\n\nComplete with:\n\n```json\n{\n "status": "ok",\n "featureId": "active-feature-id",\n "summary": "what changed",\n "artifactsChanged": [{ "path": "src/file.ts" }],\n "validationRun": [\n { "command": "bun test tests/foo.test.ts", "status": "passed", "summary": "3 pass, exercised foo behavior" }\n ],\n "validationScope": "targeted",\n "featureReviewDepth": "standard",\n "featureReview": { "status": "passed", "summary": "review summary", "blockingFindings": [] }\n}\n```\n\nIf `flow_feature_complete` returns a `session.resumePacket` or\n`session.budget.phaseBoundary`, stop after reporting the compact handoff. If\ngenuinely blocked, call `flow_feature_complete` with `status: "needs_input"` and\nan `outcome` that explains the blocker and next step. Never fabricate validation\nor review evidence to force progress.\n';
1934
+ var SKILL_default6 = '---\nname: flow-run\ndescription: "Use when an approved Flow plan has a feature to implement, validate, or complete in the v4 runtime, and the work is scoped to one active feature. For planning a goal first use flow-plan; for the full goal-to-completion loop or resuming a session use flow."\n---\n\n# Flow Run\n\nUse this skill for implementation after a Flow plan is approved. Work one feature at a time.\n\nIf `flow_run_start` is unavailable, stop and tell the user to check that `opencode-plugin-flow` is loaded in OpenCode.\n\n## Start\n\n- Call `flow_status`.\n- If `flow_status` returns a `session.resumePacket` or\n `session.budget.phaseBoundary`, stop the current autonomous loop and report\n the resume instructions. Only call `flow_run_start` with\n `phaseBoundaryAck: true` at the start of a fresh user invocation that is\n explicitly resuming the Flow session; do not acknowledge a boundary inside\n the same uninterrupted loop that created it.\n- Call `flow_run_start` with no `featureId` unless the user or plan requires a specific runnable feature.\n- Treat the returned feature as the sole scope until it is completed, blocked, or reset.\n- Helper rule: when a named helper skill is unavailable, record the gap and\n keep the corresponding claims conservative instead of simulating its checks.\n- Load `flow-deslop` for cleanup/refactor features.\n- Load `flow-ui-quality` for frontend, UX, responsive, accessibility, or visual work.\n\n## Implement\n\n- Read the feature `targets`, `summary`, `validation`, dependencies, and plan `requirements`/`decisions`.\n- Treat the feature\'s `reviewDepth` as the minimum feature-review depth that\n must be recorded in `flow_feature_complete`.\n- For broad, risky, or multi-target work, record an implementation pass\n decision before editing: `serial`, `candidate-exact-path`,\n `candidate-worktree`, `tournament`, or `skipped`. Use\n `../flow/references/parallel-orchestration.md` for the decision rules,\n manifest fields, and compact `orchestrationPasses` record.\n- Classify `candidateEligibility` (`eligible`, `not_eligible`, or `unknown`)\n and `candidateDecision` (`used`, `skipped`, or `serial_required`) separately;\n implementation decisions must use `eligible` or `not_eligible` and always set\n an explicit `decision`. The valid pairings and the candidate execution\n evidence rules are in `../flow/references/parallel-orchestration.md` under\n "Implementation pass decision" — follow that reference when composing the\n record.\n- Record structured `decisionFactors`: `shared_state`, `overlapping_files`,\n `small_slice`, `needs_manager_judgment`, `independent_surface`, and\n `validation_available`.\n- Keep edits scoped to the active feature. If new scope appears, stop and replan or defer it to another feature.\n- Preserve unrelated user changes in the worktree.\n- When a wrong assumption invalidates the feature, use `flow_feature_reset`; do not pile patches onto a bad path.\n- Do not stage, commit, push, amend, rebase, publish, or mutate releases as part\n of feature execution. If the user explicitly asks for commit preparation, load\n `flow-commit` only after `flow_feature_complete` has been recorded, unless the\n user explicitly asks for a WIP commit path. Keep Git boundaries separate from\n Flow state recording.\n\n## Validate\n\n- For complex validation, regression-sensitive changes, browser QA, route QA,\n failure-prone checks, unclear coverage, exploratory QA, or\n `validationRun` summarization, load `flow-test` (helper rule applies).\n- Read `references/validation-rubric.md` before completing.\n- Run the strongest practical checks for the changed behavior.\n- Record concrete command names, status, and observed results. "Tests pass" is not evidence.\n- Non-final features complete with `validationScope: "targeted"`.\n- The final feature must run a broad project-level gate and use `validationScope: "broad"`.\n\nFor broad validation research, risky changes, or unclear coverage, use\n`../flow/references/parallel-orchestration.md` to fan out named Flow workers.\nUse the mode-to-agent mapping in that reference instead of generic subagents.\nWrite its pass manifest before fan-out, paste the matching handoff template\nfrom `../flow/references/handoff-format.md` into every worker prompt, and\napply its verification tiers to the handoffs that come back.\nThey may report command output they actually ran or propose focused checks; the\nmanager decides what is strong enough to record.\n\nFor independent implementation attempts, use candidate workers only with\nexplicit user authorization plus isolated worktrees or exact non-overlapping\npath ownership. Treat their output as candidate patches. The manager inspects,\nmerges or rejects, validates, and records Flow state serially. Record whether a\ncandidate was `accepted`, `modified`, or `rejected`.\nWhen a candidate pass or serial/skipped implementation decision materially\nshaped the feature, include its compact record in\n`flow_feature_complete.orchestrationPasses`. Do not paste full worker handoffs\nor long logs into the runtime payload.\n\n## Review and complete\n\nBefore `flow_feature_complete`, obtain a `featureReview` payload. Load\n`flow-review`; for read-only subagent reviews, the manager receives the review\npacket and records both `featureReviewDepth` and `featureReview`.\n\nSend reviewers a compact review packet. Do not rely on the accumulated parent\nconversation. Include only:\n\n- active feature id, title, summary, `reviewDepth`, targets, validation, and dependencies\n- relevant plan requirements, decisions, and final review policy\n- changed files and a short diff summary\n- validation evidence with exact commands, status, and observed result\n- targeted paths or risk lenses the reviewer must inspect\n\nIf the review returns `status: "failed"`, do not fix inside the review pass.\nRecord the failed attempt by calling `flow_feature_complete` with the otherwise\nprepared completion payload, the failed `featureReview`, and the attempted\n`featureReviewDepth`; the runtime will reject completion and update the retry\nbudget. Default to stopping and reporting the blocker. When the user already\nauthorized autonomous implementation, make at most one repair and run one retry\nreview. If the retry fails or the runtime reports review retry budget\nexhausted, stop with the blocker.\n\nIf `flow_status` reports `setup.skills` or `flow-review` cannot be loaded, do\nnot record a Flow-gated `featureReview` or `finalReview`. You may perform an\nadvisory review using available context or the bundled review fallback provided\nby plugin config, then complete with `status: "needs_input"` if review evidence\nis required to proceed.\n\nFor the final feature, also obtain a `finalReview` payload whose `reviewDepth` equals the approved plan\'s `finalReviewPolicy`.\n\nComplete with:\n\n```json\n{\n "status": "ok",\n "featureId": "active-feature-id",\n "summary": "what changed",\n "artifactsChanged": [{ "path": "src/file.ts" }],\n "validationRun": [\n { "command": "bun test tests/foo.test.ts", "status": "passed", "summary": "3 pass, exercised foo behavior" }\n ],\n "validationScope": "targeted",\n "featureReviewDepth": "standard",\n "featureReview": { "status": "passed", "summary": "review summary", "blockingFindings": [] },\n "orchestrationPasses": [\n {\n "id": "active-feature-id-implementation-decision",\n "kind": "implementation-decision",\n "decision": "serial",\n "decisionReason": "Shared contract edits made worker ownership unsafe.",\n "candidateEligibility": "not_eligible",\n "candidateDecision": "serial_required",\n "decisionFactors": ["shared_state", "overlapping_files"],\n "writeScope": "manager-serial",\n "verificationStatus": "not-needed",\n "outcome": "accepted"\n }\n ]\n}\n```\n\nIf `flow_feature_complete` returns a `session.resumePacket` or\n`session.budget.phaseBoundary`, stop after reporting the compact handoff. If\ngenuinely blocked, call `flow_feature_complete` with `status: "needs_input"` and\nan `outcome` that explains the blocker and next step. Never fabricate validation\nor review evidence to force progress.\n';
1618
1935
 
1619
1936
  // skills/flow-test/SKILL.md
1620
1937
  var SKILL_default7 = `---
@@ -2635,6 +2952,182 @@ var FeatureReviewDepthSchema = z.enum([
2635
2952
  "detailed"
2636
2953
  ]);
2637
2954
  var FinalReviewPolicySchema = z.enum(["broad", "detailed"]);
2955
+ var OrchestrationPassKindSchema = z.enum([
2956
+ "discovery",
2957
+ "audit",
2958
+ "review",
2959
+ "validation",
2960
+ "verification",
2961
+ "candidate",
2962
+ "implementation-decision"
2963
+ ]);
2964
+ var OrchestrationModeSchema = z.enum([
2965
+ "evidence",
2966
+ "review",
2967
+ "validation",
2968
+ "audit",
2969
+ "verifier",
2970
+ "candidate-implementation"
2971
+ ]);
2972
+ var OrchestrationDecisionSchema = z.enum([
2973
+ "serial",
2974
+ "parallel",
2975
+ "candidate-exact-path",
2976
+ "candidate-worktree",
2977
+ "tournament",
2978
+ "skipped"
2979
+ ]);
2980
+ var OrchestrationCandidateEligibilitySchema = z.enum([
2981
+ "eligible",
2982
+ "not_eligible",
2983
+ "unknown"
2984
+ ]);
2985
+ var OrchestrationCandidateDecisionSchema = z.enum([
2986
+ "used",
2987
+ "skipped",
2988
+ "serial_required"
2989
+ ]);
2990
+ var OrchestrationDecisionFactorSchema = z.enum([
2991
+ "shared_state",
2992
+ "overlapping_files",
2993
+ "small_slice",
2994
+ "needs_manager_judgment",
2995
+ "independent_surface",
2996
+ "validation_available"
2997
+ ]);
2998
+ var OrchestrationWriteScopeSchema = z.enum([
2999
+ "none",
3000
+ "manager-serial",
3001
+ "exact-path",
3002
+ "isolated-worktree",
3003
+ "mixed"
3004
+ ]);
3005
+ var OrchestrationVerificationStatusSchema = z.enum([
3006
+ "not-needed",
3007
+ "pending",
3008
+ "passed",
3009
+ "failed",
3010
+ "mixed",
3011
+ "downgraded"
3012
+ ]);
3013
+ var OrchestrationOutcomeSchema = z.enum([
3014
+ "accepted",
3015
+ "modified",
3016
+ "rejected",
3017
+ "partial",
3018
+ "not-covered",
3019
+ "superseded"
3020
+ ]);
3021
+ var CANDIDATE_SHAPED_DECISIONS = new Set([
3022
+ "candidate-exact-path",
3023
+ "candidate-worktree",
3024
+ "tournament"
3025
+ ]);
3026
+ function isCandidateShapedDecision(decision) {
3027
+ return decision !== undefined && CANDIDATE_SHAPED_DECISIONS.has(decision);
3028
+ }
3029
+ function hasCandidateExecutionEvidence(pass) {
3030
+ return pass.kind === "candidate" || pass.modes.includes("candidate-implementation") || pass.candidateWorkerCount > 0;
3031
+ }
3032
+ function hasVerifierExecutionEvidence(pass) {
3033
+ return pass.kind === "verification" || pass.modes.includes("verifier") || pass.verifierWorkerCount > 0;
3034
+ }
3035
+ var OrchestrationPassRecordSchema = z.object({
3036
+ id: z.string().min(1),
3037
+ kind: OrchestrationPassKindSchema,
3038
+ decision: OrchestrationDecisionSchema.optional(),
3039
+ decisionReason: z.string().min(1).optional(),
3040
+ candidateEligibility: OrchestrationCandidateEligibilitySchema.default("unknown"),
3041
+ candidateDecision: OrchestrationCandidateDecisionSchema.optional(),
3042
+ decisionFactors: z.array(OrchestrationDecisionFactorSchema).default([]),
3043
+ modes: z.array(OrchestrationModeSchema).default([]),
3044
+ workerCount: z.number().int().nonnegative().default(0),
3045
+ candidateWorkerCount: z.number().int().nonnegative().default(0),
3046
+ verifierWorkerCount: z.number().int().nonnegative().default(0),
3047
+ sliceIds: z.array(z.string().min(1)).default([]),
3048
+ dependsOn: z.array(z.string().min(1)).default([]),
3049
+ writeScope: OrchestrationWriteScopeSchema.default("none"),
3050
+ handoffRefs: z.array(z.string().min(1)).default([]),
3051
+ verificationStatus: OrchestrationVerificationStatusSchema.default("not-needed"),
3052
+ outcome: OrchestrationOutcomeSchema.default("accepted"),
3053
+ synthesisRef: z.string().min(1).optional()
3054
+ }).strict().superRefine((value, ctx) => {
3055
+ const issue = (path, message) => ctx.addIssue({ code: "custom", path: [path], message });
3056
+ const isImplementationDecision = value.kind === "implementation-decision";
3057
+ const candidateEligibilityIsUnknown = value.candidateEligibility === "unknown";
3058
+ if (value.candidateWorkerCount > value.workerCount) {
3059
+ issue("candidateWorkerCount", "candidateWorkerCount cannot exceed total workerCount.");
3060
+ }
3061
+ if (value.verifierWorkerCount > value.workerCount) {
3062
+ issue("verifierWorkerCount", "verifierWorkerCount cannot exceed total workerCount.");
3063
+ }
3064
+ if (isCandidateShapedDecision(value.decision) && !hasCandidateExecutionEvidence(value)) {
3065
+ issue("decision", "Candidate-shaped decisions require candidate execution evidence: a candidate pass, candidate-implementation mode, or candidateWorkerCount > 0.");
3066
+ }
3067
+ if (isImplementationDecision) {
3068
+ if (value.decision === "parallel") {
3069
+ issue("decision", "Implementation decisions cannot use decision 'parallel'; use 'serial', 'skipped', or a candidate-shaped decision.");
3070
+ }
3071
+ if (candidateEligibilityIsUnknown) {
3072
+ issue("candidateEligibility", "Implementation decisions must include explicit candidateEligibility.");
3073
+ }
3074
+ if (!value.candidateDecision) {
3075
+ issue("candidateDecision", "Implementation decisions must include explicit candidateDecision.");
3076
+ }
3077
+ if (!value.decision) {
3078
+ issue("decision", "Implementation decisions must include explicit decision.");
3079
+ }
3080
+ if (value.decisionFactors.length === 0) {
3081
+ issue("decisionFactors", "Implementation decisions must include at least one decisionFactor.");
3082
+ }
3083
+ }
3084
+ if (!value.candidateDecision)
3085
+ return;
3086
+ if (!isImplementationDecision && candidateEligibilityIsUnknown) {
3087
+ issue("candidateEligibility", "Candidate eligibility must be explicit when candidateDecision is set.");
3088
+ }
3089
+ if (!isImplementationDecision && (value.candidateDecision === "skipped" || value.candidateDecision === "serial_required")) {
3090
+ issue("candidateDecision", "Candidate decisions 'skipped' and 'serial_required' are only valid on implementation-decision records.");
3091
+ }
3092
+ if (value.candidateEligibility === "not_eligible" && value.candidateDecision === "used") {
3093
+ issue("candidateDecision", "Candidate decision 'used' requires eligible candidate work.");
3094
+ }
3095
+ if (value.candidateEligibility === "eligible" && value.candidateDecision === "serial_required") {
3096
+ issue("candidateDecision", "Candidate decision 'serial_required' requires not_eligible candidate work.");
3097
+ }
3098
+ if (value.candidateDecision === "skipped" && value.candidateEligibility !== "eligible") {
3099
+ issue("candidateDecision", "Candidate decision 'skipped' requires eligible candidate work.");
3100
+ }
3101
+ if (isImplementationDecision && value.decision === "skipped" && value.candidateDecision !== "skipped") {
3102
+ issue("decision", "Implementation decision 'skipped' requires candidateDecision 'skipped'.");
3103
+ }
3104
+ if (isImplementationDecision && value.candidateDecision === "skipped" && value.decision && value.decision !== "skipped") {
3105
+ issue("candidateDecision", "Candidate decision 'skipped' requires implementation decision 'skipped'.");
3106
+ }
3107
+ if (isImplementationDecision && value.candidateDecision === "serial_required" && value.decision && value.decision !== "serial") {
3108
+ issue("candidateDecision", "Candidate decision 'serial_required' requires implementation decision 'serial'.");
3109
+ }
3110
+ if (value.candidateDecision === "used") {
3111
+ if (!hasCandidateExecutionEvidence(value)) {
3112
+ issue("candidateDecision", "Candidate decision 'used' requires a candidate pass, candidate mode, or candidate worker count.");
3113
+ }
3114
+ if (value.decision && !isCandidateShapedDecision(value.decision)) {
3115
+ issue("decision", "Candidate decision 'used' requires an omitted or candidate-shaped decision.");
3116
+ }
3117
+ }
3118
+ });
3119
+ var OrchestrationTelemetrySchema = z.object({
3120
+ passCount: z.number().int().nonnegative().default(0),
3121
+ workerCount: z.number().int().nonnegative().default(0),
3122
+ candidatePassCount: z.number().int().nonnegative().default(0),
3123
+ verifierPassCount: z.number().int().nonnegative().default(0),
3124
+ candidateEligibleCount: z.number().int().nonnegative().default(0),
3125
+ candidateUsedDecisionCount: z.number().int().nonnegative().default(0),
3126
+ candidateSerialRequiredDecisionCount: z.number().int().nonnegative().default(0),
3127
+ skippedCandidateDecisionCount: z.number().int().nonnegative().default(0),
3128
+ recordedPassIds: z.array(z.string().min(1)).default([]),
3129
+ latestPasses: z.array(OrchestrationPassRecordSchema).default([])
3130
+ }).strict();
2638
3131
  var ReviewFindingSchema = z.object({
2639
3132
  summary: z.string().min(1),
2640
3133
  severity: z.enum(["blocking", "advisory"]).default("blocking")
@@ -2704,7 +3197,8 @@ var WorkerResultSchema = z.discriminatedUnion("status", [
2704
3197
  featureReviewDepth: FeatureReviewDepthSchema,
2705
3198
  featureReview: ReviewSchema,
2706
3199
  finalReview: FinalReviewSchema.optional(),
2707
- outcome: WorkerOutcomeSchema.optional()
3200
+ outcome: WorkerOutcomeSchema.optional(),
3201
+ orchestrationPasses: z.array(OrchestrationPassRecordSchema).default([])
2708
3202
  }).strict(),
2709
3203
  z.object({
2710
3204
  status: z.literal("needs_input"),
@@ -2716,7 +3210,8 @@ var WorkerResultSchema = z.discriminatedUnion("status", [
2716
3210
  featureReviewDepth: FeatureReviewDepthSchema.optional(),
2717
3211
  featureReview: ReviewSchema.optional(),
2718
3212
  finalReview: FinalReviewSchema.optional(),
2719
- outcome: NeedsInputOutcomeSchema
3213
+ outcome: NeedsInputOutcomeSchema,
3214
+ orchestrationPasses: z.array(OrchestrationPassRecordSchema).default([])
2720
3215
  }).strict()
2721
3216
  ]).superRefine((value, ctx) => {
2722
3217
  if (value.status === "ok" && value.outcome?.kind && value.outcome.kind !== "completed") {
@@ -2738,7 +3233,8 @@ var ExecutionHistoryEntrySchema = z.object({
2738
3233
  featureReviewDepth: FeatureReviewDepthSchema.optional(),
2739
3234
  featureReview: ReviewSchema.optional(),
2740
3235
  finalReview: FinalReviewSchema.optional(),
2741
- outcome: WorkerOutcomeSchema.optional()
3236
+ outcome: WorkerOutcomeSchema.optional(),
3237
+ orchestrationPasses: z.array(OrchestrationPassRecordSchema).default([])
2742
3238
  }).strict();
2743
3239
  var TokenTelemetrySchema = z.object({
2744
3240
  source: z.enum(["host_unavailable", "reported"]).default("host_unavailable"),
@@ -2764,6 +3260,7 @@ var BudgetTelemetrySchema = z.object({
2764
3260
  cacheReadTokens: null,
2765
3261
  nonCacheTokens: null
2766
3262
  }),
3263
+ orchestration: OrchestrationTelemetrySchema.prefault({}),
2767
3264
  phaseBoundary: PhaseBoundarySchema.nullable().default(null)
2768
3265
  }).strict();
2769
3266
  var SessionSchema = z.object({
@@ -2775,20 +3272,7 @@ var SessionSchema = z.object({
2775
3272
  plan: PlanSchema.nullable(),
2776
3273
  activeFeatureId: z.string().regex(FEATURE_ID_PATTERN, FEATURE_ID_MESSAGE).nullable(),
2777
3274
  history: z.array(ExecutionHistoryEntrySchema).default([]),
2778
- budget: BudgetTelemetrySchema.default({
2779
- phaseStartedAt: "unknown",
2780
- completedFeaturesSinceBoundary: 0,
2781
- reviewCount: 0,
2782
- failedReviewCount: 0,
2783
- failedReviewAttemptsByFeature: {},
2784
- tokenTelemetry: {
2785
- source: "host_unavailable",
2786
- visibleTokens: null,
2787
- cacheReadTokens: null,
2788
- nonCacheTokens: null
2789
- },
2790
- phaseBoundary: null
2791
- }),
3275
+ budget: BudgetTelemetrySchema.prefault({}),
2792
3276
  closure: z.object({
2793
3277
  kind: z.enum(["completed", "deferred", "abandoned"]),
2794
3278
  summary: z.string().min(1),
@@ -3227,6 +3711,7 @@ function nowIso() {
3227
3711
  // src/runtime/transitions.ts
3228
3712
  var MAX_HISTORY_ENTRIES = 500;
3229
3713
  var MAX_FAILED_REVIEW_ATTEMPTS_PER_FEATURE = 2;
3714
+ var MAX_LATEST_ORCHESTRATION_PASSES = 50;
3230
3715
  var FEATURE_REVIEW_DEPTH_RANK = {
3231
3716
  quick: 0,
3232
3717
  standard: 1,
@@ -3248,24 +3733,12 @@ function historyEntryFor(worker, status) {
3248
3733
  featureReviewDepth: worker.featureReviewDepth,
3249
3734
  featureReview: worker.featureReview,
3250
3735
  finalReview: worker.finalReview,
3251
- outcome: worker.outcome
3736
+ outcome: worker.outcome,
3737
+ orchestrationPasses: worker.orchestrationPasses
3252
3738
  };
3253
3739
  }
3254
3740
  function initialBudgetTelemetry() {
3255
- return {
3256
- phaseStartedAt: nowIso(),
3257
- completedFeaturesSinceBoundary: 0,
3258
- reviewCount: 0,
3259
- failedReviewCount: 0,
3260
- failedReviewAttemptsByFeature: {},
3261
- tokenTelemetry: {
3262
- source: "host_unavailable",
3263
- visibleTokens: null,
3264
- cacheReadTokens: null,
3265
- nonCacheTokens: null
3266
- },
3267
- phaseBoundary: null
3268
- };
3741
+ return { ...BudgetTelemetrySchema.parse({}), phaseStartedAt: nowIso() };
3269
3742
  }
3270
3743
  function normalizeBudgetTelemetry(session) {
3271
3744
  const defaults = initialBudgetTelemetry();
@@ -3278,9 +3751,87 @@ function normalizeBudgetTelemetry(session) {
3278
3751
  tokenTelemetry: {
3279
3752
  ...defaults.tokenTelemetry,
3280
3753
  ...session.budget.tokenTelemetry
3754
+ },
3755
+ orchestration: {
3756
+ ...defaults.orchestration,
3757
+ ...session.budget.orchestration,
3758
+ recordedPassIds: [
3759
+ ...session.budget.orchestration?.recordedPassIds ?? []
3760
+ ],
3761
+ latestPasses: [...session.budget.orchestration?.latestPasses ?? []]
3281
3762
  }
3282
3763
  };
3283
3764
  }
3765
+ function recordOrchestrationPasses(budget, passes) {
3766
+ if (passes.length === 0)
3767
+ return budget;
3768
+ const seenPassIds = new Set([
3769
+ ...budget.orchestration.recordedPassIds,
3770
+ ...budget.orchestration.latestPasses.map((pass) => pass.id)
3771
+ ]);
3772
+ const newPasses = [];
3773
+ for (const pass of passes) {
3774
+ if (seenPassIds.has(pass.id))
3775
+ continue;
3776
+ seenPassIds.add(pass.id);
3777
+ newPasses.push(pass);
3778
+ }
3779
+ if (newPasses.length === 0)
3780
+ return budget;
3781
+ const tally = {
3782
+ workerCount: 0,
3783
+ candidatePassCount: 0,
3784
+ verifierPassCount: 0,
3785
+ candidateEligibleCount: 0,
3786
+ candidateUsedDecisionCount: 0,
3787
+ candidateSerialRequiredDecisionCount: 0,
3788
+ skippedCandidateDecisionCount: 0
3789
+ };
3790
+ for (const pass of newPasses) {
3791
+ tally.workerCount += pass.workerCount;
3792
+ if (hasCandidateExecutionEvidence(pass))
3793
+ tally.candidatePassCount += 1;
3794
+ if (hasVerifierExecutionEvidence(pass))
3795
+ tally.verifierPassCount += 1;
3796
+ if (pass.kind !== "implementation-decision")
3797
+ continue;
3798
+ if (pass.candidateEligibility === "eligible") {
3799
+ tally.candidateEligibleCount += 1;
3800
+ }
3801
+ if (pass.candidateDecision === "used") {
3802
+ tally.candidateUsedDecisionCount += 1;
3803
+ }
3804
+ if (pass.candidateDecision === "serial_required") {
3805
+ tally.candidateSerialRequiredDecisionCount += 1;
3806
+ }
3807
+ if (pass.candidateDecision === "skipped") {
3808
+ tally.skippedCandidateDecisionCount += 1;
3809
+ }
3810
+ }
3811
+ const latestPasses = [...budget.orchestration.latestPasses, ...newPasses];
3812
+ return {
3813
+ ...budget,
3814
+ orchestration: {
3815
+ passCount: budget.orchestration.passCount + newPasses.length,
3816
+ workerCount: budget.orchestration.workerCount + tally.workerCount,
3817
+ candidatePassCount: budget.orchestration.candidatePassCount + tally.candidatePassCount,
3818
+ verifierPassCount: budget.orchestration.verifierPassCount + tally.verifierPassCount,
3819
+ candidateEligibleCount: budget.orchestration.candidateEligibleCount + tally.candidateEligibleCount,
3820
+ candidateUsedDecisionCount: budget.orchestration.candidateUsedDecisionCount + tally.candidateUsedDecisionCount,
3821
+ candidateSerialRequiredDecisionCount: budget.orchestration.candidateSerialRequiredDecisionCount + tally.candidateSerialRequiredDecisionCount,
3822
+ skippedCandidateDecisionCount: budget.orchestration.skippedCandidateDecisionCount + tally.skippedCandidateDecisionCount,
3823
+ recordedPassIds: [
3824
+ ...budget.orchestration.recordedPassIds,
3825
+ ...newPasses.map((pass) => pass.id)
3826
+ ],
3827
+ latestPasses: latestPasses.length > MAX_LATEST_ORCHESTRATION_PASSES ? latestPasses.slice(latestPasses.length - MAX_LATEST_ORCHESTRATION_PASSES) : latestPasses
3828
+ }
3829
+ };
3830
+ }
3831
+ function sessionWithOrchestrationPasses(session, passes) {
3832
+ const budget = recordOrchestrationPasses(normalizeBudgetTelemetry(session), passes);
3833
+ return budget === session.budget ? session : { ...session, budget };
3834
+ }
3284
3835
  function ok(value) {
3285
3836
  return { ok: true, value };
3286
3837
  }
@@ -3640,48 +4191,49 @@ function completeFeature(session, input) {
3640
4191
  if (worker.featureId !== session.activeFeatureId) {
3641
4192
  return fail(`Worker result feature '${worker.featureId}' does not match active feature '${session.activeFeatureId}'.`);
3642
4193
  }
4194
+ const sessionWithPasses = sessionWithOrchestrationPasses(session, worker.orchestrationPasses);
3643
4195
  if (worker.status === "needs_input") {
3644
4196
  const entry2 = historyEntryFor(worker, "needs_input");
3645
- const budget2 = normalizeBudgetTelemetry(session);
4197
+ const budget2 = normalizeBudgetTelemetry(sessionWithPasses);
3646
4198
  return ok(touch({
3647
- ...session,
4199
+ ...sessionWithPasses,
3648
4200
  status: "blocked",
3649
4201
  activeFeatureId: null,
3650
4202
  plan: {
3651
4203
  ...session.plan,
3652
4204
  features: updateFeature(session.plan.features, worker.featureId, "blocked")
3653
4205
  },
3654
- history: appendHistory(session.history, entry2),
4206
+ history: appendHistory(sessionWithPasses.history, entry2),
3655
4207
  budget: budget2,
3656
4208
  lastError: null
3657
4209
  }));
3658
4210
  }
3659
4211
  if (!isPassingReview(worker.featureReview)) {
3660
- return failedReviewCompletion(session, worker, worker.featureReview, "feature");
4212
+ return failedReviewCompletion(sessionWithPasses, worker, worker.featureReview, "feature");
3661
4213
  }
3662
- if (finalFeature(session, worker.featureId) && worker.finalReview && !isPassingReview(worker.finalReview)) {
3663
- return failedReviewCompletion(session, worker, worker.finalReview, "final");
4214
+ if (finalFeature(sessionWithPasses, worker.featureId) && worker.finalReview && !isPassingReview(worker.finalReview)) {
4215
+ return failedReviewCompletion(sessionWithPasses, worker, worker.finalReview, "final");
3664
4216
  }
3665
- const validation = validateCompletion(session, worker);
4217
+ const validation = validateCompletion(sessionWithPasses, worker);
3666
4218
  if (!validation.ok)
3667
4219
  return validation;
3668
4220
  const entry = historyEntryFor(worker, "completed");
3669
4221
  const features = updateFeature(session.plan.features, worker.featureId, "completed");
3670
4222
  const allComplete = features.every((feature) => feature.status === "completed");
3671
4223
  const now = nowIso();
3672
- const budget = completionBudget(session, worker);
4224
+ const budget = completionBudget(sessionWithPasses, worker);
3673
4225
  return ok(touch({
3674
- ...session,
4226
+ ...sessionWithPasses,
3675
4227
  status: allComplete ? "completed" : "ready",
3676
4228
  activeFeatureId: null,
3677
4229
  plan: { ...session.plan, features },
3678
- history: appendHistory(session.history, entry),
4230
+ history: appendHistory(sessionWithPasses.history, entry),
3679
4231
  budget,
3680
4232
  closure: allComplete ? { kind: "completed", summary: worker.summary, recordedAt: now } : null,
3681
4233
  lastError: null,
3682
4234
  timestamps: {
3683
- ...session.timestamps,
3684
- completedAt: allComplete ? now : session.timestamps.completedAt
4235
+ ...sessionWithPasses.timestamps,
4236
+ completedAt: allComplete ? now : sessionWithPasses.timestamps.completedAt
3685
4237
  }
3686
4238
  }));
3687
4239
  }
@@ -3808,6 +4360,7 @@ function summarizeSession(session) {
3808
4360
  reviewCount: budget.reviewCount,
3809
4361
  failedReviewCount: budget.failedReviewCount,
3810
4362
  failedReviewAttemptsByFeature: budget.failedReviewAttemptsByFeature,
4363
+ orchestration: budget.orchestration,
3811
4364
  tokenTelemetry: {
3812
4365
  ...budget.tokenTelemetry,
3813
4366
  note: budget.tokenTelemetry.source === "host_unavailable" ? "OpenCode does not expose per-turn usage to this plugin surface; Flow can enforce review checkpoints, but token thresholds remain manager-observed." : undefined
@@ -3882,7 +4435,8 @@ var FlowFeatureCompleteToolSchema = z2.object({
3882
4435
  featureReviewDepth: FeatureReviewDepthSchema.optional(),
3883
4436
  featureReview: ReviewSchema.optional(),
3884
4437
  finalReview: FinalReviewSchema.optional(),
3885
- outcome: z2.union([WorkerOutcomeSchema, NeedsInputOutcomeSchema]).optional()
4438
+ outcome: z2.union([WorkerOutcomeSchema, NeedsInputOutcomeSchema]).optional(),
4439
+ orchestrationPasses: z2.array(OrchestrationPassRecordSchema).optional()
3886
4440
  }).strict();
3887
4441
  function missingSessionResponse() {
3888
4442
  return {
@@ -4249,4 +4803,4 @@ export {
4249
4803
  plugin_default as default
4250
4804
  };
4251
4805
 
4252
- //# debugId=F409E930A69EDF5A64756E2164756E21
4806
+ //# debugId=C77708A78E8740C664756E2164756E21