agents-gitflow-guard 0.0.1 → 0.0.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/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  > **Are you tired of agents skipping your GitFlow?**
4
4
 
5
- A plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) that enforces the **feature preview → baseline** merge order from local git facts
6
- agents can't skip the flow, and only you can grant an exception.
5
+ A configurable branch-role guard for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH). You define your own branches
6
+ **integration** (features merge in via PR/MR), **preview** (env endpoints), **production**, **archive** — each with its own update rules. Agents can't skip the flow, and sensitive merges stay in your hands.
7
7
 
8
8
  [中文文档](README.zh.md) · [License](LICENSE)
9
9
 
@@ -22,7 +22,7 @@ agents can't skip the flow, and only you can grant an exception.
22
22
  - [How it works — the mechanism in three lines](#how-it-works--the-mechanism-in-three-lines)
23
23
  - [Configuration Reference](#configuration-reference)
24
24
  - [Gate Matrix — what gets blocked, what passes](#gate-matrix--what-gets-blocked-what-passes)
25
- - [User Exceptions (Permits) — the only way to break the rules](#user-exceptions-permits--the-only-way-to-break-the-rules)
25
+ - [Where the human stays in control](#where-the-human-stays-in-control)
26
26
  - [Installation in detail](#installation-in-detail)
27
27
  - [FAQ](#faq)
28
28
  - [Glossary](#glossary)
@@ -46,53 +46,47 @@ dsh plugin --profile web add agents-gitflow-guard
46
46
  ```jsonc
47
47
  {
48
48
  "enabled": true,
49
- "mode": "pr",
49
+ "featurePattern": "feature/[\\w-]+",
50
50
  "branches": {
51
- "base": "develop",
52
- "preview": "staging",
53
- "trunk": "main"
51
+ "integration": ["develop"], // 集成分支: feature 经 PR 合入, 受保护
52
+ "archive": ["main"] // 归档分支: 发布后由你亲手合入
54
53
  }
55
54
  }
56
55
  ```
57
56
 
58
- This one file is the entire setup: it says "this project uses the guard", "my baseline is `develop`", "my preview is `staging`". The plugin is opt-in per project — absent or `enabled: false`, it does nothing.
57
+ This one file is the entire setup: `integration` is the **only required** role; `preview` / `production` / `archive` are optional add them only if your flow needs them. The plugin is opt-in per project — absent or `enabled: false`, it does nothing.
59
58
 
60
59
  **Step 3 — verify.** Ask the agent (or run in a DSH session) to `git push origin develop`. Expect the tool call to be denied:
61
60
 
62
61
  ```text
63
62
  Error: [gitflow-guard] 已拦截: 受保护分支「develop」禁止直推
64
- 下一步: 基线分支(develop)由 PR 合入: 先合入预览并确认(P2), 再创建指向基线的 PR
63
+ 下一步: 集成分支(develop)由 PR/MR 合入 feature: 先推 feature 分支, 再 gh pr create --base develop / glab mr create --target-branch develop
65
64
  ```
66
65
 
67
- The block message is currently Chinese by default (localization is on the [Roadmap](#roadmap)); in English it means: *blocked: protected branch `develop` — direct push forbidden. Next: baseline merges via PR — merge into preview first, get P2 confirmation, then create the PR.*
66
+ The block message is currently Chinese by default (localization is on the [Roadmap](#roadmap)); in English it means: *blocked: protected branch `develop` — direct push forbidden. Next: integration branch is updated by PR/MRpush the feature branch first, then open a PR/MR into `develop`.*
68
67
 
69
- **Done.** The guard is live for this repo. Keep reading for the [full walkthrough](#full-walkthrough--one-feature-end-to-end), or jump to [Configuration](#configuration-reference) when you're ready to map your own branch names.
68
+ **Done.** The guard is live for this repo. Keep reading for the [Configuration](#configuration-reference) to map your own branches, or the [Gate Matrix](#gate-matrix--what-gets-blocked-what-passes) for the full decision table.
70
69
 
71
70
  ### Full walkthrough — one feature, end to end
72
71
 
73
- Scenario: your team ships a login page (`feature/login-page`); baseline `develop`, preview `staging`. What you and the agent experience at every step:
72
+ Scenario: your team ships a login page (`feature/login-page`); `develop` is the integration branch, `main` the archive. What you and the agent experience at every step:
74
73
 
75
74
  | # | what the agent runs | plugin decision | what you see |
76
75
  |---|---|---|---|
77
- | 1 | `git checkout -b feature/login-page` | ✅ allow (feature work is free) | branch created |
76
+ | 1 | `git checkout -b feature/login-page` (from develop) | ✅ allow (feature work is free) | branch created |
78
77
  | 2 | `git add . && git commit -m "feat: login"` | ✅ allow | committed |
79
78
  | 3 | `git push -u origin feature/login-page` | ✅ allow (pushing your feature is fine) | pushed |
80
- | 4 | `git checkout develop && git merge feature/login-page` | 🚫 **deny** — not in preview yet | blocked with: merge into staging first (PR①), test, then P2 |
81
- | 5 | *(tries to bypass)* `git checkout develop && git merge feature/login-page` in one chained command | 🚫 **deny** branch switches are simulated per segment; no bypass | same rejection |
82
- | 6 | `gh pr create --base staging` | allow (PR①: feature preview is the flow's first step) | PR created |
83
- | 7 | *(you merge PR①)* | — | feature now in `staging`, deployed to test env |
84
- | 8 | you type in DSH chat: `feature/login-page 测试 OK,可以合入` | plugin records **P2 permit** (`grant` in audit) | confirmed |
85
- | 9 | `git checkout develop && git merge feature/login-page` | ✅ allow — order (∈ preview) + P2 both satisfied | merge succeeds |
86
- | 10 | *(after the merge)* | plugin **consumes** the P2 permit (`consume` in audit) | one-shot used up |
87
- | 11 | `gitflow-guard status` / `gitflow-guard audit` | ✅ allow (read-only) | full state & trail: grant → consume |
79
+ | 4 | `git checkout develop && git merge feature/login-page` | 🚫 **deny** — integration branch is PR/MR-only | must open a PR/MR into develop |
80
+ | 5 | `gh pr create --base develop` | allow (feature integration via PR) | PR created, you review & merge |
81
+ | 6 | `git push origin main` or merge into main | 🚫 **deny** archive is user-hand only | you archive develop → main yourself after release |
88
82
 
89
- Notice what the agent *cannot* do anywhere in this flow: skip step 6/7, self-confirm in step 8, or re-use the same confirmation in a later feature. Every exception is one explicit user action, visible in the audit.
83
+ Notice what the agent *cannot* do: merge a feature straight into `develop`, or touch `main` at all. Every sensitive merge is a deliberate human action in the PR/MR page or your own terminal.
90
84
 
91
85
  ---
92
86
 
93
87
  ## Why — the problem this plugin solves
94
88
 
95
- AI coding agents work in your repository. They are *told* — via system prompts, project instruction files (`AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `.cursorrules`, and similar), and project docs — to follow a merge flow: develop on a feature branch, merge to a preview branch (the deployed test environment), let the user confirm, then merge to baseline.
89
+ AI coding agents work in your repository. They are *told* — via system prompts, project instruction files (`AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `.cursorrules`, and similar), and project docs — to follow a merge flow: develop on a feature branch, merge into the integration branch (and your preview/production stages when you have them), and leave archive/production merges to you.
96
90
 
97
91
  **That is a soft rule.** Agents skip it, reorder it, or simply "forget" it — not out of malice, but because soft instructions are optional to a model.
98
92
 
@@ -106,44 +100,43 @@ Nobody has to remember the rules — the rules are enforced.
106
100
 
107
101
  ### Signs this plugin is for you
108
102
 
109
- - Your team works with AI agents in the repository, and you have — or want — a formal branch flow (feature preview → baseline).
110
- - An agent has already cut a corner: merged straight to the baseline without the preview, or merged before tests were confirmed. If it happened once, it will happen again — this plugin is the structural fix.
111
- - You protect your baseline/trunk but don't want to rely on human review to catch every shortcut.
112
- - Multiple features develop in parallel and land in one shared preview environment, and you need per-feature verification before the baseline.
103
+ - You have — or want — a defined branch flow, from a single `develop`-style integration branch up to multi-stage preview/production pipelines.
104
+ - An agent has already cut a corner: pushed straight into a protected branch, or merged somewhere it shouldn't. If it happened once, it will happen again — this plugin is the structural fix.
105
+ - You protect your integration/archive branches but don't want to rely on human review to catch every shortcut.
106
+ - Multiple features develop in parallel and land in one shared preview environment, and you want each entry to a stricter stage reviewed.
113
107
 
114
108
  ### Concrete scenarios
115
109
 
116
- 1. **Solo developer + agent on client projects.** You hand the agent a ticket; it "helps" by merging straight into the baseline and the preview environment goes stale. One config file per project, and the agent physically cannot merge into the baseline before the preview + your confirmation — even when you're not watching.
117
- 2. **Small team (3–10) with a CI-deployed preview.** Staging auto-deploys on merge; one day an agent merged a feature into `develop` that was never deployed or tested. From then on, every baseline merge requires: feature preview **and** your chat confirmation — a deliberate, audited act, not a forgotten one.
118
- 3. **Large team, many agents.** Agents work freely on feature branches (commit, push, sync, rebaseall allowed); the gate guarantees nothing enters the baseline unconfirmed. Feature velocity stays unchanged; only the shortcut is removed.
119
- 4. **Async collaboration.** You're not always online. The guard keeps the flow honest between your sessions; exceptions remain exclusively yours to grant, and every one of them leaves an audit trail.
110
+ 1. **Solo developer + agent on client projects.** You hand the agent a ticket; it "helps" by pushing straight into the integration branch. One small config file, and the agent physically cannot touch protected branches without a PR/MR — even when you're not watching.
111
+ 2. **Small team (3–10) with a CI-deployed preview.** Staging auto-deploys on merge; one day an agent merged a feature into `develop` without review. From then on, every entry to the protected stages requires a PR/MR — a deliberate, audited act.
112
+ 3. **Enterprise with multi-env pipelines.** Many preview endpoints plus a gated production and archive lineeach role simply gets configured, and the guard scales without extra rules.
113
+ 4. **Async collaboration.** You're not always online. The guard keeps the flow honest between your sessions; production/archive merges remain yours alone.
120
114
 
121
115
  **Not for you** (see also [What it does NOT do](#what-it-does-not-do--honest-limits)):
122
116
 
123
117
  - **Trunk-based flow** — everyone merges straight to one branch: the plugin would block constantly.
124
118
  - **Personal repo without a defined flow** — nothing to enforce, no value.
125
- - **A team unwilling to establish feature preview → baseline** — the plugin enforces a flow; it will not invent one.
119
+ - **A team unwilling to give any branch a role** — the plugin needs at least one `integration` branch to protect.
126
120
 
127
121
  ---
128
122
 
129
123
  ## What it does — capabilities
130
124
 
131
- - **Blocks, before execution**: direct push / force-push / delete of protected branches; merging a feature into the baseline before it reached the preview branch; merging into trunk; an agent trying to grant itself an exception.
132
- - **Enforces order from git facts**: "is this feature merged into preview?" is answered by `merge-base --is-ancestor` on your local repository no hosting service involved, no trusting the agent's word.
133
- - **One exception authority — you**: the user can permit early PRs, confirm a feature's tests, or allow a trunk PR. Agents can never authorize themselves.
125
+ - **Blocks, before execution**: direct push / force-push / delete of protected role branches (integration / preview / production / archive); agent merging into production or archive.
126
+ - **Role-driven, fully configurable**: `integration` is the only required role; `preview` / `production` / `archive` are optional arrays of branch names or regexes, each with its own update rules (`pr` / `flexible`, `mergeBy`).
127
+ - **Merge-by-user where it matters**: production & archive merges stay in your hands the plugin blocks the agent from clicking merge, so your action *is* the confirmation.
134
128
  - **Works with any naming**: branch names are mapped by your config, never hard-coded (see [Configuration](#configuration-reference)).
135
- - **Fully audited**: every block, grant, and consumption is written to `.git/gitflow-guard/` (audit trail + state) — inside `.git`, never committed.
136
- - **Platform-agnostic core**: pure local git; optionally consults `gh` when available (PR target resolution, CI status as a log reference), and works fine without it.
129
+ - **Fully audited**: every deny is written to `.git/gitflow-guard/audit.jsonl` — inside `.git`, never committed.
130
+ - **Platform-agnostic core**: pure local git; optionally consults `gh` (GitHub) or `glab` (GitLab) for PR/MR target resolution, and works fine without them.
137
131
 
138
132
  ---
139
133
 
140
134
  ## What it does NOT do — honest limits
141
135
 
142
- - **It is not a security boundary.** Command parsing is best-effort; an agent determined to obfuscate commands can evade text analysis. What *cannot* be evaded is the order check itself: git ancestry is a fact, not a claim.
143
- - **It does not gate on CI platforms.** `gh pr checks` is logged as a reference only, never as a hard gate. No GitHub/GitLab API enforcement (that belongs in branch protection rules, which can layer on top).
144
- - **It is not a replacement for the flow itself.** Your project must actually use a feature preview baseline flow. If your team merges everything straight to one branch, this plugin will block constantly — don't enable it there.
145
- - **No multi-machine state sync (v1).** Permits are stored locally; a second machine won't see them (planned in v2).
146
- - **No pop-up notifications (v1).** Post-actions are reported through the audit trail and the conversation, not pushed to you.
136
+ - **It is not a security boundary.** Command parsing is best-effort; an agent determined to obfuscate commands can evade text analysis.
137
+ - **It does not gate on CI platforms.** CI status is logged as a reference only, never a hard gate. Real branch protection belongs in GitHub/GitLab settings, which can layer on top.
138
+ - **It is not a replacement for the flow itself.** Your project must have at least one `integration` branch; if everyone pushes straight to one branch, this plugin will block constantly — don't enable it there.
139
+ - **Production/archive are not automated** they're deliberately left to your human click; the plugin only tells agents "no".
147
140
 
148
141
  ---
149
142
 
@@ -153,51 +146,39 @@ Server-side branch protection (GitHub branch rules, GitLab protected branches) a
153
146
 
154
147
  | dimension | server-side protection | this plugin |
155
148
  |---|---|---|
156
- | what it governs | *who* may push / merge to protected branches (permissions) | *the order and prerequisites* of agent merges (workflow) |
157
- | can express "user confirmed tests" | no — at best it requires review approvals, which mean little when agents are the reviewers | yes — a dedicated, audited permit (P2) that agents cannot self-grant |
158
- | can enforce "preview before baseline" | no protection is per-branch, not per-flow | yes the gate checks feature preview before any baseline merge |
149
+ | what it governs | *who* may push / merge to protected branches (permissions) | *how* agents may enter the flow (workflow) — which role a merge lands in |
150
+ | keeps agents from merging into production/archive | no — it can't tell "agent did it" | yes — production/archive merges are blocked for agents by default |
151
+ | per-role flexibility | one rule per branch on the host | per-role `update` (`pr`/`flexible`) + `mergeBy` (`user`/`anyone`) in one config file |
159
152
  | scope | every user of the repository, humans included | DSH agents with the plugin configured (humans are not restricted) |
160
153
  | enforcement point | server-side, at push / merge time | local, before the command runs |
161
- | platform | tied to the hosting service | pure local git, platform-agnostic |
154
+ | platform | tied to the hosting service | pure local git, platform-agnostic (`gh` / `glab` optional) |
162
155
  | bypassable by | users with admin rights | anyone working outside DSH, or a determined malicious agent |
163
156
 
164
- Why this matters: branch protection answers *"can this push happen at all?"*; this plugin answers *"may this agent merge now, given the flow?"*. The strongest setup uses **both** — the plugin keeps agents honest about the workflow, and branch protection guarantees that no one, agent or human, pushes straight to a protected branch.
157
+ Why this matters: branch protection answers *"can this push happen at all?"*; this plugin answers *"may this agent enter this role, given the config?"*. The strongest setup uses **both** — the plugin keeps agents honest about the workflow, and branch protection guarantees that no one, agent or human, pushes straight to a protected branch.
165
158
 
166
159
  ---
167
160
 
168
161
  ## How it works — the mechanism in three lines
169
162
 
170
163
  1. An agent calls a shell tool (`pwsh` / `bash`) with a git command.
171
- 2. The plugin classifies the command, reads local git facts (current branch, whether the feature is an ancestor of the preview branch), consults permit state, and applies the gate matrix.
172
- 3. Violation → the tool call is **denied before it runs**, with a reason and the next step. Allowed → the command proceeds, audited.
164
+ 2. The plugin classifies the command, resolves the branch roles from `gitflow-guard.config.json`, and applies the gate matrix.
165
+ 3. Violation → the tool call is **denied before it runs**, with a reason and the next step. Allowed → the command proceeds; every deny is audited to `.git/gitflow-guard/audit.jsonl`.
173
166
 
174
- Confirmation channel: the plugin listens to your chat messages in DSH and only accepts messages whose source is a **real human** (`source.kind === 'user'`) an agent cannot forge that.
167
+ No chat-confirmation or permit store: sensitive merges (production / archive) are simply **user-only** an agent may prepare the PR/MR, but the merge click stays yours.
175
168
 
176
169
  ### Design principles — why it works
177
170
 
178
- #### 1. Local git facts are the only trusted source
171
+ #### 1. Config is the single source of truth
179
172
 
180
- The plugin never asks the agent "which branch are you on?" or "did the user confirm?" it runs read-only git queries itself (`branch --show-current`, `merge-base --is-ancestor feature preview`).
181
-
182
- Git ancestry is a fact of the repository: if the feature's HEAD is an ancestor of the preview branch, the merge happened; otherwise it did not. An agent can claim anything; the repository cannot lie.
183
-
184
- ---
173
+ Nothing about branch names or rules is hard-coded. `integration` is the only required role; `preview` / `production` / `archive` are optional arrays of exact names or regexes, each with its own `update` and `mergeBy`. The same binary scales from a solo `develop` to an enterprise multi-env pipeline.
185
174
 
186
175
  #### 2. Blocking happens before execution, not after
187
176
 
188
177
  The plugin hooks the tool pipeline at `tools/pre-execute` — the decision point that runs *before* the command is dispatched. A `deny` there means the command **never runs**; the agent only ever sees the rejection. Post-hoc detection (scanning logs after the fact) can't work as enforcement — the damage would already be done.
189
178
 
190
- ---
191
-
192
- #### 3. The confirmation channel is unforgeable by design
179
+ #### 3. The sensitive merges are unforgeably human
193
180
 
194
- Chat messages in DSH carry a producer tag (`source`). Only input typed by the real user has `source.kind === 'user'`; model output, tool results, and plugin injections all carry different sources. The plugin accepts confirmations exclusively from the user source so "user confirmed it" cannot be faked by the agent, the model, or another plugin.
195
-
196
- ---
197
-
198
- #### 4. Permits are one-shot and consumed after the action succeeds
199
-
200
- "One-shot" means every exception is explicit, auditable, and non-recurring — there is no "permanently exempted feature". "Consumed after success" means a failed attempt (e.g. a PR that fails to create) does not waste the permit: it stays valid for the next attempt. Both properties are visible in the audit trail (`grant` → `consume`).
181
+ No plugin code decides "is this merge OK?" for production or archive. The gate simply refuses to let an *agent* perform those merges, so the only path is a PR/MR page where **you** click merge and that click is the confirmation. There is no token, permit, or chat message an agent could forge to get past you.
201
182
 
202
183
  ---
203
184
 
@@ -205,73 +186,80 @@ Chat messages in DSH carry a producer tag (`source`). Only input typed by the re
205
186
 
206
187
  ### Branch roles — the model behind the checks
207
188
 
208
- The plugin models **four roles**. Only the roles are fixed; the names are yours.
189
+ Only **`integration`** is required. Every other role is optional configure what your flow actually uses, and each entry is an exact branch name **or** a regex pattern.
209
190
 
210
191
  ```text
211
- trunk ─── (optional, release) merging into it: ALWAYS BLOCKED — user hands only
212
-
213
- baseline merging into it requires: feature ∈ preview AND user confirmation (P2)
214
-
215
- preview ── merging into it: always allowed (PR①) parallel features OK
216
-
217
- feature branches — your working branches, recognized by featurePattern
192
+ feature branches ──(free)──> integration(集成分支, PR/MR 合入)
193
+
194
+ ├──> preview(可选, 环境终点, PR/MR 更新)
195
+
196
+ └──> production(可选, PR/MR + 只有你能点合并)
197
+ archive(可选, 发布后你亲手归档)
218
198
  ```
219
199
 
220
- | role | config key | protected? | enforced behavior |
200
+ | role | config key | required? | enforced behavior |
221
201
  |---|---|---|---|
222
- | **baseline** | `branches.base` | always | no direct push / force-push / delete; merges need order + P2 |
223
- | **preview** | `branches.preview` | in `pr` mode | no direct push / local merge in `pr` mode; merging *into* it is always allowed |
224
- | **trunk** | `branches.trunk` (optional) | always | nothing merges into it except the user themselves |
225
- | **feature** | matched by `confirm.featurePattern` | | free: commit / push / sync / rebase |
202
+ | **feature** | `featurePattern` | | free: commit / push / sync / rebase |
203
+ | **integration** | `branches.integration` | always | no direct push (default `pr`); features merge in via PR/MR |
204
+ | **preview** | `branches.preview` (array) | optional | no direct push; updates via PR/MR only (env endpoints) |
205
+ | **production** | `branches.production` (array) | optional | PR/MR only; merge by user only (`mergeBy: "user"`) |
206
+ | **archive** | `branches.archive` (array) | optional | user-hand only — agents cannot even create a PR |
226
207
 
227
- ### Customizing branch names — any naming works
208
+ ### Customizing branch names & rules — any naming works
228
209
 
229
- `branches` maps your repository's *actual* branch names onto the roles. Nothing is hard-coded. Example: baseline `master`, preview `beta`, trunk `production`, feature branches `fix/`- and `task/`-prefixed:
210
+ **小团队(个人 / 2-3 人)—— 最简,只有 integration:**
230
211
 
231
212
  ```jsonc
232
213
  {
233
214
  "enabled": true,
234
- "mode": "pr",
235
- "branches": {
236
- "base": "master",
237
- "preview": "beta",
238
- "trunk": "production"
239
- },
240
- "confirm": {
241
- "keywords": ["确认", "OK", "可以", "特许"],
242
- "featurePattern": "(fix|task)/[\\w-]+"
243
- }
215
+ "featurePattern": "feature/[\\w-]+",
216
+ "branches": { "integration": ["develop"] }
244
217
  }
245
218
  ```
246
219
 
247
- With this config, `master` is treated exactly as `develop` is in the default examples: agents pushing `master` are blocked; merging `fix/auth-42` into `master` is blocked until `fix/auth-42` is in `beta` *and* you confirm it; `gitflow-guard status` reports with your branch names.
220
+ **大团队(多预览环境 + 生产 + 归档):**
248
221
 
249
- **`featurePattern`**: a JS regular expression matched against branch names. Matches → feature branch (free to push, merge, sync). Non-matches that are also not role branches → "anything else" (allowed). Configure it to your actual convention.
222
+ ```jsonc
223
+ {
224
+ "enabled": true,
225
+ "featurePattern": "(topic|feature)/[\\w-]+",
226
+ "branches": {
227
+ "integration": ["develop", "topic/[\\w-]+"],
228
+ "preview": {
229
+ "branches": ["ita1", "itb1", "itb2", "sg", "vb", "r1-conf", "r1-ope", "r2-conf", "r2-ope"],
230
+ "update": "pr"
231
+ },
232
+ "production": {
233
+ "branches": ["prd-conf", "prd-ope"],
234
+ "update": "pr",
235
+ "mergeBy": "user"
236
+ },
237
+ "archive": ["main"]
238
+ }
239
+ }
240
+ ```
250
241
 
251
242
  ### Full field reference
252
243
 
253
244
  ```jsonc
254
245
  {
255
- "enabled": true, // opt-in: file exists AND enabled=true
256
- "mode": "pr", // "pr" = PR-only | "flexible" = direct push/local merge into preview allowed
246
+ "enabled": true, // opt-in: file exists AND enabled=true
247
+ "featurePattern": "feature/[\\w-]+", // JS regex matching your working/feature branches
257
248
  "branches": {
258
- "base": "develop", // REQUIRED: baseline branch
259
- "preview": "staging", // REQUIRED: preview branch
260
- "trunk": "main" // optional: trunk branch (release)
249
+ "integration": { "branches": ["develop"], "update": "pr" }, // REQUIRED
250
+ "preview": { "branches": ["ita1"], "update": "pr" }, // optional
251
+ "production": { "branches": ["prd"], "update": "pr", "mergeBy": "user" }, // optional
252
+ "archive": ["main"] // optional
261
253
  },
262
- "confirm": {
263
- "keywords": ["确认", "OK", "可以", "特许"], // chat-confirmation trigger words
264
- "featurePattern": "feature/[\\w-]+" // JS regex matching your feature branches
265
- },
266
- "ci": { "enabled": true } // optional adapter: gh pr checks logged as reference, skipped when unavailable
254
+ "ci": { "enabled": true } // optional: gh pr checks logged as reference
267
255
  }
268
256
  ```
269
257
 
270
- **Validation**: `branches.base` and `branches.preview` are required; mapping two roles to the same branch is rejected; `mode` must be `pr` or `flexible`; an invalid `featurePattern` regex is rejected. **Any error disables the plugin for that project** (errors are reported) rather than applying a half-guessed setup.
271
-
272
- **`mode`**:
273
- - `pr` (default): preview is protected features reach it only via PRs (no direct push, no local merge).
274
- - `flexible`: preview may be pushed to / merged into directly; baseline merges still require order + P2.
258
+ - Roles accept either an **array** (shorthand) or an **object** `{ branches, update?, mergeBy? }`.
259
+ - `update`: `pr` (default) = updates only via PR/MR; `flexible` = allow direct/local merges (small teams).
260
+ - `mergeBy` (production): `user` (default) = only you click merge; `anyone` = allow PR merge through.
261
+ - Each branch entry is an exact name or a regex (auto-detected).
262
+ - **Validation**: `integration` is required; overlapping role entries are rejected; invalid regex is rejected. **Any error disables the plugin for that project** (reported) rather than applying a half-guessed setup.
275
263
 
276
264
  ---
277
265
 
@@ -279,52 +267,24 @@ With this config, `master` is treated exactly as `develop` is in the default exa
279
267
 
280
268
  | agent action | decision |
281
269
  |---|---|
282
- | merge into preview (PR①) | allow (first step; parallel features OK) |
283
- | create PR targeting baseline | if feature preview · else P1 permit ? allow : 🚫 block |
284
- | create PR targeting trunk | P3 permit ? ✅ allow : 🚫 block |
285
- | merge into baseline (PR merge / local merge) | feature preview + P2 ? allow : 🚫 block |
286
- | merge into trunk | 🚫 always blocked (user hands only) |
287
- | direct push / force-push / delete protected branch | 🚫 block |
270
+ | commit / push feature / sync / rebase / read-only | allow |
271
+ | direct push / force-push / delete integration / preview / production / archive | 🚫 block (integration/preview `flexible` direct push allowed) |
272
+ | PR/MR: feature integration / preview | ✅ allow |
273
+ | PR/MR: feature production | allow to create; **merge blocked** (you merge in UI) |
274
+ | PR/MR targeting archive | 🚫 block |
275
+ | local `git merge feature/x` while on integration / preview | 🚫 block (PR/MR required); `update: flexible` allows |
288
276
  | chained commands (`checkout develop && merge feature/x`) | 🚫 blocked — branch switches are simulated per segment, no bypass |
289
- | commit / push feature / sync from baseline / rebase / read-only / `gitflow-guard status` | ✅ allow |
290
277
 
291
- `gh pr merge` resolves its target via `gh pr view` (optional adapter); without `gh`, the plugin conservatively applies the baseline rules.
278
+ The PR/MR target is resolved via `gh pr view` (GitHub) or `glab mr view` (GitLab). Without a platform CLI, the plugin is conservative.
292
279
 
293
280
  ---
294
281
 
295
- ## User Exceptions (Permits) — the only way to break the rules
296
-
297
- | permit | meaning | granted by | consumed when |
298
- |---|---|---|---|
299
- | P1 `early-pr` | create a baseline PR before order is satisfied | chat / CLI | PR created |
300
- | P2 `confirm` | "feature X tests OK" — allow baseline merge | chat / CLI | merge succeeded |
301
- | P3 `trunk-pr` | allow creating a PR targeting trunk | chat / CLI | PR created |
302
-
303
- **One-shot**: consumed automatically after the action succeeds (audited). Optional TTL via `--ttl`; expired-unused permits are logged.
304
-
305
- **Agents can never self-authorize** — the plugin blocks agents from running `permit` / `confirm`.
306
-
307
- **① Chat confirmation** — in DSH, just type it (real user message only):
308
-
309
- ```text
310
- feature/dev-x-01 测试 OK,可以合入 → P2 confirm
311
- feature/dev-x-01 提前建 PR → P1 early-pr
312
- feature/dev-x-01 可以发布上主干 → P3 trunk-pr
313
- ```
314
-
315
- (The default trigger words are Chinese; configure `confirm.keywords` for your language.)
316
-
317
- **② Terminal CLI** (user-only):
282
+ ## Where the human stays in control
318
283
 
319
- ```bash
320
- gitflow-guard permit <feature> [--kind early-pr|confirm|trunk-pr] [--ttl <minutes>]
321
- gitflow-guard confirm <feature> [--ttl <minutes>]
322
- gitflow-guard status [--repo <path>] # read-only: preview contents / permits per feature
323
- gitflow-guard audit [--lines <N>] # read-only: audit trail
324
- ```
284
+ - **Production merge** and **archive** are user-only by default: an agent may help prepare the PR/MR, but **you click the merge button** — that click *is* the confirmation. There is no separate permit store to outsource that decision.
285
+ - Every deny is written to `.git/gitflow-guard/audit.jsonl` for review (`gitflow-guard audit`).
325
286
 
326
287
  ---
327
-
328
288
  ## Installation in detail
329
289
 
330
290
  **Prerequisite**: a working [DSH](https://github.com/deepseek-ai/deepseek-harness) installation.
@@ -340,31 +300,44 @@ Then restart DSH. Upgrades are the same command, followed by another restart.
340
300
  **From source** — for contributors, or to run the latest checkout:
341
301
 
342
302
  ```bash
343
- pnpm install && pnpm build
303
+ npm install && npm run build
344
304
  dsh plugin --profile web add file:/path/to/agents-gitflow-guard
345
305
  ```
346
306
 
347
307
  The package declares `dsh.bundle.patch`, so `dsh plugin add` automatically makes it a profile layer — no manual profile editing.
348
308
 
309
+ **Claude Code hook** — the same guard inside Claude Code, no DSH required. This repo already ships the config at `.claude/settings.json`; for any other repo, add these hooks:
310
+
311
+ ```json
312
+ {
313
+ "hooks": {
314
+ "PreToolUse": [
315
+ { "matcher": "Bash", "hooks": [{ "type": "command", "command": "/abs/path/gitflow-guard check --platform claude" }] }
316
+ ]
317
+ }
318
+ }
319
+ ```
320
+
321
+ - The hook reads the payload on stdin and answers `exit 0` (allow) or `exit 2` (block — stderr is the reason shown to the model, plus a "next step" hint).
322
+ - Only `PreToolUse` is needed: the guard blocks *before* the command runs. There is no permit to consume afterwards, so no `PostToolUse` hooks are required.
323
+ - Use an **absolute path** to the binary — hook subprocesses may not inherit your shell `PATH`. `${CLAUDE_PROJECT_DIR}/bin/gitflow-guard.mjs` (after `npm run build`) also works from a checkout.
324
+ - Fully opt-in: the hook does nothing unless the repo has `gitflow-guard.config.json` with `enabled: true`.
325
+
349
326
  ---
350
327
 
351
328
  ## FAQ
352
329
 
353
330
  ### My branches don't follow the default names — can I use it?
354
331
 
355
- Yes — nothing about the branch names is fixed. The three roles (baseline, preview, trunk) are concepts; the `branches` field maps your repository's actual names onto them, and `featurePattern` tells the plugin how to recognize your feature branches.
332
+ Yes — nothing about the branch names is fixed. `integration` is the only required role; its entries (and those of `preview` / `production` / `archive`) are any exact branch names or regex patterns you like. `featurePattern` tells the plugin how to recognize your working branches.
356
333
 
357
- A team that calls its baseline `master`, its preview `beta`, and prefixes feature branches with `fix/` writes exactly that into the config, and everything — the blocks, the reports, the audit then speaks those names. There is no convention you must adopt; there is only a mapping you declare.
358
-
359
- The full worked example is in [Customizing branch names](#customizing-branch-names--any-naming-works).
334
+ A team that calls its integration branch `master`, adds a `beta` preview, and prefixes feature branches with `fix/` writes exactly that into the config; every block, report, and audit then speaks those names. There is no convention you must adopt only a mapping you declare. See [Customizing branch names & rules](#customizing-branch-names--rules--any-naming-works).
360
335
 
361
336
  ---
362
337
 
363
- ### My project doesn't use a feature → preview baseline flow.
364
-
365
- Then this plugin is not for you, and enabling it would be a frustrating mistake: every routine merge would be blocked, because the guard enforces an order your workflow does not have. It is a mechanism for a flow that already exists, not a substitute for one.
338
+ ### Do I need a preview/production/archive at all?
366
339
 
367
- One nuance worth knowing: if your team is close you do separate feature branches and a shared preview, but you prefer direct pushes into preview over PRs — the `flexible` mode keeps the order + confirmation requirement on the baseline while relaxing the preview rules.
340
+ No. Add only the roles your flow actually has. A solo repo with just `develop` configures `integration: ["develop"]` and nothing else; an enterprise with ten environments adds the `preview` array and a `production` role. The rest stays off.
368
341
 
369
342
  ---
370
343
 
@@ -372,67 +345,43 @@ One nuance worth knowing: if your team is close — you do separate feature bran
372
345
 
373
346
  No, and it is important that you don't treat it as one. It is a workflow guard: it makes an agreed process mechanically enforceable. Text-based command recognition is inherently best-effort — an agent determined to obfuscate a command can slip past the parser.
374
347
 
375
- What cannot be forged is the *order check itself*: whether a feature is an ancestor of the preview branch is a property of the repository, not a claim the agent can invent. If you need real protection against hostile agents, that belongs in branch-protection rules on your hosting service; this plugin is the layer that keeps honest workflows honest.
348
+ But what cannot be bypassed is the **role boundary itself**: merging into a protected role branch (integration / preview / production / archive) requires the configured path (PR/MR, or a human merge for production/archive). If you need real protection against hostile agents, that belongs in branch-protection rules on your hosting service.
376
349
 
377
350
  ---
378
351
 
379
- ### Why can't the agent just run `gitflow-guard permit ...` itself?
380
-
381
- Because both exception channels are sealed against it. The `permit` / `confirm` commands are classified as user-only: when they arrive as tool calls, the plugin denies them outright.
352
+ ### Why can't the agent just merge into production/archive itself?
382
353
 
383
- The chat channel is sealed the same way the plugin only accepts confirmations whose message source is `source.kind === 'user'`, a tag that only genuine human input carries; model output, tool results, and plugin injections all carry different sources.
384
-
385
- The two channels converge on the same guarantee: an exception can only ever originate from the person, never from the agent. This is the property that makes "the user is the only exception authority" more than a slogan.
354
+ Because the gate classifies those as **user-only** actions. An agent may create the PR/MR, but the plugin denies the *merge* for production and the *PR creation* (and merge) for archive. The only path is for **you** to click merge — there is no permit, token, or chat message an agent could use to confer that power on itself.
386
355
 
387
356
  ---
388
357
 
389
- ### Do I need the `gh` CLI?
390
-
391
- No. The `gh` integration is an optional adapter: it lets the plugin resolve what `gh pr merge` is actually targeting, and it logs `pr checks` status as a reference.
358
+ ### Do I need the `gh` or `glab` CLI?
392
359
 
393
- Without `gh`, the plugin simply takes the conservative path an unresolvable `pr merge` is treated under the baseline rules — and everything else works exactly the same. The core enforcement never touches a hosting service, which is also why the plugin works identically on GitHub, GitLab, a self-hosted server, or an offline repository.
360
+ No. They are optional adapters used only to resolve what a `pr merge` / `mr merge` is targeting, so the gate can tell "merge into integration/preview" (okay) from "merge into production/archive" (blocked). Without them, the plugin takes a conservative path it refuses when it can't confirm the target — and everything else works the same. The core enforcement never touches a hosting service, which is why it works identically on GitHub, GitLab, self-hosted, or offline.
394
361
 
395
362
  ---
396
363
 
397
364
  ### Will it block my normal work?
398
365
 
399
- Deliberately, no. Everything a feature branch is for — committing, pushing, syncing from the baseline, rebasing, inspecting with read-only commands, running `gitflow-guard status` — is allowed without friction.
366
+ Deliberately, no. Everything a feature branch is for — committing, pushing, syncing from `integration`, rebasing, inspecting with read-only commands, running `gitflow-guard status` — is allowed without friction.
400
367
 
401
- The blocks are reserved for exactly two families of actions: writes to protected branches, and baseline merges that skip the order or the confirmation.
402
-
403
- If you ever see a block you believe is wrong, run `gitflow-guard status` before anything else: the report shows the precise facts the decision was built on (whether the feature is in the preview, which permits exist), so a misjudgment is visible and correctable rather than mysterious.
368
+ The blocks are reserved for: (1) direct writes to protected role branches, and (2) an agent trying to merge into production or archive. If you ever see a block you believe is wrong, run `gitflow-guard status` — it shows exactly which role each local branch got, so a misjudgment is visible and correctable.
404
369
 
405
370
  ---
406
371
 
407
372
  ### What if my config has a mistake?
408
373
 
409
- The plugin prefers failing closed: any validation error in the config disables the guard for that project and reports the errors, so a half-guessed setup never applies by accident.
410
-
411
- The most common mistakes are mapping two roles to the same branch (rejected explicitly), a `featurePattern` that doesn't compile (rejected as invalid regex), and a typo in `mode`. Because the failure is loud and the file is one JSON object, the fix is usually a thirty-second correction followed by a working guard.
412
-
413
- ---
414
-
415
- ### Does it work across multiple machines?
374
+ The plugin prefers failing closed: any validation error disables the guard for that project and reports the errors, so a half-guessed setup never applies by accident.
416
375
 
417
- Within one machine, fully permit state and audit live in `.git/gitflow-guard/` and survive DSH restarts.
418
-
419
- Across machines, not yet: if you and an agent work from different computers, a confirmation granted on one machine is not visible on the other, so a merge attempted there would be blocked until you confirm again. This is a v1 limitation with a straightforward v2 plan (synchronizing state), listed in the [Roadmap](#roadmap).
420
-
421
- ---
422
-
423
- ### Will my agent's legitimate PR① (feature → preview) be blocked?
424
-
425
- No. Merging into the preview branch is the first step of the flow and is always allowed — parallel features landing in preview are precisely what the model expects.
426
-
427
- The order gate applies only to baseline merges, so the normal path (feature → preview → confirm → baseline) never trips it.
376
+ Common mistakes: missing `integration` (required), overlapping a branch across two roles (rejected explicitly), and a `featurePattern` that doesn't compile (rejected as invalid regex). The failure is loud and the file is one JSON object, so the fix is usually a thirty-second correction.
428
377
 
429
378
  ---
430
379
 
431
380
  ### What exactly is checked against the local repository?
432
381
 
433
- Three read-only queries, nothing more: the current branch (`git branch --show-current`), whether the feature is an ancestor of the preview branch (`git merge-base --is-ancestor`), and only for `gh pr merge` the PR's base branch (`gh pr view`).
382
+ The current branch (`git branch --show-current`), and only for `pr merge` / `mr merge` — the PR/MR target via `gh pr view` / `glab mr view`. Nothing about ancestry is needed, because the model is role-driven (which branch *is* the target) rather than order-driven.
434
383
 
435
- Nothing is written, no remote is contacted, and no hosting-service feature is required. This is the whole reason the plugin can make hard promises about order: the facts it trusts come from the repository itself.
384
+ Nothing is written, no remote is contacted, and no hosting-service feature is required for the core checks. Production/archive merges are simply denied for agents; the human merge happens in your UI.
436
385
 
437
386
  ---
438
387
 
@@ -443,33 +392,27 @@ MIT, free, no strings. Use it, modify it, ship it — the only obligation is kee
443
392
  If it saves your team from a shortcut gone wrong, the coffee button at the top of this page is appreciated but never required. See [License](#license).
444
393
 
445
394
  ---
446
-
447
395
  ## Glossary
448
396
 
449
397
  | term | meaning |
450
398
  |---|---|
451
- | **baseline** | your stable integration branch (`branches.base`); protected; merges need order + P2 |
452
- | **preview** | the test-env branch (`branches.preview`); features merge in freely (PR①) |
453
- | **trunk** | the release branch (`branches.trunk`, optional); user-hands only |
454
- | **feature branch** | your working branch, matched by `featurePattern` |
455
- | **PR① / PR②** | feature preview / feature baseline |
456
- | **permit** | a one-shot user-granted exception (P1 early-pr / P2 confirm / P3 trunk-pr) |
399
+ | **integration** | the branch and only required role (`branches.integration`); features merge in via PR/MR; protected |
400
+ | **preview** | optional env-endpoint branches (`branches.preview`, array); updates via PR/MR only |
401
+ | **production** | optional production branches (`branches.production`, array); PR/MR + merge by user only |
402
+ | **archive** | optional post-release archive branch (`branches.archive`); user-hand only |
403
+ | **feature branch** | your working branch, matched by `featurePattern`; free zone |
457
404
  | **gate matrix** | the decision table mapping each classified command to allow/deny |
458
- | **P2** | the user confirmation that unlocks a baseline merge |
459
405
  | **pre-execute** | the tool-pipeline hook where denial happens — before the command runs |
460
- | **`source.kind === 'user'`** | the DSH message tag that marks a real human's input the unforgeable confirmation channel |
461
- | **`merge-base --is-ancestor`** | the git query that answers "is this feature merged into preview?" truthfully |
406
+ | **merge-by-user** | production/archive merges stay in your hands your click on the PR/MR is the confirmation |
462
407
 
463
408
  ---
464
409
 
465
410
  ## Roadmap
466
411
 
467
412
  - **i18n — localized block messages**: deny messages are Chinese by default today; make them follow the user's language (and the plugin config).
468
- - **v2 — multi-machine state**: sync permits/audit across machines.
469
- - **v2 — platform adapters**: GitLab / Gitea support (interface already reserved).
470
- - **v2 — notifications**: push notice to the user when a permit is consumed (currently audit + conversation only).
471
- - **v2 — CI hard-gating research**: whether `gh pr checks` can become a real gate without hurting the platform-agnostic core.
472
- - **Ecosystem**: ready-made config templates for common workflows; community-contributed confirmation keywords.
413
+ - **v2 — audit sync**: sync `.git/gitflow-guard/audit.jsonl` across machines (audit is local-only today).
414
+ - **v2 — more pre-built templates**: ready-made config templates for common flows (solo `develop`, multi-env enterprise) as community-contributed presets.
415
+ - **v2 — CI hard-gating research**: whether `pr checks` could become a real gate without hurting the platform-agnostic core.
473
416
 
474
417
  Contributions welcome — see [Development](#development).
475
418
 
@@ -486,10 +429,10 @@ The plugin is free and open source (MIT). If it saves you and your team from a s
486
429
  ## Development
487
430
 
488
431
  ```bash
489
- pnpm install
490
- pnpm test # unit tests: classify / gate / config / permits / session / real-git integration
491
- pnpm typecheck # tsc --noEmit, 0 errors
492
- pnpm build # tsdown → lib/ (CLI and plugin share the build)
432
+ npm install
433
+ npm test # unit tests: classify / gate / config / cli / repo / platform
434
+ npm run typecheck # tsc --noEmit, 0 errors
435
+ npm run build # tsdown → lib/ (CLI and plugin share the build)
493
436
  ```
494
437
 
495
438
  **Rule**: any logic change must pass a 0-error build + all green tests before done.