opencode-worktree-guardian 0.1.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/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +353 -0
- package/codex/.codex-plugin/plugin.json +31 -0
- package/codex/hooks/guardian-hook.ts +265 -0
- package/codex/hooks/hooks.json +30 -0
- package/codex/skills/worktree-guardian/SKILL.md +29 -0
- package/commands/delete-paths.md +12 -0
- package/commands/delete-worktree.md +16 -0
- package/commands/done.md +14 -0
- package/commands/finish-workflow.md +18 -0
- package/commands/finish.md +16 -0
- package/commands/hygiene.md +16 -0
- package/commands/preserve.md +14 -0
- package/commands/recover.md +14 -0
- package/commands/report.md +14 -0
- package/commands/start.md +14 -0
- package/commands/status.md +14 -0
- package/commands/unblock-finish.md +16 -0
- package/docs/adr/0001-guardian-safety-policy.md +192 -0
- package/docs/publishing.md +55 -0
- package/docs/release-checklist.md +84 -0
- package/package.json +72 -0
- package/scripts/readiness.ts +75 -0
- package/scripts/with-safe-node-temp.mjs +78 -0
- package/skills/worktree-guardian/SKILL.md +73 -0
- package/src/config.ts +87 -0
- package/src/delete-paths-apply.ts +77 -0
- package/src/delete-paths-preflight.ts +146 -0
- package/src/delete-paths.ts +1 -0
- package/src/delete-worktree-preflight.ts +25 -0
- package/src/delete-worktree-report.ts +70 -0
- package/src/delete-worktree-targets.ts +152 -0
- package/src/delete-worktree.ts +222 -0
- package/src/delete.ts +1 -0
- package/src/deletion-fingerprint.ts +59 -0
- package/src/done-primary-publish.ts +129 -0
- package/src/done-primary-snapshot.ts +79 -0
- package/src/done-reattach.ts +32 -0
- package/src/done-shared.ts +28 -0
- package/src/done.ts +80 -0
- package/src/filesystem-boundaries.ts +49 -0
- package/src/finish-dirty-files.ts +56 -0
- package/src/finish-report.ts +80 -0
- package/src/finish.ts +212 -0
- package/src/git.ts +288 -0
- package/src/guards/allowlists.ts +83 -0
- package/src/guards/classifier.ts +39 -0
- package/src/guards/destructive-classifier.ts +189 -0
- package/src/guards/git-invocation.ts +145 -0
- package/src/guards/guard-types.ts +36 -0
- package/src/guards/options.ts +15 -0
- package/src/guards/path-policy.ts +31 -0
- package/src/guards/protected-branch-policy.ts +88 -0
- package/src/guards/shell-parser.ts +126 -0
- package/src/guards/shell-prefix.ts +100 -0
- package/src/guards.ts +3 -0
- package/src/hygiene-apply.ts +230 -0
- package/src/hygiene-scan.ts +200 -0
- package/src/hygiene.ts +10 -0
- package/src/index.ts +18 -0
- package/src/lifecycle.ts +31 -0
- package/src/plugin/direct-file-routing.ts +68 -0
- package/src/plugin/event-log.ts +60 -0
- package/src/plugin/guard-context.ts +40 -0
- package/src/plugin/hook-context.ts +35 -0
- package/src/plugin/invisible-policy.ts +28 -0
- package/src/plugin/native-tool.ts +82 -0
- package/src/plugin/plan-token-cache.ts +66 -0
- package/src/plugin/readable-output-cleanup.ts +141 -0
- package/src/plugin/readable-output-status.ts +86 -0
- package/src/plugin/readable-output-values.ts +21 -0
- package/src/plugin/readable-output-workflow.ts +70 -0
- package/src/plugin/readable-output.ts +16 -0
- package/src/plugin/server.ts +257 -0
- package/src/plugin/session-routing.ts +74 -0
- package/src/plugin/slash-commands.ts +20 -0
- package/src/preserve.ts +32 -0
- package/src/recover.ts +195 -0
- package/src/report.ts +168 -0
- package/src/session/context.ts +41 -0
- package/src/session/last-safe-state.ts +27 -0
- package/src/session/worktree-binding.ts +161 -0
- package/src/start.ts +157 -0
- package/src/state.ts +197 -0
- package/src/tool-registry.ts +35 -0
- package/src/tools.ts +8 -0
- package/src/tui.ts +113 -0
- package/src/types.ts +339 -0
- package/src/unblock-finish.ts +298 -0
- package/src/workflow-candidates.ts +111 -0
- package/src/workflow.ts +84 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable public-surface changes are tracked here.
|
|
4
|
+
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
No changes.
|
|
8
|
+
|
|
9
|
+
## 0.1.0 - 2026-06-13
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Packaged Codex adapter surface under the `./codex` export and `codex/` package files, with adapter smoke coverage.
|
|
14
|
+
- Canonical Guardian safety policy in `docs/adr/0001-guardian-safety-policy.md`.
|
|
15
|
+
- Public release hygiene documentation in `docs/release-checklist.md` and `docs/publishing.md`.
|
|
16
|
+
- Contract and package smoke coverage for public native tools, slash commands, packaged command assets, package exports, package files, and Codex adapter files.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Documented `guardian_hygiene` as the single hygiene scan, plan, and apply surface.
|
|
21
|
+
- Clarified README safety guidance to point at the canonical safety policy.
|
|
22
|
+
|
|
23
|
+
### Removed
|
|
24
|
+
|
|
25
|
+
- Removed the legacy `guardian_hygiene_cleanup` and `hygiene-cleanup` public command surfaces in favor of `guardian_hygiene`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 opencode-worktree-guardian contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# opencode-worktree-guardian
|
|
2
|
+
|
|
3
|
+
OpenCode plugin for safety-first multi-session Git worktree workflows. It creates or attaches sessions to guarded worktrees, blocks raw destructive commands, records repo-local ownership state, creates safety refs before finish operations, and reports recovery candidates without mutating by default.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Local development uses a project-local shim so OpenCode loads only the plugin server function:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
// .opencode/plugins/worktree-guardian.ts
|
|
11
|
+
import Guardian from "/absolute/path/to/opencode-worktree-guardian/src/index.ts"
|
|
12
|
+
|
|
13
|
+
export const WorktreeGuardian = Guardian.server
|
|
14
|
+
export default Guardian.server
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Replace `/absolute/path/to/opencode-worktree-guardian/src/index.ts` with the absolute path to this checkout's `src/index.ts`.
|
|
18
|
+
|
|
19
|
+
Do not mutate global OpenCode config for local smoke tests. Keep local host tests inside disposable repositories.
|
|
20
|
+
|
|
21
|
+
Package install after publish:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
opencode plug opencode-worktree-guardian
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The plugin package exposes both `./server` and `./tui`. After the package is published or otherwise made available to OpenCode's plugin installer, `opencode plug` can enable it in `opencode.json` and `tui.json`; OpenCode selects the right package export for the server or TUI plugin kind. That makes the Guardian slash commands visible in new OpenCode TUI sessions.
|
|
28
|
+
|
|
29
|
+
The package currently ships TypeScript source entrypoints for OpenCode/plugin-compatible hosts. Package smoke tests verify import through a TypeScript loader, so generic Node consumers need an appropriate TypeScript-capable loader or host until a compiled build is introduced.
|
|
30
|
+
|
|
31
|
+
Manual config equivalent:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"$schema": "https://opencode.ai/config.json",
|
|
36
|
+
"plugin": ["opencode-worktree-guardian"]
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
43
|
+
"plugin": ["opencode-worktree-guardian"]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Do not put `opencode-worktree-guardian/server` or `opencode-worktree-guardian/tui` directly in config. Those are package export names used by OpenCode's plugin loader after it resolves the package entry.
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
Repo-local config lives at `.opencode/worktree-guardian.json`. The current supported OpenCode/plugin API baseline is OpenCode `1.14.48` with `@opencode-ai/plugin` `^1.14.48`.
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"remote": "origin",
|
|
56
|
+
"baseBranch": "main",
|
|
57
|
+
"worktreeRoot": ".worktrees/$REPO",
|
|
58
|
+
"branchPrefix": "guardian/",
|
|
59
|
+
"finishMode": "create-pr",
|
|
60
|
+
"autoStart": true,
|
|
61
|
+
"autoFinish": false,
|
|
62
|
+
"autoCleanup": false,
|
|
63
|
+
"allowStashIfUnrelated": false,
|
|
64
|
+
"allowDirtyPaths": [],
|
|
65
|
+
"protectedBranches": ["main", "master", "develop", "production"]
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Defaults are delivery-first, lifecycle-managed, and cleanup-conservative. `guardian_finish` defaults to `create-pr`: it creates a safety ref, pushes the session branch, and suggests a `gh pr create` command without merging or deleting the worktree. `autoStart` is enabled by default so Guardian can create/attach a session worktree and own its finish/delete lifecycle; repo config can set `autoStart: false` to disable automatic ownership. Terminal sessions (`deleted`, `abandoned`, `finished`, or `preserved`) are not auto-started again; agents should start a new session instead of recreating stale worktrees. `autoFinish` and `autoCleanup` remain disabled unless repo config opts in. Stash mutation is never cleanup. `allowDirtyPaths` defaults to empty; when configured, it lets `guardian_finish` tolerate matching runtime or local-state dirt without deleting, stashing, reverting, staging, or committing those files. Dirty scanning enumerates untracked files, so narrow file-specific patterns such as `.claude/logs/hooks.log` or `.serena/project.yml` can match generated files inside otherwise-untracked runtime directories. `branchPrefix` is the default namespace for auto-created branch names, not the ownership rule; explicit descriptive branch names are allowed when Guardian state or a token-bound target proves ownership.
|
|
70
|
+
|
|
71
|
+
## Native Tools
|
|
72
|
+
|
|
73
|
+
- `guardian_start`: create or attach the session to a guardian worktree.
|
|
74
|
+
- `guardian_done`: plan or apply the safest implementation-done path for the current repository state. It delegates recorded session worktrees to `guardian_finish`, routes clean primary-base cleanup to `guardian_finish_workflow`, and handles dirty protected primary `baseBranch` only with an explicit `commitMessage` plus explicit confirmation; in the plugin flow the matching internal plan token is cached and reused automatically. Primary-main apply creates a safety ref before committing, pushes normally to the configured remote/base branch, proves the new commit is reachable from `remote/baseBranch`, and returns a separate cleanup plan; it does not silently apply cleanup, force-push, mutate stashes, delete remote branches, or weaken session ownership.
|
|
75
|
+
- `guardian_status`: read-only inventory of sessions, worktrees, refs, stashes, dirty files. Its native tool output renders a terminal-readable summary, while `metadata` keeps the full structured result for automation.
|
|
76
|
+
- `guardian_delete_paths`: safe exact path deletion for files and directories inside the repo. Run `mode: "plan"` first with explicit `paths`, inspect statuses and blockers, then apply through the plugin with `mode: "apply"` and `confirmDelete: true`; low-level direct calls also require the matching `confirmToken`. Tracked source deletion requires `allowTracked: true`; directory deletion requires `allowRecursive: true`. Worktree deletion remains separate and must use `guardian_delete_worktree`.
|
|
77
|
+
- `guardian_delete_worktree`: safe explicit worktree deletion. Run `mode: "plan"` first to get a confirm token, then `mode: "apply"` with that token. It creates a safety ref before removal, uses non-force `git worktree remove`, keeps the branch by default, and only uses non-force `git branch -d` when `deleteBranch: true` and ancestry is proven. Intentional unmerged local abandonment requires `deleteBranch: true` plus `abandonUnmerged: true` in both plan and apply.
|
|
78
|
+
- `guardian_unblock_finish`: safe explicit finish-unblock helper. Run `mode: "plan"` first to get a confirm token, then `mode: "apply"` with that token. The first supported action, `commit-review-artifacts`, commits only `.milestones/reviews/*impl-rating-YYYYMMDD.md` or `.milestones/reviews/*impl-rating-YYYYMMDD.txt` review artifacts and refuses mixed source changes, renames/copies, and symlink artifacts. Descriptive branch names are allowed when the recorded session owns that exact branch/worktree binding. If the session is missing from Guardian state, plan and apply can resolve exactly one checked-out worktree under the configured Guardian worktree root from the same explicit `branch` or `worktreePath`.
|
|
79
|
+
- `guardian_finish`: gated finish behavior based on `finishMode`.
|
|
80
|
+
- `guardian_finish_workflow`: high-level implementation-done cleanup workflow. Run `mode: "plan"` first to verify the primary worktree is clean, stash inventory is acceptable, the configured remote can be fetched, and redundant cleanup candidates are already merged to the freshly resolved `remote/baseBranch` commit. Run `mode: "apply"` only with the returned token; the token binds the resolved base commit and cleanup targets, then apply re-plans each target and delegates removal to `guardian_delete_worktree`. It does not create commits, choose commit messages, merge protected branches, mutate stashes, force-delete branches, or run raw cleanup.
|
|
81
|
+
- `guardian_preserve`: mark the session terminal/preserved and create a preserved ref. Preserved worktrees are cleanup-eligible through `guardian_delete_worktree`; preservation is not a long-term retention instruction.
|
|
82
|
+
- `guardian_recover`: read-only recovery refs, reflog/unreachable candidates, and suggested commands. Its native tool output renders a terminal-readable summary, while `metadata` keeps the full structured result for automation.
|
|
83
|
+
- `guardian_report_html`: write a self-contained offline control-room report to `.git/opencode-guardian/report.html` with sessions, worktrees, branch coverage, risks, recovery commands, and raw status/recover metadata.
|
|
84
|
+
- `guardian_hygiene`: scan, plan, or apply cleanup for workspace hygiene findings. With no `mode`, it classifies known scratch artifacts, nested Git repositories, suspicious research dumps, and protected exclusions without deleting or cleaning anything. With `mode: "plan"` or `mode: "apply"`, it uses the token-gated cleanup preflight and removes only internally token-bound approved paths with internal Node filesystem APIs after explicit confirmation.
|
|
85
|
+
|
|
86
|
+
## Slash Commands
|
|
87
|
+
|
|
88
|
+
When the TUI plugin entrypoint is enabled, Guardian registers these slash commands directly in OpenCode's command palette and slash command surface:
|
|
89
|
+
|
|
90
|
+
- `/guardian-status`
|
|
91
|
+
- `/guardian-done`
|
|
92
|
+
- `/guardian-start`
|
|
93
|
+
- `/guardian-finish`
|
|
94
|
+
- `/guardian-finish-workflow`
|
|
95
|
+
- `/guardian-preserve`
|
|
96
|
+
- `/guardian-recover`
|
|
97
|
+
- `/guardian-report`
|
|
98
|
+
- `/guardian-hygiene`
|
|
99
|
+
- `/guardian-delete-paths`
|
|
100
|
+
- `/guardian-delete-worktree`
|
|
101
|
+
- `/guardian-unblock-finish`
|
|
102
|
+
|
|
103
|
+
Each command submits a prompt to the current session telling the agent to use the matching native Guardian tool. If no session is open, the command shows a warning instead of mutating anything.
|
|
104
|
+
|
|
105
|
+
## Packaged Markdown Commands
|
|
106
|
+
|
|
107
|
+
The package also ships top-level `commands/*.md` assets for compatibility with hosts that support packaged markdown command discovery. Those hosts may register namespaced commands, for example:
|
|
108
|
+
|
|
109
|
+
- `/opencode-worktree-guardian:status`
|
|
110
|
+
- `/opencode-worktree-guardian:done`
|
|
111
|
+
- `/opencode-worktree-guardian:start`
|
|
112
|
+
- `/opencode-worktree-guardian:finish`
|
|
113
|
+
- `/opencode-worktree-guardian:finish-workflow`
|
|
114
|
+
- `/opencode-worktree-guardian:preserve`
|
|
115
|
+
- `/opencode-worktree-guardian:recover`
|
|
116
|
+
- `/opencode-worktree-guardian:report`
|
|
117
|
+
- `/opencode-worktree-guardian:hygiene`
|
|
118
|
+
- `/opencode-worktree-guardian:delete-paths`
|
|
119
|
+
- `/opencode-worktree-guardian:delete-worktree`
|
|
120
|
+
- `/opencode-worktree-guardian:unblock-finish`
|
|
121
|
+
|
|
122
|
+
Each packaged command is a thin prompt wrapper around the matching native Guardian tool. It does not authorize raw shell cleanup, raw worktree removal, stash mutation, raw branch deletion, or bypassing `guardian_done`, `guardian_finish`, `guardian_finish_workflow`, `guardian_delete_paths`, `guardian_delete_worktree`, `guardian_hygiene`, or `guardian_unblock_finish` preflights.
|
|
123
|
+
|
|
124
|
+
Native OpenCode command discovery is separate from packaged plugin assets. User or project commands live in `~/.config/opencode/commands/*.md` or `<repo>/.opencode/commands/*.md`; OpenCode core does not automatically discover this package's internal `commands/*.md` without a loader that supports packaged plugin commands.
|
|
125
|
+
|
|
126
|
+
## Safety Model
|
|
127
|
+
|
|
128
|
+
The canonical Guardian safety policy is [ADR 0001: Guardian Safety Policy](docs/adr/0001-guardian-safety-policy.md). This README summarizes the public workflow; the ADR is the authority for block, allow, route, plan/apply, confirmation, and deletion posture.
|
|
129
|
+
|
|
130
|
+
- Guardian blocks raw destructive Git and shell cleanup. Use native Guardian tools instead of hard reset, forced clean, raw worktree removal/prune, raw branch deletion, stash mutation, force push, protected-branch bypasses, or broad filesystem deletion.
|
|
131
|
+
- Use `guardian_start` for session worktree ownership and repair. Safe mutating Git commands can be routed into the owned worktree after Guardian records ownership; missing or invalid ownership fails closed for guarded operations.
|
|
132
|
+
- Use `guardian_done` for normal implementation completion. It plans first and delegates to `guardian_finish`, `guardian_finish_workflow`, or the confirmed primary-main publish lane according to repository state.
|
|
133
|
+
- Use `guardian_hygiene` for hygiene scan/plan/apply cleanup. Scan output is evidence only; cleanup requires `mode: "plan"`, exact-target review, explicit confirmation, then `mode: "apply"` with `confirmDelete: true`.
|
|
134
|
+
- Use `guardian_delete_paths` for intentional exact file or directory deletion, including tracked source only when explicitly allowed. Worktree deletion is separate and must use `guardian_delete_worktree`.
|
|
135
|
+
- Use `guardian_delete_worktree` for Guardian worktree, orphan-branch, stale-branch, or explicit unmerged-abandon cleanup. Run plan first, inspect blockers and safety evidence, then apply only through the native tool.
|
|
136
|
+
- Use `guardian_status`, `guardian_recover`, and `guardian_report_html` as read-only evidence/report surfaces. Their output can identify candidates but never authorizes deletion by itself.
|
|
137
|
+
- Use `guardian_unblock_finish` only for the narrow generated review-artifact finish blocker described in the ADR.
|
|
138
|
+
|
|
139
|
+
### Sample Tool Output
|
|
140
|
+
|
|
141
|
+
Clean `guardian_status` keeps the readable summary short and the structured fields available in `metadata`:
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
[GOOD] guardian_status snapshot
|
|
145
|
+
[INFO] repoRoot: /repo
|
|
146
|
+
[INFO] sessions: 1 | worktrees: 1 | orphaned: 0 | dirty: 0 | stashes: 0 | safetyRefs: 0 | preservedRefs: 0 | recoveryCandidates: 0
|
|
147
|
+
[INFO] sessions: 1
|
|
148
|
+
- session_id=ses_123 status=active branch=guardian/example worktree_path=/repo/.worktrees/example head=abc123def456
|
|
149
|
+
[INFO] worktrees: 1
|
|
150
|
+
- branch=guardian/example head=abc123def456 path=/repo/.worktrees/example
|
|
151
|
+
[INFO] suggested commands:
|
|
152
|
+
- guardian_status
|
|
153
|
+
- guardian_recover
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`guardian_status` and `guardian_recover` classify linked worktrees that are missing Guardian state. Worktrees outside the configured Guardian root are reported as `external-worktree`; temp-space worktrees such as `$TMPDIR/opencode/...`, `/tmp/...`, `/private/tmp/...`, or macOS `/var/folders/...` paths are reported as `external-temp-worktree` with `severity=fail` and metadata such as `commonGitDir`. These classifications are evidence only; they do not authorize cleanup.
|
|
157
|
+
|
|
158
|
+
`guardian_delete_worktree` requires a two-step token flow:
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
[WARN] guardian_delete_worktree planned
|
|
162
|
+
[INFO] mode: plan | deleteBranch: false | branchDeleted: false
|
|
163
|
+
[INFO] targetPath: /repo/.worktrees/repo/old-session
|
|
164
|
+
[INFO] branch: guardian/old-session | head: abc123def456
|
|
165
|
+
[WARN] confirmToken: <sha256-token>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Apply with that token removes only the linked worktree unless `deleteBranch: true` is also supplied and ancestry is proven:
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
[GOOD] guardian_delete_worktree deleted
|
|
172
|
+
[INFO] mode: apply | deleteBranch: false | branchDeleted: false
|
|
173
|
+
[INFO] targetPath: /repo/.worktrees/repo/old-session
|
|
174
|
+
[INFO] branch: guardian/old-session | head: abc123def456
|
|
175
|
+
[INFO] safetyRef: refs/opencode-guardian/ses_old/guardian/old-session/20260601T120000
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
For an already-absent Guardian worktree, or recorded state that points at the primary repo path while the stale Guardian branch is checked out nowhere, planning with `deleteBranch: true` returns `targetKind: orphan-branch` in metadata. Applying the returned token creates a safety ref, deletes only the branch, and records `branch_only_delete: true` in Guardian state. If the orphan branch is unmerged and deletion is still intended, include `abandonUnmerged: true` in both plan and apply after inspecting the reported unmerged commits.
|
|
179
|
+
|
|
180
|
+
For a local Guardian branch whose worktree and active state are already gone, planning with the exact `branch` or terminal `sessionId` and `deleteBranch: true` returns `targetKind: stale-branch` only when terminal Guardian state or matching Guardian safety refs prove ownership. Terminal states include `deleted`, `abandoned`, `finished`, and `preserved`; preserved is cleanup-eligible, not a retention promise. Applying the returned token creates a safety ref and deletes only the branch; if unmerged work is intentionally being abandoned, include `abandonUnmerged: true` in both plan and apply after inspecting the unmerged commit list.
|
|
181
|
+
|
|
182
|
+
An explicit unmerged abandon plan includes ancestry and unmerged commit evidence:
|
|
183
|
+
|
|
184
|
+
```text
|
|
185
|
+
[WARN] guardian_delete_worktree planned
|
|
186
|
+
[INFO] mode: plan | deleteBranch: true | abandonUnmerged: true
|
|
187
|
+
[INFO] branch: guardian/old-session | ancestryProven: false | unmergedCommitCount: 1
|
|
188
|
+
[WARN] confirmToken: <sha256-token>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
If worktree removal succeeds but opt-in branch deletion is blocked by Git or a final safety check, report the remaining branch and safety ref. Do not retry with raw branch deletion.
|
|
192
|
+
|
|
193
|
+
Ignored files are reported and blocked by default. For stale worktrees that contain only ignored local residue such as `.claude/` or `data/`, pass `allowIgnoredFiles: true` during `plan`, inspect the ignored file list, then pass the same flag during `apply` with the returned token.
|
|
194
|
+
|
|
195
|
+
Warning-heavy `guardian_recover` lists recovery facts without mutating state:
|
|
196
|
+
|
|
197
|
+
```text
|
|
198
|
+
[GOOD] guardian_recover snapshot
|
|
199
|
+
[INFO] repoRoot: /repo
|
|
200
|
+
[INFO] sessions: 2 | worktrees: 2 | orphaned: 1 | dirty: 0 | stashes: 1 | safetyRefs: 1 | preservedRefs: 0 | recoveryCandidates: 2
|
|
201
|
+
[WARN] orphaned sessions: 1
|
|
202
|
+
- ses_orphan
|
|
203
|
+
[WARN] worktrees without state: 1
|
|
204
|
+
- /repo/.worktrees/guardian-old
|
|
205
|
+
[WARN] stashes: 1
|
|
206
|
+
- stash@{0}
|
|
207
|
+
[INFO] recovery candidates: 2
|
|
208
|
+
- HEAD@{1}
|
|
209
|
+
[INFO] suggested commands:
|
|
210
|
+
- guardian_status
|
|
211
|
+
- guardian_recover
|
|
212
|
+
- git stash show -p stash@{0}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`guardian_report_html` writes the same inventory into a static offline HTML file and returns the path plus raw `status` and `recover` metadata:
|
|
216
|
+
|
|
217
|
+
```text
|
|
218
|
+
[GOOD] guardian_report_html wrote offline report
|
|
219
|
+
[INFO] reportPath: /repo/.git/opencode-guardian/report.html
|
|
220
|
+
[INFO] repoRoot: /repo
|
|
221
|
+
[INFO] sessions: 1 | worktrees: 1 | risks: 0 | recoveryCandidates: 0
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`guardian_hygiene` without `mode` reports workspace residue without cleanup approval:
|
|
225
|
+
|
|
226
|
+
```text
|
|
227
|
+
[WARN] guardian_hygiene scan
|
|
228
|
+
[INFO] candidates: 8 | findings: 3 | exclusions: 1
|
|
229
|
+
[WARN] known-cleanable: 1 | suspicious: 1 | nested-git: 1
|
|
230
|
+
- severity=warn category=known-cleanable path=librarian-react reason=known librarian scratch artifact
|
|
231
|
+
- severity=fail category=nested-git path=test-hyperf-kafka reason=nested Git repository has uncommitted changes
|
|
232
|
+
- severity=warn category=suspicious path=research-dump reason=untracked path resembles a clone, research dump, or scratch workspace
|
|
233
|
+
[INFO] suggested commands:
|
|
234
|
+
- guardian_hygiene
|
|
235
|
+
- guardian_status
|
|
236
|
+
- git status --short --ignored
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
`guardian_hygiene mode=plan|apply` uses a two-step confirmed cleanup flow for approved hygiene findings:
|
|
240
|
+
|
|
241
|
+
```text
|
|
242
|
+
[WARN] guardian_hygiene planned
|
|
243
|
+
[INFO] approvedTargets: 2 | removedTargets: 0 | blockers: 0 | fatal: 0
|
|
244
|
+
[INFO] approved targets:
|
|
245
|
+
- known-cleanable librarian-react: known librarian scratch artifact
|
|
246
|
+
- suspicious research-dump: untracked path resembles a clone, research dump, or scratch workspace
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
After explicit user confirmation, apply through the plugin with `confirmDelete: true`; no manual `confirmToken` is needed in the normal plugin flow. The plugin uses the cached internal token only when the session, repo, and cleanup options still match the plan, and treats empty or `CONFIRM_DELETE` token placeholders as absent. If any selected target disappears, gains tracked contents, changes fingerprint, resolves outside the repo, is a symlink root, or overlaps Guardian worktree/protected paths, apply blocks and requires a new plan.
|
|
250
|
+
|
|
251
|
+
Blocked `guardian_finish` exposes `preflight` and `report` for automation:
|
|
252
|
+
|
|
253
|
+
```text
|
|
254
|
+
{
|
|
255
|
+
"ok": false,
|
|
256
|
+
"status": "blocked",
|
|
257
|
+
"reason": "worktree has uncommitted changes",
|
|
258
|
+
"preflight": {
|
|
259
|
+
"sessionRecorded": true,
|
|
260
|
+
"sessionOwnedWorktree": true,
|
|
261
|
+
"currentBranch": "guardian/foo",
|
|
262
|
+
"branchProtected": false,
|
|
263
|
+
"dirtyFileCount": 2,
|
|
264
|
+
"allowedDirtyFiles": [".claude/logs/hooks.log"],
|
|
265
|
+
"allowedDirtyFileCount": 1,
|
|
266
|
+
"blockingDirtyFiles": ["src/app.ts"],
|
|
267
|
+
"blockingDirtyFileCount": 1,
|
|
268
|
+
"blockers": ["worktree has uncommitted changes"]
|
|
269
|
+
},
|
|
270
|
+
"report": {
|
|
271
|
+
"action": "blocked",
|
|
272
|
+
"mode": "create-pr",
|
|
273
|
+
"remote": "origin",
|
|
274
|
+
"baseBranch": "main",
|
|
275
|
+
"allowedDirtyFileCount": 1,
|
|
276
|
+
"blockingDirtyFileCount": 1,
|
|
277
|
+
"blockers": ["worktree has uncommitted changes"]
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`guardian_unblock_finish` uses the same confirm-token posture for narrow finish blockers:
|
|
283
|
+
|
|
284
|
+
```text
|
|
285
|
+
[WARN] guardian_unblock_finish planned
|
|
286
|
+
[INFO] action: commit-review-artifacts | sessionId: ses_123 | branch: guardian/foo
|
|
287
|
+
[INFO] worktreePath: /repo/.worktrees/repo/foo
|
|
288
|
+
[INFO] review artifacts: 1
|
|
289
|
+
- .milestones/reviews/foo-impl-rating-20260602.md
|
|
290
|
+
[WARN] confirmToken: <sha256-token>
|
|
291
|
+
[INFO] commitMessage: docs: add foo implementation rating
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Troubleshooting
|
|
295
|
+
|
|
296
|
+
- Blocked mutating command: run `guardian_status`; if the session owns a worktree, Guardian should route safe shell/git tools there automatically. A blocker means the recorded worktree is missing/unresolvable, state is fail-closed, or the command is unsafe.
|
|
297
|
+
- Recorded primary/protected ownership: run `guardian_start` with `createWorktree: true`; Guardian repairs the session into a proper Guardian worktree instead of requiring raw branch or switch commands.
|
|
298
|
+
- No owned worktree: normal non-destructive commands run in the current worktree. If you want Guardian ownership/routing, run `guardian_start`, then check `guardian_status` before retrying mutating commands.
|
|
299
|
+
- Intentional finish or preserve: use `guardian_finish` or `guardian_preserve`; `autoFinish` remains opt-in. Preservation creates a safety ref and terminal state, not a permanent worktree retention promise.
|
|
300
|
+
- Dirty worktree: commit real source/config/doc changes or intentionally preserve. If the only dirt is runtime/local state, configure narrow `allowDirtyPaths`; file-specific patterns can match untracked runtime files. Guardian reports those files as `allowedDirtyFiles`, leaves them untouched, and still blocks any non-matching dirty path.
|
|
301
|
+
- Dirty review artifact blocks finish: run `guardian_unblock_finish` with `mode: "plan"`, inspect the listed artifacts and confirm token, then apply only if the plan contains no source changes. If the session is unrecorded, include the explicit `branch` or `worktreePath` shown by `guardian_status`.
|
|
302
|
+
- Workspace residue: run `guardian_hygiene`; with no `mode`, known artifacts, nested repos, and suspicious paths are reported only. If the user explicitly approves cleanup, run `guardian_hygiene` with `mode: "plan"`, inspect exact targets/blockers, get explicit delete confirmation, then apply through the plugin with `mode: "apply"` and `confirmDelete: true` for approved targets. The internal token/fingerprint gate remains enforced.
|
|
303
|
+
- Intentional file or source deletion: use `guardian_delete_paths` with exact `paths`. Add `allowTracked: true` only for intended tracked-source deletion and `allowRecursive: true` only for intended directory deletion, then apply through the plugin with `confirmDelete: true` after inspecting the plan.
|
|
304
|
+
- Stash exists: inspect with `git stash list` and `git stash show -p`; Guardian will not mutate stashes.
|
|
305
|
+
- Orphaned worktree: run `guardian_recover` for evidence. If deletion is explicitly intended, run `guardian_delete_worktree` in `mode: "plan"`, inspect the preflight and confirm token, then re-run with `mode: "apply"` and that token.
|
|
306
|
+
|
|
307
|
+
## Verification
|
|
308
|
+
|
|
309
|
+
Run:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
npm run verify
|
|
313
|
+
npm run audit:deps
|
|
314
|
+
npm run test:contract
|
|
315
|
+
npm run test:smoke:package
|
|
316
|
+
npm run test:smoke:host
|
|
317
|
+
npm run test:readiness
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`test:contract` validates the plugin export, hook, tool, documented local-shim contract, and fail-closed cwd enforcement. `test:smoke:package` packs the artifact, installs it into a clean temporary consumer, and imports it by package name. `test:smoke:host` is deterministic and host-like: it uses a disposable repo and local shim/tool/hook execution without mutating global OpenCode config. `test:readiness` aggregates verification, audit, smoke checks, and `npm pack --dry-run`. Test scripts route Node, tsx, and coverage temp/cache output to external OS temp space.
|
|
321
|
+
|
|
322
|
+
The test suite uses disposable repositories only. Do not run destructive-command smoke tests against live repositories.
|
|
323
|
+
|
|
324
|
+
## Release Docs
|
|
325
|
+
|
|
326
|
+
- [Changelog](CHANGELOG.md)
|
|
327
|
+
- [Release checklist](docs/release-checklist.md)
|
|
328
|
+
- [Publishing policy](docs/publishing.md)
|
|
329
|
+
|
|
330
|
+
## Dependency Audit
|
|
331
|
+
|
|
332
|
+
Last verified on 2026-06-12:
|
|
333
|
+
|
|
334
|
+
```text
|
|
335
|
+
npm run audit:deps
|
|
336
|
+
found 0 vulnerabilities
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
## Manual OpenCode Host Check
|
|
341
|
+
|
|
342
|
+
The automated host smoke intentionally avoids model-driven OpenCode tool invocation because that path depends on provider auth and can be nondeterministic. When manually validating a real OpenCode host, use a disposable repository only.
|
|
343
|
+
|
|
344
|
+
Suggested manual checklist:
|
|
345
|
+
|
|
346
|
+
1. Create a fresh temporary Git repo.
|
|
347
|
+
2. Add the local shim from the Install section under `.opencode/plugins/worktree-guardian.ts`.
|
|
348
|
+
3. Start OpenCode from that disposable repo.
|
|
349
|
+
4. Confirm `guardian_status` is visible and returns repository inventory, including any owned worktree path.
|
|
350
|
+
5. From the base repo, ask OpenCode to run an allowed write such as `git add`; confirm Guardian routes the tool execution to the owned worktree instead of mutating the base repo.
|
|
351
|
+
6. Ask OpenCode to run a blocked command such as `git reset --hard`; confirm Guardian still blocks before mutation.
|
|
352
|
+
|
|
353
|
+
If this manual check disagrees with the deterministic smoke suite, treat the plugin as not release-ready until the discrepancy is understood.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-worktree-guardian",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Codex adapter for Guardian worktree safety hooks and commands.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Nash Gao"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://github.com/nashgao/opencode-worktree-guardian",
|
|
9
|
+
"repository": "https://github.com/nashgao/opencode-worktree-guardian",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": ["codex", "codex-plugin", "git", "worktree", "safety"],
|
|
12
|
+
"skills": "./skills/",
|
|
13
|
+
"hooks": "./hooks/hooks.json",
|
|
14
|
+
"interface": {
|
|
15
|
+
"displayName": "Worktree Guardian",
|
|
16
|
+
"shortDescription": "Guard Codex git/worktree workflows from unsafe destructive actions.",
|
|
17
|
+
"longDescription": "Worktree Guardian reuses the Guardian safety engine to block unsafe shell/git commands in Codex hooks and provides skill-driven command wrappers for Guardian workflows.",
|
|
18
|
+
"developerName": "Nash Gao",
|
|
19
|
+
"category": "Developer Tools",
|
|
20
|
+
"capabilities": ["Hooks", "Workflow", "Git Safety"],
|
|
21
|
+
"websiteURL": "https://github.com/nashgao/opencode-worktree-guardian",
|
|
22
|
+
"privacyPolicyURL": "https://github.com/nashgao/opencode-worktree-guardian#readme",
|
|
23
|
+
"termsOfServiceURL": "https://github.com/nashgao/opencode-worktree-guardian#license",
|
|
24
|
+
"defaultPrompt": [
|
|
25
|
+
"Run guardian status",
|
|
26
|
+
"Run guardian done in plan mode"
|
|
27
|
+
],
|
|
28
|
+
"brandColor": "#2563EB",
|
|
29
|
+
"screenshots": []
|
|
30
|
+
}
|
|
31
|
+
}
|