opencode-plugin-flow 6.5.0 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,36 @@ One short entry per release, written for users deciding whether to upgrade.
6
6
 
7
7
  No changes yet.
8
8
 
9
+ ## [6.6.0] - 2026-07-22
10
+
11
+ Projection-guided recovery lore makes Flow's next step easier to understand
12
+ without adding persisted diagnostics or more orchestration:
13
+
14
+ - `/flow-run` now follows one ordered compact-status route, handles idle and
15
+ planning entry explicitly, and refreshes execution state before dispatching a
16
+ recovered reviewer assignment so stale work is reset rather than redispatched.
17
+ - Status failures report the exact summary and optional recovery guidance.
18
+ Post-review uncertainty no longer incorrectly claims that no lifecycle
19
+ mutation occurred.
20
+ - Blocked-review handoffs now explain attempts, recurring and new findings,
21
+ validation evidence, Flow-reported artifacts, completed and untouched work,
22
+ the exact next action, and whether another repair attempt needs authorization.
23
+ `/flow-status` obtains that evidence through one detail read.
24
+ - Typed status, execution, reviewer, detail, delivery, operation, and close
25
+ recovery projections replace broad record casts. Accepted archive retry,
26
+ accepted manual recovery, unconfirmed replay, and archive lookup collision
27
+ remain distinct without changing Session v5 or persisted state.
28
+ - OpenCode tool serialization preserves those specialized response types while
29
+ requiring every handler to return the Flow response envelope. Expanded prompt,
30
+ runtime, close-recovery, and integration tests cover the contracts; the
31
+ failed-review retry boundary and bounded worker waves remain unchanged.
32
+
33
+ Install or update:
34
+
35
+ ```bash
36
+ opencode plugin opencode-plugin-flow@6.6.0 --global --force
37
+ ```
38
+
9
39
  ## [6.5.0] - 2026-07-22
10
40
 
11
41
  Convergence-safe recovery lore makes long Flow sessions easier to trust
package/README.md CHANGED
@@ -21,11 +21,11 @@ into the active goal.
21
21
  Install the exact npm release through OpenCode:
22
22
 
23
23
  ```bash
24
- opencode plugin opencode-plugin-flow@6.5.0 --global --force
24
+ opencode plugin opencode-plugin-flow@6.6.0 --global --force
25
25
  ```
26
26
 
27
27
  Omit `--global` for project scope. Exact version pins do not update
28
- automatically. To update, replace `6.5.0` with the new release and rerun the
28
+ automatically. To update, replace `6.6.0` with the new release and rerun the
29
29
  command.
30
30
 
31
31
  Before upgrading from Flow v5 or earlier, finish or explicitly close any active
@@ -43,7 +43,7 @@ The equivalent manual project configuration is:
43
43
  ```json
44
44
  {
45
45
  "$schema": "https://opencode.ai/config.json",
46
- "plugin": ["opencode-plugin-flow@6.5.0"]
46
+ "plugin": ["opencode-plugin-flow@6.6.0"]
47
47
  }
48
48
  ```
49
49
 
package/dist/index.js CHANGED
@@ -328,47 +328,63 @@ independent review and submits its own result.
328
328
  ## Start and scope
329
329
 
330
330
  1. Call \`flow_status { request: { view: "compact" } }\` first. Treat
331
- \`nextAction\` as the durable default workflow direction, not as a permission
332
- grant.
333
- 2. If compact status contains \`archiveRetry\`, call \`flow_session_close\` once
334
- with that projected request byte-for-byte. Report \`workflowData.delivery\`;
335
- if archive publication still fails, refresh compact status. This exact
336
- cleanup grants no new work, so it precedes goal alignment. Stop after the
337
- cleanup outcome either way.
338
- 3. Before any other manager-owned lifecycle mutation, align the compact-projected
339
- goal with the current direct \`/flow-run\` request. Continue only for the same
340
- goal or a compatible narrowing. Compatible narrowing changes method or
341
- emphasis only; it must not add, drop, reorder, or weaken an approved
342
- requirement or feature outcome. A completed-but-unclosed session must close
343
- as completed before a new request proceeds. Otherwise, for materially new
344
- or expanded work, perform no mutation, say the request has not started, and
345
- offer to continue the active goal, defer it, or abandon it. Keep this
346
- comparison conversational; create no classifier or state.
347
- 4. If compact status is \`completed\`, call \`flow_session_close\` once with its
348
- session id and revision, a fresh operation id, and \`kind: "completed"\`.
349
- Report \`workflowData.delivery\`. If archive publication is unconfirmed,
350
- follow the projected \`archiveRetry\` once byte-for-byte and stop on any
351
- remaining failure. Then stop. A materially new request can enter the
352
- appropriate Flow planning route afterward; do not fabricate a run.
353
- 5. If compact status is already \`blocked\`, load
354
- \`flow_status { request: { view: "detail" } }\` exactly once before any reset.
355
- Apply the retry and checkpoint rule under **Review and record**. If it permits
356
- a fresh run, call \`flow_feature_reset\`, refresh compact status, and continue
357
- this invocation. Otherwise report the checkpoint and stop.
358
- 6. If compact status is \`running\` and \`nextAction\` is \`flow_feature_reset\`, the
359
- pending review is source-stale. Call \`flow_feature_reset\`, refresh compact
360
- status, and continue with a fresh run. Do not redispatch that assignment.
361
- 7. If compact status is \`running\` and \`nextAction\` is
362
- \`dispatch-flow-reviewer\`, skip run start, implementation, and validation;
363
- continue at **Review and record** with the existing pending assignment.
364
- 8. A durable \`nextAction\` can still be rejected after status by an
365
- environment-sensitive guard. On rejection, refresh compact status and
366
- handle the exact error instead of forcing a stale action.
367
- 9. Call \`flow_run_start\` when a ready feature is not already running.
368
- 10. Read \`flow_status { request: { view: "execution" } }\` and use that
369
- projection as the active scope and source of revision guards.
370
- 11. Read the feature summary, targets, validation, dependencies, requirements,
371
- and decisions before editing.
331
+ \`nextAction\` as the durable default workflow direction, not as permission.
332
+ 2. If the top-level response status is \`error\`, report its exact summary and
333
+ recovery when present. State that this initial read made no lifecycle, Git,
334
+ or release mutation, and stop. Do not route an error projection's
335
+ \`nextAction\` as feature recovery.
336
+ 3. If compact status contains \`archiveRetry\`, call \`flow_session_close\` once
337
+ with that projected request byte-for-byte and report
338
+ \`workflowData.delivery\`. If archive publication remains unconfirmed, refresh
339
+ compact status. Stop after this cleanup outcome either way; it grants no new
340
+ work and therefore precedes goal alignment.
341
+ 4. When the projection contains an active goal, align it with the current
342
+ direct \`/flow-run\` request before any other manager-owned lifecycle mutation.
343
+ Continue only for the same goal or a compatible narrowing. Compatible
344
+ narrowing may change method or emphasis, but must not add, drop, reorder, or
345
+ weaken an approved requirement or feature outcome. A completed-but-unclosed
346
+ session must close as completed before a new request proceeds. For other
347
+ materially new or expanded work, make no mutation, say the request has not
348
+ started, and offer to continue the active goal, defer it, or abandon it.
349
+ Keep this comparison conversational; add no classifier or state.
350
+ 5. If status is \`idle\` or \`planning\`, report its projected planning action,
351
+ explain that \`/flow-run\` requires an approved feature, and stop without
352
+ mutation.
353
+
354
+ Route every other compact projection in this order:
355
+
356
+ - \`flow_session_close\`: close a completed session with its projected session id
357
+ and revision, a fresh operation id, and \`kind: "completed"\`. Report
358
+ \`workflowData.delivery\`, follow one projected exact \`archiveRetry\` if needed,
359
+ and stop. A materially new request can enter Flow planning afterward; do not
360
+ fabricate a run.
361
+ - Blocked \`await-user-direction\` or blocked \`flow_feature_reset\`: call
362
+ \`flow_status { request: { view: "detail" } }\` exactly once before any reset,
363
+ then apply **Blocked review** below. If it permits a fresh run, reset, refresh
364
+ compact status, and route again; otherwise report the checkpoint and stop.
365
+ - Running \`flow_feature_reset\`: the pending review is source-stale. Reset,
366
+ refresh compact status, and route again. Never redispatch that assignment.
367
+ - \`dispatch-flow-reviewer\`: read execution status. If that read errors, report
368
+ its exact summary and recovery when present and stop without dispatching; do
369
+ not infer a projection. Otherwise route that refreshed projection before
370
+ acting. Dispatch the recovered pending assignment under **Review and record**
371
+ only if \`nextAction\` is still \`dispatch-flow-reviewer\`. If it is now running
372
+ \`flow_feature_reset\`, follow the source-stale reset route and never dispatch
373
+ that assignment. Skip run start, implementation, and validation.
374
+ - \`flow_run_start\`: start the ready feature, refresh compact status, and read
375
+ execution status.
376
+ - \`flow_validation_start\`: read execution status and resume integration or
377
+ validation from the current worktree.
378
+ - \`flow_review_start\`: read execution status and continue at **Review and
379
+ record** without fabricating another validation.
380
+ - Any other action: report it and stop unless the runtime explicitly identifies
381
+ an active execution path.
382
+
383
+ Use execution status as the active scope and source of revision guards. Read
384
+ the feature summary, targets, validation, dependencies, requirements, and
385
+ decisions before editing. A projected action may still fail an
386
+ environment-sensitive guard; refresh compact status and handle that exact
387
+ rejection instead of forcing the stale action.
372
388
 
373
389
  Preserve unrelated worktree changes and stay inside the active feature. Leave
374
390
  changes owned by another planned feature for that feature. If implementation
@@ -483,35 +499,39 @@ verdict. The reviewer reads its assignment, inspects the workspace, and calls
483
499
  reviewer remains workspace-read-only and may make only this exact result
484
500
  submission as its sole lifecycle mutation.
485
501
 
486
- After the reviewer returns, read compact status rather than treating prose as
487
- the outcome. If it records a blocked outcome, immediately load
488
- \`flow_status { request: { view: "detail" } }\` exactly once for the handoff or
489
- checkpoint. Redispatch the same pending assignment after interruption or an
490
- unconfirmed reviewer return only while compact status is \`running\` and
491
- \`nextAction\` is \`dispatch-flow-reviewer\`. If status remains \`running\` with that
492
- pending assignment and \`nextAction\` is \`flow_feature_reset\`, or submission
493
- reports \`Workspace content changed after review started\`, call
494
- \`flow_feature_reset\` and do not redispatch the source-stale assignment; start a
495
- fresh run and repeat full validation and review. Never fabricate a verdict. A
496
- submitted pass completes the feature; a submitted blocking finding records a
497
- blocked outcome.
498
-
499
- Use compact \`blockedFeature.failedReviewCount\` and that one detail projection
500
- together. A \`[scope-blocker]\` checkpoints immediately and must not reset
501
- automatically. Otherwise treat the first recorded failed review as in-scope;
502
- when implementation is already authorized, it may receive one automatic
503
- \`flow_feature_reset\`. Fix only its blocking findings, then repeat full
504
- validation and full review in a fresh run. A second recorded failed review
505
- awaits explicit user direction; report recurring and new blockers, possible
506
- feature mis-sizing, and any repair that would exceed approved scope while
507
- remaining inside Flow. A current aligned request counts as direction only when
508
- it explicitly authorizes one additional attempt. If that review fails,
509
- checkpoint again.
510
-
511
- Read compact status after every recorded outcome. When invoked directly through
512
- \`/flow-run\`, report that one feature's outcome and \`nextAction\`, then stop. When
513
- the active driver is \`/flow-auto\`, return to its loop so it can start the next
514
- feature, report a blocker, or close the completed session.
502
+ After dispatch, read compact status rather than trusting reviewer prose. If the
503
+ top-level response is an error, report its exact summary and recovery when
504
+ present, say the latest lifecycle state could not be confirmed, and stop
505
+ without further mutation. Do not claim this invocation made no lifecycle
506
+ mutation: it may already have started review or recorded a reviewer result.
507
+ Never invent or submit a verdict. If status remains running, apply the
508
+ \`dispatch-flow-reviewer\` or running \`flow_feature_reset\` route above. If status
509
+ is blocked, load detail through the single blocked route above. A recorded pass
510
+ completes the feature.
511
+
512
+ ### Blocked review
513
+
514
+ Use compact \`blockedFeature.failedReviewCount\` with that one detail projection.
515
+
516
+ - A \`[scope-blocker]\` checkpoints immediately. Do not reset automatically.
517
+ - On the first ordinary failed review, existing implementation authority
518
+ permits one automatic \`flow_feature_reset\`. Fix only its blocking findings,
519
+ then run full validation and full independent review in a fresh run.
520
+ - After the second failed review, reset only when the current aligned request
521
+ explicitly authorizes one additional attempt. If that attempt fails,
522
+ checkpoint again.
523
+
524
+ When stopping blocked, label the result overall incomplete. Report what the
525
+ latest repair fixed; recurring and new blocking findings; the goal and progress;
526
+ the blocked feature, attempt, and failure count; completed and untouched
527
+ features; latest validations and \`artifactsChanged\` as Flow-reported artifact
528
+ evidence; Git and release mutation status; whether the current request started,
529
+ mapped to the active goal, or was held; the exact \`nextAction\`; and whether
530
+ another attempt requires explicit authorization.
531
+
532
+ Use that already-loaded compact status after every recorded outcome. Direct
533
+ \`/flow-run\` reports this one feature's cumulative outcome and \`nextAction\`, then
534
+ stops. Under \`/flow-auto\`, return to its lifecycle loop.
515
535
  `;
516
536
 
517
537
  // src/guidance/ids.ts
@@ -618,7 +638,16 @@ function compileFlowPromptSurface(surface) {
618
638
  case "flow-run":
619
639
  return managerCommand(surface);
620
640
  case "flow-status":
621
- return 'Call `flow_status { request: { view: "compact" } }` and report the runtime projection plus its durable default next action. Do not mutate. For a first blocked review, say that `/flow-run` must read detail before reset because a `[scope-blocker]` may refine that default.';
641
+ return [
642
+ 'Call `flow_status { request: { view: "compact" } }` first.',
643
+ "Do not mutate.",
644
+ "If the top-level response status is `error`, report its exact summary and `workflowData.failure.recovery` when present; otherwise say no recovery guidance was supplied. State that `/flow-status` made no Git or release mutation, report any lifecycle state effect disclosed by the response, and stop. Do not interpret recovery guidance as a blocked review.",
645
+ 'If `projection.status` is `blocked`, call `flow_status { request: { view: "detail" } }` exactly once and label the result overall incomplete.',
646
+ "From that detail projection, report the goal and progress; blocked feature, attempt, `failedReviewCount`, and findings; completed and untouched features; validations and `artifactsChanged` as Flow-reported artifact evidence; and the exact `nextAction`.",
647
+ "For a blocked first failed review, explain that `flow_feature_reset` is only the default and `/flow-run` must inspect any `[scope-blocker]` before reset.",
648
+ "For blocked `await-user-direction`, explain that another attempt requires explicit user direction.",
649
+ "Otherwise report the compact projection and its exact `nextAction`, state that `/flow-status` made no lifecycle, Git, or release mutation, and stop."
650
+ ].join(" ");
622
651
  case "flow-review":
623
652
  return [
624
653
  "# Flow review command",
@@ -2786,17 +2815,18 @@ function ok(summary, workflowData) {
2786
2815
  return {
2787
2816
  status: "ok",
2788
2817
  summary,
2789
- workflowData: { dataNote: dataNote(), ...workflowData }
2818
+ workflowData: { ...workflowData, dataNote: dataNote() }
2790
2819
  };
2791
2820
  }
2792
2821
  function errorResponse(error, recovery) {
2822
+ const summary = error instanceof Error ? error.message : String(error);
2793
2823
  return {
2794
2824
  status: "error",
2795
- summary: error instanceof Error ? error.message : String(error),
2825
+ summary,
2796
2826
  workflowData: {
2797
2827
  dataNote: dataNote(),
2798
2828
  failure: {
2799
- summary: error instanceof Error ? error.message : String(error),
2829
+ summary,
2800
2830
  ...recovery ? { recovery } : {}
2801
2831
  }
2802
2832
  }
@@ -2987,16 +3017,7 @@ function reviewerProjection(session, assignmentId) {
2987
3017
  completedFeatureIds: plan?.features.filter((candidate) => isFeatureComplete(session, candidate.id)).map((candidate) => candidate.id) ?? []
2988
3018
  };
2989
3019
  }
2990
- function project(session, request, pendingReviewSourceStale = false) {
2991
- if (request.view === "compact") {
2992
- return compactProjection(session, pendingReviewSourceStale);
2993
- }
2994
- if (request.view === "execution") {
2995
- return executionProjection(session, pendingReviewSourceStale);
2996
- }
2997
- if (request.view === "reviewer") {
2998
- return reviewerProjection(session, request.assignmentId);
2999
- }
3020
+ function detailProjection(session, pendingReviewSourceStale = false) {
3000
3021
  return {
3001
3022
  ...compactProjection(session, pendingReviewSourceStale),
3002
3023
  view: "detail",
@@ -3006,6 +3027,26 @@ function project(session, request, pendingReviewSourceStale = false) {
3006
3027
  operations: session.operations
3007
3028
  };
3008
3029
  }
3030
+ function idleProjection(view) {
3031
+ return {
3032
+ view,
3033
+ status: "idle",
3034
+ revision: 0,
3035
+ nextAction: "flow_plan_save"
3036
+ };
3037
+ }
3038
+ function project(session, request, pendingReviewSourceStale = false) {
3039
+ switch (request.view) {
3040
+ case "compact":
3041
+ return compactProjection(session, pendingReviewSourceStale);
3042
+ case "detail":
3043
+ return detailProjection(session, pendingReviewSourceStale);
3044
+ case "execution":
3045
+ return executionProjection(session, pendingReviewSourceStale);
3046
+ case "reviewer":
3047
+ return reviewerProjection(session, request.assignmentId);
3048
+ }
3049
+ }
3009
3050
 
3010
3051
  // src/application/session-close.ts
3011
3052
  async function loadExactArchivedClose(transaction, request) {
@@ -3041,18 +3082,19 @@ function successfulCloseResponse(session, request, replayed, summary) {
3041
3082
  }
3042
3083
  function archivePendingResponse(error, session, request, replayed) {
3043
3084
  const failure = error instanceof Error ? error.message : String(error);
3085
+ const closeState = {
3086
+ durableAccepted: true,
3087
+ archiveConfirmed: false,
3088
+ retryExactRequest: true,
3089
+ retryRequest: request
3090
+ };
3044
3091
  return {
3045
3092
  status: "error",
3046
3093
  summary: "Session close was durably accepted, but archive publication was not confirmed.",
3047
3094
  workflowData: {
3048
3095
  dataNote: dataNote(),
3049
3096
  operation: operationResult(session, request.operationId, replayed, session.closure),
3050
- closeState: {
3051
- durableAccepted: true,
3052
- archiveConfirmed: false,
3053
- retryExactRequest: true,
3054
- retryRequest: request
3055
- },
3097
+ closeState,
3056
3098
  projection: compactProjection(session),
3057
3099
  delivery: deliveryProjection(session),
3058
3100
  failure: {
@@ -3078,19 +3120,35 @@ function manualRecoveryProjection(projection) {
3078
3120
  };
3079
3121
  }
3080
3122
  function archiveCollisionResponse(error, session, request, replayed, durableAccepted = true) {
3123
+ const operation = operationResult(session, request.operationId, replayed, session.closure);
3124
+ const projection = manualRecoveryProjection(compactProjection(session));
3125
+ const failure = {
3126
+ summary: error.message,
3127
+ recovery: "Preserve both active and archived state, inspect the collision, and do not overwrite or delete either document automatically."
3128
+ };
3129
+ if (durableAccepted) {
3130
+ return {
3131
+ status: "error",
3132
+ summary: "Session close was durably accepted, but conflicting Flow state requires manual recovery.",
3133
+ workflowData: {
3134
+ dataNote: dataNote(),
3135
+ operation,
3136
+ closeState: manualRecoveryCloseState(true),
3137
+ projection,
3138
+ delivery: deliveryProjection(session),
3139
+ failure
3140
+ }
3141
+ };
3142
+ }
3081
3143
  return {
3082
3144
  status: "error",
3083
- summary: durableAccepted ? "Session close was durably accepted, but conflicting Flow state requires manual recovery." : "Session close replay could not confirm durable active state; manual recovery is required.",
3145
+ summary: "Session close replay could not confirm durable active state; manual recovery is required.",
3084
3146
  workflowData: {
3085
3147
  dataNote: dataNote(),
3086
- operation: operationResult(session, request.operationId, replayed, session.closure),
3087
- closeState: manualRecoveryCloseState(durableAccepted),
3088
- projection: manualRecoveryProjection(compactProjection(session)),
3089
- ...durableAccepted ? { delivery: deliveryProjection(session) } : {},
3090
- failure: {
3091
- summary: error.message,
3092
- recovery: "Preserve both active and archived state, inspect the collision, and do not overwrite or delete either document automatically."
3093
- }
3148
+ operation,
3149
+ closeState: manualRecoveryCloseState(false),
3150
+ projection,
3151
+ failure
3094
3152
  }
3095
3153
  };
3096
3154
  }
@@ -3117,11 +3175,13 @@ function archiveLookupCollisionResponse(error, request) {
3117
3175
  workflowData: {
3118
3176
  dataNote: dataNote(),
3119
3177
  closeState: manualRecoveryCloseState(false),
3120
- projection: manualRecoveryProjection({
3178
+ projection: {
3121
3179
  view: "compact",
3122
3180
  sessionId: request.sessionId,
3123
- status: "unknown"
3124
- }),
3181
+ status: "unknown",
3182
+ nextAction: "await-user-direction",
3183
+ archiveRetry: null
3184
+ },
3125
3185
  failure: {
3126
3186
  summary: error.message,
3127
3187
  recovery: "Preserve active and archived state, inspect the requested archive, and do not overwrite or delete either document automatically."
@@ -3214,12 +3274,7 @@ function createFlowService(repository, environment) {
3214
3274
  const session = await repository.read();
3215
3275
  if (!session) {
3216
3276
  return ok("No active Flow session.", {
3217
- projection: {
3218
- view: request.view,
3219
- status: "idle",
3220
- revision: 0,
3221
- nextAction: "flow_plan_save"
3222
- }
3277
+ projection: idleProjection(request.view)
3223
3278
  });
3224
3279
  }
3225
3280
  if (request.view !== "reviewer" && (activePendingReview(session) || session.closure)) {
@@ -3227,12 +3282,7 @@ function createFlowService(repository, environment) {
3227
3282
  const current = await transaction.load();
3228
3283
  if (!current) {
3229
3284
  return ok("No active Flow session.", {
3230
- projection: {
3231
- view: request.view,
3232
- status: "idle",
3233
- revision: 0,
3234
- nextAction: "flow_plan_save"
3235
- }
3285
+ projection: idleProjection(request.view)
3236
3286
  });
3237
3287
  }
3238
3288
  const collisionResponse = await closedArchiveCollisionStatus(transaction, current, request);
@@ -3558,7 +3608,11 @@ var SessionCloseArgs = {
3558
3608
  }).strict()
3559
3609
  };
3560
3610
  function json(value) {
3561
- return JSON.stringify(value, null, 2);
3611
+ const serialized = JSON.stringify(value, null, 2);
3612
+ if (serialized === undefined) {
3613
+ throw new Error("Flow tool response could not be serialized.");
3614
+ }
3615
+ return serialized;
3562
3616
  }
3563
3617
  function toolError(error) {
3564
3618
  return json({
@@ -3912,4 +3966,4 @@ export {
3912
3966
  plugin_default as default
3913
3967
  };
3914
3968
 
3915
- //# debugId=2A3C0C87419414A064756E2164756E21
3969
+ //# debugId=D5A6EFA1B223B77864756E2164756E21