opencode-plugin-flow 6.9.0 → 7.0.1

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/dist/index.js CHANGED
@@ -56,7 +56,8 @@ never silently fall back.
56
56
  refreshed projection. This cleanup precedes goal alignment and grants no work.
57
57
  Before another manager mutation, align the compact-projected goal with the
58
58
  request. Continue only for the same goal or a narrowing that preserves every
59
- outcome. Close a completed session as completed before new work. If the user
59
+ outcome; authority the request adds over those same outcomes is continuation,
60
+ not expansion. Close a completed session as completed before new work. If the user
60
61
  explicitly chooses deferred or abandoned closure for a non-completed session,
61
62
  call \`flow_session_close\` with compact session id/revision, fresh operation
62
63
  id, that kind, and optional summary; handle **Recovery**, follow a projected
@@ -546,9 +547,9 @@ checks from the changed behavior and risk:
546
547
  sufficient alone only for genuinely mechanical or documentation-only work.
547
548
  - UI claims need visual inspection when a runnable target is available.
548
549
  - Cleanup claims need behavior-preservation evidence, not formatting alone.
549
- - \`scope: "broad"\` is a claim about coverage, not a stronger label. Use it only
550
- for the repository's canonical applicable gate or a justified equivalent
551
- that covers the delivered repository state.
550
+ - \`scope: "broad"\` is a coverage claim, not a stronger label. Use it only for the
551
+ repository's canonical applicable gate or a justified equivalent covering the
552
+ delivered repository state.
552
553
 
553
554
  Immediately before each evidence Bash command, call \`flow_validation_start\`
554
555
  with current revision, feature id, exact command, and \`scope\` (\`focused\` or
@@ -561,11 +562,13 @@ A failed, incomplete, or source-drifted exact plan-listed observation creates a
561
562
  freshness boundary. Before new review admission, that gate needs a complete
562
563
  exit-zero observation for current source recorded after its latest relevant
563
564
  failure or drift; returning to an older digest does not revive an earlier pass,
564
- and substitute broad validation cannot discharge it. If that gate cannot pass,
565
- the normal completed path remains unavailable; fix the gate or ask the user to
566
- choose deferred or abandoned closure. Plan-listed validation prose that has never
567
- run as an exact command remains reviewer judgment, not a fabricated pass or
568
- failure.
565
+ and substitute broad validation cannot discharge it. Plan-listed validation prose
566
+ that has never run as an exact command remains reviewer judgment, not a fabricated
567
+ pass or failure.
568
+
569
+ A gate that cannot pass withholds completed closure, and reporting the blocker is
570
+ half the move. Fix the gate, or ask the user to choose deferred or abandoned
571
+ closure. Ending the turn having done neither leaves the workflow with no next step.
569
572
 
570
573
  Every host-observed validation advances the session revision, so the revision
571
574
  that armed a completed command is stale. The \`[flow-validation]\` marker reports
@@ -577,7 +580,7 @@ Use focused validation for ordinary features. For the final feature, run the
577
580
  repository's broad applicable gate after the last relevant edit. A source edit
578
581
  invalidates earlier applicability. Failed or unavailable checks are blockers,
579
582
  not passing evidence. If the canonical gate cannot run, explain why the chosen
580
- equivalent is broad enough; otherwise record the narrower evidence as focused.
583
+ equivalent is broad enough.
581
584
 
582
585
  Immediately before review admission, reconcile the preflight inventory against
583
586
  the recorded current-source observations. Do not call \`flow_review_start\` while
@@ -653,13 +656,12 @@ stops. Under \`/flow-auto\`, return to its lifecycle loop.
653
656
  `;
654
657
 
655
658
  // src/guidance/ids.ts
656
- var FLOW_GUIDANCE_TOPICS = [
659
+ var FLOW_GUIDANCE_IDS = [
657
660
  "flow",
658
661
  "flow-plan",
659
662
  "flow-run",
660
663
  "flow-review"
661
664
  ];
662
- var FLOW_GUIDANCE_IDS = FLOW_GUIDANCE_TOPICS;
663
665
 
664
666
  // src/guidance/catalog.ts
665
667
  var FLOW_MANAGER_KERNEL = [
@@ -696,12 +698,7 @@ ${FLOW_MANAGER_KERNEL}
696
698
  `,
697
699
  "flow-review": SKILL_default3
698
700
  };
699
- var FLOW_GUIDANCE_DOCUMENTS = FLOW_GUIDANCE_TOPICS.map((name) => ({
700
- relativePath: "SKILL.md",
701
- content: GUIDANCE_CONTENT[name],
702
- id: name,
703
- topic: name
704
- }));
701
+ var FLOW_GUIDANCE_DOCUMENTS = FLOW_GUIDANCE_IDS.map((id) => ({ id, content: GUIDANCE_CONTENT[id] }));
705
702
  var FLOW_GUIDANCE_BY_ID = new Map(FLOW_GUIDANCE_DOCUMENTS.map((document) => [document.id, document]));
706
703
  function getFlowGuidance(id) {
707
704
  const document = FLOW_GUIDANCE_BY_ID.get(id);
@@ -709,9 +706,6 @@ function getFlowGuidance(id) {
709
706
  throw new Error(`Missing bundled Flow guidance '${id}'.`);
710
707
  return document;
711
708
  }
712
- if (FLOW_GUIDANCE_BY_ID.size !== FLOW_GUIDANCE_IDS.length) {
713
- throw new Error("Bundled Flow guidance ids and documents are out of sync.");
714
- }
715
709
 
716
710
  // src/prompt-surfaces.ts
717
711
  var FLOW_WORKER_PROMPT = [
@@ -939,17 +933,11 @@ function createFlowCoreConfigEntries(options) {
939
933
  ...FLOW_CORE_AGENTS["flow-reviewer"],
940
934
  ...model ? { model } : {},
941
935
  ...steps ? { steps } : {},
942
- permission: {
943
- ...FLOW_CORE_AGENTS["flow-reviewer"].permission,
944
- task: { "*": "deny" }
945
- }
936
+ permission: structuredClone(FLOW_CORE_AGENTS["flow-reviewer"].permission)
946
937
  },
947
938
  "flow-worker": {
948
939
  ...FLOW_CORE_AGENTS["flow-worker"],
949
- permission: {
950
- ...FLOW_CORE_AGENTS["flow-worker"].permission,
951
- task: { "*": "deny" }
952
- }
940
+ permission: structuredClone(FLOW_CORE_AGENTS["flow-worker"].permission)
953
941
  }
954
942
  },
955
943
  command: Object.fromEntries(Object.entries(FLOW_CORE_COMMANDS).map(([name, value]) => [
@@ -1240,6 +1228,24 @@ var LONGEST_VALIDATION_INELIGIBLE_REASON = VALIDATION_INELIGIBLE_REASONS.reduce(
1240
1228
  function isValidationEligible(observation, sourceDigest) {
1241
1229
  return observation.ineligibleReason === undefined && observation.exitCode === 0 && observation.outputComplete && (sourceDigest === undefined || observation.sourceDigest === sourceDigest);
1242
1230
  }
1231
+ var NARROWING_FLAGS = new Set([
1232
+ "-t",
1233
+ "--test-name-pattern",
1234
+ "--testNamePattern",
1235
+ "-k",
1236
+ "-run",
1237
+ "--grep",
1238
+ "--filter"
1239
+ ]);
1240
+ function narrowingArguments(command) {
1241
+ return command.split(/\s+/).slice(1).filter((token) => {
1242
+ if (token.startsWith("-")) {
1243
+ return NARROWING_FLAGS.has(token.split("=")[0] ?? token);
1244
+ }
1245
+ const file = token.split("/").pop() ?? "";
1246
+ return /\.(?:test|spec)\./.test(file) || /_test\.[a-z]+$/.test(file) || /^test_.+\.py$/.test(file);
1247
+ });
1248
+ }
1243
1249
  function recordValidation(session, input) {
1244
1250
  if (input.captureId.length < 1 || input.captureId.length > MAX_VALIDATION_ID_LENGTH) {
1245
1251
  throw new FlowTransitionError(`Validation capture id must contain 1-${MAX_VALIDATION_ID_LENGTH} characters.`);
@@ -1267,6 +1273,12 @@ function recordValidation(session, input) {
1267
1273
  if (run.validations.length >= MAX_VALIDATIONS_PER_RUN) {
1268
1274
  throw new FlowTransitionError(`A feature run may contain at most ${MAX_VALIDATIONS_PER_RUN} validation observations.`);
1269
1275
  }
1276
+ if (input.scope === "broad") {
1277
+ const narrowing = narrowingArguments(input.command);
1278
+ if (narrowing.length > 0) {
1279
+ throw new FlowTransitionError(`A broad observation cannot select which tests it runs (${narrowing.join(", ")}). Arm the repository's canonical gate, or record this command as focused.`);
1280
+ }
1281
+ }
1270
1282
  const revision = session.revision + 1;
1271
1283
  const observation = {
1272
1284
  id: input.captureId,
@@ -1298,15 +1310,13 @@ function recordValidation(session, input) {
1298
1310
  function isValidationFresh(session, run, observation) {
1299
1311
  return session.runs.filter((candidate) => candidate.featureId === run.featureId).flatMap((candidate) => candidate.validations).every((candidate) => candidate.command !== observation.command || isValidationEligible(candidate) || candidate.recordedRevision < observation.recordedRevision);
1300
1312
  }
1301
- function unresolvedKnownFailedPlanCommands(session, run, sourceDigest) {
1302
- if (session.approval !== "approved")
1303
- return [];
1304
- const feature = session.plan?.features.find((candidate) => candidate.id === run.featureId);
1305
- if (!feature)
1306
- return [];
1307
- const validations = session.runs.filter((candidate) => candidate.featureId === run.featureId).flatMap((candidate) => candidate.validations);
1308
- const commands = [...new Set(feature.validation)];
1309
- return commands.filter((command) => validations.some((observation) => observation.command === command && !isValidationEligible(observation)) && !run.validations.some((observation) => observation.command === command && isValidationEligible(observation, sourceDigest) && isValidationFresh(session, run, observation)));
1313
+ function unresolvedVetoedCommands(session, run, sourceDigest) {
1314
+ const planned = session.approval === "approved" ? session.plan?.features.find((candidate) => candidate.id === run.featureId)?.validation ?? [] : [];
1315
+ const failed = session.runs.filter((candidate) => candidate.featureId === run.featureId).flatMap((candidate) => candidate.validations).filter((observation) => !isValidationEligible(observation));
1316
+ const commands = [
1317
+ ...new Set(failed.filter((observation) => observation.scope === "broad" || planned.includes(observation.command)).map((observation) => observation.command))
1318
+ ];
1319
+ return commands.filter((command) => !run.validations.some((observation) => observation.command === command && isValidationEligible(observation, sourceDigest) && isValidationFresh(session, run, observation)));
1310
1320
  }
1311
1321
 
1312
1322
  // src/domain/transitions.ts
@@ -1561,9 +1571,9 @@ function startReview(session, input, environment) {
1561
1571
  if (run.reviews.length > 0) {
1562
1572
  fail("Reset the feature before starting another full review.");
1563
1573
  }
1564
- const unresolved = unresolvedKnownFailedPlanCommands(session, run, input.sourceDigest);
1574
+ const unresolved = unresolvedVetoedCommands(session, run, input.sourceDigest);
1565
1575
  if (unresolved.length > 0) {
1566
- fail(`Review requires passing exact planned commands for the current workspace content: ${unresolved.map((command) => JSON.stringify(command)).join(", ")}.`);
1576
+ fail(`Review requires passing these exact commands for the current workspace content: ${unresolved.map((command) => JSON.stringify(command)).join(", ")}. A different command cannot discharge one that failed.`);
1567
1577
  }
1568
1578
  const kind = isFinalFeatureRun(session, run) ? "final" : "feature";
1569
1579
  const applicable = run.validations.filter((validation) => isValidationEligible(validation, input.sourceDigest) && isValidationFresh(session, run, validation));
@@ -1760,6 +1770,40 @@ function closeSession(session, input) {
1760
1770
  fail("Flow could not record the closure.");
1761
1771
  return { session: next, value: closure, replayed: false };
1762
1772
  }
1773
+
1774
+ // src/application/errors.ts
1775
+ class UnreadableFlowSessionError extends Error {
1776
+ code = "UNREADABLE_FLOW_SESSION";
1777
+ reason;
1778
+ constructor(message, reason) {
1779
+ super(message);
1780
+ this.name = "UnreadableFlowSessionError";
1781
+ this.reason = reason;
1782
+ }
1783
+ }
1784
+
1785
+ class UnsupportedFlowSessionVersionError extends Error {
1786
+ code = "UNSUPPORTED_FLOW_SESSION_VERSION";
1787
+ actualVersion;
1788
+ constructor(actualVersion) {
1789
+ super("Flow v6 supports only Session v5 active state. Close active older sessions before upgrading; archived history remains inert.");
1790
+ this.name = "UnsupportedFlowSessionVersionError";
1791
+ this.actualVersion = actualVersion;
1792
+ }
1793
+ }
1794
+
1795
+ class ArchiveCollisionError extends Error {
1796
+ code = "FLOW_ARCHIVE_COLLISION";
1797
+ constructor(message) {
1798
+ super(message);
1799
+ this.name = "ArchiveCollisionError";
1800
+ }
1801
+ }
1802
+
1803
+ // src/application/schema.ts
1804
+ import { z } from "zod";
1805
+
1806
+ // src/domain/session-invariants.ts
1763
1807
  function featurePassedBefore(session, featureId, revision) {
1764
1808
  return session.runs.some((run) => run.featureId === featureId && run.reviews.some((review) => review.result?.verdict === "passed" && review.result.recordedRevision < revision));
1765
1809
  }
@@ -1795,11 +1839,9 @@ function sessionInvariantIssues(session) {
1795
1839
  issues.push("Runs require a plan.");
1796
1840
  return issues;
1797
1841
  }
1798
- try {
1799
- assertPlan(session.plan);
1800
- } catch (error) {
1801
- issues.push(error instanceof Error ? error.message : String(error));
1802
- }
1842
+ const planProblem = planIssue(session.plan);
1843
+ if (planProblem)
1844
+ issues.push(planProblem);
1803
1845
  const featureIds = new Set(session.plan.features.map((feature) => feature.id));
1804
1846
  const runIds = new Set;
1805
1847
  const validationIds = new Set;
@@ -1888,11 +1930,9 @@ function sessionInvariantIssues(session) {
1888
1930
  if (review.result.recordedRevision <= review.createdRevision || review.result.recordedRevision > session.revision) {
1889
1931
  issues.push(`Review '${review.id}' has an invalid result revision.`);
1890
1932
  }
1891
- try {
1892
- assertReviewResult(review.result);
1893
- } catch (error) {
1894
- issues.push(error instanceof Error ? error.message : String(error));
1895
- }
1933
+ const resultProblem = review.result.findings.length > MAX_REVIEW_FINDINGS ? `A review may contain at most ${MAX_REVIEW_FINDINGS} findings.` : reviewResultSemanticIssues(review.result)[0]?.message;
1934
+ if (resultProblem)
1935
+ issues.push(resultProblem);
1896
1936
  }
1897
1937
  }
1898
1938
  const last = run.reviews.at(-1);
@@ -1921,37 +1961,7 @@ function sessionInvariantIssues(session) {
1921
1961
  return issues;
1922
1962
  }
1923
1963
 
1924
- // src/application/errors.ts
1925
- class UnreadableFlowSessionError extends Error {
1926
- code = "UNREADABLE_FLOW_SESSION";
1927
- reason;
1928
- constructor(message, reason) {
1929
- super(message);
1930
- this.name = "UnreadableFlowSessionError";
1931
- this.reason = reason;
1932
- }
1933
- }
1934
-
1935
- class UnsupportedFlowSessionVersionError extends Error {
1936
- code = "UNSUPPORTED_FLOW_SESSION_VERSION";
1937
- actualVersion;
1938
- constructor(actualVersion) {
1939
- super("Flow v6 supports only Session v5 active state. Close active older sessions before upgrading; archived history remains inert.");
1940
- this.name = "UnsupportedFlowSessionVersionError";
1941
- this.actualVersion = actualVersion;
1942
- }
1943
- }
1944
-
1945
- class ArchiveCollisionError extends Error {
1946
- code = "FLOW_ARCHIVE_COLLISION";
1947
- constructor(message) {
1948
- super(message);
1949
- this.name = "ArchiveCollisionError";
1950
- }
1951
- }
1952
-
1953
1964
  // src/application/schema.ts
1954
- import { z } from "zod";
1955
1965
  var encoder = new TextEncoder;
1956
1966
  function boundedText(label, options) {
1957
1967
  const maxBytes = options?.maxBytes ?? MAX_TEXT_BYTES;
@@ -1960,6 +1970,7 @@ function boundedText(label, options) {
1960
1970
  var FeatureIdSchema = z.string().max(MAX_SESSION_ID_LENGTH).regex(FEATURE_ID_PATTERN, FEATURE_ID_MESSAGE);
1961
1971
  var OperationIdSchema = z.string().min(1).max(128).regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]*$/);
1962
1972
  var ReviewAssignmentIdSchema = z.string().min(1).max(256);
1973
+ var RunIdSchema = z.string().min(1).max(256);
1963
1974
  var RevisionSchema = z.number().int().safe().nonnegative();
1964
1975
  var SourceDigestSchema = z.custom((value) => typeof value === "string" && /^sha256:[a-f0-9]{64}$/.test(value), "Expected a sha256: content digest.");
1965
1976
  var PlanFeatureSchema = z.object({
@@ -2000,10 +2011,14 @@ var PublicReviewResultSchema = z.object({
2000
2011
  context.addIssue({ code: "custom", ...issue });
2001
2012
  }
2002
2013
  });
2014
+ var ReviewPacketSchema = z.object({
2015
+ summary: boundedText("Review packet summary"),
2016
+ riskLenses: z.array(boundedText("Review risk lens")).max(16).default([])
2017
+ }).strict();
2003
2018
  var ValidationObservationSchema = z.object({
2004
2019
  id: z.string().min(1).max(MAX_VALIDATION_ID_LENGTH),
2005
2020
  featureId: FeatureIdSchema,
2006
- runId: z.string().min(1).max(256),
2021
+ runId: RunIdSchema,
2007
2022
  scope: z.enum(["focused", "broad"]),
2008
2023
  command: boundedText("Validation command"),
2009
2024
  sourceDigest: SourceDigestSchema,
@@ -2024,19 +2039,16 @@ var ReviewAssignmentSchema = z.object({
2024
2039
  id: ReviewAssignmentIdSchema,
2025
2040
  operationId: OperationIdSchema,
2026
2041
  featureId: FeatureIdSchema,
2027
- runId: z.string().min(1).max(256),
2042
+ runId: RunIdSchema,
2028
2043
  kind: z.enum(["feature", "final"]),
2029
2044
  sourceDigest: SourceDigestSchema,
2030
2045
  validationIds: z.array(z.string().min(1).max(MAX_VALIDATION_ID_LENGTH)).min(1).max(MAX_VALIDATIONS_PER_RUN),
2031
- packet: z.object({
2032
- summary: boundedText("Review packet summary"),
2033
- riskLenses: z.array(boundedText("Review risk lens")).max(16).default([])
2034
- }).strict(),
2046
+ packet: ReviewPacketSchema,
2035
2047
  createdRevision: RevisionSchema,
2036
2048
  result: PersistedReviewResultSchema.nullable()
2037
2049
  }).strict();
2038
2050
  var FeatureRunSchema = z.object({
2039
- id: z.string().min(1).max(256),
2051
+ id: RunIdSchema,
2040
2052
  featureId: FeatureIdSchema,
2041
2053
  attempt: z.number().int().safe().positive(),
2042
2054
  state: z.enum(["active", "completed", "blocked", "superseded"]),
@@ -2109,10 +2121,7 @@ var ReviewStartInputSchema = z.object({
2109
2121
  ...guarded,
2110
2122
  featureId: FeatureIdSchema,
2111
2123
  artifactsChanged: z.array(ArtifactSchema).max(MAX_ARTIFACTS),
2112
- packet: z.object({
2113
- summary: boundedText("Review packet summary"),
2114
- riskLenses: z.array(boundedText("Review risk lens")).max(16).default([])
2115
- }).strict()
2124
+ packet: ReviewPacketSchema
2116
2125
  }).strict()
2117
2126
  }).strict();
2118
2127
  var FeatureCompleteInputSchema = z.object({
@@ -2274,7 +2283,7 @@ function nextAction(session, pendingReviewSourceStale = false, blockedFeature =
2274
2283
  const hasPassingValidation = run.validations.some((validation) => isValidationEligible(validation) && isValidationFresh(session, run, validation) && (!finalRun || validation.scope === "broad"));
2275
2284
  if (!hasPassingValidation)
2276
2285
  return "flow_validation_start";
2277
- if (unresolvedKnownFailedPlanCommands(session, run).length > 0) {
2286
+ if (unresolvedVetoedCommands(session, run).length > 0) {
2278
2287
  return "flow_validation_start";
2279
2288
  }
2280
2289
  return "flow_review_start";
@@ -4149,8 +4158,10 @@ function registerFlowPluginInstance(scopeId, input) {
4149
4158
  });
4150
4159
  }
4151
4160
 
4152
- // src/platform/opencode/tools.ts
4161
+ // src/platform/opencode/sdk.ts
4153
4162
  import { tool } from "@opencode-ai/plugin";
4163
+
4164
+ // src/platform/opencode/tools.ts
4154
4165
  var host = tool.schema;
4155
4166
  var encoder2 = new TextEncoder;
4156
4167
  function boundedHostText(label, options) {
@@ -4191,7 +4202,9 @@ var plan = host.object({
4191
4202
  var reviewFinding = host.object({
4192
4203
  severity: host.enum(["blocking", "advisory"]),
4193
4204
  summary: text,
4194
- evidence: text.optional()
4205
+ evidence: text.optional(),
4206
+ scopeBlocker: host.boolean().optional(),
4207
+ findingId: host.string().max(MAX_SESSION_ID_LENGTH).regex(FINDING_ID_PATTERN, FINDING_ID_MESSAGE).optional()
4195
4208
  }).strict();
4196
4209
  var reviewResult = host.object({
4197
4210
  verdict: host.enum(["passed", "failed"]),
@@ -4761,4 +4774,4 @@ export {
4761
4774
  plugin_default as default
4762
4775
  };
4763
4776
 
4764
- //# debugId=44D653D2BF79482464756E2164756E21
4777
+ //# debugId=F9B2A9081022601764756E2164756E21