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 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). You define your own branches —
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 hook** — the same guard inside Claude Code, no DSH required. This repo already ships the config at `.claude/settings.json`; for any other repo, add these hooks:
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
- ```json
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
- - The hook reads the payload on stdin and answers `exit 0` (allow) or `exit 2` (block — stderr is the reason shown to the model, plus a "next step" hint).
324
- - Only `PreToolUse` is needed: the guard blocks *before* the command runs. There is no permit to consume afterwards, so no `PostToolUse` hooks are required.
325
- - Use an **absolute path** to the binary — hook subprocesses may not inherit your shell `PATH`. `${CLAUDE_PROJECT_DIR}/bin/gitflow-guard.mjs` (after `npm run build`) also works from a checkout.
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
- 一个可自由配置分支角色守卫的 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)插件。
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**——同一守卫也能在 Claude Code 里跑,不依赖 DSH。本仓库已自带 `.claude/settings.json`;其他仓库加这些 hooks:
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
- ```json
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
- - hook 读 stdin payload,按 `exit 0`(放行)/ `exit 2`(拦截,stderr 展示给模型的原因 + "下一步"提示)作答。
322
- - 只需要 `PreToolUse`:守卫在命令执行*之前*拦截;没有特许可事后消费,因此无需 `PostToolUse` 钩子。
323
- - 用**绝对路径**指向二进制——hook 子进程不一定继承你的 shell PATH。`${CLAUDE_PROJECT_DIR}/bin/gitflow-guard.mjs`(`npm run build` 后)也可以。
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-LvZrsHEn.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-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-LvZrsHEn.mjs";
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|auto>] [--command "<cmd>"] [--repo <path>]
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 (exit 0 = allow / 2 = block) and is meant for
427
- pre/post hooks of agents such as Claude Code.`
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|auto>] [--command "<cmd>"] [--repo <路径>]
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 做门禁(exit 0=放行 / 2=拦截), 供 Claude Code 等 agent 的 pre/post hook 调用。`
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.3",
4
- "description": "DSH plugin: a configurable branch-role guard — integration/preview/production/archive, each with its own update rules",
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",