agents-gitflow-guard 0.0.1

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/lib/cli.mjs ADDED
@@ -0,0 +1,166 @@
1
+ import { a as stateDir, c as gitRunner, d as loadConfig, l as isAncestor, o as currentBranch, s as findRepoRoot, t as appendAudit, u as openPermitStore } from "./src-DWE1n9Zh.mjs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ //#region src/cli.ts
5
+ const USAGE = `gitflow-guard — GitFlow 流程守卫 CLI(用户终端专属)
6
+
7
+ 用法:
8
+ gitflow-guard permit <feature> [--kind early-pr|confirm|trunk-pr] [--ttl <分钟>] [--repo <路径>]
9
+ gitflow-guard confirm <feature> [--ttl <分钟>] [--repo <路径>]
10
+ gitflow-guard status [--repo <路径>]
11
+ gitflow-guard audit [--lines <数量>] [--repo <路径>]
12
+ gitflow-guard --help
13
+
14
+ 说明:
15
+ permit/confirm 是用户专属授权操作, agent 执行会被插件拦截。
16
+ status/audit 只读, agent 可自查。`;
17
+ function parseFlags(argv) {
18
+ const positional = [];
19
+ const flags = {};
20
+ for (let i = 0; i < argv.length; i++) {
21
+ const a = argv[i];
22
+ const next = () => argv[++i];
23
+ if (a === "--repo") flags.repo = next();
24
+ else if (a === "--kind") flags.kind = next();
25
+ else if (a === "--ttl") flags.ttl = Number(next());
26
+ else if (a === "--lines") flags.lines = Number(next());
27
+ else if (a.startsWith("--repo=")) flags.repo = a.slice(7);
28
+ else if (a.startsWith("--kind=")) flags.kind = a.slice(7);
29
+ else if (a.startsWith("--ttl=")) flags.ttl = Number(a.slice(6));
30
+ else if (a.startsWith("--lines=")) flags.lines = Number(a.slice(8));
31
+ else positional.push(a);
32
+ }
33
+ return {
34
+ positional,
35
+ flags
36
+ };
37
+ }
38
+ async function resolveRepo(flags) {
39
+ if (flags.repo) return flags.repo;
40
+ return await findRepoRoot(gitRunner, process.cwd());
41
+ }
42
+ async function main(argv, opts = {}) {
43
+ const runner = opts.runner ?? gitRunner;
44
+ const [cmd, ...rest] = argv;
45
+ if (cmd === "--help" || cmd === "help" || cmd === void 0) {
46
+ console.log(USAGE);
47
+ return 0;
48
+ }
49
+ const { positional, flags } = parseFlags(rest);
50
+ try {
51
+ if (cmd === "permit") return await permit(positional, flags, runner);
52
+ if (cmd === "confirm") return await permit(positional, {
53
+ ...flags,
54
+ kind: "confirm"
55
+ }, runner);
56
+ if (cmd === "status") return await status(flags, runner);
57
+ if (cmd === "audit") return await audit(flags);
58
+ console.error(`[gitflow-guard] 未知子命令: ${cmd ?? ""}\n\n${USAGE}`);
59
+ return 1;
60
+ } catch (e) {
61
+ console.error(`[gitflow-guard] ${e.message}`);
62
+ return 1;
63
+ }
64
+ }
65
+ async function permit(positional, flags, runner) {
66
+ const feature = positional[0];
67
+ if (!feature) {
68
+ console.error("[gitflow-guard] 用法: gitflow-guard permit <feature> [--kind early-pr|confirm|trunk-pr]");
69
+ return 1;
70
+ }
71
+ const kind = flags.kind ?? "confirm";
72
+ if (kind !== "early-pr" && kind !== "confirm" && kind !== "trunk-pr") {
73
+ console.error("[gitflow-guard] --kind 必须是 early-pr / confirm / trunk-pr");
74
+ return 1;
75
+ }
76
+ const ttlMs = flags.ttl != null && Number.isFinite(flags.ttl) && flags.ttl > 0 ? flags.ttl * 6e4 : void 0;
77
+ const repoRoot = await resolveRepo(flags);
78
+ if (!repoRoot) {
79
+ console.error("[gitflow-guard] 无法定位 git 仓库(当前目录不在仓库内, 或用 --repo 指定)");
80
+ return 1;
81
+ }
82
+ const { config } = await loadConfig(repoRoot);
83
+ if (!config?.enabled) {
84
+ console.error(`[gitflow-guard] 项目未启用 gitflow-guard(${join(repoRoot, "gitflow-guard.config.json")} 不存在或 enabled=false)`);
85
+ return 1;
86
+ }
87
+ const granted = await (await openPermitStore(join(stateDir(repoRoot), "state.json"))).grant(kind, feature, ttlMs != null ? { ttlMs } : void 0);
88
+ await appendAudit(repoRoot, {
89
+ time: Date.now(),
90
+ event: "grant",
91
+ kind,
92
+ feature
93
+ });
94
+ console.log(`[gitflow-guard] 已授权: ${kind} → ${feature}${granted.expiresAt ? `(有效期至 ${new Date(granted.expiresAt).toLocaleString()})` : "(长期有效)"}`);
95
+ return 0;
96
+ }
97
+ async function status(flags, runner) {
98
+ const repoRoot = await resolveRepo(flags);
99
+ if (!repoRoot) {
100
+ console.error("[gitflow-guard] 无法定位 git 仓库");
101
+ return 1;
102
+ }
103
+ const { config, errors } = await loadConfig(repoRoot);
104
+ const enabled = config?.enabled === true;
105
+ console.log(`[gitflow-guard] status — ${repoRoot}`);
106
+ if (!enabled) {
107
+ console.log("配置: 未启用(不存在 gitflow-guard.config.json 或 enabled=false)");
108
+ for (const e of errors) console.log(` 配置错误: ${e}`);
109
+ return 0;
110
+ }
111
+ const branch = await currentBranch(runner, repoRoot);
112
+ console.log(`配置: 已启用(${config.mode} 模式) | 基线: ${config.branches.base} | 预览: ${config.branches.preview}${config.branches.trunk ? ` | 主干: ${config.branches.trunk}` : ""}`);
113
+ console.log(`当前分支: ${branch ?? "(未知)"}`);
114
+ const pattern = new RegExp(config.confirm.featurePattern);
115
+ const r = await runner.run([
116
+ "for-each-ref",
117
+ "--format=%(refname:short)",
118
+ "refs/heads/"
119
+ ], repoRoot);
120
+ const features = (r.code === 0 ? r.stdout.split("\n").map((s) => s.trim()).filter(Boolean) : []).filter((b) => pattern.test(b));
121
+ const store = await openPermitStore(join(stateDir(repoRoot), "state.json"));
122
+ const inPreview = [];
123
+ for (const f of features) if (await isAncestor(runner, repoRoot, f, config.branches.preview)) inPreview.push(f);
124
+ console.log(`预览分支(${config.branches.preview})包含的 feature:`);
125
+ for (const f of inPreview) console.log(` ✓ ${f}`);
126
+ if (inPreview.length === 0) console.log(" (无)");
127
+ console.log("feature 状态一览:");
128
+ for (const f of features) {
129
+ const confirmed = store.hasValid("confirm", f);
130
+ const early = store.hasValid("early-pr", f);
131
+ const trunk = store.hasValid("trunk-pr", f);
132
+ const mark = (ok) => ok ? "✓" : "✗";
133
+ console.log(` ${f}: 已合预览 ${mark(inPreview.includes(f))} | 已确认(P2) ${mark(confirmed)} | P1 ${early ? "✓" : "-"} | P3 ${trunk ? "✓" : "-"}`);
134
+ }
135
+ const permits = store.list();
136
+ if (permits.length > 0) {
137
+ console.log("特许记录:");
138
+ for (const p of permits) {
139
+ const state = p.used ? "已使用" : p.expiresAt && p.expiresAt <= Date.now() ? "已过期" : "未使用";
140
+ console.log(` ${p.kind} ${p.feature} (${state})`);
141
+ }
142
+ }
143
+ return 0;
144
+ }
145
+ async function audit(flags) {
146
+ const repoRoot = await resolveRepo(flags);
147
+ if (!repoRoot) {
148
+ console.error("[gitflow-guard] 无法定位 git 仓库");
149
+ return 1;
150
+ }
151
+ const lines = flags.lines != null && Number.isFinite(flags.lines) && flags.lines > 0 ? Math.floor(flags.lines) : 20;
152
+ try {
153
+ const all = (await readFile(join(stateDir(repoRoot), "audit.jsonl"), "utf8")).split("\n").filter(Boolean);
154
+ for (const line of all.slice(-lines)) try {
155
+ const e = JSON.parse(line);
156
+ console.log(` ${new Date(e.time).toLocaleString()} ${e.event} ${e.kind ?? ""} ${e.feature ?? ""}${e.command ? ` | ${e.command.slice(0, 80)}` : ""}`);
157
+ } catch {
158
+ console.log(` ${line}`);
159
+ }
160
+ } catch {
161
+ console.log(" 暂无审计记录");
162
+ }
163
+ return 0;
164
+ }
165
+ //#endregion
166
+ export { main };
@@ -0,0 +1,53 @@
1
+ import { t as Runner } from "./repo-DrgptHl1.mjs";
2
+ import { Context } from "@deepseek-ai/cordis";
3
+ //#region src/types.d.ts
4
+ /** 特许类型: P1 提前建 PR / P2 确认合入 / P3 许可 trunk PR */
5
+ type PermitKind = 'early-pr' | 'confirm' | 'trunk-pr';
6
+ //#endregion
7
+ //#region src/index.d.ts
8
+ declare const name = "gitflow-guard";
9
+ interface PluginConfig {
10
+ /** 拦截哪些工具的命令文本(默认 pwsh / bash) */
11
+ toolNames?: string[];
12
+ /** 特许默认有效期(毫秒; 默认不过期) */
13
+ permitTtlMs?: number;
14
+ }
15
+ interface EvaluateOptions {
16
+ repoRoot: string;
17
+ runner?: Runner;
18
+ /** gh 适配器执行器(测试注入; 默认真实 gh) */
19
+ ghRunner?: Runner;
20
+ /** 当前分支(缺省时内部查询) */
21
+ currentBranch?: string | null;
22
+ now?: () => number;
23
+ }
24
+ interface PendingConsume {
25
+ kind: PermitKind;
26
+ feature: string;
27
+ }
28
+ interface EvaluateResult {
29
+ outcome: 'allow' | 'deny' | 'skipped';
30
+ reason?: {
31
+ why: string;
32
+ next: string;
33
+ };
34
+ segmentCount: number;
35
+ /** 放行时可能被本次动作消费的特许(动作成功后由 post-execute 消费) */
36
+ pendingConsume: PendingConsume[];
37
+ }
38
+ interface AuditEntry {
39
+ time: number;
40
+ event: 'allow' | 'deny' | 'grant' | 'consume' | 'remind' | 'ci';
41
+ command?: string;
42
+ feature?: string;
43
+ kind?: string;
44
+ reason?: string;
45
+ }
46
+ declare function stateDir(repoRoot: string): string;
47
+ /** 审计留痕; 失败不阻断门禁 */
48
+ declare function appendAudit(repoRoot: string, entry: AuditEntry): Promise<void>;
49
+ /** 解析一条命令: 分类 → git 事实 → 门禁 → allow/deny */
50
+ declare function evaluateCommand(command: string, opts: EvaluateOptions): Promise<EvaluateResult>;
51
+ declare function apply(ctx: Context, pluginConfig?: PluginConfig): void;
52
+ //#endregion
53
+ export { AuditEntry, EvaluateOptions, EvaluateResult, PendingConsume, PluginConfig, appendAudit, apply, evaluateCommand, name, stateDir };
package/lib/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { a as stateDir, i as name, n as apply, r as evaluateCommand, t as appendAudit } from "./src-DWE1n9Zh.mjs";
2
+ export { appendAudit, apply, evaluateCommand, name, stateDir };
@@ -0,0 +1,12 @@
1
+ //#region src/repo.d.ts
2
+ interface RunResult {
3
+ code: number;
4
+ stdout: string;
5
+ stderr: string;
6
+ }
7
+ /** 命令执行器(外部边界, 测试注入 fake) */
8
+ interface Runner {
9
+ run(args: string[], cwd: string): Promise<RunResult>;
10
+ }
11
+ //#endregion
12
+ export { Runner as t };