opencode-plugin-flow 7.1.0 → 7.3.0

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
@@ -6,6 +6,48 @@ One short entry per release, written for users deciding whether to upgrade.
6
6
 
7
7
  No changes yet.
8
8
 
9
+ ## [7.3.0] - 2026-08-17
10
+
11
+ Manager commands are thin routers, and independent review is measured against a
12
+ planted adjacent defect.
13
+
14
+ - `/flow-auto`, `/flow-plan`, and `/flow-run` now call compact status and load the
15
+ one needed guide through `flow_guidance` instead of shipping the full manuals in
16
+ the always-on command templates. The Session v5 schema and the published command,
17
+ tool, guide, and agent inventory are unchanged.
18
+ - `adjacent-defect-refused` fails any passing review of the planted defect,
19
+ including a silent pass or an advisories-only pass. A committed cassette pins
20
+ `flow_feature_complete` rejecting that plant.
21
+
22
+ Install or update:
23
+
24
+ ```bash
25
+ opencode plugin opencode-plugin-flow@7.3.0 --global --force
26
+ ```
27
+
28
+ ## [7.2.0] - 2026-08-10
29
+
30
+ Measured assurance lore makes Flow's evidence boundary visible at close and adds a
31
+ paired way to test whether the workflow earns its cost.
32
+
33
+ - Close delivery now derives tiered assurance for every completion check: runtime
34
+ enforced, host attested, caller declared, or model judged. The projection names
35
+ its limitations instead of presenting unlike evidence as equally certain. The
36
+ Session v5 schema and public command/tool inventory are unchanged.
37
+ - Eval reports now count workflow ceremony and evidence interventions, and an
38
+ ungated adjacent-defect scenario gives independent review a defect that
39
+ implementation is explicitly not authorized to repair.
40
+ - `bun run benchmark` runs seed-shuffled Flow and ordinary OpenCode arms against the
41
+ same hidden-graded tasks, then compares correctness, false completion, messages,
42
+ tokens, duration, and cost. The benchmark is exploratory and does not qualify a
43
+ release.
44
+
45
+ Install or update:
46
+
47
+ ```bash
48
+ opencode plugin opencode-plugin-flow@7.2.0 --global --force
49
+ ```
50
+
9
51
  ## [7.1.0] - 2026-07-28
10
52
 
11
53
  The last route to a dishonest `completed` closure is closed, and the two claims
package/README.md CHANGED
@@ -40,7 +40,7 @@ expensive, and it is overhead when it is not.
40
40
  Install the exact npm release through OpenCode:
41
41
 
42
42
  ```bash
43
- opencode plugin opencode-plugin-flow@7.1.0 --global --force
43
+ opencode plugin opencode-plugin-flow@7.3.0 --global --force
44
44
  ```
45
45
 
46
46
  Omit `--global` for project scope. Version pins are exact and never update on
@@ -51,7 +51,7 @@ The equivalent manual project configuration is:
51
51
  ```json
52
52
  {
53
53
  "$schema": "https://opencode.ai/config.json",
54
- "plugin": ["opencode-plugin-flow@7.1.0"]
54
+ "plugin": ["opencode-plugin-flow@7.3.0"]
55
55
  }
56
56
  ```
57
57
 
@@ -128,7 +128,8 @@ you granted.
128
128
  implicitly — Flow reports the blocker and waits for an explicit retry or an
129
129
  independent-feature choice. The last passing feature allows closure, and every
130
130
  accepted close returns a delivery summary derived from recorded state: each
131
- feature's attempts, latest outcome, and terminal findings.
131
+ feature's attempts, latest outcome, terminal findings, and tiered assurance with
132
+ explicit limitations.
132
133
 
133
134
  Findings keep stable ids across retries, and a failed review must carry every
134
135
  still-live finding forward — the runtime rejects a submission that drops one. A
package/dist/index.js CHANGED
@@ -797,34 +797,55 @@ var FLOW_REVIEW_PROMPT = [
797
797
  function skillBody(id) {
798
798
  return getFlowGuidance(id).content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "").trim();
799
799
  }
800
- var MANAGER_COMMANDS = {
801
- "flow-auto": {
802
- guidance: "flow",
803
- action: "Drive the Flow lifecycle only within the user's authorized scope. Stop after planning when implementation was not authorized: $ARGUMENTS"
804
- },
805
- "flow-plan": {
806
- guidance: "flow-plan",
807
- action: "Create or revise the Flow plan for: $ARGUMENTS"
808
- },
809
- "flow-run": {
810
- guidance: "flow-run",
811
- action: "Execute exactly one approved Flow feature: $ARGUMENTS"
812
- }
813
- };
814
- function managerCommand(surface) {
815
- const command = MANAGER_COMMANDS[surface];
816
- return `${skillBody(command.guidance)}
817
-
818
- ## Command
819
-
820
- ${command.action}`;
821
- }
800
+ var FLOW_AUTO_PROMPT = [
801
+ "# Flow router",
802
+ "Drive the Flow lifecycle only within the user's authorized scope. Stop after planning when implementation was not authorized: $ARGUMENTS",
803
+ [
804
+ 'Call `flow_status { request: { view: "compact" } }` first.',
805
+ "If the top-level response is an error, report its exact summary and recovery; stop without another mutation."
806
+ ].join(" "),
807
+ [
808
+ 'With no session or a draft, load `flow_guidance { id: "flow-plan" }` and follow it.',
809
+ 'For an approved ready, running, or blocked feature, load `flow_guidance { id: "flow-run" }` and follow it.',
810
+ "If `nextAction` is `flow_session_close` or compact status contains `archiveRetry`, load `flow-run` the same way."
811
+ ].join(" "),
812
+ "",
813
+ FLOW_MANAGER_KERNEL
814
+ ].join(`
815
+ `);
816
+ var FLOW_PLAN_PROMPT = [
817
+ "# Flow plan router",
818
+ "Create or revise the Flow plan for: $ARGUMENTS",
819
+ [
820
+ 'Call `flow_status { request: { view: "compact" } }` first.',
821
+ "If the top-level response is an error, report its exact summary and recovery; stop without another mutation."
822
+ ].join(" "),
823
+ 'Load `flow_guidance { id: "flow-plan" }` and follow it.'
824
+ ].join(`
825
+ `);
826
+ var FLOW_RUN_PROMPT = [
827
+ "# Flow run router",
828
+ "Execute exactly one approved Flow feature: $ARGUMENTS",
829
+ [
830
+ 'Call `flow_status { request: { view: "compact" } }` first.',
831
+ "If the top-level response is an error, report its exact summary and recovery; stop without another mutation."
832
+ ].join(" "),
833
+ [
834
+ "If status is `idle` or `planning`, explain that `/flow-run` requires an approved feature and stop.",
835
+ 'Otherwise load `flow_guidance { id: "flow-run" }` and follow it.'
836
+ ].join(" "),
837
+ "",
838
+ FLOW_MANAGER_KERNEL
839
+ ].join(`
840
+ `);
822
841
  function compileFlowPromptSurface(surface) {
823
842
  switch (surface) {
824
843
  case "flow-auto":
844
+ return FLOW_AUTO_PROMPT;
825
845
  case "flow-plan":
846
+ return FLOW_PLAN_PROMPT;
826
847
  case "flow-run":
827
- return managerCommand(surface);
848
+ return FLOW_RUN_PROMPT;
828
849
  case "flow-status":
829
850
  return FLOW_STATUS_PROMPT;
830
851
  case "flow-review":
@@ -2355,69 +2376,132 @@ var StatusInputSchema = z.object({
2355
2376
  }).strict();
2356
2377
 
2357
2378
  // src/application/delivery.ts
2358
- var NO_ARTIFACTS = "none reported";
2359
- function formatFeature(feature) {
2360
- const findings = feature.terminalFindings.map((finding) => ` - ${finding.severity}: ${finding.summary}`);
2361
- return [
2362
- `- ${feature.id} — ${feature.title}`,
2363
- ` attempts: ${feature.attempts}; latest state: ${feature.latestState}`,
2364
- ` outcome: ${feature.outcomeSummary ?? "none recorded"}`,
2365
- findings.length > 0 ? " terminal findings:" : " terminal findings: none",
2366
- ...findings
2379
+ var LIMITATIONS = [
2380
+ "Artifact paths and the canonical gate are caller declarations; Flow validates binding, not completeness or fitness.",
2381
+ "Goal alignment, scope discipline, evidence completeness, requirement coverage, test adequacy, and review substance remain model judgments.",
2382
+ "Freshness holds when review is accepted; an archive does not attest the current workspace."
2383
+ ];
2384
+ function currentRun2(session, featureId) {
2385
+ return session.runs.findLast((run) => run.featureId === featureId && run.state !== "superseded");
2386
+ }
2387
+ function assuranceProjection(session) {
2388
+ if (!session.closure)
2389
+ throw new Error("Assurance requires a recorded closure.");
2390
+ const complete = session.closure.kind === "completed";
2391
+ const features = session.plan?.features ?? [];
2392
+ const runs = features.flatMap((feature) => {
2393
+ const run = currentRun2(session, feature.id);
2394
+ return run ? [run] : [];
2395
+ });
2396
+ const accepted = runs.flatMap((run) => {
2397
+ const ids = new Set(run.reviews.filter((review) => review.result?.verdict === "passed").flatMap((review) => review.validationIds));
2398
+ return run.validations.filter((observation) => ids.has(observation.id) && isValidationEligible(observation));
2399
+ });
2400
+ const check = (id, label, tier, satisfied, explanation) => ({
2401
+ id,
2402
+ label,
2403
+ tier,
2404
+ status: complete ? satisfied ? "satisfied" : "unsatisfied" : "not-applicable",
2405
+ explanation: complete ? explanation : `${session.closure?.kind} closure makes no completion claim.`
2406
+ });
2407
+ const completed = features.filter((feature) => isFeatureComplete(session, feature.id)).length;
2408
+ const passing = runs.filter((run) => run.reviews.some((review) => review.result?.verdict === "passed")).length;
2409
+ const structural = session.plan !== null && features.length > 0 && completed === features.length && passing === features.length && runs.some((run) => run.reviews.some((review) => review.kind === "final" && review.result?.verdict === "passed")) && !runs.some((run) => (run.reviews.at(-1)?.result?.findings ?? []).some((finding) => finding.severity === "blocking"));
2410
+ const checks = [
2411
+ check("recorded-completion", "Recorded completion", "ts-enforced", structural, `${completed}/${features.length} features and ${passing}/${features.length} independent reviews pass, including a final review with no terminal blocker.`),
2412
+ check("accepted-validation", "Accepted validation", "host-attested", runs.length === features.length && runs.every((run) => accepted.some((observation) => observation.runId === run.id)), `${runs.filter((run) => accepted.some((item) => item.runId === run.id)).length}/${features.length} terminal runs have eligible host evidence accepted by review.`)
2367
2413
  ];
2414
+ const gate = session.plan?.gate;
2415
+ checks.push(gate === undefined ? {
2416
+ id: "canonical-gate",
2417
+ label: "Canonical gate",
2418
+ tier: "caller-declared",
2419
+ status: "not-applicable",
2420
+ explanation: "This legacy plan declared no canonical gate."
2421
+ } : check("canonical-gate", "Canonical gate", "host-attested", accepted.some((observation) => observation.command === gate && observation.scope === "broad"), `${JSON.stringify(gate)} must have eligible broad evidence accepted by review.`));
2422
+ const declared = session.plan?.externalEvidence;
2423
+ const missing = unsatisfiedExternalEvidence(session).length;
2424
+ checks.push(declared === undefined ? {
2425
+ id: "external-evidence",
2426
+ label: "Declared external evidence",
2427
+ tier: "caller-declared",
2428
+ status: "not-applicable",
2429
+ explanation: "This legacy plan declared no external-evidence obligations."
2430
+ } : check("external-evidence", "Declared external evidence", declared.length === 0 ? "caller-declared" : "host-attested", missing === 0, `${declared.length - missing}/${declared.length} declared obligations have eligible evidence on their declared host with named cases passing.`));
2431
+ return {
2432
+ conclusion: !complete ? "completion-not-claimed" : checks.some((item) => item.status === "unsatisfied") ? "completion-unsupported" : "completion-supported",
2433
+ checks,
2434
+ limitations: [...LIMITATIONS]
2435
+ };
2368
2436
  }
2437
+ var TIER_LABELS = {
2438
+ "ts-enforced": "TS-enforced",
2439
+ "host-attested": "host-attested",
2440
+ "caller-declared": "caller-declared"
2441
+ };
2369
2442
  function formatReport(delivery) {
2370
- const artifacts = delivery.reportedArtifacts;
2443
+ const lines = delivery.features.flatMap((feature) => [
2444
+ `- ${feature.id} — ${feature.title}`,
2445
+ ` attempts: ${feature.attempts}; latest state: ${feature.latestState}`,
2446
+ ` outcome: ${feature.outcomeSummary ?? "none recorded"}`,
2447
+ ...feature.terminalFindings.length === 0 ? [" terminal findings: none"] : [
2448
+ " terminal findings:",
2449
+ ...feature.terminalFindings.map((finding) => ` - ${finding.severity}: ${finding.summary}`)
2450
+ ]
2451
+ ]);
2371
2452
  return [
2372
2453
  `Goal: ${delivery.goal}`,
2373
2454
  `Closure: ${delivery.closure.kind}${delivery.closure.summary ? ` — ${delivery.closure.summary}` : ""}`,
2374
2455
  `Progress: ${delivery.progress.completed} of ${delivery.progress.total} features complete`,
2375
2456
  "Features:",
2376
- ...delivery.features.flatMap(formatFeature),
2457
+ ...lines,
2458
+ `Assurance: ${delivery.assurance.conclusion.replaceAll("-", " ")}`,
2459
+ "Assurance checks:",
2460
+ ...delivery.assurance.checks.map((item) => `- ${item.status} [${TIER_LABELS[item.tier]}] ${item.label}: ${item.explanation}`),
2461
+ "Assurance limitations:",
2462
+ ...delivery.assurance.limitations.map((item) => `- ${item}`),
2377
2463
  "Artifacts as reported by Flow from caller declarations, not an exact or exhaustive Git delta:",
2378
- `- latest attempts: ${artifacts.latestAttempts.join(", ") || NO_ARTIFACTS}`,
2379
- `- superseded attempts only: ${artifacts.supersededAttemptsOnly.join(", ") || NO_ARTIFACTS}`
2464
+ `- latest attempts: ${delivery.reportedArtifacts.latestAttempts.join(", ") || "none reported"}`,
2465
+ `- superseded attempts only: ${delivery.reportedArtifacts.supersededAttemptsOnly.join(", ") || "none reported"}`
2380
2466
  ];
2381
2467
  }
2382
2468
  function deliveryProjection(session) {
2383
- if (!session.closure) {
2384
- throw new Error("A delivery projection requires a recorded closure.");
2385
- }
2386
- const planFeatures = session.plan?.features ?? [];
2387
- const featureRuns = planFeatures.map((feature) => ({
2469
+ if (!session.closure)
2470
+ throw new Error("Delivery requires a recorded closure.");
2471
+ const features = session.plan?.features ?? [];
2472
+ const grouped = features.map((feature) => ({
2388
2473
  feature,
2389
2474
  runs: session.runs.filter((run) => run.featureId === feature.id)
2390
2475
  }));
2391
- const latestRuns = featureRuns.flatMap(({ runs }) => runs.slice(-1));
2392
- const latestArtifacts = new Set(latestRuns.flatMap((run) => run.artifactsChanged.map((artifact) => artifact.path)));
2393
- const allArtifacts = new Set(session.runs.flatMap((run) => run.artifactsChanged.map((artifact) => artifact.path)));
2394
- const completed = planFeatures.filter((feature) => isFeatureComplete(session, feature.id)).length;
2476
+ const latest = grouped.flatMap(({ runs }) => runs.slice(-1));
2477
+ const latestArtifacts = new Set(latest.flatMap((run) => run.artifactsChanged.map((item) => item.path)));
2478
+ const allArtifacts = new Set(session.runs.flatMap((run) => run.artifactsChanged.map((item) => item.path)));
2395
2479
  const delivery = {
2396
2480
  goal: session.goal,
2397
- closure: {
2398
- kind: session.closure.kind,
2399
- summary: session.closure.summary
2481
+ closure: { kind: session.closure.kind, summary: session.closure.summary },
2482
+ progress: {
2483
+ completed: features.filter((feature) => isFeatureComplete(session, feature.id)).length,
2484
+ total: features.length
2400
2485
  },
2401
- progress: { completed, total: planFeatures.length },
2402
- features: featureRuns.map(({ feature, runs }) => {
2403
- const latest = runs.at(-1);
2404
- const terminalResult = latest?.reviews.at(-1)?.result;
2486
+ features: grouped.map(({ feature, runs }) => {
2487
+ const run = runs.at(-1);
2405
2488
  return {
2406
2489
  id: feature.id,
2407
2490
  title: feature.title,
2408
2491
  attempts: runs.length,
2409
- latestState: latest?.state ?? "not-started",
2410
- outcomeSummary: latest?.summary ?? null,
2411
- terminalFindings: terminalResult?.findings.map((finding) => ({
2412
- severity: finding.severity,
2413
- summary: finding.summary
2492
+ latestState: run?.state ?? "not-started",
2493
+ outcomeSummary: run?.summary ?? null,
2494
+ terminalFindings: run?.reviews.at(-1)?.result?.findings.map(({ severity, summary }) => ({
2495
+ severity,
2496
+ summary
2414
2497
  })) ?? []
2415
2498
  };
2416
2499
  }),
2417
2500
  reportedArtifacts: {
2418
2501
  latestAttempts: [...latestArtifacts].sort(),
2419
2502
  supersededAttemptsOnly: [...allArtifacts].filter((path) => !latestArtifacts.has(path)).sort()
2420
- }
2503
+ },
2504
+ assurance: assuranceProjection(session)
2421
2505
  };
2422
2506
  return { ...delivery, report: formatReport(delivery) };
2423
2507
  }
@@ -5053,4 +5137,4 @@ export {
5053
5137
  plugin_default as default
5054
5138
  };
5055
5139
 
5056
- //# debugId=59C59901BAE4E80164756E2164756E21
5140
+ //# debugId=64BC7BCE73DE931B64756E2164756E21