diff --git a/CHANGELOG.md b/CHANGELOG.md index 245651f..17e978f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ ## [Unreleased] +## [2.0.52] - 2026-05-31 + +Tighten the cleanup gate lore + +Flow 2.0.52 turns the simplification pass into an enforced maintenance boundary. Unused value exports are now driven to zero and guarded by a deadcode export budget, schema barrels stop re-exporting private internals, prompt fragments keep only active generated-prompt inputs, and adapter/runtime helper exports are narrowed to the surfaces that are still actually consumed. + +The release also removes the old final-review compatibility lore. Prior `oracleRefs`, `test_oracle`, and `test_oracle_authenticity` payload terms are rejected instead of canonicalized, final-review behavior schemas live in their own module, and detailed final-review gates require meaningful trimmed integration and regression evidence rather than whitespace-only placeholders. + +The release deliberately does not add slash commands, runtime tools, prompt modes, state paths, package exports, installer behavior, dependencies, live OpenCode UI automation, or a persisted session schema version. It intentionally contracts internal and legacy review payload surfaces while preserving the existing `@opencode-ai/plugin` and `zod` compatibility boundary. + +Constraint: Keep unused value exports at zero through `bun run check:deadcode-exports` +Constraint: Treat legacy final-review terms as invalid input, not compatibility aliases +Constraint: Keep `@opencode-ai/plugin` at `1.14.48` and `zod` at `4.1.8`; this release changes no dependency compatibility boundary +Rejected: Keep broad runtime/schema barrels for possible future callers | the package public API remains root-only, and unused internal exports were hiding ownership +Rejected: Preserve old final-review aliases after the cleanup | backwards compatibility is intentionally out of scope for this simplification release +Rejected: Count whitespace-only detailed-review checks as evidence | detailed final review requires meaningful integration and regression accounting +Confidence: high +Scope-risk: medium +Reversibility: moderate +Directive: Keep future simplification work budget-backed and source-owned; do not widen internal exports or revive compatibility aliases without a current consumer and release-note justification +Tested: `bun test tests/runtime/final-review-contracts.test.ts` (36 pass, 0 fail, 197 expect() calls); `bun run check` (release gate passed: dependency contract OK with project/plugin/root `zod=4.1.8`, architecture seams OK, fresh surfaces OK, deadcode export budget OK with `exports=0`, release hygiene OK, pack invariants OK for version `2.0.52`, bundle sanity OK, full suite 676 pass/0 fail, lint passed, bench smoke and bench gate passed); `bun run smoke:release` (passed for package `2.0.52`, wrote release-smoke evidence under `prompt-exports/release-smoke/`, real OpenCode CLI not invoked); RepoPrompt MCP code review found one P1 detailed-final-review evidence issue before release, and this patch fixes it +Not-tested: Live OpenCode UI runtime interaction; live GitHub-hosted release workflow run for tag `v2.0.52` before push + ## [2.0.51] - 2026-05-21 Keep runtime projections out of handoff lore diff --git a/docs/architecture/runtime-complexity-baseline.md b/docs/architecture/runtime-complexity-baseline.md index 6e0e720..70d5f40 100644 --- a/docs/architecture/runtime-complexity-baseline.md +++ b/docs/architecture/runtime-complexity-baseline.md @@ -10,9 +10,9 @@ This document captures measurable simplification baselines and the recurring Pha - Collector: `bun run report:runtime-simplification-metrics` - Runtime subdomain breakdown: diagnostic-only `runtime.subdomains` in the metrics report (`application`, `domain`, `transitions`, `lifecycle`, `recovery`, `rendering`, `root`, and any future `src/runtime//` directory) -## KPI baseline table (captured 2026-05-14) +## KPI baseline table (captured 2026-05-24) -Current canonical snapshot (captured `2026-05-14T07:09:17.649Z`): runtime files `124`, runtime LOC `17,480`, runtime files >= 300 LOC `7`, top-5 LOC share `9.7%`, architecture seam violations `0`. +Current canonical snapshot (captured `2026-05-24T12:31:45.180Z`): runtime files `128`, runtime LOC `17,850`, runtime files >= 300 LOC `3`, top-5 LOC share `8.5%`, architecture seam violations `0`. The May 24 simplification pass accepted a small runtime file-count and total-LOC increase to move review-gate, history-presenter, detailed-final-review, and behavior-schema internals behind stable facades while cutting hotspot concentration. Historical same-pass snapshots remain useful for trend comparison only. The 2026-05-13 current snapshot was runtime files `107`, runtime LOC `17,040`, runtime files >= 300 LOC `14`, top-5 LOC share `14.1%`, architecture seam violations `0`. The preceding 2026-05-13 baseline was runtime files `105`, runtime LOC `17,005`, runtime files >= 300 LOC `14`, top-5 LOC share `15.0%`, with `final-review-behavior-risks.ts` as the largest hotspot at `571` LOC. @@ -21,37 +21,37 @@ Historical pass snapshots are retained in investigation logs; this document trac | KPI | Baseline | Target direction | Measurement method | Notes | | --- | --- | --- | --- | --- | | Architecture seam violations | `0` | Hold at `0` | `bun run check:architecture-seams:enforce` | Enforced in CI | -| Runtime TypeScript file count | `124` | Down | `report:runtime-simplification-metrics` | Current refactors trade more files for smaller hotspots | -| Runtime LOC | `17,480` | Down | `report:runtime-simplification-metrics` | Fresh local measurement on 2026-05-14 | -| Runtime files >= 300 LOC | `7` | Down | `report:runtime-simplification-metrics` | Improved from 2026-05-13 snapshot | -| Top-5 runtime-file LOC share | `9.7%` | Down | `report:runtime-simplification-metrics` | Improved from `14.1%` 2026-05-13 snapshot | -| Runtime churn hotspot leader | `src/runtime/schema.ts` (26 touches / 30d) | Down | `git log` via metrics script | Next churn leaders: `session-lifecycle.ts` (18), `session-workspace.ts` (18), `transitions/plan.ts` (18), `domain/index.ts` (15) | -| Runtime subdomain LOC/file distribution | See 2026-05-14 subdomain snapshot below | Down or localized by touched subdomain | `report:runtime-simplification-metrics` | Diagnostic-only; helps prove localized improvement without adding a hard gate | +| Runtime TypeScript file count | `128` | Down | `report:runtime-simplification-metrics` | Current refactors trade more files for smaller hotspots | +| Runtime LOC | `17,850` | Down | `report:runtime-simplification-metrics` | Fresh local measurement on 2026-05-24 | +| Runtime files >= 300 LOC | `3` | Down | `report:runtime-simplification-metrics` | Improved from `7` on 2026-05-14 | +| Top-5 runtime-file LOC share | `8.5%` | Down | `report:runtime-simplification-metrics` | Improved from `9.7%` on 2026-05-14 | +| Runtime churn hotspot leader | `src/runtime/schema.ts` (17 touches / 30d) | Down | `git log` via metrics script | Next churn leaders: `session-presenters.ts` (13), `domain/index.ts` (13), `session-actions.ts` (11), `transitions/plan.ts` (11) | +| Runtime subdomain LOC/file distribution | See 2026-05-24 subdomain snapshot below | Down or localized by touched subdomain | `report:runtime-simplification-metrics` | Diagnostic-only; helps prove localized improvement without adding a hard gate | | Fast test lane runtime | `~0.22s` local | Hold low | timed `bun run test:fast` | Keep this as refactor safety lane | | Replay/integration lane runtime | `~0.15s` local | Hold low | timed `bun run test:replay` | Keep replay lane stable | -## 2026-05-14 runtime simplification snapshot +## 2026-05-24 runtime simplification snapshot Largest runtime files after the latest runtime simplification pass: -1. `src/runtime/schema-review-shared.ts` — `353` LOC -2. `src/runtime/transitions/execution-completion-validation.ts` — `347` LOC -3. `src/runtime/application/session-presenters.ts` — `341` LOC -4. `src/runtime/domain/final-review-coverage.ts` — `332` LOC -5. `src/runtime/application/session-actions.ts` — `326` LOC +1. `src/runtime/application/session-actions.ts` — `326` LOC +2. `src/runtime/transitions/completion-gates.ts` — `308` LOC +3. `src/runtime/application/doctor-checks.ts` — `305` LOC +4. `src/runtime/transitions/execution-completion-normalization.ts` — `295` LOC +5. `src/runtime/transitions/plan.ts` — `292` LOC Runtime subdomain snapshot (`fileCount` / `totalLoc` / large files): | Subdomain | Files | LOC | Files >= 300 LOC | | --- | ---: | ---: | ---: | -| `application` | `34` | `4,791` | `3` | -| `domain` | `30` | `4,185` | `1` | +| `application` | `35` | `4,885` | `2` | +| `domain` | `31` | `4,254` | `0` | | `json` | `2` | `431` | `0` | | `lifecycle` | `1` | `34` | `0` | | `recovery` | `2` | `153` | `0` | | `rendering` | `1` | `8` | `0` | -| `root` | `41` | `5,288` | `1` | -| `transitions` | `13` | `2,590` | `2` | +| `root` | `42` | `5,470` | `0` | +| `transitions` | `14` | `2,615` | `1` | ## Phase 4 execution loop (delete-first simplification) @@ -70,11 +70,11 @@ For each simplification phase, capture the metrics report immediately before and ## Current top hotspot candidates -1. `src/runtime/schema-review-shared.ts` (largest file: `353` LOC) -2. `src/runtime/transitions/execution-completion-validation.ts` (large file: `347` LOC) -3. `src/runtime/application/session-presenters.ts` (large file: `341` LOC) -4. `src/runtime/domain/final-review-coverage.ts` (large file: `332` LOC) -5. `src/runtime/schema.ts` (highest churn: `26` touches / 30d) +1. `src/runtime/application/session-actions.ts` (large file: `326` LOC) +2. `src/runtime/transitions/completion-gates.ts` (large file: `308` LOC) +3. `src/runtime/application/doctor-checks.ts` (large file: `305` LOC) +4. `src/runtime/transitions/execution-completion-normalization.ts` (near-threshold file: `295` LOC) +5. `src/runtime/schema.ts` (highest churn: `17` touches / 30d) ## Guardrails diff --git a/docs/development.md b/docs/development.md index a57016c..a455d74 100644 --- a/docs/development.md +++ b/docs/development.md @@ -27,6 +27,8 @@ Useful scripts: - `bun run build` - `bun run deadcode` +- `bun run report:deadcode-exports` +- `bun run check:deadcode-exports` - `bun run test` - `bun run test:fast` - `bun run test:deep` @@ -49,8 +51,9 @@ Useful scripts: Gate status terms: -- **Hard** gates fail the command and block merge/release readiness. Examples: `bun run check:dependency-contract`, `bun run check:architecture-seams:enforce`, `bun run check:generated-drift`, `bun run gate:completion-lane`, `bun run test:replay`, and `bun run bench:gate`. -- **Advisory** gates are supplemental visibility and do not block by exit code. `bun run check:boundary-report` is advisory by design today: it exits `0` and reports prompt/tool boundary findings unless a future reviewed change promotes it with script, docs, and test updates. +- **Hard** gates fail the command and block merge/release readiness. Examples: `bun run check:dependency-contract`, `bun run check:architecture-seams:enforce`, `bun run check:generated-drift`, `bun run deadcode`, `bun run gate:completion-lane`, `bun run test:replay`, and `bun run bench:gate`. +- **Advisory** gates are supplemental visibility and do not block by exit code. `bun run check:boundary-report` and `bun run report:deadcode-exports` are advisory by design today: they exit `0` and report findings unless a future reviewed change promotes them with script, docs, and test updates. +- **Ratcheted** gates fail on regression against the current reviewed budget while allowing known remaining cleanup debt to be handled incrementally. `bun run check:deadcode-exports` runs inside `bun run check` and fails if unused exported symbols or duplicate exports increase above the checked-in budget. - **Diagnostic/report** commands support investigation or planning. `bun run check:architecture-seams` is seam report mode; `bun run report:runtime-simplification-metrics` prints simplification metrics. Use the corresponding hard gate (`check:architecture-seams:enforce`) for pass/fail readiness. The full matrix, artifact owners, source-of-truth scripts, repeated-inside-`check` status, and CI/local no-weakening rules live in [`docs/maintainer-contract.md#gate-contract-matrix`](maintainer-contract.md#gate-contract-matrix). diff --git a/docs/maintainer-contract.md b/docs/maintainer-contract.md index 7ff8566..2d7c583 100644 --- a/docs/maintainer-contract.md +++ b/docs/maintainer-contract.md @@ -167,6 +167,8 @@ Accepted simplification tradeoffs should be explicit: a file-count increase is a | Architecture seams enforce | `bun run check:architecture-seams:enforce` | architecture seam owner | `scripts/cross-area/architecture-seams.mjs` | targeted seam blocker and part of `bun run check` | focused fast-fail lane and/or covered by `bun run check` | hard | yes | fails on blocked cross-layer imports | Preserve enforce mode as the merge contract; report mode is not a substitute. | | Architecture seams report | `bun run check:architecture-seams` | architecture seam owner | `scripts/cross-area/architecture-seams.mjs` | local diagnostic inventory | optional diagnostic artifact only | diagnostic | no | exits `0` while reporting seam findings | Keep separate from hard enforcement to avoid accidental blocking semantics. | | Generated drift | `bun run check:generated-drift` | prompt/review/descriptor owners | `package.json` composed script | generated prompt/review/descriptor parity check | focused fast-fail lane and/or covered by `bun run check` | hard | indirect | fails on generated prompt/review/descriptor parity drift | Keep either direct invocation in `check` or equivalent covered constituent checks + explicit proof in no-weakening notes. | +| Unused-code gate | `bun run deadcode` | package/runtime maintainers | `package.json#scripts.deadcode` + `package.json#knip` | unused file, dependency, unlisted, unresolved, binary, and catalog check | covered by `bun run check` | hard | yes | fails on hard Knip issue types | Keep symbol-level export reports separate unless intentional internal barrels and schema facades have reviewed allowlists. | +| Unused-code export budget | `bun run check:deadcode-exports`; use `bun run report:deadcode-exports` for details | package/runtime maintainers | `package.json#scripts.check:deadcode-exports` + `scripts/cross-area/deadcode-export-budget.mjs` + `package.json#knip` | unused export/type/duplicate regression budget | covered by `bun run check`; report command remains advisory | ratcheted hard budget | yes | fails when symbol-level counts exceed the checked-in budget | Lower the budget when cleanup removes high-confidence declarations; do not raise it without a reviewed reason. | | Completion lane | `bun run gate:completion-lane` | runtime completion owner | `scripts/cross-area/check-completion-lane.mjs` | focused completion invariant gate and part of `bun run check` | focused fast-fail lane and/or covered by `bun run check` | hard | yes | fails on completion-lane invariant violation | Completion/reviewer gates are release-critical and must remain hard. | | Snapshot persistence gate | `bun run test:replay` | runtime persistence owners | `tests/runtime/semantic-invariants.test.ts` + `tests/runtime/final-completion-gates.test.ts` | snapshot/runtime invariant gate and part of `bun run check` | focused fast-fail lane and/or covered by `bun run check` | hard | yes | fails on runtime invariant regression | Snapshot-first persistence is the supported authority; event/replay/checkpoint stores are intentionally absent. | | Benchmark gate | `bun run bench:gate` | performance owner | `scripts/cross-area/bench-gate.mjs` | benchmark baseline gate and part of `bun run check` | focused fast-fail lane and/or covered by `bun run check` | hard | yes | fails on benchmark baseline regression | `bench:smoke` may provide extra signal, but `bench:gate` owns baseline failure. | diff --git a/docs/releases/v2.0.52.md b/docs/releases/v2.0.52.md new file mode 100644 index 0000000..51767cc --- /dev/null +++ b/docs/releases/v2.0.52.md @@ -0,0 +1,22 @@ +# v2.0.52 + +Tighten the cleanup gate lore + +Flow 2.0.52 turns the simplification pass into an enforced maintenance boundary. Unused value exports are now driven to zero and guarded by a deadcode export budget, schema barrels stop re-exporting private internals, prompt fragments keep only active generated-prompt inputs, and adapter/runtime helper exports are narrowed to the surfaces that are still actually consumed. + +The release also removes the old final-review compatibility lore. Prior `oracleRefs`, `test_oracle`, and `test_oracle_authenticity` payload terms are rejected instead of canonicalized, final-review behavior schemas live in their own module, and detailed final-review gates require meaningful trimmed integration and regression evidence rather than whitespace-only placeholders. + +The release deliberately does not add slash commands, runtime tools, prompt modes, state paths, package exports, installer behavior, dependencies, live OpenCode UI automation, or a persisted session schema version. It intentionally contracts internal and legacy review payload surfaces while preserving the existing `@opencode-ai/plugin` and `zod` compatibility boundary. + +Constraint: Keep unused value exports at zero through `bun run check:deadcode-exports` +Constraint: Treat legacy final-review terms as invalid input, not compatibility aliases +Constraint: Keep `@opencode-ai/plugin` at `1.14.48` and `zod` at `4.1.8`; this release changes no dependency compatibility boundary +Rejected: Keep broad runtime/schema barrels for possible future callers | the package public API remains root-only, and unused internal exports were hiding ownership +Rejected: Preserve old final-review aliases after the cleanup | backwards compatibility is intentionally out of scope for this simplification release +Rejected: Count whitespace-only detailed-review checks as evidence | detailed final review requires meaningful integration and regression accounting +Confidence: high +Scope-risk: medium +Reversibility: moderate +Directive: Keep future simplification work budget-backed and source-owned; do not widen internal exports or revive compatibility aliases without a current consumer and release-note justification +Tested: `bun test tests/runtime/final-review-contracts.test.ts` (36 pass, 0 fail, 197 expect() calls); `bun run check` (release gate passed: dependency contract OK with project/plugin/root `zod=4.1.8`, architecture seams OK, fresh surfaces OK, deadcode export budget OK with `exports=0`, release hygiene OK, pack invariants OK for version `2.0.52`, bundle sanity OK, full suite 676 pass/0 fail, lint passed, bench smoke and bench gate passed); `bun run smoke:release` (passed for package `2.0.52`, wrote release-smoke evidence under `prompt-exports/release-smoke/`, real OpenCode CLI not invoked); RepoPrompt MCP code review found one P1 detailed-final-review evidence issue before release, and this patch fixes it +Not-tested: Live OpenCode UI runtime interaction; live GitHub-hosted release workflow run for tag `v2.0.52` before push diff --git a/package.json b/package.json index 0f38cd1..da1f425 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opencode-plugin-flow", - "version": "2.0.51", + "version": "2.0.52", "description": "Stateful planning and execution workflow plugin for OpenCode", "type": "module", "main": "dist/index.js", @@ -37,7 +37,9 @@ "eval:review-capture:check": "bun run ./scripts/cross-area/review-prompt-capture.ts -- --check", "eval:prompt-capture": "bun run ./scripts/cross-area/prompt-mode-capture.ts", "eval:prompt-capture:check": "bun run ./scripts/cross-area/prompt-mode-capture.ts -- --check", - "deadcode": "knip --include files,dependencies", + "deadcode": "knip --include files,dependencies,unlisted,unresolved,binaries,catalog", + "report:deadcode-exports": "knip --exports --no-exit-code --reporter compact", + "check:deadcode-exports": "node ./scripts/cross-area/deadcode-export-budget.mjs", "lint": "bunx biome check src tests bench --files-ignore-unknown=true --vcs-use-ignore-file=true", "test": "bun test", "test:fast": "bun test tests/runtime/semantic-invariants.test.ts tests/runtime/final-completion-gates.test.ts", @@ -47,7 +49,7 @@ "test:randomized": "bun test --randomize --timeout 30000", "test:randomized:regression": "bun run build && bun test --randomize --timeout 30000 && bun test --randomize --timeout 30000 --seed=1 && bun test --randomize --timeout 30000 --seed=42", "typecheck": "tsc --noEmit", - "check": "bun run typecheck && bun run eval:review-capture:check && bun run eval:prompt-capture:check && bun run check:dependency-contract && bun run check:architecture-seams:enforce && bun run check:fresh-surfaces && bun run deadcode && bun run build && bun run check:release-hygiene && bun run check:pack-invariants && bun run gate:completion-lane && bun run test:replay && bun run check:cold-start-budget && node ./scripts/cross-area/bundle-sanity.mjs && bun run test && bun run lint && bun run bench:smoke && bun run bench:gate", + "check": "bun run typecheck && bun run eval:review-capture:check && bun run eval:prompt-capture:check && bun run check:dependency-contract && bun run check:architecture-seams:enforce && bun run check:fresh-surfaces && bun run deadcode && bun run check:deadcode-exports && bun run build && bun run check:release-hygiene && bun run check:pack-invariants && bun run gate:completion-lane && bun run test:replay && bun run check:cold-start-budget && node ./scripts/cross-area/bundle-sanity.mjs && bun run test && bun run lint && bun run bench:smoke && bun run bench:gate", "install:opencode": "bun run ./src/install-opencode.ts", "uninstall:opencode": "bun run ./src/uninstall-opencode.ts" }, diff --git a/scripts/cross-area/deadcode-export-budget.mjs b/scripts/cross-area/deadcode-export-budget.mjs new file mode 100644 index 0000000..8e831eb --- /dev/null +++ b/scripts/cross-area/deadcode-export-budget.mjs @@ -0,0 +1,51 @@ +import { execFileSync } from "node:child_process"; + +const budgets = { + exports: 0, + exportedTypes: 11, + duplicateExports: 0, +}; + +function runKnipExportReport() { + return execFileSync( + "bunx", + ["knip", "--exports", "--no-exit-code", "--reporter", "compact"], + { + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }, + ); +} + +function countHeading(report, heading) { + const match = report.match(new RegExp(`${heading} \\((\\d+)\\)`, "u")); + return match ? Number(match[1]) : 0; +} + +const report = runKnipExportReport(); +const counts = { + exports: countHeading(report, "Unused exports"), + exportedTypes: countHeading(report, "Unused exported types"), + duplicateExports: countHeading(report, "Duplicate exports"), +}; + +const failures = Object.entries(budgets).flatMap(([key, budget]) => { + const count = counts[key]; + return count > budget ? [`${key}: ${count} > ${budget}`] : []; +}); + +console.log( + JSON.stringify( + { + budgets, + counts, + }, + null, + 2, + ), +); + +if (failures.length > 0) { + console.error(`Deadcode export budget exceeded: ${failures.join(", ")}`); + process.exit(1); +} diff --git a/src/adapters/opencode/config.ts b/src/adapters/opencode/config.ts index 2b0462a..cf186c9 100644 --- a/src/adapters/opencode/config.ts +++ b/src/adapters/opencode/config.ts @@ -190,7 +190,7 @@ export function createFlowCoreConfigEntries() { return { agent, command }; } -export function applyFlowCoreConfig(config: MutableConfig): void { +function applyFlowCoreConfig(config: MutableConfig): void { const entries = createFlowCoreConfigEntries(); config.agent = { ...(config.agent ?? {}), diff --git a/src/adapters/opencode/tool-projections.generated.ts b/src/adapters/opencode/tool-projections.generated.ts index 6dd7625..7848f33 100644 --- a/src/adapters/opencode/tool-projections.generated.ts +++ b/src/adapters/opencode/tool-projections.generated.ts @@ -1,9 +1,6 @@ import type { CoreActionName } from "../../core/registry"; import { renderOpenCodeToolCoreSummary } from "./tool-surface/core-action-projection"; -import { - OPENCODE_TOOL_REGISTRY, - openCodeToolDescription as registryToolDescription, -} from "./tool-surface/tool-registry"; +import { OPENCODE_TOOL_REGISTRY } from "./tool-surface/tool-registry"; // NOTE: kept as *.generated.ts for import stability; projections are registry-derived // and parity-tested in tests/descriptor-family-parity.test.ts. @@ -59,10 +56,6 @@ export function getOpenCodeToolProjection( ); } -export function openCodeToolDescription(toolName: string): string { - return registryToolDescription(toolName); -} - export function openCodeToolCoreSummary(toolName: string): string | null { const projection = getOpenCodeToolProjection(toolName); return renderOpenCodeToolCoreSummary({ diff --git a/src/adapters/opencode/tool-surface/core-action-projection.ts b/src/adapters/opencode/tool-surface/core-action-projection.ts index 60e2962..2c42c55 100644 --- a/src/adapters/opencode/tool-surface/core-action-projection.ts +++ b/src/adapters/opencode/tool-surface/core-action-projection.ts @@ -11,7 +11,7 @@ export type CoreActionProjectionMetadata = Pick< name: CoreActionName; }; -export function coreActionProjectionMetadata( +function coreActionProjectionMetadata( coreActionName: CoreActionName, ): CoreActionProjectionMetadata { const coreAction = coreActionByName(coreActionName); diff --git a/src/adapters/opencode/tool-surface/descriptor-guidance.ts b/src/adapters/opencode/tool-surface/descriptor-guidance.ts index f5fb01f..0fdd375 100644 --- a/src/adapters/opencode/tool-surface/descriptor-guidance.ts +++ b/src/adapters/opencode/tool-surface/descriptor-guidance.ts @@ -1,5 +1,4 @@ -export const FLOW_TOOL_DOCS_SECTION = - "docs/development.md#current-runtime-tools"; +const FLOW_TOOL_DOCS_SECTION = "docs/development.md#current-runtime-tools"; export const FLOW_DEFAULT_TOOL_DOCS_ROW = { section: FLOW_TOOL_DOCS_SECTION, diff --git a/src/adapters/opencode/tool-surface/descriptors.ts b/src/adapters/opencode/tool-surface/descriptors.ts index e0444d9..4a125b5 100644 --- a/src/adapters/opencode/tool-surface/descriptors.ts +++ b/src/adapters/opencode/tool-surface/descriptors.ts @@ -277,11 +277,3 @@ export const FLOW_HOST_TOOL_SURFACE_DESCRIPTORS = (descriptor): descriptor is FlowHostToolSurfaceDescriptor => descriptor.hostToolName !== null, ); - -export function getFlowSurfaceDescriptor( - id: string, -): FlowSurfaceDescriptor | null { - return ( - FLOW_SURFACE_DESCRIPTORS.find((descriptor) => descriptor.id === id) ?? null - ); -} diff --git a/src/adapters/opencode/tool-surface/docs-rows.generated.ts b/src/adapters/opencode/tool-surface/docs-rows.generated.ts index 2a8942a..dac858f 100644 --- a/src/adapters/opencode/tool-surface/docs-rows.generated.ts +++ b/src/adapters/opencode/tool-surface/docs-rows.generated.ts @@ -23,7 +23,3 @@ export const FLOW_TOOL_DOCS_ROWS: readonly FlowToolDocsRow[] = ] : [], ); - -export function renderFlowToolDocsRow(row: FlowToolDocsRow): string { - return `- \`${row.toolName}\` — ${row.description}`; -} diff --git a/src/adapters/opencode/tool-surface/mutable-workspace-permission.ts b/src/adapters/opencode/tool-surface/mutable-workspace-permission.ts index 3c6ced4..7d5e2ef 100644 --- a/src/adapters/opencode/tool-surface/mutable-workspace-permission.ts +++ b/src/adapters/opencode/tool-surface/mutable-workspace-permission.ts @@ -29,7 +29,7 @@ function requiresHiddenRootApproval(root: string): boolean { return name.startsWith(".") && name !== ".flow"; } -export function resolveMutableToolWorkspace( +function resolveMutableToolWorkspace( context: ToolContext, ): ResolvedMutableToolWorkspace { const resolved = resolveMutableSessionRoot(context); diff --git a/src/adapters/opencode/tool-surface/runtime-tools/review-tools.ts b/src/adapters/opencode/tool-surface/runtime-tools/review-tools.ts index 9a3f7cb..ddb0a23 100644 --- a/src/adapters/opencode/tool-surface/runtime-tools/review-tools.ts +++ b/src/adapters/opencode/tool-surface/runtime-tools/review-tools.ts @@ -8,9 +8,9 @@ import { toJson } from "../../../../runtime/application/workspace-runtime"; import { tool } from "../../sdk"; import { withParsedArgs } from "../parsed-tool"; import { + FinalReviewerDecisionSchema, FlowReviewRecordFeatureArgsSchema, FlowReviewRecordFeatureArgsShape, - FlowReviewRecordFinalArgsSchema, FlowReviewRecordFinalArgsShape, FlowReviewRenderArgsSchema, FlowReviewRenderArgsShape, @@ -72,7 +72,7 @@ export function createReviewRuntimeTools() { description: openCodeToolDescription("flow_review_record_final"), args: FlowReviewRecordFinalArgsShape, execute: withParsedArgs( - FlowReviewRecordFinalArgsSchema, + FinalReviewerDecisionSchema, async (input, context: ToolContext) => { context.metadata?.({ title: `Final reviewer requested ${input.status} — pending Flow persistence`, diff --git a/src/adapters/opencode/tool-surface/schemas.ts b/src/adapters/opencode/tool-surface/schemas.ts index 7c04ee3..bf42141 100644 --- a/src/adapters/opencode/tool-surface/schemas.ts +++ b/src/adapters/opencode/tool-surface/schemas.ts @@ -6,8 +6,8 @@ import { FEATURE_ID_PATTERN, } from "../../../runtime/constants"; import { + FinalReviewerDecisionSchema as RuntimeFinalReviewerDecisionSchema, FlowReviewRecordFeatureArgsSchema as RuntimeFlowReviewRecordFeatureArgsSchema, - FlowReviewRecordFinalArgsSchema as RuntimeFlowReviewRecordFinalArgsSchema, OutcomeSchema as RuntimeOutcomeSchema, PlanArgsSchema as RuntimePlanArgsSchema, PlanningContextArgsSchema as RuntimePlanningContextArgsSchema, @@ -28,8 +28,8 @@ export type ToolContext = WorkspaceContext & metadata?: OpenCodeToolContext["metadata"]; ask?: OpenCodeToolContext["ask"]; }; -export const FlowStatusViewSchema = z.enum(["compact", "detailed"]); -export const featureIdSchema = z +const FlowStatusViewSchema = z.enum(["compact", "detailed"]); +const featureIdSchema = z .string() .regex(FEATURE_ID_PATTERN, FEATURE_ID_MESSAGE); @@ -71,7 +71,7 @@ export const FlowRunCompleteFeatureArgsShape = { export const FlowReviewRecordFeatureArgsShape = RuntimeFlowReviewRecordFeatureArgsSchema.shape; export const FlowReviewRecordFinalArgsShape = - RuntimeFlowReviewRecordFinalArgsSchema.shape; + RuntimeFinalReviewerDecisionSchema.shape; export const FlowReviewRenderArgsShape = { ...ReviewReportSchema.shape, view: z.enum(["human", "structured", "both"]).optional(), @@ -101,12 +101,6 @@ export const FlowResetFeatureArgsShape = { }; export const WorkerResultArgsSchema = RuntimeWorkerResultArgsSchema; -export const RuntimeWorkerResultBaseShape = RuntimeWorkerResultBaseSchema.shape; -export const RuntimeFlowReviewRecordFeatureArgsShape = - RuntimeFlowReviewRecordFeatureArgsSchema.shape; -export const RuntimeFlowReviewRecordFinalArgsShape = - RuntimeFlowReviewRecordFinalArgsSchema.shape; - export const FlowStatusArgsSchema = z.object(FlowStatusArgsShape); export const FlowDoctorArgsSchema = z.object(FlowDoctorArgsShape); export const FlowHistoryArgsSchema = z.object(FlowHistoryArgsShape); @@ -120,13 +114,9 @@ export const FlowPlanStartArgsSchema = z.object(FlowPlanStartArgsShape); export const FlowPlanApproveArgsSchema = z.object(FlowPlanApproveArgsShape); export const FlowPlanSelectArgsSchema = z.object(FlowPlanSelectArgsShape); export const FlowRunStartArgsSchema = z.object(FlowRunStartArgsShape); -export const FlowRunCompleteFeatureArgsSchema = z.object( - FlowRunCompleteFeatureArgsShape, -); export const FlowReviewRecordFeatureArgsSchema = RuntimeFlowReviewRecordFeatureArgsSchema; -export const FlowReviewRecordFinalArgsSchema = - RuntimeFlowReviewRecordFinalArgsSchema; +export const FinalReviewerDecisionSchema = RuntimeFinalReviewerDecisionSchema; export const FlowReviewRenderArgsSchema = z.object(FlowReviewRenderArgsShape); export const FlowResetFeatureArgsSchema = z.object(FlowResetFeatureArgsShape); @@ -231,7 +221,7 @@ export const FLOW_TOOL_PAYLOAD_SCHEMA_REGISTRY = { }, flow_review_record_final: { argsShape: FlowReviewRecordFinalArgsShape, - argsSchema: FlowReviewRecordFinalArgsSchema, + argsSchema: FinalReviewerDecisionSchema, payloadSchemaOwners: [ "src/adapters/opencode/tool-surface/schemas.ts", "src/runtime/schema.ts", diff --git a/src/adapters/opencode/tool-surface/tool-registry.ts b/src/adapters/opencode/tool-surface/tool-registry.ts index ae85b98..7d845d4 100644 --- a/src/adapters/opencode/tool-surface/tool-registry.ts +++ b/src/adapters/opencode/tool-surface/tool-registry.ts @@ -309,7 +309,7 @@ export function openCodeToolDescription(toolName: string): string { return entry.hostDescription; } -export function openCodeToolRuntimeBinding( +function openCodeToolRuntimeBinding( toolName: RuntimeBoundOpenCodeToolName, ): Exclude { const entry = getOpenCodeToolRegistryEntry(toolName); diff --git a/src/audit/report-normalizer.ts b/src/audit/report-normalizer.ts index 60a7b09..70a9689 100644 --- a/src/audit/report-normalizer.ts +++ b/src/audit/report-normalizer.ts @@ -21,7 +21,7 @@ function isDirectlyReviewedWithEvidence(surface: ReviewSurface): boolean { ); } -export type ReviewCoverageSummary = { +type ReviewCoverageSummary = { directlyReviewed: ReviewSurface[]; directlyReviewedWithEvidence: ReviewSurface[]; spotChecked: ReviewSurface[]; diff --git a/src/audit/report-schema.ts b/src/audit/report-schema.ts index fb09e63..fce335e 100644 --- a/src/audit/report-schema.ts +++ b/src/audit/report-schema.ts @@ -35,7 +35,7 @@ const FindingCategorySchema = z.enum([ const FindingConfidenceSchema = z.enum(["confirmed", "likely", "speculative"]); const FindingSeveritySchema = z.enum(["high", "medium", "low"]); -export const ReviewDiscoveredSurfaceSchema = z +const ReviewDiscoveredSurfaceSchema = z .object({ name: z.string().min(1), category: SurfaceCategorySchema, @@ -58,7 +58,7 @@ export const ReviewDiscoveredSurfaceSchema = z } }); -export const ReviewValidationRunSchema = z +const ReviewValidationRunSchema = z .object({ command: z.string().min(1), status: ValidationStatusSchema, @@ -66,7 +66,7 @@ export const ReviewValidationRunSchema = z }) .strict(); -export const ReviewFindingSchema = z +const ReviewFindingSchema = z .object({ title: z.string().min(1), category: FindingCategorySchema, diff --git a/src/config.ts b/src/config.ts index 6129aab..07dd9c8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,4 @@ export { applyFlowConfig, - applyFlowCoreConfig, - createConfigHook, createFlowCoreConfigEntries, } from "./adapters/opencode/config"; diff --git a/src/core/protocols/index.ts b/src/core/protocols/index.ts index f64ca57..70310e7 100644 --- a/src/core/protocols/index.ts +++ b/src/core/protocols/index.ts @@ -1,2 +1 @@ export * from "./roles"; -export type { SemanticInvariantId } from "./semantic-invariants"; diff --git a/src/installer.ts b/src/installer.ts index 7a9fade..1bc90ed 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -35,18 +35,18 @@ Usage: Options: --help Show this message`; -export interface ResolveInstallTargetOptions { +interface ResolveInstallTargetOptions { homeDir?: string; filename?: string; } -export interface InstallBuiltPluginOptions { +interface InstallBuiltPluginOptions { sourceFile: string; destinationFile: string; logger?: (message: string) => void; } -export interface InstallCommandDependencies { +interface InstallCommandDependencies { build?: () => Promise; cwd?: string; homeDir?: string; @@ -87,7 +87,7 @@ export function resolveInstallTarget({ return join(homeDir, ...CANONICAL_OPENCODE_PLUGIN_DIRECTORY, filename); } -export function writeStdoutLine(message: string): void { +function writeStdoutLine(message: string): void { process.stdout.write(`${message}\n`); } diff --git a/src/prompts/fragments.ts b/src/prompts/fragments.ts index 3303c1b..9cea1a7 100644 --- a/src/prompts/fragments.ts +++ b/src/prompts/fragments.ts @@ -10,99 +10,13 @@ export const FLOW_FRAGMENT_INVARIANT_IDS = [ "tools.canonical_surface.no_raw_wrappers", ] as const satisfies readonly SemanticInvariantId[]; -export const FLOW_RUNTIME_TOOLS_AUTHORITATIVE_RULE = - "- Treat Flow runtime tools as authoritative."; -export const FLOW_RUNTIME_TOOLS_AUTHORITATIVE_WORKFLOW_RULE = - "- Treat Flow runtime tools as authoritative for workflow state."; export const FLOW_AUTHORITATIVE_TOOL_JSON_RULE = "- Treat returned Flow tool JSON as authoritative. OpenCode row metadata is provisional request-time UI context only; when tool JSON returns status: error, do not retry the same final-review or completion payload unchanged."; export const FLOW_NEVER_WRITE_FLOW_FILES_RULE = "- Never write .flow files directly."; -export const FLOW_COORDINATOR_BOUNDARY_RULE = - "- Stay at the coordinator layer: decide whether planning, execution, review, reset, or recovery happens next, and rely on the specialized Flow roles for their detailed contracts."; -export const FLOW_REVIEW_FINDINGS_LOOP_RULE = - "- Do not complete a feature while review findings remain. Fix them, record a finding-by-finding closure ledger with code/test/validation evidence and residual risk, rerun validation, and rereview until the feature is clean or a real blocker remains."; -export const FLOW_REVIEW_FIRST_WITHOUT_FINDINGS_PLAN_RULE = - "- Broad review-and-fix/codebase-review goals with no concrete existing findings in planning.reviewFindings must start as goalMode: review for audit/discovery; replan to goalMode: review_and_fix only after concrete findings are recorded in planning.reviewFindings."; -export const FLOW_FEATURE_REVIEW_APPROVAL_RULE = - "- Before persisting success, get flow-reviewer approval and record it through flow_review_record_feature."; -export const FLOW_FINAL_COMPLETION_PATH_RULE = - "- Treat the active feature as the final completion path whenever completing it would satisfy the session completion policy, including completionPolicy.minCompletedFeatures even if other plan features remain pending. On the final completion path, switch to broad validation, get the runtime-owned final review matching deliveryPolicy.finalReviewPolicy, persist that approval through the canonical final-review runtime tool, and include a passing finalReview before completion."; -export const FLOW_NEVER_ADVANCE_DIRTY_FEATURE_RULE = - "- Never advance to the next feature while the current feature still has review findings. Stay on the current feature until it is clean or truly blocked."; -export const FLOW_FINAL_COMPLETION_REVIEW_RULE = - "- Before final completion, run broad repo validation, perform the runtime-owned final review matching deliveryPolicy.finalReviewPolicy, fix findings, rerun broad validation, and only then finish with a passing `finalReview`. Choose broad policy for low-risk localized implementation and detailed policy for high-risk, review/review-and-fix, or high-assurance work; the final completion path can be reached by satisfying completionPolicy.minCompletedFeatures even when other plan features remain pending."; -export const FLOW_NO_INFERRED_GOAL_RULE = - "- Do not derive, infer, or invent a new goal from repository inspection when invoked without a goal and no active session exists."; -export const FLOW_RESUME_ONLY_RULE = - "- When invoked with empty input or `resume`, treat the command as resume-only. If no active session exists, stop and request a goal instead of creating one."; -export const FLOW_STRUCTURED_RECOVERY_RULE = - "- When tool errors include structured recovery metadata, satisfy `recovery.prerequisite` first. Only call canonical `recovery.nextRuntimeTool` values when present. Treat `recovery.nextCommand` as guidance. Recovery examples (including `exampleReviewScopeLedger`) are scaffold-only, never replay evidence. After repeated same-category reviewScopeLedger failures, inspect flow_status or recovery details and repair evidenceRefs before retrying."; -export const FLOW_RUNTIME_STATE_TRANSITION_RULE = - "- Use Flow runtime tools for every state transition."; -export const FLOW_COORDINATOR_ROLE_ROUTING_RULE = - "- Use flow-planner for plan creation, flow-worker for implementation plus validation, and flow-reviewer for approval instead of restating their full instructions yourself."; -export const FLOW_SUBAGENT_SUBJECT_GROUP_SPLIT_RULE = - "- Use Task/subagent handoffs for independent, bounded, role-appropriate subject groups that benefit from isolated context. Do not split tiny sequential steps, same-file chains, or tightly shared-context work just to create extra subagents."; export const FLOW_HANDOFF_MODE_DECISION_RULE = "- Handoff decision: for each planning, execution, and review phase that flow-auto owns, choose handoffMode exactly as `task_subagent`, `inline_role`, or `not_supported`. Use `task_subagent` only for an actual OpenCode Task/subagent handoff to the target role; use `inline_role` for tiny, sequential, or tightly shared-context work; use `not_supported` when Task is unavailable, denied, or not permission-allowed."; export const FLOW_HANDOFF_MODE_PROGRESS_RULE = "- Handoff reporting: before each planning, execution, or review phase, report `Phase: — handoffMode: — target: — reason: <...>`. Prefer `task_subagent` for non-trivial bounded planning/execution/review when supported; prefer `inline_role` for tiny/sequential/shared-context work; use `not_supported` when Task is unavailable/denied; derived task-progress rows are runtime projections, not proof of actual child sessions."; -export const FLOW_TASK_HANDOFF_RULE = - "- When OpenCode task/subagent invocation is available, use the Task tool to hand read-only planning research to flow-planning-researcher, bounded planning to flow-planner, implementation to flow-worker, and review to flow-reviewer so each role works in a fresh child context and reports back with artifacts, validation, and blockers. Apply the handoffMode vocabulary exactly: `task_subagent` for actual Task handoffs, `inline_role` for tiny sequential/shared-context work, and `not_supported` when Task is unavailable or denied. Treat flow-reviewer and audit/review surfaces as leaf reporting roles by default; do not recurse delegation unless a concrete independent subproblem materially benefits. Handoffs do not replace runtime ownership: persist state changes only through Flow runtime tools and never edit .flow files directly."; export const FLOW_WORKER_REVIEW_TASK_RULE = "- When OpenCode task/subagent invocation is available, ask flow-reviewer through the Task tool for an independent review in a fresh child context instead of performing the approval gate in the same worker context. This is a direct approval handoff, not recursive delegation by default; if Task is unavailable or denied, report the review with handoffMode exactly as `inline_role` for inline approval fallback or `not_supported` when Task is unavailable or denied instead of implying a child session."; -export const FLOW_PERSIST_REVIEWER_DECISIONS_RULE = - "- Persist every reviewer decision through the canonical feature or final review-record runtime tool before deciding whether to continue, fix, block, or complete."; -export const FLOW_RESOLVE_RUNTIME_ERRORS_RULE = - "- Treat runtime contract errors, completion gating failures, and failing validation as work to resolve, not stop conditions."; -export const FLOW_OPERATOR_PROGRESS_RULE = - "- Keep the user informed with concise operator progress updates at phase boundaries this mode owns: say the current phase, the immediate action, and why it matters in one short sentence before starting a major phase; after each phase, summarize the outcome/evidence and next step. Progress updates are assistant prose only; never include progress narration inside worker-result, reviewer-decision, or `finalReview` fields. Do not dump raw tool JSON or narrate every minor file read/tool call."; -export const FLOW_OPERATOR_PROGRESS_CHECKPOINTS = `Operator progress checkpoints: -- Start: classify the request or active session state. -- Planning: summarize the repo evidence being gathered and the plan/approval outcome. -- Execution: name the active feature and the implementation focus before edits. -- Validation: state the validation command or evidence target before running it, then report pass/fail. -- Review: state whether feature or final review is happening, then report approval/fix/blocker outcome. -- Recovery/reset: explain the blocker, prerequisite, canonical runtime action, and retry plan. -- Finalization: summarize completion status, remaining risk, and the runtime next step.`; - -export const FLOW_ENGINEERING_QUALITY_RULE = - "- Apply the repo's coding guidelines before completion: prefer deletion/reuse over new layers, keep diffs small, use existing scripts and utilities, inspect existing logging/telemetry/CLI-output patterns before changing `console.*`, classify each occurrence, remove only temporary debug noise, replace intentional operator/observability signals with the repo's existing logger, telemetry API, injected logger, or explicit stdout/stderr stream writes while preserving severity, message intent, and key context, if no facility exists add the smallest local injected adapter or report a blocker instead of inventing a dependency, and add or update tests for behavior changes."; -export const FLOW_RELEASE_HYGIENE_REVIEW_RULE = - "- Treat release hygiene as a review gate: do not approve work that leaves raw console calls, debugger statements, or undocumented debug-only instrumentation in release-bound source or build artifacts, do not approve changes that delete intentional operator/observability signals without evidence of an equivalent logger, telemetry, or stdout/stderr replacement preserving severity, message intent, and key context, and do not approve a new logging or telemetry dependency unless it was explicitly approved."; -export const FLOW_REVIEW_CONTEXT_DISCOVERY_RULE = - "- Treat changed files as the review seed, not the boundary: include connected context discovered through callers/callees, state or lifecycle owners, architectural neighbors, tests, and validation evidence; distinguish directly changed files from connected context and report coverage gaps/validation limits explicitly."; -export const FLOW_CONTEXT_GATHERING_RUNTIME_RULE = - "- Treat context gathering as a Flow-wide runtime contract: gather or reuse source-backed evidence before planning, execution, or review claims; persist durable repo evidence through planning.evidencePackets/sourceRefs when it should survive across commands; changed files are seeds, not boundaries; control/status surfaces only report existing context."; -export const FLOW_CONTEXT_GATHERING_READONLY_RULE = - "- Treat context gathering as a read-only evidence contract: gather or reuse source-backed evidence before planning or review claims; return durable repo evidence as evidencePackets/sourceRefs for the planner, coordinator, or runtime owner to persist; changed files are seeds, not boundaries; control/status surfaces only report existing context."; -export const FLOW_ADVERSARIAL_FAILURE_MODE_REVIEW_RULE = - "- Review changed behavior through applicable adversarial failure-mode classes before approving: lifecycle/reentrancy/idempotency, async races/event ordering, persistence failure and recovery, interaction geometry/hit-testing, accessibility semantics/live regions, and test-evidence authenticity. Treat changed files as review seeds, not boundaries. When a class is applicable, cite the concrete path checked in summary, integrationChecks, regressionChecks, blockingFindings, followUps, or suggestedValidation; when it is not applicable, do not force a finding. For final reviews, record truly required behavior classes in behaviorChecks as passed or gap_recorded, map relied-on validation through validationCoverage, and omit non-required behavior classes instead of padding them with not_applicable entries."; -export const FLOW_STACK_STANDARDS_PROFILE_RUNTIME_RULE = - "- Treat planning.stackProfile and planning.standardsProfile as the runtime-owned stack and standards profile: local repo guidance outranks official docs, official docs outrank broader Exa/websearch guidance, resolve planning.standardsProfile.gaps by using available MCP tools first (Ref MCP for official docs, Exa for current ecosystem best-practice synthesis), and websearch/webfetch only as fallback; record researched sources and resulting rules through flow_plan_context_record; keep external guidance bounded to the detected stack and never change package/dependency versions from standards research alone."; -export const FLOW_STACK_STANDARDS_PROFILE_READONLY_RULE = - "- Treat planning.stackProfile and planning.standardsProfile as runtime-owned stack and standards profiles: local repo guidance outranks official docs, official docs outrank broader Exa/websearch guidance, resolve standards gaps with available source-backed research, return researched sources and resulting rules for the planner, coordinator, or runtime owner to persist, keep external guidance bounded to the detected stack, and never change package/dependency versions from standards research alone."; - -export const FLOW_PACKAGE_MANAGER_PRIMARY_CONTRACT_RULE = - "- Treat existing package.json scripts as the primary execution contract; invoke them through the detected package manager or the repo's established script-running convention. Package-manager detection is supporting evidence. Do not assume Bun unless repo evidence says Bun."; -export const FLOW_PACKAGE_MANAGER_AMBIGUITY_PLAN_RULE = - "- If package-manager evidence is ambiguous, do not guess. Prefer existing package.json scripts and call out the ambiguity in planning context."; -export const FLOW_PACKAGE_MANAGER_PRIMARY_VALIDATION_RULE = - "- Use existing package.json scripts first for validation/build/test, invoked through the detected package manager or the repo's established script-running convention. Use raw manager-specific commands or direct tool binaries only when scripts do not cover the needed check. Do not default to Bun in non-Bun repos."; -export const FLOW_PACKAGE_MANAGER_AMBIGUITY_EXECUTION_RULE = - "- If package-manager evidence is ambiguous, do not guess a manager-specific command when an existing package.json script covers the task."; -export const FLOW_PACKAGE_MANAGER_PRIMARY_COORDINATOR_RULE = - "- Treat existing package.json scripts as primary and invoke them through the detected package manager or the repo's established script-running convention. Treat package-manager detection as supporting evidence instead of assuming Bun."; -export const FLOW_PACKAGE_MANAGER_AMBIGUITY_COORDINATOR_RULE = - "- If package-manager evidence is ambiguous, do not invent a manager-specific command; use existing scripts first and surface the ambiguity clearly if scripts are insufficient."; -export const FLOW_FINAL_COMPLETION_COMMAND_RULE = - "- On the final completion path, run broad validation, obtain the runtime-owned final approval matching deliveryPolicy.finalReviewPolicy through `flow_review_record_final`, include a passing `finalReview`, and only then persist the result through `flow_run_complete_feature`."; -export const FLOW_FINAL_COMPLETION_WORKER_STEP_RULE = - "On the final completion path, run broad validation, ask flow-reviewer for the final review matching deliveryPolicy.finalReviewPolicy, and persist that approval with the canonical final-review runtime tool using the direct reviewer decision object."; -export const FLOW_FINAL_COMPLETION_AUTO_STEP_RULE = - "On the final completion path, have flow-worker run broad validation, use flow-reviewer for the final review matching deliveryPolicy.finalReviewPolicy, persist it with the canonical final-review runtime tool using the direct reviewer decision object, and keep fixing/revalidating until the final review passes."; -export const FLOW_FINAL_REVIEW_RERECORD_BOUNDED_RETRY_RULE = - "- After `flow_review_record_final` returns `ok`, do not re-record the same final review. Retry only when `flow_review_record_final` errors or `flow_run_complete_feature` recovery requires `final_reviewer_decision`; then submit a corrected evidence-grounded decision, never an identical decision or unchanged scaffold."; -export const FLOW_SINGLETON_RUNTIME_RETRY_RULE = - "- Treat runtime tool metadata as request progress, not persisted state. After the singleton plan-approval, execution-start, or review state transition returns ok, do not repeat the same runtime call unless the tool response was lost, flow_status shows the state is still missing, or structured recovery explicitly requires that transition/artifact. For a lost-response execution-start retry, an implicit flow_run_start may return an already-running/no-state-change ok; treat that as confirmation and continue, not permission to start another feature. This does not apply to repeatable planning context/evidence recording such as flow_plan_context_record when new evidence should be persisted. Do not repeat history-appending completion calls without new worker evidence."; diff --git a/src/prompts/generated/protocol-render.ts b/src/prompts/generated/protocol-render.ts index 4a73fea..07434a4 100644 --- a/src/prompts/generated/protocol-render.ts +++ b/src/prompts/generated/protocol-render.ts @@ -113,7 +113,7 @@ function renderOutcomeFirstFrame(mode: FlowPromptMode): string[] { ]; } -export function renderGeneratedSourceNote(protocol: CoreRoleProtocol): string { +function renderGeneratedSourceNote(protocol: CoreRoleProtocol): string { const sources = [ "src/core/protocols/roles.ts", "src/core/registry/actions.ts", @@ -138,7 +138,7 @@ export function renderProtocolHeader( ].join("\n\n"); } -export function renderModeContractProtocol( +function renderModeContractProtocol( protocol: CoreRoleProtocol, modeOverride?: FlowPromptMode, ): string { @@ -163,7 +163,7 @@ export function renderModeContractProtocol( ].join("\n"); } -export function renderCoreActionProtocol(protocol: CoreRoleProtocol): string { +function renderCoreActionProtocol(protocol: CoreRoleProtocol): string { const actions = getCoreRoleActions(protocol); if (actions.length === 0) { return "Core actions: none; this role renders or reviews without mutating workflow state."; @@ -180,7 +180,7 @@ export function renderCoreActionProtocol(protocol: CoreRoleProtocol): string { ].join("\n"); } -export function renderRoleBoundaryProtocol(protocol: CoreRoleProtocol): string { +function renderRoleBoundaryProtocol(protocol: CoreRoleProtocol): string { return ["Role boundaries:", listLines(protocol.boundaryRules)].join("\n"); } @@ -228,7 +228,7 @@ export function renderAutoSkillReferences(fallbackTarget: string): string { ].join(" "); } -export function renderInvariantProtocol(protocol: CoreRoleProtocol): string { +function renderInvariantProtocol(protocol: CoreRoleProtocol): string { const ids = getCoreRoleInvariantIds(protocol); if (ids.length === 0) { return "Referenced semantic invariants: none; read-only audit coverage is governed by the audit ledger contract."; diff --git a/src/prompts/generated/skill-docs.ts b/src/prompts/generated/skill-docs.ts index 633ea92..f500e89 100644 --- a/src/prompts/generated/skill-docs.ts +++ b/src/prompts/generated/skill-docs.ts @@ -16,13 +16,13 @@ export { FLOW_SKILL_SPECS }; export const FLOW_SKILL_GENERATED_MARKER = "flow-opencode-generated-skill"; export const FLOW_SKILL_GENERATED_VERSION = "1"; -export type FlowSkillGeneratedMarker = { +type FlowSkillGeneratedMarker = { name: string; version: string; hash: string; }; -export type FlowSkillDocumentInspection = +type FlowSkillDocumentInspection = | { kind: "not_generated" } | { kind: "valid_generated"; marker: FlowSkillGeneratedMarker } | { kind: "invalid_generated"; reason: string }; @@ -86,11 +86,6 @@ export function inspectFlowSkillDocument( }; } -export function flowSkillManagedPayloadHash(document: string): string | null { - const inspection = inspectFlowSkillDocument(document); - return inspection.kind === "valid_generated" ? inspection.marker.hash : null; -} - function renderSkillFrontmatter(skill: FlowSkillSpec): string { return [ "---", diff --git a/src/runtime/application/flow-core.ts b/src/runtime/application/flow-core.ts index dbd9839..0d02ac6 100644 --- a/src/runtime/application/flow-core.ts +++ b/src/runtime/application/flow-core.ts @@ -61,27 +61,24 @@ export const FLOW_CORE_COMMAND_NAMES = [ export const FLOW_CORE_QUERY_NAMES = SESSION_READ_ACTION_NAMES; -export type FlowCoreCommandName = +type FlowCoreCommandName = | SessionWorkspaceActionName | SessionMutationActionName; -export type FlowCoreQueryName = SessionReadActionName; +type FlowCoreQueryName = SessionReadActionName; -export type FlowCoreCommandPayloadMap = SessionWorkspacePayloadMap & - SessionMutationPayloadMap; -export type FlowCoreCommandValueMap = SessionWorkspaceValueMap & - SessionMutationValueMap; -export type FlowCoreQueryPayloadMap = SessionReadPayloadMap; -export type FlowCoreQueryValueMap = SessionReadValueMap; +type FlowCoreQueryPayloadMap = SessionReadPayloadMap; -export type FlowCoreCommandResult = +type FlowCoreCommandResult = Name extends SessionWorkspaceActionName ? SessionWorkspaceResult : Name extends SessionMutationActionName ? SessionMutationResult : never; -export type FlowCoreQueryResult = - SessionReadResult; +type FlowCoreQueryResult = SessionReadResult< + SessionReadValueMap[Name], + Name +>; const FLOW_CORE_WORKSPACE_COMMAND_NAME_SET = new Set( SESSION_WORKSPACE_ACTION_NAMES, diff --git a/src/runtime/application/index.ts b/src/runtime/application/index.ts index ba98095..5a9629e 100644 --- a/src/runtime/application/index.ts +++ b/src/runtime/application/index.ts @@ -1,16 +1,5 @@ export { InvalidFlowWorkspaceRootError } from "../workspace-root"; -export type { DoctorCheck, DoctorCheckStatus } from "./doctor-checks"; export { buildDoctorReport } from "./doctor-report"; -export type { - FlowCoreCommandName, - FlowCoreCommandPayloadMap, - FlowCoreCommandResult, - FlowCoreCommandValueMap, - FlowCoreQueryName, - FlowCoreQueryPayloadMap, - FlowCoreQueryResult, - FlowCoreQueryValueMap, -} from "./flow-core"; export { executeFlowCoreCommand, executeFlowCoreQuery, @@ -23,10 +12,7 @@ export { runFlowCoreCommand, runFlowCoreQuery, } from "./flow-core"; -export { - renderDoctorSummary, - renderSessionStatusSummary, -} from "./operator-presenters"; +export { renderSessionStatusSummary } from "./operator-presenters"; export type { SessionMutationActionName, SessionMutationPayloadMap, @@ -41,23 +27,9 @@ export { export { autoPrepareResponse } from "./session-auto-prepare-presenter"; export type { RuntimeToolResponse, - SessionMutationAction, SessionMutationResult, - SessionReadAction, - SessionReadResult, - SessionReadRuntimePort, - SessionRuntimePort, - SessionWorkspaceAction, - SessionWorkspaceResult, - SessionWorkspaceRuntimePort, } from "./session-engine"; export { - DEFAULT_SESSION_READ_RUNTIME_PORT, - DEFAULT_SESSION_RUNTIME_PORT, - DEFAULT_SESSION_WORKSPACE_RUNTIME_PORT, -} from "./session-engine"; -export { - closeSessionResponse, historyResponse, missingStoredSessionResponse, statusResponse, @@ -77,7 +49,6 @@ export { export type { SessionWorkspaceActionName, SessionWorkspacePayloadMap, - SessionWorkspaceValueMap, } from "./session-workspace-actions"; export { dispatchSessionWorkspaceAction, @@ -85,18 +56,10 @@ export { runDispatchedSessionWorkspaceAction, SESSION_WORKSPACE_ACTION_NAMES, } from "./session-workspace-actions"; -export type { - ResolvedSessionRoot, - SessionRootMode, - SessionRootSource, - WorkspaceContext, - WorkspaceContextSummary, -} from "./workspace-runtime"; +export type { WorkspaceContext } from "./workspace-runtime"; export { inspectWorkspaceContext, resolveMutableSessionRoot, - resolveReadableSessionRoot, resolveSessionRoot, - toCompactJson, toJson, } from "./workspace-runtime"; diff --git a/src/runtime/application/package-manager.ts b/src/runtime/application/package-manager.ts index 22e1a80..685aae7 100644 --- a/src/runtime/application/package-manager.ts +++ b/src/runtime/application/package-manager.ts @@ -13,7 +13,7 @@ const PACKAGE_MANAGER_LOCKFILES: Array<{ { manager: "bun", filenames: ["bun.lock", "bun.lockb"] }, ]; -export type PackageManagerDetection = { +type PackageManagerDetection = { packageManager?: PackageManager; ambiguous: boolean; }; diff --git a/src/runtime/application/session-action-responses.ts b/src/runtime/application/session-action-responses.ts index d0574b7..d1e5391 100644 --- a/src/runtime/application/session-action-responses.ts +++ b/src/runtime/application/session-action-responses.ts @@ -46,7 +46,7 @@ const FAILED_MUTATION_DESCRIPTORS: Record< }, }; -export function buildLatestFailedMutation( +function buildLatestFailedMutation( actionName: FailedMutationActionName, session: Session, failure: FailedMutationResult, diff --git a/src/runtime/application/session-engine.ts b/src/runtime/application/session-engine.ts index 0011a13..d0390c7 100644 --- a/src/runtime/application/session-engine.ts +++ b/src/runtime/application/session-engine.ts @@ -145,35 +145,6 @@ export async function runSessionWorkspaceActionAtRoot( return runRuntimeActionAtRoot(worktree, action, runtime); } -export async function persistTransitionAtRoot( - actionName: Name, - worktree: string, - result: TransitionResult, - getSession: (value: T) => Session, - onSuccess: (saved: Session, value: T) => RuntimeToolResponse, - onError: ( - result: Extract, { ok: false }>, - ) => RuntimeToolResponse = (failure) => errorResponse(failure.message), - options: { - syncArtifacts?: boolean; - clearFailedAttemptOnSuccess?: FailedAttemptClearPolicy; - } = { syncArtifacts: true }, - runtime: SessionRuntimePort = DEFAULT_SESSION_RUNTIME_PORT, -): Promise { - return ( - await executeTransitionAtRoot( - actionName, - worktree, - result, - getSession, - onSuccess, - onError, - options, - runtime, - ) - ).response; -} - export async function executeTransitionAtRoot( actionName: Name, worktree: string, diff --git a/src/runtime/application/session-history-presenters.ts b/src/runtime/application/session-history-presenters.ts new file mode 100644 index 0000000..50d89e6 --- /dev/null +++ b/src/runtime/application/session-history-presenters.ts @@ -0,0 +1,127 @@ +import type { listSessionHistory } from "../lifecycle"; +import { deriveSessionOperatorState } from "../session-operator-state"; +import { explainSessionState } from "../summary"; +import { guidanceFields } from "./session-presenter-shared"; +import { toJson } from "./workspace-runtime"; + +type SessionHistory = Awaited>; +type SessionHistoryEntry = NonNullable; +type LatestFailedAttempt = SessionHistoryEntry["latestFailedAttempt"]; + +function collectHistoryEntries(history: SessionHistory): SessionHistoryEntry[] { + return [ + ...(history.active ? [history.active] : []), + ...history.stored, + ...history.completed, + ]; +} + +function latestHistoryFailedAttempt( + history: SessionHistory, +): LatestFailedAttempt { + return ( + collectHistoryEntries(history) + .filter((entry) => entry.latestFailedAttempt) + .sort((left, right) => + (right.latestFailedAttempt?.occurredAt ?? "").localeCompare( + left.latestFailedAttempt?.occurredAt ?? "", + ), + )[0]?.latestFailedAttempt ?? null + ); +} + +function historyFailedAttemptGroups(history: SessionHistory) { + const groups = new Map< + string, + { + tool: NonNullable["tool"]; + failureCategory: string; + count: number; + sessionIds: string[]; + latestOccurredAt: string | null; + recoveryHint?: string; + } + >(); + for (const entry of collectHistoryEntries(history)) { + const failure = entry.latestFailedAttempt; + if (!failure) continue; + const key = `${failure.tool}:${failure.failureCategory}`; + const existing = groups.get(key); + const attemptCount = failure.sameCategoryFailureCount ?? 1; + if (existing) { + existing.count += attemptCount; + existing.sessionIds.push(entry.id); + if ((failure.occurredAt ?? "") > (existing.latestOccurredAt ?? "")) { + existing.latestOccurredAt = failure.occurredAt ?? null; + if (failure.recoveryHint) { + existing.recoveryHint = failure.recoveryHint; + } + } + continue; + } + groups.set(key, { + tool: failure.tool, + failureCategory: failure.failureCategory, + count: attemptCount, + sessionIds: [entry.id], + latestOccurredAt: failure.occurredAt ?? null, + ...(failure.recoveryHint ? { recoveryHint: failure.recoveryHint } : {}), + }); + } + return [...groups.values()].sort((left, right) => + (right.latestOccurredAt ?? "").localeCompare(left.latestOccurredAt ?? ""), + ); +} + +export function historyResponse(history: SessionHistory, nextCommand: string) { + const activeCount = history.active ? 1 : 0; + const totalCount = + activeCount + history.stored.length + history.completed.length; + const parkedCount = history.stored.filter( + (session) => session.status !== "completed", + ).length; + const latestFailedAttempt = latestHistoryFailedAttempt(history); + const failedAttemptGroups = historyFailedAttemptGroups(history); + const metadata = { + totalCount, + activeCount, + storedCount: history.stored.length, + parkedCount, + completedCount: history.completed.length, + failedAttemptGroupCount: failedAttemptGroups.length, + }; + if (totalCount === 0) { + const guidance = explainSessionState(null); + const operator = deriveSessionOperatorState(null); + return { + payload: toJson({ + status: "missing", + summary: "No Flow session history found.", + operator, + ...guidanceFields(guidance), + history, + latestFailedAttempt, + failedAttemptGroups, + nextCommand, + }), + metadata, + }; + } + return { + payload: toJson({ + status: "ok", + summary: `Found ${totalCount} Flow session ${totalCount === 1 ? "entry" : "entries"} (${activeCount} active, ${history.stored.length} stored/${parkedCount} parked, ${history.completed.length} completed).`, + history, + latestFailedAttempt, + failedAttemptGroups, + ...(parkedCount > 0 + ? { + warning: + "Stored non-completed sessions are parked/inactive snapshots. Activate a stored session before continuing it; direct work outside Flow will not update its runtime records.", + } + : {}), + nextCommand, + }), + metadata, + }; +} diff --git a/src/runtime/application/session-mutation-finalization.ts b/src/runtime/application/session-mutation-finalization.ts index dab3eb4..68f6441 100644 --- a/src/runtime/application/session-mutation-finalization.ts +++ b/src/runtime/application/session-mutation-finalization.ts @@ -6,7 +6,7 @@ export type FailedAttemptClearPolicy = | true | { tool: LatestFailedFlowAttempt["tool"] }; -export interface SessionMutationPersistencePort { +interface SessionMutationPersistencePort { saveSessionState: (worktree: string, session: Session) => Promise; syncSessionArtifacts: (worktree: string, session: Session) => Promise; } diff --git a/src/runtime/application/session-planning-context.ts b/src/runtime/application/session-planning-context.ts index dbe1960..2e0a4a1 100644 --- a/src/runtime/application/session-planning-context.ts +++ b/src/runtime/application/session-planning-context.ts @@ -1,4 +1 @@ -export { - mergeEvidencePackets, - mergePlanningContext, -} from "../domain/planning-context"; +export { mergePlanningContext } from "../domain/planning-context"; diff --git a/src/runtime/application/session-presenters.ts b/src/runtime/application/session-presenters.ts index 4ee4251..703239d 100644 --- a/src/runtime/application/session-presenters.ts +++ b/src/runtime/application/session-presenters.ts @@ -1,8 +1,4 @@ -import type { - closeSession, - listSessionHistory, - loadStoredSession, -} from "../lifecycle"; +import type { loadStoredSession } from "../lifecycle"; import type { Session } from "../schema"; import { deriveSessionOperatorState } from "../session-operator-state"; import { deriveSessionViewModel, explainSessionState } from "../summary"; @@ -19,13 +15,11 @@ import { type WorkspaceContextSummary, } from "./workspace-runtime"; -type SessionHistory = Awaited>; -type SessionHistoryEntry = NonNullable; -type LatestFailedAttempt = SessionHistoryEntry["latestFailedAttempt"]; type StoredSessionRecord = Awaited>; -type CompletedSessionRecord = Awaited>; type StatusView = "detailed" | "compact"; +export { historyResponse } from "./session-history-presenters"; + function storedSessionGuidance( found: NonNullable, nextCommand: string, @@ -82,124 +76,6 @@ export function missingStoredSessionResponse( }); } -function collectHistoryEntries(history: SessionHistory): SessionHistoryEntry[] { - return [ - ...(history.active ? [history.active] : []), - ...history.stored, - ...history.completed, - ]; -} - -function latestHistoryFailedAttempt( - history: SessionHistory, -): LatestFailedAttempt { - return ( - collectHistoryEntries(history) - .filter((entry) => entry.latestFailedAttempt) - .sort((left, right) => - (right.latestFailedAttempt?.occurredAt ?? "").localeCompare( - left.latestFailedAttempt?.occurredAt ?? "", - ), - )[0]?.latestFailedAttempt ?? null - ); -} - -function historyFailedAttemptGroups(history: SessionHistory) { - const groups = new Map< - string, - { - tool: NonNullable["tool"]; - failureCategory: string; - count: number; - sessionIds: string[]; - latestOccurredAt: string | null; - recoveryHint?: string; - } - >(); - for (const entry of collectHistoryEntries(history)) { - const failure = entry.latestFailedAttempt; - if (!failure) continue; - const key = `${failure.tool}:${failure.failureCategory}`; - const existing = groups.get(key); - const attemptCount = failure.sameCategoryFailureCount ?? 1; - if (existing) { - existing.count += attemptCount; - existing.sessionIds.push(entry.id); - if ((failure.occurredAt ?? "") > (existing.latestOccurredAt ?? "")) { - existing.latestOccurredAt = failure.occurredAt ?? null; - if (failure.recoveryHint) { - existing.recoveryHint = failure.recoveryHint; - } - } - continue; - } - groups.set(key, { - tool: failure.tool, - failureCategory: failure.failureCategory, - count: attemptCount, - sessionIds: [entry.id], - latestOccurredAt: failure.occurredAt ?? null, - ...(failure.recoveryHint ? { recoveryHint: failure.recoveryHint } : {}), - }); - } - return [...groups.values()].sort((left, right) => - (right.latestOccurredAt ?? "").localeCompare(left.latestOccurredAt ?? ""), - ); -} - -export function historyResponse(history: SessionHistory, nextCommand: string) { - const activeCount = history.active ? 1 : 0; - const totalCount = - activeCount + history.stored.length + history.completed.length; - const parkedCount = history.stored.filter( - (session) => session.status !== "completed", - ).length; - const latestFailedAttempt = latestHistoryFailedAttempt(history); - const failedAttemptGroups = historyFailedAttemptGroups(history); - const metadata = { - totalCount, - activeCount, - storedCount: history.stored.length, - parkedCount, - completedCount: history.completed.length, - failedAttemptGroupCount: failedAttemptGroups.length, - }; - if (totalCount === 0) { - const guidance = explainSessionState(null); - const operator = deriveSessionOperatorState(null); - return { - payload: toJson({ - status: "missing", - summary: "No Flow session history found.", - operator, - ...guidanceFields(guidance), - history, - latestFailedAttempt, - failedAttemptGroups, - nextCommand, - }), - metadata, - }; - } - return { - payload: toJson({ - status: "ok", - summary: `Found ${totalCount} Flow session ${totalCount === 1 ? "entry" : "entries"} (${activeCount} active, ${history.stored.length} stored/${parkedCount} parked, ${history.completed.length} completed).`, - history, - latestFailedAttempt, - failedAttemptGroups, - ...(parkedCount > 0 - ? { - warning: - "Stored non-completed sessions are parked/inactive snapshots. Activate a stored session before continuing it; direct work outside Flow will not update its runtime records.", - } - : {}), - nextCommand, - }), - metadata, - }; -} - export async function storedSessionResponse( sessionId: string, found: NonNullable, @@ -315,26 +191,3 @@ export async function statusResponse( workspace: workspace ?? null, }); } - -export function closeSessionResponse( - completed: CompletedSessionRecord, - nextCommand: string, -) { - const operator = deriveSessionOperatorState(null); - return toJson({ - status: "ok", - summary: completed - ? `Closed the active Flow session as ${completed.closureKind}.` - : "No active Flow session existed.", - operator, - phase: operator.phase, - lane: operator.lane, - laneReason: operator.laneReason, - blocker: operator.blocker, - reason: operator.reason, - completedSessionId: completed?.sessionId ?? null, - completedTo: completed?.completedTo ?? null, - closureKind: completed?.closureKind ?? null, - nextCommand, - }); -} diff --git a/src/runtime/application/session-workspace-actions.ts b/src/runtime/application/session-workspace-actions.ts index 93540bf..74e74df 100644 --- a/src/runtime/application/session-workspace-actions.ts +++ b/src/runtime/application/session-workspace-actions.ts @@ -239,9 +239,7 @@ export const SESSION_WORKSPACE_ACTION_HANDLERS: SessionWorkspaceActionHandlerMap }, }; -export function buildSessionWorkspaceAction< - Name extends SessionWorkspaceActionName, ->( +function buildSessionWorkspaceAction( name: Name, payload: SessionWorkspacePayloadMap[Name], ): SessionWorkspaceAction { diff --git a/src/runtime/application/stack-standards-profile-helpers.ts b/src/runtime/application/stack-standards-profile-helpers.ts index ea973c3..d8fd201 100644 --- a/src/runtime/application/stack-standards-profile-helpers.ts +++ b/src/runtime/application/stack-standards-profile-helpers.ts @@ -34,7 +34,7 @@ export function addRule( profile.rules.push({ summary, sourceRefs, priority }); } -export function addGap( +function addGap( profile: StandardsProfile, stackItem: string, reason: string, diff --git a/src/runtime/application/stack-standards-signals-data.ts b/src/runtime/application/stack-standards-signals-data.ts index 1a2a923..a9c0070 100644 --- a/src/runtime/application/stack-standards-signals-data.ts +++ b/src/runtime/application/stack-standards-signals-data.ts @@ -1,17 +1,17 @@ -export type StackSignalBucket = +type StackSignalBucket = | "languages" | "frameworks" | "runtimes" | "packageManagers" | "tools"; -export type ConfigSignal = { +type ConfigSignal = { file: string; bucket: StackSignalBucket; name: string; }; -export type TextSignal = { +type TextSignal = { file: string; pattern: RegExp; bucket: StackSignalBucket; @@ -47,7 +47,7 @@ export const GUIDELINE_FILES = [ "Directory.Build.props", ] as const; -export const PACKAGE_MANAGER_EVIDENCE_FILES = [ +const PACKAGE_MANAGER_EVIDENCE_FILES = [ "package.json", "package-lock.json", "npm-shrinkwrap.json", diff --git a/src/runtime/application/workspace-boundaries.ts b/src/runtime/application/workspace-boundaries.ts index dc1a21d..420deac 100644 --- a/src/runtime/application/workspace-boundaries.ts +++ b/src/runtime/application/workspace-boundaries.ts @@ -38,10 +38,7 @@ export function resolveWorkspaceStartDirectory( : resolvedRoot; } -export function isWithinWorkspaceRoot( - root: string, - candidate: string, -): boolean { +function isWithinWorkspaceRoot(root: string, candidate: string): boolean { const pathFromRoot = relative(root, candidate); return ( pathFromRoot === "" || diff --git a/src/runtime/constants.ts b/src/runtime/constants.ts index b682aa0..6babd26 100644 --- a/src/runtime/constants.ts +++ b/src/runtime/constants.ts @@ -5,8 +5,8 @@ export const FLOW_AUTO_WITH_GOAL_COMMAND = "/flow-auto "; export const FLOW_AUTO_RESUME_COMMAND = "/flow-auto resume"; export const FLOW_STATUS_COMMAND = "/flow-status"; export const FLOW_HISTORY_COMMAND = "/flow-history"; -export const FLOW_RESET_FEATURE_COMMAND = "/flow-reset feature"; -export const FLOW_SESSION_ACTIVATE_COMMAND = "/flow-session activate"; +const FLOW_RESET_FEATURE_COMMAND = "/flow-reset feature"; +const FLOW_SESSION_ACTIVATE_COMMAND = "/flow-session activate"; export const CANONICAL_RUNTIME_TOOL_NAMES = [ "flow_review_record_feature", diff --git a/src/runtime/domain/final-review-behavior-ledger-validation.ts b/src/runtime/domain/final-review-behavior-ledger-validation.ts index 203ca70..c69a435 100644 --- a/src/runtime/domain/final-review-behavior-ledger-validation.ts +++ b/src/runtime/domain/final-review-behavior-ledger-validation.ts @@ -4,9 +4,8 @@ import type { FinalReviewBehaviorRiskClass, FinalReviewValidationCoverage, } from "./final-review-behavior-risks"; -import { normalizeBehaviorRiskClassName } from "./final-review-canonicalization"; -export type BehaviorValidationLedgerTarget = Pick< +type BehaviorValidationLedgerTarget = Pick< FinalReviewBehaviorCoverageTarget, | "evidenceRefs" | "remainingGaps" @@ -54,12 +53,11 @@ function duplicateRiskClasses(riskClasses: readonly string[]): string[] { const seen = new Set(); const duplicates = new Set(); for (const riskClass of riskClasses) { - const normalized = normalizeBehaviorRiskClassName(riskClass); - if (seen.has(normalized)) { - duplicates.add(normalized); + if (seen.has(riskClass)) { + duplicates.add(riskClass); continue; } - seen.add(normalized); + seen.add(riskClass); } return [...duplicates]; } diff --git a/src/runtime/domain/final-review-behavior-risks.ts b/src/runtime/domain/final-review-behavior-risks.ts index 3b958d7..524acc5 100644 --- a/src/runtime/domain/final-review-behavior-risks.ts +++ b/src/runtime/domain/final-review-behavior-risks.ts @@ -1,5 +1,4 @@ import { - type BehaviorValidationLedgerTarget, behaviorRefGroundingFailureReasons, behaviorValidationLedgerFailureReasons, genericAppDomainForPath, @@ -14,7 +13,7 @@ import { isTestPath } from "./final-review-coverage-paths"; import type { ReviewContextPack } from "./review-content-discovery"; import { describeReviewContextPackGroundingFailure } from "./review-context-grounding"; -export const FINAL_REVIEW_BEHAVIOR_RISK_CLASSES = [ +const FINAL_REVIEW_BEHAVIOR_RISK_CLASSES = [ "async_event_ordering", "lifecycle_reentrancy", "state_commit_rollback", @@ -68,7 +67,6 @@ export type FinalReviewBehaviorCoverageTarget = { reviewContextPack?: ReviewContextPack | undefined; }; -export type { BehaviorValidationLedgerTarget }; export { behaviorValidationLedgerFailureReasons }; function addRequired( @@ -80,7 +78,7 @@ function addRequired( } } -export function deriveRequiredFinalReviewBehaviorRisks( +function deriveRequiredFinalReviewBehaviorRisks( worker: FinalReviewWorkerEvidence, review: Pick< FinalReviewBehaviorCoverageTarget, diff --git a/src/runtime/domain/final-review-behavior-validation.ts b/src/runtime/domain/final-review-behavior-validation.ts index cc0ae92..e2c083e 100644 --- a/src/runtime/domain/final-review-behavior-validation.ts +++ b/src/runtime/domain/final-review-behavior-validation.ts @@ -1,7 +1,4 @@ -import { - type BehaviorValidationLedgerTarget, - behaviorValidationLedgerFailureReasons, -} from "./final-review-behavior-ledger-validation"; +import { behaviorValidationLedgerFailureReasons } from "./final-review-behavior-ledger-validation"; import type { FinalReviewBehaviorCoverageTarget } from "./final-review-behavior-risks"; import { artifactPathsForWorker, @@ -15,7 +12,6 @@ import { } from "./final-review-coverage-paths"; import type { ReviewContextPack } from "./review-content-discovery"; -export type { BehaviorValidationLedgerTarget }; export { behaviorValidationLedgerFailureReasons }; const FLOW_INFRASTRUCTURE_SRC_DOMAINS = new Set([ @@ -92,7 +88,7 @@ function concreteDeclaredReviewScopePath( return target; } -export function declaredReviewScopePaths( +function declaredReviewScopePaths( review: Pick, ): string[] { return (review.declaredReviewScope ?? []) diff --git a/src/runtime/domain/final-review-canonicalization.ts b/src/runtime/domain/final-review-canonicalization.ts deleted file mode 100644 index 192156b..0000000 --- a/src/runtime/domain/final-review-canonicalization.ts +++ /dev/null @@ -1,30 +0,0 @@ -export type TestEvidenceRefsCompatInput = { - testEvidenceRefs?: string[] | undefined; - oracleRefs?: string[] | undefined; -}; - -export function canonicalTestEvidenceRefs( - input: TestEvidenceRefsCompatInput, -): string[] { - return input.testEvidenceRefs ?? input.oracleRefs ?? []; -} - -export function canonicalizeTestEvidenceRefs< - T extends TestEvidenceRefsCompatInput, ->( - value: T, -): Omit & { testEvidenceRefs: string[] } { - const { oracleRefs, testEvidenceRefs, ...rest } = value; - return { - ...rest, - testEvidenceRefs: testEvidenceRefs ?? oracleRefs ?? [], - }; -} - -export function normalizeBehaviorRiskClassName( - riskClass: T, -): T | "test_evidence_authenticity" { - return riskClass === "test_oracle_authenticity" - ? "test_evidence_authenticity" - : riskClass; -} diff --git a/src/runtime/domain/final-review-coverage.ts b/src/runtime/domain/final-review-coverage.ts index fd5dd45..eefbc18 100644 --- a/src/runtime/domain/final-review-coverage.ts +++ b/src/runtime/domain/final-review-coverage.ts @@ -15,6 +15,13 @@ import { validationCommandsForWorker, } from "./final-review-coverage-evidence"; import { normalizeArtifactPath } from "./final-review-coverage-paths"; + +export { + type DetailedFinalReviewRequirementFailure, + detailedFinalReviewRequirementFailures, +} from "./final-review-detailed-requirements"; + +import { detailedFinalReviewRequirementFailureMessages } from "./final-review-detailed-requirements"; import { describeReviewContextPackGroundingFailure, type ReviewContextPack, @@ -26,18 +33,7 @@ import { } from "./review-scope-accounting"; import { finalReviewPolicyForPlan } from "./workflow-policy"; -export type { FinalReviewSurface } from "./final-review-coverage-evidence"; -export type { - ReviewContextPack, - ReviewContextPackInput, - ReviewContextRelationship, - ReviewDiscoveryReason, - ReviewDiscoverySurface, - ReviewIncludedContext, - ReviewValidationEvidence, -} from "./review-content-discovery"; - -export type FinalReviewCoverageTarget = { +type FinalReviewCoverageTarget = { reviewDepth: string; reviewedSurfaces: string[]; evidenceSummary?: string | undefined; @@ -57,18 +53,6 @@ export type FinalReviewCoverageTarget = { reviewContextPack?: ReviewContextPack | undefined; }; -export type DetailedFinalReviewRequirementFailure = - | "too_few_surfaces" - | "missing_validation_evidence" - | "missing_cross_feature_surface" - | "missing_integration_checks" - | "missing_regression_checks"; - -type DetailedFinalReviewTarget = Pick< - FinalReviewCoverageTarget, - "reviewDepth" | "reviewedSurfaces" | "integrationChecks" | "regressionChecks" ->; - export function finalReviewDepthMatchesPolicy( session: Session, reviewDepth: string | undefined, @@ -76,14 +60,6 @@ export function finalReviewDepthMatchesPolicy( return reviewDepth === finalReviewPolicyForPlan(session.plan); } -const DETAILED_FINAL_REVIEW_CROSS_FEATURE_SURFACES: readonly FinalReviewSurface[] = - [ - "integration_points", - "shared_surfaces", - "tooling_and_config", - "release_surface", - ] as const; - export function isKnownFinalReviewSurface( surface: string, ): surface is FinalReviewSurface { @@ -92,39 +68,6 @@ export function isKnownFinalReviewSurface( ); } -export function detailedFinalReviewRequirementFailures( - review: DetailedFinalReviewTarget, -): DetailedFinalReviewRequirementFailure[] { - if (review.reviewDepth !== "detailed") { - return []; - } - - const failures: DetailedFinalReviewRequirementFailure[] = []; - const reviewedSurfaceSet = new Set(review.reviewedSurfaces); - - if (review.reviewedSurfaces.length < 2) { - failures.push("too_few_surfaces"); - } - if (!reviewedSurfaceSet.has("validation_evidence")) { - failures.push("missing_validation_evidence"); - } - if ( - !DETAILED_FINAL_REVIEW_CROSS_FEATURE_SURFACES.some((surface) => - reviewedSurfaceSet.has(surface), - ) - ) { - failures.push("missing_cross_feature_surface"); - } - if (!review.integrationChecks?.length) { - failures.push("missing_integration_checks"); - } - if (!review.regressionChecks?.length) { - failures.push("missing_regression_checks"); - } - - return failures; -} - function finalReviewCoverageFailureReasons( session: Session, worker: FinalReviewWorkerEvidence, @@ -227,20 +170,7 @@ function finalReviewCoverageFailureReasons( ); } - const detailedFailureReasonMessages: Record< - DetailedFinalReviewRequirementFailure, - string - > = { - too_few_surfaces: "must cover at least two reviewedSurfaces", - missing_validation_evidence: "must include validation_evidence", - missing_cross_feature_surface: - "must include at least one cross-feature surface", - missing_integration_checks: "must include integrationChecks", - missing_regression_checks: "must include regressionChecks", - }; - for (const failure of detailedFinalReviewRequirementFailures(review)) { - reasons.push(detailedFailureReasonMessages[failure]); - } + reasons.push(...detailedFinalReviewRequirementFailureMessages(review)); const behaviorCoverageTarget = { ...review, diff --git a/src/runtime/domain/final-review-detailed-requirements.ts b/src/runtime/domain/final-review-detailed-requirements.ts new file mode 100644 index 0000000..1569e11 --- /dev/null +++ b/src/runtime/domain/final-review-detailed-requirements.ts @@ -0,0 +1,80 @@ +import type { FinalReviewSurface } from "./final-review-coverage-evidence"; + +export type DetailedFinalReviewRequirementFailure = + | "too_few_surfaces" + | "missing_validation_evidence" + | "missing_cross_feature_surface" + | "missing_integration_checks" + | "missing_regression_checks"; + +type DetailedFinalReviewTarget = { + reviewDepth: string; + reviewedSurfaces: string[]; + integrationChecks?: string[] | undefined; + regressionChecks?: string[] | undefined; +}; + +const DETAILED_FINAL_REVIEW_CROSS_FEATURE_SURFACES: readonly FinalReviewSurface[] = + [ + "integration_points", + "shared_surfaces", + "tooling_and_config", + "release_surface", + ] as const; + +const DETAILED_FINAL_REVIEW_FAILURE_MESSAGES: Record< + DetailedFinalReviewRequirementFailure, + string +> = { + too_few_surfaces: "must cover at least two reviewedSurfaces", + missing_validation_evidence: "must include validation_evidence", + missing_cross_feature_surface: + "must include at least one cross-feature surface", + missing_integration_checks: "must include integrationChecks", + missing_regression_checks: "must include regressionChecks", +}; + +function hasMeaningfulEntry(values: readonly string[] | undefined): boolean { + return values?.some((value) => value.trim().length > 0) ?? false; +} + +export function detailedFinalReviewRequirementFailures( + review: DetailedFinalReviewTarget, +): DetailedFinalReviewRequirementFailure[] { + if (review.reviewDepth !== "detailed") { + return []; + } + + const failures: DetailedFinalReviewRequirementFailure[] = []; + const reviewedSurfaceSet = new Set(review.reviewedSurfaces); + + if (review.reviewedSurfaces.length < 2) { + failures.push("too_few_surfaces"); + } + if (!reviewedSurfaceSet.has("validation_evidence")) { + failures.push("missing_validation_evidence"); + } + if ( + !DETAILED_FINAL_REVIEW_CROSS_FEATURE_SURFACES.some((surface) => + reviewedSurfaceSet.has(surface), + ) + ) { + failures.push("missing_cross_feature_surface"); + } + if (!hasMeaningfulEntry(review.integrationChecks)) { + failures.push("missing_integration_checks"); + } + if (!hasMeaningfulEntry(review.regressionChecks)) { + failures.push("missing_regression_checks"); + } + + return failures; +} + +export function detailedFinalReviewRequirementFailureMessages( + review: DetailedFinalReviewTarget, +): string[] { + return detailedFinalReviewRequirementFailures(review).map( + (failure) => DETAILED_FINAL_REVIEW_FAILURE_MESSAGES[failure], + ); +} diff --git a/src/runtime/domain/index.ts b/src/runtime/domain/index.ts index 0165e26..43e8777 100644 --- a/src/runtime/domain/index.ts +++ b/src/runtime/domain/index.ts @@ -1,33 +1,9 @@ export { featureWouldReachCompletion, summarizeCompletion } from "./completion"; -export { - deriveRequiredFinalReviewBehaviorRisks, - FINAL_REVIEW_BEHAVIOR_RISK_CLASSES, - type FinalReviewBehaviorCheck, - type FinalReviewBehaviorRiskClass, - type FinalReviewValidationCoverage, - finalReviewBehaviorCoverageFailureReasons, -} from "./final-review-behavior-risks"; -export { - canonicalizeTestEvidenceRefs, - canonicalTestEvidenceRefs, - normalizeBehaviorRiskClassName, - type TestEvidenceRefsCompatInput, -} from "./final-review-canonicalization"; +export { finalReviewBehaviorCoverageFailureReasons } from "./final-review-behavior-risks"; export { type DetailedFinalReviewRequirementFailure, describeFinalReviewCoverageFailure, - detailedFinalReviewRequirementFailures, - type FinalReviewCoverageTarget, - type FinalReviewSurface, finalReviewDepthMatchesPolicy, - isKnownFinalReviewSurface, - type ReviewContextPack, - type ReviewContextPackInput, - type ReviewContextRelationship, - type ReviewDiscoveryReason, - type ReviewDiscoverySurface, - type ReviewIncludedContext, - type ReviewValidationEvidence, } from "./final-review-coverage"; export { REVIEW_AND_FIX_FINDINGS_REQUIRED_MESSAGE, @@ -37,38 +13,25 @@ export { export { selectProjectedFeatureSubset } from "./plan-projection"; export { describeReviewFindingsMutationFailure, - mergeEvidencePackets, mergePlanningContext, } from "./planning-context"; export { buildReviewContextPack, - deriveReviewContextPackSurfaces, describeReviewContextPackGroundingFailure, - REVIEW_DISCOVERY_REASONS, reviewContextPackHasSurfaceEvidence, - surfacesForReviewDiscoveryReason, } from "./review-content-discovery"; -export { - describeReviewFindingClosureLedgerFailure, - type ReviewFindingClosureEvidence, - type ReviewFindingClosureValidationContext, -} from "./review-finding-closure-policy"; +export { describeReviewFindingClosureLedgerFailure } from "./review-finding-closure-policy"; export { buildFinalReviewerReviewScopeRecoveryDetails, buildReviewScopeRecoveryDetails, closedReviewFindingRefsForCompletion, - declaredReviewScopeForCompletion, - declaredReviewScopeForFeature, - declaredReviewScopeForPlan, describeFinalReviewerReviewScopeFailure, describeReviewScopeLedgerFailure, - isReviewScopeAccountingRequired, validatePlanReviewScopeDeclaration, } from "./review-scope-accounting"; export { buildReviewerDecision, type RecordReviewerDecisionInput, - validateReviewerDecisionInput, validateReviewerDecisionInputDetailed, } from "./reviewer-decision"; export { @@ -80,19 +43,13 @@ export { SEMANTIC_RECOVERY_EXPECTATIONS, SEMANTIC_REVIEW_SCOPE_EXPECTATIONS, SEMANTIC_TOOL_SURFACE_EXPECTATIONS, - type SemanticInvariantDescriptor, type SemanticInvariantId, - type SemanticInvariantOwnerReference, semanticInvariantById, } from "./semantic-invariants"; export { activeDecisionGate, - completedFeatureCount, completionPolicyTargetError, decisionRequiresPause, finalReviewPolicyForPlan, - reviewerPurposeForScope, sessionCompletionReached, - strictReviewGovernanceRequiredForPlan, - targetCompletedFeatureCount, } from "./workflow-policy"; diff --git a/src/runtime/domain/planning-context.ts b/src/runtime/domain/planning-context.ts index 1aa7ed5..386dfed 100644 --- a/src/runtime/domain/planning-context.ts +++ b/src/runtime/domain/planning-context.ts @@ -29,7 +29,7 @@ function mergeUniqueBySerialized( return merged; } -export function mergeEvidencePackets( +function mergeEvidencePackets( current?: readonly EvidencePacket[], next?: readonly EvidencePacket[], ): EvidencePacket[] | undefined { diff --git a/src/runtime/domain/review-content-discovery.ts b/src/runtime/domain/review-content-discovery.ts index 8194e9a..d7d30c4 100644 --- a/src/runtime/domain/review-content-discovery.ts +++ b/src/runtime/domain/review-content-discovery.ts @@ -13,26 +13,14 @@ import { } from "./review-context-normalization"; export { - deriveReviewContextPackSurfaces, describeReviewContextPackGroundingFailure, reviewContextPackHasSurfaceEvidence, - surfacesForReviewDiscoveryReason, } from "./review-context-grounding"; export type { ReviewContextPack, - ReviewContextPackGroundingEvidence, ReviewContextPackInput, - ReviewContextRelationship, - ReviewDiscoveryReason, - ReviewDiscoverySurface, - ReviewIncludedContext, - ReviewIncludedContextInput, - ReviewValidationEvidence, -} from "./review-context-normalization"; -export { - normalizeReviewDiscoveryReason, - REVIEW_DISCOVERY_REASONS, } from "./review-context-normalization"; +export { REVIEW_DISCOVERY_REASONS } from "./review-context-normalization"; export function buildReviewContextPack( input: ReviewContextPackInput, diff --git a/src/runtime/domain/review-context-grounding.ts b/src/runtime/domain/review-context-grounding.ts index f864e80..3634815 100644 --- a/src/runtime/domain/review-context-grounding.ts +++ b/src/runtime/domain/review-context-grounding.ts @@ -38,7 +38,7 @@ const REVIEW_DISCOVERY_REASON_SURFACES: Record< validation_evidence: ["validation_evidence"], }; -export function surfacesForReviewDiscoveryReason( +function surfacesForReviewDiscoveryReason( reason: ReviewDiscoveryReason, ): ReviewDiscoverySurface[] { return [...REVIEW_DISCOVERY_REASON_SURFACES[reason]]; diff --git a/src/runtime/domain/review-context-normalization.ts b/src/runtime/domain/review-context-normalization.ts index b77e938..1dd5d3b 100644 --- a/src/runtime/domain/review-context-normalization.ts +++ b/src/runtime/domain/review-context-normalization.ts @@ -73,12 +73,11 @@ export type ReviewContextPackGroundingEvidence = { validationCommands?: readonly string[] | undefined; }; -export function normalizeReviewDiscoveryReason( +function normalizeReviewDiscoveryReason( reason: string, ): ReviewDiscoveryReason | null { - const normalized = reason === "test_oracle" ? "test_evidence" : reason; - return REVIEW_DISCOVERY_REASONS.includes(normalized as ReviewDiscoveryReason) - ? (normalized as ReviewDiscoveryReason) + return REVIEW_DISCOVERY_REASONS.includes(reason as ReviewDiscoveryReason) + ? (reason as ReviewDiscoveryReason) : null; } diff --git a/src/runtime/domain/review-finding-closure-policy.ts b/src/runtime/domain/review-finding-closure-policy.ts index 7e2e411..d893d25 100644 --- a/src/runtime/domain/review-finding-closure-policy.ts +++ b/src/runtime/domain/review-finding-closure-policy.ts @@ -1,4 +1,4 @@ -export type ReviewFindingClosureEvidence = { +type ReviewFindingClosureEvidence = { findingRef: string; status: string; fixRefs?: readonly string[]; @@ -6,7 +6,7 @@ export type ReviewFindingClosureEvidence = { validationRefs?: readonly string[]; }; -export type ReviewFindingClosureValidationContext = { +type ReviewFindingClosureValidationContext = { plannedFindingRefs?: readonly string[]; closedFindingRefsForCompletion?: readonly string[]; validationCommands: readonly string[]; diff --git a/src/runtime/domain/review-scope-accounting.ts b/src/runtime/domain/review-scope-accounting.ts index e54422e..c71b5e8 100644 --- a/src/runtime/domain/review-scope-accounting.ts +++ b/src/runtime/domain/review-scope-accounting.ts @@ -9,11 +9,7 @@ export { type ReviewScopeRecoveryDetails, } from "./review-scope-recovery"; export { - declaredReviewScopeForCompletion, - declaredReviewScopeForFeature, declaredReviewScopeForPlan, isReviewScopeAccountingRequired, - type ReviewScopeLedgerEntry, - type ReviewScopeTarget, validatePlanReviewScopeDeclaration, } from "./review-scope-targets"; diff --git a/src/runtime/domain/review-scope-completion-accounting.ts b/src/runtime/domain/review-scope-completion-accounting.ts index 84cb2be..dd2e742 100644 --- a/src/runtime/domain/review-scope-completion-accounting.ts +++ b/src/runtime/domain/review-scope-completion-accounting.ts @@ -17,7 +17,7 @@ export function closedFindingRefsFor( .map((closure) => closure.findingRef); } -export function latestValidCompletedHistoryEntries( +function latestValidCompletedHistoryEntries( session: Session, ): Session["execution"]["history"] { const completedFeatureIds = new Set( diff --git a/src/runtime/domain/review-scope-ledger-entry-validation.ts b/src/runtime/domain/review-scope-ledger-entry-validation.ts index 3c4f809..ef8a776 100644 --- a/src/runtime/domain/review-scope-ledger-entry-validation.ts +++ b/src/runtime/domain/review-scope-ledger-entry-validation.ts @@ -15,7 +15,7 @@ import { type ReviewScopeTarget, } from "./review-scope-targets"; -export type LedgerValidationContext = { +type LedgerValidationContext = { declaredScope: readonly ReviewScopeTarget[]; ledger: readonly ReviewScopeLedgerEntry[]; validationCommands?: readonly string[]; diff --git a/src/runtime/domain/reviewer-decision-normalization.ts b/src/runtime/domain/reviewer-decision-normalization.ts index 3e8cf84..3aafd55 100644 --- a/src/runtime/domain/reviewer-decision-normalization.ts +++ b/src/runtime/domain/reviewer-decision-normalization.ts @@ -6,10 +6,6 @@ import type { FinalReviewBehaviorRiskClass, FinalReviewValidationCoverage, } from "./final-review-behavior-risks"; -import { - canonicalTestEvidenceRefs, - normalizeBehaviorRiskClassName, -} from "./final-review-canonicalization"; import { buildReviewContextPack, type ReviewContextPack, @@ -27,7 +23,6 @@ export type BehaviorCheckInput = { lifecycleOwnerRefs?: string[] | undefined; failurePath: string; testEvidenceRefs?: string[] | undefined; - oracleRefs?: string[] | undefined; validationRefs?: string[] | undefined; remainingGap?: string | undefined; }; @@ -38,7 +33,6 @@ export type ValidationCoverageInput = { proves?: string[] | undefined; gaps?: string[] | undefined; testEvidenceRefs?: string[] | undefined; - oracleRefs?: string[] | undefined; }; export type RecordReviewerDecisionInput = { @@ -72,12 +66,10 @@ export type RecordReviewerDecisionInput = { suggestedValidation?: ReviewerDecision["suggestedValidation"]; }; -export function normalizeBehaviorRiskClass( +function normalizeBehaviorRiskClass( riskClass: string, ): FinalReviewBehaviorRiskClass { - return normalizeBehaviorRiskClassName( - riskClass, - ) as FinalReviewBehaviorRiskClass; + return riskClass as FinalReviewBehaviorRiskClass; } export function normalizeBehaviorChecksForCoverage( @@ -91,7 +83,7 @@ export function normalizeBehaviorChecksForCoverage( stateOwnerRefs: check.stateOwnerRefs ?? [], lifecycleOwnerRefs: check.lifecycleOwnerRefs ?? [], failurePath: check.failurePath, - testEvidenceRefs: canonicalTestEvidenceRefs(check), + testEvidenceRefs: check.testEvidenceRefs ?? [], validationRefs: check.validationRefs ?? [], ...(check.remainingGap ? { remainingGap: check.remainingGap } : {}), })); @@ -107,7 +99,7 @@ export function normalizeValidationCoverageForCoverage( ), proves: item.proves ?? [], gaps: item.gaps ?? [], - testEvidenceRefs: canonicalTestEvidenceRefs(item), + testEvidenceRefs: item.testEvidenceRefs ?? [], })); } diff --git a/src/runtime/domain/reviewer-decision.ts b/src/runtime/domain/reviewer-decision.ts index 941a889..0c89bb1 100644 --- a/src/runtime/domain/reviewer-decision.ts +++ b/src/runtime/domain/reviewer-decision.ts @@ -17,12 +17,12 @@ export type { RecordReviewerDecisionInput } from "./reviewer-decision-normalizat type FinalScopeReviewerDecision = Extract; -export type ReviewerDecisionValidationFailureKind = +type ReviewerDecisionValidationFailureKind = | "shape" | "final_review_coverage" | "final_review_scope_accounting"; -export type ReviewerDecisionValidationFailure = { +type ReviewerDecisionValidationFailure = { kind: ReviewerDecisionValidationFailureKind; message: string; }; @@ -104,13 +104,6 @@ export function validateReviewerDecisionInputDetailed( return null; } -export function validateReviewerDecisionInput( - session: Session, - input: RecordReviewerDecisionInput, -): string | null { - return validateReviewerDecisionInputDetailed(session, input)?.message ?? null; -} - export function buildReviewerDecision( input: RecordReviewerDecisionInput, ): ReviewerDecision { diff --git a/src/runtime/domain/semantic-invariants.ts b/src/runtime/domain/semantic-invariants.ts index e198538..8216fea 100644 --- a/src/runtime/domain/semantic-invariants.ts +++ b/src/runtime/domain/semantic-invariants.ts @@ -5,12 +5,12 @@ import type { CompletionRecoveryKind } from "../transitions/recovery"; export type { SemanticInvariantId }; -export type SemanticInvariantOwnerReference = { +type SemanticInvariantOwnerReference = { file: string; symbols: readonly string[]; }; -export type SemanticInvariantDescriptor = { +type SemanticInvariantDescriptor = { id: SemanticInvariantId; ownerSummary: string; ownerReferences: readonly SemanticInvariantOwnerReference[]; @@ -83,13 +83,13 @@ const SEMANTIC_INVARIANT_REGISTRY = { "review.scope.payload_binding": { id: "review.scope.payload_binding", ownerSummary: - "src/runtime/schema.ts::FlowReviewRecordFeatureArgsSchema/FlowReviewRecordFinalArgsSchema", + "src/runtime/schema.ts::FlowReviewRecordFeatureArgsSchema/FinalReviewerDecisionSchema", ownerReferences: [ { file: "src/runtime/schema.ts", symbols: [ "FlowReviewRecordFeatureArgsSchema", - "FlowReviewRecordFinalArgsSchema", + "FinalReviewerDecisionSchema", ], }, ], diff --git a/src/runtime/feature-doc-drilldown.ts b/src/runtime/feature-doc-drilldown.ts index 38fe956..9208ecc 100644 --- a/src/runtime/feature-doc-drilldown.ts +++ b/src/runtime/feature-doc-drilldown.ts @@ -61,7 +61,7 @@ export type FeatureDocDrilldownSource = sessionId?: string; }; -export type FeatureDocDrilldownInput = { +type FeatureDocDrilldownInput = { featureId: string; source: FeatureDocDrilldownSource; }; diff --git a/src/runtime/json/json-like.ts b/src/runtime/json/json-like.ts index 0561224..e7f14f2 100644 --- a/src/runtime/json/json-like.ts +++ b/src/runtime/json/json-like.ts @@ -33,9 +33,7 @@ export async function readJsonLike(path: string): Promise { } } -export function stripJsonCommentsAndTrailingCommas( - contents: string, -): string | null { +function stripJsonCommentsAndTrailingCommas(contents: string): string | null { let output = ""; let inString = false; let quote: '"' | "'" | null = null; diff --git a/src/runtime/json/strict-object.ts b/src/runtime/json/strict-object.ts index 10a5ef0..a2fe3fa 100644 --- a/src/runtime/json/strict-object.ts +++ b/src/runtime/json/strict-object.ts @@ -1,6 +1,6 @@ type JsonObject = Record; -export type StrictJsonObjectParseErrorKind = +type StrictJsonObjectParseErrorKind = | "empty_payload" | "invalid_json_syntax" | "duplicate_json_key" @@ -8,7 +8,7 @@ export type StrictJsonObjectParseErrorKind = | "trailing_text" | "schema_validation_failed"; -export type StrictJsonObjectParseResult = +type StrictJsonObjectParseResult = | { ok: true; value: JsonObject } | { ok: false; diff --git a/src/runtime/lifecycle/index.ts b/src/runtime/lifecycle/index.ts index 8176590..82f3225 100644 --- a/src/runtime/lifecycle/index.ts +++ b/src/runtime/lifecycle/index.ts @@ -1,8 +1,3 @@ -export type { - CompletedSessionHistoryEntry, - SessionHistoryEntry, - StoredSessionLookup, -} from "../session-history"; export { listSessionHistory, loadStoredSession } from "../session-history"; export { activateSession, @@ -20,14 +15,10 @@ export { } from "../session-persistence"; export { ensureWorkspace, - findStoredSessionDir, readActiveSessionId, - resolveActiveSessionId, writeSessionFile, } from "../session-workspace"; export { - readSessionFromPath, resetSessionWorkspaceFsForTests, setSessionWorkspaceFsForTests, - writeSessionFileAtDir, } from "../session-workspace-io"; diff --git a/src/runtime/paths.ts b/src/runtime/paths.ts index af9370d..eac5f4f 100644 --- a/src/runtime/paths.ts +++ b/src/runtime/paths.ts @@ -1,4 +1,4 @@ -import { dirname, join, relative } from "node:path"; +import { join, relative } from "node:path"; export class InvalidFlowPathInputError extends Error { readonly code = "INVALID_FLOW_PATH_INPUT"; @@ -22,7 +22,7 @@ export class InvalidFlowPathInputError extends Error { export type LiveSessionLocation = "active" | "stored"; -export function sanitizePathComponent( +function sanitizePathComponent( kind: | "session" | "feature" @@ -79,10 +79,6 @@ export function getCompletedSessionsDir(worktree: string): string { return join(getFlowDir(worktree), "completed"); } -export function getPersistenceLocksDir(worktree: string): string { - return join(getFlowDir(worktree), "locks"); -} - function getLiveSessionsDir( worktree: string, location: LiveSessionLocation, @@ -165,7 +161,7 @@ export function getDocsDir( return getDocsDirFromSessionDir(getSessionDir(worktree, sessionId, location)); } -export function getDocsDirFromSessionDir(sessionDir: string): string { +function getDocsDirFromSessionDir(sessionDir: string): string { return join(sessionDir, "docs"); } @@ -183,7 +179,7 @@ export function getReviewsDir( ); } -export function getReviewsDirFromSessionDir(sessionDir: string): string { +function getReviewsDirFromSessionDir(sessionDir: string): string { return join(sessionDir, "reviews"); } @@ -213,13 +209,6 @@ export function getFeatureDocPath( ); } -export function getFeatureDocPathFromSessionPath( - sessionPath: string, - featureId: string, -): string { - return getFeatureDocPathFromSessionDir(dirname(sessionPath), featureId); -} - export function getFeatureDocPathFromSessionDir( sessionDir: string, featureId: string, diff --git a/src/runtime/recovery/index.ts b/src/runtime/recovery/index.ts index f5f4e5f..db7cf50 100644 --- a/src/runtime/recovery/index.ts +++ b/src/runtime/recovery/index.ts @@ -1,19 +1,8 @@ export { - allocateCompletedSessionLocation, - buildCompletedSessionLocation, - type CompletedSessionLocation, compareCompletedDescending, - completedDirectoryName, - completedTimestampForSession, findNewestCompletedSession, - moveSessionDirToCompleted, parseCompletedDirectoryName, - pathExists, } from "../session-completed-storage"; -export { - buildCompletionRecovery, - type CompletionRecoveryKind, -} from "../transitions/recovery"; export { type ClosedSessionResult, closeActiveSession, diff --git a/src/runtime/render-sections-shared.ts b/src/runtime/render-sections-shared.ts index 3e30ff1..7f1c936 100644 --- a/src/runtime/render-sections-shared.ts +++ b/src/runtime/render-sections-shared.ts @@ -57,7 +57,7 @@ export function maybeQuotedSection( return `## ${title}\n\n${toQuotedBlock(value)}`; } -export function formatFollowUpLine(item: { +function formatFollowUpLine(item: { summary: string; severity?: string | undefined; }): string { diff --git a/src/runtime/rendering/index.ts b/src/runtime/rendering/index.ts index 96bdd75..5971383 100644 --- a/src/runtime/rendering/index.ts +++ b/src/runtime/rendering/index.ts @@ -3,5 +3,3 @@ export { renderSessionDocs, renderSessionDocsAtDir, } from "../render"; -export { renderFeatureDoc } from "../render-feature-sections"; -export { renderIndexDoc } from "../render-index-sections"; diff --git a/src/runtime/schema-evidence-packets.ts b/src/runtime/schema-evidence-packets.ts index cc16c2f..2c746e0 100644 --- a/src/runtime/schema-evidence-packets.ts +++ b/src/runtime/schema-evidence-packets.ts @@ -1,18 +1,18 @@ import { z } from "zod"; import { VALIDATION_STATUSES } from "./constants"; -export const EvidencePacketValidationStatusSchema = z.enum([ +const EvidencePacketValidationStatusSchema = z.enum([ ...VALIDATION_STATUSES, "not_run", ] as const); -export const EvidencePacketValidationRunSchema = z.object({ +const EvidencePacketValidationRunSchema = z.object({ command: z.string().min(1), status: EvidencePacketValidationStatusSchema, summary: z.string().min(1), }); -export const EvidencePacketPurposeSchema = z.enum([ +const EvidencePacketPurposeSchema = z.enum([ "planning", "review", "audit", @@ -20,14 +20,14 @@ export const EvidencePacketPurposeSchema = z.enum([ "general", ]); -export const FLOW_CONTEXT_PRODUCER_LANES = [ +const FLOW_CONTEXT_PRODUCER_LANES = [ "planning", "auto_planning", "execution", "review", ] as const; -export const FLOW_CONTEXT_CONSUMER_LANES = [ +const FLOW_CONTEXT_CONSUMER_LANES = [ "status", "history", "session", @@ -36,12 +36,12 @@ export const FLOW_CONTEXT_CONSUMER_LANES = [ "control", ] as const; -export const FLOW_CONTEXT_LANES = [ +const FLOW_CONTEXT_LANES = [ ...FLOW_CONTEXT_PRODUCER_LANES, ...FLOW_CONTEXT_CONSUMER_LANES, ] as const; -export const FlowContextLaneSchema = z.enum(FLOW_CONTEXT_LANES); +const FlowContextLaneSchema = z.enum(FLOW_CONTEXT_LANES); export const EvidencePacketSchema = z .object({ @@ -66,7 +66,7 @@ export const EvidencePacketSchema = z export const EvidencePacketArraySchema = z.array(EvidencePacketSchema); -export const EvidencePacketReferenceSchema = z +const EvidencePacketReferenceSchema = z .object({ id: z.string().min(1), purpose: EvidencePacketPurposeSchema.optional(), diff --git a/src/runtime/schema-plan.ts b/src/runtime/schema-plan.ts index 633b7ad..0525952 100644 --- a/src/runtime/schema-plan.ts +++ b/src/runtime/schema-plan.ts @@ -20,7 +20,7 @@ import { } from "./schema-planning-profiles"; import { ReviewScopeTargetSchema } from "./schema-review-shared"; -export const FeatureStatusSchema = z.enum([ +const FeatureStatusSchema = z.enum([ "pending", "in_progress", "completed", @@ -34,11 +34,11 @@ export const SessionStatusSchema = z.enum([ "completed", ]); export const ApprovalStatusSchema = z.enum(["pending", "approved"]); -export const GoalModeSchema = z.enum(GOAL_MODES); -export const DecompositionPolicySchema = z.enum(DECOMPOSITION_POLICIES); +const GoalModeSchema = z.enum(GOAL_MODES); +const DecompositionPolicySchema = z.enum(DECOMPOSITION_POLICIES); export const PackageManagerSchema = z.enum(["npm", "pnpm", "yarn", "bun"]); -export const FeatureIdSchema = z +const FeatureIdSchema = z .string() .regex(FEATURE_ID_PATTERN, FEATURE_ID_MESSAGE); @@ -56,11 +56,11 @@ export const FeatureSchema = z.object({ blockedBy: z.array(z.string().min(1)).optional(), }); -export const CompletionPolicySchema = z.object({ +const CompletionPolicySchema = z.object({ minCompletedFeatures: z.number().int().positive().optional(), }); -export const DeliveryPolicySchema = z.object({ +const DeliveryPolicySchema = z.object({ priorityMode: z.enum(PRIORITY_MODES).default("balanced"), stopRule: z.enum(STOP_RULES).default("ship_when_clean"), deferAllowed: z.boolean().default(false), diff --git a/src/runtime/schema-planning-profiles.ts b/src/runtime/schema-planning-profiles.ts index ead9a6d..62d65eb 100644 --- a/src/runtime/schema-planning-profiles.ts +++ b/src/runtime/schema-planning-profiles.ts @@ -1,9 +1,9 @@ import { z } from "zod"; import { DECISION_DOMAINS, DECISION_MODES } from "./constants"; -export const EvidenceConfidenceSchema = z.enum(["low", "medium", "high"]); +const EvidenceConfidenceSchema = z.enum(["low", "medium", "high"]); -export const StackProfileEntrySchema = z +const StackProfileEntrySchema = z .object({ name: z.string().min(1), evidenceRefs: z.array(z.string().min(1)).default([]), @@ -21,7 +21,7 @@ export const StackProfileSchema = z }) .strict(); -export const StandardsSourceSchema = z +const StandardsSourceSchema = z .object({ title: z.string().min(1), sourceType: z.enum(["local", "official", "external"]), @@ -30,7 +30,7 @@ export const StandardsSourceSchema = z }) .strict(); -export const StandardsRuleSchema = z +const StandardsRuleSchema = z .object({ summary: z.string().min(1), sourceRefs: z.array(z.string().min(1)).default([]), @@ -38,7 +38,7 @@ export const StandardsRuleSchema = z }) .strict(); -export const StandardsGapSchema = z +const StandardsGapSchema = z .object({ stackItem: z.string().min(1), reason: z.string().min(1), @@ -63,7 +63,7 @@ export const ImplementationApproachSchema = z.object({ sources: z.array(z.string().min(1)).default([]), }); -export const PlanningDecisionOptionSchema = z.object({ +const PlanningDecisionOptionSchema = z.object({ label: z.string().min(1), tradeoffs: z.array(z.string().min(1)).default([]), }); diff --git a/src/runtime/schema-review-behavior.ts b/src/runtime/schema-review-behavior.ts new file mode 100644 index 0000000..2b9d1bf --- /dev/null +++ b/src/runtime/schema-review-behavior.ts @@ -0,0 +1,197 @@ +import { z } from "zod"; +import { + isSafeReviewArtifactRef, + normalizeReviewArtifactRef, +} from "./domain/final-review-coverage-paths"; +import { REVIEW_DISCOVERY_REASONS } from "./domain/review-content-discovery"; + +const BehaviorRiskClassSchema = z.enum([ + "async_event_ordering", + "lifecycle_reentrancy", + "state_commit_rollback", + "persistence_recovery", + "interaction_geometry", + "accessibility_semantics", + "test_evidence_authenticity", +]); + +export const ReviewDiscoveryReasonSchema = z.enum(REVIEW_DISCOVERY_REASONS); +const NonEmptyTrimmedStringSchema = z.string().trim().min(1); +const SafeReviewRefSchema = NonEmptyTrimmedStringSchema.transform( + normalizeReviewArtifactRef, +).refine(isSafeReviewArtifactRef, { + message: "must be a safe relative path reference", +}); + +const BehaviorCheckResultSchema = z.enum([ + "passed", + "gap_recorded", + "not_applicable", + "needs_fix", +]); + +const behaviorCheckShape = { + riskClass: BehaviorRiskClassSchema, + result: BehaviorCheckResultSchema, + invariant: z.string().min(1), + entrypointRefs: z.array(z.string().min(1)).default([]), + stateOwnerRefs: z.array(z.string().min(1)).default([]), + lifecycleOwnerRefs: z.array(z.string().min(1)).default([]), + failurePath: z.string().min(1), + testEvidenceRefs: z.array(z.string().min(1)).optional(), + validationRefs: z.array(z.string().min(1)).default([]), + remainingGap: z.string().min(1).optional(), +} as const; + +function addGapRecordedRemainingGapIssue( + value: { result: string; remainingGap?: string | undefined }, + context: z.RefinementCtx, +): void { + if (value.result === "gap_recorded" && !value.remainingGap?.trim()) { + context.addIssue({ + code: z.ZodIssueCode.custom, + path: ["remainingGap"], + message: "gap_recorded behavior checks must include remainingGap.", + }); + } +} + +function addBehaviorCheckIssues( + value: { + result: string; + remainingGap?: string | undefined; + }, + context: z.RefinementCtx, +): void { + addGapRecordedRemainingGapIssue(value, context); +} + +function duplicateRiskClasses(riskClasses: readonly string[]): string[] { + const seen = new Set(); + const duplicates = new Set(); + for (const riskClass of riskClasses) { + if (seen.has(riskClass)) { + duplicates.add(riskClass); + continue; + } + seen.add(riskClass); + } + return [...duplicates]; +} + +function addDuplicateBehaviorCheckIssues( + value: readonly { riskClass: string }[], + context: z.RefinementCtx, +): void { + for (const riskClass of duplicateRiskClasses( + value.map((check) => check.riskClass), + )) { + context.addIssue({ + code: z.ZodIssueCode.custom, + message: `behaviorChecks must contain at most one entry per riskClass: ${riskClass}`, + }); + } +} + +function addDuplicateValidationCoverageIssues( + value: readonly { behaviorClasses: readonly string[] }[], + context: z.RefinementCtx, +): void { + for (const [index, item] of value.entries()) { + for (const riskClass of duplicateRiskClasses(item.behaviorClasses)) { + context.addIssue({ + code: z.ZodIssueCode.custom, + path: [index, "behaviorClasses"], + message: `validationCoverage[${index}].behaviorClasses must contain at most one entry per riskClass: ${riskClass}`, + }); + } + } +} + +type CanonicalBehaviorCheck = { + riskClass: z.infer; + result: z.infer; + invariant: string; + entrypointRefs: string[]; + stateOwnerRefs: string[]; + lifecycleOwnerRefs: string[]; + failurePath: string; + testEvidenceRefs: string[]; + validationRefs: string[]; + remainingGap?: string | undefined; +}; + +function canonicalizeTestEvidenceRefs< + T extends { testEvidenceRefs?: string[] | undefined }, +>(value: T): Omit & { testEvidenceRefs: string[] } { + const { testEvidenceRefs, ...rest } = value; + return { + ...rest, + testEvidenceRefs: testEvidenceRefs ?? [], + }; +} + +export const BehaviorCheckSchema = z + .object(behaviorCheckShape) + .strict() + .superRefine(addBehaviorCheckIssues) + .overwrite( + canonicalizeTestEvidenceRefs, + ) as unknown as z.ZodType; + +const runtimeBehaviorCheckShape = { + ...behaviorCheckShape, + entrypointRefs: z.array(SafeReviewRefSchema).default([]), + stateOwnerRefs: z.array(SafeReviewRefSchema).default([]), + lifecycleOwnerRefs: z.array(SafeReviewRefSchema).default([]), + testEvidenceRefs: z.array(SafeReviewRefSchema).optional(), +} as const; + +const RuntimeBehaviorCheckSchema = z + .object(runtimeBehaviorCheckShape) + .strict() + .superRefine(addBehaviorCheckIssues) + .overwrite( + canonicalizeTestEvidenceRefs, + ) as unknown as z.ZodType; + +const validationCoverageShape = { + command: z.string().min(1), + behaviorClasses: z.array(BehaviorRiskClassSchema).default([]), + proves: z.array(z.string().min(1)).default([]), + gaps: z.array(z.string().min(1)).default([]), + testEvidenceRefs: z.array(z.string().min(1)).optional(), +} as const; + +type CanonicalValidationCoverage = { + command: string; + behaviorClasses: z.infer[]; + proves: string[]; + gaps: string[]; + testEvidenceRefs: string[]; +}; + +export const ValidationCoverageSchema = z + .object(validationCoverageShape) + .strict() + .overwrite( + canonicalizeTestEvidenceRefs, + ) as unknown as z.ZodType; + +const RuntimeValidationCoverageSchema = z + .object({ + ...validationCoverageShape, + testEvidenceRefs: z.array(SafeReviewRefSchema).optional(), + }) + .strict() + .overwrite( + canonicalizeTestEvidenceRefs, + ) as unknown as z.ZodType; + +export const RuntimeBehaviorCheckArraySchema = z + .array(RuntimeBehaviorCheckSchema) + .superRefine(addDuplicateBehaviorCheckIssues); + +export const RuntimeValidationCoverageArraySchema = z + .array(RuntimeValidationCoverageSchema) + .superRefine(addDuplicateValidationCoverageIssues); diff --git a/src/runtime/schema-review-decisions.ts b/src/runtime/schema-review-decisions.ts index 730e860..2ef42ec 100644 --- a/src/runtime/schema-review-decisions.ts +++ b/src/runtime/schema-review-decisions.ts @@ -48,7 +48,7 @@ export const PersistedFinalReviewSchema = ReviewSchema.extend({ evidencePackets: EvidencePacketArraySchema.optional(), }); -export const FeatureReviewerDecisionSchema = z.object({ +const FeatureReviewerDecisionSchema = z.object({ scope: z.literal("feature"), featureId: z.string().regex(FEATURE_ID_PATTERN, FEATURE_ID_MESSAGE), reviewPurpose: z.enum(REVIEW_PURPOSES).optional(), @@ -75,7 +75,7 @@ export const FinalReviewerDecisionSchema = z .strict() .superRefine(addApprovedBehaviorDecisionConsistencyChecks); -export const PersistedFinalReviewerDecisionSchema = z +const PersistedFinalReviewerDecisionSchema = z .object({ scope: z.literal("final"), reviewPurpose: z.enum(REVIEW_PURPOSES).optional(), @@ -97,5 +97,3 @@ export const ReviewerDecisionSchema = z.discriminatedUnion("scope", [ export const FlowReviewRecordFeatureArgsSchema = FeatureReviewerDecisionSchema.strict(); - -export const FlowReviewRecordFinalArgsSchema = FinalReviewerDecisionSchema; diff --git a/src/runtime/schema-review-shared.ts b/src/runtime/schema-review-shared.ts index 579c312..0411fb0 100644 --- a/src/runtime/schema-review-shared.ts +++ b/src/runtime/schema-review-shared.ts @@ -7,17 +7,22 @@ import { REVIEW_SCOPE_TARGET_KINDS, REVIEW_STATUSES, } from "./constants"; -import { - canonicalizeTestEvidenceRefs, - normalizeBehaviorRiskClassName, -} from "./domain/final-review-canonicalization"; import { isSafeReviewArtifactPath, - isSafeReviewArtifactRef, normalizeArtifactPath, - normalizeReviewArtifactRef, } from "./domain/final-review-coverage-paths"; -import { REVIEW_DISCOVERY_REASONS } from "./domain/review-content-discovery"; +import { + BehaviorCheckSchema, + ReviewDiscoveryReasonSchema, + RuntimeBehaviorCheckArraySchema, + RuntimeValidationCoverageArraySchema, + ValidationCoverageSchema, +} from "./schema-review-behavior"; + +export { + BehaviorCheckSchema, + ValidationCoverageSchema, +} from "./schema-review-behavior"; export const FollowUpSchema = z.object({ summary: z.string().min(1), @@ -44,7 +49,7 @@ export const ReviewScopeTargetSchema = z }) .strict(); -export const ReviewScopeAccountingStatusSchema = z.enum( +const ReviewScopeAccountingStatusSchema = z.enum( REVIEW_SCOPE_ACCOUNTING_STATUSES, ); @@ -64,17 +69,17 @@ export const ReviewFindingSchema = z.object({ summary: z.string().min(1), }); -export const ReviewDepthSchema = z.enum(FINAL_REVIEW_POLICIES); -export const ReviewSurfaceSchema = z.enum(FINAL_REVIEW_SURFACES); +const ReviewDepthSchema = z.enum(FINAL_REVIEW_POLICIES); +const ReviewSurfaceSchema = z.enum(FINAL_REVIEW_SURFACES); -export const FinalReviewEvidenceRefsInputSchema = z +const FinalReviewEvidenceRefsInputSchema = z .object({ changedArtifacts: z.array(z.string().min(1)), validationCommands: z.array(z.string().min(1)), }) .strict(); -export const FinalReviewEvidenceRefsPersistedSchema = z +const FinalReviewEvidenceRefsPersistedSchema = z .object({ changedArtifacts: z.array(z.string().min(1)).default([]), validationCommands: z.array(z.string().min(1)).default([]), @@ -82,247 +87,14 @@ export const FinalReviewEvidenceRefsPersistedSchema = z .strict() .default({ changedArtifacts: [], validationCommands: [] }); -export const FinalReviewEvidenceRefsSchema = - FinalReviewEvidenceRefsPersistedSchema; - -export const BehaviorRiskClassSchema = z.enum([ - "async_event_ordering", - "lifecycle_reentrancy", - "state_commit_rollback", - "persistence_recovery", - "interaction_geometry", - "accessibility_semantics", - "test_evidence_authenticity", -]); -const ReviewDiscoveryReasonSchema = z.enum(REVIEW_DISCOVERY_REASONS); - const NonEmptyTrimmedStringSchema = z.string().trim().min(1); -const SafeReviewRefSchema = NonEmptyTrimmedStringSchema.transform( - normalizeReviewArtifactRef, -).refine(isSafeReviewArtifactRef, { - message: "must be a safe relative path reference", -}); const SafeReviewPathSchema = NonEmptyTrimmedStringSchema.transform( normalizeArtifactPath, ).refine(isSafeReviewArtifactPath, { message: "must be a safe relative path", }); -const BehaviorRiskClassCompatSchema = z - .enum([...BehaviorRiskClassSchema.options, "test_oracle_authenticity"]) - .overwrite(normalizeBehaviorRiskClassName) as typeof BehaviorRiskClassSchema; - -const ReviewDiscoveryReasonCompatSchema = z - .enum([...ReviewDiscoveryReasonSchema.options, "test_oracle"]) - .overwrite((value) => - value === "test_oracle" ? "test_evidence" : value, - ) as typeof ReviewDiscoveryReasonSchema; - -export const BehaviorCheckResultSchema = z.enum([ - "passed", - "gap_recorded", - "not_applicable", - "needs_fix", -]); - -const behaviorCheckShape = { - riskClass: BehaviorRiskClassCompatSchema, - result: BehaviorCheckResultSchema, - invariant: z.string().min(1), - entrypointRefs: z.array(z.string().min(1)).default([]), - stateOwnerRefs: z.array(z.string().min(1)).default([]), - lifecycleOwnerRefs: z.array(z.string().min(1)).default([]), - failurePath: z.string().min(1), - testEvidenceRefs: z.array(z.string().min(1)).optional(), - oracleRefs: z.array(z.string().min(1)).optional(), - validationRefs: z.array(z.string().min(1)).default([]), - remainingGap: z.string().min(1).optional(), -} as const; - -function addGapRecordedRemainingGapIssue( - value: { result: string; remainingGap?: string | undefined }, - context: z.RefinementCtx, -): void { - if (value.result === "gap_recorded" && !value.remainingGap?.trim()) { - context.addIssue({ - code: z.ZodIssueCode.custom, - path: ["remainingGap"], - message: "gap_recorded behavior checks must include remainingGap.", - }); - } -} - -function arraysMatch( - left: readonly string[], - right: readonly string[], -): boolean { - if (left.length !== right.length) { - return false; - } - return left.every((value, index) => value === right[index]); -} - -function addPriorRefsConflictIssue( - value: { - testEvidenceRefs?: string[] | undefined; - oracleRefs?: string[] | undefined; - }, - context: z.RefinementCtx, -): void { - if ( - value.testEvidenceRefs !== undefined && - value.oracleRefs !== undefined && - !arraysMatch(value.testEvidenceRefs, value.oracleRefs) - ) { - context.addIssue({ - code: z.ZodIssueCode.custom, - path: ["oracleRefs"], - message: - "prior oracleRefs input must match testEvidenceRefs when both are provided.", - }); - } -} - -function addBehaviorCheckIssues( - value: { - result: string; - remainingGap?: string | undefined; - testEvidenceRefs?: string[] | undefined; - oracleRefs?: string[] | undefined; - }, - context: z.RefinementCtx, -): void { - addGapRecordedRemainingGapIssue(value, context); - addPriorRefsConflictIssue(value, context); -} - -function duplicateRiskClasses(riskClasses: readonly string[]): string[] { - const seen = new Set(); - const duplicates = new Set(); - for (const riskClass of riskClasses) { - const normalized = normalizeBehaviorRiskClassName(riskClass); - if (seen.has(normalized)) { - duplicates.add(normalized); - continue; - } - seen.add(normalized); - } - return [...duplicates]; -} - -function addDuplicateBehaviorCheckIssues( - value: readonly { riskClass: string }[], - context: z.RefinementCtx, -): void { - for (const riskClass of duplicateRiskClasses( - value.map((check) => check.riskClass), - )) { - context.addIssue({ - code: z.ZodIssueCode.custom, - message: `behaviorChecks must contain at most one entry per riskClass: ${riskClass}`, - }); - } -} - -function addDuplicateValidationCoverageIssues( - value: readonly { behaviorClasses: readonly string[] }[], - context: z.RefinementCtx, -): void { - for (const [index, item] of value.entries()) { - for (const riskClass of duplicateRiskClasses(item.behaviorClasses)) { - context.addIssue({ - code: z.ZodIssueCode.custom, - path: [index, "behaviorClasses"], - message: `validationCoverage[${index}].behaviorClasses must contain at most one entry per riskClass: ${riskClass}`, - }); - } - } -} - -type CanonicalBehaviorCheck = { - riskClass: z.infer; - result: z.infer; - invariant: string; - entrypointRefs: string[]; - stateOwnerRefs: string[]; - lifecycleOwnerRefs: string[]; - failurePath: string; - testEvidenceRefs: string[]; - validationRefs: string[]; - remainingGap?: string | undefined; -}; - -export const BehaviorCheckSchema = z - .object(behaviorCheckShape) - .strict() - .superRefine(addBehaviorCheckIssues) - .overwrite( - canonicalizeTestEvidenceRefs, - ) as unknown as z.ZodType; - -const runtimeBehaviorCheckShape = { - ...behaviorCheckShape, - entrypointRefs: z.array(SafeReviewRefSchema).default([]), - stateOwnerRefs: z.array(SafeReviewRefSchema).default([]), - lifecycleOwnerRefs: z.array(SafeReviewRefSchema).default([]), - testEvidenceRefs: z.array(SafeReviewRefSchema).optional(), - oracleRefs: z.array(SafeReviewRefSchema).optional(), -} as const; - -export const RuntimeBehaviorCheckSchema = z - .object(runtimeBehaviorCheckShape) - .strict() - .superRefine(addBehaviorCheckIssues) - .overwrite( - canonicalizeTestEvidenceRefs, - ) as unknown as z.ZodType; - -const validationCoverageShape = { - command: z.string().min(1), - behaviorClasses: z.array(BehaviorRiskClassCompatSchema).default([]), - proves: z.array(z.string().min(1)).default([]), - gaps: z.array(z.string().min(1)).default([]), - testEvidenceRefs: z.array(z.string().min(1)).optional(), - oracleRefs: z.array(z.string().min(1)).optional(), -} as const; - -type CanonicalValidationCoverage = { - command: string; - behaviorClasses: z.infer[]; - proves: string[]; - gaps: string[]; - testEvidenceRefs: string[]; -}; - -export const ValidationCoverageSchema = z - .object(validationCoverageShape) - .strict() - .superRefine(addPriorRefsConflictIssue) - .overwrite( - canonicalizeTestEvidenceRefs, - ) as unknown as z.ZodType; - -export const RuntimeValidationCoverageSchema = z - .object({ - ...validationCoverageShape, - testEvidenceRefs: z.array(SafeReviewRefSchema).optional(), - oracleRefs: z.array(SafeReviewRefSchema).optional(), - }) - .strict() - .superRefine(addPriorRefsConflictIssue) - .overwrite( - canonicalizeTestEvidenceRefs, - ) as unknown as z.ZodType; - -const RuntimeBehaviorCheckArraySchema = z - .array(RuntimeBehaviorCheckSchema) - .superRefine(addDuplicateBehaviorCheckIssues); - -const RuntimeValidationCoverageArraySchema = z - .array(RuntimeValidationCoverageSchema) - .superRefine(addDuplicateValidationCoverageIssues); - -export const ReviewContextPackSchema = z +const ReviewContextPackSchema = z .object({ task: NonEmptyTrimmedStringSchema, compareBase: NonEmptyTrimmedStringSchema.optional(), @@ -332,7 +104,7 @@ export const ReviewContextPackSchema = z z .object({ path: SafeReviewPathSchema, - reason: ReviewDiscoveryReasonCompatSchema, + reason: ReviewDiscoveryReasonSchema, surface: ReviewSurfaceSchema.optional(), summary: NonEmptyTrimmedStringSchema.optional(), }) @@ -379,8 +151,8 @@ const finalReviewCommonShape = { reviewedSurfaces: z.array(ReviewSurfaceSchema).default([]), evidenceSummary: z.string().min(1).optional(), validationAssessment: z.string().min(1).optional(), - integrationChecks: z.array(z.string().min(1)).default([]), - regressionChecks: z.array(z.string().min(1)).default([]), + integrationChecks: z.array(NonEmptyTrimmedStringSchema).default([]), + regressionChecks: z.array(NonEmptyTrimmedStringSchema).default([]), remainingGaps: z.array(z.string().min(1)).default([]), suggestedValidation: z.array(z.string().min(1)).optional(), reviewContextPack: ReviewContextPackSchema.optional(), @@ -399,5 +171,3 @@ export const finalReviewPersistedSharedShape = { behaviorChecks: z.array(BehaviorCheckSchema).optional(), validationCoverage: z.array(ValidationCoverageSchema).optional(), } as const; - -export const finalReviewSharedShape = finalReviewPersistedSharedShape; diff --git a/src/runtime/schema-worker-result-shared.ts b/src/runtime/schema-worker-result-shared.ts index 08dc274..842adf9 100644 --- a/src/runtime/schema-worker-result-shared.ts +++ b/src/runtime/schema-worker-result-shared.ts @@ -9,8 +9,8 @@ import { } from "./constants"; import { FollowUpSchema } from "./schema-review-shared"; -export const ValidationStatusSchema = z.enum(VALIDATION_STATUSES); -export const OutcomeKindSchema = z.enum(OUTCOME_KINDS); +const ValidationStatusSchema = z.enum(VALIDATION_STATUSES); +const OutcomeKindSchema = z.enum(OUTCOME_KINDS); export const ArtifactSchema = z.object({ path: z.string().min(1), @@ -27,7 +27,7 @@ export const DecisionSchema = z.object({ summary: z.string().min(1), }); -export const NoteSchema = z.object({ +const NoteSchema = z.object({ note: z.string().min(1), }); diff --git a/src/runtime/schema-worker-result.ts b/src/runtime/schema-worker-result.ts index 3a28945..7de0b03 100644 --- a/src/runtime/schema-worker-result.ts +++ b/src/runtime/schema-worker-result.ts @@ -71,12 +71,12 @@ export const WorkerResultSchema = z addReplanRequiredIssueIfNeeded(value, context); }); -export const WorkerResultOkArgsSchema = WorkerResultBaseSchema.extend({ +const WorkerResultOkArgsSchema = WorkerResultBaseSchema.extend({ status: z.literal("ok"), outcome: OutcomeSchema.optional(), }); -export const WorkerResultNeedsInputArgsSchema = WorkerResultBaseSchema.extend({ +const WorkerResultNeedsInputArgsSchema = WorkerResultBaseSchema.extend({ status: z.literal("needs_input"), outcome: OutcomeSchema, }); diff --git a/src/runtime/schema.ts b/src/runtime/schema.ts index 609e381..24bc583 100644 --- a/src/runtime/schema.ts +++ b/src/runtime/schema.ts @@ -16,8 +16,8 @@ import type { StandardsProfileSchema, } from "./schema-planning-profiles"; import type { + FinalReviewerDecisionSchema, FlowReviewRecordFeatureArgsSchema, - FlowReviewRecordFinalArgsSchema, ReviewerDecisionSchema, } from "./schema-review-decisions"; import type { @@ -25,96 +25,50 @@ import type { WorkerResultArgsSchema, WorkerResultSchema, } from "./schema-worker-result"; -import type { DecisionSchema } from "./schema-worker-result-shared"; export { EvidencePacketArraySchema, - EvidencePacketPurposeSchema, - EvidencePacketReferenceArraySchema, - EvidencePacketReferenceSchema, EvidencePacketSchema, - EvidencePacketValidationRunSchema, - EvidencePacketValidationStatusSchema, - FlowContextLaneSchema, } from "./schema-evidence-packets"; export { - ApprovalStatusSchema, - ClosureSchema, - CompletionPolicySchema, - DecompositionPolicySchema, - DeliveryPolicySchema, - FeatureIdSchema, FeatureSchema, - FeatureStatusSchema, - GoalModeSchema, PackageManagerSchema, PlanArgsSchema, PlanningContextArgsSchema, PlanningContextSchema, PlanSchema, - ReplanRecordSchema, ReviewFindingPlanningContextSchema, SessionStatusSchema, } from "./schema-plan"; export { - EvidenceConfidenceSchema, - ImplementationApproachSchema, - PlanningDecisionOptionSchema, - PlanningDecisionSchema, - StackProfileEntrySchema, StackProfileSchema, - StandardsGapSchema, StandardsProfileSchema, - StandardsRuleSchema, - StandardsSourceSchema, } from "./schema-planning-profiles"; export { - FeatureReviewerDecisionSchema, FinalReviewerDecisionSchema, FinalReviewSchema, FlowReviewRecordFeatureArgsSchema, - FlowReviewRecordFinalArgsSchema, - PersistedFinalReviewerDecisionSchema, - PersistedFinalReviewSchema, ReviewerDecisionSchema, } from "./schema-review-decisions"; export { - BehaviorCheckResultSchema, BehaviorCheckSchema, - BehaviorRiskClassSchema, - ReviewScopeAccountingStatusSchema, - ReviewScopeLedgerEntrySchema, - ReviewScopeTargetSchema, ValidationCoverageSchema, } from "./schema-review-shared"; export { type Session, SessionSchema } from "./schema-session"; export { - ExecutionHistoryEntrySchema, LatestFailedFlowAttemptSchema, WorkerResultArgsSchema, WorkerResultBaseSchema, - WorkerResultNeedsInputArgsSchema, - WorkerResultOkArgsSchema, WorkerResultSchema, } from "./schema-worker-result"; -export { - ArtifactSchema, - DecisionSchema, - FeatureResultSchema, - NoteSchema, - OutcomeKindSchema, - OutcomeSchema, - ValidationRunSchema, - ValidationStatusSchema, -} from "./schema-worker-result-shared"; +export { OutcomeSchema } from "./schema-worker-result-shared"; -export type Decision = z.infer; export type Feature = z.infer; export type FlowReviewRecordFeatureArgs = z.input< typeof FlowReviewRecordFeatureArgsSchema >; export type FlowReviewRecordFinalArgs = z.input< - typeof FlowReviewRecordFinalArgsSchema + typeof FinalReviewerDecisionSchema >; export type Plan = z.infer; export type PlanInput = z.input; diff --git a/src/runtime/session-completed-storage.ts b/src/runtime/session-completed-storage.ts index d4b0dff..d1e458d 100644 --- a/src/runtime/session-completed-storage.ts +++ b/src/runtime/session-completed-storage.ts @@ -13,7 +13,7 @@ export type CompletedSessionLocation = { completedTo: string; }; -export function completedDirectoryName( +function completedDirectoryName( sessionId: string, completedAt: string, attempt = 0, @@ -67,7 +67,7 @@ export function compareCompletedDescending( return rightSuffix - leftSuffix; } -export async function pathExists(path: string): Promise { +async function pathExists(path: string): Promise { try { await stat(path); return true; @@ -80,7 +80,7 @@ export async function pathExists(path: string): Promise { } } -export function buildCompletedSessionLocation( +function buildCompletedSessionLocation( worktree: string, sessionId: string, completedDirName: string, diff --git a/src/runtime/session-live-storage.ts b/src/runtime/session-live-storage.ts index 38b29df..75f6823 100644 --- a/src/runtime/session-live-storage.ts +++ b/src/runtime/session-live-storage.ts @@ -16,7 +16,7 @@ import { } from "./session-workspace-io"; import type { MutableWorkspaceRoot } from "./workspace-root"; -export type SessionActivationRollbackPhase = +type SessionActivationRollbackPhase = | "restore_prior_active" | "sync_live_parent_directories"; diff --git a/src/runtime/session-workspace-gitignore.ts b/src/runtime/session-workspace-gitignore.ts index 36a8407..5b526c8 100644 --- a/src/runtime/session-workspace-gitignore.ts +++ b/src/runtime/session-workspace-gitignore.ts @@ -1,4 +1,4 @@ -export const FLOW_GITIGNORE_ENTRIES = [ +const FLOW_GITIGNORE_ENTRIES = [ "active/", "stored/", "completed/", diff --git a/src/runtime/summary.ts b/src/runtime/summary.ts index 69e669c..cf208f4 100644 --- a/src/runtime/summary.ts +++ b/src/runtime/summary.ts @@ -87,7 +87,7 @@ export type SummarizedSessionDetails = { featureLines: string[]; }; -export type SessionViewModel = { +type SessionViewModel = { status: string; summary: string; session: SummarizedSessionDetails | null; @@ -276,8 +276,4 @@ export function explainSessionState(session: Session | null): SessionGuidance { } export type { SessionOperatorState } from "./session-operator-state"; -export { - deriveExecutionLane, - deriveNextCommand, - deriveSessionOperatorState, -} from "./session-operator-state"; +export { deriveNextCommand } from "./session-operator-state"; diff --git a/src/runtime/transitions/execution-completion-normalization.ts b/src/runtime/transitions/execution-completion-normalization.ts index 93c2b0d..d6fc5a3 100644 --- a/src/runtime/transitions/execution-completion-normalization.ts +++ b/src/runtime/transitions/execution-completion-normalization.ts @@ -1,13 +1,9 @@ import { buildReviewContextPack } from "../domain"; import type { FinalReviewBehaviorRiskClass } from "../domain/final-review-behavior-risks"; -import { - canonicalTestEvidenceRefs, - normalizeBehaviorRiskClassName, -} from "../domain/final-review-canonicalization"; import type { ReviewContextPackInput } from "../domain/review-content-discovery"; import type { Session, WorkerResultArgs } from "../schema"; -export type NormalizedReview = Omit< +type NormalizedReview = Omit< NonNullable, "blockingFindings" > & { @@ -20,7 +16,7 @@ type PersistedFinalReview = NonNullable< Session["execution"]["history"][number]["finalReview"] >; -export type NormalizedFinalReview = PersistedFinalReview; +type NormalizedFinalReview = PersistedFinalReview; type WorkerBehaviorCheckInput = { riskClass: string; @@ -31,7 +27,6 @@ type WorkerBehaviorCheckInput = { lifecycleOwnerRefs?: string[] | undefined; failurePath: string; testEvidenceRefs?: string[] | undefined; - oracleRefs?: string[] | undefined; validationRefs?: string[] | undefined; remainingGap?: string | undefined; }; @@ -42,10 +37,9 @@ type WorkerValidationCoverageInput = { proves?: string[] | undefined; gaps?: string[] | undefined; testEvidenceRefs?: string[] | undefined; - oracleRefs?: string[] | undefined; }; -export type NormalizedReviewFindingClosure = Omit< +type NormalizedReviewFindingClosure = Omit< NonNullable[number], "fixRefs" | "testRefs" | "validationRefs" > & { @@ -54,7 +48,7 @@ export type NormalizedReviewFindingClosure = Omit< validationRefs: string[]; }; -export type NormalizedReviewScopeLedgerEntry = Omit< +type NormalizedReviewScopeLedgerEntry = Omit< NonNullable[number], "evidenceRefs" | "findingRefs" | "validationRefs" > & { @@ -63,7 +57,7 @@ export type NormalizedReviewScopeLedgerEntry = Omit< validationRefs: string[]; }; -export type NormalizedWorkerResultBase = Omit< +type NormalizedWorkerResultBase = Omit< WorkerResultArgs, | "artifactsChanged" | "validationRun" @@ -113,9 +107,7 @@ function normalizeReview( function normalizeBehaviorRiskClass( riskClass: string, ): FinalReviewBehaviorRiskClass { - return normalizeBehaviorRiskClassName( - riskClass, - ) as FinalReviewBehaviorRiskClass; + return riskClass as FinalReviewBehaviorRiskClass; } function normalizeFinalReview( @@ -135,29 +127,27 @@ function normalizeFinalReview( behaviorChecks: ( (review.behaviorChecks ?? []) as WorkerBehaviorCheckInput[] ).map((check) => { - const { oracleRefs: _legacyOracleRefs, ...canonicalCheck } = check; return { - ...canonicalCheck, + ...check, riskClass: normalizeBehaviorRiskClass(check.riskClass), entrypointRefs: check.entrypointRefs ?? [], stateOwnerRefs: check.stateOwnerRefs ?? [], lifecycleOwnerRefs: check.lifecycleOwnerRefs ?? [], - testEvidenceRefs: canonicalTestEvidenceRefs(check), + testEvidenceRefs: check.testEvidenceRefs ?? [], validationRefs: check.validationRefs ?? [], }; }), validationCoverage: ( (review.validationCoverage ?? []) as WorkerValidationCoverageInput[] ).map((coverage) => { - const { oracleRefs: _legacyOracleRefs, ...canonicalCoverage } = coverage; return { - ...canonicalCoverage, + ...coverage, behaviorClasses: (coverage.behaviorClasses ?? []).map( normalizeBehaviorRiskClass, ), proves: coverage.proves ?? [], gaps: coverage.gaps ?? [], - testEvidenceRefs: canonicalTestEvidenceRefs(coverage), + testEvidenceRefs: coverage.testEvidenceRefs ?? [], }; }), reviewContextPack: review.reviewContextPack @@ -209,7 +199,7 @@ export function normalizeWorkerResult( }; } -export function inferWorkerOutcomeKind( +function inferWorkerOutcomeKind( worker: NormalizedWorkerResult, ): WorkerOutcomeKind | "completed" | "needs_input" { return ( diff --git a/src/runtime/transitions/execution-completion-review-gates.ts b/src/runtime/transitions/execution-completion-review-gates.ts new file mode 100644 index 0000000..07b4ff0 --- /dev/null +++ b/src/runtime/transitions/execution-completion-review-gates.ts @@ -0,0 +1,128 @@ +import { + closedReviewFindingRefsForCompletion, + describeFinalReviewCoverageFailure, + describeFinalReviewerReviewScopeFailure, + finalReviewDepthMatchesPolicy, +} from "../domain"; +import type { Session } from "../schema"; +import { deriveExecutionLane } from "../session-operator-state"; +import type { NormalizedWorkerResult } from "./execution-completion-normalization"; + +type FinalReviewerDecisionFailure = { + kind: "missing_or_invalid_reviewer_decision" | "review_scope_accounting"; + message: string; +}; + +export function isReviewPassing( + review: + | NormalizedWorkerResult["featureReview"] + | NormalizedWorkerResult["finalReview"] + | undefined, +): boolean { + return Boolean( + review && + review.status === "passed" && + review.blockingFindings.length === 0, + ); +} + +export function finalReviewerDecisionFailureMessage( + session: Session, + worker: NormalizedWorkerResult, + featureId: string, + wasFinalFeature: boolean, +): FinalReviewerDecisionFailure | null { + if (!wasFinalFeature) { + if (deriveExecutionLane(session).lane === "lite") { + return isReviewPassing(worker.featureReview) + ? null + : { + kind: "missing_or_invalid_reviewer_decision", + message: + "Worker result cannot complete without a recorded approved reviewer decision.", + }; + } + + const decision = session.execution.lastReviewerDecision; + return decision?.status === "approved" && + decision.scope === "feature" && + decision.featureId === featureId + ? null + : { + kind: "missing_or_invalid_reviewer_decision", + message: + "Worker result cannot complete without a recorded approved reviewer decision.", + }; + } + + const decision = session.execution.lastReviewerDecision; + if (!decision || decision.status !== "approved") { + return { + kind: "missing_or_invalid_reviewer_decision", + message: + "Worker result cannot complete without a recorded approved reviewer decision.", + }; + } + if (decision.scope !== "final") { + return { + kind: "missing_or_invalid_reviewer_decision", + message: + "Worker result cannot complete the session without a final-scope approved reviewer decision.", + }; + } + if (!finalReviewDepthMatchesPolicy(session, decision.reviewDepth)) { + return { + kind: "missing_or_invalid_reviewer_decision", + message: + "Worker result cannot complete the session because the recorded final reviewer decision does not match deliveryPolicy.finalReviewPolicy.", + }; + } + const coverageFailure = describeFinalReviewCoverageFailure( + session, + worker, + decision, + ); + if (coverageFailure) { + return { + kind: "missing_or_invalid_reviewer_decision", + message: `Worker result cannot complete the session because the recorded final reviewer decision ${coverageFailure}.`, + }; + } + const reviewScopeFailure = describeFinalReviewerReviewScopeFailure( + session, + decision, + { + closedFindingRefs: closedReviewFindingRefsForCompletion(session, worker), + requireClosedFindingMatch: session.plan?.goalMode === "review_and_fix", + }, + ); + return reviewScopeFailure + ? { + kind: "review_scope_accounting", + message: `Worker result cannot complete the session because the recorded final reviewer decision ${reviewScopeFailure}`, + } + : null; +} + +export function finalReviewFailureMessage( + session: Session, + worker: NormalizedWorkerResult, +): string | null { + if (!worker.finalReview) { + return null; + } + if (!isReviewPassing(worker.finalReview)) { + return "Worker result cannot complete the feature because finalReview is not passing."; + } + if (!finalReviewDepthMatchesPolicy(session, worker.finalReview.reviewDepth)) { + return "Worker result cannot complete the feature because finalReview does not match deliveryPolicy.finalReviewPolicy."; + } + const coverageFailure = describeFinalReviewCoverageFailure( + session, + worker, + worker.finalReview, + ); + return coverageFailure + ? `Worker result cannot complete the feature because finalReview ${coverageFailure}.` + : null; +} diff --git a/src/runtime/transitions/execution-completion-validation.ts b/src/runtime/transitions/execution-completion-validation.ts index ab52fac..de7987c 100644 --- a/src/runtime/transitions/execution-completion-validation.ts +++ b/src/runtime/transitions/execution-completion-validation.ts @@ -2,34 +2,25 @@ import { buildFinalReviewerReviewScopeRecoveryDetails, buildReviewScopeRecoveryDetails, closedReviewFindingRefsForCompletion, - describeFinalReviewCoverageFailure, - describeFinalReviewerReviewScopeFailure, describeReviewFindingClosureLedgerFailure, describeReviewScopeLedgerFailure, - finalReviewDepthMatchesPolicy, } from "../domain"; import type { Session, WorkerResultArgs } from "../schema"; -import { deriveExecutionLane } from "../session-operator-state"; import { type NormalizedWorkerResult, normalizeWorkerResult, } from "./execution-completion-normalization"; -import { buildCompletionRecovery } from "./recovery"; +import { + finalReviewerDecisionFailureMessage, + finalReviewFailureMessage, + isReviewPassing, +} from "./execution-completion-review-gates"; +import { + buildCompletionRecovery, + type CompletionRecoveryKind, +} from "./recovery"; import { fail, succeed, type TransitionResult } from "./shared"; -function isReviewPassing( - review: - | NormalizedWorkerResult["featureReview"] - | NormalizedWorkerResult["finalReview"] - | undefined, -): boolean { - return Boolean( - review && - review.status === "passed" && - review.blockingFindings.length === 0, - ); -} - function isValidationPassing( validationRun: NormalizedWorkerResult["validationRun"], ): boolean { @@ -39,110 +30,17 @@ function isValidationPassing( ); } -type FinalReviewerDecisionFailure = { - kind: "missing_or_invalid_reviewer_decision" | "review_scope_accounting"; - message: string; -}; - -function finalReviewerDecisionFailureMessage( - session: Session, - worker: NormalizedWorkerResult, +function failCompletion( featureId: string, wasFinalFeature: boolean, -): FinalReviewerDecisionFailure | null { - if (!wasFinalFeature) { - if (deriveExecutionLane(session).lane === "lite") { - return isReviewPassing(worker.featureReview) - ? null - : { - kind: "missing_or_invalid_reviewer_decision", - message: - "Worker result cannot complete without a recorded approved reviewer decision.", - }; - } - - const decision = session.execution.lastReviewerDecision; - return decision?.status === "approved" && - decision.scope === "feature" && - decision.featureId === featureId - ? null - : { - kind: "missing_or_invalid_reviewer_decision", - message: - "Worker result cannot complete without a recorded approved reviewer decision.", - }; - } - - const decision = session.execution.lastReviewerDecision; - if (!decision || decision.status !== "approved") { - return { - kind: "missing_or_invalid_reviewer_decision", - message: - "Worker result cannot complete without a recorded approved reviewer decision.", - }; - } - if (decision.scope !== "final") { - return { - kind: "missing_or_invalid_reviewer_decision", - message: - "Worker result cannot complete the session without a final-scope approved reviewer decision.", - }; - } - if (!finalReviewDepthMatchesPolicy(session, decision.reviewDepth)) { - return { - kind: "missing_or_invalid_reviewer_decision", - message: - "Worker result cannot complete the session because the recorded final reviewer decision does not match deliveryPolicy.finalReviewPolicy.", - }; - } - const coverageFailure = describeFinalReviewCoverageFailure( - session, - worker, - decision, - ); - if (coverageFailure) { - return { - kind: "missing_or_invalid_reviewer_decision", - message: `Worker result cannot complete the session because the recorded final reviewer decision ${coverageFailure}.`, - }; - } - const reviewScopeFailure = describeFinalReviewerReviewScopeFailure( - session, - decision, - { - closedFindingRefs: closedReviewFindingRefsForCompletion(session, worker), - requireClosedFindingMatch: session.plan?.goalMode === "review_and_fix", - }, - ); - return reviewScopeFailure - ? { - kind: "review_scope_accounting", - message: `Worker result cannot complete the session because the recorded final reviewer decision ${reviewScopeFailure}`, - } - : null; -} - -function finalReviewFailureMessage( - session: Session, - worker: NormalizedWorkerResult, -): string | null { - if (!worker.finalReview) { - return null; - } - if (!isReviewPassing(worker.finalReview)) { - return "Worker result cannot complete the feature because finalReview is not passing."; - } - if (!finalReviewDepthMatchesPolicy(session, worker.finalReview.reviewDepth)) { - return "Worker result cannot complete the feature because finalReview does not match deliveryPolicy.finalReviewPolicy."; - } - const coverageFailure = describeFinalReviewCoverageFailure( - session, - worker, - worker.finalReview, + message: string, + kind: CompletionRecoveryKind, + details?: Record, +): TransitionResult { + return fail( + message, + buildCompletionRecovery(featureId, wasFinalFeature, kind, details), ); - return coverageFailure - ? `Worker result cannot complete the feature because finalReview ${coverageFailure}.` - : null; } export function validateSuccessfulCompletion( @@ -175,15 +73,19 @@ export function validateNormalizedSuccessfulCompletion( } if (normalizedWorker.validationRun.length === 0) { - return fail( + return failCompletion( + featureId, + wasFinalFeature, "Worker result cannot complete the feature without recorded validation evidence.", - buildCompletionRecovery(featureId, wasFinalFeature, "missing_validation"), + "missing_validation", ); } if (!isValidationPassing(normalizedWorker.validationRun)) { - return fail( + return failCompletion( + featureId, + wasFinalFeature, "Worker result cannot complete the feature because validation did not fully pass.", - buildCompletionRecovery(featureId, wasFinalFeature, "failing_validation"), + "failing_validation", ); } @@ -205,13 +107,11 @@ export function validateNormalizedSuccessfulCompletion( }, ); if (closureFailure) { - return fail( + return failCompletion( + featureId, + wasFinalFeature, closureFailure, - buildCompletionRecovery( - featureId, - wasFinalFeature, - "missing_review_closure", - ), + "missing_review_closure", ); } } @@ -223,21 +123,19 @@ export function validateNormalizedSuccessfulCompletion( wasFinalFeature, ); if (reviewScopeFailure) { - return fail( + return failCompletion( + featureId, + wasFinalFeature, `Worker result cannot complete because ${reviewScopeFailure}`, - buildCompletionRecovery( - featureId, - wasFinalFeature, - "missing_review_scope_accounting", - { - reviewScopeLedger: buildReviewScopeRecoveryDetails( - session, - normalizedWorker, - featureId, - wasFinalFeature, - ), - }, - ), + "missing_review_scope_accounting", + { + reviewScopeLedger: buildReviewScopeRecoveryDetails( + session, + normalizedWorker, + featureId, + wasFinalFeature, + ), + }, ); } @@ -249,32 +147,36 @@ export function validateNormalizedSuccessfulCompletion( false, ); if (reviewerDecisionFailure) { - return fail( + return failCompletion( + featureId, + false, reviewerDecisionFailure.message, - buildCompletionRecovery(featureId, false, "missing_reviewer_decision"), + "missing_reviewer_decision", ); } if (normalizedWorker.validationScope !== "targeted") { - return fail( + return failCompletion( + featureId, + false, "Worker result cannot complete the feature without targeted validation.", - buildCompletionRecovery(featureId, false, "missing_validation_scope"), + "missing_validation_scope", ); } } if (wasFinalFeature && normalizedWorker.validationScope !== "broad") { - return fail( + return failCompletion( + featureId, + true, "Worker result cannot complete the session without broad final validation.", - buildCompletionRecovery(featureId, true, "missing_validation_scope"), + "missing_validation_scope", ); } if (!isReviewPassing(normalizedWorker.featureReview)) { - return fail( + return failCompletion( + featureId, + wasFinalFeature, "Worker result cannot complete the feature because featureReview is not passing.", - buildCompletionRecovery( - featureId, - wasFinalFeature, - "failing_feature_review", - ), + "failing_feature_review", ); } @@ -283,23 +185,19 @@ export function validateNormalizedSuccessfulCompletion( normalizedWorker, ); if (finalReviewFailure) { - return fail( + return failCompletion( + featureId, + wasFinalFeature, finalReviewFailure, - buildCompletionRecovery( - featureId, - wasFinalFeature, - "failing_final_review", - ), + "failing_final_review", ); } if (wasFinalFeature && !normalizedWorker.finalReview) { - return fail( + return failCompletion( + featureId, + wasFinalFeature, "Worker result cannot complete the session without a finalReview.", - buildCompletionRecovery( - featureId, - wasFinalFeature, - "missing_final_review", - ), + "missing_final_review", ); } @@ -316,28 +214,26 @@ export function validateNormalizedSuccessfulCompletion( ? "missing_final_reviewer_review_scope_accounting" : "missing_reviewer_decision"; const decision = session.execution.lastReviewerDecision; - return fail( + return failCompletion( + featureId, + true, reviewerDecisionFailure.message, - buildCompletionRecovery( - featureId, - true, - recoveryKind, - reviewerDecisionFailure.kind === "review_scope_accounting" && - decision?.scope === "final" - ? { - reviewScopeLedger: buildFinalReviewerReviewScopeRecoveryDetails( - session, - decision, - { - closedFindingRefs: closedReviewFindingRefsForCompletion( - session, - normalizedWorker, - ), - }, - ), - } - : undefined, - ), + recoveryKind, + reviewerDecisionFailure.kind === "review_scope_accounting" && + decision?.scope === "final" + ? { + reviewScopeLedger: buildFinalReviewerReviewScopeRecoveryDetails( + session, + decision, + { + closedFindingRefs: closedReviewFindingRefsForCompletion( + session, + normalizedWorker, + ), + }, + ), + } + : undefined, ); } } diff --git a/src/runtime/transitions/execution-completion.ts b/src/runtime/transitions/execution-completion.ts index 500f806..b0174e4 100644 --- a/src/runtime/transitions/execution-completion.ts +++ b/src/runtime/transitions/execution-completion.ts @@ -16,5 +16,4 @@ export { completeExecutionRun, markSessionCompleted, } from "./execution-completion-finalization"; -export type { WorkerOutcomeKind } from "./execution-completion-normalization"; export { validateSuccessfulCompletion } from "./execution-completion-validation"; diff --git a/src/runtime/transitions/execution.ts b/src/runtime/transitions/execution.ts index 9a4d9ec..dabf92e 100644 --- a/src/runtime/transitions/execution.ts +++ b/src/runtime/transitions/execution.ts @@ -6,11 +6,7 @@ import { import { startRun as startExecutionRun } from "./execution-selection"; import { fail, type TransitionResult } from "./shared"; -export type { WorkerOutcomeKind } from "./execution-completion"; -export { - markSessionCompleted, - validateSuccessfulCompletion, -} from "./execution-completion"; +export { validateSuccessfulCompletion } from "./execution-completion"; export function startRun( session: Session, diff --git a/src/runtime/transitions/index.ts b/src/runtime/transitions/index.ts index 0e650b9..5e34f23 100644 --- a/src/runtime/transitions/index.ts +++ b/src/runtime/transitions/index.ts @@ -1,19 +1,3 @@ -export { - COMPLETION_GATE_DESCRIPTORS, - COMPLETION_GATE_IDS, - COMPLETION_GATE_ORDER, - COMPLETION_GATES, - CONDITIONAL_COMPLETION_GATE_ORDER, - type CompletionGateApplicability, - type CompletionGateDescriptor, - type CompletionGateId, - type CompletionGatePath, - type CompletionGateRequiredArtifact, - completionGateOrderFor, - completionRecoveryKindOrderFor, - REVIEW_AND_FIX_COMPLETION_GATE_ORDER, - requiredArtifactForCompletionGate, -} from "./completion-gates"; export { completeRun, startRun } from "./execution"; export { isRunStartAlreadyActive } from "./execution-selection"; export { @@ -22,13 +6,9 @@ export { isPlanApprovalAlreadyApplied, selectPlanFeatures, } from "./plan"; -export { - buildCompletionRecovery, - type CompletionRecoveryKind, -} from "./recovery"; export { isReviewerDecisionAlreadyRecorded, recordReviewerDecision, resetFeature, } from "./review"; -export type { TransitionRecovery, TransitionResult } from "./shared"; +export type { TransitionResult } from "./shared"; diff --git a/src/runtime/workspace-root.ts b/src/runtime/workspace-root.ts index 5b5b054..3b08191 100644 --- a/src/runtime/workspace-root.ts +++ b/src/runtime/workspace-root.ts @@ -7,7 +7,7 @@ export type MutableWorkspaceRoot = string & { readonly [mutableWorkspaceRootBrand]: "MutableWorkspaceRoot"; }; -export type MutableWorkspaceRootDetails = { +type MutableWorkspaceRootDetails = { root: string | null; trusted: boolean; rejectionReason: string | null; diff --git a/tests/config/helpers.ts b/tests/config/helpers.ts index 1346ade..05ae428 100644 --- a/tests/config/helpers.ts +++ b/tests/config/helpers.ts @@ -4,13 +4,13 @@ import { join } from "node:path"; import { tool } from "../../src/adapters/opencode/sdk"; import { createTools } from "../../src/adapters/opencode/tools"; -export type AgentTools = { +type AgentTools = { edit?: boolean; write?: boolean; bash?: boolean; }; -export type AgentPermission = { +type AgentPermission = { edit?: string; bash?: string; external_directory?: string; @@ -38,7 +38,7 @@ export type MutableConfig = { command?: Record; }; -export type ToolDefinition = { +type ToolDefinition = { args: Record; }; @@ -85,18 +85,6 @@ export function getToolSchemas() { }; } -export function expectInOrder(content: string, snippets: string[]) { - let previousIndex = -1; - - for (const snippet of snippets) { - const index = content.indexOf(snippet); - - expect(index).toBeGreaterThan(-1); - expect(index).toBeGreaterThan(previousIndex); - previousIndex = index; - } -} - export function expectNoFlowManagedCompaction(content: string) { const normalized = content.toLowerCase(); diff --git a/tests/config/tool-schemas.test.ts b/tests/config/tool-schemas.test.ts index 2de4735..0f7c425 100644 --- a/tests/config/tool-schemas.test.ts +++ b/tests/config/tool-schemas.test.ts @@ -15,8 +15,8 @@ import { FLOW_SURFACE_DESCRIPTORS } from "../../src/adapters/opencode/tool-surfa import { FLOW_TOOL_PAYLOAD_SCHEMA_REGISTRY } from "../../src/adapters/opencode/tool-surface/schemas"; import { type CoreActionName, coreActionByName } from "../../src/core/registry"; import { + FinalReviewerDecisionSchema, FlowReviewRecordFeatureArgsSchema, - FlowReviewRecordFinalArgsSchema, PlanArgsSchema, PlanningContextArgsSchema, WorkerResultArgsSchema, @@ -397,13 +397,13 @@ describe("tool schema config contracts", () => { }; expectAdapterRuntimeAgreement( schemas.flow_review_record_final, - FlowReviewRecordFinalArgsSchema, + FinalReviewerDecisionSchema, validFinalReview, true, ); expectAdapterRuntimeAgreement( schemas.flow_review_record_final, - FlowReviewRecordFinalArgsSchema, + FinalReviewerDecisionSchema, { ...validFinalReview, evidenceRefs: undefined }, false, ); @@ -642,7 +642,7 @@ describe("tool schema config contracts", () => { expect( schemas.flow_review_record_final.safeParse(finalReview).success, ).toBe(true); - expect(FlowReviewRecordFinalArgsSchema.safeParse(finalReview).success).toBe( + expect(FinalReviewerDecisionSchema.safeParse(finalReview).success).toBe( true, ); expect( @@ -650,8 +650,7 @@ describe("tool schema config contracts", () => { .success, ).toBe(false); expect( - FlowReviewRecordFinalArgsSchema.safeParse(compactContextFinalReview) - .success, + FinalReviewerDecisionSchema.safeParse(compactContextFinalReview).success, ).toBe(false); expect( schemas.flow_run_complete_feature.safeParse(workerResultWithFinalReview) diff --git a/tests/cross-area/semantic-parity-helpers.ts b/tests/cross-area/semantic-parity-helpers.ts index e963faa..b0227e3 100644 --- a/tests/cross-area/semantic-parity-helpers.ts +++ b/tests/cross-area/semantic-parity-helpers.ts @@ -9,12 +9,12 @@ const repoRoot = join(import.meta.dir, "..", ".."); const DOC_INVARIANT_MARKER_PATTERN = /^\s*-\s+\[semantic-invariant\]\s+([a-z_]+\.[a-z_]+\.[a-z_]+)\s*$/; -export type DocumentedSemanticInvariant = { +type DocumentedSemanticInvariant = { id: string; line: number; }; -export function knownSemanticInvariantIds(): SemanticInvariantId[] { +function knownSemanticInvariantIds(): SemanticInvariantId[] { return [...SEMANTIC_INVARIANT_IDS]; } @@ -41,7 +41,7 @@ export function readRepoFile(relativePath: string): string { return readFileSync(join(repoRoot, relativePath), "utf8"); } -export function extractDocumentedSemanticInvariants( +function extractDocumentedSemanticInvariants( text: string, ): DocumentedSemanticInvariant[] { const markers: DocumentedSemanticInvariant[] = []; diff --git a/tests/final-review-fixtures.ts b/tests/final-review-fixtures.ts index 5ee80ba..9218db3 100644 --- a/tests/final-review-fixtures.ts +++ b/tests/final-review-fixtures.ts @@ -1,6 +1,6 @@ import type { Session, WorkerResult } from "../src/runtime/schema"; -export const CANONICAL_FINAL_REVIEW_VALIDATION_ASSESSMENT = +const CANONICAL_FINAL_REVIEW_VALIDATION_ASSESSMENT = "bun test was mapped to the session-completion regression evidence; no unchecked behavior gap remains for this runtime-only fixture."; const DEFAULT_REVIEWED_SURFACES: NonNullable< @@ -26,12 +26,6 @@ type FinalReviewerDecisionOverrides = Partial & { evidenceRefs?: Partial; }; -type ReviewScopeLedgerEntry = NonNullable< - FinalReviewerDecision["reviewScopeLedger"] ->[number]; - -type ReviewScopeLedgerEntryOverrides = Partial; - function finalReviewBase(): Omit { return { reviewDepth: "detailed", @@ -66,18 +60,6 @@ export function createFinalReviewPayload( }; } -export function createReviewScopeLedgerEntry( - overrides: ReviewScopeLedgerEntryOverrides = {}, -): ReviewScopeLedgerEntry { - return { - scopeId: "feature:runtime-session", - status: "reviewed_no_findings", - evidenceRefs: ["tests/final-review-fixtures.ts"], - residualRisk: "No additional risk identified by fixture coverage.", - ...overrides, - }; -} - export function createApprovedFinalReviewerDecision( overrides: FinalReviewerDecisionOverrides = {}, ): FinalReviewerDecision { diff --git a/tests/prompt-behavior-eval-helpers.ts b/tests/prompt-behavior-eval-helpers.ts index 1649543..dda57bc 100644 --- a/tests/prompt-behavior-eval-helpers.ts +++ b/tests/prompt-behavior-eval-helpers.ts @@ -11,7 +11,7 @@ import { sourcePathExistsWithinRepo, } from "./prompt-eval-helpers"; -export type PromptBehaviorCriterion = +type PromptBehaviorCriterion = | "schema_valid" | "human_readable_sections" | "depth_calibrated" @@ -23,7 +23,7 @@ export type PromptBehaviorCriterion = | "actionable_next_steps" | "packet_boundaries_preserved"; -export type PromptBehaviorEvalCaseId = +type PromptBehaviorEvalCaseId = | "review-full-depth-downgrades-spot-check" | "review-overclaims-full-depth" | "review-confirmed-defect-grounded" @@ -35,7 +35,7 @@ export type PromptBehaviorEvalCaseId = | "captured-review-csv-memory-risk-calibrated" | "captured-review-overconfident-validation-gap"; -export type PromptBehaviorEvalCaseOrigin = "calibration" | "captured"; +type PromptBehaviorEvalCaseOrigin = "calibration" | "captured"; export type PromptBehaviorBehaviorRiskClass = | "async_event_ordering" @@ -66,7 +66,7 @@ export type PromptBehaviorPacketExpectations = { requiredBehaviorChecks?: PromptBehaviorRequiredBehaviorCheck[]; }; -export type PromptBehaviorEvalCase = { +type PromptBehaviorEvalCase = { id: PromptBehaviorEvalCaseId; title: string; origin?: PromptBehaviorEvalCaseOrigin; @@ -78,13 +78,13 @@ export type PromptBehaviorEvalCase = { expectedFailures?: PromptBehaviorCriterion[]; }; -export type PromptBehaviorCriterionResult = { +type PromptBehaviorCriterionResult = { criterion: PromptBehaviorCriterion; passed: boolean; summary: string; }; -export type PromptBehaviorEvalResult = { +type PromptBehaviorEvalResult = { id: string; title: string; score: number; @@ -96,7 +96,7 @@ export type PromptBehaviorEvalResult = { criteria: PromptBehaviorCriterionResult[]; }; -export type PromptBehaviorEvalSummary = { +type PromptBehaviorEvalSummary = { totalCases: number; passingCases: number; failingCases: number; @@ -108,7 +108,7 @@ export type PromptBehaviorEvalSummary = { markdownReport: string; }; -export const PROMPT_BEHAVIOR_EVAL_FIXTURE_DIR = join( +const PROMPT_BEHAVIOR_EVAL_FIXTURE_DIR = join( import.meta.dir, "__fixtures__", "prompt-behavior-evals", @@ -239,7 +239,7 @@ export function validatePromptBehaviorPacketExpectations( validateRequiredBehaviorChecks(value.requiredBehaviorChecks, caseId); } -export function validatePromptBehaviorEvalCorpus( +function validatePromptBehaviorEvalCorpus( raw: unknown, ): PromptBehaviorEvalCase[] { if (!Array.isArray(raw)) { diff --git a/tests/prompt-eval-helpers.ts b/tests/prompt-eval-helpers.ts index a2cd50d..f90a828 100644 --- a/tests/prompt-eval-helpers.ts +++ b/tests/prompt-eval-helpers.ts @@ -28,7 +28,7 @@ import { createSession } from "../src/runtime/session"; import { applyPlan, approvePlan, startRun } from "../src/runtime/transitions"; import { samplePlan } from "./runtime-test-helpers"; -export type PromptEvalCaseId = +type PromptEvalCaseId = | "adaptive-package-manager-ambiguity" | "adaptive-decision-gate" | "adaptive-retryable-outcome" @@ -61,7 +61,7 @@ export type PromptEvalCaseId = | "audit-command-review-packet-boundaries" | "audit-contract-reviewed-unreviewed-surfaces"; -export type PromptEvalCategory = +type PromptEvalCategory = | "command-entry" | "planning-evidence" | "decision-gating" @@ -72,7 +72,7 @@ export type PromptEvalCategory = | "finding-taxonomy" | "audit-coverage"; -export type PromptEvalSurface = +type PromptEvalSurface = | "adaptive_system_context" | "auto_command_template" | "planner_agent_prompt" @@ -92,9 +92,9 @@ export type PromptEvalSurface = | "audit_command_template" | "audit_contract"; -export type PromptEvalRisk = "medium" | "high"; +type PromptEvalRisk = "medium" | "high"; -export type PromptEvalCase = { +type PromptEvalCase = { id: PromptEvalCaseId; title: string; category: PromptEvalCategory; @@ -105,7 +105,7 @@ export type PromptEvalCase = { forbiddenSnippets?: string[]; }; -export type PromptEvalCoverageSummary = { +type PromptEvalCoverageSummary = { totalCases: number; byCategory: Record; bySurface: Record; @@ -129,7 +129,7 @@ export const PROMPT_EVAL_FIXTURE_DIR = join( * - Full prompt/eval gate: bun test tests/prompt-eval-corpus.test.ts tests/prompt-behavior-eval.test.ts tests/prompt-mode-behavior-eval.test.ts tests/prompt-mode-capture.test.ts tests/review-prompt-capture.test.ts */ -export const PROMPT_EVAL_CASE_IDS = [ +const PROMPT_EVAL_CASE_IDS = [ "adaptive-package-manager-ambiguity", "adaptive-decision-gate", "adaptive-retryable-outcome", @@ -216,7 +216,7 @@ export function sourcePathExistsWithinRepo( ); } -export function validatePromptEvalCorpus(raw: unknown): PromptEvalCase[] { +function validatePromptEvalCorpus(raw: unknown): PromptEvalCase[] { if (!Array.isArray(raw)) { throw new Error("Prompt eval corpus must be an array."); } @@ -406,7 +406,7 @@ const ADAPTIVE_SCENARIO_BUILDERS: Partial< }, }; -export function renderAdaptiveScenario(caseId: PromptEvalCaseId): string { +function renderAdaptiveScenario(caseId: PromptEvalCaseId): string { const session = createRunningSession(); const activeFeatureId = session.execution.activeFeatureId; if (!activeFeatureId) { diff --git a/tests/prompt-mode-behavior-eval-helpers.ts b/tests/prompt-mode-behavior-eval-helpers.ts index 7dfc8cd..a2b85e5 100644 --- a/tests/prompt-mode-behavior-eval-helpers.ts +++ b/tests/prompt-mode-behavior-eval-helpers.ts @@ -21,9 +21,9 @@ export type PromptModeBehaviorCriterion = | "forbidden_behavior_absent" | "next_step_calibrated"; -export type PromptModeBehaviorEvalCaseOrigin = "calibration" | "captured"; +type PromptModeBehaviorEvalCaseOrigin = "calibration" | "captured"; -export type PromptModeBehaviorEvalCase = { +type PromptModeBehaviorEvalCase = { id: string; mode: PromptModeBehaviorMode; title: string; @@ -42,13 +42,13 @@ export type PromptModeBehaviorEvalCase = { expectedFailures?: PromptModeBehaviorCriterion[]; }; -export type PromptModeBehaviorCriterionResult = { +type PromptModeBehaviorCriterionResult = { criterion: PromptModeBehaviorCriterion; passed: boolean; summary: string; }; -export type PromptModeBehaviorEvalResult = { +type PromptModeBehaviorEvalResult = { id: string; mode: PromptModeBehaviorMode; title: string; @@ -61,7 +61,7 @@ export type PromptModeBehaviorEvalResult = { criteria: PromptModeBehaviorCriterionResult[]; }; -export type PromptModeBehaviorEvalSummary = { +type PromptModeBehaviorEvalSummary = { totalCases: number; passingCases: number; failingCases: number; @@ -73,7 +73,7 @@ export type PromptModeBehaviorEvalSummary = { markdownReport: string; }; -export const PROMPT_MODE_BEHAVIOR_EVAL_FIXTURE_DIR = join( +const PROMPT_MODE_BEHAVIOR_EVAL_FIXTURE_DIR = join( import.meta.dir, "__fixtures__", "prompt-mode-behavior-evals", @@ -119,7 +119,7 @@ function assertStringArray( } } -export function validatePromptModeBehaviorEvalCorpus( +function validatePromptModeBehaviorEvalCorpus( raw: unknown, ): PromptModeBehaviorEvalCase[] { if (!Array.isArray(raw)) { diff --git a/tests/runtime-test-helpers.ts b/tests/runtime-test-helpers.ts index 8b213b9..f6b3a46 100644 --- a/tests/runtime-test-helpers.ts +++ b/tests/runtime-test-helpers.ts @@ -6,7 +6,6 @@ import { createTools } from "../src/adapters/opencode/tools"; import type { ReviewReport } from "../src/audit/report-schema"; import { readActiveSessionId } from "../src/runtime/session"; import { - samplePlan as canonicalSamplePlan, sampleSession as canonicalSampleSession, cloneSamplePlan, createSampleSession, @@ -17,7 +16,7 @@ export type TestToolContext = Partial & { directory?: string; }; -export type TestToolDefinition = { +type TestToolDefinition = { args: Record; execute: (args: unknown, context: TestToolContext) => Promise; }; @@ -130,5 +129,3 @@ export function sampleReviewReport( ...overrides, }; } - -export { canonicalSamplePlan, canonicalSampleSession }; diff --git a/tests/runtime/evidence-packets.test.ts b/tests/runtime/evidence-packets.test.ts index 4562dff..8ac12a5 100644 --- a/tests/runtime/evidence-packets.test.ts +++ b/tests/runtime/evidence-packets.test.ts @@ -6,8 +6,8 @@ import { } from "../../src/runtime/domain/planning-context"; import { EvidencePacketSchema, + FinalReviewerDecisionSchema, FlowReviewRecordFeatureArgsSchema, - FlowReviewRecordFinalArgsSchema, PlanningContextArgsSchema, WorkerResultArgsSchema, } from "../../src/runtime/schema"; @@ -331,7 +331,7 @@ describe("shared evidence packet primitives", () => { }); expect(featureDecision.evidencePackets?.[0]?.contextLane).toBe("review"); - const decision = FlowReviewRecordFinalArgsSchema.parse({ + const decision = FinalReviewerDecisionSchema.parse({ scope: "final", reviewPurpose: "completion_gate", reviewDepth: "detailed", diff --git a/tests/runtime/final-review-contracts.test.ts b/tests/runtime/final-review-contracts.test.ts index a230470..0cab90e 100644 --- a/tests/runtime/final-review-contracts.test.ts +++ b/tests/runtime/final-review-contracts.test.ts @@ -11,10 +11,11 @@ import { reviewContextPackHasSurfaceEvidence, } from "../../src/runtime/domain"; import { behaviorValidationLedgerFailureReasons } from "../../src/runtime/domain/final-review-behavior-ledger-validation"; +import { detailedFinalReviewRequirementFailures } from "../../src/runtime/domain/final-review-detailed-requirements"; import type { ReviewScopeRecoveryDetails } from "../../src/runtime/domain/review-scope-accounting"; import { + FinalReviewerDecisionSchema, FinalReviewSchema, - FlowReviewRecordFinalArgsSchema, SessionSchema, } from "../../src/runtime/schema"; import { createSession } from "../../src/runtime/session"; @@ -124,7 +125,7 @@ describe("runtime final review contracts", () => { "tests", ]); - const parsedRuntimeReview = FlowReviewRecordFinalArgsSchema.safeParse({ + const parsedRuntimeReview = FinalReviewerDecisionSchema.safeParse({ scope: "final", status: "approved", summary: "Final review approved.", @@ -558,28 +559,28 @@ describe("runtime final review contracts", () => { }; expect( - FlowReviewRecordFinalArgsSchema.safeParse(liveFinalDecision).success, + FinalReviewerDecisionSchema.safeParse(liveFinalDecision).success, ).toBe(false); expect( - FlowReviewRecordFinalArgsSchema.safeParse({ + FinalReviewerDecisionSchema.safeParse({ ...liveFinalDecision, evidenceRefs: {}, }).success, ).toBe(false); expect( - FlowReviewRecordFinalArgsSchema.safeParse({ + FinalReviewerDecisionSchema.safeParse({ ...liveFinalDecision, evidenceRefs: { changedArtifacts: [] }, }).success, ).toBe(false); expect( - FlowReviewRecordFinalArgsSchema.safeParse({ + FinalReviewerDecisionSchema.safeParse({ ...liveFinalDecision, evidenceRefs: { validationCommands: [] }, }).success, ).toBe(false); expect( - FlowReviewRecordFinalArgsSchema.safeParse({ + FinalReviewerDecisionSchema.safeParse({ ...liveFinalDecision, evidenceRefs: { changedArtifacts: [], validationCommands: [] }, }).success, @@ -604,6 +605,66 @@ describe("runtime final review contracts", () => { ).toBe(true); }); + test("requires meaningful detailed final-review integration and regression checks", () => { + const detailedReview = { + reviewDepth: "detailed", + reviewedSurfaces: ["validation_evidence", "shared_surfaces"], + integrationChecks: [" "], + regressionChecks: ["\t"], + }; + + expect(detailedFinalReviewRequirementFailures(detailedReview)).toEqual([ + "missing_integration_checks", + "missing_regression_checks", + ]); + expect( + detailedFinalReviewRequirementFailures({ + ...detailedReview, + integrationChecks: [" checked integration path "], + regressionChecks: [" checked regression path "], + }), + ).toEqual([]); + + const schemaBase = { + scope: "final", + status: "approved", + summary: "Final review approved.", + reviewDepth: "detailed", + reviewedSurfaces: ["validation_evidence", "shared_surfaces"], + evidenceSummary: "Reviewed validation evidence.", + validationAssessment: "Validation evidence was reviewed.", + evidenceRefs: { + changedArtifacts: ["src/runtime/session.ts"], + validationCommands: ["bun test"], + }, + }; + + expect( + FinalReviewerDecisionSchema.safeParse({ + ...schemaBase, + integrationChecks: [" "], + regressionChecks: ["Checked regression path."], + }).success, + ).toBe(false); + expect( + FinalReviewerDecisionSchema.safeParse({ + ...schemaBase, + integrationChecks: ["Checked integration path."], + regressionChecks: ["\t"], + }).success, + ).toBe(false); + + const parsed = FinalReviewerDecisionSchema.safeParse({ + ...schemaBase, + integrationChecks: [" checked integration path "], + regressionChecks: [" checked regression path "], + }); + expect(parsed.success).toBe(true); + if (!parsed.success) return; + expect(parsed.data.integrationChecks).toEqual(["checked integration path"]); + expect(parsed.data.regressionChecks).toEqual(["checked regression path"]); + }); + test("review-mode approved final reviewer decisions require complete review scope ledger", () => { const basePlan = samplePlan(); const plan = { @@ -1351,7 +1412,7 @@ describe("runtime final review contracts", () => { }); test("accepts optional behavior and validation coverage fields and rejects approved needs_fix combinations", () => { - const withOptionalCoverage = FlowReviewRecordFinalArgsSchema.safeParse({ + const withOptionalCoverage = FinalReviewerDecisionSchema.safeParse({ scope: "final", status: "approved", summary: "Final review approved.", @@ -1877,7 +1938,7 @@ describe("runtime final review contracts", () => { }); test("normalizes behavior path refs and rejects unsafe behavior refs", () => { - const parsed = FlowReviewRecordFinalArgsSchema.safeParse({ + const parsed = FinalReviewerDecisionSchema.safeParse({ scope: "final", status: "approved", summary: "Final review approved.", @@ -1918,7 +1979,7 @@ describe("runtime final review contracts", () => { expect(parsedBehaviorCheck).toBeDefined(); if (!parsedBehaviorCheck) return; expect( - FlowReviewRecordFinalArgsSchema.safeParse({ + FinalReviewerDecisionSchema.safeParse({ ...parsed.data, behaviorChecks: [ { @@ -1998,7 +2059,7 @@ describe("runtime final review contracts", () => { validationRefs: ["bun test"], }; - const duplicateCanonical = FlowReviewRecordFinalArgsSchema.safeParse({ + const duplicateCanonical = FinalReviewerDecisionSchema.safeParse({ ...baseFinalReview, behaviorChecks: [behaviorCheck, behaviorCheck], }); @@ -2010,26 +2071,10 @@ describe("runtime final review contracts", () => { "behaviorChecks must contain at most one entry per riskClass: test_evidence_authenticity", ); } - - const duplicateAlias = FlowReviewRecordFinalArgsSchema.safeParse({ - ...baseFinalReview, - behaviorChecks: [ - { ...behaviorCheck, riskClass: "test_oracle_authenticity" }, - behaviorCheck, - ], - }); - expect(duplicateAlias.success).toBe(false); - if (!duplicateAlias.success) { - expect( - duplicateAlias.error.issues.map((issue) => issue.message), - ).toContain( - "behaviorChecks must contain at most one entry per riskClass: test_evidence_authenticity", - ); - } }); test("rejects duplicate validation coverage behavior classes after canonicalization", () => { - const parsed = FlowReviewRecordFinalArgsSchema.safeParse({ + const parsed = FinalReviewerDecisionSchema.safeParse({ scope: "final", status: "approved", summary: "Final review approved.", @@ -2045,7 +2090,7 @@ describe("runtime final review contracts", () => { { command: "bun test", behaviorClasses: [ - "test_oracle_authenticity", + "test_evidence_authenticity", "test_evidence_authenticity", ], proves: ["Panel action ordering was exercised."], @@ -2063,7 +2108,7 @@ describe("runtime final review contracts", () => { } }); - test("accepts prior final-review terminology and emits canonical evidence fields", () => { + test("rejects prior final-review terminology", () => { const priorFinalReview = { status: "approved", summary: "Final review approved.", @@ -2082,84 +2127,48 @@ describe("runtime final review contracts", () => { { path: "tests/sessionPanelActions.test.ts", reason: "test_oracle", - summary: "Prior discovery reason maps to test evidence.", + summary: "Prior discovery reason is no longer accepted.", }, ], }, behaviorChecks: [ { - riskClass: "test_oracle_authenticity", + riskClass: "test_evidence_authenticity", result: "passed", invariant: "Tests exercise the product behavior path.", entrypointRefs: ["src/shell/sessionPanels.ts"], stateOwnerRefs: [], lifecycleOwnerRefs: [], failurePath: "Generic validation would miss stale action ordering.", - oracleRefs: ["tests/sessionPanelActions.test.ts"], + testEvidenceRefs: ["tests/sessionPanelActions.test.ts"], validationRefs: ["bun test tests/sessionPanelActions.test.ts"], }, ], validationCoverage: [ { command: "bun test tests/sessionPanelActions.test.ts", - behaviorClasses: ["test_oracle_authenticity"], + behaviorClasses: ["test_evidence_authenticity"], proves: ["Panel action ordering was exercised."], gaps: [], - oracleRefs: ["tests/sessionPanelActions.test.ts"], + testEvidenceRefs: ["tests/sessionPanelActions.test.ts"], }, ], } as const; - const parsed = FlowReviewRecordFinalArgsSchema.safeParse({ + const parsed = FinalReviewerDecisionSchema.safeParse({ scope: "final", ...priorFinalReview, }); - expect(parsed.success).toBe(true); + expect(parsed.success).toBe(false); const parsedFinalReview = FinalReviewSchema.safeParse({ ...priorFinalReview, status: "passed", }); - expect(parsedFinalReview.success).toBe(true); - if (!parsed.success || !parsedFinalReview.success) return; - const [behaviorCheck] = parsed.data.behaviorChecks ?? []; - const [coverage] = parsed.data.validationCoverage ?? []; - const [finalReviewBehaviorCheck] = - parsedFinalReview.data.behaviorChecks ?? []; - const [finalReviewCoverage] = - parsedFinalReview.data.validationCoverage ?? []; - expect(behaviorCheck?.riskClass).toBe("test_evidence_authenticity"); - expect(behaviorCheck?.testEvidenceRefs).toEqual([ - "tests/sessionPanelActions.test.ts", - ]); - expect( - (behaviorCheck as { oracleRefs?: string[] } | undefined)?.oracleRefs, - ).toBe(undefined); - expect(coverage?.behaviorClasses).toEqual(["test_evidence_authenticity"]); - expect(coverage?.testEvidenceRefs).toEqual([ - "tests/sessionPanelActions.test.ts", - ]); - expect( - (coverage as { oracleRefs?: string[] } | undefined)?.oracleRefs, - ).toBe(undefined); - expect(finalReviewBehaviorCheck?.riskClass).toBe( - "test_evidence_authenticity", - ); - expect(finalReviewBehaviorCheck?.testEvidenceRefs).toEqual([ - "tests/sessionPanelActions.test.ts", - ]); - expect(finalReviewCoverage?.behaviorClasses).toEqual([ - "test_evidence_authenticity", - ]); - expect(finalReviewCoverage?.testEvidenceRefs).toEqual([ - "tests/sessionPanelActions.test.ts", - ]); - expect(parsed.data.reviewContextPack?.includedContext[0]?.reason).toBe( - "test_evidence", - ); + expect(parsedFinalReview.success).toBe(false); }); - test("rejects conflicting prior and canonical test evidence refs", () => { - const parsed = FlowReviewRecordFinalArgsSchema.safeParse({ + test("rejects prior oracleRefs evidence fields", () => { + const parsed = FinalReviewerDecisionSchema.safeParse({ scope: "final", status: "approved", summary: "Final review approved.", @@ -2181,7 +2190,7 @@ describe("runtime final review contracts", () => { lifecycleOwnerRefs: [], failurePath: "Generic validation would miss stale action ordering.", testEvidenceRefs: ["tests/sessionPanelActions.test.ts"], - oracleRefs: ["tests/other.test.ts"], + oracleRefs: ["tests/sessionPanelActions.test.ts"], validationRefs: ["bun test"], }, ], @@ -2210,14 +2219,14 @@ describe("runtime final review contracts", () => { lifecycleOwnerRefs: [], failurePath: "Generic validation would miss stale action ordering.", testEvidenceRefs: ["tests/sessionPanelActions.test.ts"], - oracleRefs: ["tests/other.test.ts"], + oracleRefs: ["tests/sessionPanelActions.test.ts"], validationRefs: ["bun test"], }, ], }).success, ).toBe(false); expect( - FlowReviewRecordFinalArgsSchema.safeParse({ + FinalReviewerDecisionSchema.safeParse({ scope: "final", status: "approved", summary: "Final review approved.", @@ -2236,7 +2245,7 @@ describe("runtime final review contracts", () => { proves: ["Tests exercise the product behavior path."], gaps: [], testEvidenceRefs: ["tests/sessionPanelActions.test.ts"], - oracleRefs: ["tests/other.test.ts"], + oracleRefs: ["tests/sessionPanelActions.test.ts"], }, ], }).success, @@ -2595,7 +2604,7 @@ describe("runtime final review contracts", () => { }); test("trims review context pack schema fields and rejects unsafe paths", () => { - const parsed = FlowReviewRecordFinalArgsSchema.safeParse({ + const parsed = FinalReviewerDecisionSchema.safeParse({ scope: "final", status: "approved", summary: "Final review approved.", @@ -2677,7 +2686,7 @@ describe("runtime final review contracts", () => { "C:/tmp/escape.ts", "src//runtime/session.ts", ]) { - const unsafe = FlowReviewRecordFinalArgsSchema.safeParse({ + const unsafe = FinalReviewerDecisionSchema.safeParse({ scope: "final", status: "approved", summary: "Final review approved.", diff --git a/tests/runtime/semantic-invariants.test.ts b/tests/runtime/semantic-invariants.test.ts index 707ee71..1475f7e 100644 --- a/tests/runtime/semantic-invariants.test.ts +++ b/tests/runtime/semantic-invariants.test.ts @@ -19,8 +19,8 @@ import type { WorkerResult, } from "../../src/runtime/schema"; import { + FinalReviewerDecisionSchema, FlowReviewRecordFeatureArgsSchema, - FlowReviewRecordFinalArgsSchema, } from "../../src/runtime/schema"; import { createSession } from "../../src/runtime/session"; import { @@ -176,7 +176,7 @@ describe("runtime semantic invariants", () => { "src/runtime/summary.ts": ["explainSessionState", "summarizeSession"], "src/runtime/schema.ts": [ "FlowReviewRecordFeatureArgsSchema", - "FlowReviewRecordFinalArgsSchema", + "FinalReviewerDecisionSchema", ], "src/runtime/transitions/recovery.ts": ["buildCompletionRecovery"], "src/adapters/opencode/tools.ts": ["createTools"], @@ -447,7 +447,7 @@ describe("runtime semantic invariants", () => { ).toBe(false); expect( - FlowReviewRecordFinalArgsSchema.safeParse({ + FinalReviewerDecisionSchema.safeParse({ scope: SEMANTIC_REVIEW_SCOPE_EXPECTATIONS.finalScope, featureId: "setup-runtime", reviewDepth: "detailed", @@ -477,7 +477,7 @@ describe("runtime semantic invariants", () => { ).toBe(false); expect( - FlowReviewRecordFinalArgsSchema.safeParse({ + FinalReviewerDecisionSchema.safeParse({ scope: SEMANTIC_REVIEW_SCOPE_EXPECTATIONS.finalScope, reviewDepth: "detailed", reviewedSurfaces: [ diff --git a/tests/runtime/worker-result-contracts.test.ts b/tests/runtime/worker-result-contracts.test.ts index 4fca504..c2c2022 100644 --- a/tests/runtime/worker-result-contracts.test.ts +++ b/tests/runtime/worker-result-contracts.test.ts @@ -286,15 +286,12 @@ describe("runtime worker result contracts", () => { integrationChecks: ["Checked runtime integration."], regressionChecks: ["Checked runtime regression evidence."], remainingGaps: [], - behaviorChecks: [ - { ...behaviorCheck, riskClass: "test_oracle_authenticity" }, - behaviorCheck, - ], + behaviorChecks: [behaviorCheck, behaviorCheck], validationCoverage: [ { command: "bun test", behaviorClasses: [ - "test_oracle_authenticity", + "test_evidence_authenticity", "test_evidence_authenticity", ], proves: ["Tests exercise the behavior path."], @@ -422,24 +419,24 @@ describe("runtime worker result contracts", () => { remainingGaps: [], behaviorChecks: [ { - riskClass: "test_oracle_authenticity", + riskClass: "test_evidence_authenticity", result: "passed", - invariant: "Prior test evidence payloads are normalized.", + invariant: "Test evidence payloads are preserved.", entrypointRefs: ["src/runtime/session.ts"], stateOwnerRefs: [], lifecycleOwnerRefs: [], - failurePath: "Prior refs could leak into persisted history.", - oracleRefs: ["tests/runtime/worker-result-contracts.test.ts"], + failurePath: "Test refs could be omitted from persisted history.", + testEvidenceRefs: ["tests/runtime/worker-result-contracts.test.ts"], validationRefs: ["bun test"], }, ], validationCoverage: [ { command: "bun test", - behaviorClasses: ["test_oracle_authenticity"], - proves: ["Prior worker final review payload was normalized."], + behaviorClasses: ["test_evidence_authenticity"], + proves: ["Worker final review payload was persisted."], gaps: [], - oracleRefs: ["tests/runtime/worker-result-contracts.test.ts"], + testEvidenceRefs: ["tests/runtime/worker-result-contracts.test.ts"], }, ], status: "needs_followup", @@ -469,26 +466,12 @@ describe("runtime worker result contracts", () => { expect(persistedFinalReview?.behaviorChecks?.[0]?.testEvidenceRefs).toEqual( ["tests/runtime/worker-result-contracts.test.ts"], ); - expect( - ( - persistedFinalReview?.behaviorChecks?.[0] as - | { oracleRefs?: string[] } - | undefined - )?.oracleRefs, - ).toBeUndefined(); expect( persistedFinalReview?.validationCoverage?.[0]?.behaviorClasses, ).toEqual(["test_evidence_authenticity"]); expect( persistedFinalReview?.validationCoverage?.[0]?.testEvidenceRefs, ).toEqual(["tests/runtime/worker-result-contracts.test.ts"]); - expect( - ( - persistedFinalReview?.validationCoverage?.[0] as - | { oracleRefs?: string[] } - | undefined - )?.oracleRefs, - ).toBeUndefined(); expect( result.value.execution.history.at(-1)?.evidencePackets?.[0]?.id, ).toBe("packet:worker-context"); diff --git a/tests/schema-equivalence.test-d.ts b/tests/schema-equivalence.test-d.ts index efeb2ce..ae7bafc 100644 --- a/tests/schema-equivalence.test-d.ts +++ b/tests/schema-equivalence.test-d.ts @@ -1,8 +1,8 @@ import type { z } from "zod"; import type { + FinalReviewerDecisionSchema, FlowPlanApplyArgsSchema, FlowReviewRecordFeatureArgsSchema, - FlowReviewRecordFinalArgsSchema, } from "../src/adapters/opencode/tool-surface/schemas"; import type * as RuntimeSchemaBarrel from "../src/runtime/schema"; import type { @@ -23,71 +23,29 @@ type ExpectedFlowPlanApplyArgs = { }; type ExpectedRuntimeSchemaBarrelValueExports = - | "ApprovalStatusSchema" - | "ArtifactSchema" - | "BehaviorCheckResultSchema" | "BehaviorCheckSchema" - | "BehaviorRiskClassSchema" - | "ClosureSchema" - | "CompletionPolicySchema" - | "DecisionSchema" - | "DecompositionPolicySchema" - | "DeliveryPolicySchema" - | "EvidenceConfidenceSchema" | "EvidencePacketArraySchema" - | "EvidencePacketPurposeSchema" - | "EvidencePacketReferenceArraySchema" - | "EvidencePacketReferenceSchema" | "EvidencePacketSchema" - | "EvidencePacketValidationRunSchema" - | "EvidencePacketValidationStatusSchema" - | "ExecutionHistoryEntrySchema" - | "FeatureIdSchema" - | "FeatureResultSchema" - | "FeatureReviewerDecisionSchema" | "FeatureSchema" - | "FeatureStatusSchema" | "FinalReviewSchema" | "FinalReviewerDecisionSchema" - | "FlowContextLaneSchema" | "FlowReviewRecordFeatureArgsSchema" - | "FlowReviewRecordFinalArgsSchema" - | "GoalModeSchema" - | "ImplementationApproachSchema" | "LatestFailedFlowAttemptSchema" - | "NoteSchema" - | "OutcomeKindSchema" | "OutcomeSchema" | "PackageManagerSchema" - | "PersistedFinalReviewSchema" - | "PersistedFinalReviewerDecisionSchema" | "PlanArgsSchema" | "PlanSchema" | "PlanningContextArgsSchema" | "PlanningContextSchema" - | "PlanningDecisionOptionSchema" - | "PlanningDecisionSchema" - | "ReplanRecordSchema" | "ReviewFindingPlanningContextSchema" - | "ReviewScopeAccountingStatusSchema" - | "ReviewScopeLedgerEntrySchema" - | "ReviewScopeTargetSchema" | "ReviewerDecisionSchema" | "SessionSchema" | "SessionStatusSchema" - | "StackProfileEntrySchema" | "StackProfileSchema" - | "StandardsGapSchema" | "StandardsProfileSchema" - | "StandardsRuleSchema" - | "StandardsSourceSchema" | "ValidationCoverageSchema" - | "ValidationRunSchema" - | "ValidationStatusSchema" | "WorkerResultArgsSchema" | "WorkerResultBaseSchema" - | "WorkerResultNeedsInputArgsSchema" - | "WorkerResultOkArgsSchema" | "WorkerResultSchema"; export type _runtimeSchemaBarrelValueExportsStayExplicit = Expect< @@ -132,10 +90,7 @@ export type _featureReviewArgsMatchExpected = Expect< >; export type _finalReviewArgsMatchExpected = Expect< - Equal< - z.input, - FlowReviewRecordFinalArgs - > + Equal, FlowReviewRecordFinalArgs> >; export type _reviewerDecisionFeatureSliceStaysAligned = diff --git a/tests/transitions-consolidation.test.ts b/tests/transitions-consolidation.test.ts index 1a22544..2b9c469 100644 --- a/tests/transitions-consolidation.test.ts +++ b/tests/transitions-consolidation.test.ts @@ -29,8 +29,9 @@ describe("transition consolidation", () => { expect(files).toContain("recovery.ts"); expect(files).toContain("review.ts"); expect(files).toContain("shared.ts"); - // Budget includes completion-gate projection + execution-selection split modules. - expect(files.length).toBeLessThanOrEqual(13); + // Budget includes completion-gate projection, execution-selection, and + // completion review-gate split modules. + expect(files.length).toBeLessThanOrEqual(14); }); test("transition and tool hotspots stay within maintainability caps", () => {