opencode-plugin-flow 4.1.4 → 4.1.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 +4 -0
- package/README.md +41 -10
- package/dist/cli.js +4 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
One short line per release. For the full rationale behind each entry, see the
|
|
4
4
|
commit history and review evidence.
|
|
5
5
|
|
|
6
|
+
## [4.1.5] - 2026-06-16
|
|
7
|
+
|
|
8
|
+
Make Flow installation follow OpenCode's native plugin installer, with pre-start skill sync and older-version fallback.
|
|
9
|
+
|
|
6
10
|
## [4.1.4] - 2026-06-16
|
|
7
11
|
|
|
8
12
|
Make Flow skill loading restart-aware across every command, add manual sync repair, and treat missing optional helpers as explicit coverage gaps.
|
package/README.md
CHANGED
|
@@ -16,15 +16,19 @@ The manager still owns every Flow state change. Workers gather evidence; they do
|
|
|
16
16
|
|
|
17
17
|
## Install
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Use OpenCode's plugin installer when your OpenCode version supports it:
|
|
20
20
|
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
21
|
+
```bash
|
|
22
|
+
opencode plugin opencode-plugin-flow@4.1.5 --global
|
|
23
|
+
npx -y opencode-plugin-flow@4.1.5 sync
|
|
25
24
|
```
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
The first command adds Flow to your global OpenCode plugin config. The `sync`
|
|
27
|
+
command pre-installs Flow's managed skills so the next OpenCode startup can load
|
|
28
|
+
the refreshed skill registry.
|
|
29
|
+
|
|
30
|
+
Then start or restart OpenCode. On startup, the plugin syncs its global skills
|
|
31
|
+
into:
|
|
28
32
|
|
|
29
33
|
```text
|
|
30
34
|
~/.config/opencode/skills/flow/SKILL.md
|
|
@@ -35,6 +39,21 @@ Restart OpenCode once. On startup, the plugin syncs its global skills into:
|
|
|
35
39
|
~/.config/opencode/skills/flow-ui-quality/SKILL.md
|
|
36
40
|
```
|
|
37
41
|
|
|
42
|
+
If your OpenCode version does not have `opencode plugin`, add Flow to your
|
|
43
|
+
OpenCode config manually instead:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"plugin": ["opencode-plugin-flow@4.1.5"]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Then run the same pre-start skill sync and start or restart OpenCode:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx -y opencode-plugin-flow@4.1.5 sync
|
|
55
|
+
```
|
|
56
|
+
|
|
38
57
|
Project-local skill overrides still work through OpenCode's normal lookup:
|
|
39
58
|
|
|
40
59
|
```text
|
|
@@ -47,10 +66,18 @@ the skill registry for the running process, so a just-synced skill can exist on
|
|
|
47
66
|
disk while still being unavailable to that process. Flow reports this through
|
|
48
67
|
`flow_status` as `setup.skills.status: "restart_required"`.
|
|
49
68
|
|
|
69
|
+
To update a pinned Flow version, replace the existing OpenCode plugin entry and
|
|
70
|
+
sync the matching skill bundle:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
opencode plugin opencode-plugin-flow@4.1.5 --global --force
|
|
74
|
+
npx -y opencode-plugin-flow@4.1.5 sync
|
|
75
|
+
```
|
|
76
|
+
|
|
50
77
|
To inspect the installed skill set:
|
|
51
78
|
|
|
52
79
|
```bash
|
|
53
|
-
npx -y opencode-plugin-flow@4.1.
|
|
80
|
+
npx -y opencode-plugin-flow@4.1.5 doctor
|
|
54
81
|
```
|
|
55
82
|
|
|
56
83
|
If a command reports `Skill "flow-review" not found. Available skills...` or a
|
|
@@ -58,7 +85,7 @@ similar Flow skill-loading error, run `/flow-status` or the doctor command above
|
|
|
58
85
|
first. Missing, incomplete, or outdated managed skills can be repaired with:
|
|
59
86
|
|
|
60
87
|
```bash
|
|
61
|
-
npx -y opencode-plugin-flow@4.1.
|
|
88
|
+
npx -y opencode-plugin-flow@4.1.5 sync
|
|
62
89
|
```
|
|
63
90
|
|
|
64
91
|
Then restart OpenCode so the refreshed registry is loaded. `sync` manages all
|
|
@@ -146,8 +173,12 @@ The Flow version is its own OpenCode-native design: skills-first, manager-owned
|
|
|
146
173
|
|
|
147
174
|
## Uninstall
|
|
148
175
|
|
|
176
|
+
First remove `opencode-plugin-flow` from your OpenCode plugin config so future
|
|
177
|
+
OpenCode startups stop loading Flow. Then remove Flow-owned synced skills:
|
|
178
|
+
|
|
149
179
|
```bash
|
|
150
|
-
|
|
180
|
+
npx -y opencode-plugin-flow@4.1.5 uninstall
|
|
151
181
|
```
|
|
152
182
|
|
|
153
|
-
This removes Flow-owned synced skills when
|
|
183
|
+
Restart OpenCode after both steps. This removes Flow-owned synced skills when
|
|
184
|
+
they are pristine. User-edited or foreign skill folders are kept.
|
package/dist/cli.js
CHANGED
|
@@ -247,7 +247,7 @@ Start a follow-up wave when first-wave handoffs reveal:
|
|
|
247
247
|
|
|
248
248
|
Do not recurse by default. If a worker says it needs another worker, the manager
|
|
249
249
|
decides whether that is a second wave and writes the next bounded prompt.
|
|
250
|
-
`;var
|
|
250
|
+
`;var O='# Recovery playbook\n\nUse this when a Flow tool returns `status: "error"`, a blocker, or a `nextAction` that conflicts with memory.\n\n## First response\n\n1. Re-anchor with `flow_status`.\n2. Read the returned `summary`, `recovery`, `lastError`, and active feature.\n3. Fix the cause, then retry the smallest valid Flow action.\n\n## Common cases\n\n- `missing_session`: start with `flow_plan_save` using the user\'s goal.\n- `missing_goal`: ask for a concrete goal before planning.\n- `Approved plans cannot be changed`: use `flow_feature_reset` when only affected features need another pass; otherwise close and start a new goal.\n- `No feature is currently running`: call `flow_run_start` before completing.\n- `already in progress`: finish, reset, or block the active feature before starting another.\n- `Completion requires recorded validation evidence`: run real validation and include at least one passing `validationRun`.\n- `Completion requires all recorded validation to pass`: fix failures and rerun. Do not relabel failed checks as passed.\n- `Non-final feature completion requires targeted validation`: use `validationScope: "targeted"` for ordinary features.\n- `Final feature completion requires broad validation`: run the project-level gate and use `validationScope: "broad"`.\n- `Completion requires a passing featureReview`: run or request a real review and include a passing `featureReview` only when there are no blocking findings.\n- `Final feature completion requires a finalReview`: perform final review and include `finalReview`.\n- `Final review depth must match the plan policy`: use `reviewDepth` equal to the approved plan\'s `finalReviewPolicy`; valid final-review values are `broad` and `detailed`.\n- `Cannot close ... unfinished features`: complete, reset, defer, or abandon honestly. Do not mark completed while work remains.\n\n## Reset guidance\n\nUse `flow_feature_reset` when the active or completed work was built on the wrong assumption, validation revealed a design issue, dependencies need to be rerun, or dependent features must be invalidated. Resetting a feature also resets its dependents.\n\n## Closure guidance\n\nUse `flow_session_close`:\n\n- `completed`: only after all planned features are complete.\n- `deferred`: the user intentionally postpones unfinished work.\n- `abandoned`: the session should be archived without claiming delivery.\n\nAfter closure, the active `.flow/session.json` is removed and the archived JSON is stored under `.flow/history/`.\n';var z=`# Verification gates
|
|
251
251
|
|
|
252
252
|
Verification is how Flow keeps parallel work from turning into parallel
|
|
253
253
|
guesswork. Worker handoffs are candidate evidence; the manager decides what can
|
|
@@ -1208,8 +1208,8 @@ Approve only when the interface is both useful and inspectable:
|
|
|
1208
1208
|
- Screenshot/browser evidence supports the claim whenever feasible.
|
|
1209
1209
|
|
|
1210
1210
|
Never approve a UI change based only on code shape. If users will judge it visually, Flow evidence should include visual inspection.
|
|
1211
|
-
`;var w=[{name:"flow",files:[{relativePath:"SKILL.md",content:B},{relativePath:"references/recovery-playbook.md",content:
|
|
1212
|
-
`)}async function f(e){try{return await Ue(e,"utf8")}catch(a){if(a.code==="ENOENT")return null;throw a}}function A(e){let a=new Map;if(!e)return a;for(let t of e.split(/\r?\n/)){let r=/^file=(.+) sha256=([a-f0-9]{64})$/.exec(t)??/^file=(.+)=sha256:([a-f0-9]{64})$/.exec(t);if(r?.[1]&&r[2])a.set(r[1],r[2]);let n=/^hash=sha256:([a-f0-9]{64})$/.exec(t);if(n?.[1]&&!a.has("SKILL.md"))a.set("SKILL.md",n[1])}return a}function je(e){if(!e)return null;for(let a of e.split(/\r?\n/)){let t=/^version=(.+)$/.exec(a);if(t?.[1])return t[1]}return null}function k(e,a){let t=Pe(l(e,...a.split("/")));if(t!==e&&t.startsWith(`${e}${De}`))return t;throw Error(`Unsafe skill file path '${a}'.`)}async function We(e,a,t){let r=l(t,e.name),n=l(r,U),s=await f(n),d=A(s);if(await f(l(r,"SKILL.md"))!==null&&s===null)return{name:e.name,action:"skipped_foreign"};let p=!1,m=!1;for(let o of e.files){let i=k(r,o.relativePath),u=await f(i);if(u===o.content)continue;p=!0;let v=d.get(o.relativePath);if(u!==null&&(v?_(u)!==v:s!==null))await y(`${i}.backup`,u,"utf8"),m=!0}if(!p&&s===b(e,a))return{name:e.name,action:"unchanged"};if(!p)return await y(n,b(e,a),"utf8"),{name:e.name,action:"marker_updated"};let g=s!==null;for(let o of e.files){let i=k(r,o.relativePath);await Ce(Te(i),{recursive:!0}),await y(i,o.content,"utf8")}return await y(n,b(e,a),"utf8"),{name:e.name,action:m?"updated_with_backup":g?"updated":"installed"}}function
|
|
1211
|
+
`;var w=[{name:"flow",files:[{relativePath:"SKILL.md",content:B},{relativePath:"references/recovery-playbook.md",content:O},{relativePath:"references/parallel-orchestration.md",content:W},{relativePath:"references/handoff-format.md",content:j},{relativePath:"references/verification-gates.md",content:z}]},{name:"flow-plan",files:[{relativePath:"SKILL.md",content:M},{relativePath:"references/planning-examples.md",content:G},{relativePath:"references/parallel-discovery.md",content:S}]},{name:"flow-run",files:[{relativePath:"SKILL.md",content:Y},{relativePath:"references/validation-rubric.md",content:X},{relativePath:"references/audit-rubric.md",content:K}]},{name:"flow-review",files:[{relativePath:"SKILL.md",content:$},{relativePath:"references/review-rubric.md",content:L}]},{name:"flow-deslop",files:[{relativePath:"SKILL.md",content:E},{relativePath:"references/smell-rubric.md",content:V},{relativePath:"references/refactor-workflow.md",content:N}]},{name:"flow-ui-quality",files:[{relativePath:"SKILL.md",content:Q},{relativePath:"references/ui-rubric.md",content:J},{relativePath:"references/visual-verification.md",content:Z}]}];var U=".flow-skill-version";function x(){return process.env.HOME??process.env.USERPROFILE??""}function q(e=x()){return l(e,".config","opencode","skills")}function _(e){return Fe("sha256").update(e).digest("hex")}function b(e,a){return[`version=${a}`,...e.files.map((t)=>`file=${t.relativePath} sha256=${_(t.content)}`),""].join(`
|
|
1212
|
+
`)}async function f(e){try{return await Ue(e,"utf8")}catch(a){if(a.code==="ENOENT")return null;throw a}}function A(e){let a=new Map;if(!e)return a;for(let t of e.split(/\r?\n/)){let r=/^file=(.+) sha256=([a-f0-9]{64})$/.exec(t)??/^file=(.+)=sha256:([a-f0-9]{64})$/.exec(t);if(r?.[1]&&r[2])a.set(r[1],r[2]);let n=/^hash=sha256:([a-f0-9]{64})$/.exec(t);if(n?.[1]&&!a.has("SKILL.md"))a.set("SKILL.md",n[1])}return a}function je(e){if(!e)return null;for(let a of e.split(/\r?\n/)){let t=/^version=(.+)$/.exec(a);if(t?.[1])return t[1]}return null}function k(e,a){let t=Pe(l(e,...a.split("/")));if(t!==e&&t.startsWith(`${e}${De}`))return t;throw Error(`Unsafe skill file path '${a}'.`)}async function We(e,a,t){let r=l(t,e.name),n=l(r,U),s=await f(n),d=A(s);if(await f(l(r,"SKILL.md"))!==null&&s===null)return{name:e.name,action:"skipped_foreign"};let p=!1,m=!1;for(let o of e.files){let i=k(r,o.relativePath),u=await f(i);if(u===o.content)continue;p=!0;let v=d.get(o.relativePath);if(u!==null&&(v?_(u)!==v:s!==null))await y(`${i}.backup`,u,"utf8"),m=!0}if(!p&&s===b(e,a))return{name:e.name,action:"unchanged"};if(!p)return await y(n,b(e,a),"utf8"),{name:e.name,action:"marker_updated"};let g=s!==null;for(let o of e.files){let i=k(r,o.relativePath);await Ce(Te(i),{recursive:!0}),await y(i,o.content,"utf8")}return await y(n,b(e,a),"utf8"),{name:e.name,action:m?"updated_with_backup":g?"updated":"installed"}}function Oe(){return w.map((e)=>e.name)}function ze(e){return`npx -y opencode-plugin-flow@${e} doctor`}function T(){if(process.env.npm_package_version)return process.env.npm_package_version;try{let e=Ae(import.meta.url);for(let a of["../package.json","../../package.json"])try{let t=e(a);if(t.version)return t.version}catch{}}catch{}return"0.0.0"}async function ae(e,a=x()){let t=q(a);return Promise.all(w.map((r)=>We(r,e,t)))}async function te(e=T(),a=x()){let t=q(a),r=new Set(Oe()),n=await Promise.all(w.map(async(o)=>{let i=l(t,o.name),u=await f(l(i,U)),v=je(u),P=A(u);if(await f(l(i,"SKILL.md"))===null)return{name:o.name,path:i,status:"missing",markerVersion:v,missingFiles:o.files.map((h)=>h.relativePath),editedFiles:[],outdatedFiles:[]};if(u===null)return{name:o.name,path:i,status:"foreign",markerVersion:v,missingFiles:[],editedFiles:[],outdatedFiles:[]};let I=[],R=[],F=[];for(let h of o.files){let C=await f(k(i,h.relativePath));if(C===null){I.push(h.relativePath);continue}if(C===h.content)continue;let D=P.get(h.relativePath);if(D&&_(C)!==D){R.push(h.relativePath);continue}F.push(h.relativePath)}let ne=u!==b(o,e),se=I.length>0?"incomplete":R.length>0?"edited":ne||F.length>0?"outdated":"ok";return{name:o.name,path:i,status:se,markerVersion:v,missingFiles:I,editedFiles:R,outdatedFiles:F}})),s=[];try{s=await ee(t)}catch(o){if(o.code!=="ENOENT")throw o}let d=s.filter((o)=>(o==="flow"||o.startsWith("flow-"))&&!r.has(o)).map((o)=>l(t,o)),c=n.filter((o)=>["missing","incomplete","outdated"].includes(o.status)).map((o)=>o.name),p=n.filter((o)=>["foreign","edited"].includes(o.status)).map((o)=>o.name),m=p.length>0,g=c.length>0;return{status:m?"action_required":g?"sync_required":"ok",version:e,root:t,expectedSkills:[...r],skills:n,syncRequiredSkills:c,actionRequiredSkills:p,unmanagedFlowSkills:d}}function H(e,a,t){if(t.length===0)return;e.push(`- ${a}: ${t.join(", ")}`)}function oe(e){let a=["Flow doctor",`- status: ${e.status}`,`- plugin version: ${e.version}`,`- skills root: ${e.root}`,`- expected skills: ${e.expectedSkills.join(", ")}`];H(a,"startup sync can install/update",e.syncRequiredSkills),H(a,"needs user decision",e.actionRequiredSkills),a.push("","Skills:");for(let t of e.skills){if(a.push(`- ${t.name}: ${t.status} (${t.path})${t.markerVersion?` marker=${t.markerVersion}`:""}`),t.missingFiles.length>0)a.push(` missing: ${t.missingFiles.join(", ")}`);if(t.editedFiles.length>0)a.push(` edited: ${t.editedFiles.join(", ")}`);if(t.outdatedFiles.length>0)a.push(` outdated: ${t.outdatedFiles.join(", ")}`)}if(e.unmanagedFlowSkills.length>0){a.push("","Unmanaged Flow-like skill folders:");for(let t of e.unmanagedFlowSkills)a.push(`- ${t}`)}if(a.push("","Recommendation:"),e.status==="ok")a.push("- Flow skills are present and current.");else if(e.status==="sync_required")a.push("- Start or restart OpenCode with opencode-plugin-flow enabled so startup sync can install or update the listed skills. If Flow then reports restart_required, restart OpenCode once more so the refreshed skill registry is used.");else a.push("- Resolve user-owned or edited managed skill folders, then restart OpenCode. Move a folder aside to let Flow recreate it, or keep it intentionally as a local override.");return a.push(`- Details command: ${ze(e.version)}`),`${a.join(`
|
|
1213
1213
|
`)}
|
|
1214
1214
|
`}async function re(e=x()){let a=q(e),t=[],r=[],n;try{n=await ee(a)}catch(s){if(s.code==="ENOENT")return{removed:t,kept:r};throw s}for(let s of n){if(s!=="flow"&&!s.startsWith("flow-"))continue;let d=l(a,s),c=await f(l(d,U));if(c===null){r.push(d);continue}let p=A(c),m=!1;for(let[g,o]of p){let i=await f(k(d,g));if(i!==null&&_(i)!==o){m=!0;break}}if(m){r.push(d);continue}await qe(d,{recursive:!0,force:!0}),t.push(d)}return{removed:t,kept:r}}async function Be(e){let a=e[2];if(a!=="uninstall"&&a!=="doctor"&&a!=="sync"){process.stderr.write(`usage: opencode-plugin-flow <doctor|sync|uninstall>
|
|
1215
1215
|
`),process.exitCode=2;return}if(a==="doctor"){process.stdout.write(oe(await te()));return}if(a==="sync"){let r=T(),n=await ae(r),s=n.filter((c)=>["installed","updated","updated_with_backup"].includes(c.action)),d=n.filter((c)=>c.action==="skipped_foreign");process.stdout.write(`Flow skill sync (${r})
|
|
@@ -1218,6 +1218,6 @@ Never approve a UI change based only on code shape. If users will judge it visua
|
|
|
1218
1218
|
`);if(d.length>0)process.stdout.write(`Some managed skill folders are user-owned or edited; run doctor for repair guidance.
|
|
1219
1219
|
`);return}let t=await re();for(let r of t.removed)process.stdout.write(`Removed Flow skill: ${r}
|
|
1220
1220
|
`);for(let r of t.kept)process.stdout.write(`Kept non-Flow or user-edited skill: ${r}
|
|
1221
|
-
`);process.stdout.write(`Remove
|
|
1221
|
+
`);process.stdout.write(`Remove opencode-plugin-flow from your OpenCode plugin config and restart OpenCode.
|
|
1222
1222
|
`)}Be(process.argv).catch((e)=>{process.stderr.write(`${e instanceof Error?e.message:String(e)}
|
|
1223
1223
|
`),process.exitCode=1});
|