opencode-workflow-guard 1.10.1 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,7 +42,7 @@ WORKFLOW_GUARD_LIVE_E2E=1 npm run test:install # Also run policy probes with Ope
42
42
 
43
43
  The plugin enforces **24 deterministic policy rules** across four main pillars:
44
44
 
45
- * **Branch & History Protection:** Enforces feature-branch workflows (blocks edits/commits on `main`/`master`), hard-blocks direct or forced pushes to protected branches, enforces mergeability pre-flight checks, and requires PR changelogs / changesets.
45
+ * **Branch & History Protection:** Enforces feature-branch workflows (blocks edits/commits on `main`/`master`), hard-blocks direct or forced pushes to protected branches, enforces mergeability pre-flight checks, enforces secondary review approval before PR creation by default, and requires PR changelogs / changesets.
46
46
  * **Task & Verification Discipline:** Gates file mutations on active `todowrite` tasks with subagent inheritance, blocks concurrent direct edits to the same canonical file across sessions, requires same-session fresh reads before `edit`/`write` can replace existing files, blocks silent task deletion, enforces fresh test verification evidence before task completion, and journals completion claims vs. verification mismatches.
47
47
  * **Secrets & Workspace Confinement:** Confines edit tools and recognized shell/git mutations within the workspace root (including symlink and `../` escape checks), scrubs sensitive environment variables in agent subshells, and redacts `.env` reads with safe schema masks. Arbitrary executables are not OS-sandboxed; use containers or filesystem isolation when hard confinement is required.
48
48
  * **Destructive CLI & Environment Safety:** Intercepts destructive cloud/database/infrastructure commands, stops script laundering and interpreter evasion, blocks TTY hangs (`vim`, `nano`, `sudo`), and guards against dangerous package manager flags.
@@ -108,6 +108,22 @@ OpenCode's model/provider request timeout is separate from Workflow Guard. In Op
108
108
 
109
109
  ---
110
110
 
111
+ ## Secondary Review Requirement
112
+
113
+ Workflow Guard enforces secondary review approval (`requireReview`) by default during PR creation (`gh pr create` and `az repos pr create`). Before a pull request can be opened, an agent must invoke a secondary review subagent and record passing review approval using `record_review` evaluated against `guard_review_rubric`.
114
+
115
+ To opt out of the PR secondary review gate for a project, set `requireReview: false` in `.opencode/workflow-guard.json`:
116
+
117
+ ```json
118
+ {
119
+ "requireReview": false
120
+ }
121
+ ```
122
+
123
+ Or set `WORKFLOW_GUARD_REQUIRE_REVIEW=0` in your environment. Conversely, `WORKFLOW_GUARD_REQUIRE_REVIEW=1` enforces the review gate regardless of repository configuration.
124
+
125
+ ---
126
+
111
127
  ## Recommended Companion Configuration
112
128
 
113
129
  For defense in depth, pair `opencode-workflow-guard` with OpenCode's native permission rules in `opencode.json` (see [OpenCode Permissions](https://opencode.ai/docs/permissions/)):
package/docs/policies.md CHANGED
@@ -181,7 +181,7 @@ Besides enforcement hooks, the guard registers companion tools in OpenCode:
181
181
 
182
182
  ### Inspection Tools
183
183
  - `guard_status`, `guard_audit`, `guard_why`, `guard_review_rubric`, `record_review`, `guard_review_followups`, and `guard_review_followup_resolve` provide runtime introspection of guardrail state, audit entries, block explanations, reviewer rubric definitions, reviewer decisions, and durable local review debt. `guard_status` also exposes the effective Ralph opt-in, iteration budget, and calling-session outcome when applicable.
184
- - **Priority-Ranked Review Gate:** `guard_review_rubric` includes P0-P3 severity tiers. When recording reviews via `record_review`, approvals that include active P0 (blocker) or P1 (major defect) issues are rejected until blockers are resolved.
184
+ - **Priority-Ranked Review Gate:** Secondary review approval (`requireReview`) is enforced by default during PR preflight (`gh pr create` / `az repos pr create`). Before creating a pull request, a secondary reviewer subagent must evaluate the changes using `guard_review_rubric` and record approval with `record_review`. Approvals that include active P0 (blocker) or P1 (major defect) issues are rejected until blockers are resolved. Projects can opt out via `.opencode/workflow-guard.json` (`{ "requireReview": false }`) or environment variable `WORKFLOW_GUARD_REQUIRE_REVIEW=0`. Conversely, `WORKFLOW_GUARD_REQUIRE_REVIEW=1` forces the review requirement regardless of project config.
185
185
  - **Durable Lower-Priority Follow-ups:** Accepted review summaries containing a P2 or P3 finding are persisted in the project's local Workflow Guard SQLite database. Open findings are included in compacted agent context and remain open across sessions until explicitly resolved with `guard_review_followup_resolve`; they are not silently discarded with the review session.
186
186
  - **Privacy-Safe Local Telemetry:** Guard decisions remain in the local audit JSONL. Tool after-hook observations add `callID` and duration records for local diagnosis and performance analysis; the current hook API does not distinguish success from every failure path, so these records do not claim a success outcome. Command and patch bodies are never written to the journal; only their byte count and SHA-256 fingerprint are retained for correlation.
187
187
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-workflow-guard",
3
- "version": "1.10.1",
3
+ "version": "1.11.0",
4
4
  "description": "Deterministic workflow and safety policy enforcement for OpenCode",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/lib/state.ts CHANGED
@@ -218,9 +218,11 @@ export function getProjectConfig(root: string): ProjectConfig {
218
218
  }
219
219
 
220
220
  export function isReviewRequired(root: string): boolean {
221
- if (process.env.WORKFLOW_GUARD_REQUIRE_REVIEW === "1") return true;
221
+ const env = process.env.WORKFLOW_GUARD_REQUIRE_REVIEW?.toLowerCase();
222
+ if (env === "0" || env === "false" || env === "off") return false;
223
+ if (env === "1" || env === "true" || env === "on") return true;
222
224
  const cfg = getProjectConfig(root);
223
- return cfg.requireReview === true;
225
+ return cfg.requireReview !== false;
224
226
  }
225
227
 
226
228
  export function isDocumentationRequired(root: string): boolean {
package/test/test.mts CHANGED
@@ -62,6 +62,7 @@ import {
62
62
  checkLockfileSync,
63
63
  branchHasDocumentationChange,
64
64
  isDocumentationRequired,
65
+ isReviewRequired,
65
66
  getOperationProfile,
66
67
  isRecoveryCheckpointsEnabled,
67
68
  getSubagentMutationBudget,
@@ -241,6 +242,7 @@ check("allow git push origin feature/x", !(await shell("git push origin feature/
241
242
  check("allow push to main-backup (ref-like path)", !(await shell("git push origin main-backup")));
242
243
 
243
244
  console.log("- Policy 3: PR changelog (GitHub & Azure DevOps) -");
245
+ process.env.WORKFLOW_GUARD_REQUIRE_REVIEW = "0";
244
246
  check("block gh pr create without changelog", blocked(await shell("gh pr create --title t --body 'no changes here'")));
245
247
  check("allow gh pr create with Changelog: body", !(await shell("gh pr create --title t --body 'Changelog: fixed stuff'")));
246
248
  check("allow gh pr create with Summary release information", !(await shell("gh pr create --title t --body '## Summary\n- Fix stale tool outcome tracking'")));
@@ -341,6 +343,8 @@ check("unrelated shell workdir cannot supply PR changelog evidence", blocked(awa
341
343
  spawnSync("git", ["worktree", "remove", "--force", changesetWorktree], { cwd: changesetRepo });
342
344
  rmSync(changesetRepo, { recursive: true, force: true });
343
345
 
346
+ delete process.env.WORKFLOW_GUARD_REQUIRE_REVIEW;
347
+
344
348
  console.log("- Policy 4: destructive commands -");
345
349
  check("block kubectl delete", blocked(await shell("kubectl delete pod foo")));
346
350
  check("block helm uninstall", blocked(await shell("helm uninstall my-release")));
@@ -1802,6 +1806,44 @@ setWorkspaceRoot(root);
1802
1806
  reloadProjectConfig(root);
1803
1807
  resetReviewState();
1804
1808
 
1809
+ // Default requireReview behavior: true by default, opt-out with config false or env 0
1810
+ const defaultReviewRepo = mkdtempSync(join(tmpdir(), "wg-default-review-"));
1811
+ mkdirSync(join(defaultReviewRepo, ".opencode"), { recursive: true });
1812
+ setWorkspaceRoot(defaultReviewRepo);
1813
+ reloadProjectConfig(defaultReviewRepo);
1814
+ check("requireReview defaults to true when unconfigured", isReviewRequired(defaultReviewRepo) === true);
1815
+ resetReviewState();
1816
+ check(
1817
+ "PR creation blocked by default without recorded review in unconfigured repo",
1818
+ blocked(await shell("gh pr create --title t --body 'Changelog: update'")),
1819
+ );
1820
+ writeFileSync(join(defaultReviewRepo, ".opencode", "workflow-guard.json"), JSON.stringify({ requireReview: false }));
1821
+ reloadProjectConfig(defaultReviewRepo);
1822
+ check("explicit requireReview: false disables review gate", isReviewRequired(defaultReviewRepo) === false);
1823
+ check(
1824
+ "PR creation allowed when requireReview is explicitly false",
1825
+ !(await shell("gh pr create --title t --body 'Changelog: update'")),
1826
+ );
1827
+ process.env.WORKFLOW_GUARD_REQUIRE_REVIEW = "1";
1828
+ check("WORKFLOW_GUARD_REQUIRE_REVIEW=1 forces review even when config is false", isReviewRequired(defaultReviewRepo) === true);
1829
+ check(
1830
+ "PR creation blocked when WORKFLOW_GUARD_REQUIRE_REVIEW=1 overrides config false",
1831
+ blocked(await shell("gh pr create --title t --body 'Changelog: update'")),
1832
+ );
1833
+ writeFileSync(join(defaultReviewRepo, ".opencode", "workflow-guard.json"), JSON.stringify({ requireReview: true }));
1834
+ reloadProjectConfig(defaultReviewRepo);
1835
+ process.env.WORKFLOW_GUARD_REQUIRE_REVIEW = "0";
1836
+ check("WORKFLOW_GUARD_REQUIRE_REVIEW=0 overrides config true", isReviewRequired(defaultReviewRepo) === false);
1837
+ check(
1838
+ "PR creation allowed when WORKFLOW_GUARD_REQUIRE_REVIEW=0 overrides config true",
1839
+ !(await shell("gh pr create --title t --body 'Changelog: update'")),
1840
+ );
1841
+ delete process.env.WORKFLOW_GUARD_REQUIRE_REVIEW;
1842
+ rmSync(defaultReviewRepo, { recursive: true, force: true });
1843
+ setWorkspaceRoot(root);
1844
+ reloadProjectConfig(root);
1845
+ resetReviewState();
1846
+
1805
1847
  // 12. Custom Plugin Tools & Event Auditing
1806
1848
  console.log("- Custom Tools & Event Auditing -");
1807
1849
  const customPlugin = await (defaultExport?.server ?? WorkflowGuard)({
@@ -2191,6 +2233,7 @@ check(
2191
2233
  // Now update README.md or docs
2192
2234
  writeFileSync(join(docRepo, "README.md"), "# Updated Docs\n");
2193
2235
  spawnSync("git", ["commit", "-am", "docs: update README"], { cwd: docRepo });
2236
+ recordReviewResult("reviewer-agent", "LGTM - 5 axes verified", true, undefined, docRepo);
2194
2237
  check("branchHasDocumentationChange returns true once docs are modified", branchHasDocumentationChange(docRepo));
2195
2238
  check(
2196
2239
  "gh pr create passes once documentation update is present",