agents-gitflow-guard 0.0.21 → 0.0.22
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 -24
- package/README.zh.md +32 -24
- package/lib/cli.mjs +74 -81
- package/lib/index.mjs +1 -1
- package/lib/{src-DPJRoEJq.mjs → src-DMlNMtXn.mjs} +5 -4
- package/opencode/gitflow-guard.ts +98 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -40,17 +40,17 @@ You define your own branches —
|
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
# DSH — in-process plugin; restart DSH afterwards (plugins load at startup)
|
|
43
|
-
dsh plugin --profile web add agents-gitflow-guard@0.0.
|
|
43
|
+
dsh plugin --profile web add agents-gitflow-guard@0.0.22
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
# Claude Code · Codex · OpenCode · Antigravity — standalone hooks, no DSH needed
|
|
48
|
-
npm i -g agents-gitflow-guard@0.0.
|
|
48
|
+
npm i -g agents-gitflow-guard@0.0.22
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
52
|
# Pi — in-process extension
|
|
53
|
-
npm i -D agents-gitflow-guard@0.0.
|
|
53
|
+
npm i -D agents-gitflow-guard@0.0.22
|
|
54
54
|
```
|
|
55
55
|
|
|
56
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.)
|
|
@@ -68,7 +68,7 @@ gitflow-guard wire --client claude --project --yes
|
|
|
68
68
|
# Codex / OpenCode / Antigravity (each its own config file; --yes skips the y/N prompt)
|
|
69
69
|
gitflow-guard wire --client codex --project --yes
|
|
70
70
|
gitflow-guard wire --client opencode --project --yes
|
|
71
|
-
gitflow-guard wire --client antigravity --project --yes
|
|
71
|
+
gitflow-guard wire --client antigravity --project --yes
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
```bash
|
|
@@ -356,14 +356,14 @@ The PR/MR target is resolved via `gh pr view` (GitHub) or `glab mr view` (GitLab
|
|
|
356
356
|
|
|
357
357
|
| Agent | Install command | After that |
|
|
358
358
|
|---|---|---|
|
|
359
|
-
| DSH | `dsh plugin --profile web add agents-gitflow-guard@0.0.
|
|
360
|
-
| Claude Code · Codex · OpenCode · Antigravity | `npm i -g agents-gitflow-guard@0.0.
|
|
361
|
-
| Pi | `npm i -D agents-gitflow-guard@0.0.
|
|
359
|
+
| DSH | `dsh plugin --profile web add agents-gitflow-guard@0.0.22` | restart DSH — the plugin auto-mounts as a profile layer |
|
|
360
|
+
| Claude Code · Codex · OpenCode · Antigravity | `npm i -g agents-gitflow-guard@0.0.22` | `gitflow-guard wire --client <name>` — one command per client (below) |
|
|
361
|
+
| Pi | `npm i -D agents-gitflow-guard@0.0.22` | copy `pi/gitflow-guard.ts` into `.pi/extensions/` (below) |
|
|
362
362
|
|
|
363
363
|
**DSH — in-process plugin** (the standard path, already covered in [Quick Start](#quick-start--30-seconds-to-a-guarded-repo)):
|
|
364
364
|
|
|
365
365
|
```bash
|
|
366
|
-
dsh plugin --profile web add agents-gitflow-guard@0.0.
|
|
366
|
+
dsh plugin --profile web add agents-gitflow-guard@0.0.22 # pin recommended, see note above
|
|
367
367
|
```
|
|
368
368
|
|
|
369
369
|
Then restart DSH. Upgrades are the same command, followed by another restart.
|
|
@@ -380,11 +380,11 @@ The package declares `dsh.bundle.patch`, so `dsh plugin add` automatically makes
|
|
|
380
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):
|
|
381
381
|
|
|
382
382
|
```bash
|
|
383
|
-
npm i -g agents-gitflow-guard@0.0.
|
|
383
|
+
npm i -g agents-gitflow-guard@0.0.22 # provides the `gitflow-guard` binary
|
|
384
384
|
gitflow-guard wire --client claude --project --yes
|
|
385
385
|
gitflow-guard wire --client codex --project --yes
|
|
386
386
|
gitflow-guard wire --client opencode --project --yes
|
|
387
|
-
gitflow-guard wire --client antigravity --project --yes
|
|
387
|
+
gitflow-guard wire --client antigravity --project --yes
|
|
388
388
|
```
|
|
389
389
|
|
|
390
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:
|
|
@@ -411,18 +411,18 @@ gitflow-guard wire --client antigravity --project --yes # experimental
|
|
|
411
411
|
}
|
|
412
412
|
```
|
|
413
413
|
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
hooks
|
|
417
|
-
|
|
418
|
-
event: tool.before.bash
|
|
419
|
-
actions:
|
|
420
|
-
- bash: |
|
|
421
|
-
gitflow-guard check --platform opencode
|
|
414
|
+
```ts
|
|
415
|
+
// OpenCode — `.opencode/plugins/gitflow-guard.ts` (copy of the shipped `opencode/gitflow-guard.ts`;
|
|
416
|
+
// OpenCode 1.18+ dropped hooks.yaml, the extension point is now plugins — `tool.execute.before`,
|
|
417
|
+
// deny = throwing; `wire --client opencode` copies the file for you)
|
|
422
418
|
```
|
|
419
|
+
`gitflow-guard wire --client opencode` writes this file from the package; hand-write only if you know what you are doing.
|
|
423
420
|
|
|
424
421
|
```json
|
|
425
422
|
// Antigravity (Google) — .agents/hooks.json
|
|
423
|
+
// (Antigravity hook processes run with cwd = the config file's directory, so a relative
|
|
424
|
+
// bin/… path breaks; `wire` writes an absolute path for project scope, `gitflow-guard`
|
|
425
|
+
// from PATH for global scope. Shown here: globally-installed form.)
|
|
426
426
|
{
|
|
427
427
|
"gitflow-guard": {
|
|
428
428
|
"PreToolUse": [
|
|
@@ -440,7 +440,7 @@ hooks:
|
|
|
440
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:
|
|
441
441
|
|
|
442
442
|
```bash
|
|
443
|
-
npm i -D agents-gitflow-guard@0.0.
|
|
443
|
+
npm i -D agents-gitflow-guard@0.0.22
|
|
444
444
|
mkdir -p .pi/extensions
|
|
445
445
|
cp node_modules/agents-gitflow-guard/pi/gitflow-guard.ts .pi/extensions/gitflow-guard.ts
|
|
446
446
|
```
|
|
@@ -545,10 +545,18 @@ If it saves your team from a shortcut gone wrong, the coffee button at the top o
|
|
|
545
545
|
|
|
546
546
|
## Roadmap
|
|
547
547
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
- **
|
|
551
|
-
- **
|
|
548
|
+
**Shipped**:
|
|
549
|
+
|
|
550
|
+
- ✅ **i18n — localized block messages** (0.0.3): English by default, `"locale": "zh"` for Chinese; custom locales via `registerLocale` since 0.0.12.
|
|
551
|
+
- ✅ **Zero-config onboarding** (0.0.20): built-in `develop` + `main` defaults, deep-merge override, guard on by default with no config file.
|
|
552
|
+
- ✅ **One-command wiring** (0.0.20): `gitflow-guard wire` / `setup` writes each stdin-hook client's hook entry (Claude Code / Codex / OpenCode / Antigravity).
|
|
553
|
+
- ✅ **Six platforms** (0.0.2–0.0.17): DSH (in-process), Claude Code, Codex, OpenCode, Antigravity, Pi (in-process).
|
|
554
|
+
|
|
555
|
+
**v2 (open)**:
|
|
556
|
+
|
|
557
|
+
- **audit sync**: sync the user-level audit log across machines (audit is local-only today; since 0.0.14 it lives outside the repository).
|
|
558
|
+
- **more pre-built templates**: extend the built-in defaults with ready-made presets for common flows (solo `develop`, multi-env enterprise) as community-contributed configs.
|
|
559
|
+
- **CI hard-gating research**: whether `pr checks` could become a real gate without hurting the platform-agnostic core.
|
|
552
560
|
|
|
553
561
|
Contributions welcome — see [Development](#development).
|
|
554
562
|
|
|
@@ -566,9 +574,10 @@ The plugin is free and open source (MIT). If it saves you and your team from a s
|
|
|
566
574
|
|
|
567
575
|
```bash
|
|
568
576
|
npm install
|
|
569
|
-
npm test # unit tests: classify / gate / config / cli / repo / platform / i18n / index / accuracy-audit
|
|
577
|
+
npm test # unit tests: classify / gate / config / cli / repo / platform / i18n / index / accuracy-audit / pi
|
|
570
578
|
npm run typecheck # tsc --noEmit, 0 errors
|
|
571
579
|
npm run build # tsdown → lib/ (CLI and plugin share the build)
|
|
580
|
+
npm run check:pins # assert package.json version matches every README lock-version pin and the CHANGELOG heading
|
|
572
581
|
npm run verify:matrix # continuous cross-agent regression: DSH logic + zh-locale regression + Claude Code / Codex / OpenCode / Antigravity hook wiring + Pi extension
|
|
573
582
|
```
|
|
574
583
|
|
package/README.zh.md
CHANGED
|
@@ -39,17 +39,17 @@
|
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
41
|
# DSH —— 进程内插件(装完重启 DSH; 插件在进程启动时加载)
|
|
42
|
-
dsh plugin --profile web add agents-gitflow-guard@0.0.
|
|
42
|
+
dsh plugin --profile web add agents-gitflow-guard@0.0.22
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
46
|
# Claude Code · Codex · OpenCode · Antigravity —— 独立 hook,不需要 DSH
|
|
47
|
-
npm i -g agents-gitflow-guard@0.0.
|
|
47
|
+
npm i -g agents-gitflow-guard@0.0.22
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
51
|
# Pi —— 进程内扩展
|
|
52
|
-
npm i -D agents-gitflow-guard@0.0.
|
|
52
|
+
npm i -D agents-gitflow-guard@0.0.22
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
> **版本坑**: 裸 `add` 或不带版本的 `npm i` 装的是安装时刻的 `latest`——在 npm/pnpm 注册表缓存或镜像陈旧的机器上可能拿到旧版本。看到版本不对就锁版本。(DSH 用户: pnpm 打印的 peer 依赖 *警告* 属预期——DSH 启动时经共享模块回退提供 `@deepseek-ai/cordis` / `@deepseek-ai/dsh-tools`,插件正常工作。)
|
|
@@ -67,7 +67,7 @@ gitflow-guard wire --client claude --project --yes
|
|
|
67
67
|
# Codex / OpenCode / Antigravity(各写各的配置文件; --yes 跳过 y/N 确认)
|
|
68
68
|
gitflow-guard wire --client codex --project --yes
|
|
69
69
|
gitflow-guard wire --client opencode --project --yes
|
|
70
|
-
gitflow-guard wire --client antigravity --project --yes
|
|
70
|
+
gitflow-guard wire --client antigravity --project --yes
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
```bash
|
|
@@ -354,14 +354,14 @@ PR/MR 目标通过 `gh pr view`(GitHub)或 `glab mr view`(GitLab)解析;没有
|
|
|
354
354
|
|
|
355
355
|
| 客户端 | 安装命令 | 装完再做什么 |
|
|
356
356
|
|---|---|---|
|
|
357
|
-
| DSH | `dsh plugin --profile web add agents-gitflow-guard@0.0.
|
|
358
|
-
| Claude Code · Codex · OpenCode · Antigravity | `npm i -g agents-gitflow-guard@0.0.
|
|
359
|
-
| Pi | `npm i -D agents-gitflow-guard@0.0.
|
|
357
|
+
| DSH | `dsh plugin --profile web add agents-gitflow-guard@0.0.22` | 重启 DSH——插件自动挂为 profile 层 |
|
|
358
|
+
| Claude Code · Codex · OpenCode · Antigravity | `npm i -g agents-gitflow-guard@0.0.22` | `gitflow-guard wire --client <名>`——每个客户端一条命令(见下) |
|
|
359
|
+
| Pi | `npm i -D agents-gitflow-guard@0.0.22` | 把 `pi/gitflow-guard.ts` 拷进 `.pi/extensions/`(见下) |
|
|
360
360
|
|
|
361
361
|
**DSH —— 进程内插件**(标准路径,已在[快速开始](#快速开始30-秒用上)覆盖):
|
|
362
362
|
|
|
363
363
|
```bash
|
|
364
|
-
dsh plugin --profile web add agents-gitflow-guard@0.0.
|
|
364
|
+
dsh plugin --profile web add agents-gitflow-guard@0.0.22 # 建议锁版本, 见上文提示
|
|
365
365
|
```
|
|
366
366
|
|
|
367
367
|
然后重启 DSH。升级用同一命令,再重启一次。
|
|
@@ -378,11 +378,11 @@ dsh plugin --profile web add file:/path/to/agents-gitflow-guard
|
|
|
378
378
|
**各 agent 独立 hook**——Claude Code / Codex / OpenCode / Antigravity,不依赖 DSH。全局装一次 CLI,然后**每客户端一条命令接线**(守卫凭内置默认配置已默认开启,接线是唯一剩下的事):
|
|
379
379
|
|
|
380
380
|
```bash
|
|
381
|
-
npm i -g agents-gitflow-guard@0.0.
|
|
381
|
+
npm i -g agents-gitflow-guard@0.0.22 # 提供 `gitflow-guard` 二进制
|
|
382
382
|
gitflow-guard wire --client claude --project --yes
|
|
383
383
|
gitflow-guard wire --client codex --project --yes
|
|
384
384
|
gitflow-guard wire --client opencode --project --yes
|
|
385
|
-
gitflow-guard wire --client antigravity --project --yes
|
|
385
|
+
gitflow-guard wire --client antigravity --project --yes
|
|
386
386
|
```
|
|
387
387
|
|
|
388
388
|
`wire` 读取已有配置文件(如有)并把 hook 条目合入——不碰其他内容、幂等(已接则跳过)、支持 `--dry-run` 预览与 `--unwire` 移除、写 `--global` 前必先询问。它写入的准确文件(供参考,也可代替 `wire` 手写)是:
|
|
@@ -409,18 +409,17 @@ gitflow-guard wire --client antigravity --project --yes # 实验支持
|
|
|
409
409
|
}
|
|
410
410
|
```
|
|
411
411
|
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
hooks
|
|
415
|
-
|
|
416
|
-
event: tool.before.bash
|
|
417
|
-
actions:
|
|
418
|
-
- bash: |
|
|
419
|
-
gitflow-guard check --platform opencode
|
|
412
|
+
```ts
|
|
413
|
+
// OpenCode — `.opencode/plugins/gitflow-guard.ts`(随包 `opencode/gitflow-guard.ts` 的副本;
|
|
414
|
+
// OpenCode 1.18+ 已移除 hooks.yaml,扩展点改为 plugins —— `tool.execute.before` 事件,
|
|
415
|
+
// 拒绝语义 = 抛错; `wire --client opencode` 自动复制该文件)
|
|
420
416
|
```
|
|
417
|
+
`gitflow-guard wire --client opencode` 会从包内写入此文件;非必要不建议手写。
|
|
421
418
|
|
|
422
419
|
```json
|
|
423
420
|
// Antigravity (Google) — .agents/hooks.json
|
|
421
|
+
// (agy hook 进程 cwd = hook 配置文件所在目录,相对 bin/… 会解析失败; `wire` 项目级写绝对路径、
|
|
422
|
+
// 全局写 PATH 上的 gitflow-guard。此处展示全局安装形态。)
|
|
424
423
|
{
|
|
425
424
|
"gitflow-guard": {
|
|
426
425
|
"PreToolUse": [
|
|
@@ -438,7 +437,7 @@ hooks:
|
|
|
438
437
|
Pi 以进程内扩展装载(没有 stdin payload,也没有子进程 hook)。把随包发布的入口装进项目、包留在 devDependencies:
|
|
439
438
|
|
|
440
439
|
```bash
|
|
441
|
-
npm i -D agents-gitflow-guard@0.0.
|
|
440
|
+
npm i -D agents-gitflow-guard@0.0.22
|
|
442
441
|
mkdir -p .pi/extensions
|
|
443
442
|
cp node_modules/agents-gitflow-guard/pi/gitflow-guard.ts .pi/extensions/gitflow-guard.ts
|
|
444
443
|
```
|
|
@@ -543,10 +542,18 @@ MIT,免费,无条件。随便用、随便改、随便发,唯一义务是保留
|
|
|
543
542
|
|
|
544
543
|
## 路线图
|
|
545
544
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
- **
|
|
549
|
-
-
|
|
545
|
+
**已落地**:
|
|
546
|
+
|
|
547
|
+
- ✅ **i18n——拦截文案本地化**(0.0.3):默认英文,`"locale": "zh"` 切中文;0.0.12 起支持 `registerLocale` 自定义语言。
|
|
548
|
+
- ✅ **零配置开箱**(0.0.20):内置 `develop` + `main` 默认配置、深度合并覆盖,无配置文件也默认开启守卫。
|
|
549
|
+
- ✅ **一键接线**(0.0.20):`gitflow-guard wire` / `setup` 一条命令写入各 stdin-hook 客户端(Claude Code / Codex / OpenCode / Antigravity)的 hook 条目。
|
|
550
|
+
- ✅ **六个平台**(0.0.2–0.0.17):DSH(进程内)、Claude Code、Codex、OpenCode、Antigravity、Pi(进程内)。
|
|
551
|
+
|
|
552
|
+
**v2 规划中**:
|
|
553
|
+
|
|
554
|
+
- **审计同步**:跨机器同步用户级审计日志(现仍仅本地;0.0.14 起已存于仓库外)。
|
|
555
|
+
- **更多预制模板**:在内置默认之上延伸,为常用流程(solo `develop`、多环境企业)提供社区贡献的现成配置预设。
|
|
556
|
+
- **CI 硬门槛研究**:`pr checks` 能否在不伤平台无关核心的前提下变成真实门槛。
|
|
550
557
|
|
|
551
558
|
欢迎贡献——见[开发](#开发)。
|
|
552
559
|
|
|
@@ -564,9 +571,10 @@ MIT,免费,无条件。随便用、随便改、随便发,唯一义务是保留
|
|
|
564
571
|
|
|
565
572
|
```bash
|
|
566
573
|
npm install
|
|
567
|
-
npm test # 单测: classify / gate / config / cli / repo / platform / i18n / index / accuracy-audit
|
|
574
|
+
npm test # 单测: classify / gate / config / cli / repo / platform / i18n / index / accuracy-audit / pi
|
|
568
575
|
npm run typecheck # tsc --noEmit, 0 Error
|
|
569
576
|
npm run build # tsdown → lib/(CLI 与插件共用)
|
|
577
|
+
npm run check:pins # 校验 package.json 版本与双语 README 锁版本示例及 CHANGELOG 标题一致
|
|
570
578
|
npm run verify:matrix # 连续复测矩阵: DSH 逻辑 + zh 文案回归 + Claude Code / Codex / OpenCode / Antigravity hook 编码 + Pi 扩展
|
|
571
579
|
```
|
|
572
580
|
|
package/lib/cli.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { _ as resolveLocale, d as gitRunner, f as loadConfig, h as makeT, i as formatDeny, l as currentBranch, o as stateDir, p as roleMatches, r as evaluateCommand, u as findRepoRoot, v as classify } from "./src-
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { _ as resolveLocale, d as gitRunner, f as loadConfig, h as makeT, i as formatDeny, l as currentBranch, o as stateDir, p as roleMatches, r as evaluateCommand, u as findRepoRoot, v as classify } from "./src-DMlNMtXn.mjs";
|
|
2
|
+
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
6
7
|
//#region src/platform.ts
|
|
7
8
|
function str(v) {
|
|
8
9
|
return typeof v === "string" ? v : "";
|
|
@@ -37,7 +38,7 @@ function extractHookPayload(raw, platform = "auto") {
|
|
|
37
38
|
cwd = str(j.cwd);
|
|
38
39
|
} else if (plat === "antigravity") {
|
|
39
40
|
command = str(j.toolCall?.args?.CommandLine);
|
|
40
|
-
cwd = str(j.
|
|
41
|
+
cwd = str(j.toolCall?.args?.Cwd);
|
|
41
42
|
}
|
|
42
43
|
if (!command) return null;
|
|
43
44
|
return {
|
|
@@ -98,7 +99,9 @@ const CLIENTS = [
|
|
|
98
99
|
function isWireClient(v) {
|
|
99
100
|
return CLIENTS.includes(v);
|
|
100
101
|
}
|
|
101
|
-
/** 各客户端的 hook 落位规格(dsh/pi 无 hook 文件, 仅输出接入引导)
|
|
102
|
+
/** 各客户端的 hook 落位规格(dsh/pi 无 hook 文件, 仅输出接入引导)
|
|
103
|
+
* opencode: OpenCode 1.18+ 已废弃 hooks.yaml(实机零调用, 见 docs/e2e/TestResult/opencode.md),
|
|
104
|
+
* 官方扩展点为 plugins 目录 —— wire 把随包插件 opencode/gitflow-guard.ts 复制到插件目录。 */
|
|
102
105
|
const WIRE_CLIENTS = [
|
|
103
106
|
{
|
|
104
107
|
client: "claude",
|
|
@@ -112,14 +115,13 @@ const WIRE_CLIENTS = [
|
|
|
112
115
|
},
|
|
113
116
|
{
|
|
114
117
|
client: "opencode",
|
|
115
|
-
projectPath: ".opencode/
|
|
116
|
-
globalPath: () => join(homedir(), ".config", "opencode", "
|
|
118
|
+
projectPath: ".opencode/plugins/gitflow-guard.ts",
|
|
119
|
+
globalPath: () => join(homedir(), ".config", "opencode", "plugins", "gitflow-guard.ts")
|
|
117
120
|
},
|
|
118
121
|
{
|
|
119
122
|
client: "antigravity",
|
|
120
123
|
projectPath: ".agents/hooks.json",
|
|
121
|
-
globalPath: () => join(homedir(), ".gemini", "config", "hooks.json")
|
|
122
|
-
experimental: true
|
|
124
|
+
globalPath: () => join(homedir(), ".gemini", "config", "hooks.json")
|
|
123
125
|
},
|
|
124
126
|
{
|
|
125
127
|
client: "dsh",
|
|
@@ -132,24 +134,18 @@ const WIRE_CLIENTS = [
|
|
|
132
134
|
globalPath: () => ""
|
|
133
135
|
}
|
|
134
136
|
];
|
|
135
|
-
/**
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
" actions:",
|
|
148
|
-
" - bash: |",
|
|
149
|
-
` ${COMMANDS.opencode}`
|
|
150
|
-
].join("\n");
|
|
151
|
-
const YAML_ID_GUARD = /^\s*- id: gitflow-guard\s*$/m;
|
|
152
|
-
const YAML_ID_ANY = /^\s*- id:/m;
|
|
137
|
+
/** 随包发布的 OpenCode 插件源文件(wire --client opencode 复制到插件目录; dev 下即仓库 opencode/) */
|
|
138
|
+
const OPENCODE_PLUGIN_SOURCE = fileURLToPath(new URL("../opencode/gitflow-guard.ts", import.meta.url));
|
|
139
|
+
/** 各 JSON 客户端的 hook 命令(与 references/*.md 逐一对应)。
|
|
140
|
+
* antigravity 必须绝对路径: agy hook 进程 cwd = hook 配置文件所在目录(TestResult/antigravity.md AGY-D2),
|
|
141
|
+
* 相对 bin/... 会解析为 .agents/bin/... → MODULE_NOT_FOUND; 全局落位无仓库根, 用 PATH 上的 gitflow-guard。 */
|
|
142
|
+
function commandFor(client, repoRoot) {
|
|
143
|
+
switch (client) {
|
|
144
|
+
case "claude": return "node ${CLAUDE_PROJECT_DIR}/bin/gitflow-guard.mjs check --platform claude";
|
|
145
|
+
case "codex": return "node bin/gitflow-guard.mjs check --platform codex";
|
|
146
|
+
case "antigravity": return repoRoot ? `node ${join(repoRoot, "bin", "gitflow-guard.mjs")} check --platform antigravity` : "gitflow-guard check --platform antigravity";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
153
149
|
/** 读取文本文件; 缺失返回 null(其余异常也视为缺失, 决策保守) */
|
|
154
150
|
async function readText(path) {
|
|
155
151
|
try {
|
|
@@ -169,6 +165,20 @@ function jsonContains(obj, needle) {
|
|
|
169
165
|
if (obj !== null && typeof obj === "object") return Object.values(obj).some((x) => jsonContains(x, needle));
|
|
170
166
|
return false;
|
|
171
167
|
}
|
|
168
|
+
/** JSON 递归搜索(谓词版): 用于旧格式条目的柔性识别 */
|
|
169
|
+
function jsonContainsBy(obj, pred) {
|
|
170
|
+
if (pred(obj)) return true;
|
|
171
|
+
if (Array.isArray(obj)) return obj.some((x) => jsonContainsBy(x, pred));
|
|
172
|
+
if (obj !== null && typeof obj === "object") return Object.values(obj).some((x) => jsonContainsBy(x, pred));
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
/** 判断命令是否为本插件 antigravity 条目: 新格式绝对路径 node <root>/bin/gitflow-guard.mjs … 或
|
|
176
|
+
* AGY-D2 之前的旧相对格式 node bin/gitflow-guard.mjs …, 或全局 PATH 形态(无 .mjs);
|
|
177
|
+
* 格式演进后旧条目仍能被识别/替换/移除, 避免新旧双条并存。 */
|
|
178
|
+
function antigravityCommandish(cmd) {
|
|
179
|
+
if (typeof cmd !== "string") return false;
|
|
180
|
+
return cmd.includes("gitflow-guard.mjs check --platform antigravity") || cmd === "gitflow-guard check --platform antigravity";
|
|
181
|
+
}
|
|
172
182
|
function parseJsonOrThrow(path, raw) {
|
|
173
183
|
try {
|
|
174
184
|
const parsed = JSON.parse(raw);
|
|
@@ -182,8 +192,8 @@ async function writeJson(path, obj) {
|
|
|
182
192
|
await writeText(path, `${JSON.stringify(obj, null, 2)}\n`);
|
|
183
193
|
}
|
|
184
194
|
/** JSON 客户端(claude/codex/antigravity)新增 hook 条目; 非破坏性合并, 同命令已存在则跳过 */
|
|
185
|
-
async function addJsonEntry(path, client, dryRun) {
|
|
186
|
-
const cmd =
|
|
195
|
+
async function addJsonEntry(path, client, dryRun, repoRoot) {
|
|
196
|
+
const cmd = commandFor(client, repoRoot);
|
|
187
197
|
const raw = await readText(path);
|
|
188
198
|
const obj = raw === null ? {} : parseJsonOrThrow(path, raw);
|
|
189
199
|
if (jsonContains(obj, cmd)) return "exists";
|
|
@@ -203,7 +213,11 @@ async function addJsonEntry(path, client, dryRun) {
|
|
|
203
213
|
if (client === "antigravity") {
|
|
204
214
|
const block = obj["gitflow-guard"] ??= { PreToolUse: [] };
|
|
205
215
|
if (!Array.isArray(block.PreToolUse)) throw new Error(`invalid ${path}: gitflow-guard.PreToolUse must be an array`);
|
|
206
|
-
block.PreToolUse
|
|
216
|
+
const entries = block.PreToolUse;
|
|
217
|
+
if (entries.some((e) => jsonContainsBy(e, (v) => v === cmd))) return "exists";
|
|
218
|
+
const next = entries.filter((e) => !jsonContainsBy(e, antigravityCommandish));
|
|
219
|
+
next.push(entry);
|
|
220
|
+
block.PreToolUse = next;
|
|
207
221
|
} else {
|
|
208
222
|
const hooksObj = obj["hooks"] ??= {};
|
|
209
223
|
const arr = hooksObj["PreToolUse"] ??= [];
|
|
@@ -214,14 +228,17 @@ async function addJsonEntry(path, client, dryRun) {
|
|
|
214
228
|
return "added";
|
|
215
229
|
}
|
|
216
230
|
/** JSON 客户端移除本插件条目; 不动其他内容 */
|
|
217
|
-
async function removeJsonEntry(path, client, dryRun) {
|
|
218
|
-
const cmd =
|
|
231
|
+
async function removeJsonEntry(path, client, dryRun, repoRoot) {
|
|
232
|
+
const cmd = commandFor(client, repoRoot);
|
|
219
233
|
const raw = await readText(path);
|
|
220
234
|
if (raw === null) return "absent";
|
|
221
235
|
const obj = parseJsonOrThrow(path, raw);
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
|
|
236
|
+
if (client === "antigravity") {
|
|
237
|
+
const block = obj["gitflow-guard"];
|
|
238
|
+
if (!block || !jsonContainsBy(block, antigravityCommandish)) return "absent";
|
|
239
|
+
delete obj["gitflow-guard"];
|
|
240
|
+
} else {
|
|
241
|
+
if (!jsonContains(obj, cmd)) return "absent";
|
|
225
242
|
const hooksObj = obj["hooks"];
|
|
226
243
|
const arr = hooksObj?.["PreToolUse"];
|
|
227
244
|
if (Array.isArray(arr)) {
|
|
@@ -234,60 +251,36 @@ async function removeJsonEntry(path, client, dryRun) {
|
|
|
234
251
|
if (!dryRun) await writeJson(path, obj);
|
|
235
252
|
return "removed";
|
|
236
253
|
}
|
|
237
|
-
/** OpenCode
|
|
238
|
-
async function
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if (hooksIdx === -1) {
|
|
246
|
-
const joined = [
|
|
247
|
-
...lines,
|
|
248
|
-
"",
|
|
249
|
-
...block
|
|
250
|
-
].join("\n");
|
|
251
|
-
if (!dryRun) await writeText(path, joined);
|
|
252
|
-
return "added";
|
|
253
|
-
}
|
|
254
|
-
lines.splice(hooksIdx + 1, 0, ...block);
|
|
255
|
-
if (!dryRun) await writeText(path, lines.join("\n"));
|
|
256
|
-
return "added";
|
|
254
|
+
/** OpenCode 插件: 复制随包插件文件; 已存在同文件视为已接线(幂等) */
|
|
255
|
+
async function addPluginFile(path, dryRun) {
|
|
256
|
+
if (await readText(path) !== null) return "exists";
|
|
257
|
+
let source;
|
|
258
|
+
try {
|
|
259
|
+
source = await readFile(OPENCODE_PLUGIN_SOURCE, "utf8");
|
|
260
|
+
} catch {
|
|
261
|
+
throw new Error(`cannot read bundled opencode plugin source at ${OPENCODE_PLUGIN_SOURCE} — install the package (npm i -g agents-gitflow-guard) or copy opencode/gitflow-guard.ts into the project's .opencode/plugins/ manually`);
|
|
257
262
|
}
|
|
258
|
-
if (!dryRun) await writeText(path,
|
|
263
|
+
if (!dryRun) await writeText(path, source);
|
|
259
264
|
return "added";
|
|
260
265
|
}
|
|
261
|
-
/** OpenCode
|
|
262
|
-
async function
|
|
263
|
-
|
|
264
|
-
if (
|
|
265
|
-
if (!YAML_ID_GUARD.test(raw)) return "absent";
|
|
266
|
-
const lines = raw.split("\n");
|
|
267
|
-
const start = lines.findIndex((l) => YAML_ID_GUARD.test(l));
|
|
268
|
-
let end = lines.length;
|
|
269
|
-
for (let i = start + 1; i < lines.length; i++) if (YAML_ID_ANY.test(lines[i])) {
|
|
270
|
-
end = i;
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
let rest = [...lines.slice(0, start), ...lines.slice(end)];
|
|
274
|
-
if (!rest.some((l) => YAML_ID_ANY.test(l))) rest = rest.filter((l) => !/^hooks:\s*$/.test(l));
|
|
275
|
-
const text = rest.join("\n");
|
|
276
|
-
if (!dryRun) await writeText(path, text);
|
|
266
|
+
/** OpenCode 插件: 删除插件文件; 不动其他插件 */
|
|
267
|
+
async function removePluginFile(path, dryRun) {
|
|
268
|
+
if (await readText(path) === null) return "absent";
|
|
269
|
+
if (!dryRun) await unlink(path);
|
|
277
270
|
return "removed";
|
|
278
271
|
}
|
|
279
272
|
/** 执行一次 wire 落位/移除/预览; dsh/pi 由上层直接短路, 不进这里 */
|
|
280
|
-
async function applyWire(client, path, unwire, dryRun) {
|
|
281
|
-
if (client === "opencode") return unwire ?
|
|
282
|
-
return unwire ? removeJsonEntry(path, client, dryRun) : addJsonEntry(path, client, dryRun);
|
|
273
|
+
async function applyWire(client, path, unwire, dryRun, repoRoot = null) {
|
|
274
|
+
if (client === "opencode") return unwire ? removePluginFile(path, dryRun) : addPluginFile(path, dryRun);
|
|
275
|
+
return unwire ? removeJsonEntry(path, client, dryRun, repoRoot) : addJsonEntry(path, client, dryRun, repoRoot);
|
|
283
276
|
}
|
|
284
|
-
/** 只读探测:
|
|
285
|
-
async function isWired(client, path) {
|
|
277
|
+
/** 只读探测: 该客户端是否已接线(opencode 判插件文件存在; JSON 客户端按命令精确匹配) */
|
|
278
|
+
async function isWired(client, path, repoRoot = null) {
|
|
286
279
|
const raw = await readText(path);
|
|
287
280
|
if (raw === null) return false;
|
|
288
|
-
if (client === "opencode") return
|
|
281
|
+
if (client === "opencode") return true;
|
|
289
282
|
try {
|
|
290
|
-
return jsonContains(JSON.parse(raw),
|
|
283
|
+
return jsonContains(JSON.parse(raw), commandFor(client, repoRoot));
|
|
291
284
|
} catch {
|
|
292
285
|
return false;
|
|
293
286
|
}
|
|
@@ -416,7 +409,7 @@ async function status(flags, runner) {
|
|
|
416
409
|
const hints = [];
|
|
417
410
|
for (const spec of WIRE_CLIENTS) {
|
|
418
411
|
if (spec.client === "dsh" || spec.client === "pi") continue;
|
|
419
|
-
if (!await isWired(spec.client, join(repoRoot, spec.projectPath))) hints.push(spec.client);
|
|
412
|
+
if (!await isWired(spec.client, join(repoRoot, spec.projectPath), repoRoot)) hints.push(spec.client);
|
|
420
413
|
}
|
|
421
414
|
if (hints.length > 0) {
|
|
422
415
|
console.log(t("cli.statusWireHints"));
|
|
@@ -490,7 +483,7 @@ async function wireCore(client, scope, opts, t) {
|
|
|
490
483
|
path
|
|
491
484
|
}));
|
|
492
485
|
if (opts.dryRun) {
|
|
493
|
-
const res = await applyWire(client, path, !!opts.unwire, true);
|
|
486
|
+
const res = await applyWire(client, path, !!opts.unwire, true, opts.repoRoot);
|
|
494
487
|
if (res === "added") console.log(t("cli.wireDryRunAdd", {
|
|
495
488
|
client,
|
|
496
489
|
path
|
|
@@ -515,7 +508,7 @@ async function wireCore(client, scope, opts, t) {
|
|
|
515
508
|
if (ans !== "y" && ans !== "yes") return 1;
|
|
516
509
|
}
|
|
517
510
|
}
|
|
518
|
-
const res = await applyWire(client, path, !!opts.unwire, false);
|
|
511
|
+
const res = await applyWire(client, path, !!opts.unwire, false, opts.repoRoot);
|
|
519
512
|
if (res === "added") console.log(t("cli.wireCreated", {
|
|
520
513
|
client,
|
|
521
514
|
path
|
package/lib/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as name, c as createPiExtension, g as registerLocale, i as formatDeny, m as MESSAGE_KEYS, n as apply, o as stateDir, r as evaluateCommand, s as userStateRoot, t as appendAudit } from "./src-
|
|
1
|
+
import { a as name, c as createPiExtension, g as registerLocale, i as formatDeny, m as MESSAGE_KEYS, n as apply, o as stateDir, r as evaluateCommand, s as userStateRoot, t as appendAudit } from "./src-DMlNMtXn.mjs";
|
|
2
2
|
export { MESSAGE_KEYS, appendAudit, apply, createPiExtension, evaluateCommand, formatDeny, name, registerLocale, stateDir, userStateRoot };
|
|
@@ -732,9 +732,10 @@ Notes:
|
|
|
732
732
|
--locale overrides the message language for this invocation (flag > project config > English).
|
|
733
733
|
check reads the hook payload on stdin (platform-specific protocol: claude/opencode exit 2,
|
|
734
734
|
codex/antigravity JSON on stdout) and is meant for pre/post hooks of AI agents.
|
|
735
|
-
wire writes each client's hook config
|
|
736
|
-
in-process and only print guidance. No config file
|
|
737
|
-
apply out of the box; create
|
|
735
|
+
wire writes each client's hook config (or the OpenCode plugin file) into the project
|
|
736
|
+
(default) or global scope; dsh/pi are in-process and only print guidance. No config file
|
|
737
|
+
needed — built-in defaults (develop+main) apply out of the box; create
|
|
738
|
+
gitflow-guard.config.json to override, or set "enabled": false to turn off.`
|
|
738
739
|
};
|
|
739
740
|
const zh = {
|
|
740
741
|
"role.integration": () => "集成分支",
|
|
@@ -839,7 +840,7 @@ const zh = {
|
|
|
839
840
|
--locale 可临时覆盖本次调用的文案语言(旗标 > 项目配置 > 英文)。
|
|
840
841
|
check 读 stdin hook payload 做门禁(平台协议: claude/opencode exit 2, codex/antigravity stdout JSON),
|
|
841
842
|
供 Claude Code / Codex / OpenCode 等 agent 的 pre/post hook 调用。
|
|
842
|
-
wire 把各客户端默认 hook 写入工程(默认)
|
|
843
|
+
wire 把各客户端默认 hook 写入工程(默认)或全局作用域(opencode 为复制插件文件); dsh/pi 为进程内接入, 仅打印引导。
|
|
843
844
|
无需配置文件 — 内置默认(develop+main)开箱即用; 建 gitflow-guard.config.json 可覆盖, 或写 "enabled": false 关闭。`
|
|
844
845
|
};
|
|
845
846
|
/** 内置文案注册表: en 为兜底语言; 下游可经 registerLocale 追加 */
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// OpenCode 插件(随包发布; `wire --client opencode` 会复制本文件到 .opencode/plugins/):
|
|
2
|
+
// 在 tool.execute.before 事件拦截 bash/powershell 工具, 经守卫 CLI 门禁(check --platform opencode),
|
|
3
|
+
// 拒绝(exit 2)时抛错阻断工具执行; 守卫不可用时 fail-open 放行, 不破坏工具管道。
|
|
4
|
+
//
|
|
5
|
+
// 背景: OpenCode 1.18+ 已移除 hooks.yaml 机制(实机测试零调用, 见 docs/e2e/TestResult/opencode.md),
|
|
6
|
+
// 官方扩展点为 plugins 目录 + 事件订阅, 阻断语义 = handler 抛错(官方 env-protection 示例同款)。
|
|
7
|
+
// 插件导出必须是**函数**(工厂), 返回事件处理器对象 —— 直接导出对象会报 "Plugin export is not a function"。
|
|
8
|
+
// 协议参考: .agents/hooks/references/opencode.md。
|
|
9
|
+
//
|
|
10
|
+
// 用法: 项目内执行 `gitflow-guard wire --client opencode --project --yes`, wire 会把本文件
|
|
11
|
+
// 复制到 <project>/.opencode/plugins/gitflow-guard.ts(全局落位到 ~/.config/opencode/plugins/);
|
|
12
|
+
// 也可手工复制。零外部依赖: 不 import @opencode-ai/plugin, 客户端原生加载本文件即可。
|
|
13
|
+
|
|
14
|
+
import { spawn } from 'node:child_process'
|
|
15
|
+
import { existsSync } from 'node:fs'
|
|
16
|
+
import { dirname, join, resolve } from 'node:path'
|
|
17
|
+
import { fileURLToPath } from 'node:url'
|
|
18
|
+
|
|
19
|
+
/** 插件自身所在目录: 项目级 = <项目>/.opencode/plugins, 全局 = ~/.config/opencode/plugins */
|
|
20
|
+
const PLUGIN_DIR = dirname(fileURLToPath(import.meta.url))
|
|
21
|
+
|
|
22
|
+
interface GuardTarget {
|
|
23
|
+
bin: string
|
|
24
|
+
args: string[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 组装 spawn 目标: opencode 插件在 opencode 进程内执行, process.execPath 是 opencode 自身而非 node,
|
|
28
|
+
* 不能拿它当解释器 —— Unix 直跑脚本(#!/usr/bin/env node shebang), Windows 用 PATH 上的 node。 */
|
|
29
|
+
function makeTarget(bin: string): GuardTarget {
|
|
30
|
+
return process.platform === 'win32' ? { bin: 'node', args: [bin] } : { bin, args: [] }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 定位守卫 CLI(按可用性取第一个):
|
|
34
|
+
* 1. 插件上两级(项目根)的 bin/gitflow-guard.mjs —— 项目级落位时插件在 <项目>/.opencode/plugins/;
|
|
35
|
+
* 2. $OPENCODE_PROJECT_DIR/bin/gitflow-guard.mjs —— 客户端展开的项目目录兜底;
|
|
36
|
+
* 3. GITFLOW_GUARD_BIN 显式指定;
|
|
37
|
+
* 4. PATH 上的 gitflow-guard —— 全局安装场景。 */
|
|
38
|
+
function guardTarget(): GuardTarget | null {
|
|
39
|
+
const projectRoot = resolve(dirname(dirname(PLUGIN_DIR)))
|
|
40
|
+
const local = join(projectRoot, 'bin', 'gitflow-guard.mjs')
|
|
41
|
+
if (existsSync(local)) return makeTarget(local)
|
|
42
|
+
const envProject = process.env.OPENCODE_PROJECT_DIR
|
|
43
|
+
if (envProject) {
|
|
44
|
+
const viaEnv = join(envProject, 'bin', 'gitflow-guard.mjs')
|
|
45
|
+
if (existsSync(viaEnv)) return makeTarget(viaEnv)
|
|
46
|
+
}
|
|
47
|
+
if (process.env.GITFLOW_GUARD_BIN) return makeTarget(process.env.GITFLOW_GUARD_BIN)
|
|
48
|
+
return makeTarget('gitflow-guard')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 运行守卫 check; exit 2 = 拒绝(抛错阻断工具), 其余一律放行(fail-open) */
|
|
52
|
+
async function runGuard(command: string): Promise<void> {
|
|
53
|
+
const target = guardTarget()
|
|
54
|
+
if (!target) {
|
|
55
|
+
console.error('[gitflow-guard] guard CLI not found — allowing tool call (fail-open)')
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
const args = [...target.args, 'check', '--platform', 'opencode', '--command', command]
|
|
59
|
+
let stderr = ''
|
|
60
|
+
// 对象属性承载 spawn 错误: TS 对回调内赋值的局部变量不做跨闭包窄化(会推断为 never)
|
|
61
|
+
const outcome: { spawnError?: Error } = {}
|
|
62
|
+
const code = await new Promise<number>((resolvePromise) => {
|
|
63
|
+
const child = spawn(target.bin, args, { stdio: ['ignore', 'pipe', 'pipe'] })
|
|
64
|
+
let settled = false
|
|
65
|
+
child.stderr.on('data', (d: Buffer) => (stderr += d.toString()))
|
|
66
|
+
child.on('error', (e) => {
|
|
67
|
+
// spawn 失败等内部故障: fail-open, 不阻断工具管道; 告警统一在尾部输出(避免双打)
|
|
68
|
+
if (settled) return
|
|
69
|
+
settled = true
|
|
70
|
+
outcome.spawnError = e
|
|
71
|
+
resolvePromise(-1)
|
|
72
|
+
})
|
|
73
|
+
child.on('close', (code) => {
|
|
74
|
+
if (settled) return
|
|
75
|
+
settled = true
|
|
76
|
+
resolvePromise(code ?? -1)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
if (code === 2) throw new Error(stderr.trim() || `[gitflow-guard] blocked: ${command}`)
|
|
80
|
+
if (code !== 0) {
|
|
81
|
+
const spawnMsg = outcome.spawnError?.message
|
|
82
|
+
if (spawnMsg) console.error(`[gitflow-guard] cannot spawn guard: ${spawnMsg} — allowing tool call (fail-open)`)
|
|
83
|
+
else console.error(`[gitflow-guard] check exited ${code} — allowing tool call (fail-open): ${stderr.trim()}`)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** OpenCode 插件工厂(官方要求导出函数): 订阅 tool.execute.before, 仅拦 bash/powershell */
|
|
88
|
+
export default async function gitflowGuardPlugin() {
|
|
89
|
+
return {
|
|
90
|
+
'tool.execute.before': async (input: { tool?: string; args?: Record<string, unknown> }, output: { args?: Record<string, unknown> }) => {
|
|
91
|
+
// 只拦截命令执行面(bash/powershell); 其余工具(read/edit 等)不经过守卫
|
|
92
|
+
if (input.tool !== 'bash' && input.tool !== 'powershell') return
|
|
93
|
+
const command = typeof output.args?.command === 'string' ? output.args.command : ''
|
|
94
|
+
if (!command) return
|
|
95
|
+
await runGuard(command)
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents-gitflow-guard",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"description": "A configurable branch-role guard for AI coding agents (DSH / Claude Code / Codex / OpenCode / Antigravity / Pi) — zero-config defaults (develop+main) out of the box, deep-merge override, and per-client native hooks via gitflow-guard wire/setup; integration/preview/production/archive, each with its own update rules",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"bin",
|
|
20
20
|
"patch.yml",
|
|
21
21
|
"pi",
|
|
22
|
+
"opencode",
|
|
22
23
|
"README.md",
|
|
23
24
|
"README.zh.md"
|
|
24
25
|
],
|