opencode-plugin-flow 3.3.3 → 3.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,52 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [3.3.5] - 2026-06-13
6
+
7
+ Make the source ownership map executable
8
+
9
+ Flow's current maintainer docs already described a simple architecture: skills carry judgment, runtime owns safe session state, distribution owns install and sync, and the OpenCode adapter binds host surfaces to those internals. The existing architecture guardrail did not fully enforce that map. It still watched the older `core` / `workflow` / `runtime` / `adapters` graph, which left current owners such as `src/distribution`, `src/config-shared.ts`, and root package entrypoints outside the hard check.
10
+
11
+ This release broadens the seam guardrail to match the living codebase. `scripts/cross-area/architecture-seams.mjs` now scans `src/**`, classifies shared config, runtime, distribution, adapters, root entrypoints, core protocols, and the reserved workflow bucket, and rejects the dependency shortcuts that would blur those owners. Runtime can no longer import distribution, adapters, or root entrypoint facades; distribution cannot import runtime or adapters; shared config must stay implementation-free; adapters can compose runtime, distribution, shared config, and core without importing root entrypoint facades. The default `bun run check` now runs the enforced seam gate.
12
+
13
+ The code was adjusted to satisfy that stricter contract instead of documenting an exception. Flow's config projection moved into `src/config-shared.ts`, leaving the OpenCode config adapter as thin hook wiring. Runtime readiness no longer reaches into distribution install-sync helpers directly; the OpenCode status tool injects the install-check provider when it asks runtime for workspace readiness. The seam checker also resolves common TypeScript/JavaScript extensions and directory `index.*` imports before classifying edges, so the guardrail is harder to bypass by import spelling.
14
+
15
+ No commands, tools, state paths, schemas, synced file ownership rules, runtime transitions, completion gates, review policy defaults, validation strictness, package exports, public payloads, or skill guidance changed.
16
+
17
+ Constraint: Make the documented source ownership map executable without widening Flow's public workflow surface
18
+ Constraint: Keep distribution and adapter facts injected into runtime instead of imported from runtime code
19
+ Rejected: Leave the guardrail scoped to historical layer buckets | that would keep the current distribution and shared-config owners unenforced
20
+ Rejected: Add a new runtime exception for install readiness | the cleaner boundary is adapter-provided install diagnostics
21
+ Confidence: high
22
+ Scope-risk: low
23
+ Reversibility: clean - the refactor preserves public imports and can be reverted with the seam rules if needed
24
+ Directive: Future source-owner changes must update the ADR, seam checker, and cross-area seam tests together
25
+ Tested: `bun run check` (typecheck, lint, build, full test suite, architecture seam enforcement, npm-tarball install smoke asserting the 3.3.5 README pin, bundle sanity); `bun run smoke:release` (packed candidate tarball install smoke and release evidence bundle)
26
+ Not-tested: Live OpenCode UI restart against the release candidate; this release changes internal source boundaries, maintainer guardrails, docs, tests, and release metadata only.
27
+
28
+ ## [3.3.4] - 2026-06-13
29
+
30
+ Prune the historical maze and promote the living contracts
31
+
32
+ Flow's current behavior was already defined by the skills-first maintainer contract, the source, and tests, but the docs tree still carried a large pre-v3 paper trail: superseded plans, incident investigations, and retired architecture archives. Those files were useful while the architecture was moving quickly, but by 3.3 they had become a navigation hazard — easy to quote as if they were current, noisy enough to hide the actual contracts, and stale enough to send maintainers back into deleted prompt/projection doctrine.
33
+
34
+ This release removes that historical maze. The durable skills-first architecture decision is now captured as ADR 0001; the remaining current lessons from the deleted investigations are distilled into a skill review checklist; the maintainer contract now names the workspace root guard and hidden-root edit approval as separate write gates; and release/contributor docs now point maintainers to ADRs, current docs, source, and tests for present-day contracts. Release notes and `CHANGELOG.md` remain historical records, but stale release-note links were replaced where they would otherwise point at deleted plans.
35
+
36
+ The completion lane also gets its named maintainer affordance back: `bun run check:completion-lane` now runs the focused final-completion, semantic-invariant, hard-invariant, and recovery tests referenced by the transition owner. The package version and README exact install pin move together to `3.3.4` so OpenCode's spec-string package cache installs this release when users bump the pin.
37
+
38
+ No commands, tools, state paths, schemas, synced file ownership rules, runtime transitions, completion gates, review policy defaults, validation strictness, package exports, or public payloads changed.
39
+
40
+ Constraint: Preserve release history while deleting superseded plans, investigations, and pre-v3 architecture archives that no longer define current behavior
41
+ Constraint: Promote still-current lessons into current docs, ADRs, or the skill review checklist before deleting historical files
42
+ Rejected: Keep tombstone plan files just to preserve old links | that would recreate the stale navigation surface this release removes
43
+ Rejected: Rewrite old release notes wholesale | release notes remain historical evidence, not current contracts
44
+ Confidence: high
45
+ Scope-risk: low
46
+ Reversibility: clean — deleted historical files remain recoverable from git history; current contracts are now shorter and explicit
47
+ Directive: Future maintainers should use `docs/maintainer-contract.md`, ADRs, `docs/contributor-map.md`, source, and tests for current behavior; use release notes only as historical context
48
+ Tested: `bun run check` (typecheck, lint, build, full test suite, npm-tarball install smoke asserting the 3.3.4 README pin, bundle sanity); `bun run smoke:release` (packed candidate tarball install smoke and release evidence bundle); `bun run check:completion-lane`; stale-reference scan for deleted historical paths; `package.json` parse check
49
+ Not-tested: Live OpenCode UI restart against the release candidate; this release changes documentation, release metadata, and maintainer checks only.
50
+
5
51
  ## [3.3.3] - 2026-06-13
6
52
 
7
53
  Split the action hotspot without changing Flow's runtime contract
package/README.md CHANGED
@@ -29,7 +29,7 @@ Add Flow to the `plugin` array in your `opencode.json` (global `~/.config/openco
29
29
 
30
30
  ```json
31
31
  {
32
- "plugin": ["opencode-plugin-flow@3.3.3"]
32
+ "plugin": ["opencode-plugin-flow@3.3.5"]
33
33
  }
34
34
  ```
35
35
 
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 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 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",E="SKILL.md.backup",A=w(".config","opencode","plugins","flow.js"),R=`// Managed by flow-opencode install/uninstall
3
- `,B="flow-opencode-generated-skill",$=`<!-- ${B} `,K=new RegExp(`^<!-- ${B} 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 y(e){let t=e.split(`
4
- `),a=t.flatMap((u,Q)=>u.startsWith($)?[Q]:[]);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 z="opencode-plugin-flow",O="file=",N="=sha256:";function m(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(N);if(s===-1)continue;let o=i.slice(0,s),n=i.slice(s+N.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")!==z||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!==z||!i)return null;let s=t.get("hash");return{plugin:a,version:i,hash:s?.startsWith("sha256:")?s.slice(7):null}}import{mkdir as He,readFile as D,rm as I,writeFile as Ce}from"node:fs/promises";import{dirname as Qe,join as V,sep as Je}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 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 $={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:$.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 K}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",B="SKILL.md.backup",A=w(".config","opencode","plugins","flow.js"),R=`// Managed by flow-opencode install/uninstall
3
+ `,z="flow-opencode-generated-skill",M=`<!-- ${z} `,C=new RegExp(`^<!-- ${z} name=([a-z0-9]+(?:-[a-z0-9]+)*) version=([0-9]+) hash=sha256:([a-f0-9]{64}) -->$`,"u");function p(e){return K("sha256").update(e,"utf8").digest("hex")}function y(e){let t=e.split(`
4
+ `),a=t.flatMap((u,Z)=>u.startsWith(M)?[Z]:[]);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(C);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 E="opencode-plugin-flow",N="file=",O="=sha256:";function m(e){let t=new Map;for(let a of e.split(`
6
+ `)){if(!a.startsWith(N))continue;let i=a.slice(N.length),s=i.lastIndexOf(O);if(s===-1)continue;let o=i.slice(0,s),n=i.slice(s+O.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")!==E||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!==E||!i)return null;let s=t.get("hash");return{plugin:a,version:i,hash:s?.startsWith("sha256:")?s.slice(7):null}}import{mkdir as He,readFile as D,rm as I,writeFile as Qe}from"node:fs/promises";import{dirname as Ze,join as V,sep as $e}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(`
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 U(){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=V(e.root,`${i}.md`),o=V(e.root,`.${i}.flow-version`),n=await L(o),r=n===null?null:b(n,e.kind,i);if(r===null)continue;let d=await L(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 L(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 H({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 X({homeDir:e,dryRun:t,logger:a,kind:"command",root:c(e,g),files:G(),removed:i.removedCommands,keptUserEdited:i.keptUserEditedCommands}),await X({homeDir:e,dryRun:t,logger:a,kind:"agent",root:c(e,_),files:U(),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 m(i)){if(r==="SKILL.md")continue;let l=C(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 y(a).kind==="valid_generated"?"pristine":"user_edited"}if(a===null)return"foreign";let o=y(a);if(o.kind==="valid_generated")return"pristine";if(o.kind==="invalid_generated")return"user_edited";return"foreign"}function C(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 m(t).keys()){let s=C(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,E),{force:!0});for(let i of[...a].sort((s,o)=>o.length-s.length))await F(i);await F(e)}async function F(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 X(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 F(e.root)}var q=`opencode-plugin-flow — Flow plugin lifecycle commands
16
+ `}function L(){return new Map(Object.entries(W).map(([e,t])=>[e,P(t)]))}function U(){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=V(e.root,`${i}.md`),o=V(e.root,`.${i}.flow-version`),n=await G(o),r=n===null?null:b(n,e.kind,i);if(r===null)continue;let d=await G(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 G(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 H({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 X({homeDir:e,dryRun:t,logger:a,kind:"command",root:c(e,g),files:L(),removed:i.removedCommands,keptUserEdited:i.keptUserEditedCommands}),await X({homeDir:e,dryRun:t,logger:a,kind:"agent",root:c(e,_),files:U(),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 m(i)){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(a===null)return"pristine";if(s.hash!==null&&p(a)===s.hash)return"pristine";return y(a).kind==="valid_generated"?"pristine":"user_edited"}if(a===null)return"foreign";let o=y(a);if(o.kind==="valid_generated")return"pristine";if(o.kind==="invalid_generated")return"user_edited";return"foreign"}function Q(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 m(t).keys()){let s=Q(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,B),{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 X(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
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 M(e){process.stdout.write(`${e}
29
+ --help Show this message`;function J(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 M(q),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 J(F),0;let a=t.shift();if(a!=="uninstall")return S(`Unknown command: ${a}
32
32
 
33
- ${q}`),1;let i=!1;for(let s of t){if(s==="--dry-run"){i=!0;continue}return S(`Unknown argument: ${s}
33
+ ${F}`),1;let i=!1;for(let s of t){if(s==="--dry-run"){i=!0;continue}return S(`Unknown argument: ${s}
34
34
 
35
- ${q}`),1}return await H({homeDir:process.env.HOME??he(),dryRun:i,logger:M}),0}try{process.exitCode=await ue(process.argv.slice(2))}catch(e){S(e instanceof Error?e.message:String(e)),process.exitCode=1}
35
+ ${F}`),1}return await H({homeDir:process.env.HOME??he(),dryRun:i,logger:J}),0}try{process.exitCode=await ue(process.argv.slice(2))}catch(e){S(e instanceof Error?e.message:String(e)),process.exitCode=1}