@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/dashboard/dashboard.html +459 -77
- package/dashboard/lib/guards.mjs +19 -1
- package/dashboard/lib/semantic-model.mjs +38 -23
- package/dashboard/lib/triage-heading.mjs +20 -0
- package/dashboard/start-broadcast.mjs +23 -23
- package/dashboard/start.mjs +22 -28
- package/dist/index.js +176 -24
- package/package.json +1 -1
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 {
|
|
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
|
|
2308
|
+
import { execFile as execFile3 } from "child_process";
|
|
2244
2309
|
import path14 from "path";
|
|
2245
|
-
import { promisify as
|
|
2246
|
-
var exec =
|
|
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 --
|
|
3123
|
-
test: (cmd) =>
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
4306
|
-
|
|
4307
|
-
|
|
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
|
|
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"),
|
|
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
|
|
5331
|
+
import { execFile as execFile6 } from "child_process";
|
|
5180
5332
|
import path37 from "path";
|
|
5181
|
-
import { promisify as
|
|
5182
|
-
var
|
|
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
|
|
5393
|
+
const { stdout } = await execFileAsync5("git", ["ls-remote", "--tags", "--", registryUrl], {
|
|
5242
5394
|
env: gitEnv2(),
|
|
5243
5395
|
timeout: 2e4
|
|
5244
5396
|
});
|
package/package.json
CHANGED