agents-gitflow-guard 0.0.3 → 0.0.5
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 +33 -7
- package/README.zh.md +28 -6
- package/lib/cli.mjs +10 -2
- package/lib/index.mjs +1 -1
- package/lib/{src-LvZrsHEn.mjs → src-2TtpUFC9.mjs} +6 -5
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> **Are you tired of agents skipping your GitFlow?**
|
|
4
4
|
|
|
5
|
-
A configurable branch-role guard for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH)
|
|
5
|
+
A configurable branch-role guard for AI coding agents — [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH), Claude Code, Codex, and OpenCode.
|
|
6
|
+
You define your own branches —
|
|
6
7
|
**integration** (features merge in via PR/MR), **preview** (env endpoints), **production**, **archive** — each with its own update rules. Agents can't skip the flow, and sensitive merges stay in your hands.
|
|
7
8
|
|
|
8
9
|
[中文文档](README.zh.md) · [License](LICENSE)
|
|
@@ -308,9 +309,10 @@ dsh plugin --profile web add file:/path/to/agents-gitflow-guard
|
|
|
308
309
|
|
|
309
310
|
The package declares `dsh.bundle.patch`, so `dsh plugin add` automatically makes it a profile layer — no manual profile editing.
|
|
310
311
|
|
|
311
|
-
**Claude Code
|
|
312
|
+
**Claude Code / Codex / OpenCode hooks** — the same guard inside those agents, no DSH required. This repo ships project configs at `.claude/settings.json` (Claude Code), `.codex/hooks.json` (Codex) and `.opencode/hook/hooks.yaml` (OpenCode); any other repo adds its own:
|
|
312
313
|
|
|
313
|
-
```
|
|
314
|
+
```jsonc
|
|
315
|
+
// Claude Code — .claude/settings.json
|
|
314
316
|
{
|
|
315
317
|
"hooks": {
|
|
316
318
|
"PreToolUse": [
|
|
@@ -320,9 +322,30 @@ The package declares `dsh.bundle.patch`, so `dsh plugin add` automatically makes
|
|
|
320
322
|
}
|
|
321
323
|
```
|
|
322
324
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
325
|
+
```jsonc
|
|
326
|
+
// Codex — .codex/hooks.json
|
|
327
|
+
{
|
|
328
|
+
"hooks": {
|
|
329
|
+
"PreToolUse": [
|
|
330
|
+
{ "matcher": "^Bash$", "hooks": [{ "type": "command", "command": "node bin/gitflow-guard.mjs check --platform codex" }] }
|
|
331
|
+
]
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
```yaml
|
|
337
|
+
# OpenCode — .opencode/hook/hooks.yaml
|
|
338
|
+
hooks:
|
|
339
|
+
- id: gitflow-guard
|
|
340
|
+
event: tool.before.bash
|
|
341
|
+
actions:
|
|
342
|
+
- bash: |
|
|
343
|
+
node "$OPENCODE_PROJECT_DIR/bin/gitflow-guard.mjs" check --platform opencode
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
- 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 (Codex also accepts the legacy `{"decision":"block","reason":...}` shape, which `--platform antigravity` uses).
|
|
347
|
+
- 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.
|
|
348
|
+
- 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) also work from a checkout.
|
|
326
349
|
- Fully opt-in: the hook does nothing unless the repo has `gitflow-guard.config.json` with `enabled: true`.
|
|
327
350
|
|
|
328
351
|
---
|
|
@@ -435,9 +458,12 @@ npm install
|
|
|
435
458
|
npm test # unit tests: classify / gate / config / cli / repo / platform
|
|
436
459
|
npm run typecheck # tsc --noEmit, 0 errors
|
|
437
460
|
npm run build # tsdown → lib/ (CLI and plugin share the build)
|
|
461
|
+
npm run verify:matrix # continuous cross-agent regression: DSH logic + Claude Code / Codex / antigravity hook wiring
|
|
438
462
|
```
|
|
439
463
|
|
|
440
|
-
**Rule**: any logic change must pass a 0-error build + all green tests before done.
|
|
464
|
+
**Rule**: any logic change must pass a 0-error build + all green tests + a green `verify:matrix` before done.
|
|
465
|
+
|
|
466
|
+
**Adding a new agent client** (e.g. Gemini / OpenCode / Cursor): all of these must change in one commit — `src/platform.ts` (+tests, `HookPlatform` union), a repo hook config beside `.claude/settings.json` / `.codex/hooks.json`, `.agents/hooks/references/<tool>.md`, `scripts/verify-matrix.mjs`, the README hook section and the top tagline, `package.json` description/keywords, and `CHANGELOG`. Done only when `npm run verify:matrix` is green. (Same checklist in [AGENTS.md](AGENTS.md) §8.)
|
|
441
467
|
|
|
442
468
|
---
|
|
443
469
|
|
package/README.zh.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **有没有受够了 agent 跳过你的合入流程?**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
一个可自由配置分支角色的流程守卫,为 AI 编码 agent 而生——[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)、Claude Code、Codex、OpenCode。
|
|
6
6
|
你自己定义分支——**集成分支**(feature 经 PR/MR 合入)、**预览分支**(环境终点)、**生产分支**、**归档分支**——每个角色各自配规则。agent 无法跳过流程,敏感合并始终留在你手上。
|
|
7
7
|
|
|
8
8
|
[English](README.md) · [许可证](LICENSE)
|
|
@@ -306,9 +306,10 @@ dsh plugin --profile web add file:/path/to/agents-gitflow-guard
|
|
|
306
306
|
|
|
307
307
|
包自带 `dsh.bundle.patch` 声明,`dsh plugin add` 自动把它挂为 profile 层,无需手工编辑 profile。
|
|
308
308
|
|
|
309
|
-
**Claude Code hook
|
|
309
|
+
**Claude Code / Codex / OpenCode hook**——同一守卫也能在这些 agent 里跑,不依赖 DSH。本仓库已自带 `.claude/settings.json`(Claude Code)、`.codex/hooks.json`(Codex)和 `.opencode/hook/hooks.yaml`(OpenCode);其他仓库加自己的 hooks:
|
|
310
310
|
|
|
311
|
-
```
|
|
311
|
+
```jsonc
|
|
312
|
+
// Claude Code — .claude/settings.json
|
|
312
313
|
{
|
|
313
314
|
"hooks": {
|
|
314
315
|
"PreToolUse": [
|
|
@@ -318,9 +319,30 @@ dsh plugin --profile web add file:/path/to/agents-gitflow-guard
|
|
|
318
319
|
}
|
|
319
320
|
```
|
|
320
321
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
322
|
+
```jsonc
|
|
323
|
+
// Codex — .codex/hooks.json
|
|
324
|
+
{
|
|
325
|
+
"hooks": {
|
|
326
|
+
"PreToolUse": [
|
|
327
|
+
{ "matcher": "^Bash$", "hooks": [{ "type": "command", "command": "node bin/gitflow-guard.mjs check --platform codex" }] }
|
|
328
|
+
]
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
```yaml
|
|
334
|
+
# OpenCode — .opencode/hook/hooks.yaml
|
|
335
|
+
hooks:
|
|
336
|
+
- id: gitflow-guard
|
|
337
|
+
event: tool.before.bash
|
|
338
|
+
actions:
|
|
339
|
+
- bash: |
|
|
340
|
+
node "$OPENCODE_PROJECT_DIR/bin/gitflow-guard.mjs" check --platform opencode
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
- hook 读 stdin payload,按**各平台协议**作答:Claude Code / OpenCode → `exit 2`(stderr 展示原因 + "下一步"提示);Codex → stdout 输出 JSON `{"hookSpecificOutput":{"permissionDecision":"deny",...}}`(Codex 也兼容旧的 `{"decision":"block","reason":...}` 形状,`--platform antigravity` 用的就是它)。
|
|
344
|
+
- 只需要**执行前事件**:守卫在命令执行*之前*拦截;没有特许可事后消费,因此无需执行后钩子。
|
|
345
|
+
- 用**绝对路径**指向二进制——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`(均在项目目录执行)也可以。
|
|
324
346
|
- 完全 opt-in:仓库没有 `gitflow-guard.config.json`(或 `enabled` 非 true)时 hook 什么都不做。
|
|
325
347
|
|
|
326
348
|
---
|
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-
|
|
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";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
//#region src/platform.ts
|
|
@@ -30,6 +30,9 @@ function extractHookPayload(raw, platform = "auto") {
|
|
|
30
30
|
if (plat === "claude" || plat === "codex") {
|
|
31
31
|
command = str(j.tool_input?.command);
|
|
32
32
|
cwd = str(j.cwd);
|
|
33
|
+
} else if (plat === "opencode") {
|
|
34
|
+
command = str(j.tool_args?.command) || str(j.tool_args?.cmd);
|
|
35
|
+
cwd = str(j.cwd);
|
|
33
36
|
} else if (plat === "copilot") {
|
|
34
37
|
command = str(j.tool_input?.command) || str(j.toolArgs?.command);
|
|
35
38
|
cwd = str(j.cwd);
|
|
@@ -45,12 +48,13 @@ function extractHookPayload(raw, platform = "auto") {
|
|
|
45
48
|
event: eventFrom(j.hook_event_name)
|
|
46
49
|
};
|
|
47
50
|
}
|
|
48
|
-
/** 按 payload 判别平台: 非空 turn_id→codex, toolCall→antigravity, 其余→claude */
|
|
51
|
+
/** 按 payload 判别平台: 非空 turn_id→codex, toolCall→antigravity, tool_args→opencode, 其余→claude */
|
|
49
52
|
function detectPlatform(raw) {
|
|
50
53
|
const j = parseRaw(raw);
|
|
51
54
|
if (!j) return "claude";
|
|
52
55
|
if (j.turn_id) return "codex";
|
|
53
56
|
if (j.toolCall) return "antigravity";
|
|
57
|
+
if (j.tool_args) return "opencode";
|
|
54
58
|
return "claude";
|
|
55
59
|
}
|
|
56
60
|
/** deny 编码: 各平台的拦截协议(exit 码 + stdout/stderr) */
|
|
@@ -60,6 +64,10 @@ function encodeDeny(platform, reason) {
|
|
|
60
64
|
exitCode: 2,
|
|
61
65
|
stderr: reason
|
|
62
66
|
};
|
|
67
|
+
case "opencode": return {
|
|
68
|
+
exitCode: 2,
|
|
69
|
+
stderr: reason
|
|
70
|
+
};
|
|
63
71
|
case "codex": return {
|
|
64
72
|
exitCode: 0,
|
|
65
73
|
stdout: JSON.stringify({ hookSpecificOutput: {
|
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-
|
|
1
|
+
import { a as name, i as formatDeny, n as apply, o as stateDir, r as evaluateCommand, t as appendAudit } from "./src-2TtpUFC9.mjs";
|
|
2
2
|
export { appendAudit, apply, evaluateCommand, formatDeny, name, stateDir };
|
|
@@ -418,13 +418,13 @@ const en = {
|
|
|
418
418
|
Usage:
|
|
419
419
|
gitflow-guard status [--repo <path>]
|
|
420
420
|
gitflow-guard audit [--lines <count>] [--repo <path>]
|
|
421
|
-
gitflow-guard check [--platform <claude|
|
|
421
|
+
gitflow-guard check [--platform <auto|claude|codex|opencode|antigravity>] [--command "<cmd>"] [--repo <path>]
|
|
422
422
|
gitflow-guard --help
|
|
423
423
|
|
|
424
424
|
Notes:
|
|
425
425
|
status/audit are read-only; the agent can self-inspect.
|
|
426
|
-
check reads the hook payload on stdin (
|
|
427
|
-
pre/post hooks of agents
|
|
426
|
+
check reads the hook payload on stdin (platform-specific protocol: claude/opencode exit 2,
|
|
427
|
+
codex/antigravity JSON on stdout) and is meant for pre/post hooks of AI agents.`
|
|
428
428
|
};
|
|
429
429
|
const zh = {
|
|
430
430
|
"role.integration": () => "集成分支",
|
|
@@ -488,12 +488,13 @@ const zh = {
|
|
|
488
488
|
用法:
|
|
489
489
|
gitflow-guard status [--repo <路径>]
|
|
490
490
|
gitflow-guard audit [--lines <数量>] [--repo <路径>]
|
|
491
|
-
gitflow-guard check [--platform <claude|
|
|
491
|
+
gitflow-guard check [--platform <auto|claude|codex|opencode|antigravity>] [--command "<cmd>"] [--repo <路径>]
|
|
492
492
|
gitflow-guard --help
|
|
493
493
|
|
|
494
494
|
说明:
|
|
495
495
|
status/audit 只读, agent 可自查。
|
|
496
|
-
check 读 stdin hook payload 做门禁(
|
|
496
|
+
check 读 stdin hook payload 做门禁(平台协议: claude/opencode exit 2, codex/antigravity stdout JSON),
|
|
497
|
+
供 Claude Code / Codex / OpenCode 等 agent 的 pre/post hook 调用。`
|
|
497
498
|
};
|
|
498
499
|
const MESSAGE_KEYS = Object.keys(en);
|
|
499
500
|
/** key 合法性与 en/zh 双字典完整性检查(开发期一次) */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents-gitflow-guard",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"description": "A configurable branch-role guard for AI coding agents (DSH / Claude Code / Codex / OpenCode) — integration/preview/production/archive, each with its own update rules",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.mjs",
|
|
@@ -26,7 +26,11 @@
|
|
|
26
26
|
"keywords": [
|
|
27
27
|
"dsh",
|
|
28
28
|
"deepseek-harness",
|
|
29
|
+
"claude-code",
|
|
30
|
+
"codex",
|
|
31
|
+
"opencode",
|
|
29
32
|
"plugin",
|
|
33
|
+
"hook",
|
|
30
34
|
"gitflow",
|
|
31
35
|
"git",
|
|
32
36
|
"workflow",
|
|
@@ -46,7 +50,8 @@
|
|
|
46
50
|
"build": "tsdown",
|
|
47
51
|
"prepack": "npm run build",
|
|
48
52
|
"test": "vitest run",
|
|
49
|
-
"typecheck": "tsc --noEmit"
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"verify:matrix": "npm run build && node scripts/verify-matrix.mjs"
|
|
50
55
|
},
|
|
51
56
|
"peerDependencies": {
|
|
52
57
|
"@deepseek-ai/cordis": "^4.0.1",
|