opencode-plugin-flow 4.3.8 → 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/CHANGELOG.md +23 -0
- package/README.md +6 -5
- package/dist/adapters/opencode/tools.d.ts +23 -1
- package/dist/cli.js +140 -10
- package/dist/cli.js.map +2 -2
- package/dist/index.js +296 -74
- package/dist/index.js.map +4 -4
- package/dist/runtime/api.d.ts +19 -0
- package/dist/runtime/schema.d.ts +197 -3
- package/dist/runtime/transitions.d.ts +12 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -168,6 +168,12 @@ completion.
|
|
|
168
168
|
The manager, not the worker, may carry compact records into
|
|
169
169
|
\`flow_feature_complete.orchestrationPasses\`. Use one record per material pass or
|
|
170
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.
|
|
171
177
|
|
|
172
178
|
\`\`\`json
|
|
173
179
|
{
|
|
@@ -175,6 +181,16 @@ implementation decision; keep handoffs and long artifacts outside \`.flow/**\`.
|
|
|
175
181
|
"kind": "discovery | audit | review | validation | verification | candidate | implementation-decision",
|
|
176
182
|
"decision": "serial | parallel | candidate-exact-path | candidate-worktree | tournament | skipped",
|
|
177
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
|
+
],
|
|
178
194
|
"modes": ["evidence"],
|
|
179
195
|
"workerCount": 1,
|
|
180
196
|
"candidateWorkerCount": 0,
|
|
@@ -184,7 +200,7 @@ implementation decision; keep handoffs and long artifacts outside \`.flow/**\`.
|
|
|
184
200
|
"writeScope": "none | manager-serial | exact-path | isolated-worktree | mixed",
|
|
185
201
|
"handoffRefs": ["/tmp/flow-handoff.md"],
|
|
186
202
|
"verificationStatus": "not-needed | pending | passed | failed | mixed | downgraded",
|
|
187
|
-
"outcome": "accepted | rejected | partial | not-covered | superseded",
|
|
203
|
+
"outcome": "accepted | modified | rejected | partial | not-covered | superseded",
|
|
188
204
|
"synthesisRef": "/tmp/flow-synthesis.md"
|
|
189
205
|
}
|
|
190
206
|
\`\`\`
|
|
@@ -246,11 +262,32 @@ Before implementing a broad, risky, or multi-target feature, record one manager
|
|
|
246
262
|
decision. This is required even when the answer is "stay serial"; the point is
|
|
247
263
|
to make the skipped parallelism visible instead of relying on memory.
|
|
248
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
|
+
|
|
249
284
|
Use one of these decisions:
|
|
250
285
|
|
|
251
286
|
- \`serial\`: the manager implements directly because slices overlap, the next
|
|
252
287
|
edit depends on one shared contract, or prompt/merge overhead would exceed the
|
|
253
|
-
value.
|
|
288
|
+
value. Pair unsafe or not-useful worker cases with
|
|
289
|
+
\`candidateEligibility: "not_eligible"\` and
|
|
290
|
+
\`candidateDecision: "serial_required"\`.
|
|
254
291
|
- \`candidate-exact-path\`: one or more candidate workers may edit exact
|
|
255
292
|
non-overlapping paths or modules named by the manager.
|
|
256
293
|
- \`candidate-worktree\`: one or more candidate workers may edit in isolated
|
|
@@ -258,11 +295,54 @@ Use one of these decisions:
|
|
|
258
295
|
- \`tournament\`: several isolated candidate implementations compete for the same
|
|
259
296
|
outcome; the manager filters by tests, review, and source inspection before
|
|
260
297
|
accepting one.
|
|
261
|
-
- \`
|
|
262
|
-
|
|
263
|
-
|
|
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.
|
|
264
343
|
|
|
265
344
|
Record the decision in the pass manifest with a stable pass id,
|
|
345
|
+
\`candidateEligibility\`, \`candidateDecision\`, \`decisionFactors\`,
|
|
266
346
|
\`decisionReason\`, \`writeScope\`, expected verification, and where any handoff or
|
|
267
347
|
synthesis artifact will live. If the feature completes, include the compact
|
|
268
348
|
record in the \`orchestrationPasses\` array of the \`flow_feature_complete\`
|
|
@@ -398,7 +478,17 @@ N handoffs collected and checked in Stage 5 before anything is synthesized.
|
|
|
398
478
|
|
|
399
479
|
For implementation decisions, add a manifest row even when no worker is spawned:
|
|
400
480
|
\`kind=implementation-decision\`, \`decision=serial\` or \`decision=skipped\`,
|
|
481
|
+
\`candidateEligibility\`, \`candidateDecision\`, \`decisionFactors\`,
|
|
401
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).
|
|
402
492
|
This is how Flow distinguishes deliberate serial work from forgotten candidate
|
|
403
493
|
or verifier passes.
|
|
404
494
|
|
|
@@ -449,7 +539,8 @@ For each row, fill in:
|
|
|
449
539
|
review packet location that the manager can re-open.
|
|
450
540
|
- \`verificationStatus\`: \`not-needed\`, \`pending\`, \`passed\`, \`failed\`, \`mixed\`,
|
|
451
541
|
or \`downgraded\`.
|
|
452
|
-
- \`outcome\`: \`accepted\`, \`rejected\`, \`partial\`, \`not-covered\`, or
|
|
542
|
+
- \`outcome\`: \`accepted\`, \`modified\`, \`rejected\`, \`partial\`, \`not-covered\`, or
|
|
543
|
+
\`superseded\`.
|
|
453
544
|
- \`synthesisRef\`: the manager-owned synthesis file or plan field that carries
|
|
454
545
|
the accepted result forward.
|
|
455
546
|
|
|
@@ -482,7 +573,8 @@ handoff only after a cheap manager-side pass:
|
|
|
482
573
|
artifact they depend on.
|
|
483
574
|
- Candidate implementation claims identify whether they came from exact path
|
|
484
575
|
ownership or an isolated worktree, and whether the manager inspected the
|
|
485
|
-
resulting patch.
|
|
576
|
+
resulting patch. Record the manager result as \`accepted\`, \`modified\`, or
|
|
577
|
+
\`rejected\` where that is the most precise candidate outcome.
|
|
486
578
|
- Contradictions between workers are either resolved or explicitly marked as
|
|
487
579
|
contested.
|
|
488
580
|
|
|
@@ -523,6 +615,11 @@ Apply the manager synthesis barrier before presenting or recording anything:
|
|
|
523
615
|
- When workers disagree, inspect the cited artifact or rerun the cited command
|
|
524
616
|
instead of arbitrating from summaries. Do not average conflicting claims.
|
|
525
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.
|
|
526
623
|
- Re-read critical files or docs that will be cited in the final decision.
|
|
527
624
|
- Move only distilled, evidence-backed claims forward; raw handoffs remain
|
|
528
625
|
candidate evidence, not a plan, review, completion payload, or final answer.
|
|
@@ -557,6 +654,9 @@ decision that materially affected the feature:
|
|
|
557
654
|
"kind": "implementation-decision",
|
|
558
655
|
"decision": "serial",
|
|
559
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"],
|
|
560
660
|
"modes": [],
|
|
561
661
|
"workerCount": 0,
|
|
562
662
|
"candidateWorkerCount": 0,
|
|
@@ -579,6 +679,21 @@ these compact records into \`session.budget.orchestration\` and stores them on
|
|
|
579
679
|
the feature history entry. Do not store full handoffs, long logs, or scratch
|
|
580
680
|
tables in \`.flow/session.json\`.
|
|
581
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
|
+
|
|
582
697
|
Persist the manifest and the synthesis when another pass may follow or the
|
|
583
698
|
session is long enough to be compacted or resumed: write the distilled result —
|
|
584
699
|
the accounted manifest, accepted claims with evidence and confidence, dropped
|
|
@@ -600,7 +715,8 @@ Stop after a pass when:
|
|
|
600
715
|
- every dependency edge named in the manifest has either a verified upstream
|
|
601
716
|
result or an explicit not-covered outcome.
|
|
602
717
|
- implementation pass decisions are recorded, including skipped candidate
|
|
603
|
-
workers
|
|
718
|
+
workers, candidate eligibility, candidate decision, structured factors, and
|
|
719
|
+
the reason eligible workers were skipped.
|
|
604
720
|
- remaining gaps are explicit and do not block the Flow artifact being produced.
|
|
605
721
|
|
|
606
722
|
Start a bounded follow-up pass only when:
|
|
@@ -619,6 +735,20 @@ reason, such as a high-stakes verifier check or a newly discovered bounded
|
|
|
619
735
|
slice. Do not recurse by default: if a worker says it needs another worker, the
|
|
620
736
|
manager decides whether that is a follow-up pass and writes the next bounded
|
|
621
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.
|
|
622
752
|
`;
|
|
623
753
|
|
|
624
754
|
// skills/flow/references/parallel-pass-example.md
|
|
@@ -1801,7 +1931,7 @@ Never trim failing output, relabel a failed command as passed, or use "not run"
|
|
|
1801
1931
|
`;
|
|
1802
1932
|
|
|
1803
1933
|
// skills/flow-run/SKILL.md
|
|
1804
|
-
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-
|
|
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';
|
|
1805
1935
|
|
|
1806
1936
|
// skills/flow-test/SKILL.md
|
|
1807
1937
|
var SKILL_default7 = `---
|
|
@@ -2847,6 +2977,24 @@ var OrchestrationDecisionSchema = z.enum([
|
|
|
2847
2977
|
"tournament",
|
|
2848
2978
|
"skipped"
|
|
2849
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
|
+
]);
|
|
2850
2998
|
var OrchestrationWriteScopeSchema = z.enum([
|
|
2851
2999
|
"none",
|
|
2852
3000
|
"manager-serial",
|
|
@@ -2864,16 +3012,34 @@ var OrchestrationVerificationStatusSchema = z.enum([
|
|
|
2864
3012
|
]);
|
|
2865
3013
|
var OrchestrationOutcomeSchema = z.enum([
|
|
2866
3014
|
"accepted",
|
|
3015
|
+
"modified",
|
|
2867
3016
|
"rejected",
|
|
2868
3017
|
"partial",
|
|
2869
3018
|
"not-covered",
|
|
2870
3019
|
"superseded"
|
|
2871
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
|
+
}
|
|
2872
3035
|
var OrchestrationPassRecordSchema = z.object({
|
|
2873
3036
|
id: z.string().min(1),
|
|
2874
3037
|
kind: OrchestrationPassKindSchema,
|
|
2875
3038
|
decision: OrchestrationDecisionSchema.optional(),
|
|
2876
3039
|
decisionReason: z.string().min(1).optional(),
|
|
3040
|
+
candidateEligibility: OrchestrationCandidateEligibilitySchema.default("unknown"),
|
|
3041
|
+
candidateDecision: OrchestrationCandidateDecisionSchema.optional(),
|
|
3042
|
+
decisionFactors: z.array(OrchestrationDecisionFactorSchema).default([]),
|
|
2877
3043
|
modes: z.array(OrchestrationModeSchema).default([]),
|
|
2878
3044
|
workerCount: z.number().int().nonnegative().default(0),
|
|
2879
3045
|
candidateWorkerCount: z.number().int().nonnegative().default(0),
|
|
@@ -2885,13 +3051,81 @@ var OrchestrationPassRecordSchema = z.object({
|
|
|
2885
3051
|
verificationStatus: OrchestrationVerificationStatusSchema.default("not-needed"),
|
|
2886
3052
|
outcome: OrchestrationOutcomeSchema.default("accepted"),
|
|
2887
3053
|
synthesisRef: z.string().min(1).optional()
|
|
2888
|
-
}).strict()
|
|
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
|
+
});
|
|
2889
3119
|
var OrchestrationTelemetrySchema = z.object({
|
|
2890
3120
|
passCount: z.number().int().nonnegative().default(0),
|
|
2891
3121
|
workerCount: z.number().int().nonnegative().default(0),
|
|
2892
3122
|
candidatePassCount: z.number().int().nonnegative().default(0),
|
|
2893
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),
|
|
2894
3127
|
skippedCandidateDecisionCount: z.number().int().nonnegative().default(0),
|
|
3128
|
+
recordedPassIds: z.array(z.string().min(1)).default([]),
|
|
2895
3129
|
latestPasses: z.array(OrchestrationPassRecordSchema).default([])
|
|
2896
3130
|
}).strict();
|
|
2897
3131
|
var ReviewFindingSchema = z.object({
|
|
@@ -3026,14 +3260,7 @@ var BudgetTelemetrySchema = z.object({
|
|
|
3026
3260
|
cacheReadTokens: null,
|
|
3027
3261
|
nonCacheTokens: null
|
|
3028
3262
|
}),
|
|
3029
|
-
orchestration: OrchestrationTelemetrySchema.
|
|
3030
|
-
passCount: 0,
|
|
3031
|
-
workerCount: 0,
|
|
3032
|
-
candidatePassCount: 0,
|
|
3033
|
-
verifierPassCount: 0,
|
|
3034
|
-
skippedCandidateDecisionCount: 0,
|
|
3035
|
-
latestPasses: []
|
|
3036
|
-
}),
|
|
3263
|
+
orchestration: OrchestrationTelemetrySchema.prefault({}),
|
|
3037
3264
|
phaseBoundary: PhaseBoundarySchema.nullable().default(null)
|
|
3038
3265
|
}).strict();
|
|
3039
3266
|
var SessionSchema = z.object({
|
|
@@ -3045,28 +3272,7 @@ var SessionSchema = z.object({
|
|
|
3045
3272
|
plan: PlanSchema.nullable(),
|
|
3046
3273
|
activeFeatureId: z.string().regex(FEATURE_ID_PATTERN, FEATURE_ID_MESSAGE).nullable(),
|
|
3047
3274
|
history: z.array(ExecutionHistoryEntrySchema).default([]),
|
|
3048
|
-
budget: BudgetTelemetrySchema.
|
|
3049
|
-
phaseStartedAt: "unknown",
|
|
3050
|
-
completedFeaturesSinceBoundary: 0,
|
|
3051
|
-
reviewCount: 0,
|
|
3052
|
-
failedReviewCount: 0,
|
|
3053
|
-
failedReviewAttemptsByFeature: {},
|
|
3054
|
-
tokenTelemetry: {
|
|
3055
|
-
source: "host_unavailable",
|
|
3056
|
-
visibleTokens: null,
|
|
3057
|
-
cacheReadTokens: null,
|
|
3058
|
-
nonCacheTokens: null
|
|
3059
|
-
},
|
|
3060
|
-
orchestration: {
|
|
3061
|
-
passCount: 0,
|
|
3062
|
-
workerCount: 0,
|
|
3063
|
-
candidatePassCount: 0,
|
|
3064
|
-
verifierPassCount: 0,
|
|
3065
|
-
skippedCandidateDecisionCount: 0,
|
|
3066
|
-
latestPasses: []
|
|
3067
|
-
},
|
|
3068
|
-
phaseBoundary: null
|
|
3069
|
-
}),
|
|
3275
|
+
budget: BudgetTelemetrySchema.prefault({}),
|
|
3070
3276
|
closure: z.object({
|
|
3071
3277
|
kind: z.enum(["completed", "deferred", "abandoned"]),
|
|
3072
3278
|
summary: z.string().min(1),
|
|
@@ -3532,28 +3738,7 @@ function historyEntryFor(worker, status) {
|
|
|
3532
3738
|
};
|
|
3533
3739
|
}
|
|
3534
3740
|
function initialBudgetTelemetry() {
|
|
3535
|
-
return {
|
|
3536
|
-
phaseStartedAt: nowIso(),
|
|
3537
|
-
completedFeaturesSinceBoundary: 0,
|
|
3538
|
-
reviewCount: 0,
|
|
3539
|
-
failedReviewCount: 0,
|
|
3540
|
-
failedReviewAttemptsByFeature: {},
|
|
3541
|
-
tokenTelemetry: {
|
|
3542
|
-
source: "host_unavailable",
|
|
3543
|
-
visibleTokens: null,
|
|
3544
|
-
cacheReadTokens: null,
|
|
3545
|
-
nonCacheTokens: null
|
|
3546
|
-
},
|
|
3547
|
-
orchestration: {
|
|
3548
|
-
passCount: 0,
|
|
3549
|
-
workerCount: 0,
|
|
3550
|
-
candidatePassCount: 0,
|
|
3551
|
-
verifierPassCount: 0,
|
|
3552
|
-
skippedCandidateDecisionCount: 0,
|
|
3553
|
-
latestPasses: []
|
|
3554
|
-
},
|
|
3555
|
-
phaseBoundary: null
|
|
3556
|
-
};
|
|
3741
|
+
return { ...BudgetTelemetrySchema.parse({}), phaseStartedAt: nowIso() };
|
|
3557
3742
|
}
|
|
3558
3743
|
function normalizeBudgetTelemetry(session) {
|
|
3559
3744
|
const defaults = initialBudgetTelemetry();
|
|
@@ -3570,20 +3755,20 @@ function normalizeBudgetTelemetry(session) {
|
|
|
3570
3755
|
orchestration: {
|
|
3571
3756
|
...defaults.orchestration,
|
|
3572
3757
|
...session.budget.orchestration,
|
|
3758
|
+
recordedPassIds: [
|
|
3759
|
+
...session.budget.orchestration?.recordedPassIds ?? []
|
|
3760
|
+
],
|
|
3573
3761
|
latestPasses: [...session.budget.orchestration?.latestPasses ?? []]
|
|
3574
3762
|
}
|
|
3575
3763
|
};
|
|
3576
3764
|
}
|
|
3577
|
-
function passUsesCandidate(pass) {
|
|
3578
|
-
return pass.kind === "candidate" || pass.modes.includes("candidate-implementation") || pass.decision === "candidate-exact-path" || pass.decision === "candidate-worktree" || pass.decision === "tournament";
|
|
3579
|
-
}
|
|
3580
|
-
function passUsesVerifier(pass) {
|
|
3581
|
-
return pass.kind === "verification" || pass.modes.includes("verifier");
|
|
3582
|
-
}
|
|
3583
3765
|
function recordOrchestrationPasses(budget, passes) {
|
|
3584
3766
|
if (passes.length === 0)
|
|
3585
3767
|
return budget;
|
|
3586
|
-
const seenPassIds = new Set(
|
|
3768
|
+
const seenPassIds = new Set([
|
|
3769
|
+
...budget.orchestration.recordedPassIds,
|
|
3770
|
+
...budget.orchestration.latestPasses.map((pass) => pass.id)
|
|
3771
|
+
]);
|
|
3587
3772
|
const newPasses = [];
|
|
3588
3773
|
for (const pass of passes) {
|
|
3589
3774
|
if (seenPassIds.has(pass.id))
|
|
@@ -3593,15 +3778,52 @@ function recordOrchestrationPasses(budget, passes) {
|
|
|
3593
3778
|
}
|
|
3594
3779
|
if (newPasses.length === 0)
|
|
3595
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
|
+
}
|
|
3596
3811
|
const latestPasses = [...budget.orchestration.latestPasses, ...newPasses];
|
|
3597
3812
|
return {
|
|
3598
3813
|
...budget,
|
|
3599
3814
|
orchestration: {
|
|
3600
3815
|
passCount: budget.orchestration.passCount + newPasses.length,
|
|
3601
|
-
workerCount: budget.orchestration.workerCount +
|
|
3602
|
-
candidatePassCount: budget.orchestration.candidatePassCount +
|
|
3603
|
-
verifierPassCount: budget.orchestration.verifierPassCount +
|
|
3604
|
-
|
|
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
|
+
],
|
|
3605
3827
|
latestPasses: latestPasses.length > MAX_LATEST_ORCHESTRATION_PASSES ? latestPasses.slice(latestPasses.length - MAX_LATEST_ORCHESTRATION_PASSES) : latestPasses
|
|
3606
3828
|
}
|
|
3607
3829
|
};
|
|
@@ -4581,4 +4803,4 @@ export {
|
|
|
4581
4803
|
plugin_default as default
|
|
4582
4804
|
};
|
|
4583
4805
|
|
|
4584
|
-
//# debugId=
|
|
4806
|
+
//# debugId=C77708A78E8740C664756E2164756E21
|