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
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# ADR 0001: Guardian Safety Policy
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Guardian protects multi-session Git worktree workflows by routing normal work into owned worktrees and by requiring native, token-gated tools for destructive or lifecycle operations. This policy records the canonical safety contract for public Guardian surfaces.
|
|
10
|
+
|
|
11
|
+
This document is the authority for block, allow, route, plan/apply, confirmation, and deletion posture. README, skills, slash commands, packaged markdown commands, and Codex adapter docs are discoverability surfaces and must summarize or point here instead of defining a conflicting policy.
|
|
12
|
+
|
|
13
|
+
## Policy Authority And Public Surfaces
|
|
14
|
+
|
|
15
|
+
The public native tools are:
|
|
16
|
+
|
|
17
|
+
- `guardian_start`
|
|
18
|
+
- `guardian_status`
|
|
19
|
+
- `guardian_recover`
|
|
20
|
+
- `guardian_report_html`
|
|
21
|
+
- `guardian_done`
|
|
22
|
+
- `guardian_finish`
|
|
23
|
+
- `guardian_finish_workflow`
|
|
24
|
+
- `guardian_preserve`
|
|
25
|
+
- `guardian_unblock_finish`
|
|
26
|
+
- `guardian_delete_worktree`
|
|
27
|
+
- `guardian_delete_paths`
|
|
28
|
+
- `guardian_hygiene`
|
|
29
|
+
|
|
30
|
+
OpenCode slash commands and packaged `commands/*.md` wrappers are prompt surfaces only. They must instruct the agent to use the matching native tool and must not authorize raw shell cleanup, raw worktree removal, stash mutation, raw branch deletion, force-push, protected-branch bypasses, or deletion outside Guardian preflights.
|
|
31
|
+
|
|
32
|
+
The Codex adapter is also a first-class public surface. It must invoke the same Guardian tool policies through the adapter CLI and hooks, not through raw destructive Git or shell commands.
|
|
33
|
+
|
|
34
|
+
## Raw Destructive Git And Shell Blocks
|
|
35
|
+
|
|
36
|
+
Guardian blocks raw destructive shell and Git commands in `tool.execute.before`. Blocked command classes include:
|
|
37
|
+
|
|
38
|
+
- hard reset and forced clean,
|
|
39
|
+
- raw worktree removal or prune,
|
|
40
|
+
- raw `git worktree add` outside Guardian-owned roots,
|
|
41
|
+
- raw branch deletion,
|
|
42
|
+
- stash mutation,
|
|
43
|
+
- force push,
|
|
44
|
+
- destructive checkout, restore, or switch forms,
|
|
45
|
+
- shell-wrapped variants and context-mode code payloads,
|
|
46
|
+
- `rm -rf` against known Guardian worktrees.
|
|
47
|
+
|
|
48
|
+
Agents must use Guardian-native tools for lifecycle and deletion work. A prompt, slash command, skill, or adapter invocation cannot override these blocks.
|
|
49
|
+
|
|
50
|
+
## Read-Only And Normal Safe Git Allowances
|
|
51
|
+
|
|
52
|
+
Read-only inventory and recovery surfaces are allowed through `guardian_status`, `guardian_recover`, and `guardian_hygiene` without `mode`. Their output is evidence only and does not authorize cleanup.
|
|
53
|
+
|
|
54
|
+
When a session owns a valid Guardian worktree, normal safe mutating commands such as `git add` and `git commit` may proceed through Guardian routing. Without recorded ownership, normal non-destructive commands may run in the current worktree, but destructive cleanup, reset, stash, force-push, worktree-removal, and protected-branch bypass guards still apply.
|
|
55
|
+
|
|
56
|
+
## `guardian_start` Session And Worktree Ownership
|
|
57
|
+
|
|
58
|
+
Guardian owns a session worktree after the chat-system hook creates or attaches one, unless repo config disables automatic ownership with `autoStart: false`. `guardian_start` is the explicit path to create or attach ownership before the hook has run.
|
|
59
|
+
|
|
60
|
+
Ownership is proven by Guardian state or by token-bound target resolution, not by branch prefix alone. Explicit descriptive branch names are allowed when state or a token-bound target proves the exact branch/worktree binding.
|
|
61
|
+
|
|
62
|
+
If older or corrupted state records an active session on the primary repo worktree or a protected branch, `guardian_start` with `createWorktree: true` is the repair path. It creates a fresh Guardian worktree, overwrites the poisoned binding, and leaves the primary worktree untouched. Without allowed worktree creation, Guardian must block rather than return the bad binding.
|
|
63
|
+
|
|
64
|
+
## Session Routing And Mismatch Fail-Closed Rules
|
|
65
|
+
|
|
66
|
+
Guardian hooks do not move the OpenCode host process cwd. Before safe shell or Git tools run, Guardian rewrites the execution directory to the recorded worktree and then reapplies destructive-command guards.
|
|
67
|
+
|
|
68
|
+
Direct file mutation tool paths under the primary repo are rewritten into the recorded worktree when the session owns one. They are blocked when the recorded worktree cannot be validated. Missing, unresolvable, unrecorded, stale, primary-worktree, or protected-branch session bindings fail closed.
|
|
69
|
+
|
|
70
|
+
## Protected Branch Bypass Prevention
|
|
71
|
+
|
|
72
|
+
Protected branches include the configured `protectedBranches`, with defaults including `main`, `master`, `develop`, and `production`.
|
|
73
|
+
|
|
74
|
+
When config context is available, Guardian blocks manual bypasses such as pushing `HEAD` or `guardian/*` directly to a protected branch, and merging `guardian/*` while already on a protected branch. Normal Guardian work must finish through `guardian_done` or the lower-level finish tools. A command wrapper must not ask the agent to bypass this policy with raw push, merge, switch, or branch commands.
|
|
75
|
+
|
|
76
|
+
## `guardian_done` Implementation-Done Policy
|
|
77
|
+
|
|
78
|
+
`guardian_done` is the user-facing implementation-done workflow. Run `mode: "plan"` first and inspect the selected lane, preflight facts, dirty files, blockers, and confirmation posture.
|
|
79
|
+
|
|
80
|
+
The supported lanes are:
|
|
81
|
+
|
|
82
|
+
- `session-finish`: delegates recorded session worktrees to `guardian_finish`.
|
|
83
|
+
- `cleanup-only`: routes clean primary-base cleanup through `guardian_finish_workflow`.
|
|
84
|
+
- `primary-main-publish`: handles dirty protected primary `baseBranch` work only with an explicit `commitMessage`, explicit confirmation, and the matching internally token-bound dirty snapshot.
|
|
85
|
+
|
|
86
|
+
Primary-main apply creates a pre-commit safety ref, commits only token-bound dirty paths, pushes normally to the configured remote/base branch, fetches, proves remote reachability, and returns a separate cleanup plan. It must not silently apply cleanup, force-push, mutate stashes, delete remote branches, merge PRs, or treat old primary/protected session records as ownership.
|
|
87
|
+
|
|
88
|
+
In plugin flow, the internal plan token may be cached and reused only when session, repo, options, and dirty snapshot still match the plan. Blank token values and confirmation placeholders are treated as absent.
|
|
89
|
+
|
|
90
|
+
## `guardian_finish` Session Finish Policy
|
|
91
|
+
|
|
92
|
+
Use `guardian_finish` for explicit low-level session finishing. Prefer `guardian_done` for normal completion.
|
|
93
|
+
|
|
94
|
+
Finish always creates a safety ref before risky operations and reports preflight facts and blockers. Dirty worktrees block finish unless every dirty path matches explicit `allowDirtyPaths` config. Allowed dirty paths are reported as `allowedDirtyFiles` and left untouched. Guardian must not delete, stash, revert, stage, or commit allowed dirty files.
|
|
95
|
+
|
|
96
|
+
`create-pr` pushes the session branch and suggests a PR command; it does not create a PR natively. `merge-to-base` requires explicit approval. Cleanup can run only when `autoCleanup` or `allowCleanup` is enabled and ancestry is proven.
|
|
97
|
+
|
|
98
|
+
## `guardian_finish_workflow` Cleanup Policy
|
|
99
|
+
|
|
100
|
+
Use `guardian_finish_workflow` only after implementation is already committed, pushed, merged to the configured base, and synced locally.
|
|
101
|
+
|
|
102
|
+
Run `mode: "plan"` first. The workflow verifies the primary worktree is clean, stash policy is satisfied, the configured remote can be fetched, and redundant cleanup candidates are already merged to the freshly resolved `remote/baseBranch` commit. It discovers Guardian-root worktrees and non-protected, checked-out-nowhere local branches whose heads are ancestors of that base commit. At most 25 cleanup candidates can be applied from one plan.
|
|
103
|
+
|
|
104
|
+
Run `mode: "apply"` only with the returned token after explicit confirmation. The token binds the resolved base commit and cleanup targets. Apply re-plans each target and delegates deletion to `guardian_delete_worktree`. It must not create commits, choose commit messages, merge protected branches, mutate stashes, force-delete branches, or run raw filesystem/Git cleanup. Any blocker fails closed and apply deletes nothing until a fresh plan has no blockers.
|
|
105
|
+
|
|
106
|
+
## `guardian_preserve` Policy
|
|
107
|
+
|
|
108
|
+
`guardian_preserve` marks the current Guardian-owned session terminal/preserved and creates a preserved ref. It does not delete, clean, reset, stash, push, or merge.
|
|
109
|
+
|
|
110
|
+
Preservation is not a permanent retention instruction. Preserved worktrees remain cleanup-eligible through `guardian_delete_worktree` once that tool proves deletion is safe.
|
|
111
|
+
|
|
112
|
+
## `guardian_unblock_finish` Policy
|
|
113
|
+
|
|
114
|
+
Use `guardian_unblock_finish` only when `guardian_finish` is blocked by narrow generated review-rating artifacts. It is not a broad cleanup or source-change commit tool.
|
|
115
|
+
|
|
116
|
+
Run `mode: "plan"` first. The supported action is `commit-review-artifacts`, which may commit only `.milestones/reviews/*impl-rating-YYYYMMDD.md` or `.milestones/reviews/*impl-rating-YYYYMMDD.txt` review artifacts. If Guardian state does not record the session, plan must receive an explicit `branch` or `worktreePath` that resolves exactly one checked-out worktree under the configured Guardian worktree root.
|
|
117
|
+
|
|
118
|
+
Run `mode: "apply"` only with the fresh token and the same explicit branch or worktree path when state is still missing. Apply creates a safety ref, stages only approved review artifacts, commits them, and updates Guardian state. It refuses mixed dirty/source paths, renames/copies, symlink artifacts, deletions, ignored files, stashes, and cleanup.
|
|
119
|
+
|
|
120
|
+
## `guardian_delete_worktree` Worktree Deletion Policy
|
|
121
|
+
|
|
122
|
+
Use `guardian_delete_worktree` for stale, preserved, finished, orphaned, or explicitly abandoned Guardian worktree cleanup. Raw `git worktree remove`, `git worktree prune`, raw branch deletion, and filesystem deletion are forbidden substitutes.
|
|
123
|
+
|
|
124
|
+
Run `mode: "plan"` first. It resolves exactly one target by `targetPath`, `sessionId`, or `branch`, runs preflight checks, and returns a confirm token. Run `mode: "apply"` only with that token and the same options after explicit confirmation.
|
|
125
|
+
|
|
126
|
+
Apply recomputes the token from the normalized repo root, target kind, target path, worktree-listed state, branch or detached marker, HEAD, session identity/status, `deleteBranch`, `abandonUnmerged`, ancestry evidence, unmerged commits, and `allowIgnoredFiles`. Stale or missing tokens block.
|
|
127
|
+
|
|
128
|
+
Apply refuses primary repo worktree deletion, current execution worktree deletion, dirty or untracked targets, protected-branch worktrees even when `deleteBranch` is false, detached HEADs, ignored files unless `allowIgnoredFiles: true` is present in both plan and apply, and repo stashes unless `allowStashIfUnrelated` is enabled. Passing the primary repo as `targetPath` remains blocked.
|
|
129
|
+
|
|
130
|
+
Apply creates a safety ref before non-force `git worktree remove <path>`. Branch deletion is opt-in with `deleteBranch: true`; by default it requires ancestry proof and uses non-force `git branch -d`.
|
|
131
|
+
|
|
132
|
+
## `guardian_delete_worktree` Orphan And Stale-Branch Policy
|
|
133
|
+
|
|
134
|
+
If a recorded Guardian session's worktree path is absent from `git worktree list`, or state points at the primary repo path while the recorded Guardian branch is checked out nowhere, `guardian_delete_worktree` can perform branch-only orphan cleanup with `deleteBranch: true`. It deletes no filesystem path, does not allow primary repo worktree deletion, verifies the branch exists, verifies it is checked out nowhere, verifies it is not protected, creates a safety ref, and uses non-force branch deletion after ancestry and checkout checks pass.
|
|
135
|
+
|
|
136
|
+
If a local Guardian branch remains after its worktree and active state are gone, pass the exact `branch` or terminal `sessionId` with `deleteBranch: true` for stale-branch cleanup. Terminal states include `deleted`, `abandoned`, `finished`, and `preserved`. This branch-only path is allowed only when terminal Guardian state or matching `refs/opencode-guardian` safety refs prove ownership. Branch prefixes alone are not ownership proof.
|
|
137
|
+
|
|
138
|
+
## `guardian_delete_worktree` Unmerged Abandon Policy
|
|
139
|
+
|
|
140
|
+
Intentional unmerged local abandonment requires `deleteBranch: true` and `abandonUnmerged: true` in both plan and apply. Plan reports ancestry evidence, the base ref, and unmerged commits that remain recoverable from the safety ref.
|
|
141
|
+
|
|
142
|
+
Apply creates the safety ref first, then removes the clean Guardian worktree and deletes the local branch, recording the session as `abandoned`. This lane does not relax primary/current worktree, dirty-target, protected-branch, checked-out orphan branch, stale-token, stash, ignored-file, or missing-safety-ref blockers.
|
|
143
|
+
|
|
144
|
+
## `guardian_delete_paths` Exact Path Deletion Policy
|
|
145
|
+
|
|
146
|
+
Use `guardian_delete_paths` when the user intentionally wants to delete exact files or directories that are not Guardian hygiene findings, including source files.
|
|
147
|
+
|
|
148
|
+
Run `mode: "plan"` first with explicit `paths`. The plan reports each path's repo-relative path, absolute path, kind, tracked/ignored/untracked status, tracked contents, and blockers. Apply only after explicit confirmation with `mode: "apply"` and `confirmDelete: true`; low-level direct calls also require the matching `confirmToken`.
|
|
149
|
+
|
|
150
|
+
Tracked source deletion requires `allowTracked: true`. Directory deletion requires `allowRecursive: true`. Worktree deletion must use `guardian_delete_worktree`, not `guardian_delete_paths`.
|
|
151
|
+
|
|
152
|
+
The tool blocks paths outside the repo, the repo root, `.git`, `.opencode`, dependency roots such as `node_modules` and `vendor`, configured or registered Guardian worktree roots, the current worktree root, missing paths, symlink roots, overlapping selections, tracked contents without `allowTracked: true`, and directories without `allowRecursive: true`.
|
|
153
|
+
|
|
154
|
+
Apply re-runs the same fingerprinted preflight immediately before deletion, deletes files with internal Node `fs` APIs, and does not stage changes. Tracked deletions remain visible in `git status` for review and commit.
|
|
155
|
+
|
|
156
|
+
## `guardian_hygiene` Scan Policy
|
|
157
|
+
|
|
158
|
+
`guardian_hygiene` without `mode` is report-only. It detects untracked or ignored scratch artifacts, nested Git repositories, suspicious research dumps, generated cache roots, and protected exclusions. Scan output does not authorize deletion.
|
|
159
|
+
|
|
160
|
+
`guardian_status`, `guardian_recover`, and `guardian_hygiene` scan output are evidence-only surfaces. Research clones, downloaded upstream repos, generated fixtures, and temporary test data should live outside the active project tree, preferably under OS temp space such as `$TMPDIR/opencode/<repo>/<session>/`.
|
|
161
|
+
|
|
162
|
+
## `guardian_hygiene` Plan/Apply Cleanup Policy
|
|
163
|
+
|
|
164
|
+
`guardian_hygiene` is the single hygiene scan/plan/apply surface. Use `mode: "plan"` before cleanup; inspect exact approved targets, blockers, and summary; then get explicit user confirmation. Apply with `mode: "apply"` and `confirmDelete: true` using the same cleanup options.
|
|
165
|
+
|
|
166
|
+
Default cleanup includes current hygiene finding categories: known scratch artifacts, clean nested Git repositories, suspicious residue roots, generated `node-compile-cache/`, `node-coverage-*`, and `tsx-<digits>/` cache roots. Dirty `nested-git` findings require explicit `allowDirtyNestedGit: true`.
|
|
167
|
+
|
|
168
|
+
The plugin tool flow caches the plan token only for matching session, repo, and options. Empty token values and `CONFIRM_DELETE` placeholders are treated as absent; the cached token may be injected only when the plan matches. Low-level direct calls to `guardianHygiene` still require the matching `confirmToken`.
|
|
169
|
+
|
|
170
|
+
Apply re-runs preflight and removes only token-bound approved paths using internal Node `fs` APIs. It never suggests or shells out to broad cleanup commands. Cleanup blocks tracked files, protected directories, configured or registered Guardian worktrees, paths outside the repo root, `.git`, symlink cleanup roots, missing selected paths, stale fingerprints, and selected roots with unexpected tracked contents.
|
|
171
|
+
|
|
172
|
+
## Stale Tokens, Fingerprints, And Safety Ref Posture
|
|
173
|
+
|
|
174
|
+
Plan output is evidence, not approval. Apply must bind to the current preflight state through a fresh confirm token, cached internal token, or fingerprint appropriate to that tool surface.
|
|
175
|
+
|
|
176
|
+
Tokens and fingerprints must cover the target identity and safety-relevant options. Apply must re-run preflight and block when token data is stale, missing, mismatched, or derived from a different repo, session, dirty snapshot, base commit, path list, target, or cleanup option set.
|
|
177
|
+
|
|
178
|
+
Safety refs are required before risky finish, preserve, deletion, orphan cleanup, stale-branch cleanup, explicit unmerged abandon, and finish-unblock operations. Safety refs are recovery evidence; they do not authorize raw cleanup or bypass plan/apply gates.
|
|
179
|
+
|
|
180
|
+
## Codex Adapter Hook Policy
|
|
181
|
+
|
|
182
|
+
The Codex adapter must route Guardian workflows through `codex/hooks/guardian-hook.ts` in this source repository, or through `node_modules/opencode-worktree-guardian/codex/hooks/guardian-hook.ts` after package install. Codex plugin hooks invoke the same adapter from `hooks/hooks.json`.
|
|
183
|
+
|
|
184
|
+
For `guardian done`, `guardian_hygiene`, `guardian_delete_paths`, and `guardian_finish_workflow`, Codex usage must run plan first and apply only after explicit user confirmation with the same options. The adapter may reuse matching cached internal plan tokens and must not ask users to copy internal confirm tokens.
|
|
185
|
+
|
|
186
|
+
The Codex adapter must never replace Guardian workflows with raw `git reset --hard`, `git clean -fd`, `git worktree remove`, `git worktree prune`, `git branch -D`, `git stash drop`, `git stash clear`, force-push, broad filesystem deletion, or protected-branch bypass commands.
|
|
187
|
+
|
|
188
|
+
## Legacy Alias Deprecation And Removal Policy
|
|
189
|
+
|
|
190
|
+
Legacy command or tool aliases that overlap a current safety surface must be removed only as an intentional public-surface change. Removal must update native tool registries, slash command rewrites, TUI commands, packaged markdown commands, README, skills, package smoke expectations, and contract tests.
|
|
191
|
+
|
|
192
|
+
Removed aliases must not remain documented as active commands. Historical notes may mention them only as removed or deprecated legacy surfaces. The current hygiene cleanup authority is `guardian_hygiene` with scan, plan, and apply modes; there is no separate active hygiene cleanup alias.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Publishing Policy
|
|
2
|
+
|
|
3
|
+
This project publishes only from an intentional release branch after the release checklist passes. Do not run `npm publish` from routine development or remediation tasks.
|
|
4
|
+
|
|
5
|
+
## Versioning Policy
|
|
6
|
+
|
|
7
|
+
- The package uses semver.
|
|
8
|
+
- Patch versions are for compatible fixes, documentation updates shipped with the package, and release-process corrections.
|
|
9
|
+
- Minor versions are for new compatible public Guardian surfaces, new host or adapter support, or substantial safety-policy additions.
|
|
10
|
+
- Major versions are for breaking public-surface changes, removed commands or tools, changed confirmation semantics, changed package exports, or changed host requirements.
|
|
11
|
+
- `package.json` is the source of the package version being prepared. `CHANGELOG.md` records public-surface changes but must not claim the version has been published until publication has happened.
|
|
12
|
+
|
|
13
|
+
Before publishing, update the version intentionally with npm's versioning flow or another explicit semver change. Do not change the version as a side effect of unrelated work.
|
|
14
|
+
|
|
15
|
+
## What Must Be True Before Publishing
|
|
16
|
+
|
|
17
|
+
- `docs/release-checklist.md` has been completed.
|
|
18
|
+
- `npm run test:readiness` exits 0.
|
|
19
|
+
- `npm audit --registry=https://registry.npmjs.org --omit=dev --audit-level=low` exits 0.
|
|
20
|
+
- `npm pack --dry-run --json` has been inspected and contains only intended package files.
|
|
21
|
+
- OpenCode host smoke and manual disposable-repo host QA have passed.
|
|
22
|
+
- Codex adapter tests and manual disposable-repo adapter QA have passed.
|
|
23
|
+
- The README, safety policy, OpenCode skill docs, Codex skill docs, and packaged command docs agree on the same public surface.
|
|
24
|
+
- `CHANGELOG.md` has a dated section for the version being published, moved from `Unreleased`, with no claim that the version shipped before the publish actually happens.
|
|
25
|
+
- The worktree is clean except for intentional release changes.
|
|
26
|
+
|
|
27
|
+
## npm Publish Notes
|
|
28
|
+
|
|
29
|
+
Use npm's dry run before publication:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm pack --dry-run --json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Use npm publish only after the dry-run package contents and release checklist are accepted:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm publish --access public
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Do not publish if the package includes local evidence files, `.omo/`, `.milestones/`, `.worktrees/`, generated caches, test output, or untracked scratch data.
|
|
42
|
+
|
|
43
|
+
If publication fails after a version bump or tag was created locally, do not reuse ambiguous state. Record the failure, inspect npm and git state, and either retry the same unchanged artifact when safe or prepare a new patch version.
|
|
44
|
+
|
|
45
|
+
## Commit And Tag Policy
|
|
46
|
+
|
|
47
|
+
- The release commit should contain the version bump, changelog entry, and any release-only documentation updates.
|
|
48
|
+
- Tag only the commit that was actually published.
|
|
49
|
+
- Use an annotated git tag named `v<version>`, for example `v0.1.0`.
|
|
50
|
+
- Push the tag only after `npm publish` succeeds for the same version.
|
|
51
|
+
- If `npm publish` has not succeeded, do not create or push a release tag that implies the package is public.
|
|
52
|
+
|
|
53
|
+
## Post-Publish Checks
|
|
54
|
+
|
|
55
|
+
After publish, verify the package metadata and install path from npm, then update release notes if the project uses GitHub releases. Do not backfill the changelog with unverifiable claims; record only what was actually published and tagged.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Release Checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist before any public package publication. Run it from the repository root on a clean release branch.
|
|
4
|
+
|
|
5
|
+
## Preconditions
|
|
6
|
+
|
|
7
|
+
- `package.json` names the intended package, version, exports, files, scripts, engines, repository, license, and `prepublishOnly` policy.
|
|
8
|
+
- `CHANGELOG.md` has an `Unreleased` section and does not claim a version has shipped before publication.
|
|
9
|
+
- `README.md`, `docs/adr/0001-guardian-safety-policy.md`, OpenCode command docs, OpenCode skill docs, and Codex adapter docs describe the same public tool surface.
|
|
10
|
+
- The worktree contains only intentional release changes.
|
|
11
|
+
- No `npm publish` command has been run from this checklist.
|
|
12
|
+
|
|
13
|
+
## Required Commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run test:readiness
|
|
17
|
+
npm audit --registry=https://registry.npmjs.org --omit=dev --audit-level=low
|
|
18
|
+
npm pack --dry-run --json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The release is blocked if any required command exits non-zero, reports a dependency audit issue at or above the configured audit level, or shows an unexpected package file list.
|
|
22
|
+
|
|
23
|
+
## Package Dry-Run Inspection
|
|
24
|
+
|
|
25
|
+
Inspect `npm pack --dry-run --json` output and confirm the package includes the expected public assets:
|
|
26
|
+
|
|
27
|
+
- `README.md`
|
|
28
|
+
- `LICENSE`
|
|
29
|
+
- `CHANGELOG.md`
|
|
30
|
+
- `commands/`
|
|
31
|
+
- `skills/`
|
|
32
|
+
- `src/`
|
|
33
|
+
- `scripts/`
|
|
34
|
+
- `codex/`
|
|
35
|
+
- `docs/`
|
|
36
|
+
|
|
37
|
+
Confirm the output does not include local evidence files, worktree state, test logs, untracked scratch directories, or generated caches.
|
|
38
|
+
|
|
39
|
+
## OpenCode Host Check
|
|
40
|
+
|
|
41
|
+
Run the deterministic host smoke before manual host validation:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm run test:smoke:host
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
For manual QA, use a disposable Git repository only:
|
|
48
|
+
|
|
49
|
+
1. Install or shim the release candidate into the disposable repo.
|
|
50
|
+
2. Start OpenCode from that disposable repo.
|
|
51
|
+
3. Confirm the Guardian plugin loads and exposes `guardian_status`.
|
|
52
|
+
4. Confirm `guardian_status` returns repository inventory.
|
|
53
|
+
5. Confirm a safe Git write is routed into the Guardian-owned worktree when ownership exists.
|
|
54
|
+
6. Confirm a raw destructive command such as `git reset --hard` is blocked before mutation.
|
|
55
|
+
|
|
56
|
+
Do not validate manual OpenCode behavior against a live project repository.
|
|
57
|
+
|
|
58
|
+
## Codex Adapter Check
|
|
59
|
+
|
|
60
|
+
Run the Codex adapter smoke coverage before publication:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm run test -- test/codex-adapter.test.ts test/package-smoke.test.ts
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Then inspect `npm pack --dry-run --json` output and confirm the packed package contains:
|
|
67
|
+
|
|
68
|
+
- `codex/.codex-plugin/plugin.json`
|
|
69
|
+
- `codex/hooks/hooks.json`
|
|
70
|
+
- `codex/hooks/guardian-hook.ts`
|
|
71
|
+
- `codex/skills/worktree-guardian/SKILL.md`
|
|
72
|
+
|
|
73
|
+
For manual QA, invoke the adapter only in a disposable Git repository. Confirm `guardian_status` returns readable output and the pre-tool hook blocks a raw destructive command such as `git reset --hard`.
|
|
74
|
+
|
|
75
|
+
## Final Gate
|
|
76
|
+
|
|
77
|
+
Publish only when:
|
|
78
|
+
|
|
79
|
+
- all required commands pass,
|
|
80
|
+
- OpenCode host smoke and manual host checks pass,
|
|
81
|
+
- Codex adapter tests and manual adapter checks pass,
|
|
82
|
+
- `npm pack --dry-run --json` contains exactly the intended public package surface,
|
|
83
|
+
- `CHANGELOG.md` has been updated for the release without inventing unpublished history,
|
|
84
|
+
- the git tag and package version policy in `docs/publishing.md` is satisfied.
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-worktree-guardian",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenCode plugin that guards Git worktree workflows from unsafe cleanup, reset, stash, and force-push operations.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./codex": "./codex/hooks/guardian-hook.ts",
|
|
10
|
+
"./server": "./src/index.ts",
|
|
11
|
+
"./tui": "./src/tui.ts"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"CHANGELOG.md",
|
|
15
|
+
"codex",
|
|
16
|
+
"commands",
|
|
17
|
+
"docs",
|
|
18
|
+
"src",
|
|
19
|
+
"scripts",
|
|
20
|
+
"skills",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "node scripts/with-safe-node-temp.mjs -- node --import tsx --test test/*.test.ts",
|
|
26
|
+
"lint": "npm run typecheck",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"coverage": "node scripts/with-safe-node-temp.mjs -- node --import tsx --test --experimental-test-coverage --test-coverage-lines=80 test/*.test.ts",
|
|
29
|
+
"verify": "npm run lint && npm run coverage",
|
|
30
|
+
"audit:deps": "npm audit --registry=https://registry.npmjs.org --omit=dev",
|
|
31
|
+
"test:contract": "node scripts/with-safe-node-temp.mjs -- node --import tsx --test test/plugin-contract.test.ts test/index.test.ts",
|
|
32
|
+
"test:smoke:package": "node scripts/with-safe-node-temp.mjs -- node --import tsx --test test/package-smoke.test.ts",
|
|
33
|
+
"test:smoke:host": "node scripts/with-safe-node-temp.mjs -- node --import tsx --test test/opencode-host-smoke.test.ts",
|
|
34
|
+
"test:readiness": "node scripts/with-safe-node-temp.mjs -- node --import tsx scripts/readiness.ts",
|
|
35
|
+
"prepublishOnly": "npm run test:readiness"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20",
|
|
39
|
+
"opencode": ">=1.14.48"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@opencode-ai/plugin": "^1.14.48",
|
|
43
|
+
"tsx": "^4.22.4",
|
|
44
|
+
"zod": "^4.4.3"
|
|
45
|
+
},
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/nashgao/opencode-worktree-guardian.git"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/nashgao/opencode-worktree-guardian#readme",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/nashgao/opencode-worktree-guardian/issues"
|
|
54
|
+
},
|
|
55
|
+
"keywords": [
|
|
56
|
+
"opencode",
|
|
57
|
+
"plugin",
|
|
58
|
+
"git",
|
|
59
|
+
"worktree",
|
|
60
|
+
"guardian",
|
|
61
|
+
"safety"
|
|
62
|
+
],
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/node": "^25.7.0",
|
|
65
|
+
"typescript": "^6.0.3"
|
|
66
|
+
},
|
|
67
|
+
"types": "./src/index.ts",
|
|
68
|
+
"directories": {
|
|
69
|
+
"test": "test"
|
|
70
|
+
},
|
|
71
|
+
"author": ""
|
|
72
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const authoredRoots = ["src", "test", "scripts"];
|
|
8
|
+
const forbiddenExtensions = new Set([".js", ".mjs", ".cjs"]);
|
|
9
|
+
const allowedAuthoredJavaScript = new Set(["scripts/with-safe-node-temp.mjs"]);
|
|
10
|
+
const commandTimeoutMs = 10 * 60 * 1000;
|
|
11
|
+
const commands: Array<[string, string[]]> = [
|
|
12
|
+
["npm", ["run", "verify"]],
|
|
13
|
+
["npm", ["run", "audit:deps"]],
|
|
14
|
+
["npm", ["run", "test:contract"]],
|
|
15
|
+
["npm", ["run", "test:smoke:package"]],
|
|
16
|
+
["npm", ["run", "test:smoke:host"]],
|
|
17
|
+
["npm", ["pack", "--dry-run", "--json"]],
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function normalizeRelative(value: string) {
|
|
21
|
+
return value.split(path.sep).join("/");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function collectForbiddenAuthoredFiles(root: string) {
|
|
25
|
+
const found: string[] = [];
|
|
26
|
+
async function visit(directory: string) {
|
|
27
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
const fullPath = path.join(directory, entry.name);
|
|
30
|
+
if (entry.isDirectory()) {
|
|
31
|
+
await visit(fullPath);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const relative = normalizeRelative(path.relative(root, fullPath));
|
|
35
|
+
if (forbiddenExtensions.has(path.extname(entry.name)) && !allowedAuthoredJavaScript.has(relative)) found.push(fullPath);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const authoredRoot of authoredRoots) await visit(path.join(root, authoredRoot));
|
|
40
|
+
return found;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const forbiddenAuthoredFiles = await collectForbiddenAuthoredFiles(process.cwd());
|
|
44
|
+
if (forbiddenAuthoredFiles.length) {
|
|
45
|
+
console.error("Authored JavaScript files are not allowed:");
|
|
46
|
+
for (const file of forbiddenAuthoredFiles) console.error(`- ${normalizeRelative(path.relative(process.cwd(), file))}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const [command, args] of commands) {
|
|
51
|
+
const label = [command, ...args].join(" ");
|
|
52
|
+
process.stdout.write(`\n> ${label}\n`);
|
|
53
|
+
try {
|
|
54
|
+
const { stdout, stderr } = await execFileAsync(command, args, {
|
|
55
|
+
maxBuffer: 30 * 1024 * 1024,
|
|
56
|
+
timeout: commandTimeoutMs,
|
|
57
|
+
killSignal: "SIGTERM",
|
|
58
|
+
env: { ...process.env, CI: "true", GIT_TERMINAL_PROMPT: "0" },
|
|
59
|
+
});
|
|
60
|
+
if (stdout) process.stdout.write(stdout);
|
|
61
|
+
if (stderr) process.stderr.write(stderr);
|
|
62
|
+
} catch (error: unknown) {
|
|
63
|
+
const commandError = error as { stdout?: string; stderr?: string; code?: number | string | null; signal?: NodeJS.Signals | null; killed?: boolean };
|
|
64
|
+
if (commandError.stdout) process.stdout.write(commandError.stdout);
|
|
65
|
+
if (commandError.stderr) process.stderr.write(commandError.stderr);
|
|
66
|
+
if (commandError.killed || commandError.signal === "SIGTERM") {
|
|
67
|
+
console.error(`\nReadiness command timed out after ${commandTimeoutMs}ms: ${label}`);
|
|
68
|
+
process.exit(124);
|
|
69
|
+
}
|
|
70
|
+
console.error(`\nReadiness command failed: ${label}`);
|
|
71
|
+
process.exit(typeof commandError.code === "number" ? commandError.code : 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log("\nReadiness checks passed.");
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
const safeTempDirectoryName = "opencode-worktree-guardian-node";
|
|
8
|
+
const fallbackTempBases = [
|
|
9
|
+
path.join("/tmp", "opencode"),
|
|
10
|
+
path.join(os.homedir(), ".cache", "opencode", "tmp"),
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function isSameOrInside(candidate, root) {
|
|
14
|
+
const relative = path.relative(root, candidate);
|
|
15
|
+
return relative === "" || Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function resolveSafeTempRoot(projectRoot) {
|
|
19
|
+
for (const candidate of [os.tmpdir(), ...fallbackTempBases]) {
|
|
20
|
+
try {
|
|
21
|
+
const candidatePath = path.resolve(candidate);
|
|
22
|
+
await fs.mkdir(candidatePath, { recursive: true });
|
|
23
|
+
const realCandidate = await fs.realpath(candidatePath);
|
|
24
|
+
if (isSameOrInside(realCandidate, projectRoot)) continue;
|
|
25
|
+
const safeRoot = path.join(realCandidate, safeTempDirectoryName);
|
|
26
|
+
await fs.mkdir(safeRoot, { recursive: true });
|
|
27
|
+
return fs.realpath(safeRoot);
|
|
28
|
+
} catch {}
|
|
29
|
+
}
|
|
30
|
+
throw new Error("Unable to resolve an external temp directory for Node test scripts");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function envPathInsideProject(value, projectRoot) {
|
|
34
|
+
if (!value) return false;
|
|
35
|
+
return isSameOrInside(path.resolve(value), projectRoot);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const separatorIndex = process.argv.indexOf("--");
|
|
39
|
+
const command = separatorIndex >= 0 ? process.argv[separatorIndex + 1] : process.argv[2];
|
|
40
|
+
const args = separatorIndex >= 0 ? process.argv.slice(separatorIndex + 2) : process.argv.slice(3);
|
|
41
|
+
|
|
42
|
+
if (!command) {
|
|
43
|
+
console.error("Usage: node scripts/with-safe-node-temp.mjs -- <command> [...args]");
|
|
44
|
+
process.exit(2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const projectRoot = await fs.realpath(process.cwd());
|
|
48
|
+
const safeTempRoot = await resolveSafeTempRoot(projectRoot);
|
|
49
|
+
const nodeCompileCache = path.join(safeTempRoot, "node-compile-cache");
|
|
50
|
+
await fs.mkdir(nodeCompileCache, { recursive: true });
|
|
51
|
+
|
|
52
|
+
const env = {
|
|
53
|
+
...process.env,
|
|
54
|
+
TMPDIR: safeTempRoot,
|
|
55
|
+
TMP: safeTempRoot,
|
|
56
|
+
TEMP: safeTempRoot,
|
|
57
|
+
NODE_COMPILE_CACHE: envPathInsideProject(process.env.NODE_COMPILE_CACHE, projectRoot) ? nodeCompileCache : process.env.NODE_COMPILE_CACHE ?? nodeCompileCache,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (args.includes("--experimental-test-coverage") || envPathInsideProject(process.env.NODE_V8_COVERAGE, projectRoot)) {
|
|
61
|
+
const existingCoverage = process.env.NODE_V8_COVERAGE;
|
|
62
|
+
env.NODE_V8_COVERAGE = existingCoverage && !envPathInsideProject(existingCoverage, projectRoot)
|
|
63
|
+
? existingCoverage
|
|
64
|
+
: path.join(safeTempRoot, `node-coverage-${process.pid}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const child = spawn(command, args, { stdio: "inherit", env });
|
|
68
|
+
child.on("error", (error) => {
|
|
69
|
+
console.error(error.message);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
});
|
|
72
|
+
child.on("exit", (code, signal) => {
|
|
73
|
+
if (signal) {
|
|
74
|
+
console.error(`${command} exited with signal ${signal}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
process.exit(code ?? 1);
|
|
78
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Worktree Guardian
|
|
2
|
+
|
|
3
|
+
Use the native guardian tools for worktree inspection and completion. The skill is guidance only; the plugin hooks enforce safety.
|
|
4
|
+
|
|
5
|
+
Canonical safety policy: [ADR 0001: Guardian Safety Policy](../../docs/adr/0001-guardian-safety-policy.md).
|
|
6
|
+
|
|
7
|
+
## Packaged command surface
|
|
8
|
+
|
|
9
|
+
- Hosts with packaged plugin command discovery, such as `oh-my-openagent`, can expose this package's top-level `commands/*.md` files as namespaced commands like `/opencode-worktree-guardian:status`, `/opencode-worktree-guardian:hygiene`, `/opencode-worktree-guardian:delete-paths`, and `/opencode-worktree-guardian:delete-worktree`.
|
|
10
|
+
- Treat those slash commands as prompt wrappers only. The native `guardian_*` tools remain the authority for safety checks and mutation gates.
|
|
11
|
+
- Native OpenCode user/project commands are separate files under `~/.config/opencode/commands/*.md` or `<repo>/.opencode/commands/*.md`.
|
|
12
|
+
|
|
13
|
+
## Rules
|
|
14
|
+
|
|
15
|
+
- Do not run raw cleanup, reset, stash mutation, force-push, worktree removal, raw `git worktree add` outside Guardian-owned roots, raw branch deletion, or `rm -rf` against worktrees. Use the matching native Guardian tool.
|
|
16
|
+
- Use `guardian_status` for read-only inventory.
|
|
17
|
+
- Use `guardian_done` for normal implementation completion. Prefer it over raw protected-branch push/merge commands or low-level finish tools unless the user explicitly asks for those tools.
|
|
18
|
+
- Use `guardian_hygiene` for hygiene scan/plan/apply cleanup. With no `mode`, it scans only. For cleanup, first run `mode: "plan"`; inspect exact approved targets and blockers, get explicit user confirmation, then run `mode: "apply"` with `confirmDelete: true`.
|
|
19
|
+
- Use `guardian_delete_paths` when the user intentionally wants exact files or directories deleted, including tracked source only with explicit `allowTracked: true`. Worktree deletion must use `guardian_delete_worktree`.
|
|
20
|
+
- Use `guardian_report_html` or `/guardian report` when the user wants a browser-readable branch/worktree/session report. It writes a static offline file at `.git/opencode-guardian/report.html` and returns the exact path.
|
|
21
|
+
- Use `guardian_delete_worktree` only when the user explicitly wants Guardian-mediated worktree, orphan-branch, stale-branch, or unmerged-abandon cleanup. First run `mode: "plan"`; only run `mode: "apply"` with the returned `confirmToken` after checking blockers and confirming intent.
|
|
22
|
+
- Use `guardian_unblock_finish` only when `guardian_finish` is blocked by narrow generated review artifacts. First run `mode: "plan"`; only apply after explicit confirmation with the returned `confirmToken` and the same target options.
|
|
23
|
+
- Use `guardian_finish` for explicit low-level gated completion.
|
|
24
|
+
- Use `guardian_preserve` only to create a safety ref and mark a session terminal/preserved. Preserved worktrees are cleanup-eligible; do not treat preservation as a reason to keep disk state forever.
|
|
25
|
+
- Use `guardian_recover` for safety refs, orphaned sessions, stash inventory, reflog, and recovery suggestions.
|
|
26
|
+
- For mutating commands such as `git add` or `git commit`, rely on Guardian routing after the default auto-start hook or explicit `guardian_start` records a session worktree. Repo config `autoStart=false` disables automatic ownership; without recorded ownership, normal non-destructive commands run in the current worktree.
|
|
27
|
+
- If Guardian state records the current session on the primary repo worktree or a protected branch, use `guardian_start` with `createWorktree: true` to repair the session into a proper Guardian worktree. Do not use raw `git switch`, raw branch creation, or protected-branch bypass commands to escape the poisoned binding.
|
|
28
|
+
- If the plugin blocks a command, report the blocker, preserved path, branch, safety refs, and suggested guardian tool.
|
|
29
|
+
|
|
30
|
+
## Defaults
|
|
31
|
+
|
|
32
|
+
- `finishMode`: `create-pr`
|
|
33
|
+
- `branchPrefix`: default branch naming only, not ownership proof
|
|
34
|
+
- `autoStart`: enabled by default; repo config `autoStart=false` opts out of automatic ownership
|
|
35
|
+
- `autoFinish`: disabled unless repo config opts in
|
|
36
|
+
- `autoCleanup`: disabled unless repo config opts in
|
|
37
|
+
- stash mutation is never cleanup
|
|
38
|
+
- workspace hygiene scan mode is report-only; cleanup requires `guardian_hygiene mode=plan`, exact-target review, explicit confirmation, and `mode=apply confirmDelete=true`
|
|
39
|
+
- `guardian_delete_paths` is the exact intentional path deletion surface
|
|
40
|
+
- `guardian_delete_worktree` is the only worktree deletion surface
|
|
41
|
+
- read-only scan/report surfaces are evidence, not approval to mutate
|
|
42
|
+
|
|
43
|
+
## Scratch posture
|
|
44
|
+
|
|
45
|
+
- Put research clones, downloaded upstream repos, generated fixtures, and temporary test data outside the active project tree, preferably under OS temp space such as `$TMPDIR/opencode/<repo>/<session>/`.
|
|
46
|
+
- If scratch inside a repo is explicitly required, use a configured scratch root and keep it clearly session-scoped.
|
|
47
|
+
- Treat `guardian_hygiene` scan findings as evidence to report. If cleanup is approved, use `guardian_hygiene mode=plan`, inspect targets/blockers, get explicit confirmation, then apply with `confirmDelete: true`.
|
|
48
|
+
- Treat `external-temp-worktree` and `external-worktree` findings in `guardian_status`/`guardian_recover` as report-only evidence, not cleanup approval.
|
|
49
|
+
|
|
50
|
+
## Delete posture
|
|
51
|
+
|
|
52
|
+
- Never run raw `git worktree remove`, `git worktree prune`, `rm -rf`, or raw branch deletion. The hooks intentionally block those commands.
|
|
53
|
+
- Use `guardian_hygiene` for hygiene findings, `guardian_delete_paths` for exact file or directory deletion, and `guardian_delete_worktree` for Guardian worktree/branch cleanup.
|
|
54
|
+
- `guardian_delete_worktree` requires plan/apply and explicit confirmation. Branch deletion is opt-in with `deleteBranch: true`; unmerged abandonment additionally requires `abandonUnmerged: true` in both plan and apply.
|
|
55
|
+
- `guardian_status`, `guardian_recover`, and `guardian_hygiene` scan output are evidence-only. Their output can identify candidates, but it is not approval to delete.
|
|
56
|
+
|
|
57
|
+
## Recovery posture
|
|
58
|
+
|
|
59
|
+
Recovery is read-only by default. Creating recovery branches requires explicit user approval.
|
|
60
|
+
|
|
61
|
+
## Finish-unblock posture
|
|
62
|
+
|
|
63
|
+
- Never loosen `guardian_finish` dirty-worktree safety to force progress.
|
|
64
|
+
- `guardian_finish` may tolerate dirty runtime/local-state files only when every dirty path matches explicit repo config `allowDirtyPaths`. File-specific patterns match untracked runtime files, so prefer narrow entries like `.claude/logs/hooks.log` over broad directory allowlists unless the whole directory is intentionally allowed. Allowed dirty files are reported and left untouched; Guardian must not delete, stash, revert, stage, or commit them.
|
|
65
|
+
- Treat any non-matching dirty source, config, doc, or policy file as a blocker even when some runtime dirt is allowed.
|
|
66
|
+
- Prefer `guardian_unblock_finish` for the narrow case where review-rating artifacts are the only blocker.
|
|
67
|
+
- Treat plan output as evidence, not approval. Apply requires an explicit fresh confirm token for the exact current preflight.
|
|
68
|
+
- Do not use the unblocker for source changes, deletions, ignored files, stashes, or cleanup.
|
|
69
|
+
|
|
70
|
+
## Release references
|
|
71
|
+
|
|
72
|
+
- Release checklist: [docs/release-checklist.md](../../docs/release-checklist.md)
|
|
73
|
+
- Publishing policy: [docs/publishing.md](../../docs/publishing.md)
|