opencode-plugin-flow 4.3.4 → 4.3.5

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
@@ -169,7 +169,7 @@ stay separate:
169
169
  | --- | --- | --- |
170
170
  | Repo shape is unclear before planning | Discovery pass | Requirements, decisions, targets, validation entries, or a review-first feature |
171
171
  | A broad finding set needs refutation | Audit pass | Surviving findings with guards checked and gaps named |
172
- | Changed files or risk lenses are too broad for one review pass | Review pass | One \`featureReview\` or \`finalReview\` payload owned by the manager |
172
+ | Changed files or risk lenses are too broad for one review pass | Review pass | One feature review packet or \`finalReview\` payload owned by the manager |
173
173
  | Test strategy or route coverage is unclear | Validation pass | Candidate commands or authorized raw command evidence |
174
174
  | A claim is single-source, surprising, high-stakes, or payload-bound | Verification pass | Per-claim keep, narrow, rewrite, or remove decisions |
175
175
  | Multiple implementation paths are plausible | Candidate pass | Candidate patches inspected and validated by the manager before use |
@@ -403,7 +403,7 @@ Where accepted evidence goes:
403
403
  - Validation evidence may become \`validationRun\` only when the worker was
404
404
  explicitly authorized to run the command and reported the exact command,
405
405
  status, and raw outcome summary.
406
- - Review evidence informs \`featureReview\` or \`finalReview\`, but the manager
406
+ - Review evidence informs \`featureReviewDepth\` plus \`featureReview\`, or \`finalReview\`, but the manager
407
407
  owns the pass/fail verdict and must resolve blockers, contradictions, and
408
408
  coverage gaps before returning the payload.
409
409
  - Audit evidence becomes findings only after refutation; blocking findings need
@@ -530,10 +530,10 @@ suggestions do not move into the next pass or user-facing answer.
530
530
  `;
531
531
 
532
532
  // skills/flow/references/recovery-playbook.md
533
- var recovery_playbook_default = '# Recovery playbook\n\nUse this when a Flow tool returns `status: "error"`, a blocker, or a `nextAction` that conflicts with memory.\n\n## First response\n\n1. Re-anchor with `flow_status`.\n2. Read the returned `summary`, `recovery`, `lastError`, and active feature.\n3. Fix the cause, then retry the smallest valid Flow action.\n\n## Common cases\n\n- `missing_session`: start with `flow_plan_save` using the user\'s goal.\n- `missing_goal`: ask for a concrete goal before planning.\n- `Approved plans cannot be changed`: use `flow_feature_reset` when only affected features need another pass; otherwise close and start a new goal.\n- `No feature is currently running`: call `flow_run_start` before completing.\n- `already in progress`: finish, reset, or block the active feature before starting another.\n- `Completion requires recorded validation evidence`: run real validation and include at least one passing `validationRun`.\n- `Completion requires all recorded validation to pass`: fix failures and rerun. Do not relabel failed checks as passed.\n- `Non-final feature completion requires targeted validation`: use `validationScope: "targeted"` for ordinary features.\n- `Final feature completion requires broad validation`: run the project-level gate and use `validationScope: "broad"`.\n- `Completion requires a passing featureReview`: run or request a real review and include a passing `featureReview` only when there are no blocking findings.\n- `Final feature completion requires a finalReview`: perform final review and include `finalReview`.\n- `Final review depth must match the plan policy`: use `reviewDepth` equal to the approved plan\'s `finalReviewPolicy`; valid final-review values are `broad` and `detailed`.\n- `Cannot close ... unfinished features`: complete, reset, defer, or abandon honestly. Do not mark completed while work remains.\n\n## Reset guidance\n\nUse `flow_feature_reset` when the active or completed work was built on the wrong assumption, validation revealed a design issue, dependencies need to be rerun, or dependent features must be invalidated. Resetting a feature also resets its dependents.\n\n## Closure guidance\n\nUse `flow_session_close`:\n\n- `completed`: only after all planned features are complete.\n- `deferred`: the user intentionally postpones unfinished work.\n- `abandoned`: the session should be archived without claiming delivery.\n\nAfter closure, the active `.flow/session.json` is removed and the archived JSON is stored under `.flow/history/`.\n';
533
+ var recovery_playbook_default = '# Recovery playbook\n\nUse this when a Flow tool returns `status: "error"`, a blocker, or a `nextAction` that conflicts with memory.\n\n## First response\n\n1. Re-anchor with `flow_status`.\n2. Read the returned `summary`, `recovery`, `lastError`, and active feature.\n3. Fix the cause, then retry the smallest valid Flow action.\n\n## Common cases\n\n- `missing_session`: start with `flow_plan_save` using the user\'s goal.\n- `missing_goal`: ask for a concrete goal before planning.\n- `Approved plans cannot be changed`: use `flow_feature_reset` when only affected features need another pass; otherwise close and start a new goal.\n- `No feature is currently running`: call `flow_run_start` before completing.\n- `already in progress`: finish, reset, or block the active feature before starting another.\n- `Completion requires recorded validation evidence`: run real validation and include at least one passing `validationRun`.\n- `Completion requires all recorded validation to pass`: fix failures and rerun. Do not relabel failed checks as passed.\n- `Non-final feature completion requires targeted validation`: use `validationScope: "targeted"` for ordinary features.\n- `Final feature completion requires broad validation`: run the project-level gate and use `validationScope: "broad"`.\n- `Feature review depth ... does not meet the plan requirement`: rerun review\n at the feature\'s planned depth or reset/replan if the depth was chosen\n incorrectly.\n- `Completion requires a passing featureReview`: run or request a real review and include a passing `featureReview` only when there are no blocking findings.\n- `Review retry budget exhausted`: stop and report the remaining blocker. Do\n not keep patching; reset or replan only after explicit user direction.\n- `Final feature completion requires a finalReview`: perform final review and include `finalReview`.\n- `Final review depth must match the plan policy`: use `reviewDepth` equal to the approved plan\'s `finalReviewPolicy`; valid final-review values are `broad` and `detailed`.\n- `Completed ... features since the last Flow checkpoint`: stop the current\n root session and resume from `.flow/session.json` in a fresh OpenCode session.\n- `Cannot close ... unfinished features`: complete, reset, defer, or abandon honestly. Do not mark completed while work remains.\n\n## Reset guidance\n\nUse `flow_feature_reset` when the active or completed work was built on the wrong assumption, validation revealed a design issue, dependencies need to be rerun, or dependent features must be invalidated. Resetting a feature also resets its dependents.\n\n## Closure guidance\n\nUse `flow_session_close`:\n\n- `completed`: only after all planned features are complete.\n- `deferred`: the user intentionally postpones unfinished work.\n- `abandoned`: the session should be archived without claiming delivery.\n\nAfter closure, the active `.flow/session.json` is removed and the archived JSON is stored under `.flow/history/`.\n';
534
534
 
535
535
  // skills/flow/SKILL.md
536
- var SKILL_default = "---\nname: flow\ndescription: Manage the end-to-end Flow loop for skills-first OpenCode work. Use when a user asks for Flow-guided delivery from goal to completion, resumable autonomous delivery, or resuming or closing a Flow session. For plan-only work use flow-plan; for executing one approved feature use flow-run.\n---\n\n# Flow\n\nUse Flow as a minimal state ledger, not as a framework. Skills provide judgment; the runtime only records the approved plan, active feature, validation evidence, review evidence, and closure.\n\nRouting: this manager skill owns the whole loop and every state-changing `flow_*` call. Load `flow-plan` alone for plan-only requests and `flow-run` alone when an approved plan needs one feature executed. Answer status-only questions with `flow_status`; no skill load is needed. `flow-test`, `flow-deslop`, and `flow-ui-quality` are optional helpers loaded from inside the loop; `flow-commit` is user-triggered only and never part of the autonomous loop.\n\n## Loop\n\n1. Call `flow_status` first. Trust its active session and next action over conversation memory.\n If the result includes `setup.skills`, follow the Skill Availability rules\n below before loading any Flow skill.\n2. If there is no active session and the user gave a goal, load `flow-plan`, save a plan with `flow_plan_save`, then approve it with `flow_plan_approve` only after explicit user approval or prior authorization for autonomous implementation. If there is no goal, ask for one.\n3. Load `flow-run`, call `flow_run_start`, implement exactly one feature, validate it, and prepare a `flow_feature_complete` payload. For validation-heavy, regression-sensitive, browser QA, route QA, or failure-prone work, use `flow-test` to choose and summarize evidence before completion.\n4. Load `flow-review` for the required feature review. The reviewer reports a `featureReview` payload; the manager records it inside `flow_feature_complete`.\n5. On the final feature, run broad validation and include `finalReview` in the same `flow_feature_complete` call. Its `reviewDepth` must match the plan's `finalReviewPolicy`.\n6. After all features are complete, archive the session with `flow_session_close` using `kind: \"completed\"`.\n\nUse `references/parallel-orchestration.md` for broad read-only discovery, audit, validation, review, verification, or candidate implementation passes. It is the whole pass playbook — orient, slice, manifest, fan out, account, verify, synthesize, extend or stop — and its `references/handoff-format.md` companion holds the worker report templates the manager pastes into prompts. Hidden Flow workers are injected by plugin config; invoke the named worker when it is available. The manager owns every `flow_*` state change.\n\nDo not commit, push, amend, rebase, publish, or mutate releases during the\nautonomous Flow loop. Load `flow-commit` only when the user explicitly asks for\ncommit preparation or commit creation.\n\n## Skill Availability\n\nIf `flow_status` returns `setup.skills`, report that setup status and stop\nnative-loading Flow skills in the current OpenCode startup. Missing, incomplete,\nor outdated managed skills require a sync/restart cycle before their native skill\ninstructions can be trusted by the running process. Public command bundles are\nself-contained and may continue when the command prompt already embeds the\nrequired Flow instructions.\n\nIf optional helper skills such as `flow-test`, `flow-deslop`, or\n`flow-ui-quality` are unavailable, continue only with explicit coverage gaps. Do\nnot copy their rubrics into another skill and do not claim their quality checks\nwere completed.\n\n## Runtime Surface\n\n- `flow_status`: read the active session.\n- `flow_plan_save`: create a session and/or save a draft plan.\n- `flow_plan_approve`: lock the draft plan.\n- `flow_run_start`: start one runnable feature.\n- `flow_feature_complete`: record completion or a real blocker with validation and review evidence.\n- `flow_feature_reset`: reset one feature and its dependents.\n- `flow_session_close`: archive the active session as `completed`, `deferred`, or `abandoned`.\n\nThere is no `flow_context`, no separate review-record tool, and no multi-session activation surface. The single active source of truth is `.flow/session.json`; closed sessions are archived under `.flow/history/`.\n\nPlanning and running require loaded Flow tools; do not simulate plan approval or feature completion when the runtime is unavailable. Review may still return advisory output when tools, skills, or references are stale or unavailable, but the manager must not record it as Flow-gated evidence.\n\n## Hard Gates\n\n- Approved plans are immutable. To change direction, reset affected features or close the session and start a new goal.\n- Only one feature can be active at a time.\n- Completion requires at least one passing `validationRun` entry.\n- Non-final completion requires `validationScope: \"targeted\"`.\n- Final completion requires `validationScope: \"broad\"` and a passing `finalReview`.\n- Every completed feature requires a passing `featureReview` with no blocking findings.\n- `flow_session_close` accepts `kind: \"completed\"` only after an approved plan has passed final completion.\n\n## Recovery\n\n- Confused state: call `flow_status` and follow `nextAction`.\n- Wrong assumption or failed implementation path: use `flow_feature_reset` for the feature and dependents, then rerun from the corrected plan.\n- Missing validation or review evidence: gather real evidence, then call `flow_feature_complete`.\n- Approved plan is materially wrong: reset the affected features, save a revised plan if the session is back in planning; otherwise close and start a new goal.\n- Unknown runtime error: read `summary` and `recovery`; see `references/recovery-playbook.md` for common cases.\n\nNever fabricate validation output, backfill review approval you did not perform, or close as `deferred`/`abandoned` merely to avoid an unfinished-work blocker.\n";
536
+ var SKILL_default = "---\nname: flow\ndescription: Manage the end-to-end Flow loop for skills-first OpenCode work. Use when a user asks for Flow-guided delivery from goal to completion, resumable autonomous delivery, or resuming or closing a Flow session. For plan-only work use flow-plan; for executing one approved feature use flow-run.\n---\n\n# Flow\n\nUse Flow as a minimal state ledger, not as a framework. Skills provide judgment; the runtime only records the approved plan, active feature, validation evidence, review evidence, and closure.\n\nRouting: this manager skill owns the whole loop and every state-changing `flow_*` call. Load `flow-plan` alone for plan-only requests and `flow-run` alone when an approved plan needs one feature executed. Answer status-only questions with `flow_status`; no skill load is needed. `flow-test`, `flow-deslop`, and `flow-ui-quality` are optional helpers loaded from inside the loop; `flow-commit` is user-triggered only and never part of the autonomous loop.\n\n## Loop\n\n1. Call `flow_status` first. Trust its active session and next action over conversation memory.\n If the result includes `setup.skills`, follow the Skill Availability rules\n below before loading any Flow skill.\n If it includes `session.resumePacket` or `session.budget.phaseBoundary`, stop\n and report the resume instructions unless this is a fresh user invocation\n explicitly resuming the session. Only then may the next `flow_run_start` use\n `phaseBoundaryAck: true`.\n2. If there is no active session and the user gave a goal, load `flow-plan`, save a plan with `flow_plan_save`, then approve it with `flow_plan_approve` only after explicit user approval or prior authorization for autonomous implementation. If there is no goal, ask for one.\n3. Load `flow-run`, call `flow_run_start`, implement exactly one feature, validate it, and prepare a `flow_feature_complete` payload. For validation-heavy, regression-sensitive, browser QA, route QA, or failure-prone work, use `flow-test` to choose and summarize evidence before completion.\n4. Load `flow-review` for the required feature review. Send a compact review\n packet, not the accumulated root transcript. The reviewer reports\n `featureReviewDepth` and `featureReview`; the manager records both inside\n `flow_feature_complete`.\n5. On the final feature, run broad validation and include `finalReview` in the same `flow_feature_complete` call. Its `reviewDepth` must match the plan's `finalReviewPolicy`.\n6. After all features are complete, archive the session with `flow_session_close` using `kind: \"completed\"`.\n\nUse `references/parallel-orchestration.md` for broad read-only discovery, audit, validation, review, verification, or candidate implementation passes. It is the whole pass playbook — orient, slice, manifest, fan out, account, verify, synthesize, extend or stop — and its `references/handoff-format.md` companion holds the worker report templates the manager pastes into prompts. Hidden Flow workers are injected by plugin config; invoke the named worker when it is available. The manager owns every `flow_*` state change.\n\nDo not commit, push, amend, rebase, publish, or mutate releases during the\nautonomous Flow loop. Load `flow-commit` only when the user explicitly asks for\ncommit preparation or commit creation.\n\n## Skill Availability\n\nIf `flow_status` returns `setup.skills`, report that setup status and stop\nnative-loading Flow skills in the current OpenCode startup. Missing, incomplete,\nor outdated managed skills require a sync/restart cycle before their native skill\ninstructions can be trusted by the running process. Public command bundles are\nself-contained and may continue when the command prompt already embeds the\nrequired Flow instructions.\n\nIf optional helper skills such as `flow-test`, `flow-deslop`, or\n`flow-ui-quality` are unavailable, continue only with explicit coverage gaps. Do\nnot copy their rubrics into another skill and do not claim their quality checks\nwere completed.\n\n## Runtime Surface\n\n- `flow_status`: read the active session.\n- `flow_plan_save`: create a session and/or save a draft plan.\n- `flow_plan_approve`: lock the draft plan.\n- `flow_run_start`: start one runnable feature.\n- `flow_feature_complete`: record completion or a real blocker with validation and review evidence.\n- `flow_feature_reset`: reset one feature and its dependents.\n- `flow_session_close`: archive the active session as `completed`, `deferred`, or `abandoned`.\n\nThere is no `flow_context`, no separate review-record tool, and no multi-session activation surface. The single active source of truth is `.flow/session.json`; closed sessions are archived under `.flow/history/`.\n\nPlanning and running require loaded Flow tools; do not simulate plan approval or feature completion when the runtime is unavailable. Review may still return advisory output when tools, skills, or references are stale or unavailable, but the manager must not record it as Flow-gated evidence.\n\n## Hard Gates\n\n- Approved plans are immutable. To change direction, reset affected features or close the session and start a new goal.\n- Only one feature can be active at a time.\n- Each feature's planned `reviewDepth` is the minimum accepted\n `featureReviewDepth` for completion.\n- Completion requires at least one passing `validationRun` entry.\n- Non-final completion requires `validationScope: \"targeted\"`.\n- Final completion requires `validationScope: \"broad\"` and a passing `finalReview`.\n- Every completed feature requires a passing `featureReview` with no blocking findings.\n- Failed reviews pause the loop by default. Autonomous repair may make at most\n one repair plus one retry review before stopping.\n- Phase boundaries stop the current root session; resume from\n `.flow/session.json` in a fresh OpenCode session.\n- `flow_session_close` accepts `kind: \"completed\"` only after an approved plan has passed final completion.\n\n## Budget And Retry Boundaries\n\nFlow can enforce review retry counts and feature-count phase boundaries in the\nruntime ledger. The current OpenCode plugin surface does not expose per-turn\ntoken usage to Flow; when usage is visible in the host UI or logs, stop the\ncurrent autonomous loop once the root session is large enough to threaten\nlatency or cost and report a compact handoff instead of continuing. Treat high\nvisible tokens, high non-cache tokens, or repeated reviewer fan-out as a reason\nto finish the current feature, emit the resume packet, and continue in a fresh\nsession.\n\n## Recovery\n\n- Confused state: call `flow_status` and follow `nextAction`.\n- Wrong assumption or failed implementation path: use `flow_feature_reset` for the feature and dependents, then rerun from the corrected plan.\n- Missing validation or review evidence: gather real evidence, then call `flow_feature_complete`.\n- Approved plan is materially wrong: reset the affected features, save a revised plan if the session is back in planning; otherwise close and start a new goal.\n- Unknown runtime error: read `summary` and `recovery`; see `references/recovery-playbook.md` for common cases.\n\nNever fabricate validation output, backfill review approval you did not perform, or close as `deferred`/`abandoned` merely to avoid an unfinished-work blocker.\n";
537
537
 
538
538
  // skills/flow-commit/SKILL.md
539
539
  var SKILL_default2 = `---
@@ -1198,6 +1198,7 @@ Call \`flow_plan_save\` with:
1198
1198
  "id": "lowercase-kebab-case",
1199
1199
  "title": "Short title",
1200
1200
  "summary": "Outcome this feature delivers",
1201
+ "reviewDepth": "standard",
1201
1202
  "targets": ["files, modules, routes, commands, or docs in scope"],
1202
1203
  "validation": ["focused checks expected before completion"],
1203
1204
  "dependsOn": []
@@ -1209,6 +1210,14 @@ Call \`flow_plan_save\` with:
1209
1210
 
1210
1211
  Use only \`finalReviewPolicy: "broad"\` or \`"detailed"\`. These are the canonical final-review policy and \`reviewDepth\` enum values. Use \`"broad"\` only for low-risk, narrow work. Use \`"detailed"\` for behavioral changes, cross-module edits, migrations, releases, security-sensitive code, or large refactors.
1211
1212
 
1213
+ Set each feature's \`reviewDepth\` to one of:
1214
+
1215
+ - \`quick\`: docs, comments, config-only changes, generated output, or mechanical changes fully covered by tooling.
1216
+ - \`standard\`: the default for ordinary implementation slices. The review reads every changed file and relevant tests.
1217
+ - \`detailed\`: persistence, migrations, concurrency, security, cross-module behavior, release/package surfaces, large refactors, weak validation, or any work where a missed edge case would be expensive.
1218
+
1219
+ Do not make reviews shallower to save tokens. Reduce token use by splitting features, keeping \`targets\` precise, and using scoped review packets during execution.
1220
+
1212
1221
  ## Plan quality gate
1213
1222
 
1214
1223
  Before saving or asking for approval, load
@@ -1228,6 +1237,10 @@ to execute.
1228
1237
  validation entry should name the expected test level, such as targeted unit,
1229
1238
  integration, browser/e2e, package/build, docs/static, cleanup preservation, or
1230
1239
  broad project gate.
1240
+ - Assign \`reviewDepth\` from risk. Use \`detailed\` for persistence, migration,
1241
+ concurrency, security, final-delivery-adjacent, or cross-module slices; use
1242
+ \`standard\` for normal code changes; reserve \`quick\` for low-risk non-behavioral
1243
+ work.
1231
1244
 
1232
1245
  ## Approval
1233
1246
 
@@ -1312,9 +1325,12 @@ Feature review:
1312
1325
 
1313
1326
  \`\`\`json
1314
1327
  {
1315
- "status": "passed",
1316
- "summary": "Reviewed changed runtime files and focused tests; validation covers the new gate.",
1317
- "blockingFindings": []
1328
+ "featureReviewDepth": "standard",
1329
+ "featureReview": {
1330
+ "status": "passed",
1331
+ "summary": "Reviewed changed runtime files and focused tests; validation covers the new gate.",
1332
+ "blockingFindings": []
1333
+ }
1318
1334
  }
1319
1335
  \`\`\`
1320
1336
 
@@ -1322,14 +1338,17 @@ Failed feature review:
1322
1338
 
1323
1339
  \`\`\`json
1324
1340
  {
1325
- "status": "failed",
1326
- "summary": "Validation does not exercise the changed persistence path.",
1327
- "blockingFindings": [
1328
- {
1329
- "summary": "No test covers archive removal of .flow/session.json after close.",
1330
- "severity": "blocking"
1331
- }
1332
- ]
1341
+ "featureReviewDepth": "detailed",
1342
+ "featureReview": {
1343
+ "status": "failed",
1344
+ "summary": "Validation does not exercise the changed persistence path.",
1345
+ "blockingFindings": [
1346
+ {
1347
+ "summary": "No test covers archive removal of .flow/session.json after close.",
1348
+ "severity": "blocking"
1349
+ }
1350
+ ]
1351
+ }
1333
1352
  }
1334
1353
  \`\`\`
1335
1354
 
@@ -1389,6 +1408,11 @@ These instructions run in two contexts, and only one of them can load helpers:
1389
1408
 
1390
1409
  - Call \`flow_status\` when available.
1391
1410
  - Identify whether this is a feature review or final review.
1411
+ - Prefer the manager's compact review packet over parent-session memory. The
1412
+ packet should name the active feature, minimum \`reviewDepth\`, changed files,
1413
+ diff summary, validation evidence, and targeted paths or risk lenses. If the
1414
+ packet is missing important scope or evidence, record that as a coverage gap
1415
+ or blocker instead of searching the full conversation transcript.
1392
1416
  - Read the approved plan fields relevant to the work: \`requirements\`, \`decisions\`, feature \`targets\`, feature \`validation\`, and dependencies.
1393
1417
  - For final review, also compare the original goal, full feature list, completed
1394
1418
  feature evidence, changed artifacts, and final validation against the
@@ -1410,16 +1434,23 @@ These instructions run in two contexts, and only one of them can load helpers:
1410
1434
 
1411
1435
  ## Output
1412
1436
 
1413
- For a feature review, return:
1437
+ For a feature review, return a packet the manager can copy into
1438
+ \`flow_feature_complete\`:
1414
1439
 
1415
1440
  \`\`\`json
1416
1441
  {
1417
- "status": "passed",
1418
- "summary": "what was reviewed and why it is acceptable",
1419
- "blockingFindings": []
1442
+ "featureReviewDepth": "standard",
1443
+ "featureReview": {
1444
+ "status": "passed",
1445
+ "summary": "what was reviewed and why it is acceptable",
1446
+ "blockingFindings": []
1447
+ }
1420
1448
  }
1421
1449
  \`\`\`
1422
1450
 
1451
+ \`featureReviewDepth\` must be at least the feature's planned \`reviewDepth\`.
1452
+ Use the actual depth performed: \`quick\`, \`standard\`, or \`detailed\`.
1453
+
1423
1454
  For a final review, return:
1424
1455
 
1425
1456
  \`\`\`json
@@ -1571,96 +1602,7 @@ Never trim failing output, relabel a failed command as passed, or use "not run"
1571
1602
  `;
1572
1603
 
1573
1604
  // skills/flow-run/SKILL.md
1574
- var SKILL_default6 = `---
1575
- name: flow-run
1576
- description: "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."
1577
- ---
1578
-
1579
- # Flow Run
1580
-
1581
- Use this skill for implementation after a Flow plan is approved. Work one feature at a time.
1582
-
1583
- If \`flow_run_start\` is unavailable, stop and tell the user to check that \`opencode-plugin-flow\` is loaded in OpenCode.
1584
-
1585
- ## Start
1586
-
1587
- - Call \`flow_status\`.
1588
- - Call \`flow_run_start\` with no \`featureId\` unless the user or plan requires a specific runnable feature.
1589
- - Treat the returned feature as the sole scope until it is completed, blocked, or reset.
1590
- - Helper rule: when a named helper skill is unavailable, record the gap and
1591
- keep the corresponding claims conservative instead of simulating its checks.
1592
- - Load \`flow-deslop\` for cleanup/refactor features.
1593
- - Load \`flow-ui-quality\` for frontend, UX, responsive, accessibility, or visual work.
1594
-
1595
- ## Implement
1596
-
1597
- - Read the feature \`targets\`, \`summary\`, \`validation\`, dependencies, and plan \`requirements\`/\`decisions\`.
1598
- - Keep edits scoped to the active feature. If new scope appears, stop and replan or defer it to another feature.
1599
- - Preserve unrelated user changes in the worktree.
1600
- - When a wrong assumption invalidates the feature, use \`flow_feature_reset\`; do not pile patches onto a bad path.
1601
- - Do not stage, commit, push, amend, rebase, publish, or mutate releases as part
1602
- of feature execution. If the user explicitly asks for commit preparation, load
1603
- \`flow-commit\` only after \`flow_feature_complete\` has been recorded, unless the
1604
- user explicitly asks for a WIP commit path. Keep Git boundaries separate from
1605
- Flow state recording.
1606
-
1607
- ## Validate
1608
-
1609
- - For complex validation, regression-sensitive changes, browser QA, route QA,
1610
- failure-prone checks, unclear coverage, exploratory QA, or
1611
- \`validationRun\` summarization, load \`flow-test\` (helper rule applies).
1612
- - Read \`references/validation-rubric.md\` before completing.
1613
- - Run the strongest practical checks for the changed behavior.
1614
- - Record concrete command names, status, and observed results. "Tests pass" is not evidence.
1615
- - Non-final features complete with \`validationScope: "targeted"\`.
1616
- - The final feature must run a broad project-level gate and use \`validationScope: "broad"\`.
1617
-
1618
- For broad validation research, risky changes, or unclear coverage, use
1619
- \`../flow/references/parallel-orchestration.md\` to fan out named Flow workers.
1620
- Use the mode-to-agent mapping in that reference instead of generic subagents.
1621
- Write its pass manifest before fan-out, paste the matching handoff template
1622
- from \`../flow/references/handoff-format.md\` into every worker prompt, and
1623
- apply its verification tiers to the handoffs that come back.
1624
- They may report command output they actually ran or propose focused checks; the
1625
- manager decides what is strong enough to record.
1626
-
1627
- For independent implementation attempts, use candidate workers only with
1628
- explicit user authorization plus isolated worktrees or exact non-overlapping
1629
- path ownership. Treat their output as candidate patches. The manager inspects,
1630
- merges, validates, and records Flow state serially.
1631
-
1632
- ## Review and complete
1633
-
1634
- Before \`flow_feature_complete\`, obtain a \`featureReview\` payload. Load
1635
- \`flow-review\`; for read-only subagent reviews, the manager receives the payload
1636
- and records it.
1637
-
1638
- If \`flow_status\` reports \`setup.skills\` or \`flow-review\` cannot be loaded, do
1639
- not record a Flow-gated \`featureReview\` or \`finalReview\`. You may perform an
1640
- advisory review using available context or the bundled review fallback provided
1641
- by plugin config, then complete with \`status: "needs_input"\` if review evidence
1642
- is required to proceed.
1643
-
1644
- For the final feature, also obtain a \`finalReview\` payload whose \`reviewDepth\` equals the approved plan's \`finalReviewPolicy\`.
1645
-
1646
- Complete with:
1647
-
1648
- \`\`\`json
1649
- {
1650
- "status": "ok",
1651
- "featureId": "active-feature-id",
1652
- "summary": "what changed",
1653
- "artifactsChanged": [{ "path": "src/file.ts" }],
1654
- "validationRun": [
1655
- { "command": "bun test tests/foo.test.ts", "status": "passed", "summary": "3 pass, exercised foo behavior" }
1656
- ],
1657
- "validationScope": "targeted",
1658
- "featureReview": { "status": "passed", "summary": "review summary", "blockingFindings": [] }
1659
- }
1660
- \`\`\`
1661
-
1662
- If genuinely blocked, call \`flow_feature_complete\` with \`status: "needs_input"\` and an \`outcome\` that explains the blocker and next step. Never fabricate validation or review evidence to force progress.
1663
- `;
1605
+ 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';
1664
1606
 
1665
1607
  // skills/flow-test/SKILL.md
1666
1608
  var SKILL_default7 = `---
@@ -2091,6 +2033,7 @@ var FLOW_AUTO_BUNDLED_INSTRUCTIONS = bundledFlowInstructions([
2091
2033
  ]);
2092
2034
  var FLOW_SELF_CONTAINED_COMMAND_PREFLIGHT = [
2093
2035
  "Call `flow_status` first. If the result includes `setup.skills`, report the setup status and continue with the bundled public Flow command instructions below.",
2036
+ "If `flow_status` includes `session.resumePacket` or `session.budget.phaseBoundary`, stop the current autonomous loop and report the resume instructions unless this is a fresh user invocation explicitly resuming the session; only then may `flow_run_start` use `phaseBoundaryAck: true`.",
2094
2037
  "After `flow_status`, briefly state which bundled Flow command is running and for what goal, then continue.",
2095
2038
  "Do not call native Flow skills for `flow`, `flow-plan`, `flow-run`, or `flow-review` from public Flow commands. In bundled sections, `load` means read and use the corresponding bundled section in this command, and missing native public Flow skills are not blockers.",
2096
2039
  "Optional helper skills (`flow-test`, `flow-deslop`, `flow-ui-quality`, and user-triggered `flow-commit`) are not bundled fallbacks. If one is unavailable, record the coverage gap exactly as the bundled instructions require."
@@ -2111,6 +2054,7 @@ var FLOW_RUN_COMMAND_TEMPLATE = flowBundledCommandTemplate("Flow run", "Execute
2111
2054
  var FLOW_REVIEW_COMMAND_TEMPLATE = flowBundledCommandTemplate("Flow review", "Review: $ARGUMENTS", FLOW_REVIEW_BUNDLED_INSTRUCTIONS);
2112
2055
  var FLOW_REVIEW_AGENT_INSTRUCTIONS = [
2113
2056
  "Use Flow review mode. Call `flow_status` first. Do not call the native skill tool for `flow-review`; the canonical Flow review instructions and rubric are already embedded below. If Flow setup reports stale/unavailable skills, continue as advisory review only and do not present advisory review as Flow-gated `featureReview` or `finalReview` evidence.",
2057
+ "Prefer the manager's compact review packet over the accumulated root transcript. Return feature review packets with `featureReviewDepth` plus `featureReview`; final reviews still return `finalReview` with `reviewDepth`.",
2114
2058
  "When the manager assigns a parallel review slice instead of a direct Flow review command, cite or drop every claim, label single-source, inferred, and unsettled claims, and return only the assigned Flow handoff. Report blocked if the assigned scope, expected coverage, or handoff shape is missing.",
2115
2059
  "",
2116
2060
  "## Bundled Flow review instructions",
@@ -2657,6 +2601,11 @@ var SessionStatusSchema = z.enum([
2657
2601
  var ReviewStatusSchema = z.enum(["passed", "failed"]);
2658
2602
  var ValidationStatusSchema = z.enum(["passed", "failed"]);
2659
2603
  var ValidationScopeSchema = z.enum(["targeted", "broad"]);
2604
+ var FeatureReviewDepthSchema = z.enum([
2605
+ "quick",
2606
+ "standard",
2607
+ "detailed"
2608
+ ]);
2660
2609
  var FinalReviewPolicySchema = z.enum(["broad", "detailed"]);
2661
2610
  var ReviewFindingSchema = z.object({
2662
2611
  summary: z.string().min(1),
@@ -2683,6 +2632,7 @@ var FeatureSchema = z.object({
2683
2632
  title: z.string().min(1),
2684
2633
  summary: z.string().min(1),
2685
2634
  status: FeatureStatusSchema.default("pending"),
2635
+ reviewDepth: FeatureReviewDepthSchema.default("standard"),
2686
2636
  targets: z.array(z.string().min(1)).default([]),
2687
2637
  validation: z.array(z.string().min(1)).default([]),
2688
2638
  dependsOn: z.array(z.string().regex(FEATURE_ID_PATTERN)).default([])
@@ -2699,6 +2649,7 @@ var PlanInputSchema = PlanSchema.omit({ features: true }).extend({
2699
2649
  finalReviewPolicy: FinalReviewPolicySchema.optional(),
2700
2650
  features: z.array(FeatureSchema.omit({ status: true }).extend({
2701
2651
  status: FeatureStatusSchema.optional(),
2652
+ reviewDepth: FeatureReviewDepthSchema.optional(),
2702
2653
  targets: z.array(z.string().min(1)).optional(),
2703
2654
  validation: z.array(z.string().min(1)).optional(),
2704
2655
  dependsOn: z.array(z.string().regex(FEATURE_ID_PATTERN)).optional()
@@ -2722,6 +2673,7 @@ var WorkerResultSchema = z.discriminatedUnion("status", [
2722
2673
  artifactsChanged: z.array(ArtifactSchema).default([]),
2723
2674
  validationRun: z.array(ValidationRunSchema).default([]),
2724
2675
  validationScope: ValidationScopeSchema,
2676
+ featureReviewDepth: FeatureReviewDepthSchema,
2725
2677
  featureReview: ReviewSchema,
2726
2678
  finalReview: FinalReviewSchema.optional(),
2727
2679
  outcome: WorkerOutcomeSchema.optional()
@@ -2733,6 +2685,7 @@ var WorkerResultSchema = z.discriminatedUnion("status", [
2733
2685
  artifactsChanged: z.array(ArtifactSchema).default([]),
2734
2686
  validationRun: z.array(ValidationRunSchema).default([]),
2735
2687
  validationScope: ValidationScopeSchema.optional(),
2688
+ featureReviewDepth: FeatureReviewDepthSchema.optional(),
2736
2689
  featureReview: ReviewSchema.optional(),
2737
2690
  finalReview: FinalReviewSchema.optional(),
2738
2691
  outcome: NeedsInputOutcomeSchema
@@ -2754,10 +2707,37 @@ var ExecutionHistoryEntrySchema = z.object({
2754
2707
  artifactsChanged: z.array(ArtifactSchema).default([]),
2755
2708
  validationRun: z.array(ValidationRunSchema).default([]),
2756
2709
  validationScope: ValidationScopeSchema.optional(),
2710
+ featureReviewDepth: FeatureReviewDepthSchema.optional(),
2757
2711
  featureReview: ReviewSchema.optional(),
2758
2712
  finalReview: FinalReviewSchema.optional(),
2759
2713
  outcome: WorkerOutcomeSchema.optional()
2760
2714
  }).strict();
2715
+ var TokenTelemetrySchema = z.object({
2716
+ source: z.enum(["host_unavailable", "reported"]).default("host_unavailable"),
2717
+ visibleTokens: z.number().int().nonnegative().nullable().default(null),
2718
+ cacheReadTokens: z.number().int().nonnegative().nullable().default(null),
2719
+ nonCacheTokens: z.number().int().nonnegative().nullable().default(null)
2720
+ }).strict();
2721
+ var PhaseBoundarySchema = z.object({
2722
+ reason: z.enum(["feature_limit", "token_limit", "review_failure_limit"]),
2723
+ summary: z.string().min(1),
2724
+ resumeInstructions: z.string().min(1),
2725
+ recordedAt: z.string().min(1)
2726
+ }).strict();
2727
+ var BudgetTelemetrySchema = z.object({
2728
+ phaseStartedAt: z.string().min(1).default("unknown"),
2729
+ completedFeaturesSinceBoundary: z.number().int().nonnegative().default(0),
2730
+ reviewCount: z.number().int().nonnegative().default(0),
2731
+ failedReviewCount: z.number().int().nonnegative().default(0),
2732
+ failedReviewAttemptsByFeature: z.record(z.string().regex(FEATURE_ID_PATTERN, FEATURE_ID_MESSAGE), z.number().int().nonnegative()).default({}),
2733
+ tokenTelemetry: TokenTelemetrySchema.default({
2734
+ source: "host_unavailable",
2735
+ visibleTokens: null,
2736
+ cacheReadTokens: null,
2737
+ nonCacheTokens: null
2738
+ }),
2739
+ phaseBoundary: PhaseBoundarySchema.nullable().default(null)
2740
+ }).strict();
2761
2741
  var SessionSchema = z.object({
2762
2742
  version: z.literal(2),
2763
2743
  id: z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid session id."),
@@ -2767,6 +2747,20 @@ var SessionSchema = z.object({
2767
2747
  plan: PlanSchema.nullable(),
2768
2748
  activeFeatureId: z.string().regex(FEATURE_ID_PATTERN, FEATURE_ID_MESSAGE).nullable(),
2769
2749
  history: z.array(ExecutionHistoryEntrySchema).default([]),
2750
+ budget: BudgetTelemetrySchema.default({
2751
+ phaseStartedAt: "unknown",
2752
+ completedFeaturesSinceBoundary: 0,
2753
+ reviewCount: 0,
2754
+ failedReviewCount: 0,
2755
+ failedReviewAttemptsByFeature: {},
2756
+ tokenTelemetry: {
2757
+ source: "host_unavailable",
2758
+ visibleTokens: null,
2759
+ cacheReadTokens: null,
2760
+ nonCacheTokens: null
2761
+ },
2762
+ phaseBoundary: null
2763
+ }),
2770
2764
  closure: z.object({
2771
2765
  kind: z.enum(["completed", "deferred", "abandoned"]),
2772
2766
  summary: z.string().min(1),
@@ -3204,6 +3198,13 @@ function nowIso() {
3204
3198
 
3205
3199
  // src/runtime/transitions.ts
3206
3200
  var MAX_HISTORY_ENTRIES = 500;
3201
+ var MAX_FEATURES_PER_PHASE = 3;
3202
+ var MAX_FAILED_REVIEW_ATTEMPTS_PER_FEATURE = 2;
3203
+ var FEATURE_REVIEW_DEPTH_RANK = {
3204
+ quick: 0,
3205
+ standard: 1,
3206
+ detailed: 2
3207
+ };
3207
3208
  function appendHistory(history, entry) {
3208
3209
  const next = [...history, entry];
3209
3210
  return next.length > MAX_HISTORY_ENTRIES ? next.slice(next.length - MAX_HISTORY_ENTRIES) : next;
@@ -3217,11 +3218,42 @@ function historyEntryFor(worker, status) {
3217
3218
  artifactsChanged: worker.artifactsChanged,
3218
3219
  validationRun: worker.validationRun,
3219
3220
  validationScope: worker.validationScope,
3221
+ featureReviewDepth: worker.featureReviewDepth,
3220
3222
  featureReview: worker.featureReview,
3221
3223
  finalReview: worker.finalReview,
3222
3224
  outcome: worker.outcome
3223
3225
  };
3224
3226
  }
3227
+ function initialBudgetTelemetry() {
3228
+ return {
3229
+ phaseStartedAt: nowIso(),
3230
+ completedFeaturesSinceBoundary: 0,
3231
+ reviewCount: 0,
3232
+ failedReviewCount: 0,
3233
+ failedReviewAttemptsByFeature: {},
3234
+ tokenTelemetry: {
3235
+ source: "host_unavailable",
3236
+ visibleTokens: null,
3237
+ cacheReadTokens: null,
3238
+ nonCacheTokens: null
3239
+ },
3240
+ phaseBoundary: null
3241
+ };
3242
+ }
3243
+ function normalizeBudgetTelemetry(session) {
3244
+ const defaults = initialBudgetTelemetry();
3245
+ return {
3246
+ ...defaults,
3247
+ ...session.budget,
3248
+ failedReviewAttemptsByFeature: {
3249
+ ...session.budget.failedReviewAttemptsByFeature
3250
+ },
3251
+ tokenTelemetry: {
3252
+ ...defaults.tokenTelemetry,
3253
+ ...session.budget.tokenTelemetry
3254
+ }
3255
+ };
3256
+ }
3225
3257
  function ok(value) {
3226
3258
  return { ok: true, value };
3227
3259
  }
@@ -3246,6 +3278,7 @@ function clonePlan(input) {
3246
3278
  title: feature.title,
3247
3279
  summary: feature.summary,
3248
3280
  status: "pending",
3281
+ reviewDepth: feature.reviewDepth ?? "standard",
3249
3282
  targets: feature.targets ?? [],
3250
3283
  validation: feature.validation ?? [],
3251
3284
  dependsOn: feature.dependsOn ?? []
@@ -3299,6 +3332,7 @@ function createSession(goal) {
3299
3332
  plan: null,
3300
3333
  activeFeatureId: null,
3301
3334
  history: [],
3335
+ budget: initialBudgetTelemetry(),
3302
3336
  closure: null,
3303
3337
  lastError: null,
3304
3338
  timestamps: {
@@ -3329,6 +3363,7 @@ function applyPlan(session, planInput) {
3329
3363
  plan,
3330
3364
  activeFeatureId: null,
3331
3365
  history: [],
3366
+ budget: initialBudgetTelemetry(),
3332
3367
  closure: null,
3333
3368
  lastError: null,
3334
3369
  timestamps: { ...session.timestamps, completedAt: null }
@@ -3372,7 +3407,7 @@ function nextRunnableFeature(features, requestedId) {
3372
3407
  function updateFeature(features, featureId, status) {
3373
3408
  return features.map((feature) => feature.id === featureId ? { ...feature, status } : feature.status === "in_progress" && status === "in_progress" ? { ...feature, status: "pending" } : feature);
3374
3409
  }
3375
- function startRun(session, featureId) {
3410
+ function startRun(session, featureId, options) {
3376
3411
  if (session.status === "completed") {
3377
3412
  return fail("This Flow session is already completed.");
3378
3413
  }
@@ -3382,6 +3417,10 @@ function startRun(session, featureId) {
3382
3417
  if (session.status === "blocked") {
3383
3418
  return fail("Blocked features must be reset before rerun.", "Call flow_feature_reset for the blocked feature, then start it again.");
3384
3419
  }
3420
+ const budget = normalizeBudgetTelemetry(session);
3421
+ if (budget.phaseBoundary && !options?.phaseBoundaryAck) {
3422
+ return fail(budget.phaseBoundary.summary, budget.phaseBoundary.resumeInstructions);
3423
+ }
3385
3424
  if (session.activeFeatureId) {
3386
3425
  if (!featureId || featureId === session.activeFeatureId) {
3387
3426
  const active = session.plan.features.find((feature) => feature.id === session.activeFeatureId);
@@ -3401,6 +3440,12 @@ function startRun(session, featureId) {
3401
3440
  ...session,
3402
3441
  status: "running",
3403
3442
  plan: nextPlan,
3443
+ budget: budget.phaseBoundary ? {
3444
+ ...budget,
3445
+ phaseStartedAt: nowIso(),
3446
+ completedFeaturesSinceBoundary: 0,
3447
+ phaseBoundary: null
3448
+ } : budget,
3404
3449
  activeFeatureId: selected.value.id,
3405
3450
  lastError: null
3406
3451
  });
@@ -3417,6 +3462,12 @@ function finalFeature(session, featureId) {
3417
3462
  return false;
3418
3463
  return session.plan.features.every((feature) => feature.id === featureId || feature.status === "completed");
3419
3464
  }
3465
+ function activeFeature(session, featureId) {
3466
+ return session.plan?.features.find((feature) => feature.id === featureId) ?? null;
3467
+ }
3468
+ function reviewDepthMeetsRequirement(actual, required) {
3469
+ return FEATURE_REVIEW_DEPTH_RANK[actual] >= FEATURE_REVIEW_DEPTH_RANK[required];
3470
+ }
3420
3471
  function completionFailure(session, tool, message, recovery) {
3421
3472
  return fail(message, recovery, {
3422
3473
  ...session,
@@ -3425,6 +3476,8 @@ function completionFailure(session, tool, message, recovery) {
3425
3476
  }
3426
3477
  function validateCompletion(session, worker) {
3427
3478
  const wasFinal = finalFeature(session, worker.featureId);
3479
+ const feature = activeFeature(session, worker.featureId);
3480
+ const requiredReviewDepth = feature?.reviewDepth ?? "standard";
3428
3481
  if (worker.validationRun.length === 0) {
3429
3482
  return completionFailure(session, "flow_feature_complete", "Completion requires recorded validation evidence.", "Run the targeted or broad validation command and record the result.");
3430
3483
  }
@@ -3434,6 +3487,9 @@ function validateCompletion(session, worker) {
3434
3487
  if (!wasFinal && worker.validationScope !== "targeted") {
3435
3488
  return completionFailure(session, "flow_feature_complete", "Non-final feature completion requires targeted validation.", "Record validationScope: targeted for ordinary feature completion.");
3436
3489
  }
3490
+ if (!reviewDepthMeetsRequirement(worker.featureReviewDepth, requiredReviewDepth)) {
3491
+ return completionFailure(session, "flow_feature_complete", `Feature review depth '${worker.featureReviewDepth}' does not meet the plan requirement '${requiredReviewDepth}'.`, "Run the feature review at the planned depth or reset/replan if the depth is wrong.");
3492
+ }
3437
3493
  if (wasFinal && worker.validationScope !== "broad") {
3438
3494
  return completionFailure(session, "flow_feature_complete", "Final feature completion requires broad validation.", "Run the project-level gate and record validationScope: broad.");
3439
3495
  }
@@ -3454,6 +3510,85 @@ function validateCompletion(session, worker) {
3454
3510
  }
3455
3511
  return ok(undefined);
3456
3512
  }
3513
+ function incrementFailedReviewAttempt(session, worker, review, reviewKind) {
3514
+ const budget = normalizeBudgetTelemetry(session);
3515
+ const attempts = (budget.failedReviewAttemptsByFeature[worker.featureId] ?? 0) + 1;
3516
+ const exhausted = attempts >= MAX_FAILED_REVIEW_ATTEMPTS_PER_FEATURE;
3517
+ const nextBudget = {
3518
+ ...budget,
3519
+ failedReviewCount: budget.failedReviewCount + 1,
3520
+ failedReviewAttemptsByFeature: {
3521
+ ...budget.failedReviewAttemptsByFeature,
3522
+ [worker.featureId]: attempts
3523
+ },
3524
+ phaseBoundary: exhausted ? {
3525
+ reason: "review_failure_limit",
3526
+ summary: "Review retry budget exhausted. Stop and report the blocker before making more changes.",
3527
+ resumeInstructions: "Ask the user how to proceed, or reset the feature after an explicit decision. Do not keep auto-repairing this review failure.",
3528
+ recordedAt: nowIso()
3529
+ } : budget.phaseBoundary
3530
+ };
3531
+ if (!exhausted) {
3532
+ return {
3533
+ session: { ...session, budget: nextBudget },
3534
+ attempts,
3535
+ exhausted
3536
+ };
3537
+ }
3538
+ const entry = historyEntryFor({
3539
+ ...worker,
3540
+ summary: `${reviewKind === "final" ? "Final review" : "Feature review"} failed after ${attempts} attempts: ${review.summary}`,
3541
+ outcome: {
3542
+ kind: "blocked",
3543
+ summary: review.summary,
3544
+ resolutionHint: "Report the review blocker and wait for explicit reset, replan, or repair approval."
3545
+ }
3546
+ }, "blocked");
3547
+ return {
3548
+ session: touch({
3549
+ ...session,
3550
+ status: "blocked",
3551
+ activeFeatureId: null,
3552
+ plan: session.plan ? {
3553
+ ...session.plan,
3554
+ features: updateFeature(session.plan.features, worker.featureId, "blocked")
3555
+ } : session.plan,
3556
+ history: appendHistory(session.history, entry),
3557
+ budget: nextBudget
3558
+ }),
3559
+ attempts,
3560
+ exhausted
3561
+ };
3562
+ }
3563
+ function failedReviewCompletion(session, worker, review, reviewKind) {
3564
+ const failedReview = incrementFailedReviewAttempt(session, worker, review, reviewKind);
3565
+ const reviewName = reviewKind === "final" ? "finalReview" : "featureReview";
3566
+ return completionFailure(failedReview.session, "flow_feature_complete", failedReview.exhausted ? "Review retry budget exhausted for this feature." : `Completion requires a passing ${reviewName} with no blocking findings.`, failedReview.exhausted ? "Stop and report the remaining review blocker. Reset or replan only after explicit user direction." : `Pause and report the review blocker. If autonomous repair was explicitly authorized, make at most one repair and retry once; this was failed review attempt ${failedReview.attempts}/${MAX_FAILED_REVIEW_ATTEMPTS_PER_FEATURE}.`);
3567
+ }
3568
+ function clearFailedReviewAttempts(budget, featureId) {
3569
+ const { [featureId]: _cleared, ...remainingAttempts } = budget.failedReviewAttemptsByFeature;
3570
+ return {
3571
+ ...budget,
3572
+ failedReviewAttemptsByFeature: remainingAttempts
3573
+ };
3574
+ }
3575
+ function completionBudget(session, worker, allComplete) {
3576
+ const budget = clearFailedReviewAttempts(normalizeBudgetTelemetry(session), worker.featureId);
3577
+ const completedFeaturesSinceBoundary = budget.completedFeaturesSinceBoundary + 1;
3578
+ const reviewCount = budget.reviewCount + (worker.finalReview ? 2 : 1);
3579
+ const reachedFeatureLimit = !allComplete && completedFeaturesSinceBoundary >= MAX_FEATURES_PER_PHASE;
3580
+ return {
3581
+ ...budget,
3582
+ completedFeaturesSinceBoundary,
3583
+ reviewCount,
3584
+ phaseBoundary: reachedFeatureLimit ? {
3585
+ reason: "feature_limit",
3586
+ summary: `Completed ${completedFeaturesSinceBoundary} features since the last Flow checkpoint. Stop this OpenCode session and resume from .flow/session.json in a fresh session.`,
3587
+ resumeInstructions: "Start a fresh OpenCode session in this workspace, call flow_status, then call flow_run_start with phaseBoundaryAck: true to begin the next phase.",
3588
+ recordedAt: nowIso()
3589
+ } : budget.phaseBoundary
3590
+ };
3591
+ }
3457
3592
  function completeFeature(session, input) {
3458
3593
  if (!session.plan || session.status !== "running" || !session.activeFeatureId) {
3459
3594
  return fail("No feature is currently running.");
@@ -3461,7 +3596,7 @@ function completeFeature(session, input) {
3461
3596
  const parsed = WorkerResultSchema.safeParse(input);
3462
3597
  if (!parsed.success) {
3463
3598
  const issues = parsed.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".") || "payload"}: ${issue.message}`).join("; ");
3464
- return fail(`flow_feature_complete payload is invalid: ${issues}.`, 'Provide status, featureId, and summary. Results with status "ok" also need validationScope, at least one validationRun entry, and a featureReview; final features add a finalReview.');
3599
+ return fail(`flow_feature_complete payload is invalid: ${issues}.`, 'Provide status, featureId, and summary. Results with status "ok" also need validationScope, at least one validationRun entry, featureReviewDepth, and a featureReview; final features add a finalReview.');
3465
3600
  }
3466
3601
  const worker = parsed.data;
3467
3602
  if (worker.featureId !== session.activeFeatureId) {
@@ -3469,6 +3604,7 @@ function completeFeature(session, input) {
3469
3604
  }
3470
3605
  if (worker.status === "needs_input") {
3471
3606
  const entry2 = historyEntryFor(worker, "needs_input");
3607
+ const budget2 = normalizeBudgetTelemetry(session);
3472
3608
  return ok(touch({
3473
3609
  ...session,
3474
3610
  status: "blocked",
@@ -3478,9 +3614,16 @@ function completeFeature(session, input) {
3478
3614
  features: updateFeature(session.plan.features, worker.featureId, "blocked")
3479
3615
  },
3480
3616
  history: appendHistory(session.history, entry2),
3617
+ budget: budget2,
3481
3618
  lastError: null
3482
3619
  }));
3483
3620
  }
3621
+ if (!isPassingReview(worker.featureReview)) {
3622
+ return failedReviewCompletion(session, worker, worker.featureReview, "feature");
3623
+ }
3624
+ if (finalFeature(session, worker.featureId) && worker.finalReview && !isPassingReview(worker.finalReview)) {
3625
+ return failedReviewCompletion(session, worker, worker.finalReview, "final");
3626
+ }
3484
3627
  const validation = validateCompletion(session, worker);
3485
3628
  if (!validation.ok)
3486
3629
  return validation;
@@ -3488,12 +3631,14 @@ function completeFeature(session, input) {
3488
3631
  const features = updateFeature(session.plan.features, worker.featureId, "completed");
3489
3632
  const allComplete = features.every((feature) => feature.status === "completed");
3490
3633
  const now = nowIso();
3634
+ const budget = completionBudget(session, worker, allComplete);
3491
3635
  return ok(touch({
3492
3636
  ...session,
3493
3637
  status: allComplete ? "completed" : "ready",
3494
3638
  activeFeatureId: null,
3495
3639
  plan: { ...session.plan, features },
3496
3640
  history: appendHistory(session.history, entry),
3641
+ budget,
3497
3642
  closure: allComplete ? { kind: "completed", summary: worker.summary, recordedAt: now } : null,
3498
3643
  lastError: null,
3499
3644
  timestamps: {
@@ -3527,6 +3672,13 @@ function resetFeature(session, featureId) {
3527
3672
  const affected = dependentFeatureIds(session.plan.features, featureId);
3528
3673
  const activeFeatureId = session.activeFeatureId && affected.has(session.activeFeatureId) ? null : session.activeFeatureId;
3529
3674
  const nextFeatures = session.plan.features.map((feature) => affected.has(feature.id) ? { ...feature, status: "pending" } : feature);
3675
+ const budget = normalizeBudgetTelemetry(session);
3676
+ const failedReviewAttemptsByFeature = {
3677
+ ...budget.failedReviewAttemptsByFeature
3678
+ };
3679
+ for (const featureIdToClear of affected) {
3680
+ delete failedReviewAttemptsByFeature[featureIdToClear];
3681
+ }
3530
3682
  const nextStatus = session.approval !== "approved" ? "planning" : activeFeatureId ? "running" : nextFeatures.some((feature) => feature.status === "blocked") ? "blocked" : "ready";
3531
3683
  return ok(touch({
3532
3684
  ...session,
@@ -3536,6 +3688,11 @@ function resetFeature(session, featureId) {
3536
3688
  ...session.plan,
3537
3689
  features: nextFeatures
3538
3690
  },
3691
+ budget: {
3692
+ ...budget,
3693
+ failedReviewAttemptsByFeature,
3694
+ phaseBoundary: budget.phaseBoundary?.reason === "review_failure_limit" ? null : budget.phaseBoundary
3695
+ },
3539
3696
  closure: null,
3540
3697
  lastError: null,
3541
3698
  timestamps: { ...session.timestamps, completedAt: null }
@@ -3583,6 +3740,7 @@ function summarizeSession(session) {
3583
3740
  const latestHistoryEntry = session.history.at(-1) ?? null;
3584
3741
  const blockedEntry = session.status === "blocked" ? latestHistoryEntry : null;
3585
3742
  const active = session.activeFeatureId ? features.find((feature) => feature.id === session.activeFeatureId) : null;
3743
+ const budget = normalizeBudgetTelemetry(session);
3586
3744
  return {
3587
3745
  status: session.status,
3588
3746
  summary: session.closure?.summary ?? session.lastError?.summary ?? blockedEntry?.summary ?? session.plan?.summary ?? "Flow session is active.",
@@ -3596,6 +3754,30 @@ function summarizeSession(session) {
3596
3754
  activeFeature: active ?? null,
3597
3755
  progress: { completed: completed.length, total: features.length },
3598
3756
  features,
3757
+ budget: {
3758
+ phaseStartedAt: budget.phaseStartedAt,
3759
+ completedFeaturesSinceBoundary: budget.completedFeaturesSinceBoundary,
3760
+ reviewCount: budget.reviewCount,
3761
+ failedReviewCount: budget.failedReviewCount,
3762
+ failedReviewAttemptsByFeature: budget.failedReviewAttemptsByFeature,
3763
+ tokenTelemetry: {
3764
+ ...budget.tokenTelemetry,
3765
+ note: budget.tokenTelemetry.source === "host_unavailable" ? "OpenCode does not expose per-turn usage to this plugin surface; Flow can enforce feature/review checkpoints, but token thresholds remain manager-observed." : undefined
3766
+ },
3767
+ phaseBoundary: budget.phaseBoundary
3768
+ },
3769
+ resumePacket: budget.phaseBoundary ? {
3770
+ sessionId: session.id,
3771
+ goal: session.goal,
3772
+ status: session.status,
3773
+ activeFeatureId: session.activeFeatureId,
3774
+ progress: {
3775
+ completed: completed.length,
3776
+ total: features.length
3777
+ },
3778
+ phaseBoundary: budget.phaseBoundary,
3779
+ nextAction: "Start a fresh OpenCode session in this workspace, call flow_status, then call flow_run_start with phaseBoundaryAck: true."
3780
+ } : null,
3599
3781
  closure: session.closure,
3600
3782
  lastError: session.lastError,
3601
3783
  latestHistoryEntry,
@@ -3609,6 +3791,9 @@ function nextAction(session) {
3609
3791
  return "Save a plan with flow_plan_save.";
3610
3792
  if (session.approval !== "approved")
3611
3793
  return "Approve the plan.";
3794
+ if (session.budget.phaseBoundary) {
3795
+ return "Start a fresh OpenCode session, call flow_status, then acknowledge the phase boundary with flow_run_start.";
3796
+ }
3612
3797
  if (session.status === "ready")
3613
3798
  return "Start the next feature.";
3614
3799
  if (session.status === "running")
@@ -3626,7 +3811,8 @@ var FlowPlanSaveSchema = z2.object({
3626
3811
  plan: PlanInputSchema.optional()
3627
3812
  }).strict();
3628
3813
  var FlowRunStartSchema = z2.object({
3629
- featureId: z2.string().min(1).optional()
3814
+ featureId: z2.string().min(1).optional(),
3815
+ phaseBoundaryAck: z2.boolean().optional()
3630
3816
  }).strict();
3631
3817
  var FlowFeatureResetSchema = z2.object({
3632
3818
  featureId: z2.string().min(1)
@@ -3642,6 +3828,7 @@ var FlowFeatureCompleteToolSchema = z2.object({
3642
3828
  artifactsChanged: z2.array(ArtifactSchema).optional(),
3643
3829
  validationRun: z2.array(ValidationRunSchema).optional(),
3644
3830
  validationScope: ValidationScopeSchema.optional(),
3831
+ featureReviewDepth: FeatureReviewDepthSchema.optional(),
3645
3832
  featureReview: ReviewSchema.optional(),
3646
3833
  finalReview: FinalReviewSchema.optional(),
3647
3834
  outcome: z2.union([WorkerOutcomeSchema, NeedsInputOutcomeSchema]).optional()
@@ -3756,7 +3943,7 @@ async function flowRunStart(worktree, input) {
3756
3943
  if (!session) {
3757
3944
  return missingSessionResponse();
3758
3945
  }
3759
- const result = startRun(session, args.featureId);
3946
+ const result = startRun(session, args.featureId, args.phaseBoundaryAck === undefined ? undefined : { phaseBoundaryAck: args.phaseBoundaryAck });
3760
3947
  if (!result.ok)
3761
3948
  return responseFromFailure(result);
3762
3949
  const saved = await saveSession(worktree, result.value.session);
@@ -3776,8 +3963,15 @@ async function flowFeatureComplete(worktree, input) {
3776
3963
  }
3777
3964
  const result = completeFeature(session, worker);
3778
3965
  if (!result.ok) {
3779
- if (result.session)
3780
- await saveSession(worktree, result.session);
3966
+ if (result.session) {
3967
+ const saved2 = await saveSession(worktree, result.session);
3968
+ return {
3969
+ ...summarizeSession(saved2),
3970
+ status: "error",
3971
+ summary: result.message,
3972
+ ...result.recovery ? { recovery: result.recovery } : {}
3973
+ };
3974
+ }
3781
3975
  return responseFromFailure(result);
3782
3976
  }
3783
3977
  const saved = await saveSession(worktree, result.value);
@@ -4004,4 +4198,4 @@ export {
4004
4198
  plugin_default as default
4005
4199
  };
4006
4200
 
4007
- //# debugId=72872E938F875B1A64756E2164756E21
4201
+ //# debugId=519CC894E2A4E68464756E2164756E21