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 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**, then restart DSH (plugins load at process startup):
39
+ **Step 1 — install.** All six clients consume the same npm package `agents-gitflow-guard` — pick yours:
40
40
 
41
41
  ```bash
42
- # installs the latest release
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
- > **Version gotcha**: a bare `add` 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. The peer-dependency *warning* pnpm may print is expected: DSH supplies `@deepseek-ai/cordis` / `@deepseek-ai/dsh-tools` through its shared profile module fallback at startup (the plugin works normally).
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
- Using another agent? The same npm package also serves Claude Code, Codex, OpenCode, Antigravity, and Pi — see the per-agent install table in [Installation in detail](#installation-in-detail).
51
+ ```bash
52
+ # Pi — in-process extension
53
+ npm i -D agents-gitflow-guard@0.0.20
54
+ ```
51
55
 
52
- **Step 2configure**, create `gitflow-guard.config.json` in your **project root**:
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
- ```jsonc
55
- {
56
- "enabled": true,
57
- "featurePattern": "feature/[\\w-]+",
58
- "branches": {
59
- "integration": ["develop"], // integration: features merge in via PR, protected
60
- "archive": ["main"] // archive: archived by you after release
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
- 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.
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; add `"locale": "zh"` to the config to switch to Chinese (see [Configuration Reference](#configuration-reference)).
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. 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.
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 only required role; `preview` / `production` / `archive` are optional arrays of branch names or regexes, each with its own update rules (`pr` / `flexible`, `mergeBy`).
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` 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.
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
- 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.
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` | always | no direct push (default `pr`); features merge in via PR/MR |
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) | optional | archive PR/MR may be created by agents; the merge stays user-hand only |
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, // opt-in: file exists AND 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" }, // REQUIRED
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**: `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.
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. A missing file or explicit `enabled: false` stays silent either way.
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.18` | restart DSH — the plugin auto-mounts as a profile layer |
317
- | Claude Code · Codex · OpenCode · Antigravity | `npm i -g agents-gitflow-guard@0.0.18` | wire a hook to the `gitflow-guard` binary (below) |
318
- | Pi | `npm i -D agents-gitflow-guard@0.0.18` | copy `pi/gitflow-guard.ts` into `.pi/extensions/` (below) |
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.18 # pin recommended, see note above
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 reference the `gitflow-guard` binary in each hook config:
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.18 # provides the `gitflow-guard` binary
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
- This repo ships project configs at `.claude/settings.json` (Claude Code), `.codex/hooks.json` (Codex), `.opencode/hook/hooks.yaml` (OpenCode) and `.agents/hooks.json` (Antigravity / Google); any other repo adds its own:
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.18
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
- - Fully opt-in: the hook does nothing unless the repo has `gitflow-guard.config.json` with `enabled: true`.
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` 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.
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: 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.
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 branch and only required role (`branches.integration`); features merge in via PR/MR; protected |
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 步——安装**,然后重启 DSH(插件在进程启动时加载):
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
- > **版本坑**: 裸 `add` 装的是安装时刻的 `latest`——在 npm/pnpm 注册表缓存或镜像陈旧的机器上可能拿到旧版本。看到版本不对就锁版本。pnpm 打印的 peer 依赖 *警告* 属预期: DSH 启动时经共享模块回退提供 `@deepseek-ai/cordis` / `@deepseek-ai/dsh-tools`(插件正常工作)。
45
+ ```bash
46
+ # Claude Code · Codex · OpenCode · Antigravity —— 独立 hook,不需要 DSH
47
+ npm i -g agents-gitflow-guard@0.0.20
48
+ ```
48
49
 
49
- 用的是别的 agent? 同一个 npm 包也适用于 Claude Code / Codex / OpenCode / Antigravity / Pi——见[安装详解](#安装详解)的逐客户端安装表。
50
+ ```bash
51
+ # Pi —— 进程内扩展
52
+ npm i -D agents-gitflow-guard@0.0.20
53
+ ```
50
54
 
51
- **第 2 步——配置**,在**项目根目录**创建 `gitflow-guard.config.json`:
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
- ```jsonc
54
- {
55
- "enabled": true,
56
- "featurePattern": "feature/[\\w-]+",
57
- "branches": {
58
- "integration": ["develop"], // 集成分支: feature 经 PR 合入, 受保护
59
- "archive": ["main"] // 归档分支: 发布后由你亲手合入
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
- 这一个文件就是全部配置:其中的 **`integration` 是唯一必填**角色;`preview` / `production` / `archive` 都是可选,只有你配了才启用对应关卡。插件按项目 opt-in——文件不存在或 `enabled: false` 时什么都不做。
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
- **文案默认是英文**(面向国际化)。要在你的项目里看中文,在 `gitflow-guard.config.json` 里加 `"locale": "zh"`;中文效果是:*已拦截:受保护分支「develop」禁止直推 / 下一步:集成分支(develop)由 PR/MR 合入 feature……*
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` 是唯一必填;`preview` / `production` / `archive` 是可选数组(精确名或正则),每个角色独立 `update`(`pr` / `flexible`)与 `mergeBy`。
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` 是唯一必填角色;`preview` / `production` / `archive` 是可选数组(精确名或正则),每个都有自己的 `update` 与 `mergeBy`。同一个二进制从单条 `develop` 一直可扩到企业多环境流水线。
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
- 只有 **`integration`** 是必填。其余全部可选——按你的流程配就好,每条目可以是精确分支名**或**正则。
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` | 必填 | 禁直推(默认 `pr`);feature 只经 PR/MR 合入 |
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`(数组) | 可选 | 允许 agent 创建指向它的 PR/MR; 合并仍限用户亲手 |
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, // opt-in: 文件存在且 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
- - **校验**:`integration` 必填;角色条目重叠会被拒;非法正则会报错。**任何错误都会让该项目的插件禁用并上报**(而不是用半吊子配置)
280
- - **strict 模式**:默认配置损坏时 stderr 告警一次后放行(fail-open,避免一个笔误卡死工具管道);`"strict": true` 把配置异常与内部错误翻转为**拦截**(fail-closed)——供高风险仓库选用。文件不存在或显式 `enabled: false` 两种模式下都保持静默。
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.18` | 重启 DSH——插件自动挂为 profile 层 |
313
- | Claude Code · Codex · OpenCode · Antigravity | `npm i -g agents-gitflow-guard@0.0.18` | 在各自 hook 配置里指向 `gitflow-guard` 二进制(见下) |
314
- | Pi | `npm i -D agents-gitflow-guard@0.0.18` | 把 `pi/gitflow-guard.ts` 拷进 `.pi/extensions/`(见下) |
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.18 # 建议锁版本, 见上文提示
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,然后引用 `gitflow-guard` 二进制:
376
+ **各 agent 独立 hook**——Claude Code / Codex / OpenCode / Antigravity,不依赖 DSH。全局装一次 CLI,然后**每客户端一条命令接线**(守卫凭内置默认配置已默认开启,接线是唯一剩下的事):
334
377
 
335
378
  ```bash
336
- npm i -g agents-gitflow-guard@0.0.18 # 提供 `gitflow-guard` 二进制
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
- 本仓库已自带 `.claude/settings.json`(Claude Code)、`.codex/hooks.json`(Codex)、`.opencode/hook/hooks.yaml`(OpenCode) `.agents/hooks.json`(Antigravity / Google);其他仓库加自己的 hooks:
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.18
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
- - 完全 opt-in:仓库没有 `gitflow-guard.config.json`(或 `enabled` true)时 hook 什么都不做。
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` 是唯一必填;它的条目(以及 `preview`/`production`/`archive` 的)可以是任意精确分支名或正则。`featurePattern` 告诉插件怎么认你的工作分支。
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。可执行对抗语料见 `tests/accuracy-audit.spec.ts`。
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
- 常见错误:`integration` 缺失(必填)、同一个分支被配到两个角色里(显式拒绝)、`featurePattern` 写不成合法正则(报错)。失败提示很明确,文件又是一个 JSON 对象,通常三十秒改好。
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** | 集成分支,唯一必填角色(`branches.integration`);feature 经 PR/MR 合入;受保护 |
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,合并仍限用户亲手 |