@dadado/agent-kit-cli 4.8.2 → 4.8.4

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/dist/index.js CHANGED
@@ -1767,8 +1767,11 @@ import path22 from "path";
1767
1767
  import { defineCommand as defineCommand6 } from "citty";
1768
1768
 
1769
1769
  // src/invariants/hooks-health.ts
1770
- import { access as access4, readFile as readFile6 } from "fs/promises";
1770
+ import { execFile as execFile2 } from "child_process";
1771
+ import { constants as constants2, access as access4, readFile as readFile6, stat } from "fs/promises";
1771
1772
  import path12 from "path";
1773
+ import { promisify as promisify2 } from "util";
1774
+ var execFileAsync2 = promisify2(execFile2);
1772
1775
  var EXPECTED_EVENTS = [
1773
1776
  "sessionStart",
1774
1777
  "preCompact",
@@ -1784,6 +1787,37 @@ async function exists(p) {
1784
1787
  return false;
1785
1788
  }
1786
1789
  }
1790
+ async function isExecutable(p) {
1791
+ try {
1792
+ await access4(p, constants2.X_OK);
1793
+ return true;
1794
+ } catch {
1795
+ return false;
1796
+ }
1797
+ }
1798
+ async function resolveAgentKitCli(rootDir) {
1799
+ const root = path12.resolve(rootDir);
1800
+ const candidates = [
1801
+ path12.join(root, "node_modules", ".bin", "agent-kit"),
1802
+ path12.join(root, "packages", "cli", "dist", "index.js")
1803
+ ];
1804
+ for (const c of candidates) {
1805
+ if (await exists(c)) return c;
1806
+ }
1807
+ try {
1808
+ const { stdout } = await execFileAsync2("which", ["agent-kit"], { encoding: "utf8" });
1809
+ const hit = stdout.trim().split("\n")[0]?.trim();
1810
+ if (hit) return hit;
1811
+ } catch {
1812
+ }
1813
+ return null;
1814
+ }
1815
+ function commandLooksLikeAdapter(command) {
1816
+ const trimmed = command.trim();
1817
+ if (!trimmed) return null;
1818
+ const m = trimmed.match(/(\.cursor\/hooks\/agent\/[A-Za-z0-9._-]+\.sh)\b/);
1819
+ return m?.[1] ?? null;
1820
+ }
1787
1821
  async function assessHooksHealth(rootDir) {
1788
1822
  const root = path12.resolve(rootDir);
1789
1823
  const hooksJsonPath = ".cursor/hooks.json";
@@ -1812,6 +1846,7 @@ async function assessHooksHealth(rootDir) {
1812
1846
  };
1813
1847
  }
1814
1848
  const hooks = parsed.hooks ?? {};
1849
+ const adapterRels = /* @__PURE__ */ new Set();
1815
1850
  for (const event of EXPECTED_EVENTS) {
1816
1851
  const list = hooks[event];
1817
1852
  if (Array.isArray(list) && list.length > 0) {
@@ -1822,6 +1857,8 @@ async function assessHooksHealth(rootDir) {
1822
1857
  if (command.endsWith(".py") || command.includes("python")) {
1823
1858
  reasons.push(`${event} still points at a Python script (${command})`);
1824
1859
  }
1860
+ const rel = commandLooksLikeAdapter(command);
1861
+ if (rel) adapterRels.add(rel);
1825
1862
  }
1826
1863
  } else {
1827
1864
  reasons.push(`missing hook event: ${event}`);
@@ -1830,6 +1867,34 @@ async function assessHooksHealth(rootDir) {
1830
1867
  const resolveLib = path12.join(root, ".cursor", "hooks", "agent", "resolve-agent-kit.sh");
1831
1868
  if (!await exists(resolveLib)) {
1832
1869
  reasons.push("missing `.cursor/hooks/agent/resolve-agent-kit.sh` (thin adapter resolver)");
1870
+ } else if (!await isExecutable(resolveLib)) {
1871
+ reasons.push("`.cursor/hooks/agent/resolve-agent-kit.sh` is not executable (chmod +x)");
1872
+ }
1873
+ for (const rel of adapterRels) {
1874
+ const abs = path12.join(root, rel);
1875
+ if (!await exists(abs)) {
1876
+ reasons.push(`missing adapter script: \`${rel}\``);
1877
+ continue;
1878
+ }
1879
+ try {
1880
+ const st = await stat(abs);
1881
+ if (!st.isFile()) {
1882
+ reasons.push(`adapter path is not a file: \`${rel}\``);
1883
+ continue;
1884
+ }
1885
+ } catch {
1886
+ reasons.push(`unreadable adapter script: \`${rel}\``);
1887
+ continue;
1888
+ }
1889
+ if (!await isExecutable(abs)) {
1890
+ reasons.push(`adapter not executable: \`${rel}\` (chmod +x)`);
1891
+ }
1892
+ }
1893
+ const cli = await resolveAgentKitCli(root);
1894
+ if (!cli) {
1895
+ reasons.push(
1896
+ "agent-kit CLI not resolvable (PATH, node_modules/.bin/agent-kit, or packages/cli/dist)"
1897
+ );
1833
1898
  }
1834
1899
  if (Array.isArray(hooks.stop) && hooks.stop.length > 0) {
1835
1900
  reasons.push("`stop` hook is registered (forbidden; remove it)");
@@ -2240,10 +2305,10 @@ async function detectSafety(rootDir, trackedFiles) {
2240
2305
  import path19 from "path";
2241
2306
 
2242
2307
  // src/scanner/detect-git.ts
2243
- import { execFile as execFile2 } from "child_process";
2308
+ import { execFile as execFile3 } from "child_process";
2244
2309
  import path14 from "path";
2245
- import { promisify as promisify2 } from "util";
2246
- var exec = promisify2(execFile2);
2310
+ import { promisify as promisify3 } from "util";
2311
+ var exec = promisify3(execFile3);
2247
2312
  function remoteHostname(remoteUrl) {
2248
2313
  const scpMatch = remoteUrl.match(/^[^@]+@([^:]+):/);
2249
2314
  if (scpMatch?.[1]) return scpMatch[1].toLowerCase();
@@ -3034,6 +3099,8 @@ var doctorCommand = defineCommand6({
3034
3099
  });
3035
3100
 
3036
3101
  // src/commands/guard.ts
3102
+ import { execFile as execFile4 } from "child_process";
3103
+ import { promisify as promisify4 } from "util";
3037
3104
  import { defineCommand as defineCommand7 } from "citty";
3038
3105
 
3039
3106
  // src/hooks/read-stdin-json.ts
@@ -3075,10 +3142,21 @@ var SECRET_PATTERNS2 = [
3075
3142
  re: /\bsk-[A-Za-z0-9]{20,}\b/
3076
3143
  }
3077
3144
  ];
3145
+ function maskSecretExcerpt(raw) {
3146
+ return raw.replace(/\b(ghp_|sk-|AKIA)([A-Za-z0-9_]{4,})/g, (_m, p1, p2) => {
3147
+ return `${p1}${"*".repeat(Math.min(8, p2.length))}`;
3148
+ }).replace(
3149
+ /(=\s*['"]?)([^\s'"]{4,})/g,
3150
+ (_m, p1, p2) => `${p1}${"*".repeat(Math.min(8, p2.length))}`
3151
+ ).replace(
3152
+ /("(?:password|apiKey|api_key|secret|token|auth)"\s*:\s*")([^"]{4,})(")/gi,
3153
+ (_m, p1, p2, p3) => `${p1}${"*".repeat(Math.min(8, p2.length))}${p3}`
3154
+ );
3155
+ }
3078
3156
  function excerptAround(text, index, len) {
3079
3157
  const start = Math.max(0, index - 8);
3080
3158
  const end = Math.min(text.length, index + len + 8);
3081
- return text.slice(start, end).replace(/\s+/g, " ");
3159
+ return maskSecretExcerpt(text.slice(start, end).replace(/\s+/g, " "));
3082
3160
  }
3083
3161
  function scanTextForSecrets(text) {
3084
3162
  if (!text) return [];
@@ -3105,6 +3183,7 @@ function secretsAdviseMessage(hits) {
3105
3183
 
3106
3184
  // src/invariants/shell-guard.ts
3107
3185
  var CITE2 = "agent-kit guard shell (ADR 2026-07-29_cli-invariants-thin-hook-adapters)";
3186
+ var PROTECTED_BRANCH_RE = /^(?:main|master|prod)$/;
3108
3187
  function normalizeShellCommand(command) {
3109
3188
  return command.replace(/\s+/g, " ").trim();
3110
3189
  }
@@ -3116,11 +3195,61 @@ function shellInvocationHeads(command) {
3116
3195
  function anyHeadMatches(command, re) {
3117
3196
  return shellInvocationHeads(command).some((head) => re.test(head));
3118
3197
  }
3198
+ function isProtectedBranch(name) {
3199
+ return typeof name === "string" && PROTECTED_BRANCH_RE.test(name.trim());
3200
+ }
3201
+ function normalizePushRefspecToken(token) {
3202
+ let t = token.trim();
3203
+ if (t.startsWith("'") && t.endsWith("'") && t.length >= 2 || t.startsWith('"') && t.endsWith('"') && t.length >= 2) {
3204
+ t = t.slice(1, -1).trim();
3205
+ }
3206
+ if (t.startsWith("+")) t = t.slice(1);
3207
+ if (t.startsWith("refs/heads/")) t = t.slice("refs/heads/".length);
3208
+ if (t.startsWith("origin/")) t = t.slice("origin/".length);
3209
+ return t;
3210
+ }
3211
+ function pushHeadHasProtectedDest(head) {
3212
+ if (/HEAD:(?:refs\/heads\/)?(?:main|master|prod)\b/.test(head)) return true;
3213
+ if (/(?:^|\s)-(?:u|--set-upstream)\s+\S+\s+(?:main|master|prod)(?:\s|$)/.test(head)) {
3214
+ return true;
3215
+ }
3216
+ const after = head.replace(/^(?:[\w./-]+\/)?git\s+push\b/, "");
3217
+ for (const raw of after.split(/\s+/).filter(Boolean)) {
3218
+ if (raw.startsWith("-")) continue;
3219
+ const dest = raw.includes(":") ? raw.slice(raw.lastIndexOf(":") + 1) : raw;
3220
+ if (PROTECTED_BRANCH_RE.test(normalizePushRefspecToken(dest))) return true;
3221
+ }
3222
+ return false;
3223
+ }
3224
+ function isBareOrHeadPushToCurrent(head) {
3225
+ if (!/^(?:[\w./-]+\/)?git\s+push\b/.test(head)) return false;
3226
+ if (pushHeadHasProtectedDest(head)) {
3227
+ return false;
3228
+ }
3229
+ if (/(?:^|\s)\+?(?:refs\/heads\/)?(?:origin\/)?(?:staging|develop|homologacao)(?:\s|$|:)/.test(
3230
+ head
3231
+ ) || /HEAD:(?:refs\/heads\/)?(?!main|master|prod)[A-Za-z0-9._/-]+/.test(head)) {
3232
+ return false;
3233
+ }
3234
+ const after = head.replace(/^(?:[\w./-]+\/)?git\s+push\b/, "").trim();
3235
+ 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();
3236
+ if (!withoutFlags) return true;
3237
+ const tokens = withoutFlags.split(/\s+/);
3238
+ if (tokens.length === 1) return true;
3239
+ if (tokens.length >= 2 && tokens[1] === "HEAD") return true;
3240
+ if (/\bHEAD\b/.test(withoutFlags) && !/HEAD:/.test(withoutFlags)) return true;
3241
+ return false;
3242
+ }
3119
3243
  var SHELL_DENY_RULES = [
3120
3244
  {
3121
3245
  id: "git-checkout-path",
3122
- description: "git checkout -- <paths> discards working-tree edits",
3123
- test: (cmd) => anyHeadMatches(cmd, /^(?:[\w./-]+\/)?git\s+checkout\s+--(?:\s|$)/)
3246
+ description: "git checkout -- / HEAD -- / . discards working-tree edits",
3247
+ test: (cmd) => shellInvocationHeads(cmd).some((head) => {
3248
+ if (!/^(?:[\w./-]+\/)?git\s+checkout\b/.test(head)) return false;
3249
+ if (/\s--(?:\s|$)/.test(head)) return true;
3250
+ if (/\scheckout\s+\.(?:\s|$)/.test(head)) return true;
3251
+ return false;
3252
+ })
3124
3253
  },
3125
3254
  {
3126
3255
  id: "git-restore",
@@ -3142,19 +3271,25 @@ var SHELL_DENY_RULES = [
3142
3271
  {
3143
3272
  id: "git-push-main",
3144
3273
  description: "direct push to main/master/prod bypasses staging",
3145
- test: (cmd) => shellInvocationHeads(cmd).some((head) => {
3274
+ test: (cmd, opts) => shellInvocationHeads(cmd).some((head) => {
3146
3275
  if (!/^(?:[\w./-]+\/)?git\s+push\b/.test(head)) return false;
3147
- return /(?:^|\s)(?:origin\/)?(?:main|master|prod)(?:\s|$|:)/.test(head) || /HEAD:(?:refs\/heads\/)?(?:main|master|prod)\b/.test(head) || /(?:^|\s)-(?:u|--set-upstream)\s+\S+\s+(?:main|master|prod)(?:\s|$)/.test(head);
3276
+ if (pushHeadHasProtectedDest(head)) {
3277
+ return true;
3278
+ }
3279
+ if (isProtectedBranch(opts?.currentBranch) && isBareOrHeadPushToCurrent(head)) {
3280
+ return true;
3281
+ }
3282
+ return false;
3148
3283
  })
3149
3284
  }
3150
3285
  ];
3151
- function evaluateShellCommand(command) {
3286
+ function evaluateShellCommand(command, opts = {}) {
3152
3287
  const normalized = normalizeShellCommand(command);
3153
3288
  if (!normalized) {
3154
3289
  return { permission: "allow" };
3155
3290
  }
3156
3291
  for (const rule of SHELL_DENY_RULES) {
3157
- if (rule.test(normalized)) {
3292
+ if (rule.test(normalized, opts)) {
3158
3293
  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.`;
3159
3294
  return {
3160
3295
  permission: "deny",
@@ -3168,6 +3303,18 @@ function evaluateShellCommand(command) {
3168
3303
  }
3169
3304
 
3170
3305
  // src/commands/guard.ts
3306
+ var execFileAsync3 = promisify4(execFile4);
3307
+ async function detectCurrentBranch() {
3308
+ try {
3309
+ const { stdout } = await execFileAsync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
3310
+ encoding: "utf8"
3311
+ });
3312
+ const branch = stdout.trim();
3313
+ return branch && branch !== "HEAD" ? branch : void 0;
3314
+ } catch {
3315
+ return void 0;
3316
+ }
3317
+ }
3171
3318
  var guardCommand = defineCommand7({
3172
3319
  meta: {
3173
3320
  name: "guard",
@@ -3196,7 +3343,8 @@ var guardCommand = defineCommand7({
3196
3343
  const payload = await readStdinJson();
3197
3344
  command = typeof payload.command === "string" ? payload.command : "";
3198
3345
  }
3199
- const result = evaluateShellCommand(command);
3346
+ const currentBranch = await detectCurrentBranch();
3347
+ const result = evaluateShellCommand(command, { currentBranch });
3200
3348
  console.log(JSON.stringify(result));
3201
3349
  }
3202
3350
  }),
@@ -3224,7 +3372,7 @@ var guardCommand = defineCommand7({
3224
3372
  continue: true,
3225
3373
  user_message: secretsAdviseMessage(hits),
3226
3374
  agent_message: secretsAdviseMessage(hits),
3227
- hits
3375
+ hits: hits.map((h) => ({ patternId: h.patternId }))
3228
3376
  })
3229
3377
  );
3230
3378
  }
@@ -4299,12 +4447,16 @@ import path30 from "path";
4299
4447
  import { defineCommand as defineCommand12 } from "citty";
4300
4448
 
4301
4449
  // src/invariants/monitors-untriaged.ts
4302
- import { execFile as execFile3 } from "child_process";
4303
- import { readFile as readFile14, readdir as readdir3, stat } from "fs/promises";
4450
+ import { execFile as execFile5 } from "child_process";
4451
+ import { readFile as readFile14, readdir as readdir3, stat as stat2 } from "fs/promises";
4304
4452
  import path29 from "path";
4305
- import { promisify as promisify3 } from "util";
4306
- var execFileAsync2 = promisify3(execFile3);
4307
- var TRIAGE_HEADING_RE = /^#{2,6}\s+.*\b(triage|follow-?up plan|residuals plan)\b/im;
4453
+ import { promisify as promisify5 } from "util";
4454
+
4455
+ // src/invariants/triage-heading.ts
4456
+ var TRIAGE_HEADING_RE = /^#{2,6}\s+(?:Triage note|Follow-?up plan|Residuals plan)\b/im;
4457
+
4458
+ // src/invariants/monitors-untriaged.ts
4459
+ var execFileAsync4 = promisify5(execFile5);
4308
4460
  var CITE4 = "agent-kit monitors --untriaged (ADR 2026-07-27_plan-review-triage-untriaged-not-mtime; never newest-mtime-wins)";
4309
4461
  function hasOpenGaps(content) {
4310
4462
  if (/###\s+Still open[^\n]*\n+(?:\s*\n)*(?:None\.|none\.|\*None\*)/i.test(content)) {
@@ -4330,7 +4482,7 @@ async function listMonitorFiles(memoryDir) {
4330
4482
  async function gitFreshMonitorNames(rootDir) {
4331
4483
  const names = /* @__PURE__ */ new Set();
4332
4484
  try {
4333
- const { stdout } = await execFileAsync2(
4485
+ const { stdout } = await execFileAsync4(
4334
4486
  "git",
4335
4487
  ["status", "--porcelain", "--", ".cursor/memory"],
4336
4488
  { cwd: rootDir, maxBuffer: 2 * 1024 * 1024 }
@@ -4369,7 +4521,7 @@ async function selectUntriagedMonitors(rootDir) {
4369
4521
  for (const name of allNames) {
4370
4522
  const abs = path29.join(memoryDir, name);
4371
4523
  try {
4372
- const [content, st] = await Promise.all([readFile14(abs, "utf8"), stat(abs)]);
4524
+ const [content, st] = await Promise.all([readFile14(abs, "utf8"), stat2(abs)]);
4373
4525
  byName.set(name, { content, mtimeMs: st.mtimeMs });
4374
4526
  } catch {
4375
4527
  }
@@ -5176,10 +5328,10 @@ var statusCommand = defineCommand15({
5176
5328
  import { defineCommand as defineCommand16 } from "citty";
5177
5329
 
5178
5330
  // src/lifecycle/check-updates.ts
5179
- import { execFile as execFile4 } from "child_process";
5331
+ import { execFile as execFile6 } from "child_process";
5180
5332
  import path37 from "path";
5181
- import { promisify as promisify4 } from "util";
5182
- var execFileAsync3 = promisify4(execFile4);
5333
+ import { promisify as promisify6 } from "util";
5334
+ var execFileAsync5 = promisify6(execFile6);
5183
5335
  var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/i;
5184
5336
  var FACTORY_URL_MARKERS = ["agent-kit-dev"];
5185
5337
  var FACTORY_REFS = /* @__PURE__ */ new Set(["staging", "homologacao", "develop", "dev"]);
@@ -5238,7 +5390,7 @@ function pickLatestSemverTag(lsRemoteStdout) {
5238
5390
  }
5239
5391
  async function fetchLatestPublicVersion(registryUrl = DEFAULT_REGISTRY_URL) {
5240
5392
  assertSafeRegistrySource(registryUrl, "main");
5241
- const { stdout } = await execFileAsync3("git", ["ls-remote", "--tags", "--", registryUrl], {
5393
+ const { stdout } = await execFileAsync5("git", ["ls-remote", "--tags", "--", registryUrl], {
5242
5394
  env: gitEnv2(),
5243
5395
  timeout: 2e4
5244
5396
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dadado/agent-kit-cli",
3
- "version": "4.8.2",
3
+ "version": "4.8.4",
4
4
  "description": "Agent Kit CLI: HITL framework install and tooling for AI-assisted IDEs (rules, skills, plan/handoff, context).",
5
5
  "type": "module",
6
6
  "bin": {