agents-gitflow-guard 0.0.18 → 0.0.20
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 +90 -42
- package/README.zh.md +90 -42
- package/lib/cli.mjs +377 -4
- package/lib/index.mjs +1 -1
- package/lib/{src-BQYC4N6b.mjs → src-DPJRoEJq.mjs} +216 -28
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -36,33 +36,51 @@ You define your own branches —
|
|
|
36
36
|
|
|
37
37
|
## Quick Start — 30 seconds to a guarded repo
|
|
38
38
|
|
|
39
|
-
**Step 1 — install
|
|
39
|
+
**Step 1 — install.** All six clients consume the same npm package `agents-gitflow-guard` — pick yours:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
#
|
|
43
|
-
dsh plugin --profile web add agents-gitflow-guard
|
|
44
|
-
# ...or pin an exact known-good version (recommended; also bypasses stale registry caches)
|
|
45
|
-
dsh plugin --profile web add agents-gitflow-guard@0.0.18
|
|
42
|
+
# DSH — in-process plugin; restart DSH afterwards (plugins load at startup)
|
|
43
|
+
dsh plugin --profile web add agents-gitflow-guard@0.0.20
|
|
46
44
|
```
|
|
47
45
|
|
|
48
|
-
|
|
46
|
+
```bash
|
|
47
|
+
# Claude Code · Codex · OpenCode · Antigravity — standalone hooks, no DSH needed
|
|
48
|
+
npm i -g agents-gitflow-guard@0.0.20
|
|
49
|
+
```
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
```bash
|
|
52
|
+
# Pi — in-process extension
|
|
53
|
+
npm i -D agents-gitflow-guard@0.0.20
|
|
54
|
+
```
|
|
51
55
|
|
|
52
|
-
**
|
|
56
|
+
> **Version gotcha**: a bare `add` or unpinned `npm i` resolves whatever `latest` is at install time — on machines behind a stale npm/pnpm registry cache or mirror it may install an old version. If the installed version looks wrong, pin it explicitly. (DSH users: the pnpm peer-dependency *warning* is expected — DSH supplies `@deepseek-ai/cordis` / `@deepseek-ai/dsh-tools` through its shared profile module fallback at startup; the plugin works normally.)
|
|
57
|
+
>
|
|
58
|
+
> The hook clients (Claude Code · Codex · OpenCode · Antigravity) need one wiring step after install — **one command per client** (below). Pi needs a copy step; DSH is already wired by install.
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
**Step 2 — wire your client (no config file needed).** The guard ships with **built-in defaults that protect `develop` (integration) + `main` (archive)** — zero configuration, on by default. The only thing you need is to tell your AI client to invoke the guard, with one command per stdin-hook client (DSH is wired automatically; Pi just copies a file, see below):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Claude Code → this repo's .claude/settings.json
|
|
64
|
+
gitflow-guard wire --client claude --project --yes
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Codex / OpenCode / Antigravity (each its own config file; --yes skips the y/N prompt)
|
|
69
|
+
gitflow-guard wire --client codex --project --yes
|
|
70
|
+
gitflow-guard wire --client opencode --project --yes
|
|
71
|
+
gitflow-guard wire --client antigravity --project --yes # experimental
|
|
63
72
|
```
|
|
64
73
|
|
|
65
|
-
|
|
74
|
+
```bash
|
|
75
|
+
# Preview (no writes) / remove / interactive guide:
|
|
76
|
+
gitflow-guard wire --client claude --dry-run
|
|
77
|
+
gitflow-guard wire --client claude --unwire
|
|
78
|
+
gitflow-guard setup
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`wire` merges into your existing config **non-destructively** (already-present hooks are left untouched), and writes to your **project dir by default** — `--global` (all repos on this machine) always asks first or needs `--yes`. Per-client files and formats are mirrored in [Installation in detail](#installation-in-detail).
|
|
82
|
+
|
|
83
|
+
> ⚠️ **main is protected by default.** Trunk / single-branch users (everyone pushes straight to one branch) will get blocked on direct `main` pushes until they opt out — create `gitflow-guard.config.json` with `{ "enabled": false }`, or map your own branches (see [Configuration Reference](#configuration-reference)). `gitflow-guard status` repeats this notice whenever the built-in defaults are in effect.
|
|
66
84
|
|
|
67
85
|
**Step 3 — verify.** Ask the agent (or run in a DSH session) to `git push origin develop`. Expect the tool call to be denied:
|
|
68
86
|
|
|
@@ -71,9 +89,9 @@ Error: [gitflow-guard] blocked: Protected branch "develop" forbids direct push
|
|
|
71
89
|
Next: Integration branch (develop) is updated via PR/MR from a feature branch: push the feature first, then `gh pr create --base develop` / `glab mr create --target-branch develop`.
|
|
72
90
|
```
|
|
73
91
|
|
|
74
|
-
Messages are English by default;
|
|
92
|
+
Messages are English by default; create a config with `"locale": "zh"` to switch to Chinese (see [Configuration Reference](#configuration-reference)).
|
|
75
93
|
|
|
76
|
-
**Done.** The guard is live for this repo.
|
|
94
|
+
**Done.** The guard is live for this repo with the built-in defaults. Want more stages (`preview` / `production`) or different branch names? Write a `gitflow-guard.config.json` and only the fields you care about — everything else keeps the built-in defaults. For the full decision table, see the [Gate Matrix](#gate-matrix--what-gets-blocked-what-passes).
|
|
77
95
|
|
|
78
96
|
### Full walkthrough — one feature, end to end
|
|
79
97
|
|
|
@@ -131,7 +149,7 @@ Nobody has to remember the rules — the rules are enforced.
|
|
|
131
149
|
## What it does — capabilities
|
|
132
150
|
|
|
133
151
|
- **Blocks, before execution**: direct push / force-push / delete of protected role branches (integration / preview / production / archive); agent merging into production or archive.
|
|
134
|
-
- **Role-driven, fully configurable**: `integration` is the
|
|
152
|
+
- **Role-driven, fully configurable**: `integration` (built-in default: `develop`) is the core role; `preview` / `production` / `archive` are optional arrays of branch names or regexes, each with its own update rules (`pr` / `flexible`, `mergeBy`).
|
|
135
153
|
- **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.
|
|
136
154
|
- **Works with any naming**: branch names are mapped by your config, never hard-coded (see [Configuration](#configuration-reference)).
|
|
137
155
|
- **Fully audited**: every deny is appended to an audit log under your user state directory (`~/.local/state/gitflow-guard/`, `%LOCALAPPDATA%\gitflow-guard` on Windows) — outside the repository, never committed, outside the agent's writable sandbox, and shared across all linked worktrees of one repository.
|
|
@@ -178,7 +196,7 @@ No chat-confirmation or permit store: sensitive merges (production / archive) ar
|
|
|
178
196
|
|
|
179
197
|
#### 1. Config is the single source of truth
|
|
180
198
|
|
|
181
|
-
Nothing about branch names or rules is hard-coded. `integration`
|
|
199
|
+
Nothing about branch names or rules is hard-coded. `integration` ships as a built-in default (`develop`); `preview` / `production` / `archive` are optional arrays of exact names or regexes, each with its own `update` and `mergeBy` — deep-merged over the defaults. The same binary scales from a solo `develop` to an enterprise multi-env pipeline.
|
|
182
200
|
|
|
183
201
|
#### 2. Blocking happens before execution, not after
|
|
184
202
|
|
|
@@ -192,9 +210,28 @@ No plugin code decides "is this merge OK?" for production or archive. The gate s
|
|
|
192
210
|
|
|
193
211
|
## Configuration Reference
|
|
194
212
|
|
|
213
|
+
### Built-in defaults & deep-merge override
|
|
214
|
+
|
|
215
|
+
The guard is **on by default** — no `gitflow-guard.config.json` needed. It protects:
|
|
216
|
+
|
|
217
|
+
| default | role | rule |
|
|
218
|
+
|---|---|---|
|
|
219
|
+
| `develop` | **integration** | no direct push; updates via PR/MR (`update: "pr"`) |
|
|
220
|
+
| `main` | **archive** | no direct push / no agent merge; the archive merge is yours (`mergeBy: "user"`) |
|
|
221
|
+
|
|
222
|
+
When you do create `gitflow-guard.config.json`, its fields are **deep-merged over the defaults**: each field/role you write replaces the default for that field/role, everything you don't write keeps the default. Write only what you want to change:
|
|
223
|
+
|
|
224
|
+
```jsonc
|
|
225
|
+
{
|
|
226
|
+
"branches": { "production": ["release-[\\w-]+"] } // defaults keep develop+main; production is added
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Disable entirely** (trunk / single-branch flows): `{ "enabled": false }`. Fixing an accidental block is a one-file change, and `gitflow-guard status` always explains what is in effect (including when it is the built-in defaults).
|
|
231
|
+
|
|
195
232
|
### Branch roles — the model behind the checks
|
|
196
233
|
|
|
197
|
-
|
|
234
|
+
A **role** maps branch names (or regexes) to a rule set. `integration` is provided by the defaults; every other role is optional.
|
|
198
235
|
|
|
199
236
|
```text
|
|
200
237
|
feature branches ──(free)──> integration (integration branch; updates via PR/MR)
|
|
@@ -208,10 +245,10 @@ archive (optional; you archive after release)
|
|
|
208
245
|
| role | config key | required? | enforced behavior |
|
|
209
246
|
|---|---|---|---|
|
|
210
247
|
| **feature** | `featurePattern` | — | free: commit / push / sync / rebase |
|
|
211
|
-
| **integration** | `branches.integration` |
|
|
248
|
+
| **integration** | `branches.integration` | default (`develop`) | no direct push (default `pr`); features merge in via PR/MR |
|
|
212
249
|
| **preview** | `branches.preview` (array) | optional | no direct push; updates via PR/MR only (env endpoints) |
|
|
213
250
|
| **production** | `branches.production` (array) | optional | PR/MR only; merge by user only (`mergeBy: "user"`) |
|
|
214
|
-
| **archive** | `branches.archive` (array) |
|
|
251
|
+
| **archive** | `branches.archive` (array) | default (`main`) | archive PR/MR may be created by agents; the merge stays user-hand only |
|
|
215
252
|
|
|
216
253
|
### Customizing branch names & rules — any naming works
|
|
217
254
|
|
|
@@ -251,10 +288,10 @@ archive (optional; you archive after release)
|
|
|
251
288
|
|
|
252
289
|
```jsonc
|
|
253
290
|
{
|
|
254
|
-
"enabled": true, //
|
|
291
|
+
"enabled": true, // default true — set false to turn the guard off
|
|
255
292
|
"featurePattern": "feature/[\\w-]+", // JS regex matching your working/feature branches
|
|
256
293
|
"branches": {
|
|
257
|
-
"integration": { "branches": ["develop"], "update": "pr" }, //
|
|
294
|
+
"integration": { "branches": ["develop"], "update": "pr" }, // default: ["develop"] — omit to keep
|
|
258
295
|
"preview": { "branches": ["ita1"], "update": "pr" }, // optional
|
|
259
296
|
"production": { "branches": ["prd"], "update": "pr", "mergeBy": "user" }, // optional
|
|
260
297
|
"archive": ["main"] // optional
|
|
@@ -280,8 +317,8 @@ archive (optional; you archive after release)
|
|
|
280
317
|
registerLocale('fr', fr)
|
|
281
318
|
```
|
|
282
319
|
- **Unknown locales**: an unregistered `"locale"` value falls back to English during interception (by design — hooks never stall on wording), so a typo is easy to miss; the one-line warning shows up in `gitflow-guard status`.
|
|
283
|
-
- **Validation**:
|
|
284
|
-
- **Strict mode**: by default a broken config warns on stderr once and lets the command pass (fail-open, so a typo can't wedge your tooling). `"strict": true` flips config errors and internal errors to **block** (fail-closed) — for high-risk repos.
|
|
320
|
+
- **Validation**: overlapping role entries are rejected; invalid regex is rejected. **Any config error reverses the project to "not enabled"** (reported) rather than applying a half-guessed setup; watch out that a role you override with the same branch name as a default role (e.g. mapping `main` to integration while the default archive is still `main`) is an overlap error — cover or drop the other role too.
|
|
321
|
+
- **Strict mode**: by default a broken config warns on stderr once and lets the command pass (fail-open, so a typo can't wedge your tooling). `"strict": true` flips config errors and internal errors to **block** (fail-closed) — for high-risk repos. An explicit `enabled: false` stays silent; a *missing* file is not an error anymore — the built-in defaults (develop+main) are in effect.
|
|
285
322
|
|
|
286
323
|
---
|
|
287
324
|
|
|
@@ -296,6 +333,12 @@ archive (optional; you archive after release)
|
|
|
296
333
|
| PR/MR into archive | ✅ create allowed; 🚫 merge blocked (you merge in UI) |
|
|
297
334
|
| local `git merge feature/x` while on integration / preview | 🚫 block (PR/MR required); `update: flexible` allows |
|
|
298
335
|
| chained commands (`checkout develop && merge feature/x`) | 🚫 blocked — branch switches are simulated per segment, no bypass |
|
|
336
|
+
| force-recreate a protected branch (`git checkout -B/-C <branch>` / `git switch -C`) | 🚫 block (direct ref-update gate) |
|
|
337
|
+
| retarget/delete a protected branch via `git symbolic-ref` | 🚫 block (direct ref-update gate) |
|
|
338
|
+
| `git cherry-pick` / `git revert` while on integration / preview / production / archive | 🚫 block (history rewrite on a protected branch); `-n` / `--no-commit` and `--abort`/`--continue`/`--skip`/`--quit` pass |
|
|
339
|
+
| `sudo`-wrapped git commands (privilege wrapper) | 🚫 wrapper peeled (`sudo -u …` included), underlying command gated |
|
|
340
|
+
|
|
341
|
+
> Two deliberate non-gates, so they don't get "closed" by accident later: `git tag -f` (moving a tag, even pointing at a protected branch) stays exempt — tags are outside the branch-role scope, same as `push --tags`; and a plain `git commit` on a protected branch stays allowed — the guard governs branch roles and merge paths, not content, and the following `git push` is still blocked (remote stays clean).
|
|
299
342
|
|
|
300
343
|
The PR/MR target is resolved via `gh pr view` (GitHub) or `glab mr view` (GitLab). Without a platform CLI, the plugin is conservative.
|
|
301
344
|
|
|
@@ -313,14 +356,14 @@ The PR/MR target is resolved via `gh pr view` (GitHub) or `glab mr view` (GitLab
|
|
|
313
356
|
|
|
314
357
|
| Agent | Install command | After that |
|
|
315
358
|
|---|---|---|
|
|
316
|
-
| DSH | `dsh plugin --profile web add agents-gitflow-guard@0.0.
|
|
317
|
-
| Claude Code · Codex · OpenCode · Antigravity | `npm i -g agents-gitflow-guard@0.0.
|
|
318
|
-
| Pi | `npm i -D agents-gitflow-guard@0.0.
|
|
359
|
+
| DSH | `dsh plugin --profile web add agents-gitflow-guard@0.0.20` | restart DSH — the plugin auto-mounts as a profile layer |
|
|
360
|
+
| Claude Code · Codex · OpenCode · Antigravity | `npm i -g agents-gitflow-guard@0.0.20` | `gitflow-guard wire --client <name>` — one command per client (below) |
|
|
361
|
+
| Pi | `npm i -D agents-gitflow-guard@0.0.20` | copy `pi/gitflow-guard.ts` into `.pi/extensions/` (below) |
|
|
319
362
|
|
|
320
363
|
**DSH — in-process plugin** (the standard path, already covered in [Quick Start](#quick-start--30-seconds-to-a-guarded-repo)):
|
|
321
364
|
|
|
322
365
|
```bash
|
|
323
|
-
dsh plugin --profile web add agents-gitflow-guard@0.0.
|
|
366
|
+
dsh plugin --profile web add agents-gitflow-guard@0.0.20 # pin recommended, see note above
|
|
324
367
|
```
|
|
325
368
|
|
|
326
369
|
Then restart DSH. Upgrades are the same command, followed by another restart.
|
|
@@ -334,13 +377,17 @@ dsh plugin --profile web add file:/path/to/agents-gitflow-guard
|
|
|
334
377
|
|
|
335
378
|
The package declares `dsh.bundle.patch`, so `dsh plugin add` automatically makes it a profile layer — no manual profile editing.
|
|
336
379
|
|
|
337
|
-
**Standalone agent hooks** — Claude Code / Codex / OpenCode / Antigravity, no DSH required. Install the CLI once, then
|
|
380
|
+
**Standalone agent hooks** — Claude Code / Codex / OpenCode / Antigravity, no DSH required. Install the CLI once, then wire one client per command (the guard is on by default via its built-in config, so wiring is all that remains):
|
|
338
381
|
|
|
339
382
|
```bash
|
|
340
|
-
npm i -g agents-gitflow-guard@0.0.
|
|
383
|
+
npm i -g agents-gitflow-guard@0.0.20 # provides the `gitflow-guard` binary
|
|
384
|
+
gitflow-guard wire --client claude --project --yes
|
|
385
|
+
gitflow-guard wire --client codex --project --yes
|
|
386
|
+
gitflow-guard wire --client opencode --project --yes
|
|
387
|
+
gitflow-guard wire --client antigravity --project --yes # experimental
|
|
341
388
|
```
|
|
342
389
|
|
|
343
|
-
|
|
390
|
+
`wire` reads the existing config file (if any), merges the hook entry in without touching anything else, is idempotent (already wired → skipped), supports `--dry-run` to preview and `--unwire` to remove, and asks before touching `--global` files. The exact files it writes (for reference, and for hand-writing instead of `wire`) are:
|
|
344
391
|
|
|
345
392
|
```jsonc
|
|
346
393
|
// Claude Code — .claude/settings.json
|
|
@@ -393,7 +440,7 @@ hooks:
|
|
|
393
440
|
Pi loads extensions in-process (no stdin payload, no subprocess hook). Install the shipped entry point into the project and keep the package in devDependencies:
|
|
394
441
|
|
|
395
442
|
```bash
|
|
396
|
-
npm i -D agents-gitflow-guard@0.0.
|
|
443
|
+
npm i -D agents-gitflow-guard@0.0.20
|
|
397
444
|
mkdir -p .pi/extensions
|
|
398
445
|
cp node_modules/agents-gitflow-guard/pi/gitflow-guard.ts .pi/extensions/gitflow-guard.ts
|
|
399
446
|
```
|
|
@@ -407,7 +454,8 @@ cp node_modules/agents-gitflow-guard/pi/gitflow-guard.ts .pi/extensions/gitflow-
|
|
|
407
454
|
- The hook reads the payload on stdin and answers with that platform's protocol: Claude Code / OpenCode → `exit 2` (stderr is the reason + "next step" hint); Codex → JSON `{"hookSpecificOutput":{"permissionDecision":"deny",...}}` on stdout; Antigravity → JSON `{"decision":"deny","reason":...}` on stdout with `exit 0` (Antigravity requires exit 0 and rejects `hookSpecificOutput` / non-allow values). Pi has no stdin protocol: the in-process extension listens to the official `tool_call` event and denies via its return value `{ block: true, reason }` (the CLI subprocess only speaks the internal exit-2 contract).
|
|
408
455
|
- Only the pre-tool event is needed: the guard blocks *before* the command runs. There is no permit to consume afterwards, so no post-tool hooks are required.
|
|
409
456
|
- The examples above call the globally-installed `gitflow-guard` (`npm i -g`). If a hook subprocess can't see it on its `PATH`, point at the full binary path from `npm bin -g` — hook subprocesses may not inherit your interactive shell `PATH`. The `bin/gitflow-guard.mjs` paths in this repo's shipped configs only work from a checkout.
|
|
410
|
-
-
|
|
457
|
+
- **On by default**: the built-in config (integration=`develop`, archive=`main`) is active without any file. Trunk / single-branch repos: create `gitflow-guard.config.json` with `{ "enabled": false }`, or map your own branches. A custom config **deep-merges** over the defaults — write only the fields you want to change.
|
|
458
|
+
- `wire` never removes or rewrites your existing hook entries — it only adds its own command (deduped) and, with `--unwire`, removes exactly that command again.
|
|
411
459
|
|
|
412
460
|
---
|
|
413
461
|
|
|
@@ -415,7 +463,7 @@ cp node_modules/agents-gitflow-guard/pi/gitflow-guard.ts .pi/extensions/gitflow-
|
|
|
415
463
|
|
|
416
464
|
### My branches don't follow the default names — can I use it?
|
|
417
465
|
|
|
418
|
-
Yes — nothing about the branch names is fixed. `integration`
|
|
466
|
+
Yes — nothing about the branch names is fixed. `integration` ships as a built-in default (`develop`) and any custom config deep-merges over it; 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.
|
|
419
467
|
|
|
420
468
|
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).
|
|
421
469
|
|
|
@@ -431,7 +479,7 @@ No. Add only the roles your flow actually has. A solo repo with just `develop` c
|
|
|
431
479
|
|
|
432
480
|
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.
|
|
433
481
|
|
|
434
|
-
Within its supported command forms, the role boundary is enforced locally: merging into a protected role branch (integration / preview / production / archive) requires the configured path (PR/MR, or a human merge for production/archive). Standard obfuscation wrappers are classified and blocked — shell wrappers (`sh -c` / `bash -lc`), subshells and backtick/`$()` nesting, `env`/`command`/`nohup`/`xargs` prefixes and `VAR=x` assignments, absolute paths, pipelines and `||` tails, git global options (`-C .`, `--git-dir=…`), wildcard refspecs (`refs/heads/*:refs/heads/*`), `git pull` used as fetch+merge, and the `send-pack`/`update-ref` plumbing. The executable adversarial corpus lives in `tests/accuracy-audit.spec.ts`.
|
|
482
|
+
Within its supported command forms, the role boundary is enforced locally: merging into a protected role branch (integration / preview / production / archive) requires the configured path (PR/MR, or a human merge for production/archive). Standard obfuscation wrappers are classified and blocked — shell wrappers (`sh -c` / `bash -lc`), subshells and backtick/`$()` nesting, `env`/`command`/`nohup`/`xargs`/`sudo` prefixes and `VAR=x` assignments, absolute paths, pipelines and `||` tails, git global options (`-C .`, `--git-dir=…`), wildcard refspecs (`refs/heads/*:refs/heads/*`), `git pull` used as fetch+merge, and the `send-pack`/`update-ref`/`symbolic-ref` plumbing; force-recreating a protected branch (`checkout -B`/`switch -C`) and cherry-pick/revert on a protected branch are blocked by the ref-update / ref-move gates. The executable adversarial corpus lives in `tests/accuracy-audit.spec.ts`.
|
|
435
483
|
|
|
436
484
|
What remains **locally non-defensible**: direct forge-API calls (`gh api repos/…/pulls/N/merge`, `curl`) and commands inside interpreter subprocesses (`node -e "child_process.exec('git push …')"`); arbitrarily deep quoting or encoding stays best-effort by nature. The real, non-bypassable boundary lives in branch-protection rules on your hosting service. Use both — treat this guard as instant feedback and audit trail, not as a security boundary.
|
|
437
485
|
|
|
@@ -461,7 +509,7 @@ The blocks are reserved for: (1) direct writes to protected role branches, and (
|
|
|
461
509
|
|
|
462
510
|
A half-guessed setup is never applied by accident: any validation error disables the guard for that project and reports the errors.
|
|
463
511
|
|
|
464
|
-
Common mistakes:
|
|
512
|
+
Common mistakes: overriding a role with the same branch name as a default role (e.g. `main` as integration while the default archive is still `main` — an explicit overlap error; cover or drop the other role too), overlapping a branch across two roles (rejected), 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.
|
|
465
513
|
|
|
466
514
|
---
|
|
467
515
|
|
|
@@ -484,7 +532,7 @@ If it saves your team from a shortcut gone wrong, the coffee button at the top o
|
|
|
484
532
|
|
|
485
533
|
| term | meaning |
|
|
486
534
|
|---|---|
|
|
487
|
-
| **integration** | the
|
|
535
|
+
| **integration** | the core role (built-in default: `develop`); features merge in via PR/MR; protected |
|
|
488
536
|
| **preview** | optional env-endpoint branches (`branches.preview`, array); updates via PR/MR only |
|
|
489
537
|
| **production** | optional production branches (`branches.production`, array); PR/MR + merge by user only |
|
|
490
538
|
| **archive** | optional post-release archive branch (`branches.archive`); user-hand only |
|
package/README.zh.md
CHANGED
|
@@ -35,33 +35,51 @@
|
|
|
35
35
|
|
|
36
36
|
## 快速开始——30 秒用上
|
|
37
37
|
|
|
38
|
-
**第 1
|
|
38
|
+
**第 1 步——安装**。六个客户端吃同一个 npm 包 `agents-gitflow-guard`,按你的 agent 选一条:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
#
|
|
42
|
-
dsh plugin --profile web add agents-gitflow-guard
|
|
43
|
-
# ...或锁定已知良好版本(推荐; 同时绕开 registry 陈旧缓存)
|
|
44
|
-
dsh plugin --profile web add agents-gitflow-guard@0.0.18
|
|
41
|
+
# DSH —— 进程内插件(装完重启 DSH; 插件在进程启动时加载)
|
|
42
|
+
dsh plugin --profile web add agents-gitflow-guard@0.0.20
|
|
45
43
|
```
|
|
46
44
|
|
|
47
|
-
|
|
45
|
+
```bash
|
|
46
|
+
# Claude Code · Codex · OpenCode · Antigravity —— 独立 hook,不需要 DSH
|
|
47
|
+
npm i -g agents-gitflow-guard@0.0.20
|
|
48
|
+
```
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
```bash
|
|
51
|
+
# Pi —— 进程内扩展
|
|
52
|
+
npm i -D agents-gitflow-guard@0.0.20
|
|
53
|
+
```
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
> **版本坑**: 裸 `add` 或不带版本的 `npm i` 装的是安装时刻的 `latest`——在 npm/pnpm 注册表缓存或镜像陈旧的机器上可能拿到旧版本。看到版本不对就锁版本。(DSH 用户: pnpm 打印的 peer 依赖 *警告* 属预期——DSH 启动时经共享模块回退提供 `@deepseek-ai/cordis` / `@deepseek-ai/dsh-tools`,插件正常工作。)
|
|
56
|
+
>
|
|
57
|
+
> hook 客户端(Claude Code · Codex · OpenCode · Antigravity)装完还要各做一步接线——**每个客户端一条命令**(见下)。Pi 拷贝一个文件;DSH 装完即已接线。
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
**第 2 步——接线(无需配置文件)。** 守卫内置**默认配置,开箱即用:默认保护 `develop`(integration)+ `main`(archive)**,零配置。你要做的只是让 AI 客户端去调用守卫——每个 stdin-hook 客户端一条命令(DSH 自动接线;Pi 拷文件,见下):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Claude Code → 本仓库 .claude/settings.json
|
|
63
|
+
gitflow-guard wire --client claude --project --yes
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Codex / OpenCode / Antigravity(各写各的配置文件; --yes 跳过 y/N 确认)
|
|
68
|
+
gitflow-guard wire --client codex --project --yes
|
|
69
|
+
gitflow-guard wire --client opencode --project --yes
|
|
70
|
+
gitflow-guard wire --client antigravity --project --yes # 实验支持
|
|
62
71
|
```
|
|
63
72
|
|
|
64
|
-
|
|
73
|
+
```bash
|
|
74
|
+
# 只预览不写入 / 移除 / 交互向导:
|
|
75
|
+
gitflow-guard wire --client claude --dry-run
|
|
76
|
+
gitflow-guard wire --client claude --unwire
|
|
77
|
+
gitflow-guard setup
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`wire` 对已有配置**非破坏性合并**(已存在的 hook 不动),默认只写**当前工程目录**;`--global`(本机所有仓库)写入前必先确认或需 `--yes`。各客户端的文件与格式见[安装详解](#安装详解)。
|
|
81
|
+
|
|
82
|
+
> ⚠️ **main 默认受保护。** trunk / 单分支工作流(所有人直推同一条分支)的用户,装完第一次直推 `main` 就会被拦——创建 `gitflow-guard.config.json` 写 `{ "enabled": false }`,或自行映射分支(见[配置参考](#配置参考))。`gitflow-guard status` 在默认配置生效时也会反复提示这一点。
|
|
65
83
|
|
|
66
84
|
**第 3 步——验证**。让 agent 执行 `git push origin develop`,预期工具调用被拒绝:
|
|
67
85
|
|
|
@@ -70,9 +88,9 @@ Error: [gitflow-guard] blocked: Protected branch "develop" forbids direct push
|
|
|
70
88
|
Next: Integration branch (develop) is updated via PR/MR from a feature branch: push the feature first, then `gh pr create --base develop` / `glab mr create --target-branch develop`.
|
|
71
89
|
```
|
|
72
90
|
|
|
73
|
-
**文案默认是英文**(面向国际化)
|
|
91
|
+
**文案默认是英文**(面向国际化)。要在你的项目里看中文,创建配置并加 `"locale": "zh"`;中文效果是:*已拦截:受保护分支「develop」禁止直推 / 下一步:集成分支(develop)由 PR/MR 合入 feature……*
|
|
74
92
|
|
|
75
|
-
**完成。**
|
|
93
|
+
**完成。** 守卫已用内置默认配置生效。想要更多关卡(`preview` / `production`)或改分支名?写一个 `gitflow-guard.config.json`,只写你在意的字段,其余保持内置默认。完整判定表见[门禁矩阵](#门禁矩阵拦什么放什么)。
|
|
76
94
|
|
|
77
95
|
### 完整实战示例——一个 feature 的端到端旅程
|
|
78
96
|
|
|
@@ -128,7 +146,7 @@ AI 编码 agent 在你的仓库里工作。它通过系统提示词、项目智
|
|
|
128
146
|
## 它能做什么
|
|
129
147
|
|
|
130
148
|
- **执行前拦截**:直推 / 强推 / 删除受保护角色分支(integration / preview / production / archive);agent 试图合入生产或归档。
|
|
131
|
-
- **角色驱动、完全可配**:`integration`
|
|
149
|
+
- **角色驱动、完全可配**:`integration`(内置默认 `develop`)是核心角色;`preview` / `production` / `archive` 是可选数组(精确名或正则),每个角色独立 `update`(`pr` / `flexible`)与 `mergeBy`,自定义配置深度合并于默认之上。
|
|
132
150
|
- **在关键处保留人的操作权**:生产与归档合并始终在你手上——插件阻止 agent 点击合并,于是你的动作*就是*确认。
|
|
133
151
|
- **任何命名都行**:分支名全由配置映射,绝无硬编码(见[配置参考](#配置参考))。
|
|
134
152
|
- **全程审计**:每次拦截都追加到用户级状态目录(macOS/Linux `~/.local/state/gitflow-guard/`,Windows `%LOCALAPPDATA%\gitflow-guard`)下的审计日志——在仓库外、绝不进版本库、位于 agent 可写沙箱之外,且同一仓库的所有 worktree 共享一份日志。
|
|
@@ -175,7 +193,7 @@ AI 编码 agent 在你的仓库里工作。它通过系统提示词、项目智
|
|
|
175
193
|
|
|
176
194
|
#### 1. 配置是唯一事实来源
|
|
177
195
|
|
|
178
|
-
分支名和规则没有任何硬编码。`integration`
|
|
196
|
+
分支名和规则没有任何硬编码。`integration` 以内置默认(`develop`)提供;`preview` / `production` / `archive` 是可选数组(精确名或正则),每个都有自己的 `update` 与 `mergeBy` — 在默认之上深度合并。同一个二进制从单条 `develop` 一直可扩到企业多环境流水线。
|
|
179
197
|
|
|
180
198
|
#### 2. 拦截发生在执行前,不是执行后
|
|
181
199
|
|
|
@@ -189,9 +207,28 @@ AI 编码 agent 在你的仓库里工作。它通过系统提示词、项目智
|
|
|
189
207
|
|
|
190
208
|
## 配置参考
|
|
191
209
|
|
|
210
|
+
### 内置默认配置 + 深度合并覆盖
|
|
211
|
+
|
|
212
|
+
守卫**默认开启**——不需要 `gitflow-guard.config.json`。默认保护:
|
|
213
|
+
|
|
214
|
+
| 默认值 | 角色 | 规则 |
|
|
215
|
+
|---|---|---|
|
|
216
|
+
| `develop` | **integration** | 禁直推;只经 PR/MR 合入(`update: "pr"`) |
|
|
217
|
+
| `main` | **archive** | 禁直推 / 禁 agent 合并;归档合并留给你(`mergeBy: "user"`) |
|
|
218
|
+
|
|
219
|
+
当你创建 `gitflow-guard.config.json` 时,它的字段会**深度合并覆盖默认**:写到的字段/角色替换默认,没写的保持默认。只写你想改的:
|
|
220
|
+
|
|
221
|
+
```jsonc
|
|
222
|
+
{
|
|
223
|
+
"branches": { "production": ["release-[\\w-]+"] } // 默认的 develop+main 不变;新增 production
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**完全关闭**(trunk / 单分支流程):`{ "enabled": false }`。误拦时改一个文件即可恢复;`gitflow-guard status` 始终说明当前生效的是内置默认还是自定义配置。
|
|
228
|
+
|
|
192
229
|
### 分支角色——插件校验的模型
|
|
193
230
|
|
|
194
|
-
|
|
231
|
+
**角色**把分支名(或正则)映射到规则集。`integration` 由内置默认提供;其余角色全部可选。
|
|
195
232
|
|
|
196
233
|
```text
|
|
197
234
|
feature 分支 ──(自由)──> integration(集成分支, PR/MR 合入)
|
|
@@ -205,10 +242,10 @@ archive(可选, 发布后你亲手归档)
|
|
|
205
242
|
| 角色 | 配置键 | 必填? | 强制行为 |
|
|
206
243
|
|---|---|---|---|
|
|
207
244
|
| **feature** | `featurePattern` | — | 自由: commit / push / 同步 / rebase |
|
|
208
|
-
| **integration** | `branches.integration` |
|
|
245
|
+
| **integration** | `branches.integration` | 默认(`develop`) | 禁直推(默认 `pr`);feature 只经 PR/MR 合入 |
|
|
209
246
|
| **preview** | `branches.preview`(数组) | 可选 | 禁直推;只走 PR/MR(环境终点) |
|
|
210
247
|
| **production** | `branches.production`(数组) | 可选 | 只走 PR/MR;合并仅限你(`mergeBy: "user"`) |
|
|
211
|
-
| **archive** | `branches.archive`(数组) |
|
|
248
|
+
| **archive** | `branches.archive`(数组) | 默认(`main`) | 允许 agent 创建指向它的 PR/MR; 合并仍限用户亲手 |
|
|
212
249
|
|
|
213
250
|
### 自定义分支名与规则——任何命名都可以
|
|
214
251
|
|
|
@@ -248,10 +285,10 @@ archive(可选, 发布后你亲手归档)
|
|
|
248
285
|
|
|
249
286
|
```jsonc
|
|
250
287
|
{
|
|
251
|
-
"enabled": true, //
|
|
288
|
+
"enabled": true, // 默认 true — 写 false 即关闭守卫
|
|
252
289
|
"featurePattern": "feature/[\\w-]+", // 识别工作/feature 分支的 JS 正则
|
|
253
290
|
"branches": {
|
|
254
|
-
"integration": { "branches": ["develop"], "update": "pr" }, //
|
|
291
|
+
"integration": { "branches": ["develop"], "update": "pr" }, // 默认 ["develop"] — 省略即保持默认
|
|
255
292
|
"preview": { "branches": ["ita1"], "update": "pr" }, // 可选
|
|
256
293
|
"production": { "branches": ["prd"], "update": "pr", "mergeBy": "user" }, // 可选
|
|
257
294
|
"archive": ["main"] // 可选
|
|
@@ -276,8 +313,8 @@ archive(可选, 发布后你亲手归档)
|
|
|
276
313
|
registerLocale('fr', fr)
|
|
277
314
|
```
|
|
278
315
|
- **未注册语言**:拦截路径对未注册的 `"locale"` 静默回退英文(设计如此——hook 不因文案缺失卡死),笔误因此容易被忽略;一行告警在 `gitflow-guard status` 中可见。
|
|
279
|
-
-
|
|
280
|
-
- **strict 模式**:默认配置损坏时 stderr 告警一次后放行(fail-open,避免一个笔误卡死工具管道);`"strict": true` 把配置异常与内部错误翻转为**拦截**(fail-closed)
|
|
316
|
+
- **校验**:角色条目重叠会被拒;非法正则会报错。**任何配置错误都会让该项目的守卫回退为"未启用"并上报**(而不是用半吊子配置)。注意:你覆盖的角色若与默认角色同名(如把 `main` 映射为 integration 而默认 archive 仍是 `main`)会触发重叠报错——需一并覆盖或去掉另一角色。
|
|
317
|
+
- **strict 模式**:默认配置损坏时 stderr 告警一次后放行(fail-open,避免一个笔误卡死工具管道);`"strict": true` 把配置异常与内部错误翻转为**拦截**(fail-closed)——供高风险仓库选用。显式 `enabled: false` 保持静默;而*文件不存在*不再是"未启用"——内置默认(develop+main)直接生效。
|
|
281
318
|
|
|
282
319
|
---
|
|
283
320
|
|
|
@@ -292,6 +329,12 @@ archive(可选, 发布后你亲手归档)
|
|
|
292
329
|
| 指向 archive 的 PR/MR | ✅ 可创建;🚫 合并被拦(你在 UI 合并) |
|
|
293
330
|
| 在 integration / preview 上 `git merge feature/x`(本地) | 🚫 拦(须 PR/MR);`update: flexible` 则放行 |
|
|
294
331
|
| 串联命令(`checkout develop && merge feature/x`) | 🚫 拦——逐段模拟分支切换,无法绕序 |
|
|
332
|
+
| 强制重建受保护分支(`git checkout -B/-C <分支>` / `git switch -C`) | 🚫 拦(直改 ref-update 门禁) |
|
|
333
|
+
| 用 `git symbolic-ref` 重定向/删除受保护分支 | 🚫 拦(直改 ref-update 门禁) |
|
|
334
|
+
| 在 integration / preview / production / archive 上 `git cherry-pick` / `git revert` | 🚫 拦(受保护分支上改写历史);`-n`/`--no-commit` 与 `--abort`/`--continue`/`--skip`/`--quit` 放行 |
|
|
335
|
+
| `sudo` 包装的 git 命令(特权外壳) | 🚫 剥壳(含 `sudo -u …`)后按内层命令判定 |
|
|
336
|
+
|
|
337
|
+
> 两处**刻意不拦**的边界,防止后来者「顺手堵上」造成语义回归:`git tag -f` 移动 tag(即使指向受保护分支)维持豁免——tag 不在分支角色守卫范围,与 `push --tags` 同型;受保护分支上的普通 `git commit` 维持放行——守卫只管分支角色与合入路径、不管内容,后续 `git push` 仍被拦(远端零污染)。
|
|
295
338
|
|
|
296
339
|
PR/MR 目标通过 `gh pr view`(GitHub)或 `glab mr view`(GitLab)解析;没有平台 CLI 时插件走保守路径。
|
|
297
340
|
|
|
@@ -309,14 +352,14 @@ PR/MR 目标通过 `gh pr view`(GitHub)或 `glab mr view`(GitLab)解析;没有
|
|
|
309
352
|
|
|
310
353
|
| 客户端 | 安装命令 | 装完再做什么 |
|
|
311
354
|
|---|---|---|
|
|
312
|
-
| DSH | `dsh plugin --profile web add agents-gitflow-guard@0.0.
|
|
313
|
-
| Claude Code · Codex · OpenCode · Antigravity | `npm i -g agents-gitflow-guard@0.0.
|
|
314
|
-
| Pi | `npm i -D agents-gitflow-guard@0.0.
|
|
355
|
+
| DSH | `dsh plugin --profile web add agents-gitflow-guard@0.0.20` | 重启 DSH——插件自动挂为 profile 层 |
|
|
356
|
+
| Claude Code · Codex · OpenCode · Antigravity | `npm i -g agents-gitflow-guard@0.0.20` | `gitflow-guard wire --client <名>`——每个客户端一条命令(见下) |
|
|
357
|
+
| Pi | `npm i -D agents-gitflow-guard@0.0.20` | 把 `pi/gitflow-guard.ts` 拷进 `.pi/extensions/`(见下) |
|
|
315
358
|
|
|
316
359
|
**DSH —— 进程内插件**(标准路径,已在[快速开始](#快速开始30-秒用上)覆盖):
|
|
317
360
|
|
|
318
361
|
```bash
|
|
319
|
-
dsh plugin --profile web add agents-gitflow-guard@0.0.
|
|
362
|
+
dsh plugin --profile web add agents-gitflow-guard@0.0.20 # 建议锁版本, 见上文提示
|
|
320
363
|
```
|
|
321
364
|
|
|
322
365
|
然后重启 DSH。升级用同一命令,再重启一次。
|
|
@@ -330,13 +373,17 @@ dsh plugin --profile web add file:/path/to/agents-gitflow-guard
|
|
|
330
373
|
|
|
331
374
|
包自带 `dsh.bundle.patch` 声明,`dsh plugin add` 自动把它挂为 profile 层,无需手工编辑 profile。
|
|
332
375
|
|
|
333
|
-
**各 agent 独立 hook**——Claude Code / Codex / OpenCode / Antigravity,不依赖 DSH。全局装一次 CLI
|
|
376
|
+
**各 agent 独立 hook**——Claude Code / Codex / OpenCode / Antigravity,不依赖 DSH。全局装一次 CLI,然后**每客户端一条命令接线**(守卫凭内置默认配置已默认开启,接线是唯一剩下的事):
|
|
334
377
|
|
|
335
378
|
```bash
|
|
336
|
-
npm i -g agents-gitflow-guard@0.0.
|
|
379
|
+
npm i -g agents-gitflow-guard@0.0.20 # 提供 `gitflow-guard` 二进制
|
|
380
|
+
gitflow-guard wire --client claude --project --yes
|
|
381
|
+
gitflow-guard wire --client codex --project --yes
|
|
382
|
+
gitflow-guard wire --client opencode --project --yes
|
|
383
|
+
gitflow-guard wire --client antigravity --project --yes # 实验支持
|
|
337
384
|
```
|
|
338
385
|
|
|
339
|
-
|
|
386
|
+
`wire` 读取已有配置文件(如有)并把 hook 条目合入——不碰其他内容、幂等(已接则跳过)、支持 `--dry-run` 预览与 `--unwire` 移除、写 `--global` 前必先询问。它写入的准确文件(供参考,也可代替 `wire` 手写)是:
|
|
340
387
|
|
|
341
388
|
```jsonc
|
|
342
389
|
// Claude Code — .claude/settings.json
|
|
@@ -389,7 +436,7 @@ hooks:
|
|
|
389
436
|
Pi 以进程内扩展装载(没有 stdin payload,也没有子进程 hook)。把随包发布的入口装进项目、包留在 devDependencies:
|
|
390
437
|
|
|
391
438
|
```bash
|
|
392
|
-
npm i -D agents-gitflow-guard@0.0.
|
|
439
|
+
npm i -D agents-gitflow-guard@0.0.20
|
|
393
440
|
mkdir -p .pi/extensions
|
|
394
441
|
cp node_modules/agents-gitflow-guard/pi/gitflow-guard.ts .pi/extensions/gitflow-guard.ts
|
|
395
442
|
```
|
|
@@ -403,7 +450,8 @@ cp node_modules/agents-gitflow-guard/pi/gitflow-guard.ts .pi/extensions/gitflow-
|
|
|
403
450
|
- hook 读 stdin payload,按**各平台协议**作答:Claude Code / OpenCode → `exit 2`(stderr 展示原因 + "下一步"提示);Codex → stdout 输出 JSON `{"hookSpecificOutput":{"permissionDecision":"deny",...}}`;Antigravity → stdout 输出 `{"decision":"deny","reason":...}` 且 **exit 0**(Antigravity 要求 exit 0,拒绝 hookSpecificOutput/非 allow 值)。Pi 没有 stdin 协议:进程内扩展监听官方 `tool_call` 事件,经返回值 `{ block: true, reason }` 拒绝(守卫 CLI 子进程只承载内部 exit-2 契约)。
|
|
404
451
|
- 只需要**执行前事件**:守卫在命令执行*之前*拦截;没有特许可事后消费,因此无需执行后钩子。
|
|
405
452
|
- 上面示例调的是全局安装的 `gitflow-guard`(`npm i -g`)。若 hook 子进程在它的 `PATH` 里找不到,就指向 `npm bin -g` 给出的完整二进制路径——hook 子进程不一定继承你交互 shell 的 PATH。本仓库自带配置里的 `bin/gitflow-guard.mjs` 路径只对 checkout 贡献者有效。
|
|
406
|
-
-
|
|
453
|
+
- **默认开启**:内置默认配置(integration=`develop`, archive=`main`)无需任何文件即生效。trunk / 单分支仓库:建 `gitflow-guard.config.json` 写 `{ "enabled": false }`,或自行映射分支。自定义配置在默认之上**深度合并**——只写你想改的字段。
|
|
454
|
+
- `wire` 从不删除或重写你已有的 hook 条目——只添加自己的命令(去重);`--unwire` 精确移除同一条命令。
|
|
407
455
|
|
|
408
456
|
---
|
|
409
457
|
|
|
@@ -411,7 +459,7 @@ cp node_modules/agents-gitflow-guard/pi/gitflow-guard.ts .pi/extensions/gitflow-
|
|
|
411
459
|
|
|
412
460
|
### 我的分支不叫默认名字,能用吗?
|
|
413
461
|
|
|
414
|
-
能用——分支名没有任何写死。`integration`
|
|
462
|
+
能用——分支名没有任何写死。`integration` 由内置默认提供(`develop`),自定义配置在默认之上深度合并;它的条目(以及 `preview`/`production`/`archive` 的)可以是任意精确分支名或正则。`featurePattern` 告诉插件怎么认你的工作分支。
|
|
415
463
|
|
|
416
464
|
把集成分支叫 `master`、加一个 `beta` 预览、feature 前缀用 `fix/`——写进配置即可;拦截、报告、审计都跟着你的命名走。没有任何你必须遵守的约定,只有你声明的映射。见[自定义分支名与规则](#自定义分支名与规则任何命名都可以)。
|
|
417
465
|
|
|
@@ -427,7 +475,7 @@ cp node_modules/agents-gitflow-guard/pi/gitflow-guard.ts .pi/extensions/gitflow-
|
|
|
427
475
|
|
|
428
476
|
不是,请注意别把它当安全工具。它是工作流守卫:把既定流程变成可机制执行的东西。基于文本的命令识别天然是尽力而为——铁心混淆命令的 agent 可以绕过解析器。
|
|
429
477
|
|
|
430
|
-
在其支持的命令形态内,角色边界在本地强制生效:合入受保护角色分支(integration / preview / production / archive)必须走配置好的路径(PR/MR,或生产/归档的人工合并)。常见混淆包装已纳入分类与拦截——shell 包装(`sh -c` / `bash -lc`)、子 shell 与反引号/`$()` 内嵌、`env`/`command`/`nohup`/`xargs` 前缀与 `VAR=x` 赋值、绝对路径、管道与 `||` 后半段、git 全局选项(`-C .`、`--git-dir=…`)、通配 refspec(`refs/heads/*:refs/heads/*`)、当 fetch+merge 用的 `git pull`,以及 `send-pack`/`update-ref` 等 plumbing
|
|
478
|
+
在其支持的命令形态内,角色边界在本地强制生效:合入受保护角色分支(integration / preview / production / archive)必须走配置好的路径(PR/MR,或生产/归档的人工合并)。常见混淆包装已纳入分类与拦截——shell 包装(`sh -c` / `bash -lc`)、子 shell 与反引号/`$()` 内嵌、`env`/`command`/`nohup`/`xargs`/`sudo` 前缀与 `VAR=x` 赋值、绝对路径、管道与 `||` 后半段、git 全局选项(`-C .`、`--git-dir=…`)、通配 refspec(`refs/heads/*:refs/heads/*`)、当 fetch+merge 用的 `git pull`,以及 `send-pack`/`update-ref`/`symbolic-ref` 等 plumbing;强制重建受保护分支(`checkout -B`/`switch -C`)与受保护分支上的 cherry-pick/revert 由 ref-update / ref-move 门禁拦截。可执行对抗语料见 `tests/accuracy-audit.spec.ts`。
|
|
431
479
|
|
|
432
480
|
已知**本地不可防**的通道:直连 forge API(`gh api repos/…/pulls/N/merge`、`curl`)与解释器子进程内嵌(`node -e "child_process.exec('git push …')"`);任意深度的引号/编码变换天然只能尽力而为。真正不可绕过的边界在你托管服务的分支保护设置。两边都用——把本守卫当作即时反馈与审计留痕,而不是安全边界。
|
|
433
481
|
|
|
@@ -457,7 +505,7 @@ cp node_modules/agents-gitflow-guard/pi/gitflow-guard.ts .pi/extensions/gitflow-
|
|
|
457
505
|
|
|
458
506
|
半吊子配置绝不会意外生效:任何校验错误都会让该项目的守卫禁用并上报错误。
|
|
459
507
|
|
|
460
|
-
|
|
508
|
+
常见错误:覆盖的角色与默认角色同名(如把 `main` 设为 integration 而默认 archive 仍是 `main`——显式重叠报错,需一并覆盖或去掉另一角色)、同一个分支被配到两个角色里(显式拒绝)、`featurePattern` 写不成合法正则(报错)。失败提示很明确,文件又是一个 JSON 对象,通常三十秒改好。
|
|
461
509
|
|
|
462
510
|
---
|
|
463
511
|
|
|
@@ -480,7 +528,7 @@ MIT,免费,无条件。随便用、随便改、随便发,唯一义务是保留
|
|
|
480
528
|
|
|
481
529
|
| 术语 | 含义 |
|
|
482
530
|
|---|---|
|
|
483
|
-
| **integration** |
|
|
531
|
+
| **integration** | 集成分支,核心角色(内置默认 `develop`);feature 经 PR/MR 合入;受保护 |
|
|
484
532
|
| **preview** | 可选环境终点分支(`branches.preview`,数组);只走 PR/MR 更新 |
|
|
485
533
|
| **production** | 可选生产分支(`branches.production`,数组);PR/MR + 合并仅限用户 |
|
|
486
534
|
| **archive** | 可选的发布后归档分支(`branches.archive`,数组);允许 agent 创建指向它的 PR/MR,合并仍限用户亲手 |
|