opencode-plugin-flow 3.3.8 → 3.3.10

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
@@ -2,6 +2,50 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [3.3.10] - 2026-06-14
6
+
7
+ Teach Flow to fan out discovery without making execution parallel
8
+
9
+ Flow's planning and review skills already ask for strong context, but broad audits and research-heavy goals still pushed one agent through every module, risk lens, or API stream serially. That made good context expensive, and it left no Flow-native guidance for borrowing host subagents safely when the work naturally splits into independent read-only slices.
10
+
11
+ This release adds a parallel discovery reference to the planning skill. It teaches the manager agent to profile the repository first, split only independent read-only discovery slices, require structured worker handoffs, and synthesize the evidence into existing Flow fields such as `planning.research`, `planning.reviewFindings`, requirements, architecture decisions, feature targets, review scopes, and notes. The review skill can use the same pattern for very large reviews while keeping severity, deduplication, refutation, and `flow_review_record` in the main reviewer. The run skill now states the boundary explicitly: parallel discovery may inform a feature, but Flow execution remains one active feature at a time.
12
+
13
+ The release intentionally keeps the runtime small. No commands, tools, agents, state paths, persisted schemas, runtime transitions, completion gates, review policy defaults, validation strictness, package exports, public payloads, or Flow-owned subagent roles changed. The only code change is distribution sync registration so the new reference file ships and updates like the existing skill references.
14
+
15
+ Constraint: Allow broad planning and review discovery to use host-native parallel workers without making Flow's state machine parallel
16
+ Constraint: Preserve existing context-pack fields instead of adding worker result state or a new runtime payload
17
+ Rejected: Add a dedicated `/flow-discover` command or new tool | the pattern is judgment-heavy guidance, and the existing skills already own planning and review orchestration
18
+ Rejected: Let workers call state-changing Flow tools | the manager must remain the single owner of plan, review, execution, and session mutations
19
+ Confidence: high
20
+ Scope-risk: low
21
+ Reversibility: clean - the reference file, skill wording, sync registration, docs, and release metadata can be reverted without migrating sessions
22
+ Directive: Use parallel discovery only for independent read-only planning or review slices; synthesize worker handoffs before saving Flow state, then execute one active feature at a time
23
+ Tested: `bun run check` (typecheck, lint, build, full test suite, architecture seam enforcement, npm-tarball install smoke asserting the 3.3.10 README pin, bundle sanity); `bun run smoke:release` (packed candidate tarball retained under release-smoke evidence, install smoke run against that tarball, manual live OpenCode checklist generated with the retained tarball install spec); focused skill sync/config tests; `bun run lint`; `.agents/skills/flow-contribution-check/scripts/preflight.sh commit`; `.agents/skills/flow-contribution-check/scripts/preflight.sh push`
24
+ Not-tested: Live OpenCode UI restart against the release candidate; this release changes skill guidance, synced reference content, maintainer docs, and release metadata only.
25
+
26
+ ## [3.3.9] - 2026-06-14
27
+
28
+ Make context handoff inspectable without bloating session docs
29
+
30
+ Flow's context pack had become useful enough that operators needed a direct read-only inspection surface, but the first implementation risked pushing too much product judgment and project-map detail into the always-rendered session docs. That made the feature feel heavier than the workflow backend should be.
31
+
32
+ This release adds `flow_context`, an eighth canonical runtime tool that reads the active session without mutating `.flow/**`. It exposes the derived context pack in summary, feature, or full views, including workflow profile, context quality, readiness, traceability, diagnostics, and an optional lightweight project structure map for handoff and review. The existing `flow_status` surface keeps the compact operator answer, while `flow_context` owns deeper inspection.
33
+
34
+ The release deliberately keeps the simplification boundary tight. Context quality is advisory instead of a readiness blocker, project structure mapping is on-demand through `flow_context` instead of embedded in every rendered `.flow/**/docs/context.md`, and shared path matching now drives both traceability and project-map role classification. Broad planned targets such as `.`, `./`, `*`, and `**/*` warn for specificity without falsely reporting in-scope changed artifacts as scope drift. Sensitive, ignored, absolute, and parent-relative focus paths are redacted from the project map focus summary.
35
+
36
+ No commands, state paths, persisted session schema version, runtime transition semantics, completion gates, review policy defaults, package exports, synced file ownership rules, or public mutation payloads changed. The public tool surface expands from seven to eight tools by adding one read-only projection tool, and `ignore` is added as a small runtime dependency for `.gitignore`-aware project-map filtering.
37
+
38
+ Constraint: Add deeper context inspection without turning context quality into a hard workflow gate
39
+ Constraint: Keep persisted session docs lightweight and source-of-truth state unchanged
40
+ Rejected: Revert the context work wholesale | the useful inspection and traceability improvements are worth keeping after simplification
41
+ Rejected: Embed the project structure map in every `docs/context.md` render | on-demand inspection avoids routine filesystem walks, snapshot churn, and unnecessary privacy surface
42
+ Confidence: high
43
+ Scope-risk: medium
44
+ Reversibility: clean - the read-only tool, derived projections, docs, tests, and dependency can be reverted without migrating sessions
45
+ Directive: Use `flow_context` for deep handoff inspection; keep `flow_status` as the compact operator readiness answer
46
+ Tested: `bun run check` (typecheck, lint, build, 440-test suite, architecture seam enforcement, npm-tarball install smoke asserting the 3.3.9 README pin, bundle sanity); `bun run smoke:release` (packed candidate tarball retained under release-smoke evidence, install smoke run against that tarball, manual live OpenCode checklist generated with the retained tarball install spec); `bun run report:architecture-metrics`; focused context-pack/render tests; `.agents/skills/flow-contribution-check/scripts/preflight.sh commit`; `.agents/skills/flow-contribution-check/scripts/preflight.sh push`
47
+ Not-tested: Live OpenCode UI restart against the release candidate; this release changes a read-only tool surface, derived projections, rendered docs, skill guidance, docs, tests, dependency metadata, and release metadata only.
48
+
5
49
  ## [3.3.8] - 2026-06-14
6
50
 
7
51
  Make workflow readiness answer for scope drift
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.8"]
34
+ "plugin": ["opencode-plugin-flow@3.3.10"]
35
35
  }
36
36
  ```
37
37
 
@@ -120,11 +120,12 @@ These five are the whole command surface since v3.1. The v2/v3.0 convenience com
120
120
 
121
121
  ### Tools
122
122
 
123
- The plugin registers a small tool surface (7 tools) that owns all `.flow/**` mutations:
123
+ The plugin registers a small tool surface (8 tools). All `.flow/**` mutations go through the state-changing tools; `flow_context` is read-only:
124
124
 
125
125
  | Tool | Purpose |
126
126
  | --- | --- |
127
127
  | `flow_status` | Session state, doctor-style readiness, and a computed suggested next step. |
128
+ | `flow_context` | Read-only context pack, quality score, traceability, and project structure map for planning/review handoff. |
128
129
  | `flow_plan_save` | Create or update the draft plan (planning context plus features). |
129
130
  | `flow_plan_approve` | Approve the plan, optionally restricted to a feature subset. |
130
131
  | `flow_run_start` | Start the next runnable feature. |
@@ -132,7 +133,7 @@ The plugin registers a small tool surface (7 tools) that owns all `.flow/**` mut
132
133
  | `flow_review_record` | Record a reviewer decision (`scope: feature` or `final`). |
133
134
  | `flow_session` | Activate or close a session, list history, or show a stored session. |
134
135
 
135
- These seven tools are the whole registered surface — the v2 tool-name redirect stubs that shipped in 3.0 were removed in v3.1 as scheduled. Existing v2 sessions still migrate seamlessly (the session schema is unchanged).
136
+ These eight tools are the whole registered surface — the v2 tool-name redirect stubs that shipped in 3.0 were removed in v3.1 as scheduled. Existing v2 sessions still migrate seamlessly (the session schema is unchanged).
136
137
 
137
138
  ### Agents
138
139
 
@@ -152,7 +153,7 @@ Plus: atomic, locked, path-safe writes under `.flow/**`; schema validation of al
152
153
 
153
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.
154
155
 
155
- `/flow-status` also reports advisory `workflowReadiness`, `contextTraceability`, and `contextDiagnostics`. These fields show whether the session is ready for planning, execution, feature review, final review, or release; which planned targets changed; which validation and review evidence exists; and where planned context is weak. They do not replace review judgment, but they make context gaps visible before they become unverifiable completion claims.
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
157
 
157
158
  ## What Flow writes
158
159
 
@@ -161,7 +162,7 @@ Flow stores workflow state in the project/worktree where OpenCode is running:
161
162
  ```text
162
163
  .flow/active/<session-id>/session.json # active session (source of truth)
163
164
  .flow/active/<session-id>/docs/** # readable derived views
164
- .flow/active/<session-id>/docs/context.md # derived context pack, readiness, traceability, and diagnostics
165
+ .flow/active/<session-id>/docs/context.md # derived context pack, readiness, quality, traceability, and diagnostics
165
166
  .flow/stored/<session-id>/session.json # parked resumable sessions
166
167
  .flow/completed/<session-id>-<timestamp>/** # closed session history
167
168
  .flow/locks/
package/dist/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import{homedir as he}from"node:os";import{readdir as ie,readFile as ae,rm as h,rmdir as se}from"node:fs/promises";import{dirname as oe,join as c,normalize as ne,sep as re}from"node:path";var Q={fast:"low",balanced:"medium",deep:"high"},j={"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:Q.deep,permission:{edit:"deny",bash:"deny",task:{"*":"deny"},"flow_*":"deny",flow_status:"allow",flow_review_record:"allow"}}},W={"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"}},k=["flow-doctor","flow-history","flow-reset","flow-session"];import{createHash as Z}from"node:crypto";import{join as w}from"node:path";var x=w(".config","opencode","skills"),g=w(".config","opencode","commands"),_=w(".config","opencode","agents"),v=".flow-skill-version",N="SKILL.md.backup",A=w(".config","opencode","plugins","flow.js"),R=`// Managed by flow-opencode install/uninstall
3
- `,E="flow-opencode-generated-skill",$=`<!-- ${E} `,K=new RegExp(`^<!-- ${E} name=([a-z0-9]+(?:-[a-z0-9]+)*) version=([0-9]+) hash=sha256:([a-f0-9]{64}) -->$`,"u");function p(e){return Z("sha256").update(e,"utf8").digest("hex")}function m(e){let t=e.split(`
4
- `),a=t.flatMap((u,J)=>u.startsWith($)?[J]:[]);if(a.length===0)return{kind:"not_generated"};if(a.length>1)return{kind:"invalid_generated",reason:"duplicate_marker"};let i=a[0];if(i===void 0)return{kind:"not_generated"};let s=t[i];if(s===void 0)return{kind:"invalid_generated",reason:"malformed_marker"};let o=s.match(K);if(!o)return{kind:"invalid_generated",reason:"malformed_marker"};let[,n,r,d]=o;if(n===void 0||r===void 0||d===void 0)return{kind:"invalid_generated",reason:"malformed_marker"};let l=[...t.slice(0,i),...t.slice(i+1)].join(`
5
- `);if(p(l)!==d)return{kind:"invalid_generated",reason:"hash_mismatch"};return{kind:"valid_generated",marker:{name:n,version:r,hash:d}}}var V="opencode-plugin-flow",O="file=",B="=sha256:";function y(e){let t=new Map;for(let a of e.split(`
6
- `)){if(!a.startsWith(O))continue;let i=a.slice(O.length),s=i.lastIndexOf(B);if(s===-1)continue;let o=i.slice(0,s),n=i.slice(s+B.length);if(o.length>0&&/^[a-f0-9]{64}$/.test(n))t.set(o,n)}return t}function b(e,t,a){let i=new Map;for(let n of e.split(`
7
- `)){let r=n.indexOf("=");if(r===-1)continue;i.set(n.slice(0,r),n.slice(r+1))}let s=i.get("version"),o=i.get("hash");if(i.get("plugin")!==V||i.get("kind")!==t||i.get("name")!==a||!s||!o?.startsWith("sha256:"))return null;return{kind:t,name:a,version:s,hash:o.slice(7)}}function T(e){let t=new Map;for(let o of e.split(`
8
- `)){let n=o.indexOf("=");if(n===-1)continue;t.set(o.slice(0,n),o.slice(n+1))}let a=t.get("plugin"),i=t.get("version");if(a!==V||!i)return null;let s=t.get("hash");return{plugin:a,version:i,hash:s?.startsWith("sha256:")?s.slice(7):null}}import{mkdir as Me,readFile as D,rm as I,writeFile as Xe}from"node:fs/promises";import{dirname as Je,join as z,sep as Qe}from"node:path";function P(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 he}from"node:os";import{readdir as ae,readFile as ie,rm as h,rmdir as se}from"node:fs/promises";import{dirname as oe,join as c,normalize as ne,sep as re}from"node:path";var J={fast:"low",balanced:"medium",deep:"high"},j={"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:J.deep,permission:{edit:"deny",bash:"deny",task:{"*":"deny"},"flow_*":"deny",flow_status:"allow",flow_review_record:"allow"}}},W={"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"}},k=["flow-doctor","flow-history","flow-reset","flow-session"];import{createHash as P}from"node:crypto";import{join as v}from"node:path";var x=v(".config","opencode","skills"),g=v(".config","opencode","commands"),_=v(".config","opencode","agents"),w=".flow-skill-version",N="SKILL.md.backup",R=v(".config","opencode","plugins","flow.js"),A=`// Managed by flow-opencode install/uninstall
3
+ `,E="flow-opencode-generated-skill",K=`<!-- ${E} `,Z=new RegExp(`^<!-- ${E} name=([a-z0-9]+(?:-[a-z0-9]+)*) version=([0-9]+) hash=sha256:([a-f0-9]{64}) -->$`,"u");function p(e){return P("sha256").update(e,"utf8").digest("hex")}function m(e){let t=e.split(`
4
+ `),i=t.flatMap((u,H)=>u.startsWith(K)?[H]:[]);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 s=t[a];if(s===void 0)return{kind:"invalid_generated",reason:"malformed_marker"};let o=s.match(Z);if(!o)return{kind:"invalid_generated",reason:"malformed_marker"};let[,n,r,d]=o;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(p(l)!==d)return{kind:"invalid_generated",reason:"hash_mismatch"};return{kind:"valid_generated",marker:{name:n,version:r,hash:d}}}var z="opencode-plugin-flow",O="file=",B="=sha256:";function y(e){let t=new Map;for(let i of e.split(`
6
+ `)){if(!i.startsWith(O))continue;let a=i.slice(O.length),s=a.lastIndexOf(B);if(s===-1)continue;let o=a.slice(0,s),n=a.slice(s+B.length);if(o.length>0&&/^[a-f0-9]{64}$/.test(n))t.set(o,n)}return t}function b(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 s=a.get("version"),o=a.get("hash");if(a.get("plugin")!==z||a.get("kind")!==t||a.get("name")!==i||!s||!o?.startsWith("sha256:"))return null;return{kind:t,name:i,version:s,hash:o.slice(7)}}function T(e){let t=new Map;for(let o of e.split(`
8
+ `)){let n=o.indexOf("=");if(n===-1)continue;t.set(o.slice(0,n),o.slice(n+1))}let i=t.get("plugin"),a=t.get("version");if(i!==z||!a)return null;let s=t.get("hash");return{plugin:i,version:a,hash:s?.startsWith("sha256:")?s.slice(7):null}}import{mkdir as He,readFile as $,rm as F,writeFile as Je}from"node:fs/promises";import{dirname as Ke,join as U,sep as Ze}from"node:path";function D(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}
@@ -13,7 +13,7 @@ ${e.template}
13
13
  `)}
14
14
 
15
15
  ${e.prompt}
16
- `}function G(){return new Map(Object.entries(W).map(([e,t])=>[e,P(t)]))}function L(){return new Map(Object.entries(j).map(([e,t])=>[e,ee(t)]))}async function Y(e){let t=[],a=[];for(let i of e.names){let s=z(e.root,`${i}.md`),o=z(e.root,`.${i}.flow-version`),n=await U(o),r=n===null?null:b(n,e.kind,i);if(r===null)continue;let d=await U(s);if(d!==null&&p(d)!==r.hash){a.push(s);continue}if(!e.dryRun)await I(s,{force:!0}),await I(o,{force:!0}),await I(`${s}.backup`,{force:!0});t.push(s)}return{removed:t,keptUserEdited:a}}async function U(e){try{return await D(e,"utf8")}catch(t){if(t.code==="ENOENT")return null;throw t}}function te(e){if(!e)return[];let t=["permission:"];for(let[a,i]of Object.entries(e)){if(typeof i==="string"){t.push(` ${JSON.stringify(a)}: ${JSON.stringify(i)}`);continue}if(i&&typeof i==="object"){t.push(` ${JSON.stringify(a)}:`);for(let[s,o]of Object.entries(i))t.push(` ${JSON.stringify(s)}: ${JSON.stringify(o)}`)}}return t}async function M({homeDir:e,dryRun:t=!1,logger:a}){let i={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"){i.keptUserEditedSkills.push(l),a?.(`Kept user-edited Flow skill at ${l}; remove it manually if it is no longer needed.`);continue}if(!t)await ce(l);i.removedSkills.push(l),a?.(`${t?"Would remove":"Removed"} Flow skill at ${l}.`)}let o=await Y({kind:"command",root:c(e,g),names:k,dryRun:t});for(let d of o.removed)i.removedCommands.push(d),a?.(`${t?"Would remove":"Removed"} retired Flow command at ${d}.`);for(let d of o.keptUserEdited)i.keptUserEditedCommands.push(d),a?.(`Kept user-edited Flow command at ${d}; remove it manually if it is no longer needed.`);await C({homeDir:e,dryRun:t,logger:a,kind:"command",root:c(e,g),files:G(),removed:i.removedCommands,keptUserEdited:i.keptUserEditedCommands}),await C({homeDir:e,dryRun:t,logger:a,kind:"agent",root:c(e,_),files:L(),removed:i.removedAgents,keptUserEdited:i.keptUserEditedAgents});let n=c(e,A),r=await f(n);if(r!==null)if(r.startsWith(R)){if(!t)await h(n,{force:!0});i.removedPreNpmPlugin=n,a?.(`${t?"Would remove":"Removed"} pre-npm Flow plugin copy at ${n}.`)}else i.keptForeignPreNpmPlugin=n,a?.(`Kept ${n}: it is not managed by Flow. Remove it manually if it is a stale Flow copy.`);return a?.('Finally, remove "opencode-plugin-flow" from the plugin array in opencode.json and restart OpenCode.'),i}async function de(e){let t=c(e,"SKILL.md"),a=await f(t),i=await f(c(e,v)),s=i===null?null:T(i);if(s!==null&&i!==null){for(let[r,d]of y(i)){if(r==="SKILL.md")continue;let l=X(e,r);if(l===null)continue;let u=await f(l);if(u!==null&&p(u)!==d)return"user_edited"}if(a===null)return"pristine";if(s.hash!==null&&p(a)===s.hash)return"pristine";return m(a).kind==="valid_generated"?"pristine":"user_edited"}if(a===null)return"foreign";let o=m(a);if(o.kind==="valid_generated")return"pristine";if(o.kind==="invalid_generated")return"user_edited";return"foreign"}function X(e,t){let a=ne(c(e,...t.split("/")));if(a!==e&&a.startsWith(`${e}${re}`))return a;return null}async function ce(e){let t=await f(c(e,v)),a=new Set;if(t!==null)for(let i of y(t).keys()){let s=X(e,i);if(s===null)continue;await h(s,{force:!0}),await h(`${s}.backup`,{force:!0});let o=oe(s);if(o!==e)a.add(o)}await h(c(e,"SKILL.md"),{force:!0}),await h(c(e,v),{force:!0}),await h(c(e,N),{force:!0});for(let i of[...a].sort((s,o)=>o.length-s.length))await q(i);await q(e)}async function q(e){try{await se(e)}catch(t){let a=t.code;if(a!=="ENOENT"&&a!=="ENOTEMPTY")throw t}}async function le(e){try{return(await ie(e,{withFileTypes:!0})).filter((a)=>a.isDirectory()).map((a)=>a.name).sort()}catch(t){if(t.code==="ENOENT")return[];throw t}}async function f(e){try{return await ae(e,"utf8")}catch(t){if(t.code==="ENOENT")return null;throw t}}async function C(e){for(let[t,a]of e.files){let i=c(e.root,`${t}.md`),s=c(e.root,`.${t}.flow-version`),o=await f(i),n=await f(s),r=n===null?null:b(n,e.kind,t);if(o===null&&r===null)continue;if(!(r!==null||o===a))continue;if(o!==null&&r!==null&&p(o)!==r.hash){e.keptUserEdited.push(i),e.logger?.(`Kept user-edited Flow ${e.kind} at ${i}; remove it manually if it is no longer needed.`);continue}if(!e.dryRun)await h(i,{force:!0}),await h(s,{force:!0}),await h(`${i}.backup`,{force:!0});e.removed.push(i),e.logger?.(`${e.dryRun?"Would remove":"Removed"} Flow ${e.kind} at ${i}.`)}if(!e.dryRun)await q(e.root)}var F=`opencode-plugin-flow — Flow plugin lifecycle commands
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
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 H(e){process.stdout.write(`${e}
29
+ --help Show this message`;function X(e){process.stdout.write(`${e}
30
30
  `)}function S(e){process.stderr.write(`${e}
31
- `)}async function ue(e){let t=[...e];if(t.length===0||t.includes("--help")||t.includes("-h"))return H(F),0;let a=t.shift();if(a!=="uninstall")return S(`Unknown command: ${a}
31
+ `)}async function ue(e){let t=[...e];if(t.length===0||t.includes("--help")||t.includes("-h"))return X(q),0;let i=t.shift();if(i!=="uninstall")return S(`Unknown command: ${i}
32
32
 
33
- ${F}`),1;let i=!1;for(let s of t){if(s==="--dry-run"){i=!0;continue}return S(`Unknown argument: ${s}
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
- ${F}`),1}return await M({homeDir:process.env.HOME??he(),dryRun:i,logger:H}),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 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}