finitemonkey 0.1.0-next.19 → 0.1.0-next.20

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.
Files changed (2) hide show
  1. package/dist/finitemonkey.mjs +737 -360
  2. package/package.json +1 -1
@@ -810,8 +810,8 @@ var parsePreset = (raw) => raw === void 0 ? "neutral" : PRESETS.find((preset) =>
810
810
  var COMMITTED_STATE = ["config.yml", "runtime/", "tasks/", "rules.yml", "policy.yml", "findings/"];
811
811
 
812
812
  // src/install/lock-file.ts
813
- var ENTRY_KINDS = ["settings", "gitignore", "generated", "markdown"];
814
- var strings = (value) => Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
813
+ var ENTRY_KINDS = ["settings", "gitignore", "generated", "markdown", "githook"];
814
+ var strings = (value) => Array.isArray(value) && value.every((item2) => typeof item2 === "string") ? value : void 0;
815
815
  var parseAdded = (value) => {
816
816
  if (!isRecord(value)) return void 0;
817
817
  const deny2 = strings(value["deny"]);
@@ -828,7 +828,7 @@ var parseAdded = (value) => {
828
828
  var parseEntry = (value) => {
829
829
  if (!isRecord(value)) return void 0;
830
830
  const { path, kind, existed, hash, backup } = value;
831
- const known2 = ENTRY_KINDS.find((item) => item === kind);
831
+ const known2 = ENTRY_KINDS.find((item2) => item2 === kind);
832
832
  if (typeof path !== "string" || known2 === void 0 || typeof existed !== "boolean" || typeof hash !== "string") {
833
833
  return void 0;
834
834
  }
@@ -851,7 +851,7 @@ var parseEntries = (value) => {
851
851
  };
852
852
  var fromRecord = (record3) => {
853
853
  const { schema, version, preset, installedAt, updatedAt } = record3;
854
- const known2 = PRESETS.find((item) => item === preset);
854
+ const known2 = PRESETS.find((item2) => item2 === preset);
855
855
  const entries = parseEntries(record3["entries"]);
856
856
  const createdDirs = strings(record3["createdDirs"]);
857
857
  if (schema !== LOCK_SCHEMA) return reject(`schema is not ${LOCK_SCHEMA}`);
@@ -870,134 +870,6 @@ var parseLock = (text6) => {
870
870
  var renderLock = (lock) => `${JSON.stringify(lock, null, 2)}
871
871
  `;
872
872
 
873
- // src/policy/policy.ts
874
- import { join as join3 } from "node:path";
875
-
876
- // src/git/project-config.ts
877
- import { join as join2 } from "node:path";
878
- var CONFIG_FILE2 = ".finitemonkey/config.yml";
879
- var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
880
- var stripComment = (line2) => {
881
- let quote;
882
- for (let index = 0; index < line2.length; index += 1) {
883
- const char = line2.charAt(index);
884
- if (quote === void 0 && (char === '"' || char === "'")) quote = char;
885
- else if (char === quote) quote = void 0;
886
- else if (quote === void 0 && char === "#" && (index === 0 || /\s/.test(line2.charAt(index - 1)))) {
887
- return line2.slice(0, index);
888
- }
889
- }
890
- return line2;
891
- };
892
- var flowList = (value) => {
893
- const trimmed = value.trim();
894
- if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
895
- return trimmed.slice(1, -1).split(",").map(unquote).filter((item) => item !== "");
896
- };
897
- var keyValue = (body2) => {
898
- const match = /^("[^"]*"|'[^']*'|[^:]+?)\s*:(?:\s+(.*))?$/.exec(body2);
899
- if (match === null) return void 0;
900
- return { key: unquote(match[1] ?? ""), value: match[2] ?? "" };
901
- };
902
- var addItem = (frame, item) => {
903
- if (frame.key === void 0) return;
904
- const parent = frame.tree[frame.key];
905
- if (Array.isArray(parent)) parent.push(item);
906
- else frame.tree[frame.key] = [item];
907
- };
908
- var placeLine = (stack, indent, body2) => {
909
- while (stack.length > 1 && (stack[stack.length - 1]?.indent ?? 0) >= indent && !body2.startsWith("- ")) stack.pop();
910
- const top = stack[stack.length - 1];
911
- if (top === void 0) return;
912
- if (body2.startsWith("- ")) {
913
- addItem(top, unquote(body2.slice(2)));
914
- return;
915
- }
916
- const pair = keyValue(body2);
917
- if (pair === void 0) return;
918
- const parentTree = top.key === void 0 ? top.tree : ensureTree(top.tree, top.key);
919
- if (pair.value === "") {
920
- stack.push({ indent, tree: parentTree, key: pair.key });
921
- return;
922
- }
923
- parentTree[pair.key] = flowList(pair.value) ?? unquote(pair.value);
924
- };
925
- var ensureTree = (tree, key) => {
926
- const existing = tree[key];
927
- if (existing !== void 0 && typeof existing !== "string" && !Array.isArray(existing)) return existing;
928
- const fresh3 = {};
929
- tree[key] = fresh3;
930
- return fresh3;
931
- };
932
- var parseConfigYaml = (text6) => {
933
- const root = {};
934
- const stack = [{ indent: -1, tree: root, key: void 0 }];
935
- for (const raw of text6.split(/\r?\n/)) {
936
- const line2 = stripComment(raw).trimEnd();
937
- if (line2.trim() === "" || line2.trim() === "---") continue;
938
- const indent = line2.length - line2.trimStart().length;
939
- placeLine(stack, indent, line2.trim());
940
- }
941
- return root;
942
- };
943
- var isTree = (node) => typeof node === "object" && !Array.isArray(node);
944
- var configAt = (tree, ...path) => path.reduce((node, key) => isTree(node) ? node[key] : void 0, tree);
945
- var configList = (tree, ...path) => {
946
- const node = configAt(tree, ...path);
947
- if (typeof node === "string") return node === "" ? [] : [node];
948
- return Array.isArray(node) ? node.filter((item) => typeof item === "string") : [];
949
- };
950
- var configMap = (tree, ...path) => {
951
- const node = configAt(tree, ...path);
952
- if (!isTree(node)) return {};
953
- return Object.fromEntries(
954
- Object.entries(node).filter((entry2) => typeof entry2[1] === "string")
955
- );
956
- };
957
- var loadProjectConfig = (runtime, root) => {
958
- const text6 = runtime.readText(toAbsolutePath(join2(root, CONFIG_FILE2)));
959
- return text6.ok ? parseConfigYaml(text6.value) : {};
960
- };
961
-
962
- // src/policy/policy.ts
963
- var POLICY_FILE = ".finitemonkey/policy.yml";
964
- var GUARD_MODES = ["off", "shadow", "active"];
965
- var LEVELS = ["light", "standard", "strict"];
966
- var SEVERITIES = ["low", "medium", "high"];
967
- var DEFAULT_PROMOTE_AFTER = 20;
968
- var OPTIONAL_GUARDS = ["prose"];
969
- var DEFAULT_POLICY = {
970
- exists: false,
971
- level: "standard",
972
- severity: "medium",
973
- promoteAfter: DEFAULT_PROMOTE_AFTER,
974
- guards: {}
975
- };
976
- var pick = (options, value, fallback) => options.find((option) => option === value) ?? fallback;
977
- var parsePolicy = (text6) => {
978
- const tree = parseConfigYaml(text6);
979
- const raw = configAt(tree, "promoteAfter");
980
- const promoteAfter = typeof raw === "string" ? Number.parseInt(raw, 10) : Number.NaN;
981
- const guards = Object.fromEntries(
982
- Object.entries(configMap(tree, "guards")).flatMap(([id, mode]) => {
983
- const known2 = GUARD_MODES.find((option) => option === mode);
984
- return known2 === void 0 ? [] : [[id, known2]];
985
- })
986
- );
987
- return {
988
- exists: true,
989
- level: pick(LEVELS, configAt(tree, "level"), DEFAULT_POLICY.level),
990
- severity: pick(SEVERITIES, configAt(tree, "severity"), DEFAULT_POLICY.severity),
991
- promoteAfter: Number.isInteger(promoteAfter) && promoteAfter > 0 ? promoteAfter : DEFAULT_PROMOTE_AFTER,
992
- guards
993
- };
994
- };
995
- var policyPath = (root) => toAbsolutePath(join3(root, POLICY_FILE));
996
- var loadPolicy = (runtime, root) => {
997
- const text6 = runtime.readText(policyPath(root));
998
- return text6.ok ? parsePolicy(text6.value) : DEFAULT_POLICY;
999
- };
1000
-
1001
873
  // src/install/managed-block.ts
1002
874
  var lineBreak = (text6) => text6.includes("\r\n") ? "\r\n" : "\n";
1003
875
  var blockRange = (lines3, markers) => {
@@ -1147,7 +1019,7 @@ var hookHandler = (runner, spec2, failClosedSupported) => {
1147
1019
  };
1148
1020
  var OWN_SHELL = /(^|[\s/"'])finitemonkey(@[^\s"]+|\.mjs|\.cmd|\.exe)?"?\s+hook\s/;
1149
1021
  var OWN_SCRIPT = /finitemonkey(\.mjs|\.cmd|\.exe)?$/;
1150
- var stringsOf = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
1022
+ var stringsOf = (value) => Array.isArray(value) ? value.filter((item2) => typeof item2 === "string") : [];
1151
1023
  var isOwnHandler = (handler) => {
1152
1024
  if (!isRecord(handler)) return false;
1153
1025
  const command = handler["command"];
@@ -1187,6 +1059,218 @@ var verifySkillTemplate = (runner, lang) => {
1187
1059
  };
1188
1060
  var isOwnSkill = hasMarker;
1189
1061
 
1062
+ // src/githooks/install.ts
1063
+ var HOOK_MARKERS = { begin: BLOCK_BEGIN, end: BLOCK_END };
1064
+ var GIT_HOOK_NAMES = ["pre-commit", "commit-msg", "pre-push"];
1065
+ var LEFTHOOK_FILES = ["lefthook.yml", "lefthook.yaml", ".lefthook.yml", ".lefthook.yaml"];
1066
+ var PRE_COMMIT_FILE = ".pre-commit-config.yaml";
1067
+ var HOOK_ARGS = {
1068
+ "pre-commit": "secrets scan --staged",
1069
+ "commit-msg": 'commit-msg "$1"',
1070
+ "pre-push": 'push check "$1"'
1071
+ };
1072
+ var item = (path, before, after, proposal) => {
1073
+ const base = { path, kind: "githook", before };
1074
+ if (proposal !== void 0) return { ...base, action: "skip-foreign", after: before ?? "", proposal };
1075
+ if (before === void 0) return { ...base, action: "create", after };
1076
+ return { ...base, action: before === after ? "unchanged" : "merge", after };
1077
+ };
1078
+ var huskyItems = (project, runner) => GIT_HOOK_NAMES.map((hook) => {
1079
+ const path = `.husky/${hook}`;
1080
+ const before = project.read(path);
1081
+ return item(path, before, withManagedBlock(before, HOOK_MARKERS, [cliCommand(runner, HOOK_ARGS[hook])]));
1082
+ });
1083
+ var lefthookBody = (runner) => [
1084
+ "pre-commit:",
1085
+ " commands:",
1086
+ " finitemonkey-secrets:",
1087
+ ` run: ${cliCommand(runner, "secrets scan --staged")}`,
1088
+ "commit-msg:",
1089
+ " commands:",
1090
+ " finitemonkey-commit-msg:",
1091
+ ` run: ${cliCommand(runner, "commit-msg {1}")}`,
1092
+ "pre-push:",
1093
+ " commands:",
1094
+ " finitemonkey-push:",
1095
+ ` run: ${cliCommand(runner, "push check {1}")}`,
1096
+ " use_stdin: true"
1097
+ ];
1098
+ var definesHook = (text6) => GIT_HOOK_NAMES.some((hook) => new RegExp(`^${hook}:`, "m").test(withoutManagedBlock(text6, HOOK_MARKERS)));
1099
+ var lefthookItem = (project, runner, path) => {
1100
+ const before = project.read(path);
1101
+ const after = withManagedBlock(before, HOOK_MARKERS, lefthookBody(runner));
1102
+ return before !== void 0 && definesHook(before) ? item(path, before, after, after) : item(path, before, after);
1103
+ };
1104
+ var preCommitBody = (runner, indent) => {
1105
+ const hook = (id, args2, stage) => [
1106
+ `${indent} - id: ${id}`,
1107
+ `${indent} name: ${id}`,
1108
+ `${indent} entry: ${cliCommand(runner, args2)}`,
1109
+ `${indent} language: system`,
1110
+ `${indent} pass_filenames: ${stage === "commit-msg" ? "true" : "false"}`,
1111
+ `${indent} always_run: true`,
1112
+ `${indent} stages: [${stage}]`
1113
+ ];
1114
+ return [
1115
+ `${indent}- repo: local`,
1116
+ `${indent} hooks:`,
1117
+ ...hook("finitemonkey-secrets", "secrets scan --staged", "pre-commit"),
1118
+ ...hook("finitemonkey-commit-msg", "commit-msg", "commit-msg"),
1119
+ ...hook("finitemonkey-push", "push check", "pre-push")
1120
+ ];
1121
+ };
1122
+ var TOP_KEY = /^[A-Za-z_][\w-]*:/;
1123
+ var reposIsLast = (text6) => {
1124
+ const keys = withoutManagedBlock(text6, HOOK_MARKERS).split(/\r?\n/).filter((line2) => TOP_KEY.test(line2));
1125
+ return keys.at(-1)?.startsWith("repos:") === true;
1126
+ };
1127
+ var preCommitItem = (project, runner) => {
1128
+ const before = project.read(PRE_COMMIT_FILE) ?? "";
1129
+ const indent = /^( *)- repo:/m.exec(before)?.[1] ?? " ";
1130
+ const after = withManagedBlock(before, HOOK_MARKERS, preCommitBody(runner, indent));
1131
+ return reposIsLast(before) ? item(PRE_COMMIT_FILE, before, after) : item(PRE_COMMIT_FILE, before, after, after);
1132
+ };
1133
+ var usesHusky = (project) => {
1134
+ if (project.isDir(".husky")) return true;
1135
+ return /"husky"\s*:/.test(project.read("package.json") ?? "");
1136
+ };
1137
+ var gitHookItems = (project, runner) => {
1138
+ const lefthook = LEFTHOOK_FILES.find((path) => project.exists(path));
1139
+ return [
1140
+ ...usesHusky(project) ? huskyItems(project, runner) : [],
1141
+ ...lefthook === void 0 ? [] : [lefthookItem(project, runner, lefthook)],
1142
+ ...project.exists(PRE_COMMIT_FILE) ? [preCommitItem(project, runner)] : []
1143
+ ];
1144
+ };
1145
+
1146
+ // src/policy/policy.ts
1147
+ import { join as join3 } from "node:path";
1148
+
1149
+ // src/git/project-config.ts
1150
+ import { join as join2 } from "node:path";
1151
+ var CONFIG_FILE2 = ".finitemonkey/config.yml";
1152
+ var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
1153
+ var stripComment = (line2) => {
1154
+ let quote;
1155
+ for (let index = 0; index < line2.length; index += 1) {
1156
+ const char = line2.charAt(index);
1157
+ if (quote === void 0 && (char === '"' || char === "'")) quote = char;
1158
+ else if (char === quote) quote = void 0;
1159
+ else if (quote === void 0 && char === "#" && (index === 0 || /\s/.test(line2.charAt(index - 1)))) {
1160
+ return line2.slice(0, index);
1161
+ }
1162
+ }
1163
+ return line2;
1164
+ };
1165
+ var flowList = (value) => {
1166
+ const trimmed = value.trim();
1167
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
1168
+ return trimmed.slice(1, -1).split(",").map(unquote).filter((item2) => item2 !== "");
1169
+ };
1170
+ var keyValue = (body2) => {
1171
+ const match = /^("[^"]*"|'[^']*'|[^:]+?)\s*:(?:\s+(.*))?$/.exec(body2);
1172
+ if (match === null) return void 0;
1173
+ return { key: unquote(match[1] ?? ""), value: match[2] ?? "" };
1174
+ };
1175
+ var addItem = (frame, item2) => {
1176
+ if (frame.key === void 0) return;
1177
+ const parent = frame.tree[frame.key];
1178
+ if (Array.isArray(parent)) parent.push(item2);
1179
+ else frame.tree[frame.key] = [item2];
1180
+ };
1181
+ var placeLine = (stack, indent, body2) => {
1182
+ while (stack.length > 1 && (stack[stack.length - 1]?.indent ?? 0) >= indent && !body2.startsWith("- ")) stack.pop();
1183
+ const top = stack[stack.length - 1];
1184
+ if (top === void 0) return;
1185
+ if (body2.startsWith("- ")) {
1186
+ addItem(top, unquote(body2.slice(2)));
1187
+ return;
1188
+ }
1189
+ const pair = keyValue(body2);
1190
+ if (pair === void 0) return;
1191
+ const parentTree = top.key === void 0 ? top.tree : ensureTree(top.tree, top.key);
1192
+ if (pair.value === "") {
1193
+ stack.push({ indent, tree: parentTree, key: pair.key });
1194
+ return;
1195
+ }
1196
+ parentTree[pair.key] = flowList(pair.value) ?? unquote(pair.value);
1197
+ };
1198
+ var ensureTree = (tree, key) => {
1199
+ const existing = tree[key];
1200
+ if (existing !== void 0 && typeof existing !== "string" && !Array.isArray(existing)) return existing;
1201
+ const fresh3 = {};
1202
+ tree[key] = fresh3;
1203
+ return fresh3;
1204
+ };
1205
+ var parseConfigYaml = (text6) => {
1206
+ const root = {};
1207
+ const stack = [{ indent: -1, tree: root, key: void 0 }];
1208
+ for (const raw of text6.split(/\r?\n/)) {
1209
+ const line2 = stripComment(raw).trimEnd();
1210
+ if (line2.trim() === "" || line2.trim() === "---") continue;
1211
+ const indent = line2.length - line2.trimStart().length;
1212
+ placeLine(stack, indent, line2.trim());
1213
+ }
1214
+ return root;
1215
+ };
1216
+ var isTree = (node) => typeof node === "object" && !Array.isArray(node);
1217
+ var configAt = (tree, ...path) => path.reduce((node, key) => isTree(node) ? node[key] : void 0, tree);
1218
+ var configList = (tree, ...path) => {
1219
+ const node = configAt(tree, ...path);
1220
+ if (typeof node === "string") return node === "" ? [] : [node];
1221
+ return Array.isArray(node) ? node.filter((item2) => typeof item2 === "string") : [];
1222
+ };
1223
+ var configMap = (tree, ...path) => {
1224
+ const node = configAt(tree, ...path);
1225
+ if (!isTree(node)) return {};
1226
+ return Object.fromEntries(
1227
+ Object.entries(node).filter((entry2) => typeof entry2[1] === "string")
1228
+ );
1229
+ };
1230
+ var loadProjectConfig = (runtime, root) => {
1231
+ const text6 = runtime.readText(toAbsolutePath(join2(root, CONFIG_FILE2)));
1232
+ return text6.ok ? parseConfigYaml(text6.value) : {};
1233
+ };
1234
+
1235
+ // src/policy/policy.ts
1236
+ var POLICY_FILE = ".finitemonkey/policy.yml";
1237
+ var GUARD_MODES = ["off", "shadow", "active"];
1238
+ var LEVELS = ["light", "standard", "strict"];
1239
+ var SEVERITIES = ["low", "medium", "high"];
1240
+ var DEFAULT_PROMOTE_AFTER = 20;
1241
+ var OPTIONAL_GUARDS = ["prose"];
1242
+ var DEFAULT_POLICY = {
1243
+ exists: false,
1244
+ level: "standard",
1245
+ severity: "medium",
1246
+ promoteAfter: DEFAULT_PROMOTE_AFTER,
1247
+ guards: {}
1248
+ };
1249
+ var pick = (options, value, fallback) => options.find((option) => option === value) ?? fallback;
1250
+ var parsePolicy = (text6) => {
1251
+ const tree = parseConfigYaml(text6);
1252
+ const raw = configAt(tree, "promoteAfter");
1253
+ const promoteAfter = typeof raw === "string" ? Number.parseInt(raw, 10) : Number.NaN;
1254
+ const guards = Object.fromEntries(
1255
+ Object.entries(configMap(tree, "guards")).flatMap(([id, mode]) => {
1256
+ const known2 = GUARD_MODES.find((option) => option === mode);
1257
+ return known2 === void 0 ? [] : [[id, known2]];
1258
+ })
1259
+ );
1260
+ return {
1261
+ exists: true,
1262
+ level: pick(LEVELS, configAt(tree, "level"), DEFAULT_POLICY.level),
1263
+ severity: pick(SEVERITIES, configAt(tree, "severity"), DEFAULT_POLICY.severity),
1264
+ promoteAfter: Number.isInteger(promoteAfter) && promoteAfter > 0 ? promoteAfter : DEFAULT_PROMOTE_AFTER,
1265
+ guards
1266
+ };
1267
+ };
1268
+ var policyPath = (root) => toAbsolutePath(join3(root, POLICY_FILE));
1269
+ var loadPolicy = (runtime, root) => {
1270
+ const text6 = runtime.readText(policyPath(root));
1271
+ return text6.ok ? parsePolicy(text6.value) : DEFAULT_POLICY;
1272
+ };
1273
+
1190
1274
  // src/install/agents-block.ts
1191
1275
  var MARKDOWN_MARKERS = { begin: MD_BLOCK_BEGIN, end: MD_BLOCK_END };
1192
1276
  var IMPORT_LINE = /^@AGENTS\.md[ \t]*$/m;
@@ -1461,26 +1545,26 @@ var pendingMigrations = (migrations, from, to) => {
1461
1545
  return version === void 0 ? [] : [{ migration, version }];
1462
1546
  }).filter(({ version }) => compareSemver(version, low) > 0 && compareSemver(version, high) <= 0).sort((a, b) => compareSemver(a.version, b.version)).map(({ migration }) => migration);
1463
1547
  };
1464
- var migrateItem = (item, previous, steps) => {
1465
- if (steps.length === 0 || item.before === void 0) return item;
1466
- const migrated = steps.reduce((text6, step3) => step3(text6), item.after);
1467
- if (migrated === item.after) return item;
1468
- const recorded = previous.entries.find((entry2) => entry2.path === item.path)?.hash;
1469
- if (recorded !== hashText(item.before)) {
1470
- return { ...item, action: "keep-edited", after: item.before, proposal: migrated };
1548
+ var migrateItem = (item2, previous, steps) => {
1549
+ if (steps.length === 0 || item2.before === void 0) return item2;
1550
+ const migrated = steps.reduce((text6, step3) => step3(text6), item2.after);
1551
+ if (migrated === item2.after) return item2;
1552
+ const recorded = previous.entries.find((entry2) => entry2.path === item2.path)?.hash;
1553
+ if (recorded !== hashText(item2.before)) {
1554
+ return { ...item2, action: "keep-edited", after: item2.before, proposal: migrated };
1471
1555
  }
1472
- return { ...item, action: migrated === item.before ? "unchanged" : "update", after: migrated };
1556
+ return { ...item2, action: migrated === item2.before ? "unchanged" : "update", after: migrated };
1473
1557
  };
1474
1558
  var applyMigrations = (items, previous, version, migrations) => {
1475
1559
  if (previous === void 0) return items;
1476
1560
  const pending = pendingMigrations(migrations, previous.version, version);
1477
1561
  if (pending.length === 0) return items;
1478
1562
  return items.map(
1479
- (item) => migrateItem(
1480
- item,
1563
+ (item2) => migrateItem(
1564
+ item2,
1481
1565
  previous,
1482
1566
  pending.flatMap(
1483
- (migration) => migration.files.filter((file) => file.path === item.path).map((file) => file.migrate)
1567
+ (migration) => migration.files.filter((file) => file.path === item2.path).map((file) => file.migrate)
1484
1568
  )
1485
1569
  )
1486
1570
  );
@@ -1536,8 +1620,8 @@ var mergeHooks = (hooks, isOwn, wanted) => {
1536
1620
  const entries = events.flatMap((event) => {
1537
1621
  const groups = hooks[event];
1538
1622
  if (groups !== void 0 && !Array.isArray(groups)) return [[event, groups]];
1539
- const merged = mergeEvent(asList(groups), isOwn, wanted[event]);
1540
- return merged.length === 0 ? [] : [[event, merged]];
1623
+ const merged2 = mergeEvent(asList(groups), isOwn, wanted[event]);
1624
+ return merged2.length === 0 ? [] : [[event, merged2]];
1541
1625
  });
1542
1626
  return Object.fromEntries(entries);
1543
1627
  };
@@ -1673,15 +1757,15 @@ var settingsItem = (project, detection, input, previous) => {
1673
1757
  marketplace: input.marketplace,
1674
1758
  language: input.claudeLanguage
1675
1759
  };
1676
- const merged = mergeSettings(current2.value, wanted, previous?.handlers ?? []);
1677
- const after = renderJson(merged.settings, before === void 0 ? DEFAULT_STYLE : styleOf(before));
1760
+ const merged2 = mergeSettings(current2.value, wanted, previous?.handlers ?? []);
1761
+ const after = renderJson(merged2.settings, before === void 0 ? DEFAULT_STYLE : styleOf(before));
1678
1762
  return accept({
1679
1763
  path: SETTINGS_FILE,
1680
1764
  kind: "settings",
1681
1765
  action: actionFor(before, after, "merge"),
1682
1766
  before,
1683
1767
  after,
1684
- added: joinAdded(previous?.added, merged.added),
1768
+ added: joinAdded(previous?.added, merged2.added),
1685
1769
  handlers: Object.values(hooks)
1686
1770
  });
1687
1771
  };
@@ -1733,7 +1817,13 @@ var buildPlan = (project, detection, input, previous, migrations = MIGRATIONS) =
1733
1817
  detection,
1734
1818
  preset: input.preset,
1735
1819
  items: applyMigrations(
1736
- [settings.value, gitignoreItem(project), ...markdownItems(project, detection.runner, input.lang), ...generated],
1820
+ [
1821
+ settings.value,
1822
+ gitignoreItem(project),
1823
+ ...markdownItems(project, detection.runner, input.lang),
1824
+ ...generated,
1825
+ ...gitHookItems(project, detection.runner)
1826
+ ],
1737
1827
  previous,
1738
1828
  input.version,
1739
1829
  migrations
@@ -1741,7 +1831,7 @@ var buildPlan = (project, detection, input, previous, migrations = MIGRATIONS) =
1741
1831
  previous
1742
1832
  });
1743
1833
  };
1744
- var hasChanges = (plan) => plan.items.some((item) => CHANGING.includes(item.action));
1834
+ var hasChanges = (plan) => plan.items.some((item2) => CHANGING.includes(item2.action));
1745
1835
  var needsWrite = (plan) => hasChanges(plan) || plan.previous === void 0;
1746
1836
 
1747
1837
  // src/install/apply.ts
@@ -1753,33 +1843,33 @@ var writeText = (project, relative9, text6) => {
1753
1843
  return written.ok ? accept(void 0) : reject({ path: relative9, detail: written.error });
1754
1844
  };
1755
1845
  var restore = (project, items) => {
1756
- for (const item of items) {
1757
- if (item.before === void 0) project.runtime.remove(project.abs(item.path));
1758
- else project.runtime.writeAtomic(project.abs(item.path), item.before);
1846
+ for (const item2 of items) {
1847
+ if (item2.before === void 0) project.runtime.remove(project.abs(item2.path));
1848
+ else project.runtime.writeAtomic(project.abs(item2.path), item2.before);
1759
1849
  }
1760
1850
  };
1761
1851
  var backupItems = (project, dir, items) => {
1762
- const manifest = items.map((item) => ({ path: item.path, hash: hashText(item.before ?? "") }));
1763
- for (const item of items) {
1764
- const saved = writeText(project, `${dir}/${item.path}`, item.before ?? "");
1852
+ const manifest = items.map((item2) => ({ path: item2.path, hash: hashText(item2.before ?? "") }));
1853
+ for (const item2 of items) {
1854
+ const saved = writeText(project, `${dir}/${item2.path}`, item2.before ?? "");
1765
1855
  if (!saved.ok) return saved;
1766
1856
  }
1767
1857
  return writeText(project, `${dir}/manifest.json`, `${JSON.stringify(manifest, null, 2)}
1768
1858
  `);
1769
1859
  };
1770
- var entryOf = (plan, item, backupDir) => {
1771
- const previous = plan.previous?.entries.find((entry2) => entry2.path === item.path);
1772
- const existed = previous?.existed ?? item.before !== void 0;
1773
- const backup = previous?.backup ?? (existed && backupDir !== void 0 ? `${backupDir}/${item.path}` : void 0);
1774
- const hash = item.action === "keep-edited" ? previous?.hash ?? hashText(item.proposal ?? item.after) : hashText(item.after);
1860
+ var entryOf = (plan, item2, backupDir) => {
1861
+ const previous = plan.previous?.entries.find((entry2) => entry2.path === item2.path);
1862
+ const existed = previous?.existed ?? item2.before !== void 0;
1863
+ const backup = previous?.backup ?? (existed && backupDir !== void 0 ? `${backupDir}/${item2.path}` : void 0);
1864
+ const hash = item2.action === "keep-edited" ? previous?.hash ?? hashText(item2.proposal ?? item2.after) : hashText(item2.after);
1775
1865
  return {
1776
- path: item.path,
1777
- kind: item.kind,
1866
+ path: item2.path,
1867
+ kind: item2.kind,
1778
1868
  existed,
1779
1869
  hash,
1780
1870
  ...backup === void 0 ? {} : { backup },
1781
- ...item.added === void 0 ? {} : { added: item.added },
1782
- ...item.handlers === void 0 ? {} : { handlers: item.handlers }
1871
+ ...item2.added === void 0 ? {} : { added: item2.added },
1872
+ ...item2.handlers === void 0 ? {} : { handlers: item2.handlers }
1783
1873
  };
1784
1874
  };
1785
1875
  var nextLock = (plan, version, now, backupDir, createdDirs, only) => ({
@@ -1789,22 +1879,22 @@ var nextLock = (plan, version, now, backupDir, createdDirs, only) => ({
1789
1879
  installedAt: plan.previous?.installedAt ?? now,
1790
1880
  updatedAt: now,
1791
1881
  createdDirs: [.../* @__PURE__ */ new Set([...plan.previous?.createdDirs ?? [], ...createdDirs])],
1792
- entries: plan.items.filter((item) => item.action !== "skip-foreign").flatMap((item) => {
1793
- if (only === void 0 || only.includes(item.path)) return [entryOf(plan, item, backupDir)];
1794
- const kept = plan.previous?.entries.find((entry2) => entry2.path === item.path);
1882
+ entries: plan.items.filter((item2) => item2.action !== "skip-foreign").flatMap((item2) => {
1883
+ if (only === void 0 || only.includes(item2.path)) return [entryOf(plan, item2, backupDir)];
1884
+ const kept = plan.previous?.entries.find((entry2) => entry2.path === item2.path);
1795
1885
  return kept === void 0 ? [] : [kept];
1796
1886
  })
1797
1887
  });
1798
- var needsBackup = (plan, item) => item.before !== void 0 && plan.previous?.entries.find((entry2) => entry2.path === item.path)?.backup === void 0;
1888
+ var needsBackup = (plan, item2) => item2.before !== void 0 && plan.previous?.entries.find((entry2) => entry2.path === item2.path)?.backup === void 0;
1799
1889
  var writeAll = (project, items) => {
1800
1890
  const done = [];
1801
- for (const item of items) {
1802
- const written = writeText(project, item.path, item.after);
1891
+ for (const item2 of items) {
1892
+ const written = writeText(project, item2.path, item2.after);
1803
1893
  if (!written.ok) {
1804
1894
  restore(project, done);
1805
1895
  return written;
1806
1896
  }
1807
- done.push(item);
1897
+ done.push(item2);
1808
1898
  }
1809
1899
  return accept(void 0);
1810
1900
  };
@@ -1813,9 +1903,9 @@ var createdBy = (project) => OWN_DIRS.filter((dir) => !project.exists(dir));
1813
1903
  var commit = (project, plan, version, createdDirs, only) => {
1814
1904
  const now = project.runtime.now();
1815
1905
  const changing = plan.items.filter(
1816
- (item) => CHANGING.includes(item.action) && (only === void 0 || only.includes(item.path))
1906
+ (item2) => CHANGING.includes(item2.action) && (only === void 0 || only.includes(item2.path))
1817
1907
  );
1818
- const toBackup = changing.filter((item) => needsBackup(plan, item));
1908
+ const toBackup = changing.filter((item2) => needsBackup(plan, item2));
1819
1909
  const backupDir = toBackup.length === 0 ? void 0 : `${BACKUPS_DIR}/${stamp(now)}`;
1820
1910
  if (backupDir !== void 0) {
1821
1911
  const saved = backupItems(project, backupDir, toBackup);
@@ -1829,7 +1919,7 @@ var commit = (project, plan, version, createdDirs, only) => {
1829
1919
  restore(project, changing);
1830
1920
  return locked;
1831
1921
  }
1832
- return accept({ lock, written: changing.map((item) => item.path), backup: backupDir });
1922
+ return accept({ lock, written: changing.map((item2) => item2.path), backup: backupDir });
1833
1923
  };
1834
1924
  var withInstallLock = (project, work) => {
1835
1925
  const dir = project.runtime.mkdir(project.abs(LOCKS_DIR));
@@ -1998,9 +2088,9 @@ var applyFixes = (ctx, write3) => {
1998
2088
  const input = planInputFor(ctx, previous.value.preset, void 0, false);
1999
2089
  const plan = buildPlan(project, detect(project, gitRoot2, input), input, previous.value);
2000
2090
  if (!plan.ok) return reject({ kind: "plan", error: plan.error });
2001
- const pending = plan.value.items.filter((item) => CHANGING.includes(item.action) && fixable.includes(item.path));
2091
+ const pending = plan.value.items.filter((item2) => CHANGING.includes(item2.action) && fixable.includes(item2.path));
2002
2092
  if (pending.length === 0 || !write3) {
2003
- return accept({ installed: true, written: pending.map((item) => item.path), backup: void 0 });
2093
+ return accept({ installed: true, written: pending.map((item2) => item2.path), backup: void 0 });
2004
2094
  }
2005
2095
  const applied = applyPlan(project, plan.value, ctx.version, fixable);
2006
2096
  if (!applied.ok) return reject({ kind: "apply", error: applied.error });
@@ -2870,7 +2960,7 @@ var cleanupPeriod = (env, all) => {
2870
2960
  };
2871
2961
  var PRECEDENCE = ["user", "project", "local", "managed"];
2872
2962
  var pluginOn = (all, id) => {
2873
- const ordered = PRECEDENCE.flatMap((scope2) => all.filter((item) => item.scope === scope2));
2963
+ const ordered = PRECEDENCE.flatMap((scope2) => all.filter((item2) => item2.scope === scope2));
2874
2964
  const values = ordered.map(({ settings }) => settings["enabledPlugins"]).map((plugins) => isRecord(plugins) ? plugins[id] : void 0).filter((value) => typeof value === "boolean");
2875
2965
  return values.at(-1) === true;
2876
2966
  };
@@ -3079,7 +3169,7 @@ var readState = (runtime, paths) => {
3079
3169
  const snapshot2 = raw?.["snapshot"];
3080
3170
  return {
3081
3171
  ...typeof active === "string" ? { active } : {},
3082
- done: Array.isArray(done) ? done.filter((item) => typeof item === "string") : [],
3172
+ done: Array.isArray(done) ? done.filter((item2) => typeof item2 === "string") : [],
3083
3173
  ...blocks === void 0 ? {} : { blocks },
3084
3174
  ...isRecord(snapshot2) ? { snapshot: snapshot2 } : {}
3085
3175
  };
@@ -3139,8 +3229,8 @@ var toApproval = (value) => {
3139
3229
  if (task === void 0 || check2 === void 0 || at === void 0) return void 0;
3140
3230
  return { task, check: check2, at, reason: text2(value, "reason") ?? "" };
3141
3231
  };
3142
- var list = (raw, map) => Array.isArray(raw) ? raw.flatMap((item) => {
3143
- const value = map(item);
3232
+ var list = (raw, map) => Array.isArray(raw) ? raw.flatMap((item2) => {
3233
+ const value = map(item2);
3144
3234
  return value === void 0 ? [] : [value];
3145
3235
  }) : [];
3146
3236
  var readGrants = (store, paths) => list(readJsonFile(store, paths.grants)?.["grants"], toGrant);
@@ -3846,10 +3936,10 @@ var normalizeCommand = (text6, dialect = "bash") => {
3846
3936
  return { commands: collector.out, incomplete: collector.incomplete };
3847
3937
  };
3848
3938
  var dialectOf = (toolName3) => toolName3 === "PowerShell" ? "powershell" : "bash";
3849
- var segmentsOf = (command) => normalizeCommand(command).commands.map((item) => ({
3850
- program: item.program,
3851
- args: item.args,
3852
- redirectedFrom: item.redirects.filter((redirect) => redirect.op === "<").map((redirect) => redirect.target)
3939
+ var segmentsOf = (command) => normalizeCommand(command).commands.map((item2) => ({
3940
+ program: item2.program,
3941
+ args: item2.args,
3942
+ redirectedFrom: item2.redirects.filter((redirect) => redirect.op === "<").map((redirect) => redirect.target)
3853
3943
  }));
3854
3944
 
3855
3945
  // src/guards/shell.ts
@@ -4033,7 +4123,7 @@ var targets = (input) => {
4033
4123
  };
4034
4124
  var selfGuard = (input) => {
4035
4125
  const found = targets(input);
4036
- const store = found.find((item) => item.kind === "allow_store");
4126
+ const store = found.find((item2) => item2.kind === "allow_store");
4037
4127
  const { t } = input;
4038
4128
  if (store !== void 0)
4039
4129
  return deny(
@@ -4373,8 +4463,8 @@ var CONFIG_FILE3 = "config-hooks.json";
4373
4463
  var GUARDED_SOURCES = /* @__PURE__ */ new Set(["user_settings", "project_settings", "local_settings"]);
4374
4464
  var countIn = (value) => {
4375
4465
  if (typeof value === "string") return /\bfinitemonkey\b|\bfm hook\b/.test(value) ? 1 : 0;
4376
- if (Array.isArray(value)) return value.reduce((sum, item) => sum + countIn(item), 0);
4377
- return isRecord(value) ? Object.values(value).reduce((sum, item) => sum + countIn(item), 0) : 0;
4466
+ if (Array.isArray(value)) return value.reduce((sum, item2) => sum + countIn(item2), 0);
4467
+ return isRecord(value) ? Object.values(value).reduce((sum, item2) => sum + countIn(item2), 0) : 0;
4378
4468
  };
4379
4469
  var censusOf = (text6) => {
4380
4470
  if (text6 === void 0) return { hooks: 0, disabled: false };
@@ -4799,16 +4889,16 @@ var positive2 = (record3, key, at, issues) => {
4799
4889
  };
4800
4890
  var strings2 = (value, at, issues) => {
4801
4891
  if (value === void 0 || value === null) return [];
4802
- if (Array.isArray(value) && value.every((item) => typeof item === "string")) return value;
4892
+ if (Array.isArray(value) && value.every((item2) => typeof item2 === "string")) return value;
4803
4893
  issues.push(`${at}: must be a list of text`);
4804
4894
  return [];
4805
4895
  };
4806
4896
  var oneOf = (options, value) => options.find((option) => option === value);
4807
4897
  var gitRequire = (value, at, issues) => {
4808
4898
  const items = strings2(value, at, issues);
4809
- const known2 = items.map((item) => oneOf(GIT_REQUIREMENTS, item));
4810
- if (known2.some((item) => item === void 0)) issues.push(`${at}: use ${GIT_REQUIREMENTS.join(", ")}`);
4811
- return known2.filter((item) => item !== void 0);
4899
+ const known2 = items.map((item2) => oneOf(GIT_REQUIREMENTS, item2));
4900
+ if (known2.some((item2) => item2 === void 0)) issues.push(`${at}: use ${GIT_REQUIREMENTS.join(", ")}`);
4901
+ return known2.filter((item2) => item2 !== void 0);
4812
4902
  };
4813
4903
  var appFields = (record3, kind, at, issues) => {
4814
4904
  const port = positive2(record3, "port", at, issues);
@@ -4858,8 +4948,8 @@ var acceptance = (value, issues) => {
4858
4948
  issues.push("acceptance: must list at least one check");
4859
4949
  return [];
4860
4950
  }
4861
- const items = value.map((item, index) => acceptanceItem(item, index, issues));
4862
- const checks = items.filter((item) => item !== void 0);
4951
+ const items = value.map((item2, index) => acceptanceItem(item2, index, issues));
4952
+ const checks = items.filter((item2) => item2 !== void 0);
4863
4953
  const ids2 = checks.map((check2) => check2.id);
4864
4954
  const duplicate = ids2.find((id, index) => ids2.indexOf(id) !== index);
4865
4955
  if (duplicate !== void 0) issues.push(`acceptance: duplicate id "${duplicate}"`);
@@ -4979,7 +5069,7 @@ var entry = (value) => {
4979
5069
  const exitCode = value["exitCode"];
4980
5070
  const failures = value["failures"];
4981
5071
  if (!(typeof exitCode === "number" || exitCode === null) || !Array.isArray(failures)) return void 0;
4982
- return { exitCode, failures: failures.filter((item) => typeof item === "string") };
5072
+ return { exitCode, failures: failures.filter((item2) => typeof item2 === "string") };
4983
5073
  };
4984
5074
  var readBaseline = (runtime, paths) => {
4985
5075
  const raw = readJsonFile(runtime, paths.cache(BASELINE_FILE));
@@ -4987,8 +5077,8 @@ var readBaseline = (runtime, paths) => {
4987
5077
  const checks = raw?.["checks"];
4988
5078
  if (typeof treeHash2 !== "string" || !isRecord(checks)) return void 0;
4989
5079
  const parsed = Object.entries(checks).flatMap(([id, value]) => {
4990
- const item = entry(value);
4991
- return item === void 0 ? [] : [[id, item]];
5080
+ const item2 = entry(value);
5081
+ return item2 === void 0 ? [] : [[id, item2]];
4992
5082
  });
4993
5083
  return { treeHash: treeHash2, checks: Object.fromEntries(parsed) };
4994
5084
  };
@@ -5028,8 +5118,8 @@ var resolveBase = (git, declared) => {
5028
5118
  return declared;
5029
5119
  }
5030
5120
  const remote = defaultRemoteBranch(git);
5031
- const merged = remote === void 0 ? void 0 : git.out(["merge-base", "HEAD", remote])?.trim();
5032
- return nonEmpty(merged) ?? nonEmpty(headSha(git)) ?? EMPTY_TREE;
5121
+ const merged2 = remote === void 0 ? void 0 : git.out(["merge-base", "HEAD", remote])?.trim();
5122
+ return nonEmpty(merged2) ?? nonEmpty(headSha(git)) ?? EMPTY_TREE;
5033
5123
  };
5034
5124
  var nulFields = (text6) => text6.split("\0").filter((part2) => part2 !== "");
5035
5125
  var untracked = (git) => nulFields(git.out(["ls-files", "--others", "--exclude-standard", "-z", "--", ".", EXCLUDE_FM]) ?? "");
@@ -5113,18 +5203,18 @@ var diffShape = (runtime, git, base, changes) => ({
5113
5203
  // src/gate/pr-parse.ts
5114
5204
  var FAILED = /* @__PURE__ */ new Set(["FAILURE", "CANCELLED", "TIMED_OUT", "ACTION_REQUIRED", "STARTUP_FAILURE", "ERROR"]);
5115
5205
  var PENDING = /* @__PURE__ */ new Set(["IN_PROGRESS", "QUEUED", "PENDING", "WAITING", "REQUESTED", "EXPECTED"]);
5116
- var ciState = (item) => {
5117
- const conclusion = (optionalString(item, "conclusion") ?? "").toUpperCase();
5118
- const status = (optionalString(item, "status") ?? optionalString(item, "state") ?? "").toUpperCase();
5206
+ var ciState = (item2) => {
5207
+ const conclusion = (optionalString(item2, "conclusion") ?? "").toUpperCase();
5208
+ const status = (optionalString(item2, "status") ?? optionalString(item2, "state") ?? "").toUpperCase();
5119
5209
  if (FAILED.has(conclusion) || FAILED.has(status)) return "failed";
5120
5210
  if (conclusion === "" && (PENDING.has(status) || status === "")) return "pending";
5121
5211
  if (PENDING.has(status)) return "pending";
5122
5212
  return "passed";
5123
5213
  };
5124
- var ciChecks = (value) => Array.isArray(value) ? value.flatMap((item) => {
5125
- if (!isRecord(item)) return [];
5126
- const name = optionalString(item, "name") ?? optionalString(item, "context") ?? "?";
5127
- return [{ name, state: ciState(item) }];
5214
+ var ciChecks = (value) => Array.isArray(value) ? value.flatMap((item2) => {
5215
+ if (!isRecord(item2)) return [];
5216
+ const name = optionalString(item2, "name") ?? optionalString(item2, "context") ?? "?";
5217
+ return [{ name, state: ciState(item2) }];
5128
5218
  }) : [];
5129
5219
  var parsePrView = (text6) => {
5130
5220
  const parsed = parseJsonObject(text6);
@@ -5397,7 +5487,7 @@ var maxBlocks = (env) => {
5397
5487
  var failureSignature = (report2) => sha256Hex(
5398
5488
  JSON.stringify([
5399
5489
  report2.treeHash,
5400
- report2.items.filter((item) => item.status === "failed").map((item) => [item.id, item.notes])
5490
+ report2.items.filter((item2) => item2.status === "failed").map((item2) => [item2.id, item2.notes])
5401
5491
  ])
5402
5492
  );
5403
5493
  var nextBlock = (previous, report2, env) => {
@@ -5489,12 +5579,12 @@ var PRS_AUTH_TIMEOUT_MS = 2e3;
5489
5579
  var parsePrList = (text6) => {
5490
5580
  const parsed = parseJson(text6);
5491
5581
  if (!parsed.ok || !Array.isArray(parsed.value)) return void 0;
5492
- return parsed.value.flatMap((item) => {
5493
- if (!isRecord(item) || typeof item["number"] !== "number") return [];
5494
- const mergeable = optionalString(item, "mergeable");
5495
- const status = optionalString(item, "mergeStateStatus");
5582
+ return parsed.value.flatMap((item2) => {
5583
+ if (!isRecord(item2) || typeof item2["number"] !== "number") return [];
5584
+ const mergeable = optionalString(item2, "mergeable");
5585
+ const status = optionalString(item2, "mergeStateStatus");
5496
5586
  const state = mergeable === "CONFLICTING" || status === "DIRTY" ? "conflict" : status === "BEHIND" ? "behind" : "ok";
5497
- return [{ number: item["number"], state }];
5587
+ return [{ number: item2["number"], state }];
5498
5588
  });
5499
5589
  };
5500
5590
  var cachedPrs = (runtime, paths) => {
@@ -5571,7 +5661,7 @@ var params = (value) => isRecord(value) ? Object.fromEntries(
5571
5661
  )
5572
5662
  ) : {};
5573
5663
  var notes = (value) => Array.isArray(value) ? value.flatMap(
5574
- (item) => isRecord(item) && typeof item["key"] === "string" ? [{ key: item["key"], params: params(item["params"]) }] : []
5664
+ (item2) => isRecord(item2) && typeof item2["key"] === "string" ? [{ key: item2["key"], params: params(item2["params"]) }] : []
5575
5665
  ) : [];
5576
5666
  var text3 = (record3, key) => {
5577
5667
  const value = record3[key];
@@ -5593,7 +5683,7 @@ var optional = (record3) => {
5593
5683
  ...typeof durationMs === "number" ? { durationMs } : {},
5594
5684
  ...reason === void 0 ? {} : { reason },
5595
5685
  ...releasedBy === void 0 ? {} : { releasedBy },
5596
- ...Array.isArray(failures) ? { failures: failures.filter((item) => typeof item === "string") } : {},
5686
+ ...Array.isArray(failures) ? { failures: failures.filter((item2) => typeof item2 === "string") } : {},
5597
5687
  ...typeof testCount === "number" ? { testCount } : {}
5598
5688
  };
5599
5689
  };
@@ -6206,7 +6296,14 @@ var en_default = {
6206
6296
  "secrets.live.rejected": "{host} rejected the key in ${variable} (HTTP {status}): it no longer works.",
6207
6297
  "secrets.live.unknown": "{host} answered HTTP {status}; that does not say whether the key in ${variable} works. Check it in the provider's console.",
6208
6298
  "secrets.live.unreachable": "Could not reach {host} with curl, so the key in ${variable} was not checked.",
6209
- "secrets.live.no_value": "${variable} is empty or not set in this terminal; export it first, nothing was sent"
6299
+ "secrets.live.no_value": "${variable} is empty or not set in this terminal; export it first, nothing was sent",
6300
+ "githooks.command.commit_msg": "Check a commit message: Conventional Commits header and no AI attribution (for the commit-msg git hook)",
6301
+ "githooks.command.push_check": "Check what a push sends: secrets, commit messages, merges that dropped lines and protected branches (for the pre-push git hook)",
6302
+ "githooks.command.ci_gate": "Run the same checks on a pull request's commits in CI, one line per failing check with its fix",
6303
+ "githooks.pass": "ok {check}",
6304
+ "githooks.fail": "FAIL {check}: {detail}. Fix: {fix}",
6305
+ "githooks.error.failed": "{count, plural, one {# check failed} other {# checks failed}}",
6306
+ "githooks.error.no_base": "the base {detail} does not exist here; fetch it first (actions/checkout needs fetch-depth: 0)"
6210
6307
  };
6211
6308
 
6212
6309
  // src/i18n/es.json
@@ -6777,7 +6874,14 @@ var es_default = {
6777
6874
  "secrets.live.rejected": "{host} rechazó la clave en ${variable} (HTTP {status}): ya no funciona.",
6778
6875
  "secrets.live.unknown": "{host} respondió HTTP {status}; eso no dice si la clave en ${variable} funciona. Revísala en la consola del proveedor.",
6779
6876
  "secrets.live.unreachable": "No se pudo llegar a {host} con curl, así que la clave en ${variable} no se comprobó.",
6780
- "secrets.live.no_value": "${variable} está vacía o no existe en esta terminal; expórtala antes, no se envió nada"
6877
+ "secrets.live.no_value": "${variable} está vacía o no existe en esta terminal; expórtala antes, no se envió nada",
6878
+ "githooks.command.commit_msg": "Revisa un mensaje de commit: encabezado Conventional Commits y ninguna atribución a IA (para el hook commit-msg de git)",
6879
+ "githooks.command.push_check": "Revisa lo que envía un push: secretos, mensajes de commit, merges que perdieron líneas y ramas protegidas (para el hook pre-push de git)",
6880
+ "githooks.command.ci_gate": "Ejecuta las mismas revisiones en los commits de un pull request en CI, una línea por revisión fallida con su arreglo",
6881
+ "githooks.pass": "ok {check}",
6882
+ "githooks.fail": "FALLÓ {check}: {detail}. Arreglo: {fix}",
6883
+ "githooks.error.failed": "{count, plural, one {# revisión falló} other {# revisiones fallaron}}",
6884
+ "githooks.error.no_base": "la base {detail} no existe aquí; tráela antes (actions/checkout necesita fetch-depth: 0)"
6781
6885
  };
6782
6886
 
6783
6887
  // src/i18n/pt-BR.json
@@ -7348,7 +7452,14 @@ var pt_BR_default = {
7348
7452
  "secrets.live.rejected": "{host} recusou a chave em ${variable} (HTTP {status}): ela não funciona mais.",
7349
7453
  "secrets.live.unknown": "{host} respondeu HTTP {status}; isso não diz se a chave em ${variable} funciona. Confira no painel do provedor.",
7350
7454
  "secrets.live.unreachable": "Não deu para alcançar {host} com o curl, então a chave em ${variable} não foi verificada.",
7351
- "secrets.live.no_value": "${variable} está vazia ou não existe neste terminal; exporte antes, nada foi enviado"
7455
+ "secrets.live.no_value": "${variable} está vazia ou não existe neste terminal; exporte antes, nada foi enviado",
7456
+ "githooks.command.commit_msg": "Confere uma mensagem de commit: cabeçalho Conventional Commits e nenhuma atribuição a IA (para o hook commit-msg do git)",
7457
+ "githooks.command.push_check": "Confere o que um push envia: segredos, mensagens de commit, merges que perderam linhas e branches protegidas (para o hook pre-push do git)",
7458
+ "githooks.command.ci_gate": "Roda as mesmas checagens nos commits de um pull request no CI, uma linha por checagem que falhou com o conserto",
7459
+ "githooks.pass": "ok {check}",
7460
+ "githooks.fail": "FALHOU {check}: {detail}. Conserto: {fix}",
7461
+ "githooks.error.failed": "{count, plural, one {# checagem falhou} other {# checagens falharam}}",
7462
+ "githooks.error.no_base": "a base {detail} não existe aqui; busque antes (o actions/checkout precisa de fetch-depth: 0)"
7352
7463
  };
7353
7464
 
7354
7465
  // src/i18n/review.json
@@ -7920,7 +8031,14 @@ var review_default = {
7920
8031
  "secrets.live.rejected": "machine",
7921
8032
  "secrets.live.unknown": "machine",
7922
8033
  "secrets.live.unreachable": "machine",
7923
- "secrets.live.no_value": "machine"
8034
+ "secrets.live.no_value": "machine",
8035
+ "githooks.command.commit_msg": "machine",
8036
+ "githooks.command.push_check": "machine",
8037
+ "githooks.command.ci_gate": "machine",
8038
+ "githooks.pass": "machine",
8039
+ "githooks.fail": "machine",
8040
+ "githooks.error.failed": "machine",
8041
+ "githooks.error.no_base": "machine"
7924
8042
  },
7925
8043
  es: {
7926
8044
  "command.init.summary": "machine",
@@ -8489,7 +8607,14 @@ var review_default = {
8489
8607
  "secrets.live.rejected": "machine",
8490
8608
  "secrets.live.unknown": "machine",
8491
8609
  "secrets.live.unreachable": "machine",
8492
- "secrets.live.no_value": "machine"
8610
+ "secrets.live.no_value": "machine",
8611
+ "githooks.command.commit_msg": "machine",
8612
+ "githooks.command.push_check": "machine",
8613
+ "githooks.command.ci_gate": "machine",
8614
+ "githooks.pass": "machine",
8615
+ "githooks.fail": "machine",
8616
+ "githooks.error.failed": "machine",
8617
+ "githooks.error.no_base": "machine"
8493
8618
  }
8494
8619
  };
8495
8620
 
@@ -8512,13 +8637,13 @@ var STATUS_KEYS = {
8512
8637
  unverified: "gate.status.unverified"
8513
8638
  };
8514
8639
  var isMessageKey = (key) => key in catalogs.en;
8515
- var noteText = (t, item) => maskText(isMessageKey(item.key) ? t(item.key, item.params) : item.key);
8516
- var reasonText = (t, item) => item.status === "unverified" && item.notes.length === 0 && item.reason !== void 0 && isMessageKey(`gate.reason.${item.reason}`) ? [t(`gate.reason.${item.reason}`)] : [];
8517
- var itemDetails = (t, item) => [
8518
- ...item.notes.map((entry2) => noteText(t, entry2)),
8519
- ...reasonText(t, item)
8640
+ var noteText = (t, item2) => maskText(isMessageKey(item2.key) ? t(item2.key, item2.params) : item2.key);
8641
+ var reasonText = (t, item2) => item2.status === "unverified" && item2.notes.length === 0 && item2.reason !== void 0 && isMessageKey(`gate.reason.${item2.reason}`) ? [t(`gate.reason.${item2.reason}`)] : [];
8642
+ var itemDetails = (t, item2) => [
8643
+ ...item2.notes.map((entry2) => noteText(t, entry2)),
8644
+ ...reasonText(t, item2)
8520
8645
  ];
8521
- var label = (item) => item.command === void 0 ? item.kind : `${item.kind} \`${maskText(item.command)}\``;
8646
+ var label = (item2) => item2.command === void 0 ? item2.kind : `${item2.kind} \`${maskText(item2.command)}\``;
8522
8647
  var reportLines = (t, report2) => {
8523
8648
  const width = Math.max(...Object.values(STATUS_KEYS).map((key) => t(key).length)) + 2;
8524
8649
  return [
@@ -8527,9 +8652,9 @@ var reportLines = (t, report2) => {
8527
8652
  tree: report2.treeHash
8528
8653
  }),
8529
8654
  ...report2.contractEdited ? [` ${t("gate.report.contract_edited")}`] : [],
8530
- ...report2.items.flatMap((item) => [
8531
- ` ${t(STATUS_KEYS[item.status]).padEnd(width)}${item.id} ${label(item)}`,
8532
- ...itemDetails(t, item).map((line2) => ` ${" ".repeat(width)} ${line2}`)
8655
+ ...report2.items.flatMap((item2) => [
8656
+ ` ${t(STATUS_KEYS[item2.status]).padEnd(width)}${item2.id} ${label(item2)}`,
8657
+ ...itemDetails(t, item2).map((line2) => ` ${" ".repeat(width)} ${line2}`)
8533
8658
  ]),
8534
8659
  t(`gate.verdict.${report2.verdict}`, report2.counts)
8535
8660
  ];
@@ -8546,24 +8671,24 @@ var REQUIRE_KEYS = {
8546
8671
  worktree: "gate.require.worktree",
8547
8672
  merge: "gate.require.merge"
8548
8673
  };
8549
- var requirement = (t, item) => `${item.id} ${t(REQUIRE_KEYS[item.kind] ?? "gate.require.other", { command: maskText(item.command ?? item.kind), kind: item.kind })}`;
8674
+ var requirement = (t, item2) => `${item2.id} ${t(REQUIRE_KEYS[item2.kind] ?? "gate.require.other", { command: maskText(item2.command ?? item2.kind), kind: item2.kind })}`;
8550
8675
  var CLAIM_MAX = 120;
8551
8676
  var claimOf = (message) => {
8552
8677
  const first = maskText((message ?? "").trim().split(/\n/)[0] ?? "");
8553
8678
  return first.length > CLAIM_MAX ? `${first.slice(0, CLAIM_MAX)}…` : first;
8554
8679
  };
8555
8680
  var blockReason = (t, report2, lastMessage) => {
8556
- const failing = report2.items.filter((item) => item.status === "failed");
8681
+ const failing2 = report2.items.filter((item2) => item2.status === "failed");
8557
8682
  return [
8558
8683
  t("gate.model.title", { task: report2.task, tree: report2.treeHash }),
8559
8684
  t("gate.model.claim", { claim: claimOf(lastMessage) || t("gate.model.no_claim") }),
8560
- t("gate.model.requires", { list: failing.map((item) => requirement(t, item)).join("; ") }),
8685
+ t("gate.model.requires", { list: failing2.map((item2) => requirement(t, item2)).join("; ") }),
8561
8686
  t("gate.model.not_enough", {
8562
- list: failing.map((item) => `${item.id}: ${itemDetails(t, item).join(", ")}`).join("; ")
8687
+ list: failing2.map((item2) => `${item2.id}: ${itemDetails(t, item2).join(", ")}`).join("; ")
8563
8688
  })
8564
8689
  ].join("\n");
8565
8690
  };
8566
- var unverifiedSummary = (t, report2) => report2.items.filter((item) => item.status === "unverified").map((item) => `${item.id} (${itemDetails(t, item).join(", ") || t("gate.reason.no_evidence")})`).join("; ");
8691
+ var unverifiedSummary = (t, report2) => report2.items.filter((item2) => item2.status === "unverified").map((item2) => `${item2.id} (${itemDetails(t, item2).join(", ") || t("gate.reason.no_evidence")})`).join("; ");
8567
8692
 
8568
8693
  // src/gate/types.ts
8569
8694
  var note = (key, params2 = {}) => ({ key, params: params2 });
@@ -8597,22 +8722,22 @@ var fromEvidence = (id, kind, entry2, stale) => {
8597
8722
  };
8598
8723
  };
8599
8724
  var verdictOf = (items) => {
8600
- if (items.some((item) => item.status === "failed")) return "failed";
8601
- return items.some((item) => item.status === "unverified") ? "incomplete" : "passed";
8725
+ if (items.some((item2) => item2.status === "failed")) return "failed";
8726
+ return items.some((item2) => item2.status === "unverified") ? "incomplete" : "passed";
8602
8727
  };
8603
8728
  var buildReport = (input) => {
8604
8729
  const { contract, ledger, treeHash: treeHash2 } = input;
8605
8730
  const items = contract.acceptance.map((check2) => {
8606
8731
  const command = REUSABLE_KINDS.includes(check2.kind) ? check2.run : void 0;
8607
8732
  const entry2 = latestFor(
8608
- ledger.filter((item) => item.task === contract.id),
8733
+ ledger.filter((item2) => item2.task === contract.id),
8609
8734
  check2.id,
8610
8735
  treeHash2,
8611
8736
  command
8612
8737
  );
8613
8738
  return fromEvidence(check2.id, check2.kind, entry2, hasStale(ledger, check2.id, treeHash2));
8614
8739
  });
8615
- const count4 = (status) => items.filter((item) => item.status === status).length;
8740
+ const count4 = (status) => items.filter((item2) => item2.status === status).length;
8616
8741
  return {
8617
8742
  schema: GATE_SCHEMA,
8618
8743
  task: contract.id,
@@ -8654,7 +8779,7 @@ var currentReport = (runtime, paths, sessionId, taskId) => {
8654
8779
  fastPath: true
8655
8780
  });
8656
8781
  };
8657
- var ids = (report2, status) => report2.items.filter((item) => item.status === status).map((item) => item.id).join(", ") || "-";
8782
+ var ids = (report2, status) => report2.items.filter((item2) => item2.status === status).map((item2) => item2.id).join(", ") || "-";
8658
8783
  var snapshotOf = (t, report2, grants, at) => ({
8659
8784
  at,
8660
8785
  task: report2?.task ?? "",
@@ -8662,7 +8787,7 @@ var snapshotOf = (t, report2, grants, at) => ({
8662
8787
  implicit: report2?.implicit ?? true,
8663
8788
  tree: report2?.treeHash ?? "",
8664
8789
  passed: report2 === void 0 ? "-" : ids(report2, "passed"),
8665
- failed: report2 === void 0 ? "-" : report2.items.filter((item) => item.status === "failed").map((item) => `${item.id} (${itemDetails(t, item).join(", ")})`).join("; ") || "-",
8790
+ failed: report2 === void 0 ? "-" : report2.items.filter((item2) => item2.status === "failed").map((item2) => `${item2.id} (${itemDetails(t, item2).join(", ")})`).join("; ") || "-",
8666
8791
  unverified: report2 === void 0 ? "-" : ids(report2, "unverified"),
8667
8792
  grants: grants.join(", ") || "-"
8668
8793
  });
@@ -8758,8 +8883,8 @@ var toReview = (value) => {
8758
8883
  };
8759
8884
  var readReviews = (runtime, paths) => {
8760
8885
  const raw = readJsonFile(runtime, paths.cache(REVIEWS_FILE))?.["reviews"];
8761
- return Array.isArray(raw) ? raw.flatMap((item) => {
8762
- const review2 = toReview(item);
8886
+ return Array.isArray(raw) ? raw.flatMap((item2) => {
8887
+ const review2 = toReview(item2);
8763
8888
  return review2 === void 0 ? [] : [review2];
8764
8889
  }) : [];
8765
8890
  };
@@ -9199,7 +9324,7 @@ var gitCheck = (check2, ctx) => {
9199
9324
  // src/gate/checks/manual.ts
9200
9325
  var manualCheck = (check2, ctx) => {
9201
9326
  const approval = readApprovals(ctx.runtime, ctx.paths).findLast(
9202
- (item) => item.task === ctx.contract.id && item.check === check2.id
9327
+ (item2) => item2.task === ctx.contract.id && item2.check === check2.id
9203
9328
  );
9204
9329
  if (approval === void 0) {
9205
9330
  return unverified("awaiting_person", [
@@ -9250,11 +9375,11 @@ var foundOutcome = (status, ctx) => {
9250
9375
  if (status.localHead !== void 0 && pr.headRefOid !== "" && status.localHead !== pr.headRefOid) {
9251
9376
  info.push(note("gate.pr.stale_head", { number: pr.number }));
9252
9377
  }
9253
- const failing = pr.checks.filter((check2) => check2.state === "failed");
9378
+ const failing2 = pr.checks.filter((check2) => check2.state === "failed");
9254
9379
  const pending = pr.checks.filter((check2) => check2.state === "pending");
9255
9380
  if (isConflicting(pr)) return failed([...info, ...conflictNotes(pr, ctx)]);
9256
- if (failing.length > 0)
9257
- return failed([...info, ...failing.map((check2) => note("gate.pr.ci_failed", { name: check2.name }))]);
9381
+ if (failing2.length > 0)
9382
+ return failed([...info, ...failing2.map((check2) => note("gate.pr.ci_failed", { name: check2.name }))]);
9258
9383
  if (pending.length > 0)
9259
9384
  return unverified("pending", [
9260
9385
  ...info,
@@ -9825,7 +9950,7 @@ var windowEnd = (units, from, size) => {
9825
9950
  while (to + 1 < Math.min(units.length, from + size) && units[to + 1]?.section === section) to += 1;
9826
9951
  return to;
9827
9952
  };
9828
- var windowScore = (hits, window) => hits.filter((item) => item.unit >= window.from && item.unit <= window.to).reduce((sum, item) => sum + item.weight, 0);
9953
+ var windowScore = (hits, window) => hits.filter((item2) => item2.unit >= window.from && item2.unit <= window.to).reduce((sum, item2) => sum + item2.weight, 0);
9829
9954
  var hotWindows = (hits, units, size, threshold2) => {
9830
9955
  const windows = [];
9831
9956
  for (let from = 0; from < units.length; from += 1) {
@@ -9835,12 +9960,12 @@ var hotWindows = (hits, units, size, threshold2) => {
9835
9960
  return windows;
9836
9961
  };
9837
9962
  var spanOf2 = (hits, from, to) => {
9838
- const inside = hits.filter((item) => item.unit >= from && item.unit <= to);
9839
- const lines3 = inside.map((item) => item.line);
9963
+ const inside = hits.filter((item2) => item2.unit >= from && item2.unit <= to);
9964
+ const lines3 = inside.map((item2) => item2.line);
9840
9965
  return {
9841
9966
  startLine: Math.min(...lines3),
9842
9967
  endLine: Math.max(...lines3),
9843
- score: inside.reduce((sum, item) => sum + item.weight, 0),
9968
+ score: inside.reduce((sum, item2) => sum + item2.weight, 0),
9844
9969
  hits: inside
9845
9970
  };
9846
9971
  };
@@ -9900,7 +10025,7 @@ var proseGateStatus = (runtime, root) => {
9900
10025
  if (!files.ok) return files;
9901
10026
  return accept({
9902
10027
  files: files.value,
9903
- flagged: files.value.filter((item) => isFlagged(item.report)),
10028
+ flagged: files.value.filter((item2) => isFlagged(item2.report)),
9904
10029
  blocking: projectPreset(runtime, root) === "gabriel"
9905
10030
  });
9906
10031
  };
@@ -9912,7 +10037,7 @@ var proseOutcome = (status) => {
9912
10037
  if (!status.ok) return unverified("tool_missing", [note("gate.prose.unavailable")]);
9913
10038
  const { files, flagged, blocking: blocking2 } = status.value;
9914
10039
  const key = blocking2 ? "gate.prose.finding" : "gate.prose.warning";
9915
- const notes2 = flagged.flatMap((item) => item.report.spans.map((span) => ({ file: item.file, span }))).slice(0, MAX_LISTED3).map(({ file, span }) => note(key, { file, line: span.startLine, rules: rulesOf(span) }));
10040
+ const notes2 = flagged.flatMap((item2) => item2.report.spans.map((span) => ({ file: item2.file, span }))).slice(0, MAX_LISTED3).map(({ file, span }) => note(key, { file, line: span.startLine, rules: rulesOf(span) }));
9916
10041
  if (notes2.length === 0) return passed([note("gate.prose.clean", { count: files.length })]);
9917
10042
  return blocking2 ? failed(notes2) : passed(notes2);
9918
10043
  };
@@ -9939,7 +10064,7 @@ var reviewCheck = (check2, ctx) => {
9939
10064
  return { status: "passed", notes: [note("gate.review.done", { agent: review2.agent, at: review2.at })] };
9940
10065
  }
9941
10066
  const approval = readApprovals(ctx.runtime, ctx.paths).findLast(
9942
- (item) => item.task === ctx.contract.id && item.check === check2.id
10067
+ (item2) => item2.task === ctx.contract.id && item2.check === check2.id
9943
10068
  );
9944
10069
  if (approval !== void 0) {
9945
10070
  return {
@@ -9967,7 +10092,7 @@ var parseAppCheck = (stdout) => {
9967
10092
  if (!envelope.ok) return void 0;
9968
10093
  const data = envelope.value["data"];
9969
10094
  if (!isRecord(data)) return void 0;
9970
- const kind = KINDS2.find((item) => item === data["kind"]);
10095
+ const kind = KINDS2.find((item2) => item2 === data["kind"]);
9971
10096
  const { output, reused, stopped, durationMs, exitCode, bodyExcerpt } = data;
9972
10097
  if (kind === void 0 || typeof output !== "string" || typeof reused !== "boolean" || typeof stopped !== "boolean")
9973
10098
  return void 0;
@@ -10098,8 +10223,8 @@ var parseBaseline = (raw) => {
10098
10223
  const list4 = parsed.value["entries"];
10099
10224
  if (!Array.isArray(list4)) return reject("entries must be a list");
10100
10225
  const entries = [];
10101
- for (const [index, item] of list4.entries()) {
10102
- const entry2 = parseEntry2(item);
10226
+ for (const [index, item2] of list4.entries()) {
10227
+ const entry2 = parseEntry2(item2);
10103
10228
  if (entry2 === void 0) return reject(`entry ${String(index)} is incomplete`);
10104
10229
  entries.push(entry2);
10105
10230
  }
@@ -10828,10 +10953,10 @@ var parseDropped = (text6) => {
10828
10953
  const items = [];
10829
10954
  const invalid = [];
10830
10955
  list4.forEach((value, index) => {
10831
- const item = itemOf(value);
10832
- if (item === void 0) invalid.push(invalidFinding(DROPPED_FILE, `item ${String(index)}: needs file and reason`));
10833
- else if (!REASONS.has(item.reason)) invalid.push(invalidFinding(item.file, `reason ${item.reason}`));
10834
- else items.push(item);
10956
+ const item2 = itemOf(value);
10957
+ if (item2 === void 0) invalid.push(invalidFinding(DROPPED_FILE, `item ${String(index)}: needs file and reason`));
10958
+ else if (!REASONS.has(item2.reason)) invalid.push(invalidFinding(item2.file, `reason ${item2.reason}`));
10959
+ else items.push(item2);
10835
10960
  });
10836
10961
  return accept({ items, invalid });
10837
10962
  };
@@ -10842,7 +10967,7 @@ var loadDropped = (runtime, root, file) => {
10842
10967
  return parseDropped(text6.value);
10843
10968
  };
10844
10969
  var isDropped = (finding3, items) => items.some(
10845
- (item) => item.file === finding3.file && (item.side === void 0 || finding3.side === void 0 || item.side === finding3.side) && (item.text === void 0 || normalizeLine(item.text) === finding3.text)
10970
+ (item2) => item2.file === finding3.file && (item2.side === void 0 || finding3.side === void 0 || item2.side === finding3.side) && (item2.text === void 0 || normalizeLine(item2.text) === finding3.text)
10846
10971
  );
10847
10972
 
10848
10973
  // src/git/merge/generated.ts
@@ -10913,15 +11038,15 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
10913
11038
  if (!runtime.git(["worktree", "add", "--detach", dir, commit2.value], repo.root).ok)
10914
11039
  return reject("could not check out the merge result");
10915
11040
  try {
10916
- for (const command of new Set(declared.map((item) => item.command))) {
11041
+ for (const command of new Set(declared.map((item2) => item2.command))) {
10917
11042
  const ran = runGenerator(runtime, dir, command);
10918
11043
  if (!ran.ok) return ran;
10919
11044
  }
10920
11045
  return accept(
10921
11046
  declared.flatMap(({ path }) => {
10922
11047
  const fresh3 = runtime.readText(toAbsolutePath(join24(dir, path)));
10923
- const merged = blobText(runtime, repo, commit2.value, path);
10924
- if (fresh3.ok && fresh3.value === merged) return [];
11048
+ const merged2 = blobText(runtime, repo, commit2.value, path);
11049
+ if (fresh3.ok && fresh3.value === merged2) return [];
10925
11050
  return [{ kind: "generated-differs", file: path, side: void 0, line: void 0, text: path, weak: false }];
10926
11051
  })
10927
11052
  );
@@ -10973,7 +11098,7 @@ var missingAdditions = (hunks, file, elsewhere) => {
10973
11098
  };
10974
11099
  var expectations = (file, hunks, removal) => {
10975
11100
  const base = countLines(file.base);
10976
- const addsOf = (side) => tally(hunks.filter((item) => item.side === side).flatMap((item) => item.added));
11101
+ const addsOf = (side) => tally(hunks.filter((item2) => item2.side === side).flatMap((item2) => item2.added));
10977
11102
  const ours = addsOf("ours");
10978
11103
  const theirs = addsOf("theirs");
10979
11104
  const keys = /* @__PURE__ */ new Set([...base.keys(), ...ours.keys(), ...theirs.keys(), ...removal.union.keys()]);
@@ -11187,7 +11312,7 @@ var verifyMerge = (runtime, repo, options = {}) => {
11187
11312
  ...pairs2.filter((pair) => !generated.has(pair.key)).flatMap((pair) => verifyFile(context, pair)),
11188
11313
  ...regenerated.value
11189
11314
  ];
11190
- const open = [...dropped.value.invalid, ...all.filter((item) => !isDropped(item, dropped.value.items))];
11315
+ const open = [...dropped.value.invalid, ...all.filter((item2) => !isDropped(item2, dropped.value.items))];
11191
11316
  return {
11192
11317
  schema: MERGE_VERIFY_SCHEMA,
11193
11318
  exit: open.length === 0 ? 0 : 1,
@@ -11279,8 +11404,8 @@ var recordBaseline = (runtime, repo, owner) => {
11279
11404
  var recordEdits = (runtime, repo, owner, paths) => {
11280
11405
  if (paths.length === 0) return;
11281
11406
  const known2 = readEdits(runtime, repo, owner);
11282
- const merged = [.../* @__PURE__ */ new Set([...known2, ...paths])].sort();
11283
- if (merged.length !== known2.size) write(runtime, fileOf(repo, "edits", owner), { paths: merged });
11407
+ const merged2 = [.../* @__PURE__ */ new Set([...known2, ...paths])].sort();
11408
+ if (merged2.length !== known2.size) write(runtime, fileOf(repo, "edits", owner), { paths: merged2 });
11284
11409
  };
11285
11410
  var recordShellChanges = (runtime, repo, owner) => {
11286
11411
  const entries = readDirty(runtime, repo.root);
@@ -11433,23 +11558,23 @@ var mergeCheck = (_check, ctx) => {
11433
11558
  const repo = repoOf(ctx);
11434
11559
  if (repo === void 0) return unverified("tool_missing", [note("gate.merge.unavailable")]);
11435
11560
  const items = mergeGateStatus(ctx.runtime, repo, ownerOf(ctx));
11436
- const short2 = (commit2) => commit2 === "MERGE_HEAD" ? commit2 : commit2.slice(0, 12);
11437
- const broken = items.filter((item) => item.report.exit === 2);
11561
+ const short3 = (commit2) => commit2 === "MERGE_HEAD" ? commit2 : commit2.slice(0, 12);
11562
+ const broken = items.filter((item2) => item2.report.exit === 2);
11438
11563
  if (broken.length > 0) {
11439
11564
  return unverified(
11440
11565
  "tool_missing",
11441
11566
  broken.map(
11442
- (item) => note("gate.merge.unanalyzed", { commit: short2(item.commit), detail: item.report.error ?? "" })
11567
+ (item2) => note("gate.merge.unanalyzed", { commit: short3(item2.commit), detail: item2.report.error ?? "" })
11443
11568
  )
11444
11569
  );
11445
11570
  }
11446
- const lost = items.filter((item) => item.report.exit === 1);
11571
+ const lost = items.filter((item2) => item2.report.exit === 1);
11447
11572
  if (lost.length > 0) {
11448
11573
  return failed(
11449
11574
  lost.map(
11450
- (item) => note("gate.merge.lost", {
11451
- commit: short2(item.commit),
11452
- findings: listed3(item.report.findings.map(findingLabel))
11575
+ (item2) => note("gate.merge.lost", {
11576
+ commit: short3(item2.commit),
11577
+ findings: listed3(item2.report.findings.map(findingLabel))
11453
11578
  })
11454
11579
  )
11455
11580
  );
@@ -11567,9 +11692,9 @@ var failureDetail = (t, failure4) => {
11567
11692
  });
11568
11693
  }
11569
11694
  };
11570
- var failingList = (t, report2) => report2.items.filter((item) => item.status === "failed").map((item) => `${item.id} (${itemDetails(t, item).join(", ")})`).join("; ");
11695
+ var failingList = (t, report2) => report2.items.filter((item2) => item2.status === "failed").map((item2) => `${item2.id} (${itemDetails(t, item2).join(", ")})`).join("; ");
11571
11696
  var touchesTests = (report2) => report2.items.some(
11572
- (item) => item.status === "failed" && item.notes.some((entry2) => entry2.key.startsWith("gate.tests."))
11697
+ (item2) => item2.status === "failed" && item2.notes.some((entry2) => entry2.key.startsWith("gate.tests."))
11573
11698
  );
11574
11699
  var markDone = (input, report2, root) => {
11575
11700
  if (report2.implicit) return;
@@ -12329,8 +12454,8 @@ var writtenTexts = (tool, input) => {
12329
12454
  };
12330
12455
  var stringLeaves = (value, out = []) => {
12331
12456
  if (typeof value === "string") out.push(value);
12332
- else if (Array.isArray(value)) for (const item of value) stringLeaves(item, out);
12333
- else if (isRecord(value)) for (const item of Object.values(value)) stringLeaves(item, out);
12457
+ else if (Array.isArray(value)) for (const item2 of value) stringLeaves(item2, out);
12458
+ else if (isRecord(value)) for (const item2 of Object.values(value)) stringLeaves(item2, out);
12334
12459
  return out;
12335
12460
  };
12336
12461
 
@@ -13284,16 +13409,16 @@ var rmOutcome = (input, command, worktrees) => {
13284
13409
  };
13285
13410
  var snapshotsOf = (input, owner, pending) => {
13286
13411
  const done = /* @__PURE__ */ new Map();
13287
- for (const item of pending) {
13288
- if (done.has(item.repo.root)) continue;
13289
- const taken = takeSnapshot(input.runtime, item.repo, { op: item.op, owner, command: item.command });
13412
+ for (const item2 of pending) {
13413
+ if (done.has(item2.repo.root)) continue;
13414
+ const taken = takeSnapshot(input.runtime, item2.repo, { op: item2.op, owner, command: item2.command });
13290
13415
  if (!taken.ok) {
13291
13416
  return {
13292
13417
  kind: "block",
13293
- reason: input.t("git.block.snapshot_failed", { command: item.command, detail: taken.error })
13418
+ reason: input.t("git.block.snapshot_failed", { command: item2.command, detail: taken.error })
13294
13419
  };
13295
13420
  }
13296
- done.set(item.repo.root, input.t("git.snapshot.context", { command: item.command, ref: taken.value.ref }));
13421
+ done.set(item2.repo.root, input.t("git.snapshot.context", { command: item2.command, ref: taken.value.ref }));
13297
13422
  }
13298
13423
  if (done.size === 0) return ALLOW;
13299
13424
  const context = [...done.values()].join("\n");
@@ -13627,8 +13752,8 @@ var sourceOf = (value, isFile, segment, heredocs) => {
13627
13752
  var collect3 = (target2, segment, args2, spec2, heredocs) => {
13628
13753
  const values = flagValues(args2, spec2);
13629
13754
  if (values.length === 0) return void 0;
13630
- const title = values.find((item) => spec2.title.includes(item.flag));
13631
- const bodies = values.filter((item) => !spec2.title.includes(item.flag)).map((item) => sourceOf(item.value, spec2.file.includes(item.flag), segment, heredocs));
13755
+ const title = values.find((item2) => spec2.title.includes(item2.flag));
13756
+ const bodies = values.filter((item2) => !spec2.title.includes(item2.flag)).map((item2) => sourceOf(item2.value, spec2.file.includes(item2.flag), segment, heredocs));
13632
13757
  return { target: target2, title: title === void 0 ? void 0 : sourceOf(title.value, false, segment, heredocs), bodies };
13633
13758
  };
13634
13759
  var gitSubcommand = (args2) => {
@@ -13738,8 +13863,8 @@ var proseCommandGuard = (input) => {
13738
13863
  const found = proseInCommand(optionalString(toolInput2(input.payload), "command") ?? "");
13739
13864
  if (found.length === 0) return ALLOW4;
13740
13865
  const blocking2 = projectPreset(input.runtime, projectRoot(input)) === "gabriel";
13741
- for (const item of found) {
13742
- const decision = decisionFor(input, item, blocking2);
13866
+ for (const item2 of found) {
13867
+ const decision = decisionFor(input, item2, blocking2);
13743
13868
  if (decision !== void 0) return decision;
13744
13869
  }
13745
13870
  return ALLOW4;
@@ -14031,24 +14156,24 @@ ${right}`;
14031
14156
  var mergeSpecific = (left, right) => {
14032
14157
  if (!isRecord(left)) return right;
14033
14158
  if (!isRecord(right)) return left;
14034
- const merged = { ...left, ...right };
14035
- merged["additionalContext"] = joinText(left["additionalContext"], right["additionalContext"]);
14159
+ const merged2 = { ...left, ...right };
14160
+ merged2["additionalContext"] = joinText(left["additionalContext"], right["additionalContext"]);
14036
14161
  const leftRank = DECISION_RANK[String(left["permissionDecision"])] ?? -1;
14037
14162
  const rightRank = DECISION_RANK[String(right["permissionDecision"])] ?? -1;
14038
14163
  if (leftRank > rightRank) {
14039
- merged["permissionDecision"] = left["permissionDecision"];
14040
- merged["permissionDecisionReason"] = left["permissionDecisionReason"];
14164
+ merged2["permissionDecision"] = left["permissionDecision"];
14165
+ merged2["permissionDecisionReason"] = left["permissionDecisionReason"];
14041
14166
  }
14042
- return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== void 0));
14167
+ return Object.fromEntries(Object.entries(merged2).filter(([, value]) => value !== void 0));
14043
14168
  };
14044
14169
  var mergeOutputs = (left, right) => {
14045
14170
  if (right === void 0) return left;
14046
- const merged = { ...left, ...right };
14171
+ const merged2 = { ...left, ...right };
14047
14172
  const systemMessage = joinText(left["systemMessage"], right["systemMessage"]);
14048
- if (systemMessage !== void 0) merged["systemMessage"] = systemMessage;
14173
+ if (systemMessage !== void 0) merged2["systemMessage"] = systemMessage;
14049
14174
  const specific = mergeSpecific(left["hookSpecificOutput"], right["hookSpecificOutput"]);
14050
- if (specific !== void 0) merged["hookSpecificOutput"] = specific;
14051
- return merged;
14175
+ if (specific !== void 0) merged2["hookSpecificOutput"] = specific;
14176
+ return merged2;
14052
14177
  };
14053
14178
  var composeDecisions = async (input, handlers) => {
14054
14179
  let output = {};
@@ -14060,14 +14185,14 @@ var composeDecisions = async (input, handlers) => {
14060
14185
  return { kind: "allow", output };
14061
14186
  };
14062
14187
  var mergeHandlers = (...sets) => {
14063
- const merged = {};
14188
+ const merged2 = {};
14064
14189
  for (const set of sets) {
14065
14190
  for (const event of HOOK_EVENTS) {
14066
14191
  const list4 = set[event];
14067
- if (list4 !== void 0) merged[event] = [...merged[event] ?? [], ...list4];
14192
+ if (list4 !== void 0) merged2[event] = [...merged2[event] ?? [], ...list4];
14068
14193
  }
14069
14194
  }
14070
- return merged;
14195
+ return merged2;
14071
14196
  };
14072
14197
  var SECRETS_HANDLERS = {
14073
14198
  PreToolUse: [secretsPreToolUse],
@@ -14235,7 +14360,7 @@ var expand = (text6, vars) => text6.replace(/\$\{(\w+)\}|\$(\w+)/g, (match, brac
14235
14360
  const name = braced ?? bare ?? "";
14236
14361
  return vars[name] ?? match;
14237
14362
  });
14238
- var strings3 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
14363
+ var strings3 = (value) => Array.isArray(value) ? value.filter((item2) => typeof item2 === "string") : [];
14239
14364
  var invocationOf = (entry2, vars) => {
14240
14365
  const raw = optionalString(entry2.handler, "command");
14241
14366
  if (raw === void 0 || raw.trim() === "") return void 0;
@@ -14536,7 +14661,7 @@ var PRECEDENCE2 = ["managed", "local", "project", "user"];
14536
14661
  var pluginEnabled = (env) => {
14537
14662
  const all = readSettings(env);
14538
14663
  for (const scope2 of PRECEDENCE2) {
14539
- const value = asRecord(all.find((item) => item.scope === scope2)?.settings["enabledPlugins"])[PLUGIN_ID];
14664
+ const value = asRecord(all.find((item2) => item2.scope === scope2)?.settings["enabledPlugins"])[PLUGIN_ID];
14540
14665
  if (typeof value === "boolean") return { value, scope: scope2 };
14541
14666
  }
14542
14667
  return void 0;
@@ -14900,7 +15025,7 @@ var toItem = (ctx, finding3) => ({
14900
15025
  detail: ctx.t(finding3.messageKey, finding3.params),
14901
15026
  ...finding3.fixKey === void 0 ? {} : { fix: ctx.t(finding3.fixKey, finding3.params) }
14902
15027
  });
14903
- var count3 = (items, status) => items.filter((item) => item.status === status).length;
15028
+ var count3 = (items, status) => items.filter((item2) => item2.status === status).length;
14904
15029
  var hookItems = (ctx, findings) => findings.flatMap(
14905
15030
  (finding3) => finding3.hook === void 0 ? [] : [
14906
15031
  {
@@ -14922,18 +15047,18 @@ var buildReport2 = (ctx, findings) => {
14922
15047
 
14923
15048
  // src/commands/doctor.ts
14924
15049
  var MARK = { ok: "ok ", warn: "warn", fail: "FAIL" };
14925
- var itemLines = (ctx, item) => [
14926
- `[${MARK[item.status]}] ${item.check}: ${item.detail}`,
14927
- ...item.fix !== void 0 && item.status !== "ok" ? [` ${ctx.t("output.fix", { fix: item.fix })}`] : []
15050
+ var itemLines = (ctx, item2) => [
15051
+ `[${MARK[item2.status]}] ${item2.check}: ${item2.detail}`,
15052
+ ...item2.fix !== void 0 && item2.status !== "ok" ? [` ${ctx.t("output.fix", { fix: item2.fix })}`] : []
14928
15053
  ];
14929
15054
  var doctorResult = (ctx, report2, extra = []) => {
14930
15055
  const human2 = [
14931
15056
  ctx.t("doctor.title", { version: ctx.version }),
14932
15057
  ...extra,
14933
- ...report2.checks.flatMap((item) => itemLines(ctx, item)),
15058
+ ...report2.checks.flatMap((item2) => itemLines(ctx, item2)),
14934
15059
  ctx.t("doctor.summary", report2.summary)
14935
15060
  ];
14936
- const failed4 = report2.checks.find((item) => item.status === "fail");
15061
+ const failed4 = report2.checks.find((item2) => item2.status === "fail");
14937
15062
  if (failed4 === void 0) return { ok: true, exitCode: ExitCode.Ok, data: report2, human: human2 };
14938
15063
  return {
14939
15064
  ok: false,
@@ -14957,7 +15082,7 @@ var fixLines = (ctx, fixed) => {
14957
15082
  ...fixed.backup === void 0 ? [] : [ctx.t("doctor.fix.backup", { path: fixed.backup })]
14958
15083
  ];
14959
15084
  };
14960
- var manualLines = (ctx, report2) => report2.checks.some((item) => item.status !== "ok" && item.fix !== void 0) ? [ctx.t("doctor.fix.manual")] : [];
15085
+ var manualLines = (ctx, report2) => report2.checks.some((item2) => item2.status !== "ok" && item2.fix !== void 0) ? [ctx.t("doctor.fix.manual")] : [];
14961
15086
  var writeReport = (ctx, data, result) => {
14962
15087
  const env = doctorEnvFrom(ctx);
14963
15088
  const path = reportPath(env);
@@ -15092,6 +15217,172 @@ var gateCommand = {
15092
15217
  }
15093
15218
  };
15094
15219
 
15220
+ // src/githooks/commit-msg.ts
15221
+ var COMMIT_TYPES = [
15222
+ "feat",
15223
+ "fix",
15224
+ "docs",
15225
+ "style",
15226
+ "refactor",
15227
+ "perf",
15228
+ "test",
15229
+ "build",
15230
+ "ci",
15231
+ "chore",
15232
+ "revert"
15233
+ ];
15234
+ var MAX_HEADER = 100;
15235
+ var HEADER = new RegExp(`^(?:${COMMIT_TYPES.join("|")})(?:\\([^()\\r\\n]+\\))?!?: \\S`);
15236
+ var EXEMPT = /^(?:Merge |Revert "|fixup! |squash! |amend! |Initial commit$)/;
15237
+ var AI_NAMES = /claude|anthropic|copilot|chatgpt|openai|cursor|gemini|codex|devin|aider/i;
15238
+ var ATTRIBUTION = [
15239
+ /^co-authored-by:.*$/gim,
15240
+ /generated (?:with|by) \[?(?:claude|copilot|chatgpt|cursor|gemini|codex)/i,
15241
+ /🤖 generated/i,
15242
+ /claude\.ai\/code\//i
15243
+ ];
15244
+ var meaningful2 = (message) => message.replace(/\r\n/g, "\n").split("\n").filter((line2) => !line2.startsWith("#"));
15245
+ var hasAttribution = (body2) => {
15246
+ const [coAuthor, ...rest] = ATTRIBUTION;
15247
+ const coAuthors = coAuthor === void 0 ? [] : body2.match(coAuthor) ?? [];
15248
+ return coAuthors.some((line2) => AI_NAMES.test(line2)) || rest.some((pattern) => pattern.test(body2));
15249
+ };
15250
+ var commitMessageProblems = (message) => {
15251
+ const lines3 = meaningful2(message);
15252
+ const header = lines3.find((line2) => line2.trim() !== "") ?? "";
15253
+ const problems = [];
15254
+ if (!EXEMPT.test(header) && !HEADER.test(header)) problems.push("header");
15255
+ if (header.length > MAX_HEADER) problems.push("header_length");
15256
+ if (hasAttribution(lines3.join("\n"))) problems.push("attribution");
15257
+ return problems;
15258
+ };
15259
+
15260
+ // src/githooks/checks.ts
15261
+ var DIFF_FLAGS2 = ["-U0", "--no-color", "--no-ext-diff", "--no-renames"];
15262
+ var short = (sha) => sha.slice(0, 12);
15263
+ var secretsInRange = (runtime, root, range) => {
15264
+ const log = runtime.git(["log", "-p", "--format=commit %H", ...DIFF_FLAGS2, ...range, "--"], root);
15265
+ const baseline = loadBaseline(runtime, root);
15266
+ const accepted2 = acceptedFingerprints(baseline.ok ? baseline.value : EMPTY_BASELINE, runtime.now());
15267
+ const found = log.ok ? blocking(diffFindings(log.stdout)).filter((item2) => !accepted2.has(item2.fingerprint)) : [];
15268
+ const failures = log.ok ? found.map(
15269
+ (item2) => `${item2.ruleId} at ${item2.file}:${String(item2.line)}${item2.commit === void 0 ? "" : ` (${short(item2.commit)})`}`
15270
+ ) : [`git log ${range.join(" ")} failed`];
15271
+ return { id: "secrets", failures, fix: "finitemonkey secrets scan --history" };
15272
+ };
15273
+ var RECORD = "";
15274
+ var commitMessagesInRange = (runtime, root, range) => {
15275
+ const log = runtime.git(["log", "--no-merges", `--format=%H%x00%B${RECORD}`, ...range, "--"], root);
15276
+ const failures = log.ok ? log.stdout.split(RECORD).map((record3) => record3.replace(/^\n/, "")).filter((record3) => record3.includes("\0")).flatMap((record3) => {
15277
+ const [sha = "", body2 = ""] = record3.split("\0");
15278
+ return commitMessageProblems(body2).map((problem4) => `${short(sha)} ${problem4}`);
15279
+ }) : [`git log ${range.join(" ")} failed`];
15280
+ return { id: "commit-messages", failures, fix: "git rebase -i <base> and reword the listed commits" };
15281
+ };
15282
+ var mergesInRange = (runtime, repo, range) => {
15283
+ const listed4 = runtime.git(["rev-list", "--merges", ...range, "--"], repo.root);
15284
+ const merges = listed4.ok ? listed4.stdout.split("\n").filter((line2) => line2 !== "") : [];
15285
+ const failures = merges.flatMap((sha) => {
15286
+ const report2 = verifyMerge(runtime, repo, { rev: sha });
15287
+ if (report2.exit === 0) return [];
15288
+ return [
15289
+ report2.exit === 2 ? `${short(sha)} could not be analyzed` : `${short(sha)} dropped ${String(report2.findings.length)} line(s)`
15290
+ ];
15291
+ });
15292
+ return { id: "merges", failures, fix: "finitemonkey merge verify <merge-commit>" };
15293
+ };
15294
+ var failing = (checks) => checks.filter((check2) => check2.failures.length > 0);
15295
+
15296
+ // src/githooks/ci.ts
15297
+ var ciBase = (runtime, repo, env, base) => {
15298
+ if (base !== void 0 && base !== "") return base;
15299
+ const target2 = env["GITHUB_BASE_REF"];
15300
+ if (target2 !== void 0 && target2 !== "") return `origin/${target2}`;
15301
+ return `origin/${repoFacts(runtime, repo).defaultBranch()}`;
15302
+ };
15303
+ var baseExists = (runtime, repo, base) => runtime.git(["rev-parse", "--verify", "--quiet", `${base}^{commit}`], repo.root).ok;
15304
+ var ciChecks2 = (runtime, repo, base) => {
15305
+ const range = [`${base}..HEAD`];
15306
+ return [
15307
+ secretsInRange(runtime, repo.root, range),
15308
+ commitMessagesInRange(runtime, repo.root, range),
15309
+ mergesInRange(runtime, repo, range)
15310
+ ];
15311
+ };
15312
+
15313
+ // src/githooks/pre-push.ts
15314
+ var ZERO2 = /^0+$/;
15315
+ var parsePushUpdates = (stdin) => stdin.split(/\r?\n/).map((line2) => line2.trim().split(/\s+/)).flatMap(
15316
+ ([localRef, localSha, remoteRef, remoteSha]) => localRef === void 0 || localSha === void 0 || remoteRef === void 0 || remoteSha === void 0 ? [] : [{ localRef, localSha, remoteRef, remoteSha }]
15317
+ );
15318
+ var updatesFromEnv = (env) => {
15319
+ const from = env["PRE_COMMIT_FROM_REF"];
15320
+ const to = env["PRE_COMMIT_TO_REF"];
15321
+ if (from === void 0 || to === void 0 || from === "" || to === "") return [];
15322
+ const branch = env["PRE_COMMIT_REMOTE_BRANCH"] ?? "";
15323
+ return [{ localRef: to, localSha: to, remoteRef: branch, remoteSha: from }];
15324
+ };
15325
+ var branchOf = (ref) => ref.replace(/^refs\/heads\//, "");
15326
+ var isDeletion = (update) => ZERO2.test(update.localSha);
15327
+ var isNewRef = (update) => ZERO2.test(update.remoteSha);
15328
+ var rangeOf = (update, remote) => isNewRef(update) ? [update.localSha, "--not", `--remotes=${remote}`] : [`${update.remoteSha}..${update.localSha}`];
15329
+ var rewrites = (runtime, repo, update) => !isNewRef(update) && !isDeletion(update) && !runtime.git(["merge-base", "--is-ancestor", update.remoteSha, update.localSha], repo.root).ok;
15330
+ var protectedCheck = (runtime, repo, updates) => {
15331
+ const facts = repoFacts(runtime, repo);
15332
+ const failures = updates.flatMap((update) => {
15333
+ const branch = branchOf(update.remoteRef);
15334
+ if (!update.remoteRef.startsWith("refs/heads/") || !facts.isProtected(branch)) return [];
15335
+ if (isDeletion(update)) return [`deletes protected branch ${branch}`];
15336
+ return rewrites(runtime, repo, update) ? [`rewrites protected branch ${branch} (force push)`] : [];
15337
+ });
15338
+ return { id: "protected", failures, fix: "push to a new branch and open a pull request" };
15339
+ };
15340
+ var merged = (id, fix, checks) => ({
15341
+ id,
15342
+ fix,
15343
+ failures: [...new Set(checks.flatMap((check2) => check2.failures))]
15344
+ });
15345
+ var prePushChecks = (runtime, repo, remote, updates) => {
15346
+ const pushed = updates.filter((update) => !isDeletion(update));
15347
+ const ranges = pushed.map((update) => rangeOf(update, remote));
15348
+ const secrets = ranges.map((range) => secretsInRange(runtime, repo.root, range));
15349
+ const messages = ranges.map((range) => commitMessagesInRange(runtime, repo.root, range));
15350
+ const merges = ranges.map((range) => mergesInRange(runtime, repo, range));
15351
+ return [
15352
+ protectedCheck(runtime, repo, updates),
15353
+ merged("secrets", "finitemonkey secrets scan --history", secrets),
15354
+ merged("commit-messages", "git rebase -i <base> and reword the listed commits", messages),
15355
+ merged("merges", "finitemonkey merge verify <merge-commit>", merges)
15356
+ ];
15357
+ };
15358
+
15359
+ // src/githooks/render.ts
15360
+ var MAX_LISTED8 = 5;
15361
+ var detailOf = (check2) => {
15362
+ const listed4 = check2.failures.slice(0, MAX_LISTED8).join("; ");
15363
+ const rest = check2.failures.length - MAX_LISTED8;
15364
+ return rest > 0 ? `${listed4}; +${String(rest)}` : listed4;
15365
+ };
15366
+ var checkLine = (t, check2) => check2.failures.length === 0 ? t("githooks.pass", { check: check2.id }) : t("githooks.fail", { check: check2.id, detail: detailOf(check2), fix: check2.fix });
15367
+ var checksResult = (t, schema, code, checks) => {
15368
+ const data = { schema, checks };
15369
+ const bad = failing(checks);
15370
+ const human2 = bad.length === 0 ? checks.map((check2) => checkLine(t, check2)) : bad.map((check2) => checkLine(t, check2));
15371
+ if (bad.length === 0) return success(data, human2);
15372
+ const [first] = bad;
15373
+ return {
15374
+ ok: false,
15375
+ exitCode: ExitCode.CheckFailed,
15376
+ data,
15377
+ human: human2,
15378
+ error: {
15379
+ code,
15380
+ message: t("githooks.error.failed", { count: bad.length }),
15381
+ fix: first?.fix ?? ""
15382
+ }
15383
+ };
15384
+ };
15385
+
15095
15386
  // src/commands/git-problems.ts
15096
15387
  var notRepository = (dir) => ({
15097
15388
  code: "not_a_git_repo",
@@ -15103,14 +15394,90 @@ var notRepository = (dir) => ({
15103
15394
  var gitProblem = (code, messageKey, detail, fix, exitCode = ExitCode.CheckFailed) => ({ code, messageKey, params: { detail }, fix, exitCode });
15104
15395
  var repoOf2 = (ctx) => openRepo(ctx.runtime, ctx.cwd);
15105
15396
 
15397
+ // src/commands/git-checks.ts
15398
+ var COMMIT_MSG_SCHEMA = "finitemonkey/commit-msg@1";
15399
+ var PUSH_CHECK_SCHEMA = "finitemonkey/push-check@1";
15400
+ var CI_GATE_SCHEMA = "finitemonkey/ci-gate@1";
15401
+ var COMMIT_MSG_USAGE = "finitemonkey commit-msg <message-file>";
15402
+ var commitMsgCommand = {
15403
+ path: ["commit-msg"],
15404
+ summaryKey: "githooks.command.commit_msg",
15405
+ usage: COMMIT_MSG_USAGE,
15406
+ status: "ready",
15407
+ maxPositionals: 1,
15408
+ run: (ctx, args2) => {
15409
+ const file = args2.positionals[0];
15410
+ if (file === void 0)
15411
+ return Promise.resolve(
15412
+ failureFrom(
15413
+ ctx.t,
15414
+ usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: "<message-file>" })
15415
+ )
15416
+ );
15417
+ const text6 = ctx.runtime.readText(toAbsolutePath(file, ctx.cwd));
15418
+ if (!text6.ok)
15419
+ return Promise.resolve(
15420
+ failureFrom(
15421
+ ctx.t,
15422
+ usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: file })
15423
+ )
15424
+ );
15425
+ const failures = commitMessageProblems(text6.value);
15426
+ const fix = "git commit --amend, using type(scope): subject and no AI attribution lines";
15427
+ return Promise.resolve(
15428
+ checksResult(ctx.t, COMMIT_MSG_SCHEMA, "githook_failed", [{ id: "commit-messages", failures, fix }])
15429
+ );
15430
+ }
15431
+ };
15432
+ var pushCheckCommand = {
15433
+ path: ["push", "check"],
15434
+ summaryKey: "githooks.command.push_check",
15435
+ usage: "finitemonkey push check [<remote>] < pre-push stdin",
15436
+ status: "ready",
15437
+ maxPositionals: 2,
15438
+ run: async (ctx, args2) => {
15439
+ const repo = repoOf2(ctx);
15440
+ if (repo === void 0) return failureFrom(ctx.t, notRepository(ctx.cwd));
15441
+ const piped = parsePushUpdates(ctx.io.stdin.isTTY === true ? "" : await readAll(ctx.io.stdin));
15442
+ const updates = piped.length > 0 ? piped : updatesFromEnv(ctx.env);
15443
+ const checks = prePushChecks(ctx.runtime, repo, args2.positionals[0] ?? "origin", updates);
15444
+ return checksResult(ctx.t, PUSH_CHECK_SCHEMA, "githook_failed", checks);
15445
+ }
15446
+ };
15447
+ var ciGateCommand = {
15448
+ path: ["ci", "gate"],
15449
+ summaryKey: "githooks.command.ci_gate",
15450
+ usage: "finitemonkey ci gate [--base <ref>]",
15451
+ status: "ready",
15452
+ options: { base: { type: "string" } },
15453
+ run: (ctx, args2) => {
15454
+ const repo = repoOf2(ctx);
15455
+ if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
15456
+ const base = ciBase(ctx.runtime, repo, ctx.env, stringOption(args2.values, "base"));
15457
+ if (!baseExists(ctx.runtime, repo, base))
15458
+ return Promise.resolve(
15459
+ failureFrom(
15460
+ ctx.t,
15461
+ gitProblem(
15462
+ "githook_failed",
15463
+ "githooks.error.no_base",
15464
+ base,
15465
+ `git fetch origin && finitemonkey ci gate --base ${base}`
15466
+ )
15467
+ )
15468
+ );
15469
+ return Promise.resolve(checksResult(ctx.t, CI_GATE_SCHEMA, "ci_gate_failed", ciChecks2(ctx.runtime, repo, base)));
15470
+ }
15471
+ };
15472
+
15106
15473
  // src/commands/git-merge-verify.ts
15107
15474
  var MERGE_VERIFY_RUN_SCHEMA = "finitemonkey/merge-verify-run@1";
15108
- var short = (sha) => (sha ?? "?").slice(0, 12);
15109
- var humanOf = (ctx, item) => {
15110
- const { report: report2 } = item;
15475
+ var short2 = (sha) => (sha ?? "?").slice(0, 12);
15476
+ var humanOf = (ctx, item2) => {
15477
+ const { report: report2 } = item2;
15111
15478
  const range = {
15112
- base: short(report2.base),
15113
- result: item.commit === "MERGE_HEAD" ? "working tree" : short(item.commit)
15479
+ base: short2(report2.base),
15480
+ result: item2.commit === "MERGE_HEAD" ? "working tree" : short2(item2.commit)
15114
15481
  };
15115
15482
  if (report2.exit === 2) return [ctx.t("git.error.merge_unanalyzable", { detail: report2.error ?? "" })];
15116
15483
  if (report2.exit === 0) {
@@ -15139,15 +15506,15 @@ var targetsOf2 = (ctx, repo, rev, since) => {
15139
15506
  return headIsMerge(ctx, repo) ? ["HEAD"] : [];
15140
15507
  };
15141
15508
  var resultOf = (ctx, checked) => {
15142
- const human2 = [...checked.flatMap((item) => humanOf(ctx, item)), ctx.t("git.merge.limits")];
15509
+ const human2 = [...checked.flatMap((item2) => humanOf(ctx, item2)), ctx.t("git.merge.limits")];
15143
15510
  const data = { schema: MERGE_VERIFY_RUN_SCHEMA, merges: checked };
15144
- const broken = checked.find((item) => item.report.exit === 2);
15511
+ const broken = checked.find((item2) => item2.report.exit === 2);
15145
15512
  if (broken !== void 0) {
15146
15513
  const detail = broken.report.error ?? "";
15147
15514
  const problem4 = gitProblem("merge_unanalyzable", "git.error.merge_unanalyzable", detail, "git status");
15148
15515
  return failureFrom(ctx.t, { ...problem4, exitCode: ExitCode.Usage, data }, human2);
15149
15516
  }
15150
- const lost = checked.reduce((count4, item) => count4 + item.report.findings.length, 0);
15517
+ const lost = checked.reduce((count4, item2) => count4 + item2.report.findings.length, 0);
15151
15518
  if (lost === 0) return success(data, human2);
15152
15519
  return failureFrom(
15153
15520
  ctx.t,
@@ -15330,7 +15697,7 @@ var parentsOf2 = (runtime, repo, commit2) => {
15330
15697
  }, {});
15331
15698
  };
15332
15699
  var viewOf = (runtime, repo, ref, journal) => {
15333
- const entry2 = journal.find((item) => item.ref === ref);
15700
+ const entry2 = journal.find((item2) => item2.ref === ref);
15334
15701
  if (entry2 !== void 0) {
15335
15702
  return { ref, commit: entry2.commit, at: entry2.at, op: entry2.op, head: entry2.head, index: entry2.index };
15336
15703
  }
@@ -15745,7 +16112,7 @@ var valueProblems = (lang, catalog, reference) => Object.keys(catalog).sort().fl
15745
16112
  var pluralProblems = (lang, key, value, source) => {
15746
16113
  const own = choicesOf(value);
15747
16114
  return choicesOf(source).flatMap((choice) => {
15748
- const found = own.find((item) => item.name === choice.name && item.kind === choice.kind);
16115
+ const found = own.find((item2) => item2.name === choice.name && item2.kind === choice.kind);
15749
16116
  const expected = choice.kind === "plural" ? requiredPluralBranches(lang) : choice.branches;
15750
16117
  const actual = found?.branches ?? [];
15751
16118
  const missingBranch = expected.some((branch) => !actual.includes(branch));
@@ -16112,11 +16479,11 @@ var authorityWarning = (t, authority) => ({
16112
16479
  source: authority.source
16113
16480
  })
16114
16481
  });
16115
- var itemWarning = (t, item) => {
16116
- if (item.action === "skip-foreign")
16117
- return [{ code: "foreign_file", message: t("install.warn.foreign", { path: item.path }) }];
16118
- if (item.action === "keep-edited")
16119
- return [{ code: "edited_file", message: t("install.warn.edited", { path: item.path }) }];
16482
+ var itemWarning = (t, item2) => {
16483
+ if (item2.action === "skip-foreign")
16484
+ return [{ code: "foreign_file", message: t("install.warn.foreign", { path: item2.path }) }];
16485
+ if (item2.action === "keep-edited")
16486
+ return [{ code: "edited_file", message: t("install.warn.edited", { path: item2.path }) }];
16120
16487
  return [];
16121
16488
  };
16122
16489
  var planWarnings = (t, plan) => [
@@ -16125,10 +16492,10 @@ var planWarnings = (t, plan) => [
16125
16492
  ...plan.detection.location.wslMount ? [{ code: "wsl_mount", message: t("install.warn.wsl_mount", { path: plan.detection.root }) }] : [],
16126
16493
  ...plan.detection.location.synced ? [{ code: "synced_folder", message: t("install.warn.synced_folder", { path: plan.detection.root }) }] : [],
16127
16494
  ...plan.detection.authorities.map((authority) => authorityWarning(t, authority)),
16128
- ...plan.items.flatMap((item) => itemWarning(t, item))
16495
+ ...plan.items.flatMap((item2) => itemWarning(t, item2))
16129
16496
  ];
16130
16497
  var planDiff = (plan) => plan.items.flatMap(
16131
- (item) => item.action === "keep-edited" || item.action === "skip-foreign" ? lineDiff(item.path, item.before ?? "", item.proposal ?? item.after) : lineDiff(item.path, item.before ?? "", item.after)
16498
+ (item2) => item2.action === "keep-edited" || item2.action === "skip-foreign" ? lineDiff(item2.path, item2.before ?? "", item2.proposal ?? item2.after) : lineDiff(item2.path, item2.before ?? "", item2.after)
16132
16499
  );
16133
16500
  var planData = (t, plan, mode, applied, backup) => ({
16134
16501
  schema: PLAN_SCHEMA,
@@ -16489,7 +16856,7 @@ var acceptEntry = (ctx, args2) => {
16489
16856
  return failure3(ctx, baselineInvalid(baseline.error));
16490
16857
  }
16491
16858
  const scanned = scanPath(ctx.runtime, root, ".");
16492
- const finding3 = scanned.ok ? scanned.value.find((item) => item.id === args2.id) : void 0;
16859
+ const finding3 = scanned.ok ? scanned.value.find((item2) => item2.id === args2.id) : void 0;
16493
16860
  if (finding3 === void 0) {
16494
16861
  return failure3(
16495
16862
  ctx,
@@ -16499,7 +16866,7 @@ var acceptEntry = (ctx, args2) => {
16499
16866
  );
16500
16867
  }
16501
16868
  const entry2 = entryOf3(ctx, finding3, args2);
16502
- const entries = [...baseline.value.entries.filter((item) => item.id !== entry2.id), entry2];
16869
+ const entries = [...baseline.value.entries.filter((item2) => item2.id !== entry2.id), entry2];
16503
16870
  const saved = saveBaseline(ctx.runtime, root, { schema: baseline.value.schema, entries });
16504
16871
  if (!saved.ok) {
16505
16872
  return failure3(ctx, writeFailed(BASELINE_FILE2, saved.error));
@@ -16603,7 +16970,7 @@ var list2 = (ctx, root) => {
16603
16970
  }));
16604
16971
  const human2 = incidents.length === 0 ? [ctx.t("secrets.incident.none")] : [
16605
16972
  ctx.t("secrets.incident.title", { count: incidents.length }),
16606
- ...incidents.flatMap((item) => itemLines2(ctx, item))
16973
+ ...incidents.flatMap((item2) => itemLines2(ctx, item2))
16607
16974
  ];
16608
16975
  return success({ schema: "finitemonkey/secrets-incidents@1", incidents }, human2);
16609
16976
  };
@@ -16866,26 +17233,26 @@ var dataOf = (mode, outcome2, incidents) => {
16866
17233
  var humanOf2 = (ctx, data) => [
16867
17234
  ctx.t("secrets.scan.title", { mode: data.mode, ...data.summary }),
16868
17235
  ...data.findings.map(
16869
- (item) => ctx.t("secrets.scan.item", {
16870
- status: item.status,
16871
- rule: item.rule,
16872
- where: `${item.file}:${String(item.line)}`,
16873
- id: item.id,
16874
- mask: item.mask
17236
+ (item2) => ctx.t("secrets.scan.item", {
17237
+ status: item2.status,
17238
+ rule: item2.rule,
17239
+ where: `${item2.file}:${String(item2.line)}`,
17240
+ id: item2.id,
17241
+ mask: item2.mask
16875
17242
  })
16876
17243
  ),
16877
17244
  ...data.summary.total === 0 ? [ctx.t("secrets.scan.clean")] : []
16878
17245
  ];
16879
17246
  var resultOf3 = (ctx, data) => {
16880
17247
  const human2 = humanOf2(ctx, data);
16881
- const failing = data.findings.filter((item) => item.status !== "known" && item.severity !== "warning").length;
16882
- if (failing === 0) return success(data, human2);
17248
+ const failing2 = data.findings.filter((item2) => item2.status !== "known" && item2.severity !== "warning").length;
17249
+ if (failing2 === 0) return success(data, human2);
16883
17250
  return failureFrom(
16884
17251
  ctx.t,
16885
17252
  {
16886
17253
  code: "secrets_found",
16887
17254
  messageKey: "secrets.error.found",
16888
- params: { count: failing },
17255
+ params: { count: failing2 },
16889
17256
  fix: "finitemonkey secrets accept <id> --reason <why>",
16890
17257
  exitCode: ExitCode.CheckFailed,
16891
17258
  data
@@ -16946,18 +17313,18 @@ var inline2 = (value) => {
16946
17313
  };
16947
17314
  var lines2 = (value, indent) => {
16948
17315
  if (Array.isArray(value)) {
16949
- return value.flatMap((item) => {
16950
- const flat = inline2(item);
17316
+ return value.flatMap((item2) => {
17317
+ const flat = inline2(item2);
16951
17318
  if (flat !== void 0) return [`${indent}- ${flat}`];
16952
- const [head = "", ...rest] = lines2(item, `${indent} `);
17319
+ const [head = "", ...rest] = lines2(item2, `${indent} `);
16953
17320
  return [`${indent}- ${head.trimStart()}`, ...rest];
16954
17321
  });
16955
17322
  }
16956
17323
  if (isScalar(value)) return [`${indent}${scalarText(value)}`];
16957
- return Object.entries(value).flatMap(([key, item]) => {
16958
- const flat = inline2(item);
17324
+ return Object.entries(value).flatMap(([key, item2]) => {
17325
+ const flat = inline2(item2);
16959
17326
  const name = scalarText(key);
16960
- return flat === void 0 ? [`${indent}${name}:`, ...lines2(item, `${indent} `)] : [`${indent}${name}: ${flat}`];
17327
+ return flat === void 0 ? [`${indent}${name}:`, ...lines2(item2, `${indent} `)] : [`${indent}${name}: ${flat}`];
16961
17328
  });
16962
17329
  };
16963
17330
  var stringifyYaml = (value) => `${lines2(value, "").join("\n")}
@@ -17088,9 +17455,9 @@ var noRepo = (ctx) => failureFrom(ctx.t, problem3("not_a_git_repo", "error.not_a
17088
17455
  var list3 = (values, name) => {
17089
17456
  const value = values[name];
17090
17457
  const raw = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
17091
- return raw.filter((item) => typeof item === "string" && item.trim() !== "");
17458
+ return raw.filter((item2) => typeof item2 === "string" && item2.trim() !== "");
17092
17459
  };
17093
- var csv = (values, name) => list3(values, name).flatMap((item) => item.split(",").map((part2) => part2.trim())).filter((part2) => part2 !== "");
17460
+ var csv = (values, name) => list3(values, name).flatMap((item2) => item2.split(",").map((part2) => part2.trim())).filter((part2) => part2 !== "");
17094
17461
  var positive3 = (values, name) => {
17095
17462
  const parsed = Number.parseInt(stringOption(values, name) ?? "", 10);
17096
17463
  return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
@@ -17098,7 +17465,7 @@ var positive3 = (values, name) => {
17098
17465
  var draftFrom = (ctx, paths, id, values) => {
17099
17466
  const files = positive3(values, "budget-files");
17100
17467
  const lines3 = positive3(values, "budget-lines");
17101
- const size = SIZES.find((item) => item === stringOption(values, "size")) ?? "S";
17468
+ const size = SIZES.find((item2) => item2 === stringOption(values, "size")) ?? "S";
17102
17469
  return {
17103
17470
  id,
17104
17471
  goal: stringOption(values, "goal") ?? id,
@@ -17107,7 +17474,7 @@ var draftFrom = (ctx, paths, id, values) => {
17107
17474
  paths: csv(values, "paths"),
17108
17475
  runs: list3(values, "run"),
17109
17476
  tests: stringOption(values, "tests"),
17110
- git: csv(values, "git").filter((item) => GIT_REQUIREMENTS.some((known2) => known2 === item)),
17477
+ git: csv(values, "git").filter((item2) => GIT_REQUIREMENTS.some((known2) => known2 === item2)),
17111
17478
  manual: list3(values, "manual"),
17112
17479
  budget: files === void 0 && lines3 === void 0 ? void 0 : { ...files === void 0 ? {} : { files }, ...lines3 === void 0 ? {} : { lines: lines3 } }
17113
17480
  };
@@ -17219,11 +17586,18 @@ var taskDoneCommand = {
17219
17586
  run: (ctx, args2) => Promise.resolve(doneTask(ctx, args2.positionals[0]))
17220
17587
  };
17221
17588
 
17589
+ // src/install/githook-block.ts
17590
+ var withoutHookBlock = (text6) => withoutManagedBlock(text6, { begin: BLOCK_BEGIN, end: BLOCK_END });
17591
+
17222
17592
  // src/install/uninstall.ts
17593
+ var blockless = (kind, current2) => {
17594
+ if (kind === "gitignore") return withoutBlock(current2);
17595
+ return kind === "markdown" ? withoutMarkdownBlock(current2) : withoutHookBlock(current2);
17596
+ };
17223
17597
  var structural = (entry2, current2) => {
17224
- if (entry2.kind === "gitignore" || entry2.kind === "markdown") {
17225
- const text6 = entry2.kind === "gitignore" ? withoutBlock(current2) : withoutMarkdownBlock(current2);
17226
- return text6 === "" && !entry2.existed ? void 0 : text6;
17598
+ if (entry2.kind === "gitignore" || entry2.kind === "markdown" || entry2.kind === "githook") {
17599
+ const text6 = blockless(entry2.kind, current2);
17600
+ return text6.trim() === "" && !entry2.existed ? void 0 : text6;
17227
17601
  }
17228
17602
  const parsed = parseSettings(current2);
17229
17603
  if (!parsed.ok || entry2.added === void 0) return current2;
@@ -17254,13 +17628,13 @@ var planRemoval = (project, lock, options) => ({
17254
17628
  purge: options.purge,
17255
17629
  removeDirs: lock.createdDirs.filter((dir) => dir !== STATE_DIR).toSorted((a, b) => b.split("/").length - a.split("/").length)
17256
17630
  });
17257
- var applyItem = (project, item) => {
17258
- if (item.action === "delete") {
17259
- const removed = project.runtime.remove(project.abs(item.path));
17260
- return removed.ok ? removed : reject({ path: item.path, detail: removed.error });
17631
+ var applyItem = (project, item2) => {
17632
+ if (item2.action === "delete") {
17633
+ const removed = project.runtime.remove(project.abs(item2.path));
17634
+ return removed.ok ? removed : reject({ path: item2.path, detail: removed.error });
17261
17635
  }
17262
- if (item.after === void 0 || item.action === "gone" || item.action.startsWith("keep")) return accept(void 0);
17263
- return writeText(project, item.path, item.after);
17636
+ if (item2.after === void 0 || item2.action === "gone" || item2.action.startsWith("keep")) return accept(void 0);
17637
+ return writeText(project, item2.path, item2.after);
17264
17638
  };
17265
17639
  var isEmptyDir = (project, relative9) => {
17266
17640
  const listed4 = project.runtime.listDir(project.abs(relative9));
@@ -17275,8 +17649,8 @@ var finish3 = (project, plan) => {
17275
17649
  };
17276
17650
  var applyRemoval = (project, plan) => {
17277
17651
  const changed = withInstallLock(project, () => {
17278
- for (const item of plan.items) {
17279
- const done = applyItem(project, item);
17652
+ for (const item2 of plan.items) {
17653
+ const done = applyItem(project, item2);
17280
17654
  if (!done.ok) return done;
17281
17655
  }
17282
17656
  return accept(void 0);
@@ -17301,7 +17675,7 @@ var dataOf2 = (root, plan, applied) => ({
17301
17675
  purge: plan.purge,
17302
17676
  root,
17303
17677
  actions: plan.items.map(({ path, action }) => ({ path, action })),
17304
- kept: plan.items.filter((item) => item.action.startsWith("keep")).map(({ path }) => path),
17678
+ kept: plan.items.filter((item2) => item2.action.startsWith("keep")).map(({ path }) => path),
17305
17679
  outside: OUTSIDE.map(({ command }) => command)
17306
17680
  });
17307
17681
  var linesOf2 = (ctx, data) => [
@@ -17422,6 +17796,9 @@ var REGISTRY = [
17422
17796
  rescueCommand,
17423
17797
  mergeVerifyCommand,
17424
17798
  appCheckCommand,
17799
+ commitMsgCommand,
17800
+ pushCheckCommand,
17801
+ ciGateCommand,
17425
17802
  versionCommand,
17426
17803
  helpCommand
17427
17804
  ];