finitemonkey 0.1.0-next.15 → 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 +852 -380
- package/package.json +1 -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 })}
|
|
@@ -2014,11 +2170,11 @@ var maskDeep = (value) => {
|
|
|
2014
2170
|
};
|
|
2015
2171
|
|
|
2016
2172
|
// src/task/paths.ts
|
|
2017
|
-
import { join as
|
|
2173
|
+
import { join as join6 } from "node:path";
|
|
2018
2174
|
var FM_DIR = ".finitemonkey";
|
|
2019
2175
|
var fmPaths = (root) => {
|
|
2020
|
-
const dir = toAbsolutePath(
|
|
2021
|
-
const at = (...parts) => toAbsolutePath(
|
|
2176
|
+
const dir = toAbsolutePath(join6(root, FM_DIR));
|
|
2177
|
+
const at = (...parts) => toAbsolutePath(join6(dir, ...parts));
|
|
2022
2178
|
return {
|
|
2023
2179
|
root,
|
|
2024
2180
|
dir,
|
|
@@ -2254,16 +2410,16 @@ var reportMarkdown = (env, ctx, envelope) => {
|
|
|
2254
2410
|
];
|
|
2255
2411
|
return maskText(withoutHome(lines3.join("\n"), env.runtime.homeDir()));
|
|
2256
2412
|
};
|
|
2257
|
-
var reportPath = (env) => toAbsolutePath(
|
|
2413
|
+
var reportPath = (env) => toAbsolutePath(join7(env.root, ...REPORTS_DIR.split("/"), `doctor-${stamp(env.runtime.now())}.md`));
|
|
2258
2414
|
|
|
2259
2415
|
// src/install/plugin-yield.ts
|
|
2260
|
-
import { join as
|
|
2416
|
+
import { join as join8 } from "node:path";
|
|
2261
2417
|
var PLUGIN_VIA = "plugin";
|
|
2262
2418
|
var YIELD_STDOUT = `${JSON.stringify({ continue: true })}
|
|
2263
2419
|
`;
|
|
2264
2420
|
var projectOwnsHooks = (ctx) => {
|
|
2265
2421
|
const root = ctx.env["CLAUDE_PROJECT_DIR"] ?? ctx.cwd;
|
|
2266
|
-
const text6 = ctx.runtime.readText(toAbsolutePath(
|
|
2422
|
+
const text6 = ctx.runtime.readText(toAbsolutePath(join8(root, ...SETTINGS_FILE.split("/"))));
|
|
2267
2423
|
if (!text6.ok) return false;
|
|
2268
2424
|
const parsed = parseJsonObject(text6.value);
|
|
2269
2425
|
return parsed.ok && ownHandlersIn(parsed.value) > 0;
|
|
@@ -2277,7 +2433,7 @@ var STABLE_PLUGIN_ID = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
|
2277
2433
|
var NEXT_PLUGIN_ID = `${PLUGIN_NAME}@${NEXT_MARKETPLACE_NAME}`;
|
|
2278
2434
|
|
|
2279
2435
|
// src/doctor/settings.ts
|
|
2280
|
-
import { join as
|
|
2436
|
+
import { join as join9 } from "node:path";
|
|
2281
2437
|
var MANAGED = {
|
|
2282
2438
|
darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
|
|
2283
2439
|
linux: "/etc/claude-code/managed-settings.json",
|
|
@@ -2286,7 +2442,7 @@ var MANAGED = {
|
|
|
2286
2442
|
var userSettingsPath = ({ env, runtime }) => {
|
|
2287
2443
|
const configDir = env["CLAUDE_CONFIG_DIR"];
|
|
2288
2444
|
return toAbsolutePath(
|
|
2289
|
-
configDir === void 0 ?
|
|
2445
|
+
configDir === void 0 ? join9(runtime.homeDir(), ".claude", "settings.json") : join9(configDir, "settings.json")
|
|
2290
2446
|
);
|
|
2291
2447
|
};
|
|
2292
2448
|
var settingsPaths = (env) => {
|
|
@@ -2294,8 +2450,8 @@ var settingsPaths = (env) => {
|
|
|
2294
2450
|
return [
|
|
2295
2451
|
...managed === void 0 ? [] : [{ scope: "managed", path: toAbsolutePath(managed) }],
|
|
2296
2452
|
{ scope: "user", path: userSettingsPath(env) },
|
|
2297
|
-
{ scope: "project", path: toAbsolutePath(
|
|
2298
|
-
{ 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")) }
|
|
2299
2455
|
];
|
|
2300
2456
|
};
|
|
2301
2457
|
var readSettings = (env) => settingsPaths(env).flatMap(({ scope: scope2, path }) => {
|
|
@@ -2439,7 +2595,7 @@ var checkClaudeSettings = (env) => {
|
|
|
2439
2595
|
};
|
|
2440
2596
|
|
|
2441
2597
|
// src/doctor/environment.ts
|
|
2442
|
-
import { join as
|
|
2598
|
+
import { join as join10 } from "node:path";
|
|
2443
2599
|
var AUTH_VARIABLES = [
|
|
2444
2600
|
"ANTHROPIC_API_KEY",
|
|
2445
2601
|
"ANTHROPIC_AUTH_TOKEN",
|
|
@@ -2477,7 +2633,7 @@ var pathDirs = ({ env, platform }) => {
|
|
|
2477
2633
|
var copiesOnPath = (env) => {
|
|
2478
2634
|
const names = EXECUTABLES[env.platform] ?? ["finitemonkey"];
|
|
2479
2635
|
const found = pathDirs(env).flatMap(
|
|
2480
|
-
(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))
|
|
2481
2637
|
);
|
|
2482
2638
|
const byDir = new Map(found.map((path) => [path.replace(/\.(cmd|exe|ps1)$/i, ""), path]));
|
|
2483
2639
|
return [...byDir.values()];
|
|
@@ -2511,7 +2667,7 @@ var checkAuthVariables = ({ env, platform }) => {
|
|
|
2511
2667
|
};
|
|
2512
2668
|
|
|
2513
2669
|
// src/doctor/hooks.ts
|
|
2514
|
-
import { join as
|
|
2670
|
+
import { join as join31 } from "node:path";
|
|
2515
2671
|
|
|
2516
2672
|
// src/hooks/events.ts
|
|
2517
2673
|
var HOOK_EVENTS = [
|
|
@@ -2558,9 +2714,9 @@ var deadlineFor = (event, raw) => {
|
|
|
2558
2714
|
};
|
|
2559
2715
|
|
|
2560
2716
|
// src/hooks/fields.ts
|
|
2561
|
-
var textField = (
|
|
2717
|
+
var textField = (record3, ...names) => {
|
|
2562
2718
|
for (const name of names) {
|
|
2563
|
-
const value =
|
|
2719
|
+
const value = record3[name];
|
|
2564
2720
|
if (typeof value === "string") return value;
|
|
2565
2721
|
}
|
|
2566
2722
|
return void 0;
|
|
@@ -2655,8 +2811,8 @@ var closeTask = (state, id) => assemble({
|
|
|
2655
2811
|
|
|
2656
2812
|
// src/guards/release/grants.ts
|
|
2657
2813
|
var GRANT_MINUTES = 30;
|
|
2658
|
-
var text2 = (
|
|
2659
|
-
const value =
|
|
2814
|
+
var text2 = (record3, key) => {
|
|
2815
|
+
const value = record3[key];
|
|
2660
2816
|
return typeof value === "string" ? value : void 0;
|
|
2661
2817
|
};
|
|
2662
2818
|
var toGrant = (value) => {
|
|
@@ -3298,10 +3454,10 @@ var PS_ENV = /^\$env:([A-Za-z_][A-Za-z0-9_]*)=?(.*)$/is;
|
|
|
3298
3454
|
var CHANGES_DIR = /* @__PURE__ */ new Set(["cd", "pushd", "set-location", "push-location"]);
|
|
3299
3455
|
var lex = (text6, language) => language === "bash" ? lexBash(text6) : lexPowerShell(text6, language === "cmd");
|
|
3300
3456
|
var isWindowsAbsolute = (path) => /^[A-Za-z]:\//.test(path);
|
|
3301
|
-
var joinDir = (
|
|
3457
|
+
var joinDir = (current2, target) => {
|
|
3302
3458
|
const next2 = toPosixPath(target);
|
|
3303
3459
|
if (next2.startsWith("/") || isWindowsAbsolute(next2) || next2.startsWith("~")) return next2;
|
|
3304
|
-
return
|
|
3460
|
+
return current2 === void 0 ? posix.normalize(next2) : posix.join(current2, next2);
|
|
3305
3461
|
};
|
|
3306
3462
|
var assignments = (words, language) => {
|
|
3307
3463
|
if (language !== "bash") {
|
|
@@ -3414,9 +3570,9 @@ var COMMIT_VALUE_SHORTS = /* @__PURE__ */ new Set(["m", "F", "C", "c", "t"]);
|
|
|
3414
3570
|
var VERIFY_SUBCOMMANDS = /* @__PURE__ */ new Set(["commit", "push", "merge", "am", "rebase", "cherry-pick", "revert", "pull"]);
|
|
3415
3571
|
var isHooksPath = (value) => /^core\.hookspath(=|$)/i.test(value);
|
|
3416
3572
|
var envFindings = (command) => {
|
|
3417
|
-
const
|
|
3573
|
+
const pairs2 = Object.entries(command.env);
|
|
3418
3574
|
const exported = command.argv[0] === "export" ? command.argv.slice(1).map((arg) => arg.split("=", 2)) : [];
|
|
3419
|
-
return [...
|
|
3575
|
+
return [...pairs2, ...exported].filter(([name = ""]) => HOOK_ENV.test(name)).map(([name = "", value = ""]) => ({ kind: "hook_env", detail: `${name}=${value}` }));
|
|
3420
3576
|
};
|
|
3421
3577
|
var splitGit = (args) => {
|
|
3422
3578
|
let index = 0;
|
|
@@ -3574,7 +3730,7 @@ var selfGuard = (input) => {
|
|
|
3574
3730
|
};
|
|
3575
3731
|
|
|
3576
3732
|
// src/guards/tests/hook.ts
|
|
3577
|
-
import { join as
|
|
3733
|
+
import { join as join11, relative as relative3 } from "node:path";
|
|
3578
3734
|
|
|
3579
3735
|
// src/guards/tests/patterns.ts
|
|
3580
3736
|
var JS_TEST = /(^|\/)(__tests__|__test__)\/|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)tests?\/.*\.[cm]?[jt]sx?$/;
|
|
@@ -3764,7 +3920,7 @@ var shellViolations = (input, root) => {
|
|
|
3764
3920
|
return parseShell(command).flatMap(
|
|
3765
3921
|
(simple) => writtenPaths(simple).flatMap((path) => {
|
|
3766
3922
|
const local = localPath(root, path);
|
|
3767
|
-
const exists = input.runtime.stat(toAbsolutePath(
|
|
3923
|
+
const exists = input.runtime.stat(toAbsolutePath(join11(root, local))) !== void 0;
|
|
3768
3924
|
if (!exists) return [];
|
|
3769
3925
|
if (isProtectedConfig(local)) return [{ kind: "config_changed", path: local }];
|
|
3770
3926
|
return isTestFile(local) ? [{ kind: "test_removed", path: local }] : [];
|
|
@@ -3784,20 +3940,20 @@ var testsGuard = (input) => {
|
|
|
3784
3940
|
};
|
|
3785
3941
|
|
|
3786
3942
|
// src/gate/hooks/config.ts
|
|
3787
|
-
import { join as
|
|
3943
|
+
import { join as join13 } from "node:path";
|
|
3788
3944
|
|
|
3789
3945
|
// src/gate/hooks/common.ts
|
|
3790
|
-
import { dirname as dirname5, join as
|
|
3946
|
+
import { dirname as dirname5, join as join12 } from "node:path";
|
|
3791
3947
|
var DEFAULT_GATE_BUDGET_MS = 3e5;
|
|
3792
3948
|
var sessionCwd = (input) => input.payload.cwd === void 0 ? input.cwd : toAbsolutePath(input.payload.cwd, input.cwd);
|
|
3793
3949
|
var MAX_DEPTH2 = 64;
|
|
3794
3950
|
var findRepoRoot = (runtime, start) => {
|
|
3795
|
-
let
|
|
3951
|
+
let current2 = start;
|
|
3796
3952
|
for (let depth = 0; depth < MAX_DEPTH2; depth += 1) {
|
|
3797
|
-
if (runtime.stat(toAbsolutePath(
|
|
3798
|
-
const parent = toAbsolutePath(dirname5(
|
|
3799
|
-
if (parent ===
|
|
3800
|
-
|
|
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;
|
|
3801
3957
|
}
|
|
3802
3958
|
return void 0;
|
|
3803
3959
|
};
|
|
@@ -3817,7 +3973,7 @@ var contextOutput = (event, additionalContext, systemMessage) => ({
|
|
|
3817
3973
|
var human = (text6) => `finitemonkey: ${text6}`;
|
|
3818
3974
|
|
|
3819
3975
|
// src/gate/hooks/config.ts
|
|
3820
|
-
var
|
|
3976
|
+
var CONFIG_FILE3 = "config-hooks.json";
|
|
3821
3977
|
var GUARDED_SOURCES = /* @__PURE__ */ new Set(["user_settings", "project_settings", "local_settings"]);
|
|
3822
3978
|
var countIn = (value) => {
|
|
3823
3979
|
if (typeof value === "string") return /\bfinitemonkey\b|\bfm hook\b/.test(value) ? 1 : 0;
|
|
@@ -3831,20 +3987,20 @@ var censusOf = (text6) => {
|
|
|
3831
3987
|
return { hooks: countIn(parsed.value["hooks"]), disabled: parsed.value["disableAllHooks"] === true };
|
|
3832
3988
|
};
|
|
3833
3989
|
var settingsFiles = (paths, env) => [
|
|
3834
|
-
toAbsolutePath(
|
|
3835
|
-
toAbsolutePath(
|
|
3836
|
-
...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"))]
|
|
3837
3993
|
];
|
|
3838
3994
|
var read2 = (runtime, path) => {
|
|
3839
3995
|
const text6 = runtime.readText(path);
|
|
3840
3996
|
return text6.ok ? text6.value : void 0;
|
|
3841
3997
|
};
|
|
3842
3998
|
var known = (runtime, paths) => {
|
|
3843
|
-
const raw = readJsonFile(runtime, paths.cache(
|
|
3999
|
+
const raw = readJsonFile(runtime, paths.cache(CONFIG_FILE3))?.["hooks"];
|
|
3844
4000
|
return isRecord(raw) ? Object.fromEntries(Object.entries(raw).filter((entry2) => typeof entry2[1] === "number")) : {};
|
|
3845
4001
|
};
|
|
3846
4002
|
var save = (runtime, paths, counts) => {
|
|
3847
|
-
writeJsonFile(runtime, paths.cache(
|
|
4003
|
+
writeJsonFile(runtime, paths.cache(CONFIG_FILE3), { hooks: { ...counts } });
|
|
3848
4004
|
};
|
|
3849
4005
|
var recordConfigHooks = (runtime, paths, env) => {
|
|
3850
4006
|
const counts = Object.fromEntries(
|
|
@@ -3969,6 +4125,7 @@ var implicitContract = (files) => {
|
|
|
3969
4125
|
{ id: "prose", kind: "prose" },
|
|
3970
4126
|
{ id: "worktree", kind: "worktree" },
|
|
3971
4127
|
{ id: "merge", kind: "merge" },
|
|
4128
|
+
{ id: "callers", kind: "callers" },
|
|
3972
4129
|
{ id: "pr", kind: "pr", requireOpen: false }
|
|
3973
4130
|
],
|
|
3974
4131
|
decisionsNeeded: [],
|
|
@@ -3977,93 +4134,7 @@ var implicitContract = (files) => {
|
|
|
3977
4134
|
};
|
|
3978
4135
|
|
|
3979
4136
|
// src/gate/prepare.ts
|
|
3980
|
-
import { join as
|
|
3981
|
-
|
|
3982
|
-
// src/git/project-config.ts
|
|
3983
|
-
import { join as join12 } from "node:path";
|
|
3984
|
-
var CONFIG_FILE3 = ".finitemonkey/config.yml";
|
|
3985
|
-
var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
|
|
3986
|
-
var stripComment = (line2) => {
|
|
3987
|
-
let quote;
|
|
3988
|
-
for (let index = 0; index < line2.length; index += 1) {
|
|
3989
|
-
const char = line2.charAt(index);
|
|
3990
|
-
if (quote === void 0 && (char === '"' || char === "'")) quote = char;
|
|
3991
|
-
else if (char === quote) quote = void 0;
|
|
3992
|
-
else if (quote === void 0 && char === "#" && (index === 0 || /\s/.test(line2.charAt(index - 1)))) {
|
|
3993
|
-
return line2.slice(0, index);
|
|
3994
|
-
}
|
|
3995
|
-
}
|
|
3996
|
-
return line2;
|
|
3997
|
-
};
|
|
3998
|
-
var flowList = (value) => {
|
|
3999
|
-
const trimmed = value.trim();
|
|
4000
|
-
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
|
|
4001
|
-
return trimmed.slice(1, -1).split(",").map(unquote).filter((item) => item !== "");
|
|
4002
|
-
};
|
|
4003
|
-
var keyValue = (body2) => {
|
|
4004
|
-
const match = /^("[^"]*"|'[^']*'|[^:]+?)\s*:(?:\s+(.*))?$/.exec(body2);
|
|
4005
|
-
if (match === null) return void 0;
|
|
4006
|
-
return { key: unquote(match[1] ?? ""), value: match[2] ?? "" };
|
|
4007
|
-
};
|
|
4008
|
-
var addItem = (frame, item) => {
|
|
4009
|
-
if (frame.key === void 0) return;
|
|
4010
|
-
const parent = frame.tree[frame.key];
|
|
4011
|
-
if (Array.isArray(parent)) parent.push(item);
|
|
4012
|
-
else frame.tree[frame.key] = [item];
|
|
4013
|
-
};
|
|
4014
|
-
var placeLine = (stack, indent, body2) => {
|
|
4015
|
-
while (stack.length > 1 && (stack[stack.length - 1]?.indent ?? 0) >= indent && !body2.startsWith("- ")) stack.pop();
|
|
4016
|
-
const top = stack[stack.length - 1];
|
|
4017
|
-
if (top === void 0) return;
|
|
4018
|
-
if (body2.startsWith("- ")) {
|
|
4019
|
-
addItem(top, unquote(body2.slice(2)));
|
|
4020
|
-
return;
|
|
4021
|
-
}
|
|
4022
|
-
const pair = keyValue(body2);
|
|
4023
|
-
if (pair === void 0) return;
|
|
4024
|
-
const parentTree = top.key === void 0 ? top.tree : ensureTree(top.tree, top.key);
|
|
4025
|
-
if (pair.value === "") {
|
|
4026
|
-
stack.push({ indent, tree: parentTree, key: pair.key });
|
|
4027
|
-
return;
|
|
4028
|
-
}
|
|
4029
|
-
parentTree[pair.key] = flowList(pair.value) ?? unquote(pair.value);
|
|
4030
|
-
};
|
|
4031
|
-
var ensureTree = (tree, key) => {
|
|
4032
|
-
const existing = tree[key];
|
|
4033
|
-
if (existing !== void 0 && typeof existing !== "string" && !Array.isArray(existing)) return existing;
|
|
4034
|
-
const fresh3 = {};
|
|
4035
|
-
tree[key] = fresh3;
|
|
4036
|
-
return fresh3;
|
|
4037
|
-
};
|
|
4038
|
-
var parseConfigYaml = (text6) => {
|
|
4039
|
-
const root = {};
|
|
4040
|
-
const stack = [{ indent: -1, tree: root, key: void 0 }];
|
|
4041
|
-
for (const raw of text6.split(/\r?\n/)) {
|
|
4042
|
-
const line2 = stripComment(raw).trimEnd();
|
|
4043
|
-
if (line2.trim() === "" || line2.trim() === "---") continue;
|
|
4044
|
-
const indent = line2.length - line2.trimStart().length;
|
|
4045
|
-
placeLine(stack, indent, line2.trim());
|
|
4046
|
-
}
|
|
4047
|
-
return root;
|
|
4048
|
-
};
|
|
4049
|
-
var isTree = (node) => typeof node === "object" && !Array.isArray(node);
|
|
4050
|
-
var configAt = (tree, ...path) => path.reduce((node, key) => isTree(node) ? node[key] : void 0, tree);
|
|
4051
|
-
var configList = (tree, ...path) => {
|
|
4052
|
-
const node = configAt(tree, ...path);
|
|
4053
|
-
if (typeof node === "string") return node === "" ? [] : [node];
|
|
4054
|
-
return Array.isArray(node) ? node.filter((item) => typeof item === "string") : [];
|
|
4055
|
-
};
|
|
4056
|
-
var configMap = (tree, ...path) => {
|
|
4057
|
-
const node = configAt(tree, ...path);
|
|
4058
|
-
if (!isTree(node)) return {};
|
|
4059
|
-
return Object.fromEntries(
|
|
4060
|
-
Object.entries(node).filter((entry2) => typeof entry2[1] === "string")
|
|
4061
|
-
);
|
|
4062
|
-
};
|
|
4063
|
-
var loadProjectConfig = (runtime, root) => {
|
|
4064
|
-
const text6 = runtime.readText(toAbsolutePath(join12(root, CONFIG_FILE3)));
|
|
4065
|
-
return text6.ok ? parseConfigYaml(text6.value) : {};
|
|
4066
|
-
};
|
|
4137
|
+
import { join as join15 } from "node:path";
|
|
4067
4138
|
|
|
4068
4139
|
// src/core/yaml/scalar.ts
|
|
4069
4140
|
var ESCAPES = {
|
|
@@ -4343,8 +4414,8 @@ var TASK_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
|
4343
4414
|
var TASK_SCHEMA = "finitemonkey/task@1";
|
|
4344
4415
|
|
|
4345
4416
|
// src/task/validate.ts
|
|
4346
|
-
var str = (
|
|
4347
|
-
const value =
|
|
4417
|
+
var str = (record3, key, at, issues, required) => {
|
|
4418
|
+
const value = record3[key];
|
|
4348
4419
|
if (value === void 0 || value === null) {
|
|
4349
4420
|
if (required) issues.push(`${at}${key}: required`);
|
|
4350
4421
|
return void 0;
|
|
@@ -4355,8 +4426,8 @@ var str = (record2, key, at, issues, required) => {
|
|
|
4355
4426
|
}
|
|
4356
4427
|
return value;
|
|
4357
4428
|
};
|
|
4358
|
-
var positive = (
|
|
4359
|
-
const value =
|
|
4429
|
+
var positive = (record3, key, at, issues) => {
|
|
4430
|
+
const value = record3[key];
|
|
4360
4431
|
if (value === void 0 || value === null) return void 0;
|
|
4361
4432
|
if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
|
|
4362
4433
|
issues.push(`${at}${key}: must be a positive integer`);
|
|
@@ -4375,12 +4446,12 @@ var gitRequire = (value, at, issues) => {
|
|
|
4375
4446
|
if (known2.some((item) => item === void 0)) issues.push(`${at}: use ${GIT_REQUIREMENTS.join(", ")}`);
|
|
4376
4447
|
return known2.filter((item) => item !== void 0);
|
|
4377
4448
|
};
|
|
4378
|
-
var checkFields = (
|
|
4379
|
-
const run4 = str(
|
|
4380
|
-
const timeoutS = positive(
|
|
4381
|
-
const description = str(
|
|
4382
|
-
const require2 = kind === "git" ? gitRequire(
|
|
4383
|
-
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"];
|
|
4384
4455
|
if (open !== void 0 && typeof open !== "boolean") issues.push(`${at}require_open: must be true or false`);
|
|
4385
4456
|
return {
|
|
4386
4457
|
...run4 === void 0 ? {} : { run: run4 },
|
|
@@ -4451,6 +4522,7 @@ var validateContract = (value, implicit = false) => {
|
|
|
4451
4522
|
const contractScope = scope(value["scope"], issues);
|
|
4452
4523
|
const checks = acceptance(value["acceptance"], issues);
|
|
4453
4524
|
const decisionsNeeded = strings2(value["decisions_needed"], "decisions_needed", issues);
|
|
4525
|
+
const callersIgnore = strings2(value["callers_ignore"], "callers_ignore", issues);
|
|
4454
4526
|
if (issues.length > 0 || id === void 0 || goal === void 0 || size === void 0) return reject(issues);
|
|
4455
4527
|
return accept({
|
|
4456
4528
|
id,
|
|
@@ -4460,6 +4532,7 @@ var validateContract = (value, implicit = false) => {
|
|
|
4460
4532
|
scope: contractScope,
|
|
4461
4533
|
acceptance: checks,
|
|
4462
4534
|
decisionsNeeded,
|
|
4535
|
+
...callersIgnore.length === 0 ? {} : { callersIgnore },
|
|
4463
4536
|
implicit
|
|
4464
4537
|
});
|
|
4465
4538
|
};
|
|
@@ -4476,7 +4549,7 @@ var parseContract = (id, text6) => {
|
|
|
4476
4549
|
if (valid.value.id !== id) return reject({ kind: "invalid", id, issues: [`id: must match the file name (${id})`] });
|
|
4477
4550
|
return accept(valid.value);
|
|
4478
4551
|
};
|
|
4479
|
-
var
|
|
4552
|
+
var pick2 = (lock, sources, id) => {
|
|
4480
4553
|
const approved = lock.approved[id];
|
|
4481
4554
|
if (approved !== void 0) return { text: approved, source: "lock" };
|
|
4482
4555
|
const base = sources.base(taskRelative(id));
|
|
@@ -4487,7 +4560,7 @@ var pick = (lock, sources, id) => {
|
|
|
4487
4560
|
var loadTask = (files, paths, sources, id) => {
|
|
4488
4561
|
if (!TASK_ID.test(id)) return reject({ kind: "not_found", id });
|
|
4489
4562
|
const lock = readTrust(files, paths);
|
|
4490
|
-
const chosen =
|
|
4563
|
+
const chosen = pick2(lock, sources, id);
|
|
4491
4564
|
if (chosen === void 0) return reject({ kind: "not_found", id });
|
|
4492
4565
|
const parsed = parseContract(id, chosen.text);
|
|
4493
4566
|
if (!parsed.ok) return parsed;
|
|
@@ -4542,7 +4615,7 @@ var readBaseline = (runtime, paths) => {
|
|
|
4542
4615
|
var writeBaseline = (runtime, paths, treeHash2, checks) => writeJsonFile(runtime, paths.cache(BASELINE_FILE), { treeHash: treeHash2, checks: { ...checks } }).ok;
|
|
4543
4616
|
|
|
4544
4617
|
// src/gate/diff-shape.ts
|
|
4545
|
-
import { join as
|
|
4618
|
+
import { join as join14 } from "node:path";
|
|
4546
4619
|
|
|
4547
4620
|
// src/gate/git.ts
|
|
4548
4621
|
var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
@@ -4649,7 +4722,7 @@ var aheadOfUpstream = (git) => {
|
|
|
4649
4722
|
|
|
4650
4723
|
// src/gate/diff-shape.ts
|
|
4651
4724
|
var untrackedLines = (runtime, root, changes) => changes.filter((change) => change.status === "A").reduce((sum, change) => {
|
|
4652
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
4725
|
+
const text6 = runtime.readText(toAbsolutePath(join14(root, change.path)));
|
|
4653
4726
|
return sum + (text6.ok ? text6.value.split("\n").length : 0);
|
|
4654
4727
|
}, 0);
|
|
4655
4728
|
var diffShape = (runtime, git, base, changes) => ({
|
|
@@ -4676,18 +4749,18 @@ var ciChecks = (value) => Array.isArray(value) ? value.flatMap((item) => {
|
|
|
4676
4749
|
var parsePrView = (text6) => {
|
|
4677
4750
|
const parsed = parseJsonObject(text6);
|
|
4678
4751
|
if (!parsed.ok) return void 0;
|
|
4679
|
-
const
|
|
4680
|
-
const number =
|
|
4752
|
+
const record3 = parsed.value;
|
|
4753
|
+
const number = record3["number"];
|
|
4681
4754
|
if (typeof number !== "number") return void 0;
|
|
4682
4755
|
return {
|
|
4683
4756
|
number,
|
|
4684
|
-
url: optionalString(
|
|
4685
|
-
state: optionalString(
|
|
4686
|
-
mergeable: optionalString(
|
|
4687
|
-
mergeStateStatus: optionalString(
|
|
4688
|
-
baseRefName: optionalString(
|
|
4689
|
-
headRefOid: optionalString(
|
|
4690
|
-
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"])
|
|
4691
4764
|
};
|
|
4692
4765
|
};
|
|
4693
4766
|
var isConflicting = (pr) => pr.mergeable === "CONFLICTING" || pr.mergeStateStatus === "DIRTY";
|
|
@@ -4855,10 +4928,10 @@ var applySize = (contract, verdict) => {
|
|
|
4855
4928
|
// src/gate/prepare.ts
|
|
4856
4929
|
var diskFiles = (runtime, root) => ({
|
|
4857
4930
|
read: (relative9) => {
|
|
4858
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
4931
|
+
const text6 = runtime.readText(toAbsolutePath(join15(root, relative9)));
|
|
4859
4932
|
return text6.ok ? text6.value : void 0;
|
|
4860
4933
|
},
|
|
4861
|
-
exists: (relative9) => runtime.stat(toAbsolutePath(
|
|
4934
|
+
exists: (relative9) => runtime.stat(toAbsolutePath(join15(root, relative9))) !== void 0
|
|
4862
4935
|
});
|
|
4863
4936
|
var baseFiles = (git, base, disk) => ({
|
|
4864
4937
|
read: (relative9) => base === EMPTY_TREE ? disk.read(relative9) : fileAt(git, base, relative9),
|
|
@@ -5120,19 +5193,19 @@ var params = (value) => isRecord(value) ? Object.fromEntries(
|
|
|
5120
5193
|
var notes = (value) => Array.isArray(value) ? value.flatMap(
|
|
5121
5194
|
(item) => isRecord(item) && typeof item["key"] === "string" ? [{ key: item["key"], params: params(item["params"]) }] : []
|
|
5122
5195
|
) : [];
|
|
5123
|
-
var text3 = (
|
|
5124
|
-
const value =
|
|
5196
|
+
var text3 = (record3, key) => {
|
|
5197
|
+
const value = record3[key];
|
|
5125
5198
|
return typeof value === "string" ? value : void 0;
|
|
5126
5199
|
};
|
|
5127
|
-
var optional = (
|
|
5128
|
-
const exitCode =
|
|
5129
|
-
const durationMs =
|
|
5130
|
-
const command = text3(
|
|
5131
|
-
const stdoutSha256 = text3(
|
|
5132
|
-
const releasedBy = text3(
|
|
5133
|
-
const reason = oneOf2(UNVERIFIED_REASONS,
|
|
5134
|
-
const failures =
|
|
5135
|
-
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"];
|
|
5136
5209
|
return {
|
|
5137
5210
|
...command === void 0 ? {} : { command },
|
|
5138
5211
|
...typeof exitCode === "number" || exitCode === null ? { exitCode } : {},
|
|
@@ -5147,14 +5220,14 @@ var optional = (record2) => {
|
|
|
5147
5220
|
var parseEvidence = (line2) => {
|
|
5148
5221
|
const parsed = parseJsonObject(line2);
|
|
5149
5222
|
if (!parsed.ok) return void 0;
|
|
5150
|
-
const
|
|
5151
|
-
const status = oneOf2(EVIDENCE_STATUSES,
|
|
5152
|
-
const task = text3(
|
|
5153
|
-
const check2 = text3(
|
|
5154
|
-
const kind = text3(
|
|
5155
|
-
const treeHash2 = text3(
|
|
5156
|
-
const at = text3(
|
|
5157
|
-
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;
|
|
5158
5231
|
if (task === void 0 || check2 === void 0 || kind === void 0 || treeHash2 === void 0 || at === void 0) {
|
|
5159
5232
|
return void 0;
|
|
5160
5233
|
}
|
|
@@ -5166,8 +5239,8 @@ var parseEvidence = (line2) => {
|
|
|
5166
5239
|
status,
|
|
5167
5240
|
treeHash: treeHash2,
|
|
5168
5241
|
at,
|
|
5169
|
-
notes: notes(
|
|
5170
|
-
...optional(
|
|
5242
|
+
notes: notes(record3["notes"]),
|
|
5243
|
+
...optional(record3)
|
|
5171
5244
|
};
|
|
5172
5245
|
};
|
|
5173
5246
|
var readLedger = (runtime, path) => {
|
|
@@ -5706,7 +5779,15 @@ var en_default = {
|
|
|
5706
5779
|
"gate.secrets.warning": "warning: {rule} at {file}:{line}",
|
|
5707
5780
|
"doctor.secrets.remote_credential": "git remote {remotes} has a credential in its URL, stored in plain text in .git/config",
|
|
5708
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",
|
|
5709
|
-
"secrets.session.remote_credential": "git remote {remotes} has a credential in its URL (plain text in .git/config). Ask the person to run `git remote set-url {remote} <url without user:token@>` and rotate the token; do not print the URL."
|
|
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."
|
|
5710
5791
|
};
|
|
5711
5792
|
|
|
5712
5793
|
// src/i18n/es.json
|
|
@@ -6230,7 +6311,15 @@ var es_default = {
|
|
|
6230
6311
|
"gate.secrets.warning": "aviso: {rule} en {file}:{line}",
|
|
6231
6312
|
"doctor.secrets.remote_credential": "el remoto git {remotes} tiene una credencial en la URL, guardada en texto plano en .git/config",
|
|
6232
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",
|
|
6233
|
-
"secrets.session.remote_credential": "el remoto git {remotes} tiene una credencial en la URL (texto plano en .git/config). Pide a la persona que ejecute `git remote set-url {remote} <url sin usuario:token@>` y rote el token; no imprimas la URL."
|
|
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."
|
|
6234
6323
|
};
|
|
6235
6324
|
|
|
6236
6325
|
// src/i18n/pt-BR.json
|
|
@@ -6754,7 +6843,15 @@ var pt_BR_default = {
|
|
|
6754
6843
|
"gate.secrets.warning": "aviso: {rule} em {file}:{line}",
|
|
6755
6844
|
"doctor.secrets.remote_credential": "o remoto git {remotes} tem uma credencial na URL, guardada em texto puro no .git/config",
|
|
6756
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",
|
|
6757
|
-
"secrets.session.remote_credential": "o remoto git {remotes} tem uma credencial na URL (texto puro no .git/config). Peça para a pessoa rodar `git remote set-url {remote} <url sem usuario:token@>` e rotacionar o token; não imprima a URL."
|
|
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."
|
|
6758
6855
|
};
|
|
6759
6856
|
|
|
6760
6857
|
// src/i18n/review.json
|
|
@@ -7279,7 +7376,15 @@ var review_default = {
|
|
|
7279
7376
|
"gate.secrets.warning": "machine",
|
|
7280
7377
|
"doctor.secrets.remote_credential": "machine",
|
|
7281
7378
|
"doctor.secrets.fix_remote_credential": "machine",
|
|
7282
|
-
"secrets.session.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"
|
|
7283
7388
|
},
|
|
7284
7389
|
es: {
|
|
7285
7390
|
"command.init.summary": "machine",
|
|
@@ -7801,7 +7906,15 @@ var review_default = {
|
|
|
7801
7906
|
"gate.secrets.warning": "machine",
|
|
7802
7907
|
"doctor.secrets.remote_credential": "machine",
|
|
7803
7908
|
"doctor.secrets.fix_remote_credential": "machine",
|
|
7804
|
-
"secrets.session.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"
|
|
7805
7918
|
}
|
|
7806
7919
|
};
|
|
7807
7920
|
|
|
@@ -8080,6 +8193,256 @@ var recordReview = (runtime, paths, treeHash2, agent) => {
|
|
|
8080
8193
|
return writeJsonFile(runtime, paths.cache(REVIEWS_FILE), { reviews: reviews.map((review2) => ({ ...review2 })) }).ok;
|
|
8081
8194
|
};
|
|
8082
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
|
+
|
|
8083
8446
|
// src/gate/test-summary.ts
|
|
8084
8447
|
var ANSI2 = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
8085
8448
|
var COUNTERS = [
|
|
@@ -8276,13 +8639,13 @@ var manualCheck = (check2, ctx) => {
|
|
|
8276
8639
|
};
|
|
8277
8640
|
|
|
8278
8641
|
// src/gate/checks/placeholders.ts
|
|
8279
|
-
import { join as
|
|
8642
|
+
import { join as join17 } from "node:path";
|
|
8280
8643
|
var MAX_LISTED2 = 10;
|
|
8281
8644
|
var SKIP_FILES = /\.(md|mdx|txt|rst|lock|snap|svg|png|jpg|gif|json)$|(^|\/)(CHANGELOG|LICENSE)/i;
|
|
8282
8645
|
var PLACEHOLDER = /\b(TODO|FIXME|XXX)\b|not implemented|NotImplementedError|unimplemented!|todo!\(|lorem ipsum/i;
|
|
8283
8646
|
var findPlaceholders = (lines3) => lines3.filter((line2) => !isTestFile(line2.path) && !SKIP_FILES.test(line2.path) && PLACEHOLDER.test(line2.text));
|
|
8284
8647
|
var untrackedAdded = (ctx) => ctx.changes.filter((change) => change.status === "A" && !SKIP_FILES.test(change.path)).flatMap((change) => {
|
|
8285
|
-
const text6 = ctx.runtime.readText(toAbsolutePath(
|
|
8648
|
+
const text6 = ctx.runtime.readText(toAbsolutePath(join17(ctx.paths.root, change.path)));
|
|
8286
8649
|
return text6.ok ? text6.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
|
|
8287
8650
|
});
|
|
8288
8651
|
var placeholdersCheck = (_check, ctx) => {
|
|
@@ -9014,15 +9377,15 @@ var reviewCheck = (check2, ctx) => {
|
|
|
9014
9377
|
};
|
|
9015
9378
|
|
|
9016
9379
|
// src/secrets/baseline.ts
|
|
9017
|
-
import { join as
|
|
9380
|
+
import { join as join18 } from "node:path";
|
|
9018
9381
|
var BASELINE_FILE2 = ".finitemonkey/findings/baseline.json";
|
|
9019
9382
|
var BASELINE_SCHEMA = "finitemonkey/secrets-baseline@1";
|
|
9020
9383
|
var BASELINE_STATES = ["aceito", "falso-positivo"];
|
|
9021
9384
|
var EXCEPTION_MAX_DAYS = 90;
|
|
9022
9385
|
var EMPTY_BASELINE = { schema: BASELINE_SCHEMA, entries: [] };
|
|
9023
|
-
var baselinePath = (root) => toAbsolutePath(
|
|
9024
|
-
var text5 = (
|
|
9025
|
-
const value =
|
|
9386
|
+
var baselinePath = (root) => toAbsolutePath(join18(root, BASELINE_FILE2));
|
|
9387
|
+
var text5 = (record3, key) => {
|
|
9388
|
+
const value = record3[key];
|
|
9026
9389
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
9027
9390
|
};
|
|
9028
9391
|
var stateOf = (value) => BASELINE_STATES.find((state) => state === value);
|
|
@@ -9080,22 +9443,22 @@ var withoutFixed = (baseline, fixed) => {
|
|
|
9080
9443
|
var acceptedFingerprints = (baseline, now) => new Set(baseline.entries.filter((entry2) => entry2.expiresAt >= now).map((entry2) => entry2.fingerprint));
|
|
9081
9444
|
|
|
9082
9445
|
// src/secrets/incidents.ts
|
|
9083
|
-
import { join as
|
|
9446
|
+
import { join as join19 } from "node:path";
|
|
9084
9447
|
var INCIDENTS_DIR = ".finitemonkey/incidents";
|
|
9085
9448
|
var INCIDENT_STATES = ["aberto", "exposicao-local", "rotacionado", "fechado"];
|
|
9086
9449
|
var INCIDENT_SOURCES = ["tool-output", "prompt", "history", "scan"];
|
|
9087
9450
|
var ID = /^inc-[0-9a-f]{12}$/;
|
|
9088
9451
|
var incidentId = (rule, fingerprint) => `inc-${sha256Hex(`${rule}\0${fingerprint}`).slice(0, 12)}`;
|
|
9089
|
-
var incidentsDir = (root) => toAbsolutePath(
|
|
9090
|
-
var incidentFile = (root, id) => toAbsolutePath(
|
|
9091
|
-
var
|
|
9092
|
-
var str2 = (
|
|
9093
|
-
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];
|
|
9094
9457
|
return typeof value === "string" ? value : void 0;
|
|
9095
9458
|
};
|
|
9096
9459
|
var parseEvent = (value) => {
|
|
9097
9460
|
if (!isRecord(value)) return void 0;
|
|
9098
|
-
const state =
|
|
9461
|
+
const state = pick3(INCIDENT_STATES, value["state"]);
|
|
9099
9462
|
const at = str2(value, "at");
|
|
9100
9463
|
const by = str2(value, "by");
|
|
9101
9464
|
return state === void 0 || at === void 0 || by === void 0 ? void 0 : { state, at, by };
|
|
@@ -9108,12 +9471,12 @@ var parseHistory = (value) => {
|
|
|
9108
9471
|
var parseIncident = (raw) => {
|
|
9109
9472
|
const parsed = parseJsonObject(raw);
|
|
9110
9473
|
if (!parsed.ok) return parsed;
|
|
9111
|
-
const
|
|
9112
|
-
const [id, rule, where, fingerprint, createdAt, updatedAt] = ["id", "rule", "where", "fingerprint", "createdAt", "updatedAt"].map((key) => str2(
|
|
9113
|
-
const state =
|
|
9114
|
-
const source =
|
|
9115
|
-
const history = parseHistory(
|
|
9116
|
-
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");
|
|
9117
9480
|
if (id === void 0 || !ID.test(id) || rule === void 0 || where === void 0) return reject("incomplete");
|
|
9118
9481
|
if (fingerprint === void 0 || createdAt === void 0 || updatedAt === void 0) return reject("incomplete");
|
|
9119
9482
|
if (state === void 0 || source === void 0 || history === void 0) return reject("incomplete");
|
|
@@ -9142,7 +9505,7 @@ var listIncidents = (runtime, root) => {
|
|
|
9142
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));
|
|
9143
9506
|
};
|
|
9144
9507
|
var saveIncident = (runtime, root, incident) => {
|
|
9145
|
-
const ignore = runtime.writeText(toAbsolutePath(
|
|
9508
|
+
const ignore = runtime.writeText(toAbsolutePath(join19(incidentsDir(root), ".gitignore")), "*\n");
|
|
9146
9509
|
if (!ignore.ok) return ignore;
|
|
9147
9510
|
return runtime.writeText(incidentFile(root, incident.id), `${JSON.stringify(incident, null, 2)}
|
|
9148
9511
|
`);
|
|
@@ -9189,7 +9552,7 @@ var moveIncident = (incident, to, now, by) => ({
|
|
|
9189
9552
|
var isRotated = (incident) => incident.state === "rotacionado" || incident.state === "fechado";
|
|
9190
9553
|
|
|
9191
9554
|
// src/secrets/scan.ts
|
|
9192
|
-
import { join as
|
|
9555
|
+
import { join as join20, relative as relative4 } from "node:path";
|
|
9193
9556
|
|
|
9194
9557
|
// src/secrets/located.ts
|
|
9195
9558
|
var findingId = (ruleId, file, fingerprint) => `fm-${sha256Hex(`${ruleId}\0${file}\0${fingerprint}`).slice(0, 16)}`;
|
|
@@ -9289,7 +9652,7 @@ var listed2 = (runtime, root, target) => {
|
|
|
9289
9652
|
const base = toAbsolutePath(target, root);
|
|
9290
9653
|
const walked = runtime.walkFiles(base);
|
|
9291
9654
|
if (!walked.ok) return walked;
|
|
9292
|
-
return accept(walked.value.map((name) => toPosixPath(relative4(root,
|
|
9655
|
+
return accept(walked.value.map((name) => toPosixPath(relative4(root, join20(base, toPosixPath(name))))));
|
|
9293
9656
|
};
|
|
9294
9657
|
var scanFile = (runtime, root, file) => {
|
|
9295
9658
|
const text6 = runtime.readText(toAbsolutePath(file, root));
|
|
@@ -9359,7 +9722,7 @@ var secretsCheck = (_check, ctx) => {
|
|
|
9359
9722
|
};
|
|
9360
9723
|
|
|
9361
9724
|
// src/gate/checks/tests.ts
|
|
9362
|
-
import { join as
|
|
9725
|
+
import { join as join21 } from "node:path";
|
|
9363
9726
|
|
|
9364
9727
|
// src/guards/tests/tree.ts
|
|
9365
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 }] : [];
|
|
@@ -9391,7 +9754,7 @@ var treeViolations = (ctx) => {
|
|
|
9391
9754
|
changes: relevant,
|
|
9392
9755
|
before: (path) => fileAt(ctx.git, ctx.base, path),
|
|
9393
9756
|
after: (path) => {
|
|
9394
|
-
const text6 = ctx.runtime.readText(toAbsolutePath(
|
|
9757
|
+
const text6 = ctx.runtime.readText(toAbsolutePath(join21(ctx.paths.root, path)));
|
|
9395
9758
|
return text6.ok ? text6.value : void 0;
|
|
9396
9759
|
},
|
|
9397
9760
|
acceptedConfigs: ctx.trust.configs,
|
|
@@ -9412,7 +9775,7 @@ var testsCheck = (check2, ctx) => {
|
|
|
9412
9775
|
};
|
|
9413
9776
|
|
|
9414
9777
|
// src/git/tree.ts
|
|
9415
|
-
import { join as
|
|
9778
|
+
import { join as join22 } from "node:path";
|
|
9416
9779
|
|
|
9417
9780
|
// src/secrets/guards/paths.ts
|
|
9418
9781
|
var SAFE_ENV = /^\.env\.(?:example|sample|template|dist|defaults|schema)$/i;
|
|
@@ -9451,7 +9814,7 @@ var dropSensitive = (runtime, repo, env, options) => {
|
|
|
9451
9814
|
return removed.ok ? accept(void 0) : removed;
|
|
9452
9815
|
};
|
|
9453
9816
|
var withTempIndex = (runtime, repo, options, work) => {
|
|
9454
|
-
const path = toAbsolutePath(
|
|
9817
|
+
const path = toAbsolutePath(join22(repo.gitDir, `finitemonkey-${options.stamp}.index`));
|
|
9455
9818
|
const env = { GIT_INDEX_FILE: path, GIT_OPTIONAL_LOCKS: "0" };
|
|
9456
9819
|
try {
|
|
9457
9820
|
return work(env);
|
|
@@ -9684,26 +10047,26 @@ var entryOf2 = (kind, xy, path) => ({
|
|
|
9684
10047
|
staged: kind !== "untracked" && !xy.startsWith("."),
|
|
9685
10048
|
unstaged: kind === "untracked" || xy.charAt(1) !== "."
|
|
9686
10049
|
});
|
|
9687
|
-
var afterFields = (
|
|
10050
|
+
var afterFields = (record3, count4) => record3.split(" ").slice(count4).join(" ");
|
|
9688
10051
|
var parsePorcelainV2 = (stdout) => {
|
|
9689
10052
|
const records = stdout.split("\0");
|
|
9690
10053
|
const out = [];
|
|
9691
10054
|
for (let index = 0; index < records.length; index += 1) {
|
|
9692
|
-
const
|
|
9693
|
-
const xy =
|
|
9694
|
-
if (
|
|
9695
|
-
else if (
|
|
9696
|
-
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)));
|
|
9697
10060
|
index += 1;
|
|
9698
|
-
} else if (
|
|
9699
|
-
out.push(entryOf2("unmerged", xy, afterFields(
|
|
9700
|
-
} 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)));
|
|
9701
10064
|
}
|
|
9702
10065
|
return out.filter((entry2) => entry2.path !== "" && !entry2.path.startsWith(STATE_PREFIX));
|
|
9703
10066
|
};
|
|
9704
10067
|
|
|
9705
10068
|
// src/git/merge/dropped.ts
|
|
9706
|
-
import { join as
|
|
10069
|
+
import { join as join23 } from "node:path";
|
|
9707
10070
|
|
|
9708
10071
|
// src/git/merge/lines.ts
|
|
9709
10072
|
var normalizeLine = (line2) => line2.replace(/\r$/, "").trim().replace(/\s*,$/, "");
|
|
@@ -9783,7 +10146,7 @@ var parseDropped = (text6) => {
|
|
|
9783
10146
|
return accept({ items, invalid });
|
|
9784
10147
|
};
|
|
9785
10148
|
var loadDropped = (runtime, root, file) => {
|
|
9786
|
-
const path = toAbsolutePath(file ??
|
|
10149
|
+
const path = toAbsolutePath(file ?? join23(root, DROPPED_FILE), root);
|
|
9787
10150
|
const text6 = runtime.readText(path);
|
|
9788
10151
|
if (!text6.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text6.error}`);
|
|
9789
10152
|
return parseDropped(text6.value);
|
|
@@ -9793,7 +10156,7 @@ var isDropped = (finding3, items) => items.some(
|
|
|
9793
10156
|
);
|
|
9794
10157
|
|
|
9795
10158
|
// src/git/merge/generated.ts
|
|
9796
|
-
import { join as
|
|
10159
|
+
import { join as join24 } from "node:path";
|
|
9797
10160
|
var LOCKFILES3 = /* @__PURE__ */ new Set([
|
|
9798
10161
|
"package-lock.json",
|
|
9799
10162
|
"npm-shrinkwrap.json",
|
|
@@ -9856,7 +10219,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
|
|
|
9856
10219
|
if (declared.length === 0) return accept([]);
|
|
9857
10220
|
const commit2 = resultCommit(runtime, repo, inputs);
|
|
9858
10221
|
if (!commit2.ok) return commit2;
|
|
9859
|
-
const dir =
|
|
10222
|
+
const dir = join24(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
|
|
9860
10223
|
if (!runtime.git(["worktree", "add", "--detach", dir, commit2.value], repo.root).ok)
|
|
9861
10224
|
return reject("could not check out the merge result");
|
|
9862
10225
|
try {
|
|
@@ -9866,7 +10229,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
|
|
|
9866
10229
|
}
|
|
9867
10230
|
return accept(
|
|
9868
10231
|
declared.flatMap(({ path }) => {
|
|
9869
|
-
const fresh3 = runtime.readText(toAbsolutePath(
|
|
10232
|
+
const fresh3 = runtime.readText(toAbsolutePath(join24(dir, path)));
|
|
9870
10233
|
const merged = blobText(runtime, repo, commit2.value, path);
|
|
9871
10234
|
if (fresh3.ok && fresh3.value === merged) return [];
|
|
9872
10235
|
return [{ kind: "generated-differs", file: path, side: void 0, line: void 0, text: path, weak: false }];
|
|
@@ -10080,16 +10443,16 @@ var failed2 = (error, inputs) => ({
|
|
|
10080
10443
|
generated: [],
|
|
10081
10444
|
error
|
|
10082
10445
|
});
|
|
10083
|
-
var
|
|
10446
|
+
var keyOf2 = (diff) => (diff.status === "added" || diff.status === "copied" ? diff.newPath : diff.oldPath ?? diff.newPath) ?? "";
|
|
10084
10447
|
var pairsOf = (ours, theirs) => {
|
|
10085
|
-
const keys = [...new Set([...ours, ...theirs].map(
|
|
10448
|
+
const keys = [...new Set([...ours, ...theirs].map(keyOf2))].filter((key) => key !== "").sort();
|
|
10086
10449
|
return keys.map((key) => ({
|
|
10087
10450
|
key,
|
|
10088
|
-
ours: ours.find((diff) =>
|
|
10089
|
-
theirs: theirs.find((diff) =>
|
|
10451
|
+
ours: ours.find((diff) => keyOf2(diff) === key),
|
|
10452
|
+
theirs: theirs.find((diff) => keyOf2(diff) === key)
|
|
10090
10453
|
}));
|
|
10091
10454
|
};
|
|
10092
|
-
var contextOf2 = (runtime, repo, inputs,
|
|
10455
|
+
var contextOf2 = (runtime, repo, inputs, pairs2) => {
|
|
10093
10456
|
const resultText = memoBy((path) => blobText(runtime, repo, inputs.result, path));
|
|
10094
10457
|
const context = {
|
|
10095
10458
|
runtime,
|
|
@@ -10097,7 +10460,7 @@ var contextOf2 = (runtime, repo, inputs, pairs) => {
|
|
|
10097
10460
|
inputs,
|
|
10098
10461
|
resultText,
|
|
10099
10462
|
resultFiles: memo2(
|
|
10100
|
-
() =>
|
|
10463
|
+
() => pairs2.flatMap((pair) => {
|
|
10101
10464
|
const path = resultPathOf(context, pair);
|
|
10102
10465
|
return path === void 0 ? [] : [path];
|
|
10103
10466
|
})
|
|
@@ -10114,24 +10477,24 @@ var verifyMerge = (runtime, repo, options = {}) => {
|
|
|
10114
10477
|
const theirs = sideDiff(runtime, repo, inputs.value.base, inputs.value.theirs);
|
|
10115
10478
|
if (!ours.ok) return failed2(ours.error, inputs.value);
|
|
10116
10479
|
if (!theirs.ok) return failed2(theirs.error, inputs.value);
|
|
10117
|
-
const
|
|
10480
|
+
const pairs2 = pairsOf(ours.value, theirs.value);
|
|
10118
10481
|
const generators = configMap(loadProjectConfig(runtime, repo.root), "merge", "generators");
|
|
10119
10482
|
const generated = generatedPaths(
|
|
10120
10483
|
runtime,
|
|
10121
10484
|
repo,
|
|
10122
|
-
|
|
10485
|
+
pairs2.map((pair) => pair.key),
|
|
10123
10486
|
generators
|
|
10124
10487
|
);
|
|
10125
10488
|
const context = contextOf2(
|
|
10126
10489
|
runtime,
|
|
10127
10490
|
repo,
|
|
10128
10491
|
inputs.value,
|
|
10129
|
-
|
|
10492
|
+
pairs2.filter((pair) => !generated.has(pair.key))
|
|
10130
10493
|
);
|
|
10131
10494
|
const regenerated = verifyGenerated(runtime, repo, inputs.value, [...generated], generators);
|
|
10132
10495
|
if (!regenerated.ok) return failed2(regenerated.error, inputs.value);
|
|
10133
10496
|
const all = [
|
|
10134
|
-
...
|
|
10497
|
+
...pairs2.filter((pair) => !generated.has(pair.key)).flatMap((pair) => verifyFile(context, pair)),
|
|
10135
10498
|
...regenerated.value
|
|
10136
10499
|
];
|
|
10137
10500
|
const open = [...dropped.value.invalid, ...all.filter((item) => !isDropped(item, dropped.value.items))];
|
|
@@ -10143,7 +10506,7 @@ var verifyMerge = (runtime, repo, options = {}) => {
|
|
|
10143
10506
|
theirs: inputs.value.theirs,
|
|
10144
10507
|
result: inputs.value.result,
|
|
10145
10508
|
bases: inputs.value.bases,
|
|
10146
|
-
files:
|
|
10509
|
+
files: pairs2.length,
|
|
10147
10510
|
findings: open,
|
|
10148
10511
|
justified: all.length - (open.length - dropped.value.invalid.length),
|
|
10149
10512
|
generated: [...generated].sort(),
|
|
@@ -10152,19 +10515,19 @@ var verifyMerge = (runtime, repo, options = {}) => {
|
|
|
10152
10515
|
};
|
|
10153
10516
|
|
|
10154
10517
|
// src/git/ownership.ts
|
|
10155
|
-
import { join as
|
|
10518
|
+
import { join as join25 } from "node:path";
|
|
10156
10519
|
var OWNERSHIP_DIR = ".finitemonkey/git";
|
|
10157
10520
|
var ownerKey = (raw) => {
|
|
10158
10521
|
const cleaned = (raw ?? "").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80);
|
|
10159
10522
|
return cleaned === "" ? "unknown" : cleaned;
|
|
10160
10523
|
};
|
|
10161
|
-
var fileOf = (repo, kind, owner) => toAbsolutePath(
|
|
10524
|
+
var fileOf = (repo, kind, owner) => toAbsolutePath(join25(repo.root, OWNERSHIP_DIR, kind, `${ownerKey(owner)}.json`));
|
|
10162
10525
|
var readDirty = (runtime, root) => {
|
|
10163
10526
|
const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
|
|
10164
10527
|
return status.ok ? parsePorcelainV2(status.stdout) : void 0;
|
|
10165
10528
|
};
|
|
10166
10529
|
var hashDirty = (runtime, repo, entries) => {
|
|
10167
|
-
const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(
|
|
10530
|
+
const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(join25(repo.root, entry2.path)))?.kind === "file");
|
|
10168
10531
|
const hashed = present2.length === 0 ? void 0 : runtime.gitWith(["hash-object", "--stdin-paths"], repo.root, {
|
|
10169
10532
|
input: `${present2.map((entry2) => entry2.path).join("\n")}
|
|
10170
10533
|
`
|
|
@@ -10417,7 +10780,8 @@ var DEFAULT_CHECKS = {
|
|
|
10417
10780
|
prose: proseCheck,
|
|
10418
10781
|
worktree: worktreeCheck,
|
|
10419
10782
|
merge: mergeCheck,
|
|
10420
|
-
review: reviewCheck
|
|
10783
|
+
review: reviewCheck,
|
|
10784
|
+
callers: callersCheck
|
|
10421
10785
|
};
|
|
10422
10786
|
|
|
10423
10787
|
// src/gate/run.ts
|
|
@@ -10611,7 +10975,7 @@ var subagentStopGate = (input) => {
|
|
|
10611
10975
|
};
|
|
10612
10976
|
|
|
10613
10977
|
// src/gate/hooks/subagent.ts
|
|
10614
|
-
import { join as
|
|
10978
|
+
import { join as join26 } from "node:path";
|
|
10615
10979
|
var NO_CONTRACT_AGENTS = /(^|:)(reviewer|verifier|explore|plan|claude-code-guide|statusline-setup)$/i;
|
|
10616
10980
|
var KNOWN_KINDS = /* @__PURE__ */ new Set([
|
|
10617
10981
|
"command",
|
|
@@ -10625,7 +10989,8 @@ var KNOWN_KINDS = /* @__PURE__ */ new Set([
|
|
|
10625
10989
|
"prose",
|
|
10626
10990
|
"worktree",
|
|
10627
10991
|
"merge",
|
|
10628
|
-
"review"
|
|
10992
|
+
"review",
|
|
10993
|
+
"callers"
|
|
10629
10994
|
]);
|
|
10630
10995
|
var requirementText = (t, check2) => {
|
|
10631
10996
|
const key = `gate.require.${check2.kind}`;
|
|
@@ -10646,12 +11011,12 @@ var subagentStart = (input) => {
|
|
|
10646
11011
|
const active = readState(runtime, paths).active;
|
|
10647
11012
|
if (active === void 0) return { kind: "allow" };
|
|
10648
11013
|
const disk = (relative9) => {
|
|
10649
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
11014
|
+
const text6 = runtime.readText(toAbsolutePath(join26(paths.root, relative9)));
|
|
10650
11015
|
return text6.ok ? text6.value : void 0;
|
|
10651
11016
|
};
|
|
10652
11017
|
const loaded = loadTask(runtime, paths, { disk, base: () => void 0 }, active);
|
|
10653
11018
|
if (!loaded.ok) return { kind: "allow" };
|
|
10654
|
-
const briefing = runtime.stat(toAbsolutePath(
|
|
11019
|
+
const briefing = runtime.stat(toAbsolutePath(join26(paths.dir, "briefing.md"))) !== void 0;
|
|
10655
11020
|
return { kind: "allow", output: contextOutput("SubagentStart", contractFacts(t, loaded.value.contract, briefing)) };
|
|
10656
11021
|
};
|
|
10657
11022
|
|
|
@@ -10668,7 +11033,7 @@ var PORTAO_HANDLERS = {
|
|
|
10668
11033
|
};
|
|
10669
11034
|
|
|
10670
11035
|
// src/prod/guard.ts
|
|
10671
|
-
import { join as
|
|
11036
|
+
import { join as join28 } from "node:path";
|
|
10672
11037
|
|
|
10673
11038
|
// src/git/command-text.ts
|
|
10674
11039
|
var MAX_SHOWN = 160;
|
|
@@ -11010,7 +11375,7 @@ var remoteHostsOf = (command) => {
|
|
|
11010
11375
|
};
|
|
11011
11376
|
|
|
11012
11377
|
// src/prod/targets.ts
|
|
11013
|
-
import { join as
|
|
11378
|
+
import { join as join27 } from "node:path";
|
|
11014
11379
|
var DEPLOY_FILES = [
|
|
11015
11380
|
"deploy.sh",
|
|
11016
11381
|
"scripts/deploy.sh",
|
|
@@ -11034,12 +11399,12 @@ var hostsInText = (text6) => {
|
|
|
11034
11399
|
return [...urls, ...keyed, ...commands].filter((host) => host !== "" && !host.includes("$"));
|
|
11035
11400
|
};
|
|
11036
11401
|
var workflowFiles = (runtime, root) => {
|
|
11037
|
-
const listed4 = runtime.listDir(toAbsolutePath(
|
|
11402
|
+
const listed4 = runtime.listDir(toAbsolutePath(join27(root, ".github", "workflows")));
|
|
11038
11403
|
if (!listed4.ok) return [];
|
|
11039
11404
|
return listed4.value.filter((name) => /deploy/i.test(name) && /\.ya?ml$/i.test(name)).map((name) => `.github/workflows/${name}`);
|
|
11040
11405
|
};
|
|
11041
11406
|
var detectedHosts = (runtime, root, files) => files.flatMap((file) => {
|
|
11042
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
11407
|
+
const text6 = runtime.readText(toAbsolutePath(join27(root, file)));
|
|
11043
11408
|
return text6.ok ? hostsInText(text6.value) : [];
|
|
11044
11409
|
});
|
|
11045
11410
|
var loadProdTargets = (runtime, root, config) => {
|
|
@@ -11051,7 +11416,7 @@ var loadProdTargets = (runtime, root, config) => {
|
|
|
11051
11416
|
...detectedHosts(runtime, root, deployFiles)
|
|
11052
11417
|
];
|
|
11053
11418
|
const declaredDeploy = configAt(config, "prod", "deploy");
|
|
11054
|
-
const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(
|
|
11419
|
+
const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(join27(root, file))) !== void 0);
|
|
11055
11420
|
return {
|
|
11056
11421
|
hosts: new Set(hosts.map(hostKey).filter((host) => host !== "")),
|
|
11057
11422
|
kubeContexts: configList(config, "prod", "kube_contexts"),
|
|
@@ -11228,8 +11593,8 @@ var recordEvidence = (runtime, root, owner, reads) => {
|
|
|
11228
11593
|
] : []
|
|
11229
11594
|
);
|
|
11230
11595
|
if (lines3.length === 0) return;
|
|
11231
|
-
const path = toAbsolutePath(
|
|
11232
|
-
runtime.mkdir(toAbsolutePath(
|
|
11596
|
+
const path = toAbsolutePath(join28(root, EVIDENCE_FILE));
|
|
11597
|
+
runtime.mkdir(toAbsolutePath(join28(root, ".finitemonkey")));
|
|
11233
11598
|
const before = runtime.readText(path);
|
|
11234
11599
|
const text6 = before.ok ? before.value : "";
|
|
11235
11600
|
runtime.writeAtomic(path, `${text6}${text6 === "" || text6.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
|
|
@@ -11331,7 +11696,7 @@ var withAlias = (invocation, expansion) => ({
|
|
|
11331
11696
|
});
|
|
11332
11697
|
|
|
11333
11698
|
// src/git/path-key.ts
|
|
11334
|
-
import { posix as
|
|
11699
|
+
import { posix as posix3 } from "node:path";
|
|
11335
11700
|
var WSL_MOUNT3 = /^\/mnt\/([a-zA-Z])(\/|$)/;
|
|
11336
11701
|
var DRIVE = /^([a-zA-Z]):(\/|$)/;
|
|
11337
11702
|
var pathKey = (path) => {
|
|
@@ -11340,7 +11705,7 @@ var pathKey = (path) => {
|
|
|
11340
11705
|
(_match, letter, slash) => `${letter}:${slash || "/"}`
|
|
11341
11706
|
);
|
|
11342
11707
|
const drive = DRIVE.exec(posixPath);
|
|
11343
|
-
const normalized =
|
|
11708
|
+
const normalized = posix3.normalize(drive === null ? posixPath : posixPath.slice(2) || "/");
|
|
11344
11709
|
const trimmed = normalized.length > 1 ? normalized.replace(/\/+$/, "") : normalized;
|
|
11345
11710
|
return drive === null ? trimmed : `${(drive[1] ?? "").toLowerCase()}:${trimmed}`.toLowerCase();
|
|
11346
11711
|
};
|
|
@@ -11356,15 +11721,15 @@ var isRootLike = (path, home) => {
|
|
|
11356
11721
|
if (key === "/" || /^[a-z]:\/?$/.test(key)) return true;
|
|
11357
11722
|
return home !== void 0 && home !== "" && key === pathKey(home);
|
|
11358
11723
|
};
|
|
11359
|
-
var resolveFrom = (base, ...parts) => parts.reduce((
|
|
11360
|
-
if (part2 === void 0 || part2 === "") return
|
|
11724
|
+
var resolveFrom = (base, ...parts) => parts.reduce((current2, part2) => {
|
|
11725
|
+
if (part2 === void 0 || part2 === "") return current2;
|
|
11361
11726
|
const next2 = toPosixPath(part2);
|
|
11362
11727
|
if (next2.startsWith("/") || DRIVE.test(next2)) return next2;
|
|
11363
|
-
return
|
|
11728
|
+
return posix3.join(toPosixPath(current2), next2);
|
|
11364
11729
|
}, toPosixPath(base));
|
|
11365
11730
|
|
|
11366
11731
|
// src/git/pathspec.ts
|
|
11367
|
-
import { posix as
|
|
11732
|
+
import { posix as posix4 } from "node:path";
|
|
11368
11733
|
var MAGIC = /^:(?:\(([^)]*)\)|([!^/]*))(.*)$/s;
|
|
11369
11734
|
var GLOB = /[*?[]/;
|
|
11370
11735
|
var specOf = (raw, prefix) => {
|
|
@@ -11374,7 +11739,7 @@ var specOf = (raw, prefix) => {
|
|
|
11374
11739
|
const rest = magic === null ? posixRaw : magic[3] ?? "";
|
|
11375
11740
|
const exclude = /(?:^|,)exclude(?:,|$)|[!^]/.test(words);
|
|
11376
11741
|
const top = /(?:^|,)top(?:,|$)|\//.test(words);
|
|
11377
|
-
const joined =
|
|
11742
|
+
const joined = posix4.normalize(top ? rest : posix4.join(prefix === "" ? "." : prefix, rest));
|
|
11378
11743
|
return { pattern: joined === "./" ? "." : joined.replace(/\/$/, ""), exclude };
|
|
11379
11744
|
};
|
|
11380
11745
|
var globRegex3 = (pattern) => {
|
|
@@ -11407,7 +11772,7 @@ var discard = (op, scope2, pathspecs = []) => ({
|
|
|
11407
11772
|
discard: { scope: scope2, pathspecs }
|
|
11408
11773
|
});
|
|
11409
11774
|
var SEVERITY = { pass: 0, snapshot: 1, discard: 2, block: 3 };
|
|
11410
|
-
var worst = (verdicts) => verdicts.reduce((
|
|
11775
|
+
var worst = (verdicts) => verdicts.reduce((current2, next2) => SEVERITY[next2.kind] > SEVERITY[current2.kind] ? next2 : current2, PASS);
|
|
11411
11776
|
|
|
11412
11777
|
// src/git/options.ts
|
|
11413
11778
|
var canonical = (name, spec2) => {
|
|
@@ -11768,24 +12133,24 @@ var branchName = (ref) => {
|
|
|
11768
12133
|
if (ref.startsWith("refs/tags/")) return void 0;
|
|
11769
12134
|
return ref.replace(/^refs\/heads\//, "");
|
|
11770
12135
|
};
|
|
11771
|
-
var refspecTarget = (refspec,
|
|
12136
|
+
var refspecTarget = (refspec, current2) => {
|
|
11772
12137
|
const plain = refspec.replace(/^\+/, "");
|
|
11773
12138
|
const colon = plain.indexOf(":");
|
|
11774
12139
|
const destination = colon < 0 ? plain : plain.slice(colon + 1);
|
|
11775
|
-
if (destination === "HEAD" || colon < 0 && plain === "HEAD") return
|
|
12140
|
+
if (destination === "HEAD" || colon < 0 && plain === "HEAD") return current2;
|
|
11776
12141
|
if (destination === "" || destination.startsWith("refs/tags/")) return void 0;
|
|
11777
12142
|
return branchName(destination);
|
|
11778
12143
|
};
|
|
11779
12144
|
var pushDestinations = (parsed, facts) => {
|
|
11780
12145
|
if (hasAny(parsed, "--all", "--branches", "--mirror")) return "all";
|
|
11781
12146
|
const refspecs = parsed.positionals.slice(1).filter((refspec) => refspec !== ":");
|
|
11782
|
-
const
|
|
12147
|
+
const current2 = facts.currentBranch();
|
|
11783
12148
|
if (refspecs.length === 0) {
|
|
11784
|
-
if (hasAny(parsed, "--tags") ||
|
|
11785
|
-
return [facts.upstream(
|
|
12149
|
+
if (hasAny(parsed, "--tags") || current2 === void 0) return [];
|
|
12150
|
+
return [facts.upstream(current2)?.branch ?? current2];
|
|
11786
12151
|
}
|
|
11787
12152
|
return refspecs.flatMap((refspec) => {
|
|
11788
|
-
const target = refspecTarget(refspec,
|
|
12153
|
+
const target = refspecTarget(refspec, current2);
|
|
11789
12154
|
return target === void 0 ? [] : [target];
|
|
11790
12155
|
});
|
|
11791
12156
|
};
|
|
@@ -11793,10 +12158,10 @@ var leaseVerdict = (parsed, facts) => {
|
|
|
11793
12158
|
const leases = valuesOf(parsed, "--force-with-lease");
|
|
11794
12159
|
const explicit = leases.length > 0 && leases.every((lease) => /^[^:]+:.+$/.test(lease));
|
|
11795
12160
|
if (!explicit || !hasAny(parsed, "--force-if-includes")) return block2("force-push", "--force-with-lease");
|
|
11796
|
-
const
|
|
12161
|
+
const current2 = facts.currentBranch();
|
|
11797
12162
|
const leased = leases.map((lease) => branchName(lease.slice(0, lease.indexOf(":"))));
|
|
11798
12163
|
const destinations = pushDestinations(parsed, facts);
|
|
11799
|
-
const own =
|
|
12164
|
+
const own = current2 !== void 0 && !facts.isProtected(current2) && leased.every((name) => name === current2) && destinations !== "all" && destinations.every((name) => name === current2);
|
|
11800
12165
|
return own ? PASS : block2("lease-not-own", leased.join(", "));
|
|
11801
12166
|
};
|
|
11802
12167
|
var pushVerdict = (invocation, facts) => {
|
|
@@ -11819,8 +12184,8 @@ var LEAVES = /* @__PURE__ */ new Set(["--abort", "--quit", "--skip", "--dry-run"
|
|
|
11819
12184
|
var commitVerdict = (invocation, facts) => {
|
|
11820
12185
|
if (invocation.args.some((arg) => LEAVES.has(arg))) return PASS;
|
|
11821
12186
|
if (invocation.subcommand === "rebase") return PASS;
|
|
11822
|
-
const
|
|
11823
|
-
return
|
|
12187
|
+
const current2 = facts.currentBranch();
|
|
12188
|
+
return current2 !== void 0 && facts.isProtected(current2) ? block2("protected-commit", current2) : PASS;
|
|
11824
12189
|
};
|
|
11825
12190
|
var pushVerdict2 = (invocation, facts) => {
|
|
11826
12191
|
const parsed = parseOptions(invocation.args, PUSH_OPTIONS);
|
|
@@ -12041,33 +12406,33 @@ var listWorktrees = (runtime, cwd) => {
|
|
|
12041
12406
|
};
|
|
12042
12407
|
|
|
12043
12408
|
// src/git/journal.ts
|
|
12044
|
-
import { join as
|
|
12409
|
+
import { join as join29 } from "node:path";
|
|
12045
12410
|
var JOURNAL_FILE = ".finitemonkey/git-journal.jsonl";
|
|
12046
12411
|
var JOURNAL_SCHEMA = "finitemonkey/git-journal@1";
|
|
12047
|
-
var journalPath = (root) => toAbsolutePath(
|
|
12412
|
+
var journalPath = (root) => toAbsolutePath(join29(root, JOURNAL_FILE));
|
|
12048
12413
|
var headsOf = (value) => isRecord(value) ? Object.fromEntries(
|
|
12049
12414
|
Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
|
|
12050
12415
|
) : {};
|
|
12051
12416
|
var parseJournalLine = (line2) => {
|
|
12052
12417
|
const parsed = parseJsonObject(line2);
|
|
12053
12418
|
if (!parsed.ok) return void 0;
|
|
12054
|
-
const
|
|
12055
|
-
const ref = optionalString(
|
|
12056
|
-
const commit2 = optionalString(
|
|
12057
|
-
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");
|
|
12058
12423
|
if (ref === void 0 || commit2 === void 0 || at === void 0) return void 0;
|
|
12059
12424
|
return {
|
|
12060
12425
|
schema: JOURNAL_SCHEMA,
|
|
12061
12426
|
at,
|
|
12062
|
-
op: optionalString(
|
|
12427
|
+
op: optionalString(record3, "op") ?? "",
|
|
12063
12428
|
ref,
|
|
12064
12429
|
commit: commit2,
|
|
12065
|
-
head: optionalString(
|
|
12066
|
-
branch: optionalString(
|
|
12067
|
-
index: optionalString(
|
|
12068
|
-
owner: optionalString(
|
|
12069
|
-
command: optionalString(
|
|
12070
|
-
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"])
|
|
12071
12436
|
};
|
|
12072
12437
|
};
|
|
12073
12438
|
var readJournal = (runtime, root) => {
|
|
@@ -12080,7 +12445,7 @@ var readJournal = (runtime, root) => {
|
|
|
12080
12445
|
};
|
|
12081
12446
|
var appendJournal = (runtime, root, entry2) => {
|
|
12082
12447
|
const path = journalPath(root);
|
|
12083
|
-
const made = runtime.mkdir(toAbsolutePath(
|
|
12448
|
+
const made = runtime.mkdir(toAbsolutePath(join29(root, ".finitemonkey")));
|
|
12084
12449
|
if (!made.ok) return made;
|
|
12085
12450
|
const previous = runtime.readText(path);
|
|
12086
12451
|
const before = previous.ok ? previous.value : "";
|
|
@@ -12322,6 +12687,83 @@ var GIT_HANDLERS = {
|
|
|
12322
12687
|
PostToolUse: [gitPostToolUse]
|
|
12323
12688
|
};
|
|
12324
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
|
+
|
|
12325
12767
|
// src/prose/marker-guard.ts
|
|
12326
12768
|
import { relative as relative6 } from "node:path";
|
|
12327
12769
|
|
|
@@ -12631,7 +13073,7 @@ var liveFindings = (input, texts) => {
|
|
|
12631
13073
|
const accepted2 = acceptedFingerprints(baseline.ok ? baseline.value : EMPTY_BASELINE, input.runtime.now());
|
|
12632
13074
|
return texts.flatMap((text6) => scanText(text6, { rules: CREDENTIAL_RULES })).filter((finding3) => !accepted2.has(finding3.fingerprint));
|
|
12633
13075
|
};
|
|
12634
|
-
var
|
|
13076
|
+
var record2 = (input, findings, draft, where) => {
|
|
12635
13077
|
const ids2 = findings.map((finding3) => {
|
|
12636
13078
|
const saved = recordIncident(
|
|
12637
13079
|
input.runtime,
|
|
@@ -12647,7 +13089,7 @@ var secretsUserPromptSubmit = (input) => {
|
|
|
12647
13089
|
const prompt = optionalString(input.payload.raw, "prompt") ?? "";
|
|
12648
13090
|
const findings = liveFindings(input, [prompt]);
|
|
12649
13091
|
if (findings.length === 0) return { kind: "allow" };
|
|
12650
|
-
const incident =
|
|
13092
|
+
const incident = record2(input, findings, { source: "prompt", state: "exposicao-local" }, "prompt");
|
|
12651
13093
|
const items = findings.map((finding3) => ({ ruleId: finding3.ruleId, where: lineWhere(input.t, finding3) }));
|
|
12652
13094
|
return {
|
|
12653
13095
|
kind: "block",
|
|
@@ -12658,7 +13100,7 @@ var secretsPostToolUse = (input) => {
|
|
|
12658
13100
|
const findings = liveFindings(input, stringLeaves(input.payload.raw["tool_response"]));
|
|
12659
13101
|
if (findings.length === 0) return { kind: "allow" };
|
|
12660
13102
|
const tool = toolName2(input.payload) || "tool";
|
|
12661
|
-
const incident =
|
|
13103
|
+
const incident = record2(input, findings, { source: "tool-output", state: "aberto" }, `tool-output:${tool}`);
|
|
12662
13104
|
const items = findings.map((finding3) => ({ ruleId: finding3.ruleId, where: lineWhere(input.t, finding3) }));
|
|
12663
13105
|
const context = input.t("secrets.post.context", { tool, found: describeFindings(input.t, items), incident });
|
|
12664
13106
|
return {
|
|
@@ -12940,12 +13382,13 @@ var SECRETS_HANDLERS = {
|
|
|
12940
13382
|
PostToolUse: [secretsPostToolUse],
|
|
12941
13383
|
SessionStart: [secretsSessionStart]
|
|
12942
13384
|
};
|
|
12943
|
-
var
|
|
12944
|
-
SECRETS_HANDLERS,
|
|
12945
|
-
PORTAO_HANDLERS,
|
|
12946
|
-
PROSE_HANDLERS,
|
|
12947
|
-
GIT_HANDLERS
|
|
12948
|
-
|
|
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));
|
|
12949
13392
|
|
|
12950
13393
|
// src/hooks/payload.ts
|
|
12951
13394
|
var EMPTY_PAYLOAD = {
|
|
@@ -13356,12 +13799,12 @@ var hookSchemaProblems = (hooks, claude) => {
|
|
|
13356
13799
|
};
|
|
13357
13800
|
|
|
13358
13801
|
// src/doctor/hook-sources.ts
|
|
13359
|
-
import { join as
|
|
13802
|
+
import { join as join30 } from "node:path";
|
|
13360
13803
|
var entriesOf = (source, hooks, own) => Object.entries(asRecord(hooks)).flatMap(
|
|
13361
13804
|
([event, groups]) => asList(groups).flatMap((group) => {
|
|
13362
|
-
const
|
|
13363
|
-
const matcher = optionalString(
|
|
13364
|
-
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 }));
|
|
13365
13808
|
})
|
|
13366
13809
|
);
|
|
13367
13810
|
var PROJECT_SCOPES = { project: "project", local: "local" };
|
|
@@ -13369,10 +13812,10 @@ var projectHookFiles = (env) => readSettings(env).flatMap(({ scope: scope2, path
|
|
|
13369
13812
|
const source = PROJECT_SCOPES[scope2];
|
|
13370
13813
|
return source === void 0 || !("hooks" in settings) ? [] : [{ source, path, hooks: settings["hooks"] }];
|
|
13371
13814
|
});
|
|
13372
|
-
var pluginsDir = ({ env, runtime }) =>
|
|
13815
|
+
var pluginsDir = ({ env, runtime }) => join30(env["CLAUDE_CONFIG_DIR"] ?? join30(runtime.homeDir(), ".claude"), "plugins");
|
|
13373
13816
|
var installRecord = (value, root) => {
|
|
13374
13817
|
const records = Array.isArray(value) ? value.filter(isRecord) : isRecord(value) ? [value] : [];
|
|
13375
|
-
return records.find((
|
|
13818
|
+
return records.find((record3) => record3["projectPath"] === root) ?? records[0];
|
|
13376
13819
|
};
|
|
13377
13820
|
var readJson = (env, path) => {
|
|
13378
13821
|
const text6 = env.runtime.readText(path);
|
|
@@ -13380,18 +13823,18 @@ var readJson = (env, path) => {
|
|
|
13380
13823
|
return parsed?.ok === true ? parsed.value : void 0;
|
|
13381
13824
|
};
|
|
13382
13825
|
var installedPlugin = (env) => {
|
|
13383
|
-
const index = readJson(env, toAbsolutePath(
|
|
13384
|
-
const
|
|
13385
|
-
const installPath =
|
|
13386
|
-
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;
|
|
13387
13830
|
const root = toAbsolutePath(installPath);
|
|
13388
|
-
const manifest = readJson(env, toAbsolutePath(
|
|
13389
|
-
const hooksPath = toAbsolutePath(
|
|
13831
|
+
const manifest = readJson(env, toAbsolutePath(join30(root, ".claude-plugin", "plugin.json")));
|
|
13832
|
+
const hooksPath = toAbsolutePath(join30(root, "hooks", "hooks.json"));
|
|
13390
13833
|
const text6 = env.runtime.readText(hooksPath);
|
|
13391
13834
|
const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
|
|
13392
13835
|
return {
|
|
13393
13836
|
root,
|
|
13394
|
-
version: (manifest === void 0 ? void 0 : optionalString(manifest, "version")) ?? optionalString(
|
|
13837
|
+
version: (manifest === void 0 ? void 0 : optionalString(manifest, "version")) ?? optionalString(record3, "version"),
|
|
13395
13838
|
hooksFile: parsed?.ok === true ? { source: "plugin", path: hooksPath, hooks: parsed.value["hooks"] } : void 0,
|
|
13396
13839
|
hooksText: text6.ok ? text6.value : void 0
|
|
13397
13840
|
};
|
|
@@ -13431,7 +13874,7 @@ var schemaFindings = (env, plugin, claude) => {
|
|
|
13431
13874
|
const files = [...projectHookFiles(env), ...plugin?.hooksFile === void 0 ? [] : [plugin.hooksFile]];
|
|
13432
13875
|
const invalidJson = [
|
|
13433
13876
|
...unreadableProjectFiles(env),
|
|
13434
|
-
...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"))] : []
|
|
13435
13878
|
];
|
|
13436
13879
|
return [
|
|
13437
13880
|
...invalidJson.flatMap((file) => schemaFinding(file, ["not valid JSON"], label2)),
|
|
@@ -13452,7 +13895,7 @@ var pluginFindings = (env, projectOwn) => {
|
|
|
13452
13895
|
];
|
|
13453
13896
|
};
|
|
13454
13897
|
var coverageFindings = (env, entries) => {
|
|
13455
|
-
const installed = env.runtime.stat(toAbsolutePath(
|
|
13898
|
+
const installed = env.runtime.stat(toAbsolutePath(join31(env.root, ...LOCK_FILE.split("/")))) !== void 0;
|
|
13456
13899
|
if (entries.length === 0 && !installed) {
|
|
13457
13900
|
return [finding("hooks.registered", "warn", "doctor.hooks.none", {}, "doctor.hooks.fix_none")];
|
|
13458
13901
|
}
|
|
@@ -13504,17 +13947,17 @@ var checkHooks = (env) => {
|
|
|
13504
13947
|
};
|
|
13505
13948
|
|
|
13506
13949
|
// src/doctor/instructions.ts
|
|
13507
|
-
import { join as
|
|
13950
|
+
import { join as join32 } from "node:path";
|
|
13508
13951
|
var INSTRUCTION_LINE_CEILING = 200;
|
|
13509
13952
|
var TOP_INSTRUCTIONS = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md"];
|
|
13510
13953
|
var RULES_DIR = ".claude/rules";
|
|
13511
13954
|
var SHOWN = 3;
|
|
13512
13955
|
var readProjectFile = (env, relative9) => {
|
|
13513
|
-
const text6 = env.runtime.readText(toAbsolutePath(
|
|
13956
|
+
const text6 = env.runtime.readText(toAbsolutePath(join32(env.root, ...relative9.split("/"))));
|
|
13514
13957
|
return text6.ok ? text6.value : void 0;
|
|
13515
13958
|
};
|
|
13516
13959
|
var instructionFiles = (env) => {
|
|
13517
|
-
const listed4 = env.runtime.listDir(toAbsolutePath(
|
|
13960
|
+
const listed4 = env.runtime.listDir(toAbsolutePath(join32(env.root, ...RULES_DIR.split("/"))));
|
|
13518
13961
|
const rules = listed4.ok ? listed4.value.filter((name) => name.endsWith(".md")).map((name) => `${RULES_DIR}/${name}`) : [];
|
|
13519
13962
|
return [...TOP_INSTRUCTIONS, ...rules].flatMap((path) => {
|
|
13520
13963
|
const text6 = readProjectFile(env, path);
|
|
@@ -13619,12 +14062,12 @@ var checkInstructions = (env) => {
|
|
|
13619
14062
|
};
|
|
13620
14063
|
|
|
13621
14064
|
// src/doctor/line-endings.ts
|
|
13622
|
-
import { join as
|
|
14065
|
+
import { join as join33 } from "node:path";
|
|
13623
14066
|
var LF_RULE = /^\*\s.*\beol=lf\b/m;
|
|
13624
14067
|
var checkLineEndings = ({ root, runtime }) => {
|
|
13625
14068
|
const autocrlf = runtime.git(["config", "--get", "core.autocrlf"], root);
|
|
13626
14069
|
if (!autocrlf.ok || autocrlf.stdout.trim().toLowerCase() !== "true") return [];
|
|
13627
|
-
const attributes = runtime.readText(toAbsolutePath(
|
|
14070
|
+
const attributes = runtime.readText(toAbsolutePath(join33(root, ".gitattributes")));
|
|
13628
14071
|
if (attributes.ok && LF_RULE.test(attributes.value)) return [];
|
|
13629
14072
|
return [
|
|
13630
14073
|
finding("git.autocrlf", "warn", "doctor.git.autocrlf", { file: ".gitattributes" }, "doctor.git.fix_autocrlf")
|
|
@@ -13632,7 +14075,7 @@ var checkLineEndings = ({ root, runtime }) => {
|
|
|
13632
14075
|
};
|
|
13633
14076
|
|
|
13634
14077
|
// src/doctor/memory.ts
|
|
13635
|
-
import { join as
|
|
14078
|
+
import { join as join34 } from "node:path";
|
|
13636
14079
|
var ceilingOf = (config) => {
|
|
13637
14080
|
const value = configAt(parseConfigYaml(config ?? ""), "memory", "maxLines");
|
|
13638
14081
|
const parsed = typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
|
|
@@ -13640,9 +14083,9 @@ var ceilingOf = (config) => {
|
|
|
13640
14083
|
};
|
|
13641
14084
|
var lineCount2 = (text6) => text6.replace(/\r?\n$/, "").split(/\r?\n/).length;
|
|
13642
14085
|
var checkAgentsFile = ({ root, runtime }) => {
|
|
13643
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
14086
|
+
const text6 = runtime.readText(toAbsolutePath(join34(root, AGENTS_FILE)));
|
|
13644
14087
|
if (!text6.ok) return [];
|
|
13645
|
-
const config = runtime.readText(toAbsolutePath(
|
|
14088
|
+
const config = runtime.readText(toAbsolutePath(join34(root, CONFIG_FILE)));
|
|
13646
14089
|
const max = ceilingOf(config.ok ? config.value : void 0);
|
|
13647
14090
|
const lines3 = lineCount2(text6.value);
|
|
13648
14091
|
const params2 = { file: AGENTS_FILE, lines: lines3, max };
|
|
@@ -13650,7 +14093,7 @@ var checkAgentsFile = ({ root, runtime }) => {
|
|
|
13650
14093
|
};
|
|
13651
14094
|
|
|
13652
14095
|
// src/doctor/project-files.ts
|
|
13653
|
-
import { join as
|
|
14096
|
+
import { join as join35 } from "node:path";
|
|
13654
14097
|
var variants = (base, extensions) => extensions.map((extension) => `${base}${extension}`);
|
|
13655
14098
|
var SCRIPT = [".js", ".cjs", ".mjs", ".ts", ".cts", ".mts"];
|
|
13656
14099
|
var DATA = ["", ".json", ".yml", ".yaml"];
|
|
@@ -13681,7 +14124,7 @@ var CONFIG_GROUPS = [
|
|
|
13681
14124
|
files: [...variants("commitlint.config", SCRIPT), ...variants(".commitlintrc", [...DATA, ".js"])]
|
|
13682
14125
|
}
|
|
13683
14126
|
];
|
|
13684
|
-
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");
|
|
13685
14128
|
var crowded = (env, groups, build) => groups.flatMap(({ name, files }) => {
|
|
13686
14129
|
const found = present(env, files);
|
|
13687
14130
|
return found.length > 1 ? [build(name, found.join(", "))] : [];
|
|
@@ -14160,11 +14603,11 @@ var unreachable = (runtime, repo, shas) => {
|
|
|
14160
14603
|
var fsckDangling = (runtime, repo) => {
|
|
14161
14604
|
const result = runtime.git(["fsck", "--no-reflogs", "--no-progress", "--dangling"], repo.root);
|
|
14162
14605
|
const lines3 = linesOf(result.stdout);
|
|
14163
|
-
const
|
|
14606
|
+
const pick4 = (kind) => lines3.flatMap((line2) => {
|
|
14164
14607
|
const [label2, type, sha] = line2.split(" ");
|
|
14165
14608
|
return label2 === "dangling" && type === kind && sha !== void 0 ? [sha] : [];
|
|
14166
14609
|
});
|
|
14167
|
-
return { commits:
|
|
14610
|
+
return { commits: pick4("commit"), blobs: pick4("blob").length };
|
|
14168
14611
|
};
|
|
14169
14612
|
var describe = (runtime, repo, shas) => {
|
|
14170
14613
|
if (shas.length === 0) return /* @__PURE__ */ new Map();
|
|
@@ -14264,7 +14707,7 @@ var rescueCommand = {
|
|
|
14264
14707
|
};
|
|
14265
14708
|
|
|
14266
14709
|
// src/git/undo.ts
|
|
14267
|
-
import { basename as basename3, dirname as dirname8, join as
|
|
14710
|
+
import { basename as basename3, dirname as dirname8, join as join36 } from "node:path";
|
|
14268
14711
|
var INDEX_SUBJECT = "finitemonkey index:";
|
|
14269
14712
|
var gitLine = (runtime, repo, args) => {
|
|
14270
14713
|
const result = runtime.git(args, repo.root);
|
|
@@ -14307,7 +14750,7 @@ var findSnapshot = (views, wanted) => {
|
|
|
14307
14750
|
};
|
|
14308
14751
|
var stampOf2 = (iso) => iso.replace(/[:.]/g, "-");
|
|
14309
14752
|
var worktreePath = (runtime, repo) => {
|
|
14310
|
-
const base =
|
|
14753
|
+
const base = join36(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
|
|
14311
14754
|
return toAbsolutePath(runtime.stat(toAbsolutePath(base)) === void 0 ? base : `${base}-2`);
|
|
14312
14755
|
};
|
|
14313
14756
|
var intoWorktree = (runtime, repo, view2) => {
|
|
@@ -14320,8 +14763,8 @@ var step2 = (runtime, repo, args) => {
|
|
|
14320
14763
|
return result.ok ? accept(void 0) : reject(failure2(args.join(" "), result));
|
|
14321
14764
|
};
|
|
14322
14765
|
var inPlace = (runtime, repo, view2) => {
|
|
14323
|
-
const
|
|
14324
|
-
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) {
|
|
14325
14768
|
const moved = step2(runtime, repo, ["reset", "--soft", view2.head]);
|
|
14326
14769
|
if (!moved.ok) return moved;
|
|
14327
14770
|
}
|
|
@@ -14542,7 +14985,7 @@ var hookCommand = {
|
|
|
14542
14985
|
};
|
|
14543
14986
|
|
|
14544
14987
|
// src/commands/i18n-check.ts
|
|
14545
|
-
import { join as
|
|
14988
|
+
import { join as join37 } from "node:path";
|
|
14546
14989
|
|
|
14547
14990
|
// src/i18n/message.ts
|
|
14548
14991
|
var ARG = /^\s*([a-zA-Z][a-zA-Z0-9_]*)\s*(?:,\s*(plural|select)\s*,)?/;
|
|
@@ -14744,7 +15187,7 @@ var unreadable = (dir, file, detail) => ({
|
|
|
14744
15187
|
exitCode: ExitCode.NotConfigured
|
|
14745
15188
|
});
|
|
14746
15189
|
var readCatalog = (ctx, dir, lang) => {
|
|
14747
|
-
const file = toAbsolutePath(
|
|
15190
|
+
const file = toAbsolutePath(join37(dir, `${lang}.json`));
|
|
14748
15191
|
const text6 = ctx.runtime.readText(file);
|
|
14749
15192
|
if (!text6.ok) return reject(unreadable(dir, file, text6.error));
|
|
14750
15193
|
const parsed = parseJsonObject(text6.value);
|
|
@@ -14767,7 +15210,7 @@ var describeProblem = (ctx, problem4) => {
|
|
|
14767
15210
|
return ` ${problem4.lang} ${problem4.key} ${kind}`;
|
|
14768
15211
|
};
|
|
14769
15212
|
var loadReview = (ctx, dir) => {
|
|
14770
|
-
const text6 = ctx.runtime.readText(toAbsolutePath(
|
|
15213
|
+
const text6 = ctx.runtime.readText(toAbsolutePath(join37(dir, "review.json")));
|
|
14771
15214
|
const parsed = text6.ok ? parseJsonObject(text6.value) : void 0;
|
|
14772
15215
|
if (parsed?.ok !== true) return void 0;
|
|
14773
15216
|
const statuses = (lang) => {
|
|
@@ -14808,10 +15251,10 @@ var i18nCheckCommand = {
|
|
|
14808
15251
|
};
|
|
14809
15252
|
|
|
14810
15253
|
// src/history/look-back.ts
|
|
14811
|
-
import { join as
|
|
15254
|
+
import { join as join38 } from "node:path";
|
|
14812
15255
|
|
|
14813
15256
|
// src/history/patterns.ts
|
|
14814
|
-
import { posix as
|
|
15257
|
+
import { posix as posix5 } from "node:path";
|
|
14815
15258
|
var PATTERNS = ["done_after_failing_tests", "env_read", "no_verify", "force_push", "test_skip"];
|
|
14816
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)/;
|
|
14817
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;
|
|
@@ -14833,7 +15276,7 @@ var stringField = (tool, name) => {
|
|
|
14833
15276
|
return typeof value === "string" ? value : "";
|
|
14834
15277
|
};
|
|
14835
15278
|
var isEnvPath = (path) => {
|
|
14836
|
-
const name =
|
|
15279
|
+
const name = posix5.basename(path.replace(/\\/g, "/"));
|
|
14837
15280
|
return /^\.env(\.[A-Za-z0-9_-]+)?$/.test(name) && !ENV_TEMPLATE.test(name);
|
|
14838
15281
|
};
|
|
14839
15282
|
var envRead = (tool) => {
|
|
@@ -14921,7 +15364,7 @@ var addResults = (draft, blocks) => {
|
|
|
14921
15364
|
};
|
|
14922
15365
|
var parseTranscript = (jsonl) => {
|
|
14923
15366
|
const turns = [];
|
|
14924
|
-
let
|
|
15367
|
+
let current2;
|
|
14925
15368
|
for (const line2 of jsonl.split(/\r?\n/)) {
|
|
14926
15369
|
if (line2.trim() === "") continue;
|
|
14927
15370
|
const parsed = parseJsonObject(line2);
|
|
@@ -14929,13 +15372,13 @@ var parseTranscript = (jsonl) => {
|
|
|
14929
15372
|
const entry2 = parsed.value;
|
|
14930
15373
|
const blocks = blocksOf(entry2["message"]);
|
|
14931
15374
|
if (entry2["type"] === "user") {
|
|
14932
|
-
if (
|
|
15375
|
+
if (current2 !== void 0 && addResults(current2, blocks)) continue;
|
|
14933
15376
|
const prompt = blocks.map(textOf3).join("\n").trim();
|
|
14934
15377
|
if (prompt === "") continue;
|
|
14935
|
-
|
|
14936
|
-
turns.push(
|
|
14937
|
-
} else if (entry2["type"] === "assistant" &&
|
|
14938
|
-
addAssistant(
|
|
15378
|
+
current2 = startTurn(prompt);
|
|
15379
|
+
turns.push(current2);
|
|
15380
|
+
} else if (entry2["type"] === "assistant" && current2 !== void 0) {
|
|
15381
|
+
addAssistant(current2, blocks);
|
|
14939
15382
|
}
|
|
14940
15383
|
}
|
|
14941
15384
|
return turns;
|
|
@@ -14944,7 +15387,7 @@ var parseTranscript = (jsonl) => {
|
|
|
14944
15387
|
// src/history/look-back.ts
|
|
14945
15388
|
var SAMPLES = 3;
|
|
14946
15389
|
var projectLogKey = (root) => root.replace(/[^A-Za-z0-9-]/g, "-");
|
|
14947
|
-
var projectLogDir = (runtime, root, env) =>
|
|
15390
|
+
var projectLogDir = (runtime, root, env) => join38(env["CLAUDE_CONFIG_DIR"] ?? join38(runtime.homeDir(), ".claude"), "projects", projectLogKey(root));
|
|
14948
15391
|
var lookBack = (runtime, root, env) => {
|
|
14949
15392
|
const dir = projectLogDir(runtime, root, env);
|
|
14950
15393
|
const listed4 = runtime.listDir(toAbsolutePath(dir));
|
|
@@ -14965,7 +15408,7 @@ var lookBack = (runtime, root, env) => {
|
|
|
14965
15408
|
};
|
|
14966
15409
|
let turns = 0;
|
|
14967
15410
|
for (const name of files) {
|
|
14968
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
15411
|
+
const text6 = runtime.readText(toAbsolutePath(join38(dir, name)));
|
|
14969
15412
|
if (!text6.ok) continue;
|
|
14970
15413
|
for (const turn of parseTranscript(text6.value)) {
|
|
14971
15414
|
turns += 1;
|
|
@@ -15181,6 +15624,32 @@ var initCommand = {
|
|
|
15181
15624
|
}
|
|
15182
15625
|
};
|
|
15183
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
|
+
|
|
15184
15653
|
// src/commands/prose-check.ts
|
|
15185
15654
|
import { relative as relative8 } from "node:path";
|
|
15186
15655
|
|
|
@@ -15543,7 +16012,7 @@ var view = (finding3, status) => ({
|
|
|
15543
16012
|
status,
|
|
15544
16013
|
...finding3.commit === void 0 ? {} : { commit: finding3.commit }
|
|
15545
16014
|
});
|
|
15546
|
-
var
|
|
16015
|
+
var modeOf3 = (values) => {
|
|
15547
16016
|
const chosen = [values["staged"] === true, values["history"] === true, stringOption(values, "path") !== void 0];
|
|
15548
16017
|
if (chosen.filter(Boolean).length > 1) return void 0;
|
|
15549
16018
|
if (chosen[0] === true) return "staged";
|
|
@@ -15634,7 +16103,7 @@ var secretsScanCommand = {
|
|
|
15634
16103
|
status: "ready",
|
|
15635
16104
|
options: { staged: { type: "boolean" }, history: { type: "boolean" }, path: { type: "string" } },
|
|
15636
16105
|
run: async (ctx, args) => {
|
|
15637
|
-
const mode =
|
|
16106
|
+
const mode = modeOf3(args.values);
|
|
15638
16107
|
const fix = "finitemonkey help secrets scan";
|
|
15639
16108
|
if (mode === void 0) return failureFrom(ctx.t, usageProblem("invalid_arguments", "secrets.error.one_mode", fix));
|
|
15640
16109
|
const root = projectRoot(ctx);
|
|
@@ -15656,7 +16125,7 @@ var secretsScanCommand = {
|
|
|
15656
16125
|
};
|
|
15657
16126
|
|
|
15658
16127
|
// src/task/create.ts
|
|
15659
|
-
import { join as
|
|
16128
|
+
import { join as join39 } from "node:path";
|
|
15660
16129
|
|
|
15661
16130
|
// src/core/yaml/write.ts
|
|
15662
16131
|
var PLAIN_SAFE = /^[A-Za-z_./][A-Za-z0-9_./ ()-]*$/;
|
|
@@ -15715,7 +16184,8 @@ var contractYaml = (contract) => ({
|
|
|
15715
16184
|
tests_protected: contract.scope.testsProtected
|
|
15716
16185
|
},
|
|
15717
16186
|
acceptance: contract.acceptance.map(checkYaml),
|
|
15718
|
-
decisions_needed: [...contract.decisionsNeeded]
|
|
16187
|
+
decisions_needed: [...contract.decisionsNeeded],
|
|
16188
|
+
...contract.callersIgnore === void 0 ? {} : { callers_ignore: [...contract.callersIgnore] }
|
|
15719
16189
|
});
|
|
15720
16190
|
var contractJson = (contract) => ({
|
|
15721
16191
|
id: contract.id,
|
|
@@ -15740,10 +16210,10 @@ var explicitChecks = (draft) => [
|
|
|
15740
16210
|
var draftChecks = (runtime, paths, draft) => {
|
|
15741
16211
|
const disk = {
|
|
15742
16212
|
read: (relative9) => {
|
|
15743
|
-
const text6 = runtime.readText(toAbsolutePath(
|
|
16213
|
+
const text6 = runtime.readText(toAbsolutePath(join39(paths.root, relative9)));
|
|
15744
16214
|
return text6.ok ? text6.value : void 0;
|
|
15745
16215
|
},
|
|
15746
|
-
exists: (relative9) => runtime.stat(toAbsolutePath(
|
|
16216
|
+
exists: (relative9) => runtime.stat(toAbsolutePath(join39(paths.root, relative9))) !== void 0
|
|
15747
16217
|
};
|
|
15748
16218
|
const detected = detectChecks(disk);
|
|
15749
16219
|
const base = draft.runs.length > 0 || draft.tests !== void 0 ? explicitChecks(draft) : detected;
|
|
@@ -15756,6 +16226,7 @@ var draftChecks = (runtime, paths, draft) => {
|
|
|
15756
16226
|
{ id: "prose", kind: "prose" },
|
|
15757
16227
|
{ id: "worktree", kind: "worktree" },
|
|
15758
16228
|
{ id: "merge", kind: "merge" },
|
|
16229
|
+
{ id: "callers", kind: "callers" },
|
|
15759
16230
|
...draft.paths.length > 0 || draft.budget !== void 0 ? [{ id: "diff", kind: "diff" }] : [],
|
|
15760
16231
|
...draft.git.length > 0 ? [{ id: "git", kind: "git", require: draft.git }] : [],
|
|
15761
16232
|
{ id: "pr", kind: "pr", requireOpen: draft.git.includes("pr_open") },
|
|
@@ -15945,15 +16416,15 @@ var taskDoneCommand = {
|
|
|
15945
16416
|
};
|
|
15946
16417
|
|
|
15947
16418
|
// src/install/uninstall.ts
|
|
15948
|
-
var structural = (entry2,
|
|
16419
|
+
var structural = (entry2, current2) => {
|
|
15949
16420
|
if (entry2.kind === "gitignore" || entry2.kind === "markdown") {
|
|
15950
|
-
const text6 = entry2.kind === "gitignore" ? withoutBlock(
|
|
16421
|
+
const text6 = entry2.kind === "gitignore" ? withoutBlock(current2) : withoutMarkdownBlock(current2);
|
|
15951
16422
|
return text6 === "" && !entry2.existed ? void 0 : text6;
|
|
15952
16423
|
}
|
|
15953
|
-
const parsed = parseSettings(
|
|
15954
|
-
if (!parsed.ok || entry2.added === void 0) return
|
|
16424
|
+
const parsed = parseSettings(current2);
|
|
16425
|
+
if (!parsed.ok || entry2.added === void 0) return current2;
|
|
15955
16426
|
const settings = unmergeSettings(parsed.value, entry2.added, entry2.handlers ?? []);
|
|
15956
|
-
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));
|
|
15957
16428
|
};
|
|
15958
16429
|
var unchanged = (project, entry2) => {
|
|
15959
16430
|
if (!entry2.existed) return { path: entry2.path, action: "delete", after: void 0 };
|
|
@@ -15961,17 +16432,17 @@ var unchanged = (project, entry2) => {
|
|
|
15961
16432
|
return original === void 0 ? void 0 : { path: entry2.path, action: "restore", after: original };
|
|
15962
16433
|
};
|
|
15963
16434
|
var removalFor = (project, entry2, options) => {
|
|
15964
|
-
const
|
|
15965
|
-
if (
|
|
16435
|
+
const current2 = project.read(entry2.path);
|
|
16436
|
+
if (current2 === void 0) return { path: entry2.path, action: "gone", after: void 0 };
|
|
15966
16437
|
if (entry2.kind === "generated" && options.keepGenerated) {
|
|
15967
|
-
return { path: entry2.path, action: "keep-generated", after:
|
|
16438
|
+
return { path: entry2.path, action: "keep-generated", after: current2 };
|
|
15968
16439
|
}
|
|
15969
|
-
if (hashText(
|
|
16440
|
+
if (hashText(current2) === entry2.hash) {
|
|
15970
16441
|
const exact2 = unchanged(project, entry2);
|
|
15971
16442
|
if (exact2 !== void 0) return exact2;
|
|
15972
16443
|
}
|
|
15973
|
-
if (entry2.kind === "generated") return { path: entry2.path, action: "keep-edited", after:
|
|
15974
|
-
const after = structural(entry2,
|
|
16444
|
+
if (entry2.kind === "generated") return { path: entry2.path, action: "keep-edited", after: current2 };
|
|
16445
|
+
const after = structural(entry2, current2);
|
|
15975
16446
|
return { path: entry2.path, action: after === void 0 ? "delete" : "unmerge", after };
|
|
15976
16447
|
};
|
|
15977
16448
|
var planRemoval = (project, lock, options) => ({
|
|
@@ -16136,6 +16607,7 @@ var REGISTRY = [
|
|
|
16136
16607
|
taskDoneCommand,
|
|
16137
16608
|
secretsScanCommand,
|
|
16138
16609
|
secretsAcceptCommand,
|
|
16610
|
+
policyFalsePositiveCommand,
|
|
16139
16611
|
secretsIncidentCommand,
|
|
16140
16612
|
proseCheckCommand,
|
|
16141
16613
|
snapshotCommand,
|
|
@@ -16147,7 +16619,7 @@ var REGISTRY = [
|
|
|
16147
16619
|
];
|
|
16148
16620
|
|
|
16149
16621
|
// src/i18n/lang-settings.ts
|
|
16150
|
-
import { join as
|
|
16622
|
+
import { join as join40 } from "node:path";
|
|
16151
16623
|
var read3 = (runtime, path) => {
|
|
16152
16624
|
const text6 = runtime.readText(toAbsolutePath(path));
|
|
16153
16625
|
return text6.ok ? text6.value : void 0;
|
|
@@ -16162,14 +16634,14 @@ var languageIn = (text6) => {
|
|
|
16162
16634
|
const value = parsed.ok && isRecord(parsed.value) ? parsed.value["language"] : void 0;
|
|
16163
16635
|
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
16164
16636
|
};
|
|
16165
|
-
var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ??
|
|
16637
|
+
var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join40(runtime.homeDir(), ".claude");
|
|
16166
16638
|
var langSettings = (runtime, cwd, env, platform) => {
|
|
16167
16639
|
const root = findGitRoot(cwd, runtime) ?? cwd;
|
|
16168
|
-
const config = parseConfigYaml(read3(runtime,
|
|
16640
|
+
const config = parseConfigYaml(read3(runtime, join40(root, CONFIG_FILE)) ?? "");
|
|
16169
16641
|
const claudeFiles = [
|
|
16170
|
-
|
|
16171
|
-
|
|
16172
|
-
|
|
16642
|
+
join40(root, ".claude", "settings.local.json"),
|
|
16643
|
+
join40(root, ".claude", "settings.json"),
|
|
16644
|
+
join40(claudeDir(runtime, env), "settings.json")
|
|
16173
16645
|
];
|
|
16174
16646
|
return {
|
|
16175
16647
|
config: configText(config, "lang"),
|