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 +1 -1
- package/docs/installation.md +16 -0
- package/docs/policies.md +1 -1
- package/package.json +1 -1
- package/src/lib/state.ts +4 -2
- package/test/test.mts +43 -0
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.
|
package/docs/installation.md
CHANGED
|
@@ -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:** `
|
|
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
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
|
-
|
|
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
|
|
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",
|