opencode-plugin-flow 7.1.0 → 7.2.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,29 @@ One short entry per release, written for users deciding whether to upgrade.
6
6
 
7
7
  No changes yet.
8
8
 
9
+ ## [7.2.0] - 2026-08-10
10
+
11
+ Measured assurance lore makes Flow's evidence boundary visible at close and adds a
12
+ paired way to test whether the workflow earns its cost.
13
+
14
+ - Close delivery now derives tiered assurance for every completion check: runtime
15
+ enforced, host attested, caller declared, or model judged. The projection names
16
+ its limitations instead of presenting unlike evidence as equally certain. The
17
+ Session v5 schema and public command/tool inventory are unchanged.
18
+ - Eval reports now count workflow ceremony and evidence interventions, and an
19
+ ungated adjacent-defect scenario gives independent review a defect that
20
+ implementation is explicitly not authorized to repair.
21
+ - `bun run benchmark` runs seed-shuffled Flow and ordinary OpenCode arms against the
22
+ same hidden-graded tasks, then compares correctness, false completion, messages,
23
+ tokens, duration, and cost. The benchmark is exploratory and does not qualify a
24
+ release.
25
+
26
+ Install or update:
27
+
28
+ ```bash
29
+ opencode plugin opencode-plugin-flow@7.2.0 --global --force
30
+ ```
31
+
9
32
  ## [7.1.0] - 2026-07-28
10
33
 
11
34
  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.2.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.2.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
@@ -2355,69 +2355,132 @@ var StatusInputSchema = z.object({
2355
2355
  }).strict();
2356
2356
 
2357
2357
  // 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
2358
+ var LIMITATIONS = [
2359
+ "Artifact paths and the canonical gate are caller declarations; Flow validates binding, not completeness or fitness.",
2360
+ "Goal alignment, scope discipline, evidence completeness, requirement coverage, test adequacy, and review substance remain model judgments.",
2361
+ "Freshness holds when review is accepted; an archive does not attest the current workspace."
2362
+ ];
2363
+ function currentRun2(session, featureId) {
2364
+ return session.runs.findLast((run) => run.featureId === featureId && run.state !== "superseded");
2365
+ }
2366
+ function assuranceProjection(session) {
2367
+ if (!session.closure)
2368
+ throw new Error("Assurance requires a recorded closure.");
2369
+ const complete = session.closure.kind === "completed";
2370
+ const features = session.plan?.features ?? [];
2371
+ const runs = features.flatMap((feature) => {
2372
+ const run = currentRun2(session, feature.id);
2373
+ return run ? [run] : [];
2374
+ });
2375
+ const accepted = runs.flatMap((run) => {
2376
+ const ids = new Set(run.reviews.filter((review) => review.result?.verdict === "passed").flatMap((review) => review.validationIds));
2377
+ return run.validations.filter((observation) => ids.has(observation.id) && isValidationEligible(observation));
2378
+ });
2379
+ const check = (id, label, tier, satisfied, explanation) => ({
2380
+ id,
2381
+ label,
2382
+ tier,
2383
+ status: complete ? satisfied ? "satisfied" : "unsatisfied" : "not-applicable",
2384
+ explanation: complete ? explanation : `${session.closure?.kind} closure makes no completion claim.`
2385
+ });
2386
+ const completed = features.filter((feature) => isFeatureComplete(session, feature.id)).length;
2387
+ const passing = runs.filter((run) => run.reviews.some((review) => review.result?.verdict === "passed")).length;
2388
+ 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"));
2389
+ const checks = [
2390
+ 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.`),
2391
+ 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
2392
  ];
2393
+ const gate = session.plan?.gate;
2394
+ checks.push(gate === undefined ? {
2395
+ id: "canonical-gate",
2396
+ label: "Canonical gate",
2397
+ tier: "caller-declared",
2398
+ status: "not-applicable",
2399
+ explanation: "This legacy plan declared no canonical gate."
2400
+ } : 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.`));
2401
+ const declared = session.plan?.externalEvidence;
2402
+ const missing = unsatisfiedExternalEvidence(session).length;
2403
+ checks.push(declared === undefined ? {
2404
+ id: "external-evidence",
2405
+ label: "Declared external evidence",
2406
+ tier: "caller-declared",
2407
+ status: "not-applicable",
2408
+ explanation: "This legacy plan declared no external-evidence obligations."
2409
+ } : 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.`));
2410
+ return {
2411
+ conclusion: !complete ? "completion-not-claimed" : checks.some((item) => item.status === "unsatisfied") ? "completion-unsupported" : "completion-supported",
2412
+ checks,
2413
+ limitations: [...LIMITATIONS]
2414
+ };
2368
2415
  }
2416
+ var TIER_LABELS = {
2417
+ "ts-enforced": "TS-enforced",
2418
+ "host-attested": "host-attested",
2419
+ "caller-declared": "caller-declared"
2420
+ };
2369
2421
  function formatReport(delivery) {
2370
- const artifacts = delivery.reportedArtifacts;
2422
+ const lines = delivery.features.flatMap((feature) => [
2423
+ `- ${feature.id} — ${feature.title}`,
2424
+ ` attempts: ${feature.attempts}; latest state: ${feature.latestState}`,
2425
+ ` outcome: ${feature.outcomeSummary ?? "none recorded"}`,
2426
+ ...feature.terminalFindings.length === 0 ? [" terminal findings: none"] : [
2427
+ " terminal findings:",
2428
+ ...feature.terminalFindings.map((finding) => ` - ${finding.severity}: ${finding.summary}`)
2429
+ ]
2430
+ ]);
2371
2431
  return [
2372
2432
  `Goal: ${delivery.goal}`,
2373
2433
  `Closure: ${delivery.closure.kind}${delivery.closure.summary ? ` — ${delivery.closure.summary}` : ""}`,
2374
2434
  `Progress: ${delivery.progress.completed} of ${delivery.progress.total} features complete`,
2375
2435
  "Features:",
2376
- ...delivery.features.flatMap(formatFeature),
2436
+ ...lines,
2437
+ `Assurance: ${delivery.assurance.conclusion.replaceAll("-", " ")}`,
2438
+ "Assurance checks:",
2439
+ ...delivery.assurance.checks.map((item) => `- ${item.status} [${TIER_LABELS[item.tier]}] ${item.label}: ${item.explanation}`),
2440
+ "Assurance limitations:",
2441
+ ...delivery.assurance.limitations.map((item) => `- ${item}`),
2377
2442
  "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}`
2443
+ `- latest attempts: ${delivery.reportedArtifacts.latestAttempts.join(", ") || "none reported"}`,
2444
+ `- superseded attempts only: ${delivery.reportedArtifacts.supersededAttemptsOnly.join(", ") || "none reported"}`
2380
2445
  ];
2381
2446
  }
2382
2447
  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) => ({
2448
+ if (!session.closure)
2449
+ throw new Error("Delivery requires a recorded closure.");
2450
+ const features = session.plan?.features ?? [];
2451
+ const grouped = features.map((feature) => ({
2388
2452
  feature,
2389
2453
  runs: session.runs.filter((run) => run.featureId === feature.id)
2390
2454
  }));
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;
2455
+ const latest = grouped.flatMap(({ runs }) => runs.slice(-1));
2456
+ const latestArtifacts = new Set(latest.flatMap((run) => run.artifactsChanged.map((item) => item.path)));
2457
+ const allArtifacts = new Set(session.runs.flatMap((run) => run.artifactsChanged.map((item) => item.path)));
2395
2458
  const delivery = {
2396
2459
  goal: session.goal,
2397
- closure: {
2398
- kind: session.closure.kind,
2399
- summary: session.closure.summary
2460
+ closure: { kind: session.closure.kind, summary: session.closure.summary },
2461
+ progress: {
2462
+ completed: features.filter((feature) => isFeatureComplete(session, feature.id)).length,
2463
+ total: features.length
2400
2464
  },
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;
2465
+ features: grouped.map(({ feature, runs }) => {
2466
+ const run = runs.at(-1);
2405
2467
  return {
2406
2468
  id: feature.id,
2407
2469
  title: feature.title,
2408
2470
  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
2471
+ latestState: run?.state ?? "not-started",
2472
+ outcomeSummary: run?.summary ?? null,
2473
+ terminalFindings: run?.reviews.at(-1)?.result?.findings.map(({ severity, summary }) => ({
2474
+ severity,
2475
+ summary
2414
2476
  })) ?? []
2415
2477
  };
2416
2478
  }),
2417
2479
  reportedArtifacts: {
2418
2480
  latestAttempts: [...latestArtifacts].sort(),
2419
2481
  supersededAttemptsOnly: [...allArtifacts].filter((path) => !latestArtifacts.has(path)).sort()
2420
- }
2482
+ },
2483
+ assurance: assuranceProjection(session)
2421
2484
  };
2422
2485
  return { ...delivery, report: formatReport(delivery) };
2423
2486
  }
@@ -5053,4 +5116,4 @@ export {
5053
5116
  plugin_default as default
5054
5117
  };
5055
5118
 
5056
- //# debugId=59C59901BAE4E80164756E2164756E21
5119
+ //# debugId=50DAD2770F6274F964756E2164756E21