agents-gitflow-guard 0.0.7 → 0.0.9

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
@@ -204,7 +204,7 @@ archive (optional; you archive after release)
204
204
  | **integration** | `branches.integration` | always | no direct push (default `pr`); features merge in via PR/MR |
205
205
  | **preview** | `branches.preview` (array) | optional | no direct push; updates via PR/MR only (env endpoints) |
206
206
  | **production** | `branches.production` (array) | optional | PR/MR only; merge by user only (`mergeBy: "user"`) |
207
- | **archive** | `branches.archive` (array) | optional | user-hand only agents cannot even create a PR |
207
+ | **archive** | `branches.archive` (array) | optional | archive PR/MR may be created by agents; the merge stays user-hand only |
208
208
 
209
209
  ### Customizing branch names & rules — any naming works
210
210
 
@@ -274,7 +274,7 @@ archive (optional; you archive after release)
274
274
  | direct push / force-push / delete integration / preview / production / archive | 🚫 block (integration/preview `flexible` direct push allowed) |
275
275
  | PR/MR: feature → integration / preview | ✅ allow |
276
276
  | PR/MR: feature → production | ✅ allow to create; **merge blocked** (you merge in UI) |
277
- | PR/MR targeting archive | 🚫 block |
277
+ | PR/MR into archive | ✅ create allowed; 🚫 merge blocked (you merge in UI) |
278
278
  | local `git merge feature/x` while on integration / preview | 🚫 block (PR/MR required); `update: flexible` allows |
279
279
  | chained commands (`checkout develop && merge feature/x`) | 🚫 blocked — branch switches are simulated per segment, no bypass |
280
280
 
@@ -354,6 +354,12 @@ hooks:
354
354
  }
355
355
  ```
356
356
 
357
+ **GitHub Copilot — deliberately no hook here.** Copilot ships its own guardrails for exactly this job: per-tool **allow/deny/ask** permissions and project **rules** (`rules.json` + `AGENTS.md`). Point Copilot users at the official docs instead of a plugin hook:
358
+
359
+ - [Allowing and denying tool use (GitHub Docs)](https://docs.github.com/en/copilot/how-tos/copilot-cli/use-copilot-cli/allowing-tools)
360
+ - [Adding custom rules for the Copilot coding agent (GitHub Docs)](https://docs.github.com/en/copilot/customizing-copilot/adding-custom-rules-for-the-copilot-coding-agent)
361
+ - Optional: Copilot also has a [hooks system](https://docs.github.com/en/copilot/reference/hooks-reference) (`preToolUse` → `permissionDecision:"deny"`) if you want command-level interception.
362
+
357
363
  - 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).
358
364
  - 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.
359
365
  - Use an **absolute path** to the binary — hook subprocesses may not inherit your shell `PATH`. `${CLAUDE_PROJECT_DIR}/bin/gitflow-guard.mjs` (Claude Code), `node bin/gitflow-guard.mjs` (Codex, runs from the project working directory), or `$OPENCODE_PROJECT_DIR/bin/gitflow-guard.mjs` (OpenCode) or `node bin/gitflow-guard.mjs` (Antigravity, relative to the workspace `.agents/` dir) also work from a checkout.
package/README.zh.md CHANGED
@@ -201,7 +201,7 @@ archive(可选, 发布后你亲手归档)
201
201
  | **integration** | `branches.integration` | 必填 | 禁直推(默认 `pr`);feature 只经 PR/MR 合入 |
202
202
  | **preview** | `branches.preview`(数组) | 可选 | 禁直推;只走 PR/MR(环境终点) |
203
203
  | **production** | `branches.production`(数组) | 可选 | 只走 PR/MR;合并仅限你(`mergeBy: "user"`) |
204
- | **archive** | `branches.archive`(数组) | 可选 | 仅用户——agent 连建 PR 都不行 |
204
+ | **archive** | `branches.archive`(数组) | 可选 | 允许 agent 创建指向它的 PR/MR; 合并仍限用户亲手 |
205
205
 
206
206
  ### 自定义分支名与规则——任何命名都可以
207
207
 
@@ -351,6 +351,12 @@ hooks:
351
351
  }
352
352
  ```
353
353
 
354
+ **GitHub Copilot —— 故意不提供 hook**。Copilot 自带这套守卫的原生能力: 工具级 **allow/deny/ask** 权限 + 项目 **rules**(`rules.json` + `AGENTS.md`)。对 Copilot 用户,直接引官方文档即可,不需要我们的插件:
355
+
356
+ - [允许和拒绝工具使用(GitHub Docs)](https://docs.github.com/en/copilot/how-tos/copilot-cli/use-copilot-cli/allowing-tools)
357
+ - [为 Copilot coding agent 添加自定义规则(GitHub Docs)](https://docs.github.com/en/copilot/customizing-copilot/adding-custom-rules-for-the-copilot-coding-agent)
358
+ - 可选: Copilot 也有官方 [hooks 系统](https://docs.github.com/en/copilot/reference/hooks-reference)(`preToolUse` → `permissionDecision:"deny"`),想要命令级拦截可以自己接。
359
+
354
360
  - 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 值)。
355
361
  - 只需要**执行前事件**:守卫在命令执行*之前*拦截;没有特许可事后消费,因此无需执行后钩子。
356
362
  - 用**绝对路径**指向二进制——hook 子进程不一定继承你的 shell PATH。Claude Code 用 `${CLAUDE_PROJECT_DIR}/bin/gitflow-guard.mjs`,Codex 用 `node bin/gitflow-guard.mjs`,OpenCode 用 `$OPENCODE_PROJECT_DIR/bin/gitflow-guard.mjs`、Antigravity 用 `node bin/gitflow-guard.mjs`(相对 workspace `.agents/` 目录)也可以。
@@ -432,7 +438,7 @@ MIT,免费,无条件。随便用、随便改、随便发,唯一义务是保留
432
438
  | **integration** | 集成分支,唯一必填角色(`branches.integration`);feature 经 PR/MR 合入;受保护 |
433
439
  | **preview** | 可选环境终点分支(`branches.preview`,数组);只走 PR/MR 更新 |
434
440
  | **production** | 可选生产分支(`branches.production`,数组);PR/MR + 合并仅限用户 |
435
- | **archive** | 可选发布后归档分支(`branches.archive`);仅用户亲手 |
441
+ | **archive** | `branches.archive`(数组) | 可选 | 允许 agent 创建指向它的 PR/MR; 合并仍限用户亲手 |
436
442
  | **feature 分支** | 你的工作分支,由 `featurePattern` 识别;自由区 |
437
443
  | **门禁矩阵** | 把每条被分类的命令映射为放行/拦截的判定表 |
438
444
  | **pre-execute** | 工具管线中拦截发生的钩子——在命令运行之前 |
package/lib/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { c as findRepoRoot, d as resolveLocale, f as loadConfig, i as formatDeny, l as gitRunner, m as classify, o as stateDir, p as roleMatches, r as evaluateCommand, s as currentBranch, u as makeT } from "./src-2TtpUFC9.mjs";
1
+ import { c as findRepoRoot, d as resolveLocale, f as loadConfig, i as formatDeny, l as gitRunner, m as classify, o as stateDir, p as roleMatches, r as evaluateCommand, s as currentBranch, u as makeT } from "./src-CeEqUVQP.mjs";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  //#region src/platform.ts
@@ -33,9 +33,6 @@ function extractHookPayload(raw, platform = "auto") {
33
33
  } else if (plat === "opencode") {
34
34
  command = str(j.tool_args?.command) || str(j.tool_args?.cmd);
35
35
  cwd = str(j.cwd);
36
- } else if (plat === "copilot") {
37
- command = str(j.tool_input?.command) || str(j.toolArgs?.command);
38
- cwd = str(j.cwd);
39
36
  } else if (plat === "antigravity") {
40
37
  command = str(j.toolCall?.args?.CommandLine);
41
38
  cwd = str(j.cwd);
@@ -84,10 +81,6 @@ function encodeDeny(platform, reason) {
84
81
  reason
85
82
  })
86
83
  };
87
- case "copilot": return {
88
- exitCode: 2,
89
- stderr: reason
90
- };
91
84
  }
92
85
  }
93
86
  //#endregion
package/lib/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as name, i as formatDeny, n as apply, o as stateDir, r as evaluateCommand, t as appendAudit } from "./src-2TtpUFC9.mjs";
1
+ import { a as name, i as formatDeny, n as apply, o as stateDir, r as evaluateCommand, t as appendAudit } from "./src-CeEqUVQP.mjs";
2
2
  export { appendAudit, apply, evaluateCommand, formatDeny, name, stateDir };
@@ -97,6 +97,7 @@ function parsePush(args, ctx) {
97
97
  for (const a of args) if (a === "-f" || a === "--force" || a === "--force-with-lease" || a.startsWith("--force-with-lease=")) force = true;
98
98
  else if (a === "--delete" || a === "-d") isDelete = true;
99
99
  else if (a === "--all" || a === "--mirror") all = true;
100
+ else if (a === "--tags") return [{ kind: "other" }];
100
101
  else if (a.startsWith("-")) {} else nonFlag.push(a);
101
102
  if (all) return [{
102
103
  kind: "push",
@@ -113,10 +114,15 @@ function parsePush(args, ctx) {
113
114
  delete: false
114
115
  }];
115
116
  return refspecs.map((ref) => {
117
+ let refForce = force;
118
+ if (ref.startsWith("+")) {
119
+ refForce = true;
120
+ ref = ref.slice(1);
121
+ }
116
122
  if (ref.startsWith(":")) return {
117
123
  kind: "push",
118
124
  dst: stripRefPrefix(ref.slice(1)) || null,
119
- force,
125
+ force: refForce,
120
126
  delete: true
121
127
  };
122
128
  const colon = ref.indexOf(":");
@@ -126,20 +132,20 @@ function parsePush(args, ctx) {
126
132
  return {
127
133
  kind: "push",
128
134
  dst: dst ? stripRefPrefix(dst) : null,
129
- force,
135
+ force: refForce,
130
136
  delete: deleteTarget || isDelete
131
137
  };
132
138
  }
133
139
  if (ref === "HEAD") return {
134
140
  kind: "push",
135
141
  dst: ctx.currentBranch ?? null,
136
- force,
142
+ force: refForce,
137
143
  delete: isDelete
138
144
  };
139
145
  return {
140
146
  kind: "push",
141
147
  dst: stripRefPrefix(ref),
142
- force,
148
+ force: refForce,
143
149
  delete: isDelete
144
150
  };
145
151
  });
@@ -379,8 +385,6 @@ const en = {
379
385
  "mergeFeature.next": (v) => `Push the feature branch first, then open a PR/MR into ${v.branch}.`,
380
386
  "prCreateNoTarget.why": () => "Cannot determine the PR/MR target branch",
381
387
  "prCreateNoTarget.next": (v) => `Specify --base/--target-branch explicitly (e.g. \`gh pr create --base ${v.base}\`).`,
382
- "prCreateArchive.why": () => "Archive branches are user-managed; PRs/MRs into them are not allowed",
383
- "prCreateArchive.next": () => "Do the release/archive yourself in your terminal or UI.",
384
388
  "prCreateHead.why": (v) => `Current branch (${v.head}) is not a feature branch, so it cannot be the source of a PR/MR into ${v.role}`,
385
389
  "prCreateHead.next": () => "Open the PR/MR into integration/preview/production from a feature/topic branch.",
386
390
  "prMergeProduction.why": () => "Merging into production is allowed only by you (click merge)",
@@ -449,8 +453,6 @@ const zh = {
449
453
  "mergeFeature.next": (v) => `先推 feature 分支, 再创建指向 ${v.branch} 的 PR/MR`,
450
454
  "prCreateNoTarget.why": () => "无法确定 PR/MR 目标分支",
451
455
  "prCreateNoTarget.next": (v) => `请显式指定 --base/--target-branch(如 gh pr create --base ${v.base})`,
452
- "prCreateArchive.why": () => "归档分支(archive)仅用户亲手操作, 不允许创建指向它的 PR/MR",
453
- "prCreateArchive.next": () => "发布/归档由你自己在终端或 UI 完成",
454
456
  "prCreateHead.why": (v) => `当前分支(${v.head})不是 feature 分支, 不能作为指向${v.role}的 PR/MR 源`,
455
457
  "prCreateHead.next": () => "请从 feature/topic 分支上创建指向集成/预览/生产分支的 PR/MR",
456
458
  "prMergeProduction.why": () => "合入生产(production)分支仅允许用户亲手点合并",
@@ -598,7 +600,6 @@ function decidePrCreate(c, facts, config, t) {
598
600
  const targetRole = roleOfBranch(c.target, config);
599
601
  const head = facts.currentBranch;
600
602
  const headRole = roleOfBranch(head, config);
601
- if (targetRole === "archive") return deny(t("prCreateArchive.why"), t("prCreateArchive.next"));
602
603
  if (targetRole === "integration" || targetRole === "preview" || targetRole === "production") {
603
604
  if (headRole !== "feature") return deny(t("prCreateHead.why", {
604
605
  head: head ?? t("head.unknown"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-gitflow-guard",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "A configurable branch-role guard for AI coding agents (DSH / Claude Code / Codex / OpenCode / Antigravity) — integration/preview/production/archive, each with its own update rules",
5
5
  "license": "MIT",
6
6
  "type": "module",