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 +152 -209
- package/README.zh.md +182 -239
- package/lib/cli.mjs +153 -80
- package/lib/index.d.mts +7 -18
- package/lib/index.mjs +2 -2
- package/lib/src-Dv6jKDsO.mjs +716 -0
- package/package.json +7 -7
- package/lib/src-DWE1n9Zh.mjs +0 -813
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> **Are you tired of agents skipping your GitFlow?**
|
|
4
4
|
|
|
5
|
-
A
|
|
6
|
-
|
|
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
|
-
- [
|
|
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
|
-
"
|
|
49
|
+
"featurePattern": "feature/[\\w-]+",
|
|
50
50
|
"branches": {
|
|
51
|
-
"
|
|
52
|
-
"
|
|
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:
|
|
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
|
-
下一步:
|
|
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:
|
|
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/MR — push 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 [
|
|
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`);
|
|
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** —
|
|
81
|
-
| 5 |
|
|
82
|
-
| 6 | `
|
|
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
|
|
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
|
|
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
|
-
-
|
|
110
|
-
- An agent has already cut a corner:
|
|
111
|
-
- You protect your
|
|
112
|
-
- Multiple features develop in parallel and land in one shared preview environment, and you
|
|
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
|
|
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`
|
|
118
|
-
3. **
|
|
119
|
-
4. **Async collaboration.** You're not always online. The guard keeps the flow honest between your sessions;
|
|
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 line — each 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
|
|
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
|
|
132
|
-
- **
|
|
133
|
-
- **
|
|
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
|
|
136
|
-
- **Platform-agnostic core**: pure local git; optionally consults `gh`
|
|
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.
|
|
143
|
-
- **It does not gate on CI platforms.**
|
|
144
|
-
- **It is not a replacement for the flow itself.** Your project must
|
|
145
|
-
- **
|
|
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) | *
|
|
157
|
-
|
|
|
158
|
-
|
|
|
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
|
|
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,
|
|
172
|
-
3. Violation → the tool call is **denied before it runs**, with a reason and the next step. Allowed → the command proceeds
|
|
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
|
-
|
|
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.
|
|
171
|
+
#### 1. Config is the single source of truth
|
|
179
172
|
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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 |
|
|
200
|
+
| role | config key | required? | enforced behavior |
|
|
221
201
|
|---|---|---|---|
|
|
222
|
-
| **
|
|
223
|
-
| **
|
|
224
|
-
| **
|
|
225
|
-
| **
|
|
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
|
-
|
|
210
|
+
**小团队(个人 / 2-3 人)—— 最简,只有 integration:**
|
|
230
211
|
|
|
231
212
|
```jsonc
|
|
232
213
|
{
|
|
233
214
|
"enabled": true,
|
|
234
|
-
"
|
|
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
|
-
|
|
220
|
+
**大团队(多预览环境 + 生产 + 归档):**
|
|
248
221
|
|
|
249
|
-
|
|
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,
|
|
256
|
-
"
|
|
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
|
-
"
|
|
259
|
-
"preview": "
|
|
260
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
-
|
|
274
|
-
- `
|
|
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
|
-
|
|
|
283
|
-
|
|
|
284
|
-
|
|
|
285
|
-
|
|
|
286
|
-
|
|
|
287
|
-
|
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
320
|
-
gitflow-guard
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
| **
|
|
452
|
-
| **preview** |
|
|
453
|
-
| **
|
|
454
|
-
| **
|
|
455
|
-
| **
|
|
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
|
-
|
|
|
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 —
|
|
469
|
-
- **v2 —
|
|
470
|
-
- **v2 —
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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.
|