agents-gitflow-guard 0.0.14 → 0.0.16
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 +3 -3
- package/README.zh.md +3 -3
- package/lib/cli.mjs +4 -4
- package/lib/index.d.mts +6 -5
- package/lib/index.mjs +1 -1
- package/lib/{src-C1CZM7Q4.mjs → src-p6q9dCIr.mjs} +31 -15
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ You define your own branches —
|
|
|
42
42
|
# installs the latest release
|
|
43
43
|
dsh plugin --profile web add agents-gitflow-guard
|
|
44
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.
|
|
45
|
+
dsh plugin --profile web add agents-gitflow-guard@0.0.16
|
|
46
46
|
```
|
|
47
47
|
|
|
48
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).
|
|
@@ -132,7 +132,7 @@ Nobody has to remember the rules — the rules are enforced.
|
|
|
132
132
|
- **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`).
|
|
133
133
|
- **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.
|
|
134
134
|
- **Works with any naming**: branch names are mapped by your config, never hard-coded (see [Configuration](#configuration-reference)).
|
|
135
|
-
- **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,
|
|
135
|
+
- **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.
|
|
136
136
|
- **Platform-agnostic core**: pure local git; optionally consults `gh` (GitHub) or `glab` (GitLab) for PR/MR target resolution, and works fine without them.
|
|
137
137
|
|
|
138
138
|
---
|
|
@@ -312,7 +312,7 @@ The PR/MR target is resolved via `gh pr view` (GitHub) or `glab mr view` (GitLab
|
|
|
312
312
|
**From the npm registry** — the standard path, already covered in [Quick Start](#quick-start--30-seconds-to-a-guarded-repo):
|
|
313
313
|
|
|
314
314
|
```bash
|
|
315
|
-
dsh plugin --profile web add agents-gitflow-guard@0.0.
|
|
315
|
+
dsh plugin --profile web add agents-gitflow-guard@0.0.16 # pin recommended, see note above
|
|
316
316
|
```
|
|
317
317
|
|
|
318
318
|
Then restart DSH. Upgrades are the same command, followed by another restart.
|
package/README.zh.md
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
# 安装最新版
|
|
42
42
|
dsh plugin --profile web add agents-gitflow-guard
|
|
43
43
|
# ...或锁定已知良好版本(推荐; 同时绕开 registry 陈旧缓存)
|
|
44
|
-
dsh plugin --profile web add agents-gitflow-guard@0.0.
|
|
44
|
+
dsh plugin --profile web add agents-gitflow-guard@0.0.16
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
> **版本坑**: 裸 `add` 装的是安装时刻的 `latest`——在 npm/pnpm 注册表缓存或镜像陈旧的机器上可能拿到旧版本。看到版本不对就锁版本。pnpm 打印的 peer 依赖 *警告* 属预期: DSH 启动时经共享模块回退提供 `@deepseek-ai/cordis` / `@deepseek-ai/dsh-tools`(插件正常工作)。
|
|
@@ -129,7 +129,7 @@ AI 编码 agent 在你的仓库里工作。它通过系统提示词、项目智
|
|
|
129
129
|
- **角色驱动、完全可配**:`integration` 是唯一必填;`preview` / `production` / `archive` 是可选数组(精确名或正则),每个角色独立 `update`(`pr` / `flexible`)与 `mergeBy`。
|
|
130
130
|
- **在关键处保留人的操作权**:生产与归档合并始终在你手上——插件阻止 agent 点击合并,于是你的动作*就是*确认。
|
|
131
131
|
- **任何命名都行**:分支名全由配置映射,绝无硬编码(见[配置参考](#配置参考))。
|
|
132
|
-
- **全程审计**:每次拦截都追加到用户级状态目录(macOS/Linux `~/.local/state/gitflow-guard/`,Windows `%LOCALAPPDATA%\gitflow-guard`)
|
|
132
|
+
- **全程审计**:每次拦截都追加到用户级状态目录(macOS/Linux `~/.local/state/gitflow-guard/`,Windows `%LOCALAPPDATA%\gitflow-guard`)下的审计日志——在仓库外、绝不进版本库、位于 agent 可写沙箱之外,且同一仓库的所有 worktree 共享一份日志。
|
|
133
133
|
- **平台无关核心**:纯本地 git;可选调用 `gh`(GitHub)或 `glab`(GitLab)做 PR/MR 目标解析,没有它们照样工作。
|
|
134
134
|
|
|
135
135
|
---
|
|
@@ -308,7 +308,7 @@ PR/MR 目标通过 `gh pr view`(GitHub)或 `glab mr view`(GitLab)解析;没有
|
|
|
308
308
|
**从 npm registry**——标准路径,已在[快速开始](#快速开始30-秒用上)覆盖:
|
|
309
309
|
|
|
310
310
|
```bash
|
|
311
|
-
dsh plugin --profile web add agents-gitflow-guard@0.0.
|
|
311
|
+
dsh plugin --profile web add agents-gitflow-guard@0.0.16 # 建议锁版本, 见上文提示
|
|
312
312
|
```
|
|
313
313
|
|
|
314
314
|
然后重启 DSH。升级用同一命令,再重启一次。
|
package/lib/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as classify, c as currentBranch, d as loadConfig, f as roleMatches, g as resolveLocale, i as formatDeny, l as findRepoRoot, m as makeT, o as stateDir, r as evaluateCommand, u as gitRunner } from "./src-
|
|
1
|
+
import { _ as classify, c as currentBranch, d as loadConfig, f as roleMatches, g as resolveLocale, i as formatDeny, l as findRepoRoot, m as makeT, o as stateDir, r as evaluateCommand, u as gitRunner } from "./src-p6q9dCIr.mjs";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
//#region src/platform.ts
|
|
@@ -129,7 +129,7 @@ async function main(argv, opts = {}) {
|
|
|
129
129
|
}
|
|
130
130
|
try {
|
|
131
131
|
if (cmd === "status") return await status(flags, runner);
|
|
132
|
-
if (cmd === "audit") return await audit(flags);
|
|
132
|
+
if (cmd === "audit") return await audit(flags, runner);
|
|
133
133
|
if (cmd === "check") return await check(flags);
|
|
134
134
|
const t = makeT(await resolveFrameworkLocale(flags, runner));
|
|
135
135
|
console.error(`${t("cli.unknownCommand", { cmd: cmd ?? "" })}\n\n${t("usage.text")}`);
|
|
@@ -192,7 +192,7 @@ async function status(flags, runner) {
|
|
|
192
192
|
for (const b of localBranches) console.log(` ${b} → ${classifyBranch(b)}`);
|
|
193
193
|
return 0;
|
|
194
194
|
}
|
|
195
|
-
async function audit(flags) {
|
|
195
|
+
async function audit(flags, runner) {
|
|
196
196
|
const repoRoot = await resolveRepo(flags, gitRunner);
|
|
197
197
|
if (!repoRoot) {
|
|
198
198
|
console.error(makeT(resolveLocale(flags.locale))("cli.cannotLocate"));
|
|
@@ -202,7 +202,7 @@ async function audit(flags) {
|
|
|
202
202
|
const t = makeT(cliLocale(flags, config?.locale));
|
|
203
203
|
const lines = flags.lines != null && Number.isFinite(flags.lines) && flags.lines > 0 ? Math.floor(flags.lines) : 20;
|
|
204
204
|
try {
|
|
205
|
-
const all = (await readFile(join(stateDir(repoRoot), "audit.jsonl"), "utf8")).split("\n").filter(Boolean);
|
|
205
|
+
const all = (await readFile(join(await stateDir(repoRoot, gitRunner), "audit.jsonl"), "utf8")).split("\n").filter(Boolean);
|
|
206
206
|
for (const line of all.slice(-lines)) try {
|
|
207
207
|
const e = JSON.parse(line);
|
|
208
208
|
console.log(` ${new Date(e.time).toISOString()} ${e.event} ${e.role ?? ""}${e.command ? ` | ${e.command.slice(0, 80)}` : ""}${e.reason ? ` | ${e.reason.slice(0, 60)}` : ""}`);
|
package/lib/index.d.mts
CHANGED
|
@@ -60,13 +60,14 @@ interface AuditEntry {
|
|
|
60
60
|
declare function userStateRoot(): string;
|
|
61
61
|
/**
|
|
62
62
|
* 仓库运行时状态目录(审计流水等), 键为「仓库名-真实路径哈希」。
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
63
|
+
* 键由 git 权威解析(rev-parse --git-common-dir): linked worktree 与主仓库共用
|
|
64
|
+
* 同一状态目录(≤0.0.13 共享语义), 且天然规避 Windows 8.3 短名/大小写差异;
|
|
65
|
+
* git 查询不可用时回退 fs realpath。刻意放在仓库外、且在 agent 文件沙箱
|
|
66
|
+
* (workspace-write)可写区之外 —— 凡 agent 可写之处的状态都可能被伪造而自我授权。
|
|
66
67
|
*/
|
|
67
|
-
declare function stateDir(repoRoot: string): string
|
|
68
|
+
declare function stateDir(repoRoot: string, runner?: Runner): Promise<string>;
|
|
68
69
|
/** 审计留痕; 失败不阻断门禁 */
|
|
69
|
-
declare function appendAudit(repoRoot: string, entry: AuditEntry): Promise<void>;
|
|
70
|
+
declare function appendAudit(repoRoot: string, entry: AuditEntry, runner?: Runner): Promise<void>;
|
|
70
71
|
/** 解析一条命令: 分类 → git 事实 → 门禁 → allow/deny */
|
|
71
72
|
declare function evaluateCommand(command: string, opts: EvaluateOptions): Promise<EvaluateResult>;
|
|
72
73
|
declare function formatDeny(locale: Locale, why: string, next: string): string;
|
package/lib/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as name, h as registerLocale, i as formatDeny, n as apply, o as stateDir, p as MESSAGE_KEYS, r as evaluateCommand, s as userStateRoot, t as appendAudit } from "./src-
|
|
1
|
+
import { a as name, h as registerLocale, i as formatDeny, n as apply, o as stateDir, p as MESSAGE_KEYS, r as evaluateCommand, s as userStateRoot, t as appendAudit } from "./src-p6q9dCIr.mjs";
|
|
2
2
|
export { MESSAGE_KEYS, appendAudit, apply, evaluateCommand, formatDeny, name, registerLocale, stateDir, userStateRoot };
|
|
@@ -1021,6 +1021,15 @@ async function findRepoRoot(runner, cwd) {
|
|
|
1021
1021
|
const r = await runner.run(["rev-parse", "--show-toplevel"], cwd);
|
|
1022
1022
|
return r.code === 0 ? r.stdout.trim() || null : null;
|
|
1023
1023
|
}
|
|
1024
|
+
/** 共享 .git 目录(git 权威绝对路径): linked worktree 返回主仓库 .git, 主仓库返回自身 .git; 查询失败返回 null */
|
|
1025
|
+
async function commonGitDir(runner, cwd) {
|
|
1026
|
+
const r = await runner.run([
|
|
1027
|
+
"rev-parse",
|
|
1028
|
+
"--path-format=absolute",
|
|
1029
|
+
"--git-common-dir"
|
|
1030
|
+
], cwd);
|
|
1031
|
+
return r.code === 0 ? r.stdout.trim() || null : null;
|
|
1032
|
+
}
|
|
1024
1033
|
async function currentBranch(runner, cwd) {
|
|
1025
1034
|
const r = await runner.run(["branch", "--show-current"], cwd);
|
|
1026
1035
|
return r.code === 0 ? r.stdout.trim() || null : null;
|
|
@@ -1129,30 +1138,37 @@ function userStateRoot() {
|
|
|
1129
1138
|
const xdg = process.env.XDG_STATE_HOME?.trim();
|
|
1130
1139
|
return join(xdg || join(homedir(), ".local", "state"), "gitflow-guard");
|
|
1131
1140
|
}
|
|
1132
|
-
|
|
1141
|
+
/** fs 兜底规范化(仅当 git 查询不可用时使用; Windows 8.3 短名等以 git 输出为准) */
|
|
1142
|
+
function safeRealpath(p) {
|
|
1133
1143
|
try {
|
|
1134
|
-
return realpathSync(
|
|
1144
|
+
return realpathSync(p);
|
|
1135
1145
|
} catch {
|
|
1136
|
-
return
|
|
1146
|
+
return p;
|
|
1137
1147
|
}
|
|
1138
1148
|
}
|
|
1139
1149
|
/**
|
|
1140
1150
|
* 仓库运行时状态目录(审计流水等), 键为「仓库名-真实路径哈希」。
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1143
|
-
*
|
|
1151
|
+
* 键由 git 权威解析(rev-parse --git-common-dir): linked worktree 与主仓库共用
|
|
1152
|
+
* 同一状态目录(≤0.0.13 共享语义), 且天然规避 Windows 8.3 短名/大小写差异;
|
|
1153
|
+
* git 查询不可用时回退 fs realpath。刻意放在仓库外、且在 agent 文件沙箱
|
|
1154
|
+
* (workspace-write)可写区之外 —— 凡 agent 可写之处的状态都可能被伪造而自我授权。
|
|
1144
1155
|
*/
|
|
1145
|
-
function stateDir(repoRoot) {
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1156
|
+
async function stateDir(repoRoot, runner = gitRunner) {
|
|
1157
|
+
let key = safeRealpath(repoRoot);
|
|
1158
|
+
try {
|
|
1159
|
+
const common = await commonGitDir(runner, repoRoot);
|
|
1160
|
+
if (common) key = common.replace(/[/\\]\.git[/\\]?$/, "") || common;
|
|
1161
|
+
} catch {}
|
|
1162
|
+
const hash = createHash("sha256").update(key).digest("hex").slice(0, 12);
|
|
1163
|
+
const name = basename(key).replace(/[^\w.-]+/g, "-") || "repo";
|
|
1149
1164
|
return join(userStateRoot(), "repos", `${name}-${hash}`);
|
|
1150
1165
|
}
|
|
1151
1166
|
/** 审计留痕; 失败不阻断门禁 */
|
|
1152
|
-
async function appendAudit(repoRoot, entry) {
|
|
1167
|
+
async function appendAudit(repoRoot, entry, runner = gitRunner) {
|
|
1153
1168
|
try {
|
|
1154
|
-
await
|
|
1155
|
-
await
|
|
1169
|
+
const dir = await stateDir(repoRoot, runner);
|
|
1170
|
+
await mkdir(dir, { recursive: true });
|
|
1171
|
+
await appendFile(join(dir, "audit.jsonl"), `${JSON.stringify(entry)}\n`, "utf8");
|
|
1156
1172
|
} catch {}
|
|
1157
1173
|
}
|
|
1158
1174
|
/** 解析一条命令: 分类 → git 事实 → 门禁 → allow/deny */
|
|
@@ -1191,7 +1207,7 @@ async function evaluateCommand(command, opts) {
|
|
|
1191
1207
|
event: "deny",
|
|
1192
1208
|
command,
|
|
1193
1209
|
reason: decision.reason
|
|
1194
|
-
});
|
|
1210
|
+
}, env.runner);
|
|
1195
1211
|
return {
|
|
1196
1212
|
outcome: "deny",
|
|
1197
1213
|
reason: {
|
|
@@ -1222,7 +1238,7 @@ async function logCiReference(seg, env) {
|
|
|
1222
1238
|
event: "ci",
|
|
1223
1239
|
command: seg.pr ?? void 0,
|
|
1224
1240
|
role: state
|
|
1225
|
-
});
|
|
1241
|
+
}, env.runner);
|
|
1226
1242
|
}
|
|
1227
1243
|
/** 按段预取 git 事实(异步 I/O 全部前置, 门禁保持纯函数) */
|
|
1228
1244
|
async function factsFor(seg, env) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents-gitflow-guard",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
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",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"keywords": [
|
|
28
28
|
"dsh",
|
|
29
29
|
"deepseek-harness",
|
|
30
|
+
"dsh-plugin",
|
|
30
31
|
"claude-code",
|
|
31
32
|
"codex",
|
|
32
33
|
"opencode",
|
|
@@ -60,7 +61,9 @@
|
|
|
60
61
|
"prepack": "npm run build",
|
|
61
62
|
"test": "vitest run",
|
|
62
63
|
"typecheck": "tsc --noEmit",
|
|
63
|
-
"verify:matrix": "npm run build && node scripts/verify-matrix.mjs"
|
|
64
|
+
"verify:matrix": "npm run build && node scripts/verify-matrix.mjs",
|
|
65
|
+
"check:pins": "node scripts/check-version-pins.mjs",
|
|
66
|
+
"prepublishOnly": "npm run check:pins"
|
|
64
67
|
},
|
|
65
68
|
"peerDependencies": {
|
|
66
69
|
"@deepseek-ai/cordis": "^4.0.1",
|