@dadado/agent-kit-cli 5.15.1 → 5.17.0

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.
Files changed (72) hide show
  1. package/dashboard/dashboard-data.mjs +69 -65
  2. package/dashboard/lib/guards.d.mts +5 -0
  3. package/dashboard/lib/guards.mjs +31 -0
  4. package/dashboard/lib/identity-probe.mjs +56 -0
  5. package/dashboard/lib/pipeline-cache.mjs +58 -0
  6. package/dashboard/lib/processes.mjs +71 -0
  7. package/dashboard/serve.mjs +43 -1
  8. package/dashboard/start-broadcast.mjs +2 -12
  9. package/dashboard/start.mjs +2 -13
  10. package/dist/add-HTYIFKMN.js +150 -0
  11. package/dist/chunk-2IPXQFJE.js +253 -0
  12. package/dist/chunk-2RDRMGD3.js +82 -0
  13. package/dist/chunk-2YU2H7I6.js +42 -0
  14. package/dist/chunk-4IWG5CVE.js +214 -0
  15. package/dist/chunk-5VCOWTTP.js +6 -0
  16. package/dist/chunk-77V75A7O.js +39 -0
  17. package/dist/chunk-7HDQEK6T.js +26 -0
  18. package/dist/chunk-AW74INUW.js +1456 -0
  19. package/dist/chunk-B73KQ5KX.js +972 -0
  20. package/dist/chunk-CBCW5R5J.js +484 -0
  21. package/dist/chunk-DE3LQFSJ.js +18 -0
  22. package/dist/chunk-DQA434Y4.js +106 -0
  23. package/dist/chunk-F4VMYR7U.js +318 -0
  24. package/dist/chunk-F6C3GFKV.js +57 -0
  25. package/dist/chunk-HFUH3HVS.js +282 -0
  26. package/dist/chunk-HS7LVVLO.js +74 -0
  27. package/dist/chunk-IHROBTNM.js +135 -0
  28. package/dist/chunk-KD2MYBB6.js +213 -0
  29. package/dist/chunk-MAPOQEA2.js +43 -0
  30. package/dist/chunk-N3HTJVBR.js +341 -0
  31. package/dist/chunk-NQ7IQ5XT.js +26 -0
  32. package/dist/chunk-OUQZC744.js +1315 -0
  33. package/dist/chunk-PCCQUWE5.js +817 -0
  34. package/dist/chunk-PNFF6OU3.js +273 -0
  35. package/dist/chunk-PNG7LO2F.js +53 -0
  36. package/dist/chunk-QTON2GQX.js +203 -0
  37. package/dist/chunk-S6GMFDKV.js +50 -0
  38. package/dist/chunk-SMGFN3Z4.js +503 -0
  39. package/dist/chunk-STGFBAYT.js +641 -0
  40. package/dist/chunk-TFZ2JUXH.js +1081 -0
  41. package/dist/chunk-VY7MTPJ2.js +205 -0
  42. package/dist/chunk-W24CMQ32.js +525 -0
  43. package/dist/chunk-WW3YZKHW.js +165 -0
  44. package/dist/chunk-ZB43YMHW.js +20 -0
  45. package/dist/chunk-ZCTSUQRR.js +874 -0
  46. package/dist/chunk-ZXKZBTXE.js +17 -0
  47. package/dist/contribute-C6AW2P6J.js +367 -0
  48. package/dist/cursor-awareness-KTL2RGKD.js +77 -0
  49. package/dist/dashboard-YSNUGBFY.js +21 -0
  50. package/dist/dashboard-broadcast-M4CMW7RX.js +119 -0
  51. package/dist/diff-VHNR62SI.js +74 -0
  52. package/dist/doctor-PET2HRRJ.js +236 -0
  53. package/dist/dogfood-KKFVRWT6.js +366 -0
  54. package/dist/guard-EHXF76ID.js +530 -0
  55. package/dist/handoff-3UI6VKVG.js +302 -0
  56. package/dist/hook-DYIMKJYL.js +1074 -0
  57. package/dist/index.js +62 -14068
  58. package/dist/init-Q4LBM6KT.js +127 -0
  59. package/dist/install-RHOS3OYZ.js +34 -0
  60. package/dist/mission-control-LJACEVVN.js +89 -0
  61. package/dist/monitors-QV2IS3MP.js +176 -0
  62. package/dist/plan-index-JMC7Z4DW.js +44 -0
  63. package/dist/public-inbound-radar-WP3XF7GS.js +68 -0
  64. package/dist/remote-issues-SZ4HDQL5.js +44 -0
  65. package/dist/run-I3BFGULT.js +243 -0
  66. package/dist/run-plan-FO7CL4M3.js +118 -0
  67. package/dist/scan-NI2SWJ2E.js +33 -0
  68. package/dist/setup-global-M42GEODI.js +340 -0
  69. package/dist/status-XPVQVQMX.js +150 -0
  70. package/dist/update-FJ2EBZVO.js +283 -0
  71. package/dist/validate-V2CW7J4B.js +201 -0
  72. package/package.json +3 -3
@@ -0,0 +1,530 @@
1
+ import {
2
+ readStdinJson
3
+ } from "./chunk-DE3LQFSJ.js";
4
+
5
+ // src/commands/guard.ts
6
+ import { execFile } from "child_process";
7
+ import { promisify } from "util";
8
+ import { defineCommand } from "citty";
9
+
10
+ // src/hooks/format-guard-shell.ts
11
+ function resolveGuardShellFormat(value) {
12
+ return value === "claude" ? "claude" : "cursor";
13
+ }
14
+ function extractGuardShellInput(payload, format) {
15
+ const p = payload && typeof payload === "object" ? payload : {};
16
+ const cwd = typeof p.cwd === "string" && p.cwd ? p.cwd : void 0;
17
+ if (format === "claude") {
18
+ const command2 = typeof p.tool_input?.command === "string" ? p.tool_input.command : "";
19
+ return { command: command2, cwd };
20
+ }
21
+ const command = typeof p.command === "string" ? p.command : "";
22
+ return { command, cwd };
23
+ }
24
+ function formatGuardShellOutput(result, format) {
25
+ if (format !== "claude") return JSON.stringify(result);
26
+ if (result.permission !== "deny") return "";
27
+ return JSON.stringify({
28
+ hookSpecificOutput: {
29
+ hookEventName: "PreToolUse",
30
+ permissionDecision: "deny",
31
+ permissionDecisionReason: result.agent_message ?? `Denied by agent-kit guard shell (rule \`${result.rule ?? "unknown"}\`).`
32
+ }
33
+ });
34
+ }
35
+
36
+ // src/invariants/secrets-scan.ts
37
+ var CITE = "agent-kit guard prompt (docs/cursor-native-audit.md)";
38
+ var SECRET_PATTERNS = [
39
+ {
40
+ id: "json-secret-kv",
41
+ re: /"(password|apiKey|api_key|secret|token|auth)"\s*:\s*"[^"]{12,}"/i
42
+ },
43
+ {
44
+ id: "env-assignment",
45
+ re: /\b(?:API_KEY|SECRET|PASSWORD|TOKEN|ACCESS_KEY|PRIVATE_KEY)\s*=\s*['"]?[^\s'"]{12,}/i
46
+ },
47
+ {
48
+ id: "aws-access-key",
49
+ re: /\bAKIA[0-9A-Z]{16}\b/
50
+ },
51
+ {
52
+ // Classic (`ghp_`), OAuth (`gho_`), user-to-server (`ghu_`), server-to-server
53
+ // (`ghs_`), refresh (`ghr_`) and fine-grained (`github_pat_`) tokens.
54
+ id: "github-pat",
55
+ re: /\b(?:gh[pousr]_[A-Za-z0-9_]{36,}|github_pat_[A-Za-z0-9_]{22,})\b/
56
+ },
57
+ {
58
+ id: "pem-private-key",
59
+ re: /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY-----/
60
+ },
61
+ {
62
+ id: "slack-token",
63
+ re: /\bxox[abposr]-[A-Za-z0-9-]{10,}\b/
64
+ },
65
+ // Hyphenated vendor keys (`sk-ant-api03-…`, `sk-proj-…`) cannot be matched by
66
+ // `openai-sk`: its body class excludes `-`, so it stops at the first separator.
67
+ // Listed before `openai-sk`; the two cannot both hit the same span.
68
+ {
69
+ id: "sk-hyphenated-vendor",
70
+ re: /\bsk-[A-Za-z0-9]{2,12}-[A-Za-z0-9_-]{16,}\b/
71
+ },
72
+ {
73
+ // Single-segment `sk-` bodies only (no `-` in the class) — see `sk-hyphenated-vendor`.
74
+ id: "openai-sk",
75
+ re: /\bsk-[A-Za-z0-9]{20,}\b/
76
+ }
77
+ ];
78
+ function maskSecretExcerpt(raw) {
79
+ return raw.replace(
80
+ /\b(github_pat_|gh[pousr]_|xox[abposr]-|sk-|AKIA)([A-Za-z0-9_-]{4,})/g,
81
+ (_m, p1, p2) => `${p1}${"*".repeat(Math.min(8, p2.length))}`
82
+ ).replace(
83
+ /(PRIVATE KEY-----\s*)([A-Za-z0-9+/=]+)/g,
84
+ (_m, p1, p2) => `${p1}${"*".repeat(Math.min(8, p2.length))}`
85
+ ).replace(
86
+ /(=\s*['"]?)([^\s'"]{4,})/g,
87
+ (_m, p1, p2) => `${p1}${"*".repeat(Math.min(8, p2.length))}`
88
+ ).replace(
89
+ /("(?:password|apiKey|api_key|secret|token|auth)"\s*:\s*")([^"]{4,})(")/gi,
90
+ (_m, p1, p2, p3) => `${p1}${"*".repeat(Math.min(8, p2.length))}${p3}`
91
+ );
92
+ }
93
+ function excerptAround(text, index, len) {
94
+ const start = Math.max(0, index - 8);
95
+ const end = Math.min(text.length, index + len + 8);
96
+ return maskSecretExcerpt(text.slice(start, end).replace(/\s+/g, " "));
97
+ }
98
+ function scanTextForSecrets(text) {
99
+ if (!text) return [];
100
+ const hits = [];
101
+ for (const { id, re } of SECRET_PATTERNS) {
102
+ const flags = re.flags.includes("g") ? re.flags : `${re.flags}g`;
103
+ const global = new RegExp(re.source, flags);
104
+ let match = global.exec(text);
105
+ while (match) {
106
+ hits.push({
107
+ patternId: id,
108
+ excerpt: excerptAround(text, match.index, match[0].length)
109
+ });
110
+ if (!global.global) break;
111
+ match = global.exec(text);
112
+ }
113
+ }
114
+ return hits;
115
+ }
116
+ function secretsAdviseMessage(hits) {
117
+ const ids = [...new Set(hits.map((h) => h.patternId))].join(", ");
118
+ return `Possible secret pattern(s) in prompt (${ids}). Cite: ${CITE}. Remove live credentials before submitting; use env vars or a secrets store.`;
119
+ }
120
+
121
+ // src/invariants/shell-guard.ts
122
+ var CITE2 = "agent-kit guard shell (ADR 2026-07-29_cli-invariants-thin-hook-adapters)";
123
+ var PROTECTED_BRANCH_RE = /^(?:main|master|prod)$/;
124
+ function normalizeShellCommand(command) {
125
+ return command.replace(/\s+/g, " ").trim();
126
+ }
127
+ function joinNonTerminatingNewlines(command) {
128
+ return command.replace(/(?<!\\)((?:\\\\)*)\\\r?\n/g, "$1");
129
+ }
130
+ var SEGMENT_SPLIT = /\r?\n|&&|\|\||[;|]|(?<![<>])&(?![<>])/;
131
+ function splitSegments(command) {
132
+ return command.split(SEGMENT_SPLIT).map(normalizeShellCommand).filter(Boolean);
133
+ }
134
+ function shellSegments(command) {
135
+ const joined = splitSegments(joinNonTerminatingNewlines(command));
136
+ const raw = splitSegments(command);
137
+ return [.../* @__PURE__ */ new Set([...joined, ...raw])];
138
+ }
139
+ function stripLeadingEnvAssignments(segment) {
140
+ return segment.replace(/^(?:\w+=\S+\s+)*/, "");
141
+ }
142
+ var WRAPPER_ARG_FLAGS = /* @__PURE__ */ new Map([
143
+ ["sudo", /* @__PURE__ */ new Set(["-u", "-g", "-h", "-p", "-C", "-D", "-r", "-t", "-U", "-T"])],
144
+ ["env", /* @__PURE__ */ new Set(["-u", "-C", "-S", "--unset", "--chdir", "--split-string"])],
145
+ ["command", /* @__PURE__ */ new Set()],
146
+ ["exec", /* @__PURE__ */ new Set(["-a"])],
147
+ ["nohup", /* @__PURE__ */ new Set()],
148
+ ["time", /* @__PURE__ */ new Set(["-f", "-o", "--format", "--output"])]
149
+ ]);
150
+ var GIT_GLOBAL_OPTS = /* @__PURE__ */ new Map([
151
+ ["-C", true],
152
+ ["-c", true],
153
+ ["--git-dir", true],
154
+ ["--work-tree", true],
155
+ ["--namespace", true],
156
+ ["--no-pager", false],
157
+ ["-P", false],
158
+ ["--literal-pathspecs", false]
159
+ ]);
160
+ function canonicalGitHead(segment) {
161
+ let tokens = normalizeShellCommand(segment).split(" ").filter(Boolean);
162
+ for (; ; ) {
163
+ const t = tokens[0];
164
+ if (t === void 0) break;
165
+ if (/^[({]/.test(t)) {
166
+ const rest = t.replace(/^[({]+/, "");
167
+ tokens = rest ? [rest, ...tokens.slice(1)] : tokens.slice(1);
168
+ continue;
169
+ }
170
+ if (/^\w+=/.test(t)) {
171
+ tokens = tokens.slice(1);
172
+ continue;
173
+ }
174
+ const argFlags = WRAPPER_ARG_FLAGS.get(t);
175
+ if (argFlags) {
176
+ let i = 1;
177
+ while (i < tokens.length && tokens[i]?.startsWith("-")) {
178
+ i += argFlags.has(tokens[i]) ? 2 : 1;
179
+ }
180
+ tokens = tokens.slice(i);
181
+ continue;
182
+ }
183
+ break;
184
+ }
185
+ if (tokens.length > 0) {
186
+ const last = tokens[tokens.length - 1].replace(/[)}]+$/, "");
187
+ tokens = last ? [...tokens.slice(0, -1), last] : tokens.slice(0, -1);
188
+ }
189
+ if (tokens.length > 0 && /^(?:[\w./-]+\/)?git$/.test(tokens[0])) {
190
+ let i = 1;
191
+ while (i < tokens.length) {
192
+ const t = tokens[i];
193
+ const name = t.includes("=") ? t.slice(0, t.indexOf("=")) : t;
194
+ const takesArg = GIT_GLOBAL_OPTS.get(name);
195
+ if (takesArg === void 0) break;
196
+ i += takesArg && !t.includes("=") ? 2 : 1;
197
+ }
198
+ tokens = [tokens[0], ...tokens.slice(i)];
199
+ }
200
+ return tokens.join(" ");
201
+ }
202
+ function shellInvocationHeads(command) {
203
+ return shellSegments(command).map(canonicalGitHead).filter(Boolean);
204
+ }
205
+ function segmentHasAllowMainPushEnv(segment) {
206
+ if (process.env.ALLOW_MAIN_PUSH === "1") return true;
207
+ const leading = segment.match(/^(?:\w+=\S+\s+)*/)?.[0] ?? "";
208
+ return /(?:^|\s)ALLOW_MAIN_PUSH=1(?:\s|$)/.test(leading);
209
+ }
210
+ function hasForbiddenMainPushFlags(head) {
211
+ for (const t of head.split(/\s+/).filter(Boolean)) {
212
+ if (t === "--force" || t === "-f" || t === "--force-with-lease" || t === "--no-verify" || t === "--all" || t === "--tags") {
213
+ return true;
214
+ }
215
+ }
216
+ return false;
217
+ }
218
+ var AUTHORIZED_MAIN_PUSH_FLAGS = /* @__PURE__ */ new Set([
219
+ "-u",
220
+ "--set-upstream",
221
+ "-v",
222
+ "--verbose",
223
+ "-q",
224
+ "--quiet",
225
+ "--progress",
226
+ "--no-progress"
227
+ ]);
228
+ function isAuthorizedProdMainPush(head) {
229
+ if (!/^(?:[\w./-]+\/)?git\s+push\b/.test(head)) return false;
230
+ if (hasForbiddenMainPushFlags(head)) return false;
231
+ const after = head.replace(/^(?:[\w./-]+\/)?git\s+push\b/, "").trim();
232
+ const positional = [];
233
+ for (const t of after.split(/\s+/).filter(Boolean)) {
234
+ if (t.startsWith("-")) {
235
+ if (!AUTHORIZED_MAIN_PUSH_FLAGS.has(t)) return false;
236
+ continue;
237
+ }
238
+ positional.push(t);
239
+ }
240
+ const refspec = positional.length === 2 ? positional[1] : void 0;
241
+ if (!refspec) return false;
242
+ const unquoted = refspec.replace(/['"\\]/g, "");
243
+ if (unquoted.startsWith("+")) return false;
244
+ if (unquoted.includes(":")) {
245
+ const src = unquoted.slice(0, unquoted.lastIndexOf(":"));
246
+ if (src !== "HEAD") return false;
247
+ }
248
+ const dest = unquoted.includes(":") ? unquoted.slice(unquoted.lastIndexOf(":") + 1) : unquoted;
249
+ return normalizePushRefspecToken(dest) === "main";
250
+ }
251
+ function segmentAllowsMainPush(segment) {
252
+ if (!segmentHasAllowMainPushEnv(segment)) return false;
253
+ return isAuthorizedProdMainPush(stripLeadingEnvAssignments(segment));
254
+ }
255
+ var PUBLIC_REPO_RE = /agent-kit-startup\/agent-kit(?![\w-])/i;
256
+ function segmentHasAllowPublicPushEnv(segment) {
257
+ if (process.env.ALLOW_PUBLIC_PUSH === "1") return true;
258
+ const leading = segment.match(/^(?:\w+=\S+\s+)*/)?.[0] ?? "";
259
+ return /(?:^|\s)ALLOW_PUBLIC_PUSH=1(?:\s|$)/.test(leading);
260
+ }
261
+ function pushTargetsPublicRepo(head, remotes) {
262
+ if (!/^(?:[\w./-]+\/)?git\s+push\b/.test(head)) return false;
263
+ if (PUBLIC_REPO_RE.test(head)) return true;
264
+ if (!remotes) return false;
265
+ const after = head.replace(/^(?:[\w./-]+\/)?git\s+push\b/, "").trim();
266
+ const remoteToken = after.split(/\s+/).find((t) => t && !t.startsWith("-"));
267
+ if (!remoteToken) return false;
268
+ const url = remotes[remoteToken];
269
+ return typeof url === "string" && PUBLIC_REPO_RE.test(url);
270
+ }
271
+ function ghTargetsPublicRepo(head) {
272
+ if (!/^(?:[\w./-]+\/)?gh\s+pr\s+(?:create|merge)\b/.test(head)) return false;
273
+ return PUBLIC_REPO_RE.test(head);
274
+ }
275
+ function anyHeadMatches(command, re) {
276
+ return shellInvocationHeads(command).some((head) => re.test(head));
277
+ }
278
+ function isProtectedBranch(name) {
279
+ return typeof name === "string" && PROTECTED_BRANCH_RE.test(name.trim());
280
+ }
281
+ function normalizePushRefspecToken(token) {
282
+ let t = token.trim();
283
+ t = t.replace(/['"\\]/g, "");
284
+ if (t.startsWith("+")) t = t.slice(1);
285
+ if (t.startsWith("refs/heads/")) t = t.slice("refs/heads/".length);
286
+ if (t.startsWith("origin/")) t = t.slice("origin/".length);
287
+ return t;
288
+ }
289
+ function pushHeadHasProtectedDest(head) {
290
+ if (/(?:HEAD|@):(?:refs\/heads\/)?(?:main|master|prod)\b/.test(head)) return true;
291
+ if (/(?:^|\s)-(?:u|--set-upstream)\s+\S+\s+(?:main|master|prod)(?:\s|$)/.test(head)) {
292
+ return true;
293
+ }
294
+ const after = head.replace(/^(?:[\w./-]+\/)?git\s+push\b/, "");
295
+ for (const raw of after.split(/\s+/).filter(Boolean)) {
296
+ if (raw.startsWith("-")) continue;
297
+ const dest = raw.includes(":") ? raw.slice(raw.lastIndexOf(":") + 1) : raw;
298
+ if (PROTECTED_BRANCH_RE.test(normalizePushRefspecToken(dest))) return true;
299
+ }
300
+ return false;
301
+ }
302
+ function isBareOrHeadPushToCurrent(head) {
303
+ if (!/^(?:[\w./-]+\/)?git\s+push\b/.test(head)) return false;
304
+ if (pushHeadHasProtectedDest(head)) {
305
+ return false;
306
+ }
307
+ if (/(?:^|\s)\+?(?:refs\/heads\/)?(?:origin\/)?(?:staging|develop|homologacao)(?:\s|$|:)/.test(
308
+ head
309
+ ) || /(?:HEAD|@):(?:refs\/heads\/)?(?!main|master|prod)[A-Za-z0-9._/-]+/.test(head)) {
310
+ return false;
311
+ }
312
+ const after = head.replace(/^(?:[\w./-]+\/)?git\s+push\b/, "").trim();
313
+ const withoutFlags = after.replace(/(?:^|\s)(?:--force|-f|-u|--set-upstream|--tags|--all|--prune)(?=\s|$)/g, " ").replace(/(?:^|\s)--\w[\w-]*(?:=\S+)?/g, " ").replace(/\s+/g, " ").trim();
314
+ if (!withoutFlags) return true;
315
+ const tokens = withoutFlags.split(/\s+/);
316
+ if (tokens.length === 1) return true;
317
+ if (tokens.length >= 2 && (tokens[1] === "HEAD" || tokens[1] === "@")) return true;
318
+ if (/\bHEAD\b|(?:^|\s)@(?:\s|$)/.test(withoutFlags) && !/(?:HEAD|@):/.test(withoutFlags)) {
319
+ return true;
320
+ }
321
+ return false;
322
+ }
323
+ var SHELL_DENY_RULES = [
324
+ {
325
+ id: "git-checkout-path",
326
+ description: "git checkout -- / HEAD -- / . discards working-tree edits",
327
+ test: (cmd) => shellInvocationHeads(cmd).some((head) => {
328
+ if (!/^(?:[\w./-]+\/)?git\s+checkout\b/.test(head)) return false;
329
+ if (/\s--(?:\s|$)/.test(head)) return true;
330
+ if (/\scheckout\s+\.(?:\s|$)/.test(head)) return true;
331
+ return false;
332
+ })
333
+ },
334
+ {
335
+ id: "git-restore",
336
+ description: "git restore discards working-tree edits",
337
+ test: (cmd) => anyHeadMatches(cmd, /^(?:[\w./-]+\/)?git\s+restore\b/)
338
+ },
339
+ {
340
+ id: "git-reset-hard",
341
+ description: "git reset --hard destroys uncommitted work",
342
+ test: (cmd) => anyHeadMatches(cmd, /^(?:[\w./-]+\/)?git\s+reset\b.*--hard\b/)
343
+ },
344
+ {
345
+ id: "git-clean-fd",
346
+ description: "git clean -fd removes untracked files",
347
+ test: (cmd) => shellInvocationHeads(cmd).some((head) => {
348
+ if (!/^(?:[\w./-]+\/)?git\s+clean\b/.test(head)) return false;
349
+ const tokens = head.split(/\s+/);
350
+ const shortBundle = (t) => /^-[a-zA-Z]+$/.test(t);
351
+ const hasForce = tokens.some((t) => t === "--force" || shortBundle(t) && t.includes("f"));
352
+ const hasD = tokens.some((t) => shortBundle(t) && t.includes("d"));
353
+ return hasForce && hasD;
354
+ })
355
+ },
356
+ {
357
+ id: "public-repo-direct-write",
358
+ description: "agents ship to the public repo only via scripts/sync-public.mjs (CI); no direct git push / gh pr create|merge against it",
359
+ test: (cmd, opts) => shellSegments(cmd).some((segment) => {
360
+ if (segmentHasAllowPublicPushEnv(segment)) return false;
361
+ const head = canonicalGitHead(segment);
362
+ return pushTargetsPublicRepo(head, opts?.remotes) || ghTargetsPublicRepo(head);
363
+ })
364
+ },
365
+ {
366
+ id: "git-push-main",
367
+ description: "direct push to main/master/prod bypasses staging",
368
+ test: (cmd, opts) => shellSegments(cmd).some((segment) => {
369
+ if (segmentAllowsMainPush(segment)) return false;
370
+ const head = canonicalGitHead(segment);
371
+ if (!/^(?:[\w./-]+\/)?git\s+push\b/.test(head)) return false;
372
+ if (pushHeadHasProtectedDest(head)) {
373
+ return true;
374
+ }
375
+ if (isProtectedBranch(opts?.currentBranch) && isBareOrHeadPushToCurrent(head)) {
376
+ return true;
377
+ }
378
+ return false;
379
+ })
380
+ }
381
+ ];
382
+ function evaluateShellCommand(command, opts = {}) {
383
+ const normalized = normalizeShellCommand(command);
384
+ if (!normalized) {
385
+ return { permission: "allow" };
386
+ }
387
+ for (const rule of SHELL_DENY_RULES) {
388
+ if (rule.test(command, opts)) {
389
+ const agent_message = `Denied by ${CITE2}: ${rule.description} (rule \`${rule.id}\`). Use /git-staging; never discard human hunks or push protected branches from the agent.`;
390
+ return {
391
+ permission: "deny",
392
+ rule: rule.id,
393
+ agent_message,
394
+ user_message: agent_message
395
+ };
396
+ }
397
+ }
398
+ return { permission: "allow" };
399
+ }
400
+
401
+ // src/commands/guard.ts
402
+ var execFileAsync = promisify(execFile);
403
+ async function detectCurrentBranch(cwd) {
404
+ try {
405
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
406
+ encoding: "utf8",
407
+ cwd
408
+ });
409
+ const branch = stdout.trim();
410
+ return branch && branch !== "HEAD" ? branch : void 0;
411
+ } catch {
412
+ return void 0;
413
+ }
414
+ }
415
+ async function detectRemotes(cwd) {
416
+ try {
417
+ const { stdout } = await execFileAsync("git", ["remote", "-v"], { encoding: "utf8", cwd });
418
+ const remotes = {};
419
+ for (const line of stdout.split("\n")) {
420
+ const m = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)$/);
421
+ if (m?.[1] && m[2]) remotes[m[1]] = m[2];
422
+ }
423
+ return Object.keys(remotes).length > 0 ? remotes : void 0;
424
+ } catch {
425
+ return void 0;
426
+ }
427
+ }
428
+ var defaultGuardShellDeps = { detectCurrentBranch, detectRemotes };
429
+ var GIT_OR_GH_RE = /\b(git|gh)\b/;
430
+ async function evaluateGuardShell(command, cwd = process.cwd(), deps = defaultGuardShellDeps) {
431
+ if (!GIT_OR_GH_RE.test(command)) return evaluateShellCommand(command);
432
+ const [currentBranch, remotes] = await Promise.all([
433
+ deps.detectCurrentBranch(cwd),
434
+ deps.detectRemotes(cwd)
435
+ ]);
436
+ return evaluateShellCommand(command, { currentBranch, remotes });
437
+ }
438
+ async function runGuardShellHook(formatArg, deps = {}) {
439
+ const format = resolveGuardShellFormat(formatArg);
440
+ const run = async () => {
441
+ let command = typeof deps.command === "string" ? deps.command : "";
442
+ let cwd = deps.cwd ?? process.cwd();
443
+ if (!command) {
444
+ const readStdin = deps.readStdin ?? readStdinJson;
445
+ const input = extractGuardShellInput(await readStdin(), format);
446
+ command = input.command;
447
+ if (input.cwd) cwd = input.cwd;
448
+ }
449
+ const result = await evaluateGuardShell(command, cwd, {
450
+ detectCurrentBranch: deps.detectCurrentBranch ?? defaultGuardShellDeps.detectCurrentBranch,
451
+ detectRemotes: deps.detectRemotes ?? defaultGuardShellDeps.detectRemotes
452
+ });
453
+ return formatGuardShellOutput(result, format);
454
+ };
455
+ if (format !== "claude") return run();
456
+ try {
457
+ return await run();
458
+ } catch {
459
+ return "";
460
+ }
461
+ }
462
+ var guardCommand = defineCommand({
463
+ meta: {
464
+ name: "guard",
465
+ description: "Deny/annotate guards (shell, prompt). Hooks are thin adapters."
466
+ },
467
+ subCommands: {
468
+ shell: defineCommand({
469
+ meta: {
470
+ name: "shell",
471
+ description: "Evaluate a shell command against the git-workflow / protected-branch deny-list (git checkout|restore|reset --hard|clean -fd + pushes to main/master/prod + direct git push/gh pr create|merge against the public repo). Not a general destructive-command guard: rm -rf, chmod, dd are allowed."
472
+ },
473
+ args: {
474
+ json: {
475
+ type: "boolean",
476
+ default: true,
477
+ description: "Print Cursor beforeShellExecution JSON (default)"
478
+ },
479
+ format: {
480
+ type: "string",
481
+ default: "cursor",
482
+ description: "cursor (default, beforeShellExecution JSON) | claude (PreToolUse deny JSON, empty on allow)"
483
+ },
484
+ command: {
485
+ type: "string",
486
+ description: "Command string (otherwise read from stdin: JSON.command, or tool_input.command with --format claude)"
487
+ }
488
+ },
489
+ async run({ args }) {
490
+ const command = typeof args.command === "string" ? args.command : void 0;
491
+ const out = await runGuardShellHook(args.format, { command });
492
+ if (out) console.log(out);
493
+ }
494
+ }),
495
+ prompt: defineCommand({
496
+ meta: {
497
+ name: "prompt",
498
+ description: "Scan prompt text for secret patterns (advisory; fail-open at hook)"
499
+ },
500
+ args: {
501
+ json: {
502
+ type: "boolean",
503
+ default: true
504
+ }
505
+ },
506
+ async run() {
507
+ const payload = await readStdinJson();
508
+ const text = typeof payload.prompt === "string" && payload.prompt || typeof payload.text === "string" && payload.text || "";
509
+ const hits = scanTextForSecrets(text);
510
+ if (hits.length === 0) {
511
+ console.log(JSON.stringify({ continue: true, hits: [] }));
512
+ return;
513
+ }
514
+ console.log(
515
+ JSON.stringify({
516
+ continue: true,
517
+ user_message: secretsAdviseMessage(hits),
518
+ agent_message: secretsAdviseMessage(hits),
519
+ hits: hits.map((h) => ({ patternId: h.patternId }))
520
+ })
521
+ );
522
+ }
523
+ })
524
+ }
525
+ });
526
+ export {
527
+ evaluateGuardShell,
528
+ guardCommand,
529
+ runGuardShellHook
530
+ };