opencode-plugin-flow 3.3.14 → 3.3.16

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,14 @@ the rejected alternatives, and the validation that was run — see
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [3.3.16] - 2026-06-14
10
+
11
+ Serialize Flow tool mutations through a session transaction so concurrent planning and execution updates compose instead of racing stale reads or stale docs.
12
+
13
+ ## [3.3.15] - 2026-06-14
14
+
15
+ Clarify derived signal authority so readiness blockers stay operational, context quality stays advisory, and context packs carry the same handoff contract.
16
+
9
17
  ## [3.3.14] - 2026-06-14
10
18
 
11
19
  Delete stale architecture/test/docs scaffolding that only protected retired prompt/audit history or implementation-shape assertions, with no runtime or public surface change.
package/README.md CHANGED
@@ -31,7 +31,7 @@ Add Flow to the `plugin` array in your `opencode.json` (global `~/.config/openco
31
31
 
32
32
  ```json
33
33
  {
34
- "plugin": ["opencode-plugin-flow@3.3.14"]
34
+ "plugin": ["opencode-plugin-flow@3.3.16"]
35
35
  }
36
36
  ```
37
37
 
@@ -153,7 +153,13 @@ Plus: atomic, locked, path-safe writes under `.flow/**`; schema validation of al
153
153
 
154
154
  Everything judgment-shaped — how to decompose a plan, how deep to review, what counts as good evidence, when to stop and ask — lives in the skills, where you can read and override it.
155
155
 
156
- `/flow-status` also reports advisory `workflowReadiness`, `contextQuality`, `contextTraceability`, and `contextDiagnostics`. These fields show whether the session is ready for planning, execution, feature review, final review, or release; how strong the planned context is; which planned targets changed; which validation and review evidence exists; and where planned context is weak. `flow_context` exposes the same derived handoff in summary, feature, or full views, optionally with a lightweight project structure map. They do not replace review judgment, but they make context gaps visible before they become unverifiable completion claims.
156
+ Flow uses three authority levels for derived status signals:
157
+
158
+ - **Hard gate** — runtime rejects the action. The completion and persistence gates above are hard gates.
159
+ - **Workflow blocker** — derived status says the agent should not proceed until the issue is resolved or explicitly justified. `workflowReadiness.state` values such as `blocked_by_context`, `blocked_by_validation`, and `blocked_by_review` are workflow blockers, not persisted state or runtime invariants.
160
+ - **Advisory diagnostic** — useful planning or review signal, but judgment-owned. `contextQuality` and broad/weak-context diagnostics are advisory unless they expose concrete drift that readiness reports as blocked.
161
+
162
+ `/flow-status` also reports derived `workflowReadiness`, `contextQuality`, `contextTraceability`, and `contextDiagnostics`. These fields show whether the session is ready for planning, execution, feature review, final review, or release; how strong the planned context is; which planned targets changed; which validation and review evidence exists; and where planned context is weak. `flow_context` exposes the same derived handoff in summary, feature, or full views, optionally with a lightweight project structure map. They do not replace review judgment, but they make context gaps visible before they become unverifiable completion claims.
157
163
 
158
164
  ## What Flow writes
159
165
 
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ ${e.template}
13
13
  `)}
14
14
 
15
15
  ${e.prompt}
16
- `}function L(){return new Map(Object.entries(W).map(([e,t])=>[e,D(t)]))}function G(){return new Map(Object.entries(j).map(([e,t])=>[e,ee(t)]))}async function C(e){let t=[],i=[];for(let a of e.names){let s=U(e.root,`${a}.md`),o=U(e.root,`.${a}.flow-version`),n=await V(o),r=n===null?null:b(n,e.kind,a);if(r===null)continue;let d=await V(s);if(d!==null&&p(d)!==r.hash){i.push(s);continue}if(!e.dryRun)await F(s,{force:!0}),await F(o,{force:!0}),await F(`${s}.backup`,{force:!0});t.push(s)}return{removed:t,keptUserEdited:i}}async function V(e){try{return await $(e,"utf8")}catch(t){if(t.code==="ENOENT")return null;throw t}}function te(e){if(!e)return[];let t=["permission:"];for(let[i,a]of Object.entries(e)){if(typeof a==="string"){t.push(` ${JSON.stringify(i)}: ${JSON.stringify(a)}`);continue}if(a&&typeof a==="object"){t.push(` ${JSON.stringify(i)}:`);for(let[s,o]of Object.entries(a))t.push(` ${JSON.stringify(s)}: ${JSON.stringify(o)}`)}}return t}async function M({homeDir:e,dryRun:t=!1,logger:i}){let a={removedSkills:[],keptUserEditedSkills:[],removedCommands:[],keptUserEditedCommands:[],removedAgents:[],keptUserEditedAgents:[],removedPreNpmPlugin:null,keptForeignPreNpmPlugin:null},s=c(e,x);for(let d of await le(s)){if(d!=="flow"&&!d.startsWith("flow-"))continue;let l=c(s,d),u=await de(l);if(u==="foreign")continue;if(u==="user_edited"){a.keptUserEditedSkills.push(l),i?.(`Kept user-edited Flow skill at ${l}; remove it manually if it is no longer needed.`);continue}if(!t)await ce(l);a.removedSkills.push(l),i?.(`${t?"Would remove":"Removed"} Flow skill at ${l}.`)}let o=await C({kind:"command",root:c(e,g),names:k,dryRun:t});for(let d of o.removed)a.removedCommands.push(d),i?.(`${t?"Would remove":"Removed"} retired Flow command at ${d}.`);for(let d of o.keptUserEdited)a.keptUserEditedCommands.push(d),i?.(`Kept user-edited Flow command at ${d}; remove it manually if it is no longer needed.`);await Y({homeDir:e,dryRun:t,logger:i,kind:"command",root:c(e,g),files:L(),removed:a.removedCommands,keptUserEdited:a.keptUserEditedCommands}),await Y({homeDir:e,dryRun:t,logger:i,kind:"agent",root:c(e,_),files:G(),removed:a.removedAgents,keptUserEdited:a.keptUserEditedAgents});let n=c(e,R),r=await f(n);if(r!==null)if(r.startsWith(A)){if(!t)await h(n,{force:!0});a.removedPreNpmPlugin=n,i?.(`${t?"Would remove":"Removed"} pre-npm Flow plugin copy at ${n}.`)}else a.keptForeignPreNpmPlugin=n,i?.(`Kept ${n}: it is not managed by Flow. Remove it manually if it is a stale Flow copy.`);return i?.('Finally, remove "opencode-plugin-flow" from the plugin array in opencode.json and restart OpenCode.'),a}async function de(e){let t=c(e,"SKILL.md"),i=await f(t),a=await f(c(e,w)),s=a===null?null:T(a);if(s!==null&&a!==null){for(let[r,d]of y(a)){if(r==="SKILL.md")continue;let l=Q(e,r);if(l===null)continue;let u=await f(l);if(u!==null&&p(u)!==d)return"user_edited"}if(i===null)return"pristine";if(s.hash!==null&&p(i)===s.hash)return"pristine";return m(i).kind==="valid_generated"?"pristine":"user_edited"}if(i===null)return"foreign";let o=m(i);if(o.kind==="valid_generated")return"pristine";if(o.kind==="invalid_generated")return"user_edited";return"foreign"}function Q(e,t){let i=ne(c(e,...t.split("/")));if(i!==e&&i.startsWith(`${e}${re}`))return i;return null}async function ce(e){let t=await f(c(e,w)),i=new Set;if(t!==null)for(let a of y(t).keys()){let s=Q(e,a);if(s===null)continue;await h(s,{force:!0}),await h(`${s}.backup`,{force:!0});let o=oe(s);if(o!==e)i.add(o)}await h(c(e,"SKILL.md"),{force:!0}),await h(c(e,w),{force:!0}),await h(c(e,N),{force:!0});for(let a of[...i].sort((s,o)=>o.length-s.length))await I(a);await I(e)}async function I(e){try{await se(e)}catch(t){let i=t.code;if(i!=="ENOENT"&&i!=="ENOTEMPTY")throw t}}async function le(e){try{return(await ae(e,{withFileTypes:!0})).filter((i)=>i.isDirectory()).map((i)=>i.name).sort()}catch(t){if(t.code==="ENOENT")return[];throw t}}async function f(e){try{return await ie(e,"utf8")}catch(t){if(t.code==="ENOENT")return null;throw t}}async function Y(e){for(let[t,i]of e.files){let a=c(e.root,`${t}.md`),s=c(e.root,`.${t}.flow-version`),o=await f(a),n=await f(s),r=n===null?null:b(n,e.kind,t);if(o===null&&r===null)continue;if(!(r!==null||o===i))continue;if(o!==null&&r!==null&&p(o)!==r.hash){e.keptUserEdited.push(a),e.logger?.(`Kept user-edited Flow ${e.kind} at ${a}; remove it manually if it is no longer needed.`);continue}if(!e.dryRun)await h(a,{force:!0}),await h(s,{force:!0}),await h(`${a}.backup`,{force:!0});e.removed.push(a),e.logger?.(`${e.dryRun?"Would remove":"Removed"} Flow ${e.kind} at ${a}.`)}if(!e.dryRun)await I(e.root)}var q=`opencode-plugin-flow — Flow plugin lifecycle commands
16
+ `}function L(){return new Map(Object.entries(W).map(([e,t])=>[e,D(t)]))}function Q(){return new Map(Object.entries(j).map(([e,t])=>[e,ee(t)]))}async function G(e){let t=[],i=[];for(let a of e.names){let s=U(e.root,`${a}.md`),o=U(e.root,`.${a}.flow-version`),n=await V(o),r=n===null?null:b(n,e.kind,a);if(r===null)continue;let d=await V(s);if(d!==null&&p(d)!==r.hash){i.push(s);continue}if(!e.dryRun)await F(s,{force:!0}),await F(o,{force:!0}),await F(`${s}.backup`,{force:!0});t.push(s)}return{removed:t,keptUserEdited:i}}async function V(e){try{return await $(e,"utf8")}catch(t){if(t.code==="ENOENT")return null;throw t}}function te(e){if(!e)return[];let t=["permission:"];for(let[i,a]of Object.entries(e)){if(typeof a==="string"){t.push(` ${JSON.stringify(i)}: ${JSON.stringify(a)}`);continue}if(a&&typeof a==="object"){t.push(` ${JSON.stringify(i)}:`);for(let[s,o]of Object.entries(a))t.push(` ${JSON.stringify(s)}: ${JSON.stringify(o)}`)}}return t}async function Y({homeDir:e,dryRun:t=!1,logger:i}){let a={removedSkills:[],keptUserEditedSkills:[],removedCommands:[],keptUserEditedCommands:[],removedAgents:[],keptUserEditedAgents:[],removedPreNpmPlugin:null,keptForeignPreNpmPlugin:null},s=c(e,x);for(let d of await le(s)){if(d!=="flow"&&!d.startsWith("flow-"))continue;let l=c(s,d),u=await de(l);if(u==="foreign")continue;if(u==="user_edited"){a.keptUserEditedSkills.push(l),i?.(`Kept user-edited Flow skill at ${l}; remove it manually if it is no longer needed.`);continue}if(!t)await ce(l);a.removedSkills.push(l),i?.(`${t?"Would remove":"Removed"} Flow skill at ${l}.`)}let o=await G({kind:"command",root:c(e,g),names:k,dryRun:t});for(let d of o.removed)a.removedCommands.push(d),i?.(`${t?"Would remove":"Removed"} retired Flow command at ${d}.`);for(let d of o.keptUserEdited)a.keptUserEditedCommands.push(d),i?.(`Kept user-edited Flow command at ${d}; remove it manually if it is no longer needed.`);await C({homeDir:e,dryRun:t,logger:i,kind:"command",root:c(e,g),files:L(),removed:a.removedCommands,keptUserEdited:a.keptUserEditedCommands}),await C({homeDir:e,dryRun:t,logger:i,kind:"agent",root:c(e,_),files:Q(),removed:a.removedAgents,keptUserEdited:a.keptUserEditedAgents});let n=c(e,R),r=await f(n);if(r!==null)if(r.startsWith(A)){if(!t)await h(n,{force:!0});a.removedPreNpmPlugin=n,i?.(`${t?"Would remove":"Removed"} pre-npm Flow plugin copy at ${n}.`)}else a.keptForeignPreNpmPlugin=n,i?.(`Kept ${n}: it is not managed by Flow. Remove it manually if it is a stale Flow copy.`);return i?.('Finally, remove "opencode-plugin-flow" from the plugin array in opencode.json and restart OpenCode.'),a}async function de(e){let t=c(e,"SKILL.md"),i=await f(t),a=await f(c(e,w)),s=a===null?null:T(a);if(s!==null&&a!==null){for(let[r,d]of y(a)){if(r==="SKILL.md")continue;let l=M(e,r);if(l===null)continue;let u=await f(l);if(u!==null&&p(u)!==d)return"user_edited"}if(i===null)return"pristine";if(s.hash!==null&&p(i)===s.hash)return"pristine";return m(i).kind==="valid_generated"?"pristine":"user_edited"}if(i===null)return"foreign";let o=m(i);if(o.kind==="valid_generated")return"pristine";if(o.kind==="invalid_generated")return"user_edited";return"foreign"}function M(e,t){let i=ne(c(e,...t.split("/")));if(i!==e&&i.startsWith(`${e}${re}`))return i;return null}async function ce(e){let t=await f(c(e,w)),i=new Set;if(t!==null)for(let a of y(t).keys()){let s=M(e,a);if(s===null)continue;await h(s,{force:!0}),await h(`${s}.backup`,{force:!0});let o=oe(s);if(o!==e)i.add(o)}await h(c(e,"SKILL.md"),{force:!0}),await h(c(e,w),{force:!0}),await h(c(e,N),{force:!0});for(let a of[...i].sort((s,o)=>o.length-s.length))await I(a);await I(e)}async function I(e){try{await se(e)}catch(t){let i=t.code;if(i!=="ENOENT"&&i!=="ENOTEMPTY")throw t}}async function le(e){try{return(await ae(e,{withFileTypes:!0})).filter((i)=>i.isDirectory()).map((i)=>i.name).sort()}catch(t){if(t.code==="ENOENT")return[];throw t}}async function f(e){try{return await ie(e,"utf8")}catch(t){if(t.code==="ENOENT")return null;throw t}}async function C(e){for(let[t,i]of e.files){let a=c(e.root,`${t}.md`),s=c(e.root,`.${t}.flow-version`),o=await f(a),n=await f(s),r=n===null?null:b(n,e.kind,t);if(o===null&&r===null)continue;if(!(r!==null||o===i))continue;if(o!==null&&r!==null&&p(o)!==r.hash){e.keptUserEdited.push(a),e.logger?.(`Kept user-edited Flow ${e.kind} at ${a}; remove it manually if it is no longer needed.`);continue}if(!e.dryRun)await h(a,{force:!0}),await h(s,{force:!0}),await h(`${a}.backup`,{force:!0});e.removed.push(a),e.logger?.(`${e.dryRun?"Would remove":"Removed"} Flow ${e.kind} at ${a}.`)}if(!e.dryRun)await I(e.root)}var q=`opencode-plugin-flow — Flow plugin lifecycle commands
17
17
 
18
18
  Usage:
19
19
  bunx opencode-plugin-flow uninstall [--dry-run]
@@ -32,4 +32,4 @@ Options:
32
32
 
33
33
  ${q}`),1;let a=!1;for(let s of t){if(s==="--dry-run"){a=!0;continue}return S(`Unknown argument: ${s}
34
34
 
35
- ${q}`),1}return await M({homeDir:process.env.HOME??he(),dryRun:a,logger:X}),0}try{process.exitCode=await ue(process.argv.slice(2))}catch(e){S(e instanceof Error?e.message:String(e)),process.exitCode=1}
35
+ ${q}`),1}return await Y({homeDir:process.env.HOME??he(),dryRun:a,logger:X}),0}try{process.exitCode=await ue(process.argv.slice(2))}catch(e){S(e instanceof Error?e.message:String(e)),process.exitCode=1}