finitemonkey 0.1.0-next.13 → 0.1.0-next.15
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/finitemonkey.mjs +633 -265
- package/package.json +2 -1
package/dist/finitemonkey.mjs
CHANGED
|
@@ -1620,8 +1620,8 @@ var shannonEntropy = (text6) => {
|
|
|
1620
1620
|
const counts = /* @__PURE__ */ new Map();
|
|
1621
1621
|
for (const char of text6) counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
1622
1622
|
let entropy = 0;
|
|
1623
|
-
for (const
|
|
1624
|
-
const p =
|
|
1623
|
+
for (const count4 of counts.values()) {
|
|
1624
|
+
const p = count4 / text6.length;
|
|
1625
1625
|
entropy -= p * Math.log2(p);
|
|
1626
1626
|
}
|
|
1627
1627
|
return entropy;
|
|
@@ -1670,6 +1670,35 @@ var defineRule = (spec2) => ({
|
|
|
1670
1670
|
skipTestPaths: spec2.skipTestPaths ?? false,
|
|
1671
1671
|
placeholders: spec2.placeholders ?? true
|
|
1672
1672
|
});
|
|
1673
|
+
var isWarning = (finding3) => finding3.severity === "warning";
|
|
1674
|
+
var blocking = (findings) => findings.filter((finding3) => !isWarning(finding3));
|
|
1675
|
+
|
|
1676
|
+
// src/secrets/rules/exposure.ts
|
|
1677
|
+
var ARGUMENT_CONTEXT = String.raw`(?:^|\n)[ \t-]*(?:command|entrypoint|CMD|ENTRYPOINT|RUN|ExecStart(?:Pre|Post)?|ExecReload)\b[^\n]*?`;
|
|
1678
|
+
var SECRET_FLAG = String.raw`(?<=[\s"'\[,])--?[A-Za-z0-9_-]*(?:password|passwd|pass|secret|token|requirepass|api-?key)`;
|
|
1679
|
+
var EXPOSURE_RULES = [
|
|
1680
|
+
defineRule({
|
|
1681
|
+
id: "secret-in-process-args",
|
|
1682
|
+
summary: "Literal password, token or key passed as a command-line argument in compose, a Dockerfile or a systemd unit (visible in ps and docker inspect)",
|
|
1683
|
+
kind: "practice",
|
|
1684
|
+
severity: "warning",
|
|
1685
|
+
keywords: ["command", "entrypoint", "cmd", "run", "execstart", "execreload"],
|
|
1686
|
+
source: String.raw`${ARGUMENT_CONTEXT}${SECRET_FLAG}(?:=["']?|["'][ \t]*,[ \t]*["']|[ \t]+["']?)(?<secret>[^\s"',\]$\x60/][^\s"',\]\x60]{3,127})`,
|
|
1687
|
+
flags: "i",
|
|
1688
|
+
minLength: 4
|
|
1689
|
+
}),
|
|
1690
|
+
defineRule({
|
|
1691
|
+
id: "tls-disabled",
|
|
1692
|
+
summary: "TLS verification turned off in code: sslmode set to disable, rejectUnauthorized or InsecureSkipVerify set to skip checks, Python verify set to False, NODE_TLS_REJECT_UNAUTHORIZED set to 0",
|
|
1693
|
+
kind: "practice",
|
|
1694
|
+
severity: "warning",
|
|
1695
|
+
keywords: ["sslmode", "rejectunauthorized", "verify", "insecureskipverify", "node_tls_reject_unauthorized"],
|
|
1696
|
+
source: String.raw`(?<secret>\bsslmode[ \t]*=[ \t]*["']?disable\b|\brejectUnauthorized["']?[ \t]*:[ \t]*false\b|\bverify[ \t]*=[ \t]*False\b|\bInsecureSkipVerify[ \t]*:[ \t]*true\b|\bNODE_TLS_REJECT_UNAUTHORIZED["']?[ \t]*(?:=|:)[ \t]*["']?0\b)`,
|
|
1697
|
+
minLength: 1,
|
|
1698
|
+
placeholders: false,
|
|
1699
|
+
skipTestPaths: true
|
|
1700
|
+
})
|
|
1701
|
+
];
|
|
1673
1702
|
|
|
1674
1703
|
// src/secrets/rules/providers.ts
|
|
1675
1704
|
var PROVIDER_RULES = [
|
|
@@ -1893,7 +1922,7 @@ var STRUCTURAL_RULES = [
|
|
|
1893
1922
|
];
|
|
1894
1923
|
|
|
1895
1924
|
// src/secrets/rules/catalog.ts
|
|
1896
|
-
var RULES = [...PROVIDER_RULES, ...STRUCTURAL_RULES];
|
|
1925
|
+
var RULES = [...PROVIDER_RULES, ...STRUCTURAL_RULES, ...EXPOSURE_RULES];
|
|
1897
1926
|
var CREDENTIAL_RULES = RULES.filter((rule) => rule.kind === "credential");
|
|
1898
1927
|
var ruleById = (id) => RULES.find((rule) => rule.id === id);
|
|
1899
1928
|
|
|
@@ -2227,8 +2256,28 @@ var reportMarkdown = (env, ctx, envelope) => {
|
|
|
2227
2256
|
};
|
|
2228
2257
|
var reportPath = (env) => toAbsolutePath(join5(env.root, ...REPORTS_DIR.split("/"), `doctor-${stamp(env.runtime.now())}.md`));
|
|
2229
2258
|
|
|
2230
|
-
// src/
|
|
2259
|
+
// src/install/plugin-yield.ts
|
|
2231
2260
|
import { join as join6 } from "node:path";
|
|
2261
|
+
var PLUGIN_VIA = "plugin";
|
|
2262
|
+
var YIELD_STDOUT = `${JSON.stringify({ continue: true })}
|
|
2263
|
+
`;
|
|
2264
|
+
var projectOwnsHooks = (ctx) => {
|
|
2265
|
+
const root = ctx.env["CLAUDE_PROJECT_DIR"] ?? ctx.cwd;
|
|
2266
|
+
const text6 = ctx.runtime.readText(toAbsolutePath(join6(root, ...SETTINGS_FILE.split("/"))));
|
|
2267
|
+
if (!text6.ok) return false;
|
|
2268
|
+
const parsed = parseJsonObject(text6.value);
|
|
2269
|
+
return parsed.ok && ownHandlersIn(parsed.value) > 0;
|
|
2270
|
+
};
|
|
2271
|
+
|
|
2272
|
+
// src/install/plugin-hooks.ts
|
|
2273
|
+
var PLUGIN_NAME = "finitemonkey";
|
|
2274
|
+
var MARKETPLACE_NAME = "finitemonkey";
|
|
2275
|
+
var NEXT_MARKETPLACE_NAME = "finitemonkey-next";
|
|
2276
|
+
var STABLE_PLUGIN_ID = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
2277
|
+
var NEXT_PLUGIN_ID = `${PLUGIN_NAME}@${NEXT_MARKETPLACE_NAME}`;
|
|
2278
|
+
|
|
2279
|
+
// src/doctor/settings.ts
|
|
2280
|
+
import { join as join7 } from "node:path";
|
|
2232
2281
|
var MANAGED = {
|
|
2233
2282
|
darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
|
|
2234
2283
|
linux: "/etc/claude-code/managed-settings.json",
|
|
@@ -2237,7 +2286,7 @@ var MANAGED = {
|
|
|
2237
2286
|
var userSettingsPath = ({ env, runtime }) => {
|
|
2238
2287
|
const configDir = env["CLAUDE_CONFIG_DIR"];
|
|
2239
2288
|
return toAbsolutePath(
|
|
2240
|
-
configDir === void 0 ?
|
|
2289
|
+
configDir === void 0 ? join7(runtime.homeDir(), ".claude", "settings.json") : join7(configDir, "settings.json")
|
|
2241
2290
|
);
|
|
2242
2291
|
};
|
|
2243
2292
|
var settingsPaths = (env) => {
|
|
@@ -2245,8 +2294,8 @@ var settingsPaths = (env) => {
|
|
|
2245
2294
|
return [
|
|
2246
2295
|
...managed === void 0 ? [] : [{ scope: "managed", path: toAbsolutePath(managed) }],
|
|
2247
2296
|
{ scope: "user", path: userSettingsPath(env) },
|
|
2248
|
-
{ scope: "project", path: toAbsolutePath(
|
|
2249
|
-
{ scope: "local", path: toAbsolutePath(
|
|
2297
|
+
{ scope: "project", path: toAbsolutePath(join7(env.root, ".claude", "settings.json")) },
|
|
2298
|
+
{ scope: "local", path: toAbsolutePath(join7(env.root, ".claude", "settings.local.json")) }
|
|
2250
2299
|
];
|
|
2251
2300
|
};
|
|
2252
2301
|
var readSettings = (env) => settingsPaths(env).flatMap(({ scope: scope2, path }) => {
|
|
@@ -2358,6 +2407,21 @@ var cleanupPeriod = (env, all) => {
|
|
|
2358
2407
|
)
|
|
2359
2408
|
];
|
|
2360
2409
|
};
|
|
2410
|
+
var PRECEDENCE = ["user", "project", "local", "managed"];
|
|
2411
|
+
var pluginOn = (all, id) => {
|
|
2412
|
+
const ordered = PRECEDENCE.flatMap((scope2) => all.filter((item) => item.scope === scope2));
|
|
2413
|
+
const values = ordered.map(({ settings }) => settings["enabledPlugins"]).map((plugins) => isRecord(plugins) ? plugins[id] : void 0).filter((value) => typeof value === "boolean");
|
|
2414
|
+
return values.at(-1) === true;
|
|
2415
|
+
};
|
|
2416
|
+
var pluginChannels = (all) => pluginOn(all, STABLE_PLUGIN_ID) && pluginOn(all, NEXT_PLUGIN_ID) ? [
|
|
2417
|
+
finding(
|
|
2418
|
+
"settings.plugin_channels",
|
|
2419
|
+
"fail",
|
|
2420
|
+
"doctor.settings.plugin_channels",
|
|
2421
|
+
{ stable: STABLE_PLUGIN_ID, next: NEXT_PLUGIN_ID },
|
|
2422
|
+
"doctor.settings.fix_plugin_channels"
|
|
2423
|
+
)
|
|
2424
|
+
] : [];
|
|
2361
2425
|
var checkClaudeSettings = (env) => {
|
|
2362
2426
|
const all = readSettings(env);
|
|
2363
2427
|
const plugins = enabledPlugins(all);
|
|
@@ -2367,6 +2431,7 @@ var checkClaudeSettings = (env) => {
|
|
|
2367
2431
|
all,
|
|
2368
2432
|
plugins.some((id) => id.startsWith("finitemonkey@"))
|
|
2369
2433
|
),
|
|
2434
|
+
...pluginChannels(all),
|
|
2370
2435
|
...agentTeams(env, all),
|
|
2371
2436
|
...loopPlugins(plugins),
|
|
2372
2437
|
...cleanupPeriod(env, all)
|
|
@@ -2374,7 +2439,7 @@ var checkClaudeSettings = (env) => {
|
|
|
2374
2439
|
};
|
|
2375
2440
|
|
|
2376
2441
|
// src/doctor/environment.ts
|
|
2377
|
-
import { join as
|
|
2442
|
+
import { join as join8 } from "node:path";
|
|
2378
2443
|
var AUTH_VARIABLES = [
|
|
2379
2444
|
"ANTHROPIC_API_KEY",
|
|
2380
2445
|
"ANTHROPIC_AUTH_TOKEN",
|
|
@@ -2412,7 +2477,7 @@ var pathDirs = ({ env, platform }) => {
|
|
|
2412
2477
|
var copiesOnPath = (env) => {
|
|
2413
2478
|
const names = EXECUTABLES[env.platform] ?? ["finitemonkey"];
|
|
2414
2479
|
const found = pathDirs(env).flatMap(
|
|
2415
|
-
(dir) => names.map((name) => toAbsolutePath(
|
|
2480
|
+
(dir) => names.map((name) => toAbsolutePath(join8(dir, name))).filter((path) => env.runtime.stat(path)?.kind === "file").map((path) => env.runtime.realpath(path))
|
|
2416
2481
|
);
|
|
2417
2482
|
const byDir = new Map(found.map((path) => [path.replace(/\.(cmd|exe|ps1)$/i, ""), path]));
|
|
2418
2483
|
return [...byDir.values()];
|
|
@@ -2446,7 +2511,7 @@ var checkAuthVariables = ({ env, platform }) => {
|
|
|
2446
2511
|
};
|
|
2447
2512
|
|
|
2448
2513
|
// src/doctor/hooks.ts
|
|
2449
|
-
import { join as
|
|
2514
|
+
import { join as join29 } from "node:path";
|
|
2450
2515
|
|
|
2451
2516
|
// src/hooks/events.ts
|
|
2452
2517
|
var HOOK_EVENTS = [
|
|
@@ -3126,9 +3191,9 @@ var skipOptions = (args, wrapper) => {
|
|
|
3126
3191
|
}
|
|
3127
3192
|
return index + wrapper.positionals;
|
|
3128
3193
|
};
|
|
3129
|
-
var shift = (stage,
|
|
3194
|
+
var shift = (stage, count4, name) => ({
|
|
3130
3195
|
...stage,
|
|
3131
|
-
argv: stage.argv.slice(
|
|
3196
|
+
argv: stage.argv.slice(count4),
|
|
3132
3197
|
via: [...stage.via, name]
|
|
3133
3198
|
});
|
|
3134
3199
|
var envCommand = (stage) => {
|
|
@@ -3509,7 +3574,7 @@ var selfGuard = (input) => {
|
|
|
3509
3574
|
};
|
|
3510
3575
|
|
|
3511
3576
|
// src/guards/tests/hook.ts
|
|
3512
|
-
import { join as
|
|
3577
|
+
import { join as join9, relative as relative3 } from "node:path";
|
|
3513
3578
|
|
|
3514
3579
|
// src/guards/tests/patterns.ts
|
|
3515
3580
|
var JS_TEST = /(^|\/)(__tests__|__test__)\/|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)tests?\/.*\.[cm]?[jt]sx?$/;
|
|
@@ -3699,7 +3764,7 @@ var shellViolations = (input, root) => {
|
|
|
3699
3764
|
return parseShell(command).flatMap(
|
|
3700
3765
|
(simple) => writtenPaths(simple).flatMap((path) => {
|
|
3701
3766
|
const local = localPath(root, path);
|
|
3702
|
-
const exists = input.runtime.stat(toAbsolutePath(
|
|
3767
|
+
const exists = input.runtime.stat(toAbsolutePath(join9(root, local))) !== void 0;
|
|
3703
3768
|
if (!exists) return [];
|
|
3704
3769
|
if (isProtectedConfig(local)) return [{ kind: "config_changed", path: local }];
|
|
3705
3770
|
return isTestFile(local) ? [{ kind: "test_removed", path: local }] : [];
|
|
@@ -3719,17 +3784,17 @@ var testsGuard = (input) => {
|
|
|
3719
3784
|
};
|
|
3720
3785
|
|
|
3721
3786
|
// src/gate/hooks/config.ts
|
|
3722
|
-
import { join as
|
|
3787
|
+
import { join as join11 } from "node:path";
|
|
3723
3788
|
|
|
3724
3789
|
// src/gate/hooks/common.ts
|
|
3725
|
-
import { dirname as dirname5, join as
|
|
3790
|
+
import { dirname as dirname5, join as join10 } from "node:path";
|
|
3726
3791
|
var DEFAULT_GATE_BUDGET_MS = 3e5;
|
|
3727
3792
|
var sessionCwd = (input) => input.payload.cwd === void 0 ? input.cwd : toAbsolutePath(input.payload.cwd, input.cwd);
|
|
3728
3793
|
var MAX_DEPTH2 = 64;
|
|
3729
3794
|
var findRepoRoot = (runtime, start) => {
|
|
3730
3795
|
let current = start;
|
|
3731
3796
|
for (let depth = 0; depth < MAX_DEPTH2; depth += 1) {
|
|
3732
|
-
if (runtime.stat(toAbsolutePath(
|
|
3797
|
+
if (runtime.stat(toAbsolutePath(join10(current, ".git"))) !== void 0) return current;
|
|
3733
3798
|
const parent = toAbsolutePath(dirname5(current));
|
|
3734
3799
|
if (parent === current) return void 0;
|
|
3735
3800
|
current = parent;
|
|
@@ -3766,9 +3831,9 @@ var censusOf = (text6) => {
|
|
|
3766
3831
|
return { hooks: countIn(parsed.value["hooks"]), disabled: parsed.value["disableAllHooks"] === true };
|
|
3767
3832
|
};
|
|
3768
3833
|
var settingsFiles = (paths, env) => [
|
|
3769
|
-
toAbsolutePath(
|
|
3770
|
-
toAbsolutePath(
|
|
3771
|
-
...env["HOME"] === void 0 ? [] : [toAbsolutePath(
|
|
3834
|
+
toAbsolutePath(join11(paths.root, ".claude", "settings.json")),
|
|
3835
|
+
toAbsolutePath(join11(paths.root, ".claude", "settings.local.json")),
|
|
3836
|
+
...env["HOME"] === void 0 ? [] : [toAbsolutePath(join11(env["HOME"], ".claude", "settings.json"))]
|
|
3772
3837
|
];
|
|
3773
3838
|
var read2 = (runtime, path) => {
|
|
3774
3839
|
const text6 = runtime.readText(path);
|
|
@@ -3912,7 +3977,93 @@ var implicitContract = (files) => {
|
|
|
3912
3977
|
};
|
|
3913
3978
|
|
|
3914
3979
|
// src/gate/prepare.ts
|
|
3915
|
-
import { join as
|
|
3980
|
+
import { join as join14 } from "node:path";
|
|
3981
|
+
|
|
3982
|
+
// src/git/project-config.ts
|
|
3983
|
+
import { join as join12 } from "node:path";
|
|
3984
|
+
var CONFIG_FILE3 = ".finitemonkey/config.yml";
|
|
3985
|
+
var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
|
|
3986
|
+
var stripComment = (line2) => {
|
|
3987
|
+
let quote;
|
|
3988
|
+
for (let index = 0; index < line2.length; index += 1) {
|
|
3989
|
+
const char = line2.charAt(index);
|
|
3990
|
+
if (quote === void 0 && (char === '"' || char === "'")) quote = char;
|
|
3991
|
+
else if (char === quote) quote = void 0;
|
|
3992
|
+
else if (quote === void 0 && char === "#" && (index === 0 || /\s/.test(line2.charAt(index - 1)))) {
|
|
3993
|
+
return line2.slice(0, index);
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
return line2;
|
|
3997
|
+
};
|
|
3998
|
+
var flowList = (value) => {
|
|
3999
|
+
const trimmed = value.trim();
|
|
4000
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
|
|
4001
|
+
return trimmed.slice(1, -1).split(",").map(unquote).filter((item) => item !== "");
|
|
4002
|
+
};
|
|
4003
|
+
var keyValue = (body2) => {
|
|
4004
|
+
const match = /^("[^"]*"|'[^']*'|[^:]+?)\s*:(?:\s+(.*))?$/.exec(body2);
|
|
4005
|
+
if (match === null) return void 0;
|
|
4006
|
+
return { key: unquote(match[1] ?? ""), value: match[2] ?? "" };
|
|
4007
|
+
};
|
|
4008
|
+
var addItem = (frame, item) => {
|
|
4009
|
+
if (frame.key === void 0) return;
|
|
4010
|
+
const parent = frame.tree[frame.key];
|
|
4011
|
+
if (Array.isArray(parent)) parent.push(item);
|
|
4012
|
+
else frame.tree[frame.key] = [item];
|
|
4013
|
+
};
|
|
4014
|
+
var placeLine = (stack, indent, body2) => {
|
|
4015
|
+
while (stack.length > 1 && (stack[stack.length - 1]?.indent ?? 0) >= indent && !body2.startsWith("- ")) stack.pop();
|
|
4016
|
+
const top = stack[stack.length - 1];
|
|
4017
|
+
if (top === void 0) return;
|
|
4018
|
+
if (body2.startsWith("- ")) {
|
|
4019
|
+
addItem(top, unquote(body2.slice(2)));
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
const pair = keyValue(body2);
|
|
4023
|
+
if (pair === void 0) return;
|
|
4024
|
+
const parentTree = top.key === void 0 ? top.tree : ensureTree(top.tree, top.key);
|
|
4025
|
+
if (pair.value === "") {
|
|
4026
|
+
stack.push({ indent, tree: parentTree, key: pair.key });
|
|
4027
|
+
return;
|
|
4028
|
+
}
|
|
4029
|
+
parentTree[pair.key] = flowList(pair.value) ?? unquote(pair.value);
|
|
4030
|
+
};
|
|
4031
|
+
var ensureTree = (tree, key) => {
|
|
4032
|
+
const existing = tree[key];
|
|
4033
|
+
if (existing !== void 0 && typeof existing !== "string" && !Array.isArray(existing)) return existing;
|
|
4034
|
+
const fresh3 = {};
|
|
4035
|
+
tree[key] = fresh3;
|
|
4036
|
+
return fresh3;
|
|
4037
|
+
};
|
|
4038
|
+
var parseConfigYaml = (text6) => {
|
|
4039
|
+
const root = {};
|
|
4040
|
+
const stack = [{ indent: -1, tree: root, key: void 0 }];
|
|
4041
|
+
for (const raw of text6.split(/\r?\n/)) {
|
|
4042
|
+
const line2 = stripComment(raw).trimEnd();
|
|
4043
|
+
if (line2.trim() === "" || line2.trim() === "---") continue;
|
|
4044
|
+
const indent = line2.length - line2.trimStart().length;
|
|
4045
|
+
placeLine(stack, indent, line2.trim());
|
|
4046
|
+
}
|
|
4047
|
+
return root;
|
|
4048
|
+
};
|
|
4049
|
+
var isTree = (node) => typeof node === "object" && !Array.isArray(node);
|
|
4050
|
+
var configAt = (tree, ...path) => path.reduce((node, key) => isTree(node) ? node[key] : void 0, tree);
|
|
4051
|
+
var configList = (tree, ...path) => {
|
|
4052
|
+
const node = configAt(tree, ...path);
|
|
4053
|
+
if (typeof node === "string") return node === "" ? [] : [node];
|
|
4054
|
+
return Array.isArray(node) ? node.filter((item) => typeof item === "string") : [];
|
|
4055
|
+
};
|
|
4056
|
+
var configMap = (tree, ...path) => {
|
|
4057
|
+
const node = configAt(tree, ...path);
|
|
4058
|
+
if (!isTree(node)) return {};
|
|
4059
|
+
return Object.fromEntries(
|
|
4060
|
+
Object.entries(node).filter((entry2) => typeof entry2[1] === "string")
|
|
4061
|
+
);
|
|
4062
|
+
};
|
|
4063
|
+
var loadProjectConfig = (runtime, root) => {
|
|
4064
|
+
const text6 = runtime.readText(toAbsolutePath(join12(root, CONFIG_FILE3)));
|
|
4065
|
+
return text6.ok ? parseConfigYaml(text6.value) : {};
|
|
4066
|
+
};
|
|
3916
4067
|
|
|
3917
4068
|
// src/core/yaml/scalar.ts
|
|
3918
4069
|
var ESCAPES = {
|
|
@@ -4060,7 +4211,7 @@ var parseValue = (text6) => {
|
|
|
4060
4211
|
|
|
4061
4212
|
// src/core/yaml/parse.ts
|
|
4062
4213
|
var BOM = new RegExp(`^${String.fromCharCode(65279)}`);
|
|
4063
|
-
var
|
|
4214
|
+
var stripComment2 = (text6) => {
|
|
4064
4215
|
for (let index = 0; index < text6.length; index += 1) {
|
|
4065
4216
|
const char = text6[index];
|
|
4066
4217
|
if (char === '"' || char === "'") {
|
|
@@ -4075,7 +4226,7 @@ var toLines = (source) => {
|
|
|
4075
4226
|
const lines3 = [];
|
|
4076
4227
|
const raw = source.replace(BOM, "").split(/\r?\n/);
|
|
4077
4228
|
for (const [index, full] of raw.entries()) {
|
|
4078
|
-
const text6 =
|
|
4229
|
+
const text6 = stripComment2(full).trimEnd();
|
|
4079
4230
|
const body2 = text6.trimStart();
|
|
4080
4231
|
if (body2 === "" || body2 === "---" && lines3.length === 0) continue;
|
|
4081
4232
|
const indentText = text6.slice(0, text6.length - body2.length);
|
|
@@ -4183,7 +4334,8 @@ var CHECK_KINDS = [
|
|
|
4183
4334
|
"worktree",
|
|
4184
4335
|
"merge",
|
|
4185
4336
|
"prod",
|
|
4186
|
-
"prose"
|
|
4337
|
+
"prose",
|
|
4338
|
+
"review"
|
|
4187
4339
|
];
|
|
4188
4340
|
var GIT_REQUIREMENTS = ["committed", "pushed", "pr_open"];
|
|
4189
4341
|
var SIZES = ["S", "M", "L"];
|
|
@@ -4389,6 +4541,9 @@ var readBaseline = (runtime, paths) => {
|
|
|
4389
4541
|
};
|
|
4390
4542
|
var writeBaseline = (runtime, paths, treeHash2, checks) => writeJsonFile(runtime, paths.cache(BASELINE_FILE), { treeHash: treeHash2, checks: { ...checks } }).ok;
|
|
4391
4543
|
|
|
4544
|
+
// src/gate/diff-shape.ts
|
|
4545
|
+
import { join as join13 } from "node:path";
|
|
4546
|
+
|
|
4392
4547
|
// src/gate/git.ts
|
|
4393
4548
|
var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
4394
4549
|
var GIT_TIMEOUT_MS = 15e3;
|
|
@@ -4488,10 +4643,20 @@ var numstat = (git, base) => {
|
|
|
4488
4643
|
};
|
|
4489
4644
|
var dirtyPaths = (git) => nulFields(git.out(["status", "--porcelain", "-z", "--", ".", EXCLUDE_FM]) ?? "").map((entry2) => entry2.slice(3));
|
|
4490
4645
|
var aheadOfUpstream = (git) => {
|
|
4491
|
-
const
|
|
4492
|
-
return
|
|
4646
|
+
const count4 = git.out(["rev-list", "--count", "@{u}..HEAD"])?.trim();
|
|
4647
|
+
return count4 === void 0 ? void 0 : Number(count4);
|
|
4493
4648
|
};
|
|
4494
4649
|
|
|
4650
|
+
// src/gate/diff-shape.ts
|
|
4651
|
+
var untrackedLines = (runtime, root, changes) => changes.filter((change) => change.status === "A").reduce((sum, change) => {
|
|
4652
|
+
const text6 = runtime.readText(toAbsolutePath(join13(root, change.path)));
|
|
4653
|
+
return sum + (text6.ok ? text6.value.split("\n").length : 0);
|
|
4654
|
+
}, 0);
|
|
4655
|
+
var diffShape = (runtime, git, base, changes) => ({
|
|
4656
|
+
files: changes.map((change) => change.path),
|
|
4657
|
+
lines: numstat(git, base).lines + untrackedLines(runtime, git.cwd, changes)
|
|
4658
|
+
});
|
|
4659
|
+
|
|
4495
4660
|
// src/gate/pr-parse.ts
|
|
4496
4661
|
var FAILED = /* @__PURE__ */ new Set(["FAILURE", "CANCELLED", "TIMED_OUT", "ACTION_REQUIRED", "STARTUP_FAILURE", "ERROR"]);
|
|
4497
4662
|
var PENDING = /* @__PURE__ */ new Set(["IN_PROGRESS", "QUEUED", "PENDING", "WAITING", "REQUESTED", "EXPECTED"]);
|
|
@@ -4582,13 +4747,118 @@ var conflictFiles = (git, base) => {
|
|
|
4582
4747
|
return [];
|
|
4583
4748
|
};
|
|
4584
4749
|
|
|
4750
|
+
// src/core/glob.ts
|
|
4751
|
+
var escape = (char) => /[.+^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
|
|
4752
|
+
var globToRegExp = (glob) => {
|
|
4753
|
+
const pattern = glob.replace(/^\.\//, "");
|
|
4754
|
+
let out = "";
|
|
4755
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
4756
|
+
const char = pattern[index] ?? "";
|
|
4757
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
4758
|
+
const slash = pattern[index + 2] === "/";
|
|
4759
|
+
out += slash ? "(?:.*/)?" : ".*";
|
|
4760
|
+
index += slash ? 2 : 1;
|
|
4761
|
+
} else if (char === "*") out += "[^/]*";
|
|
4762
|
+
else if (char === "?") out += "[^/]";
|
|
4763
|
+
else out += escape(char);
|
|
4764
|
+
}
|
|
4765
|
+
return new RegExp(`^${out}${pattern.endsWith("/") ? ".*" : ""}$`);
|
|
4766
|
+
};
|
|
4767
|
+
var matchesAny = (path, globs) => {
|
|
4768
|
+
const normalized = toPosixPath(path);
|
|
4769
|
+
return globs.some((glob) => globToRegExp(glob).test(normalized));
|
|
4770
|
+
};
|
|
4771
|
+
|
|
4772
|
+
// src/gate/size.ts
|
|
4773
|
+
var DEFAULT_SENSITIVE = [
|
|
4774
|
+
"**/*payment*",
|
|
4775
|
+
"**/*payment*/**",
|
|
4776
|
+
"**/*billing*",
|
|
4777
|
+
"**/*billing*/**",
|
|
4778
|
+
"**/*checkout*/**",
|
|
4779
|
+
"**/auth/**",
|
|
4780
|
+
"**/*auth.*",
|
|
4781
|
+
"**/oauth*",
|
|
4782
|
+
"**/migrations/**",
|
|
4783
|
+
"**/migration/**",
|
|
4784
|
+
"**/*.sql",
|
|
4785
|
+
"**/openapi*.*",
|
|
4786
|
+
"**/swagger*.*",
|
|
4787
|
+
"**/*.proto",
|
|
4788
|
+
"**/*.graphql",
|
|
4789
|
+
"**/*stripe*",
|
|
4790
|
+
"**/*openai*",
|
|
4791
|
+
"**/*anthropic*"
|
|
4792
|
+
];
|
|
4793
|
+
var DEFAULT_SIZE_CONFIG = {
|
|
4794
|
+
m: { files: 5, lines: 150 },
|
|
4795
|
+
l: { files: 20, lines: 600 },
|
|
4796
|
+
sensitive: DEFAULT_SENSITIVE
|
|
4797
|
+
};
|
|
4798
|
+
var count2 = (tree, fallback, ...path) => {
|
|
4799
|
+
const value = configAt(tree, ...path);
|
|
4800
|
+
const parsed = typeof value === "string" ? Number(value) : Number.NaN;
|
|
4801
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
4802
|
+
};
|
|
4803
|
+
var threshold = (tree, key, fallback) => ({
|
|
4804
|
+
files: count2(tree, fallback.files, "size", key, "files"),
|
|
4805
|
+
lines: count2(tree, fallback.lines, "size", key, "lines")
|
|
4806
|
+
});
|
|
4807
|
+
var sizeConfig = (tree) => {
|
|
4808
|
+
const declared = configAt(tree, "size", "sensitive");
|
|
4809
|
+
return {
|
|
4810
|
+
m: threshold(tree, "m", DEFAULT_SIZE_CONFIG.m),
|
|
4811
|
+
l: threshold(tree, "l", DEFAULT_SIZE_CONFIG.l),
|
|
4812
|
+
sensitive: declared === void 0 ? DEFAULT_SENSITIVE : configList(tree, "size", "sensitive")
|
|
4813
|
+
};
|
|
4814
|
+
};
|
|
4815
|
+
var reaches = (shape, limit) => shape.files.length >= limit.files || shape.lines >= limit.lines;
|
|
4816
|
+
var rank = (size) => SIZES.indexOf(size);
|
|
4817
|
+
var larger = (a, b) => rank(a) >= rank(b) ? a : b;
|
|
4818
|
+
var computeSize = (shape, config) => {
|
|
4819
|
+
const sensitive = shape.files.filter((file) => matchesAny(file, config.sensitive));
|
|
4820
|
+
const byVolume = reaches(shape, config.l) ? "L" : reaches(shape, config.m) ? "M" : "S";
|
|
4821
|
+
const computed = sensitive.length > 0 ? larger(byVolume, "M") : byVolume;
|
|
4822
|
+
return { computed, files: shape.files.length, lines: shape.lines, sensitive };
|
|
4823
|
+
};
|
|
4824
|
+
var sizeVerdict = (declared, shape, config) => {
|
|
4825
|
+
const result = computeSize(shape, config);
|
|
4826
|
+
return { ...result, declared, effective: larger(declared, result.computed) };
|
|
4827
|
+
};
|
|
4828
|
+
var upgraded = (verdict) => rank(verdict.effective) > rank(verdict.declared);
|
|
4829
|
+
|
|
4830
|
+
// src/gate/size-contract.ts
|
|
4831
|
+
var REVIEW_CHECK = "size-review";
|
|
4832
|
+
var INVENTORY_CHECK = "size-inventory";
|
|
4833
|
+
var DECISION_CHECK = "size-decision";
|
|
4834
|
+
var INVENTORY = "inventory of the files, behaviours and callers the change touches";
|
|
4835
|
+
var DECISION = "a recorded decision on the approach for a change this large";
|
|
4836
|
+
var reviewChecks = (verdict) => verdict.effective === "S" ? [] : [{ id: REVIEW_CHECK, kind: "review" }];
|
|
4837
|
+
var largeChecks = (verdict) => verdict.effective === "L" && verdict.declared !== "S" ? [
|
|
4838
|
+
{ id: INVENTORY_CHECK, kind: "manual", description: INVENTORY },
|
|
4839
|
+
{ id: DECISION_CHECK, kind: "manual", description: DECISION }
|
|
4840
|
+
] : [];
|
|
4841
|
+
var sizeRequirements = (verdict) => [
|
|
4842
|
+
...reviewChecks(verdict),
|
|
4843
|
+
...largeChecks(verdict)
|
|
4844
|
+
];
|
|
4845
|
+
var applySize = (contract, verdict) => {
|
|
4846
|
+
if (contract.implicit) return contract;
|
|
4847
|
+
const declaredIds = new Set(contract.acceptance.map((check2) => check2.id));
|
|
4848
|
+
const declaredKinds = new Set(contract.acceptance.map((check2) => check2.kind));
|
|
4849
|
+
const extra = sizeRequirements(verdict).filter(
|
|
4850
|
+
(check2) => !declaredIds.has(check2.id) && !(check2.kind === "review" && declaredKinds.has("review"))
|
|
4851
|
+
);
|
|
4852
|
+
return extra.length === 0 && !upgraded(verdict) ? contract : { ...contract, size: verdict.effective, acceptance: [...contract.acceptance, ...extra] };
|
|
4853
|
+
};
|
|
4854
|
+
|
|
4585
4855
|
// src/gate/prepare.ts
|
|
4586
4856
|
var diskFiles = (runtime, root) => ({
|
|
4587
4857
|
read: (relative9) => {
|
|
4588
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
4858
|
+
const text6 = runtime.readText(toAbsolutePath(join14(root, relative9)));
|
|
4589
4859
|
return text6.ok ? text6.value : void 0;
|
|
4590
4860
|
},
|
|
4591
|
-
exists: (relative9) => runtime.stat(toAbsolutePath(
|
|
4861
|
+
exists: (relative9) => runtime.stat(toAbsolutePath(join14(root, relative9))) !== void 0
|
|
4592
4862
|
});
|
|
4593
4863
|
var baseFiles = (git, base, disk) => ({
|
|
4594
4864
|
read: (relative9) => base === EMPTY_TREE ? disk.read(relative9) : fileAt(git, base, relative9),
|
|
@@ -4620,6 +4890,7 @@ var skipReason = (runtime, paths, loaded, changes, tree, sessionId) => {
|
|
|
4620
4890
|
const session = readSession(runtime, paths);
|
|
4621
4891
|
return session !== void 0 && session.sessionId === sessionId && session.startTree === tree ? "no_session_edits" : void 0;
|
|
4622
4892
|
};
|
|
4893
|
+
var measureSize = (runtime, git, base, changes, contract) => sizeVerdict(contract.size, diffShape(runtime, git, base, changes), sizeConfig(loadProjectConfig(runtime, git.cwd)));
|
|
4623
4894
|
var prepareGate = (options) => {
|
|
4624
4895
|
const probe = gitAt(options.runtime, options.cwd);
|
|
4625
4896
|
const root = gitRoot(probe);
|
|
@@ -4639,11 +4910,13 @@ var prepareGate = (options) => {
|
|
|
4639
4910
|
const skip = options.noSkip === true ? void 0 : skipReason(options.runtime, paths, loaded.value, changes, tree, options.sessionId);
|
|
4640
4911
|
if (skip !== void 0) return { kind: "skipped", reason: skip };
|
|
4641
4912
|
const pr = memo(() => prStatus(options.runtime, git, paths));
|
|
4913
|
+
const size = measureSize(options.runtime, git, base, changes, loaded.value.contract);
|
|
4642
4914
|
const ctx = {
|
|
4643
4915
|
runtime: options.runtime,
|
|
4644
4916
|
git,
|
|
4645
4917
|
paths,
|
|
4646
|
-
contract: loaded.value.contract,
|
|
4918
|
+
contract: applySize(loaded.value.contract, size),
|
|
4919
|
+
size,
|
|
4647
4920
|
trusted: loaded.value.trusted,
|
|
4648
4921
|
treeHash: tree,
|
|
4649
4922
|
base,
|
|
@@ -5006,6 +5279,8 @@ var en_default = {
|
|
|
5006
5279
|
"doctor.settings.fix_managed_hooks_only": "Ask the administrator of the managed settings to add the Finite Monkey hooks there, or to turn allowManagedHooksOnly off",
|
|
5007
5280
|
"doctor.settings.plugin_only": "strictPluginOnlyCustomization is on ({scopes}) and the finitemonkey plugin is not enabled; the project hooks do not load",
|
|
5008
5281
|
"doctor.settings.fix_plugin_only": "Install the plugin: finitemonkey init --marketplace --yes, then /plugin install finitemonkey@finitemonkey in Claude Code",
|
|
5282
|
+
"doctor.settings.plugin_channels": "Both {stable} and {next} are enabled. They share the plugin name, so Claude Code keeps only one set of hooks",
|
|
5283
|
+
"doctor.settings.fix_plugin_channels": "To stay on stable run claude plugin disable {next}; to test next run claude plugin disable {stable}",
|
|
5009
5284
|
"doctor.settings.agent_teams": "{name} is on; teammates run in their own sessions, and Finite Monkey does not check their work before they report done",
|
|
5010
5285
|
"doctor.settings.fix_agent_teams": "Run `finitemonkey gate` before accepting a teammate's result, or unset the variable",
|
|
5011
5286
|
"doctor.settings.loop_plugins": "{names} enabled: it sends the agent back to work at Stop, and its loop has no guard of its own",
|
|
@@ -5420,7 +5695,18 @@ var en_default = {
|
|
|
5420
5695
|
"history.pattern.no_verify": "{count, plural, one {# git command} other {# git commands}} with --no-verify",
|
|
5421
5696
|
"history.pattern.force_push": "{count, plural, one {# force push} other {# force pushes}}",
|
|
5422
5697
|
"history.pattern.test_skip": "{count, plural, one {# test edit} other {# test edits}} that added .skip, .only or a skip marker",
|
|
5423
|
-
"history.estimate": "These are estimates from pattern matching, not evidence; nothing was saved and nothing left this machine."
|
|
5698
|
+
"history.estimate": "These are estimates from pattern matching, not evidence; nothing was saved and nothing left this machine.",
|
|
5699
|
+
"gate.require.review": "a reviewer subagent read the change at its current state",
|
|
5700
|
+
"gate.size.declared": "size {effective}: {files, plural, one {# file} other {# files}}, {lines, plural, one {# line} other {# lines}}",
|
|
5701
|
+
"gate.size.grew": "declared {declared}, the diff makes it {effective}: {files, plural, one {# file} other {# files}}, {lines, plural, one {# line} other {# lines}}",
|
|
5702
|
+
"gate.size.sensitive": "sensitive path touched: {file}",
|
|
5703
|
+
"gate.review.missing": "no review of the current state: run a reviewer subagent on the diff, or the person writes `fm: approve {check} <reason>` in the chat",
|
|
5704
|
+
"gate.review.done": "reviewed by {agent} at {at}",
|
|
5705
|
+
"secrets.warn.write": "Finite Monkey warning (the write went through): {found}. A secret passed as a command-line argument shows in ps and docker inspect; disabled TLS verification lets anyone on the path read the traffic. Move the value to an environment variable or a secret file, and keep TLS checks on outside tests.",
|
|
5706
|
+
"gate.secrets.warning": "warning: {rule} at {file}:{line}",
|
|
5707
|
+
"doctor.secrets.remote_credential": "git remote {remotes} has a credential in its URL, stored in plain text in .git/config",
|
|
5708
|
+
"doctor.secrets.fix_remote_credential": "run `git remote set-url {remote} <url without user:token@>`, use a credential helper or SSH, and rotate the token",
|
|
5709
|
+
"secrets.session.remote_credential": "git remote {remotes} has a credential in its URL (plain text in .git/config). Ask the person to run `git remote set-url {remote} <url without user:token@>` and rotate the token; do not print the URL."
|
|
5424
5710
|
};
|
|
5425
5711
|
|
|
5426
5712
|
// src/i18n/es.json
|
|
@@ -5517,6 +5803,8 @@ var es_default = {
|
|
|
5517
5803
|
"doctor.settings.fix_managed_hooks_only": "Pide al administrador de los settings gestionados que añada allí los hooks de Finite Monkey, o que desactive allowManagedHooksOnly",
|
|
5518
5804
|
"doctor.settings.plugin_only": "strictPluginOnlyCustomization está activado ({scopes}) y el plugin finitemonkey no está activo; los hooks del proyecto no se cargan",
|
|
5519
5805
|
"doctor.settings.fix_plugin_only": "Instala el plugin: finitemonkey init --marketplace --yes, después /plugin install finitemonkey@finitemonkey en Claude Code",
|
|
5806
|
+
"doctor.settings.plugin_channels": "{stable} y {next} están activos a la vez. Comparten el nombre del plugin, así que Claude Code se queda con un solo conjunto de hooks",
|
|
5807
|
+
"doctor.settings.fix_plugin_channels": "Para quedarte en la versión estable ejecuta claude plugin disable {next}; para probar next ejecuta claude plugin disable {stable}",
|
|
5520
5808
|
"doctor.settings.agent_teams": "{name} está activado; los compañeros de equipo corren en sesiones propias, y Finite Monkey no revisa su trabajo antes de que digan que terminaron",
|
|
5521
5809
|
"doctor.settings.fix_agent_teams": "Ejecuta `finitemonkey gate` antes de aceptar el resultado de un compañero, o quita la variable",
|
|
5522
5810
|
"doctor.settings.loop_plugins": "{names} activo: devuelve al agente al trabajo en Stop, y su bucle no tiene guardia propia",
|
|
@@ -5931,7 +6219,18 @@ var es_default = {
|
|
|
5931
6219
|
"history.pattern.no_verify": "{count, plural, one {# comando git} other {# comandos git}} con --no-verify",
|
|
5932
6220
|
"history.pattern.force_push": "{count, plural, one {# push forzado} other {# push forzados}}",
|
|
5933
6221
|
"history.pattern.test_skip": "{count, plural, one {# edición} other {# ediciones}} de pruebas que añadieron .skip, .only o una marca para saltar",
|
|
5934
|
-
"history.estimate": "Son estimaciones por patrones de texto, no evidencia; no se guardó nada y nada salió de esta máquina."
|
|
6222
|
+
"history.estimate": "Son estimaciones por patrones de texto, no evidencia; no se guardó nada y nada salió de esta máquina.",
|
|
6223
|
+
"gate.require.review": "un subagente revisor leyó el cambio en su estado actual",
|
|
6224
|
+
"gate.size.declared": "tamaño {effective}: {files, plural, one {# archivo} other {# archivos}}, {lines, plural, one {# línea} other {# líneas}}",
|
|
6225
|
+
"gate.size.grew": "declarada {declared}, el diff la vuelve {effective}: {files, plural, one {# archivo} other {# archivos}}, {lines, plural, one {# línea} other {# líneas}}",
|
|
6226
|
+
"gate.size.sensitive": "ruta sensible tocada: {file}",
|
|
6227
|
+
"gate.review.missing": "ninguna revisión del estado actual: ejecuta un subagente revisor sobre el diff, o la persona escribe `fm: approve {check} <motivo>` en el chat",
|
|
6228
|
+
"gate.review.done": "revisado por {agent} en {at}",
|
|
6229
|
+
"secrets.warn.write": "Aviso de Finite Monkey (la escritura se hizo): {found}. Un secreto pasado como argumento de línea de comandos aparece en ps y en docker inspect; la verificación TLS desactivada deja que cualquiera en el camino lea el tráfico. Lleva el valor a una variable de entorno o a un archivo de secreto y mantén el TLS verificado fuera de las pruebas.",
|
|
6230
|
+
"gate.secrets.warning": "aviso: {rule} en {file}:{line}",
|
|
6231
|
+
"doctor.secrets.remote_credential": "el remoto git {remotes} tiene una credencial en la URL, guardada en texto plano en .git/config",
|
|
6232
|
+
"doctor.secrets.fix_remote_credential": "ejecuta `git remote set-url {remote} <url sin usuario:token@>`, usa un credential helper o SSH y rota el token",
|
|
6233
|
+
"secrets.session.remote_credential": "el remoto git {remotes} tiene una credencial en la URL (texto plano en .git/config). Pide a la persona que ejecute `git remote set-url {remote} <url sin usuario:token@>` y rote el token; no imprimas la URL."
|
|
5935
6234
|
};
|
|
5936
6235
|
|
|
5937
6236
|
// src/i18n/pt-BR.json
|
|
@@ -6028,6 +6327,8 @@ var pt_BR_default = {
|
|
|
6028
6327
|
"doctor.settings.fix_managed_hooks_only": "Peça ao administrador dos settings gerenciados para incluir os hooks do Finite Monkey lá, ou para desligar allowManagedHooksOnly",
|
|
6029
6328
|
"doctor.settings.plugin_only": "strictPluginOnlyCustomization está ligado ({scopes}) e o plugin finitemonkey não está ativo; os hooks do projeto não carregam",
|
|
6030
6329
|
"doctor.settings.fix_plugin_only": "Instale o plugin: finitemonkey init --marketplace --yes, depois /plugin install finitemonkey@finitemonkey no Claude Code",
|
|
6330
|
+
"doctor.settings.plugin_channels": "{stable} e {next} estão ativos ao mesmo tempo. Os dois têm o mesmo nome de plugin, então o Claude Code fica com só um conjunto de hooks",
|
|
6331
|
+
"doctor.settings.fix_plugin_channels": "Para ficar no estável rode claude plugin disable {next}; para testar o next rode claude plugin disable {stable}",
|
|
6031
6332
|
"doctor.settings.agent_teams": "{name} está ligado; os colegas de equipe rodam em sessões próprias, e o Finite Monkey não confere o trabalho deles antes de dizerem que terminaram",
|
|
6032
6333
|
"doctor.settings.fix_agent_teams": "Rode `finitemonkey gate` antes de aceitar o resultado de um colega, ou remova a variável",
|
|
6033
6334
|
"doctor.settings.loop_plugins": "{names} ativo: ele manda o agente de volta ao trabalho no Stop, e o laço dele não tem guarda própria",
|
|
@@ -6442,7 +6743,18 @@ var pt_BR_default = {
|
|
|
6442
6743
|
"history.pattern.no_verify": "{count, plural, one {# comando git} other {# comandos git}} com --no-verify",
|
|
6443
6744
|
"history.pattern.force_push": "{count, plural, one {# push forçado} other {# pushes forçados}}",
|
|
6444
6745
|
"history.pattern.test_skip": "{count, plural, one {# edição} other {# edições}} de teste que acrescentaram .skip, .only ou marcador de pular",
|
|
6445
|
-
"history.estimate": "São estimativas por padrão de texto, não evidência; nada foi salvo e nada saiu desta máquina."
|
|
6746
|
+
"history.estimate": "São estimativas por padrão de texto, não evidência; nada foi salvo e nada saiu desta máquina.",
|
|
6747
|
+
"gate.require.review": "um subagente revisor leu a mudança no estado atual",
|
|
6748
|
+
"gate.size.declared": "tamanho {effective}: {files, plural, one {# arquivo} other {# arquivos}}, {lines, plural, one {# linha} other {# linhas}}",
|
|
6749
|
+
"gate.size.grew": "declarada {declared}, o diff a torna {effective}: {files, plural, one {# arquivo} other {# arquivos}}, {lines, plural, one {# linha} other {# linhas}}",
|
|
6750
|
+
"gate.size.sensitive": "caminho sensível alterado: {file}",
|
|
6751
|
+
"gate.review.missing": "nenhuma revisão do estado atual: rode um subagente revisor no diff, ou a pessoa escreve `fm: approve {check} <motivo>` no chat",
|
|
6752
|
+
"gate.review.done": "revisado por {agent} em {at}",
|
|
6753
|
+
"secrets.warn.write": "Aviso do Finite Monkey (a escrita foi feita): {found}. Segredo passado como argumento de linha de comando aparece no ps e no docker inspect; verificação TLS desligada deixa qualquer um no caminho ler o tráfego. Leve o valor para uma variável de ambiente ou um arquivo de segredo e mantenha o TLS verificado fora dos testes.",
|
|
6754
|
+
"gate.secrets.warning": "aviso: {rule} em {file}:{line}",
|
|
6755
|
+
"doctor.secrets.remote_credential": "o remoto git {remotes} tem uma credencial na URL, guardada em texto puro no .git/config",
|
|
6756
|
+
"doctor.secrets.fix_remote_credential": "rode `git remote set-url {remote} <url sem usuario:token@>`, use um credential helper ou SSH e rotacione o token",
|
|
6757
|
+
"secrets.session.remote_credential": "o remoto git {remotes} tem uma credencial na URL (texto puro no .git/config). Peça para a pessoa rodar `git remote set-url {remote} <url sem usuario:token@>` e rotacionar o token; não imprima a URL."
|
|
6446
6758
|
};
|
|
6447
6759
|
|
|
6448
6760
|
// src/i18n/review.json
|
|
@@ -6540,6 +6852,8 @@ var review_default = {
|
|
|
6540
6852
|
"doctor.settings.fix_managed_hooks_only": "machine",
|
|
6541
6853
|
"doctor.settings.plugin_only": "machine",
|
|
6542
6854
|
"doctor.settings.fix_plugin_only": "machine",
|
|
6855
|
+
"doctor.settings.plugin_channels": "machine",
|
|
6856
|
+
"doctor.settings.fix_plugin_channels": "machine",
|
|
6543
6857
|
"doctor.settings.agent_teams": "machine",
|
|
6544
6858
|
"doctor.settings.fix_agent_teams": "machine",
|
|
6545
6859
|
"doctor.settings.loop_plugins": "machine",
|
|
@@ -6954,7 +7268,18 @@ var review_default = {
|
|
|
6954
7268
|
"history.pattern.no_verify": "machine",
|
|
6955
7269
|
"history.pattern.force_push": "machine",
|
|
6956
7270
|
"history.pattern.test_skip": "machine",
|
|
6957
|
-
"history.estimate": "machine"
|
|
7271
|
+
"history.estimate": "machine",
|
|
7272
|
+
"gate.require.review": "machine",
|
|
7273
|
+
"gate.size.declared": "machine",
|
|
7274
|
+
"gate.size.grew": "machine",
|
|
7275
|
+
"gate.size.sensitive": "machine",
|
|
7276
|
+
"gate.review.missing": "machine",
|
|
7277
|
+
"gate.review.done": "machine",
|
|
7278
|
+
"secrets.warn.write": "machine",
|
|
7279
|
+
"gate.secrets.warning": "machine",
|
|
7280
|
+
"doctor.secrets.remote_credential": "machine",
|
|
7281
|
+
"doctor.secrets.fix_remote_credential": "machine",
|
|
7282
|
+
"secrets.session.remote_credential": "machine"
|
|
6958
7283
|
},
|
|
6959
7284
|
es: {
|
|
6960
7285
|
"command.init.summary": "machine",
|
|
@@ -7049,6 +7374,8 @@ var review_default = {
|
|
|
7049
7374
|
"doctor.settings.fix_managed_hooks_only": "machine",
|
|
7050
7375
|
"doctor.settings.plugin_only": "machine",
|
|
7051
7376
|
"doctor.settings.fix_plugin_only": "machine",
|
|
7377
|
+
"doctor.settings.plugin_channels": "machine",
|
|
7378
|
+
"doctor.settings.fix_plugin_channels": "machine",
|
|
7052
7379
|
"doctor.settings.agent_teams": "machine",
|
|
7053
7380
|
"doctor.settings.fix_agent_teams": "machine",
|
|
7054
7381
|
"doctor.settings.loop_plugins": "machine",
|
|
@@ -7463,7 +7790,18 @@ var review_default = {
|
|
|
7463
7790
|
"history.pattern.no_verify": "machine",
|
|
7464
7791
|
"history.pattern.force_push": "machine",
|
|
7465
7792
|
"history.pattern.test_skip": "machine",
|
|
7466
|
-
"history.estimate": "machine"
|
|
7793
|
+
"history.estimate": "machine",
|
|
7794
|
+
"gate.require.review": "machine",
|
|
7795
|
+
"gate.size.declared": "machine",
|
|
7796
|
+
"gate.size.grew": "machine",
|
|
7797
|
+
"gate.size.sensitive": "machine",
|
|
7798
|
+
"gate.review.missing": "machine",
|
|
7799
|
+
"gate.review.done": "machine",
|
|
7800
|
+
"secrets.warn.write": "machine",
|
|
7801
|
+
"gate.secrets.warning": "machine",
|
|
7802
|
+
"doctor.secrets.remote_credential": "machine",
|
|
7803
|
+
"doctor.secrets.fix_remote_credential": "machine",
|
|
7804
|
+
"secrets.session.remote_credential": "machine"
|
|
7467
7805
|
}
|
|
7468
7806
|
};
|
|
7469
7807
|
|
|
@@ -7586,7 +7924,7 @@ var buildReport = (input) => {
|
|
|
7586
7924
|
);
|
|
7587
7925
|
return fromEvidence(check2.id, check2.kind, entry2, hasStale(ledger, check2.id, treeHash2));
|
|
7588
7926
|
});
|
|
7589
|
-
const
|
|
7927
|
+
const count4 = (status) => items.filter((item) => item.status === status).length;
|
|
7590
7928
|
return {
|
|
7591
7929
|
schema: GATE_SCHEMA,
|
|
7592
7930
|
task: contract.id,
|
|
@@ -7597,7 +7935,7 @@ var buildReport = (input) => {
|
|
|
7597
7935
|
source: input.source,
|
|
7598
7936
|
contractEdited: input.contractEdited,
|
|
7599
7937
|
verdict: verdictOf(items),
|
|
7600
|
-
counts: { passed:
|
|
7938
|
+
counts: { passed: count4("passed"), failed: count4("failed"), unverified: count4("unverified") },
|
|
7601
7939
|
items,
|
|
7602
7940
|
decisionsNeeded: contract.decisionsNeeded,
|
|
7603
7941
|
fastPath: input.fastPath
|
|
@@ -7721,6 +8059,27 @@ var preCompact = (input) => {
|
|
|
7721
8059
|
return { kind: "allow" };
|
|
7722
8060
|
};
|
|
7723
8061
|
|
|
8062
|
+
// src/gate/review.ts
|
|
8063
|
+
var REVIEWS_FILE = "reviews.json";
|
|
8064
|
+
var REVIEWER_AGENT = /(^|:)[\w-]*reviewer$/i;
|
|
8065
|
+
var KEEP = 50;
|
|
8066
|
+
var toReview = (value) => {
|
|
8067
|
+
if (!isRecord(value)) return void 0;
|
|
8068
|
+
const { treeHash: treeHash2, agent, at } = value;
|
|
8069
|
+
return typeof treeHash2 === "string" && typeof agent === "string" && typeof at === "string" ? { treeHash: treeHash2, agent, at } : void 0;
|
|
8070
|
+
};
|
|
8071
|
+
var readReviews = (runtime, paths) => {
|
|
8072
|
+
const raw = readJsonFile(runtime, paths.cache(REVIEWS_FILE))?.["reviews"];
|
|
8073
|
+
return Array.isArray(raw) ? raw.flatMap((item) => {
|
|
8074
|
+
const review2 = toReview(item);
|
|
8075
|
+
return review2 === void 0 ? [] : [review2];
|
|
8076
|
+
}) : [];
|
|
8077
|
+
};
|
|
8078
|
+
var recordReview = (runtime, paths, treeHash2, agent) => {
|
|
8079
|
+
const reviews = [...readReviews(runtime, paths), { treeHash: treeHash2, agent, at: runtime.now() }].slice(-KEEP);
|
|
8080
|
+
return writeJsonFile(runtime, paths.cache(REVIEWS_FILE), { reviews: reviews.map((review2) => ({ ...review2 })) }).ok;
|
|
8081
|
+
};
|
|
8082
|
+
|
|
7724
8083
|
// src/gate/test-summary.ts
|
|
7725
8084
|
var ANSI2 = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
7726
8085
|
var COUNTERS = [
|
|
@@ -7860,44 +8219,14 @@ var runShellCheck = (check2, run4, ctx) => {
|
|
|
7860
8219
|
};
|
|
7861
8220
|
var commandCheck = (check2, ctx) => check2.run === void 0 ? unverified("not_implemented") : runShellCheck(check2, check2.run, ctx);
|
|
7862
8221
|
|
|
7863
|
-
// src/gate/checks/diff.ts
|
|
7864
|
-
import { join as join12 } from "node:path";
|
|
7865
|
-
|
|
7866
|
-
// src/core/glob.ts
|
|
7867
|
-
var escape = (char) => /[.+^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
|
|
7868
|
-
var globToRegExp = (glob) => {
|
|
7869
|
-
const pattern = glob.replace(/^\.\//, "");
|
|
7870
|
-
let out = "";
|
|
7871
|
-
for (let index = 0; index < pattern.length; index += 1) {
|
|
7872
|
-
const char = pattern[index] ?? "";
|
|
7873
|
-
if (char === "*" && pattern[index + 1] === "*") {
|
|
7874
|
-
const slash = pattern[index + 2] === "/";
|
|
7875
|
-
out += slash ? "(?:.*/)?" : ".*";
|
|
7876
|
-
index += slash ? 2 : 1;
|
|
7877
|
-
} else if (char === "*") out += "[^/]*";
|
|
7878
|
-
else if (char === "?") out += "[^/]";
|
|
7879
|
-
else out += escape(char);
|
|
7880
|
-
}
|
|
7881
|
-
return new RegExp(`^${out}${pattern.endsWith("/") ? ".*" : ""}$`);
|
|
7882
|
-
};
|
|
7883
|
-
var matchesAny = (path, globs) => {
|
|
7884
|
-
const normalized = toPosixPath(path);
|
|
7885
|
-
return globs.some((glob) => globToRegExp(glob).test(normalized));
|
|
7886
|
-
};
|
|
7887
|
-
|
|
7888
8222
|
// src/gate/checks/diff.ts
|
|
7889
8223
|
var MAX_LISTED = 10;
|
|
7890
|
-
var untrackedLines = (ctx) => ctx.changes.filter((change) => change.status === "A").reduce((sum, change) => {
|
|
7891
|
-
const text6 = ctx.runtime.readText(toAbsolutePath(join12(ctx.paths.root, change.path)));
|
|
7892
|
-
return sum + (text6.ok ? text6.value.split("\n").length : 0);
|
|
7893
|
-
}, 0);
|
|
7894
8224
|
var diffCheck = (_check, ctx) => {
|
|
7895
8225
|
const { paths, diffBudget } = ctx.contract.scope;
|
|
7896
8226
|
if (paths.length === 0 && diffBudget === void 0) return passed([note("gate.diff.no_scope")]);
|
|
7897
8227
|
const outside = paths.length === 0 ? [] : ctx.changes.filter((change) => !matchesAny(change.path, paths));
|
|
7898
|
-
const stat = numstat(ctx.git, ctx.base);
|
|
7899
8228
|
const files = ctx.changes.length;
|
|
7900
|
-
const lines3 =
|
|
8229
|
+
const lines3 = diffShape(ctx.runtime, ctx.git, ctx.base, ctx.changes).lines;
|
|
7901
8230
|
const notes2 = [
|
|
7902
8231
|
...outside.slice(0, MAX_LISTED).map((change) => note("gate.diff.outside", { file: change.path })),
|
|
7903
8232
|
...diffBudget?.files !== void 0 && files > diffBudget.files ? [note("gate.diff.too_many_files", { count: files, max: diffBudget.files })] : [],
|
|
@@ -7947,13 +8276,13 @@ var manualCheck = (check2, ctx) => {
|
|
|
7947
8276
|
};
|
|
7948
8277
|
|
|
7949
8278
|
// src/gate/checks/placeholders.ts
|
|
7950
|
-
import { join as
|
|
8279
|
+
import { join as join15 } from "node:path";
|
|
7951
8280
|
var MAX_LISTED2 = 10;
|
|
7952
8281
|
var SKIP_FILES = /\.(md|mdx|txt|rst|lock|snap|svg|png|jpg|gif|json)$|(^|\/)(CHANGELOG|LICENSE)/i;
|
|
7953
8282
|
var PLACEHOLDER = /\b(TODO|FIXME|XXX)\b|not implemented|NotImplementedError|unimplemented!|todo!\(|lorem ipsum/i;
|
|
7954
8283
|
var findPlaceholders = (lines3) => lines3.filter((line2) => !isTestFile(line2.path) && !SKIP_FILES.test(line2.path) && PLACEHOLDER.test(line2.text));
|
|
7955
8284
|
var untrackedAdded = (ctx) => ctx.changes.filter((change) => change.status === "A" && !SKIP_FILES.test(change.path)).flatMap((change) => {
|
|
7956
|
-
const text6 = ctx.runtime.readText(toAbsolutePath(
|
|
8285
|
+
const text6 = ctx.runtime.readText(toAbsolutePath(join15(ctx.paths.root, change.path)));
|
|
7957
8286
|
return text6.ok ? text6.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
|
|
7958
8287
|
});
|
|
7959
8288
|
var placeholdersCheck = (_check, ctx) => {
|
|
@@ -8465,10 +8794,10 @@ var hit = (rule, units, index, weight) => {
|
|
|
8465
8794
|
return { rule, unit: index, line: unit?.line ?? 0, weight, excerpt: unit?.text ?? "" };
|
|
8466
8795
|
};
|
|
8467
8796
|
var isProse = (unit) => unit?.kind === "sentence";
|
|
8468
|
-
var samePara = (units, from,
|
|
8797
|
+
var samePara = (units, from, count4) => {
|
|
8469
8798
|
const first = units[from];
|
|
8470
8799
|
if (!isProse(first)) return false;
|
|
8471
|
-
return units.slice(from, from +
|
|
8800
|
+
return units.slice(from, from + count4).every((unit) => isProse(unit) && unit.paragraph === first.paragraph);
|
|
8472
8801
|
};
|
|
8473
8802
|
var runsOf = (units, size, matches) => {
|
|
8474
8803
|
const starts = [];
|
|
@@ -8525,13 +8854,13 @@ var emoji = (units) => units.flatMap(
|
|
|
8525
8854
|
var dashes = (units) => {
|
|
8526
8855
|
const perParagraph = /* @__PURE__ */ new Map();
|
|
8527
8856
|
for (const unit of units) {
|
|
8528
|
-
const
|
|
8529
|
-
perParagraph.set(unit.paragraph, (perParagraph.get(unit.paragraph) ?? 0) +
|
|
8857
|
+
const count4 = unit.text.split("—").length - 1;
|
|
8858
|
+
perParagraph.set(unit.paragraph, (perParagraph.get(unit.paragraph) ?? 0) + count4);
|
|
8530
8859
|
}
|
|
8531
|
-
return [...perParagraph].flatMap(([paragraph,
|
|
8532
|
-
if (
|
|
8860
|
+
return [...perParagraph].flatMap(([paragraph, count4]) => {
|
|
8861
|
+
if (count4 === 0) return [];
|
|
8533
8862
|
const index = units.findIndex((unit) => unit.paragraph === paragraph && unit.text.includes("—"));
|
|
8534
|
-
const weight =
|
|
8863
|
+
const weight = count4 >= 2 ? STRUCTURE_WEIGHTS.dashSeries : STRUCTURE_WEIGHTS.dash;
|
|
8535
8864
|
return [hit("em-dash-series", units, index, weight)];
|
|
8536
8865
|
});
|
|
8537
8866
|
};
|
|
@@ -8559,11 +8888,11 @@ var windowEnd = (units, from, size) => {
|
|
|
8559
8888
|
return to;
|
|
8560
8889
|
};
|
|
8561
8890
|
var windowScore = (hits, window) => hits.filter((item) => item.unit >= window.from && item.unit <= window.to).reduce((sum, item) => sum + item.weight, 0);
|
|
8562
|
-
var hotWindows = (hits, units, size,
|
|
8891
|
+
var hotWindows = (hits, units, size, threshold2) => {
|
|
8563
8892
|
const windows = [];
|
|
8564
8893
|
for (let from = 0; from < units.length; from += 1) {
|
|
8565
8894
|
const window = { from, to: windowEnd(units, from, size) };
|
|
8566
|
-
if (windowScore(hits, window) >=
|
|
8895
|
+
if (windowScore(hits, window) >= threshold2) windows.push(window);
|
|
8567
8896
|
}
|
|
8568
8897
|
return windows;
|
|
8569
8898
|
};
|
|
@@ -8590,10 +8919,10 @@ var byPosition = (left, right) => left.unit - right.unit || left.line - right.li
|
|
|
8590
8919
|
var scoreProse = (text6, options = {}) => {
|
|
8591
8920
|
const { units, allowed: allowed2 } = extractProse(text6);
|
|
8592
8921
|
const size = options.window ?? DENSITY_WINDOW;
|
|
8593
|
-
const
|
|
8922
|
+
const threshold2 = options.threshold ?? DENSITY_THRESHOLD;
|
|
8594
8923
|
const catalog = options.context === "record" ? [...phraseRules(), ...recordRules()] : phraseRules();
|
|
8595
8924
|
const hits = [...phraseHits(units, catalog), ...structureHits(units)].sort(byPosition);
|
|
8596
|
-
const spans = mergeSpans(hits, hotWindows(hits, units, size,
|
|
8925
|
+
const spans = mergeSpans(hits, hotWindows(hits, units, size, threshold2));
|
|
8597
8926
|
return { hits, spans, allowed: allowed2 };
|
|
8598
8927
|
};
|
|
8599
8928
|
var isFlagged = (report2) => report2.spans.length > 0;
|
|
@@ -8643,22 +8972,55 @@ var MAX_LISTED3 = 10;
|
|
|
8643
8972
|
var rulesOf = (span) => [...new Set(span.hits.map((hit3) => hit3.rule))].join(", ");
|
|
8644
8973
|
var proseOutcome = (status) => {
|
|
8645
8974
|
if (!status.ok) return unverified("tool_missing", [note("gate.prose.unavailable")]);
|
|
8646
|
-
const { files, flagged, blocking } = status.value;
|
|
8647
|
-
const key =
|
|
8975
|
+
const { files, flagged, blocking: blocking2 } = status.value;
|
|
8976
|
+
const key = blocking2 ? "gate.prose.finding" : "gate.prose.warning";
|
|
8648
8977
|
const notes2 = flagged.flatMap((item) => item.report.spans.map((span) => ({ file: item.file, span }))).slice(0, MAX_LISTED3).map(({ file, span }) => note(key, { file, line: span.startLine, rules: rulesOf(span) }));
|
|
8649
8978
|
if (notes2.length === 0) return passed([note("gate.prose.clean", { count: files.length })]);
|
|
8650
|
-
return
|
|
8979
|
+
return blocking2 ? failed(notes2) : passed(notes2);
|
|
8651
8980
|
};
|
|
8652
8981
|
var proseCheck = (_check, ctx) => proseOutcome(proseGateStatus(ctx.runtime, ctx.paths.root));
|
|
8653
8982
|
|
|
8983
|
+
// src/gate/checks/review.ts
|
|
8984
|
+
var MAX_SENSITIVE = 5;
|
|
8985
|
+
var sizeNotes = (ctx) => {
|
|
8986
|
+
const size = ctx.size;
|
|
8987
|
+
if (size === void 0) return [];
|
|
8988
|
+
return [
|
|
8989
|
+
note(size.declared === size.effective ? "gate.size.declared" : "gate.size.grew", {
|
|
8990
|
+
declared: size.declared,
|
|
8991
|
+
effective: size.effective,
|
|
8992
|
+
files: size.files,
|
|
8993
|
+
lines: size.lines
|
|
8994
|
+
}),
|
|
8995
|
+
...size.sensitive.slice(0, MAX_SENSITIVE).map((file) => note("gate.size.sensitive", { file }))
|
|
8996
|
+
];
|
|
8997
|
+
};
|
|
8998
|
+
var reviewCheck = (check2, ctx) => {
|
|
8999
|
+
const review2 = readReviews(ctx.runtime, ctx.paths).findLast((entry2) => entry2.treeHash === ctx.treeHash);
|
|
9000
|
+
if (review2 !== void 0) {
|
|
9001
|
+
return { status: "passed", notes: [note("gate.review.done", { agent: review2.agent, at: review2.at })] };
|
|
9002
|
+
}
|
|
9003
|
+
const approval = readApprovals(ctx.runtime, ctx.paths).findLast(
|
|
9004
|
+
(item) => item.task === ctx.contract.id && item.check === check2.id
|
|
9005
|
+
);
|
|
9006
|
+
if (approval !== void 0) {
|
|
9007
|
+
return {
|
|
9008
|
+
status: "passed",
|
|
9009
|
+
notes: [note("gate.manual.approved", { at: approval.at, reason: approval.reason })],
|
|
9010
|
+
releasedBy: `person@${approval.at}`
|
|
9011
|
+
};
|
|
9012
|
+
}
|
|
9013
|
+
return failed([...sizeNotes(ctx), note("gate.review.missing", { check: check2.id })]);
|
|
9014
|
+
};
|
|
9015
|
+
|
|
8654
9016
|
// src/secrets/baseline.ts
|
|
8655
|
-
import { join as
|
|
9017
|
+
import { join as join16 } from "node:path";
|
|
8656
9018
|
var BASELINE_FILE2 = ".finitemonkey/findings/baseline.json";
|
|
8657
9019
|
var BASELINE_SCHEMA = "finitemonkey/secrets-baseline@1";
|
|
8658
9020
|
var BASELINE_STATES = ["aceito", "falso-positivo"];
|
|
8659
9021
|
var EXCEPTION_MAX_DAYS = 90;
|
|
8660
9022
|
var EMPTY_BASELINE = { schema: BASELINE_SCHEMA, entries: [] };
|
|
8661
|
-
var baselinePath = (root) => toAbsolutePath(
|
|
9023
|
+
var baselinePath = (root) => toAbsolutePath(join16(root, BASELINE_FILE2));
|
|
8662
9024
|
var text5 = (record2, key) => {
|
|
8663
9025
|
const value = record2[key];
|
|
8664
9026
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
@@ -8718,14 +9080,14 @@ var withoutFixed = (baseline, fixed) => {
|
|
|
8718
9080
|
var acceptedFingerprints = (baseline, now) => new Set(baseline.entries.filter((entry2) => entry2.expiresAt >= now).map((entry2) => entry2.fingerprint));
|
|
8719
9081
|
|
|
8720
9082
|
// src/secrets/incidents.ts
|
|
8721
|
-
import { join as
|
|
9083
|
+
import { join as join17 } from "node:path";
|
|
8722
9084
|
var INCIDENTS_DIR = ".finitemonkey/incidents";
|
|
8723
9085
|
var INCIDENT_STATES = ["aberto", "exposicao-local", "rotacionado", "fechado"];
|
|
8724
9086
|
var INCIDENT_SOURCES = ["tool-output", "prompt", "history", "scan"];
|
|
8725
9087
|
var ID = /^inc-[0-9a-f]{12}$/;
|
|
8726
9088
|
var incidentId = (rule, fingerprint) => `inc-${sha256Hex(`${rule}\0${fingerprint}`).slice(0, 12)}`;
|
|
8727
|
-
var incidentsDir = (root) => toAbsolutePath(
|
|
8728
|
-
var incidentFile = (root, id) => toAbsolutePath(
|
|
9089
|
+
var incidentsDir = (root) => toAbsolutePath(join17(root, INCIDENTS_DIR));
|
|
9090
|
+
var incidentFile = (root, id) => toAbsolutePath(join17(incidentsDir(root), `${id}.json`));
|
|
8729
9091
|
var pick2 = (options, value) => options.find((option) => option === value);
|
|
8730
9092
|
var str2 = (record2, key) => {
|
|
8731
9093
|
const value = record2[key];
|
|
@@ -8780,7 +9142,7 @@ var listIncidents = (runtime, root) => {
|
|
|
8780
9142
|
return names.value.filter((name) => name.endsWith(".json")).map((name) => readIncident(runtime, root, name.slice(0, -".json".length))).flatMap((parsed) => parsed.ok ? [parsed.value] : []).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
8781
9143
|
};
|
|
8782
9144
|
var saveIncident = (runtime, root, incident) => {
|
|
8783
|
-
const ignore = runtime.writeText(toAbsolutePath(
|
|
9145
|
+
const ignore = runtime.writeText(toAbsolutePath(join17(incidentsDir(root), ".gitignore")), "*\n");
|
|
8784
9146
|
if (!ignore.ok) return ignore;
|
|
8785
9147
|
return runtime.writeText(incidentFile(root, incident.id), `${JSON.stringify(incident, null, 2)}
|
|
8786
9148
|
`);
|
|
@@ -8827,7 +9189,7 @@ var moveIncident = (incident, to, now, by) => ({
|
|
|
8827
9189
|
var isRotated = (incident) => incident.state === "rotacionado" || incident.state === "fechado";
|
|
8828
9190
|
|
|
8829
9191
|
// src/secrets/scan.ts
|
|
8830
|
-
import { join as
|
|
9192
|
+
import { join as join18, relative as relative4 } from "node:path";
|
|
8831
9193
|
|
|
8832
9194
|
// src/secrets/located.ts
|
|
8833
9195
|
var findingId = (ruleId, file, fingerprint) => `fm-${sha256Hex(`${ruleId}\0${file}\0${fingerprint}`).slice(0, 16)}`;
|
|
@@ -8927,7 +9289,7 @@ var listed2 = (runtime, root, target) => {
|
|
|
8927
9289
|
const base = toAbsolutePath(target, root);
|
|
8928
9290
|
const walked = runtime.walkFiles(base);
|
|
8929
9291
|
if (!walked.ok) return walked;
|
|
8930
|
-
return accept(walked.value.map((name) => toPosixPath(relative4(root,
|
|
9292
|
+
return accept(walked.value.map((name) => toPosixPath(relative4(root, join18(base, toPosixPath(name))))));
|
|
8931
9293
|
};
|
|
8932
9294
|
var scanFile = (runtime, root, file) => {
|
|
8933
9295
|
const text6 = runtime.readText(toAbsolutePath(file, root));
|
|
@@ -8987,15 +9349,17 @@ var secretsCheck = (_check, ctx) => {
|
|
|
8987
9349
|
const status = secretsGateStatus(ctx.runtime, ctx.paths.root, sinceOf(ctx));
|
|
8988
9350
|
if (!status.ok) return unverified("tool_missing", [note("gate.secrets.unavailable")]);
|
|
8989
9351
|
const { findings, openIncidents } = status.value;
|
|
9352
|
+
const warnings = findings.filter(isWarning).slice(0, MAX_LISTED4).map((finding3) => note("gate.secrets.warning", { file: finding3.file, line: finding3.line, rule: finding3.ruleId }));
|
|
8990
9353
|
const notes2 = [
|
|
8991
|
-
...findings.slice(0, MAX_LISTED4).map((finding3) => note("gate.secrets.finding", { file: finding3.file, line: finding3.line, rule: finding3.ruleId })),
|
|
9354
|
+
...blocking(findings).slice(0, MAX_LISTED4).map((finding3) => note("gate.secrets.finding", { file: finding3.file, line: finding3.line, rule: finding3.ruleId })),
|
|
8992
9355
|
...openIncidents.slice(0, MAX_LISTED4).map((incident) => note("gate.secrets.incident", { id: incident.id, state: incident.state }))
|
|
8993
9356
|
];
|
|
8994
|
-
|
|
9357
|
+
if (notes2.length > 0) return failed([...notes2, ...warnings]);
|
|
9358
|
+
return passed(warnings.length > 0 ? warnings : [note("gate.secrets.clean")]);
|
|
8995
9359
|
};
|
|
8996
9360
|
|
|
8997
9361
|
// src/gate/checks/tests.ts
|
|
8998
|
-
import { join as
|
|
9362
|
+
import { join as join19 } from "node:path";
|
|
8999
9363
|
|
|
9000
9364
|
// src/guards/tests/tree.ts
|
|
9001
9365
|
var outOfScope = (input, change) => input.testsProtected && input.scopePaths.length > 0 && change.status !== "A" && isTestFile(change.path) && !matchesAny(change.path, input.scopePaths) ? [{ kind: "out_of_scope", path: change.path }] : [];
|
|
@@ -9027,7 +9391,7 @@ var treeViolations = (ctx) => {
|
|
|
9027
9391
|
changes: relevant,
|
|
9028
9392
|
before: (path) => fileAt(ctx.git, ctx.base, path),
|
|
9029
9393
|
after: (path) => {
|
|
9030
|
-
const text6 = ctx.runtime.readText(toAbsolutePath(
|
|
9394
|
+
const text6 = ctx.runtime.readText(toAbsolutePath(join19(ctx.paths.root, path)));
|
|
9031
9395
|
return text6.ok ? text6.value : void 0;
|
|
9032
9396
|
},
|
|
9033
9397
|
acceptedConfigs: ctx.trust.configs,
|
|
@@ -9048,7 +9412,7 @@ var testsCheck = (check2, ctx) => {
|
|
|
9048
9412
|
};
|
|
9049
9413
|
|
|
9050
9414
|
// src/git/tree.ts
|
|
9051
|
-
import { join as
|
|
9415
|
+
import { join as join20 } from "node:path";
|
|
9052
9416
|
|
|
9053
9417
|
// src/secrets/guards/paths.ts
|
|
9054
9418
|
var SAFE_ENV = /^\.env\.(?:example|sample|template|dist|defaults|schema)$/i;
|
|
@@ -9087,7 +9451,7 @@ var dropSensitive = (runtime, repo, env, options) => {
|
|
|
9087
9451
|
return removed.ok ? accept(void 0) : removed;
|
|
9088
9452
|
};
|
|
9089
9453
|
var withTempIndex = (runtime, repo, options, work) => {
|
|
9090
|
-
const path = toAbsolutePath(
|
|
9454
|
+
const path = toAbsolutePath(join20(repo.gitDir, `finitemonkey-${options.stamp}.index`));
|
|
9091
9455
|
const env = { GIT_INDEX_FILE: path, GIT_OPTIONAL_LOCKS: "0" };
|
|
9092
9456
|
try {
|
|
9093
9457
|
return work(env);
|
|
@@ -9320,7 +9684,7 @@ var entryOf2 = (kind, xy, path) => ({
|
|
|
9320
9684
|
staged: kind !== "untracked" && !xy.startsWith("."),
|
|
9321
9685
|
unstaged: kind === "untracked" || xy.charAt(1) !== "."
|
|
9322
9686
|
});
|
|
9323
|
-
var afterFields = (record2,
|
|
9687
|
+
var afterFields = (record2, count4) => record2.split(" ").slice(count4).join(" ");
|
|
9324
9688
|
var parsePorcelainV2 = (stdout) => {
|
|
9325
9689
|
const records = stdout.split("\0");
|
|
9326
9690
|
const out = [];
|
|
@@ -9338,94 +9702,8 @@ var parsePorcelainV2 = (stdout) => {
|
|
|
9338
9702
|
return out.filter((entry2) => entry2.path !== "" && !entry2.path.startsWith(STATE_PREFIX));
|
|
9339
9703
|
};
|
|
9340
9704
|
|
|
9341
|
-
// src/git/project-config.ts
|
|
9342
|
-
import { join as join19 } from "node:path";
|
|
9343
|
-
var CONFIG_FILE3 = ".finitemonkey/config.yml";
|
|
9344
|
-
var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
|
|
9345
|
-
var stripComment2 = (line2) => {
|
|
9346
|
-
let quote;
|
|
9347
|
-
for (let index = 0; index < line2.length; index += 1) {
|
|
9348
|
-
const char = line2.charAt(index);
|
|
9349
|
-
if (quote === void 0 && (char === '"' || char === "'")) quote = char;
|
|
9350
|
-
else if (char === quote) quote = void 0;
|
|
9351
|
-
else if (quote === void 0 && char === "#" && (index === 0 || /\s/.test(line2.charAt(index - 1)))) {
|
|
9352
|
-
return line2.slice(0, index);
|
|
9353
|
-
}
|
|
9354
|
-
}
|
|
9355
|
-
return line2;
|
|
9356
|
-
};
|
|
9357
|
-
var flowList = (value) => {
|
|
9358
|
-
const trimmed = value.trim();
|
|
9359
|
-
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
|
|
9360
|
-
return trimmed.slice(1, -1).split(",").map(unquote).filter((item) => item !== "");
|
|
9361
|
-
};
|
|
9362
|
-
var keyValue = (body2) => {
|
|
9363
|
-
const match = /^("[^"]*"|'[^']*'|[^:]+?)\s*:(?:\s+(.*))?$/.exec(body2);
|
|
9364
|
-
if (match === null) return void 0;
|
|
9365
|
-
return { key: unquote(match[1] ?? ""), value: match[2] ?? "" };
|
|
9366
|
-
};
|
|
9367
|
-
var addItem = (frame, item) => {
|
|
9368
|
-
if (frame.key === void 0) return;
|
|
9369
|
-
const parent = frame.tree[frame.key];
|
|
9370
|
-
if (Array.isArray(parent)) parent.push(item);
|
|
9371
|
-
else frame.tree[frame.key] = [item];
|
|
9372
|
-
};
|
|
9373
|
-
var placeLine = (stack, indent, body2) => {
|
|
9374
|
-
while (stack.length > 1 && (stack[stack.length - 1]?.indent ?? 0) >= indent && !body2.startsWith("- ")) stack.pop();
|
|
9375
|
-
const top = stack[stack.length - 1];
|
|
9376
|
-
if (top === void 0) return;
|
|
9377
|
-
if (body2.startsWith("- ")) {
|
|
9378
|
-
addItem(top, unquote(body2.slice(2)));
|
|
9379
|
-
return;
|
|
9380
|
-
}
|
|
9381
|
-
const pair = keyValue(body2);
|
|
9382
|
-
if (pair === void 0) return;
|
|
9383
|
-
const parentTree = top.key === void 0 ? top.tree : ensureTree(top.tree, top.key);
|
|
9384
|
-
if (pair.value === "") {
|
|
9385
|
-
stack.push({ indent, tree: parentTree, key: pair.key });
|
|
9386
|
-
return;
|
|
9387
|
-
}
|
|
9388
|
-
parentTree[pair.key] = flowList(pair.value) ?? unquote(pair.value);
|
|
9389
|
-
};
|
|
9390
|
-
var ensureTree = (tree, key) => {
|
|
9391
|
-
const existing = tree[key];
|
|
9392
|
-
if (existing !== void 0 && typeof existing !== "string" && !Array.isArray(existing)) return existing;
|
|
9393
|
-
const fresh3 = {};
|
|
9394
|
-
tree[key] = fresh3;
|
|
9395
|
-
return fresh3;
|
|
9396
|
-
};
|
|
9397
|
-
var parseConfigYaml = (text6) => {
|
|
9398
|
-
const root = {};
|
|
9399
|
-
const stack = [{ indent: -1, tree: root, key: void 0 }];
|
|
9400
|
-
for (const raw of text6.split(/\r?\n/)) {
|
|
9401
|
-
const line2 = stripComment2(raw).trimEnd();
|
|
9402
|
-
if (line2.trim() === "" || line2.trim() === "---") continue;
|
|
9403
|
-
const indent = line2.length - line2.trimStart().length;
|
|
9404
|
-
placeLine(stack, indent, line2.trim());
|
|
9405
|
-
}
|
|
9406
|
-
return root;
|
|
9407
|
-
};
|
|
9408
|
-
var isTree = (node) => typeof node === "object" && !Array.isArray(node);
|
|
9409
|
-
var configAt = (tree, ...path) => path.reduce((node, key) => isTree(node) ? node[key] : void 0, tree);
|
|
9410
|
-
var configList = (tree, ...path) => {
|
|
9411
|
-
const node = configAt(tree, ...path);
|
|
9412
|
-
if (typeof node === "string") return node === "" ? [] : [node];
|
|
9413
|
-
return Array.isArray(node) ? node.filter((item) => typeof item === "string") : [];
|
|
9414
|
-
};
|
|
9415
|
-
var configMap = (tree, ...path) => {
|
|
9416
|
-
const node = configAt(tree, ...path);
|
|
9417
|
-
if (!isTree(node)) return {};
|
|
9418
|
-
return Object.fromEntries(
|
|
9419
|
-
Object.entries(node).filter((entry2) => typeof entry2[1] === "string")
|
|
9420
|
-
);
|
|
9421
|
-
};
|
|
9422
|
-
var loadProjectConfig = (runtime, root) => {
|
|
9423
|
-
const text6 = runtime.readText(toAbsolutePath(join19(root, CONFIG_FILE3)));
|
|
9424
|
-
return text6.ok ? parseConfigYaml(text6.value) : {};
|
|
9425
|
-
};
|
|
9426
|
-
|
|
9427
9705
|
// src/git/merge/dropped.ts
|
|
9428
|
-
import { join as
|
|
9706
|
+
import { join as join21 } from "node:path";
|
|
9429
9707
|
|
|
9430
9708
|
// src/git/merge/lines.ts
|
|
9431
9709
|
var normalizeLine = (line2) => line2.replace(/\r$/, "").trim().replace(/\s*,$/, "");
|
|
@@ -9505,7 +9783,7 @@ var parseDropped = (text6) => {
|
|
|
9505
9783
|
return accept({ items, invalid });
|
|
9506
9784
|
};
|
|
9507
9785
|
var loadDropped = (runtime, root, file) => {
|
|
9508
|
-
const path = toAbsolutePath(file ??
|
|
9786
|
+
const path = toAbsolutePath(file ?? join21(root, DROPPED_FILE), root);
|
|
9509
9787
|
const text6 = runtime.readText(path);
|
|
9510
9788
|
if (!text6.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text6.error}`);
|
|
9511
9789
|
return parseDropped(text6.value);
|
|
@@ -9515,7 +9793,7 @@ var isDropped = (finding3, items) => items.some(
|
|
|
9515
9793
|
);
|
|
9516
9794
|
|
|
9517
9795
|
// src/git/merge/generated.ts
|
|
9518
|
-
import { join as
|
|
9796
|
+
import { join as join22 } from "node:path";
|
|
9519
9797
|
var LOCKFILES3 = /* @__PURE__ */ new Set([
|
|
9520
9798
|
"package-lock.json",
|
|
9521
9799
|
"npm-shrinkwrap.json",
|
|
@@ -9578,7 +9856,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
|
|
|
9578
9856
|
if (declared.length === 0) return accept([]);
|
|
9579
9857
|
const commit2 = resultCommit(runtime, repo, inputs);
|
|
9580
9858
|
if (!commit2.ok) return commit2;
|
|
9581
|
-
const dir =
|
|
9859
|
+
const dir = join22(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
|
|
9582
9860
|
if (!runtime.git(["worktree", "add", "--detach", dir, commit2.value], repo.root).ok)
|
|
9583
9861
|
return reject("could not check out the merge result");
|
|
9584
9862
|
try {
|
|
@@ -9588,7 +9866,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
|
|
|
9588
9866
|
}
|
|
9589
9867
|
return accept(
|
|
9590
9868
|
declared.flatMap(({ path }) => {
|
|
9591
|
-
const fresh3 = runtime.readText(toAbsolutePath(
|
|
9869
|
+
const fresh3 = runtime.readText(toAbsolutePath(join22(dir, path)));
|
|
9592
9870
|
const merged = blobText(runtime, repo, commit2.value, path);
|
|
9593
9871
|
if (fresh3.ok && fresh3.value === merged) return [];
|
|
9594
9872
|
return [{ kind: "generated-differs", file: path, side: void 0, line: void 0, text: path, weak: false }];
|
|
@@ -9874,19 +10152,19 @@ var verifyMerge = (runtime, repo, options = {}) => {
|
|
|
9874
10152
|
};
|
|
9875
10153
|
|
|
9876
10154
|
// src/git/ownership.ts
|
|
9877
|
-
import { join as
|
|
10155
|
+
import { join as join23 } from "node:path";
|
|
9878
10156
|
var OWNERSHIP_DIR = ".finitemonkey/git";
|
|
9879
10157
|
var ownerKey = (raw) => {
|
|
9880
10158
|
const cleaned = (raw ?? "").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80);
|
|
9881
10159
|
return cleaned === "" ? "unknown" : cleaned;
|
|
9882
10160
|
};
|
|
9883
|
-
var fileOf = (repo, kind, owner) => toAbsolutePath(
|
|
10161
|
+
var fileOf = (repo, kind, owner) => toAbsolutePath(join23(repo.root, OWNERSHIP_DIR, kind, `${ownerKey(owner)}.json`));
|
|
9884
10162
|
var readDirty = (runtime, root) => {
|
|
9885
10163
|
const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
|
|
9886
10164
|
return status.ok ? parsePorcelainV2(status.stdout) : void 0;
|
|
9887
10165
|
};
|
|
9888
10166
|
var hashDirty = (runtime, repo, entries) => {
|
|
9889
|
-
const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(
|
|
10167
|
+
const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(join23(repo.root, entry2.path)))?.kind === "file");
|
|
9890
10168
|
const hashed = present2.length === 0 ? void 0 : runtime.gitWith(["hash-object", "--stdin-paths"], repo.root, {
|
|
9891
10169
|
input: `${present2.map((entry2) => entry2.path).join("\n")}
|
|
9892
10170
|
`
|
|
@@ -10138,7 +10416,8 @@ var DEFAULT_CHECKS = {
|
|
|
10138
10416
|
secrets: secretsCheck,
|
|
10139
10417
|
prose: proseCheck,
|
|
10140
10418
|
worktree: worktreeCheck,
|
|
10141
|
-
merge: mergeCheck
|
|
10419
|
+
merge: mergeCheck,
|
|
10420
|
+
review: reviewCheck
|
|
10142
10421
|
};
|
|
10143
10422
|
|
|
10144
10423
|
// src/gate/run.ts
|
|
@@ -10318,10 +10597,21 @@ var isOwnWorktree = (input) => {
|
|
|
10318
10597
|
const main2 = findRepoRoot(runtime, input.cwd);
|
|
10319
10598
|
return front !== void 0 && main2 !== void 0 && front !== main2;
|
|
10320
10599
|
};
|
|
10321
|
-
var
|
|
10600
|
+
var noteReview = (input) => {
|
|
10601
|
+
const agent = textField(input.payload.raw, "agent_type") ?? "";
|
|
10602
|
+
if (!REVIEWER_AGENT.test(agent)) return;
|
|
10603
|
+
const root = findRepoRoot(input.runtime, sessionCwd(input));
|
|
10604
|
+
if (root === void 0) return;
|
|
10605
|
+
const tree = treeHash(gitAt(input.runtime, root));
|
|
10606
|
+
if (tree !== void 0) recordReview(input.runtime, fmPaths(root), tree, agent);
|
|
10607
|
+
};
|
|
10608
|
+
var subagentStopGate = (input) => {
|
|
10609
|
+
noteReview(input);
|
|
10610
|
+
return isOwnWorktree(input) ? stopGate(input) : { kind: "allow" };
|
|
10611
|
+
};
|
|
10322
10612
|
|
|
10323
10613
|
// src/gate/hooks/subagent.ts
|
|
10324
|
-
import { join as
|
|
10614
|
+
import { join as join24 } from "node:path";
|
|
10325
10615
|
var NO_CONTRACT_AGENTS = /(^|:)(reviewer|verifier|explore|plan|claude-code-guide|statusline-setup)$/i;
|
|
10326
10616
|
var KNOWN_KINDS = /* @__PURE__ */ new Set([
|
|
10327
10617
|
"command",
|
|
@@ -10334,7 +10624,8 @@ var KNOWN_KINDS = /* @__PURE__ */ new Set([
|
|
|
10334
10624
|
"secrets",
|
|
10335
10625
|
"prose",
|
|
10336
10626
|
"worktree",
|
|
10337
|
-
"merge"
|
|
10627
|
+
"merge",
|
|
10628
|
+
"review"
|
|
10338
10629
|
]);
|
|
10339
10630
|
var requirementText = (t, check2) => {
|
|
10340
10631
|
const key = `gate.require.${check2.kind}`;
|
|
@@ -10355,12 +10646,12 @@ var subagentStart = (input) => {
|
|
|
10355
10646
|
const active = readState(runtime, paths).active;
|
|
10356
10647
|
if (active === void 0) return { kind: "allow" };
|
|
10357
10648
|
const disk = (relative9) => {
|
|
10358
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
10649
|
+
const text6 = runtime.readText(toAbsolutePath(join24(paths.root, relative9)));
|
|
10359
10650
|
return text6.ok ? text6.value : void 0;
|
|
10360
10651
|
};
|
|
10361
10652
|
const loaded = loadTask(runtime, paths, { disk, base: () => void 0 }, active);
|
|
10362
10653
|
if (!loaded.ok) return { kind: "allow" };
|
|
10363
|
-
const briefing = runtime.stat(toAbsolutePath(
|
|
10654
|
+
const briefing = runtime.stat(toAbsolutePath(join24(paths.dir, "briefing.md"))) !== void 0;
|
|
10364
10655
|
return { kind: "allow", output: contextOutput("SubagentStart", contractFacts(t, loaded.value.contract, briefing)) };
|
|
10365
10656
|
};
|
|
10366
10657
|
|
|
@@ -10377,7 +10668,7 @@ var PORTAO_HANDLERS = {
|
|
|
10377
10668
|
};
|
|
10378
10669
|
|
|
10379
10670
|
// src/prod/guard.ts
|
|
10380
|
-
import { join as
|
|
10671
|
+
import { join as join26 } from "node:path";
|
|
10381
10672
|
|
|
10382
10673
|
// src/git/command-text.ts
|
|
10383
10674
|
var MAX_SHOWN = 160;
|
|
@@ -10719,7 +11010,7 @@ var remoteHostsOf = (command) => {
|
|
|
10719
11010
|
};
|
|
10720
11011
|
|
|
10721
11012
|
// src/prod/targets.ts
|
|
10722
|
-
import { join as
|
|
11013
|
+
import { join as join25 } from "node:path";
|
|
10723
11014
|
var DEPLOY_FILES = [
|
|
10724
11015
|
"deploy.sh",
|
|
10725
11016
|
"scripts/deploy.sh",
|
|
@@ -10743,12 +11034,12 @@ var hostsInText = (text6) => {
|
|
|
10743
11034
|
return [...urls, ...keyed, ...commands].filter((host) => host !== "" && !host.includes("$"));
|
|
10744
11035
|
};
|
|
10745
11036
|
var workflowFiles = (runtime, root) => {
|
|
10746
|
-
const listed4 = runtime.listDir(toAbsolutePath(
|
|
11037
|
+
const listed4 = runtime.listDir(toAbsolutePath(join25(root, ".github", "workflows")));
|
|
10747
11038
|
if (!listed4.ok) return [];
|
|
10748
11039
|
return listed4.value.filter((name) => /deploy/i.test(name) && /\.ya?ml$/i.test(name)).map((name) => `.github/workflows/${name}`);
|
|
10749
11040
|
};
|
|
10750
11041
|
var detectedHosts = (runtime, root, files) => files.flatMap((file) => {
|
|
10751
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
11042
|
+
const text6 = runtime.readText(toAbsolutePath(join25(root, file)));
|
|
10752
11043
|
return text6.ok ? hostsInText(text6.value) : [];
|
|
10753
11044
|
});
|
|
10754
11045
|
var loadProdTargets = (runtime, root, config) => {
|
|
@@ -10760,7 +11051,7 @@ var loadProdTargets = (runtime, root, config) => {
|
|
|
10760
11051
|
...detectedHosts(runtime, root, deployFiles)
|
|
10761
11052
|
];
|
|
10762
11053
|
const declaredDeploy = configAt(config, "prod", "deploy");
|
|
10763
|
-
const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(
|
|
11054
|
+
const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(join25(root, file))) !== void 0);
|
|
10764
11055
|
return {
|
|
10765
11056
|
hosts: new Set(hosts.map(hostKey).filter((host) => host !== "")),
|
|
10766
11057
|
kubeContexts: configList(config, "prod", "kube_contexts"),
|
|
@@ -10937,8 +11228,8 @@ var recordEvidence = (runtime, root, owner, reads) => {
|
|
|
10937
11228
|
] : []
|
|
10938
11229
|
);
|
|
10939
11230
|
if (lines3.length === 0) return;
|
|
10940
|
-
const path = toAbsolutePath(
|
|
10941
|
-
runtime.mkdir(toAbsolutePath(
|
|
11231
|
+
const path = toAbsolutePath(join26(root, EVIDENCE_FILE));
|
|
11232
|
+
runtime.mkdir(toAbsolutePath(join26(root, ".finitemonkey")));
|
|
10942
11233
|
const before = runtime.readText(path);
|
|
10943
11234
|
const text6 = before.ok ? before.value : "";
|
|
10944
11235
|
runtime.writeAtomic(path, `${text6}${text6 === "" || text6.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
|
|
@@ -11750,10 +12041,10 @@ var listWorktrees = (runtime, cwd) => {
|
|
|
11750
12041
|
};
|
|
11751
12042
|
|
|
11752
12043
|
// src/git/journal.ts
|
|
11753
|
-
import { join as
|
|
12044
|
+
import { join as join27 } from "node:path";
|
|
11754
12045
|
var JOURNAL_FILE = ".finitemonkey/git-journal.jsonl";
|
|
11755
12046
|
var JOURNAL_SCHEMA = "finitemonkey/git-journal@1";
|
|
11756
|
-
var journalPath = (root) => toAbsolutePath(
|
|
12047
|
+
var journalPath = (root) => toAbsolutePath(join27(root, JOURNAL_FILE));
|
|
11757
12048
|
var headsOf = (value) => isRecord(value) ? Object.fromEntries(
|
|
11758
12049
|
Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
|
|
11759
12050
|
) : {};
|
|
@@ -11789,7 +12080,7 @@ var readJournal = (runtime, root) => {
|
|
|
11789
12080
|
};
|
|
11790
12081
|
var appendJournal = (runtime, root, entry2) => {
|
|
11791
12082
|
const path = journalPath(root);
|
|
11792
|
-
const made = runtime.mkdir(toAbsolutePath(
|
|
12083
|
+
const made = runtime.mkdir(toAbsolutePath(join27(root, ".finitemonkey")));
|
|
11793
12084
|
if (!made.ok) return made;
|
|
11794
12085
|
const previous = runtime.readText(path);
|
|
11795
12086
|
const before = previous.ok ? previous.value : "";
|
|
@@ -12291,10 +12582,10 @@ var textOf2 = (input, found) => {
|
|
|
12291
12582
|
}
|
|
12292
12583
|
return accept(parts.join("\n\n"));
|
|
12293
12584
|
};
|
|
12294
|
-
var decisionFor = (input, found,
|
|
12585
|
+
var decisionFor = (input, found, blocking2) => {
|
|
12295
12586
|
const text6 = textOf2(input, found);
|
|
12296
12587
|
if (!text6.ok) {
|
|
12297
|
-
return
|
|
12588
|
+
return blocking2 ? { kind: "block", reason: input.t("prose.block.unreadable", { ...text6.error }) } : void 0;
|
|
12298
12589
|
}
|
|
12299
12590
|
const report2 = scoreProse(text6.value, { context: "record" });
|
|
12300
12591
|
if (report2.allowed || report2.spans.length === 0) return void 0;
|
|
@@ -12303,7 +12594,7 @@ var decisionFor = (input, found, blocking) => {
|
|
|
12303
12594
|
spans: describeSpans(input.t, report2.spans),
|
|
12304
12595
|
fixes: describeFixes(input.t, report2.spans)
|
|
12305
12596
|
};
|
|
12306
|
-
if (
|
|
12597
|
+
if (blocking2) return { kind: "block", reason: input.t("prose.block.command", params2) };
|
|
12307
12598
|
const additionalContext = `finitemonkey: ${input.t("prose.warn.command", params2)}`;
|
|
12308
12599
|
return { kind: "allow", output: { hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext } } };
|
|
12309
12600
|
};
|
|
@@ -12311,9 +12602,9 @@ var proseCommandGuard = (input) => {
|
|
|
12311
12602
|
if (!SHELLS3.has(toolName2(input.payload))) return ALLOW4;
|
|
12312
12603
|
const found = proseInCommand(optionalString(toolInput2(input.payload), "command") ?? "");
|
|
12313
12604
|
if (found.length === 0) return ALLOW4;
|
|
12314
|
-
const
|
|
12605
|
+
const blocking2 = projectPreset(input.runtime, projectRoot(input)) === "gabriel";
|
|
12315
12606
|
for (const item of found) {
|
|
12316
|
-
const decision = decisionFor(input, item,
|
|
12607
|
+
const decision = decisionFor(input, item, blocking2);
|
|
12317
12608
|
if (decision !== void 0) return decision;
|
|
12318
12609
|
}
|
|
12319
12610
|
return ALLOW4;
|
|
@@ -12477,11 +12768,24 @@ var relativeFile = (cwd, file) => {
|
|
|
12477
12768
|
const rel = relative7(cwd, file).split("\\").join("/");
|
|
12478
12769
|
return rel.startsWith("..") || rel === "" ? file : rel;
|
|
12479
12770
|
};
|
|
12771
|
+
var warningDecision = (t, warnings) => {
|
|
12772
|
+
if (warnings.length === 0) return ALLOW5;
|
|
12773
|
+
const context = t("secrets.warn.write", { found: describeFindings(t, warnings) });
|
|
12774
|
+
return { kind: "allow", output: { hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: context } } };
|
|
12775
|
+
};
|
|
12480
12776
|
var writeDecision = (input, tool) => {
|
|
12481
12777
|
const { t } = input;
|
|
12778
|
+
const warnings = [];
|
|
12482
12779
|
for (const written of writtenTexts(tool, toolInput2(input.payload))) {
|
|
12483
12780
|
const file = relativeFile(input.cwd, written.file);
|
|
12484
|
-
const
|
|
12781
|
+
const all = scanText(written.text, { path: file });
|
|
12782
|
+
warnings.push(
|
|
12783
|
+
...all.filter(isWarning).map((finding3) => ({
|
|
12784
|
+
ruleId: finding3.ruleId,
|
|
12785
|
+
where: t("secrets.where.file", { file, line: finding3.line, column: finding3.column })
|
|
12786
|
+
}))
|
|
12787
|
+
);
|
|
12788
|
+
const found = blocking(all);
|
|
12485
12789
|
if (found.length === 0) continue;
|
|
12486
12790
|
const items = found.map((finding3) => ({
|
|
12487
12791
|
ruleId: finding3.ruleId,
|
|
@@ -12489,7 +12793,7 @@ var writeDecision = (input, tool) => {
|
|
|
12489
12793
|
}));
|
|
12490
12794
|
return { kind: "block", reason: t("secrets.block.write", { file, found: describeFindings(t, items) }) };
|
|
12491
12795
|
}
|
|
12492
|
-
return
|
|
12796
|
+
return warningDecision(t, warnings);
|
|
12493
12797
|
};
|
|
12494
12798
|
var bashMessage = (t, finding3) => {
|
|
12495
12799
|
switch (finding3.kind) {
|
|
@@ -12503,8 +12807,9 @@ var bashMessage = (t, finding3) => {
|
|
|
12503
12807
|
};
|
|
12504
12808
|
var commitDecision = (input, includeUnstaged) => {
|
|
12505
12809
|
const staged = scanStaged(input.runtime, input.cwd, includeUnstaged);
|
|
12506
|
-
|
|
12507
|
-
|
|
12810
|
+
const found = staged.ok ? blocking(staged.value) : [];
|
|
12811
|
+
if (found.length === 0) return ALLOW5;
|
|
12812
|
+
const items = found.map((finding3) => ({
|
|
12508
12813
|
ruleId: finding3.ruleId,
|
|
12509
12814
|
where: input.t("secrets.where.file", { file: finding3.file, line: finding3.line, column: finding3.column })
|
|
12510
12815
|
}));
|
|
@@ -12516,7 +12821,7 @@ var bashDecision = (input) => {
|
|
|
12516
12821
|
const segments = segmentsOf(command);
|
|
12517
12822
|
const [first] = bashFindings(command, segments);
|
|
12518
12823
|
if (first !== void 0) return { kind: "block", reason: bashMessage(input.t, first) };
|
|
12519
|
-
const found = scanText(command);
|
|
12824
|
+
const found = blocking(scanText(command));
|
|
12520
12825
|
if (found.length > 0) {
|
|
12521
12826
|
const items = found.map((finding3) => ({ ruleId: finding3.ruleId, where: lineWhere(input.t, finding3) }));
|
|
12522
12827
|
return { kind: "block", reason: input.t("secrets.block.bash", { found: describeFindings(input.t, items) }) };
|
|
@@ -12541,6 +12846,44 @@ var secretsPreToolUse = (input) => {
|
|
|
12541
12846
|
return ALLOW5;
|
|
12542
12847
|
};
|
|
12543
12848
|
|
|
12849
|
+
// src/secrets/remotes.ts
|
|
12850
|
+
var USER_AND_SECRET = /^[a-z][a-z0-9+.-]*:\/\/[^\s/@:]+:[^\s/@]+@/i;
|
|
12851
|
+
var USERINFO = /^[a-z][a-z0-9+.-]*:\/\/(?<user>[^\s/@]+)@/i;
|
|
12852
|
+
var hasCredential = (url) => {
|
|
12853
|
+
if (USER_AND_SECRET.test(url)) return true;
|
|
12854
|
+
const user = USERINFO.exec(url)?.groups?.["user"];
|
|
12855
|
+
return user !== void 0 && scanText(user, { rules: CREDENTIAL_RULES }).length > 0;
|
|
12856
|
+
};
|
|
12857
|
+
var parseRemotes = (output) => {
|
|
12858
|
+
const remotes = /* @__PURE__ */ new Map();
|
|
12859
|
+
for (const line2 of output.split(/\r?\n/)) {
|
|
12860
|
+
const [name, url] = line2.trim().split(/\s+/);
|
|
12861
|
+
if (name === void 0 || url === void 0 || name === "") continue;
|
|
12862
|
+
remotes.set(name, [...remotes.get(name) ?? [], url]);
|
|
12863
|
+
}
|
|
12864
|
+
return remotes;
|
|
12865
|
+
};
|
|
12866
|
+
var remotesWithCredentials = (runtime, root) => {
|
|
12867
|
+
const listed4 = runtime.git(["remote", "-v"], root);
|
|
12868
|
+
if (!listed4.ok) return [];
|
|
12869
|
+
return [...parseRemotes(listed4.stdout)].filter(([, urls]) => urls.some(hasCredential)).map(([name]) => name).sort();
|
|
12870
|
+
};
|
|
12871
|
+
|
|
12872
|
+
// src/secrets/guards/session.ts
|
|
12873
|
+
var secretsSessionStart = (input) => {
|
|
12874
|
+
const remotes = remotesWithCredentials(input.runtime, input.cwd);
|
|
12875
|
+
if (remotes.length === 0) return { kind: "allow" };
|
|
12876
|
+
const params2 = { remotes: remotes.join(", "), remote: remotes[0] ?? "origin" };
|
|
12877
|
+
const text6 = input.t("secrets.session.remote_credential", params2);
|
|
12878
|
+
return {
|
|
12879
|
+
kind: "allow",
|
|
12880
|
+
output: {
|
|
12881
|
+
systemMessage: `finitemonkey: ${text6}`,
|
|
12882
|
+
hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text6 }
|
|
12883
|
+
}
|
|
12884
|
+
};
|
|
12885
|
+
};
|
|
12886
|
+
|
|
12544
12887
|
// src/hooks/handlers.ts
|
|
12545
12888
|
var allow = () => ({ kind: "allow" });
|
|
12546
12889
|
var DECISION_RANK = { allow: 0, defer: 0, ask: 1, deny: 2 };
|
|
@@ -12594,7 +12937,8 @@ var mergeHandlers = (...sets) => {
|
|
|
12594
12937
|
var SECRETS_HANDLERS = {
|
|
12595
12938
|
PreToolUse: [secretsPreToolUse],
|
|
12596
12939
|
UserPromptSubmit: [secretsUserPromptSubmit],
|
|
12597
|
-
PostToolUse: [secretsPostToolUse]
|
|
12940
|
+
PostToolUse: [secretsPostToolUse],
|
|
12941
|
+
SessionStart: [secretsSessionStart]
|
|
12598
12942
|
};
|
|
12599
12943
|
var DEFAULT_HANDLERS = mergeHandlers(
|
|
12600
12944
|
SECRETS_HANDLERS,
|
|
@@ -13012,7 +13356,7 @@ var hookSchemaProblems = (hooks, claude) => {
|
|
|
13012
13356
|
};
|
|
13013
13357
|
|
|
13014
13358
|
// src/doctor/hook-sources.ts
|
|
13015
|
-
import { join as
|
|
13359
|
+
import { join as join28 } from "node:path";
|
|
13016
13360
|
var entriesOf = (source, hooks, own) => Object.entries(asRecord(hooks)).flatMap(
|
|
13017
13361
|
([event, groups]) => asList(groups).flatMap((group) => {
|
|
13018
13362
|
const record2 = asRecord(group);
|
|
@@ -13025,7 +13369,7 @@ var projectHookFiles = (env) => readSettings(env).flatMap(({ scope: scope2, path
|
|
|
13025
13369
|
const source = PROJECT_SCOPES[scope2];
|
|
13026
13370
|
return source === void 0 || !("hooks" in settings) ? [] : [{ source, path, hooks: settings["hooks"] }];
|
|
13027
13371
|
});
|
|
13028
|
-
var pluginsDir = ({ env, runtime }) =>
|
|
13372
|
+
var pluginsDir = ({ env, runtime }) => join28(env["CLAUDE_CONFIG_DIR"] ?? join28(runtime.homeDir(), ".claude"), "plugins");
|
|
13029
13373
|
var installRecord = (value, root) => {
|
|
13030
13374
|
const records = Array.isArray(value) ? value.filter(isRecord) : isRecord(value) ? [value] : [];
|
|
13031
13375
|
return records.find((record2) => record2["projectPath"] === root) ?? records[0];
|
|
@@ -13036,13 +13380,13 @@ var readJson = (env, path) => {
|
|
|
13036
13380
|
return parsed?.ok === true ? parsed.value : void 0;
|
|
13037
13381
|
};
|
|
13038
13382
|
var installedPlugin = (env) => {
|
|
13039
|
-
const index = readJson(env, toAbsolutePath(
|
|
13383
|
+
const index = readJson(env, toAbsolutePath(join28(pluginsDir(env), "installed_plugins.json")));
|
|
13040
13384
|
const record2 = installRecord(asRecord(index?.["plugins"])[PLUGIN_ID], env.root);
|
|
13041
13385
|
const installPath = record2 === void 0 ? void 0 : optionalString(record2, "installPath");
|
|
13042
13386
|
if (record2 === void 0 || installPath === void 0) return void 0;
|
|
13043
13387
|
const root = toAbsolutePath(installPath);
|
|
13044
|
-
const manifest = readJson(env, toAbsolutePath(
|
|
13045
|
-
const hooksPath = toAbsolutePath(
|
|
13388
|
+
const manifest = readJson(env, toAbsolutePath(join28(root, ".claude-plugin", "plugin.json")));
|
|
13389
|
+
const hooksPath = toAbsolutePath(join28(root, "hooks", "hooks.json"));
|
|
13046
13390
|
const text6 = env.runtime.readText(hooksPath);
|
|
13047
13391
|
const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
|
|
13048
13392
|
return {
|
|
@@ -13052,10 +13396,10 @@ var installedPlugin = (env) => {
|
|
|
13052
13396
|
hooksText: text6.ok ? text6.value : void 0
|
|
13053
13397
|
};
|
|
13054
13398
|
};
|
|
13055
|
-
var
|
|
13399
|
+
var PRECEDENCE2 = ["managed", "local", "project", "user"];
|
|
13056
13400
|
var pluginEnabled = (env) => {
|
|
13057
13401
|
const all = readSettings(env);
|
|
13058
|
-
for (const scope2 of
|
|
13402
|
+
for (const scope2 of PRECEDENCE2) {
|
|
13059
13403
|
const value = asRecord(all.find((item) => item.scope === scope2)?.settings["enabledPlugins"])[PLUGIN_ID];
|
|
13060
13404
|
if (typeof value === "boolean") return { value, scope: scope2 };
|
|
13061
13405
|
}
|
|
@@ -13087,7 +13431,7 @@ var schemaFindings = (env, plugin, claude) => {
|
|
|
13087
13431
|
const files = [...projectHookFiles(env), ...plugin?.hooksFile === void 0 ? [] : [plugin.hooksFile]];
|
|
13088
13432
|
const invalidJson = [
|
|
13089
13433
|
...unreadableProjectFiles(env),
|
|
13090
|
-
...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(
|
|
13434
|
+
...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(join29(plugin.root, "hooks", "hooks.json"))] : []
|
|
13091
13435
|
];
|
|
13092
13436
|
return [
|
|
13093
13437
|
...invalidJson.flatMap((file) => schemaFinding(file, ["not valid JSON"], label2)),
|
|
@@ -13108,7 +13452,7 @@ var pluginFindings = (env, projectOwn) => {
|
|
|
13108
13452
|
];
|
|
13109
13453
|
};
|
|
13110
13454
|
var coverageFindings = (env, entries) => {
|
|
13111
|
-
const installed = env.runtime.stat(toAbsolutePath(
|
|
13455
|
+
const installed = env.runtime.stat(toAbsolutePath(join29(env.root, ...LOCK_FILE.split("/")))) !== void 0;
|
|
13112
13456
|
if (entries.length === 0 && !installed) {
|
|
13113
13457
|
return [finding("hooks.registered", "warn", "doctor.hooks.none", {}, "doctor.hooks.fix_none")];
|
|
13114
13458
|
}
|
|
@@ -13160,17 +13504,17 @@ var checkHooks = (env) => {
|
|
|
13160
13504
|
};
|
|
13161
13505
|
|
|
13162
13506
|
// src/doctor/instructions.ts
|
|
13163
|
-
import { join as
|
|
13507
|
+
import { join as join30 } from "node:path";
|
|
13164
13508
|
var INSTRUCTION_LINE_CEILING = 200;
|
|
13165
13509
|
var TOP_INSTRUCTIONS = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md"];
|
|
13166
13510
|
var RULES_DIR = ".claude/rules";
|
|
13167
13511
|
var SHOWN = 3;
|
|
13168
13512
|
var readProjectFile = (env, relative9) => {
|
|
13169
|
-
const text6 = env.runtime.readText(toAbsolutePath(
|
|
13513
|
+
const text6 = env.runtime.readText(toAbsolutePath(join30(env.root, ...relative9.split("/"))));
|
|
13170
13514
|
return text6.ok ? text6.value : void 0;
|
|
13171
13515
|
};
|
|
13172
13516
|
var instructionFiles = (env) => {
|
|
13173
|
-
const listed4 = env.runtime.listDir(toAbsolutePath(
|
|
13517
|
+
const listed4 = env.runtime.listDir(toAbsolutePath(join30(env.root, ...RULES_DIR.split("/"))));
|
|
13174
13518
|
const rules = listed4.ok ? listed4.value.filter((name) => name.endsWith(".md")).map((name) => `${RULES_DIR}/${name}`) : [];
|
|
13175
13519
|
return [...TOP_INSTRUCTIONS, ...rules].flatMap((path) => {
|
|
13176
13520
|
const text6 = readProjectFile(env, path);
|
|
@@ -13275,12 +13619,12 @@ var checkInstructions = (env) => {
|
|
|
13275
13619
|
};
|
|
13276
13620
|
|
|
13277
13621
|
// src/doctor/line-endings.ts
|
|
13278
|
-
import { join as
|
|
13622
|
+
import { join as join31 } from "node:path";
|
|
13279
13623
|
var LF_RULE = /^\*\s.*\beol=lf\b/m;
|
|
13280
13624
|
var checkLineEndings = ({ root, runtime }) => {
|
|
13281
13625
|
const autocrlf = runtime.git(["config", "--get", "core.autocrlf"], root);
|
|
13282
13626
|
if (!autocrlf.ok || autocrlf.stdout.trim().toLowerCase() !== "true") return [];
|
|
13283
|
-
const attributes = runtime.readText(toAbsolutePath(
|
|
13627
|
+
const attributes = runtime.readText(toAbsolutePath(join31(root, ".gitattributes")));
|
|
13284
13628
|
if (attributes.ok && LF_RULE.test(attributes.value)) return [];
|
|
13285
13629
|
return [
|
|
13286
13630
|
finding("git.autocrlf", "warn", "doctor.git.autocrlf", { file: ".gitattributes" }, "doctor.git.fix_autocrlf")
|
|
@@ -13288,7 +13632,7 @@ var checkLineEndings = ({ root, runtime }) => {
|
|
|
13288
13632
|
};
|
|
13289
13633
|
|
|
13290
13634
|
// src/doctor/memory.ts
|
|
13291
|
-
import { join as
|
|
13635
|
+
import { join as join32 } from "node:path";
|
|
13292
13636
|
var ceilingOf = (config) => {
|
|
13293
13637
|
const value = configAt(parseConfigYaml(config ?? ""), "memory", "maxLines");
|
|
13294
13638
|
const parsed = typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
|
|
@@ -13296,9 +13640,9 @@ var ceilingOf = (config) => {
|
|
|
13296
13640
|
};
|
|
13297
13641
|
var lineCount2 = (text6) => text6.replace(/\r?\n$/, "").split(/\r?\n/).length;
|
|
13298
13642
|
var checkAgentsFile = ({ root, runtime }) => {
|
|
13299
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
13643
|
+
const text6 = runtime.readText(toAbsolutePath(join32(root, AGENTS_FILE)));
|
|
13300
13644
|
if (!text6.ok) return [];
|
|
13301
|
-
const config = runtime.readText(toAbsolutePath(
|
|
13645
|
+
const config = runtime.readText(toAbsolutePath(join32(root, CONFIG_FILE)));
|
|
13302
13646
|
const max = ceilingOf(config.ok ? config.value : void 0);
|
|
13303
13647
|
const lines3 = lineCount2(text6.value);
|
|
13304
13648
|
const params2 = { file: AGENTS_FILE, lines: lines3, max };
|
|
@@ -13306,7 +13650,7 @@ var checkAgentsFile = ({ root, runtime }) => {
|
|
|
13306
13650
|
};
|
|
13307
13651
|
|
|
13308
13652
|
// src/doctor/project-files.ts
|
|
13309
|
-
import { join as
|
|
13653
|
+
import { join as join33 } from "node:path";
|
|
13310
13654
|
var variants = (base, extensions) => extensions.map((extension) => `${base}${extension}`);
|
|
13311
13655
|
var SCRIPT = [".js", ".cjs", ".mjs", ".ts", ".cts", ".mts"];
|
|
13312
13656
|
var DATA = ["", ".json", ".yml", ".yaml"];
|
|
@@ -13337,7 +13681,7 @@ var CONFIG_GROUPS = [
|
|
|
13337
13681
|
files: [...variants("commitlint.config", SCRIPT), ...variants(".commitlintrc", [...DATA, ".js"])]
|
|
13338
13682
|
}
|
|
13339
13683
|
];
|
|
13340
|
-
var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(
|
|
13684
|
+
var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(join33(env.root, file)))?.kind === "file");
|
|
13341
13685
|
var crowded = (env, groups, build) => groups.flatMap(({ name, files }) => {
|
|
13342
13686
|
const found = present(env, files);
|
|
13343
13687
|
return found.length > 1 ? [build(name, found.join(", "))] : [];
|
|
@@ -13361,6 +13705,22 @@ var checkProjectFiles = (env) => [
|
|
|
13361
13705
|
)
|
|
13362
13706
|
];
|
|
13363
13707
|
|
|
13708
|
+
// src/doctor/remotes.ts
|
|
13709
|
+
var checkRemoteCredentials = ({ root, runtime }) => {
|
|
13710
|
+
const remotes = remotesWithCredentials(runtime, root);
|
|
13711
|
+
if (remotes.length === 0) return [];
|
|
13712
|
+
const params2 = { remotes: remotes.join(", "), remote: remotes[0] ?? "origin" };
|
|
13713
|
+
return [
|
|
13714
|
+
finding(
|
|
13715
|
+
"secrets.remote_credential",
|
|
13716
|
+
"warn",
|
|
13717
|
+
"doctor.secrets.remote_credential",
|
|
13718
|
+
params2,
|
|
13719
|
+
"doctor.secrets.fix_remote_credential"
|
|
13720
|
+
)
|
|
13721
|
+
];
|
|
13722
|
+
};
|
|
13723
|
+
|
|
13364
13724
|
// src/doctor/run.ts
|
|
13365
13725
|
var DOCTOR_SCHEMA = "finitemonkey/doctor@1";
|
|
13366
13726
|
var CHECKS = [
|
|
@@ -13376,6 +13736,7 @@ var CHECKS = [
|
|
|
13376
13736
|
checkInstructions,
|
|
13377
13737
|
checkProjectFiles,
|
|
13378
13738
|
checkLineEndings,
|
|
13739
|
+
checkRemoteCredentials,
|
|
13379
13740
|
checkAgentsFile,
|
|
13380
13741
|
checkHooks
|
|
13381
13742
|
];
|
|
@@ -13403,7 +13764,7 @@ var toItem = (ctx, finding3) => ({
|
|
|
13403
13764
|
detail: ctx.t(finding3.messageKey, finding3.params),
|
|
13404
13765
|
...finding3.fixKey === void 0 ? {} : { fix: ctx.t(finding3.fixKey, finding3.params) }
|
|
13405
13766
|
});
|
|
13406
|
-
var
|
|
13767
|
+
var count3 = (items, status) => items.filter((item) => item.status === status).length;
|
|
13407
13768
|
var hookItems = (ctx, findings) => findings.flatMap(
|
|
13408
13769
|
(finding3) => finding3.hook === void 0 ? [] : [
|
|
13409
13770
|
{
|
|
@@ -13419,7 +13780,7 @@ var buildReport2 = (ctx, findings) => {
|
|
|
13419
13780
|
schema: DOCTOR_SCHEMA,
|
|
13420
13781
|
checks,
|
|
13421
13782
|
hooks: hookItems(ctx, findings),
|
|
13422
|
-
summary: { ok:
|
|
13783
|
+
summary: { ok: count3(checks, "ok"), warn: count3(checks, "warn"), fail: count3(checks, "fail") }
|
|
13423
13784
|
};
|
|
13424
13785
|
};
|
|
13425
13786
|
|
|
@@ -13736,7 +14097,7 @@ var resultOf = (ctx, checked) => {
|
|
|
13736
14097
|
const problem4 = gitProblem("merge_unanalyzable", "git.error.merge_unanalyzable", detail, "git status");
|
|
13737
14098
|
return failureFrom(ctx.t, { ...problem4, exitCode: ExitCode.Usage, data }, human2);
|
|
13738
14099
|
}
|
|
13739
|
-
const lost = checked.reduce((
|
|
14100
|
+
const lost = checked.reduce((count4, item) => count4 + item.report.findings.length, 0);
|
|
13740
14101
|
if (lost === 0) return success(data, human2);
|
|
13741
14102
|
return failureFrom(
|
|
13742
14103
|
ctx.t,
|
|
@@ -13903,7 +14264,7 @@ var rescueCommand = {
|
|
|
13903
14264
|
};
|
|
13904
14265
|
|
|
13905
14266
|
// src/git/undo.ts
|
|
13906
|
-
import { basename as basename3, dirname as dirname8, join as
|
|
14267
|
+
import { basename as basename3, dirname as dirname8, join as join34 } from "node:path";
|
|
13907
14268
|
var INDEX_SUBJECT = "finitemonkey index:";
|
|
13908
14269
|
var gitLine = (runtime, repo, args) => {
|
|
13909
14270
|
const result = runtime.git(args, repo.root);
|
|
@@ -13946,7 +14307,7 @@ var findSnapshot = (views, wanted) => {
|
|
|
13946
14307
|
};
|
|
13947
14308
|
var stampOf2 = (iso) => iso.replace(/[:.]/g, "-");
|
|
13948
14309
|
var worktreePath = (runtime, repo) => {
|
|
13949
|
-
const base =
|
|
14310
|
+
const base = join34(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
|
|
13950
14311
|
return toAbsolutePath(runtime.stat(toAbsolutePath(base)) === void 0 ? base : `${base}-2`);
|
|
13951
14312
|
};
|
|
13952
14313
|
var intoWorktree = (runtime, repo, view2) => {
|
|
@@ -14134,19 +14495,6 @@ var helpCommand = {
|
|
|
14134
14495
|
)
|
|
14135
14496
|
};
|
|
14136
14497
|
|
|
14137
|
-
// src/install/plugin-yield.ts
|
|
14138
|
-
import { join as join34 } from "node:path";
|
|
14139
|
-
var PLUGIN_VIA = "plugin";
|
|
14140
|
-
var YIELD_STDOUT = `${JSON.stringify({ continue: true })}
|
|
14141
|
-
`;
|
|
14142
|
-
var projectOwnsHooks = (ctx) => {
|
|
14143
|
-
const root = ctx.env["CLAUDE_PROJECT_DIR"] ?? ctx.cwd;
|
|
14144
|
-
const text6 = ctx.runtime.readText(toAbsolutePath(join34(root, ...SETTINGS_FILE.split("/"))));
|
|
14145
|
-
if (!text6.ok) return false;
|
|
14146
|
-
const parsed = parseJsonObject(text6.value);
|
|
14147
|
-
return parsed.ok && ownHandlersIn(parsed.value) > 0;
|
|
14148
|
-
};
|
|
14149
|
-
|
|
14150
14498
|
// src/commands/hook.ts
|
|
14151
14499
|
var hookCommand = {
|
|
14152
14500
|
path: ["hook"],
|
|
@@ -14280,17 +14628,17 @@ var rulesFor = (locale) => {
|
|
|
14280
14628
|
var branchFor = (node, value, locale) => {
|
|
14281
14629
|
const { branches } = node;
|
|
14282
14630
|
if (node.kind === "select") return branches[String(value)] ?? branches["other"];
|
|
14283
|
-
const
|
|
14284
|
-
if (!Number.isFinite(
|
|
14285
|
-
return branches[`=${String(
|
|
14631
|
+
const count4 = typeof value === "number" ? value : Number(value);
|
|
14632
|
+
if (!Number.isFinite(count4)) return branches["other"];
|
|
14633
|
+
return branches[`=${String(count4)}`] ?? branches[rulesFor(locale).select(count4)] ?? branches["other"];
|
|
14286
14634
|
};
|
|
14287
|
-
var render = (nodes, params2, locale,
|
|
14635
|
+
var render = (nodes, params2, locale, count4) => nodes.map((node) => {
|
|
14288
14636
|
if (node.kind === "text") return node.text;
|
|
14289
|
-
if (node.kind === "count") return
|
|
14637
|
+
if (node.kind === "count") return count4 === void 0 ? "#" : String(count4);
|
|
14290
14638
|
const value = params2[node.name];
|
|
14291
14639
|
if (node.kind === "arg") return value === void 0 ? `{${node.name}}` : String(value);
|
|
14292
14640
|
const branch = branchFor(node, value, locale) ?? [];
|
|
14293
|
-
return render(branch, params2, locale, node.kind === "plural" ? value :
|
|
14641
|
+
return render(branch, params2, locale, node.kind === "plural" ? value : count4);
|
|
14294
14642
|
}).join("");
|
|
14295
14643
|
var formatMessage = (source, params2, locale) => render(parseMessage(source), params2, locale, void 0);
|
|
14296
14644
|
var walk = (nodes, visit2) => {
|
|
@@ -14320,7 +14668,7 @@ var choicesOf = (source) => {
|
|
|
14320
14668
|
var requiredPluralBranches = (locale) => {
|
|
14321
14669
|
const rules = rulesFor(locale);
|
|
14322
14670
|
const seen = /* @__PURE__ */ new Set(["other"]);
|
|
14323
|
-
for (let
|
|
14671
|
+
for (let count4 = 0; count4 <= 1e3; count4 += 1) seen.add(rules.select(count4));
|
|
14324
14672
|
return [...seen].sort();
|
|
14325
14673
|
};
|
|
14326
14674
|
|
|
@@ -14633,6 +14981,7 @@ var lookBack = (runtime, root, env) => {
|
|
|
14633
14981
|
|
|
14634
14982
|
// src/install/diff.ts
|
|
14635
14983
|
var MAX_DIFF_LINES = 120;
|
|
14984
|
+
var MAX_LCS_CELLS = 4e6;
|
|
14636
14985
|
var table = (a, b) => {
|
|
14637
14986
|
const rows = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
14638
14987
|
for (let i = a.length - 1; i >= 0; i -= 1) {
|
|
@@ -14667,8 +15016,27 @@ var walk2 = (a, b) => {
|
|
|
14667
15016
|
return ops;
|
|
14668
15017
|
};
|
|
14669
15018
|
var splitLines = (text6) => text6 === "" ? [] : text6.replace(/\r?\n$/, "").split(/\r?\n/);
|
|
15019
|
+
var sharedPrefix = (a, b) => {
|
|
15020
|
+
let count4 = 0;
|
|
15021
|
+
while (count4 < a.length && count4 < b.length && a[count4] === b[count4]) count4 += 1;
|
|
15022
|
+
return count4;
|
|
15023
|
+
};
|
|
15024
|
+
var sharedSuffix = (a, b, prefix) => {
|
|
15025
|
+
let count4 = 0;
|
|
15026
|
+
while (count4 < a.length - prefix && count4 < b.length - prefix && a[a.length - 1 - count4] === b[b.length - 1 - count4])
|
|
15027
|
+
count4 += 1;
|
|
15028
|
+
return count4;
|
|
15029
|
+
};
|
|
15030
|
+
var changedOps = (a, b) => {
|
|
15031
|
+
const prefix = sharedPrefix(a, b);
|
|
15032
|
+
const suffix = sharedSuffix(a, b, prefix);
|
|
15033
|
+
const left = a.slice(prefix, a.length - suffix);
|
|
15034
|
+
const right = b.slice(prefix, b.length - suffix);
|
|
15035
|
+
if (left.length * right.length <= MAX_LCS_CELLS) return walk2(left, right).filter(([op]) => op !== "=");
|
|
15036
|
+
return [...left.map((line2) => ["-", line2]), ...right.map((line2) => ["+", line2])];
|
|
15037
|
+
};
|
|
14670
15038
|
var lineDiff = (path, before, after) => {
|
|
14671
|
-
const changes =
|
|
15039
|
+
const changes = changedOps(splitLines(before), splitLines(after));
|
|
14672
15040
|
if (changes.length === 0) return [];
|
|
14673
15041
|
const lines3 = changes.map(([op, line2]) => `${op} ${line2}`);
|
|
14674
15042
|
const shown3 = lines3.slice(0, MAX_DIFF_LINES);
|
|
@@ -15241,7 +15609,7 @@ var humanOf2 = (ctx, data) => [
|
|
|
15241
15609
|
];
|
|
15242
15610
|
var resultOf3 = (ctx, data) => {
|
|
15243
15611
|
const human2 = humanOf2(ctx, data);
|
|
15244
|
-
const failing = data.
|
|
15612
|
+
const failing = data.findings.filter((item) => item.status !== "known" && item.severity !== "warning").length;
|
|
15245
15613
|
if (failing === 0) return success(data, human2);
|
|
15246
15614
|
return failureFrom(
|
|
15247
15615
|
ctx.t,
|