opencode-workflow-guard 1.9.0 → 1.9.2
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/docs/accountability-evolution-plan.md +106 -0
- package/docs/assets/workflow-guard-tui-badge.png +0 -0
- package/docs/installation.md +163 -0
- package/docs/managed-deployment.md +15 -0
- package/docs/policies.md +211 -0
- package/docs/superpowers/plans/2026-08-28-title-settle-default.md +122 -0
- package/docs/superpowers/specs/2026-08-28-title-settle-default-design.md +25 -0
- package/docs/testing.md +56 -0
- package/docs/troubleshooting.md +84 -0
- package/package.json +4 -1
- package/src/workflow-guard-ui.ts +14 -14
- package/src/workflow-guard.ts +1 -1
- package/test/run-test.mjs +13 -0
- package/test/test-e2e.mts +288 -0
- package/test/test.mts +3481 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Title Settle Default Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Make Workflow Guard's title-settle behavior enabled by default while preserving `titleSettleWorkaround: false` as an explicit opt-out.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Treat the missing setting consistently as enabled at both consumers: the continuation server hook and TUI project-option reader. Keep the existing bounded title-settle implementation unchanged; only its effective default changes.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, OpenCode TUI plugin API, Node test harness, jsonc-parser.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### Task 1: Lock Down Default And Opt-Out Semantics
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `test/test.mts:1140-1155,2835-2860`
|
|
17
|
+
|
|
18
|
+
- [ ] **Step 1: Change the TUI regression to expect title settling on without configuration**
|
|
19
|
+
|
|
20
|
+
Before selecting the title-settle option, assert the missing setting reads as enabled. Then select its option callback and assert the persisted effective value becomes false:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
registeredTuiCommands.find((command) => command.name === "workflow-guard.project-options")?.run?.();
|
|
24
|
+
check("title settle workaround project option defaults on", readProjectOption(tuiCommandOptionsDir, "titleSettleWorkaround") === true);
|
|
25
|
+
tuiDialogSelectProps?.options?.find((option: any) => option.value === "titleSettleWorkaround")?.onSelect?.(fakeTuiApi.ui.dialog);
|
|
26
|
+
check("tui project-options command can disable title settle workaround", readProjectOption(tuiCommandOptionsDir, "titleSettleWorkaround") === false);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- [ ] **Step 2: Change continuation regressions to cover default-on and explicit opt-out**
|
|
30
|
+
|
|
31
|
+
Use the existing title-settle continuation fixture around line 2844. Remove the requirement to write `{ titleSettleWorkaround: true }` for the positive case, and add a sibling case/fixture with `{ titleSettleWorkaround: false }` proving continuation does not wait for title settling when explicitly disabled. Preserve the existing assertions that title generation remains native and the wait remains bounded.
|
|
32
|
+
|
|
33
|
+
- [ ] **Step 3: Run the unit harness to verify the new expectations fail**
|
|
34
|
+
|
|
35
|
+
Run: `npm test`
|
|
36
|
+
|
|
37
|
+
Expected: the new default-on assertions fail against current production code while unrelated checks remain green.
|
|
38
|
+
|
|
39
|
+
### Task 2: Implement Enabled-Unless-False Semantics
|
|
40
|
+
|
|
41
|
+
**Files:**
|
|
42
|
+
- Modify: `src/workflow-guard-ui.ts:36-43`
|
|
43
|
+
- Modify: `src/workflow-guard.ts:672`
|
|
44
|
+
|
|
45
|
+
- [ ] **Step 1: Make the TUI reader default title settling to enabled**
|
|
46
|
+
|
|
47
|
+
Update `readProjectOption` so both project memory and title settling are enabled unless explicitly false, while the other opt-in settings remain enabled only by explicit true:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const enabledByDefault = option === "projectMemory" || option === "titleSettleWorkaround";
|
|
51
|
+
if (!existsSync(path)) return enabledByDefault;
|
|
52
|
+
// parse existing config as before
|
|
53
|
+
return enabledByDefault ? config?.[option] !== false : config?.[option] === true;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- [ ] **Step 2: Make continuation use the same effective default**
|
|
57
|
+
|
|
58
|
+
Change the title-settle gate in `src/workflow-guard.ts` from:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const settleTitle = getProjectConfig(effectiveRoot).titleSettleWorkaround === true;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
to:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const settleTitle = getProjectConfig(effectiveRoot).titleSettleWorkaround !== false;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- [ ] **Step 3: Run the unit harness**
|
|
71
|
+
|
|
72
|
+
Run: `npm test`
|
|
73
|
+
|
|
74
|
+
Expected: all checks pass, including default-enabled and explicit-false regressions.
|
|
75
|
+
|
|
76
|
+
### Task 3: Document The New Default
|
|
77
|
+
|
|
78
|
+
**Files:**
|
|
79
|
+
- Modify: `docs/installation.md:103`
|
|
80
|
+
- Modify: `docs/policies.md:86`
|
|
81
|
+
- Modify: `.changeset/fix-project-option-toggles.md`
|
|
82
|
+
|
|
83
|
+
- [ ] **Step 1: Update installation guidance**
|
|
84
|
+
|
|
85
|
+
Replace the statement that title settling is off by default with text stating that it is on by default because Workflow Guard's automatic continuation can race OpenCode title generation, and that projects may opt out with `titleSettleWorkaround: false`.
|
|
86
|
+
|
|
87
|
+
- [ ] **Step 2: Update policy documentation**
|
|
88
|
+
|
|
89
|
+
Describe title settling as the default continuation behavior rather than opt-in behavior. Retain the guarantees that the wait is bounded and does not rewrite titles or provider/model parameters.
|
|
90
|
+
|
|
91
|
+
- [ ] **Step 3: Expand the existing patch changeset**
|
|
92
|
+
|
|
93
|
+
Make `.changeset/fix-project-option-toggles.md` mention both user-visible changes: functioning TUI toggles and title settling enabled by default with explicit opt-out.
|
|
94
|
+
|
|
95
|
+
### Task 4: Verify The Final Tree
|
|
96
|
+
|
|
97
|
+
**Files:**
|
|
98
|
+
- Verify only
|
|
99
|
+
|
|
100
|
+
- [ ] **Step 1: Run type checking**
|
|
101
|
+
|
|
102
|
+
Run: `npm run typecheck`
|
|
103
|
+
|
|
104
|
+
Expected: exit 0 with no TypeScript errors.
|
|
105
|
+
|
|
106
|
+
- [ ] **Step 2: Run the full unit/adversarial harness**
|
|
107
|
+
|
|
108
|
+
Run: `npm test`
|
|
109
|
+
|
|
110
|
+
Expected: all checks pass with 0 failures.
|
|
111
|
+
|
|
112
|
+
- [ ] **Step 3: Run packaged installation verification**
|
|
113
|
+
|
|
114
|
+
Run: `npm run test:install`
|
|
115
|
+
|
|
116
|
+
Expected: all non-model-driven installation/runtime checks pass with 0 failures.
|
|
117
|
+
|
|
118
|
+
- [ ] **Step 4: Check the diff**
|
|
119
|
+
|
|
120
|
+
Run: `git diff --check`
|
|
121
|
+
|
|
122
|
+
Expected: exit 0 with no whitespace errors.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Title Settle Default Design
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
Workflow Guard can automatically continue a root session that still owns unfinished todos. That synthetic continuation can race OpenCode's asynchronous native session-title generation. The existing `titleSettleWorkaround` behavior avoids the race by briefly waiting for native title generation, but it currently requires explicit opt-in even though Workflow Guard's continuation behavior creates the need for the workaround.
|
|
6
|
+
|
|
7
|
+
## Decision
|
|
8
|
+
|
|
9
|
+
Enable the title-settle behavior by default. A missing `titleSettleWorkaround` setting means enabled; `titleSettleWorkaround: false` remains an explicit project-level opt-out.
|
|
10
|
+
|
|
11
|
+
The server and TUI must expose the same effective semantics. The project-options menu therefore renders Title settle workaround as On when the setting is absent, and selecting it persists `false`. Selecting it again persists `true`.
|
|
12
|
+
|
|
13
|
+
No configuration migration is required. Existing projects that do not mention the setting acquire the safer default automatically, while projects already configured with `false` retain their behavior.
|
|
14
|
+
|
|
15
|
+
## Scope
|
|
16
|
+
|
|
17
|
+
- Change effective server behavior from `=== true` to enabled unless explicitly false.
|
|
18
|
+
- Change the TUI project-option reader to report the same enabled-by-default value.
|
|
19
|
+
- Preserve all existing bounded-wait, ownership, title, provider, and model behavior.
|
|
20
|
+
- Update installation and policy documentation to describe the default and opt-out.
|
|
21
|
+
- Update regression coverage for default-enabled behavior and explicit-false opt-out.
|
|
22
|
+
|
|
23
|
+
## Verification
|
|
24
|
+
|
|
25
|
+
Regression tests must prove that an unconfigured project uses title settling, explicit `false` bypasses title settling, and the TUI reports/toggles the same effective state. The repository typecheck, unit harness, and packaged-install test must remain green.
|
package/docs/testing.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Testing & Verification Guide
|
|
2
|
+
|
|
3
|
+
This repository includes both in-memory unit tests and live runtime installation tests.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Test Commands
|
|
8
|
+
|
|
9
|
+
### 1. Build & Typecheck
|
|
10
|
+
Runs strict TypeScript compilation checks across all source and test files:
|
|
11
|
+
```bash
|
|
12
|
+
npm run typecheck # or: npm run build
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 2. In-Memory Unit Tests
|
|
16
|
+
Runs the comprehensive unit and adversarial tests covering all guard policies, lifecycle validation, subagent parent-chain resolution, privilege-isolated verification, and path/symlink boundary checks:
|
|
17
|
+
```bash
|
|
18
|
+
npm test # runs test.mts with isolated XDG state/data (Node >= 22.18)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 3. OpenCode Runtime & Install Tests
|
|
22
|
+
Packs the npm tarball, installs it into an isolated project to verify the modular entrypoint resolves, then copies the server source into an isolated `.opencode/workflow-guard-source/` directory and exposes its `WorkflowGuard` plugin function through an explicitly registered `.opencode/plugins/` adapter. A provider-free config probe verifies OpenCode resolves that local plugin before the opt-in model-driven `opencode run` probes exercise its hooks:
|
|
23
|
+
```bash
|
|
24
|
+
npm run test:install # package install + provider-free OpenCode loader check
|
|
25
|
+
WORKFLOW_GUARD_LIVE_E2E=1 npm run test:install # also run model-driven policy probes with OpenCode's most recently selected model
|
|
26
|
+
```
|
|
27
|
+
Package and loader checks remain deterministic. Live probes explicitly select the first entry in OpenCode's persisted recent-model state (`${XDG_STATE_HOME:-~/.local/state}/opencode/model.json`) instead of silently inheriting the configured default model. Set `WORKFLOW_GUARD_LIVE_MODEL=provider/model` to override that selection; provider credentials still come from the normal OpenCode environment. If the selected model provider rejects a live prompt before guard behavior can run because credits, rate limits, capacity, or overload make the provider unavailable, that live case is reported as unavailable rather than as a guard failure. Other live-runtime failures still fail the suite.
|
|
28
|
+
|
|
29
|
+
### 4. Full Verification Suite
|
|
30
|
+
Runs typecheck, unit tests, and live runtime installation tests in sequence:
|
|
31
|
+
```bash
|
|
32
|
+
npm run test:all
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Test Suite Coverage
|
|
38
|
+
|
|
39
|
+
| Test Group | Checks |
|
|
40
|
+
|---|---|
|
|
41
|
+
| **Policy 1: Native Todo Gate** | Verifies edits are blocked when todos are missing, completed, or cancelled, and allowed when `pending` or `in_progress` todos exist. |
|
|
42
|
+
| **Policy 1: Lifecycle** | Verifies flexible task completion, concurrent `in_progress` tasks (single-focus rule removed), and silent deletion prevention. |
|
|
43
|
+
| **Policy 1: Subagent Inheritance** | Verifies subagents walk up the `parentID` hierarchy to inherit parent/grandparent todos, and handles cycle termination. |
|
|
44
|
+
| **Policies 2 & 7: Git Branches** | Verifies push blocking on `main`/`master`, feature-branch editing allowances, and git commit gates. |
|
|
45
|
+
| **Policy 3: PR Changelog** | Verifies `gh pr create` requires a changelog in body or diff. |
|
|
46
|
+
| **Policy 4: Live Commands** | Verifies destructive CLI command blocking across Kubernetes, Terraform, Helm, cloud CLIs, and databases, plus user-set `WORKFLOW_GUARD_ALLOW_LIVE=1` override. |
|
|
47
|
+
| **Policy 5: MCP Mutations** | Verifies GitHub/Azure mutating MCP tools are blocked while read-only tools pass. |
|
|
48
|
+
| **Policy 6: Settings Tamper** | Verifies protection of `opencode.json`, `~/.config/opencode/*`, and `opencode auth`. |
|
|
49
|
+
| **Policy 8: Workspace Boundary** | Verifies path traversal (`../`), symlinks, and absolute path escape protection in `edit`, `write`, `apply_patch`, and shell mutations. |
|
|
50
|
+
| **Policy 10: Evidence Verification** | Verifies isolated verification execution, timeout handling, scrubbed environment, and finalization gating on fresh passing tests. |
|
|
51
|
+
| **Adversarial Invariants** | Verifies defense against compound shell mutations, symlink escapes, chained git normalization, verify script abuse, and external repo mutations. |
|
|
52
|
+
| **Audit & Permission Events** | Verifies the typed `permission.ask` hook, permission replies, and command journaling in the audit trail. |
|
|
53
|
+
| **Completion Claims & Tool Honesty** | Verifies the claims-vs-evidence detector (missing/failing/stale/fresh states), `experimental.text.complete` journaling, and `tool.definition` enrichment idempotence. |
|
|
54
|
+
| **Compaction & TUI Feedback** | Verifies active task injection in `experimental.session.compacting`, the static TUI badge, guard block warning toasts, and clean error propagation. |
|
|
55
|
+
| **Runtime Instance Isolation** | Verifies concurrent plugin instances keep workspace root and SDK client state isolated via `runWithRuntimeState`. |
|
|
56
|
+
| **Modular Exports & Shape** | Verifies V1 `PluginModule` `{ id, server }` structure, modular policy imports, and `output.args` hook contract. |
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Troubleshooting Guide
|
|
2
|
+
|
|
3
|
+
Common issues, root causes, and solutions when using `opencode-workflow-guard`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
### 1. `plugin config hook failed` / `Unexpected server error` on startup
|
|
8
|
+
|
|
9
|
+
- **Symptoms:** OpenCode crashes or reports `Unexpected server error` when starting a session, with logs showing `TypeError: undefined is not an object (evaluating '...event')`.
|
|
10
|
+
- **Root Cause:** OpenCode 1.18+'s legacy loader treats *every exported function* in a plugin module as a separate plugin instance. If a plugin exports helper functions (like `guardToolCall` or `setWorkspaceRoot`) alongside a default function, OpenCode calls them as plugins, pushes `undefined` into the hook registry, and crashes event dispatch on `session.created`.
|
|
11
|
+
- **Solution:** Ensure `src/workflow-guard.ts` uses the V1 `PluginModule` export format (`export default { id: "workflow-guard", server: WorkflowGuard } satisfies PluginModule`). File plugins require an explicit `id` string.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### 2. Edits Blocked: `Blocked: no active todo item`
|
|
16
|
+
|
|
17
|
+
- **Symptoms:** The agent attempts an edit (`edit`, `write`, `apply_patch`) and receives a block message directing it to create a task list.
|
|
18
|
+
- **Root Cause:** Policy 1 blocks file-editing tools until the session's native todo list (`GET /session/:id/todo`) contains at least one task with status `pending` or `in_progress`.
|
|
19
|
+
- **Solution:** The agent must call `todowrite` first. Once all tasks are marked `completed` or `cancelled`, edits will block again until a new breakdown is created for subsequent work.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
### 3. Multiple tasks are `in_progress`
|
|
24
|
+
|
|
25
|
+
- **Symptoms:** A task list contains multiple `in_progress` items during parallel work.
|
|
26
|
+
- **Root Cause:** This is supported. Policy 1 permits multiple tasks to be `in_progress` so independent work and subagents can proceed concurrently.
|
|
27
|
+
- **Solution:** No correction is required. Keep each active item in every replacement list until it is explicitly marked `completed` or `cancelled`.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
### 4. `Blocked todowrite: active task '...' was removed`
|
|
32
|
+
|
|
33
|
+
- **Symptoms:** `todowrite` is blocked after an active item disappears from the submitted list.
|
|
34
|
+
- **Root Cause:** Each `todowrite` call replaces the complete list. Policy 1 therefore requires active tasks to be explicitly completed or cancelled rather than silently omitted. Independent items may otherwise be completed out of order.
|
|
35
|
+
- **Solution:** Keep the task in the list and mark it `completed` or `cancelled` before removing it in a later request.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
### 5. Edits Blocked: `Blocked: the workspace is on a protected branch (main/master)`
|
|
40
|
+
|
|
41
|
+
- **Symptoms:** Direct edits or git commit/merge commands are rejected.
|
|
42
|
+
- **Root Cause:** Policy 7 requires all code modifications to happen on a feature branch.
|
|
43
|
+
- **Solution:** Switch to a feature branch (`git switch -c feat/my-feature`) before making changes.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### 6. Edits Blocked: `Blocked: file path '...' escapes workspace root`
|
|
48
|
+
|
|
49
|
+
- **Symptoms:** Edits targeting files with `../` or external absolute paths are rejected.
|
|
50
|
+
- **Root Cause:** Policy 8 prevents directory traversal outside the workspace root.
|
|
51
|
+
- **Solution:** Ensure all target file paths resolve within the project directory.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### 7. Subagents Cannot Edit Files
|
|
56
|
+
|
|
57
|
+
- **Symptoms:** A subagent spawned via `task` fails to edit files.
|
|
58
|
+
- **Root Cause:** OpenCode disables `todowrite` for subagents by default.
|
|
59
|
+
- **Solution:** The guard automatically walks up the `parentID` session hierarchy so subagents inherit the parent session's active tasks. Ensure the parent session has active tasks before a subagent attempts guarded mutations.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
### 8. Destructive Commands Blocked (`kubectl`, `terraform`, `psql`, etc.)
|
|
64
|
+
|
|
65
|
+
- **Symptoms:** A CLI command is rejected with a live-system mutation warning.
|
|
66
|
+
- **Root Cause:** Policy 4 blocks destructive cloud, database, and infrastructure operations.
|
|
67
|
+
- **Solution:** To intentionally run a live command, set `WORKFLOW_GUARD_ALLOW_LIVE=1` in the environment before launching OpenCode. There is no in-command override; an agent cannot grant this permission to itself.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### 9. TUI plugin is missing or remains on an older release
|
|
72
|
+
|
|
73
|
+
- **Symptoms:** Workflow Guard does not appear in the TUI, or npm reports a newer Workflow Guard release while OpenCode still runs an older one.
|
|
74
|
+
- **Root Cause:** OpenCode caches npm plugins. A bare package name is resolved as `@latest`, but an already-populated package cache can continue using the version that originally populated that cache key. Running `npm update` in `~/.config/opencode` does not update this OpenCode-managed plugin installation.
|
|
75
|
+
- **Solution:** Use OpenCode's documented plugin installer with the explicit version you want. `--force` replaces the configured plugin version, and the explicit version gives OpenCode a new package-cache key:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
VERSION=$(npm view opencode-workflow-guard version)
|
|
79
|
+
opencode plugin "opencode-workflow-guard@$VERSION" --global --force
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
OpenCode documents `opencode plugin <module>` as installing a plugin and updating its config, with `--global` for global config and `--force` to replace an existing plugin version: https://opencode.ai/docs/cli/#plugin. Its plugin documentation also describes npm plugins as OpenCode-managed, cached installations: https://opencode.ai/docs/plugins/#how-plugins-are-installed.
|
|
83
|
+
|
|
84
|
+
After the command reports `Detected server + tui targets`, confirm it reports replacements/additions for both the OpenCode and TUI config files, then restart OpenCode. Manual cache deletion should not be necessary.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-workflow-guard",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.2",
|
|
4
4
|
"description": "Deterministic workflow and safety policy enforcement for OpenCode",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"bin/",
|
|
25
|
+
"docs/",
|
|
25
26
|
"src/",
|
|
27
|
+
"test/",
|
|
28
|
+
"tsconfig.json",
|
|
26
29
|
"README.md",
|
|
27
30
|
"LICENSE",
|
|
28
31
|
"ACKNOWLEDGEMENTS.md"
|
package/src/workflow-guard-ui.ts
CHANGED
|
@@ -35,11 +35,12 @@ type ProjectToggle = "recoveryCheckpoints" | "projectMemory" | "learning" | "tit
|
|
|
35
35
|
|
|
36
36
|
export function readProjectOption(root: string, option: ProjectToggle): boolean {
|
|
37
37
|
const path = projectConfigPath(root);
|
|
38
|
-
|
|
38
|
+
const enabledByDefault = option === "projectMemory" || option === "titleSettleWorkaround";
|
|
39
|
+
if (!existsSync(path)) return enabledByDefault;
|
|
39
40
|
const errors: ParseError[] = [];
|
|
40
41
|
const config = parse(readFileSync(path, "utf8"), errors, { allowTrailingComma: true });
|
|
41
42
|
if (errors.length > 0) throw new Error(`Invalid Workflow Guard project config: ${path}`);
|
|
42
|
-
return
|
|
43
|
+
return enabledByDefault ? config?.[option] !== false : config?.[option] === true;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
function writeProjectOption(root: string, option: ProjectToggle, enabled: boolean): string {
|
|
@@ -89,23 +90,22 @@ export const WorkflowGuardTui: TuiPlugin = async (api) => {
|
|
|
89
90
|
const learning = readProjectOption(root, "learning");
|
|
90
91
|
const titleSettle = readProjectOption(root, "titleSettleWorkaround");
|
|
91
92
|
const ralphMode = readProjectOption(root, "ralphMode");
|
|
93
|
+
const toggle = (key: ProjectToggle) => {
|
|
94
|
+
const enabled = !readProjectOption(root, key);
|
|
95
|
+
const path = writeProjectOption(root, key, enabled);
|
|
96
|
+
api.ui.dialog.clear();
|
|
97
|
+
api.ui.toast({ variant: "success", title: "Workflow Guard", message: `Saved ${key} ${enabled ? "on" : "off"} in ${path}. Restart OpenCode to apply.` });
|
|
98
|
+
};
|
|
92
99
|
api.ui.dialog.replace(() => api.ui.DialogSelect({
|
|
93
100
|
title: "Workflow Guard Project Options",
|
|
94
101
|
current: undefined,
|
|
95
102
|
options: [
|
|
96
|
-
{ title: `Recovery checkpoints: ${recovery ? "On" : "Off"}`, value: "recoveryCheckpoints", description: "Toggle durable pre-run Git checkpoints" },
|
|
97
|
-
{ title: `Project memory: ${memory ? "On" : "Off"}`, value: "projectMemory", description: "Toggle durable local project memory" },
|
|
98
|
-
{ title: `Learner mode: ${learning ? "On" : "Off"}`, value: "learning", description: "Toggle evidence-based learning tools" },
|
|
99
|
-
{ title: `Title settle workaround: ${titleSettle ? "On" : "Off"}`, value: "titleSettleWorkaround", description: "Delay automatic continuation while OpenCode generates a session title" },
|
|
100
|
-
{ title: `Ralph mode: ${ralphMode ? "On" : "Off"}`, value: "ralphMode", description: "Opt into bounded autonomous continuation of already-owned todos" },
|
|
103
|
+
{ title: `Recovery checkpoints: ${recovery ? "On" : "Off"}`, value: "recoveryCheckpoints", description: "Toggle durable pre-run Git checkpoints", onSelect: () => toggle("recoveryCheckpoints") },
|
|
104
|
+
{ title: `Project memory: ${memory ? "On" : "Off"}`, value: "projectMemory", description: "Toggle durable local project memory", onSelect: () => toggle("projectMemory") },
|
|
105
|
+
{ title: `Learner mode: ${learning ? "On" : "Off"}`, value: "learning", description: "Toggle evidence-based learning tools", onSelect: () => toggle("learning") },
|
|
106
|
+
{ title: `Title settle workaround: ${titleSettle ? "On" : "Off"}`, value: "titleSettleWorkaround", description: "Delay automatic continuation while OpenCode generates a session title", onSelect: () => toggle("titleSettleWorkaround") },
|
|
107
|
+
{ title: `Ralph mode: ${ralphMode ? "On" : "Off"}`, value: "ralphMode", description: "Opt into bounded autonomous continuation of already-owned todos", onSelect: () => toggle("ralphMode") },
|
|
101
108
|
],
|
|
102
|
-
onSelect: (option) => {
|
|
103
|
-
const key = option.value as ProjectToggle;
|
|
104
|
-
const enabled = !readProjectOption(root, key);
|
|
105
|
-
const path = writeProjectOption(root, key, enabled);
|
|
106
|
-
api.ui.dialog.clear();
|
|
107
|
-
api.ui.toast({ variant: "success", title: "Workflow Guard", message: `Saved ${key} ${enabled ? "on" : "off"} in ${path}. Restart OpenCode to apply.` });
|
|
108
|
-
},
|
|
109
109
|
}));
|
|
110
110
|
},
|
|
111
111
|
}],
|
package/src/workflow-guard.ts
CHANGED
|
@@ -669,7 +669,7 @@ export const WorkflowGuard: Plugin = async (ctx) => {
|
|
|
669
669
|
if (recoveryRun !== undefined) {
|
|
670
670
|
finalizeRecoveryCheckpoint(effectiveRoot, sessionID, recoveryRun);
|
|
671
671
|
}
|
|
672
|
-
const settleTitle = getProjectConfig(effectiveRoot).titleSettleWorkaround
|
|
672
|
+
const settleTitle = getProjectConfig(effectiveRoot).titleSettleWorkaround !== false;
|
|
673
673
|
await runWithRuntimeState(effectiveRoot, ctx.client, () => continueUnfinishedSession(sessionID, settleTitle));
|
|
674
674
|
}
|
|
675
675
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const testHome = mkdtempSync(join(tmpdir(), "workflow-guard-test-home-"));
|
|
6
|
+
process.env.XDG_STATE_HOME = join(testHome, "state");
|
|
7
|
+
process.env.XDG_DATA_HOME = join(testHome, "data");
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
await import("./test.mts");
|
|
11
|
+
} finally {
|
|
12
|
+
rmSync(testHome, { recursive: true, force: true });
|
|
13
|
+
}
|