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 +46 -0
- package/README.md +1 -1
- package/dist/cli.js +12 -12
- package/dist/index.js +52 -52
- package/dist/index.js.map +7 -7
- package/package.json +3 -2
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
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
|
|
3
|
-
`,
|
|
4
|
-
`),a=t.flatMap((u,
|
|
5
|
-
`);if(p(l)!==d)return{kind:"invalid_generated",reason:"hash_mismatch"};return{kind:"valid_generated",marker:{name:n,version:r,hash:d}}}var
|
|
6
|
-
`)){if(!a.startsWith(
|
|
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")!==
|
|
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!==
|
|
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
|
|
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
|
|
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
|
|
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
|
-
${
|
|
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
|
-
${
|
|
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}
|