opencode-plugin-flow 3.3.5 → 3.3.7
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 +6 -1
- package/dist/cli.js +11 -11
- package/dist/index.js +92 -89
- package/dist/index.js.map +7 -6
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [3.3.7] - 2026-06-14
|
|
6
|
+
|
|
7
|
+
Make the planned context visible enough to review
|
|
8
|
+
|
|
9
|
+
Flow's v3 workflow already asked agents to record the files, contracts, risks, and validation checks they relied on, but that context lived inside the session payload and scattered status summaries. It was durable, yet not easy to inspect after compaction, handoff, or review. That left an avoidable gap between the plan the agent believed it was following and the surfaces a reviewer could quickly verify.
|
|
10
|
+
|
|
11
|
+
This release turns the existing plan context into a first-class derived view. Every rendered session now includes `.flow/active/<session-id>/docs/context.md`, a compact context pack assembled from the existing repo profile, research, requirements, architecture decisions, feature file targets, review scope, changed artifacts, validation commands, and notes. The session JSON remains the source of truth; the new markdown is a readable projection like the existing index and feature docs.
|
|
12
|
+
|
|
13
|
+
`/flow-status` now also reports advisory `contextDiagnostics` when the planned context is weak or contradicted by execution evidence. It flags missing repo profile or research, feature slices with no file targets, no reviewable scope, no verification plan, completed work without recorded validation commands, and changed artifacts that fall outside the planned file targets or review scope. The skills were updated to use those diagnostics as planning/review prompts without treating them as new hard gates.
|
|
14
|
+
|
|
15
|
+
No commands, tools, schemas, package exports, synced file ownership rules, runtime transitions, completion gates, review policy defaults, validation strictness, or public mutation payloads changed. The only `.flow/**` path added is a derived markdown view under the existing session docs directory.
|
|
16
|
+
|
|
17
|
+
Constraint: Make context selection reviewable after compaction without adding a new command, tool, schema field, or runtime payload
|
|
18
|
+
Constraint: Keep the session JSON authoritative and make `docs/context.md` a derived projection only
|
|
19
|
+
Rejected: Add a persisted `contextPack` field | the existing plan and planning fields already contain the source data, and a new field would widen the schema for a rendering problem
|
|
20
|
+
Rejected: Turn context diagnostics into completion blockers immediately | weak context is often judgment-shaped, so the right first step is visible evidence for planning and review
|
|
21
|
+
Confidence: high
|
|
22
|
+
Scope-risk: low
|
|
23
|
+
Reversibility: clean - the derived renderer, status diagnostics, skill wording, and snapshots can be reverted without touching persisted sessions or public tools
|
|
24
|
+
Directive: Reviewers should compare changed artifacts and validation evidence against `.flow/**/docs/context.md` or `flow_status.contextDiagnostics` before accepting completion claims
|
|
25
|
+
Tested: `bun run check` (typecheck, lint, build, 431-test suite, architecture seam enforcement, npm-tarball install smoke asserting the 3.3.7 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/status/render tests; `git diff --check`
|
|
26
|
+
Not-tested: Live OpenCode UI restart against the release candidate; this release changes derived session docs, status diagnostics, skill guidance, docs, tests, and release metadata only.
|
|
27
|
+
|
|
28
|
+
## [3.3.6] - 2026-06-14
|
|
29
|
+
|
|
30
|
+
Make operational maturity visible without widening Flow's surface
|
|
31
|
+
|
|
32
|
+
Flow's architecture is now small and enforceable, but the day-to-day maintainer workflow still had gaps that depended on memory: contribution preflight lived in ad hoc command habits, simplification claims had no quick metrics baseline, planning/review context discipline was implied rather than named, and release smoke mentioned a manual live checklist without generating one tied to the candidate artifact.
|
|
33
|
+
|
|
34
|
+
This release adds those missing maintainer affordances. A repo-local `flow-contribution-check` skill now documents commit and push preflight, backed by a shell script that checks whitespace, staged diff hygiene, optional redacted `gitleaks` scans when available, architecture seams, release hygiene, and path-sensitive focused checks. A new `bun run report:architecture-metrics` script reports source-owner counts, largest files, public workflow surface counts, seam violations, test-file count, and built bundle sizes so future simplification claims have a stable baseline. The release smoke wrapper now keeps the packed candidate tarball under `.release-artifacts/release-smoke/candidate-tarball/`, runs smoke against that exact tarball, and writes a manual live OpenCode checklist whose install spec points at the same artifact.
|
|
35
|
+
|
|
36
|
+
The skills also get a workflow-quality tightening inspired by context-engineering practice: planning now asks the agent to record inspected files, tests, docs, contracts, risks, unknowns, and out-of-scope surfaces using existing plan fields; review now checks that context against the completed work and validation evidence. This stays skill-level only: no new runtime `contextPack` payload, no schema migration, and no extra tool.
|
|
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: Improve contributor preflight, simplification visibility, and release-candidate evidence without adding public workflow surface
|
|
41
|
+
Constraint: Keep context-pack discipline in existing plan/review fields instead of creating a new runtime payload
|
|
42
|
+
Rejected: Let the generated live checklist fall back to `opencode-plugin-flow@<version>` for pre-tag validation | that can test npm instead of the packed release candidate
|
|
43
|
+
Rejected: Make architecture metrics a hard gate immediately | the first useful step is a stable report baseline, not a new blocking threshold
|
|
44
|
+
Confidence: high
|
|
45
|
+
Scope-risk: low
|
|
46
|
+
Reversibility: clean - the scripts, skill guidance, and docs can be reverted without touching persisted sessions or public tool contracts
|
|
47
|
+
Directive: Future simplification or release-process claims should cite `bun run report:architecture-metrics`, and pre-tag live validation should use the retained candidate tarball generated by `bun run smoke:release`
|
|
48
|
+
Tested: `bun run check` (typecheck, lint, build, full test suite including metrics/checklist/release-smoke regressions, architecture seam enforcement, npm-tarball install smoke asserting the 3.3.6 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); `.agents/skills/flow-contribution-check/scripts/preflight.sh commit`; `bun run report:architecture-metrics`
|
|
49
|
+
Not-tested: Live OpenCode UI restart against the release candidate; this release changes maintainer guardrails, release evidence scaffolding, docs, tests, and skill guidance only.
|
|
50
|
+
|
|
5
51
|
## [3.3.5] - 2026-06-13
|
|
6
52
|
|
|
7
53
|
Make the source ownership map executable
|
package/README.md
CHANGED
|
@@ -23,13 +23,15 @@ Common examples:
|
|
|
23
23
|
|
|
24
24
|
Flow creates a tracked session under `.flow/**`, drafts a plan, executes one feature at a time, records validation evidence, reviews, and either continues, recovers, or stops with a concrete blocker.
|
|
25
25
|
|
|
26
|
+
It also renders a reviewable context pack for each session so the files, contracts, validation checks, and planning assumptions the agent relied on are visible after compaction or handoff.
|
|
27
|
+
|
|
26
28
|
## Install
|
|
27
29
|
|
|
28
30
|
Add Flow to the `plugin` array in your `opencode.json` (global `~/.config/opencode/opencode.json` or per-project):
|
|
29
31
|
|
|
30
32
|
```json
|
|
31
33
|
{
|
|
32
|
-
"plugin": ["opencode-plugin-flow@3.3.
|
|
34
|
+
"plugin": ["opencode-plugin-flow@3.3.7"]
|
|
33
35
|
}
|
|
34
36
|
```
|
|
35
37
|
|
|
@@ -150,6 +152,8 @@ Plus: atomic, locked, path-safe writes under `.flow/**`; schema validation of al
|
|
|
150
152
|
|
|
151
153
|
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.
|
|
152
154
|
|
|
155
|
+
`/flow-status` also reports advisory `contextDiagnostics` when the planned context is weak, such as missing repo profile entries, empty feature file targets, or missing validation plans. These warnings do not replace review judgment, but they make context gaps visible before they become unverifiable completion claims.
|
|
156
|
+
|
|
153
157
|
## What Flow writes
|
|
154
158
|
|
|
155
159
|
Flow stores workflow state in the project/worktree where OpenCode is running:
|
|
@@ -157,6 +161,7 @@ Flow stores workflow state in the project/worktree where OpenCode is running:
|
|
|
157
161
|
```text
|
|
158
162
|
.flow/active/<session-id>/session.json # active session (source of truth)
|
|
159
163
|
.flow/active/<session-id>/docs/** # readable derived views
|
|
164
|
+
.flow/active/<session-id>/docs/context.md # derived context pack and diagnostics
|
|
160
165
|
.flow/stored/<session-id>/session.json # parked resumable sessions
|
|
161
166
|
.flow/completed/<session-id>-<timestamp>/** # closed session history
|
|
162
167
|
.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
|
|
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 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"),A=w(".config","opencode","agents"),v=".flow-skill-version",N="SKILL.md.backup",_=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(`
|
|
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 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,A),files:L(),removed:i.removedAgents,keptUserEdited:i.keptUserEditedAgents});let n=c(e,_),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
|
|
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 H(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 H(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
|
-
${F}`),1}return await
|
|
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}
|