opencode-plugin-flow 3.3.18 → 3.3.20

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.20] - 2026-06-14
10
+
11
+ Hide the Flow reviewer behind `/flow-review` and `/flow-auto`, keeping direct reviewer selection advisory instead of a confusing session entry point.
12
+
13
+ ## [3.3.19] - 2026-06-14
14
+
15
+ Ship deslop and UI-quality skills so cleanup and frontend work get evidence-backed rubrics without adding commands, tools, agents, or runtime state.
16
+
9
17
  ## [3.3.18] - 2026-06-14
10
18
 
11
19
  Split the context-pack, markdown render, and distribution sync hotspots into smaller owner-named modules while preserving the public package, runtime, and workflow surfaces.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `opencode-plugin-flow` adds a resumable planning, execution, validation, and review workflow to OpenCode. Use it when a task is important enough that you want a plan, recorded validation evidence, review gates, and a clear way to resume later.
4
4
 
5
- Flow is skills-first: four hand-authored skills carry the workflow guidance, and a small plugin provides durable `.flow/**` session state plus a few hard, code-enforced invariants.
5
+ Flow is skills-first: six hand-authored skills carry the workflow guidance, and a small plugin provides durable `.flow/**` session state plus a few hard, code-enforced invariants.
6
6
 
7
7
  ## Quick start
8
8
 
@@ -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.18"]
34
+ "plugin": ["opencode-plugin-flow@3.3.20"]
35
35
  }
36
36
  ```
37
37
 
@@ -41,11 +41,13 @@ On startup the plugin syncs its global skills, commands, and review agent into O
41
41
 
42
42
  ```text
43
43
  ~/.config/opencode/skills/flow/SKILL.md # driving loop
44
+ ~/.config/opencode/skills/flow-deslop/SKILL.md # cleanup and code-smell discipline
44
45
  ~/.config/opencode/skills/flow-plan/SKILL.md # decomposition heuristics
45
46
  ~/.config/opencode/skills/flow-run/SKILL.md # validation discipline
46
47
  ~/.config/opencode/skills/flow-review/SKILL.md # review rubric
48
+ ~/.config/opencode/skills/flow-ui-quality/SKILL.md # UX/UI quality and visual verification
47
49
  ~/.config/opencode/commands/flow-auto.md # slash command pointers
48
- ~/.config/opencode/agents/flow-reviewer.md # read-only review agent
50
+ ~/.config/opencode/agents/flow-reviewer.md # hidden read-only review subagent
49
51
  ```
50
52
 
51
53
  **Restart OpenCode once after the first install or after an update** so freshly synced skills, commands, and agents are discovered.
@@ -93,16 +95,18 @@ This removes the Flow-owned global skill folders, command files, agent files, an
93
95
 
94
96
  ## Skills, commands, and tools
95
97
 
96
- ### The four skills
98
+ ### The six skills
97
99
 
98
100
  | Skill | What it carries |
99
101
  | --- | --- |
100
102
  | `flow` | The driving loop: check status, plan, run, review, repeat; stop conditions, when to ask the user, recovery playbook. |
103
+ | `flow-deslop` | Evidence-backed cleanup, code-smell classification, safe refactor workflow, and AI-slop removal guidance. |
101
104
  | `flow-plan` | How to decompose work into features, size them, profile the repo, and when a plan is safe to auto-approve. |
102
105
  | `flow-run` | One-feature-at-a-time discipline and what counts as validation evidence. |
103
106
  | `flow-review` | Review depth criteria, finding taxonomy, and report format. |
107
+ | `flow-ui-quality` | UX/UI design intent, frontend polish, accessibility, responsive states, and visual verification guidance. |
104
108
 
105
- Deeper methodology (recovery, shared parallel orchestration, worked plan examples, validation, audit, and review rubrics) lives in `references/` files next to the skills and is loaded only when needed.
109
+ Deeper methodology (recovery, shared parallel orchestration, worked plan examples, validation, audit, review, deslop, refactor, UI, and visual verification rubrics) lives in `references/` files next to the skills and is loaded only when needed.
106
110
 
107
111
  ### Commands
108
112
 
@@ -137,7 +141,7 @@ These eight tools are the whole registered surface — the v2 tool-name redirect
137
141
 
138
142
  ### Agents
139
143
 
140
- Flow ships one dedicated subagent: `flow-reviewer`, a read-only reviewer used for fresh-context review passes. Its read-only boundary is enforced by OpenCode's native per-agent permissions, not by prompt text. Everything else runs in your normal agent, guided by the skills.
144
+ Flow ships one dedicated hidden subagent: `flow-reviewer`, a read-only reviewer used behind `/flow-review` and Flow's review gates. Its read-only boundary is enforced by OpenCode's native per-agent permissions, not by prompt text. Start user-facing work with `/flow-auto` or `/flow-review`; everything else runs in your normal agent, guided by the skills.
141
145
 
142
146
  ## What the plugin enforces vs. what skills guide
143
147
 
package/dist/cli.js CHANGED
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import{homedir as ge}from"node:os";import{readdir as de,readFile as ce,rm as u,rmdir as le}from"node:fs/promises";import{dirname as ue,join as c,normalize as he,sep as pe}from"node:path";var M={fast:"low",balanced:"medium",deep:"high"},V={"flow-reviewer":{mode:"all",description:"Review Flow work read-only and record a reviewer decision.",prompt:"You are the Flow reviewer. Load the `flow-review` skill, review the requested work read-only, then record your decision with flow_review_record.",reasoningEffort:M.deep,permission:{edit:"deny",bash:"deny",task:{"*":"deny"},"flow_*":"deny",flow_status:"allow",flow_review_record:"allow"}}},G={"flow-plan":{description:"Create, update, or approve a Flow plan",template:"Load the `flow-plan` skill and plan: $ARGUMENTS"},"flow-run":{description:"Run one approved Flow feature",template:"Load the `flow-run` skill and execute the next approved Flow feature. $ARGUMENTS"},"flow-auto":{description:"Drive the Flow loop autonomously until completion or a real blocker",template:"Load the `flow` skill and drive the Flow loop (status, plan, run, review) until completion or a real blocker: $ARGUMENTS"},"flow-status":{description:"Inspect the active Flow session and workspace readiness",template:"Call flow_status (detailed) and report session state, readiness checks, and the suggested next step."},"flow-review":{description:"Run a read-only Flow review with a fresh context",agent:"flow-reviewer",subtask:!0,template:"Load the `flow-review` skill and review: $ARGUMENTS"}},R=["flow-doctor","flow-history","flow-reset","flow-session"];import{createHash as D}from"node:crypto";import{join as g}from"node:path";var A=g(".config","opencode","skills"),m=g(".config","opencode","commands"),T=g(".config","opencode","agents"),v=".flow-skill-version",Y="SKILL.md.backup",q=g(".config","opencode","plugins","flow.js"),S=`// Managed by flow-opencode install/uninstall
3
- `,X="flow-opencode-generated-skill",L=`<!-- ${X} `,ee=new RegExp(`^<!-- ${X} name=([a-z0-9]+(?:-[a-z0-9]+)*) version=([0-9]+) hash=sha256:([a-f0-9]{64}) -->$`,"u");function h(e){return D("sha256").update(e,"utf8").digest("hex")}function y(e){let t=e.split(`
4
- `),i=t.flatMap((p,E)=>p.startsWith(L)?[E]:[]);if(i.length===0)return{kind:"not_generated"};if(i.length>1)return{kind:"invalid_generated",reason:"duplicate_marker"};let a=i[0];if(a===void 0)return{kind:"not_generated"};let o=t[a];if(o===void 0)return{kind:"invalid_generated",reason:"malformed_marker"};let s=o.match(ee);if(!s)return{kind:"invalid_generated",reason:"malformed_marker"};let[,n,r,d]=s;if(n===void 0||r===void 0||d===void 0)return{kind:"invalid_generated",reason:"malformed_marker"};let l=[...t.slice(0,a),...t.slice(a+1)].join(`
5
- `);if(h(l)!==d)return{kind:"invalid_generated",reason:"hash_mismatch"};return{kind:"valid_generated",marker:{name:n,version:r,hash:d}}}var H="opencode-plugin-flow",Q="file=",C="=sha256:";function b(e){let t=new Map;for(let i of e.split(`
6
- `)){if(!i.startsWith(Q))continue;let a=i.slice(Q.length),o=a.lastIndexOf(C);if(o===-1)continue;let s=a.slice(0,o),n=a.slice(o+C.length);if(s.length>0&&/^[a-f0-9]{64}$/.test(n))t.set(s,n)}return t}function k(e,t,i){let a=new Map;for(let n of e.split(`
7
- `)){let r=n.indexOf("=");if(r===-1)continue;a.set(n.slice(0,r),n.slice(r+1))}let o=a.get("version"),s=a.get("hash");if(a.get("plugin")!==H||a.get("kind")!==t||a.get("name")!==i||!o||!s?.startsWith("sha256:"))return null;return{kind:t,name:i,version:o,hash:s.slice(7)}}function j(e){let t=new Map;for(let s of e.split(`
8
- `)){let n=s.indexOf("=");if(n===-1)continue;t.set(s.slice(0,n),s.slice(n+1))}let i=t.get("plugin"),a=t.get("version");if(i!==H||!a)return null;let o=t.get("hash");return{plugin:i,version:a,hash:o?.startsWith("sha256:")?o.slice(7):null}}import{mkdir as At,rm as F,writeFile as Tt}from"node:fs/promises";import{join as J,sep as St}from"node:path";import{readFile as ae}from"node:fs/promises";async function w(e){try{return await ae(e,"utf8")}catch(t){if(t.code==="ENOENT")return null;throw t}}function Z(e){return`${["---",`description: ${JSON.stringify(e.description)}`,...e.agent?[`agent: ${JSON.stringify(e.agent)}`]:[],...e.subtask===void 0?[]:[`subtask: ${e.subtask}`],"---"].join(`
2
+ import{homedir as ge}from"node:os";import{readdir as ce,readFile as de,rm as u,rmdir as le}from"node:fs/promises";import{dirname as ue,join as d,normalize as he,sep as pe}from"node:path";var M={fast:"low",balanced:"medium",deep:"high"},W={"flow-reviewer":{mode:"subagent",hidden:!0,description:"Internal read-only Flow review subagent. Use /flow-review or /flow-auto.",prompt:"You are the Flow reviewer, a hidden read-only subagent. Load `flow-review`; call flow_status first. If no active compatible Flow session exists, do advisory review only and do not call flow_review_record. After one successful/no-op flow_review_record, report the verdict and stop.",reasoningEffort:M.deep,permission:{edit:"deny",bash:"deny",task:{"*":"deny"},"flow_*":"deny",flow_status:"allow",flow_review_record:"allow"}}},V={"flow-plan":{description:"Create, update, or approve a Flow plan",template:"Load the `flow-plan` skill and plan: $ARGUMENTS"},"flow-run":{description:"Run one approved Flow feature",template:"Load the `flow-run` skill and execute the next approved Flow feature. $ARGUMENTS"},"flow-auto":{description:"Drive the Flow loop autonomously until completion or a real blocker",template:"Load the `flow` skill and drive the Flow loop (status, plan, run, review) until completion or a real blocker: $ARGUMENTS"},"flow-status":{description:"Inspect the active Flow session and workspace readiness",template:"Call flow_status (detailed) and report session state, readiness checks, and the suggested next step."},"flow-review":{description:"Run a read-only Flow review with a fresh context",agent:"flow-reviewer",subtask:!0,template:"Load the `flow-review` skill and review: $ARGUMENTS"}},R=["flow-doctor","flow-history","flow-reset","flow-session"];import{createHash as E}from"node:crypto";import{join as g}from"node:path";var A=g(".config","opencode","skills"),m=g(".config","opencode","commands"),I=g(".config","opencode","agents"),v=".flow-skill-version",G="SKILL.md.backup",T=g(".config","opencode","plugins","flow.js"),q=`// Managed by flow-opencode install/uninstall
3
+ `,X="flow-opencode-generated-skill",L=`<!-- ${X} `,ee=new RegExp(`^<!-- ${X} name=([a-z0-9]+(?:-[a-z0-9]+)*) version=([0-9]+) hash=sha256:([a-f0-9]{64}) -->$`,"u");function h(e){return E("sha256").update(e,"utf8").digest("hex")}function y(e){let t=e.split(`
4
+ `),i=t.flatMap((p,K)=>p.startsWith(L)?[K]:[]);if(i.length===0)return{kind:"not_generated"};if(i.length>1)return{kind:"invalid_generated",reason:"duplicate_marker"};let a=i[0];if(a===void 0)return{kind:"not_generated"};let o=t[a];if(o===void 0)return{kind:"invalid_generated",reason:"malformed_marker"};let s=o.match(ee);if(!s)return{kind:"invalid_generated",reason:"malformed_marker"};let[,n,r,c]=s;if(n===void 0||r===void 0||c===void 0)return{kind:"invalid_generated",reason:"malformed_marker"};let l=[...t.slice(0,a),...t.slice(a+1)].join(`
5
+ `);if(h(l)!==c)return{kind:"invalid_generated",reason:"hash_mismatch"};return{kind:"valid_generated",marker:{name:n,version:r,hash:c}}}var Q="opencode-plugin-flow",P="file=",C="=sha256:";function b(e){let t=new Map;for(let i of e.split(`
6
+ `)){if(!i.startsWith(P))continue;let a=i.slice(P.length),o=a.lastIndexOf(C);if(o===-1)continue;let s=a.slice(0,o),n=a.slice(o+C.length);if(s.length>0&&/^[a-f0-9]{64}$/.test(n))t.set(s,n)}return t}function k(e,t,i){let a=new Map;for(let n of e.split(`
7
+ `)){let r=n.indexOf("=");if(r===-1)continue;a.set(n.slice(0,r),n.slice(r+1))}let o=a.get("version"),s=a.get("hash");if(a.get("plugin")!==Q||a.get("kind")!==t||a.get("name")!==i||!o||!s?.startsWith("sha256:"))return null;return{kind:t,name:i,version:o,hash:s.slice(7)}}function F(e){let t=new Map;for(let s of e.split(`
8
+ `)){let n=s.indexOf("=");if(n===-1)continue;t.set(s.slice(0,n),s.slice(n+1))}let i=t.get("plugin"),a=t.get("version");if(i!==Q||!a)return null;let o=t.get("hash");return{plugin:i,version:a,hash:o?.startsWith("sha256:")?o.slice(7):null}}import{mkdir as Dt,rm as S,writeFile as Kt}from"node:fs/promises";import{join as Y,sep as Et}from"node:path";import{readFile as ae}from"node:fs/promises";async function w(e){try{return await ae(e,"utf8")}catch(t){if(t.code==="ENOENT")return null;throw t}}function J(e){return`${["---",`description: ${JSON.stringify(e.description)}`,...e.agent?[`agent: ${JSON.stringify(e.agent)}`]:[],...e.subtask===void 0?[]:[`subtask: ${e.subtask}`],"---"].join(`
9
9
  `)}
10
10
 
11
11
  ${e.template}
12
- `}function $(e){return`${["---",`description: ${JSON.stringify(e.description)}`,`mode: ${e.mode}`,...e.reasoningEffort?[`reasoningEffort: ${e.reasoningEffort}`]:[],...e.permission?ie(e.permission):[],"---"].join(`
12
+ `}function Z(e){return`${["---",`description: ${JSON.stringify(e.description)}`,`mode: ${e.mode}`,...e.hidden===void 0?[]:[`hidden: ${e.hidden}`],...e.reasoningEffort?[`reasoningEffort: ${e.reasoningEffort}`]:[],...e.permission?ie(e.permission):[],"---"].join(`
13
13
  `)}
14
14
 
15
15
  ${e.prompt}
16
- `}function x(){return new Map(Object.entries(G).map(([e,t])=>[e,Z(t)]))}function _(){return new Map(Object.entries(V).map(([e,t])=>[e,$(t)]))}async function W(e){let t=[],i=[];for(let a of e.names){let o=J(e.root,`${a}.md`),s=J(e.root,`.${a}.flow-version`),n=await w(s),r=n===null?null:k(n,e.kind,a);if(r===null)continue;let d=await w(o);if(d!==null&&h(d)!==r.hash){i.push(o);continue}if(!e.dryRun)await F(o,{force:!0}),await F(s,{force:!0}),await F(`${o}.backup`,{force:!0});t.push(o)}return{removed:t,keptUserEdited:i}}function ie(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[o,s]of Object.entries(a))t.push(` ${JSON.stringify(o)}: ${JSON.stringify(s)}`)}}return t}async function P({homeDir:e,dryRun:t=!1,logger:i}){let a={removedSkills:[],keptUserEditedSkills:[],removedCommands:[],keptUserEditedCommands:[],removedAgents:[],keptUserEditedAgents:[],removedPreNpmPlugin:null,keptForeignPreNpmPlugin:null},o=c(e,A);for(let d of await we(o)){if(d!=="flow"&&!d.startsWith("flow-"))continue;let l=c(o,d),p=await fe(l);if(p==="foreign")continue;if(p==="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 ve(l);a.removedSkills.push(l),i?.(`${t?"Would remove":"Removed"} Flow skill at ${l}.`)}let s=await W({kind:"command",root:c(e,m),names:R,dryRun:t});for(let d of s.removed)a.removedCommands.push(d),i?.(`${t?"Would remove":"Removed"} retired Flow command at ${d}.`);for(let d of s.keptUserEdited)a.keptUserEditedCommands.push(d),i?.(`Kept user-edited Flow command at ${d}; remove it manually if it is no longer needed.`);await O({homeDir:e,dryRun:t,logger:i,kind:"command",root:c(e,m),files:x(),removed:a.removedCommands,keptUserEdited:a.keptUserEditedCommands}),await O({homeDir:e,dryRun:t,logger:i,kind:"agent",root:c(e,T),files:_(),removed:a.removedAgents,keptUserEdited:a.keptUserEditedAgents});let n=c(e,q),r=await f(n);if(r!==null)if(r.startsWith(S)){if(!t)await u(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 fe(e){let t=c(e,"SKILL.md"),i=await f(t),a=await f(c(e,v)),o=a===null?null:j(a);if(o!==null&&a!==null){for(let[r,d]of b(a)){if(r==="SKILL.md")continue;let l=N(e,r);if(l===null)continue;let p=await f(l);if(p!==null&&h(p)!==d)return"user_edited"}if(i===null)return"pristine";if(o.hash!==null&&h(i)===o.hash)return"pristine";return y(i).kind==="valid_generated"?"pristine":"user_edited"}if(i===null)return"foreign";let s=y(i);if(s.kind==="valid_generated")return"pristine";if(s.kind==="invalid_generated")return"user_edited";return"foreign"}function N(e,t){let i=he(c(e,...t.split("/")));if(i!==e&&i.startsWith(`${e}${pe}`))return i;return null}async function ve(e){let t=await f(c(e,v)),i=new Set;if(t!==null)for(let a of b(t).keys()){let o=N(e,a);if(o===null)continue;await u(o,{force:!0}),await u(`${o}.backup`,{force:!0});let s=ue(o);if(s!==e)i.add(s)}await u(c(e,"SKILL.md"),{force:!0}),await u(c(e,v),{force:!0}),await u(c(e,Y),{force:!0});for(let a of[...i].sort((o,s)=>s.length-o.length))await B(a);await B(e)}async function B(e){try{await le(e)}catch(t){let i=t.code;if(i!=="ENOENT"&&i!=="ENOTEMPTY")throw t}}async function we(e){try{return(await de(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 ce(e,"utf8")}catch(t){if(t.code==="ENOENT")return null;throw t}}async function O(e){for(let[t,i]of e.files){let a=c(e.root,`${t}.md`),o=c(e.root,`.${t}.flow-version`),s=await f(a),n=await f(o),r=n===null?null:k(n,e.kind,t);if(s===null&&r===null)continue;if(!(r!==null||s===i))continue;if(s!==null&&r!==null&&h(s)!==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 u(a,{force:!0}),await u(o,{force:!0}),await u(`${a}.backup`,{force:!0});e.removed.push(a),e.logger?.(`${e.dryRun?"Would remove":"Removed"} Flow ${e.kind} at ${a}.`)}if(!e.dryRun)await B(e.root)}var I=`opencode-plugin-flow — Flow plugin lifecycle commands
16
+ `}function x(){return new Map(Object.entries(V).map(([e,t])=>[e,J(t)]))}function _(){return new Map(Object.entries(W).map(([e,t])=>[e,Z(t)]))}async function j(e){let t=[],i=[];for(let a of e.names){let o=Y(e.root,`${a}.md`),s=Y(e.root,`.${a}.flow-version`),n=await w(s),r=n===null?null:k(n,e.kind,a);if(r===null)continue;let c=await w(o);if(c!==null&&h(c)!==r.hash){i.push(o);continue}if(!e.dryRun)await S(o,{force:!0}),await S(s,{force:!0}),await S(`${o}.backup`,{force:!0});t.push(o)}return{removed:t,keptUserEdited:i}}function ie(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[o,s]of Object.entries(a))t.push(` ${JSON.stringify(o)}: ${JSON.stringify(s)}`)}}return t}async function O({homeDir:e,dryRun:t=!1,logger:i}){let a={removedSkills:[],keptUserEditedSkills:[],removedCommands:[],keptUserEditedCommands:[],removedAgents:[],keptUserEditedAgents:[],removedPreNpmPlugin:null,keptForeignPreNpmPlugin:null},o=d(e,A);for(let c of await we(o)){if(c!=="flow"&&!c.startsWith("flow-"))continue;let l=d(o,c),p=await fe(l);if(p==="foreign")continue;if(p==="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 ve(l);a.removedSkills.push(l),i?.(`${t?"Would remove":"Removed"} Flow skill at ${l}.`)}let s=await j({kind:"command",root:d(e,m),names:R,dryRun:t});for(let c of s.removed)a.removedCommands.push(c),i?.(`${t?"Would remove":"Removed"} retired Flow command at ${c}.`);for(let c of s.keptUserEdited)a.keptUserEditedCommands.push(c),i?.(`Kept user-edited Flow command at ${c}; remove it manually if it is no longer needed.`);await $({homeDir:e,dryRun:t,logger:i,kind:"command",root:d(e,m),files:x(),removed:a.removedCommands,keptUserEdited:a.keptUserEditedCommands}),await $({homeDir:e,dryRun:t,logger:i,kind:"agent",root:d(e,I),files:_(),removed:a.removedAgents,keptUserEdited:a.keptUserEditedAgents});let n=d(e,T),r=await f(n);if(r!==null)if(r.startsWith(q)){if(!t)await u(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 fe(e){let t=d(e,"SKILL.md"),i=await f(t),a=await f(d(e,v)),o=a===null?null:F(a);if(o!==null&&a!==null){for(let[r,c]of b(a)){if(r==="SKILL.md")continue;let l=N(e,r);if(l===null)continue;let p=await f(l);if(p!==null&&h(p)!==c)return"user_edited"}if(i===null)return"pristine";if(o.hash!==null&&h(i)===o.hash)return"pristine";return y(i).kind==="valid_generated"?"pristine":"user_edited"}if(i===null)return"foreign";let s=y(i);if(s.kind==="valid_generated")return"pristine";if(s.kind==="invalid_generated")return"user_edited";return"foreign"}function N(e,t){let i=he(d(e,...t.split("/")));if(i!==e&&i.startsWith(`${e}${pe}`))return i;return null}async function ve(e){let t=await f(d(e,v)),i=new Set;if(t!==null)for(let a of b(t).keys()){let o=N(e,a);if(o===null)continue;await u(o,{force:!0}),await u(`${o}.backup`,{force:!0});let s=ue(o);if(s!==e)i.add(s)}await u(d(e,"SKILL.md"),{force:!0}),await u(d(e,v),{force:!0}),await u(d(e,G),{force:!0});for(let a of[...i].sort((o,s)=>s.length-o.length))await U(a);await U(e)}async function U(e){try{await le(e)}catch(t){let i=t.code;if(i!=="ENOENT"&&i!=="ENOTEMPTY")throw t}}async function we(e){try{return(await ce(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 de(e,"utf8")}catch(t){if(t.code==="ENOENT")return null;throw t}}async function $(e){for(let[t,i]of e.files){let a=d(e.root,`${t}.md`),o=d(e.root,`.${t}.flow-version`),s=await f(a),n=await f(o),r=n===null?null:k(n,e.kind,t);if(s===null&&r===null)continue;if(!(r!==null||s===i))continue;if(s!==null&&r!==null&&h(s)!==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 u(a,{force:!0}),await u(o,{force:!0}),await u(`${a}.backup`,{force:!0});e.removed.push(a),e.logger?.(`${e.dryRun?"Would remove":"Removed"} Flow ${e.kind} at ${a}.`)}if(!e.dryRun)await U(e.root)}var B=`opencode-plugin-flow — Flow plugin lifecycle commands
17
17
 
18
18
  Usage:
19
19
  bunx opencode-plugin-flow uninstall [--dry-run]
@@ -26,10 +26,10 @@ Commands:
26
26
 
27
27
  Options:
28
28
  --dry-run Show what would be removed without deleting anything
29
- --help Show this message`;function K(e){process.stdout.write(`${e}
29
+ --help Show this message`;function D(e){process.stdout.write(`${e}
30
30
  `)}function z(e){process.stderr.write(`${e}
31
- `)}async function me(e){let t=[...e];if(t.length===0||t.includes("--help")||t.includes("-h"))return K(I),0;let i=t.shift();if(i!=="uninstall")return z(`Unknown command: ${i}
31
+ `)}async function me(e){let t=[...e];if(t.length===0||t.includes("--help")||t.includes("-h"))return D(B),0;let i=t.shift();if(i!=="uninstall")return z(`Unknown command: ${i}
32
32
 
33
- ${I}`),1;let a=!1;for(let o of t){if(o==="--dry-run"){a=!0;continue}return z(`Unknown argument: ${o}
33
+ ${B}`),1;let a=!1;for(let o of t){if(o==="--dry-run"){a=!0;continue}return z(`Unknown argument: ${o}
34
34
 
35
- ${I}`),1}return await P({homeDir:process.env.HOME??ge(),dryRun:a,logger:K}),0}try{process.exitCode=await me(process.argv.slice(2))}catch(e){z(e instanceof Error?e.message:String(e)),process.exitCode=1}
35
+ ${B}`),1}return await O({homeDir:process.env.HOME??ge(),dryRun:a,logger:D}),0}try{process.exitCode=await me(process.argv.slice(2))}catch(e){z(e instanceof Error?e.message:String(e)),process.exitCode=1}