finitemonkey 0.1.0-next.14 → 0.1.0-next.16
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/README.es.md +1 -1
- package/README.md +1 -1
- package/README.pt-BR.md +1 -1
- package/dist/finitemonkey.mjs +1270 -463
- package/package.json +2 -1
package/dist/finitemonkey.mjs
CHANGED
|
@@ -40,8 +40,8 @@ var parseJsonObject = (text6) => {
|
|
|
40
40
|
if (!parsed.ok) return parsed;
|
|
41
41
|
return isRecord(parsed.value) ? accept(parsed.value) : reject("expected a JSON object");
|
|
42
42
|
};
|
|
43
|
-
var optionalString = (
|
|
44
|
-
const value =
|
|
43
|
+
var optionalString = (record3, key) => {
|
|
44
|
+
const value = record3[key];
|
|
45
45
|
return typeof value === "string" ? value : void 0;
|
|
46
46
|
};
|
|
47
47
|
|
|
@@ -544,11 +544,11 @@ var parseEntries = (value) => {
|
|
|
544
544
|
const entries = value.map(parseEntry);
|
|
545
545
|
return entries.every((entry2) => entry2 !== void 0) ? entries : void 0;
|
|
546
546
|
};
|
|
547
|
-
var fromRecord = (
|
|
548
|
-
const { schema, version, preset, installedAt, updatedAt } =
|
|
547
|
+
var fromRecord = (record3) => {
|
|
548
|
+
const { schema, version, preset, installedAt, updatedAt } = record3;
|
|
549
549
|
const known2 = PRESETS.find((item) => item === preset);
|
|
550
|
-
const entries = parseEntries(
|
|
551
|
-
const createdDirs = strings(
|
|
550
|
+
const entries = parseEntries(record3["entries"]);
|
|
551
|
+
const createdDirs = strings(record3["createdDirs"]);
|
|
552
552
|
if (schema !== LOCK_SCHEMA) return reject(`schema is not ${LOCK_SCHEMA}`);
|
|
553
553
|
if (typeof version !== "string" || typeof installedAt !== "string" || typeof updatedAt !== "string") {
|
|
554
554
|
return reject("version, installedAt and updatedAt must be text");
|
|
@@ -565,6 +565,134 @@ var parseLock = (text6) => {
|
|
|
565
565
|
var renderLock = (lock) => `${JSON.stringify(lock, null, 2)}
|
|
566
566
|
`;
|
|
567
567
|
|
|
568
|
+
// src/policy/policy.ts
|
|
569
|
+
import { join as join3 } from "node:path";
|
|
570
|
+
|
|
571
|
+
// src/git/project-config.ts
|
|
572
|
+
import { join as join2 } from "node:path";
|
|
573
|
+
var CONFIG_FILE2 = ".finitemonkey/config.yml";
|
|
574
|
+
var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
|
|
575
|
+
var stripComment = (line2) => {
|
|
576
|
+
let quote;
|
|
577
|
+
for (let index = 0; index < line2.length; index += 1) {
|
|
578
|
+
const char = line2.charAt(index);
|
|
579
|
+
if (quote === void 0 && (char === '"' || char === "'")) quote = char;
|
|
580
|
+
else if (char === quote) quote = void 0;
|
|
581
|
+
else if (quote === void 0 && char === "#" && (index === 0 || /\s/.test(line2.charAt(index - 1)))) {
|
|
582
|
+
return line2.slice(0, index);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return line2;
|
|
586
|
+
};
|
|
587
|
+
var flowList = (value) => {
|
|
588
|
+
const trimmed = value.trim();
|
|
589
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
|
|
590
|
+
return trimmed.slice(1, -1).split(",").map(unquote).filter((item) => item !== "");
|
|
591
|
+
};
|
|
592
|
+
var keyValue = (body2) => {
|
|
593
|
+
const match = /^("[^"]*"|'[^']*'|[^:]+?)\s*:(?:\s+(.*))?$/.exec(body2);
|
|
594
|
+
if (match === null) return void 0;
|
|
595
|
+
return { key: unquote(match[1] ?? ""), value: match[2] ?? "" };
|
|
596
|
+
};
|
|
597
|
+
var addItem = (frame, item) => {
|
|
598
|
+
if (frame.key === void 0) return;
|
|
599
|
+
const parent = frame.tree[frame.key];
|
|
600
|
+
if (Array.isArray(parent)) parent.push(item);
|
|
601
|
+
else frame.tree[frame.key] = [item];
|
|
602
|
+
};
|
|
603
|
+
var placeLine = (stack, indent, body2) => {
|
|
604
|
+
while (stack.length > 1 && (stack[stack.length - 1]?.indent ?? 0) >= indent && !body2.startsWith("- ")) stack.pop();
|
|
605
|
+
const top = stack[stack.length - 1];
|
|
606
|
+
if (top === void 0) return;
|
|
607
|
+
if (body2.startsWith("- ")) {
|
|
608
|
+
addItem(top, unquote(body2.slice(2)));
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const pair = keyValue(body2);
|
|
612
|
+
if (pair === void 0) return;
|
|
613
|
+
const parentTree = top.key === void 0 ? top.tree : ensureTree(top.tree, top.key);
|
|
614
|
+
if (pair.value === "") {
|
|
615
|
+
stack.push({ indent, tree: parentTree, key: pair.key });
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
parentTree[pair.key] = flowList(pair.value) ?? unquote(pair.value);
|
|
619
|
+
};
|
|
620
|
+
var ensureTree = (tree, key) => {
|
|
621
|
+
const existing = tree[key];
|
|
622
|
+
if (existing !== void 0 && typeof existing !== "string" && !Array.isArray(existing)) return existing;
|
|
623
|
+
const fresh3 = {};
|
|
624
|
+
tree[key] = fresh3;
|
|
625
|
+
return fresh3;
|
|
626
|
+
};
|
|
627
|
+
var parseConfigYaml = (text6) => {
|
|
628
|
+
const root = {};
|
|
629
|
+
const stack = [{ indent: -1, tree: root, key: void 0 }];
|
|
630
|
+
for (const raw of text6.split(/\r?\n/)) {
|
|
631
|
+
const line2 = stripComment(raw).trimEnd();
|
|
632
|
+
if (line2.trim() === "" || line2.trim() === "---") continue;
|
|
633
|
+
const indent = line2.length - line2.trimStart().length;
|
|
634
|
+
placeLine(stack, indent, line2.trim());
|
|
635
|
+
}
|
|
636
|
+
return root;
|
|
637
|
+
};
|
|
638
|
+
var isTree = (node) => typeof node === "object" && !Array.isArray(node);
|
|
639
|
+
var configAt = (tree, ...path) => path.reduce((node, key) => isTree(node) ? node[key] : void 0, tree);
|
|
640
|
+
var configList = (tree, ...path) => {
|
|
641
|
+
const node = configAt(tree, ...path);
|
|
642
|
+
if (typeof node === "string") return node === "" ? [] : [node];
|
|
643
|
+
return Array.isArray(node) ? node.filter((item) => typeof item === "string") : [];
|
|
644
|
+
};
|
|
645
|
+
var configMap = (tree, ...path) => {
|
|
646
|
+
const node = configAt(tree, ...path);
|
|
647
|
+
if (!isTree(node)) return {};
|
|
648
|
+
return Object.fromEntries(
|
|
649
|
+
Object.entries(node).filter((entry2) => typeof entry2[1] === "string")
|
|
650
|
+
);
|
|
651
|
+
};
|
|
652
|
+
var loadProjectConfig = (runtime, root) => {
|
|
653
|
+
const text6 = runtime.readText(toAbsolutePath(join2(root, CONFIG_FILE2)));
|
|
654
|
+
return text6.ok ? parseConfigYaml(text6.value) : {};
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
// src/policy/policy.ts
|
|
658
|
+
var POLICY_FILE = ".finitemonkey/policy.yml";
|
|
659
|
+
var GUARD_MODES = ["off", "shadow", "active"];
|
|
660
|
+
var LEVELS = ["light", "standard", "strict"];
|
|
661
|
+
var SEVERITIES = ["low", "medium", "high"];
|
|
662
|
+
var DEFAULT_PROMOTE_AFTER = 20;
|
|
663
|
+
var OPTIONAL_GUARDS = ["prose"];
|
|
664
|
+
var DEFAULT_POLICY = {
|
|
665
|
+
exists: false,
|
|
666
|
+
level: "standard",
|
|
667
|
+
severity: "medium",
|
|
668
|
+
promoteAfter: DEFAULT_PROMOTE_AFTER,
|
|
669
|
+
guards: {}
|
|
670
|
+
};
|
|
671
|
+
var pick = (options, value, fallback) => options.find((option) => option === value) ?? fallback;
|
|
672
|
+
var parsePolicy = (text6) => {
|
|
673
|
+
const tree = parseConfigYaml(text6);
|
|
674
|
+
const raw = configAt(tree, "promoteAfter");
|
|
675
|
+
const promoteAfter = typeof raw === "string" ? Number.parseInt(raw, 10) : Number.NaN;
|
|
676
|
+
const guards = Object.fromEntries(
|
|
677
|
+
Object.entries(configMap(tree, "guards")).flatMap(([id, mode]) => {
|
|
678
|
+
const known2 = GUARD_MODES.find((option) => option === mode);
|
|
679
|
+
return known2 === void 0 ? [] : [[id, known2]];
|
|
680
|
+
})
|
|
681
|
+
);
|
|
682
|
+
return {
|
|
683
|
+
exists: true,
|
|
684
|
+
level: pick(LEVELS, configAt(tree, "level"), DEFAULT_POLICY.level),
|
|
685
|
+
severity: pick(SEVERITIES, configAt(tree, "severity"), DEFAULT_POLICY.severity),
|
|
686
|
+
promoteAfter: Number.isInteger(promoteAfter) && promoteAfter > 0 ? promoteAfter : DEFAULT_PROMOTE_AFTER,
|
|
687
|
+
guards
|
|
688
|
+
};
|
|
689
|
+
};
|
|
690
|
+
var policyPath = (root) => toAbsolutePath(join3(root, POLICY_FILE));
|
|
691
|
+
var loadPolicy = (runtime, root) => {
|
|
692
|
+
const text6 = runtime.readText(policyPath(root));
|
|
693
|
+
return text6.ok ? parsePolicy(text6.value) : DEFAULT_POLICY;
|
|
694
|
+
};
|
|
695
|
+
|
|
568
696
|
// src/install/managed-block.ts
|
|
569
697
|
var lineBreak = (text6) => text6.includes("\r\n") ? "\r\n" : "\n";
|
|
570
698
|
var blockRange = (lines3, markers) => {
|
|
@@ -594,6 +722,11 @@ var withoutManagedBlock = (text6, markers) => {
|
|
|
594
722
|
// src/install/templates/en.ts
|
|
595
723
|
var en = {
|
|
596
724
|
configHeader: "Finite Monkey project settings. Commit this file. `finitemonkey update` keeps your edits.",
|
|
725
|
+
policyHeader: [
|
|
726
|
+
"Guard policy for this project. Modes: off, shadow (record what it would block), active.",
|
|
727
|
+
"The guards for secrets, production, lost git work and false done stay active whatever this file says.",
|
|
728
|
+
"A guard missing from this list starts in shadow and is promoted after promoteAfter firings without a false positive."
|
|
729
|
+
],
|
|
597
730
|
agentsBlock: ({ gate, doctor }) => [
|
|
598
731
|
"## Finite Monkey",
|
|
599
732
|
"",
|
|
@@ -616,6 +749,11 @@ var en = {
|
|
|
616
749
|
// src/install/templates/es.ts
|
|
617
750
|
var es = {
|
|
618
751
|
configHeader: "Configuración de Finite Monkey en este proyecto. Versiona este archivo. `finitemonkey update` conserva tus cambios.",
|
|
752
|
+
policyHeader: [
|
|
753
|
+
"Política de los guards de este proyecto. Modos: off, shadow (registra lo que bloquearía), active.",
|
|
754
|
+
"Los guards de secretos, producción, trabajo de git perdido y falso terminado siguen activos diga lo que diga este archivo.",
|
|
755
|
+
"Un guard que no está en esta lista empieza en shadow y se promueve tras promoteAfter disparos sin falso positivo."
|
|
756
|
+
],
|
|
619
757
|
agentsBlock: ({ gate, doctor }) => [
|
|
620
758
|
"## Finite Monkey",
|
|
621
759
|
"",
|
|
@@ -638,6 +776,11 @@ var es = {
|
|
|
638
776
|
// src/install/templates/pt-BR.ts
|
|
639
777
|
var ptBR = {
|
|
640
778
|
configHeader: "Configuração do Finite Monkey neste projeto. Versione este arquivo. O `finitemonkey update` mantém suas edições.",
|
|
779
|
+
policyHeader: [
|
|
780
|
+
"Política dos guards deste projeto. Modos: off, shadow (registra o que bloquearia), active.",
|
|
781
|
+
"Os guards de segredos, produção, trabalho de git perdido e falso pronto ficam ativos, diga o que disser este arquivo.",
|
|
782
|
+
"Um guard que não está nesta lista começa em shadow e é promovido depois de promoteAfter disparos sem falso positivo."
|
|
783
|
+
],
|
|
641
784
|
agentsBlock: ({ gate, doctor }) => [
|
|
642
785
|
"## Finite Monkey",
|
|
643
786
|
"",
|
|
@@ -856,7 +999,7 @@ var DENY_FLOOR = {
|
|
|
856
999
|
var DENY_RULES = [...DENY_FLOOR.secrets, ...DENY_FLOOR.git, ...DENY_FLOOR.proof];
|
|
857
1000
|
|
|
858
1001
|
// src/install/detect.ts
|
|
859
|
-
import { join as
|
|
1002
|
+
import { join as join4 } from "node:path";
|
|
860
1003
|
|
|
861
1004
|
// src/install/json-text.ts
|
|
862
1005
|
var DEFAULT_STYLE = { indent: " ", finalNewline: true };
|
|
@@ -868,11 +1011,11 @@ var renderJson = (value, style) => `${JSON.stringify(value, null, style.indent)}
|
|
|
868
1011
|
var parseSettings = (text6) => text6 === void 0 || text6.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text6);
|
|
869
1012
|
var asList = (value) => Array.isArray(value) ? value : [];
|
|
870
1013
|
var asRecord = (value) => isRecord(value) ? value : {};
|
|
871
|
-
var setKey = (
|
|
872
|
-
if (!(key in
|
|
1014
|
+
var setKey = (record3, key, value) => {
|
|
1015
|
+
if (!(key in record3)) return value === void 0 ? record3 : { ...record3, [key]: value };
|
|
873
1016
|
return Object.fromEntries(
|
|
874
|
-
Object.entries(
|
|
875
|
-
if (name !== key) return [[name,
|
|
1017
|
+
Object.entries(record3).flatMap(([name, current2]) => {
|
|
1018
|
+
if (name !== key) return [[name, current2]];
|
|
876
1019
|
return value === void 0 ? [] : [[name, value]];
|
|
877
1020
|
})
|
|
878
1021
|
);
|
|
@@ -949,7 +1092,7 @@ var scopeAuthorities = (text6, source) => {
|
|
|
949
1092
|
var userSettingsText = (project, env) => {
|
|
950
1093
|
const home = env["CLAUDE_CONFIG_DIR"] ?? env["HOME"] ?? env["USERPROFILE"];
|
|
951
1094
|
if (home === void 0) return void 0;
|
|
952
|
-
const file = env["CLAUDE_CONFIG_DIR"] === void 0 ?
|
|
1095
|
+
const file = env["CLAUDE_CONFIG_DIR"] === void 0 ? join4(home, ".claude", "settings.json") : join4(home, "settings.json");
|
|
953
1096
|
const text6 = project.runtime.readText(toAbsolutePath(file));
|
|
954
1097
|
return text6.ok ? text6.value : void 0;
|
|
955
1098
|
};
|
|
@@ -1041,6 +1184,18 @@ var applyMigrations = (items, previous, version, migrations) => {
|
|
|
1041
1184
|
// src/install/migrations/index.ts
|
|
1042
1185
|
var MIGRATIONS = [];
|
|
1043
1186
|
|
|
1187
|
+
// src/install/policy-template.ts
|
|
1188
|
+
var policyTemplate = (lang) => [
|
|
1189
|
+
`# ${GENERATED_MARKER}`,
|
|
1190
|
+
...TEMPLATES[lang].policyHeader.map((line2) => `# ${line2}`),
|
|
1191
|
+
"level: standard",
|
|
1192
|
+
"severity: medium",
|
|
1193
|
+
`promoteAfter: ${String(DEFAULT_PROMOTE_AFTER)}`,
|
|
1194
|
+
"guards:",
|
|
1195
|
+
...OPTIONAL_GUARDS.map((id) => ` ${id}: active`),
|
|
1196
|
+
""
|
|
1197
|
+
].join("\n");
|
|
1198
|
+
|
|
1044
1199
|
// src/install/settings-merge.ts
|
|
1045
1200
|
var SETTINGS_SCHEMA_URL = "https://json.schemastore.org/claude-code-settings.json";
|
|
1046
1201
|
var ATTRIBUTION_OFF = { commit: "", pr: "", sessionUrl: false };
|
|
@@ -1095,9 +1250,9 @@ var withPermissions = (settings, lists) => {
|
|
|
1095
1250
|
var missing = (wanted, existing) => wanted.filter((rule) => !existing.includes(rule));
|
|
1096
1251
|
var withoutAdded = (existing, added) => existing.filter((rule) => typeof rule !== "string" || !added.includes(rule));
|
|
1097
1252
|
var ownHookMatcher = (recorded) => (handler) => isOwnHandler(handler) || recorded.some((own) => JSON.stringify(own) === JSON.stringify(handler));
|
|
1098
|
-
var mergeSettings = (
|
|
1099
|
-
const schema = Object.keys(
|
|
1100
|
-
const base = schema ? { $schema: SETTINGS_SCHEMA_URL } :
|
|
1253
|
+
var mergeSettings = (current2, wanted, recorded) => {
|
|
1254
|
+
const schema = Object.keys(current2).length === 0;
|
|
1255
|
+
const base = schema ? { $schema: SETTINGS_SCHEMA_URL } : current2;
|
|
1101
1256
|
const hooks = mergeHooks(asRecord(base["hooks"]), ownHookMatcher(recorded), wanted.hooks);
|
|
1102
1257
|
const permissions = asRecord(base["permissions"]);
|
|
1103
1258
|
const existingDeny = asList(permissions["deny"]);
|
|
@@ -1122,10 +1277,10 @@ var mergeSettings = (current, wanted, recorded) => {
|
|
|
1122
1277
|
return { settings, added: language === void 0 ? added : { ...added, language } };
|
|
1123
1278
|
};
|
|
1124
1279
|
var sameAttribution = (value) => JSON.stringify(value) === JSON.stringify(ATTRIBUTION_OFF);
|
|
1125
|
-
var unmergeSettings = (
|
|
1126
|
-
const hooks = mergeHooks(asRecord(
|
|
1127
|
-
const permissions = asRecord(
|
|
1128
|
-
let settings = withPermissions(withHooks(
|
|
1280
|
+
var unmergeSettings = (current2, added, recorded) => {
|
|
1281
|
+
const hooks = mergeHooks(asRecord(current2["hooks"]), ownHookMatcher(recorded), {});
|
|
1282
|
+
const permissions = asRecord(current2["permissions"]);
|
|
1283
|
+
let settings = withPermissions(withHooks(current2, hooks), {
|
|
1129
1284
|
deny: withoutAdded(asList(permissions["deny"]), added.deny),
|
|
1130
1285
|
ask: withoutAdded(asList(permissions["ask"]), added.ask),
|
|
1131
1286
|
blockReads: added.blockReads && permissions[BLOCK_READS] === true ? void 0 : permissions[BLOCK_READS]
|
|
@@ -1202,8 +1357,8 @@ var wantedHooks = (detection) => Object.fromEntries(
|
|
|
1202
1357
|
);
|
|
1203
1358
|
var settingsItem = (project, detection, input, previous) => {
|
|
1204
1359
|
const before = project.read(SETTINGS_FILE);
|
|
1205
|
-
const
|
|
1206
|
-
if (!
|
|
1360
|
+
const current2 = parseSettings(before);
|
|
1361
|
+
if (!current2.ok) return reject({ kind: "settings", detail: current2.error });
|
|
1207
1362
|
const hooks = wantedHooks(detection);
|
|
1208
1363
|
const wanted = {
|
|
1209
1364
|
hooks,
|
|
@@ -1213,7 +1368,7 @@ var settingsItem = (project, detection, input, previous) => {
|
|
|
1213
1368
|
marketplace: input.marketplace,
|
|
1214
1369
|
language: input.claudeLanguage
|
|
1215
1370
|
};
|
|
1216
|
-
const merged = mergeSettings(
|
|
1371
|
+
const merged = mergeSettings(current2.value, wanted, previous?.handlers ?? []);
|
|
1217
1372
|
const after = renderJson(merged.settings, before === void 0 ? DEFAULT_STYLE : styleOf(before));
|
|
1218
1373
|
return accept({
|
|
1219
1374
|
path: SETTINGS_FILE,
|
|
@@ -1254,7 +1409,8 @@ var generatedItem = (project, file, previous) => {
|
|
|
1254
1409
|
var generatedFiles = (input, runner, vendor) => {
|
|
1255
1410
|
const own = [
|
|
1256
1411
|
{ path: CONFIG_FILE, template: configTemplate(input.preset, input.lang), isOurs: hasMarker },
|
|
1257
|
-
{ path: VERIFY_SKILL, template: verifySkillTemplate(runner, input.lang), isOurs: isOwnSkill }
|
|
1412
|
+
{ path: VERIFY_SKILL, template: verifySkillTemplate(runner, input.lang), isOurs: isOwnSkill },
|
|
1413
|
+
{ path: POLICY_FILE, template: policyTemplate(input.lang), isOurs: hasMarker }
|
|
1258
1414
|
];
|
|
1259
1415
|
if (!vendor) return accept(own);
|
|
1260
1416
|
if (input.bundle === void 0) return reject({ kind: "bundle", detail: "the running finitemonkey is not a bundle" });
|
|
@@ -1428,9 +1584,9 @@ var detectLang = (flag2, env, settings = {}) => {
|
|
|
1428
1584
|
};
|
|
1429
1585
|
|
|
1430
1586
|
// src/install/project.ts
|
|
1431
|
-
import { dirname as dirname4, join as
|
|
1587
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
1432
1588
|
var projectAt = (root, runtime) => {
|
|
1433
|
-
const abs = (relative9) => toAbsolutePath(
|
|
1589
|
+
const abs = (relative9) => toAbsolutePath(join5(root, ...relative9.split("/")));
|
|
1434
1590
|
return {
|
|
1435
1591
|
root,
|
|
1436
1592
|
runtime,
|
|
@@ -1444,12 +1600,12 @@ var projectAt = (root, runtime) => {
|
|
|
1444
1600
|
};
|
|
1445
1601
|
};
|
|
1446
1602
|
var findGitRoot = (start, runtime) => {
|
|
1447
|
-
let
|
|
1603
|
+
let current2 = start;
|
|
1448
1604
|
for (; ; ) {
|
|
1449
|
-
if (runtime.stat(toAbsolutePath(
|
|
1450
|
-
const parent = dirname4(
|
|
1451
|
-
if (parent ===
|
|
1452
|
-
|
|
1605
|
+
if (runtime.stat(toAbsolutePath(join5(current2, ".git"))) !== void 0) return toAbsolutePath(current2);
|
|
1606
|
+
const parent = dirname4(current2);
|
|
1607
|
+
if (parent === current2) return void 0;
|
|
1608
|
+
current2 = parent;
|
|
1453
1609
|
}
|
|
1454
1610
|
};
|
|
1455
1611
|
|
|
@@ -1547,7 +1703,7 @@ var applyFixes = (ctx, write3) => {
|
|
|
1547
1703
|
};
|
|
1548
1704
|
|
|
1549
1705
|
// src/doctor/report-file.ts
|
|
1550
|
-
import { join as
|
|
1706
|
+
import { join as join7 } from "node:path";
|
|
1551
1707
|
|
|
1552
1708
|
// src/evidence/log.ts
|
|
1553
1709
|
var logEvent = (runtime, paths, event, data) => runtime.appendText(paths.log, `${JSON.stringify({ at: runtime.now(), event, ...data })}
|
|
@@ -1620,8 +1776,8 @@ var shannonEntropy = (text6) => {
|
|
|
1620
1776
|
const counts = /* @__PURE__ */ new Map();
|
|
1621
1777
|
for (const char of text6) counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
1622
1778
|
let entropy = 0;
|
|
1623
|
-
for (const
|
|
1624
|
-
const p =
|
|
1779
|
+
for (const count4 of counts.values()) {
|
|
1780
|
+
const p = count4 / text6.length;
|
|
1625
1781
|
entropy -= p * Math.log2(p);
|
|
1626
1782
|
}
|
|
1627
1783
|
return entropy;
|
|
@@ -1670,6 +1826,35 @@ var defineRule = (spec2) => ({
|
|
|
1670
1826
|
skipTestPaths: spec2.skipTestPaths ?? false,
|
|
1671
1827
|
placeholders: spec2.placeholders ?? true
|
|
1672
1828
|
});
|
|
1829
|
+
var isWarning = (finding3) => finding3.severity === "warning";
|
|
1830
|
+
var blocking = (findings) => findings.filter((finding3) => !isWarning(finding3));
|
|
1831
|
+
|
|
1832
|
+
// src/secrets/rules/exposure.ts
|
|
1833
|
+
var ARGUMENT_CONTEXT = String.raw`(?:^|\n)[ \t-]*(?:command|entrypoint|CMD|ENTRYPOINT|RUN|ExecStart(?:Pre|Post)?|ExecReload)\b[^\n]*?`;
|
|
1834
|
+
var SECRET_FLAG = String.raw`(?<=[\s"'\[,])--?[A-Za-z0-9_-]*(?:password|passwd|pass|secret|token|requirepass|api-?key)`;
|
|
1835
|
+
var EXPOSURE_RULES = [
|
|
1836
|
+
defineRule({
|
|
1837
|
+
id: "secret-in-process-args",
|
|
1838
|
+
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)",
|
|
1839
|
+
kind: "practice",
|
|
1840
|
+
severity: "warning",
|
|
1841
|
+
keywords: ["command", "entrypoint", "cmd", "run", "execstart", "execreload"],
|
|
1842
|
+
source: String.raw`${ARGUMENT_CONTEXT}${SECRET_FLAG}(?:=["']?|["'][ \t]*,[ \t]*["']|[ \t]+["']?)(?<secret>[^\s"',\]$\x60/][^\s"',\]\x60]{3,127})`,
|
|
1843
|
+
flags: "i",
|
|
1844
|
+
minLength: 4
|
|
1845
|
+
}),
|
|
1846
|
+
defineRule({
|
|
1847
|
+
id: "tls-disabled",
|
|
1848
|
+
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",
|
|
1849
|
+
kind: "practice",
|
|
1850
|
+
severity: "warning",
|
|
1851
|
+
keywords: ["sslmode", "rejectunauthorized", "verify", "insecureskipverify", "node_tls_reject_unauthorized"],
|
|
1852
|
+
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)`,
|
|
1853
|
+
minLength: 1,
|
|
1854
|
+
placeholders: false,
|
|
1855
|
+
skipTestPaths: true
|
|
1856
|
+
})
|
|
1857
|
+
];
|
|
1673
1858
|
|
|
1674
1859
|
// src/secrets/rules/providers.ts
|
|
1675
1860
|
var PROVIDER_RULES = [
|
|
@@ -1893,7 +2078,7 @@ var STRUCTURAL_RULES = [
|
|
|
1893
2078
|
];
|
|
1894
2079
|
|
|
1895
2080
|
// src/secrets/rules/catalog.ts
|
|
1896
|
-
var RULES = [...PROVIDER_RULES, ...STRUCTURAL_RULES];
|
|
2081
|
+
var RULES = [...PROVIDER_RULES, ...STRUCTURAL_RULES, ...EXPOSURE_RULES];
|
|
1897
2082
|
var CREDENTIAL_RULES = RULES.filter((rule) => rule.kind === "credential");
|
|
1898
2083
|
var ruleById = (id) => RULES.find((rule) => rule.id === id);
|
|
1899
2084
|
|
|
@@ -1985,11 +2170,11 @@ var maskDeep = (value) => {
|
|
|
1985
2170
|
};
|
|
1986
2171
|
|
|
1987
2172
|
// src/task/paths.ts
|
|
1988
|
-
import { join as
|
|
2173
|
+
import { join as join6 } from "node:path";
|
|
1989
2174
|
var FM_DIR = ".finitemonkey";
|
|
1990
2175
|
var fmPaths = (root) => {
|
|
1991
|
-
const dir = toAbsolutePath(
|
|
1992
|
-
const at = (...parts) => toAbsolutePath(
|
|
2176
|
+
const dir = toAbsolutePath(join6(root, FM_DIR));
|
|
2177
|
+
const at = (...parts) => toAbsolutePath(join6(dir, ...parts));
|
|
1993
2178
|
return {
|
|
1994
2179
|
root,
|
|
1995
2180
|
dir,
|
|
@@ -2225,16 +2410,16 @@ var reportMarkdown = (env, ctx, envelope) => {
|
|
|
2225
2410
|
];
|
|
2226
2411
|
return maskText(withoutHome(lines3.join("\n"), env.runtime.homeDir()));
|
|
2227
2412
|
};
|
|
2228
|
-
var reportPath = (env) => toAbsolutePath(
|
|
2413
|
+
var reportPath = (env) => toAbsolutePath(join7(env.root, ...REPORTS_DIR.split("/"), `doctor-${stamp(env.runtime.now())}.md`));
|
|
2229
2414
|
|
|
2230
2415
|
// src/install/plugin-yield.ts
|
|
2231
|
-
import { join as
|
|
2416
|
+
import { join as join8 } from "node:path";
|
|
2232
2417
|
var PLUGIN_VIA = "plugin";
|
|
2233
2418
|
var YIELD_STDOUT = `${JSON.stringify({ continue: true })}
|
|
2234
2419
|
`;
|
|
2235
2420
|
var projectOwnsHooks = (ctx) => {
|
|
2236
2421
|
const root = ctx.env["CLAUDE_PROJECT_DIR"] ?? ctx.cwd;
|
|
2237
|
-
const text6 = ctx.runtime.readText(toAbsolutePath(
|
|
2422
|
+
const text6 = ctx.runtime.readText(toAbsolutePath(join8(root, ...SETTINGS_FILE.split("/"))));
|
|
2238
2423
|
if (!text6.ok) return false;
|
|
2239
2424
|
const parsed = parseJsonObject(text6.value);
|
|
2240
2425
|
return parsed.ok && ownHandlersIn(parsed.value) > 0;
|
|
@@ -2248,7 +2433,7 @@ var STABLE_PLUGIN_ID = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
|
2248
2433
|
var NEXT_PLUGIN_ID = `${PLUGIN_NAME}@${NEXT_MARKETPLACE_NAME}`;
|
|
2249
2434
|
|
|
2250
2435
|
// src/doctor/settings.ts
|
|
2251
|
-
import { join as
|
|
2436
|
+
import { join as join9 } from "node:path";
|
|
2252
2437
|
var MANAGED = {
|
|
2253
2438
|
darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
|
|
2254
2439
|
linux: "/etc/claude-code/managed-settings.json",
|
|
@@ -2257,7 +2442,7 @@ var MANAGED = {
|
|
|
2257
2442
|
var userSettingsPath = ({ env, runtime }) => {
|
|
2258
2443
|
const configDir = env["CLAUDE_CONFIG_DIR"];
|
|
2259
2444
|
return toAbsolutePath(
|
|
2260
|
-
configDir === void 0 ?
|
|
2445
|
+
configDir === void 0 ? join9(runtime.homeDir(), ".claude", "settings.json") : join9(configDir, "settings.json")
|
|
2261
2446
|
);
|
|
2262
2447
|
};
|
|
2263
2448
|
var settingsPaths = (env) => {
|
|
@@ -2265,8 +2450,8 @@ var settingsPaths = (env) => {
|
|
|
2265
2450
|
return [
|
|
2266
2451
|
...managed === void 0 ? [] : [{ scope: "managed", path: toAbsolutePath(managed) }],
|
|
2267
2452
|
{ scope: "user", path: userSettingsPath(env) },
|
|
2268
|
-
{ scope: "project", path: toAbsolutePath(
|
|
2269
|
-
{ scope: "local", path: toAbsolutePath(
|
|
2453
|
+
{ scope: "project", path: toAbsolutePath(join9(env.root, ".claude", "settings.json")) },
|
|
2454
|
+
{ scope: "local", path: toAbsolutePath(join9(env.root, ".claude", "settings.local.json")) }
|
|
2270
2455
|
];
|
|
2271
2456
|
};
|
|
2272
2457
|
var readSettings = (env) => settingsPaths(env).flatMap(({ scope: scope2, path }) => {
|
|
@@ -2410,7 +2595,7 @@ var checkClaudeSettings = (env) => {
|
|
|
2410
2595
|
};
|
|
2411
2596
|
|
|
2412
2597
|
// src/doctor/environment.ts
|
|
2413
|
-
import { join as
|
|
2598
|
+
import { join as join10 } from "node:path";
|
|
2414
2599
|
var AUTH_VARIABLES = [
|
|
2415
2600
|
"ANTHROPIC_API_KEY",
|
|
2416
2601
|
"ANTHROPIC_AUTH_TOKEN",
|
|
@@ -2448,7 +2633,7 @@ var pathDirs = ({ env, platform }) => {
|
|
|
2448
2633
|
var copiesOnPath = (env) => {
|
|
2449
2634
|
const names = EXECUTABLES[env.platform] ?? ["finitemonkey"];
|
|
2450
2635
|
const found = pathDirs(env).flatMap(
|
|
2451
|
-
(dir) => names.map((name) => toAbsolutePath(
|
|
2636
|
+
(dir) => names.map((name) => toAbsolutePath(join10(dir, name))).filter((path) => env.runtime.stat(path)?.kind === "file").map((path) => env.runtime.realpath(path))
|
|
2452
2637
|
);
|
|
2453
2638
|
const byDir = new Map(found.map((path) => [path.replace(/\.(cmd|exe|ps1)$/i, ""), path]));
|
|
2454
2639
|
return [...byDir.values()];
|
|
@@ -2482,7 +2667,7 @@ var checkAuthVariables = ({ env, platform }) => {
|
|
|
2482
2667
|
};
|
|
2483
2668
|
|
|
2484
2669
|
// src/doctor/hooks.ts
|
|
2485
|
-
import { join as
|
|
2670
|
+
import { join as join31 } from "node:path";
|
|
2486
2671
|
|
|
2487
2672
|
// src/hooks/events.ts
|
|
2488
2673
|
var HOOK_EVENTS = [
|
|
@@ -2529,9 +2714,9 @@ var deadlineFor = (event, raw) => {
|
|
|
2529
2714
|
};
|
|
2530
2715
|
|
|
2531
2716
|
// src/hooks/fields.ts
|
|
2532
|
-
var textField = (
|
|
2717
|
+
var textField = (record3, ...names) => {
|
|
2533
2718
|
for (const name of names) {
|
|
2534
|
-
const value =
|
|
2719
|
+
const value = record3[name];
|
|
2535
2720
|
if (typeof value === "string") return value;
|
|
2536
2721
|
}
|
|
2537
2722
|
return void 0;
|
|
@@ -2626,8 +2811,8 @@ var closeTask = (state, id) => assemble({
|
|
|
2626
2811
|
|
|
2627
2812
|
// src/guards/release/grants.ts
|
|
2628
2813
|
var GRANT_MINUTES = 30;
|
|
2629
|
-
var text2 = (
|
|
2630
|
-
const value =
|
|
2814
|
+
var text2 = (record3, key) => {
|
|
2815
|
+
const value = record3[key];
|
|
2631
2816
|
return typeof value === "string" ? value : void 0;
|
|
2632
2817
|
};
|
|
2633
2818
|
var toGrant = (value) => {
|
|
@@ -3162,9 +3347,9 @@ var skipOptions = (args, wrapper) => {
|
|
|
3162
3347
|
}
|
|
3163
3348
|
return index + wrapper.positionals;
|
|
3164
3349
|
};
|
|
3165
|
-
var shift = (stage,
|
|
3350
|
+
var shift = (stage, count4, name) => ({
|
|
3166
3351
|
...stage,
|
|
3167
|
-
argv: stage.argv.slice(
|
|
3352
|
+
argv: stage.argv.slice(count4),
|
|
3168
3353
|
via: [...stage.via, name]
|
|
3169
3354
|
});
|
|
3170
3355
|
var envCommand = (stage) => {
|
|
@@ -3269,10 +3454,10 @@ var PS_ENV = /^\$env:([A-Za-z_][A-Za-z0-9_]*)=?(.*)$/is;
|
|
|
3269
3454
|
var CHANGES_DIR = /* @__PURE__ */ new Set(["cd", "pushd", "set-location", "push-location"]);
|
|
3270
3455
|
var lex = (text6, language) => language === "bash" ? lexBash(text6) : lexPowerShell(text6, language === "cmd");
|
|
3271
3456
|
var isWindowsAbsolute = (path) => /^[A-Za-z]:\//.test(path);
|
|
3272
|
-
var joinDir = (
|
|
3457
|
+
var joinDir = (current2, target) => {
|
|
3273
3458
|
const next2 = toPosixPath(target);
|
|
3274
3459
|
if (next2.startsWith("/") || isWindowsAbsolute(next2) || next2.startsWith("~")) return next2;
|
|
3275
|
-
return
|
|
3460
|
+
return current2 === void 0 ? posix.normalize(next2) : posix.join(current2, next2);
|
|
3276
3461
|
};
|
|
3277
3462
|
var assignments = (words, language) => {
|
|
3278
3463
|
if (language !== "bash") {
|
|
@@ -3385,9 +3570,9 @@ var COMMIT_VALUE_SHORTS = /* @__PURE__ */ new Set(["m", "F", "C", "c", "t"]);
|
|
|
3385
3570
|
var VERIFY_SUBCOMMANDS = /* @__PURE__ */ new Set(["commit", "push", "merge", "am", "rebase", "cherry-pick", "revert", "pull"]);
|
|
3386
3571
|
var isHooksPath = (value) => /^core\.hookspath(=|$)/i.test(value);
|
|
3387
3572
|
var envFindings = (command) => {
|
|
3388
|
-
const
|
|
3573
|
+
const pairs2 = Object.entries(command.env);
|
|
3389
3574
|
const exported = command.argv[0] === "export" ? command.argv.slice(1).map((arg) => arg.split("=", 2)) : [];
|
|
3390
|
-
return [...
|
|
3575
|
+
return [...pairs2, ...exported].filter(([name = ""]) => HOOK_ENV.test(name)).map(([name = "", value = ""]) => ({ kind: "hook_env", detail: `${name}=${value}` }));
|
|
3391
3576
|
};
|
|
3392
3577
|
var splitGit = (args) => {
|
|
3393
3578
|
let index = 0;
|
|
@@ -3545,7 +3730,7 @@ var selfGuard = (input) => {
|
|
|
3545
3730
|
};
|
|
3546
3731
|
|
|
3547
3732
|
// src/guards/tests/hook.ts
|
|
3548
|
-
import { join as
|
|
3733
|
+
import { join as join11, relative as relative3 } from "node:path";
|
|
3549
3734
|
|
|
3550
3735
|
// src/guards/tests/patterns.ts
|
|
3551
3736
|
var JS_TEST = /(^|\/)(__tests__|__test__)\/|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)tests?\/.*\.[cm]?[jt]sx?$/;
|
|
@@ -3735,7 +3920,7 @@ var shellViolations = (input, root) => {
|
|
|
3735
3920
|
return parseShell(command).flatMap(
|
|
3736
3921
|
(simple) => writtenPaths(simple).flatMap((path) => {
|
|
3737
3922
|
const local = localPath(root, path);
|
|
3738
|
-
const exists = input.runtime.stat(toAbsolutePath(
|
|
3923
|
+
const exists = input.runtime.stat(toAbsolutePath(join11(root, local))) !== void 0;
|
|
3739
3924
|
if (!exists) return [];
|
|
3740
3925
|
if (isProtectedConfig(local)) return [{ kind: "config_changed", path: local }];
|
|
3741
3926
|
return isTestFile(local) ? [{ kind: "test_removed", path: local }] : [];
|
|
@@ -3755,20 +3940,20 @@ var testsGuard = (input) => {
|
|
|
3755
3940
|
};
|
|
3756
3941
|
|
|
3757
3942
|
// src/gate/hooks/config.ts
|
|
3758
|
-
import { join as
|
|
3943
|
+
import { join as join13 } from "node:path";
|
|
3759
3944
|
|
|
3760
3945
|
// src/gate/hooks/common.ts
|
|
3761
|
-
import { dirname as dirname5, join as
|
|
3946
|
+
import { dirname as dirname5, join as join12 } from "node:path";
|
|
3762
3947
|
var DEFAULT_GATE_BUDGET_MS = 3e5;
|
|
3763
3948
|
var sessionCwd = (input) => input.payload.cwd === void 0 ? input.cwd : toAbsolutePath(input.payload.cwd, input.cwd);
|
|
3764
3949
|
var MAX_DEPTH2 = 64;
|
|
3765
3950
|
var findRepoRoot = (runtime, start) => {
|
|
3766
|
-
let
|
|
3951
|
+
let current2 = start;
|
|
3767
3952
|
for (let depth = 0; depth < MAX_DEPTH2; depth += 1) {
|
|
3768
|
-
if (runtime.stat(toAbsolutePath(
|
|
3769
|
-
const parent = toAbsolutePath(dirname5(
|
|
3770
|
-
if (parent ===
|
|
3771
|
-
|
|
3953
|
+
if (runtime.stat(toAbsolutePath(join12(current2, ".git"))) !== void 0) return current2;
|
|
3954
|
+
const parent = toAbsolutePath(dirname5(current2));
|
|
3955
|
+
if (parent === current2) return void 0;
|
|
3956
|
+
current2 = parent;
|
|
3772
3957
|
}
|
|
3773
3958
|
return void 0;
|
|
3774
3959
|
};
|
|
@@ -3788,7 +3973,7 @@ var contextOutput = (event, additionalContext, systemMessage) => ({
|
|
|
3788
3973
|
var human = (text6) => `finitemonkey: ${text6}`;
|
|
3789
3974
|
|
|
3790
3975
|
// src/gate/hooks/config.ts
|
|
3791
|
-
var
|
|
3976
|
+
var CONFIG_FILE3 = "config-hooks.json";
|
|
3792
3977
|
var GUARDED_SOURCES = /* @__PURE__ */ new Set(["user_settings", "project_settings", "local_settings"]);
|
|
3793
3978
|
var countIn = (value) => {
|
|
3794
3979
|
if (typeof value === "string") return /\bfinitemonkey\b|\bfm hook\b/.test(value) ? 1 : 0;
|
|
@@ -3802,20 +3987,20 @@ var censusOf = (text6) => {
|
|
|
3802
3987
|
return { hooks: countIn(parsed.value["hooks"]), disabled: parsed.value["disableAllHooks"] === true };
|
|
3803
3988
|
};
|
|
3804
3989
|
var settingsFiles = (paths, env) => [
|
|
3805
|
-
toAbsolutePath(
|
|
3806
|
-
toAbsolutePath(
|
|
3807
|
-
...env["HOME"] === void 0 ? [] : [toAbsolutePath(
|
|
3990
|
+
toAbsolutePath(join13(paths.root, ".claude", "settings.json")),
|
|
3991
|
+
toAbsolutePath(join13(paths.root, ".claude", "settings.local.json")),
|
|
3992
|
+
...env["HOME"] === void 0 ? [] : [toAbsolutePath(join13(env["HOME"], ".claude", "settings.json"))]
|
|
3808
3993
|
];
|
|
3809
3994
|
var read2 = (runtime, path) => {
|
|
3810
3995
|
const text6 = runtime.readText(path);
|
|
3811
3996
|
return text6.ok ? text6.value : void 0;
|
|
3812
3997
|
};
|
|
3813
3998
|
var known = (runtime, paths) => {
|
|
3814
|
-
const raw = readJsonFile(runtime, paths.cache(
|
|
3999
|
+
const raw = readJsonFile(runtime, paths.cache(CONFIG_FILE3))?.["hooks"];
|
|
3815
4000
|
return isRecord(raw) ? Object.fromEntries(Object.entries(raw).filter((entry2) => typeof entry2[1] === "number")) : {};
|
|
3816
4001
|
};
|
|
3817
4002
|
var save = (runtime, paths, counts) => {
|
|
3818
|
-
writeJsonFile(runtime, paths.cache(
|
|
4003
|
+
writeJsonFile(runtime, paths.cache(CONFIG_FILE3), { hooks: { ...counts } });
|
|
3819
4004
|
};
|
|
3820
4005
|
var recordConfigHooks = (runtime, paths, env) => {
|
|
3821
4006
|
const counts = Object.fromEntries(
|
|
@@ -3940,6 +4125,7 @@ var implicitContract = (files) => {
|
|
|
3940
4125
|
{ id: "prose", kind: "prose" },
|
|
3941
4126
|
{ id: "worktree", kind: "worktree" },
|
|
3942
4127
|
{ id: "merge", kind: "merge" },
|
|
4128
|
+
{ id: "callers", kind: "callers" },
|
|
3943
4129
|
{ id: "pr", kind: "pr", requireOpen: false }
|
|
3944
4130
|
],
|
|
3945
4131
|
decisionsNeeded: [],
|
|
@@ -3948,7 +4134,7 @@ var implicitContract = (files) => {
|
|
|
3948
4134
|
};
|
|
3949
4135
|
|
|
3950
4136
|
// src/gate/prepare.ts
|
|
3951
|
-
import { join as
|
|
4137
|
+
import { join as join15 } from "node:path";
|
|
3952
4138
|
|
|
3953
4139
|
// src/core/yaml/scalar.ts
|
|
3954
4140
|
var ESCAPES = {
|
|
@@ -4096,7 +4282,7 @@ var parseValue = (text6) => {
|
|
|
4096
4282
|
|
|
4097
4283
|
// src/core/yaml/parse.ts
|
|
4098
4284
|
var BOM = new RegExp(`^${String.fromCharCode(65279)}`);
|
|
4099
|
-
var
|
|
4285
|
+
var stripComment2 = (text6) => {
|
|
4100
4286
|
for (let index = 0; index < text6.length; index += 1) {
|
|
4101
4287
|
const char = text6[index];
|
|
4102
4288
|
if (char === '"' || char === "'") {
|
|
@@ -4111,7 +4297,7 @@ var toLines = (source) => {
|
|
|
4111
4297
|
const lines3 = [];
|
|
4112
4298
|
const raw = source.replace(BOM, "").split(/\r?\n/);
|
|
4113
4299
|
for (const [index, full] of raw.entries()) {
|
|
4114
|
-
const text6 =
|
|
4300
|
+
const text6 = stripComment2(full).trimEnd();
|
|
4115
4301
|
const body2 = text6.trimStart();
|
|
4116
4302
|
if (body2 === "" || body2 === "---" && lines3.length === 0) continue;
|
|
4117
4303
|
const indentText = text6.slice(0, text6.length - body2.length);
|
|
@@ -4219,7 +4405,8 @@ var CHECK_KINDS = [
|
|
|
4219
4405
|
"worktree",
|
|
4220
4406
|
"merge",
|
|
4221
4407
|
"prod",
|
|
4222
|
-
"prose"
|
|
4408
|
+
"prose",
|
|
4409
|
+
"review"
|
|
4223
4410
|
];
|
|
4224
4411
|
var GIT_REQUIREMENTS = ["committed", "pushed", "pr_open"];
|
|
4225
4412
|
var SIZES = ["S", "M", "L"];
|
|
@@ -4227,8 +4414,8 @@ var TASK_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
|
4227
4414
|
var TASK_SCHEMA = "finitemonkey/task@1";
|
|
4228
4415
|
|
|
4229
4416
|
// src/task/validate.ts
|
|
4230
|
-
var str = (
|
|
4231
|
-
const value =
|
|
4417
|
+
var str = (record3, key, at, issues, required) => {
|
|
4418
|
+
const value = record3[key];
|
|
4232
4419
|
if (value === void 0 || value === null) {
|
|
4233
4420
|
if (required) issues.push(`${at}${key}: required`);
|
|
4234
4421
|
return void 0;
|
|
@@ -4239,8 +4426,8 @@ var str = (record2, key, at, issues, required) => {
|
|
|
4239
4426
|
}
|
|
4240
4427
|
return value;
|
|
4241
4428
|
};
|
|
4242
|
-
var positive = (
|
|
4243
|
-
const value =
|
|
4429
|
+
var positive = (record3, key, at, issues) => {
|
|
4430
|
+
const value = record3[key];
|
|
4244
4431
|
if (value === void 0 || value === null) return void 0;
|
|
4245
4432
|
if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
|
|
4246
4433
|
issues.push(`${at}${key}: must be a positive integer`);
|
|
@@ -4259,12 +4446,12 @@ var gitRequire = (value, at, issues) => {
|
|
|
4259
4446
|
if (known2.some((item) => item === void 0)) issues.push(`${at}: use ${GIT_REQUIREMENTS.join(", ")}`);
|
|
4260
4447
|
return known2.filter((item) => item !== void 0);
|
|
4261
4448
|
};
|
|
4262
|
-
var checkFields = (
|
|
4263
|
-
const run4 = str(
|
|
4264
|
-
const timeoutS = positive(
|
|
4265
|
-
const description = str(
|
|
4266
|
-
const require2 = kind === "git" ? gitRequire(
|
|
4267
|
-
const open =
|
|
4449
|
+
var checkFields = (record3, kind, at, issues) => {
|
|
4450
|
+
const run4 = str(record3, "run", at, issues, kind === "command");
|
|
4451
|
+
const timeoutS = positive(record3, "timeout_s", at, issues);
|
|
4452
|
+
const description = str(record3, "description", at, issues, kind === "manual");
|
|
4453
|
+
const require2 = kind === "git" ? gitRequire(record3["require"] ?? ["committed"], `${at}require`, issues) : void 0;
|
|
4454
|
+
const open = record3["require_open"];
|
|
4268
4455
|
if (open !== void 0 && typeof open !== "boolean") issues.push(`${at}require_open: must be true or false`);
|
|
4269
4456
|
return {
|
|
4270
4457
|
...run4 === void 0 ? {} : { run: run4 },
|
|
@@ -4335,6 +4522,7 @@ var validateContract = (value, implicit = false) => {
|
|
|
4335
4522
|
const contractScope = scope(value["scope"], issues);
|
|
4336
4523
|
const checks = acceptance(value["acceptance"], issues);
|
|
4337
4524
|
const decisionsNeeded = strings2(value["decisions_needed"], "decisions_needed", issues);
|
|
4525
|
+
const callersIgnore = strings2(value["callers_ignore"], "callers_ignore", issues);
|
|
4338
4526
|
if (issues.length > 0 || id === void 0 || goal === void 0 || size === void 0) return reject(issues);
|
|
4339
4527
|
return accept({
|
|
4340
4528
|
id,
|
|
@@ -4344,6 +4532,7 @@ var validateContract = (value, implicit = false) => {
|
|
|
4344
4532
|
scope: contractScope,
|
|
4345
4533
|
acceptance: checks,
|
|
4346
4534
|
decisionsNeeded,
|
|
4535
|
+
...callersIgnore.length === 0 ? {} : { callersIgnore },
|
|
4347
4536
|
implicit
|
|
4348
4537
|
});
|
|
4349
4538
|
};
|
|
@@ -4360,7 +4549,7 @@ var parseContract = (id, text6) => {
|
|
|
4360
4549
|
if (valid.value.id !== id) return reject({ kind: "invalid", id, issues: [`id: must match the file name (${id})`] });
|
|
4361
4550
|
return accept(valid.value);
|
|
4362
4551
|
};
|
|
4363
|
-
var
|
|
4552
|
+
var pick2 = (lock, sources, id) => {
|
|
4364
4553
|
const approved = lock.approved[id];
|
|
4365
4554
|
if (approved !== void 0) return { text: approved, source: "lock" };
|
|
4366
4555
|
const base = sources.base(taskRelative(id));
|
|
@@ -4371,7 +4560,7 @@ var pick = (lock, sources, id) => {
|
|
|
4371
4560
|
var loadTask = (files, paths, sources, id) => {
|
|
4372
4561
|
if (!TASK_ID.test(id)) return reject({ kind: "not_found", id });
|
|
4373
4562
|
const lock = readTrust(files, paths);
|
|
4374
|
-
const chosen =
|
|
4563
|
+
const chosen = pick2(lock, sources, id);
|
|
4375
4564
|
if (chosen === void 0) return reject({ kind: "not_found", id });
|
|
4376
4565
|
const parsed = parseContract(id, chosen.text);
|
|
4377
4566
|
if (!parsed.ok) return parsed;
|
|
@@ -4425,6 +4614,9 @@ var readBaseline = (runtime, paths) => {
|
|
|
4425
4614
|
};
|
|
4426
4615
|
var writeBaseline = (runtime, paths, treeHash2, checks) => writeJsonFile(runtime, paths.cache(BASELINE_FILE), { treeHash: treeHash2, checks: { ...checks } }).ok;
|
|
4427
4616
|
|
|
4617
|
+
// src/gate/diff-shape.ts
|
|
4618
|
+
import { join as join14 } from "node:path";
|
|
4619
|
+
|
|
4428
4620
|
// src/gate/git.ts
|
|
4429
4621
|
var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
4430
4622
|
var GIT_TIMEOUT_MS = 15e3;
|
|
@@ -4524,10 +4716,20 @@ var numstat = (git, base) => {
|
|
|
4524
4716
|
};
|
|
4525
4717
|
var dirtyPaths = (git) => nulFields(git.out(["status", "--porcelain", "-z", "--", ".", EXCLUDE_FM]) ?? "").map((entry2) => entry2.slice(3));
|
|
4526
4718
|
var aheadOfUpstream = (git) => {
|
|
4527
|
-
const
|
|
4528
|
-
return
|
|
4719
|
+
const count4 = git.out(["rev-list", "--count", "@{u}..HEAD"])?.trim();
|
|
4720
|
+
return count4 === void 0 ? void 0 : Number(count4);
|
|
4529
4721
|
};
|
|
4530
4722
|
|
|
4723
|
+
// src/gate/diff-shape.ts
|
|
4724
|
+
var untrackedLines = (runtime, root, changes) => changes.filter((change) => change.status === "A").reduce((sum, change) => {
|
|
4725
|
+
const text6 = runtime.readText(toAbsolutePath(join14(root, change.path)));
|
|
4726
|
+
return sum + (text6.ok ? text6.value.split("\n").length : 0);
|
|
4727
|
+
}, 0);
|
|
4728
|
+
var diffShape = (runtime, git, base, changes) => ({
|
|
4729
|
+
files: changes.map((change) => change.path),
|
|
4730
|
+
lines: numstat(git, base).lines + untrackedLines(runtime, git.cwd, changes)
|
|
4731
|
+
});
|
|
4732
|
+
|
|
4531
4733
|
// src/gate/pr-parse.ts
|
|
4532
4734
|
var FAILED = /* @__PURE__ */ new Set(["FAILURE", "CANCELLED", "TIMED_OUT", "ACTION_REQUIRED", "STARTUP_FAILURE", "ERROR"]);
|
|
4533
4735
|
var PENDING = /* @__PURE__ */ new Set(["IN_PROGRESS", "QUEUED", "PENDING", "WAITING", "REQUESTED", "EXPECTED"]);
|
|
@@ -4547,18 +4749,18 @@ var ciChecks = (value) => Array.isArray(value) ? value.flatMap((item) => {
|
|
|
4547
4749
|
var parsePrView = (text6) => {
|
|
4548
4750
|
const parsed = parseJsonObject(text6);
|
|
4549
4751
|
if (!parsed.ok) return void 0;
|
|
4550
|
-
const
|
|
4551
|
-
const number =
|
|
4752
|
+
const record3 = parsed.value;
|
|
4753
|
+
const number = record3["number"];
|
|
4552
4754
|
if (typeof number !== "number") return void 0;
|
|
4553
4755
|
return {
|
|
4554
4756
|
number,
|
|
4555
|
-
url: optionalString(
|
|
4556
|
-
state: optionalString(
|
|
4557
|
-
mergeable: optionalString(
|
|
4558
|
-
mergeStateStatus: optionalString(
|
|
4559
|
-
baseRefName: optionalString(
|
|
4560
|
-
headRefOid: optionalString(
|
|
4561
|
-
checks: ciChecks(
|
|
4757
|
+
url: optionalString(record3, "url") ?? "",
|
|
4758
|
+
state: optionalString(record3, "state") ?? "",
|
|
4759
|
+
mergeable: optionalString(record3, "mergeable") ?? "UNKNOWN",
|
|
4760
|
+
mergeStateStatus: optionalString(record3, "mergeStateStatus") ?? "UNKNOWN",
|
|
4761
|
+
baseRefName: optionalString(record3, "baseRefName") ?? "",
|
|
4762
|
+
headRefOid: optionalString(record3, "headRefOid") ?? "",
|
|
4763
|
+
checks: ciChecks(record3["statusCheckRollup"])
|
|
4562
4764
|
};
|
|
4563
4765
|
};
|
|
4564
4766
|
var isConflicting = (pr) => pr.mergeable === "CONFLICTING" || pr.mergeStateStatus === "DIRTY";
|
|
@@ -4618,13 +4820,118 @@ var conflictFiles = (git, base) => {
|
|
|
4618
4820
|
return [];
|
|
4619
4821
|
};
|
|
4620
4822
|
|
|
4823
|
+
// src/core/glob.ts
|
|
4824
|
+
var escape = (char) => /[.+^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
|
|
4825
|
+
var globToRegExp = (glob) => {
|
|
4826
|
+
const pattern = glob.replace(/^\.\//, "");
|
|
4827
|
+
let out = "";
|
|
4828
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
4829
|
+
const char = pattern[index] ?? "";
|
|
4830
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
4831
|
+
const slash = pattern[index + 2] === "/";
|
|
4832
|
+
out += slash ? "(?:.*/)?" : ".*";
|
|
4833
|
+
index += slash ? 2 : 1;
|
|
4834
|
+
} else if (char === "*") out += "[^/]*";
|
|
4835
|
+
else if (char === "?") out += "[^/]";
|
|
4836
|
+
else out += escape(char);
|
|
4837
|
+
}
|
|
4838
|
+
return new RegExp(`^${out}${pattern.endsWith("/") ? ".*" : ""}$`);
|
|
4839
|
+
};
|
|
4840
|
+
var matchesAny = (path, globs) => {
|
|
4841
|
+
const normalized = toPosixPath(path);
|
|
4842
|
+
return globs.some((glob) => globToRegExp(glob).test(normalized));
|
|
4843
|
+
};
|
|
4844
|
+
|
|
4845
|
+
// src/gate/size.ts
|
|
4846
|
+
var DEFAULT_SENSITIVE = [
|
|
4847
|
+
"**/*payment*",
|
|
4848
|
+
"**/*payment*/**",
|
|
4849
|
+
"**/*billing*",
|
|
4850
|
+
"**/*billing*/**",
|
|
4851
|
+
"**/*checkout*/**",
|
|
4852
|
+
"**/auth/**",
|
|
4853
|
+
"**/*auth.*",
|
|
4854
|
+
"**/oauth*",
|
|
4855
|
+
"**/migrations/**",
|
|
4856
|
+
"**/migration/**",
|
|
4857
|
+
"**/*.sql",
|
|
4858
|
+
"**/openapi*.*",
|
|
4859
|
+
"**/swagger*.*",
|
|
4860
|
+
"**/*.proto",
|
|
4861
|
+
"**/*.graphql",
|
|
4862
|
+
"**/*stripe*",
|
|
4863
|
+
"**/*openai*",
|
|
4864
|
+
"**/*anthropic*"
|
|
4865
|
+
];
|
|
4866
|
+
var DEFAULT_SIZE_CONFIG = {
|
|
4867
|
+
m: { files: 5, lines: 150 },
|
|
4868
|
+
l: { files: 20, lines: 600 },
|
|
4869
|
+
sensitive: DEFAULT_SENSITIVE
|
|
4870
|
+
};
|
|
4871
|
+
var count2 = (tree, fallback, ...path) => {
|
|
4872
|
+
const value = configAt(tree, ...path);
|
|
4873
|
+
const parsed = typeof value === "string" ? Number(value) : Number.NaN;
|
|
4874
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
4875
|
+
};
|
|
4876
|
+
var threshold = (tree, key, fallback) => ({
|
|
4877
|
+
files: count2(tree, fallback.files, "size", key, "files"),
|
|
4878
|
+
lines: count2(tree, fallback.lines, "size", key, "lines")
|
|
4879
|
+
});
|
|
4880
|
+
var sizeConfig = (tree) => {
|
|
4881
|
+
const declared = configAt(tree, "size", "sensitive");
|
|
4882
|
+
return {
|
|
4883
|
+
m: threshold(tree, "m", DEFAULT_SIZE_CONFIG.m),
|
|
4884
|
+
l: threshold(tree, "l", DEFAULT_SIZE_CONFIG.l),
|
|
4885
|
+
sensitive: declared === void 0 ? DEFAULT_SENSITIVE : configList(tree, "size", "sensitive")
|
|
4886
|
+
};
|
|
4887
|
+
};
|
|
4888
|
+
var reaches = (shape, limit) => shape.files.length >= limit.files || shape.lines >= limit.lines;
|
|
4889
|
+
var rank = (size) => SIZES.indexOf(size);
|
|
4890
|
+
var larger = (a, b) => rank(a) >= rank(b) ? a : b;
|
|
4891
|
+
var computeSize = (shape, config) => {
|
|
4892
|
+
const sensitive = shape.files.filter((file) => matchesAny(file, config.sensitive));
|
|
4893
|
+
const byVolume = reaches(shape, config.l) ? "L" : reaches(shape, config.m) ? "M" : "S";
|
|
4894
|
+
const computed = sensitive.length > 0 ? larger(byVolume, "M") : byVolume;
|
|
4895
|
+
return { computed, files: shape.files.length, lines: shape.lines, sensitive };
|
|
4896
|
+
};
|
|
4897
|
+
var sizeVerdict = (declared, shape, config) => {
|
|
4898
|
+
const result = computeSize(shape, config);
|
|
4899
|
+
return { ...result, declared, effective: larger(declared, result.computed) };
|
|
4900
|
+
};
|
|
4901
|
+
var upgraded = (verdict) => rank(verdict.effective) > rank(verdict.declared);
|
|
4902
|
+
|
|
4903
|
+
// src/gate/size-contract.ts
|
|
4904
|
+
var REVIEW_CHECK = "size-review";
|
|
4905
|
+
var INVENTORY_CHECK = "size-inventory";
|
|
4906
|
+
var DECISION_CHECK = "size-decision";
|
|
4907
|
+
var INVENTORY = "inventory of the files, behaviours and callers the change touches";
|
|
4908
|
+
var DECISION = "a recorded decision on the approach for a change this large";
|
|
4909
|
+
var reviewChecks = (verdict) => verdict.effective === "S" ? [] : [{ id: REVIEW_CHECK, kind: "review" }];
|
|
4910
|
+
var largeChecks = (verdict) => verdict.effective === "L" && verdict.declared !== "S" ? [
|
|
4911
|
+
{ id: INVENTORY_CHECK, kind: "manual", description: INVENTORY },
|
|
4912
|
+
{ id: DECISION_CHECK, kind: "manual", description: DECISION }
|
|
4913
|
+
] : [];
|
|
4914
|
+
var sizeRequirements = (verdict) => [
|
|
4915
|
+
...reviewChecks(verdict),
|
|
4916
|
+
...largeChecks(verdict)
|
|
4917
|
+
];
|
|
4918
|
+
var applySize = (contract, verdict) => {
|
|
4919
|
+
if (contract.implicit) return contract;
|
|
4920
|
+
const declaredIds = new Set(contract.acceptance.map((check2) => check2.id));
|
|
4921
|
+
const declaredKinds = new Set(contract.acceptance.map((check2) => check2.kind));
|
|
4922
|
+
const extra = sizeRequirements(verdict).filter(
|
|
4923
|
+
(check2) => !declaredIds.has(check2.id) && !(check2.kind === "review" && declaredKinds.has("review"))
|
|
4924
|
+
);
|
|
4925
|
+
return extra.length === 0 && !upgraded(verdict) ? contract : { ...contract, size: verdict.effective, acceptance: [...contract.acceptance, ...extra] };
|
|
4926
|
+
};
|
|
4927
|
+
|
|
4621
4928
|
// src/gate/prepare.ts
|
|
4622
4929
|
var diskFiles = (runtime, root) => ({
|
|
4623
4930
|
read: (relative9) => {
|
|
4624
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
4931
|
+
const text6 = runtime.readText(toAbsolutePath(join15(root, relative9)));
|
|
4625
4932
|
return text6.ok ? text6.value : void 0;
|
|
4626
4933
|
},
|
|
4627
|
-
exists: (relative9) => runtime.stat(toAbsolutePath(
|
|
4934
|
+
exists: (relative9) => runtime.stat(toAbsolutePath(join15(root, relative9))) !== void 0
|
|
4628
4935
|
});
|
|
4629
4936
|
var baseFiles = (git, base, disk) => ({
|
|
4630
4937
|
read: (relative9) => base === EMPTY_TREE ? disk.read(relative9) : fileAt(git, base, relative9),
|
|
@@ -4656,6 +4963,7 @@ var skipReason = (runtime, paths, loaded, changes, tree, sessionId) => {
|
|
|
4656
4963
|
const session = readSession(runtime, paths);
|
|
4657
4964
|
return session !== void 0 && session.sessionId === sessionId && session.startTree === tree ? "no_session_edits" : void 0;
|
|
4658
4965
|
};
|
|
4966
|
+
var measureSize = (runtime, git, base, changes, contract) => sizeVerdict(contract.size, diffShape(runtime, git, base, changes), sizeConfig(loadProjectConfig(runtime, git.cwd)));
|
|
4659
4967
|
var prepareGate = (options) => {
|
|
4660
4968
|
const probe = gitAt(options.runtime, options.cwd);
|
|
4661
4969
|
const root = gitRoot(probe);
|
|
@@ -4675,11 +4983,13 @@ var prepareGate = (options) => {
|
|
|
4675
4983
|
const skip = options.noSkip === true ? void 0 : skipReason(options.runtime, paths, loaded.value, changes, tree, options.sessionId);
|
|
4676
4984
|
if (skip !== void 0) return { kind: "skipped", reason: skip };
|
|
4677
4985
|
const pr = memo(() => prStatus(options.runtime, git, paths));
|
|
4986
|
+
const size = measureSize(options.runtime, git, base, changes, loaded.value.contract);
|
|
4678
4987
|
const ctx = {
|
|
4679
4988
|
runtime: options.runtime,
|
|
4680
4989
|
git,
|
|
4681
4990
|
paths,
|
|
4682
|
-
contract: loaded.value.contract,
|
|
4991
|
+
contract: applySize(loaded.value.contract, size),
|
|
4992
|
+
size,
|
|
4683
4993
|
trusted: loaded.value.trusted,
|
|
4684
4994
|
treeHash: tree,
|
|
4685
4995
|
base,
|
|
@@ -4883,19 +5193,19 @@ var params = (value) => isRecord(value) ? Object.fromEntries(
|
|
|
4883
5193
|
var notes = (value) => Array.isArray(value) ? value.flatMap(
|
|
4884
5194
|
(item) => isRecord(item) && typeof item["key"] === "string" ? [{ key: item["key"], params: params(item["params"]) }] : []
|
|
4885
5195
|
) : [];
|
|
4886
|
-
var text3 = (
|
|
4887
|
-
const value =
|
|
5196
|
+
var text3 = (record3, key) => {
|
|
5197
|
+
const value = record3[key];
|
|
4888
5198
|
return typeof value === "string" ? value : void 0;
|
|
4889
5199
|
};
|
|
4890
|
-
var optional = (
|
|
4891
|
-
const exitCode =
|
|
4892
|
-
const durationMs =
|
|
4893
|
-
const command = text3(
|
|
4894
|
-
const stdoutSha256 = text3(
|
|
4895
|
-
const releasedBy = text3(
|
|
4896
|
-
const reason = oneOf2(UNVERIFIED_REASONS,
|
|
4897
|
-
const failures =
|
|
4898
|
-
const testCount =
|
|
5200
|
+
var optional = (record3) => {
|
|
5201
|
+
const exitCode = record3["exitCode"];
|
|
5202
|
+
const durationMs = record3["durationMs"];
|
|
5203
|
+
const command = text3(record3, "command");
|
|
5204
|
+
const stdoutSha256 = text3(record3, "stdoutSha256");
|
|
5205
|
+
const releasedBy = text3(record3, "releasedBy");
|
|
5206
|
+
const reason = oneOf2(UNVERIFIED_REASONS, record3["reason"]);
|
|
5207
|
+
const failures = record3["failures"];
|
|
5208
|
+
const testCount = record3["testCount"];
|
|
4899
5209
|
return {
|
|
4900
5210
|
...command === void 0 ? {} : { command },
|
|
4901
5211
|
...typeof exitCode === "number" || exitCode === null ? { exitCode } : {},
|
|
@@ -4910,14 +5220,14 @@ var optional = (record2) => {
|
|
|
4910
5220
|
var parseEvidence = (line2) => {
|
|
4911
5221
|
const parsed = parseJsonObject(line2);
|
|
4912
5222
|
if (!parsed.ok) return void 0;
|
|
4913
|
-
const
|
|
4914
|
-
const status = oneOf2(EVIDENCE_STATUSES,
|
|
4915
|
-
const task = text3(
|
|
4916
|
-
const check2 = text3(
|
|
4917
|
-
const kind = text3(
|
|
4918
|
-
const treeHash2 = text3(
|
|
4919
|
-
const at = text3(
|
|
4920
|
-
if (
|
|
5223
|
+
const record3 = parsed.value;
|
|
5224
|
+
const status = oneOf2(EVIDENCE_STATUSES, record3["status"]);
|
|
5225
|
+
const task = text3(record3, "task");
|
|
5226
|
+
const check2 = text3(record3, "check");
|
|
5227
|
+
const kind = text3(record3, "kind");
|
|
5228
|
+
const treeHash2 = text3(record3, "treeHash");
|
|
5229
|
+
const at = text3(record3, "at");
|
|
5230
|
+
if (record3["schema"] !== EVIDENCE_SCHEMA || status === void 0) return void 0;
|
|
4921
5231
|
if (task === void 0 || check2 === void 0 || kind === void 0 || treeHash2 === void 0 || at === void 0) {
|
|
4922
5232
|
return void 0;
|
|
4923
5233
|
}
|
|
@@ -4929,8 +5239,8 @@ var parseEvidence = (line2) => {
|
|
|
4929
5239
|
status,
|
|
4930
5240
|
treeHash: treeHash2,
|
|
4931
5241
|
at,
|
|
4932
|
-
notes: notes(
|
|
4933
|
-
...optional(
|
|
5242
|
+
notes: notes(record3["notes"]),
|
|
5243
|
+
...optional(record3)
|
|
4934
5244
|
};
|
|
4935
5245
|
};
|
|
4936
5246
|
var readLedger = (runtime, path) => {
|
|
@@ -5458,7 +5768,26 @@ var en_default = {
|
|
|
5458
5768
|
"history.pattern.no_verify": "{count, plural, one {# git command} other {# git commands}} with --no-verify",
|
|
5459
5769
|
"history.pattern.force_push": "{count, plural, one {# force push} other {# force pushes}}",
|
|
5460
5770
|
"history.pattern.test_skip": "{count, plural, one {# test edit} other {# test edits}} that added .skip, .only or a skip marker",
|
|
5461
|
-
"history.estimate": "These are estimates from pattern matching, not evidence; nothing was saved and nothing left this machine."
|
|
5771
|
+
"history.estimate": "These are estimates from pattern matching, not evidence; nothing was saved and nothing left this machine.",
|
|
5772
|
+
"gate.require.review": "a reviewer subagent read the change at its current state",
|
|
5773
|
+
"gate.size.declared": "size {effective}: {files, plural, one {# file} other {# files}}, {lines, plural, one {# line} other {# lines}}",
|
|
5774
|
+
"gate.size.grew": "declared {declared}, the diff makes it {effective}: {files, plural, one {# file} other {# files}}, {lines, plural, one {# line} other {# lines}}",
|
|
5775
|
+
"gate.size.sensitive": "sensitive path touched: {file}",
|
|
5776
|
+
"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",
|
|
5777
|
+
"gate.review.done": "reviewed by {agent} at {at}",
|
|
5778
|
+
"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.",
|
|
5779
|
+
"gate.secrets.warning": "warning: {rule} at {file}:{line}",
|
|
5780
|
+
"doctor.secrets.remote_credential": "git remote {remotes} has a credential in its URL, stored in plain text in .git/config",
|
|
5781
|
+
"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",
|
|
5782
|
+
"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.",
|
|
5783
|
+
"gate.require.callers": "no removed or renamed symbol is still called",
|
|
5784
|
+
"gate.callers.none": "{count, plural, one {# removed definition} other {# removed definitions}}, no caller left",
|
|
5785
|
+
"gate.callers.open": "{name} is gone but still called in {count, plural, one {# place} other {# places}}",
|
|
5786
|
+
"gate.callers.at": "{name} called at {file}:{line}",
|
|
5787
|
+
"gate.callers.warning": "warning only: {percent}% of callers findings were released with callers_ignore ({released} of {total}), above the 5% ceiling",
|
|
5788
|
+
"command.policy_false_positive.summary": "Record a false positive of a guard in shadow so it is not promoted",
|
|
5789
|
+
"policy.error.person_only": "Only the person can record a false positive, and this does not look like their terminal (no TTY, or running inside Claude Code)",
|
|
5790
|
+
"policy.false_positive.recorded": "Recorded for {guard}: {count, plural, one {# false positive} other {# false positives}}; it stays in shadow until you set it in .finitemonkey/policy.yml."
|
|
5462
5791
|
};
|
|
5463
5792
|
|
|
5464
5793
|
// src/i18n/es.json
|
|
@@ -5971,7 +6300,26 @@ var es_default = {
|
|
|
5971
6300
|
"history.pattern.no_verify": "{count, plural, one {# comando git} other {# comandos git}} con --no-verify",
|
|
5972
6301
|
"history.pattern.force_push": "{count, plural, one {# push forzado} other {# push forzados}}",
|
|
5973
6302
|
"history.pattern.test_skip": "{count, plural, one {# edición} other {# ediciones}} de pruebas que añadieron .skip, .only o una marca para saltar",
|
|
5974
|
-
"history.estimate": "Son estimaciones por patrones de texto, no evidencia; no se guardó nada y nada salió de esta máquina."
|
|
6303
|
+
"history.estimate": "Son estimaciones por patrones de texto, no evidencia; no se guardó nada y nada salió de esta máquina.",
|
|
6304
|
+
"gate.require.review": "un subagente revisor leyó el cambio en su estado actual",
|
|
6305
|
+
"gate.size.declared": "tamaño {effective}: {files, plural, one {# archivo} other {# archivos}}, {lines, plural, one {# línea} other {# líneas}}",
|
|
6306
|
+
"gate.size.grew": "declarada {declared}, el diff la vuelve {effective}: {files, plural, one {# archivo} other {# archivos}}, {lines, plural, one {# línea} other {# líneas}}",
|
|
6307
|
+
"gate.size.sensitive": "ruta sensible tocada: {file}",
|
|
6308
|
+
"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",
|
|
6309
|
+
"gate.review.done": "revisado por {agent} en {at}",
|
|
6310
|
+
"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.",
|
|
6311
|
+
"gate.secrets.warning": "aviso: {rule} en {file}:{line}",
|
|
6312
|
+
"doctor.secrets.remote_credential": "el remoto git {remotes} tiene una credencial en la URL, guardada en texto plano en .git/config",
|
|
6313
|
+
"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",
|
|
6314
|
+
"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.",
|
|
6315
|
+
"gate.require.callers": "ningún símbolo eliminado o renombrado sigue siendo llamado",
|
|
6316
|
+
"gate.callers.none": "{count, plural, one {# definición eliminada} other {# definiciones eliminadas}}, ningún llamador quedó",
|
|
6317
|
+
"gate.callers.open": "{name} ya no existe, pero se llama en {count, plural, one {# lugar} other {# lugares}}",
|
|
6318
|
+
"gate.callers.at": "{name} llamado en {file}:{line}",
|
|
6319
|
+
"gate.callers.warning": "solo aviso: {percent}% de los hallazgos de callers se liberaron con callers_ignore ({released} de {total}), por encima del techo de 5%",
|
|
6320
|
+
"command.policy_false_positive.summary": "Registra un falso positivo de un guard en shadow para que no se promueva",
|
|
6321
|
+
"policy.error.person_only": "Solo la persona puede registrar un falso positivo, y esto no parece su terminal (sin TTY, o dentro de Claude Code)",
|
|
6322
|
+
"policy.false_positive.recorded": "Registrado para {guard}: {count, plural, one {# falso positivo} other {# falsos positivos}}; se queda en shadow hasta que fijes el modo en .finitemonkey/policy.yml."
|
|
5975
6323
|
};
|
|
5976
6324
|
|
|
5977
6325
|
// src/i18n/pt-BR.json
|
|
@@ -6484,7 +6832,26 @@ var pt_BR_default = {
|
|
|
6484
6832
|
"history.pattern.no_verify": "{count, plural, one {# comando git} other {# comandos git}} com --no-verify",
|
|
6485
6833
|
"history.pattern.force_push": "{count, plural, one {# push forçado} other {# pushes forçados}}",
|
|
6486
6834
|
"history.pattern.test_skip": "{count, plural, one {# edição} other {# edições}} de teste que acrescentaram .skip, .only ou marcador de pular",
|
|
6487
|
-
"history.estimate": "São estimativas por padrão de texto, não evidência; nada foi salvo e nada saiu desta máquina."
|
|
6835
|
+
"history.estimate": "São estimativas por padrão de texto, não evidência; nada foi salvo e nada saiu desta máquina.",
|
|
6836
|
+
"gate.require.review": "um subagente revisor leu a mudança no estado atual",
|
|
6837
|
+
"gate.size.declared": "tamanho {effective}: {files, plural, one {# arquivo} other {# arquivos}}, {lines, plural, one {# linha} other {# linhas}}",
|
|
6838
|
+
"gate.size.grew": "declarada {declared}, o diff a torna {effective}: {files, plural, one {# arquivo} other {# arquivos}}, {lines, plural, one {# linha} other {# linhas}}",
|
|
6839
|
+
"gate.size.sensitive": "caminho sensível alterado: {file}",
|
|
6840
|
+
"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",
|
|
6841
|
+
"gate.review.done": "revisado por {agent} em {at}",
|
|
6842
|
+
"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.",
|
|
6843
|
+
"gate.secrets.warning": "aviso: {rule} em {file}:{line}",
|
|
6844
|
+
"doctor.secrets.remote_credential": "o remoto git {remotes} tem uma credencial na URL, guardada em texto puro no .git/config",
|
|
6845
|
+
"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",
|
|
6846
|
+
"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.",
|
|
6847
|
+
"gate.require.callers": "nenhum símbolo removido ou renomeado continua sendo chamado",
|
|
6848
|
+
"gate.callers.none": "{count, plural, one {# definição removida} other {# definições removidas}}, nenhum chamador sobrou",
|
|
6849
|
+
"gate.callers.open": "{name} saiu, mas é chamado em {count, plural, one {# lugar} other {# lugares}}",
|
|
6850
|
+
"gate.callers.at": "{name} chamado em {file}:{line}",
|
|
6851
|
+
"gate.callers.warning": "só aviso: {percent}% dos achados de callers foram liberados com callers_ignore ({released} de {total}), acima do teto de 5%",
|
|
6852
|
+
"command.policy_false_positive.summary": "Registra um falso positivo de um guard em shadow para ele não ser promovido",
|
|
6853
|
+
"policy.error.person_only": "Só a pessoa pode registrar um falso positivo, e isto não parece o terminal dela (sem TTY, ou rodando dentro do Claude Code)",
|
|
6854
|
+
"policy.false_positive.recorded": "Registrado para {guard}: {count, plural, one {# falso positivo} other {# falsos positivos}}; ele fica em shadow até você definir o modo em .finitemonkey/policy.yml."
|
|
6488
6855
|
};
|
|
6489
6856
|
|
|
6490
6857
|
// src/i18n/review.json
|
|
@@ -6998,7 +7365,26 @@ var review_default = {
|
|
|
6998
7365
|
"history.pattern.no_verify": "machine",
|
|
6999
7366
|
"history.pattern.force_push": "machine",
|
|
7000
7367
|
"history.pattern.test_skip": "machine",
|
|
7001
|
-
"history.estimate": "machine"
|
|
7368
|
+
"history.estimate": "machine",
|
|
7369
|
+
"gate.require.review": "machine",
|
|
7370
|
+
"gate.size.declared": "machine",
|
|
7371
|
+
"gate.size.grew": "machine",
|
|
7372
|
+
"gate.size.sensitive": "machine",
|
|
7373
|
+
"gate.review.missing": "machine",
|
|
7374
|
+
"gate.review.done": "machine",
|
|
7375
|
+
"secrets.warn.write": "machine",
|
|
7376
|
+
"gate.secrets.warning": "machine",
|
|
7377
|
+
"doctor.secrets.remote_credential": "machine",
|
|
7378
|
+
"doctor.secrets.fix_remote_credential": "machine",
|
|
7379
|
+
"secrets.session.remote_credential": "machine",
|
|
7380
|
+
"gate.require.callers": "machine",
|
|
7381
|
+
"gate.callers.none": "machine",
|
|
7382
|
+
"gate.callers.open": "machine",
|
|
7383
|
+
"gate.callers.at": "machine",
|
|
7384
|
+
"gate.callers.warning": "machine",
|
|
7385
|
+
"command.policy_false_positive.summary": "machine",
|
|
7386
|
+
"policy.error.person_only": "machine",
|
|
7387
|
+
"policy.false_positive.recorded": "machine"
|
|
7002
7388
|
},
|
|
7003
7389
|
es: {
|
|
7004
7390
|
"command.init.summary": "machine",
|
|
@@ -7509,7 +7895,26 @@ var review_default = {
|
|
|
7509
7895
|
"history.pattern.no_verify": "machine",
|
|
7510
7896
|
"history.pattern.force_push": "machine",
|
|
7511
7897
|
"history.pattern.test_skip": "machine",
|
|
7512
|
-
"history.estimate": "machine"
|
|
7898
|
+
"history.estimate": "machine",
|
|
7899
|
+
"gate.require.review": "machine",
|
|
7900
|
+
"gate.size.declared": "machine",
|
|
7901
|
+
"gate.size.grew": "machine",
|
|
7902
|
+
"gate.size.sensitive": "machine",
|
|
7903
|
+
"gate.review.missing": "machine",
|
|
7904
|
+
"gate.review.done": "machine",
|
|
7905
|
+
"secrets.warn.write": "machine",
|
|
7906
|
+
"gate.secrets.warning": "machine",
|
|
7907
|
+
"doctor.secrets.remote_credential": "machine",
|
|
7908
|
+
"doctor.secrets.fix_remote_credential": "machine",
|
|
7909
|
+
"secrets.session.remote_credential": "machine",
|
|
7910
|
+
"gate.require.callers": "machine",
|
|
7911
|
+
"gate.callers.none": "machine",
|
|
7912
|
+
"gate.callers.open": "machine",
|
|
7913
|
+
"gate.callers.at": "machine",
|
|
7914
|
+
"gate.callers.warning": "machine",
|
|
7915
|
+
"command.policy_false_positive.summary": "machine",
|
|
7916
|
+
"policy.error.person_only": "machine",
|
|
7917
|
+
"policy.false_positive.recorded": "machine"
|
|
7513
7918
|
}
|
|
7514
7919
|
};
|
|
7515
7920
|
|
|
@@ -7632,7 +8037,7 @@ var buildReport = (input) => {
|
|
|
7632
8037
|
);
|
|
7633
8038
|
return fromEvidence(check2.id, check2.kind, entry2, hasStale(ledger, check2.id, treeHash2));
|
|
7634
8039
|
});
|
|
7635
|
-
const
|
|
8040
|
+
const count4 = (status) => items.filter((item) => item.status === status).length;
|
|
7636
8041
|
return {
|
|
7637
8042
|
schema: GATE_SCHEMA,
|
|
7638
8043
|
task: contract.id,
|
|
@@ -7643,7 +8048,7 @@ var buildReport = (input) => {
|
|
|
7643
8048
|
source: input.source,
|
|
7644
8049
|
contractEdited: input.contractEdited,
|
|
7645
8050
|
verdict: verdictOf(items),
|
|
7646
|
-
counts: { passed:
|
|
8051
|
+
counts: { passed: count4("passed"), failed: count4("failed"), unverified: count4("unverified") },
|
|
7647
8052
|
items,
|
|
7648
8053
|
decisionsNeeded: contract.decisionsNeeded,
|
|
7649
8054
|
fastPath: input.fastPath
|
|
@@ -7767,6 +8172,277 @@ var preCompact = (input) => {
|
|
|
7767
8172
|
return { kind: "allow" };
|
|
7768
8173
|
};
|
|
7769
8174
|
|
|
8175
|
+
// src/gate/review.ts
|
|
8176
|
+
var REVIEWS_FILE = "reviews.json";
|
|
8177
|
+
var REVIEWER_AGENT = /(^|:)[\w-]*reviewer$/i;
|
|
8178
|
+
var KEEP = 50;
|
|
8179
|
+
var toReview = (value) => {
|
|
8180
|
+
if (!isRecord(value)) return void 0;
|
|
8181
|
+
const { treeHash: treeHash2, agent, at } = value;
|
|
8182
|
+
return typeof treeHash2 === "string" && typeof agent === "string" && typeof at === "string" ? { treeHash: treeHash2, agent, at } : void 0;
|
|
8183
|
+
};
|
|
8184
|
+
var readReviews = (runtime, paths) => {
|
|
8185
|
+
const raw = readJsonFile(runtime, paths.cache(REVIEWS_FILE))?.["reviews"];
|
|
8186
|
+
return Array.isArray(raw) ? raw.flatMap((item) => {
|
|
8187
|
+
const review2 = toReview(item);
|
|
8188
|
+
return review2 === void 0 ? [] : [review2];
|
|
8189
|
+
}) : [];
|
|
8190
|
+
};
|
|
8191
|
+
var recordReview = (runtime, paths, treeHash2, agent) => {
|
|
8192
|
+
const reviews = [...readReviews(runtime, paths), { treeHash: treeHash2, agent, at: runtime.now() }].slice(-KEEP);
|
|
8193
|
+
return writeJsonFile(runtime, paths.cache(REVIEWS_FILE), { reviews: reviews.map((review2) => ({ ...review2 })) }).ok;
|
|
8194
|
+
};
|
|
8195
|
+
|
|
8196
|
+
// src/gate/callers/rate.ts
|
|
8197
|
+
var FLAGGED_EVENT = "callers.flagged";
|
|
8198
|
+
var RELEASED_EVENT = "callers.released";
|
|
8199
|
+
var MIN_SAMPLE = 20;
|
|
8200
|
+
var MAX_FALSE_POSITIVE = 0.05;
|
|
8201
|
+
var pairs = (record3) => {
|
|
8202
|
+
const { task, names } = record3;
|
|
8203
|
+
if (typeof task !== "string" || !Array.isArray(names)) return [];
|
|
8204
|
+
return names.filter((name) => typeof name === "string").map((name) => `${task}\0${name}`);
|
|
8205
|
+
};
|
|
8206
|
+
var callersRate = (runtime, paths) => {
|
|
8207
|
+
const text6 = runtime.readText(paths.log);
|
|
8208
|
+
const flagged = /* @__PURE__ */ new Set();
|
|
8209
|
+
const released2 = /* @__PURE__ */ new Set();
|
|
8210
|
+
for (const line2 of text6.ok ? text6.value.split("\n") : []) {
|
|
8211
|
+
const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
|
|
8212
|
+
if (parsed === void 0 || !parsed.ok || !isRecord(parsed.value)) continue;
|
|
8213
|
+
const event = parsed.value["event"];
|
|
8214
|
+
const target = event === FLAGGED_EVENT ? flagged : event === RELEASED_EVENT ? released2 : void 0;
|
|
8215
|
+
if (target !== void 0) for (const pair of pairs(parsed.value)) target.add(pair);
|
|
8216
|
+
}
|
|
8217
|
+
const total = (/* @__PURE__ */ new Set([...flagged, ...released2])).size;
|
|
8218
|
+
const rate = total === 0 ? 0 : released2.size / total;
|
|
8219
|
+
return {
|
|
8220
|
+
flagged: total,
|
|
8221
|
+
released: released2.size,
|
|
8222
|
+
rate,
|
|
8223
|
+
downgraded: total >= MIN_SAMPLE && rate > MAX_FALSE_POSITIVE
|
|
8224
|
+
};
|
|
8225
|
+
};
|
|
8226
|
+
|
|
8227
|
+
// src/gate/callers/scan.ts
|
|
8228
|
+
import { join as join16 } from "node:path";
|
|
8229
|
+
|
|
8230
|
+
// src/gate/callers/imports.ts
|
|
8231
|
+
import { posix as posix2 } from "node:path";
|
|
8232
|
+
var JS_SPECIFIER = /(?:\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)["']([^"']+)["']/g;
|
|
8233
|
+
var JS_EXTENSION = /\.(?:[cm]?[jt]sx?)$/;
|
|
8234
|
+
var stripModule = (path) => path.replace(JS_EXTENSION, "").replace(/\/index$/, "");
|
|
8235
|
+
var jsSpecifiers = (text6) => [...text6.matchAll(JS_SPECIFIER)].map((match) => match[1] ?? "");
|
|
8236
|
+
var jsImports = (callerPath, callerText, origin) => {
|
|
8237
|
+
const target = stripModule(origin);
|
|
8238
|
+
return jsSpecifiers(callerText).some((specifier) => {
|
|
8239
|
+
if (specifier.startsWith(".")) {
|
|
8240
|
+
return stripModule(posix2.normalize(posix2.join(posix2.dirname(callerPath), specifier))) === target;
|
|
8241
|
+
}
|
|
8242
|
+
const bare = stripModule(specifier.replace(/^[@~#]\//, ""));
|
|
8243
|
+
return target === bare || target.endsWith(`/${bare}`);
|
|
8244
|
+
});
|
|
8245
|
+
};
|
|
8246
|
+
var pyImports = (callerText, origin) => {
|
|
8247
|
+
const stem = posix2.basename(origin).replace(/\.py$/, "");
|
|
8248
|
+
const module = stem === "__init__" ? posix2.basename(posix2.dirname(origin)) : stem;
|
|
8249
|
+
const word2 = new RegExp(`\\b${module}\\b`);
|
|
8250
|
+
return callerText.split(/\r?\n/).some((line2) => /^\s*(?:from|import)\s/.test(line2) && word2.test(line2));
|
|
8251
|
+
};
|
|
8252
|
+
var goImports = (callerPath, callerText, origin) => {
|
|
8253
|
+
const dir = posix2.dirname(origin);
|
|
8254
|
+
if (posix2.dirname(callerPath) === dir) return true;
|
|
8255
|
+
const last = posix2.basename(dir);
|
|
8256
|
+
return new RegExp(`"[^"]*\\b${last}"`).test(callerText);
|
|
8257
|
+
};
|
|
8258
|
+
var importsOrigin = (language, callerPath, callerText, origins) => origins.some((origin) => {
|
|
8259
|
+
if (callerPath === origin) return true;
|
|
8260
|
+
switch (language.imports) {
|
|
8261
|
+
case "none":
|
|
8262
|
+
return true;
|
|
8263
|
+
case "package":
|
|
8264
|
+
return goImports(callerPath, callerText, origin);
|
|
8265
|
+
case "module":
|
|
8266
|
+
return language.id === "py" ? pyImports(callerText, origin) : jsImports(callerPath, callerText, origin);
|
|
8267
|
+
}
|
|
8268
|
+
});
|
|
8269
|
+
|
|
8270
|
+
// src/gate/callers/languages.ts
|
|
8271
|
+
import { extname } from "node:path";
|
|
8272
|
+
var JS_NAME = "[A-Za-z_$][\\w$]*";
|
|
8273
|
+
var LANGUAGES2 = [
|
|
8274
|
+
{
|
|
8275
|
+
id: "js",
|
|
8276
|
+
extensions: [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"],
|
|
8277
|
+
definitions: [
|
|
8278
|
+
new RegExp(`^\\s*(?:export\\s+)?(?:default\\s+)?(?:async\\s+)?function\\s*\\*?\\s*(${JS_NAME})`),
|
|
8279
|
+
new RegExp(`^\\s*(?:export\\s+)?(?:default\\s+)?(?:abstract\\s+)?class\\s+(${JS_NAME})`),
|
|
8280
|
+
new RegExp(`^\\s*export\\s+(?:declare\\s+)?(?:const|let|var|enum|interface|type)\\s+(${JS_NAME})`),
|
|
8281
|
+
new RegExp(
|
|
8282
|
+
`^\\s+(?:public\\s+)?(?:static\\s+)?(?:async\\s+)?(${JS_NAME})\\s*\\([^)]*\\)\\s*(?::[^{=]+)?\\{\\s*$`
|
|
8283
|
+
)
|
|
8284
|
+
],
|
|
8285
|
+
imports: "module",
|
|
8286
|
+
comment: /^\s*(?:\/\/|\/\*|\*)/
|
|
8287
|
+
},
|
|
8288
|
+
{
|
|
8289
|
+
id: "py",
|
|
8290
|
+
extensions: [".py"],
|
|
8291
|
+
definitions: [/^\s*(?:async\s+)?def\s+([A-Za-z]\w*)/, /^\s*class\s+([A-Za-z]\w*)/],
|
|
8292
|
+
imports: "module",
|
|
8293
|
+
comment: /^\s*#/
|
|
8294
|
+
},
|
|
8295
|
+
{
|
|
8296
|
+
id: "go",
|
|
8297
|
+
extensions: [".go"],
|
|
8298
|
+
definitions: [/^func\s+(?:\([^)]*\)\s*)?([A-Za-z]\w*)/, /^type\s+([A-Za-z]\w*)\s+(?:struct|interface)/],
|
|
8299
|
+
imports: "package",
|
|
8300
|
+
comment: /^\s*\/\//
|
|
8301
|
+
},
|
|
8302
|
+
{
|
|
8303
|
+
id: "rb",
|
|
8304
|
+
extensions: [".rb"],
|
|
8305
|
+
definitions: [/^\s*def\s+(?:self\.)?([A-Za-z]\w*[?!]?)/, /^\s*(?:class|module)\s+([A-Z]\w*)/],
|
|
8306
|
+
imports: "none",
|
|
8307
|
+
comment: /^\s*#/
|
|
8308
|
+
},
|
|
8309
|
+
{
|
|
8310
|
+
id: "rs",
|
|
8311
|
+
extensions: [".rs"],
|
|
8312
|
+
definitions: [
|
|
8313
|
+
/^\s*(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?fn\s+([A-Za-z]\w*)/,
|
|
8314
|
+
/^\s*(?:pub\s+)?(?:struct|enum|trait)\s+([A-Z]\w*)/
|
|
8315
|
+
],
|
|
8316
|
+
imports: "none",
|
|
8317
|
+
comment: /^\s*\/\//
|
|
8318
|
+
}
|
|
8319
|
+
];
|
|
8320
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
8321
|
+
"if",
|
|
8322
|
+
"for",
|
|
8323
|
+
"while",
|
|
8324
|
+
"switch",
|
|
8325
|
+
"catch",
|
|
8326
|
+
"function",
|
|
8327
|
+
"return",
|
|
8328
|
+
"constructor",
|
|
8329
|
+
"with",
|
|
8330
|
+
"do",
|
|
8331
|
+
"else"
|
|
8332
|
+
]);
|
|
8333
|
+
var languageOf = (path) => {
|
|
8334
|
+
const extension = extname(path).toLowerCase();
|
|
8335
|
+
return LANGUAGES2.find((language) => language.extensions.includes(extension));
|
|
8336
|
+
};
|
|
8337
|
+
var definitionsIn = (language, text6) => {
|
|
8338
|
+
const names = /* @__PURE__ */ new Set();
|
|
8339
|
+
for (const line2 of text6.split(/\r?\n/)) {
|
|
8340
|
+
for (const pattern of language.definitions) {
|
|
8341
|
+
const name = pattern.exec(line2)?.[1];
|
|
8342
|
+
if (name !== void 0 && !KEYWORDS.has(name)) names.add(name);
|
|
8343
|
+
}
|
|
8344
|
+
}
|
|
8345
|
+
return names;
|
|
8346
|
+
};
|
|
8347
|
+
var definesName = (language, line2, name) => language.definitions.some((pattern) => pattern.exec(line2)?.[1] === name);
|
|
8348
|
+
|
|
8349
|
+
// src/gate/callers/scan.ts
|
|
8350
|
+
var MAX_SYMBOLS = 200;
|
|
8351
|
+
var current = (runtime, root, path) => {
|
|
8352
|
+
const text6 = runtime.readText(toAbsolutePath(join16(root, path)));
|
|
8353
|
+
return text6.ok ? text6.value : "";
|
|
8354
|
+
};
|
|
8355
|
+
var keyOf = (language, name) => `${language.id}\0${name}`;
|
|
8356
|
+
var redefinedNames = (runtime, git, changes) => {
|
|
8357
|
+
const keys = /* @__PURE__ */ new Set();
|
|
8358
|
+
for (const change of changes) {
|
|
8359
|
+
const language = languageOf(change.path);
|
|
8360
|
+
if (language === void 0 || change.status === "D") continue;
|
|
8361
|
+
for (const name of definitionsIn(language, current(runtime, git.cwd, change.path))) keys.add(keyOf(language, name));
|
|
8362
|
+
}
|
|
8363
|
+
return keys;
|
|
8364
|
+
};
|
|
8365
|
+
var removedSymbols = (runtime, git, base, changes) => {
|
|
8366
|
+
const redefined = redefinedNames(runtime, git, changes);
|
|
8367
|
+
const found = /* @__PURE__ */ new Map();
|
|
8368
|
+
for (const change of changes) {
|
|
8369
|
+
const language = languageOf(change.oldPath ?? change.path);
|
|
8370
|
+
if (language === void 0 || change.status === "A") continue;
|
|
8371
|
+
const before = definitionsIn(language, fileAt(git, base, change.oldPath ?? change.path) ?? "");
|
|
8372
|
+
const origins = [change.path, ...change.oldPath === void 0 ? [] : [change.oldPath]];
|
|
8373
|
+
for (const name of before) {
|
|
8374
|
+
const key = keyOf(language, name);
|
|
8375
|
+
if (redefined.has(key)) continue;
|
|
8376
|
+
const prior = found.get(key);
|
|
8377
|
+
found.set(key, { name, language, origins: [...prior?.origins ?? [], ...origins] });
|
|
8378
|
+
}
|
|
8379
|
+
}
|
|
8380
|
+
return [...found.values()].slice(0, MAX_SYMBOLS);
|
|
8381
|
+
};
|
|
8382
|
+
var parseGrep = (output) => output.split("\n").filter((row) => row !== "").flatMap((row) => {
|
|
8383
|
+
const [file = "", line2 = "", ...rest] = row.split("\0");
|
|
8384
|
+
const number = Number(line2);
|
|
8385
|
+
return file === "" || !Number.isInteger(number) ? [] : [{ file: toPosixPath(file), line: number, text: rest.join("\0") }];
|
|
8386
|
+
});
|
|
8387
|
+
var grep = (git, names) => {
|
|
8388
|
+
if (names.length === 0) return [];
|
|
8389
|
+
const patterns = names.flatMap((name) => ["-e", name]);
|
|
8390
|
+
return parseGrep(
|
|
8391
|
+
git.out(["grep", "-n", "-z", "-w", "-I", "--untracked", "--no-color", ...patterns, "--", ".", EXCLUDE_FM]) ?? ""
|
|
8392
|
+
);
|
|
8393
|
+
};
|
|
8394
|
+
var mentions = (text6, name) => new RegExp(`(?<![\\w$])${name.replace(/[$?!]/g, "\\$&")}(?![\\w$])`).test(text6);
|
|
8395
|
+
var callSites = (runtime, git, symbols) => {
|
|
8396
|
+
const hits = grep(git, [...new Set(symbols.map((symbol) => symbol.name))]);
|
|
8397
|
+
const texts = /* @__PURE__ */ new Map();
|
|
8398
|
+
const textOf4 = (file) => {
|
|
8399
|
+
const cached2 = texts.get(file) ?? current(runtime, git.cwd, file);
|
|
8400
|
+
texts.set(file, cached2);
|
|
8401
|
+
return cached2;
|
|
8402
|
+
};
|
|
8403
|
+
return symbols.flatMap(
|
|
8404
|
+
(symbol) => hits.filter((hit3) => languageOf(hit3.file)?.id === symbol.language.id && mentions(hit3.text, symbol.name)).filter((hit3) => !symbol.language.comment.test(hit3.text) && !definesName(symbol.language, hit3.text, symbol.name)).filter((hit3) => importsOrigin(symbol.language, hit3.file, textOf4(hit3.file), symbol.origins)).map((hit3) => ({ name: symbol.name, file: hit3.file, line: hit3.line }))
|
|
8405
|
+
);
|
|
8406
|
+
};
|
|
8407
|
+
|
|
8408
|
+
// src/gate/checks/callers.ts
|
|
8409
|
+
var MAX_SITES = 10;
|
|
8410
|
+
var byName = (sites) => {
|
|
8411
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
8412
|
+
for (const site of sites) grouped.set(site.name, [...grouped.get(site.name) ?? [], site]);
|
|
8413
|
+
return grouped;
|
|
8414
|
+
};
|
|
8415
|
+
var siteNotes = (open) => [
|
|
8416
|
+
...[...open].map(([name, sites]) => note("gate.callers.open", { name, count: sites.length })),
|
|
8417
|
+
...[...open.values()].flat().slice(0, MAX_SITES).map((site) => note("gate.callers.at", { name: site.name, file: site.file, line: site.line }))
|
|
8418
|
+
];
|
|
8419
|
+
var record = (ctx, event, names) => {
|
|
8420
|
+
if (names.length > 0) logEvent(ctx.runtime, ctx.paths, event, { task: ctx.contract.id, names: [...names] });
|
|
8421
|
+
};
|
|
8422
|
+
var callersCheck = (_check, ctx) => {
|
|
8423
|
+
const symbols = removedSymbols(ctx.runtime, ctx.git, ctx.base, ctx.changes);
|
|
8424
|
+
const grouped = byName(callSites(ctx.runtime, ctx.git, symbols));
|
|
8425
|
+
const ignore = new Set(ctx.contract.callersIgnore ?? []);
|
|
8426
|
+
const open = new Map([...grouped].filter(([name]) => !ignore.has(name)));
|
|
8427
|
+
record(
|
|
8428
|
+
ctx,
|
|
8429
|
+
RELEASED_EVENT,
|
|
8430
|
+
[...grouped.keys()].filter((name) => ignore.has(name))
|
|
8431
|
+
);
|
|
8432
|
+
record(ctx, FLAGGED_EVENT, [...open.keys()]);
|
|
8433
|
+
if (open.size === 0) return passed([note("gate.callers.none", { count: symbols.length })]);
|
|
8434
|
+
const rate = callersRate(ctx.runtime, ctx.paths);
|
|
8435
|
+
if (!rate.downgraded) return failed(siteNotes(open));
|
|
8436
|
+
return passed([
|
|
8437
|
+
note("gate.callers.warning", {
|
|
8438
|
+
percent: Math.round(rate.rate * 100),
|
|
8439
|
+
released: rate.released,
|
|
8440
|
+
total: rate.flagged
|
|
8441
|
+
}),
|
|
8442
|
+
...siteNotes(open)
|
|
8443
|
+
]);
|
|
8444
|
+
};
|
|
8445
|
+
|
|
7770
8446
|
// src/gate/test-summary.ts
|
|
7771
8447
|
var ANSI2 = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
7772
8448
|
var COUNTERS = [
|
|
@@ -7906,44 +8582,14 @@ var runShellCheck = (check2, run4, ctx) => {
|
|
|
7906
8582
|
};
|
|
7907
8583
|
var commandCheck = (check2, ctx) => check2.run === void 0 ? unverified("not_implemented") : runShellCheck(check2, check2.run, ctx);
|
|
7908
8584
|
|
|
7909
|
-
// src/gate/checks/diff.ts
|
|
7910
|
-
import { join as join13 } from "node:path";
|
|
7911
|
-
|
|
7912
|
-
// src/core/glob.ts
|
|
7913
|
-
var escape = (char) => /[.+^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
|
|
7914
|
-
var globToRegExp = (glob) => {
|
|
7915
|
-
const pattern = glob.replace(/^\.\//, "");
|
|
7916
|
-
let out = "";
|
|
7917
|
-
for (let index = 0; index < pattern.length; index += 1) {
|
|
7918
|
-
const char = pattern[index] ?? "";
|
|
7919
|
-
if (char === "*" && pattern[index + 1] === "*") {
|
|
7920
|
-
const slash = pattern[index + 2] === "/";
|
|
7921
|
-
out += slash ? "(?:.*/)?" : ".*";
|
|
7922
|
-
index += slash ? 2 : 1;
|
|
7923
|
-
} else if (char === "*") out += "[^/]*";
|
|
7924
|
-
else if (char === "?") out += "[^/]";
|
|
7925
|
-
else out += escape(char);
|
|
7926
|
-
}
|
|
7927
|
-
return new RegExp(`^${out}${pattern.endsWith("/") ? ".*" : ""}$`);
|
|
7928
|
-
};
|
|
7929
|
-
var matchesAny = (path, globs) => {
|
|
7930
|
-
const normalized = toPosixPath(path);
|
|
7931
|
-
return globs.some((glob) => globToRegExp(glob).test(normalized));
|
|
7932
|
-
};
|
|
7933
|
-
|
|
7934
8585
|
// src/gate/checks/diff.ts
|
|
7935
8586
|
var MAX_LISTED = 10;
|
|
7936
|
-
var untrackedLines = (ctx) => ctx.changes.filter((change) => change.status === "A").reduce((sum, change) => {
|
|
7937
|
-
const text6 = ctx.runtime.readText(toAbsolutePath(join13(ctx.paths.root, change.path)));
|
|
7938
|
-
return sum + (text6.ok ? text6.value.split("\n").length : 0);
|
|
7939
|
-
}, 0);
|
|
7940
8587
|
var diffCheck = (_check, ctx) => {
|
|
7941
8588
|
const { paths, diffBudget } = ctx.contract.scope;
|
|
7942
8589
|
if (paths.length === 0 && diffBudget === void 0) return passed([note("gate.diff.no_scope")]);
|
|
7943
8590
|
const outside = paths.length === 0 ? [] : ctx.changes.filter((change) => !matchesAny(change.path, paths));
|
|
7944
|
-
const stat = numstat(ctx.git, ctx.base);
|
|
7945
8591
|
const files = ctx.changes.length;
|
|
7946
|
-
const lines3 =
|
|
8592
|
+
const lines3 = diffShape(ctx.runtime, ctx.git, ctx.base, ctx.changes).lines;
|
|
7947
8593
|
const notes2 = [
|
|
7948
8594
|
...outside.slice(0, MAX_LISTED).map((change) => note("gate.diff.outside", { file: change.path })),
|
|
7949
8595
|
...diffBudget?.files !== void 0 && files > diffBudget.files ? [note("gate.diff.too_many_files", { count: files, max: diffBudget.files })] : [],
|
|
@@ -7993,13 +8639,13 @@ var manualCheck = (check2, ctx) => {
|
|
|
7993
8639
|
};
|
|
7994
8640
|
|
|
7995
8641
|
// src/gate/checks/placeholders.ts
|
|
7996
|
-
import { join as
|
|
8642
|
+
import { join as join17 } from "node:path";
|
|
7997
8643
|
var MAX_LISTED2 = 10;
|
|
7998
8644
|
var SKIP_FILES = /\.(md|mdx|txt|rst|lock|snap|svg|png|jpg|gif|json)$|(^|\/)(CHANGELOG|LICENSE)/i;
|
|
7999
8645
|
var PLACEHOLDER = /\b(TODO|FIXME|XXX)\b|not implemented|NotImplementedError|unimplemented!|todo!\(|lorem ipsum/i;
|
|
8000
8646
|
var findPlaceholders = (lines3) => lines3.filter((line2) => !isTestFile(line2.path) && !SKIP_FILES.test(line2.path) && PLACEHOLDER.test(line2.text));
|
|
8001
8647
|
var untrackedAdded = (ctx) => ctx.changes.filter((change) => change.status === "A" && !SKIP_FILES.test(change.path)).flatMap((change) => {
|
|
8002
|
-
const text6 = ctx.runtime.readText(toAbsolutePath(
|
|
8648
|
+
const text6 = ctx.runtime.readText(toAbsolutePath(join17(ctx.paths.root, change.path)));
|
|
8003
8649
|
return text6.ok ? text6.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
|
|
8004
8650
|
});
|
|
8005
8651
|
var placeholdersCheck = (_check, ctx) => {
|
|
@@ -8511,10 +9157,10 @@ var hit = (rule, units, index, weight) => {
|
|
|
8511
9157
|
return { rule, unit: index, line: unit?.line ?? 0, weight, excerpt: unit?.text ?? "" };
|
|
8512
9158
|
};
|
|
8513
9159
|
var isProse = (unit) => unit?.kind === "sentence";
|
|
8514
|
-
var samePara = (units, from,
|
|
9160
|
+
var samePara = (units, from, count4) => {
|
|
8515
9161
|
const first = units[from];
|
|
8516
9162
|
if (!isProse(first)) return false;
|
|
8517
|
-
return units.slice(from, from +
|
|
9163
|
+
return units.slice(from, from + count4).every((unit) => isProse(unit) && unit.paragraph === first.paragraph);
|
|
8518
9164
|
};
|
|
8519
9165
|
var runsOf = (units, size, matches) => {
|
|
8520
9166
|
const starts = [];
|
|
@@ -8571,13 +9217,13 @@ var emoji = (units) => units.flatMap(
|
|
|
8571
9217
|
var dashes = (units) => {
|
|
8572
9218
|
const perParagraph = /* @__PURE__ */ new Map();
|
|
8573
9219
|
for (const unit of units) {
|
|
8574
|
-
const
|
|
8575
|
-
perParagraph.set(unit.paragraph, (perParagraph.get(unit.paragraph) ?? 0) +
|
|
9220
|
+
const count4 = unit.text.split("—").length - 1;
|
|
9221
|
+
perParagraph.set(unit.paragraph, (perParagraph.get(unit.paragraph) ?? 0) + count4);
|
|
8576
9222
|
}
|
|
8577
|
-
return [...perParagraph].flatMap(([paragraph,
|
|
8578
|
-
if (
|
|
9223
|
+
return [...perParagraph].flatMap(([paragraph, count4]) => {
|
|
9224
|
+
if (count4 === 0) return [];
|
|
8579
9225
|
const index = units.findIndex((unit) => unit.paragraph === paragraph && unit.text.includes("—"));
|
|
8580
|
-
const weight =
|
|
9226
|
+
const weight = count4 >= 2 ? STRUCTURE_WEIGHTS.dashSeries : STRUCTURE_WEIGHTS.dash;
|
|
8581
9227
|
return [hit("em-dash-series", units, index, weight)];
|
|
8582
9228
|
});
|
|
8583
9229
|
};
|
|
@@ -8605,11 +9251,11 @@ var windowEnd = (units, from, size) => {
|
|
|
8605
9251
|
return to;
|
|
8606
9252
|
};
|
|
8607
9253
|
var windowScore = (hits, window) => hits.filter((item) => item.unit >= window.from && item.unit <= window.to).reduce((sum, item) => sum + item.weight, 0);
|
|
8608
|
-
var hotWindows = (hits, units, size,
|
|
9254
|
+
var hotWindows = (hits, units, size, threshold2) => {
|
|
8609
9255
|
const windows = [];
|
|
8610
9256
|
for (let from = 0; from < units.length; from += 1) {
|
|
8611
9257
|
const window = { from, to: windowEnd(units, from, size) };
|
|
8612
|
-
if (windowScore(hits, window) >=
|
|
9258
|
+
if (windowScore(hits, window) >= threshold2) windows.push(window);
|
|
8613
9259
|
}
|
|
8614
9260
|
return windows;
|
|
8615
9261
|
};
|
|
@@ -8636,10 +9282,10 @@ var byPosition = (left, right) => left.unit - right.unit || left.line - right.li
|
|
|
8636
9282
|
var scoreProse = (text6, options = {}) => {
|
|
8637
9283
|
const { units, allowed: allowed2 } = extractProse(text6);
|
|
8638
9284
|
const size = options.window ?? DENSITY_WINDOW;
|
|
8639
|
-
const
|
|
9285
|
+
const threshold2 = options.threshold ?? DENSITY_THRESHOLD;
|
|
8640
9286
|
const catalog = options.context === "record" ? [...phraseRules(), ...recordRules()] : phraseRules();
|
|
8641
9287
|
const hits = [...phraseHits(units, catalog), ...structureHits(units)].sort(byPosition);
|
|
8642
|
-
const spans = mergeSpans(hits, hotWindows(hits, units, size,
|
|
9288
|
+
const spans = mergeSpans(hits, hotWindows(hits, units, size, threshold2));
|
|
8643
9289
|
return { hits, spans, allowed: allowed2 };
|
|
8644
9290
|
};
|
|
8645
9291
|
var isFlagged = (report2) => report2.spans.length > 0;
|
|
@@ -8689,24 +9335,57 @@ var MAX_LISTED3 = 10;
|
|
|
8689
9335
|
var rulesOf = (span) => [...new Set(span.hits.map((hit3) => hit3.rule))].join(", ");
|
|
8690
9336
|
var proseOutcome = (status) => {
|
|
8691
9337
|
if (!status.ok) return unverified("tool_missing", [note("gate.prose.unavailable")]);
|
|
8692
|
-
const { files, flagged, blocking } = status.value;
|
|
8693
|
-
const key =
|
|
9338
|
+
const { files, flagged, blocking: blocking2 } = status.value;
|
|
9339
|
+
const key = blocking2 ? "gate.prose.finding" : "gate.prose.warning";
|
|
8694
9340
|
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) }));
|
|
8695
9341
|
if (notes2.length === 0) return passed([note("gate.prose.clean", { count: files.length })]);
|
|
8696
|
-
return
|
|
9342
|
+
return blocking2 ? failed(notes2) : passed(notes2);
|
|
8697
9343
|
};
|
|
8698
9344
|
var proseCheck = (_check, ctx) => proseOutcome(proseGateStatus(ctx.runtime, ctx.paths.root));
|
|
8699
9345
|
|
|
9346
|
+
// src/gate/checks/review.ts
|
|
9347
|
+
var MAX_SENSITIVE = 5;
|
|
9348
|
+
var sizeNotes = (ctx) => {
|
|
9349
|
+
const size = ctx.size;
|
|
9350
|
+
if (size === void 0) return [];
|
|
9351
|
+
return [
|
|
9352
|
+
note(size.declared === size.effective ? "gate.size.declared" : "gate.size.grew", {
|
|
9353
|
+
declared: size.declared,
|
|
9354
|
+
effective: size.effective,
|
|
9355
|
+
files: size.files,
|
|
9356
|
+
lines: size.lines
|
|
9357
|
+
}),
|
|
9358
|
+
...size.sensitive.slice(0, MAX_SENSITIVE).map((file) => note("gate.size.sensitive", { file }))
|
|
9359
|
+
];
|
|
9360
|
+
};
|
|
9361
|
+
var reviewCheck = (check2, ctx) => {
|
|
9362
|
+
const review2 = readReviews(ctx.runtime, ctx.paths).findLast((entry2) => entry2.treeHash === ctx.treeHash);
|
|
9363
|
+
if (review2 !== void 0) {
|
|
9364
|
+
return { status: "passed", notes: [note("gate.review.done", { agent: review2.agent, at: review2.at })] };
|
|
9365
|
+
}
|
|
9366
|
+
const approval = readApprovals(ctx.runtime, ctx.paths).findLast(
|
|
9367
|
+
(item) => item.task === ctx.contract.id && item.check === check2.id
|
|
9368
|
+
);
|
|
9369
|
+
if (approval !== void 0) {
|
|
9370
|
+
return {
|
|
9371
|
+
status: "passed",
|
|
9372
|
+
notes: [note("gate.manual.approved", { at: approval.at, reason: approval.reason })],
|
|
9373
|
+
releasedBy: `person@${approval.at}`
|
|
9374
|
+
};
|
|
9375
|
+
}
|
|
9376
|
+
return failed([...sizeNotes(ctx), note("gate.review.missing", { check: check2.id })]);
|
|
9377
|
+
};
|
|
9378
|
+
|
|
8700
9379
|
// src/secrets/baseline.ts
|
|
8701
|
-
import { join as
|
|
9380
|
+
import { join as join18 } from "node:path";
|
|
8702
9381
|
var BASELINE_FILE2 = ".finitemonkey/findings/baseline.json";
|
|
8703
9382
|
var BASELINE_SCHEMA = "finitemonkey/secrets-baseline@1";
|
|
8704
9383
|
var BASELINE_STATES = ["aceito", "falso-positivo"];
|
|
8705
9384
|
var EXCEPTION_MAX_DAYS = 90;
|
|
8706
9385
|
var EMPTY_BASELINE = { schema: BASELINE_SCHEMA, entries: [] };
|
|
8707
|
-
var baselinePath = (root) => toAbsolutePath(
|
|
8708
|
-
var text5 = (
|
|
8709
|
-
const value =
|
|
9386
|
+
var baselinePath = (root) => toAbsolutePath(join18(root, BASELINE_FILE2));
|
|
9387
|
+
var text5 = (record3, key) => {
|
|
9388
|
+
const value = record3[key];
|
|
8710
9389
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
8711
9390
|
};
|
|
8712
9391
|
var stateOf = (value) => BASELINE_STATES.find((state) => state === value);
|
|
@@ -8764,22 +9443,22 @@ var withoutFixed = (baseline, fixed) => {
|
|
|
8764
9443
|
var acceptedFingerprints = (baseline, now) => new Set(baseline.entries.filter((entry2) => entry2.expiresAt >= now).map((entry2) => entry2.fingerprint));
|
|
8765
9444
|
|
|
8766
9445
|
// src/secrets/incidents.ts
|
|
8767
|
-
import { join as
|
|
9446
|
+
import { join as join19 } from "node:path";
|
|
8768
9447
|
var INCIDENTS_DIR = ".finitemonkey/incidents";
|
|
8769
9448
|
var INCIDENT_STATES = ["aberto", "exposicao-local", "rotacionado", "fechado"];
|
|
8770
9449
|
var INCIDENT_SOURCES = ["tool-output", "prompt", "history", "scan"];
|
|
8771
9450
|
var ID = /^inc-[0-9a-f]{12}$/;
|
|
8772
9451
|
var incidentId = (rule, fingerprint) => `inc-${sha256Hex(`${rule}\0${fingerprint}`).slice(0, 12)}`;
|
|
8773
|
-
var incidentsDir = (root) => toAbsolutePath(
|
|
8774
|
-
var incidentFile = (root, id) => toAbsolutePath(
|
|
8775
|
-
var
|
|
8776
|
-
var str2 = (
|
|
8777
|
-
const value =
|
|
9452
|
+
var incidentsDir = (root) => toAbsolutePath(join19(root, INCIDENTS_DIR));
|
|
9453
|
+
var incidentFile = (root, id) => toAbsolutePath(join19(incidentsDir(root), `${id}.json`));
|
|
9454
|
+
var pick3 = (options, value) => options.find((option) => option === value);
|
|
9455
|
+
var str2 = (record3, key) => {
|
|
9456
|
+
const value = record3[key];
|
|
8778
9457
|
return typeof value === "string" ? value : void 0;
|
|
8779
9458
|
};
|
|
8780
9459
|
var parseEvent = (value) => {
|
|
8781
9460
|
if (!isRecord(value)) return void 0;
|
|
8782
|
-
const state =
|
|
9461
|
+
const state = pick3(INCIDENT_STATES, value["state"]);
|
|
8783
9462
|
const at = str2(value, "at");
|
|
8784
9463
|
const by = str2(value, "by");
|
|
8785
9464
|
return state === void 0 || at === void 0 || by === void 0 ? void 0 : { state, at, by };
|
|
@@ -8792,12 +9471,12 @@ var parseHistory = (value) => {
|
|
|
8792
9471
|
var parseIncident = (raw) => {
|
|
8793
9472
|
const parsed = parseJsonObject(raw);
|
|
8794
9473
|
if (!parsed.ok) return parsed;
|
|
8795
|
-
const
|
|
8796
|
-
const [id, rule, where, fingerprint, createdAt, updatedAt] = ["id", "rule", "where", "fingerprint", "createdAt", "updatedAt"].map((key) => str2(
|
|
8797
|
-
const state =
|
|
8798
|
-
const source =
|
|
8799
|
-
const history = parseHistory(
|
|
8800
|
-
const firstCommit = str2(
|
|
9474
|
+
const record3 = parsed.value;
|
|
9475
|
+
const [id, rule, where, fingerprint, createdAt, updatedAt] = ["id", "rule", "where", "fingerprint", "createdAt", "updatedAt"].map((key) => str2(record3, key));
|
|
9476
|
+
const state = pick3(INCIDENT_STATES, record3["state"]);
|
|
9477
|
+
const source = pick3(INCIDENT_SOURCES, record3["source"]);
|
|
9478
|
+
const history = parseHistory(record3["history"]);
|
|
9479
|
+
const firstCommit = str2(record3, "firstCommit");
|
|
8801
9480
|
if (id === void 0 || !ID.test(id) || rule === void 0 || where === void 0) return reject("incomplete");
|
|
8802
9481
|
if (fingerprint === void 0 || createdAt === void 0 || updatedAt === void 0) return reject("incomplete");
|
|
8803
9482
|
if (state === void 0 || source === void 0 || history === void 0) return reject("incomplete");
|
|
@@ -8826,7 +9505,7 @@ var listIncidents = (runtime, root) => {
|
|
|
8826
9505
|
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));
|
|
8827
9506
|
};
|
|
8828
9507
|
var saveIncident = (runtime, root, incident) => {
|
|
8829
|
-
const ignore = runtime.writeText(toAbsolutePath(
|
|
9508
|
+
const ignore = runtime.writeText(toAbsolutePath(join19(incidentsDir(root), ".gitignore")), "*\n");
|
|
8830
9509
|
if (!ignore.ok) return ignore;
|
|
8831
9510
|
return runtime.writeText(incidentFile(root, incident.id), `${JSON.stringify(incident, null, 2)}
|
|
8832
9511
|
`);
|
|
@@ -8873,7 +9552,7 @@ var moveIncident = (incident, to, now, by) => ({
|
|
|
8873
9552
|
var isRotated = (incident) => incident.state === "rotacionado" || incident.state === "fechado";
|
|
8874
9553
|
|
|
8875
9554
|
// src/secrets/scan.ts
|
|
8876
|
-
import { join as
|
|
9555
|
+
import { join as join20, relative as relative4 } from "node:path";
|
|
8877
9556
|
|
|
8878
9557
|
// src/secrets/located.ts
|
|
8879
9558
|
var findingId = (ruleId, file, fingerprint) => `fm-${sha256Hex(`${ruleId}\0${file}\0${fingerprint}`).slice(0, 16)}`;
|
|
@@ -8973,7 +9652,7 @@ var listed2 = (runtime, root, target) => {
|
|
|
8973
9652
|
const base = toAbsolutePath(target, root);
|
|
8974
9653
|
const walked = runtime.walkFiles(base);
|
|
8975
9654
|
if (!walked.ok) return walked;
|
|
8976
|
-
return accept(walked.value.map((name) => toPosixPath(relative4(root,
|
|
9655
|
+
return accept(walked.value.map((name) => toPosixPath(relative4(root, join20(base, toPosixPath(name))))));
|
|
8977
9656
|
};
|
|
8978
9657
|
var scanFile = (runtime, root, file) => {
|
|
8979
9658
|
const text6 = runtime.readText(toAbsolutePath(file, root));
|
|
@@ -9033,15 +9712,17 @@ var secretsCheck = (_check, ctx) => {
|
|
|
9033
9712
|
const status = secretsGateStatus(ctx.runtime, ctx.paths.root, sinceOf(ctx));
|
|
9034
9713
|
if (!status.ok) return unverified("tool_missing", [note("gate.secrets.unavailable")]);
|
|
9035
9714
|
const { findings, openIncidents } = status.value;
|
|
9715
|
+
const warnings = findings.filter(isWarning).slice(0, MAX_LISTED4).map((finding3) => note("gate.secrets.warning", { file: finding3.file, line: finding3.line, rule: finding3.ruleId }));
|
|
9036
9716
|
const notes2 = [
|
|
9037
|
-
...findings.slice(0, MAX_LISTED4).map((finding3) => note("gate.secrets.finding", { file: finding3.file, line: finding3.line, rule: finding3.ruleId })),
|
|
9717
|
+
...blocking(findings).slice(0, MAX_LISTED4).map((finding3) => note("gate.secrets.finding", { file: finding3.file, line: finding3.line, rule: finding3.ruleId })),
|
|
9038
9718
|
...openIncidents.slice(0, MAX_LISTED4).map((incident) => note("gate.secrets.incident", { id: incident.id, state: incident.state }))
|
|
9039
9719
|
];
|
|
9040
|
-
|
|
9720
|
+
if (notes2.length > 0) return failed([...notes2, ...warnings]);
|
|
9721
|
+
return passed(warnings.length > 0 ? warnings : [note("gate.secrets.clean")]);
|
|
9041
9722
|
};
|
|
9042
9723
|
|
|
9043
9724
|
// src/gate/checks/tests.ts
|
|
9044
|
-
import { join as
|
|
9725
|
+
import { join as join21 } from "node:path";
|
|
9045
9726
|
|
|
9046
9727
|
// src/guards/tests/tree.ts
|
|
9047
9728
|
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 }] : [];
|
|
@@ -9073,7 +9754,7 @@ var treeViolations = (ctx) => {
|
|
|
9073
9754
|
changes: relevant,
|
|
9074
9755
|
before: (path) => fileAt(ctx.git, ctx.base, path),
|
|
9075
9756
|
after: (path) => {
|
|
9076
|
-
const text6 = ctx.runtime.readText(toAbsolutePath(
|
|
9757
|
+
const text6 = ctx.runtime.readText(toAbsolutePath(join21(ctx.paths.root, path)));
|
|
9077
9758
|
return text6.ok ? text6.value : void 0;
|
|
9078
9759
|
},
|
|
9079
9760
|
acceptedConfigs: ctx.trust.configs,
|
|
@@ -9094,7 +9775,7 @@ var testsCheck = (check2, ctx) => {
|
|
|
9094
9775
|
};
|
|
9095
9776
|
|
|
9096
9777
|
// src/git/tree.ts
|
|
9097
|
-
import { join as
|
|
9778
|
+
import { join as join22 } from "node:path";
|
|
9098
9779
|
|
|
9099
9780
|
// src/secrets/guards/paths.ts
|
|
9100
9781
|
var SAFE_ENV = /^\.env\.(?:example|sample|template|dist|defaults|schema)$/i;
|
|
@@ -9133,7 +9814,7 @@ var dropSensitive = (runtime, repo, env, options) => {
|
|
|
9133
9814
|
return removed.ok ? accept(void 0) : removed;
|
|
9134
9815
|
};
|
|
9135
9816
|
var withTempIndex = (runtime, repo, options, work) => {
|
|
9136
|
-
const path = toAbsolutePath(
|
|
9817
|
+
const path = toAbsolutePath(join22(repo.gitDir, `finitemonkey-${options.stamp}.index`));
|
|
9137
9818
|
const env = { GIT_INDEX_FILE: path, GIT_OPTIONAL_LOCKS: "0" };
|
|
9138
9819
|
try {
|
|
9139
9820
|
return work(env);
|
|
@@ -9366,112 +10047,26 @@ var entryOf2 = (kind, xy, path) => ({
|
|
|
9366
10047
|
staged: kind !== "untracked" && !xy.startsWith("."),
|
|
9367
10048
|
unstaged: kind === "untracked" || xy.charAt(1) !== "."
|
|
9368
10049
|
});
|
|
9369
|
-
var afterFields = (
|
|
10050
|
+
var afterFields = (record3, count4) => record3.split(" ").slice(count4).join(" ");
|
|
9370
10051
|
var parsePorcelainV2 = (stdout) => {
|
|
9371
10052
|
const records = stdout.split("\0");
|
|
9372
10053
|
const out = [];
|
|
9373
10054
|
for (let index = 0; index < records.length; index += 1) {
|
|
9374
|
-
const
|
|
9375
|
-
const xy =
|
|
9376
|
-
if (
|
|
9377
|
-
else if (
|
|
9378
|
-
out.push(entryOf2("changed", xy, afterFields(
|
|
10055
|
+
const record3 = records[index] ?? "";
|
|
10056
|
+
const xy = record3.split(" ")[1] ?? "..";
|
|
10057
|
+
if (record3.startsWith("1 ")) out.push(entryOf2("changed", xy, afterFields(record3, PORCELAIN_FIELDS.ordinary)));
|
|
10058
|
+
else if (record3.startsWith("2 ")) {
|
|
10059
|
+
out.push(entryOf2("changed", xy, afterFields(record3, PORCELAIN_FIELDS.renamed)));
|
|
9379
10060
|
index += 1;
|
|
9380
|
-
} else if (
|
|
9381
|
-
out.push(entryOf2("unmerged", xy, afterFields(
|
|
9382
|
-
} else if (
|
|
10061
|
+
} else if (record3.startsWith("u ")) {
|
|
10062
|
+
out.push(entryOf2("unmerged", xy, afterFields(record3, PORCELAIN_FIELDS.unmerged)));
|
|
10063
|
+
} else if (record3.startsWith("? ")) out.push(entryOf2("untracked", "??", record3.slice(2)));
|
|
9383
10064
|
}
|
|
9384
10065
|
return out.filter((entry2) => entry2.path !== "" && !entry2.path.startsWith(STATE_PREFIX));
|
|
9385
10066
|
};
|
|
9386
10067
|
|
|
9387
|
-
// src/git/project-config.ts
|
|
9388
|
-
import { join as join20 } from "node:path";
|
|
9389
|
-
var CONFIG_FILE3 = ".finitemonkey/config.yml";
|
|
9390
|
-
var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
|
|
9391
|
-
var stripComment2 = (line2) => {
|
|
9392
|
-
let quote;
|
|
9393
|
-
for (let index = 0; index < line2.length; index += 1) {
|
|
9394
|
-
const char = line2.charAt(index);
|
|
9395
|
-
if (quote === void 0 && (char === '"' || char === "'")) quote = char;
|
|
9396
|
-
else if (char === quote) quote = void 0;
|
|
9397
|
-
else if (quote === void 0 && char === "#" && (index === 0 || /\s/.test(line2.charAt(index - 1)))) {
|
|
9398
|
-
return line2.slice(0, index);
|
|
9399
|
-
}
|
|
9400
|
-
}
|
|
9401
|
-
return line2;
|
|
9402
|
-
};
|
|
9403
|
-
var flowList = (value) => {
|
|
9404
|
-
const trimmed = value.trim();
|
|
9405
|
-
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
|
|
9406
|
-
return trimmed.slice(1, -1).split(",").map(unquote).filter((item) => item !== "");
|
|
9407
|
-
};
|
|
9408
|
-
var keyValue = (body2) => {
|
|
9409
|
-
const match = /^("[^"]*"|'[^']*'|[^:]+?)\s*:(?:\s+(.*))?$/.exec(body2);
|
|
9410
|
-
if (match === null) return void 0;
|
|
9411
|
-
return { key: unquote(match[1] ?? ""), value: match[2] ?? "" };
|
|
9412
|
-
};
|
|
9413
|
-
var addItem = (frame, item) => {
|
|
9414
|
-
if (frame.key === void 0) return;
|
|
9415
|
-
const parent = frame.tree[frame.key];
|
|
9416
|
-
if (Array.isArray(parent)) parent.push(item);
|
|
9417
|
-
else frame.tree[frame.key] = [item];
|
|
9418
|
-
};
|
|
9419
|
-
var placeLine = (stack, indent, body2) => {
|
|
9420
|
-
while (stack.length > 1 && (stack[stack.length - 1]?.indent ?? 0) >= indent && !body2.startsWith("- ")) stack.pop();
|
|
9421
|
-
const top = stack[stack.length - 1];
|
|
9422
|
-
if (top === void 0) return;
|
|
9423
|
-
if (body2.startsWith("- ")) {
|
|
9424
|
-
addItem(top, unquote(body2.slice(2)));
|
|
9425
|
-
return;
|
|
9426
|
-
}
|
|
9427
|
-
const pair = keyValue(body2);
|
|
9428
|
-
if (pair === void 0) return;
|
|
9429
|
-
const parentTree = top.key === void 0 ? top.tree : ensureTree(top.tree, top.key);
|
|
9430
|
-
if (pair.value === "") {
|
|
9431
|
-
stack.push({ indent, tree: parentTree, key: pair.key });
|
|
9432
|
-
return;
|
|
9433
|
-
}
|
|
9434
|
-
parentTree[pair.key] = flowList(pair.value) ?? unquote(pair.value);
|
|
9435
|
-
};
|
|
9436
|
-
var ensureTree = (tree, key) => {
|
|
9437
|
-
const existing = tree[key];
|
|
9438
|
-
if (existing !== void 0 && typeof existing !== "string" && !Array.isArray(existing)) return existing;
|
|
9439
|
-
const fresh3 = {};
|
|
9440
|
-
tree[key] = fresh3;
|
|
9441
|
-
return fresh3;
|
|
9442
|
-
};
|
|
9443
|
-
var parseConfigYaml = (text6) => {
|
|
9444
|
-
const root = {};
|
|
9445
|
-
const stack = [{ indent: -1, tree: root, key: void 0 }];
|
|
9446
|
-
for (const raw of text6.split(/\r?\n/)) {
|
|
9447
|
-
const line2 = stripComment2(raw).trimEnd();
|
|
9448
|
-
if (line2.trim() === "" || line2.trim() === "---") continue;
|
|
9449
|
-
const indent = line2.length - line2.trimStart().length;
|
|
9450
|
-
placeLine(stack, indent, line2.trim());
|
|
9451
|
-
}
|
|
9452
|
-
return root;
|
|
9453
|
-
};
|
|
9454
|
-
var isTree = (node) => typeof node === "object" && !Array.isArray(node);
|
|
9455
|
-
var configAt = (tree, ...path) => path.reduce((node, key) => isTree(node) ? node[key] : void 0, tree);
|
|
9456
|
-
var configList = (tree, ...path) => {
|
|
9457
|
-
const node = configAt(tree, ...path);
|
|
9458
|
-
if (typeof node === "string") return node === "" ? [] : [node];
|
|
9459
|
-
return Array.isArray(node) ? node.filter((item) => typeof item === "string") : [];
|
|
9460
|
-
};
|
|
9461
|
-
var configMap = (tree, ...path) => {
|
|
9462
|
-
const node = configAt(tree, ...path);
|
|
9463
|
-
if (!isTree(node)) return {};
|
|
9464
|
-
return Object.fromEntries(
|
|
9465
|
-
Object.entries(node).filter((entry2) => typeof entry2[1] === "string")
|
|
9466
|
-
);
|
|
9467
|
-
};
|
|
9468
|
-
var loadProjectConfig = (runtime, root) => {
|
|
9469
|
-
const text6 = runtime.readText(toAbsolutePath(join20(root, CONFIG_FILE3)));
|
|
9470
|
-
return text6.ok ? parseConfigYaml(text6.value) : {};
|
|
9471
|
-
};
|
|
9472
|
-
|
|
9473
10068
|
// src/git/merge/dropped.ts
|
|
9474
|
-
import { join as
|
|
10069
|
+
import { join as join23 } from "node:path";
|
|
9475
10070
|
|
|
9476
10071
|
// src/git/merge/lines.ts
|
|
9477
10072
|
var normalizeLine = (line2) => line2.replace(/\r$/, "").trim().replace(/\s*,$/, "");
|
|
@@ -9551,7 +10146,7 @@ var parseDropped = (text6) => {
|
|
|
9551
10146
|
return accept({ items, invalid });
|
|
9552
10147
|
};
|
|
9553
10148
|
var loadDropped = (runtime, root, file) => {
|
|
9554
|
-
const path = toAbsolutePath(file ??
|
|
10149
|
+
const path = toAbsolutePath(file ?? join23(root, DROPPED_FILE), root);
|
|
9555
10150
|
const text6 = runtime.readText(path);
|
|
9556
10151
|
if (!text6.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text6.error}`);
|
|
9557
10152
|
return parseDropped(text6.value);
|
|
@@ -9561,7 +10156,7 @@ var isDropped = (finding3, items) => items.some(
|
|
|
9561
10156
|
);
|
|
9562
10157
|
|
|
9563
10158
|
// src/git/merge/generated.ts
|
|
9564
|
-
import { join as
|
|
10159
|
+
import { join as join24 } from "node:path";
|
|
9565
10160
|
var LOCKFILES3 = /* @__PURE__ */ new Set([
|
|
9566
10161
|
"package-lock.json",
|
|
9567
10162
|
"npm-shrinkwrap.json",
|
|
@@ -9624,7 +10219,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
|
|
|
9624
10219
|
if (declared.length === 0) return accept([]);
|
|
9625
10220
|
const commit2 = resultCommit(runtime, repo, inputs);
|
|
9626
10221
|
if (!commit2.ok) return commit2;
|
|
9627
|
-
const dir =
|
|
10222
|
+
const dir = join24(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
|
|
9628
10223
|
if (!runtime.git(["worktree", "add", "--detach", dir, commit2.value], repo.root).ok)
|
|
9629
10224
|
return reject("could not check out the merge result");
|
|
9630
10225
|
try {
|
|
@@ -9634,7 +10229,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
|
|
|
9634
10229
|
}
|
|
9635
10230
|
return accept(
|
|
9636
10231
|
declared.flatMap(({ path }) => {
|
|
9637
|
-
const fresh3 = runtime.readText(toAbsolutePath(
|
|
10232
|
+
const fresh3 = runtime.readText(toAbsolutePath(join24(dir, path)));
|
|
9638
10233
|
const merged = blobText(runtime, repo, commit2.value, path);
|
|
9639
10234
|
if (fresh3.ok && fresh3.value === merged) return [];
|
|
9640
10235
|
return [{ kind: "generated-differs", file: path, side: void 0, line: void 0, text: path, weak: false }];
|
|
@@ -9848,16 +10443,16 @@ var failed2 = (error, inputs) => ({
|
|
|
9848
10443
|
generated: [],
|
|
9849
10444
|
error
|
|
9850
10445
|
});
|
|
9851
|
-
var
|
|
10446
|
+
var keyOf2 = (diff) => (diff.status === "added" || diff.status === "copied" ? diff.newPath : diff.oldPath ?? diff.newPath) ?? "";
|
|
9852
10447
|
var pairsOf = (ours, theirs) => {
|
|
9853
|
-
const keys = [...new Set([...ours, ...theirs].map(
|
|
10448
|
+
const keys = [...new Set([...ours, ...theirs].map(keyOf2))].filter((key) => key !== "").sort();
|
|
9854
10449
|
return keys.map((key) => ({
|
|
9855
10450
|
key,
|
|
9856
|
-
ours: ours.find((diff) =>
|
|
9857
|
-
theirs: theirs.find((diff) =>
|
|
10451
|
+
ours: ours.find((diff) => keyOf2(diff) === key),
|
|
10452
|
+
theirs: theirs.find((diff) => keyOf2(diff) === key)
|
|
9858
10453
|
}));
|
|
9859
10454
|
};
|
|
9860
|
-
var contextOf2 = (runtime, repo, inputs,
|
|
10455
|
+
var contextOf2 = (runtime, repo, inputs, pairs2) => {
|
|
9861
10456
|
const resultText = memoBy((path) => blobText(runtime, repo, inputs.result, path));
|
|
9862
10457
|
const context = {
|
|
9863
10458
|
runtime,
|
|
@@ -9865,7 +10460,7 @@ var contextOf2 = (runtime, repo, inputs, pairs) => {
|
|
|
9865
10460
|
inputs,
|
|
9866
10461
|
resultText,
|
|
9867
10462
|
resultFiles: memo2(
|
|
9868
|
-
() =>
|
|
10463
|
+
() => pairs2.flatMap((pair) => {
|
|
9869
10464
|
const path = resultPathOf(context, pair);
|
|
9870
10465
|
return path === void 0 ? [] : [path];
|
|
9871
10466
|
})
|
|
@@ -9882,24 +10477,24 @@ var verifyMerge = (runtime, repo, options = {}) => {
|
|
|
9882
10477
|
const theirs = sideDiff(runtime, repo, inputs.value.base, inputs.value.theirs);
|
|
9883
10478
|
if (!ours.ok) return failed2(ours.error, inputs.value);
|
|
9884
10479
|
if (!theirs.ok) return failed2(theirs.error, inputs.value);
|
|
9885
|
-
const
|
|
10480
|
+
const pairs2 = pairsOf(ours.value, theirs.value);
|
|
9886
10481
|
const generators = configMap(loadProjectConfig(runtime, repo.root), "merge", "generators");
|
|
9887
10482
|
const generated = generatedPaths(
|
|
9888
10483
|
runtime,
|
|
9889
10484
|
repo,
|
|
9890
|
-
|
|
10485
|
+
pairs2.map((pair) => pair.key),
|
|
9891
10486
|
generators
|
|
9892
10487
|
);
|
|
9893
10488
|
const context = contextOf2(
|
|
9894
10489
|
runtime,
|
|
9895
10490
|
repo,
|
|
9896
10491
|
inputs.value,
|
|
9897
|
-
|
|
10492
|
+
pairs2.filter((pair) => !generated.has(pair.key))
|
|
9898
10493
|
);
|
|
9899
10494
|
const regenerated = verifyGenerated(runtime, repo, inputs.value, [...generated], generators);
|
|
9900
10495
|
if (!regenerated.ok) return failed2(regenerated.error, inputs.value);
|
|
9901
10496
|
const all = [
|
|
9902
|
-
...
|
|
10497
|
+
...pairs2.filter((pair) => !generated.has(pair.key)).flatMap((pair) => verifyFile(context, pair)),
|
|
9903
10498
|
...regenerated.value
|
|
9904
10499
|
];
|
|
9905
10500
|
const open = [...dropped.value.invalid, ...all.filter((item) => !isDropped(item, dropped.value.items))];
|
|
@@ -9911,7 +10506,7 @@ var verifyMerge = (runtime, repo, options = {}) => {
|
|
|
9911
10506
|
theirs: inputs.value.theirs,
|
|
9912
10507
|
result: inputs.value.result,
|
|
9913
10508
|
bases: inputs.value.bases,
|
|
9914
|
-
files:
|
|
10509
|
+
files: pairs2.length,
|
|
9915
10510
|
findings: open,
|
|
9916
10511
|
justified: all.length - (open.length - dropped.value.invalid.length),
|
|
9917
10512
|
generated: [...generated].sort(),
|
|
@@ -9920,19 +10515,19 @@ var verifyMerge = (runtime, repo, options = {}) => {
|
|
|
9920
10515
|
};
|
|
9921
10516
|
|
|
9922
10517
|
// src/git/ownership.ts
|
|
9923
|
-
import { join as
|
|
10518
|
+
import { join as join25 } from "node:path";
|
|
9924
10519
|
var OWNERSHIP_DIR = ".finitemonkey/git";
|
|
9925
10520
|
var ownerKey = (raw) => {
|
|
9926
10521
|
const cleaned = (raw ?? "").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80);
|
|
9927
10522
|
return cleaned === "" ? "unknown" : cleaned;
|
|
9928
10523
|
};
|
|
9929
|
-
var fileOf = (repo, kind, owner) => toAbsolutePath(
|
|
10524
|
+
var fileOf = (repo, kind, owner) => toAbsolutePath(join25(repo.root, OWNERSHIP_DIR, kind, `${ownerKey(owner)}.json`));
|
|
9930
10525
|
var readDirty = (runtime, root) => {
|
|
9931
10526
|
const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
|
|
9932
10527
|
return status.ok ? parsePorcelainV2(status.stdout) : void 0;
|
|
9933
10528
|
};
|
|
9934
10529
|
var hashDirty = (runtime, repo, entries) => {
|
|
9935
|
-
const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(
|
|
10530
|
+
const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(join25(repo.root, entry2.path)))?.kind === "file");
|
|
9936
10531
|
const hashed = present2.length === 0 ? void 0 : runtime.gitWith(["hash-object", "--stdin-paths"], repo.root, {
|
|
9937
10532
|
input: `${present2.map((entry2) => entry2.path).join("\n")}
|
|
9938
10533
|
`
|
|
@@ -10184,7 +10779,9 @@ var DEFAULT_CHECKS = {
|
|
|
10184
10779
|
secrets: secretsCheck,
|
|
10185
10780
|
prose: proseCheck,
|
|
10186
10781
|
worktree: worktreeCheck,
|
|
10187
|
-
merge: mergeCheck
|
|
10782
|
+
merge: mergeCheck,
|
|
10783
|
+
review: reviewCheck,
|
|
10784
|
+
callers: callersCheck
|
|
10188
10785
|
};
|
|
10189
10786
|
|
|
10190
10787
|
// src/gate/run.ts
|
|
@@ -10364,10 +10961,21 @@ var isOwnWorktree = (input) => {
|
|
|
10364
10961
|
const main2 = findRepoRoot(runtime, input.cwd);
|
|
10365
10962
|
return front !== void 0 && main2 !== void 0 && front !== main2;
|
|
10366
10963
|
};
|
|
10367
|
-
var
|
|
10964
|
+
var noteReview = (input) => {
|
|
10965
|
+
const agent = textField(input.payload.raw, "agent_type") ?? "";
|
|
10966
|
+
if (!REVIEWER_AGENT.test(agent)) return;
|
|
10967
|
+
const root = findRepoRoot(input.runtime, sessionCwd(input));
|
|
10968
|
+
if (root === void 0) return;
|
|
10969
|
+
const tree = treeHash(gitAt(input.runtime, root));
|
|
10970
|
+
if (tree !== void 0) recordReview(input.runtime, fmPaths(root), tree, agent);
|
|
10971
|
+
};
|
|
10972
|
+
var subagentStopGate = (input) => {
|
|
10973
|
+
noteReview(input);
|
|
10974
|
+
return isOwnWorktree(input) ? stopGate(input) : { kind: "allow" };
|
|
10975
|
+
};
|
|
10368
10976
|
|
|
10369
10977
|
// src/gate/hooks/subagent.ts
|
|
10370
|
-
import { join as
|
|
10978
|
+
import { join as join26 } from "node:path";
|
|
10371
10979
|
var NO_CONTRACT_AGENTS = /(^|:)(reviewer|verifier|explore|plan|claude-code-guide|statusline-setup)$/i;
|
|
10372
10980
|
var KNOWN_KINDS = /* @__PURE__ */ new Set([
|
|
10373
10981
|
"command",
|
|
@@ -10380,7 +10988,9 @@ var KNOWN_KINDS = /* @__PURE__ */ new Set([
|
|
|
10380
10988
|
"secrets",
|
|
10381
10989
|
"prose",
|
|
10382
10990
|
"worktree",
|
|
10383
|
-
"merge"
|
|
10991
|
+
"merge",
|
|
10992
|
+
"review",
|
|
10993
|
+
"callers"
|
|
10384
10994
|
]);
|
|
10385
10995
|
var requirementText = (t, check2) => {
|
|
10386
10996
|
const key = `gate.require.${check2.kind}`;
|
|
@@ -10401,12 +11011,12 @@ var subagentStart = (input) => {
|
|
|
10401
11011
|
const active = readState(runtime, paths).active;
|
|
10402
11012
|
if (active === void 0) return { kind: "allow" };
|
|
10403
11013
|
const disk = (relative9) => {
|
|
10404
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
11014
|
+
const text6 = runtime.readText(toAbsolutePath(join26(paths.root, relative9)));
|
|
10405
11015
|
return text6.ok ? text6.value : void 0;
|
|
10406
11016
|
};
|
|
10407
11017
|
const loaded = loadTask(runtime, paths, { disk, base: () => void 0 }, active);
|
|
10408
11018
|
if (!loaded.ok) return { kind: "allow" };
|
|
10409
|
-
const briefing = runtime.stat(toAbsolutePath(
|
|
11019
|
+
const briefing = runtime.stat(toAbsolutePath(join26(paths.dir, "briefing.md"))) !== void 0;
|
|
10410
11020
|
return { kind: "allow", output: contextOutput("SubagentStart", contractFacts(t, loaded.value.contract, briefing)) };
|
|
10411
11021
|
};
|
|
10412
11022
|
|
|
@@ -10423,7 +11033,7 @@ var PORTAO_HANDLERS = {
|
|
|
10423
11033
|
};
|
|
10424
11034
|
|
|
10425
11035
|
// src/prod/guard.ts
|
|
10426
|
-
import { join as
|
|
11036
|
+
import { join as join28 } from "node:path";
|
|
10427
11037
|
|
|
10428
11038
|
// src/git/command-text.ts
|
|
10429
11039
|
var MAX_SHOWN = 160;
|
|
@@ -10765,7 +11375,7 @@ var remoteHostsOf = (command) => {
|
|
|
10765
11375
|
};
|
|
10766
11376
|
|
|
10767
11377
|
// src/prod/targets.ts
|
|
10768
|
-
import { join as
|
|
11378
|
+
import { join as join27 } from "node:path";
|
|
10769
11379
|
var DEPLOY_FILES = [
|
|
10770
11380
|
"deploy.sh",
|
|
10771
11381
|
"scripts/deploy.sh",
|
|
@@ -10789,12 +11399,12 @@ var hostsInText = (text6) => {
|
|
|
10789
11399
|
return [...urls, ...keyed, ...commands].filter((host) => host !== "" && !host.includes("$"));
|
|
10790
11400
|
};
|
|
10791
11401
|
var workflowFiles = (runtime, root) => {
|
|
10792
|
-
const listed4 = runtime.listDir(toAbsolutePath(
|
|
11402
|
+
const listed4 = runtime.listDir(toAbsolutePath(join27(root, ".github", "workflows")));
|
|
10793
11403
|
if (!listed4.ok) return [];
|
|
10794
11404
|
return listed4.value.filter((name) => /deploy/i.test(name) && /\.ya?ml$/i.test(name)).map((name) => `.github/workflows/${name}`);
|
|
10795
11405
|
};
|
|
10796
11406
|
var detectedHosts = (runtime, root, files) => files.flatMap((file) => {
|
|
10797
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
11407
|
+
const text6 = runtime.readText(toAbsolutePath(join27(root, file)));
|
|
10798
11408
|
return text6.ok ? hostsInText(text6.value) : [];
|
|
10799
11409
|
});
|
|
10800
11410
|
var loadProdTargets = (runtime, root, config) => {
|
|
@@ -10806,7 +11416,7 @@ var loadProdTargets = (runtime, root, config) => {
|
|
|
10806
11416
|
...detectedHosts(runtime, root, deployFiles)
|
|
10807
11417
|
];
|
|
10808
11418
|
const declaredDeploy = configAt(config, "prod", "deploy");
|
|
10809
|
-
const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(
|
|
11419
|
+
const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(join27(root, file))) !== void 0);
|
|
10810
11420
|
return {
|
|
10811
11421
|
hosts: new Set(hosts.map(hostKey).filter((host) => host !== "")),
|
|
10812
11422
|
kubeContexts: configList(config, "prod", "kube_contexts"),
|
|
@@ -10983,8 +11593,8 @@ var recordEvidence = (runtime, root, owner, reads) => {
|
|
|
10983
11593
|
] : []
|
|
10984
11594
|
);
|
|
10985
11595
|
if (lines3.length === 0) return;
|
|
10986
|
-
const path = toAbsolutePath(
|
|
10987
|
-
runtime.mkdir(toAbsolutePath(
|
|
11596
|
+
const path = toAbsolutePath(join28(root, EVIDENCE_FILE));
|
|
11597
|
+
runtime.mkdir(toAbsolutePath(join28(root, ".finitemonkey")));
|
|
10988
11598
|
const before = runtime.readText(path);
|
|
10989
11599
|
const text6 = before.ok ? before.value : "";
|
|
10990
11600
|
runtime.writeAtomic(path, `${text6}${text6 === "" || text6.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
|
|
@@ -11086,7 +11696,7 @@ var withAlias = (invocation, expansion) => ({
|
|
|
11086
11696
|
});
|
|
11087
11697
|
|
|
11088
11698
|
// src/git/path-key.ts
|
|
11089
|
-
import { posix as
|
|
11699
|
+
import { posix as posix3 } from "node:path";
|
|
11090
11700
|
var WSL_MOUNT3 = /^\/mnt\/([a-zA-Z])(\/|$)/;
|
|
11091
11701
|
var DRIVE = /^([a-zA-Z]):(\/|$)/;
|
|
11092
11702
|
var pathKey = (path) => {
|
|
@@ -11095,7 +11705,7 @@ var pathKey = (path) => {
|
|
|
11095
11705
|
(_match, letter, slash) => `${letter}:${slash || "/"}`
|
|
11096
11706
|
);
|
|
11097
11707
|
const drive = DRIVE.exec(posixPath);
|
|
11098
|
-
const normalized =
|
|
11708
|
+
const normalized = posix3.normalize(drive === null ? posixPath : posixPath.slice(2) || "/");
|
|
11099
11709
|
const trimmed = normalized.length > 1 ? normalized.replace(/\/+$/, "") : normalized;
|
|
11100
11710
|
return drive === null ? trimmed : `${(drive[1] ?? "").toLowerCase()}:${trimmed}`.toLowerCase();
|
|
11101
11711
|
};
|
|
@@ -11111,15 +11721,15 @@ var isRootLike = (path, home) => {
|
|
|
11111
11721
|
if (key === "/" || /^[a-z]:\/?$/.test(key)) return true;
|
|
11112
11722
|
return home !== void 0 && home !== "" && key === pathKey(home);
|
|
11113
11723
|
};
|
|
11114
|
-
var resolveFrom = (base, ...parts) => parts.reduce((
|
|
11115
|
-
if (part2 === void 0 || part2 === "") return
|
|
11724
|
+
var resolveFrom = (base, ...parts) => parts.reduce((current2, part2) => {
|
|
11725
|
+
if (part2 === void 0 || part2 === "") return current2;
|
|
11116
11726
|
const next2 = toPosixPath(part2);
|
|
11117
11727
|
if (next2.startsWith("/") || DRIVE.test(next2)) return next2;
|
|
11118
|
-
return
|
|
11728
|
+
return posix3.join(toPosixPath(current2), next2);
|
|
11119
11729
|
}, toPosixPath(base));
|
|
11120
11730
|
|
|
11121
11731
|
// src/git/pathspec.ts
|
|
11122
|
-
import { posix as
|
|
11732
|
+
import { posix as posix4 } from "node:path";
|
|
11123
11733
|
var MAGIC = /^:(?:\(([^)]*)\)|([!^/]*))(.*)$/s;
|
|
11124
11734
|
var GLOB = /[*?[]/;
|
|
11125
11735
|
var specOf = (raw, prefix) => {
|
|
@@ -11129,7 +11739,7 @@ var specOf = (raw, prefix) => {
|
|
|
11129
11739
|
const rest = magic === null ? posixRaw : magic[3] ?? "";
|
|
11130
11740
|
const exclude = /(?:^|,)exclude(?:,|$)|[!^]/.test(words);
|
|
11131
11741
|
const top = /(?:^|,)top(?:,|$)|\//.test(words);
|
|
11132
|
-
const joined =
|
|
11742
|
+
const joined = posix4.normalize(top ? rest : posix4.join(prefix === "" ? "." : prefix, rest));
|
|
11133
11743
|
return { pattern: joined === "./" ? "." : joined.replace(/\/$/, ""), exclude };
|
|
11134
11744
|
};
|
|
11135
11745
|
var globRegex3 = (pattern) => {
|
|
@@ -11162,7 +11772,7 @@ var discard = (op, scope2, pathspecs = []) => ({
|
|
|
11162
11772
|
discard: { scope: scope2, pathspecs }
|
|
11163
11773
|
});
|
|
11164
11774
|
var SEVERITY = { pass: 0, snapshot: 1, discard: 2, block: 3 };
|
|
11165
|
-
var worst = (verdicts) => verdicts.reduce((
|
|
11775
|
+
var worst = (verdicts) => verdicts.reduce((current2, next2) => SEVERITY[next2.kind] > SEVERITY[current2.kind] ? next2 : current2, PASS);
|
|
11166
11776
|
|
|
11167
11777
|
// src/git/options.ts
|
|
11168
11778
|
var canonical = (name, spec2) => {
|
|
@@ -11523,24 +12133,24 @@ var branchName = (ref) => {
|
|
|
11523
12133
|
if (ref.startsWith("refs/tags/")) return void 0;
|
|
11524
12134
|
return ref.replace(/^refs\/heads\//, "");
|
|
11525
12135
|
};
|
|
11526
|
-
var refspecTarget = (refspec,
|
|
12136
|
+
var refspecTarget = (refspec, current2) => {
|
|
11527
12137
|
const plain = refspec.replace(/^\+/, "");
|
|
11528
12138
|
const colon = plain.indexOf(":");
|
|
11529
12139
|
const destination = colon < 0 ? plain : plain.slice(colon + 1);
|
|
11530
|
-
if (destination === "HEAD" || colon < 0 && plain === "HEAD") return
|
|
12140
|
+
if (destination === "HEAD" || colon < 0 && plain === "HEAD") return current2;
|
|
11531
12141
|
if (destination === "" || destination.startsWith("refs/tags/")) return void 0;
|
|
11532
12142
|
return branchName(destination);
|
|
11533
12143
|
};
|
|
11534
12144
|
var pushDestinations = (parsed, facts) => {
|
|
11535
12145
|
if (hasAny(parsed, "--all", "--branches", "--mirror")) return "all";
|
|
11536
12146
|
const refspecs = parsed.positionals.slice(1).filter((refspec) => refspec !== ":");
|
|
11537
|
-
const
|
|
12147
|
+
const current2 = facts.currentBranch();
|
|
11538
12148
|
if (refspecs.length === 0) {
|
|
11539
|
-
if (hasAny(parsed, "--tags") ||
|
|
11540
|
-
return [facts.upstream(
|
|
12149
|
+
if (hasAny(parsed, "--tags") || current2 === void 0) return [];
|
|
12150
|
+
return [facts.upstream(current2)?.branch ?? current2];
|
|
11541
12151
|
}
|
|
11542
12152
|
return refspecs.flatMap((refspec) => {
|
|
11543
|
-
const target = refspecTarget(refspec,
|
|
12153
|
+
const target = refspecTarget(refspec, current2);
|
|
11544
12154
|
return target === void 0 ? [] : [target];
|
|
11545
12155
|
});
|
|
11546
12156
|
};
|
|
@@ -11548,10 +12158,10 @@ var leaseVerdict = (parsed, facts) => {
|
|
|
11548
12158
|
const leases = valuesOf(parsed, "--force-with-lease");
|
|
11549
12159
|
const explicit = leases.length > 0 && leases.every((lease) => /^[^:]+:.+$/.test(lease));
|
|
11550
12160
|
if (!explicit || !hasAny(parsed, "--force-if-includes")) return block2("force-push", "--force-with-lease");
|
|
11551
|
-
const
|
|
12161
|
+
const current2 = facts.currentBranch();
|
|
11552
12162
|
const leased = leases.map((lease) => branchName(lease.slice(0, lease.indexOf(":"))));
|
|
11553
12163
|
const destinations = pushDestinations(parsed, facts);
|
|
11554
|
-
const own =
|
|
12164
|
+
const own = current2 !== void 0 && !facts.isProtected(current2) && leased.every((name) => name === current2) && destinations !== "all" && destinations.every((name) => name === current2);
|
|
11555
12165
|
return own ? PASS : block2("lease-not-own", leased.join(", "));
|
|
11556
12166
|
};
|
|
11557
12167
|
var pushVerdict = (invocation, facts) => {
|
|
@@ -11574,8 +12184,8 @@ var LEAVES = /* @__PURE__ */ new Set(["--abort", "--quit", "--skip", "--dry-run"
|
|
|
11574
12184
|
var commitVerdict = (invocation, facts) => {
|
|
11575
12185
|
if (invocation.args.some((arg) => LEAVES.has(arg))) return PASS;
|
|
11576
12186
|
if (invocation.subcommand === "rebase") return PASS;
|
|
11577
|
-
const
|
|
11578
|
-
return
|
|
12187
|
+
const current2 = facts.currentBranch();
|
|
12188
|
+
return current2 !== void 0 && facts.isProtected(current2) ? block2("protected-commit", current2) : PASS;
|
|
11579
12189
|
};
|
|
11580
12190
|
var pushVerdict2 = (invocation, facts) => {
|
|
11581
12191
|
const parsed = parseOptions(invocation.args, PUSH_OPTIONS);
|
|
@@ -11796,33 +12406,33 @@ var listWorktrees = (runtime, cwd) => {
|
|
|
11796
12406
|
};
|
|
11797
12407
|
|
|
11798
12408
|
// src/git/journal.ts
|
|
11799
|
-
import { join as
|
|
12409
|
+
import { join as join29 } from "node:path";
|
|
11800
12410
|
var JOURNAL_FILE = ".finitemonkey/git-journal.jsonl";
|
|
11801
12411
|
var JOURNAL_SCHEMA = "finitemonkey/git-journal@1";
|
|
11802
|
-
var journalPath = (root) => toAbsolutePath(
|
|
12412
|
+
var journalPath = (root) => toAbsolutePath(join29(root, JOURNAL_FILE));
|
|
11803
12413
|
var headsOf = (value) => isRecord(value) ? Object.fromEntries(
|
|
11804
12414
|
Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
|
|
11805
12415
|
) : {};
|
|
11806
12416
|
var parseJournalLine = (line2) => {
|
|
11807
12417
|
const parsed = parseJsonObject(line2);
|
|
11808
12418
|
if (!parsed.ok) return void 0;
|
|
11809
|
-
const
|
|
11810
|
-
const ref = optionalString(
|
|
11811
|
-
const commit2 = optionalString(
|
|
11812
|
-
const at = optionalString(
|
|
12419
|
+
const record3 = parsed.value;
|
|
12420
|
+
const ref = optionalString(record3, "ref");
|
|
12421
|
+
const commit2 = optionalString(record3, "commit");
|
|
12422
|
+
const at = optionalString(record3, "at");
|
|
11813
12423
|
if (ref === void 0 || commit2 === void 0 || at === void 0) return void 0;
|
|
11814
12424
|
return {
|
|
11815
12425
|
schema: JOURNAL_SCHEMA,
|
|
11816
12426
|
at,
|
|
11817
|
-
op: optionalString(
|
|
12427
|
+
op: optionalString(record3, "op") ?? "",
|
|
11818
12428
|
ref,
|
|
11819
12429
|
commit: commit2,
|
|
11820
|
-
head: optionalString(
|
|
11821
|
-
branch: optionalString(
|
|
11822
|
-
index: optionalString(
|
|
11823
|
-
owner: optionalString(
|
|
11824
|
-
command: optionalString(
|
|
11825
|
-
heads: headsOf(
|
|
12430
|
+
head: optionalString(record3, "head"),
|
|
12431
|
+
branch: optionalString(record3, "branch"),
|
|
12432
|
+
index: optionalString(record3, "index"),
|
|
12433
|
+
owner: optionalString(record3, "owner"),
|
|
12434
|
+
command: optionalString(record3, "command"),
|
|
12435
|
+
heads: headsOf(record3["heads"])
|
|
11826
12436
|
};
|
|
11827
12437
|
};
|
|
11828
12438
|
var readJournal = (runtime, root) => {
|
|
@@ -11835,7 +12445,7 @@ var readJournal = (runtime, root) => {
|
|
|
11835
12445
|
};
|
|
11836
12446
|
var appendJournal = (runtime, root, entry2) => {
|
|
11837
12447
|
const path = journalPath(root);
|
|
11838
|
-
const made = runtime.mkdir(toAbsolutePath(
|
|
12448
|
+
const made = runtime.mkdir(toAbsolutePath(join29(root, ".finitemonkey")));
|
|
11839
12449
|
if (!made.ok) return made;
|
|
11840
12450
|
const previous = runtime.readText(path);
|
|
11841
12451
|
const before = previous.ok ? previous.value : "";
|
|
@@ -12077,6 +12687,83 @@ var GIT_HANDLERS = {
|
|
|
12077
12687
|
PostToolUse: [gitPostToolUse]
|
|
12078
12688
|
};
|
|
12079
12689
|
|
|
12690
|
+
// src/policy/guards.ts
|
|
12691
|
+
var SHADOW_FILE = "shadow.json";
|
|
12692
|
+
var numberOf = (value) => typeof value === "number" && Number.isInteger(value) ? value : 0;
|
|
12693
|
+
var readShadow = (runtime, paths) => {
|
|
12694
|
+
const text6 = runtime.readText(paths.cache(SHADOW_FILE));
|
|
12695
|
+
const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
|
|
12696
|
+
if (parsed?.ok !== true) return {};
|
|
12697
|
+
return Object.fromEntries(
|
|
12698
|
+
Object.entries(parsed.value).flatMap(
|
|
12699
|
+
([id, value]) => isRecord(value) ? [
|
|
12700
|
+
[
|
|
12701
|
+
id,
|
|
12702
|
+
{
|
|
12703
|
+
firings: numberOf(value["firings"]),
|
|
12704
|
+
falsePositives: numberOf(value["falsePositives"]),
|
|
12705
|
+
promoted: value["promoted"] === true
|
|
12706
|
+
}
|
|
12707
|
+
]
|
|
12708
|
+
] : []
|
|
12709
|
+
)
|
|
12710
|
+
);
|
|
12711
|
+
};
|
|
12712
|
+
var writeShadow = (runtime, paths, state) => {
|
|
12713
|
+
runtime.writeText(paths.cache(SHADOW_FILE), `${JSON.stringify(state, null, 2)}
|
|
12714
|
+
`);
|
|
12715
|
+
};
|
|
12716
|
+
var modeOf2 = (set, policy, shadow) => {
|
|
12717
|
+
if (set.damage) return "active";
|
|
12718
|
+
const chosen = policy.guards[set.id];
|
|
12719
|
+
if (chosen !== void 0) return chosen;
|
|
12720
|
+
if (!policy.exists) return "active";
|
|
12721
|
+
return shadow[set.id]?.promoted === true ? "active" : "shadow";
|
|
12722
|
+
};
|
|
12723
|
+
var recordShadow = (input, paths, set, policy, reason) => {
|
|
12724
|
+
const { runtime } = input;
|
|
12725
|
+
const state = readShadow(runtime, paths);
|
|
12726
|
+
const before = state[set.id] ?? { firings: 0, falsePositives: 0, promoted: false };
|
|
12727
|
+
const firings = before.firings + 1;
|
|
12728
|
+
const promoted = !before.promoted && firings >= policy.promoteAfter && before.falsePositives === 0;
|
|
12729
|
+
writeShadow(runtime, paths, { ...state, [set.id]: { ...before, firings, promoted: before.promoted || promoted } });
|
|
12730
|
+
logEvent(runtime, paths, "guard_shadow", {
|
|
12731
|
+
guard: set.id,
|
|
12732
|
+
hook: input.event,
|
|
12733
|
+
tool: toolName2(input.payload),
|
|
12734
|
+
wouldHave: "block",
|
|
12735
|
+
reason
|
|
12736
|
+
});
|
|
12737
|
+
if (promoted) {
|
|
12738
|
+
logEvent(runtime, paths, "guard_promoted", {
|
|
12739
|
+
guard: set.id,
|
|
12740
|
+
firings,
|
|
12741
|
+
falsePositives: before.falsePositives,
|
|
12742
|
+
promoteAfter: policy.promoteAfter
|
|
12743
|
+
});
|
|
12744
|
+
}
|
|
12745
|
+
};
|
|
12746
|
+
var governed = (set, handler) => async (input) => {
|
|
12747
|
+
const paths = projectPaths(input);
|
|
12748
|
+
if (paths === void 0) return handler(input);
|
|
12749
|
+
const policy = loadPolicy(input.runtime, paths.root);
|
|
12750
|
+
const mode = modeOf2(set, policy, readShadow(input.runtime, paths));
|
|
12751
|
+
if (mode === "off") return { kind: "allow" };
|
|
12752
|
+
const decision = await handler(input);
|
|
12753
|
+
if (mode === "active" || decision.kind !== "block") return decision;
|
|
12754
|
+
recordShadow(input, paths, set, policy, decision.reason);
|
|
12755
|
+
return { kind: "allow" };
|
|
12756
|
+
};
|
|
12757
|
+
var withPolicy = (set) => {
|
|
12758
|
+
if (set.damage) return set.handlers;
|
|
12759
|
+
const wrapped = {};
|
|
12760
|
+
for (const event of HOOK_EVENTS) {
|
|
12761
|
+
const list4 = set.handlers[event];
|
|
12762
|
+
if (list4 !== void 0) wrapped[event] = list4.map((handler) => governed(set, handler));
|
|
12763
|
+
}
|
|
12764
|
+
return wrapped;
|
|
12765
|
+
};
|
|
12766
|
+
|
|
12080
12767
|
// src/prose/marker-guard.ts
|
|
12081
12768
|
import { relative as relative6 } from "node:path";
|
|
12082
12769
|
|
|
@@ -12337,10 +13024,10 @@ var textOf2 = (input, found) => {
|
|
|
12337
13024
|
}
|
|
12338
13025
|
return accept(parts.join("\n\n"));
|
|
12339
13026
|
};
|
|
12340
|
-
var decisionFor = (input, found,
|
|
13027
|
+
var decisionFor = (input, found, blocking2) => {
|
|
12341
13028
|
const text6 = textOf2(input, found);
|
|
12342
13029
|
if (!text6.ok) {
|
|
12343
|
-
return
|
|
13030
|
+
return blocking2 ? { kind: "block", reason: input.t("prose.block.unreadable", { ...text6.error }) } : void 0;
|
|
12344
13031
|
}
|
|
12345
13032
|
const report2 = scoreProse(text6.value, { context: "record" });
|
|
12346
13033
|
if (report2.allowed || report2.spans.length === 0) return void 0;
|
|
@@ -12349,7 +13036,7 @@ var decisionFor = (input, found, blocking) => {
|
|
|
12349
13036
|
spans: describeSpans(input.t, report2.spans),
|
|
12350
13037
|
fixes: describeFixes(input.t, report2.spans)
|
|
12351
13038
|
};
|
|
12352
|
-
if (
|
|
13039
|
+
if (blocking2) return { kind: "block", reason: input.t("prose.block.command", params2) };
|
|
12353
13040
|
const additionalContext = `finitemonkey: ${input.t("prose.warn.command", params2)}`;
|
|
12354
13041
|
return { kind: "allow", output: { hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext } } };
|
|
12355
13042
|
};
|
|
@@ -12357,9 +13044,9 @@ var proseCommandGuard = (input) => {
|
|
|
12357
13044
|
if (!SHELLS3.has(toolName2(input.payload))) return ALLOW4;
|
|
12358
13045
|
const found = proseInCommand(optionalString(toolInput2(input.payload), "command") ?? "");
|
|
12359
13046
|
if (found.length === 0) return ALLOW4;
|
|
12360
|
-
const
|
|
13047
|
+
const blocking2 = projectPreset(input.runtime, projectRoot(input)) === "gabriel";
|
|
12361
13048
|
for (const item of found) {
|
|
12362
|
-
const decision = decisionFor(input, item,
|
|
13049
|
+
const decision = decisionFor(input, item, blocking2);
|
|
12363
13050
|
if (decision !== void 0) return decision;
|
|
12364
13051
|
}
|
|
12365
13052
|
return ALLOW4;
|
|
@@ -12386,7 +13073,7 @@ var liveFindings = (input, texts) => {
|
|
|
12386
13073
|
const accepted2 = acceptedFingerprints(baseline.ok ? baseline.value : EMPTY_BASELINE, input.runtime.now());
|
|
12387
13074
|
return texts.flatMap((text6) => scanText(text6, { rules: CREDENTIAL_RULES })).filter((finding3) => !accepted2.has(finding3.fingerprint));
|
|
12388
13075
|
};
|
|
12389
|
-
var
|
|
13076
|
+
var record2 = (input, findings, draft, where) => {
|
|
12390
13077
|
const ids2 = findings.map((finding3) => {
|
|
12391
13078
|
const saved = recordIncident(
|
|
12392
13079
|
input.runtime,
|
|
@@ -12402,7 +13089,7 @@ var secretsUserPromptSubmit = (input) => {
|
|
|
12402
13089
|
const prompt = optionalString(input.payload.raw, "prompt") ?? "";
|
|
12403
13090
|
const findings = liveFindings(input, [prompt]);
|
|
12404
13091
|
if (findings.length === 0) return { kind: "allow" };
|
|
12405
|
-
const incident =
|
|
13092
|
+
const incident = record2(input, findings, { source: "prompt", state: "exposicao-local" }, "prompt");
|
|
12406
13093
|
const items = findings.map((finding3) => ({ ruleId: finding3.ruleId, where: lineWhere(input.t, finding3) }));
|
|
12407
13094
|
return {
|
|
12408
13095
|
kind: "block",
|
|
@@ -12413,7 +13100,7 @@ var secretsPostToolUse = (input) => {
|
|
|
12413
13100
|
const findings = liveFindings(input, stringLeaves(input.payload.raw["tool_response"]));
|
|
12414
13101
|
if (findings.length === 0) return { kind: "allow" };
|
|
12415
13102
|
const tool = toolName2(input.payload) || "tool";
|
|
12416
|
-
const incident =
|
|
13103
|
+
const incident = record2(input, findings, { source: "tool-output", state: "aberto" }, `tool-output:${tool}`);
|
|
12417
13104
|
const items = findings.map((finding3) => ({ ruleId: finding3.ruleId, where: lineWhere(input.t, finding3) }));
|
|
12418
13105
|
const context = input.t("secrets.post.context", { tool, found: describeFindings(input.t, items), incident });
|
|
12419
13106
|
return {
|
|
@@ -12523,11 +13210,24 @@ var relativeFile = (cwd, file) => {
|
|
|
12523
13210
|
const rel = relative7(cwd, file).split("\\").join("/");
|
|
12524
13211
|
return rel.startsWith("..") || rel === "" ? file : rel;
|
|
12525
13212
|
};
|
|
13213
|
+
var warningDecision = (t, warnings) => {
|
|
13214
|
+
if (warnings.length === 0) return ALLOW5;
|
|
13215
|
+
const context = t("secrets.warn.write", { found: describeFindings(t, warnings) });
|
|
13216
|
+
return { kind: "allow", output: { hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: context } } };
|
|
13217
|
+
};
|
|
12526
13218
|
var writeDecision = (input, tool) => {
|
|
12527
13219
|
const { t } = input;
|
|
13220
|
+
const warnings = [];
|
|
12528
13221
|
for (const written of writtenTexts(tool, toolInput2(input.payload))) {
|
|
12529
13222
|
const file = relativeFile(input.cwd, written.file);
|
|
12530
|
-
const
|
|
13223
|
+
const all = scanText(written.text, { path: file });
|
|
13224
|
+
warnings.push(
|
|
13225
|
+
...all.filter(isWarning).map((finding3) => ({
|
|
13226
|
+
ruleId: finding3.ruleId,
|
|
13227
|
+
where: t("secrets.where.file", { file, line: finding3.line, column: finding3.column })
|
|
13228
|
+
}))
|
|
13229
|
+
);
|
|
13230
|
+
const found = blocking(all);
|
|
12531
13231
|
if (found.length === 0) continue;
|
|
12532
13232
|
const items = found.map((finding3) => ({
|
|
12533
13233
|
ruleId: finding3.ruleId,
|
|
@@ -12535,7 +13235,7 @@ var writeDecision = (input, tool) => {
|
|
|
12535
13235
|
}));
|
|
12536
13236
|
return { kind: "block", reason: t("secrets.block.write", { file, found: describeFindings(t, items) }) };
|
|
12537
13237
|
}
|
|
12538
|
-
return
|
|
13238
|
+
return warningDecision(t, warnings);
|
|
12539
13239
|
};
|
|
12540
13240
|
var bashMessage = (t, finding3) => {
|
|
12541
13241
|
switch (finding3.kind) {
|
|
@@ -12549,8 +13249,9 @@ var bashMessage = (t, finding3) => {
|
|
|
12549
13249
|
};
|
|
12550
13250
|
var commitDecision = (input, includeUnstaged) => {
|
|
12551
13251
|
const staged = scanStaged(input.runtime, input.cwd, includeUnstaged);
|
|
12552
|
-
|
|
12553
|
-
|
|
13252
|
+
const found = staged.ok ? blocking(staged.value) : [];
|
|
13253
|
+
if (found.length === 0) return ALLOW5;
|
|
13254
|
+
const items = found.map((finding3) => ({
|
|
12554
13255
|
ruleId: finding3.ruleId,
|
|
12555
13256
|
where: input.t("secrets.where.file", { file: finding3.file, line: finding3.line, column: finding3.column })
|
|
12556
13257
|
}));
|
|
@@ -12562,7 +13263,7 @@ var bashDecision = (input) => {
|
|
|
12562
13263
|
const segments = segmentsOf(command);
|
|
12563
13264
|
const [first] = bashFindings(command, segments);
|
|
12564
13265
|
if (first !== void 0) return { kind: "block", reason: bashMessage(input.t, first) };
|
|
12565
|
-
const found = scanText(command);
|
|
13266
|
+
const found = blocking(scanText(command));
|
|
12566
13267
|
if (found.length > 0) {
|
|
12567
13268
|
const items = found.map((finding3) => ({ ruleId: finding3.ruleId, where: lineWhere(input.t, finding3) }));
|
|
12568
13269
|
return { kind: "block", reason: input.t("secrets.block.bash", { found: describeFindings(input.t, items) }) };
|
|
@@ -12587,6 +13288,44 @@ var secretsPreToolUse = (input) => {
|
|
|
12587
13288
|
return ALLOW5;
|
|
12588
13289
|
};
|
|
12589
13290
|
|
|
13291
|
+
// src/secrets/remotes.ts
|
|
13292
|
+
var USER_AND_SECRET = /^[a-z][a-z0-9+.-]*:\/\/[^\s/@:]+:[^\s/@]+@/i;
|
|
13293
|
+
var USERINFO = /^[a-z][a-z0-9+.-]*:\/\/(?<user>[^\s/@]+)@/i;
|
|
13294
|
+
var hasCredential = (url) => {
|
|
13295
|
+
if (USER_AND_SECRET.test(url)) return true;
|
|
13296
|
+
const user = USERINFO.exec(url)?.groups?.["user"];
|
|
13297
|
+
return user !== void 0 && scanText(user, { rules: CREDENTIAL_RULES }).length > 0;
|
|
13298
|
+
};
|
|
13299
|
+
var parseRemotes = (output) => {
|
|
13300
|
+
const remotes = /* @__PURE__ */ new Map();
|
|
13301
|
+
for (const line2 of output.split(/\r?\n/)) {
|
|
13302
|
+
const [name, url] = line2.trim().split(/\s+/);
|
|
13303
|
+
if (name === void 0 || url === void 0 || name === "") continue;
|
|
13304
|
+
remotes.set(name, [...remotes.get(name) ?? [], url]);
|
|
13305
|
+
}
|
|
13306
|
+
return remotes;
|
|
13307
|
+
};
|
|
13308
|
+
var remotesWithCredentials = (runtime, root) => {
|
|
13309
|
+
const listed4 = runtime.git(["remote", "-v"], root);
|
|
13310
|
+
if (!listed4.ok) return [];
|
|
13311
|
+
return [...parseRemotes(listed4.stdout)].filter(([, urls]) => urls.some(hasCredential)).map(([name]) => name).sort();
|
|
13312
|
+
};
|
|
13313
|
+
|
|
13314
|
+
// src/secrets/guards/session.ts
|
|
13315
|
+
var secretsSessionStart = (input) => {
|
|
13316
|
+
const remotes = remotesWithCredentials(input.runtime, input.cwd);
|
|
13317
|
+
if (remotes.length === 0) return { kind: "allow" };
|
|
13318
|
+
const params2 = { remotes: remotes.join(", "), remote: remotes[0] ?? "origin" };
|
|
13319
|
+
const text6 = input.t("secrets.session.remote_credential", params2);
|
|
13320
|
+
return {
|
|
13321
|
+
kind: "allow",
|
|
13322
|
+
output: {
|
|
13323
|
+
systemMessage: `finitemonkey: ${text6}`,
|
|
13324
|
+
hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text6 }
|
|
13325
|
+
}
|
|
13326
|
+
};
|
|
13327
|
+
};
|
|
13328
|
+
|
|
12590
13329
|
// src/hooks/handlers.ts
|
|
12591
13330
|
var allow = () => ({ kind: "allow" });
|
|
12592
13331
|
var DECISION_RANK = { allow: 0, defer: 0, ask: 1, deny: 2 };
|
|
@@ -12640,14 +13379,16 @@ var mergeHandlers = (...sets) => {
|
|
|
12640
13379
|
var SECRETS_HANDLERS = {
|
|
12641
13380
|
PreToolUse: [secretsPreToolUse],
|
|
12642
13381
|
UserPromptSubmit: [secretsUserPromptSubmit],
|
|
12643
|
-
PostToolUse: [secretsPostToolUse]
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
13382
|
+
PostToolUse: [secretsPostToolUse],
|
|
13383
|
+
SessionStart: [secretsSessionStart]
|
|
13384
|
+
};
|
|
13385
|
+
var GUARD_SETS = [
|
|
13386
|
+
{ id: "secrets", damage: true, handlers: SECRETS_HANDLERS },
|
|
13387
|
+
{ id: "gate", damage: true, handlers: PORTAO_HANDLERS },
|
|
13388
|
+
{ id: "prose", damage: false, handlers: PROSE_HANDLERS },
|
|
13389
|
+
{ id: "git", damage: true, handlers: GIT_HANDLERS }
|
|
13390
|
+
];
|
|
13391
|
+
var DEFAULT_HANDLERS = mergeHandlers(...GUARD_SETS.map(withPolicy));
|
|
12651
13392
|
|
|
12652
13393
|
// src/hooks/payload.ts
|
|
12653
13394
|
var EMPTY_PAYLOAD = {
|
|
@@ -13058,12 +13799,12 @@ var hookSchemaProblems = (hooks, claude) => {
|
|
|
13058
13799
|
};
|
|
13059
13800
|
|
|
13060
13801
|
// src/doctor/hook-sources.ts
|
|
13061
|
-
import { join as
|
|
13802
|
+
import { join as join30 } from "node:path";
|
|
13062
13803
|
var entriesOf = (source, hooks, own) => Object.entries(asRecord(hooks)).flatMap(
|
|
13063
13804
|
([event, groups]) => asList(groups).flatMap((group) => {
|
|
13064
|
-
const
|
|
13065
|
-
const matcher = optionalString(
|
|
13066
|
-
return asList(
|
|
13805
|
+
const record3 = asRecord(group);
|
|
13806
|
+
const matcher = optionalString(record3, "matcher");
|
|
13807
|
+
return asList(record3["hooks"]).filter((handler) => isRecord(handler) && own(handler)).map((handler) => ({ source, event, matcher, handler }));
|
|
13067
13808
|
})
|
|
13068
13809
|
);
|
|
13069
13810
|
var PROJECT_SCOPES = { project: "project", local: "local" };
|
|
@@ -13071,10 +13812,10 @@ var projectHookFiles = (env) => readSettings(env).flatMap(({ scope: scope2, path
|
|
|
13071
13812
|
const source = PROJECT_SCOPES[scope2];
|
|
13072
13813
|
return source === void 0 || !("hooks" in settings) ? [] : [{ source, path, hooks: settings["hooks"] }];
|
|
13073
13814
|
});
|
|
13074
|
-
var pluginsDir = ({ env, runtime }) =>
|
|
13815
|
+
var pluginsDir = ({ env, runtime }) => join30(env["CLAUDE_CONFIG_DIR"] ?? join30(runtime.homeDir(), ".claude"), "plugins");
|
|
13075
13816
|
var installRecord = (value, root) => {
|
|
13076
13817
|
const records = Array.isArray(value) ? value.filter(isRecord) : isRecord(value) ? [value] : [];
|
|
13077
|
-
return records.find((
|
|
13818
|
+
return records.find((record3) => record3["projectPath"] === root) ?? records[0];
|
|
13078
13819
|
};
|
|
13079
13820
|
var readJson = (env, path) => {
|
|
13080
13821
|
const text6 = env.runtime.readText(path);
|
|
@@ -13082,18 +13823,18 @@ var readJson = (env, path) => {
|
|
|
13082
13823
|
return parsed?.ok === true ? parsed.value : void 0;
|
|
13083
13824
|
};
|
|
13084
13825
|
var installedPlugin = (env) => {
|
|
13085
|
-
const index = readJson(env, toAbsolutePath(
|
|
13086
|
-
const
|
|
13087
|
-
const installPath =
|
|
13088
|
-
if (
|
|
13826
|
+
const index = readJson(env, toAbsolutePath(join30(pluginsDir(env), "installed_plugins.json")));
|
|
13827
|
+
const record3 = installRecord(asRecord(index?.["plugins"])[PLUGIN_ID], env.root);
|
|
13828
|
+
const installPath = record3 === void 0 ? void 0 : optionalString(record3, "installPath");
|
|
13829
|
+
if (record3 === void 0 || installPath === void 0) return void 0;
|
|
13089
13830
|
const root = toAbsolutePath(installPath);
|
|
13090
|
-
const manifest = readJson(env, toAbsolutePath(
|
|
13091
|
-
const hooksPath = toAbsolutePath(
|
|
13831
|
+
const manifest = readJson(env, toAbsolutePath(join30(root, ".claude-plugin", "plugin.json")));
|
|
13832
|
+
const hooksPath = toAbsolutePath(join30(root, "hooks", "hooks.json"));
|
|
13092
13833
|
const text6 = env.runtime.readText(hooksPath);
|
|
13093
13834
|
const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
|
|
13094
13835
|
return {
|
|
13095
13836
|
root,
|
|
13096
|
-
version: (manifest === void 0 ? void 0 : optionalString(manifest, "version")) ?? optionalString(
|
|
13837
|
+
version: (manifest === void 0 ? void 0 : optionalString(manifest, "version")) ?? optionalString(record3, "version"),
|
|
13097
13838
|
hooksFile: parsed?.ok === true ? { source: "plugin", path: hooksPath, hooks: parsed.value["hooks"] } : void 0,
|
|
13098
13839
|
hooksText: text6.ok ? text6.value : void 0
|
|
13099
13840
|
};
|
|
@@ -13133,7 +13874,7 @@ var schemaFindings = (env, plugin, claude) => {
|
|
|
13133
13874
|
const files = [...projectHookFiles(env), ...plugin?.hooksFile === void 0 ? [] : [plugin.hooksFile]];
|
|
13134
13875
|
const invalidJson = [
|
|
13135
13876
|
...unreadableProjectFiles(env),
|
|
13136
|
-
...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(
|
|
13877
|
+
...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(join31(plugin.root, "hooks", "hooks.json"))] : []
|
|
13137
13878
|
];
|
|
13138
13879
|
return [
|
|
13139
13880
|
...invalidJson.flatMap((file) => schemaFinding(file, ["not valid JSON"], label2)),
|
|
@@ -13154,7 +13895,7 @@ var pluginFindings = (env, projectOwn) => {
|
|
|
13154
13895
|
];
|
|
13155
13896
|
};
|
|
13156
13897
|
var coverageFindings = (env, entries) => {
|
|
13157
|
-
const installed = env.runtime.stat(toAbsolutePath(
|
|
13898
|
+
const installed = env.runtime.stat(toAbsolutePath(join31(env.root, ...LOCK_FILE.split("/")))) !== void 0;
|
|
13158
13899
|
if (entries.length === 0 && !installed) {
|
|
13159
13900
|
return [finding("hooks.registered", "warn", "doctor.hooks.none", {}, "doctor.hooks.fix_none")];
|
|
13160
13901
|
}
|
|
@@ -13206,17 +13947,17 @@ var checkHooks = (env) => {
|
|
|
13206
13947
|
};
|
|
13207
13948
|
|
|
13208
13949
|
// src/doctor/instructions.ts
|
|
13209
|
-
import { join as
|
|
13950
|
+
import { join as join32 } from "node:path";
|
|
13210
13951
|
var INSTRUCTION_LINE_CEILING = 200;
|
|
13211
13952
|
var TOP_INSTRUCTIONS = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md"];
|
|
13212
13953
|
var RULES_DIR = ".claude/rules";
|
|
13213
13954
|
var SHOWN = 3;
|
|
13214
13955
|
var readProjectFile = (env, relative9) => {
|
|
13215
|
-
const text6 = env.runtime.readText(toAbsolutePath(
|
|
13956
|
+
const text6 = env.runtime.readText(toAbsolutePath(join32(env.root, ...relative9.split("/"))));
|
|
13216
13957
|
return text6.ok ? text6.value : void 0;
|
|
13217
13958
|
};
|
|
13218
13959
|
var instructionFiles = (env) => {
|
|
13219
|
-
const listed4 = env.runtime.listDir(toAbsolutePath(
|
|
13960
|
+
const listed4 = env.runtime.listDir(toAbsolutePath(join32(env.root, ...RULES_DIR.split("/"))));
|
|
13220
13961
|
const rules = listed4.ok ? listed4.value.filter((name) => name.endsWith(".md")).map((name) => `${RULES_DIR}/${name}`) : [];
|
|
13221
13962
|
return [...TOP_INSTRUCTIONS, ...rules].flatMap((path) => {
|
|
13222
13963
|
const text6 = readProjectFile(env, path);
|
|
@@ -13321,12 +14062,12 @@ var checkInstructions = (env) => {
|
|
|
13321
14062
|
};
|
|
13322
14063
|
|
|
13323
14064
|
// src/doctor/line-endings.ts
|
|
13324
|
-
import { join as
|
|
14065
|
+
import { join as join33 } from "node:path";
|
|
13325
14066
|
var LF_RULE = /^\*\s.*\beol=lf\b/m;
|
|
13326
14067
|
var checkLineEndings = ({ root, runtime }) => {
|
|
13327
14068
|
const autocrlf = runtime.git(["config", "--get", "core.autocrlf"], root);
|
|
13328
14069
|
if (!autocrlf.ok || autocrlf.stdout.trim().toLowerCase() !== "true") return [];
|
|
13329
|
-
const attributes = runtime.readText(toAbsolutePath(
|
|
14070
|
+
const attributes = runtime.readText(toAbsolutePath(join33(root, ".gitattributes")));
|
|
13330
14071
|
if (attributes.ok && LF_RULE.test(attributes.value)) return [];
|
|
13331
14072
|
return [
|
|
13332
14073
|
finding("git.autocrlf", "warn", "doctor.git.autocrlf", { file: ".gitattributes" }, "doctor.git.fix_autocrlf")
|
|
@@ -13334,7 +14075,7 @@ var checkLineEndings = ({ root, runtime }) => {
|
|
|
13334
14075
|
};
|
|
13335
14076
|
|
|
13336
14077
|
// src/doctor/memory.ts
|
|
13337
|
-
import { join as
|
|
14078
|
+
import { join as join34 } from "node:path";
|
|
13338
14079
|
var ceilingOf = (config) => {
|
|
13339
14080
|
const value = configAt(parseConfigYaml(config ?? ""), "memory", "maxLines");
|
|
13340
14081
|
const parsed = typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
|
|
@@ -13342,9 +14083,9 @@ var ceilingOf = (config) => {
|
|
|
13342
14083
|
};
|
|
13343
14084
|
var lineCount2 = (text6) => text6.replace(/\r?\n$/, "").split(/\r?\n/).length;
|
|
13344
14085
|
var checkAgentsFile = ({ root, runtime }) => {
|
|
13345
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
14086
|
+
const text6 = runtime.readText(toAbsolutePath(join34(root, AGENTS_FILE)));
|
|
13346
14087
|
if (!text6.ok) return [];
|
|
13347
|
-
const config = runtime.readText(toAbsolutePath(
|
|
14088
|
+
const config = runtime.readText(toAbsolutePath(join34(root, CONFIG_FILE)));
|
|
13348
14089
|
const max = ceilingOf(config.ok ? config.value : void 0);
|
|
13349
14090
|
const lines3 = lineCount2(text6.value);
|
|
13350
14091
|
const params2 = { file: AGENTS_FILE, lines: lines3, max };
|
|
@@ -13352,7 +14093,7 @@ var checkAgentsFile = ({ root, runtime }) => {
|
|
|
13352
14093
|
};
|
|
13353
14094
|
|
|
13354
14095
|
// src/doctor/project-files.ts
|
|
13355
|
-
import { join as
|
|
14096
|
+
import { join as join35 } from "node:path";
|
|
13356
14097
|
var variants = (base, extensions) => extensions.map((extension) => `${base}${extension}`);
|
|
13357
14098
|
var SCRIPT = [".js", ".cjs", ".mjs", ".ts", ".cts", ".mts"];
|
|
13358
14099
|
var DATA = ["", ".json", ".yml", ".yaml"];
|
|
@@ -13383,7 +14124,7 @@ var CONFIG_GROUPS = [
|
|
|
13383
14124
|
files: [...variants("commitlint.config", SCRIPT), ...variants(".commitlintrc", [...DATA, ".js"])]
|
|
13384
14125
|
}
|
|
13385
14126
|
];
|
|
13386
|
-
var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(
|
|
14127
|
+
var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(join35(env.root, file)))?.kind === "file");
|
|
13387
14128
|
var crowded = (env, groups, build) => groups.flatMap(({ name, files }) => {
|
|
13388
14129
|
const found = present(env, files);
|
|
13389
14130
|
return found.length > 1 ? [build(name, found.join(", "))] : [];
|
|
@@ -13407,6 +14148,22 @@ var checkProjectFiles = (env) => [
|
|
|
13407
14148
|
)
|
|
13408
14149
|
];
|
|
13409
14150
|
|
|
14151
|
+
// src/doctor/remotes.ts
|
|
14152
|
+
var checkRemoteCredentials = ({ root, runtime }) => {
|
|
14153
|
+
const remotes = remotesWithCredentials(runtime, root);
|
|
14154
|
+
if (remotes.length === 0) return [];
|
|
14155
|
+
const params2 = { remotes: remotes.join(", "), remote: remotes[0] ?? "origin" };
|
|
14156
|
+
return [
|
|
14157
|
+
finding(
|
|
14158
|
+
"secrets.remote_credential",
|
|
14159
|
+
"warn",
|
|
14160
|
+
"doctor.secrets.remote_credential",
|
|
14161
|
+
params2,
|
|
14162
|
+
"doctor.secrets.fix_remote_credential"
|
|
14163
|
+
)
|
|
14164
|
+
];
|
|
14165
|
+
};
|
|
14166
|
+
|
|
13410
14167
|
// src/doctor/run.ts
|
|
13411
14168
|
var DOCTOR_SCHEMA = "finitemonkey/doctor@1";
|
|
13412
14169
|
var CHECKS = [
|
|
@@ -13422,6 +14179,7 @@ var CHECKS = [
|
|
|
13422
14179
|
checkInstructions,
|
|
13423
14180
|
checkProjectFiles,
|
|
13424
14181
|
checkLineEndings,
|
|
14182
|
+
checkRemoteCredentials,
|
|
13425
14183
|
checkAgentsFile,
|
|
13426
14184
|
checkHooks
|
|
13427
14185
|
];
|
|
@@ -13449,7 +14207,7 @@ var toItem = (ctx, finding3) => ({
|
|
|
13449
14207
|
detail: ctx.t(finding3.messageKey, finding3.params),
|
|
13450
14208
|
...finding3.fixKey === void 0 ? {} : { fix: ctx.t(finding3.fixKey, finding3.params) }
|
|
13451
14209
|
});
|
|
13452
|
-
var
|
|
14210
|
+
var count3 = (items, status) => items.filter((item) => item.status === status).length;
|
|
13453
14211
|
var hookItems = (ctx, findings) => findings.flatMap(
|
|
13454
14212
|
(finding3) => finding3.hook === void 0 ? [] : [
|
|
13455
14213
|
{
|
|
@@ -13465,7 +14223,7 @@ var buildReport2 = (ctx, findings) => {
|
|
|
13465
14223
|
schema: DOCTOR_SCHEMA,
|
|
13466
14224
|
checks,
|
|
13467
14225
|
hooks: hookItems(ctx, findings),
|
|
13468
|
-
summary: { ok:
|
|
14226
|
+
summary: { ok: count3(checks, "ok"), warn: count3(checks, "warn"), fail: count3(checks, "fail") }
|
|
13469
14227
|
};
|
|
13470
14228
|
};
|
|
13471
14229
|
|
|
@@ -13782,7 +14540,7 @@ var resultOf = (ctx, checked) => {
|
|
|
13782
14540
|
const problem4 = gitProblem("merge_unanalyzable", "git.error.merge_unanalyzable", detail, "git status");
|
|
13783
14541
|
return failureFrom(ctx.t, { ...problem4, exitCode: ExitCode.Usage, data }, human2);
|
|
13784
14542
|
}
|
|
13785
|
-
const lost = checked.reduce((
|
|
14543
|
+
const lost = checked.reduce((count4, item) => count4 + item.report.findings.length, 0);
|
|
13786
14544
|
if (lost === 0) return success(data, human2);
|
|
13787
14545
|
return failureFrom(
|
|
13788
14546
|
ctx.t,
|
|
@@ -13845,11 +14603,11 @@ var unreachable = (runtime, repo, shas) => {
|
|
|
13845
14603
|
var fsckDangling = (runtime, repo) => {
|
|
13846
14604
|
const result = runtime.git(["fsck", "--no-reflogs", "--no-progress", "--dangling"], repo.root);
|
|
13847
14605
|
const lines3 = linesOf(result.stdout);
|
|
13848
|
-
const
|
|
14606
|
+
const pick4 = (kind) => lines3.flatMap((line2) => {
|
|
13849
14607
|
const [label2, type, sha] = line2.split(" ");
|
|
13850
14608
|
return label2 === "dangling" && type === kind && sha !== void 0 ? [sha] : [];
|
|
13851
14609
|
});
|
|
13852
|
-
return { commits:
|
|
14610
|
+
return { commits: pick4("commit"), blobs: pick4("blob").length };
|
|
13853
14611
|
};
|
|
13854
14612
|
var describe = (runtime, repo, shas) => {
|
|
13855
14613
|
if (shas.length === 0) return /* @__PURE__ */ new Map();
|
|
@@ -13949,7 +14707,7 @@ var rescueCommand = {
|
|
|
13949
14707
|
};
|
|
13950
14708
|
|
|
13951
14709
|
// src/git/undo.ts
|
|
13952
|
-
import { basename as basename3, dirname as dirname8, join as
|
|
14710
|
+
import { basename as basename3, dirname as dirname8, join as join36 } from "node:path";
|
|
13953
14711
|
var INDEX_SUBJECT = "finitemonkey index:";
|
|
13954
14712
|
var gitLine = (runtime, repo, args) => {
|
|
13955
14713
|
const result = runtime.git(args, repo.root);
|
|
@@ -13992,7 +14750,7 @@ var findSnapshot = (views, wanted) => {
|
|
|
13992
14750
|
};
|
|
13993
14751
|
var stampOf2 = (iso) => iso.replace(/[:.]/g, "-");
|
|
13994
14752
|
var worktreePath = (runtime, repo) => {
|
|
13995
|
-
const base =
|
|
14753
|
+
const base = join36(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
|
|
13996
14754
|
return toAbsolutePath(runtime.stat(toAbsolutePath(base)) === void 0 ? base : `${base}-2`);
|
|
13997
14755
|
};
|
|
13998
14756
|
var intoWorktree = (runtime, repo, view2) => {
|
|
@@ -14005,8 +14763,8 @@ var step2 = (runtime, repo, args) => {
|
|
|
14005
14763
|
return result.ok ? accept(void 0) : reject(failure2(args.join(" "), result));
|
|
14006
14764
|
};
|
|
14007
14765
|
var inPlace = (runtime, repo, view2) => {
|
|
14008
|
-
const
|
|
14009
|
-
if (view2.head !== void 0 && view2.head !==
|
|
14766
|
+
const current2 = gitLine(runtime, repo, ["rev-parse", "--verify", "--quiet", "HEAD"]);
|
|
14767
|
+
if (view2.head !== void 0 && view2.head !== current2) {
|
|
14010
14768
|
const moved = step2(runtime, repo, ["reset", "--soft", view2.head]);
|
|
14011
14769
|
if (!moved.ok) return moved;
|
|
14012
14770
|
}
|
|
@@ -14227,7 +14985,7 @@ var hookCommand = {
|
|
|
14227
14985
|
};
|
|
14228
14986
|
|
|
14229
14987
|
// src/commands/i18n-check.ts
|
|
14230
|
-
import { join as
|
|
14988
|
+
import { join as join37 } from "node:path";
|
|
14231
14989
|
|
|
14232
14990
|
// src/i18n/message.ts
|
|
14233
14991
|
var ARG = /^\s*([a-zA-Z][a-zA-Z0-9_]*)\s*(?:,\s*(plural|select)\s*,)?/;
|
|
@@ -14313,17 +15071,17 @@ var rulesFor = (locale) => {
|
|
|
14313
15071
|
var branchFor = (node, value, locale) => {
|
|
14314
15072
|
const { branches } = node;
|
|
14315
15073
|
if (node.kind === "select") return branches[String(value)] ?? branches["other"];
|
|
14316
|
-
const
|
|
14317
|
-
if (!Number.isFinite(
|
|
14318
|
-
return branches[`=${String(
|
|
15074
|
+
const count4 = typeof value === "number" ? value : Number(value);
|
|
15075
|
+
if (!Number.isFinite(count4)) return branches["other"];
|
|
15076
|
+
return branches[`=${String(count4)}`] ?? branches[rulesFor(locale).select(count4)] ?? branches["other"];
|
|
14319
15077
|
};
|
|
14320
|
-
var render = (nodes, params2, locale,
|
|
15078
|
+
var render = (nodes, params2, locale, count4) => nodes.map((node) => {
|
|
14321
15079
|
if (node.kind === "text") return node.text;
|
|
14322
|
-
if (node.kind === "count") return
|
|
15080
|
+
if (node.kind === "count") return count4 === void 0 ? "#" : String(count4);
|
|
14323
15081
|
const value = params2[node.name];
|
|
14324
15082
|
if (node.kind === "arg") return value === void 0 ? `{${node.name}}` : String(value);
|
|
14325
15083
|
const branch = branchFor(node, value, locale) ?? [];
|
|
14326
|
-
return render(branch, params2, locale, node.kind === "plural" ? value :
|
|
15084
|
+
return render(branch, params2, locale, node.kind === "plural" ? value : count4);
|
|
14327
15085
|
}).join("");
|
|
14328
15086
|
var formatMessage = (source, params2, locale) => render(parseMessage(source), params2, locale, void 0);
|
|
14329
15087
|
var walk = (nodes, visit2) => {
|
|
@@ -14353,7 +15111,7 @@ var choicesOf = (source) => {
|
|
|
14353
15111
|
var requiredPluralBranches = (locale) => {
|
|
14354
15112
|
const rules = rulesFor(locale);
|
|
14355
15113
|
const seen = /* @__PURE__ */ new Set(["other"]);
|
|
14356
|
-
for (let
|
|
15114
|
+
for (let count4 = 0; count4 <= 1e3; count4 += 1) seen.add(rules.select(count4));
|
|
14357
15115
|
return [...seen].sort();
|
|
14358
15116
|
};
|
|
14359
15117
|
|
|
@@ -14429,7 +15187,7 @@ var unreadable = (dir, file, detail) => ({
|
|
|
14429
15187
|
exitCode: ExitCode.NotConfigured
|
|
14430
15188
|
});
|
|
14431
15189
|
var readCatalog = (ctx, dir, lang) => {
|
|
14432
|
-
const file = toAbsolutePath(
|
|
15190
|
+
const file = toAbsolutePath(join37(dir, `${lang}.json`));
|
|
14433
15191
|
const text6 = ctx.runtime.readText(file);
|
|
14434
15192
|
if (!text6.ok) return reject(unreadable(dir, file, text6.error));
|
|
14435
15193
|
const parsed = parseJsonObject(text6.value);
|
|
@@ -14452,7 +15210,7 @@ var describeProblem = (ctx, problem4) => {
|
|
|
14452
15210
|
return ` ${problem4.lang} ${problem4.key} ${kind}`;
|
|
14453
15211
|
};
|
|
14454
15212
|
var loadReview = (ctx, dir) => {
|
|
14455
|
-
const text6 = ctx.runtime.readText(toAbsolutePath(
|
|
15213
|
+
const text6 = ctx.runtime.readText(toAbsolutePath(join37(dir, "review.json")));
|
|
14456
15214
|
const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
|
|
14457
15215
|
if (parsed?.ok !== true) return void 0;
|
|
14458
15216
|
const statuses = (lang) => {
|
|
@@ -14493,10 +15251,10 @@ var i18nCheckCommand = {
|
|
|
14493
15251
|
};
|
|
14494
15252
|
|
|
14495
15253
|
// src/history/look-back.ts
|
|
14496
|
-
import { join as
|
|
15254
|
+
import { join as join38 } from "node:path";
|
|
14497
15255
|
|
|
14498
15256
|
// src/history/patterns.ts
|
|
14499
|
-
import { posix as
|
|
15257
|
+
import { posix as posix5 } from "node:path";
|
|
14500
15258
|
var PATTERNS = ["done_after_failing_tests", "env_read", "no_verify", "force_push", "test_skip"];
|
|
14501
15259
|
var TEST_COMMAND = /(^|[\s;&|(])((npm|pnpm|yarn|bun)\s+(run\s+)?(test|check)\b|npx\s+(vitest|jest)\b|vitest\b|jest\b|pytest\b|go\s+test\b|cargo\s+test\b|node\s+--test\b|mocha\b)/;
|
|
14502
15260
|
var DONE_CLAIM = /\b(done|all (tests|checks) (pass|passed|are passing)|everything (passes|works)|it works|pronto|feito|conclu[ií]do|listo|terminado)\b/i;
|
|
@@ -14518,7 +15276,7 @@ var stringField = (tool, name) => {
|
|
|
14518
15276
|
return typeof value === "string" ? value : "";
|
|
14519
15277
|
};
|
|
14520
15278
|
var isEnvPath = (path) => {
|
|
14521
|
-
const name =
|
|
15279
|
+
const name = posix5.basename(path.replace(/\\/g, "/"));
|
|
14522
15280
|
return /^\.env(\.[A-Za-z0-9_-]+)?$/.test(name) && !ENV_TEMPLATE.test(name);
|
|
14523
15281
|
};
|
|
14524
15282
|
var envRead = (tool) => {
|
|
@@ -14606,7 +15364,7 @@ var addResults = (draft, blocks) => {
|
|
|
14606
15364
|
};
|
|
14607
15365
|
var parseTranscript = (jsonl) => {
|
|
14608
15366
|
const turns = [];
|
|
14609
|
-
let
|
|
15367
|
+
let current2;
|
|
14610
15368
|
for (const line2 of jsonl.split(/\r?\n/)) {
|
|
14611
15369
|
if (line2.trim() === "") continue;
|
|
14612
15370
|
const parsed = parseJsonObject(line2);
|
|
@@ -14614,13 +15372,13 @@ var parseTranscript = (jsonl) => {
|
|
|
14614
15372
|
const entry2 = parsed.value;
|
|
14615
15373
|
const blocks = blocksOf(entry2["message"]);
|
|
14616
15374
|
if (entry2["type"] === "user") {
|
|
14617
|
-
if (
|
|
15375
|
+
if (current2 !== void 0 && addResults(current2, blocks)) continue;
|
|
14618
15376
|
const prompt = blocks.map(textOf3).join("\n").trim();
|
|
14619
15377
|
if (prompt === "") continue;
|
|
14620
|
-
|
|
14621
|
-
turns.push(
|
|
14622
|
-
} else if (entry2["type"] === "assistant" &&
|
|
14623
|
-
addAssistant(
|
|
15378
|
+
current2 = startTurn(prompt);
|
|
15379
|
+
turns.push(current2);
|
|
15380
|
+
} else if (entry2["type"] === "assistant" && current2 !== void 0) {
|
|
15381
|
+
addAssistant(current2, blocks);
|
|
14624
15382
|
}
|
|
14625
15383
|
}
|
|
14626
15384
|
return turns;
|
|
@@ -14629,7 +15387,7 @@ var parseTranscript = (jsonl) => {
|
|
|
14629
15387
|
// src/history/look-back.ts
|
|
14630
15388
|
var SAMPLES = 3;
|
|
14631
15389
|
var projectLogKey = (root) => root.replace(/[^A-Za-z0-9-]/g, "-");
|
|
14632
|
-
var projectLogDir = (runtime, root, env) =>
|
|
15390
|
+
var projectLogDir = (runtime, root, env) => join38(env["CLAUDE_CONFIG_DIR"] ?? join38(runtime.homeDir(), ".claude"), "projects", projectLogKey(root));
|
|
14633
15391
|
var lookBack = (runtime, root, env) => {
|
|
14634
15392
|
const dir = projectLogDir(runtime, root, env);
|
|
14635
15393
|
const listed4 = runtime.listDir(toAbsolutePath(dir));
|
|
@@ -14650,7 +15408,7 @@ var lookBack = (runtime, root, env) => {
|
|
|
14650
15408
|
};
|
|
14651
15409
|
let turns = 0;
|
|
14652
15410
|
for (const name of files) {
|
|
14653
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
15411
|
+
const text6 = runtime.readText(toAbsolutePath(join38(dir, name)));
|
|
14654
15412
|
if (!text6.ok) continue;
|
|
14655
15413
|
for (const turn of parseTranscript(text6.value)) {
|
|
14656
15414
|
turns += 1;
|
|
@@ -14666,6 +15424,7 @@ var lookBack = (runtime, root, env) => {
|
|
|
14666
15424
|
|
|
14667
15425
|
// src/install/diff.ts
|
|
14668
15426
|
var MAX_DIFF_LINES = 120;
|
|
15427
|
+
var MAX_LCS_CELLS = 4e6;
|
|
14669
15428
|
var table = (a, b) => {
|
|
14670
15429
|
const rows = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
14671
15430
|
for (let i = a.length - 1; i >= 0; i -= 1) {
|
|
@@ -14700,8 +15459,27 @@ var walk2 = (a, b) => {
|
|
|
14700
15459
|
return ops;
|
|
14701
15460
|
};
|
|
14702
15461
|
var splitLines = (text6) => text6 === "" ? [] : text6.replace(/\r?\n$/, "").split(/\r?\n/);
|
|
15462
|
+
var sharedPrefix = (a, b) => {
|
|
15463
|
+
let count4 = 0;
|
|
15464
|
+
while (count4 < a.length && count4 < b.length && a[count4] === b[count4]) count4 += 1;
|
|
15465
|
+
return count4;
|
|
15466
|
+
};
|
|
15467
|
+
var sharedSuffix = (a, b, prefix) => {
|
|
15468
|
+
let count4 = 0;
|
|
15469
|
+
while (count4 < a.length - prefix && count4 < b.length - prefix && a[a.length - 1 - count4] === b[b.length - 1 - count4])
|
|
15470
|
+
count4 += 1;
|
|
15471
|
+
return count4;
|
|
15472
|
+
};
|
|
15473
|
+
var changedOps = (a, b) => {
|
|
15474
|
+
const prefix = sharedPrefix(a, b);
|
|
15475
|
+
const suffix = sharedSuffix(a, b, prefix);
|
|
15476
|
+
const left = a.slice(prefix, a.length - suffix);
|
|
15477
|
+
const right = b.slice(prefix, b.length - suffix);
|
|
15478
|
+
if (left.length * right.length <= MAX_LCS_CELLS) return walk2(left, right).filter(([op]) => op !== "=");
|
|
15479
|
+
return [...left.map((line2) => ["-", line2]), ...right.map((line2) => ["+", line2])];
|
|
15480
|
+
};
|
|
14703
15481
|
var lineDiff = (path, before, after) => {
|
|
14704
|
-
const changes =
|
|
15482
|
+
const changes = changedOps(splitLines(before), splitLines(after));
|
|
14705
15483
|
if (changes.length === 0) return [];
|
|
14706
15484
|
const lines3 = changes.map(([op, line2]) => `${op} ${line2}`);
|
|
14707
15485
|
const shown3 = lines3.slice(0, MAX_DIFF_LINES);
|
|
@@ -14846,6 +15624,32 @@ var initCommand = {
|
|
|
14846
15624
|
}
|
|
14847
15625
|
};
|
|
14848
15626
|
|
|
15627
|
+
// src/commands/policy.ts
|
|
15628
|
+
var FIX = `finitemonkey policy false-positive <${OPTIONAL_GUARDS.join("|")}>`;
|
|
15629
|
+
var recordFalsePositive = (ctx, guard) => {
|
|
15630
|
+
if (!isPerson(ctx)) return failureFrom(ctx.t, needsPersonProblem("policy.error.person_only", FIX));
|
|
15631
|
+
if (!OPTIONAL_GUARDS.some((id) => id === guard)) {
|
|
15632
|
+
return failureFrom(ctx.t, usageProblem("invalid_arguments", "error.invalid_arguments", FIX, { detail: guard }));
|
|
15633
|
+
}
|
|
15634
|
+
const paths = fmPaths(findRepoRoot(ctx.runtime, ctx.cwd) ?? ctx.cwd);
|
|
15635
|
+
const state = readShadow(ctx.runtime, paths);
|
|
15636
|
+
const before = state[guard] ?? { firings: 0, falsePositives: 0, promoted: false };
|
|
15637
|
+
const falsePositives = before.falsePositives + 1;
|
|
15638
|
+
writeShadow(ctx.runtime, paths, { ...state, [guard]: { ...before, falsePositives } });
|
|
15639
|
+
logEvent(ctx.runtime, paths, "guard_false_positive", { guard, falsePositives });
|
|
15640
|
+
return success({ guard, falsePositives }, [
|
|
15641
|
+
ctx.t("policy.false_positive.recorded", { guard, count: falsePositives })
|
|
15642
|
+
]);
|
|
15643
|
+
};
|
|
15644
|
+
var policyFalsePositiveCommand = {
|
|
15645
|
+
path: ["policy", "false-positive"],
|
|
15646
|
+
summaryKey: "command.policy_false_positive.summary",
|
|
15647
|
+
usage: FIX,
|
|
15648
|
+
status: "ready",
|
|
15649
|
+
maxPositionals: 1,
|
|
15650
|
+
run: (ctx, args) => Promise.resolve(recordFalsePositive(ctx, args.positionals[0] ?? ""))
|
|
15651
|
+
};
|
|
15652
|
+
|
|
14849
15653
|
// src/commands/prose-check.ts
|
|
14850
15654
|
import { relative as relative8 } from "node:path";
|
|
14851
15655
|
|
|
@@ -15208,7 +16012,7 @@ var view = (finding3, status) => ({
|
|
|
15208
16012
|
status,
|
|
15209
16013
|
...finding3.commit === void 0 ? {} : { commit: finding3.commit }
|
|
15210
16014
|
});
|
|
15211
|
-
var
|
|
16015
|
+
var modeOf3 = (values) => {
|
|
15212
16016
|
const chosen = [values["staged"] === true, values["history"] === true, stringOption(values, "path") !== void 0];
|
|
15213
16017
|
if (chosen.filter(Boolean).length > 1) return void 0;
|
|
15214
16018
|
if (chosen[0] === true) return "staged";
|
|
@@ -15274,7 +16078,7 @@ var humanOf2 = (ctx, data) => [
|
|
|
15274
16078
|
];
|
|
15275
16079
|
var resultOf3 = (ctx, data) => {
|
|
15276
16080
|
const human2 = humanOf2(ctx, data);
|
|
15277
|
-
const failing = data.
|
|
16081
|
+
const failing = data.findings.filter((item) => item.status !== "known" && item.severity !== "warning").length;
|
|
15278
16082
|
if (failing === 0) return success(data, human2);
|
|
15279
16083
|
return failureFrom(
|
|
15280
16084
|
ctx.t,
|
|
@@ -15299,7 +16103,7 @@ var secretsScanCommand = {
|
|
|
15299
16103
|
status: "ready",
|
|
15300
16104
|
options: { staged: { type: "boolean" }, history: { type: "boolean" }, path: { type: "string" } },
|
|
15301
16105
|
run: async (ctx, args) => {
|
|
15302
|
-
const mode =
|
|
16106
|
+
const mode = modeOf3(args.values);
|
|
15303
16107
|
const fix = "finitemonkey help secrets scan";
|
|
15304
16108
|
if (mode === void 0) return failureFrom(ctx.t, usageProblem("invalid_arguments", "secrets.error.one_mode", fix));
|
|
15305
16109
|
const root = projectRoot(ctx);
|
|
@@ -15321,7 +16125,7 @@ var secretsScanCommand = {
|
|
|
15321
16125
|
};
|
|
15322
16126
|
|
|
15323
16127
|
// src/task/create.ts
|
|
15324
|
-
import { join as
|
|
16128
|
+
import { join as join39 } from "node:path";
|
|
15325
16129
|
|
|
15326
16130
|
// src/core/yaml/write.ts
|
|
15327
16131
|
var PLAIN_SAFE = /^[A-Za-z_./][A-Za-z0-9_./ ()-]*$/;
|
|
@@ -15380,7 +16184,8 @@ var contractYaml = (contract) => ({
|
|
|
15380
16184
|
tests_protected: contract.scope.testsProtected
|
|
15381
16185
|
},
|
|
15382
16186
|
acceptance: contract.acceptance.map(checkYaml),
|
|
15383
|
-
decisions_needed: [...contract.decisionsNeeded]
|
|
16187
|
+
decisions_needed: [...contract.decisionsNeeded],
|
|
16188
|
+
...contract.callersIgnore === void 0 ? {} : { callers_ignore: [...contract.callersIgnore] }
|
|
15384
16189
|
});
|
|
15385
16190
|
var contractJson = (contract) => ({
|
|
15386
16191
|
id: contract.id,
|
|
@@ -15405,10 +16210,10 @@ var explicitChecks = (draft) => [
|
|
|
15405
16210
|
var draftChecks = (runtime, paths, draft) => {
|
|
15406
16211
|
const disk = {
|
|
15407
16212
|
read: (relative9) => {
|
|
15408
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
16213
|
+
const text6 = runtime.readText(toAbsolutePath(join39(paths.root, relative9)));
|
|
15409
16214
|
return text6.ok ? text6.value : void 0;
|
|
15410
16215
|
},
|
|
15411
|
-
exists: (relative9) => runtime.stat(toAbsolutePath(
|
|
16216
|
+
exists: (relative9) => runtime.stat(toAbsolutePath(join39(paths.root, relative9))) !== void 0
|
|
15412
16217
|
};
|
|
15413
16218
|
const detected = detectChecks(disk);
|
|
15414
16219
|
const base = draft.runs.length > 0 || draft.tests !== void 0 ? explicitChecks(draft) : detected;
|
|
@@ -15421,6 +16226,7 @@ var draftChecks = (runtime, paths, draft) => {
|
|
|
15421
16226
|
{ id: "prose", kind: "prose" },
|
|
15422
16227
|
{ id: "worktree", kind: "worktree" },
|
|
15423
16228
|
{ id: "merge", kind: "merge" },
|
|
16229
|
+
{ id: "callers", kind: "callers" },
|
|
15424
16230
|
...draft.paths.length > 0 || draft.budget !== void 0 ? [{ id: "diff", kind: "diff" }] : [],
|
|
15425
16231
|
...draft.git.length > 0 ? [{ id: "git", kind: "git", require: draft.git }] : [],
|
|
15426
16232
|
{ id: "pr", kind: "pr", requireOpen: draft.git.includes("pr_open") },
|
|
@@ -15610,15 +16416,15 @@ var taskDoneCommand = {
|
|
|
15610
16416
|
};
|
|
15611
16417
|
|
|
15612
16418
|
// src/install/uninstall.ts
|
|
15613
|
-
var structural = (entry2,
|
|
16419
|
+
var structural = (entry2, current2) => {
|
|
15614
16420
|
if (entry2.kind === "gitignore" || entry2.kind === "markdown") {
|
|
15615
|
-
const text6 = entry2.kind === "gitignore" ? withoutBlock(
|
|
16421
|
+
const text6 = entry2.kind === "gitignore" ? withoutBlock(current2) : withoutMarkdownBlock(current2);
|
|
15616
16422
|
return text6 === "" && !entry2.existed ? void 0 : text6;
|
|
15617
16423
|
}
|
|
15618
|
-
const parsed = parseSettings(
|
|
15619
|
-
if (!parsed.ok || entry2.added === void 0) return
|
|
16424
|
+
const parsed = parseSettings(current2);
|
|
16425
|
+
if (!parsed.ok || entry2.added === void 0) return current2;
|
|
15620
16426
|
const settings = unmergeSettings(parsed.value, entry2.added, entry2.handlers ?? []);
|
|
15621
|
-
return Object.keys(settings).length === 0 && !entry2.existed ? void 0 : renderJson(settings, styleOf(
|
|
16427
|
+
return Object.keys(settings).length === 0 && !entry2.existed ? void 0 : renderJson(settings, styleOf(current2));
|
|
15622
16428
|
};
|
|
15623
16429
|
var unchanged = (project, entry2) => {
|
|
15624
16430
|
if (!entry2.existed) return { path: entry2.path, action: "delete", after: void 0 };
|
|
@@ -15626,17 +16432,17 @@ var unchanged = (project, entry2) => {
|
|
|
15626
16432
|
return original === void 0 ? void 0 : { path: entry2.path, action: "restore", after: original };
|
|
15627
16433
|
};
|
|
15628
16434
|
var removalFor = (project, entry2, options) => {
|
|
15629
|
-
const
|
|
15630
|
-
if (
|
|
16435
|
+
const current2 = project.read(entry2.path);
|
|
16436
|
+
if (current2 === void 0) return { path: entry2.path, action: "gone", after: void 0 };
|
|
15631
16437
|
if (entry2.kind === "generated" && options.keepGenerated) {
|
|
15632
|
-
return { path: entry2.path, action: "keep-generated", after:
|
|
16438
|
+
return { path: entry2.path, action: "keep-generated", after: current2 };
|
|
15633
16439
|
}
|
|
15634
|
-
if (hashText(
|
|
16440
|
+
if (hashText(current2) === entry2.hash) {
|
|
15635
16441
|
const exact2 = unchanged(project, entry2);
|
|
15636
16442
|
if (exact2 !== void 0) return exact2;
|
|
15637
16443
|
}
|
|
15638
|
-
if (entry2.kind === "generated") return { path: entry2.path, action: "keep-edited", after:
|
|
15639
|
-
const after = structural(entry2,
|
|
16444
|
+
if (entry2.kind === "generated") return { path: entry2.path, action: "keep-edited", after: current2 };
|
|
16445
|
+
const after = structural(entry2, current2);
|
|
15640
16446
|
return { path: entry2.path, action: after === void 0 ? "delete" : "unmerge", after };
|
|
15641
16447
|
};
|
|
15642
16448
|
var planRemoval = (project, lock, options) => ({
|
|
@@ -15801,6 +16607,7 @@ var REGISTRY = [
|
|
|
15801
16607
|
taskDoneCommand,
|
|
15802
16608
|
secretsScanCommand,
|
|
15803
16609
|
secretsAcceptCommand,
|
|
16610
|
+
policyFalsePositiveCommand,
|
|
15804
16611
|
secretsIncidentCommand,
|
|
15805
16612
|
proseCheckCommand,
|
|
15806
16613
|
snapshotCommand,
|
|
@@ -15812,7 +16619,7 @@ var REGISTRY = [
|
|
|
15812
16619
|
];
|
|
15813
16620
|
|
|
15814
16621
|
// src/i18n/lang-settings.ts
|
|
15815
|
-
import { join as
|
|
16622
|
+
import { join as join40 } from "node:path";
|
|
15816
16623
|
var read3 = (runtime, path) => {
|
|
15817
16624
|
const text6 = runtime.readText(toAbsolutePath(path));
|
|
15818
16625
|
return text6.ok ? text6.value : void 0;
|
|
@@ -15827,14 +16634,14 @@ var languageIn = (text6) => {
|
|
|
15827
16634
|
const value = parsed.ok && isRecord(parsed.value) ? parsed.value["language"] : void 0;
|
|
15828
16635
|
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
15829
16636
|
};
|
|
15830
|
-
var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ??
|
|
16637
|
+
var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join40(runtime.homeDir(), ".claude");
|
|
15831
16638
|
var langSettings = (runtime, cwd, env, platform) => {
|
|
15832
16639
|
const root = findGitRoot(cwd, runtime) ?? cwd;
|
|
15833
|
-
const config = parseConfigYaml(read3(runtime,
|
|
16640
|
+
const config = parseConfigYaml(read3(runtime, join40(root, CONFIG_FILE)) ?? "");
|
|
15834
16641
|
const claudeFiles = [
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
|
|
16642
|
+
join40(root, ".claude", "settings.local.json"),
|
|
16643
|
+
join40(root, ".claude", "settings.json"),
|
|
16644
|
+
join40(claudeDir(runtime, env), "settings.json")
|
|
15838
16645
|
];
|
|
15839
16646
|
return {
|
|
15840
16647
|
config: configText(config, "lang"),
|