finitemonkey 0.1.0-next.24 → 0.1.0-next.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/finitemonkey.mjs +1407 -916
- package/package.json +1 -1
package/dist/finitemonkey.mjs
CHANGED
|
@@ -28,15 +28,15 @@ import { dirname } from "node:path";
|
|
|
28
28
|
var accept = (value) => ({ ok: true, value });
|
|
29
29
|
var reject = (error) => ({ ok: false, error });
|
|
30
30
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31
|
-
var parseJson = (
|
|
31
|
+
var parseJson = (text9) => {
|
|
32
32
|
try {
|
|
33
|
-
return accept(JSON.parse(
|
|
33
|
+
return accept(JSON.parse(text9));
|
|
34
34
|
} catch (error) {
|
|
35
35
|
return reject(error instanceof Error ? error.message : String(error));
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
|
-
var parseJsonObject = (
|
|
39
|
-
const parsed = parseJson(
|
|
38
|
+
var parseJsonObject = (text9) => {
|
|
39
|
+
const parsed = parseJson(text9);
|
|
40
40
|
if (!parsed.ok) return parsed;
|
|
41
41
|
return isRecord(parsed.value) ? accept(parsed.value) : reject("expected a JSON object");
|
|
42
42
|
};
|
|
@@ -160,13 +160,13 @@ var attempt = (work) => {
|
|
|
160
160
|
return reject(messageOf(error));
|
|
161
161
|
}
|
|
162
162
|
};
|
|
163
|
-
var nodeWriteText = (path,
|
|
163
|
+
var nodeWriteText = (path, text9) => {
|
|
164
164
|
const temp = `${path}.${String(process.pid)}.tmp`;
|
|
165
165
|
try {
|
|
166
166
|
mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
|
|
167
167
|
const fd = openSync(temp, "w", 384);
|
|
168
168
|
try {
|
|
169
|
-
writeSync(fd,
|
|
169
|
+
writeSync(fd, text9);
|
|
170
170
|
fsyncSync(fd);
|
|
171
171
|
} finally {
|
|
172
172
|
closeSync(fd);
|
|
@@ -770,6 +770,497 @@ var appCheckCommand = {
|
|
|
770
770
|
}
|
|
771
771
|
};
|
|
772
772
|
|
|
773
|
+
// src/git/facts.ts
|
|
774
|
+
var memo = (compute) => {
|
|
775
|
+
let cached3;
|
|
776
|
+
return () => {
|
|
777
|
+
cached3 ??= { value: compute() };
|
|
778
|
+
return cached3.value;
|
|
779
|
+
};
|
|
780
|
+
};
|
|
781
|
+
var memoBy = (compute) => {
|
|
782
|
+
const cache = /* @__PURE__ */ new Map();
|
|
783
|
+
return (key) => {
|
|
784
|
+
const hit3 = cache.get(key);
|
|
785
|
+
if (hit3 !== void 0) return hit3.value;
|
|
786
|
+
const value = compute(key);
|
|
787
|
+
cache.set(key, { value });
|
|
788
|
+
return value;
|
|
789
|
+
};
|
|
790
|
+
};
|
|
791
|
+
var STATE_PREFIX = ".finitemonkey/";
|
|
792
|
+
var PORCELAIN_FIELDS = { ordinary: 8, renamed: 9, unmerged: 10 };
|
|
793
|
+
var entryOf = (kind, xy, path) => ({
|
|
794
|
+
path,
|
|
795
|
+
kind,
|
|
796
|
+
staged: kind !== "untracked" && !xy.startsWith("."),
|
|
797
|
+
unstaged: kind === "untracked" || xy.charAt(1) !== "."
|
|
798
|
+
});
|
|
799
|
+
var afterFields = (record3, count4) => record3.split(" ").slice(count4).join(" ");
|
|
800
|
+
var parsePorcelainV2 = (stdout) => {
|
|
801
|
+
const records = stdout.split("\0");
|
|
802
|
+
const out = [];
|
|
803
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
804
|
+
const record3 = records[index] ?? "";
|
|
805
|
+
const xy = record3.split(" ")[1] ?? "..";
|
|
806
|
+
if (record3.startsWith("1 ")) out.push(entryOf("changed", xy, afterFields(record3, PORCELAIN_FIELDS.ordinary)));
|
|
807
|
+
else if (record3.startsWith("2 ")) {
|
|
808
|
+
out.push(entryOf("changed", xy, afterFields(record3, PORCELAIN_FIELDS.renamed)));
|
|
809
|
+
index += 1;
|
|
810
|
+
} else if (record3.startsWith("u ")) {
|
|
811
|
+
out.push(entryOf("unmerged", xy, afterFields(record3, PORCELAIN_FIELDS.unmerged)));
|
|
812
|
+
} else if (record3.startsWith("? ")) out.push(entryOf("untracked", "??", record3.slice(2)));
|
|
813
|
+
}
|
|
814
|
+
return out.filter((entry2) => entry2.path !== "" && !entry2.path.startsWith(STATE_PREFIX));
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
// src/git/project-config.ts
|
|
818
|
+
import { join as join2 } from "node:path";
|
|
819
|
+
var CONFIG_FILE = ".finitemonkey/config.yml";
|
|
820
|
+
var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
|
|
821
|
+
var stripComment = (line2) => {
|
|
822
|
+
let quote;
|
|
823
|
+
for (let index = 0; index < line2.length; index += 1) {
|
|
824
|
+
const char = line2.charAt(index);
|
|
825
|
+
if (quote === void 0 && (char === '"' || char === "'")) quote = char;
|
|
826
|
+
else if (char === quote) quote = void 0;
|
|
827
|
+
else if (quote === void 0 && char === "#" && (index === 0 || /\s/.test(line2.charAt(index - 1)))) {
|
|
828
|
+
return line2.slice(0, index);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
return line2;
|
|
832
|
+
};
|
|
833
|
+
var flowList = (value) => {
|
|
834
|
+
const trimmed = value.trim();
|
|
835
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
|
|
836
|
+
return trimmed.slice(1, -1).split(",").map(unquote).filter((item2) => item2 !== "");
|
|
837
|
+
};
|
|
838
|
+
var keyValue = (body2) => {
|
|
839
|
+
const match = /^("[^"]*"|'[^']*'|[^:]+?)\s*:(?:\s+(.*))?$/.exec(body2);
|
|
840
|
+
if (match === null) return void 0;
|
|
841
|
+
return { key: unquote(match[1] ?? ""), value: match[2] ?? "" };
|
|
842
|
+
};
|
|
843
|
+
var addItem = (frame, item2) => {
|
|
844
|
+
if (frame.key === void 0) return;
|
|
845
|
+
const parent = frame.tree[frame.key];
|
|
846
|
+
if (Array.isArray(parent)) parent.push(item2);
|
|
847
|
+
else frame.tree[frame.key] = [item2];
|
|
848
|
+
};
|
|
849
|
+
var placeLine = (stack, indent, body2) => {
|
|
850
|
+
while (stack.length > 1 && (stack[stack.length - 1]?.indent ?? 0) >= indent && !body2.startsWith("- ")) stack.pop();
|
|
851
|
+
const top = stack[stack.length - 1];
|
|
852
|
+
if (top === void 0) return;
|
|
853
|
+
if (body2.startsWith("- ")) {
|
|
854
|
+
addItem(top, unquote(body2.slice(2)));
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
const pair = keyValue(body2);
|
|
858
|
+
if (pair === void 0) return;
|
|
859
|
+
const parentTree = top.key === void 0 ? top.tree : ensureTree(top.tree, top.key);
|
|
860
|
+
if (pair.value === "") {
|
|
861
|
+
stack.push({ indent, tree: parentTree, key: pair.key });
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
parentTree[pair.key] = flowList(pair.value) ?? unquote(pair.value);
|
|
865
|
+
};
|
|
866
|
+
var ensureTree = (tree, key) => {
|
|
867
|
+
const existing = tree[key];
|
|
868
|
+
if (existing !== void 0 && typeof existing !== "string" && !Array.isArray(existing)) return existing;
|
|
869
|
+
const fresh3 = {};
|
|
870
|
+
tree[key] = fresh3;
|
|
871
|
+
return fresh3;
|
|
872
|
+
};
|
|
873
|
+
var parseConfigYaml = (text9) => {
|
|
874
|
+
const root = {};
|
|
875
|
+
const stack = [{ indent: -1, tree: root, key: void 0 }];
|
|
876
|
+
for (const raw of text9.split(/\r?\n/)) {
|
|
877
|
+
const line2 = stripComment(raw).trimEnd();
|
|
878
|
+
if (line2.trim() === "" || line2.trim() === "---") continue;
|
|
879
|
+
const indent = line2.length - line2.trimStart().length;
|
|
880
|
+
placeLine(stack, indent, line2.trim());
|
|
881
|
+
}
|
|
882
|
+
return root;
|
|
883
|
+
};
|
|
884
|
+
var isTree = (node) => typeof node === "object" && !Array.isArray(node);
|
|
885
|
+
var configAt = (tree, ...path) => path.reduce((node, key) => isTree(node) ? node[key] : void 0, tree);
|
|
886
|
+
var configList = (tree, ...path) => {
|
|
887
|
+
const node = configAt(tree, ...path);
|
|
888
|
+
if (typeof node === "string") return node === "" ? [] : [node];
|
|
889
|
+
return Array.isArray(node) ? node.filter((item2) => typeof item2 === "string") : [];
|
|
890
|
+
};
|
|
891
|
+
var configMap = (tree, ...path) => {
|
|
892
|
+
const node = configAt(tree, ...path);
|
|
893
|
+
if (!isTree(node)) return {};
|
|
894
|
+
return Object.fromEntries(
|
|
895
|
+
Object.entries(node).filter((entry2) => typeof entry2[1] === "string")
|
|
896
|
+
);
|
|
897
|
+
};
|
|
898
|
+
var loadProjectConfig = (runtime, root) => {
|
|
899
|
+
const text9 = runtime.readText(toAbsolutePath(join2(root, CONFIG_FILE)));
|
|
900
|
+
return text9.ok ? parseConfigYaml(text9.value) : {};
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
// src/git/repo.ts
|
|
904
|
+
var DEFAULT_PROTECTED = ["main", "master", "release/*", "prod", "production", "rc-*"];
|
|
905
|
+
var openRepo = (runtime, dir) => {
|
|
906
|
+
const result = runtime.git(["rev-parse", "--show-toplevel", "--absolute-git-dir", "--show-prefix"], dir);
|
|
907
|
+
if (!result.ok) return void 0;
|
|
908
|
+
const [root, gitDir, prefix] = result.stdout.split("\n");
|
|
909
|
+
if (root === void 0 || root === "" || gitDir === void 0 || gitDir === "") return void 0;
|
|
910
|
+
return {
|
|
911
|
+
root: toAbsolutePath(root),
|
|
912
|
+
gitDir: toAbsolutePath(gitDir),
|
|
913
|
+
prefix: (prefix ?? "").replace(/\/$/, "")
|
|
914
|
+
};
|
|
915
|
+
};
|
|
916
|
+
var globRegex = (pattern) => new RegExp(`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`);
|
|
917
|
+
var matchesBranch = (branch, patterns) => patterns.some((pattern) => globRegex(pattern).test(branch));
|
|
918
|
+
var line = (runtime, root, args2) => {
|
|
919
|
+
const result = runtime.git(args2, root);
|
|
920
|
+
const value = result.stdout.trim();
|
|
921
|
+
return result.ok && value !== "" ? value : void 0;
|
|
922
|
+
};
|
|
923
|
+
var refExists = (runtime, root, ref) => runtime.git(["show-ref", "--verify", "--quiet", ref], root).ok;
|
|
924
|
+
var defaultBranchOf = (runtime, root) => {
|
|
925
|
+
const remote = line(runtime, root, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
|
|
926
|
+
if (remote !== void 0) return remote.replace(/^[^/]+\//, "");
|
|
927
|
+
const configured = line(runtime, root, ["config", "--get", "init.defaultBranch"]);
|
|
928
|
+
if (configured !== void 0 && refExists(runtime, root, `refs/heads/${configured}`)) return configured;
|
|
929
|
+
return refExists(runtime, root, "refs/heads/master") && !refExists(runtime, root, "refs/heads/main") ? "master" : "main";
|
|
930
|
+
};
|
|
931
|
+
var upstreamOf = (runtime, root, branch) => {
|
|
932
|
+
const remote = line(runtime, root, ["config", "--get", `branch.${branch}.remote`]);
|
|
933
|
+
const merge = line(runtime, root, ["config", "--get", `branch.${branch}.merge`]);
|
|
934
|
+
if (remote === void 0 || merge === void 0 || remote === ".") return void 0;
|
|
935
|
+
const name = merge.replace(/^refs\/heads\//, "");
|
|
936
|
+
return { branch: name, remoteRef: `refs/remotes/${remote}/${name}` };
|
|
937
|
+
};
|
|
938
|
+
var integrationRefs = (runtime, root, defaultBranch) => [`refs/heads/${defaultBranch}`, `refs/remotes/origin/${defaultBranch}`].filter(
|
|
939
|
+
(ref) => refExists(runtime, root, ref)
|
|
940
|
+
);
|
|
941
|
+
var contained = (runtime, root, branch, targets2) => {
|
|
942
|
+
const tip = line(runtime, root, ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]);
|
|
943
|
+
if (tip === void 0) return true;
|
|
944
|
+
return targets2.filter((target2) => target2 !== `refs/heads/${branch}`).some((target2) => runtime.git(["merge-base", "--is-ancestor", tip, target2], root).ok);
|
|
945
|
+
};
|
|
946
|
+
var shared = (runtime, root, upstream, base) => {
|
|
947
|
+
if (upstream === void 0 || !refExists(runtime, root, upstream.remoteRef)) return false;
|
|
948
|
+
const own = /<([^>]*)>/.exec(line(runtime, root, ["var", "GIT_AUTHOR_IDENT"]) ?? "")?.[1] ?? "";
|
|
949
|
+
const log = runtime.git(["log", "--format=%ae", upstream.remoteRef, ...base.map((ref) => `^${ref}`)], root);
|
|
950
|
+
return log.ok && log.stdout.split("\n").some((email) => email.trim() !== "" && email.trim() !== own);
|
|
951
|
+
};
|
|
952
|
+
var protectedPatterns = (config, defaultBranch) => [
|
|
953
|
+
defaultBranch,
|
|
954
|
+
...DEFAULT_PROTECTED,
|
|
955
|
+
...configList(config, "git", "protected")
|
|
956
|
+
];
|
|
957
|
+
var repoFacts = (runtime, repo, config = loadProjectConfig(runtime, repo.root)) => {
|
|
958
|
+
const { root } = repo;
|
|
959
|
+
const defaultBranch = memo(() => defaultBranchOf(runtime, root));
|
|
960
|
+
const patterns = memo(() => protectedPatterns(config, defaultBranch()));
|
|
961
|
+
const upstream = memoBy((branch) => upstreamOf(runtime, root, branch));
|
|
962
|
+
const integration = memo(() => integrationRefs(runtime, root, defaultBranch()));
|
|
963
|
+
return {
|
|
964
|
+
prefix: repo.prefix,
|
|
965
|
+
currentBranch: memo(() => line(runtime, root, ["symbolic-ref", "--quiet", "--short", "HEAD"])),
|
|
966
|
+
defaultBranch,
|
|
967
|
+
isProtected: (branch) => matchesBranch(branch, patterns()),
|
|
968
|
+
dirty: memo(() => {
|
|
969
|
+
const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
|
|
970
|
+
return status.ok ? parsePorcelainV2(status.stdout) : void 0;
|
|
971
|
+
}),
|
|
972
|
+
isRef: memoBy((name) => runtime.git(["rev-parse", "--verify", "--quiet", `${name}^{commit}`], root).ok),
|
|
973
|
+
branchExists: memoBy((name) => refExists(runtime, root, `refs/heads/${name}`)),
|
|
974
|
+
branchContained: memoBy((branch) => {
|
|
975
|
+
const up = upstream(branch);
|
|
976
|
+
return contained(runtime, root, branch, [...up === void 0 ? [] : [up.remoteRef], ...integration()]);
|
|
977
|
+
}),
|
|
978
|
+
upstream,
|
|
979
|
+
sharedBranch: memoBy((branch) => shared(runtime, root, upstream(branch), integration()))
|
|
980
|
+
};
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
// src/hooks/stdin.ts
|
|
984
|
+
var readAll = (source) => new Promise((resolve2, reject2) => {
|
|
985
|
+
if (source.isTTY === true) {
|
|
986
|
+
resolve2("");
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const chunks = [];
|
|
990
|
+
source.setEncoding?.("utf8");
|
|
991
|
+
source.on("data", (chunk) => {
|
|
992
|
+
chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
|
993
|
+
});
|
|
994
|
+
source.on("end", () => {
|
|
995
|
+
resolve2(chunks.join(""));
|
|
996
|
+
});
|
|
997
|
+
source.on("error", reject2);
|
|
998
|
+
});
|
|
999
|
+
var DeadlineError = class extends Error {
|
|
1000
|
+
ms;
|
|
1001
|
+
constructor(ms) {
|
|
1002
|
+
super(`deadline of ${String(ms)} ms exceeded`);
|
|
1003
|
+
this.name = "DeadlineError";
|
|
1004
|
+
this.ms = ms;
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
var withDeadline = (work, ms, startTimer) => new Promise((resolve2, reject2) => {
|
|
1008
|
+
const cancel = startTimer(ms, () => {
|
|
1009
|
+
reject2(new DeadlineError(ms));
|
|
1010
|
+
});
|
|
1011
|
+
work.then(
|
|
1012
|
+
(value) => {
|
|
1013
|
+
cancel();
|
|
1014
|
+
resolve2(value);
|
|
1015
|
+
},
|
|
1016
|
+
(error) => {
|
|
1017
|
+
cancel();
|
|
1018
|
+
reject2(error instanceof Error ? error : new Error(String(error)));
|
|
1019
|
+
}
|
|
1020
|
+
);
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
// src/review/findings.ts
|
|
1024
|
+
var GROUPS = ["must", "should", "nice"];
|
|
1025
|
+
var VERDICTS = ["approve", "approve-with-changes", "request-changes"];
|
|
1026
|
+
var text2 = (record3, key) => {
|
|
1027
|
+
const value = record3[key];
|
|
1028
|
+
return typeof value === "string" ? value.trim() : "";
|
|
1029
|
+
};
|
|
1030
|
+
var reproOf = (value) => {
|
|
1031
|
+
if (!isRecord(value)) return void 0;
|
|
1032
|
+
const command = text2(value, "command");
|
|
1033
|
+
const expect = text2(value, "expect");
|
|
1034
|
+
return command === "" || expect === "" ? void 0 : { command, expect };
|
|
1035
|
+
};
|
|
1036
|
+
var findingOf = (value, index) => {
|
|
1037
|
+
if (!isRecord(value)) return `finding ${String(index + 1)} is not an object`;
|
|
1038
|
+
const group = GROUPS.find((item2) => item2 === value["group"]);
|
|
1039
|
+
if (group === void 0) return `finding ${String(index + 1)} needs group must, should or nice`;
|
|
1040
|
+
const fields = ["what", "problem", "fix", "ifNotFixed"];
|
|
1041
|
+
const missing2 = fields.filter((key) => text2(value, key) === "");
|
|
1042
|
+
if (missing2.length > 0) return `finding ${String(index + 1)} is missing ${missing2.join(", ")}`;
|
|
1043
|
+
const repro = reproOf(value["repro"]);
|
|
1044
|
+
const demoted = group === "must" && repro === void 0;
|
|
1045
|
+
return {
|
|
1046
|
+
id: index + 1,
|
|
1047
|
+
group: demoted ? "should" : group,
|
|
1048
|
+
what: text2(value, "what"),
|
|
1049
|
+
problem: text2(value, "problem"),
|
|
1050
|
+
fix: text2(value, "fix"),
|
|
1051
|
+
ifNotFixed: text2(value, "ifNotFixed"),
|
|
1052
|
+
...repro === void 0 ? {} : { repro },
|
|
1053
|
+
activeRisk: value["activeRisk"] === true,
|
|
1054
|
+
demoted
|
|
1055
|
+
};
|
|
1056
|
+
};
|
|
1057
|
+
var parseCritique = (value) => {
|
|
1058
|
+
if (!isRecord(value)) return ["the critique must be a JSON object"];
|
|
1059
|
+
const verdict = VERDICTS.find((item2) => item2 === value["verdict"]);
|
|
1060
|
+
const list4 = value["findings"];
|
|
1061
|
+
const parsed = Array.isArray(list4) ? list4.map(findingOf) : ["findings must be a list"];
|
|
1062
|
+
const errors = [
|
|
1063
|
+
...verdict === void 0 ? [`verdict must be one of ${VERDICTS.join(", ")}`] : [],
|
|
1064
|
+
...parsed.filter((item2) => typeof item2 === "string")
|
|
1065
|
+
];
|
|
1066
|
+
if (errors.length > 0 || verdict === void 0) return errors;
|
|
1067
|
+
return { verdict, findings: parsed.filter((item2) => typeof item2 !== "string") };
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
// src/review/report.ts
|
|
1071
|
+
import { join as join4 } from "node:path";
|
|
1072
|
+
|
|
1073
|
+
// src/review/reproduce.ts
|
|
1074
|
+
import { join as join3 } from "node:path";
|
|
1075
|
+
var REVIEW_LEDGER = ".finitemonkey/review/ledger.jsonl";
|
|
1076
|
+
var REPRO_TIMEOUT_MS = 12e4;
|
|
1077
|
+
var fails = (run4) => run4 !== void 0 && run4.exitCode !== 0;
|
|
1078
|
+
var outcomeOf = (base, head) => {
|
|
1079
|
+
if (base === void 0 || head === void 0 || head.exitCode === null || base.exitCode === null) return "not-run";
|
|
1080
|
+
if (fails(head) && !fails(base)) return "head-only";
|
|
1081
|
+
return fails(head) && fails(base) ? "already-there" : "not-reproduced";
|
|
1082
|
+
};
|
|
1083
|
+
var worktreeDir = (root, side) => toAbsolutePath(join3(root, ".finitemonkey", "review", `worktree-${side}`));
|
|
1084
|
+
var runAt = (runtime, root, side, rev, findings) => {
|
|
1085
|
+
const dir = worktreeDir(root, side);
|
|
1086
|
+
runtime.git(["worktree", "remove", "--force", dir], root);
|
|
1087
|
+
const added = runtime.git(["worktree", "add", "--detach", "--force", dir, rev], root);
|
|
1088
|
+
if (!added.ok) return [];
|
|
1089
|
+
try {
|
|
1090
|
+
return findings.flatMap((finding4) => {
|
|
1091
|
+
if (finding4.repro === void 0) return [];
|
|
1092
|
+
const result = runtime.exec({
|
|
1093
|
+
command: finding4.repro.command,
|
|
1094
|
+
args: [],
|
|
1095
|
+
cwd: dir,
|
|
1096
|
+
timeoutMs: REPRO_TIMEOUT_MS,
|
|
1097
|
+
shell: true
|
|
1098
|
+
});
|
|
1099
|
+
const exitCode = result.found && !result.timedOut ? result.exitCode : null;
|
|
1100
|
+
return [{ finding: finding4.id, rev, side, command: finding4.repro.command, exitCode, at: runtime.now() }];
|
|
1101
|
+
});
|
|
1102
|
+
} finally {
|
|
1103
|
+
runtime.git(["worktree", "remove", "--force", dir], root);
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
var reproduce = (runtime, root, sides, findings) => {
|
|
1107
|
+
const musts = findings.filter((finding4) => finding4.group === "must");
|
|
1108
|
+
const runs = [...runAt(runtime, root, "base", sides.base, musts), ...runAt(runtime, root, "head", sides.head, musts)];
|
|
1109
|
+
if (runs.length > 0) {
|
|
1110
|
+
runtime.mkdir(toAbsolutePath(join3(root, ".finitemonkey", "review")));
|
|
1111
|
+
runtime.appendText(
|
|
1112
|
+
toAbsolutePath(join3(root, REVIEW_LEDGER)),
|
|
1113
|
+
runs.map((run4) => `${JSON.stringify(run4)}
|
|
1114
|
+
`).join("")
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
return findings.map((finding4) => {
|
|
1118
|
+
if (finding4.group !== "must") return { finding: finding4, outcome: "not-run" };
|
|
1119
|
+
const base = runs.find((run4) => run4.finding === finding4.id && run4.side === "base");
|
|
1120
|
+
const head = runs.find((run4) => run4.finding === finding4.id && run4.side === "head");
|
|
1121
|
+
return {
|
|
1122
|
+
finding: finding4,
|
|
1123
|
+
...base === void 0 ? {} : { base },
|
|
1124
|
+
...head === void 0 ? {} : { head },
|
|
1125
|
+
outcome: outcomeOf(base, head)
|
|
1126
|
+
};
|
|
1127
|
+
});
|
|
1128
|
+
};
|
|
1129
|
+
var settle = (item2) => {
|
|
1130
|
+
const { finding: finding4, outcome: outcome2 } = item2;
|
|
1131
|
+
if (finding4.group !== "must" || outcome2 === "head-only" || outcome2 === "not-run")
|
|
1132
|
+
return { finding: finding4, group: finding4.group, decisionNeeded: false };
|
|
1133
|
+
if (outcome2 === "already-there") return { finding: finding4, group: "already-there", decisionNeeded: finding4.activeRisk };
|
|
1134
|
+
return { finding: finding4, group: "not-reproduced", decisionNeeded: false };
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
// src/review/report.ts
|
|
1138
|
+
var readLedger = (runtime, root) => {
|
|
1139
|
+
const raw = runtime.readText(toAbsolutePath(join4(root, REVIEW_LEDGER)));
|
|
1140
|
+
if (!raw.ok) return [];
|
|
1141
|
+
return raw.value.split("\n").flatMap((line2) => {
|
|
1142
|
+
const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
|
|
1143
|
+
if (parsed === void 0 || !parsed.ok || !isRecord(parsed.value)) return [];
|
|
1144
|
+
const { finding: finding4, side, command, exitCode } = parsed.value;
|
|
1145
|
+
if (typeof finding4 !== "number" || typeof side !== "string" || typeof command !== "string") return [];
|
|
1146
|
+
return [{ finding: finding4, side, command, exitCode: typeof exitCode === "number" ? exitCode : null }];
|
|
1147
|
+
});
|
|
1148
|
+
};
|
|
1149
|
+
var GROUP_ORDER = ["must", "should", "nice", "already-there", "not-reproduced"];
|
|
1150
|
+
var resultText = (row) => {
|
|
1151
|
+
if (row === void 0) return "-";
|
|
1152
|
+
return row.exitCode === null ? "did not run" : `exit ${String(row.exitCode)}`;
|
|
1153
|
+
};
|
|
1154
|
+
var findingLines = (t, settled) => GROUP_ORDER.flatMap((group) => {
|
|
1155
|
+
const items = settled.filter((item2) => item2.group === group);
|
|
1156
|
+
if (items.length === 0) return [];
|
|
1157
|
+
return [
|
|
1158
|
+
"",
|
|
1159
|
+
`## ${t(`critique.group.${group}`)}`,
|
|
1160
|
+
...items.map(
|
|
1161
|
+
({ finding: finding4 }) => t("critique.finding", {
|
|
1162
|
+
id: finding4.id,
|
|
1163
|
+
what: finding4.what,
|
|
1164
|
+
problem: finding4.problem,
|
|
1165
|
+
fix: finding4.fix,
|
|
1166
|
+
risk: finding4.ifNotFixed
|
|
1167
|
+
})
|
|
1168
|
+
)
|
|
1169
|
+
];
|
|
1170
|
+
});
|
|
1171
|
+
var tableLines = (t, checked, ledger) => {
|
|
1172
|
+
const rows = checked.filter((item2) => item2.finding.group === "must");
|
|
1173
|
+
if (rows.length === 0) return [];
|
|
1174
|
+
const last = (id, side) => ledger.filter((row) => row.finding === id && row.side === side).at(-1);
|
|
1175
|
+
return [
|
|
1176
|
+
"",
|
|
1177
|
+
`| # | ${t("critique.table.command")} | base | head | ${t("critique.table.outcome")} |`,
|
|
1178
|
+
"|---|---|---|---|---|",
|
|
1179
|
+
...rows.map(
|
|
1180
|
+
(item2) => `| ${String(item2.finding.id)} | \`${item2.finding.repro?.command ?? "-"}\` | ${resultText(last(item2.finding.id, "base"))} | ${resultText(last(item2.finding.id, "head"))} | ${t(`critique.outcome.${item2.outcome}`)} |`
|
|
1181
|
+
)
|
|
1182
|
+
];
|
|
1183
|
+
};
|
|
1184
|
+
var critiqueLines = (t, critique, checked, ledger) => {
|
|
1185
|
+
const settled = checked.map(settle);
|
|
1186
|
+
const demoted = critique.findings.filter((finding4) => finding4.demoted);
|
|
1187
|
+
const decisions = settled.filter((item2) => item2.decisionNeeded);
|
|
1188
|
+
return [
|
|
1189
|
+
`# ${t("critique.title")}`,
|
|
1190
|
+
...findingLines(t, settled),
|
|
1191
|
+
...tableLines(t, checked, ledger),
|
|
1192
|
+
...demoted.map((finding4) => t("critique.demoted", { id: finding4.id })),
|
|
1193
|
+
...decisions.map((item2) => t("critique.decision", { id: item2.finding.id, what: item2.finding.what })),
|
|
1194
|
+
"",
|
|
1195
|
+
t("critique.verdict", { verdict: critique.verdict })
|
|
1196
|
+
];
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
// src/commands/git-problems.ts
|
|
1200
|
+
var notRepository = (dir) => ({
|
|
1201
|
+
code: "not_a_git_repo",
|
|
1202
|
+
messageKey: "error.not_a_git_repo",
|
|
1203
|
+
params: { dir },
|
|
1204
|
+
fix: "git init",
|
|
1205
|
+
exitCode: ExitCode.NotConfigured
|
|
1206
|
+
});
|
|
1207
|
+
var gitProblem = (code, messageKey, detail, fix, exitCode = ExitCode.CheckFailed) => ({ code, messageKey, params: { detail }, fix, exitCode });
|
|
1208
|
+
var repoOf = (ctx) => openRepo(ctx.runtime, ctx.cwd);
|
|
1209
|
+
|
|
1210
|
+
// src/commands/critique.ts
|
|
1211
|
+
var CRITIQUE_SCHEMA = "finitemonkey/critique@1";
|
|
1212
|
+
var USAGE = "finitemonkey critique verify [--base <ref>] [--file <findings.json>] (or the findings JSON on stdin)";
|
|
1213
|
+
var findingsText = async (ctx, root, file) => {
|
|
1214
|
+
if (file === void 0) return ctx.io.stdin.isTTY === true ? void 0 : readAll(ctx.io.stdin);
|
|
1215
|
+
const text9 = ctx.runtime.readText(toAbsolutePath(file, root));
|
|
1216
|
+
return text9.ok ? text9.value : void 0;
|
|
1217
|
+
};
|
|
1218
|
+
var critiqueVerifyCommand = {
|
|
1219
|
+
path: ["critique", "verify"],
|
|
1220
|
+
summaryKey: "critique.command.verify",
|
|
1221
|
+
usage: USAGE,
|
|
1222
|
+
status: "ready",
|
|
1223
|
+
options: { file: { type: "string" }, base: { type: "string" } },
|
|
1224
|
+
run: async (ctx, args2) => {
|
|
1225
|
+
const repo = repoOf(ctx);
|
|
1226
|
+
if (repo === void 0) return failureFrom(ctx.t, notRepository(ctx.cwd));
|
|
1227
|
+
const file = stringOption(args2.values, "file");
|
|
1228
|
+
const raw = await findingsText(ctx, repo.root, file);
|
|
1229
|
+
const parsed = raw === void 0 ? void 0 : parseJsonObject(raw);
|
|
1230
|
+
const critique = parsed?.ok === true ? parseCritique(parsed.value) : [`${file ?? "stdin"} is missing or not JSON`];
|
|
1231
|
+
if (Array.isArray(critique) || !("verdict" in critique))
|
|
1232
|
+
return failureFrom(
|
|
1233
|
+
ctx.t,
|
|
1234
|
+
usageProblem("invalid_arguments", "critique.error.invalid", USAGE, { detail: critique.join("; ") })
|
|
1235
|
+
);
|
|
1236
|
+
const base = stringOption(args2.values, "base") ?? repoFacts(ctx.runtime, repo).defaultBranch();
|
|
1237
|
+
const head = ctx.runtime.git(["rev-parse", "HEAD"], repo.root).stdout.trim();
|
|
1238
|
+
const checked = reproduce(ctx.runtime, repo.root, { base, head }, critique.findings);
|
|
1239
|
+
const human2 = critiqueLines(ctx.t, critique, checked, readLedger(ctx.runtime, repo.root));
|
|
1240
|
+
const confirmed = checked.map(settle).filter((item2) => item2.group === "must").length;
|
|
1241
|
+
const data = {
|
|
1242
|
+
schema: CRITIQUE_SCHEMA,
|
|
1243
|
+
verdict: critique.verdict,
|
|
1244
|
+
base,
|
|
1245
|
+
head,
|
|
1246
|
+
checked: checked.map((item2) => ({ ...item2, settled: settle(item2).group }))
|
|
1247
|
+
};
|
|
1248
|
+
if (confirmed === 0) return success(data, human2);
|
|
1249
|
+
return failureFrom(
|
|
1250
|
+
ctx.t,
|
|
1251
|
+
{
|
|
1252
|
+
code: "critique_must_fix",
|
|
1253
|
+
messageKey: "critique.error.must_fix",
|
|
1254
|
+
params: { count: confirmed },
|
|
1255
|
+
fix: "fix the must-fix findings, then run finitemonkey critique verify again",
|
|
1256
|
+
exitCode: ExitCode.CheckFailed,
|
|
1257
|
+
data
|
|
1258
|
+
},
|
|
1259
|
+
human2
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
|
|
773
1264
|
// src/commands/doctor.ts
|
|
774
1265
|
import { dirname as dirname7 } from "node:path";
|
|
775
1266
|
|
|
@@ -778,13 +1269,13 @@ import { dirname as dirname3 } from "node:path";
|
|
|
778
1269
|
|
|
779
1270
|
// src/install/hash.ts
|
|
780
1271
|
import { createHash } from "node:crypto";
|
|
781
|
-
var hashText = (
|
|
1272
|
+
var hashText = (text9) => `sha256:${createHash("sha256").update(text9, "utf8").digest("hex")}`;
|
|
782
1273
|
|
|
783
1274
|
// src/install/layout.ts
|
|
784
1275
|
var STATE_DIR = ".finitemonkey";
|
|
785
1276
|
var SETTINGS_FILE = ".claude/settings.json";
|
|
786
1277
|
var GITIGNORE_FILE = ".gitignore";
|
|
787
|
-
var
|
|
1278
|
+
var CONFIG_FILE2 = ".finitemonkey/config.yml";
|
|
788
1279
|
var LOCK_FILE = ".finitemonkey/lock.json";
|
|
789
1280
|
var BACKUPS_DIR = ".finitemonkey/backups";
|
|
790
1281
|
var LOCKS_DIR = ".finitemonkey/locks";
|
|
@@ -869,8 +1360,8 @@ var statusLineCommand = (value) => {
|
|
|
869
1360
|
const command = value["command"];
|
|
870
1361
|
return value["type"] === "command" && typeof command === "string" && command.trim() !== "" ? command : void 0;
|
|
871
1362
|
};
|
|
872
|
-
var statusLineIn = (
|
|
873
|
-
const parsed =
|
|
1363
|
+
var statusLineIn = (text9) => {
|
|
1364
|
+
const parsed = text9 === void 0 ? void 0 : parseJsonObject(text9);
|
|
874
1365
|
return parsed?.ok === true ? parsed.value["statusLine"] : void 0;
|
|
875
1366
|
};
|
|
876
1367
|
var parseStatusLineAdded = (value) => {
|
|
@@ -943,122 +1434,36 @@ var fromRecord = (record3) => {
|
|
|
943
1434
|
}
|
|
944
1435
|
return accept({ schema, version, preset: known2, installedAt, updatedAt, createdDirs, entries });
|
|
945
1436
|
};
|
|
946
|
-
var parseLock = (
|
|
947
|
-
const parsed = parseJsonObject(
|
|
1437
|
+
var parseLock = (text9) => {
|
|
1438
|
+
const parsed = parseJsonObject(text9);
|
|
948
1439
|
return parsed.ok ? fromRecord(parsed.value) : reject(parsed.error);
|
|
949
1440
|
};
|
|
950
1441
|
var renderLock = (lock) => `${JSON.stringify(lock, null, 2)}
|
|
951
1442
|
`;
|
|
952
1443
|
|
|
953
|
-
// src/git/project-config.ts
|
|
954
|
-
import { join as join2 } from "node:path";
|
|
955
|
-
var CONFIG_FILE2 = ".finitemonkey/config.yml";
|
|
956
|
-
var unquote = (value) => value.trim().replace(/^(["'])(.*)\1$/s, "$2");
|
|
957
|
-
var stripComment = (line2) => {
|
|
958
|
-
let quote;
|
|
959
|
-
for (let index = 0; index < line2.length; index += 1) {
|
|
960
|
-
const char = line2.charAt(index);
|
|
961
|
-
if (quote === void 0 && (char === '"' || char === "'")) quote = char;
|
|
962
|
-
else if (char === quote) quote = void 0;
|
|
963
|
-
else if (quote === void 0 && char === "#" && (index === 0 || /\s/.test(line2.charAt(index - 1)))) {
|
|
964
|
-
return line2.slice(0, index);
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
return line2;
|
|
968
|
-
};
|
|
969
|
-
var flowList = (value) => {
|
|
970
|
-
const trimmed = value.trim();
|
|
971
|
-
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
|
|
972
|
-
return trimmed.slice(1, -1).split(",").map(unquote).filter((item2) => item2 !== "");
|
|
973
|
-
};
|
|
974
|
-
var keyValue = (body2) => {
|
|
975
|
-
const match = /^("[^"]*"|'[^']*'|[^:]+?)\s*:(?:\s+(.*))?$/.exec(body2);
|
|
976
|
-
if (match === null) return void 0;
|
|
977
|
-
return { key: unquote(match[1] ?? ""), value: match[2] ?? "" };
|
|
978
|
-
};
|
|
979
|
-
var addItem = (frame, item2) => {
|
|
980
|
-
if (frame.key === void 0) return;
|
|
981
|
-
const parent = frame.tree[frame.key];
|
|
982
|
-
if (Array.isArray(parent)) parent.push(item2);
|
|
983
|
-
else frame.tree[frame.key] = [item2];
|
|
984
|
-
};
|
|
985
|
-
var placeLine = (stack, indent, body2) => {
|
|
986
|
-
while (stack.length > 1 && (stack[stack.length - 1]?.indent ?? 0) >= indent && !body2.startsWith("- ")) stack.pop();
|
|
987
|
-
const top = stack[stack.length - 1];
|
|
988
|
-
if (top === void 0) return;
|
|
989
|
-
if (body2.startsWith("- ")) {
|
|
990
|
-
addItem(top, unquote(body2.slice(2)));
|
|
991
|
-
return;
|
|
992
|
-
}
|
|
993
|
-
const pair = keyValue(body2);
|
|
994
|
-
if (pair === void 0) return;
|
|
995
|
-
const parentTree = top.key === void 0 ? top.tree : ensureTree(top.tree, top.key);
|
|
996
|
-
if (pair.value === "") {
|
|
997
|
-
stack.push({ indent, tree: parentTree, key: pair.key });
|
|
998
|
-
return;
|
|
999
|
-
}
|
|
1000
|
-
parentTree[pair.key] = flowList(pair.value) ?? unquote(pair.value);
|
|
1001
|
-
};
|
|
1002
|
-
var ensureTree = (tree, key) => {
|
|
1003
|
-
const existing = tree[key];
|
|
1004
|
-
if (existing !== void 0 && typeof existing !== "string" && !Array.isArray(existing)) return existing;
|
|
1005
|
-
const fresh3 = {};
|
|
1006
|
-
tree[key] = fresh3;
|
|
1007
|
-
return fresh3;
|
|
1008
|
-
};
|
|
1009
|
-
var parseConfigYaml = (text8) => {
|
|
1010
|
-
const root = {};
|
|
1011
|
-
const stack = [{ indent: -1, tree: root, key: void 0 }];
|
|
1012
|
-
for (const raw of text8.split(/\r?\n/)) {
|
|
1013
|
-
const line2 = stripComment(raw).trimEnd();
|
|
1014
|
-
if (line2.trim() === "" || line2.trim() === "---") continue;
|
|
1015
|
-
const indent = line2.length - line2.trimStart().length;
|
|
1016
|
-
placeLine(stack, indent, line2.trim());
|
|
1017
|
-
}
|
|
1018
|
-
return root;
|
|
1019
|
-
};
|
|
1020
|
-
var isTree = (node) => typeof node === "object" && !Array.isArray(node);
|
|
1021
|
-
var configAt = (tree, ...path) => path.reduce((node, key) => isTree(node) ? node[key] : void 0, tree);
|
|
1022
|
-
var configList = (tree, ...path) => {
|
|
1023
|
-
const node = configAt(tree, ...path);
|
|
1024
|
-
if (typeof node === "string") return node === "" ? [] : [node];
|
|
1025
|
-
return Array.isArray(node) ? node.filter((item2) => typeof item2 === "string") : [];
|
|
1026
|
-
};
|
|
1027
|
-
var configMap = (tree, ...path) => {
|
|
1028
|
-
const node = configAt(tree, ...path);
|
|
1029
|
-
if (!isTree(node)) return {};
|
|
1030
|
-
return Object.fromEntries(
|
|
1031
|
-
Object.entries(node).filter((entry2) => typeof entry2[1] === "string")
|
|
1032
|
-
);
|
|
1033
|
-
};
|
|
1034
|
-
var loadProjectConfig = (runtime, root) => {
|
|
1035
|
-
const text8 = runtime.readText(toAbsolutePath(join2(root, CONFIG_FILE2)));
|
|
1036
|
-
return text8.ok ? parseConfigYaml(text8.value) : {};
|
|
1037
|
-
};
|
|
1038
|
-
|
|
1039
1444
|
// src/install/managed-block.ts
|
|
1040
|
-
var lineBreak = (
|
|
1445
|
+
var lineBreak = (text9) => text9.includes("\r\n") ? "\r\n" : "\n";
|
|
1041
1446
|
var blockRange = (lines3, markers) => {
|
|
1042
1447
|
const start = lines3.findIndex((line2) => line2.trimEnd() === markers.begin);
|
|
1043
1448
|
if (start < 0) return void 0;
|
|
1044
1449
|
const end = lines3.findIndex((line2, index) => index > start && line2.trimEnd() === markers.end);
|
|
1045
1450
|
return end < 0 ? void 0 : { start, end };
|
|
1046
1451
|
};
|
|
1047
|
-
var withManagedBlock = (
|
|
1048
|
-
const eol =
|
|
1452
|
+
var withManagedBlock = (text9, markers, body2) => {
|
|
1453
|
+
const eol = text9 === void 0 ? "\n" : lineBreak(text9);
|
|
1049
1454
|
const block4 = [markers.begin, ...body2, markers.end];
|
|
1050
|
-
if (
|
|
1051
|
-
const lines3 =
|
|
1455
|
+
if (text9 === void 0 || text9 === "") return `${block4.join(eol)}${eol}`;
|
|
1456
|
+
const lines3 = text9.split(eol);
|
|
1052
1457
|
const range = blockRange(lines3, markers);
|
|
1053
1458
|
if (range !== void 0) return [...lines3.slice(0, range.start), ...block4, ...lines3.slice(range.end + 1)].join(eol);
|
|
1054
|
-
const separator =
|
|
1055
|
-
return `${
|
|
1459
|
+
const separator = text9.endsWith(eol) ? "" : eol;
|
|
1460
|
+
return `${text9}${separator}${block4.join(eol)}${eol}`;
|
|
1056
1461
|
};
|
|
1057
|
-
var withoutManagedBlock = (
|
|
1058
|
-
const eol = lineBreak(
|
|
1059
|
-
const lines3 =
|
|
1462
|
+
var withoutManagedBlock = (text9, markers) => {
|
|
1463
|
+
const eol = lineBreak(text9);
|
|
1464
|
+
const lines3 = text9.split(eol);
|
|
1060
1465
|
const range = blockRange(lines3, markers);
|
|
1061
|
-
if (range === void 0) return
|
|
1466
|
+
if (range === void 0) return text9;
|
|
1062
1467
|
return [...lines3.slice(0, range.start), ...lines3.slice(range.end + 1)].join(eol);
|
|
1063
1468
|
};
|
|
1064
1469
|
|
|
@@ -1155,7 +1560,7 @@ var configTemplate = (preset, lang) => [
|
|
|
1155
1560
|
`lang: ${lang}`,
|
|
1156
1561
|
""
|
|
1157
1562
|
].join("\n");
|
|
1158
|
-
var hasMarker = (
|
|
1563
|
+
var hasMarker = (text9) => text9.includes(GENERATED_MARKER);
|
|
1159
1564
|
|
|
1160
1565
|
// src/install/skill-template.ts
|
|
1161
1566
|
var cliCommand = (runner, args2) => {
|
|
@@ -1220,7 +1625,7 @@ var lefthookBody = (runner) => [
|
|
|
1220
1625
|
` run: ${cliCommand(runner, "push check {1}")}`,
|
|
1221
1626
|
" use_stdin: true"
|
|
1222
1627
|
];
|
|
1223
|
-
var definesHook = (
|
|
1628
|
+
var definesHook = (text9) => GIT_HOOK_NAMES.some((hook) => new RegExp(`^${hook}:`, "m").test(withoutManagedBlock(text9, HOOK_MARKERS)));
|
|
1224
1629
|
var lefthookItem = (project, runner, path) => {
|
|
1225
1630
|
const before = project.read(path);
|
|
1226
1631
|
const after = withManagedBlock(before, HOOK_MARKERS, lefthookBody(runner));
|
|
@@ -1245,8 +1650,8 @@ var preCommitBody = (runner, indent) => {
|
|
|
1245
1650
|
];
|
|
1246
1651
|
};
|
|
1247
1652
|
var TOP_KEY = /^[A-Za-z_][\w-]*:/;
|
|
1248
|
-
var reposIsLast = (
|
|
1249
|
-
const keys = withoutManagedBlock(
|
|
1653
|
+
var reposIsLast = (text9) => {
|
|
1654
|
+
const keys = withoutManagedBlock(text9, HOOK_MARKERS).split(/\r?\n/).filter((line2) => TOP_KEY.test(line2));
|
|
1250
1655
|
return keys.at(-1)?.startsWith("repos:") === true;
|
|
1251
1656
|
};
|
|
1252
1657
|
var preCommitItem = (project, runner) => {
|
|
@@ -1269,7 +1674,7 @@ var gitHookItems = (project, runner) => {
|
|
|
1269
1674
|
};
|
|
1270
1675
|
|
|
1271
1676
|
// src/policy/policy.ts
|
|
1272
|
-
import { join as
|
|
1677
|
+
import { join as join5 } from "node:path";
|
|
1273
1678
|
var POLICY_FILE = ".finitemonkey/policy.yml";
|
|
1274
1679
|
var GUARD_MODES = ["off", "shadow", "active"];
|
|
1275
1680
|
var LEVELS = ["light", "standard", "strict"];
|
|
@@ -1284,8 +1689,8 @@ var DEFAULT_POLICY = {
|
|
|
1284
1689
|
guards: {}
|
|
1285
1690
|
};
|
|
1286
1691
|
var pick = (options, value, fallback) => options.find((option) => option === value) ?? fallback;
|
|
1287
|
-
var parsePolicy = (
|
|
1288
|
-
const tree = parseConfigYaml(
|
|
1692
|
+
var parsePolicy = (text9) => {
|
|
1693
|
+
const tree = parseConfigYaml(text9);
|
|
1289
1694
|
const raw = configAt(tree, "promoteAfter");
|
|
1290
1695
|
const promoteAfter = typeof raw === "string" ? Number.parseInt(raw, 10) : Number.NaN;
|
|
1291
1696
|
const guards = Object.fromEntries(
|
|
@@ -1302,10 +1707,10 @@ var parsePolicy = (text8) => {
|
|
|
1302
1707
|
guards
|
|
1303
1708
|
};
|
|
1304
1709
|
};
|
|
1305
|
-
var policyPath = (root) => toAbsolutePath(
|
|
1710
|
+
var policyPath = (root) => toAbsolutePath(join5(root, POLICY_FILE));
|
|
1306
1711
|
var loadPolicy = (runtime, root) => {
|
|
1307
|
-
const
|
|
1308
|
-
return
|
|
1712
|
+
const text9 = runtime.readText(policyPath(root));
|
|
1713
|
+
return text9.ok ? parsePolicy(text9.value) : DEFAULT_POLICY;
|
|
1309
1714
|
};
|
|
1310
1715
|
|
|
1311
1716
|
// src/review/agents.ts
|
|
@@ -1322,6 +1727,7 @@ var REVIEWER_BODY = [
|
|
|
1322
1727
|
"3. Write numbered findings in three groups: must fix, should fix, nice to have. For each: what it is, the problem, the fix, and what happens if it is not fixed.",
|
|
1323
1728
|
"4. Every must-fix finding carries a reproduction command and the expected result. A finding without one goes under should fix.",
|
|
1324
1729
|
"5. End with a verdict: approve, approve with changes, or request changes.",
|
|
1730
|
+
'6. Close with the same findings as one JSON block for `finitemonkey critique verify`: `{"verdict": "approve|approve-with-changes|request-changes", "findings": [{"group": "must|should|nice", "what", "problem", "fix", "ifNotFixed", "repro": {"command", "expect"}, "activeRisk": true|false}]}`.',
|
|
1325
1731
|
"",
|
|
1326
1732
|
"You cannot edit files or start other agents. Report; do not repair."
|
|
1327
1733
|
].join("\n");
|
|
@@ -1330,9 +1736,9 @@ var VERIFIER_BODY = [
|
|
|
1330
1736
|
"",
|
|
1331
1737
|
"You check claims with commands, not with opinions. Your input is the review packet and, when there is one, the reviewer's must-fix findings with their reproduction commands. You never see the conversation that produced the change.",
|
|
1332
1738
|
"",
|
|
1333
|
-
"1.
|
|
1334
|
-
"2.
|
|
1335
|
-
"3. Report
|
|
1739
|
+
"1. Pipe the reviewer's JSON block into `finitemonkey critique verify --base <base>` on stdin. It runs each reproduction command in throwaway worktrees on the base and head commits, never in the person's working tree, and records both exit codes in `.finitemonkey/review/ledger.jsonl`.",
|
|
1740
|
+
"2. Fails only on head: the finding stands. Fails on both: it was already there. Fails on neither: not reproduced.",
|
|
1741
|
+
"3. Report the table it prints, unchanged. Do not rerun a command by hand to get a different answer.",
|
|
1336
1742
|
"",
|
|
1337
1743
|
"You cannot edit files or start other agents. Report; do not repair."
|
|
1338
1744
|
].join("\n");
|
|
@@ -1370,16 +1776,16 @@ var agentMarkdown = (spec2, marker) => [
|
|
|
1370
1776
|
// src/install/agents-block.ts
|
|
1371
1777
|
var MARKDOWN_MARKERS = { begin: MD_BLOCK_BEGIN, end: MD_BLOCK_END };
|
|
1372
1778
|
var IMPORT_LINE = /^@AGENTS\.md[ \t]*$/m;
|
|
1373
|
-
var withAgentsBlock = (
|
|
1374
|
-
|
|
1779
|
+
var withAgentsBlock = (text9, runner, lang) => withManagedBlock(
|
|
1780
|
+
text9,
|
|
1375
1781
|
MARKDOWN_MARKERS,
|
|
1376
1782
|
TEMPLATES[lang].agentsBlock({
|
|
1377
1783
|
gate: cliCommand(runner, "gate --fast --json"),
|
|
1378
1784
|
doctor: cliCommand(runner, "doctor --json")
|
|
1379
1785
|
})
|
|
1380
1786
|
);
|
|
1381
|
-
var withAgentsImport = (
|
|
1382
|
-
var withoutMarkdownBlock = (
|
|
1787
|
+
var withAgentsImport = (text9) => text9 !== void 0 && IMPORT_LINE.test(withoutManagedBlock(text9, MARKDOWN_MARKERS)) ? text9 : withManagedBlock(text9, MARKDOWN_MARKERS, [AGENTS_IMPORT]);
|
|
1788
|
+
var withoutMarkdownBlock = (text9) => withoutManagedBlock(text9, MARKDOWN_MARKERS);
|
|
1383
1789
|
|
|
1384
1790
|
// src/install/ask-floor.ts
|
|
1385
1791
|
var DEFAULT_BRANCHES = ["main", "master"];
|
|
@@ -1455,7 +1861,7 @@ var ruleFor = (raw) => {
|
|
|
1455
1861
|
};
|
|
1456
1862
|
var claudeignoreRules = (texts2) => [
|
|
1457
1863
|
...new Set(
|
|
1458
|
-
texts2.flatMap((
|
|
1864
|
+
texts2.flatMap((text9) => (text9 ?? "").split("\n")).map(ruleFor).filter((rule) => rule !== void 0)
|
|
1459
1865
|
)
|
|
1460
1866
|
];
|
|
1461
1867
|
|
|
@@ -1484,16 +1890,16 @@ var DENY_FLOOR = {
|
|
|
1484
1890
|
var DENY_RULES = [...DENY_FLOOR.secrets, ...DENY_FLOOR.git, ...DENY_FLOOR.proof];
|
|
1485
1891
|
|
|
1486
1892
|
// src/install/detect.ts
|
|
1487
|
-
import { join as
|
|
1893
|
+
import { join as join6 } from "node:path";
|
|
1488
1894
|
|
|
1489
1895
|
// src/install/json-text.ts
|
|
1490
1896
|
var DEFAULT_STYLE = { indent: " ", finalNewline: true };
|
|
1491
|
-
var styleOf = (
|
|
1492
|
-
const indented = /\n([ \t]+)\S/.exec(
|
|
1493
|
-
return { indent: indented?.[1] ?? DEFAULT_STYLE.indent, finalNewline:
|
|
1897
|
+
var styleOf = (text9) => {
|
|
1898
|
+
const indented = /\n([ \t]+)\S/.exec(text9);
|
|
1899
|
+
return { indent: indented?.[1] ?? DEFAULT_STYLE.indent, finalNewline: text9.endsWith("\n") };
|
|
1494
1900
|
};
|
|
1495
1901
|
var renderJson = (value, style) => `${JSON.stringify(value, null, style.indent)}${style.finalNewline ? "\n" : ""}`;
|
|
1496
|
-
var parseSettings = (
|
|
1902
|
+
var parseSettings = (text9) => text9 === void 0 || text9.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text9);
|
|
1497
1903
|
var asList = (value) => Array.isArray(value) ? value : [];
|
|
1498
1904
|
var asRecord = (value) => isRecord(value) ? value : {};
|
|
1499
1905
|
var setKey = (record3, key, value) => {
|
|
@@ -1530,8 +1936,8 @@ var WORKFLOW_DIRS = [
|
|
|
1530
1936
|
{ dir: "openspec", name: "openspec" }
|
|
1531
1937
|
];
|
|
1532
1938
|
var fromPackageJson = (project) => {
|
|
1533
|
-
const
|
|
1534
|
-
const parsed =
|
|
1939
|
+
const text9 = project.read("package.json");
|
|
1940
|
+
const parsed = text9 === void 0 ? void 0 : parseJsonObject(text9);
|
|
1535
1941
|
const field = parsed?.ok === true ? parsed.value["packageManager"] : void 0;
|
|
1536
1942
|
if (typeof field !== "string") return void 0;
|
|
1537
1943
|
return PACKAGE_MANAGERS.find(({ name }) => field.startsWith(`${name}@`))?.name;
|
|
@@ -1547,9 +1953,9 @@ var allHandlers = (settings) => {
|
|
|
1547
1953
|
const hooks = settings["hooks"];
|
|
1548
1954
|
return isRecord(hooks) ? Object.keys(hooks).flatMap((event) => handlersOf(settings, event)) : [];
|
|
1549
1955
|
};
|
|
1550
|
-
var settingsInfo = (
|
|
1551
|
-
if (
|
|
1552
|
-
const parsed =
|
|
1956
|
+
var settingsInfo = (text9) => {
|
|
1957
|
+
if (text9 === void 0) return { exists: false, valid: true, userHooks: 0, ownHooks: 0 };
|
|
1958
|
+
const parsed = text9.trim() === "" ? { ok: true, value: {} } : parseJsonObject(text9);
|
|
1553
1959
|
if (!parsed.ok) return { exists: true, valid: false, userHooks: 0, ownHooks: 0 };
|
|
1554
1960
|
const handlers = allHandlers(parsed.value);
|
|
1555
1961
|
const own = handlers.filter(isOwnHandler).length;
|
|
@@ -1563,8 +1969,8 @@ var pluginAuthorities = (settings, source) => {
|
|
|
1563
1969
|
return LOOP_PLUGINS.includes(name) ? [{ kind: "loop-plugin", source, name }] : [];
|
|
1564
1970
|
});
|
|
1565
1971
|
};
|
|
1566
|
-
var scopeAuthorities = (
|
|
1567
|
-
const parsed =
|
|
1972
|
+
var scopeAuthorities = (text9, source) => {
|
|
1973
|
+
const parsed = text9 === void 0 ? void 0 : parseJsonObject(text9);
|
|
1568
1974
|
if (parsed?.ok !== true) return [];
|
|
1569
1975
|
const stops = ["Stop", "SubagentStop"].flatMap(
|
|
1570
1976
|
(event) => handlersOf(parsed.value, event).filter((handler) => !isOwnHandler(handler)).map((handler) => {
|
|
@@ -1577,9 +1983,9 @@ var scopeAuthorities = (text8, source) => {
|
|
|
1577
1983
|
var userSettingsText = (project, env) => {
|
|
1578
1984
|
const home = env["CLAUDE_CONFIG_DIR"] ?? env["HOME"] ?? env["USERPROFILE"];
|
|
1579
1985
|
if (home === void 0) return void 0;
|
|
1580
|
-
const file = env["CLAUDE_CONFIG_DIR"] === void 0 ?
|
|
1581
|
-
const
|
|
1582
|
-
return
|
|
1986
|
+
const file = env["CLAUDE_CONFIG_DIR"] === void 0 ? join6(home, ".claude", "settings.json") : join6(home, "settings.json");
|
|
1987
|
+
const text9 = project.runtime.readText(toAbsolutePath(file));
|
|
1988
|
+
return text9.ok ? text9.value : void 0;
|
|
1583
1989
|
};
|
|
1584
1990
|
var detectAuthorities = (project, env) => [
|
|
1585
1991
|
...scopeAuthorities(project.read(SETTINGS_FILE), "project"),
|
|
@@ -1599,8 +2005,8 @@ var chooseRunner = (project, bin) => {
|
|
|
1599
2005
|
// src/install/gitignore-block.ts
|
|
1600
2006
|
var MARKERS = { begin: BLOCK_BEGIN, end: BLOCK_END };
|
|
1601
2007
|
var body = () => [`${STATE_DIR}/*`, ...COMMITTED_STATE.map((path) => `!${STATE_DIR}/${path}`)];
|
|
1602
|
-
var withBlock = (
|
|
1603
|
-
var withoutBlock = (
|
|
2008
|
+
var withBlock = (text9) => withManagedBlock(text9, MARKERS, body());
|
|
2009
|
+
var withoutBlock = (text9) => withoutManagedBlock(text9, MARKERS);
|
|
1604
2010
|
|
|
1605
2011
|
// src/core/semver.ts
|
|
1606
2012
|
var SEMVER = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
@@ -1643,7 +2049,7 @@ var pendingMigrations = (migrations, from, to) => {
|
|
|
1643
2049
|
};
|
|
1644
2050
|
var migrateItem = (item2, previous, steps) => {
|
|
1645
2051
|
if (steps.length === 0 || item2.before === void 0) return item2;
|
|
1646
|
-
const migrated = steps.reduce((
|
|
2052
|
+
const migrated = steps.reduce((text9, step3) => step3(text9), item2.after);
|
|
1647
2053
|
if (migrated === item2.after) return item2;
|
|
1648
2054
|
const recorded = previous.entries.find((entry2) => entry2.path === item2.path)?.hash;
|
|
1649
2055
|
if (recorded !== hashText(item2.before)) {
|
|
@@ -1809,9 +2215,9 @@ var LOCAL_SETTINGS_FILE = ".claude/settings.local.json";
|
|
|
1809
2215
|
var existingStatusLine = (project, projectSettings, env) => statusLineCommand(projectSettings["statusLine"]) ?? statusLineCommand(statusLineIn(project.read(LOCAL_SETTINGS_FILE))) ?? statusLineCommand(statusLineIn(userSettingsText(project, env)));
|
|
1810
2216
|
|
|
1811
2217
|
// src/install/vendor.ts
|
|
1812
|
-
var isBundle = (
|
|
1813
|
-
var isOwnPackage = (
|
|
1814
|
-
const parsed = parseJsonObject(
|
|
2218
|
+
var isBundle = (text9) => text9.startsWith("#!/usr/bin/env node") && text9.includes("finitemonkey");
|
|
2219
|
+
var isOwnPackage = (text9) => {
|
|
2220
|
+
const parsed = parseJsonObject(text9);
|
|
1815
2221
|
return parsed.ok && parsed.value["name"] === "finitemonkey";
|
|
1816
2222
|
};
|
|
1817
2223
|
var vendoredPackage = (version) => `${JSON.stringify({ name: "finitemonkey", version, private: true, type: "module" }, null, 2)}
|
|
@@ -1842,9 +2248,9 @@ var detect = (project, gitRoot2, input) => ({
|
|
|
1842
2248
|
location: locationOf(project.root, input.env)
|
|
1843
2249
|
});
|
|
1844
2250
|
var readPreviousLock = (project) => {
|
|
1845
|
-
const
|
|
1846
|
-
if (
|
|
1847
|
-
const parsed = parseLock(
|
|
2251
|
+
const text9 = project.read(LOCK_FILE);
|
|
2252
|
+
if (text9 === void 0) return accept(void 0);
|
|
2253
|
+
const parsed = parseLock(text9);
|
|
1848
2254
|
return parsed.ok ? accept(parsed.value) : reject({ kind: "lock", detail: parsed.error });
|
|
1849
2255
|
};
|
|
1850
2256
|
var actionFor = (before, after, existing) => {
|
|
@@ -1893,7 +2299,7 @@ var settingsItem = (project, detection, input, previous) => {
|
|
|
1893
2299
|
});
|
|
1894
2300
|
};
|
|
1895
2301
|
var markdownItems = (project, runner, lang) => [
|
|
1896
|
-
{ path: AGENTS_FILE, render: (
|
|
2302
|
+
{ path: AGENTS_FILE, render: (text9) => withAgentsBlock(text9, runner, lang) },
|
|
1897
2303
|
{ path: CLAUDE_FILE, render: withAgentsImport }
|
|
1898
2304
|
].map(({ path, render: render3 }) => {
|
|
1899
2305
|
const before = project.read(path);
|
|
@@ -1920,14 +2326,14 @@ var generatedItem = (project, file, previous) => {
|
|
|
1920
2326
|
};
|
|
1921
2327
|
var PLAYWRIGHT_VERIFIER = ".claude/agents/verifier.md";
|
|
1922
2328
|
var playwrightVerifier = (project) => {
|
|
1923
|
-
const wanted = configAt(parseConfigYaml(project.read(
|
|
2329
|
+
const wanted = configAt(parseConfigYaml(project.read(CONFIG_FILE2) ?? ""), "review", "playwright") === "true";
|
|
1924
2330
|
if (!wanted) return [];
|
|
1925
2331
|
const template = agentMarkdown(withPlaywright(VERIFIER), GENERATED_MARKER);
|
|
1926
|
-
return [{ path: PLAYWRIGHT_VERIFIER, template, isOurs: (
|
|
2332
|
+
return [{ path: PLAYWRIGHT_VERIFIER, template, isOurs: (text9) => text9.includes(GENERATED_MARKER) }];
|
|
1927
2333
|
};
|
|
1928
2334
|
var generatedFiles = (project, input, runner, vendor) => {
|
|
1929
2335
|
const own = [
|
|
1930
|
-
{ path:
|
|
2336
|
+
{ path: CONFIG_FILE2, template: configTemplate(input.preset, input.lang), isOurs: hasMarker },
|
|
1931
2337
|
{ path: VERIFY_SKILL, template: verifySkillTemplate(runner, input.lang), isOurs: isOwnSkill },
|
|
1932
2338
|
{ path: POLICY_FILE, template: policyTemplate(input.lang), isOurs: hasMarker },
|
|
1933
2339
|
...playwrightVerifier(project)
|
|
@@ -1968,9 +2374,9 @@ var needsWrite = (plan) => hasChanges(plan) || plan.previous === void 0;
|
|
|
1968
2374
|
// src/install/apply.ts
|
|
1969
2375
|
var stamp = (iso) => iso.replace(/[:.]/g, "-");
|
|
1970
2376
|
var ensureParent = (project, relative11) => project.runtime.mkdir(project.abs(dirname3(relative11)));
|
|
1971
|
-
var writeText = (project, relative11,
|
|
2377
|
+
var writeText = (project, relative11, text9) => {
|
|
1972
2378
|
const parent = ensureParent(project, relative11);
|
|
1973
|
-
const written = parent.ok ? project.runtime.writeAtomic(project.abs(relative11),
|
|
2379
|
+
const written = parent.ok ? project.runtime.writeAtomic(project.abs(relative11), text9) : parent;
|
|
1974
2380
|
return written.ok ? accept(void 0) : reject({ path: relative11, detail: written.error });
|
|
1975
2381
|
};
|
|
1976
2382
|
var restore = (project, items) => {
|
|
@@ -1988,7 +2394,7 @@ var backupItems = (project, dir, items) => {
|
|
|
1988
2394
|
return writeText(project, `${dir}/manifest.json`, `${JSON.stringify(manifest, null, 2)}
|
|
1989
2395
|
`);
|
|
1990
2396
|
};
|
|
1991
|
-
var
|
|
2397
|
+
var entryOf2 = (plan, item2, backupDir) => {
|
|
1992
2398
|
const previous = plan.previous?.entries.find((entry2) => entry2.path === item2.path);
|
|
1993
2399
|
const existed = previous?.existed ?? item2.before !== void 0;
|
|
1994
2400
|
const backup = previous?.backup ?? (existed && backupDir !== void 0 ? `${backupDir}/${item2.path}` : void 0);
|
|
@@ -2011,7 +2417,7 @@ var nextLock = (plan, version, now, backupDir, createdDirs, only) => ({
|
|
|
2011
2417
|
updatedAt: now,
|
|
2012
2418
|
createdDirs: [.../* @__PURE__ */ new Set([...plan.previous?.createdDirs ?? [], ...createdDirs])],
|
|
2013
2419
|
entries: plan.items.filter((item2) => item2.action !== "skip-foreign").flatMap((item2) => {
|
|
2014
|
-
if (only === void 0 || only.includes(item2.path)) return [
|
|
2420
|
+
if (only === void 0 || only.includes(item2.path)) return [entryOf2(plan, item2, backupDir)];
|
|
2015
2421
|
const kept = plan.previous?.entries.find((entry2) => entry2.path === item2.path);
|
|
2016
2422
|
return kept === void 0 ? [] : [kept];
|
|
2017
2423
|
})
|
|
@@ -2110,16 +2516,16 @@ var detectLang = (flag2, env, settings = {}) => {
|
|
|
2110
2516
|
};
|
|
2111
2517
|
|
|
2112
2518
|
// src/install/project.ts
|
|
2113
|
-
import { dirname as dirname4, join as
|
|
2519
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
2114
2520
|
var projectAt = (root, runtime) => {
|
|
2115
|
-
const abs = (relative11) => toAbsolutePath(
|
|
2521
|
+
const abs = (relative11) => toAbsolutePath(join7(root, ...relative11.split("/")));
|
|
2116
2522
|
return {
|
|
2117
2523
|
root,
|
|
2118
2524
|
runtime,
|
|
2119
2525
|
abs,
|
|
2120
2526
|
read: (relative11) => {
|
|
2121
|
-
const
|
|
2122
|
-
return
|
|
2527
|
+
const text9 = runtime.readText(abs(relative11));
|
|
2528
|
+
return text9.ok ? text9.value : void 0;
|
|
2123
2529
|
},
|
|
2124
2530
|
exists: (relative11) => runtime.stat(abs(relative11)) !== void 0,
|
|
2125
2531
|
isDir: (relative11) => runtime.stat(abs(relative11))?.kind === "dir"
|
|
@@ -2128,7 +2534,7 @@ var projectAt = (root, runtime) => {
|
|
|
2128
2534
|
var findGitRoot = (start, runtime) => {
|
|
2129
2535
|
let current2 = start;
|
|
2130
2536
|
for (; ; ) {
|
|
2131
|
-
if (runtime.stat(toAbsolutePath(
|
|
2537
|
+
if (runtime.stat(toAbsolutePath(join7(current2, ".git"))) !== void 0) return toAbsolutePath(current2);
|
|
2132
2538
|
const parent = dirname4(current2);
|
|
2133
2539
|
if (parent === current2) return void 0;
|
|
2134
2540
|
current2 = parent;
|
|
@@ -2192,8 +2598,8 @@ var invalidPreset = (raw) => problem("invalid_arguments", "error.invalid_preset"
|
|
|
2192
2598
|
});
|
|
2193
2599
|
var selfBundle = (ctx) => {
|
|
2194
2600
|
if (!isBundlePath(ctx.runtime.selfPath)) return void 0;
|
|
2195
|
-
const
|
|
2196
|
-
return
|
|
2601
|
+
const text9 = ctx.runtime.readText(ctx.runtime.selfPath);
|
|
2602
|
+
return text9.ok ? text9.value : void 0;
|
|
2197
2603
|
};
|
|
2198
2604
|
var INHERITED = /* @__PURE__ */ new Set(["default", "config", "claude-settings"]);
|
|
2199
2605
|
var planInputFor = (ctx, preset, bin, marketplace) => ({
|
|
@@ -2229,7 +2635,7 @@ var applyFixes = (ctx, write3) => {
|
|
|
2229
2635
|
};
|
|
2230
2636
|
|
|
2231
2637
|
// src/doctor/report-file.ts
|
|
2232
|
-
import { join as
|
|
2638
|
+
import { join as join9 } from "node:path";
|
|
2233
2639
|
|
|
2234
2640
|
// src/evidence/log.ts
|
|
2235
2641
|
var logEvent = (runtime, paths, event, data) => runtime.appendText(paths.log, `${JSON.stringify({ at: runtime.now(), event, ...data })}
|
|
@@ -2297,13 +2703,13 @@ var isPlaceholder = (value) => {
|
|
|
2297
2703
|
var isTestOrLockPath = (path) => path !== void 0 && (LOCKFILES.test(toPosixPath(path)) || TEST_PATH.test(toPosixPath(path)));
|
|
2298
2704
|
|
|
2299
2705
|
// src/secrets/entropy.ts
|
|
2300
|
-
var shannonEntropy = (
|
|
2301
|
-
if (
|
|
2706
|
+
var shannonEntropy = (text9) => {
|
|
2707
|
+
if (text9.length === 0) return 0;
|
|
2302
2708
|
const counts = /* @__PURE__ */ new Map();
|
|
2303
|
-
for (const char of
|
|
2709
|
+
for (const char of text9) counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
2304
2710
|
let entropy = 0;
|
|
2305
2711
|
for (const count4 of counts.values()) {
|
|
2306
|
-
const p = count4 /
|
|
2712
|
+
const p = count4 / text9.length;
|
|
2307
2713
|
entropy -= p * Math.log2(p);
|
|
2308
2714
|
}
|
|
2309
2715
|
return entropy;
|
|
@@ -2311,7 +2717,7 @@ var shannonEntropy = (text8) => {
|
|
|
2311
2717
|
|
|
2312
2718
|
// src/secrets/fingerprint.ts
|
|
2313
2719
|
import { createHash as createHash2 } from "node:crypto";
|
|
2314
|
-
var sha256Hex = (
|
|
2720
|
+
var sha256Hex = (text9) => createHash2("sha256").update(text9).digest("hex");
|
|
2315
2721
|
var fingerprintOf = (secret) => sha256Hex(`finitemonkey:secret:${secret}`).slice(0, 16);
|
|
2316
2722
|
var ALNUM = /[A-Za-z0-9]/g;
|
|
2317
2723
|
var MIN_TAIL_LENGTH = 12;
|
|
@@ -2321,9 +2727,9 @@ var tailOf = (secret) => {
|
|
|
2321
2727
|
};
|
|
2322
2728
|
|
|
2323
2729
|
// src/secrets/lines.ts
|
|
2324
|
-
var lineLocator = (
|
|
2730
|
+
var lineLocator = (text9) => {
|
|
2325
2731
|
const starts = [0];
|
|
2326
|
-
for (let index =
|
|
2732
|
+
for (let index = text9.indexOf("\n"); index !== -1; index = text9.indexOf("\n", index + 1)) starts.push(index + 1);
|
|
2327
2733
|
return (offset) => {
|
|
2328
2734
|
let low = 0;
|
|
2329
2735
|
let high = starts.length - 1;
|
|
@@ -2617,12 +3023,12 @@ var hasRandomRun = (secret, length) => (secret.match(new RegExp(`[A-Za-z0-9]{${S
|
|
|
2617
3023
|
);
|
|
2618
3024
|
var accepted = (rule, secret) => secret.length >= rule.minLength && (!rule.requireDigit || DIGIT.test(secret)) && (rule.minRandomRun === 0 || hasRandomRun(secret, rule.minRandomRun)) && (!rule.placeholders || !isPlaceholder(secret)) && (rule.minEntropy === 0 || shannonEntropy(secret) >= rule.minEntropy);
|
|
2619
3025
|
var NAME_CHAR = /[A-Za-z0-9_.-]/;
|
|
2620
|
-
var nameAround = (
|
|
3026
|
+
var nameAround = (text9, at) => {
|
|
2621
3027
|
let start = at;
|
|
2622
|
-
while (start > 0 && NAME_CHAR.test(
|
|
3028
|
+
while (start > 0 && NAME_CHAR.test(text9.charAt(start - 1))) start -= 1;
|
|
2623
3029
|
let end = at;
|
|
2624
|
-
while (end <
|
|
2625
|
-
return
|
|
3030
|
+
while (end < text9.length && NAME_CHAR.test(text9.charAt(end))) end += 1;
|
|
3031
|
+
return text9.slice(start, end).toLowerCase();
|
|
2626
3032
|
};
|
|
2627
3033
|
var ignoredName = (rule, match) => rule.ignoredNames.length > 0 && rule.ignoredNames.includes(nameAround(match.input, match.index));
|
|
2628
3034
|
var spanOf = (rule, match) => {
|
|
@@ -2632,9 +3038,9 @@ var spanOf = (rule, match) => {
|
|
|
2632
3038
|
const secret = match.input.slice(start, end);
|
|
2633
3039
|
return accepted(rule, secret) ? { rule, start, end, secret } : void 0;
|
|
2634
3040
|
};
|
|
2635
|
-
var ruleSpans = (rule,
|
|
3041
|
+
var ruleSpans = (rule, text9) => {
|
|
2636
3042
|
const spans = [];
|
|
2637
|
-
for (const match of
|
|
3043
|
+
for (const match of text9.matchAll(rule.pattern)) {
|
|
2638
3044
|
const span = spanOf(rule, match);
|
|
2639
3045
|
if (span !== void 0) spans.push(span);
|
|
2640
3046
|
}
|
|
@@ -2660,31 +3066,31 @@ var toFinding = (span, locate2) => {
|
|
|
2660
3066
|
tail: tailOf(span.secret)
|
|
2661
3067
|
};
|
|
2662
3068
|
};
|
|
2663
|
-
var scanText = (
|
|
2664
|
-
if (
|
|
2665
|
-
const rules = applicable(options.rules ?? RULES,
|
|
3069
|
+
var scanText = (text9, options = {}) => {
|
|
3070
|
+
if (text9.length === 0) return [];
|
|
3071
|
+
const rules = applicable(options.rules ?? RULES, text9.toLowerCase(), options.path);
|
|
2666
3072
|
if (rules.length === 0) return [];
|
|
2667
3073
|
const taken = [];
|
|
2668
3074
|
for (const rule of rules) {
|
|
2669
|
-
for (const span of ruleSpans(rule,
|
|
3075
|
+
for (const span of ruleSpans(rule, text9)) if (!overlaps(taken, span)) taken.push(span);
|
|
2670
3076
|
}
|
|
2671
3077
|
if (taken.length === 0) return [];
|
|
2672
|
-
const locate2 = lineLocator(
|
|
3078
|
+
const locate2 = lineLocator(text9);
|
|
2673
3079
|
return taken.sort((a, b) => a.start - b.start).map((span) => toFinding(span, locate2));
|
|
2674
3080
|
};
|
|
2675
3081
|
|
|
2676
3082
|
// src/mask/mask.ts
|
|
2677
3083
|
var maskToken = (finding4) => `«secret:${finding4.ruleId}:${finding4.tail}»`;
|
|
2678
|
-
var maskText = (
|
|
2679
|
-
const findings = scanText(
|
|
2680
|
-
if (findings.length === 0) return
|
|
3084
|
+
var maskText = (text9) => {
|
|
3085
|
+
const findings = scanText(text9, { rules: CREDENTIAL_RULES });
|
|
3086
|
+
if (findings.length === 0) return text9;
|
|
2681
3087
|
let out = "";
|
|
2682
3088
|
let cursor = 0;
|
|
2683
3089
|
for (const finding4 of findings) {
|
|
2684
|
-
out +=
|
|
3090
|
+
out += text9.slice(cursor, finding4.start) + maskToken(finding4);
|
|
2685
3091
|
cursor = finding4.end;
|
|
2686
3092
|
}
|
|
2687
|
-
return out +
|
|
3093
|
+
return out + text9.slice(cursor);
|
|
2688
3094
|
};
|
|
2689
3095
|
var maskDeep = (value) => {
|
|
2690
3096
|
if (typeof value === "string") return maskText(value);
|
|
@@ -2696,11 +3102,11 @@ var maskDeep = (value) => {
|
|
|
2696
3102
|
};
|
|
2697
3103
|
|
|
2698
3104
|
// src/task/paths.ts
|
|
2699
|
-
import { join as
|
|
3105
|
+
import { join as join8 } from "node:path";
|
|
2700
3106
|
var FM_DIR = ".finitemonkey";
|
|
2701
3107
|
var fmPaths = (root) => {
|
|
2702
|
-
const dir = toAbsolutePath(
|
|
2703
|
-
const at = (...parts) => toAbsolutePath(
|
|
3108
|
+
const dir = toAbsolutePath(join8(root, FM_DIR));
|
|
3109
|
+
const at = (...parts) => toAbsolutePath(join8(dir, ...parts));
|
|
2704
3110
|
return {
|
|
2705
3111
|
root,
|
|
2706
3112
|
dir,
|
|
@@ -2902,14 +3308,14 @@ var toolVersion = (env, command) => {
|
|
|
2902
3308
|
return result.ok ? result.stdout.trim().split("\n")[0] ?? "" : "not found";
|
|
2903
3309
|
};
|
|
2904
3310
|
var lastHookFailures = (env) => {
|
|
2905
|
-
const
|
|
2906
|
-
if (!
|
|
2907
|
-
return
|
|
3311
|
+
const text9 = env.runtime.readText(fmPaths(env.root).log);
|
|
3312
|
+
if (!text9.ok) return [];
|
|
3313
|
+
return text9.value.split("\n").filter((line2) => {
|
|
2908
3314
|
const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
|
|
2909
3315
|
return parsed?.ok === true && isRecord(parsed.value) && parsed.value["event"] === HOOK_FAILURE_EVENT;
|
|
2910
3316
|
}).slice(-MAX_FAILURES);
|
|
2911
3317
|
};
|
|
2912
|
-
var withoutHome = (
|
|
3318
|
+
var withoutHome = (text9, home) => home.length > 1 ? text9.split(home).join("~") : text9;
|
|
2913
3319
|
var reportMarkdown = (env, ctx, envelope) => {
|
|
2914
3320
|
const failures = lastHookFailures(env);
|
|
2915
3321
|
const lines3 = [
|
|
@@ -2936,18 +3342,18 @@ var reportMarkdown = (env, ctx, envelope) => {
|
|
|
2936
3342
|
];
|
|
2937
3343
|
return maskText(withoutHome(lines3.join("\n"), env.runtime.homeDir()));
|
|
2938
3344
|
};
|
|
2939
|
-
var reportPath = (env) => toAbsolutePath(
|
|
3345
|
+
var reportPath = (env) => toAbsolutePath(join9(env.root, ...REPORTS_DIR.split("/"), `doctor-${stamp(env.runtime.now())}.md`));
|
|
2940
3346
|
|
|
2941
3347
|
// src/install/plugin-yield.ts
|
|
2942
|
-
import { join as
|
|
3348
|
+
import { join as join10 } from "node:path";
|
|
2943
3349
|
var PLUGIN_VIA = "plugin";
|
|
2944
3350
|
var YIELD_STDOUT = `${JSON.stringify({ continue: true })}
|
|
2945
3351
|
`;
|
|
2946
3352
|
var projectOwnsHooks = (ctx) => {
|
|
2947
3353
|
const root = ctx.env["CLAUDE_PROJECT_DIR"] ?? ctx.cwd;
|
|
2948
|
-
const
|
|
2949
|
-
if (!
|
|
2950
|
-
const parsed = parseJsonObject(
|
|
3354
|
+
const text9 = ctx.runtime.readText(toAbsolutePath(join10(root, ...SETTINGS_FILE.split("/"))));
|
|
3355
|
+
if (!text9.ok) return false;
|
|
3356
|
+
const parsed = parseJsonObject(text9.value);
|
|
2951
3357
|
return parsed.ok && ownHandlersIn(parsed.value) > 0;
|
|
2952
3358
|
};
|
|
2953
3359
|
|
|
@@ -2959,7 +3365,7 @@ var STABLE_PLUGIN_ID = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
|
2959
3365
|
var NEXT_PLUGIN_ID = `${PLUGIN_NAME}@${NEXT_MARKETPLACE_NAME}`;
|
|
2960
3366
|
|
|
2961
3367
|
// src/doctor/settings.ts
|
|
2962
|
-
import { join as
|
|
3368
|
+
import { join as join11 } from "node:path";
|
|
2963
3369
|
var MANAGED = {
|
|
2964
3370
|
darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
|
|
2965
3371
|
linux: "/etc/claude-code/managed-settings.json",
|
|
@@ -2968,7 +3374,7 @@ var MANAGED = {
|
|
|
2968
3374
|
var userSettingsPath = ({ env, runtime }) => {
|
|
2969
3375
|
const configDir = env["CLAUDE_CONFIG_DIR"];
|
|
2970
3376
|
return toAbsolutePath(
|
|
2971
|
-
configDir === void 0 ?
|
|
3377
|
+
configDir === void 0 ? join11(runtime.homeDir(), ".claude", "settings.json") : join11(configDir, "settings.json")
|
|
2972
3378
|
);
|
|
2973
3379
|
};
|
|
2974
3380
|
var settingsPaths = (env) => {
|
|
@@ -2976,13 +3382,13 @@ var settingsPaths = (env) => {
|
|
|
2976
3382
|
return [
|
|
2977
3383
|
...managed === void 0 ? [] : [{ scope: "managed", path: toAbsolutePath(managed) }],
|
|
2978
3384
|
{ scope: "user", path: userSettingsPath(env) },
|
|
2979
|
-
{ scope: "project", path: toAbsolutePath(
|
|
2980
|
-
{ scope: "local", path: toAbsolutePath(
|
|
3385
|
+
{ scope: "project", path: toAbsolutePath(join11(env.root, ".claude", "settings.json")) },
|
|
3386
|
+
{ scope: "local", path: toAbsolutePath(join11(env.root, ".claude", "settings.local.json")) }
|
|
2981
3387
|
];
|
|
2982
3388
|
};
|
|
2983
3389
|
var readSettings = (env) => settingsPaths(env).flatMap(({ scope: scope2, path }) => {
|
|
2984
|
-
const
|
|
2985
|
-
const parsed =
|
|
3390
|
+
const text9 = env.runtime.readText(path);
|
|
3391
|
+
const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
|
|
2986
3392
|
return parsed?.ok === true ? [{ scope: scope2, path, settings: parsed.value }] : [];
|
|
2987
3393
|
});
|
|
2988
3394
|
var scopesWith = (all, test) => all.filter(({ settings }) => test(settings)).map(({ scope: scope2 }) => scope2);
|
|
@@ -3121,7 +3527,7 @@ var checkClaudeSettings = (env) => {
|
|
|
3121
3527
|
};
|
|
3122
3528
|
|
|
3123
3529
|
// src/doctor/environment.ts
|
|
3124
|
-
import { join as
|
|
3530
|
+
import { join as join12 } from "node:path";
|
|
3125
3531
|
var AUTH_VARIABLES = [
|
|
3126
3532
|
"ANTHROPIC_API_KEY",
|
|
3127
3533
|
"ANTHROPIC_AUTH_TOKEN",
|
|
@@ -3159,7 +3565,7 @@ var pathDirs = ({ env, platform }) => {
|
|
|
3159
3565
|
var copiesOnPath = (env) => {
|
|
3160
3566
|
const names = EXECUTABLES[env.platform] ?? ["finitemonkey"];
|
|
3161
3567
|
const found = pathDirs(env).flatMap(
|
|
3162
|
-
(dir) => names.map((name) => toAbsolutePath(
|
|
3568
|
+
(dir) => names.map((name) => toAbsolutePath(join12(dir, name))).filter((path) => env.runtime.stat(path)?.kind === "file").map((path) => env.runtime.realpath(path))
|
|
3163
3569
|
);
|
|
3164
3570
|
const byDir = new Map(found.map((path) => [path.replace(/\.(cmd|exe|ps1)$/i, ""), path]));
|
|
3165
3571
|
return [...byDir.values()];
|
|
@@ -3193,7 +3599,7 @@ var checkAuthVariables = ({ env, platform }) => {
|
|
|
3193
3599
|
};
|
|
3194
3600
|
|
|
3195
3601
|
// src/doctor/hooks.ts
|
|
3196
|
-
import { join as
|
|
3602
|
+
import { join as join36 } from "node:path";
|
|
3197
3603
|
|
|
3198
3604
|
// src/hooks/events.ts
|
|
3199
3605
|
var HOOK_EVENTS = [
|
|
@@ -3239,6 +3645,36 @@ var deadlineFor = (event, raw) => {
|
|
|
3239
3645
|
return EVENT_DEADLINES[event] ?? DEFAULT_HOOK_DEADLINE_MS;
|
|
3240
3646
|
};
|
|
3241
3647
|
|
|
3648
|
+
// src/gate/hooks/common.ts
|
|
3649
|
+
import { dirname as dirname5, join as join13 } from "node:path";
|
|
3650
|
+
var DEFAULT_GATE_BUDGET_MS = 3e5;
|
|
3651
|
+
var sessionCwd = (input) => input.payload.cwd === void 0 ? input.cwd : toAbsolutePath(input.payload.cwd, input.cwd);
|
|
3652
|
+
var MAX_DEPTH = 64;
|
|
3653
|
+
var findRepoRoot = (runtime, start) => {
|
|
3654
|
+
let current2 = start;
|
|
3655
|
+
for (let depth = 0; depth < MAX_DEPTH; depth += 1) {
|
|
3656
|
+
if (runtime.stat(toAbsolutePath(join13(current2, ".git"))) !== void 0) return current2;
|
|
3657
|
+
const parent = toAbsolutePath(dirname5(current2));
|
|
3658
|
+
if (parent === current2) return void 0;
|
|
3659
|
+
current2 = parent;
|
|
3660
|
+
}
|
|
3661
|
+
return void 0;
|
|
3662
|
+
};
|
|
3663
|
+
var projectPaths = (input) => {
|
|
3664
|
+
const root = findRepoRoot(input.runtime, sessionCwd(input));
|
|
3665
|
+
return root === void 0 ? void 0 : fmPaths(root);
|
|
3666
|
+
};
|
|
3667
|
+
var gateBudget = (input) => {
|
|
3668
|
+
const parsed = Number.parseInt(input.env["FINITEMONKEY_GATE_BUDGET_MS"] ?? "", 10);
|
|
3669
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_GATE_BUDGET_MS;
|
|
3670
|
+
};
|
|
3671
|
+
var allowWith = (output) => ({ kind: "allow", output });
|
|
3672
|
+
var contextOutput = (event, additionalContext, systemMessage) => ({
|
|
3673
|
+
hookSpecificOutput: { hookEventName: event, additionalContext },
|
|
3674
|
+
...systemMessage === void 0 ? {} : { systemMessage }
|
|
3675
|
+
});
|
|
3676
|
+
var human = (text9) => `finitemonkey: ${text9}`;
|
|
3677
|
+
|
|
3242
3678
|
// src/hooks/fields.ts
|
|
3243
3679
|
var textField = (record3, ...names) => {
|
|
3244
3680
|
for (const name of names) {
|
|
@@ -3268,7 +3704,7 @@ var editsOf = (input) => {
|
|
|
3268
3704
|
});
|
|
3269
3705
|
};
|
|
3270
3706
|
var applyEdits = (before, edits) => edits.reduce(
|
|
3271
|
-
(
|
|
3707
|
+
(text9, edit) => edit.all ? text9.split(edit.oldText).join(edit.newText) : text9.replace(edit.oldText, () => edit.newText),
|
|
3272
3708
|
before
|
|
3273
3709
|
);
|
|
3274
3710
|
|
|
@@ -3288,7 +3724,7 @@ var familyOf = (base) => {
|
|
|
3288
3724
|
if (found === void 0) throw new Error(`no reason family for ${base}`);
|
|
3289
3725
|
return found;
|
|
3290
3726
|
};
|
|
3291
|
-
var capReason = (
|
|
3727
|
+
var capReason = (text9) => text9.length <= REASON_LIMIT ? text9 : `${text9.slice(0, REASON_LIMIT - 1)}…`;
|
|
3292
3728
|
var modelReason = (t, base, params2 = {}) => {
|
|
3293
3729
|
const claim = `${base}.claim`;
|
|
3294
3730
|
const requires = `${base}.requires`;
|
|
@@ -3315,9 +3751,9 @@ var textRecord = (value) => isRecord(value) ? Object.fromEntries(
|
|
|
3315
3751
|
Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
|
|
3316
3752
|
) : {};
|
|
3317
3753
|
var readJsonFile = (runtime, path) => {
|
|
3318
|
-
const
|
|
3319
|
-
if (!
|
|
3320
|
-
const parsed = parseJsonObject(
|
|
3754
|
+
const text9 = runtime.readText(path);
|
|
3755
|
+
if (!text9.ok) return void 0;
|
|
3756
|
+
const parsed = parseJsonObject(text9.value);
|
|
3321
3757
|
return parsed.ok ? parsed.value : void 0;
|
|
3322
3758
|
};
|
|
3323
3759
|
var writeJsonFile = (runtime, path, value) => runtime.writeText(path, `${JSON.stringify({ schemaVersion: 1, ...value }, null, 2)}
|
|
@@ -3367,28 +3803,28 @@ var closeTask = (state, id) => assemble({
|
|
|
3367
3803
|
|
|
3368
3804
|
// src/guards/release/grants.ts
|
|
3369
3805
|
var GRANT_MINUTES = 30;
|
|
3370
|
-
var
|
|
3806
|
+
var text3 = (record3, key) => {
|
|
3371
3807
|
const value = record3[key];
|
|
3372
3808
|
return typeof value === "string" ? value : void 0;
|
|
3373
3809
|
};
|
|
3374
3810
|
var toGrant = (value) => {
|
|
3375
3811
|
if (!isRecord(value)) return void 0;
|
|
3376
|
-
const guard =
|
|
3377
|
-
const reason =
|
|
3378
|
-
const grantedAt =
|
|
3379
|
-
const expiresAt =
|
|
3380
|
-
const sessionId =
|
|
3812
|
+
const guard = text3(value, "guard");
|
|
3813
|
+
const reason = text3(value, "reason");
|
|
3814
|
+
const grantedAt = text3(value, "grantedAt");
|
|
3815
|
+
const expiresAt = text3(value, "expiresAt");
|
|
3816
|
+
const sessionId = text3(value, "sessionId");
|
|
3381
3817
|
if (guard === void 0 || reason === void 0 || grantedAt === void 0 || expiresAt === void 0)
|
|
3382
3818
|
return void 0;
|
|
3383
3819
|
return { guard, reason, grantedAt, expiresAt, ...sessionId === void 0 ? {} : { sessionId } };
|
|
3384
3820
|
};
|
|
3385
3821
|
var toApproval = (value) => {
|
|
3386
3822
|
if (!isRecord(value)) return void 0;
|
|
3387
|
-
const task =
|
|
3388
|
-
const check2 =
|
|
3389
|
-
const at =
|
|
3823
|
+
const task = text3(value, "task");
|
|
3824
|
+
const check2 = text3(value, "check");
|
|
3825
|
+
const at = text3(value, "at");
|
|
3390
3826
|
if (task === void 0 || check2 === void 0 || at === void 0) return void 0;
|
|
3391
|
-
return { task, check: check2, at, reason:
|
|
3827
|
+
return { task, check: check2, at, reason: text3(value, "reason") ?? "" };
|
|
3392
3828
|
};
|
|
3393
3829
|
var list = (raw, map) => Array.isArray(raw) ? raw.flatMap((item2) => {
|
|
3394
3830
|
const value = map(item2);
|
|
@@ -3430,7 +3866,7 @@ var SHELL_TOOLS = /* @__PURE__ */ new Set(["Bash", "PowerShell"]);
|
|
|
3430
3866
|
import { posix } from "node:path";
|
|
3431
3867
|
|
|
3432
3868
|
// src/shell/scan.ts
|
|
3433
|
-
var cursorOf = (
|
|
3869
|
+
var cursorOf = (text9) => ({ text: text9, pos: 0, incomplete: false, subs: [] });
|
|
3434
3870
|
var peek = (cursor, offset = 0) => cursor.text.charAt(cursor.pos + offset);
|
|
3435
3871
|
var atEnd = (cursor) => cursor.pos >= cursor.text.length;
|
|
3436
3872
|
var skipQuoted = (cursor, quote) => {
|
|
@@ -3645,8 +4081,8 @@ var nextToken = (cursor, tokens, pending) => {
|
|
|
3645
4081
|
tokens.push({ kind: "op", text: op });
|
|
3646
4082
|
if (op === "\n") flushHeredocs(cursor, tokens, pending);
|
|
3647
4083
|
};
|
|
3648
|
-
var lexBash = (
|
|
3649
|
-
const cursor = cursorOf(
|
|
4084
|
+
var lexBash = (text9) => {
|
|
4085
|
+
const cursor = cursorOf(text9.replace(/\r\n/g, "\n"));
|
|
3650
4086
|
const tokens = [];
|
|
3651
4087
|
const pending = [];
|
|
3652
4088
|
skipBlank(cursor);
|
|
@@ -3794,9 +4230,9 @@ var next = (cursor, tokens, cmd) => {
|
|
|
3794
4230
|
}
|
|
3795
4231
|
const op = OPERATORS2.find((candidate) => cursor.text.startsWith(candidate, cursor.pos));
|
|
3796
4232
|
if (op !== void 0 || cmd && peek(cursor) === "&") {
|
|
3797
|
-
const
|
|
3798
|
-
cursor.pos +=
|
|
3799
|
-
tokens.push({ kind: "op", text:
|
|
4233
|
+
const text9 = op ?? "&";
|
|
4234
|
+
cursor.pos += text9.length;
|
|
4235
|
+
tokens.push({ kind: "op", text: text9 });
|
|
3800
4236
|
return;
|
|
3801
4237
|
}
|
|
3802
4238
|
if ((peek(cursor) === "&" || peek(cursor) === ".") && peek(cursor, 1) === " " && commandStart(tokens)) {
|
|
@@ -3805,8 +4241,8 @@ var next = (cursor, tokens, cmd) => {
|
|
|
3805
4241
|
}
|
|
3806
4242
|
tokens.push(word(cursor, cmd));
|
|
3807
4243
|
};
|
|
3808
|
-
var lexPowerShell = (
|
|
3809
|
-
const cursor = cursorOf(
|
|
4244
|
+
var lexPowerShell = (text9, cmd = false) => {
|
|
4245
|
+
const cursor = cursorOf(text9.replace(/\r\n/g, "\n"));
|
|
3810
4246
|
const tokens = [];
|
|
3811
4247
|
const skip = () => {
|
|
3812
4248
|
while (peek(cursor) === " " || peek(cursor) === " ") cursor.pos += 1;
|
|
@@ -4005,11 +4441,11 @@ var unwrap = (initial, stdin) => {
|
|
|
4005
4441
|
};
|
|
4006
4442
|
|
|
4007
4443
|
// src/shell/normalize.ts
|
|
4008
|
-
var
|
|
4444
|
+
var MAX_DEPTH2 = 8;
|
|
4009
4445
|
var ASSIGNMENT2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s;
|
|
4010
4446
|
var PS_ENV = /^\$env:([A-Za-z_][A-Za-z0-9_]*)=?(.*)$/is;
|
|
4011
4447
|
var CHANGES_DIR = /* @__PURE__ */ new Set(["cd", "pushd", "set-location", "push-location"]);
|
|
4012
|
-
var lex = (
|
|
4448
|
+
var lex = (text9, language) => language === "bash" ? lexBash(text9) : lexPowerShell(text9, language === "cmd");
|
|
4013
4449
|
var isWindowsAbsolute = (path) => /^[A-Za-z]:\//.test(path);
|
|
4014
4450
|
var joinDir = (current2, target2) => {
|
|
4015
4451
|
const next2 = toPosixPath(target2);
|
|
@@ -4080,21 +4516,21 @@ var visit = (raw, language, context, depth, collector) => {
|
|
|
4080
4516
|
emit(result.stage, raw, language, context, collector);
|
|
4081
4517
|
for (const extra of result.extra) emit(extra, raw, language, context, collector);
|
|
4082
4518
|
};
|
|
4083
|
-
var collect = (
|
|
4084
|
-
if (depth >
|
|
4519
|
+
var collect = (text9, language, context, depth, collector) => {
|
|
4520
|
+
if (depth > MAX_DEPTH2) {
|
|
4085
4521
|
collector.incomplete = true;
|
|
4086
4522
|
return;
|
|
4087
4523
|
}
|
|
4088
|
-
const lexed = lex(
|
|
4524
|
+
const lexed = lex(text9, language);
|
|
4089
4525
|
if (lexed.incomplete) collector.incomplete = true;
|
|
4090
4526
|
for (const substitution of lexed.substitutions) {
|
|
4091
4527
|
collect(substitution, language, { ...context }, depth + 1, collector);
|
|
4092
4528
|
}
|
|
4093
4529
|
for (const raw of rawCommands(lexed.tokens)) visit(raw, language, context, depth, collector);
|
|
4094
4530
|
};
|
|
4095
|
-
var normalizeCommand = (
|
|
4531
|
+
var normalizeCommand = (text9, dialect = "bash") => {
|
|
4096
4532
|
const collector = { out: [], incomplete: false };
|
|
4097
|
-
collect(
|
|
4533
|
+
collect(text9, dialect, { env: {}, dir: void 0, via: [] }, 0, collector);
|
|
4098
4534
|
return { commands: collector.out, incomplete: collector.incomplete };
|
|
4099
4535
|
};
|
|
4100
4536
|
var dialectOf = (toolName3) => toolName3 === "PowerShell" ? "powershell" : "bash";
|
|
@@ -4106,7 +4542,7 @@ var segmentsOf = (command) => normalizeCommand(command).commands.map((item2) =>
|
|
|
4106
4542
|
|
|
4107
4543
|
// src/guards/shell.ts
|
|
4108
4544
|
var HEREDOC = /^<<[-<]?$/;
|
|
4109
|
-
var parseShell = (
|
|
4545
|
+
var parseShell = (text9) => normalizeCommand(text9).commands.map((command) => ({
|
|
4110
4546
|
env: command.env,
|
|
4111
4547
|
argv: command.program === "" ? [] : [command.program, ...command.args],
|
|
4112
4548
|
redirects: command.redirects.filter((redirect) => !HEREDOC.test(redirect.op)).map((redirect) => redirect.target)
|
|
@@ -4268,9 +4704,9 @@ var writtenPaths = (command) => {
|
|
|
4268
4704
|
if (name === "git" && GIT_WRITERS.has(operands2[0] ?? "")) return [...command.redirects, ...operands2.slice(1)];
|
|
4269
4705
|
return command.redirects;
|
|
4270
4706
|
};
|
|
4271
|
-
var mentioned = (
|
|
4272
|
-
if (/\.finitemonkey[\\/]allow/.test(
|
|
4273
|
-
if (/\.finitemonkey|\.gitleaksignore/.test(
|
|
4707
|
+
var mentioned = (text9) => {
|
|
4708
|
+
if (/\.finitemonkey[\\/]allow/.test(text9)) return "allow_store";
|
|
4709
|
+
if (/\.finitemonkey|\.gitleaksignore/.test(text9)) return "policy";
|
|
4274
4710
|
return void 0;
|
|
4275
4711
|
};
|
|
4276
4712
|
var bashSelfTargets = (root, commandLine) => parseShell(commandLine).flatMap(
|
|
@@ -4310,7 +4746,7 @@ var selfGuard = (input) => {
|
|
|
4310
4746
|
};
|
|
4311
4747
|
|
|
4312
4748
|
// src/guards/tests/hook.ts
|
|
4313
|
-
import { join as
|
|
4749
|
+
import { join as join14, relative as relative3 } from "node:path";
|
|
4314
4750
|
|
|
4315
4751
|
// src/guards/tests/patterns.ts
|
|
4316
4752
|
var JS_TEST = /(^|\/)(__tests__|__test__)\/|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)tests?\/.*\.[cm]?[jt]sx?$/;
|
|
@@ -4474,8 +4910,8 @@ var compareConfig = (path, before, after, accepted2) => {
|
|
|
4474
4910
|
// src/guards/tests/hook.ts
|
|
4475
4911
|
var localPath = (root, path) => toPosixPath(relative3(root, toAbsolutePath(path, root)));
|
|
4476
4912
|
var read = (input, path) => {
|
|
4477
|
-
const
|
|
4478
|
-
return
|
|
4913
|
+
const text9 = input.runtime.readText(path);
|
|
4914
|
+
return text9.ok ? text9.value : void 0;
|
|
4479
4915
|
};
|
|
4480
4916
|
var proposed = (tool, args2, before) => {
|
|
4481
4917
|
if (tool === "Write") return textField(args2, "content") ?? "";
|
|
@@ -4500,7 +4936,7 @@ var shellViolations = (input, root) => {
|
|
|
4500
4936
|
return parseShell(command).flatMap(
|
|
4501
4937
|
(simple) => writtenPaths(simple).flatMap((path) => {
|
|
4502
4938
|
const local = localPath(root, path);
|
|
4503
|
-
const exists = input.runtime.stat(toAbsolutePath(
|
|
4939
|
+
const exists = input.runtime.stat(toAbsolutePath(join14(root, local))) !== void 0;
|
|
4504
4940
|
if (!exists) return [];
|
|
4505
4941
|
if (isProtectedConfig(local)) return [{ kind: "config_changed", path: local }];
|
|
4506
4942
|
return isTestFile(local) ? [{ kind: "test_removed", path: local }] : [];
|
|
@@ -4513,46 +4949,14 @@ var testsGuard = (input) => {
|
|
|
4513
4949
|
const found = FILE_TOOLS.has(tool) ? fileViolations(input, root) : SHELL_TOOLS.has(tool) ? shellViolations(input, root) : [];
|
|
4514
4950
|
const first = found[0];
|
|
4515
4951
|
if (first === void 0 || grantFor(input, "tests") !== void 0) return { kind: "allow" };
|
|
4516
|
-
const { t } = input;
|
|
4517
|
-
const params2 = { file: first.path, before: first.before ?? 0, after: first.after ?? 0 };
|
|
4518
|
-
const reason = SHELL_TOOLS.has(tool) ? t("gate.guard.tests.shell", params2) : t(`gate.tests.${first.kind}`, params2);
|
|
4519
|
-
return deny(modelReason(t, "gate.guard.tests", { detail: reason }), t("gate.guard.tests.human", { detail: reason }));
|
|
4520
|
-
};
|
|
4521
|
-
|
|
4522
|
-
// src/look/reads.ts
|
|
4523
|
-
import { relative as relative4 } from "node:path";
|
|
4524
|
-
|
|
4525
|
-
// src/gate/hooks/common.ts
|
|
4526
|
-
import { dirname as dirname5, join as join12 } from "node:path";
|
|
4527
|
-
var DEFAULT_GATE_BUDGET_MS = 3e5;
|
|
4528
|
-
var sessionCwd = (input) => input.payload.cwd === void 0 ? input.cwd : toAbsolutePath(input.payload.cwd, input.cwd);
|
|
4529
|
-
var MAX_DEPTH2 = 64;
|
|
4530
|
-
var findRepoRoot = (runtime, start) => {
|
|
4531
|
-
let current2 = start;
|
|
4532
|
-
for (let depth = 0; depth < MAX_DEPTH2; depth += 1) {
|
|
4533
|
-
if (runtime.stat(toAbsolutePath(join12(current2, ".git"))) !== void 0) return current2;
|
|
4534
|
-
const parent = toAbsolutePath(dirname5(current2));
|
|
4535
|
-
if (parent === current2) return void 0;
|
|
4536
|
-
current2 = parent;
|
|
4537
|
-
}
|
|
4538
|
-
return void 0;
|
|
4539
|
-
};
|
|
4540
|
-
var projectPaths = (input) => {
|
|
4541
|
-
const root = findRepoRoot(input.runtime, sessionCwd(input));
|
|
4542
|
-
return root === void 0 ? void 0 : fmPaths(root);
|
|
4543
|
-
};
|
|
4544
|
-
var gateBudget = (input) => {
|
|
4545
|
-
const parsed = Number.parseInt(input.env["FINITEMONKEY_GATE_BUDGET_MS"] ?? "", 10);
|
|
4546
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_GATE_BUDGET_MS;
|
|
4952
|
+
const { t } = input;
|
|
4953
|
+
const params2 = { file: first.path, before: first.before ?? 0, after: first.after ?? 0 };
|
|
4954
|
+
const reason = SHELL_TOOLS.has(tool) ? t("gate.guard.tests.shell", params2) : t(`gate.tests.${first.kind}`, params2);
|
|
4955
|
+
return deny(modelReason(t, "gate.guard.tests", { detail: reason }), t("gate.guard.tests.human", { detail: reason }));
|
|
4547
4956
|
};
|
|
4548
|
-
var allowWith = (output) => ({ kind: "allow", output });
|
|
4549
|
-
var contextOutput = (event, additionalContext, systemMessage) => ({
|
|
4550
|
-
hookSpecificOutput: { hookEventName: event, additionalContext },
|
|
4551
|
-
...systemMessage === void 0 ? {} : { systemMessage }
|
|
4552
|
-
});
|
|
4553
|
-
var human = (text8) => `finitemonkey: ${text8}`;
|
|
4554
4957
|
|
|
4555
4958
|
// src/look/reads.ts
|
|
4959
|
+
import { relative as relative4 } from "node:path";
|
|
4556
4960
|
var READS_FILE = "look-reads.json";
|
|
4557
4961
|
var KEEP = 500;
|
|
4558
4962
|
var readLookReads = (runtime, paths) => {
|
|
@@ -4612,8 +5016,8 @@ var parseMarkers = (prompt) => {
|
|
|
4612
5016
|
// src/policy/pause.ts
|
|
4613
5017
|
var PAUSE_FILE = "pause.json";
|
|
4614
5018
|
var readPause = (runtime, paths) => {
|
|
4615
|
-
const
|
|
4616
|
-
const parsed =
|
|
5019
|
+
const text9 = runtime.readText(paths.cache(PAUSE_FILE));
|
|
5020
|
+
const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
|
|
4617
5021
|
if (parsed?.ok !== true || !isRecord(parsed.value)) return void 0;
|
|
4618
5022
|
const { sessionId, since, by } = parsed.value;
|
|
4619
5023
|
if (typeof sessionId !== "string" || typeof since !== "string") return void 0;
|
|
@@ -4660,7 +5064,7 @@ var unlessPaused = (handler) => (input) => {
|
|
|
4660
5064
|
};
|
|
4661
5065
|
|
|
4662
5066
|
// src/gate/hooks/config.ts
|
|
4663
|
-
import { join as
|
|
5067
|
+
import { join as join15 } from "node:path";
|
|
4664
5068
|
var CONFIG_FILE3 = "config-hooks.json";
|
|
4665
5069
|
var GUARDED_SOURCES = /* @__PURE__ */ new Set(["user_settings", "project_settings", "local_settings"]);
|
|
4666
5070
|
var countIn = (value) => {
|
|
@@ -4668,20 +5072,20 @@ var countIn = (value) => {
|
|
|
4668
5072
|
if (Array.isArray(value)) return value.reduce((sum, item2) => sum + countIn(item2), 0);
|
|
4669
5073
|
return isRecord(value) ? Object.values(value).reduce((sum, item2) => sum + countIn(item2), 0) : 0;
|
|
4670
5074
|
};
|
|
4671
|
-
var censusOf = (
|
|
4672
|
-
if (
|
|
4673
|
-
const parsed = parseJsonObject(
|
|
5075
|
+
var censusOf = (text9) => {
|
|
5076
|
+
if (text9 === void 0) return { hooks: 0, disabled: false };
|
|
5077
|
+
const parsed = parseJsonObject(text9);
|
|
4674
5078
|
if (!parsed.ok) return void 0;
|
|
4675
5079
|
return { hooks: countIn(parsed.value["hooks"]), disabled: parsed.value["disableAllHooks"] === true };
|
|
4676
5080
|
};
|
|
4677
5081
|
var settingsFiles = (paths, env) => [
|
|
4678
|
-
toAbsolutePath(
|
|
4679
|
-
toAbsolutePath(
|
|
4680
|
-
...env["HOME"] === void 0 ? [] : [toAbsolutePath(
|
|
5082
|
+
toAbsolutePath(join15(paths.root, ".claude", "settings.json")),
|
|
5083
|
+
toAbsolutePath(join15(paths.root, ".claude", "settings.local.json")),
|
|
5084
|
+
...env["HOME"] === void 0 ? [] : [toAbsolutePath(join15(env["HOME"], ".claude", "settings.json"))]
|
|
4681
5085
|
];
|
|
4682
5086
|
var read2 = (runtime, path) => {
|
|
4683
|
-
const
|
|
4684
|
-
return
|
|
5087
|
+
const text9 = runtime.readText(path);
|
|
5088
|
+
return text9.ok ? text9.value : void 0;
|
|
4685
5089
|
};
|
|
4686
5090
|
var known = (runtime, paths) => {
|
|
4687
5091
|
const raw = readJsonFile(runtime, paths.cache(CONFIG_FILE3))?.["hooks"];
|
|
@@ -4736,9 +5140,9 @@ var LOCKFILES2 = [
|
|
|
4736
5140
|
];
|
|
4737
5141
|
var packageManager = (files) => LOCKFILES2.find(([file]) => files.exists(file))?.[1] ?? "npm";
|
|
4738
5142
|
var scriptChecks = (files) => {
|
|
4739
|
-
const
|
|
4740
|
-
if (
|
|
4741
|
-
const parsed = parseJsonObject(
|
|
5143
|
+
const text9 = files.read("package.json");
|
|
5144
|
+
if (text9 === void 0) return [];
|
|
5145
|
+
const parsed = parseJsonObject(text9);
|
|
4742
5146
|
const scripts = parsed.ok ? parsed.value["scripts"] : void 0;
|
|
4743
5147
|
if (!isRecord(scripts)) return [];
|
|
4744
5148
|
const pm = packageManager(files);
|
|
@@ -4790,7 +5194,7 @@ var implicitContract = (files) => {
|
|
|
4790
5194
|
};
|
|
4791
5195
|
|
|
4792
5196
|
// src/gate/prepare.ts
|
|
4793
|
-
import { join as
|
|
5197
|
+
import { join as join17 } from "node:path";
|
|
4794
5198
|
|
|
4795
5199
|
// src/core/yaml/scalar.ts
|
|
4796
5200
|
var ESCAPES = {
|
|
@@ -4828,16 +5232,16 @@ var unescapeDouble = (body2) => {
|
|
|
4828
5232
|
}
|
|
4829
5233
|
return accept(out);
|
|
4830
5234
|
};
|
|
4831
|
-
var quotedEnd = (
|
|
4832
|
-
const quote =
|
|
4833
|
-
for (let index = start + 1; index <
|
|
4834
|
-
const char =
|
|
5235
|
+
var quotedEnd = (text9, start) => {
|
|
5236
|
+
const quote = text9[start];
|
|
5237
|
+
for (let index = start + 1; index < text9.length; index += 1) {
|
|
5238
|
+
const char = text9[index];
|
|
4835
5239
|
if (quote === '"' && char === "\\") {
|
|
4836
5240
|
index += 1;
|
|
4837
5241
|
continue;
|
|
4838
5242
|
}
|
|
4839
5243
|
if (char !== quote) continue;
|
|
4840
|
-
if (quote === "'" &&
|
|
5244
|
+
if (quote === "'" && text9[index + 1] === "'") {
|
|
4841
5245
|
index += 1;
|
|
4842
5246
|
continue;
|
|
4843
5247
|
}
|
|
@@ -4845,20 +5249,20 @@ var quotedEnd = (text8, start) => {
|
|
|
4845
5249
|
}
|
|
4846
5250
|
return -1;
|
|
4847
5251
|
};
|
|
4848
|
-
var parseQuoted = (
|
|
4849
|
-
const end = quotedEnd(
|
|
4850
|
-
if (end !==
|
|
4851
|
-
const body2 =
|
|
4852
|
-
return
|
|
5252
|
+
var parseQuoted = (text9) => {
|
|
5253
|
+
const end = quotedEnd(text9, 0);
|
|
5254
|
+
if (end !== text9.length - 1) return reject(`unterminated or trailing text after quoted string`);
|
|
5255
|
+
const body2 = text9.slice(1, -1);
|
|
5256
|
+
return text9.startsWith('"') ? unescapeDouble(body2) : accept(body2.replace(/''/g, "'"));
|
|
4853
5257
|
};
|
|
4854
5258
|
var NUMBER = /^-?(0|[1-9]\d*)(\.\d+)?([eE][-+]?\d+)?$/;
|
|
4855
5259
|
var UNSUPPORTED = /^[&*!|>%@`]/;
|
|
4856
|
-
var parsePlain = (
|
|
4857
|
-
if (UNSUPPORTED.test(
|
|
4858
|
-
if (
|
|
4859
|
-
if (
|
|
4860
|
-
if (NUMBER.test(
|
|
4861
|
-
return accept(
|
|
5260
|
+
var parsePlain = (text9) => {
|
|
5261
|
+
if (UNSUPPORTED.test(text9)) return reject(`unsupported YAML syntax "${text9.slice(0, 1)}"`);
|
|
5262
|
+
if (text9 === "true" || text9 === "false") return accept(text9 === "true");
|
|
5263
|
+
if (text9 === "null" || text9 === "~") return accept(null);
|
|
5264
|
+
if (NUMBER.test(text9)) return accept(Number(text9));
|
|
5265
|
+
return accept(text9);
|
|
4862
5266
|
};
|
|
4863
5267
|
|
|
4864
5268
|
// src/core/yaml/flow.ts
|
|
@@ -4885,17 +5289,17 @@ var splitTop = (body2) => {
|
|
|
4885
5289
|
if (last.trim() !== "" || parts.length > 0) parts.push(last);
|
|
4886
5290
|
return accept(parts.map((part2) => part2.trim()));
|
|
4887
5291
|
};
|
|
4888
|
-
var splitKey = (
|
|
5292
|
+
var splitKey = (text9) => {
|
|
4889
5293
|
let index = 0;
|
|
4890
|
-
if (
|
|
4891
|
-
index = quotedEnd(
|
|
5294
|
+
if (text9.startsWith('"') || text9.startsWith("'")) {
|
|
5295
|
+
index = quotedEnd(text9, 0) + 1;
|
|
4892
5296
|
if (index <= 0) return void 0;
|
|
4893
5297
|
}
|
|
4894
|
-
for (; index <
|
|
4895
|
-
const char =
|
|
4896
|
-
if (char === "#" &&
|
|
4897
|
-
if (char === ":" && (index + 1 ===
|
|
4898
|
-
return { key:
|
|
5298
|
+
for (; index < text9.length; index += 1) {
|
|
5299
|
+
const char = text9[index];
|
|
5300
|
+
if (char === "#" && text9[index - 1] === " ") return void 0;
|
|
5301
|
+
if (char === ":" && (index + 1 === text9.length || text9[index + 1] === " ")) {
|
|
5302
|
+
return { key: text9.slice(0, index).trim(), rest: text9.slice(index + 1).trim() };
|
|
4899
5303
|
}
|
|
4900
5304
|
}
|
|
4901
5305
|
return void 0;
|
|
@@ -4924,39 +5328,39 @@ var flowSeq = (parts) => {
|
|
|
4924
5328
|
}
|
|
4925
5329
|
return accept(out);
|
|
4926
5330
|
};
|
|
4927
|
-
var parseValue = (
|
|
4928
|
-
if (
|
|
4929
|
-
const first =
|
|
4930
|
-
if (first === '"' || first === "'") return parseQuoted(
|
|
5331
|
+
var parseValue = (text9) => {
|
|
5332
|
+
if (text9 === "") return accept(null);
|
|
5333
|
+
const first = text9[0] ?? "";
|
|
5334
|
+
if (first === '"' || first === "'") return parseQuoted(text9);
|
|
4931
5335
|
const close2 = OPEN[first];
|
|
4932
|
-
if (close2 === void 0) return parsePlain(
|
|
4933
|
-
if (!
|
|
4934
|
-
const parts = splitTop(
|
|
5336
|
+
if (close2 === void 0) return parsePlain(text9);
|
|
5337
|
+
if (!text9.endsWith(close2)) return reject(`flow collection must end on the same line with "${close2}"`);
|
|
5338
|
+
const parts = splitTop(text9.slice(1, -1));
|
|
4935
5339
|
if (!parts.ok) return parts;
|
|
4936
5340
|
return first === "[" ? flowSeq(parts.value) : flowMap(parts.value);
|
|
4937
5341
|
};
|
|
4938
5342
|
|
|
4939
5343
|
// src/core/yaml/parse.ts
|
|
4940
5344
|
var BOM = new RegExp(`^${String.fromCharCode(65279)}`);
|
|
4941
|
-
var stripComment2 = (
|
|
4942
|
-
for (let index = 0; index <
|
|
4943
|
-
const char =
|
|
5345
|
+
var stripComment2 = (text9) => {
|
|
5346
|
+
for (let index = 0; index < text9.length; index += 1) {
|
|
5347
|
+
const char = text9[index];
|
|
4944
5348
|
if (char === '"' || char === "'") {
|
|
4945
|
-
const end = quotedEnd(
|
|
4946
|
-
if (end < 0) return
|
|
5349
|
+
const end = quotedEnd(text9, index);
|
|
5350
|
+
if (end < 0) return text9;
|
|
4947
5351
|
index = end;
|
|
4948
|
-
} else if (char === "#" && (index === 0 ||
|
|
5352
|
+
} else if (char === "#" && (index === 0 || text9[index - 1] === " ")) return text9.slice(0, index);
|
|
4949
5353
|
}
|
|
4950
|
-
return
|
|
5354
|
+
return text9;
|
|
4951
5355
|
};
|
|
4952
5356
|
var toLines = (source) => {
|
|
4953
5357
|
const lines3 = [];
|
|
4954
5358
|
const raw = source.replace(BOM, "").split(/\r?\n/);
|
|
4955
5359
|
for (const [index, full] of raw.entries()) {
|
|
4956
|
-
const
|
|
4957
|
-
const body2 =
|
|
5360
|
+
const text9 = stripComment2(full).trimEnd();
|
|
5361
|
+
const body2 = text9.trimStart();
|
|
4958
5362
|
if (body2 === "" || body2 === "---" && lines3.length === 0) continue;
|
|
4959
|
-
const indentText =
|
|
5363
|
+
const indentText = text9.slice(0, text9.length - body2.length);
|
|
4960
5364
|
if (indentText.includes(" ")) return reject({ line: index + 1, message: "tabs are not allowed for indentation" });
|
|
4961
5365
|
if (body2 === "---" || body2 === "...")
|
|
4962
5366
|
return reject({ line: index + 1, message: "only one YAML document is allowed" });
|
|
@@ -4965,11 +5369,11 @@ var toLines = (source) => {
|
|
|
4965
5369
|
return accept(lines3);
|
|
4966
5370
|
};
|
|
4967
5371
|
var fail = (line2, message) => reject({ line: line2?.number ?? 0, message });
|
|
4968
|
-
var scalar = (line2,
|
|
4969
|
-
const value = parseValue(
|
|
5372
|
+
var scalar = (line2, text9) => {
|
|
5373
|
+
const value = parseValue(text9);
|
|
4970
5374
|
return value.ok ? value : fail(line2, value.error);
|
|
4971
5375
|
};
|
|
4972
|
-
var isItem = (
|
|
5376
|
+
var isItem = (text9) => text9 === "-" || text9.startsWith("- ");
|
|
4973
5377
|
var child = (lines3, index, parent, sameIndentSeq) => {
|
|
4974
5378
|
const next2 = lines3[index];
|
|
4975
5379
|
if (next2 !== void 0 && next2.indent > parent.indent) return block(lines3, index, next2.indent);
|
|
@@ -5212,8 +5616,8 @@ var validateContract = (value, implicit = false) => {
|
|
|
5212
5616
|
// src/task/load.ts
|
|
5213
5617
|
var contractHash = (contract) => sha256Hex(JSON.stringify(contract.acceptance.map((check2) => [check2.id, check2.kind, check2.run ?? ""])));
|
|
5214
5618
|
var taskRelative = (id) => `.finitemonkey/tasks/${id}.yml`;
|
|
5215
|
-
var parseContract = (id,
|
|
5216
|
-
const yaml = parseYaml(
|
|
5619
|
+
var parseContract = (id, text9) => {
|
|
5620
|
+
const yaml = parseYaml(text9);
|
|
5217
5621
|
if (!yaml.ok)
|
|
5218
5622
|
return reject({ kind: "invalid", id, issues: [`line ${String(yaml.error.line)}: ${yaml.error.message}`] });
|
|
5219
5623
|
const valid = validateContract(yaml.value);
|
|
@@ -5287,7 +5691,7 @@ var readBaseline = (runtime, paths) => {
|
|
|
5287
5691
|
var writeBaseline = (runtime, paths, treeHash2, checks) => writeJsonFile(runtime, paths.cache(BASELINE_FILE), { treeHash: treeHash2, checks: { ...checks } }).ok;
|
|
5288
5692
|
|
|
5289
5693
|
// src/gate/diff-shape.ts
|
|
5290
|
-
import { join as
|
|
5694
|
+
import { join as join16 } from "node:path";
|
|
5291
5695
|
|
|
5292
5696
|
// src/gate/git.ts
|
|
5293
5697
|
var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
@@ -5323,10 +5727,10 @@ var resolveBase = (git2, declared) => {
|
|
|
5323
5727
|
const merged2 = remote === void 0 ? void 0 : git2.out(["merge-base", "HEAD", remote])?.trim();
|
|
5324
5728
|
return nonEmpty(merged2) ?? nonEmpty(headSha(git2)) ?? EMPTY_TREE;
|
|
5325
5729
|
};
|
|
5326
|
-
var nulFields = (
|
|
5730
|
+
var nulFields = (text9) => text9.split("\0").filter((part2) => part2 !== "");
|
|
5327
5731
|
var untracked = (git2) => nulFields(git2.out(["ls-files", "--others", "--exclude-standard", "-z", "--", ".", EXCLUDE_FM]) ?? "");
|
|
5328
|
-
var parseNameStatus = (
|
|
5329
|
-
const fields = nulFields(
|
|
5732
|
+
var parseNameStatus = (text9) => {
|
|
5733
|
+
const fields = nulFields(text9);
|
|
5330
5734
|
const changes = [];
|
|
5331
5735
|
for (let index = 0; index < fields.length; index += 1) {
|
|
5332
5736
|
const code = fields[index] ?? "";
|
|
@@ -5394,8 +5798,8 @@ var aheadOfUpstream = (git2) => {
|
|
|
5394
5798
|
|
|
5395
5799
|
// src/gate/diff-shape.ts
|
|
5396
5800
|
var untrackedLines = (runtime, root, changes) => changes.filter((change) => change.status === "A").reduce((sum, change) => {
|
|
5397
|
-
const
|
|
5398
|
-
return sum + (
|
|
5801
|
+
const text9 = runtime.readText(toAbsolutePath(join16(root, change.path)));
|
|
5802
|
+
return sum + (text9.ok ? text9.value.split("\n").length : 0);
|
|
5399
5803
|
}, 0);
|
|
5400
5804
|
var diffShape = (runtime, git2, base, changes) => ({
|
|
5401
5805
|
files: changes.map((change) => change.path),
|
|
@@ -5418,8 +5822,8 @@ var ciChecks = (value) => Array.isArray(value) ? value.flatMap((item2) => {
|
|
|
5418
5822
|
const name = optionalString(item2, "name") ?? optionalString(item2, "context") ?? "?";
|
|
5419
5823
|
return [{ name, state: ciState(item2) }];
|
|
5420
5824
|
}) : [];
|
|
5421
|
-
var parsePrView = (
|
|
5422
|
-
const parsed = parseJsonObject(
|
|
5825
|
+
var parsePrView = (text9) => {
|
|
5826
|
+
const parsed = parseJsonObject(text9);
|
|
5423
5827
|
if (!parsed.ok) return void 0;
|
|
5424
5828
|
const record3 = parsed.value;
|
|
5425
5829
|
const number2 = record3["number"];
|
|
@@ -5600,10 +6004,10 @@ var applySize = (contract, verdict) => {
|
|
|
5600
6004
|
// src/gate/prepare.ts
|
|
5601
6005
|
var diskFiles = (runtime, root) => ({
|
|
5602
6006
|
read: (relative11) => {
|
|
5603
|
-
const
|
|
5604
|
-
return
|
|
6007
|
+
const text9 = runtime.readText(toAbsolutePath(join17(root, relative11)));
|
|
6008
|
+
return text9.ok ? text9.value : void 0;
|
|
5605
6009
|
},
|
|
5606
|
-
exists: (relative11) => runtime.stat(toAbsolutePath(
|
|
6010
|
+
exists: (relative11) => runtime.stat(toAbsolutePath(join17(root, relative11))) !== void 0
|
|
5607
6011
|
});
|
|
5608
6012
|
var baseFiles = (git2, base, disk) => ({
|
|
5609
6013
|
read: (relative11) => base === EMPTY_TREE ? disk.read(relative11) : fileAt(git2, base, relative11),
|
|
@@ -5615,7 +6019,7 @@ var loadContract = (options, paths, git2, base) => {
|
|
|
5615
6019
|
if (id === void 0) return accept(loadImplicit(options.runtime, paths, baseFiles(git2, base, disk)));
|
|
5616
6020
|
return loadTask(options.runtime, paths, { disk: disk.read, base: (relative11) => fileAt(git2, base, relative11) }, id);
|
|
5617
6021
|
};
|
|
5618
|
-
var
|
|
6022
|
+
var memo2 = (make) => {
|
|
5619
6023
|
let value;
|
|
5620
6024
|
return () => {
|
|
5621
6025
|
value ??= { v: make() };
|
|
@@ -5654,7 +6058,7 @@ var prepareGate = (options) => {
|
|
|
5654
6058
|
if (tree === void 0) return { kind: "failed", failure: { kind: "no_tree_hash" } };
|
|
5655
6059
|
const skip = options.noSkip === true ? void 0 : skipReason(options.runtime, paths, loaded.value, changes, tree, options.sessionId);
|
|
5656
6060
|
if (skip !== void 0) return { kind: "skipped", reason: skip };
|
|
5657
|
-
const pr =
|
|
6061
|
+
const pr = memo2(() => prStatus(options.runtime, git2, paths));
|
|
5658
6062
|
const size = measureSize(options.runtime, git2, base, changes, loaded.value.contract);
|
|
5659
6063
|
const ctx = {
|
|
5660
6064
|
runtime: options.runtime,
|
|
@@ -5722,11 +6126,11 @@ var trust = (input, paths, task) => {
|
|
|
5722
6126
|
});
|
|
5723
6127
|
if (ready.kind !== "ready" || ready.prepared.ctx.contract.id !== task) return false;
|
|
5724
6128
|
const lock = readTrust(runtime, paths);
|
|
5725
|
-
const
|
|
6129
|
+
const text9 = runtime.readText(paths.task(task));
|
|
5726
6130
|
return writeTrust(runtime, paths, {
|
|
5727
6131
|
...lock,
|
|
5728
6132
|
contracts: { ...lock.contracts, [task]: ready.prepared.loaded.hash },
|
|
5729
|
-
approved: task === IMPLICIT_ID || !
|
|
6133
|
+
approved: task === IMPLICIT_ID || !text9.ok ? lock.approved : { ...lock.approved, [task]: text9.value }
|
|
5730
6134
|
}).ok;
|
|
5731
6135
|
};
|
|
5732
6136
|
var apply = (input, paths, markers) => {
|
|
@@ -5778,8 +6182,8 @@ var promptRelease = (input) => {
|
|
|
5778
6182
|
var PRS_CACHE_MS = 10 * 6e4;
|
|
5779
6183
|
var PRS_TIMEOUT_MS = 3e3;
|
|
5780
6184
|
var PRS_AUTH_TIMEOUT_MS = 2e3;
|
|
5781
|
-
var parsePrList = (
|
|
5782
|
-
const parsed = parseJson(
|
|
6185
|
+
var parsePrList = (text9) => {
|
|
6186
|
+
const parsed = parseJson(text9);
|
|
5783
6187
|
if (!parsed.ok || !Array.isArray(parsed.value)) return void 0;
|
|
5784
6188
|
return parsed.value.flatMap((item2) => {
|
|
5785
6189
|
if (!isRecord(item2) || typeof item2["number"] !== "number") return [];
|
|
@@ -5865,16 +6269,16 @@ var params = (value) => isRecord(value) ? Object.fromEntries(
|
|
|
5865
6269
|
var notes = (value) => Array.isArray(value) ? value.flatMap(
|
|
5866
6270
|
(item2) => isRecord(item2) && typeof item2["key"] === "string" ? [{ key: item2["key"], params: params(item2["params"]) }] : []
|
|
5867
6271
|
) : [];
|
|
5868
|
-
var
|
|
6272
|
+
var text4 = (record3, key) => {
|
|
5869
6273
|
const value = record3[key];
|
|
5870
6274
|
return typeof value === "string" ? value : void 0;
|
|
5871
6275
|
};
|
|
5872
6276
|
var optional = (record3) => {
|
|
5873
6277
|
const exitCode = record3["exitCode"];
|
|
5874
6278
|
const durationMs = record3["durationMs"];
|
|
5875
|
-
const command =
|
|
5876
|
-
const stdoutSha256 =
|
|
5877
|
-
const releasedBy =
|
|
6279
|
+
const command = text4(record3, "command");
|
|
6280
|
+
const stdoutSha256 = text4(record3, "stdoutSha256");
|
|
6281
|
+
const releasedBy = text4(record3, "releasedBy");
|
|
5878
6282
|
const reason = oneOf2(UNVERIFIED_REASONS, record3["reason"]);
|
|
5879
6283
|
const failures = record3["failures"];
|
|
5880
6284
|
const testCount = record3["testCount"];
|
|
@@ -5894,11 +6298,11 @@ var parseEvidence = (line2) => {
|
|
|
5894
6298
|
if (!parsed.ok) return void 0;
|
|
5895
6299
|
const record3 = parsed.value;
|
|
5896
6300
|
const status = oneOf2(EVIDENCE_STATUSES, record3["status"]);
|
|
5897
|
-
const task =
|
|
5898
|
-
const check2 =
|
|
5899
|
-
const kind =
|
|
5900
|
-
const treeHash2 =
|
|
5901
|
-
const at =
|
|
6301
|
+
const task = text4(record3, "task");
|
|
6302
|
+
const check2 = text4(record3, "check");
|
|
6303
|
+
const kind = text4(record3, "kind");
|
|
6304
|
+
const treeHash2 = text4(record3, "treeHash");
|
|
6305
|
+
const at = text4(record3, "at");
|
|
5902
6306
|
if (record3["schema"] !== EVIDENCE_SCHEMA || status === void 0) return void 0;
|
|
5903
6307
|
if (task === void 0 || check2 === void 0 || kind === void 0 || treeHash2 === void 0 || at === void 0) {
|
|
5904
6308
|
return void 0;
|
|
@@ -5915,7 +6319,7 @@ var parseEvidence = (line2) => {
|
|
|
5915
6319
|
...optional(record3)
|
|
5916
6320
|
};
|
|
5917
6321
|
};
|
|
5918
|
-
var
|
|
6322
|
+
var readLedger2 = (runtime, path) => {
|
|
5919
6323
|
const content = runtime.readText(path);
|
|
5920
6324
|
if (!content.ok) return [];
|
|
5921
6325
|
return content.value.split("\n").flatMap((line2) => {
|
|
@@ -6585,7 +6989,38 @@ var en_default = {
|
|
|
6585
6989
|
"statusline.gate.red": "gate red",
|
|
6586
6990
|
"statusline.gate.incomplete": "gate incomplete",
|
|
6587
6991
|
"statusline.gate.did_not_run": "gate did not run",
|
|
6588
|
-
"statusline.context": "ctx {percent}%"
|
|
6992
|
+
"statusline.context": "ctx {percent}%",
|
|
6993
|
+
"command.gain.summary": "What the guards did: saves next to false alarms and gate failures",
|
|
6994
|
+
"gain.title": "Finite Monkey, last {days, plural, one {# day} other {# days}} (from .finitemonkey/log.jsonl and the evidence ledgers):",
|
|
6995
|
+
"gain.row.blocks": "{count, plural, one {# block} other {# blocks}} by the guards",
|
|
6996
|
+
"gain.row.saves": "{count, plural, one {# block} other {# blocks}} followed by a green gate within a day",
|
|
6997
|
+
"gain.row.false_positives": "{count, plural, one {# false alarm} other {# false alarms}} (releases typed by the person, and false positives recorded)",
|
|
6998
|
+
"gain.row.gate_runs": "{count, plural, one {# gate run} other {# gate runs}}",
|
|
6999
|
+
"gain.row.gate_failed": "{count, plural, one {# gate run} other {# gate runs}} failed",
|
|
7000
|
+
"gain.row.gate_unverified": "{count, plural, one {# gate run} other {# gate runs}} ended not verified",
|
|
7001
|
+
"gain.row.gate_time": "{minutes, plural, one {# minute} other {# minutes}} spent in the gate",
|
|
7002
|
+
"gain.up": "up {delta} on the window before",
|
|
7003
|
+
"gain.down": "down {delta} on the window before",
|
|
7004
|
+
"gain.footer": "Counts come from local files only; a save is a block followed by a passing gate, not proof that the block caused it.",
|
|
7005
|
+
"critique.command.verify": "Check a review's must-fix findings: run each reproduction command on base and head in throwaway worktrees and keep only what fails on head alone",
|
|
7006
|
+
"critique.title": "Critique",
|
|
7007
|
+
"critique.group.must": "Must fix",
|
|
7008
|
+
"critique.group.should": "Should fix",
|
|
7009
|
+
"critique.group.nice": "Nice to have",
|
|
7010
|
+
"critique.group.already-there": "Already there (fails on base too)",
|
|
7011
|
+
"critique.group.not-reproduced": "Not reproduced",
|
|
7012
|
+
"critique.finding": "{id}. {what}. Problem: {problem}. Fix: {fix}. If not fixed: {risk}.",
|
|
7013
|
+
"critique.table.command": "reproduction",
|
|
7014
|
+
"critique.table.outcome": "outcome",
|
|
7015
|
+
"critique.outcome.head-only": "fails only on head: kept",
|
|
7016
|
+
"critique.outcome.already-there": "fails on both: already there",
|
|
7017
|
+
"critique.outcome.not-reproduced": "fails on neither: not reproduced",
|
|
7018
|
+
"critique.outcome.not-run": "not run",
|
|
7019
|
+
"critique.demoted": "Finding {id} was marked must fix without a reproduction command and expected result, so it counts as should fix.",
|
|
7020
|
+
"critique.decision": "Decision needed: finding {id} ({what}) already fails on the base branch and is an active risk.",
|
|
7021
|
+
"critique.verdict": "Verdict: {verdict}",
|
|
7022
|
+
"critique.error.invalid": "the findings file is not valid: {detail}",
|
|
7023
|
+
"critique.error.must_fix": "{count, plural, one {# must-fix finding reproduced on head only} other {# must-fix findings reproduced on head only}}"
|
|
6589
7024
|
};
|
|
6590
7025
|
|
|
6591
7026
|
// src/i18n/es.json
|
|
@@ -7243,7 +7678,38 @@ var es_default = {
|
|
|
7243
7678
|
"statusline.gate.red": "compuerta roja",
|
|
7244
7679
|
"statusline.gate.incomplete": "compuerta incompleta",
|
|
7245
7680
|
"statusline.gate.did_not_run": "la compuerta no se ejecutó",
|
|
7246
|
-
"statusline.context": "ctx {percent}%"
|
|
7681
|
+
"statusline.context": "ctx {percent}%",
|
|
7682
|
+
"command.gain.summary": "Lo que hicieron los bloqueos: aciertos junto a falsas alarmas y fallos de la compuerta",
|
|
7683
|
+
"gain.title": "Finite Monkey, {days, plural, one {último # día} other {últimos # días}} (de .finitemonkey/log.jsonl y los ledgers de evidencia):",
|
|
7684
|
+
"gain.row.blocks": "{count, plural, one {# bloqueo} other {# bloqueos}} de los guards",
|
|
7685
|
+
"gain.row.saves": "{count, plural, one {# bloqueo seguido} other {# bloqueos seguidos}} de una compuerta en verde en menos de un día",
|
|
7686
|
+
"gain.row.false_positives": "{count, plural, one {# falsa alarma} other {# falsas alarmas}} (liberaciones escritas por la persona y falsos positivos registrados)",
|
|
7687
|
+
"gain.row.gate_runs": "{count, plural, one {# ejecución de la compuerta} other {# ejecuciones de la compuerta}}",
|
|
7688
|
+
"gain.row.gate_failed": "{count, plural, one {# ejecución de la compuerta falló} other {# ejecuciones de la compuerta fallaron}}",
|
|
7689
|
+
"gain.row.gate_unverified": "{count, plural, one {# ejecución de la compuerta terminó} other {# ejecuciones de la compuerta terminaron}} sin verificar",
|
|
7690
|
+
"gain.row.gate_time": "{minutes, plural, one {# minuto} other {# minutos}} en la compuerta",
|
|
7691
|
+
"gain.up": "+{delta} respecto a la ventana anterior",
|
|
7692
|
+
"gain.down": "-{delta} respecto a la ventana anterior",
|
|
7693
|
+
"gain.footer": "Los conteos salen solo de archivos locales; un acierto es un bloqueo seguido de una compuerta en verde, no prueba de que el bloqueo lo causara.",
|
|
7694
|
+
"critique.command.verify": "Revisa los hallazgos obligatorios de una revisión: ejecuta cada comando de reproducción en base y en head en worktrees desechables y conserva solo lo que falla únicamente en head",
|
|
7695
|
+
"critique.title": "Crítica",
|
|
7696
|
+
"critique.group.must": "Corregir sí o sí",
|
|
7697
|
+
"critique.group.should": "Debería corregirse",
|
|
7698
|
+
"critique.group.nice": "Sería bueno",
|
|
7699
|
+
"critique.group.already-there": "Ya estaba (falla también en base)",
|
|
7700
|
+
"critique.group.not-reproduced": "No reproducido",
|
|
7701
|
+
"critique.finding": "{id}. {what}. Problema: {problem}. Arreglo: {fix}. Si no se corrige: {risk}.",
|
|
7702
|
+
"critique.table.command": "reproducción",
|
|
7703
|
+
"critique.table.outcome": "resultado",
|
|
7704
|
+
"critique.outcome.head-only": "falla solo en head: se mantiene",
|
|
7705
|
+
"critique.outcome.already-there": "falla en ambos: ya estaba",
|
|
7706
|
+
"critique.outcome.not-reproduced": "no falla en ninguno: no reproducido",
|
|
7707
|
+
"critique.outcome.not-run": "no se ejecutó",
|
|
7708
|
+
"critique.demoted": "El hallazgo {id} venía como obligatorio sin comando de reproducción ni resultado esperado, así que cuenta como debería corregirse.",
|
|
7709
|
+
"critique.decision": "Decisión necesaria: el hallazgo {id} ({what}) ya falla en la rama base y es un riesgo activo.",
|
|
7710
|
+
"critique.verdict": "Veredicto: {verdict}",
|
|
7711
|
+
"critique.error.invalid": "el archivo de hallazgos no es válido: {detail}",
|
|
7712
|
+
"critique.error.must_fix": "{count, plural, one {# hallazgo obligatorio reproducido solo en head} other {# hallazgos obligatorios reproducidos solo en head}}"
|
|
7247
7713
|
};
|
|
7248
7714
|
|
|
7249
7715
|
// src/i18n/pt-BR.json
|
|
@@ -7901,7 +8367,38 @@ var pt_BR_default = {
|
|
|
7901
8367
|
"statusline.gate.red": "portão vermelho",
|
|
7902
8368
|
"statusline.gate.incomplete": "portão incompleto",
|
|
7903
8369
|
"statusline.gate.did_not_run": "portão não rodou",
|
|
7904
|
-
"statusline.context": "ctx {percent}%"
|
|
8370
|
+
"statusline.context": "ctx {percent}%",
|
|
8371
|
+
"command.gain.summary": "O que as travas fizeram: acertos ao lado dos alarmes falsos e das falhas do portão",
|
|
8372
|
+
"gain.title": "Finite Monkey, últimos {days, plural, one {# dia} other {# dias}} (de .finitemonkey/log.jsonl e dos ledgers de evidência):",
|
|
8373
|
+
"gain.row.blocks": "{count, plural, one {# bloqueio} other {# bloqueios}} das travas",
|
|
8374
|
+
"gain.row.saves": "{count, plural, one {# bloqueio seguido} other {# bloqueios seguidos}} de portão verde em até um dia",
|
|
8375
|
+
"gain.row.false_positives": "{count, plural, one {# alarme falso} other {# alarmes falsos}} (liberações digitadas pela pessoa e falsos positivos registrados)",
|
|
8376
|
+
"gain.row.gate_runs": "{count, plural, one {# execução do portão} other {# execuções do portão}}",
|
|
8377
|
+
"gain.row.gate_failed": "{count, plural, one {# execução do portão reprovou} other {# execuções do portão reprovaram}}",
|
|
8378
|
+
"gain.row.gate_unverified": "{count, plural, one {# execução do portão terminou} other {# execuções do portão terminaram}} sem verificar",
|
|
8379
|
+
"gain.row.gate_time": "{minutes, plural, one {# minuto} other {# minutos}} no portão",
|
|
8380
|
+
"gain.up": "+{delta} em relação à janela anterior",
|
|
8381
|
+
"gain.down": "-{delta} em relação à janela anterior",
|
|
8382
|
+
"gain.footer": "As contagens vêm só de arquivos locais; um acerto é um bloqueio seguido de portão verde, não prova de que o bloqueio causou isso.",
|
|
8383
|
+
"critique.command.verify": "Confere os achados obrigatórios de uma revisão: roda cada comando de reprodução na base e no head em worktrees descartáveis e fica só com o que falha apenas no head",
|
|
8384
|
+
"critique.title": "Crítica",
|
|
8385
|
+
"critique.group.must": "Corrigir obrigatoriamente",
|
|
8386
|
+
"critique.group.should": "Deveria corrigir",
|
|
8387
|
+
"critique.group.nice": "Seria bom",
|
|
8388
|
+
"critique.group.already-there": "Já existia (falha na base também)",
|
|
8389
|
+
"critique.group.not-reproduced": "Não reproduzido",
|
|
8390
|
+
"critique.finding": "{id}. {what}. Problema: {problem}. Conserto: {fix}. Se não corrigir: {risk}.",
|
|
8391
|
+
"critique.table.command": "reprodução",
|
|
8392
|
+
"critique.table.outcome": "resultado",
|
|
8393
|
+
"critique.outcome.head-only": "falha só no head: mantido",
|
|
8394
|
+
"critique.outcome.already-there": "falha nos dois: já existia",
|
|
8395
|
+
"critique.outcome.not-reproduced": "não falha em nenhum: não reproduzido",
|
|
8396
|
+
"critique.outcome.not-run": "não rodou",
|
|
8397
|
+
"critique.demoted": "O achado {id} veio como obrigatório sem comando de reprodução e resultado esperado, então conta como deveria corrigir.",
|
|
8398
|
+
"critique.decision": "Decisão necessária: o achado {id} ({what}) já falha na branch base e é um risco ativo.",
|
|
8399
|
+
"critique.verdict": "Veredito: {verdict}",
|
|
8400
|
+
"critique.error.invalid": "o arquivo de achados não é válido: {detail}",
|
|
8401
|
+
"critique.error.must_fix": "{count, plural, one {# achado obrigatório reproduzido só no head} other {# achados obrigatórios reproduzidos só no head}}"
|
|
7905
8402
|
};
|
|
7906
8403
|
|
|
7907
8404
|
// src/i18n/review.json
|
|
@@ -8560,7 +9057,38 @@ var review_default = {
|
|
|
8560
9057
|
"statusline.gate.red": "machine",
|
|
8561
9058
|
"statusline.gate.incomplete": "machine",
|
|
8562
9059
|
"statusline.gate.did_not_run": "machine",
|
|
8563
|
-
"statusline.context": "machine"
|
|
9060
|
+
"statusline.context": "machine",
|
|
9061
|
+
"command.gain.summary": "machine",
|
|
9062
|
+
"gain.title": "machine",
|
|
9063
|
+
"gain.row.blocks": "machine",
|
|
9064
|
+
"gain.row.saves": "machine",
|
|
9065
|
+
"gain.row.false_positives": "machine",
|
|
9066
|
+
"gain.row.gate_runs": "machine",
|
|
9067
|
+
"gain.row.gate_failed": "machine",
|
|
9068
|
+
"gain.row.gate_unverified": "machine",
|
|
9069
|
+
"gain.row.gate_time": "machine",
|
|
9070
|
+
"gain.up": "machine",
|
|
9071
|
+
"gain.down": "machine",
|
|
9072
|
+
"gain.footer": "machine",
|
|
9073
|
+
"critique.command.verify": "machine",
|
|
9074
|
+
"critique.title": "machine",
|
|
9075
|
+
"critique.group.must": "machine",
|
|
9076
|
+
"critique.group.should": "machine",
|
|
9077
|
+
"critique.group.nice": "machine",
|
|
9078
|
+
"critique.group.already-there": "machine",
|
|
9079
|
+
"critique.group.not-reproduced": "machine",
|
|
9080
|
+
"critique.finding": "machine",
|
|
9081
|
+
"critique.table.command": "machine",
|
|
9082
|
+
"critique.table.outcome": "machine",
|
|
9083
|
+
"critique.outcome.head-only": "machine",
|
|
9084
|
+
"critique.outcome.already-there": "machine",
|
|
9085
|
+
"critique.outcome.not-reproduced": "machine",
|
|
9086
|
+
"critique.outcome.not-run": "machine",
|
|
9087
|
+
"critique.demoted": "machine",
|
|
9088
|
+
"critique.decision": "machine",
|
|
9089
|
+
"critique.verdict": "machine",
|
|
9090
|
+
"critique.error.invalid": "machine",
|
|
9091
|
+
"critique.error.must_fix": "machine"
|
|
8564
9092
|
},
|
|
8565
9093
|
es: {
|
|
8566
9094
|
"command.init.summary": "machine",
|
|
@@ -9216,7 +9744,38 @@ var review_default = {
|
|
|
9216
9744
|
"statusline.gate.red": "machine",
|
|
9217
9745
|
"statusline.gate.incomplete": "machine",
|
|
9218
9746
|
"statusline.gate.did_not_run": "machine",
|
|
9219
|
-
"statusline.context": "machine"
|
|
9747
|
+
"statusline.context": "machine",
|
|
9748
|
+
"command.gain.summary": "machine",
|
|
9749
|
+
"gain.title": "machine",
|
|
9750
|
+
"gain.row.blocks": "machine",
|
|
9751
|
+
"gain.row.saves": "machine",
|
|
9752
|
+
"gain.row.false_positives": "machine",
|
|
9753
|
+
"gain.row.gate_runs": "machine",
|
|
9754
|
+
"gain.row.gate_failed": "machine",
|
|
9755
|
+
"gain.row.gate_unverified": "machine",
|
|
9756
|
+
"gain.row.gate_time": "machine",
|
|
9757
|
+
"gain.up": "machine",
|
|
9758
|
+
"gain.down": "machine",
|
|
9759
|
+
"gain.footer": "machine",
|
|
9760
|
+
"critique.command.verify": "machine",
|
|
9761
|
+
"critique.title": "machine",
|
|
9762
|
+
"critique.group.must": "machine",
|
|
9763
|
+
"critique.group.should": "machine",
|
|
9764
|
+
"critique.group.nice": "machine",
|
|
9765
|
+
"critique.group.already-there": "machine",
|
|
9766
|
+
"critique.group.not-reproduced": "machine",
|
|
9767
|
+
"critique.finding": "machine",
|
|
9768
|
+
"critique.table.command": "machine",
|
|
9769
|
+
"critique.table.outcome": "machine",
|
|
9770
|
+
"critique.outcome.head-only": "machine",
|
|
9771
|
+
"critique.outcome.already-there": "machine",
|
|
9772
|
+
"critique.outcome.not-reproduced": "machine",
|
|
9773
|
+
"critique.outcome.not-run": "machine",
|
|
9774
|
+
"critique.demoted": "machine",
|
|
9775
|
+
"critique.decision": "machine",
|
|
9776
|
+
"critique.verdict": "machine",
|
|
9777
|
+
"critique.error.invalid": "machine",
|
|
9778
|
+
"critique.error.must_fix": "machine"
|
|
9220
9779
|
}
|
|
9221
9780
|
};
|
|
9222
9781
|
|
|
@@ -9373,7 +9932,7 @@ var currentReport = (runtime, paths, sessionId, taskId) => {
|
|
|
9373
9932
|
const { ctx, loaded } = ready.prepared;
|
|
9374
9933
|
return buildReport({
|
|
9375
9934
|
contract: ctx.contract,
|
|
9376
|
-
ledger:
|
|
9935
|
+
ledger: readLedger2(runtime, paths.evidence(ctx.contract.id)),
|
|
9377
9936
|
treeHash: ctx.treeHash,
|
|
9378
9937
|
base: ctx.base,
|
|
9379
9938
|
source: loaded.source,
|
|
@@ -9399,22 +9958,22 @@ var saveSnapshot = (runtime, paths, t, sessionId) => {
|
|
|
9399
9958
|
const snapshot2 = snapshotOf(t, currentReport(runtime, paths, sessionId), grants, new Date(now).toISOString());
|
|
9400
9959
|
return writeState(runtime, paths, { ...readState(runtime, paths), snapshot: snapshot2 }).ok;
|
|
9401
9960
|
};
|
|
9402
|
-
var
|
|
9961
|
+
var text5 = (snapshot2, key) => {
|
|
9403
9962
|
const value = snapshot2[key];
|
|
9404
9963
|
return typeof value === "string" ? value : "";
|
|
9405
9964
|
};
|
|
9406
9965
|
var snapshotFacts = (t, snapshot2) => {
|
|
9407
|
-
if (snapshot2 === void 0 ||
|
|
9408
|
-
const task =
|
|
9966
|
+
if (snapshot2 === void 0 || text5(snapshot2, "at") === "") return void 0;
|
|
9967
|
+
const task = text5(snapshot2, "task");
|
|
9409
9968
|
return t(task === "" || task === "implicit" ? "gate.context.state_implicit" : "gate.context.state", {
|
|
9410
|
-
at:
|
|
9969
|
+
at: text5(snapshot2, "at"),
|
|
9411
9970
|
task,
|
|
9412
|
-
goal:
|
|
9413
|
-
tree:
|
|
9414
|
-
passed:
|
|
9415
|
-
failed:
|
|
9416
|
-
unverified:
|
|
9417
|
-
grants:
|
|
9971
|
+
goal: text5(snapshot2, "goal"),
|
|
9972
|
+
tree: text5(snapshot2, "tree"),
|
|
9973
|
+
passed: text5(snapshot2, "passed"),
|
|
9974
|
+
failed: text5(snapshot2, "failed"),
|
|
9975
|
+
unverified: text5(snapshot2, "unverified"),
|
|
9976
|
+
grants: text5(snapshot2, "grants")
|
|
9418
9977
|
});
|
|
9419
9978
|
};
|
|
9420
9979
|
|
|
@@ -9506,10 +10065,10 @@ var pairs = (record3) => {
|
|
|
9506
10065
|
return names.filter((name) => typeof name === "string").map((name) => `${task}\0${name}`);
|
|
9507
10066
|
};
|
|
9508
10067
|
var callersRate = (runtime, paths) => {
|
|
9509
|
-
const
|
|
10068
|
+
const text9 = runtime.readText(paths.log);
|
|
9510
10069
|
const flagged = /* @__PURE__ */ new Set();
|
|
9511
10070
|
const released2 = /* @__PURE__ */ new Set();
|
|
9512
|
-
for (const line2 of
|
|
10071
|
+
for (const line2 of text9.ok ? text9.value.split("\n") : []) {
|
|
9513
10072
|
const parsed = line2.trim() === "" ? void 0 : parseJsonObject(line2);
|
|
9514
10073
|
if (parsed === void 0 || !parsed.ok || !isRecord(parsed.value)) continue;
|
|
9515
10074
|
const event = parsed.value["event"];
|
|
@@ -9527,14 +10086,14 @@ var callersRate = (runtime, paths) => {
|
|
|
9527
10086
|
};
|
|
9528
10087
|
|
|
9529
10088
|
// src/gate/callers/scan.ts
|
|
9530
|
-
import { join as
|
|
10089
|
+
import { join as join18 } from "node:path";
|
|
9531
10090
|
|
|
9532
10091
|
// src/gate/callers/imports.ts
|
|
9533
10092
|
import { posix as posix2 } from "node:path";
|
|
9534
10093
|
var JS_SPECIFIER = /(?:\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)["']([^"']+)["']/g;
|
|
9535
10094
|
var JS_EXTENSION = /\.(?:[cm]?[jt]sx?)$/;
|
|
9536
10095
|
var stripModule = (path) => path.replace(JS_EXTENSION, "").replace(/\/index$/, "");
|
|
9537
|
-
var jsSpecifiers = (
|
|
10096
|
+
var jsSpecifiers = (text9) => [...text9.matchAll(JS_SPECIFIER)].map((match) => match[1] ?? "");
|
|
9538
10097
|
var jsImports = (callerPath, callerText, origin) => {
|
|
9539
10098
|
const target2 = stripModule(origin);
|
|
9540
10099
|
return jsSpecifiers(callerText).some((specifier) => {
|
|
@@ -9636,9 +10195,9 @@ var languageOf = (path) => {
|
|
|
9636
10195
|
const extension = extname(path).toLowerCase();
|
|
9637
10196
|
return LANGUAGES2.find((language) => language.extensions.includes(extension));
|
|
9638
10197
|
};
|
|
9639
|
-
var definitionsIn = (language,
|
|
10198
|
+
var definitionsIn = (language, text9) => {
|
|
9640
10199
|
const names = /* @__PURE__ */ new Set();
|
|
9641
|
-
for (const line2 of
|
|
10200
|
+
for (const line2 of text9.split(/\r?\n/)) {
|
|
9642
10201
|
for (const pattern of language.definitions) {
|
|
9643
10202
|
const name = pattern.exec(line2)?.[1];
|
|
9644
10203
|
if (name !== void 0 && !KEYWORDS.has(name)) names.add(name);
|
|
@@ -9651,8 +10210,8 @@ var definesName = (language, line2, name) => language.definitions.some((pattern)
|
|
|
9651
10210
|
// src/gate/callers/scan.ts
|
|
9652
10211
|
var MAX_SYMBOLS = 200;
|
|
9653
10212
|
var current = (runtime, root, path) => {
|
|
9654
|
-
const
|
|
9655
|
-
return
|
|
10213
|
+
const text9 = runtime.readText(toAbsolutePath(join18(root, path)));
|
|
10214
|
+
return text9.ok ? text9.value : "";
|
|
9656
10215
|
};
|
|
9657
10216
|
var keyOf = (language, name) => `${language.id}\0${name}`;
|
|
9658
10217
|
var redefinedNames = (runtime, git2, changes) => {
|
|
@@ -9693,7 +10252,7 @@ var grep = (git2, names) => {
|
|
|
9693
10252
|
git2.out(["grep", "-n", "-z", "-w", "-I", "--untracked", "--no-color", ...patterns, "--", ".", EXCLUDE_FM]) ?? ""
|
|
9694
10253
|
);
|
|
9695
10254
|
};
|
|
9696
|
-
var mentions = (
|
|
10255
|
+
var mentions = (text9, name) => new RegExp(`(?<![\\w$])${name.replace(/[$?!]/g, "\\$&")}(?![\\w$])`).test(text9);
|
|
9697
10256
|
var callSites = (runtime, git2, symbols) => {
|
|
9698
10257
|
const hits = grep(git2, [...new Set(symbols.map((symbol) => symbol.name))]);
|
|
9699
10258
|
const texts2 = /* @__PURE__ */ new Map();
|
|
@@ -9768,31 +10327,31 @@ var ZERO = [
|
|
|
9768
10327
|
/^ℹ tests 0$/m,
|
|
9769
10328
|
/No tests found/
|
|
9770
10329
|
];
|
|
9771
|
-
var pyTotal = (
|
|
9772
|
-
const line2 = /^=+ (.*) in [\d.]+s.*=+$/m.exec(
|
|
10330
|
+
var pyTotal = (text9) => {
|
|
10331
|
+
const line2 = /^=+ (.*) in [\d.]+s.*=+$/m.exec(text9)?.[1];
|
|
9773
10332
|
if (line2 === void 0) return void 0;
|
|
9774
10333
|
return [...line2.matchAll(/(\d+) (passed|failed|error|errors|skipped|xfailed|xpassed)/g)].reduce(
|
|
9775
10334
|
(sum, match) => sum + Number(match[1] ?? "0"),
|
|
9776
10335
|
0
|
|
9777
10336
|
);
|
|
9778
10337
|
};
|
|
9779
|
-
var counted = (
|
|
10338
|
+
var counted = (text9) => {
|
|
9780
10339
|
for (const counter of COUNTERS) {
|
|
9781
|
-
const total = counter.total.exec(
|
|
10340
|
+
const total = counter.total.exec(text9)?.[1];
|
|
9782
10341
|
if (total === void 0) continue;
|
|
9783
|
-
const failed4 = counter.failed?.exec(
|
|
10342
|
+
const failed4 = counter.failed?.exec(text9)?.[1];
|
|
9784
10343
|
return { total: Number(total), ...failed4 === void 0 ? {} : { failed: Number(failed4) } };
|
|
9785
10344
|
}
|
|
9786
|
-
const py = pyTotal(
|
|
10345
|
+
const py = pyTotal(text9);
|
|
9787
10346
|
return py === void 0 ? {} : { total: py };
|
|
9788
10347
|
};
|
|
9789
10348
|
var parseTestSummary = (output) => {
|
|
9790
|
-
const
|
|
10349
|
+
const text9 = output.replace(ANSI2, "");
|
|
9791
10350
|
const failures = [
|
|
9792
|
-
...new Set(FAILURE_LINES.flatMap((pattern) => [...
|
|
10351
|
+
...new Set(FAILURE_LINES.flatMap((pattern) => [...text9.matchAll(pattern)].map((match) => (match[1] ?? "").trim())))
|
|
9793
10352
|
].filter((name) => name !== "" && !/^failing tests:?$/i.test(name));
|
|
9794
|
-
if (ZERO.some((pattern) => pattern.test(
|
|
9795
|
-
return { ...counted(
|
|
10353
|
+
if (ZERO.some((pattern) => pattern.test(text9))) return { total: 0, failures };
|
|
10354
|
+
return { ...counted(text9), failures };
|
|
9796
10355
|
};
|
|
9797
10356
|
|
|
9798
10357
|
// src/gate/checks/command.ts
|
|
@@ -9803,8 +10362,8 @@ var REWRITERS = /^\s*(rtk)\b/;
|
|
|
9803
10362
|
var TRIVIAL = /* @__PURE__ */ new Set(["echo", "true", ":", "printf", "exit", "sleep", "cat", "yes", "pwd", "ls", "date", "env"]);
|
|
9804
10363
|
var TAIL = 160;
|
|
9805
10364
|
var MAX_FAILURES2 = 5;
|
|
9806
|
-
var tail = (
|
|
9807
|
-
const lines3 =
|
|
10365
|
+
var tail = (text9) => {
|
|
10366
|
+
const lines3 = text9.split(/\r?\n/).filter((line2) => line2.trim() !== "");
|
|
9808
10367
|
const last = lines3[lines3.length - 1]?.trim() ?? "";
|
|
9809
10368
|
return last.length > TAIL ? `${last.slice(0, TAIL)}…` : last;
|
|
9810
10369
|
};
|
|
@@ -9924,10 +10483,10 @@ var gitCheck = (check2, ctx) => {
|
|
|
9924
10483
|
};
|
|
9925
10484
|
|
|
9926
10485
|
// src/gate/checks/look.ts
|
|
9927
|
-
import { join as
|
|
10486
|
+
import { join as join21 } from "node:path";
|
|
9928
10487
|
|
|
9929
10488
|
// src/look/config.ts
|
|
9930
|
-
import { join as
|
|
10489
|
+
import { join as join19 } from "node:path";
|
|
9931
10490
|
|
|
9932
10491
|
// src/look/types.ts
|
|
9933
10492
|
var LOOK_RULES = [
|
|
@@ -9962,7 +10521,7 @@ var DEFAULT_SCREENS = [
|
|
|
9962
10521
|
"**/*.sass",
|
|
9963
10522
|
"**/*.less"
|
|
9964
10523
|
];
|
|
9965
|
-
var
|
|
10524
|
+
var text6 = (value) => typeof value === "string" && value !== "" ? value : void 0;
|
|
9966
10525
|
var number = (value) => {
|
|
9967
10526
|
const parsed = typeof value === "string" ? Number(value) : Number.NaN;
|
|
9968
10527
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
@@ -9979,8 +10538,8 @@ var parseLookConfig = (source) => {
|
|
|
9979
10538
|
const top = configMap(tree);
|
|
9980
10539
|
return {
|
|
9981
10540
|
port: number(top["port"]),
|
|
9982
|
-
start:
|
|
9983
|
-
host:
|
|
10541
|
+
start: text6(top["start"]),
|
|
10542
|
+
host: text6(top["host"]) ?? "127.0.0.1",
|
|
9984
10543
|
routes: configList(tree, "routes").length === 0 ? ["/"] : configList(tree, "routes"),
|
|
9985
10544
|
mocks: configList(tree, "mocks"),
|
|
9986
10545
|
screens: screens === void 0 ? DEFAULT_SCREENS : configList(tree, "screens"),
|
|
@@ -9989,18 +10548,18 @@ var parseLookConfig = (source) => {
|
|
|
9989
10548
|
};
|
|
9990
10549
|
};
|
|
9991
10550
|
var loadLookConfig = (runtime, root) => {
|
|
9992
|
-
const source = runtime.readText(toAbsolutePath(
|
|
10551
|
+
const source = runtime.readText(toAbsolutePath(join19(root, LOOK_CONFIG)));
|
|
9993
10552
|
return source.ok ? parseLookConfig(source.value) : void 0;
|
|
9994
10553
|
};
|
|
9995
10554
|
var withMock = (route, mock) => `${route}${route.includes("?") ? "&" : "?"}mock=${encodeURIComponent(mock)}`;
|
|
9996
10555
|
var lookRoutes = (config) => config.routes.flatMap((route) => [route, ...config.mocks.map((mock) => withMock(route, mock))]);
|
|
9997
10556
|
|
|
9998
10557
|
// src/look/paths.ts
|
|
9999
|
-
import { join as
|
|
10558
|
+
import { join as join20, relative as relative5 } from "node:path";
|
|
10000
10559
|
var LOOK_DIR = "look";
|
|
10001
10560
|
var SUMMARY_FILE = "summary.json";
|
|
10002
|
-
var lookDir = (paths, treeHash2) => toAbsolutePath(
|
|
10003
|
-
var summaryPath = (paths, treeHash2) => toAbsolutePath(
|
|
10561
|
+
var lookDir = (paths, treeHash2) => toAbsolutePath(join20(paths.dir, LOOK_DIR, treeHash2));
|
|
10562
|
+
var summaryPath = (paths, treeHash2) => toAbsolutePath(join20(lookDir(paths, treeHash2), SUMMARY_FILE));
|
|
10004
10563
|
var projectRelative = (paths, absolute) => toPosixPath(relative5(paths.root, absolute));
|
|
10005
10564
|
|
|
10006
10565
|
// src/gate/checks/look.ts
|
|
@@ -10018,8 +10577,8 @@ var findingNote = (value) => {
|
|
|
10018
10577
|
];
|
|
10019
10578
|
};
|
|
10020
10579
|
var readSummary = (ctx) => {
|
|
10021
|
-
const
|
|
10022
|
-
const parsed =
|
|
10580
|
+
const text9 = ctx.runtime.readText(summaryPath(ctx.paths, ctx.treeHash));
|
|
10581
|
+
const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
|
|
10023
10582
|
if (!parsed?.ok) return void 0;
|
|
10024
10583
|
const { at, captures, findings, ignoredAllows } = parsed.value;
|
|
10025
10584
|
if (typeof at !== "string" || !Array.isArray(captures) || !Array.isArray(findings)) return void 0;
|
|
@@ -10035,7 +10594,7 @@ var readSummary = (ctx) => {
|
|
|
10035
10594
|
var lastScreenEdit = (ctx) => {
|
|
10036
10595
|
const screens = loadLookConfig(ctx.runtime, ctx.paths.root)?.screens ?? DEFAULT_SCREENS;
|
|
10037
10596
|
return ctx.changes.filter((change) => change.status !== "D" && matchesAny(change.path, screens)).reduce((latest, change) => {
|
|
10038
|
-
const stat = ctx.runtime.stat(toAbsolutePath(
|
|
10597
|
+
const stat = ctx.runtime.stat(toAbsolutePath(join21(ctx.paths.root, change.path)));
|
|
10039
10598
|
return Math.max(latest, stat?.mtimeMs ?? 0);
|
|
10040
10599
|
}, 0);
|
|
10041
10600
|
};
|
|
@@ -10082,14 +10641,14 @@ var manualCheck = (check2, ctx) => {
|
|
|
10082
10641
|
};
|
|
10083
10642
|
|
|
10084
10643
|
// src/gate/checks/placeholders.ts
|
|
10085
|
-
import { join as
|
|
10644
|
+
import { join as join22 } from "node:path";
|
|
10086
10645
|
var MAX_LISTED3 = 10;
|
|
10087
10646
|
var SKIP_FILES = /\.(md|mdx|txt|rst|lock|snap|svg|png|jpg|gif|json)$|(^|\/)(CHANGELOG|LICENSE)/i;
|
|
10088
10647
|
var PLACEHOLDER = /\b(TODO|FIXME|XXX)\b|not implemented|NotImplementedError|unimplemented!|todo!\(|lorem ipsum/i;
|
|
10089
10648
|
var findPlaceholders = (lines3) => lines3.filter((line2) => !isTestFile(line2.path) && !SKIP_FILES.test(line2.path) && PLACEHOLDER.test(line2.text));
|
|
10090
10649
|
var untrackedAdded = (ctx) => ctx.changes.filter((change) => change.status === "A" && !SKIP_FILES.test(change.path)).flatMap((change) => {
|
|
10091
|
-
const
|
|
10092
|
-
return
|
|
10650
|
+
const text9 = ctx.runtime.readText(toAbsolutePath(join22(ctx.paths.root, change.path)));
|
|
10651
|
+
return text9.ok ? text9.value.split("\n").map((line2, index) => ({ path: change.path, line: index + 1, text: line2 })) : [];
|
|
10093
10652
|
});
|
|
10094
10653
|
var placeholdersCheck = (_check, ctx) => {
|
|
10095
10654
|
const tracked = addedLines(ctx.git, ctx.base);
|
|
@@ -10148,9 +10707,9 @@ var prOutcome = (check2, ctx) => {
|
|
|
10148
10707
|
|
|
10149
10708
|
// src/prose/preset.ts
|
|
10150
10709
|
var PRESET_LINE = /^preset:\s*([A-Za-z0-9_-]+)\s*$/m;
|
|
10151
|
-
var presetFromConfig = (
|
|
10710
|
+
var presetFromConfig = (text9) => parsePreset(PRESET_LINE.exec(text9)?.[1]) ?? "neutral";
|
|
10152
10711
|
var projectPreset = (runtime, root) => {
|
|
10153
|
-
const config = runtime.readText(toAbsolutePath(
|
|
10712
|
+
const config = runtime.readText(toAbsolutePath(CONFIG_FILE2, root));
|
|
10154
10713
|
return config.ok ? presetFromConfig(config.value) : "neutral";
|
|
10155
10714
|
};
|
|
10156
10715
|
|
|
@@ -10325,16 +10884,16 @@ var ANY_MARKER = /<!--\s*finitemonkey:prose-allow(?:-file)?\s*-->/g;
|
|
|
10325
10884
|
var INLINE_CODE = /`[^`]*`/g;
|
|
10326
10885
|
var FENCE2 = /^\s*(```|~~~)/;
|
|
10327
10886
|
var outsideCode = (line2) => line2.replace(INLINE_CODE, "");
|
|
10328
|
-
var unfencedLines = (
|
|
10887
|
+
var unfencedLines = (text9) => {
|
|
10329
10888
|
let fenced = false;
|
|
10330
|
-
return
|
|
10889
|
+
return text9.split("\n").filter((line2) => {
|
|
10331
10890
|
if (FENCE2.test(line2)) fenced = !fenced;
|
|
10332
10891
|
return !fenced && !FENCE2.test(line2);
|
|
10333
10892
|
});
|
|
10334
10893
|
};
|
|
10335
|
-
var allowsWholeFile = (
|
|
10894
|
+
var allowsWholeFile = (text9) => unfencedLines(text9).some((line2) => FILE_MARKER.test(outsideCode(line2)));
|
|
10336
10895
|
var allowsLine = (line2) => LINE_MARKER.test(outsideCode(line2));
|
|
10337
|
-
var markerCount = (
|
|
10896
|
+
var markerCount = (text9) => text9.match(ANY_MARKER)?.length ?? 0;
|
|
10338
10897
|
var isMarkerOnlyLine = (line2) => allowsLine(line2) && outsideCode(line2).replace(LINE_MARKER, "").trim() === "";
|
|
10339
10898
|
|
|
10340
10899
|
// src/prose/extract.ts
|
|
@@ -10354,7 +10913,7 @@ var MASKS = [
|
|
|
10354
10913
|
[/<[^>]+>/g, ""],
|
|
10355
10914
|
[/\*\*|__/g, ""]
|
|
10356
10915
|
];
|
|
10357
|
-
var maskInline = (line2) => MASKS.reduce((
|
|
10916
|
+
var maskInline = (line2) => MASKS.reduce((text9, [pattern, replacement]) => text9.replace(pattern, replacement), line2);
|
|
10358
10917
|
var BlockReader = class {
|
|
10359
10918
|
blocks = [];
|
|
10360
10919
|
current;
|
|
@@ -10445,8 +11004,8 @@ var splitBlock = (block4) => {
|
|
|
10445
11004
|
return joined.split(SENTENCE_END).flatMap((sentence) => {
|
|
10446
11005
|
const at = joined.indexOf(sentence, offset);
|
|
10447
11006
|
offset = at + sentence.length;
|
|
10448
|
-
const
|
|
10449
|
-
return
|
|
11007
|
+
const text9 = sentence.trim();
|
|
11008
|
+
return text9 === "" ? [] : [{ text: text9, line: lineAt(at) }];
|
|
10450
11009
|
});
|
|
10451
11010
|
};
|
|
10452
11011
|
var unitsOf = (blocks) => {
|
|
@@ -10467,9 +11026,9 @@ var unitsOf = (blocks) => {
|
|
|
10467
11026
|
}));
|
|
10468
11027
|
});
|
|
10469
11028
|
};
|
|
10470
|
-
var extractProse = (
|
|
10471
|
-
if (allowsWholeFile(
|
|
10472
|
-
const lines3 =
|
|
11029
|
+
var extractProse = (text9) => {
|
|
11030
|
+
if (allowsWholeFile(text9)) return { units: [], allowed: true };
|
|
11031
|
+
const lines3 = text9.split("\n").map(withoutCarriageReturn);
|
|
10473
11032
|
return { units: unitsOf(readBlocks(lines3)), allowed: false };
|
|
10474
11033
|
};
|
|
10475
11034
|
|
|
@@ -10594,7 +11153,7 @@ var CONNECTIVES = new Set(
|
|
|
10594
11153
|
" "
|
|
10595
11154
|
)
|
|
10596
11155
|
);
|
|
10597
|
-
var wordsOf = (
|
|
11156
|
+
var wordsOf = (text9) => text9.split(/\s+/).filter((word2) => WORD.test(word2));
|
|
10598
11157
|
var hit = (rule, units, index, weight) => {
|
|
10599
11158
|
const unit = units[index];
|
|
10600
11159
|
return { rule, unit: index, line: unit?.line ?? 0, weight, excerpt: unit?.text ?? "" };
|
|
@@ -10722,8 +11281,8 @@ var mergeSpans = (hits, windows) => {
|
|
|
10722
11281
|
return ranges.map(([from, to]) => spanOf2(hits, from, to)).filter((span) => span.hits.length > 0);
|
|
10723
11282
|
};
|
|
10724
11283
|
var byPosition = (left, right) => left.unit - right.unit || left.line - right.line;
|
|
10725
|
-
var scoreProse = (
|
|
10726
|
-
const { units, allowed: allowed2 } = extractProse(
|
|
11284
|
+
var scoreProse = (text9, options = {}) => {
|
|
11285
|
+
const { units, allowed: allowed2 } = extractProse(text9);
|
|
10727
11286
|
const size = options.window ?? DENSITY_WINDOW;
|
|
10728
11287
|
const threshold2 = options.threshold ?? DENSITY_THRESHOLD;
|
|
10729
11288
|
const catalog = options.context === "record" ? [...phraseRules(), ...recordRules()] : phraseRules();
|
|
@@ -10755,9 +11314,9 @@ var changedMarkdown = (runtime, root) => {
|
|
|
10755
11314
|
var scoreFiles = (runtime, root, files, forced) => {
|
|
10756
11315
|
const reports = [];
|
|
10757
11316
|
for (const file of files) {
|
|
10758
|
-
const
|
|
10759
|
-
if (!
|
|
10760
|
-
reports.push({ file: toPosixPath(file), report: scoreProse(
|
|
11317
|
+
const text9 = runtime.readText(toAbsolutePath(file, root));
|
|
11318
|
+
if (!text9.ok) return reject(`${file}: ${text9.error}`);
|
|
11319
|
+
reports.push({ file: toPosixPath(file), report: scoreProse(text9.value, { context: forced ?? contextOf(file) }) });
|
|
10761
11320
|
}
|
|
10762
11321
|
return accept(reports);
|
|
10763
11322
|
};
|
|
@@ -10856,8 +11415,8 @@ var parseAppCheck = (stdout) => {
|
|
|
10856
11415
|
var PROBE_MARGIN_MS = 15e3;
|
|
10857
11416
|
var MIN_REMAINING_MS2 = 5e3;
|
|
10858
11417
|
var TAIL2 = 160;
|
|
10859
|
-
var lastLine = (
|
|
10860
|
-
const rows =
|
|
11418
|
+
var lastLine = (text9) => {
|
|
11419
|
+
const rows = text9.split(/\r?\n/).filter((row) => row.trim() !== "" && !/^\s*at\s|^Node\.js v\d/.test(row));
|
|
10861
11420
|
const line2 = (rows.findLast((row) => /error/i.test(row)) ?? rows.at(-1))?.trim() ?? "";
|
|
10862
11421
|
return line2.length > TAIL2 ? `${line2.slice(0, TAIL2)}…` : line2;
|
|
10863
11422
|
};
|
|
@@ -10935,14 +11494,14 @@ ${run4.stderr}`)) })]);
|
|
|
10935
11494
|
};
|
|
10936
11495
|
|
|
10937
11496
|
// src/secrets/baseline.ts
|
|
10938
|
-
import { join as
|
|
11497
|
+
import { join as join23 } from "node:path";
|
|
10939
11498
|
var BASELINE_FILE2 = ".finitemonkey/findings/baseline.json";
|
|
10940
11499
|
var BASELINE_SCHEMA = "finitemonkey/secrets-baseline@1";
|
|
10941
11500
|
var BASELINE_STATES = ["aceito", "falso-positivo"];
|
|
10942
11501
|
var EXCEPTION_MAX_DAYS = 90;
|
|
10943
11502
|
var EMPTY_BASELINE = { schema: BASELINE_SCHEMA, entries: [] };
|
|
10944
|
-
var baselinePath = (root) => toAbsolutePath(
|
|
10945
|
-
var
|
|
11503
|
+
var baselinePath = (root) => toAbsolutePath(join23(root, BASELINE_FILE2));
|
|
11504
|
+
var text7 = (record3, key) => {
|
|
10946
11505
|
const value = record3[key];
|
|
10947
11506
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
10948
11507
|
};
|
|
@@ -10950,7 +11509,7 @@ var stateOf = (value) => BASELINE_STATES.find((state) => state === value);
|
|
|
10950
11509
|
var parseEntry2 = (value) => {
|
|
10951
11510
|
if (!isRecord(value)) return void 0;
|
|
10952
11511
|
const fields = ["id", "rule", "file", "fingerprint", "owner", "reason", "acceptedAt", "expiresAt"];
|
|
10953
|
-
const [id, rule, file, fingerprint, owner, reason, acceptedAt, expiresAt] = fields.map((key) =>
|
|
11512
|
+
const [id, rule, file, fingerprint, owner, reason, acceptedAt, expiresAt] = fields.map((key) => text7(value, key));
|
|
10954
11513
|
const state = stateOf(value["state"]);
|
|
10955
11514
|
const line2 = value["line"];
|
|
10956
11515
|
if (id === void 0 || rule === void 0 || file === void 0 || fingerprint === void 0) return void 0;
|
|
@@ -11001,14 +11560,14 @@ var withoutFixed = (baseline, fixed) => {
|
|
|
11001
11560
|
var acceptedFingerprints = (baseline, now) => new Set(baseline.entries.filter((entry2) => entry2.expiresAt >= now).map((entry2) => entry2.fingerprint));
|
|
11002
11561
|
|
|
11003
11562
|
// src/secrets/incidents.ts
|
|
11004
|
-
import { join as
|
|
11563
|
+
import { join as join24 } from "node:path";
|
|
11005
11564
|
var INCIDENTS_DIR = ".finitemonkey/incidents";
|
|
11006
11565
|
var INCIDENT_STATES = ["aberto", "exposicao-local", "rotacionado", "fechado"];
|
|
11007
11566
|
var INCIDENT_SOURCES = ["tool-output", "prompt", "history", "scan"];
|
|
11008
11567
|
var ID = /^inc-[0-9a-f]{12}$/;
|
|
11009
11568
|
var incidentId = (rule, fingerprint) => `inc-${sha256Hex(`${rule}\0${fingerprint}`).slice(0, 12)}`;
|
|
11010
|
-
var incidentsDir = (root) => toAbsolutePath(
|
|
11011
|
-
var incidentFile = (root, id) => toAbsolutePath(
|
|
11569
|
+
var incidentsDir = (root) => toAbsolutePath(join24(root, INCIDENTS_DIR));
|
|
11570
|
+
var incidentFile = (root, id) => toAbsolutePath(join24(incidentsDir(root), `${id}.json`));
|
|
11012
11571
|
var pick3 = (options, value) => options.find((option) => option === value);
|
|
11013
11572
|
var str2 = (record3, key) => {
|
|
11014
11573
|
const value = record3[key];
|
|
@@ -11063,7 +11622,7 @@ var listIncidents = (runtime, root) => {
|
|
|
11063
11622
|
return names.value.filter((name) => name.endsWith(".json")).map((name) => readIncident(runtime, root, name.slice(0, -".json".length))).flatMap((parsed) => parsed.ok ? [parsed.value] : []).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
11064
11623
|
};
|
|
11065
11624
|
var saveIncident = (runtime, root, incident) => {
|
|
11066
|
-
const ignore = runtime.writeText(toAbsolutePath(
|
|
11625
|
+
const ignore = runtime.writeText(toAbsolutePath(join24(incidentsDir(root), ".gitignore")), "*\n");
|
|
11067
11626
|
if (!ignore.ok) return ignore;
|
|
11068
11627
|
return runtime.writeText(incidentFile(root, incident.id), `${JSON.stringify(incident, null, 2)}
|
|
11069
11628
|
`);
|
|
@@ -11110,7 +11669,7 @@ var moveIncident = (incident, to, now, by) => ({
|
|
|
11110
11669
|
var isRotated = (incident) => incident.state === "rotacionado" || incident.state === "fechado";
|
|
11111
11670
|
|
|
11112
11671
|
// src/secrets/scan.ts
|
|
11113
|
-
import { join as
|
|
11672
|
+
import { join as join25, relative as relative6 } from "node:path";
|
|
11114
11673
|
|
|
11115
11674
|
// src/secrets/located.ts
|
|
11116
11675
|
var findingId = (ruleId, file, fingerprint) => `fm-${sha256Hex(`${ruleId}\0${file}\0${fingerprint}`).slice(0, 16)}`;
|
|
@@ -11200,7 +11759,7 @@ var diffScanner = () => {
|
|
|
11200
11759
|
var MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
11201
11760
|
var SKIPPED = /(?:^|\/)(?:\.git|node_modules|\.finitemonkey)\//;
|
|
11202
11761
|
var DIFF_FLAGS = ["-U0", "--no-color", "--no-ext-diff", "--no-renames"];
|
|
11203
|
-
var binary = (
|
|
11762
|
+
var binary = (text9) => text9.slice(0, 8e3).includes("\0");
|
|
11204
11763
|
var listed2 = (runtime, root, target2) => {
|
|
11205
11764
|
const git2 = runtime.git(["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", target2], root);
|
|
11206
11765
|
if (git2.ok)
|
|
@@ -11210,12 +11769,12 @@ var listed2 = (runtime, root, target2) => {
|
|
|
11210
11769
|
const base = toAbsolutePath(target2, root);
|
|
11211
11770
|
const walked = runtime.walkFiles(base);
|
|
11212
11771
|
if (!walked.ok) return walked;
|
|
11213
|
-
return accept(walked.value.map((name) => toPosixPath(relative6(root,
|
|
11772
|
+
return accept(walked.value.map((name) => toPosixPath(relative6(root, join25(base, toPosixPath(name))))));
|
|
11214
11773
|
};
|
|
11215
11774
|
var scanFile = (runtime, root, file) => {
|
|
11216
|
-
const
|
|
11217
|
-
if (!
|
|
11218
|
-
return scanText(
|
|
11775
|
+
const text9 = runtime.readText(toAbsolutePath(file, root));
|
|
11776
|
+
if (!text9.ok || text9.value.length > MAX_FILE_BYTES || binary(text9.value)) return [];
|
|
11777
|
+
return scanText(text9.value, { path: file }).map((finding4) => locate(finding4, file, finding4.line));
|
|
11219
11778
|
};
|
|
11220
11779
|
var scanPath = (runtime, root, target2) => {
|
|
11221
11780
|
const files = listed2(runtime, root, target2);
|
|
@@ -11280,7 +11839,7 @@ var secretsCheck = (_check, ctx) => {
|
|
|
11280
11839
|
};
|
|
11281
11840
|
|
|
11282
11841
|
// src/gate/checks/tests.ts
|
|
11283
|
-
import { join as
|
|
11842
|
+
import { join as join26 } from "node:path";
|
|
11284
11843
|
|
|
11285
11844
|
// src/guards/tests/tree.ts
|
|
11286
11845
|
var outOfScope = (input, change) => input.testsProtected && input.scopePaths.length > 0 && change.status !== "A" && isTestFile(change.path) && !matchesAny(change.path, input.scopePaths) ? [{ kind: "out_of_scope", path: change.path }] : [];
|
|
@@ -11312,8 +11871,8 @@ var treeViolations = (ctx) => {
|
|
|
11312
11871
|
changes: relevant,
|
|
11313
11872
|
before: (path) => fileAt(ctx.git, ctx.base, path),
|
|
11314
11873
|
after: (path) => {
|
|
11315
|
-
const
|
|
11316
|
-
return
|
|
11874
|
+
const text9 = ctx.runtime.readText(toAbsolutePath(join26(ctx.paths.root, path)));
|
|
11875
|
+
return text9.ok ? text9.value : void 0;
|
|
11317
11876
|
},
|
|
11318
11877
|
acceptedConfigs: ctx.trust.configs,
|
|
11319
11878
|
scopePaths: ctx.contract.scope.paths,
|
|
@@ -11333,7 +11892,7 @@ var testsCheck = (check2, ctx) => {
|
|
|
11333
11892
|
};
|
|
11334
11893
|
|
|
11335
11894
|
// src/git/tree.ts
|
|
11336
|
-
import { join as
|
|
11895
|
+
import { join as join27 } from "node:path";
|
|
11337
11896
|
|
|
11338
11897
|
// src/secrets/guards/paths.ts
|
|
11339
11898
|
var SAFE_ENV = /^\.env\.(?:example|sample|template|dist|defaults|schema)$/i;
|
|
@@ -11372,7 +11931,7 @@ var dropSensitive = (runtime, repo, env, options) => {
|
|
|
11372
11931
|
return removed.ok ? accept(void 0) : removed;
|
|
11373
11932
|
};
|
|
11374
11933
|
var withTempIndex = (runtime, repo, options, work) => {
|
|
11375
|
-
const path = toAbsolutePath(
|
|
11934
|
+
const path = toAbsolutePath(join27(repo.gitDir, `finitemonkey-${options.stamp}.index`));
|
|
11376
11935
|
const env = { GIT_INDEX_FILE: path, GIT_OPTIONAL_LOCKS: "0" };
|
|
11377
11936
|
try {
|
|
11378
11937
|
return work(env);
|
|
@@ -11477,10 +12036,10 @@ var bodyLine = (draft, line2) => {
|
|
|
11477
12036
|
else if (line2.startsWith("+")) draft.current.added.push(line2.slice(1));
|
|
11478
12037
|
};
|
|
11479
12038
|
var DIFF_GIT = /^diff --git a\/(.+) b\/(.+)$/;
|
|
11480
|
-
var parseUnifiedDiff = (
|
|
12039
|
+
var parseUnifiedDiff = (text9) => {
|
|
11481
12040
|
const out = [];
|
|
11482
12041
|
let draft;
|
|
11483
|
-
for (const raw of
|
|
12042
|
+
for (const raw of text9.split("\n")) {
|
|
11484
12043
|
const line2 = withoutCarriageReturn(raw);
|
|
11485
12044
|
const start = DIFF_GIT.exec(line2);
|
|
11486
12045
|
if (start !== null) {
|
|
@@ -11579,59 +12138,15 @@ var mergeCommitsSince = (runtime, repo, since) => {
|
|
|
11579
12138
|
};
|
|
11580
12139
|
var mergeInProgress = (runtime, repo) => revParse(runtime, repo, "MERGE_HEAD") !== void 0;
|
|
11581
12140
|
|
|
11582
|
-
// src/git/facts.ts
|
|
11583
|
-
var memo2 = (compute) => {
|
|
11584
|
-
let cached3;
|
|
11585
|
-
return () => {
|
|
11586
|
-
cached3 ??= { value: compute() };
|
|
11587
|
-
return cached3.value;
|
|
11588
|
-
};
|
|
11589
|
-
};
|
|
11590
|
-
var memoBy = (compute) => {
|
|
11591
|
-
const cache = /* @__PURE__ */ new Map();
|
|
11592
|
-
return (key) => {
|
|
11593
|
-
const hit3 = cache.get(key);
|
|
11594
|
-
if (hit3 !== void 0) return hit3.value;
|
|
11595
|
-
const value = compute(key);
|
|
11596
|
-
cache.set(key, { value });
|
|
11597
|
-
return value;
|
|
11598
|
-
};
|
|
11599
|
-
};
|
|
11600
|
-
var STATE_PREFIX = ".finitemonkey/";
|
|
11601
|
-
var PORCELAIN_FIELDS = { ordinary: 8, renamed: 9, unmerged: 10 };
|
|
11602
|
-
var entryOf2 = (kind, xy, path) => ({
|
|
11603
|
-
path,
|
|
11604
|
-
kind,
|
|
11605
|
-
staged: kind !== "untracked" && !xy.startsWith("."),
|
|
11606
|
-
unstaged: kind === "untracked" || xy.charAt(1) !== "."
|
|
11607
|
-
});
|
|
11608
|
-
var afterFields = (record3, count4) => record3.split(" ").slice(count4).join(" ");
|
|
11609
|
-
var parsePorcelainV2 = (stdout) => {
|
|
11610
|
-
const records = stdout.split("\0");
|
|
11611
|
-
const out = [];
|
|
11612
|
-
for (let index = 0; index < records.length; index += 1) {
|
|
11613
|
-
const record3 = records[index] ?? "";
|
|
11614
|
-
const xy = record3.split(" ")[1] ?? "..";
|
|
11615
|
-
if (record3.startsWith("1 ")) out.push(entryOf2("changed", xy, afterFields(record3, PORCELAIN_FIELDS.ordinary)));
|
|
11616
|
-
else if (record3.startsWith("2 ")) {
|
|
11617
|
-
out.push(entryOf2("changed", xy, afterFields(record3, PORCELAIN_FIELDS.renamed)));
|
|
11618
|
-
index += 1;
|
|
11619
|
-
} else if (record3.startsWith("u ")) {
|
|
11620
|
-
out.push(entryOf2("unmerged", xy, afterFields(record3, PORCELAIN_FIELDS.unmerged)));
|
|
11621
|
-
} else if (record3.startsWith("? ")) out.push(entryOf2("untracked", "??", record3.slice(2)));
|
|
11622
|
-
}
|
|
11623
|
-
return out.filter((entry2) => entry2.path !== "" && !entry2.path.startsWith(STATE_PREFIX));
|
|
11624
|
-
};
|
|
11625
|
-
|
|
11626
12141
|
// src/git/merge/dropped.ts
|
|
11627
|
-
import { join as
|
|
12142
|
+
import { join as join28 } from "node:path";
|
|
11628
12143
|
|
|
11629
12144
|
// src/git/merge/lines.ts
|
|
11630
12145
|
var normalizeLine = (line2) => line2.replace(/\r$/, "").trim().replace(/\s*,$/, "");
|
|
11631
12146
|
var WEAK = /^[\p{P}\p{S}\s]+$/u;
|
|
11632
12147
|
var isWeak = (line2) => WEAK.test(line2);
|
|
11633
12148
|
var meaningful = (lines3) => lines3.map(normalizeLine).filter((line2) => line2 !== "");
|
|
11634
|
-
var contentLines = (
|
|
12149
|
+
var contentLines = (text9) => text9 === void 0 ? [] : meaningful(text9.split("\n"));
|
|
11635
12150
|
var countLines = (lines3) => {
|
|
11636
12151
|
const counts = /* @__PURE__ */ new Map();
|
|
11637
12152
|
for (const line2 of lines3) counts.set(line2, (counts.get(line2) ?? 0) + 1);
|
|
@@ -11680,16 +12195,16 @@ var itemOf = (value) => {
|
|
|
11680
12195
|
approvedBy: optionalString(value, "approved_by")
|
|
11681
12196
|
};
|
|
11682
12197
|
};
|
|
11683
|
-
var invalidFinding = (file,
|
|
12198
|
+
var invalidFinding = (file, text9) => ({
|
|
11684
12199
|
kind: "invalid-dropped",
|
|
11685
12200
|
file,
|
|
11686
12201
|
side: void 0,
|
|
11687
12202
|
line: void 0,
|
|
11688
|
-
text:
|
|
12203
|
+
text: text9,
|
|
11689
12204
|
weak: false
|
|
11690
12205
|
});
|
|
11691
|
-
var parseDropped = (
|
|
11692
|
-
const parsed = parseJson(
|
|
12206
|
+
var parseDropped = (text9) => {
|
|
12207
|
+
const parsed = parseJson(text9);
|
|
11693
12208
|
if (!parsed.ok) return parsed;
|
|
11694
12209
|
const list4 = isRecord(parsed.value) ? parsed.value["dropped"] : parsed.value;
|
|
11695
12210
|
if (!Array.isArray(list4)) return reject("expected a list of dropped items");
|
|
@@ -11704,17 +12219,17 @@ var parseDropped = (text8) => {
|
|
|
11704
12219
|
return accept({ items, invalid });
|
|
11705
12220
|
};
|
|
11706
12221
|
var loadDropped = (runtime, root, file) => {
|
|
11707
|
-
const path = toAbsolutePath(file ??
|
|
11708
|
-
const
|
|
11709
|
-
if (!
|
|
11710
|
-
return parseDropped(
|
|
12222
|
+
const path = toAbsolutePath(file ?? join28(root, DROPPED_FILE), root);
|
|
12223
|
+
const text9 = runtime.readText(path);
|
|
12224
|
+
if (!text9.ok) return file === void 0 ? accept({ items: [], invalid: [] }) : reject(`${file}: ${text9.error}`);
|
|
12225
|
+
return parseDropped(text9.value);
|
|
11711
12226
|
};
|
|
11712
12227
|
var isDropped = (finding4, items) => items.some(
|
|
11713
12228
|
(item2) => item2.file === finding4.file && (item2.side === void 0 || finding4.side === void 0 || item2.side === finding4.side) && (item2.text === void 0 || normalizeLine(item2.text) === finding4.text)
|
|
11714
12229
|
);
|
|
11715
12230
|
|
|
11716
12231
|
// src/git/merge/generated.ts
|
|
11717
|
-
import { join as
|
|
12232
|
+
import { join as join29 } from "node:path";
|
|
11718
12233
|
var LOCKFILES3 = /* @__PURE__ */ new Set([
|
|
11719
12234
|
"package-lock.json",
|
|
11720
12235
|
"npm-shrinkwrap.json",
|
|
@@ -11732,11 +12247,11 @@ var LOCKFILES3 = /* @__PURE__ */ new Set([
|
|
|
11732
12247
|
]);
|
|
11733
12248
|
var GENERATOR_TIMEOUT_MS = 3e5;
|
|
11734
12249
|
var DOUBLE_STAR = "";
|
|
11735
|
-
var
|
|
12250
|
+
var globRegex2 = (pattern) => new RegExp(
|
|
11736
12251
|
`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").split("**").join(DOUBLE_STAR).replace(/\*/g, "[^/]*").split(DOUBLE_STAR).join(".*")}$`
|
|
11737
12252
|
);
|
|
11738
12253
|
var generatorFor = (path, generators) => Object.entries(generators).find(
|
|
11739
|
-
([pattern]) =>
|
|
12254
|
+
([pattern]) => globRegex2(pattern).test(path) || globRegex2(pattern).test(path.split("/").pop() ?? "")
|
|
11740
12255
|
)?.[1];
|
|
11741
12256
|
var generatedPaths = (runtime, repo, paths, generators) => {
|
|
11742
12257
|
const out = new Set(
|
|
@@ -11777,7 +12292,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
|
|
|
11777
12292
|
if (declared.length === 0) return accept([]);
|
|
11778
12293
|
const commit2 = resultCommit(runtime, repo, inputs);
|
|
11779
12294
|
if (!commit2.ok) return commit2;
|
|
11780
|
-
const dir =
|
|
12295
|
+
const dir = join29(repo.gitDir, `finitemonkey-generate-${runtime.now().replace(/[:.]/g, "-")}`);
|
|
11781
12296
|
if (!runtime.git(["worktree", "add", "--detach", dir, commit2.value], repo.root).ok)
|
|
11782
12297
|
return reject("could not check out the merge result");
|
|
11783
12298
|
try {
|
|
@@ -11787,7 +12302,7 @@ var verifyGenerated = (runtime, repo, inputs, paths, generators) => {
|
|
|
11787
12302
|
}
|
|
11788
12303
|
return accept(
|
|
11789
12304
|
declared.flatMap(({ path }) => {
|
|
11790
|
-
const fresh3 = runtime.readText(toAbsolutePath(
|
|
12305
|
+
const fresh3 = runtime.readText(toAbsolutePath(join29(dir, path)));
|
|
11791
12306
|
const merged2 = blobText(runtime, repo, commit2.value, path);
|
|
11792
12307
|
if (fresh3.ok && fresh3.value === merged2) return [];
|
|
11793
12308
|
return [{ kind: "generated-differs", file: path, side: void 0, line: void 0, text: path, weak: false }];
|
|
@@ -11830,13 +12345,13 @@ var removalOf = (ours, theirs, paired) => {
|
|
|
11830
12345
|
var missingAdditions = (hunks, file, elsewhere) => {
|
|
11831
12346
|
const present2 = new Set(file.result);
|
|
11832
12347
|
return hunks.flatMap(({ side, hunk, added }) => {
|
|
11833
|
-
const
|
|
11834
|
-
if (
|
|
12348
|
+
const text9 = added.find((line2) => !isWeak(line2) && !present2.has(line2));
|
|
12349
|
+
if (text9 === void 0 || containsSequence(file.result, added) || elsewhere(added)) return [];
|
|
11835
12350
|
const offset = Math.max(
|
|
11836
12351
|
0,
|
|
11837
|
-
hunk.added.findIndex((line2) => normalizeLine(line2) ===
|
|
12352
|
+
hunk.added.findIndex((line2) => normalizeLine(line2) === text9)
|
|
11838
12353
|
);
|
|
11839
|
-
return [{ kind: "missing-addition", file: file.path, side, line: hunk.newStart + offset, text:
|
|
12354
|
+
return [{ kind: "missing-addition", file: file.path, side, line: hunk.newStart + offset, text: text9, weak: false }];
|
|
11840
12355
|
});
|
|
11841
12356
|
};
|
|
11842
12357
|
var expectations = (file, hunks, removal) => {
|
|
@@ -11909,8 +12424,8 @@ var symbolsOf = (line2) => {
|
|
|
11909
12424
|
...title === void 0 ? [] : [{ name: title, test: true }]
|
|
11910
12425
|
];
|
|
11911
12426
|
};
|
|
11912
|
-
var escapeRegex = (
|
|
11913
|
-
var containsSymbol = (
|
|
12427
|
+
var escapeRegex = (text9) => text9.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12428
|
+
var containsSymbol = (text9, symbol) => symbol.test ? text9.includes(symbol.name) : new RegExp(`(?<![\\w$])${escapeRegex(symbol.name)}(?![\\w$])`).test(text9);
|
|
11914
12429
|
var missingSymbols = (file, side, hunks, present2) => {
|
|
11915
12430
|
const seen = /* @__PURE__ */ new Set();
|
|
11916
12431
|
return hunks.flatMap(
|
|
@@ -11929,12 +12444,12 @@ var created2 = (diff) => diff?.status === "added" || diff?.status === "copied";
|
|
|
11929
12444
|
var resultPathOf = (context, pair) => [pair.ours?.newPath, pair.theirs?.newPath, pair.key].find(
|
|
11930
12445
|
(path) => path !== void 0 && context.resultText(path) !== void 0
|
|
11931
12446
|
);
|
|
11932
|
-
var finding2 = (kind, file, side,
|
|
12447
|
+
var finding2 = (kind, file, side, text9) => ({
|
|
11933
12448
|
kind,
|
|
11934
12449
|
file,
|
|
11935
12450
|
side,
|
|
11936
12451
|
line: void 0,
|
|
11937
|
-
text:
|
|
12452
|
+
text: text9,
|
|
11938
12453
|
weak: false
|
|
11939
12454
|
});
|
|
11940
12455
|
var binaryFindings = (context, pair, path) => {
|
|
@@ -11953,8 +12468,8 @@ var binaryFindings = (context, pair, path) => {
|
|
|
11953
12468
|
return result === expected ? [] : [finding2("binary", file, side, file)];
|
|
11954
12469
|
};
|
|
11955
12470
|
var elsewhereOf = (context) => (lines3) => context.resultFiles().some((path) => containsSequence(contentLines(context.resultText(path)), lines3));
|
|
11956
|
-
var symbolFindings = (context, pair, path,
|
|
11957
|
-
const present2 = (symbol) =>
|
|
12471
|
+
var symbolFindings = (context, pair, path, text9) => {
|
|
12472
|
+
const present2 = (symbol) => text9 !== void 0 && containsSymbol(text9, symbol) || context.resultFiles().some((other) => containsSymbol(context.resultText(other) ?? "", symbol));
|
|
11958
12473
|
return [
|
|
11959
12474
|
...missingSymbols(path, "ours", pair.ours?.hunks ?? [], present2),
|
|
11960
12475
|
...missingSymbols(path, "theirs", pair.theirs?.hunks ?? [], present2)
|
|
@@ -11970,19 +12485,19 @@ var verifyFile = (context, pair) => {
|
|
|
11970
12485
|
const copied = [pair.ours, pair.theirs].find((diff) => diff?.status === "copied");
|
|
11971
12486
|
const baseSource = copied?.oldPath ?? (created2(pair.ours) || created2(pair.theirs) ? void 0 : pair.key);
|
|
11972
12487
|
const baseText = baseSource === void 0 ? void 0 : blobText(context.runtime, context.repo, context.inputs.base, baseSource);
|
|
11973
|
-
const
|
|
12488
|
+
const text9 = path === void 0 ? void 0 : context.resultText(path);
|
|
11974
12489
|
const file = path ?? pair.key;
|
|
11975
12490
|
const lines3 = checkFile(
|
|
11976
12491
|
{
|
|
11977
12492
|
path: file,
|
|
11978
12493
|
base: contentLines(baseText),
|
|
11979
|
-
result: contentLines(
|
|
12494
|
+
result: contentLines(text9),
|
|
11980
12495
|
ours: pair.ours?.hunks ?? [],
|
|
11981
12496
|
theirs: pair.theirs?.hunks ?? []
|
|
11982
12497
|
},
|
|
11983
12498
|
elsewhereOf(context)
|
|
11984
12499
|
);
|
|
11985
|
-
return [...lines3, ...symbolFindings(context, pair, file,
|
|
12500
|
+
return [...lines3, ...symbolFindings(context, pair, file, text9)];
|
|
11986
12501
|
};
|
|
11987
12502
|
|
|
11988
12503
|
// src/git/merge/verify.ts
|
|
@@ -12011,13 +12526,13 @@ var pairsOf = (ours, theirs) => {
|
|
|
12011
12526
|
}));
|
|
12012
12527
|
};
|
|
12013
12528
|
var contextOf2 = (runtime, repo, inputs, pairs2) => {
|
|
12014
|
-
const
|
|
12529
|
+
const resultText2 = memoBy((path) => blobText(runtime, repo, inputs.result, path));
|
|
12015
12530
|
const context = {
|
|
12016
12531
|
runtime,
|
|
12017
12532
|
repo,
|
|
12018
12533
|
inputs,
|
|
12019
|
-
resultText,
|
|
12020
|
-
resultFiles:
|
|
12534
|
+
resultText: resultText2,
|
|
12535
|
+
resultFiles: memo(
|
|
12021
12536
|
() => pairs2.flatMap((pair) => {
|
|
12022
12537
|
const path = resultPathOf(context, pair);
|
|
12023
12538
|
return path === void 0 ? [] : [path];
|
|
@@ -12073,19 +12588,19 @@ var verifyMerge = (runtime, repo, options = {}) => {
|
|
|
12073
12588
|
};
|
|
12074
12589
|
|
|
12075
12590
|
// src/git/ownership.ts
|
|
12076
|
-
import { join as
|
|
12591
|
+
import { join as join30 } from "node:path";
|
|
12077
12592
|
var OWNERSHIP_DIR = ".finitemonkey/git";
|
|
12078
12593
|
var ownerKey = (raw) => {
|
|
12079
12594
|
const cleaned = (raw ?? "").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80);
|
|
12080
12595
|
return cleaned === "" ? "unknown" : cleaned;
|
|
12081
12596
|
};
|
|
12082
|
-
var fileOf = (repo, kind, owner) => toAbsolutePath(
|
|
12597
|
+
var fileOf = (repo, kind, owner) => toAbsolutePath(join30(repo.root, OWNERSHIP_DIR, kind, `${ownerKey(owner)}.json`));
|
|
12083
12598
|
var readDirty = (runtime, root) => {
|
|
12084
12599
|
const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
|
|
12085
12600
|
return status.ok ? parsePorcelainV2(status.stdout) : void 0;
|
|
12086
12601
|
};
|
|
12087
12602
|
var hashDirty = (runtime, repo, entries) => {
|
|
12088
|
-
const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(
|
|
12603
|
+
const present2 = entries.filter((entry2) => runtime.stat(toAbsolutePath(join30(repo.root, entry2.path)))?.kind === "file");
|
|
12089
12604
|
const hashed = present2.length === 0 ? void 0 : runtime.gitWith(["hash-object", "--stdin-paths"], repo.root, {
|
|
12090
12605
|
input: `${present2.map((entry2) => entry2.path).join("\n")}
|
|
12091
12606
|
`
|
|
@@ -12102,8 +12617,8 @@ var hashesOf = (value) => isRecord(value) ? Object.fromEntries(
|
|
|
12102
12617
|
Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
|
|
12103
12618
|
) : {};
|
|
12104
12619
|
var readObject = (runtime, path) => {
|
|
12105
|
-
const
|
|
12106
|
-
return
|
|
12620
|
+
const text9 = runtime.readText(path);
|
|
12621
|
+
return text9.ok ? parseJsonObject(text9.value) : text9;
|
|
12107
12622
|
};
|
|
12108
12623
|
var readBaseline2 = (runtime, repo, owner) => {
|
|
12109
12624
|
const parsed = readObject(runtime, fileOf(repo, "baseline", owner));
|
|
@@ -12169,7 +12684,7 @@ var foreignFiles = (runtime, repo, owner, targets2) => {
|
|
|
12169
12684
|
};
|
|
12170
12685
|
|
|
12171
12686
|
// src/git/gate-status.ts
|
|
12172
|
-
var lines = (
|
|
12687
|
+
var lines = (text9) => text9.split("\n").filter((line2) => line2.trim() !== "");
|
|
12173
12688
|
var unpushedSince = (runtime, repo, since) => {
|
|
12174
12689
|
if (lines(runtime.git(["remote"], repo.root).stdout).length === 0) return [];
|
|
12175
12690
|
const result = runtime.git(["rev-list", `${since}..HEAD`, "--not", "--remotes"], repo.root);
|
|
@@ -12197,97 +12712,17 @@ var findingLabel = (finding4) => {
|
|
|
12197
12712
|
return `${where} ${finding4.text === "" ? finding4.kind : `"${finding4.text}"`}`;
|
|
12198
12713
|
};
|
|
12199
12714
|
|
|
12200
|
-
// src/git/repo.ts
|
|
12201
|
-
var DEFAULT_PROTECTED = ["main", "master", "release/*", "prod", "production", "rc-*"];
|
|
12202
|
-
var openRepo = (runtime, dir) => {
|
|
12203
|
-
const result = runtime.git(["rev-parse", "--show-toplevel", "--absolute-git-dir", "--show-prefix"], dir);
|
|
12204
|
-
if (!result.ok) return void 0;
|
|
12205
|
-
const [root, gitDir, prefix] = result.stdout.split("\n");
|
|
12206
|
-
if (root === void 0 || root === "" || gitDir === void 0 || gitDir === "") return void 0;
|
|
12207
|
-
return {
|
|
12208
|
-
root: toAbsolutePath(root),
|
|
12209
|
-
gitDir: toAbsolutePath(gitDir),
|
|
12210
|
-
prefix: (prefix ?? "").replace(/\/$/, "")
|
|
12211
|
-
};
|
|
12212
|
-
};
|
|
12213
|
-
var globRegex2 = (pattern) => new RegExp(`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`);
|
|
12214
|
-
var matchesBranch = (branch, patterns) => patterns.some((pattern) => globRegex2(pattern).test(branch));
|
|
12215
|
-
var line = (runtime, root, args2) => {
|
|
12216
|
-
const result = runtime.git(args2, root);
|
|
12217
|
-
const value = result.stdout.trim();
|
|
12218
|
-
return result.ok && value !== "" ? value : void 0;
|
|
12219
|
-
};
|
|
12220
|
-
var refExists = (runtime, root, ref) => runtime.git(["show-ref", "--verify", "--quiet", ref], root).ok;
|
|
12221
|
-
var defaultBranchOf = (runtime, root) => {
|
|
12222
|
-
const remote = line(runtime, root, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
|
|
12223
|
-
if (remote !== void 0) return remote.replace(/^[^/]+\//, "");
|
|
12224
|
-
const configured = line(runtime, root, ["config", "--get", "init.defaultBranch"]);
|
|
12225
|
-
if (configured !== void 0 && refExists(runtime, root, `refs/heads/${configured}`)) return configured;
|
|
12226
|
-
return refExists(runtime, root, "refs/heads/master") && !refExists(runtime, root, "refs/heads/main") ? "master" : "main";
|
|
12227
|
-
};
|
|
12228
|
-
var upstreamOf = (runtime, root, branch) => {
|
|
12229
|
-
const remote = line(runtime, root, ["config", "--get", `branch.${branch}.remote`]);
|
|
12230
|
-
const merge = line(runtime, root, ["config", "--get", `branch.${branch}.merge`]);
|
|
12231
|
-
if (remote === void 0 || merge === void 0 || remote === ".") return void 0;
|
|
12232
|
-
const name = merge.replace(/^refs\/heads\//, "");
|
|
12233
|
-
return { branch: name, remoteRef: `refs/remotes/${remote}/${name}` };
|
|
12234
|
-
};
|
|
12235
|
-
var integrationRefs = (runtime, root, defaultBranch) => [`refs/heads/${defaultBranch}`, `refs/remotes/origin/${defaultBranch}`].filter(
|
|
12236
|
-
(ref) => refExists(runtime, root, ref)
|
|
12237
|
-
);
|
|
12238
|
-
var contained = (runtime, root, branch, targets2) => {
|
|
12239
|
-
const tip = line(runtime, root, ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]);
|
|
12240
|
-
if (tip === void 0) return true;
|
|
12241
|
-
return targets2.filter((target2) => target2 !== `refs/heads/${branch}`).some((target2) => runtime.git(["merge-base", "--is-ancestor", tip, target2], root).ok);
|
|
12242
|
-
};
|
|
12243
|
-
var shared = (runtime, root, upstream, base) => {
|
|
12244
|
-
if (upstream === void 0 || !refExists(runtime, root, upstream.remoteRef)) return false;
|
|
12245
|
-
const own = /<([^>]*)>/.exec(line(runtime, root, ["var", "GIT_AUTHOR_IDENT"]) ?? "")?.[1] ?? "";
|
|
12246
|
-
const log = runtime.git(["log", "--format=%ae", upstream.remoteRef, ...base.map((ref) => `^${ref}`)], root);
|
|
12247
|
-
return log.ok && log.stdout.split("\n").some((email) => email.trim() !== "" && email.trim() !== own);
|
|
12248
|
-
};
|
|
12249
|
-
var protectedPatterns = (config, defaultBranch) => [
|
|
12250
|
-
defaultBranch,
|
|
12251
|
-
...DEFAULT_PROTECTED,
|
|
12252
|
-
...configList(config, "git", "protected")
|
|
12253
|
-
];
|
|
12254
|
-
var repoFacts = (runtime, repo, config = loadProjectConfig(runtime, repo.root)) => {
|
|
12255
|
-
const { root } = repo;
|
|
12256
|
-
const defaultBranch = memo2(() => defaultBranchOf(runtime, root));
|
|
12257
|
-
const patterns = memo2(() => protectedPatterns(config, defaultBranch()));
|
|
12258
|
-
const upstream = memoBy((branch) => upstreamOf(runtime, root, branch));
|
|
12259
|
-
const integration = memo2(() => integrationRefs(runtime, root, defaultBranch()));
|
|
12260
|
-
return {
|
|
12261
|
-
prefix: repo.prefix,
|
|
12262
|
-
currentBranch: memo2(() => line(runtime, root, ["symbolic-ref", "--quiet", "--short", "HEAD"])),
|
|
12263
|
-
defaultBranch,
|
|
12264
|
-
isProtected: (branch) => matchesBranch(branch, patterns()),
|
|
12265
|
-
dirty: memo2(() => {
|
|
12266
|
-
const status = runtime.git(["status", "--porcelain=v2", "-z", "--untracked-files=all"], root);
|
|
12267
|
-
return status.ok ? parsePorcelainV2(status.stdout) : void 0;
|
|
12268
|
-
}),
|
|
12269
|
-
isRef: memoBy((name) => runtime.git(["rev-parse", "--verify", "--quiet", `${name}^{commit}`], root).ok),
|
|
12270
|
-
branchExists: memoBy((name) => refExists(runtime, root, `refs/heads/${name}`)),
|
|
12271
|
-
branchContained: memoBy((branch) => {
|
|
12272
|
-
const up = upstream(branch);
|
|
12273
|
-
return contained(runtime, root, branch, [...up === void 0 ? [] : [up.remoteRef], ...integration()]);
|
|
12274
|
-
}),
|
|
12275
|
-
upstream,
|
|
12276
|
-
sharedBranch: memoBy((branch) => shared(runtime, root, upstream(branch), integration()))
|
|
12277
|
-
};
|
|
12278
|
-
};
|
|
12279
|
-
|
|
12280
12715
|
// src/gate/checks/worktree.ts
|
|
12281
12716
|
var MAX_LISTED7 = 8;
|
|
12282
12717
|
var listed3 = (items) => {
|
|
12283
12718
|
const head = items.slice(0, MAX_LISTED7).join(", ");
|
|
12284
12719
|
return items.length > MAX_LISTED7 ? `${head} (+${String(items.length - MAX_LISTED7)})` : head;
|
|
12285
12720
|
};
|
|
12286
|
-
var
|
|
12721
|
+
var repoOf2 = (ctx) => openRepo(ctx.runtime, ctx.paths.root);
|
|
12287
12722
|
var ownerOf = (ctx) => ownerKey(ctx.sessionId);
|
|
12288
12723
|
var declaresDestination = (ctx) => ctx.contract.acceptance.some((check2) => check2.kind === "git" && (check2.require ?? []).includes("pushed"));
|
|
12289
12724
|
var worktreeCheck = (_check, ctx) => {
|
|
12290
|
-
const repo =
|
|
12725
|
+
const repo = repoOf2(ctx);
|
|
12291
12726
|
if (repo === void 0) return unverified("tool_missing", [note("gate.worktree.unavailable")]);
|
|
12292
12727
|
const status = worktreeGateStatus(ctx.runtime, repo, ownerOf(ctx));
|
|
12293
12728
|
const unpushed = declaresDestination(ctx) ? [] : status.unpushed;
|
|
@@ -12298,7 +12733,7 @@ var worktreeCheck = (_check, ctx) => {
|
|
|
12298
12733
|
return notes2.length > 0 ? failed(notes2) : passed([note("gate.worktree.clean")]);
|
|
12299
12734
|
};
|
|
12300
12735
|
var mergeCheck = (_check, ctx) => {
|
|
12301
|
-
const repo =
|
|
12736
|
+
const repo = repoOf2(ctx);
|
|
12302
12737
|
if (repo === void 0) return unverified("tool_missing", [note("gate.merge.unavailable")]);
|
|
12303
12738
|
const items = mergeGateStatus(ctx.runtime, repo, ownerOf(ctx));
|
|
12304
12739
|
const short3 = (commit2) => commit2 === "MERGE_HEAD" ? commit2 : commit2.slice(0, 12);
|
|
@@ -12409,15 +12844,15 @@ var runGate = (options, registry = DEFAULT_CHECKS) => {
|
|
|
12409
12844
|
if (ready.kind !== "ready") return ready;
|
|
12410
12845
|
const { prepared } = ready;
|
|
12411
12846
|
const path = prepared.ctx.paths.evidence(prepared.ctx.contract.id);
|
|
12412
|
-
const before =
|
|
12847
|
+
const before = readLedger2(options.runtime, path);
|
|
12413
12848
|
if (options.fast && fastPathHit(prepared, before)) return { kind: "done", report: report(prepared, before, true) };
|
|
12414
12849
|
const fresh3 = runChecks(prepared.ctx, before, registry);
|
|
12415
12850
|
if (!appendEvidence(options.runtime, path, fresh3)) return { kind: "failed", failure: { kind: "ledger_unwritable" } };
|
|
12416
|
-
const withChecks =
|
|
12851
|
+
const withChecks = readLedger2(options.runtime, path);
|
|
12417
12852
|
if (!appendEvidence(options.runtime, path, [summaryEntry(prepared, withChecks)])) {
|
|
12418
12853
|
return { kind: "failed", failure: { kind: "ledger_unwritable" } };
|
|
12419
12854
|
}
|
|
12420
|
-
return { kind: "done", report: report(prepared,
|
|
12855
|
+
return { kind: "done", report: report(prepared, readLedger2(options.runtime, path), false) };
|
|
12421
12856
|
};
|
|
12422
12857
|
|
|
12423
12858
|
// src/gate/hooks/stop.ts
|
|
@@ -12535,7 +12970,7 @@ var subagentStopGate = (input) => {
|
|
|
12535
12970
|
};
|
|
12536
12971
|
|
|
12537
12972
|
// src/gate/hooks/subagent.ts
|
|
12538
|
-
import { join as
|
|
12973
|
+
import { join as join31 } from "node:path";
|
|
12539
12974
|
var NO_CONTRACT_AGENTS = /(^|:)(reviewer|verifier|explore|plan|claude-code-guide|statusline-setup)$/i;
|
|
12540
12975
|
var KNOWN_KINDS = /* @__PURE__ */ new Set([
|
|
12541
12976
|
"command",
|
|
@@ -12573,12 +13008,12 @@ var subagentStart = (input) => {
|
|
|
12573
13008
|
const active = readState(runtime, paths).active;
|
|
12574
13009
|
if (active === void 0) return { kind: "allow" };
|
|
12575
13010
|
const disk = (relative11) => {
|
|
12576
|
-
const
|
|
12577
|
-
return
|
|
13011
|
+
const text9 = runtime.readText(toAbsolutePath(join31(paths.root, relative11)));
|
|
13012
|
+
return text9.ok ? text9.value : void 0;
|
|
12578
13013
|
};
|
|
12579
13014
|
const loaded = loadTask(runtime, paths, { disk, base: () => void 0 }, active);
|
|
12580
13015
|
if (!loaded.ok) return { kind: "allow" };
|
|
12581
|
-
const briefing = runtime.stat(toAbsolutePath(
|
|
13016
|
+
const briefing = runtime.stat(toAbsolutePath(join31(paths.dir, "briefing.md"))) !== void 0;
|
|
12582
13017
|
return { kind: "allow", output: contextOutput("SubagentStart", contractFacts(t, loaded.value.contract, briefing)) };
|
|
12583
13018
|
};
|
|
12584
13019
|
|
|
@@ -12596,12 +13031,12 @@ var PORTAO_HANDLERS = {
|
|
|
12596
13031
|
};
|
|
12597
13032
|
|
|
12598
13033
|
// src/prod/guard.ts
|
|
12599
|
-
import { join as
|
|
13034
|
+
import { join as join33 } from "node:path";
|
|
12600
13035
|
|
|
12601
13036
|
// src/git/command-text.ts
|
|
12602
13037
|
var MAX_SHOWN = 160;
|
|
12603
|
-
var shown = (
|
|
12604
|
-
const flat =
|
|
13038
|
+
var shown = (text9) => {
|
|
13039
|
+
const flat = text9.replace(/\s+/g, " ").trim();
|
|
12605
13040
|
return flat.length > MAX_SHOWN ? `${flat.slice(0, MAX_SHOWN - 3)}...` : flat;
|
|
12606
13041
|
};
|
|
12607
13042
|
var commandText = (command) => shown([command.program, ...command.args].join(" "));
|
|
@@ -12938,7 +13373,7 @@ var remoteHostsOf = (command) => {
|
|
|
12938
13373
|
};
|
|
12939
13374
|
|
|
12940
13375
|
// src/prod/targets.ts
|
|
12941
|
-
import { join as
|
|
13376
|
+
import { join as join32 } from "node:path";
|
|
12942
13377
|
var DEPLOY_FILES = [
|
|
12943
13378
|
"deploy.sh",
|
|
12944
13379
|
"scripts/deploy.sh",
|
|
@@ -12951,9 +13386,9 @@ var SSH_URL = /ssh:\/\/(?:[^@\s/]+@)?([A-Za-z0-9.-]+)/g;
|
|
|
12951
13386
|
var HOST_KEY = /^\s*-?\s*(?:host|hostname|server|ssh_host|deploy_host|remote_host)\s*:\s*["']?([A-Za-z0-9][A-Za-z0-9.-]*)["']?\s*$/i;
|
|
12952
13387
|
var hostKey = (raw) => raw.trim().toLowerCase().replace(/^[^@]*@/, "").replace(/^\[([^\]]+)\].*$/, "$1").replace(/:\d+$/, "").replace(/\.$/, "");
|
|
12953
13388
|
var shellLine = (line2) => line2.replace(/^\s*(?:-\s*)?(?:run:\s*\|?)?/, "");
|
|
12954
|
-
var hostsInText = (
|
|
12955
|
-
const urls = [...
|
|
12956
|
-
const lines3 =
|
|
13389
|
+
var hostsInText = (text9) => {
|
|
13390
|
+
const urls = [...text9.matchAll(SSH_URL)].map((match) => match[1] ?? "");
|
|
13391
|
+
const lines3 = text9.split(/\r?\n/);
|
|
12957
13392
|
const keyed = lines3.flatMap((line2) => {
|
|
12958
13393
|
const match = HOST_KEY.exec(line2);
|
|
12959
13394
|
return match?.[1] === void 0 ? [] : [match[1]];
|
|
@@ -12962,13 +13397,13 @@ var hostsInText = (text8) => {
|
|
|
12962
13397
|
return [...urls, ...keyed, ...commands].filter((host) => host !== "" && !host.includes("$"));
|
|
12963
13398
|
};
|
|
12964
13399
|
var workflowFiles = (runtime, root) => {
|
|
12965
|
-
const listed4 = runtime.listDir(toAbsolutePath(
|
|
13400
|
+
const listed4 = runtime.listDir(toAbsolutePath(join32(root, ".github", "workflows")));
|
|
12966
13401
|
if (!listed4.ok) return [];
|
|
12967
13402
|
return listed4.value.filter((name) => /deploy/i.test(name) && /\.ya?ml$/i.test(name)).map((name) => `.github/workflows/${name}`);
|
|
12968
13403
|
};
|
|
12969
13404
|
var detectedHosts = (runtime, root, files) => files.flatMap((file) => {
|
|
12970
|
-
const
|
|
12971
|
-
return
|
|
13405
|
+
const text9 = runtime.readText(toAbsolutePath(join32(root, file)));
|
|
13406
|
+
return text9.ok ? hostsInText(text9.value) : [];
|
|
12972
13407
|
});
|
|
12973
13408
|
var loadProdTargets = (runtime, root, config) => {
|
|
12974
13409
|
const workflows = workflowFiles(runtime, root);
|
|
@@ -12979,7 +13414,7 @@ var loadProdTargets = (runtime, root, config) => {
|
|
|
12979
13414
|
...detectedHosts(runtime, root, deployFiles)
|
|
12980
13415
|
];
|
|
12981
13416
|
const declaredDeploy = configAt(config, "prod", "deploy");
|
|
12982
|
-
const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(
|
|
13417
|
+
const existing = deployFiles.find((file) => runtime.stat(toAbsolutePath(join32(root, file))) !== void 0);
|
|
12983
13418
|
return {
|
|
12984
13419
|
hosts: new Set(hosts.map(hostKey).filter((host) => host !== "")),
|
|
12985
13420
|
kubeContexts: configList(config, "prod", "kube_contexts"),
|
|
@@ -13135,15 +13570,15 @@ var blockOf = (t, hit3, command, deploy) => ({
|
|
|
13135
13570
|
})
|
|
13136
13571
|
});
|
|
13137
13572
|
var prodGuard = (runtime, root, t) => {
|
|
13138
|
-
const targets2 =
|
|
13573
|
+
const targets2 = memo(() => loadProdTargets(runtime, root, loadProjectConfig(runtime, root)));
|
|
13139
13574
|
const isProd = isProdHostOf(runtime, root, targets2);
|
|
13140
13575
|
return {
|
|
13141
13576
|
decide: (command) => {
|
|
13142
13577
|
if (!PROD_PROGRAMS.has(command.program)) return void 0;
|
|
13143
13578
|
const hit3 = prodHit(command, targets2(), isProd);
|
|
13144
13579
|
if (hit3 === void 0) return void 0;
|
|
13145
|
-
const
|
|
13146
|
-
return hit3.effect === "read" ? { kind: "read", hit: hit3, command:
|
|
13580
|
+
const text9 = commandText(command);
|
|
13581
|
+
return hit3.effect === "read" ? { kind: "read", hit: hit3, command: text9 } : blockOf(t, hit3, text9, targets2().deployPath);
|
|
13147
13582
|
}
|
|
13148
13583
|
};
|
|
13149
13584
|
};
|
|
@@ -13160,11 +13595,11 @@ var recordEvidence = (runtime, root, owner, reads) => {
|
|
|
13160
13595
|
] : []
|
|
13161
13596
|
);
|
|
13162
13597
|
if (lines3.length === 0) return;
|
|
13163
|
-
const path = toAbsolutePath(
|
|
13164
|
-
runtime.mkdir(toAbsolutePath(
|
|
13598
|
+
const path = toAbsolutePath(join33(root, EVIDENCE_FILE));
|
|
13599
|
+
runtime.mkdir(toAbsolutePath(join33(root, ".finitemonkey")));
|
|
13165
13600
|
const before = runtime.readText(path);
|
|
13166
|
-
const
|
|
13167
|
-
runtime.writeAtomic(path, `${
|
|
13601
|
+
const text9 = before.ok ? before.value : "";
|
|
13602
|
+
runtime.writeAtomic(path, `${text9}${text9 === "" || text9.endsWith("\n") ? "" : "\n"}${lines3.join("\n")}
|
|
13168
13603
|
`);
|
|
13169
13604
|
};
|
|
13170
13605
|
|
|
@@ -13179,15 +13614,15 @@ var editTexts = (input, file) => {
|
|
|
13179
13614
|
if (!Array.isArray(edits)) return [];
|
|
13180
13615
|
return edits.flatMap((edit) => {
|
|
13181
13616
|
if (!isRecord(edit)) return [];
|
|
13182
|
-
const
|
|
13183
|
-
return
|
|
13617
|
+
const text9 = optionalString(edit, "new_string");
|
|
13618
|
+
return text9 === void 0 ? [] : [{ file, text: text9, whole: false }];
|
|
13184
13619
|
});
|
|
13185
13620
|
};
|
|
13186
13621
|
var writtenTexts = (tool, input) => {
|
|
13187
13622
|
const file = optionalString(input, "file_path") ?? optionalString(input, "notebook_path") ?? "";
|
|
13188
13623
|
const single2 = (key, whole) => {
|
|
13189
|
-
const
|
|
13190
|
-
return
|
|
13624
|
+
const text9 = optionalString(input, key);
|
|
13625
|
+
return text9 === void 0 ? [] : [{ file, text: text9, whole }];
|
|
13191
13626
|
};
|
|
13192
13627
|
switch (tool) {
|
|
13193
13628
|
case "Write":
|
|
@@ -13869,24 +14304,24 @@ var withChain = (facts, chain) => {
|
|
|
13869
14304
|
const branch = chain?.branch;
|
|
13870
14305
|
return branch === void 0 ? facts : { ...facts, currentBranch: () => branch };
|
|
13871
14306
|
};
|
|
13872
|
-
var decide = (context, repo, verdict, facts,
|
|
14307
|
+
var decide = (context, repo, verdict, facts, text9) => {
|
|
13873
14308
|
switch (verdict.kind) {
|
|
13874
14309
|
case "pass":
|
|
13875
14310
|
return { kind: "pass" };
|
|
13876
14311
|
case "block":
|
|
13877
|
-
return { kind: "block", rule: verdict.rule, subject: verdict.subject, command:
|
|
14312
|
+
return { kind: "block", rule: verdict.rule, subject: verdict.subject, command: text9 };
|
|
13878
14313
|
case "snapshot":
|
|
13879
|
-
return { kind: "snapshot", repo, op: verdict.op, command:
|
|
14314
|
+
return { kind: "snapshot", repo, op: verdict.op, command: text9 };
|
|
13880
14315
|
case "discard": {
|
|
13881
14316
|
const entries = facts.dirty();
|
|
13882
|
-
if (entries === void 0) return { kind: "unknown", command:
|
|
14317
|
+
if (entries === void 0) return { kind: "unknown", command: text9 };
|
|
13883
14318
|
const foreign = foreignFiles(
|
|
13884
14319
|
context.runtime,
|
|
13885
14320
|
repo,
|
|
13886
14321
|
context.owner,
|
|
13887
14322
|
targetsOf(entries, verdict.discard, facts.prefix)
|
|
13888
14323
|
);
|
|
13889
|
-
return foreign.length > 0 ? { kind: "foreign", files: foreign, command:
|
|
14324
|
+
return foreign.length > 0 ? { kind: "foreign", files: foreign, command: text9 } : { kind: "snapshot", repo, op: verdict.op, command: text9 };
|
|
13890
14325
|
}
|
|
13891
14326
|
}
|
|
13892
14327
|
};
|
|
@@ -13973,10 +14408,10 @@ var listWorktrees = (runtime, cwd) => {
|
|
|
13973
14408
|
};
|
|
13974
14409
|
|
|
13975
14410
|
// src/git/journal.ts
|
|
13976
|
-
import { join as
|
|
14411
|
+
import { join as join34 } from "node:path";
|
|
13977
14412
|
var JOURNAL_FILE = ".finitemonkey/git-journal.jsonl";
|
|
13978
14413
|
var JOURNAL_SCHEMA = "finitemonkey/git-journal@1";
|
|
13979
|
-
var journalPath = (root) => toAbsolutePath(
|
|
14414
|
+
var journalPath = (root) => toAbsolutePath(join34(root, JOURNAL_FILE));
|
|
13980
14415
|
var headsOf = (value) => isRecord(value) ? Object.fromEntries(
|
|
13981
14416
|
Object.entries(value).filter((entry2) => typeof entry2[1] === "string")
|
|
13982
14417
|
) : {};
|
|
@@ -14003,16 +14438,16 @@ var parseJournalLine = (line2) => {
|
|
|
14003
14438
|
};
|
|
14004
14439
|
};
|
|
14005
14440
|
var readJournal = (runtime, root) => {
|
|
14006
|
-
const
|
|
14007
|
-
if (!
|
|
14008
|
-
return
|
|
14441
|
+
const text9 = runtime.readText(journalPath(root));
|
|
14442
|
+
if (!text9.ok) return [];
|
|
14443
|
+
return text9.value.split("\n").flatMap((line2) => {
|
|
14009
14444
|
const entry2 = line2.trim() === "" ? void 0 : parseJournalLine(line2);
|
|
14010
14445
|
return entry2 === void 0 ? [] : [entry2];
|
|
14011
14446
|
});
|
|
14012
14447
|
};
|
|
14013
14448
|
var appendJournal = (runtime, root, entry2) => {
|
|
14014
14449
|
const path = journalPath(root);
|
|
14015
|
-
const made = runtime.mkdir(toAbsolutePath(
|
|
14450
|
+
const made = runtime.mkdir(toAbsolutePath(join34(root, ".finitemonkey")));
|
|
14016
14451
|
if (!made.ok) return made;
|
|
14017
14452
|
const previous = runtime.readText(path);
|
|
14018
14453
|
const before = previous.ok ? previous.value : "";
|
|
@@ -14176,9 +14611,9 @@ var snapshotsOf = (input, owner, pending) => {
|
|
|
14176
14611
|
return { kind: "allow", output: { hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: context } } };
|
|
14177
14612
|
};
|
|
14178
14613
|
var review = (input, commands, owner) => {
|
|
14179
|
-
const worktrees =
|
|
14180
|
-
const repo =
|
|
14181
|
-
const prod =
|
|
14614
|
+
const worktrees = memo(() => listWorktrees(input.runtime, input.cwd));
|
|
14615
|
+
const repo = memo(() => openRepo(input.runtime, input.cwd));
|
|
14616
|
+
const prod = memo(() => prodGuard(input.runtime, repo()?.root ?? input.cwd, input.t));
|
|
14182
14617
|
const snapshots = [];
|
|
14183
14618
|
const reads = [];
|
|
14184
14619
|
const chain = { branch: void 0 };
|
|
@@ -14197,9 +14632,9 @@ var review = (input, commands, owner) => {
|
|
|
14197
14632
|
return { blocked: void 0, snapshots, reads };
|
|
14198
14633
|
};
|
|
14199
14634
|
var gitPreToolUse = (input) => {
|
|
14200
|
-
const
|
|
14201
|
-
if (
|
|
14202
|
-
const commands = normalizeCommand(
|
|
14635
|
+
const text9 = shellText(input.payload);
|
|
14636
|
+
if (text9 === void 0) return ALLOW;
|
|
14637
|
+
const commands = normalizeCommand(text9, dialectOf(toolName2(input.payload))).commands;
|
|
14203
14638
|
if (commands.length === 0) return ALLOW;
|
|
14204
14639
|
const owner = ownerOf2(input.payload);
|
|
14205
14640
|
const result = review(input, commands, owner);
|
|
@@ -14261,8 +14696,8 @@ var GIT_HANDLERS = {
|
|
|
14261
14696
|
var SHADOW_FILE = "shadow.json";
|
|
14262
14697
|
var numberOf = (value) => typeof value === "number" && Number.isInteger(value) ? value : 0;
|
|
14263
14698
|
var readShadow = (runtime, paths) => {
|
|
14264
|
-
const
|
|
14265
|
-
const parsed =
|
|
14699
|
+
const text9 = runtime.readText(paths.cache(SHADOW_FILE));
|
|
14700
|
+
const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
|
|
14266
14701
|
if (parsed?.ok !== true) return {};
|
|
14267
14702
|
return Object.fromEntries(
|
|
14268
14703
|
Object.entries(parsed.value).flatMap(
|
|
@@ -14346,11 +14781,11 @@ var ASSIGNMENT3 = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
|
14346
14781
|
var WRAPPERS2 = /* @__PURE__ */ new Set(["sudo", "command", "exec", "time", "nice", "nohup", "builtin", "env"]);
|
|
14347
14782
|
var liftHeredocs = (command) => {
|
|
14348
14783
|
const heredocs = [];
|
|
14349
|
-
const
|
|
14784
|
+
const text9 = command.replace(HEREDOC2, (_match, _quote, _tag, body2) => {
|
|
14350
14785
|
heredocs.push(body2);
|
|
14351
14786
|
return `<<__FM_HEREDOC_${String(heredocs.length - 1)}__`;
|
|
14352
14787
|
});
|
|
14353
|
-
return { text:
|
|
14788
|
+
return { text: text9, heredocs };
|
|
14354
14789
|
};
|
|
14355
14790
|
var endWord = (scan) => {
|
|
14356
14791
|
if (scan.word !== void 0) scan.words.push(scan.word);
|
|
@@ -14361,23 +14796,23 @@ var endSegment = (scan) => {
|
|
|
14361
14796
|
if (scan.words.length > 0) scan.segments.push(scan.words);
|
|
14362
14797
|
scan.words = [];
|
|
14363
14798
|
};
|
|
14364
|
-
var quotedEnd2 = (
|
|
14799
|
+
var quotedEnd2 = (text9, from, quote) => {
|
|
14365
14800
|
let index = from;
|
|
14366
|
-
while (index <
|
|
14801
|
+
while (index < text9.length && text9[index] !== quote) index += quote === '"' && text9[index] === "\\" ? 2 : 1;
|
|
14367
14802
|
return index;
|
|
14368
14803
|
};
|
|
14369
14804
|
var unescape = (value) => value.replace(/\\(["\\$`])/g, "$1");
|
|
14370
|
-
var splitWords = (
|
|
14805
|
+
var splitWords = (text9) => {
|
|
14371
14806
|
const scan = { words: [], segments: [], word: void 0 };
|
|
14372
|
-
for (let index = 0; index <
|
|
14373
|
-
const char =
|
|
14807
|
+
for (let index = 0; index < text9.length; index += 1) {
|
|
14808
|
+
const char = text9[index] ?? "";
|
|
14374
14809
|
if (char === '"' || char === "'") {
|
|
14375
|
-
const end = quotedEnd2(
|
|
14376
|
-
const inner =
|
|
14810
|
+
const end = quotedEnd2(text9, index + 1, char);
|
|
14811
|
+
const inner = text9.slice(index + 1, end);
|
|
14377
14812
|
scan.word = (scan.word ?? "") + (char === '"' ? unescape(inner) : inner);
|
|
14378
14813
|
index = end;
|
|
14379
|
-
} else if (char === "\\" && index + 1 <
|
|
14380
|
-
scan.word = (scan.word ?? "") + (
|
|
14814
|
+
} else if (char === "\\" && index + 1 < text9.length) {
|
|
14815
|
+
scan.word = (scan.word ?? "") + (text9[index + 1] === "\n" ? "" : text9[index + 1] ?? "");
|
|
14381
14816
|
index += 1;
|
|
14382
14817
|
} else if (SEPARATOR.test(char)) {
|
|
14383
14818
|
endSegment(scan);
|
|
@@ -14398,8 +14833,8 @@ var segmentOf = (words) => {
|
|
|
14398
14833
|
return { program: program.split(/[\\/]/).pop() ?? program, args: words.slice(index + 1) };
|
|
14399
14834
|
};
|
|
14400
14835
|
var shellWords = (command) => {
|
|
14401
|
-
const { text:
|
|
14402
|
-
const segments = splitWords(
|
|
14836
|
+
const { text: text9, heredocs } = liftHeredocs(command);
|
|
14837
|
+
const segments = splitWords(text9).flatMap((words) => {
|
|
14403
14838
|
const segment = segmentOf(words);
|
|
14404
14839
|
return segment === void 0 ? [] : [segment];
|
|
14405
14840
|
});
|
|
@@ -14504,7 +14939,7 @@ var sourceOf = (value, isFile, segment, heredocs) => {
|
|
|
14504
14939
|
if (inline3 !== void 0) return { kind: "inline", text: inline3 };
|
|
14505
14940
|
if (!isFile) return { kind: "inline", text: value };
|
|
14506
14941
|
if (value !== "-") return { kind: "file", path: value };
|
|
14507
|
-
const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((
|
|
14942
|
+
const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((text9) => text9 !== void 0);
|
|
14508
14943
|
return { kind: "inline", text: piped ?? "" };
|
|
14509
14944
|
};
|
|
14510
14945
|
var collect3 = (target2, segment, args2, spec2, heredocs) => {
|
|
@@ -14601,15 +15036,15 @@ var textOf2 = (input, found) => {
|
|
|
14601
15036
|
return accept(parts.join("\n\n"));
|
|
14602
15037
|
};
|
|
14603
15038
|
var decisionFor = (input, found, blocking2) => {
|
|
14604
|
-
const
|
|
14605
|
-
if (!
|
|
15039
|
+
const text9 = textOf2(input, found);
|
|
15040
|
+
if (!text9.ok) {
|
|
14606
15041
|
return blocking2 ? {
|
|
14607
15042
|
kind: "block",
|
|
14608
|
-
reason: modelReason(input.t, "prose.block.unreadable", { ...
|
|
15043
|
+
reason: modelReason(input.t, "prose.block.unreadable", { ...text9.error }),
|
|
14609
15044
|
human: input.t("prose.block.human")
|
|
14610
15045
|
} : void 0;
|
|
14611
15046
|
}
|
|
14612
|
-
const report2 = scoreProse(
|
|
15047
|
+
const report2 = scoreProse(text9.value, { context: "record" });
|
|
14613
15048
|
if (report2.allowed || report2.spans.length === 0) return void 0;
|
|
14614
15049
|
const params2 = {
|
|
14615
15050
|
target: input.t(TARGET_KEYS[found.target]),
|
|
@@ -14656,7 +15091,7 @@ var HOOK_ACTOR = "finitemonkey";
|
|
|
14656
15091
|
var liveFindings = (input, texts2) => {
|
|
14657
15092
|
const baseline = loadBaseline(input.runtime, input.cwd);
|
|
14658
15093
|
const accepted2 = acceptedFingerprints(baseline.ok ? baseline.value : EMPTY_BASELINE, input.runtime.now());
|
|
14659
|
-
return texts2.flatMap((
|
|
15094
|
+
return texts2.flatMap((text9) => scanText(text9, { rules: CREDENTIAL_RULES })).filter((finding4) => !accepted2.has(finding4.fingerprint));
|
|
14660
15095
|
};
|
|
14661
15096
|
var record2 = (input, findings, draft, where) => {
|
|
14662
15097
|
const ids2 = findings.map((finding4) => {
|
|
@@ -14917,12 +15352,12 @@ var secretsSessionStart = (input) => {
|
|
|
14917
15352
|
const remotes = remotesWithCredentials(input.runtime, input.cwd);
|
|
14918
15353
|
if (remotes.length === 0) return { kind: "allow" };
|
|
14919
15354
|
const params2 = { remotes: remotes.join(", "), remote: remotes[0] ?? "origin" };
|
|
14920
|
-
const
|
|
15355
|
+
const text9 = input.t("secrets.session.remote_credential", params2);
|
|
14921
15356
|
return {
|
|
14922
15357
|
kind: "allow",
|
|
14923
15358
|
output: {
|
|
14924
|
-
systemMessage: `finitemonkey: ${
|
|
14925
|
-
hookSpecificOutput: { hookEventName: "SessionStart", additionalContext:
|
|
15359
|
+
systemMessage: `finitemonkey: ${text9}`,
|
|
15360
|
+
hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text9 }
|
|
14926
15361
|
}
|
|
14927
15362
|
};
|
|
14928
15363
|
};
|
|
@@ -14958,7 +15393,18 @@ var mergeOutputs = (left, right) => {
|
|
|
14958
15393
|
if (specific !== void 0) merged2["hookSpecificOutput"] = specific;
|
|
14959
15394
|
return merged2;
|
|
14960
15395
|
};
|
|
15396
|
+
var recordBlock = (input, decision) => {
|
|
15397
|
+
const paths = projectPaths(input);
|
|
15398
|
+
if (paths === void 0) return;
|
|
15399
|
+
logEvent(input.runtime, paths, "guard_block", {
|
|
15400
|
+
hook: input.event,
|
|
15401
|
+
tool: toolName2(input.payload),
|
|
15402
|
+
sessionId: input.payload.sessionId ?? null,
|
|
15403
|
+
claim: (decision.reason.split("\n")[0] ?? "").slice(0, 200)
|
|
15404
|
+
});
|
|
15405
|
+
};
|
|
14961
15406
|
var shaped = (input, decision) => {
|
|
15407
|
+
recordBlock(input, decision);
|
|
14962
15408
|
if (input.event !== "PreToolUse" || decision.output !== void 0) return decision;
|
|
14963
15409
|
const reason = capReason(decision.reason);
|
|
14964
15410
|
return {
|
|
@@ -15011,9 +15457,9 @@ var EMPTY_PAYLOAD = {
|
|
|
15011
15457
|
stopHookActive: false,
|
|
15012
15458
|
raw: {}
|
|
15013
15459
|
};
|
|
15014
|
-
var parseHookPayload = (event,
|
|
15015
|
-
if (interactive &&
|
|
15016
|
-
const parsed = parseJsonObject(
|
|
15460
|
+
var parseHookPayload = (event, text9, interactive) => {
|
|
15461
|
+
if (interactive && text9.trim() === "") return accept(EMPTY_PAYLOAD);
|
|
15462
|
+
const parsed = parseJsonObject(text9);
|
|
15017
15463
|
if (!parsed.ok) return reject(`stdin: ${parsed.error}`);
|
|
15018
15464
|
const payload = {
|
|
15019
15465
|
hookEventName: optionalString(parsed.value, "hook_event_name"),
|
|
@@ -15028,53 +15474,13 @@ var parseHookPayload = (event, text8, interactive) => {
|
|
|
15028
15474
|
return accept(payload);
|
|
15029
15475
|
};
|
|
15030
15476
|
|
|
15031
|
-
// src/hooks/stdin.ts
|
|
15032
|
-
var readAll = (source) => new Promise((resolve2, reject2) => {
|
|
15033
|
-
if (source.isTTY === true) {
|
|
15034
|
-
resolve2("");
|
|
15035
|
-
return;
|
|
15036
|
-
}
|
|
15037
|
-
const chunks = [];
|
|
15038
|
-
source.setEncoding?.("utf8");
|
|
15039
|
-
source.on("data", (chunk) => {
|
|
15040
|
-
chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
|
15041
|
-
});
|
|
15042
|
-
source.on("end", () => {
|
|
15043
|
-
resolve2(chunks.join(""));
|
|
15044
|
-
});
|
|
15045
|
-
source.on("error", reject2);
|
|
15046
|
-
});
|
|
15047
|
-
var DeadlineError = class extends Error {
|
|
15048
|
-
ms;
|
|
15049
|
-
constructor(ms) {
|
|
15050
|
-
super(`deadline of ${String(ms)} ms exceeded`);
|
|
15051
|
-
this.name = "DeadlineError";
|
|
15052
|
-
this.ms = ms;
|
|
15053
|
-
}
|
|
15054
|
-
};
|
|
15055
|
-
var withDeadline = (work, ms, startTimer) => new Promise((resolve2, reject2) => {
|
|
15056
|
-
const cancel = startTimer(ms, () => {
|
|
15057
|
-
reject2(new DeadlineError(ms));
|
|
15058
|
-
});
|
|
15059
|
-
work.then(
|
|
15060
|
-
(value) => {
|
|
15061
|
-
cancel();
|
|
15062
|
-
resolve2(value);
|
|
15063
|
-
},
|
|
15064
|
-
(error) => {
|
|
15065
|
-
cancel();
|
|
15066
|
-
reject2(error instanceof Error ? error : new Error(String(error)));
|
|
15067
|
-
}
|
|
15068
|
-
);
|
|
15069
|
-
});
|
|
15070
|
-
|
|
15071
15477
|
// src/hooks/runner.ts
|
|
15072
15478
|
var HOOK_PROBE_ENV = "FINITEMONKEY_HOOK_PROBE";
|
|
15073
15479
|
var messageOf2 = (error) => error instanceof Error ? error.message : String(error);
|
|
15074
15480
|
var decide2 = (input, handlers) => composeDecisions(input, handlers[input.event] ?? [allow]);
|
|
15075
15481
|
var evaluate = async (event, options) => {
|
|
15076
|
-
const
|
|
15077
|
-
const parsed = parseHookPayload(event,
|
|
15482
|
+
const text9 = await readAll(options.stdin);
|
|
15483
|
+
const parsed = parseHookPayload(event, text9, options.stdin.isTTY === true);
|
|
15078
15484
|
if (!parsed.ok) return { kind: "failed", payload: void 0, detail: parsed.error };
|
|
15079
15485
|
const payload = parsed.value;
|
|
15080
15486
|
if (options.env?.[HOOK_PROBE_ENV] === "1") return { kind: "decided", payload, decision: { kind: "allow" } };
|
|
@@ -15151,7 +15557,7 @@ var runHook = async (event, options) => {
|
|
|
15151
15557
|
var PROBE_TIMEOUT_MS2 = 1e4;
|
|
15152
15558
|
var SLOW_HOOK_MS = 1500;
|
|
15153
15559
|
var DEFAULT_TIMEOUT_S = 60;
|
|
15154
|
-
var expand = (
|
|
15560
|
+
var expand = (text9, vars) => text9.replace(/\$\{(\w+)\}|\$(\w+)/g, (match, braced, bare) => {
|
|
15155
15561
|
const name = braced ?? bare ?? "";
|
|
15156
15562
|
return vars[name] ?? match;
|
|
15157
15563
|
});
|
|
@@ -15200,7 +15606,7 @@ var run2 = (exec, invocation, cwd, env, input) => exec({
|
|
|
15200
15606
|
env: { ...env, [HOOK_PROBE_ENV]: "1" },
|
|
15201
15607
|
...input === void 0 ? {} : { input }
|
|
15202
15608
|
});
|
|
15203
|
-
var firstLine = (
|
|
15609
|
+
var firstLine = (text9) => maskText(text9.trim().split("\n")[0] ?? "").slice(0, 160);
|
|
15204
15610
|
var classify2 = (result) => {
|
|
15205
15611
|
if (!result.found) return { kind: "failed", problem: "missing", detail: "" };
|
|
15206
15612
|
if (result.timedOut) return { kind: "failed", problem: "timeout", detail: "" };
|
|
@@ -15412,7 +15818,7 @@ var hookSchemaProblems = (hooks, claude) => {
|
|
|
15412
15818
|
};
|
|
15413
15819
|
|
|
15414
15820
|
// src/doctor/hook-sources.ts
|
|
15415
|
-
import { join as
|
|
15821
|
+
import { join as join35 } from "node:path";
|
|
15416
15822
|
var entriesOf = (source, hooks, own) => Object.entries(asRecord(hooks)).flatMap(
|
|
15417
15823
|
([event, groups]) => asList(groups).flatMap((group) => {
|
|
15418
15824
|
const record3 = asRecord(group);
|
|
@@ -15425,31 +15831,31 @@ var projectHookFiles = (env) => readSettings(env).flatMap(({ scope: scope2, path
|
|
|
15425
15831
|
const source = PROJECT_SCOPES[scope2];
|
|
15426
15832
|
return source === void 0 || !("hooks" in settings) ? [] : [{ source, path, hooks: settings["hooks"] }];
|
|
15427
15833
|
});
|
|
15428
|
-
var pluginsDir = ({ env, runtime }) =>
|
|
15834
|
+
var pluginsDir = ({ env, runtime }) => join35(env["CLAUDE_CONFIG_DIR"] ?? join35(runtime.homeDir(), ".claude"), "plugins");
|
|
15429
15835
|
var installRecord = (value, root) => {
|
|
15430
15836
|
const records = Array.isArray(value) ? value.filter(isRecord) : isRecord(value) ? [value] : [];
|
|
15431
15837
|
return records.find((record3) => record3["projectPath"] === root) ?? records[0];
|
|
15432
15838
|
};
|
|
15433
15839
|
var readJson = (env, path) => {
|
|
15434
|
-
const
|
|
15435
|
-
const parsed =
|
|
15840
|
+
const text9 = env.runtime.readText(path);
|
|
15841
|
+
const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
|
|
15436
15842
|
return parsed?.ok === true ? parsed.value : void 0;
|
|
15437
15843
|
};
|
|
15438
15844
|
var installedPlugin = (env) => {
|
|
15439
|
-
const index = readJson(env, toAbsolutePath(
|
|
15845
|
+
const index = readJson(env, toAbsolutePath(join35(pluginsDir(env), "installed_plugins.json")));
|
|
15440
15846
|
const record3 = installRecord(asRecord(index?.["plugins"])[PLUGIN_ID], env.root);
|
|
15441
15847
|
const installPath = record3 === void 0 ? void 0 : optionalString(record3, "installPath");
|
|
15442
15848
|
if (record3 === void 0 || installPath === void 0) return void 0;
|
|
15443
15849
|
const root = toAbsolutePath(installPath);
|
|
15444
|
-
const manifest = readJson(env, toAbsolutePath(
|
|
15445
|
-
const hooksPath = toAbsolutePath(
|
|
15446
|
-
const
|
|
15447
|
-
const parsed =
|
|
15850
|
+
const manifest = readJson(env, toAbsolutePath(join35(root, ".claude-plugin", "plugin.json")));
|
|
15851
|
+
const hooksPath = toAbsolutePath(join35(root, "hooks", "hooks.json"));
|
|
15852
|
+
const text9 = env.runtime.readText(hooksPath);
|
|
15853
|
+
const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
|
|
15448
15854
|
return {
|
|
15449
15855
|
root,
|
|
15450
15856
|
version: (manifest === void 0 ? void 0 : optionalString(manifest, "version")) ?? optionalString(record3, "version"),
|
|
15451
15857
|
hooksFile: parsed?.ok === true ? { source: "plugin", path: hooksPath, hooks: parsed.value["hooks"] } : void 0,
|
|
15452
|
-
hooksText:
|
|
15858
|
+
hooksText: text9.ok ? text9.value : void 0
|
|
15453
15859
|
};
|
|
15454
15860
|
};
|
|
15455
15861
|
var PRECEDENCE2 = ["managed", "local", "project", "user"];
|
|
@@ -15479,15 +15885,15 @@ var schemaFinding = (file, problems, claude) => problems.length === 0 ? [] : [
|
|
|
15479
15885
|
)
|
|
15480
15886
|
];
|
|
15481
15887
|
var unreadableProjectFiles = (env) => settingsPaths(env).filter(({ scope: scope2 }) => scope2 === "project" || scope2 === "local").flatMap(({ path }) => {
|
|
15482
|
-
const
|
|
15483
|
-
return
|
|
15888
|
+
const text9 = env.runtime.readText(path);
|
|
15889
|
+
return text9.ok && text9.value.trim() !== "" && !parseJsonObject(text9.value).ok ? [path] : [];
|
|
15484
15890
|
});
|
|
15485
15891
|
var schemaFindings = (env, plugin, claude) => {
|
|
15486
15892
|
const label2 = claude === void 0 ? "?" : claude.join(".");
|
|
15487
15893
|
const files = [...projectHookFiles(env), ...plugin?.hooksFile === void 0 ? [] : [plugin.hooksFile]];
|
|
15488
15894
|
const invalidJson = [
|
|
15489
15895
|
...unreadableProjectFiles(env),
|
|
15490
|
-
...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(
|
|
15896
|
+
...plugin?.hooksText !== void 0 && plugin.hooksFile === void 0 ? [toAbsolutePath(join36(plugin.root, "hooks", "hooks.json"))] : []
|
|
15491
15897
|
];
|
|
15492
15898
|
return [
|
|
15493
15899
|
...invalidJson.flatMap((file) => schemaFinding(file, ["not valid JSON"], label2)),
|
|
@@ -15508,7 +15914,7 @@ var pluginFindings = (env, projectOwn) => {
|
|
|
15508
15914
|
];
|
|
15509
15915
|
};
|
|
15510
15916
|
var coverageFindings = (env, entries) => {
|
|
15511
|
-
const installed = env.runtime.stat(toAbsolutePath(
|
|
15917
|
+
const installed = env.runtime.stat(toAbsolutePath(join36(env.root, ...LOCK_FILE.split("/")))) !== void 0;
|
|
15512
15918
|
if (entries.length === 0 && !installed) {
|
|
15513
15919
|
return [finding("hooks.registered", "warn", "doctor.hooks.none", {}, "doctor.hooks.fix_none")];
|
|
15514
15920
|
}
|
|
@@ -15560,21 +15966,21 @@ var checkHooks = (env) => {
|
|
|
15560
15966
|
};
|
|
15561
15967
|
|
|
15562
15968
|
// src/doctor/instructions.ts
|
|
15563
|
-
import { join as
|
|
15969
|
+
import { join as join37 } from "node:path";
|
|
15564
15970
|
var INSTRUCTION_LINE_CEILING = 200;
|
|
15565
15971
|
var TOP_INSTRUCTIONS = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md"];
|
|
15566
15972
|
var RULES_DIR = ".claude/rules";
|
|
15567
15973
|
var SHOWN = 3;
|
|
15568
15974
|
var readProjectFile = (env, relative11) => {
|
|
15569
|
-
const
|
|
15570
|
-
return
|
|
15975
|
+
const text9 = env.runtime.readText(toAbsolutePath(join37(env.root, ...relative11.split("/"))));
|
|
15976
|
+
return text9.ok ? text9.value : void 0;
|
|
15571
15977
|
};
|
|
15572
15978
|
var instructionFiles = (env) => {
|
|
15573
|
-
const listed4 = env.runtime.listDir(toAbsolutePath(
|
|
15979
|
+
const listed4 = env.runtime.listDir(toAbsolutePath(join37(env.root, ...RULES_DIR.split("/"))));
|
|
15574
15980
|
const rules = listed4.ok ? listed4.value.filter((name) => name.endsWith(".md")).map((name) => `${RULES_DIR}/${name}`) : [];
|
|
15575
15981
|
return [...TOP_INSTRUCTIONS, ...rules].flatMap((path) => {
|
|
15576
|
-
const
|
|
15577
|
-
return
|
|
15982
|
+
const text9 = readProjectFile(env, path);
|
|
15983
|
+
return text9 === void 0 ? [] : [{ path, text: text9 }];
|
|
15578
15984
|
});
|
|
15579
15985
|
};
|
|
15580
15986
|
var shortList = (items) => items.length <= SHOWN ? items.join(", ") : `${items.slice(0, SHOWN).join(", ")} (+${String(items.length - SHOWN)})`;
|
|
@@ -15622,10 +16028,10 @@ var hiddenInstructions = (env, deny2) => {
|
|
|
15622
16028
|
];
|
|
15623
16029
|
};
|
|
15624
16030
|
var localFile = (env) => readProjectFile(env, "CLAUDE.local.md") === void 0 ? [] : [finding("instructions.local", "warn", "doctor.instructions.local", {}, "doctor.instructions.fix_local")];
|
|
15625
|
-
var lineCount = (
|
|
16031
|
+
var lineCount = (text9) => text9.split("\n").length - (text9.endsWith("\n") ? 1 : 0);
|
|
15626
16032
|
var oversized = (env) => ["AGENTS.md", "CLAUDE.md"].flatMap((file) => {
|
|
15627
|
-
const
|
|
15628
|
-
const lines3 =
|
|
16033
|
+
const text9 = readProjectFile(env, file);
|
|
16034
|
+
const lines3 = text9 === void 0 ? 0 : lineCount(text9);
|
|
15629
16035
|
if (lines3 <= INSTRUCTION_LINE_CEILING) return [];
|
|
15630
16036
|
return [
|
|
15631
16037
|
finding(
|
|
@@ -15638,13 +16044,13 @@ var oversized = (env) => ["AGENTS.md", "CLAUDE.md"].flatMap((file) => {
|
|
|
15638
16044
|
];
|
|
15639
16045
|
});
|
|
15640
16046
|
var MIN_RULE_WORDS = 6;
|
|
15641
|
-
var ruleLines = (
|
|
16047
|
+
var ruleLines = (text9) => text9.split("\n").map((raw) => withoutCarriageReturn(raw).trim()).filter((line2) => !/^(#|\||```|@|>)/.test(line2)).map(
|
|
15642
16048
|
(line2) => line2.replace(/^([-*+]|\d+[.)])\s+/, "").replace(/\s+/g, " ").toLowerCase()
|
|
15643
16049
|
).filter((line2) => line2.split(" ").length >= MIN_RULE_WORDS);
|
|
15644
16050
|
var duplicatedRules = (files) => {
|
|
15645
16051
|
const seen = /* @__PURE__ */ new Map();
|
|
15646
|
-
for (const { path, text:
|
|
15647
|
-
for (const line2 of ruleLines(
|
|
16052
|
+
for (const { path, text: text9 } of files) {
|
|
16053
|
+
for (const line2 of ruleLines(text9)) seen.set(line2, (seen.get(line2) ?? /* @__PURE__ */ new Set()).add(path));
|
|
15648
16054
|
}
|
|
15649
16055
|
return [...seen].filter(([, paths]) => paths.size > 1).map(([line2]) => line2);
|
|
15650
16056
|
};
|
|
@@ -15675,12 +16081,12 @@ var checkInstructions = (env) => {
|
|
|
15675
16081
|
};
|
|
15676
16082
|
|
|
15677
16083
|
// src/doctor/line-endings.ts
|
|
15678
|
-
import { join as
|
|
16084
|
+
import { join as join38 } from "node:path";
|
|
15679
16085
|
var LF_RULE = /^\*\s.*\beol=lf\b/m;
|
|
15680
16086
|
var checkLineEndings = ({ root, runtime }) => {
|
|
15681
16087
|
const autocrlf = runtime.git(["config", "--get", "core.autocrlf"], root);
|
|
15682
16088
|
if (!autocrlf.ok || autocrlf.stdout.trim().toLowerCase() !== "true") return [];
|
|
15683
|
-
const attributes = runtime.readText(toAbsolutePath(
|
|
16089
|
+
const attributes = runtime.readText(toAbsolutePath(join38(root, ".gitattributes")));
|
|
15684
16090
|
if (attributes.ok && LF_RULE.test(attributes.value)) return [];
|
|
15685
16091
|
return [
|
|
15686
16092
|
finding("git.autocrlf", "warn", "doctor.git.autocrlf", { file: ".gitattributes" }, "doctor.git.fix_autocrlf")
|
|
@@ -15688,25 +16094,25 @@ var checkLineEndings = ({ root, runtime }) => {
|
|
|
15688
16094
|
};
|
|
15689
16095
|
|
|
15690
16096
|
// src/doctor/memory.ts
|
|
15691
|
-
import { join as
|
|
16097
|
+
import { join as join39 } from "node:path";
|
|
15692
16098
|
var ceilingOf = (config) => {
|
|
15693
16099
|
const value = configAt(parseConfigYaml(config ?? ""), "memory", "maxLines");
|
|
15694
16100
|
const parsed = typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
|
|
15695
16101
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : AGENTS_MAX_LINES;
|
|
15696
16102
|
};
|
|
15697
|
-
var lineCount2 = (
|
|
16103
|
+
var lineCount2 = (text9) => text9.replace(/\r?\n$/, "").split(/\r?\n/).length;
|
|
15698
16104
|
var checkAgentsFile = ({ root, runtime }) => {
|
|
15699
|
-
const
|
|
15700
|
-
if (!
|
|
15701
|
-
const config = runtime.readText(toAbsolutePath(
|
|
16105
|
+
const text9 = runtime.readText(toAbsolutePath(join39(root, AGENTS_FILE)));
|
|
16106
|
+
if (!text9.ok) return [];
|
|
16107
|
+
const config = runtime.readText(toAbsolutePath(join39(root, CONFIG_FILE2)));
|
|
15702
16108
|
const max = ceilingOf(config.ok ? config.value : void 0);
|
|
15703
|
-
const lines3 = lineCount2(
|
|
16109
|
+
const lines3 = lineCount2(text9.value);
|
|
15704
16110
|
const params2 = { file: AGENTS_FILE, lines: lines3, max };
|
|
15705
16111
|
return lines3 > max ? [finding("memory.agents_lines", "fail", "doctor.memory.too_long", params2, "doctor.memory.fix_too_long")] : [finding("memory.agents_lines", "ok", "doctor.memory.lines", params2)];
|
|
15706
16112
|
};
|
|
15707
16113
|
|
|
15708
16114
|
// src/doctor/project-files.ts
|
|
15709
|
-
import { join as
|
|
16115
|
+
import { join as join40 } from "node:path";
|
|
15710
16116
|
var variants = (base, extensions) => extensions.map((extension) => `${base}${extension}`);
|
|
15711
16117
|
var SCRIPT = [".js", ".cjs", ".mjs", ".ts", ".cts", ".mts"];
|
|
15712
16118
|
var DATA = ["", ".json", ".yml", ".yaml"];
|
|
@@ -15737,7 +16143,7 @@ var CONFIG_GROUPS = [
|
|
|
15737
16143
|
files: [...variants("commitlint.config", SCRIPT), ...variants(".commitlintrc", [...DATA, ".js"])]
|
|
15738
16144
|
}
|
|
15739
16145
|
];
|
|
15740
|
-
var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(
|
|
16146
|
+
var present = (env, files) => files.filter((file) => env.runtime.stat(toAbsolutePath(join40(env.root, file)))?.kind === "file");
|
|
15741
16147
|
var crowded = (env, groups, build) => groups.flatMap(({ name, files }) => {
|
|
15742
16148
|
const found = present(env, files);
|
|
15743
16149
|
return found.length > 1 ? [build(name, found.join(", "))] : [];
|
|
@@ -15881,9 +16287,9 @@ var manualLines = (ctx, report2) => report2.checks.some((item2) => item2.status
|
|
|
15881
16287
|
var writeReport = (ctx, data, result) => {
|
|
15882
16288
|
const env = doctorEnvFrom(ctx);
|
|
15883
16289
|
const path = reportPath(env);
|
|
15884
|
-
const
|
|
16290
|
+
const text9 = reportMarkdown(env, ctx, toEnvelope("doctor", result));
|
|
15885
16291
|
const dir = ctx.runtime.mkdir(toAbsolutePath(dirname7(path)));
|
|
15886
|
-
const written = dir.ok ? ctx.runtime.writeAtomic(path,
|
|
16292
|
+
const written = dir.ok ? ctx.runtime.writeAtomic(path, text9) : dir;
|
|
15887
16293
|
if (!written.ok) return failureFrom(ctx.t, applyProblem({ path, detail: written.error }));
|
|
15888
16294
|
const human2 = [...result.human ?? [], ctx.t("doctor.report.written", { path })];
|
|
15889
16295
|
return { ...result, data: { ...data, reportPath: path }, human: human2 };
|
|
@@ -15914,6 +16320,100 @@ var doctorCommand = {
|
|
|
15914
16320
|
run: (ctx, args2) => Promise.resolve(runDoctor(ctx, args2.values["fix"] === true, args2.values["report"] === true))
|
|
15915
16321
|
};
|
|
15916
16322
|
|
|
16323
|
+
// src/gain/gain.ts
|
|
16324
|
+
import { join as join41 } from "node:path";
|
|
16325
|
+
var GAIN_SCHEMA = "finitemonkey/gain@1";
|
|
16326
|
+
var DAY_MS2 = 864e5;
|
|
16327
|
+
var SAVE_WINDOW_MS = DAY_MS2;
|
|
16328
|
+
var readLog = (runtime, paths) => {
|
|
16329
|
+
const text9 = runtime.readText(paths.log);
|
|
16330
|
+
if (!text9.ok) return [];
|
|
16331
|
+
return text9.value.split("\n").flatMap((line2) => {
|
|
16332
|
+
const parsed = parseJsonObject(line2);
|
|
16333
|
+
if (!parsed.ok || !isRecord(parsed.value)) return [];
|
|
16334
|
+
const { at, event } = parsed.value;
|
|
16335
|
+
const time = typeof at === "string" ? Date.parse(at) : Number.NaN;
|
|
16336
|
+
return typeof event === "string" && Number.isFinite(time) ? [{ at: time, event }] : [];
|
|
16337
|
+
});
|
|
16338
|
+
};
|
|
16339
|
+
var readGates = (runtime, paths) => {
|
|
16340
|
+
const listed4 = runtime.listDir(toAbsolutePath(join41(paths.dir, "evidence")));
|
|
16341
|
+
const files = listed4.ok ? listed4.value.filter((name) => name.endsWith(".jsonl")) : [];
|
|
16342
|
+
return files.flatMap((name) => readLedger2(runtime, paths.evidence(name.replace(/\.jsonl$/, "")))).filter((entry2) => entry2.check === GATE_CHECK);
|
|
16343
|
+
};
|
|
16344
|
+
var countsIn = (log, gates, from, to) => {
|
|
16345
|
+
const inWindow = (at) => at >= from && at < to;
|
|
16346
|
+
const lines3 = log.filter((line2) => inWindow(line2.at));
|
|
16347
|
+
const runs = gates.filter((entry2) => inWindow(Date.parse(entry2.at)));
|
|
16348
|
+
const passedAt = gates.filter((entry2) => entry2.status === "passed").map((entry2) => Date.parse(entry2.at));
|
|
16349
|
+
const blocks = lines3.filter((line2) => line2.event === "guard_block");
|
|
16350
|
+
return {
|
|
16351
|
+
blocks: blocks.length,
|
|
16352
|
+
saves: blocks.filter((block4) => passedAt.some((at) => at > block4.at && at - block4.at <= SAVE_WINDOW_MS)).length,
|
|
16353
|
+
releases: lines3.filter((line2) => line2.event === "person.allow").length,
|
|
16354
|
+
falsePositives: lines3.filter((line2) => line2.event === "person.allow").length + lines3.filter((line2) => line2.event === "guard_false_positive").length,
|
|
16355
|
+
gateRuns: runs.length,
|
|
16356
|
+
gateFailed: runs.filter((entry2) => entry2.status === "failed").length,
|
|
16357
|
+
gateUnverified: runs.filter((entry2) => entry2.status === "unverified").length,
|
|
16358
|
+
gateMs: runs.reduce((sum, entry2) => sum + (entry2.durationMs ?? 0), 0)
|
|
16359
|
+
};
|
|
16360
|
+
};
|
|
16361
|
+
var gainReport = (runtime, paths, days) => {
|
|
16362
|
+
const to = Date.parse(runtime.now());
|
|
16363
|
+
const from = to - days * DAY_MS2;
|
|
16364
|
+
const log = readLog(runtime, paths);
|
|
16365
|
+
const gates = readGates(runtime, paths);
|
|
16366
|
+
return {
|
|
16367
|
+
schema: GAIN_SCHEMA,
|
|
16368
|
+
days,
|
|
16369
|
+
from: new Date(from).toISOString(),
|
|
16370
|
+
to: new Date(to).toISOString(),
|
|
16371
|
+
current: countsIn(log, gates, from, to + 1),
|
|
16372
|
+
previous: countsIn(log, gates, from - days * DAY_MS2, from)
|
|
16373
|
+
};
|
|
16374
|
+
};
|
|
16375
|
+
|
|
16376
|
+
// src/commands/gain.ts
|
|
16377
|
+
var DEFAULT_DAYS = 30;
|
|
16378
|
+
var trend = (ctx, now, before) => now === before ? "" : ` (${ctx.t(now > before ? "gain.up" : "gain.down", { delta: Math.abs(now - before) })})`;
|
|
16379
|
+
var ROWS = [
|
|
16380
|
+
{ key: "gain.row.blocks", pick: (counts) => counts.blocks },
|
|
16381
|
+
{ key: "gain.row.saves", pick: (counts) => counts.saves },
|
|
16382
|
+
{ key: "gain.row.false_positives", pick: (counts) => counts.falsePositives },
|
|
16383
|
+
{ key: "gain.row.gate_runs", pick: (counts) => counts.gateRuns },
|
|
16384
|
+
{ key: "gain.row.gate_failed", pick: (counts) => counts.gateFailed },
|
|
16385
|
+
{ key: "gain.row.gate_unverified", pick: (counts) => counts.gateUnverified }
|
|
16386
|
+
];
|
|
16387
|
+
var linesOf = (ctx, report2) => [
|
|
16388
|
+
ctx.t("gain.title", { days: report2.days }),
|
|
16389
|
+
...ROWS.map(
|
|
16390
|
+
({ key, pick: pick4 }) => ` ${ctx.t(key, { count: pick4(report2.current) })}${trend(ctx, pick4(report2.current), pick4(report2.previous))}`
|
|
16391
|
+
),
|
|
16392
|
+
` ${ctx.t("gain.row.gate_time", { minutes: Math.round(report2.current.gateMs / 6e4) })}`,
|
|
16393
|
+
ctx.t("gain.footer")
|
|
16394
|
+
];
|
|
16395
|
+
var runGain = (ctx, rawDays) => {
|
|
16396
|
+
const days = rawDays === void 0 ? DEFAULT_DAYS : Number.parseInt(rawDays, 10);
|
|
16397
|
+
if (!Number.isInteger(days) || days < 1 || days > 365) {
|
|
16398
|
+
return failureFrom(
|
|
16399
|
+
ctx.t,
|
|
16400
|
+
usageProblem("invalid_arguments", "error.invalid_arguments", "finitemonkey gain --days 30", {
|
|
16401
|
+
detail: "--days 1-365"
|
|
16402
|
+
})
|
|
16403
|
+
);
|
|
16404
|
+
}
|
|
16405
|
+
const report2 = gainReport(ctx.runtime, fmPaths(findRepoRoot(ctx.runtime, ctx.cwd) ?? ctx.cwd), days);
|
|
16406
|
+
return success(report2, linesOf(ctx, report2));
|
|
16407
|
+
};
|
|
16408
|
+
var gainCommand = {
|
|
16409
|
+
path: ["gain"],
|
|
16410
|
+
summaryKey: "command.gain.summary",
|
|
16411
|
+
usage: "finitemonkey gain [--days 1-365]",
|
|
16412
|
+
status: "ready",
|
|
16413
|
+
options: { days: { type: "string" } },
|
|
16414
|
+
run: (ctx, args2) => Promise.resolve(runGain(ctx, stringOption(args2.values, "days")))
|
|
16415
|
+
};
|
|
16416
|
+
|
|
15917
16417
|
// src/gate/baseline-run.ts
|
|
15918
16418
|
var runBaseline = (options) => {
|
|
15919
16419
|
const ready = prepareGate({ ...options, noSkip: true });
|
|
@@ -16178,17 +16678,6 @@ var checksResult = (t, schema, code, checks) => {
|
|
|
16178
16678
|
};
|
|
16179
16679
|
};
|
|
16180
16680
|
|
|
16181
|
-
// src/commands/git-problems.ts
|
|
16182
|
-
var notRepository = (dir) => ({
|
|
16183
|
-
code: "not_a_git_repo",
|
|
16184
|
-
messageKey: "error.not_a_git_repo",
|
|
16185
|
-
params: { dir },
|
|
16186
|
-
fix: "git init",
|
|
16187
|
-
exitCode: ExitCode.NotConfigured
|
|
16188
|
-
});
|
|
16189
|
-
var gitProblem = (code, messageKey, detail, fix, exitCode = ExitCode.CheckFailed) => ({ code, messageKey, params: { detail }, fix, exitCode });
|
|
16190
|
-
var repoOf2 = (ctx) => openRepo(ctx.runtime, ctx.cwd);
|
|
16191
|
-
|
|
16192
16681
|
// src/commands/git-checks.ts
|
|
16193
16682
|
var COMMIT_MSG_SCHEMA = "finitemonkey/commit-msg@1";
|
|
16194
16683
|
var PUSH_CHECK_SCHEMA = "finitemonkey/push-check@1";
|
|
@@ -16209,15 +16698,15 @@ var commitMsgCommand = {
|
|
|
16209
16698
|
usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: "<message-file>" })
|
|
16210
16699
|
)
|
|
16211
16700
|
);
|
|
16212
|
-
const
|
|
16213
|
-
if (!
|
|
16701
|
+
const text9 = ctx.runtime.readText(toAbsolutePath(file, ctx.cwd));
|
|
16702
|
+
if (!text9.ok)
|
|
16214
16703
|
return Promise.resolve(
|
|
16215
16704
|
failureFrom(
|
|
16216
16705
|
ctx.t,
|
|
16217
16706
|
usageProblem("invalid_arguments", "error.invalid_arguments", COMMIT_MSG_USAGE, { detail: file })
|
|
16218
16707
|
)
|
|
16219
16708
|
);
|
|
16220
|
-
const failures = commitMessageProblems(
|
|
16709
|
+
const failures = commitMessageProblems(text9.value);
|
|
16221
16710
|
const fix = "git commit --amend, using type(scope): subject and no AI attribution lines";
|
|
16222
16711
|
return Promise.resolve(
|
|
16223
16712
|
checksResult(ctx.t, COMMIT_MSG_SCHEMA, "githook_failed", [{ id: "commit-messages", failures, fix }])
|
|
@@ -16231,7 +16720,7 @@ var pushCheckCommand = {
|
|
|
16231
16720
|
status: "ready",
|
|
16232
16721
|
maxPositionals: 2,
|
|
16233
16722
|
run: async (ctx, args2) => {
|
|
16234
|
-
const repo =
|
|
16723
|
+
const repo = repoOf(ctx);
|
|
16235
16724
|
if (repo === void 0) return failureFrom(ctx.t, notRepository(ctx.cwd));
|
|
16236
16725
|
const piped = parsePushUpdates(ctx.io.stdin.isTTY === true ? "" : await readAll(ctx.io.stdin));
|
|
16237
16726
|
const updates = piped.length > 0 ? piped : updatesFromEnv(ctx.env);
|
|
@@ -16246,7 +16735,7 @@ var ciGateCommand = {
|
|
|
16246
16735
|
status: "ready",
|
|
16247
16736
|
options: { base: { type: "string" } },
|
|
16248
16737
|
run: (ctx, args2) => {
|
|
16249
|
-
const repo =
|
|
16738
|
+
const repo = repoOf(ctx);
|
|
16250
16739
|
if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
|
|
16251
16740
|
const base = ciBase(ctx.runtime, repo, ctx.env, stringOption(args2.values, "base"));
|
|
16252
16741
|
if (!baseExists(ctx.runtime, repo, base))
|
|
@@ -16332,7 +16821,7 @@ var mergeVerifyCommand = {
|
|
|
16332
16821
|
options: { since: { type: "string" }, dropped: { type: "string" } },
|
|
16333
16822
|
maxPositionals: 1,
|
|
16334
16823
|
run: (ctx, args2) => {
|
|
16335
|
-
const repo =
|
|
16824
|
+
const repo = repoOf(ctx);
|
|
16336
16825
|
if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
|
|
16337
16826
|
const dropped = stringOption(args2.values, "dropped");
|
|
16338
16827
|
const targets2 = targetsOf2(ctx, repo, args2.positionals[0], stringOption(args2.values, "since"));
|
|
@@ -16355,11 +16844,11 @@ var mergeVerifyCommand = {
|
|
|
16355
16844
|
var REFLOG_LIMIT = 300;
|
|
16356
16845
|
var LIST_LIMIT = 50;
|
|
16357
16846
|
var SHA = /^[0-9a-f]{40,64}$/;
|
|
16358
|
-
var
|
|
16847
|
+
var linesOf2 = (text9) => text9.split("\n").filter((line2) => line2.trim() !== "");
|
|
16359
16848
|
var reflogLines = (runtime, repo) => {
|
|
16360
16849
|
const result = runtime.git(["reflog", "show", "--all", `-n${String(REFLOG_LIMIT)}`, "--format=%H %gD"], repo.root);
|
|
16361
16850
|
if (!result.ok) return [];
|
|
16362
|
-
return
|
|
16851
|
+
return linesOf2(result.stdout).flatMap((line2) => {
|
|
16363
16852
|
const [sha, ref] = line2.split(" ");
|
|
16364
16853
|
return sha === void 0 || ref === void 0 || !SHA.test(sha) || ref.startsWith("refs/finitemonkey/") ? [] : [{ sha, ref }];
|
|
16365
16854
|
});
|
|
@@ -16367,11 +16856,11 @@ var reflogLines = (runtime, repo) => {
|
|
|
16367
16856
|
var unreachable = (runtime, repo, shas) => {
|
|
16368
16857
|
if (shas.length === 0) return /* @__PURE__ */ new Set();
|
|
16369
16858
|
const result = runtime.git(["rev-list", ...shas, "--not", "--branches", "--tags", "--remotes"], repo.root);
|
|
16370
|
-
return new Set(result.ok ?
|
|
16859
|
+
return new Set(result.ok ? linesOf2(result.stdout) : []);
|
|
16371
16860
|
};
|
|
16372
16861
|
var fsckDangling = (runtime, repo) => {
|
|
16373
16862
|
const result = runtime.git(["fsck", "--no-reflogs", "--no-progress", "--dangling"], repo.root);
|
|
16374
|
-
const lines3 =
|
|
16863
|
+
const lines3 = linesOf2(result.stdout);
|
|
16375
16864
|
const pick4 = (kind) => lines3.flatMap((line2) => {
|
|
16376
16865
|
const [label2, type, sha] = line2.split(" ");
|
|
16377
16866
|
return label2 === "dangling" && type === kind && sha !== void 0 ? [sha] : [];
|
|
@@ -16382,7 +16871,7 @@ var describe = (runtime, repo, shas) => {
|
|
|
16382
16871
|
if (shas.length === 0) return /* @__PURE__ */ new Map();
|
|
16383
16872
|
const result = runtime.git(["log", "--no-walk=unsorted", "--format=%H%x09%cr%x09%s", ...shas], repo.root);
|
|
16384
16873
|
return new Map(
|
|
16385
|
-
|
|
16874
|
+
linesOf2(result.stdout).flatMap((line2) => {
|
|
16386
16875
|
const [sha, when, ...subject2] = line2.split(" ");
|
|
16387
16876
|
return sha === void 0 ? [] : [[sha, [when ?? "", subject2.join(" ")]]];
|
|
16388
16877
|
})
|
|
@@ -16444,7 +16933,7 @@ var rescueCommand = {
|
|
|
16444
16933
|
status: "ready",
|
|
16445
16934
|
options: { restore: { type: "string" }, branch: { type: "string" } },
|
|
16446
16935
|
run: (ctx, args2) => {
|
|
16447
|
-
const repo =
|
|
16936
|
+
const repo = repoOf(ctx);
|
|
16448
16937
|
if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
|
|
16449
16938
|
const sha = stringOption(args2.values, "restore");
|
|
16450
16939
|
const branch = stringOption(args2.values, "branch");
|
|
@@ -16476,7 +16965,7 @@ var rescueCommand = {
|
|
|
16476
16965
|
};
|
|
16477
16966
|
|
|
16478
16967
|
// src/git/undo.ts
|
|
16479
|
-
import { basename as basename3, dirname as dirname8, join as
|
|
16968
|
+
import { basename as basename3, dirname as dirname8, join as join42 } from "node:path";
|
|
16480
16969
|
var INDEX_SUBJECT = "finitemonkey index:";
|
|
16481
16970
|
var gitLine = (runtime, repo, args2) => {
|
|
16482
16971
|
const result = runtime.git(args2, repo.root);
|
|
@@ -16519,7 +17008,7 @@ var findSnapshot = (views, wanted) => {
|
|
|
16519
17008
|
};
|
|
16520
17009
|
var stampOf2 = (iso) => iso.replace(/[:.]/g, "-");
|
|
16521
17010
|
var worktreePath = (runtime, repo) => {
|
|
16522
|
-
const base =
|
|
17011
|
+
const base = join42(dirname8(repo.root), `${basename3(repo.root)}-undo-${stampOf2(runtime.now())}`);
|
|
16523
17012
|
return toAbsolutePath(runtime.stat(toAbsolutePath(base)) === void 0 ? base : `${base}-2`);
|
|
16524
17013
|
};
|
|
16525
17014
|
var intoWorktree = (runtime, repo, view2) => {
|
|
@@ -16563,7 +17052,7 @@ var snapshotCommand = {
|
|
|
16563
17052
|
status: "ready",
|
|
16564
17053
|
options: { message: { type: "string", short: "m" } },
|
|
16565
17054
|
run: (ctx, args2) => {
|
|
16566
|
-
const repo =
|
|
17055
|
+
const repo = repoOf(ctx);
|
|
16567
17056
|
if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
|
|
16568
17057
|
const message = args2.values["message"];
|
|
16569
17058
|
const op = typeof message === "string" && message.trim() !== "" ? message.trim() : "manual";
|
|
@@ -16596,7 +17085,7 @@ var undoCommand = {
|
|
|
16596
17085
|
options: { list: { type: "boolean" }, "in-place": { type: "boolean" } },
|
|
16597
17086
|
maxPositionals: 1,
|
|
16598
17087
|
run: (ctx, args2) => {
|
|
16599
|
-
const repo =
|
|
17088
|
+
const repo = repoOf(ctx);
|
|
16600
17089
|
if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
|
|
16601
17090
|
const views = snapshotViews(ctx.runtime, repo);
|
|
16602
17091
|
if (args2.values["list"] === true) return Promise.resolve(listResult(ctx, views));
|
|
@@ -16644,7 +17133,7 @@ var GLOBAL_FLAGS = [
|
|
|
16644
17133
|
{ flag: "--help, -h", key: "flag.help" }
|
|
16645
17134
|
];
|
|
16646
17135
|
var exitCodeHelp = (ctx) => EXIT_CODE_NAMES.map((name) => ({ code: ExitCode[name], name, summary: ctx.t(exitCodeDocs[ExitCode[name]]) }));
|
|
16647
|
-
var pad = (
|
|
17136
|
+
var pad = (text9, width) => text9.padEnd(Math.max(width, text9.length + 1));
|
|
16648
17137
|
var describe2 = (ctx, spec2) => ({
|
|
16649
17138
|
name: commandName(spec2),
|
|
16650
17139
|
usage: spec2.usage,
|
|
@@ -16754,7 +17243,7 @@ var hookCommand = {
|
|
|
16754
17243
|
};
|
|
16755
17244
|
|
|
16756
17245
|
// src/commands/i18n-check.ts
|
|
16757
|
-
import { join as
|
|
17246
|
+
import { join as join43 } from "node:path";
|
|
16758
17247
|
|
|
16759
17248
|
// src/i18n/message.ts
|
|
16760
17249
|
var ARG = /^\s*([a-zA-Z][a-zA-Z0-9_]*)\s*(?:,\s*(plural|select)\s*,)?/;
|
|
@@ -16786,10 +17275,10 @@ var parseBranches = (body2, inPlural) => {
|
|
|
16786
17275
|
};
|
|
16787
17276
|
var parseNodes = (source, inPlural) => {
|
|
16788
17277
|
const nodes = [];
|
|
16789
|
-
let
|
|
17278
|
+
let text9 = "";
|
|
16790
17279
|
const flush2 = () => {
|
|
16791
|
-
if (
|
|
16792
|
-
|
|
17280
|
+
if (text9 !== "") nodes.push({ kind: "text", text: text9 });
|
|
17281
|
+
text9 = "";
|
|
16793
17282
|
};
|
|
16794
17283
|
for (let index = 0; index < source.length; index += 1) {
|
|
16795
17284
|
const char = source.charAt(index);
|
|
@@ -16799,7 +17288,7 @@ var parseNodes = (source, inPlural) => {
|
|
|
16799
17288
|
continue;
|
|
16800
17289
|
}
|
|
16801
17290
|
if (char !== "{") {
|
|
16802
|
-
|
|
17291
|
+
text9 += char;
|
|
16803
17292
|
continue;
|
|
16804
17293
|
}
|
|
16805
17294
|
const cursor = { source, index: index + 1 };
|
|
@@ -16807,18 +17296,18 @@ var parseNodes = (source, inPlural) => {
|
|
|
16807
17296
|
const inner = end < 0 ? void 0 : source.slice(index + 1, end);
|
|
16808
17297
|
const match = inner === void 0 ? null : ARG.exec(inner);
|
|
16809
17298
|
if (inner === void 0 || match === null) {
|
|
16810
|
-
|
|
17299
|
+
text9 += char;
|
|
16811
17300
|
continue;
|
|
16812
17301
|
}
|
|
16813
17302
|
const name = match[1] ?? "";
|
|
16814
17303
|
const type = match[2] === "plural" || match[2] === "select" ? match[2] : void 0;
|
|
16815
17304
|
if (type === void 0 && match[0].trim().length !== inner.trim().length) {
|
|
16816
|
-
|
|
17305
|
+
text9 += char;
|
|
16817
17306
|
continue;
|
|
16818
17307
|
}
|
|
16819
17308
|
const branches = type === void 0 ? void 0 : parseBranches(inner.slice(match[0].length), type === "plural");
|
|
16820
17309
|
if (type !== void 0 && branches === void 0) {
|
|
16821
|
-
|
|
17310
|
+
text9 += char;
|
|
16822
17311
|
continue;
|
|
16823
17312
|
}
|
|
16824
17313
|
flush2();
|
|
@@ -16941,8 +17430,8 @@ var RELEASE_HINTS = [
|
|
|
16941
17430
|
/\bFINITEMONKEY_[A-Z_]+/,
|
|
16942
17431
|
/\b[A-Z][A-Z0-9_]{2,}=/
|
|
16943
17432
|
];
|
|
16944
|
-
var releaseHints = (
|
|
16945
|
-
const match = pattern.exec(
|
|
17433
|
+
var releaseHints = (text9) => RELEASE_HINTS.flatMap((pattern) => {
|
|
17434
|
+
const match = pattern.exec(text9);
|
|
16946
17435
|
return match === null ? [] : [match[0]];
|
|
16947
17436
|
});
|
|
16948
17437
|
var hintProblems = (lang, catalog) => Object.keys(catalog).filter((key) => MODEL_FACING.test(key)).sort().flatMap((key) => {
|
|
@@ -16980,10 +17469,10 @@ var unreadable = (dir, file, detail) => ({
|
|
|
16980
17469
|
exitCode: ExitCode.NotConfigured
|
|
16981
17470
|
});
|
|
16982
17471
|
var readCatalog = (ctx, dir, lang) => {
|
|
16983
|
-
const file = toAbsolutePath(
|
|
16984
|
-
const
|
|
16985
|
-
if (!
|
|
16986
|
-
const parsed = parseJsonObject(
|
|
17472
|
+
const file = toAbsolutePath(join43(dir, `${lang}.json`));
|
|
17473
|
+
const text9 = ctx.runtime.readText(file);
|
|
17474
|
+
if (!text9.ok) return reject(unreadable(dir, file, text9.error));
|
|
17475
|
+
const parsed = parseJsonObject(text9.value);
|
|
16987
17476
|
return parsed.ok ? parsed : reject(unreadable(dir, file, parsed.error));
|
|
16988
17477
|
};
|
|
16989
17478
|
var loadCatalogs = (ctx, dir) => {
|
|
@@ -17003,8 +17492,8 @@ var describeProblem = (ctx, problem4) => {
|
|
|
17003
17492
|
return ` ${problem4.lang} ${problem4.key} ${kind}`;
|
|
17004
17493
|
};
|
|
17005
17494
|
var loadReview = (ctx, dir) => {
|
|
17006
|
-
const
|
|
17007
|
-
const parsed =
|
|
17495
|
+
const text9 = ctx.runtime.readText(toAbsolutePath(join43(dir, "review.json")));
|
|
17496
|
+
const parsed = text9.ok ? parseJsonObject(text9.value) : void 0;
|
|
17008
17497
|
if (parsed?.ok !== true) return void 0;
|
|
17009
17498
|
const statuses = (lang) => {
|
|
17010
17499
|
const value = parsed.value[lang];
|
|
@@ -17044,7 +17533,7 @@ var i18nCheckCommand = {
|
|
|
17044
17533
|
};
|
|
17045
17534
|
|
|
17046
17535
|
// src/history/look-back.ts
|
|
17047
|
-
import { join as
|
|
17536
|
+
import { join as join44 } from "node:path";
|
|
17048
17537
|
|
|
17049
17538
|
// src/history/patterns.ts
|
|
17050
17539
|
import { posix as posix5 } from "node:path";
|
|
@@ -17149,8 +17638,8 @@ var addResults = (draft, blocks) => {
|
|
|
17149
17638
|
let any = false;
|
|
17150
17639
|
for (const block4 of blocks) {
|
|
17151
17640
|
if (!isRecord(block4) || block4["type"] !== "tool_result" || typeof block4["tool_use_id"] !== "string") continue;
|
|
17152
|
-
const
|
|
17153
|
-
draft.results.set(block4["tool_use_id"], { failed: block4["is_error"] === true || FAILED_EXIT.test(
|
|
17641
|
+
const text9 = textOf3(block4["content"]);
|
|
17642
|
+
draft.results.set(block4["tool_use_id"], { failed: block4["is_error"] === true || FAILED_EXIT.test(text9), text: text9 });
|
|
17154
17643
|
any = true;
|
|
17155
17644
|
}
|
|
17156
17645
|
return any;
|
|
@@ -17180,7 +17669,7 @@ var parseTranscript = (jsonl) => {
|
|
|
17180
17669
|
// src/history/look-back.ts
|
|
17181
17670
|
var SAMPLES = 3;
|
|
17182
17671
|
var projectLogKey = (root) => root.replace(/[^A-Za-z0-9-]/g, "-");
|
|
17183
|
-
var projectLogDir = (runtime, root, env) =>
|
|
17672
|
+
var projectLogDir = (runtime, root, env) => join44(env["CLAUDE_CONFIG_DIR"] ?? join44(runtime.homeDir(), ".claude"), "projects", projectLogKey(root));
|
|
17184
17673
|
var lookBack = (runtime, root, env) => {
|
|
17185
17674
|
const dir = projectLogDir(runtime, root, env);
|
|
17186
17675
|
const listed4 = runtime.listDir(toAbsolutePath(dir));
|
|
@@ -17201,9 +17690,9 @@ var lookBack = (runtime, root, env) => {
|
|
|
17201
17690
|
};
|
|
17202
17691
|
let turns = 0;
|
|
17203
17692
|
for (const name of files) {
|
|
17204
|
-
const
|
|
17205
|
-
if (!
|
|
17206
|
-
for (const turn of parseTranscript(
|
|
17693
|
+
const text9 = runtime.readText(toAbsolutePath(join44(dir, name)));
|
|
17694
|
+
if (!text9.ok) continue;
|
|
17695
|
+
for (const turn of parseTranscript(text9.value)) {
|
|
17207
17696
|
turns += 1;
|
|
17208
17697
|
for (const hit3 of hitsIn(turn)) {
|
|
17209
17698
|
counts[hit3.pattern] += 1;
|
|
@@ -17251,7 +17740,7 @@ var walk2 = (a, b) => {
|
|
|
17251
17740
|
}
|
|
17252
17741
|
return ops;
|
|
17253
17742
|
};
|
|
17254
|
-
var splitLines = (
|
|
17743
|
+
var splitLines = (text9) => text9 === "" ? [] : text9.replace(/\r?\n$/, "").split(/\r?\n/);
|
|
17255
17744
|
var sharedPrefix = (a, b) => {
|
|
17256
17745
|
let count4 = 0;
|
|
17257
17746
|
while (count4 < a.length && count4 < b.length && a[count4] === b[count4]) count4 += 1;
|
|
@@ -17328,7 +17817,7 @@ var planData = (t, plan, mode, applied, backup) => ({
|
|
|
17328
17817
|
diff: planDiff(plan),
|
|
17329
17818
|
...backup === void 0 ? {} : { backup }
|
|
17330
17819
|
});
|
|
17331
|
-
var pad2 = (
|
|
17820
|
+
var pad2 = (text9, width) => text9.padEnd(width);
|
|
17332
17821
|
var planLines = (t, data, withDiff) => [
|
|
17333
17822
|
t("install.plan.title", { root: data.root, preset: data.preset }),
|
|
17334
17823
|
...data.actions.map(({ path, action }) => ` ${pad2(action, 13)}${path}`),
|
|
@@ -17453,7 +17942,7 @@ var applyAllows = (findings, allows, decided) => {
|
|
|
17453
17942
|
|
|
17454
17943
|
// src/look/node-capture.ts
|
|
17455
17944
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
17456
|
-
import { join as
|
|
17945
|
+
import { join as join45 } from "node:path";
|
|
17457
17946
|
|
|
17458
17947
|
// src/look/measure.ts
|
|
17459
17948
|
var measurePage = (doc) => {
|
|
@@ -17464,8 +17953,8 @@ var measurePage = (doc) => {
|
|
|
17464
17953
|
const walker = doc.createTreeWalker(doc.body, SHOW_TEXT);
|
|
17465
17954
|
const keys = /* @__PURE__ */ new Set();
|
|
17466
17955
|
for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
|
|
17467
|
-
const
|
|
17468
|
-
if (KEY2.test(
|
|
17956
|
+
const text9 = (node.textContent ?? "").trim();
|
|
17957
|
+
if (KEY2.test(text9) && !NOT_KEY.test(text9) || PLACEHOLDER2.test(text9)) keys.add(text9.slice(0, 80));
|
|
17469
17958
|
}
|
|
17470
17959
|
const small = [...doc.querySelectorAll("a, button, input, select, textarea, [role=button], [role=link]")].filter((element) => {
|
|
17471
17960
|
const box = element.getBoundingClientRect();
|
|
@@ -17498,8 +17987,8 @@ var slugOf = (route) => {
|
|
|
17498
17987
|
var watch = (page) => {
|
|
17499
17988
|
const errors = [];
|
|
17500
17989
|
const failed4 = [];
|
|
17501
|
-
const push = (list4,
|
|
17502
|
-
if (list4.length < MAX_EVENTS) list4.push(
|
|
17990
|
+
const push = (list4, text9) => {
|
|
17991
|
+
if (list4.length < MAX_EVENTS) list4.push(text9.slice(0, 200));
|
|
17503
17992
|
};
|
|
17504
17993
|
page.on("console", (message) => {
|
|
17505
17994
|
if (message.type() === "error" && !RESOURCE_ERROR.test(message.text())) push(errors, message.text());
|
|
@@ -17529,7 +18018,7 @@ var captureOne = async (browser, request, route, viewport) => {
|
|
|
17529
18018
|
const context = await browser.newContext({ viewport: { width: viewport, height: HEIGHT }, reducedMotion: "reduce" });
|
|
17530
18019
|
const page = await context.newPage();
|
|
17531
18020
|
const events = watch(page);
|
|
17532
|
-
const path =
|
|
18021
|
+
const path = join45(request.dir, `${slugOf(route)}-${String(viewport)}.png`);
|
|
17533
18022
|
const loadError = await loadAndShoot(page, `${request.baseUrl}${route}`, path, request.timeoutMs);
|
|
17534
18023
|
const measure = loadError === void 0 ? parseMeasure(await page.evaluate(MEASURE_SCRIPT)) : void 0;
|
|
17535
18024
|
await context.close();
|
|
@@ -17546,7 +18035,7 @@ var captureAll = async (browser, request) => {
|
|
|
17546
18035
|
|
|
17547
18036
|
// src/look/node-playwright.ts
|
|
17548
18037
|
import { createRequire } from "node:module";
|
|
17549
|
-
import { join as
|
|
18038
|
+
import { join as join46 } from "node:path";
|
|
17550
18039
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
17551
18040
|
var NAMES = ["playwright", "playwright-core"];
|
|
17552
18041
|
var isPlaywright = (value) => isRecord(value) && isRecord(value["chromium"]) && typeof value["chromium"]["launch"] === "function";
|
|
@@ -17561,7 +18050,7 @@ var tryLoad = async (base, name) => {
|
|
|
17561
18050
|
}
|
|
17562
18051
|
};
|
|
17563
18052
|
var loadPlaywright = async (root) => {
|
|
17564
|
-
for (const base of [
|
|
18053
|
+
for (const base of [join46(root, "package.json"), fileURLToPath3(import.meta.url)]) {
|
|
17565
18054
|
for (const name of NAMES) {
|
|
17566
18055
|
const found = await tryLoad(base, name);
|
|
17567
18056
|
if (found !== void 0) return found;
|
|
@@ -17582,8 +18071,8 @@ var launchBrowser = async (playwright) => {
|
|
|
17582
18071
|
|
|
17583
18072
|
// src/look/node-sheet.ts
|
|
17584
18073
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
17585
|
-
import { join as
|
|
17586
|
-
var escapeHtml = (
|
|
18074
|
+
import { join as join47 } from "node:path";
|
|
18075
|
+
var escapeHtml = (text9) => text9.replace(/[&<>"]/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[char] ?? char);
|
|
17587
18076
|
var card = (result) => {
|
|
17588
18077
|
const image = (() => {
|
|
17589
18078
|
try {
|
|
@@ -17605,7 +18094,7 @@ var contactSheet = async (browser, results, dir) => {
|
|
|
17605
18094
|
if (shown3.length === 0) return void 0;
|
|
17606
18095
|
const page = await browser.newPage({ viewport: { width: 1600, height: 900 } });
|
|
17607
18096
|
await page.setContent(`<!doctype html><style>${STYLE}</style>${shown3.map(card).join("")}`);
|
|
17608
|
-
const path =
|
|
18097
|
+
const path = join47(dir, "contact-sheet.png");
|
|
17609
18098
|
await page.screenshot({ path, fullPage: true });
|
|
17610
18099
|
await page.close();
|
|
17611
18100
|
return path;
|
|
@@ -17945,12 +18434,12 @@ var proseCheckCommand = {
|
|
|
17945
18434
|
};
|
|
17946
18435
|
|
|
17947
18436
|
// src/review/packet.ts
|
|
17948
|
-
import { join as
|
|
18437
|
+
import { join as join48 } from "node:path";
|
|
17949
18438
|
var REVIEW_STATE_FILE = ".finitemonkey/review/state.json";
|
|
17950
18439
|
var FULL_ROUNDS = 3;
|
|
17951
18440
|
var REVIEW_FILE_MAX_LINES = 200;
|
|
17952
18441
|
var LEDGER_MAX_LINES = 100;
|
|
17953
|
-
var statePath = (root) => toAbsolutePath(
|
|
18442
|
+
var statePath = (root) => toAbsolutePath(join48(root, REVIEW_STATE_FILE));
|
|
17954
18443
|
var readReviewState = (runtime, root) => {
|
|
17955
18444
|
const raw = runtime.readText(statePath(root));
|
|
17956
18445
|
const parsed = raw.ok ? parseJsonObject(raw.value) : void 0;
|
|
@@ -17962,7 +18451,7 @@ var git = (runtime, root, args2) => {
|
|
|
17962
18451
|
const result = runtime.git(args2, root);
|
|
17963
18452
|
return result.ok ? result.stdout : void 0;
|
|
17964
18453
|
};
|
|
17965
|
-
var lastLines = (
|
|
18454
|
+
var lastLines = (text9, max) => text9.split("\n").slice(-max).join("\n");
|
|
17966
18455
|
var section = (title, body2, empty) => `## ${title}
|
|
17967
18456
|
|
|
17968
18457
|
${body2 === void 0 || body2.trim() === "" ? empty : body2.trimEnd()}
|
|
@@ -17972,7 +18461,7 @@ var taskSections = (runtime, root) => {
|
|
|
17972
18461
|
const active = readState(runtime, paths).active;
|
|
17973
18462
|
if (active === void 0)
|
|
17974
18463
|
return [section("Contract", void 0, "(no active task)"), section("Evidence ledger", void 0, "(none)")];
|
|
17975
|
-
const contract = runtime.readText(toAbsolutePath(
|
|
18464
|
+
const contract = runtime.readText(toAbsolutePath(join48(root, taskRelative(active))));
|
|
17976
18465
|
const ledger = runtime.readText(paths.evidence(active));
|
|
17977
18466
|
return [
|
|
17978
18467
|
section("Contract", contract.ok ? contract.value : void 0, "(contract file missing)"),
|
|
@@ -17980,8 +18469,8 @@ var taskSections = (runtime, root) => {
|
|
|
17980
18469
|
];
|
|
17981
18470
|
};
|
|
17982
18471
|
var reviewFile = (runtime, root, base) => {
|
|
17983
|
-
const
|
|
17984
|
-
return
|
|
18472
|
+
const text9 = git(runtime, root, ["show", `${base}:REVIEW.md`]);
|
|
18473
|
+
return text9 === void 0 ? void 0 : text9.split("\n").slice(0, REVIEW_FILE_MAX_LINES).join("\n");
|
|
17985
18474
|
};
|
|
17986
18475
|
var nextState = (previous, base, head) => {
|
|
17987
18476
|
if (previous?.base !== base) return { base, rounds: 1, lastHead: head };
|
|
@@ -17994,7 +18483,7 @@ var buildPacket = (runtime, root, base, head) => {
|
|
|
17994
18483
|
const range = `${from}...${head}`;
|
|
17995
18484
|
const diff = git(runtime, root, ["diff", "--no-color", "--no-ext-diff", range, "--", ".", ":(exclude).finitemonkey"]);
|
|
17996
18485
|
const owners = reviewFile(runtime, root, base);
|
|
17997
|
-
const
|
|
18486
|
+
const text9 = [
|
|
17998
18487
|
`# Review packet (round ${String(state.rounds)}, ${range})`,
|
|
17999
18488
|
"",
|
|
18000
18489
|
...state.rounds > FULL_ROUNDS ? ["Only the diff since the last review round is included.", ""] : [],
|
|
@@ -18002,10 +18491,10 @@ var buildPacket = (runtime, root, base, head) => {
|
|
|
18002
18491
|
...taskSections(runtime, root),
|
|
18003
18492
|
...owners === void 0 ? [] : [section("REVIEW.md (base branch)", owners, "")]
|
|
18004
18493
|
].join("\n");
|
|
18005
|
-
runtime.mkdir(toAbsolutePath(
|
|
18494
|
+
runtime.mkdir(toAbsolutePath(join48(root, ".finitemonkey", "review")));
|
|
18006
18495
|
runtime.writeAtomic(statePath(root), `${JSON.stringify(state, null, 2)}
|
|
18007
18496
|
`);
|
|
18008
|
-
return { round: state.rounds, base, head, range, text:
|
|
18497
|
+
return { round: state.rounds, base, head, range, text: text9 };
|
|
18009
18498
|
};
|
|
18010
18499
|
|
|
18011
18500
|
// src/commands/review.ts
|
|
@@ -18017,7 +18506,7 @@ var reviewPacketCommand = {
|
|
|
18017
18506
|
status: "ready",
|
|
18018
18507
|
options: { base: { type: "string" } },
|
|
18019
18508
|
run: (ctx, args2) => {
|
|
18020
|
-
const repo =
|
|
18509
|
+
const repo = repoOf(ctx);
|
|
18021
18510
|
if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
|
|
18022
18511
|
const base = stringOption(args2.values, "base") ?? repoFacts(ctx.runtime, repo).defaultBranch();
|
|
18023
18512
|
const resolved = ctx.runtime.git(["rev-parse", "--verify", "--quiet", `${base}^{commit}`], repo.root);
|
|
@@ -18058,14 +18547,14 @@ var gitUnavailable = (detail) => ({
|
|
|
18058
18547
|
});
|
|
18059
18548
|
|
|
18060
18549
|
// src/commands/secrets-accept.ts
|
|
18061
|
-
var
|
|
18550
|
+
var DAY_MS3 = 864e5;
|
|
18062
18551
|
var daysOf = (values) => {
|
|
18063
18552
|
const raw = stringOption(values, "days");
|
|
18064
18553
|
if (raw === void 0) return EXCEPTION_MAX_DAYS;
|
|
18065
18554
|
const days = Number.parseInt(raw, 10);
|
|
18066
18555
|
return Number.isInteger(days) && days > 0 && days <= EXCEPTION_MAX_DAYS ? days : void 0;
|
|
18067
18556
|
};
|
|
18068
|
-
var plusDays = (iso, days) => new Date(Date.parse(iso) + days *
|
|
18557
|
+
var plusDays = (iso, days) => new Date(Date.parse(iso) + days * DAY_MS3).toISOString();
|
|
18069
18558
|
var failure3 = (ctx, problem4) => failureFrom(ctx.t, problem4);
|
|
18070
18559
|
var inputProblem = (ctx, id, reason, days) => {
|
|
18071
18560
|
const fix = "finitemonkey secrets accept <id> --reason <why> [--days 1-90] [--false-positive]";
|
|
@@ -18157,7 +18646,7 @@ var secretsAcceptCommand = {
|
|
|
18157
18646
|
};
|
|
18158
18647
|
|
|
18159
18648
|
// src/secrets/runbooks.ts
|
|
18160
|
-
import { dirname as dirname9, join as
|
|
18649
|
+
import { dirname as dirname9, join as join49 } from "node:path";
|
|
18161
18650
|
var PROVIDER_OF_RULE = {
|
|
18162
18651
|
"aws-access-token": "aws",
|
|
18163
18652
|
"aws-secret-access-key": "aws",
|
|
@@ -18179,13 +18668,13 @@ var RUNBOOKS_URL = "https://github.com/gabrielvieiraf/finitemonkey/blob/main/run
|
|
|
18179
18668
|
var runbookProvider = (rule) => PROVIDER_OF_RULE[rule] ?? "generic";
|
|
18180
18669
|
var runbookLink = (runtime, rule) => {
|
|
18181
18670
|
const file = `${runbookProvider(rule)}.md`;
|
|
18182
|
-
const local = toAbsolutePath(
|
|
18671
|
+
const local = toAbsolutePath(join49(dirname9(dirname9(runtime.selfPath)), "runbooks", file));
|
|
18183
18672
|
return runtime.stat(local)?.kind === "file" ? local : `${RUNBOOKS_URL}/${file}`;
|
|
18184
18673
|
};
|
|
18185
18674
|
|
|
18186
18675
|
// src/commands/secrets-incident.ts
|
|
18187
18676
|
var INCIDENT_ACTIONS = ["list", "rotated", "close", "purge"];
|
|
18188
|
-
var
|
|
18677
|
+
var USAGE2 = "finitemonkey secrets incident [list | rotated <id> | close <id> | purge <id>]";
|
|
18189
18678
|
var TARGET = { rotated: "rotacionado", close: "fechado" };
|
|
18190
18679
|
var PURGE_STEPS = [
|
|
18191
18680
|
"secrets.purge.step1",
|
|
@@ -18243,7 +18732,7 @@ var move = (ctx, root, incident, action) => {
|
|
|
18243
18732
|
if (!canMove(incident.state, to))
|
|
18244
18733
|
return action === "close" ? notRotated(ctx, incident) : fail2(
|
|
18245
18734
|
ctx,
|
|
18246
|
-
usageProblem("invalid_arguments", "secrets.error.bad_transition",
|
|
18735
|
+
usageProblem("invalid_arguments", "secrets.error.bad_transition", USAGE2, {
|
|
18247
18736
|
id: incident.id,
|
|
18248
18737
|
from: incident.state,
|
|
18249
18738
|
to
|
|
@@ -18266,7 +18755,7 @@ var actionOf = (raw) => INCIDENT_ACTIONS.find((action) => action === (raw ?? "li
|
|
|
18266
18755
|
var secretsIncidentCommand = {
|
|
18267
18756
|
path: ["secrets", "incident"],
|
|
18268
18757
|
summaryKey: "secrets.command.incident",
|
|
18269
|
-
usage:
|
|
18758
|
+
usage: USAGE2,
|
|
18270
18759
|
status: "ready",
|
|
18271
18760
|
maxPositionals: 2,
|
|
18272
18761
|
run: (ctx, args2) => {
|
|
@@ -18276,7 +18765,7 @@ var secretsIncidentCommand = {
|
|
|
18276
18765
|
return Promise.resolve(
|
|
18277
18766
|
fail2(
|
|
18278
18767
|
ctx,
|
|
18279
|
-
usageProblem("invalid_arguments", "error.invalid_arguments",
|
|
18768
|
+
usageProblem("invalid_arguments", "error.invalid_arguments", USAGE2, { detail: args2.positionals[0] ?? "" })
|
|
18280
18769
|
)
|
|
18281
18770
|
);
|
|
18282
18771
|
if (action === "list") return Promise.resolve(list2(ctx, root));
|
|
@@ -18348,7 +18837,7 @@ var liveOutcome = (result) => {
|
|
|
18348
18837
|
|
|
18349
18838
|
// src/commands/secrets-live.ts
|
|
18350
18839
|
var SECRETS_LIVE_SCHEMA = "finitemonkey/secrets-live@1";
|
|
18351
|
-
var
|
|
18840
|
+
var USAGE3 = `finitemonkey secrets live <${LIVE_PROVIDERS.join("|")}> --env <VARIABLE> [--send]`;
|
|
18352
18841
|
var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
18353
18842
|
var target = (provider) => {
|
|
18354
18843
|
const probe = LIVE_PROBES[provider];
|
|
@@ -18379,7 +18868,7 @@ var send = (ctx, provider, variable, key) => {
|
|
|
18379
18868
|
var secretsLiveCommand = {
|
|
18380
18869
|
path: ["secrets", "live"],
|
|
18381
18870
|
summaryKey: "secrets.command.live",
|
|
18382
|
-
usage:
|
|
18871
|
+
usage: USAGE3,
|
|
18383
18872
|
status: "ready",
|
|
18384
18873
|
maxPositionals: 1,
|
|
18385
18874
|
options: { env: { type: "string" }, send: { type: "boolean" } },
|
|
@@ -18390,7 +18879,7 @@ var secretsLiveCommand = {
|
|
|
18390
18879
|
return Promise.resolve(
|
|
18391
18880
|
failureFrom(
|
|
18392
18881
|
ctx.t,
|
|
18393
|
-
usageProblem("invalid_arguments", "error.invalid_arguments",
|
|
18882
|
+
usageProblem("invalid_arguments", "error.invalid_arguments", USAGE3, {
|
|
18394
18883
|
detail: provider === void 0 ? args2.positionals[0] ?? "<provider>" : "--env <VARIABLE>"
|
|
18395
18884
|
})
|
|
18396
18885
|
)
|
|
@@ -18400,7 +18889,7 @@ var secretsLiveCommand = {
|
|
|
18400
18889
|
return Promise.resolve(
|
|
18401
18890
|
failureFrom(
|
|
18402
18891
|
ctx.t,
|
|
18403
|
-
needsPersonProblem("secrets.error.person_only", `${
|
|
18892
|
+
needsPersonProblem("secrets.error.person_only", `${USAGE3.replace(" [--send]", "")} --send`, {
|
|
18404
18893
|
action: ctx.t("secrets.action.live")
|
|
18405
18894
|
})
|
|
18406
18895
|
)
|
|
@@ -18408,7 +18897,7 @@ var secretsLiveCommand = {
|
|
|
18408
18897
|
const key = ctx.env[variable] ?? "";
|
|
18409
18898
|
if (key === "")
|
|
18410
18899
|
return Promise.resolve(
|
|
18411
|
-
failureFrom(ctx.t, usageProblem("invalid_arguments", "secrets.live.no_value",
|
|
18900
|
+
failureFrom(ctx.t, usageProblem("invalid_arguments", "secrets.live.no_value", USAGE3, { variable }))
|
|
18412
18901
|
);
|
|
18413
18902
|
return Promise.resolve(send(ctx, provider, variable, key));
|
|
18414
18903
|
}
|
|
@@ -18577,7 +19066,7 @@ var gateLight = (runtime, paths) => {
|
|
|
18577
19066
|
};
|
|
18578
19067
|
|
|
18579
19068
|
// src/statusline/input.ts
|
|
18580
|
-
var
|
|
19069
|
+
var text8 = (value) => typeof value === "string" && value !== "" ? value : void 0;
|
|
18581
19070
|
var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
18582
19071
|
var usedTokens = (usage) => {
|
|
18583
19072
|
if (!isRecord(usage)) return void 0;
|
|
@@ -18597,8 +19086,8 @@ var parseStatusInput = (raw) => {
|
|
|
18597
19086
|
const record3 = parsed.ok ? parsed.value : {};
|
|
18598
19087
|
const workspace = isRecord(record3["workspace"]) ? record3["workspace"] : {};
|
|
18599
19088
|
return {
|
|
18600
|
-
sessionId:
|
|
18601
|
-
dir:
|
|
19089
|
+
sessionId: text8(record3["session_id"]),
|
|
19090
|
+
dir: text8(workspace["project_dir"]) ?? text8(workspace["current_dir"]) ?? text8(record3["cwd"]),
|
|
18602
19091
|
contextPercent: contextPercent(record3),
|
|
18603
19092
|
raw
|
|
18604
19093
|
};
|
|
@@ -18613,7 +19102,7 @@ var segmentText = (t, segment) => [
|
|
|
18613
19102
|
...segment.contextPercent === void 0 ? [] : [t("statusline.context", { percent: segment.contextPercent })]
|
|
18614
19103
|
].join(" · ");
|
|
18615
19104
|
var ANSI3 = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[A-Za-z]`, "g");
|
|
18616
|
-
var graphemes = (
|
|
19105
|
+
var graphemes = (text9) => Array.from(new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(text9), (part2) => part2.segment);
|
|
18617
19106
|
var fit = (line2, columns) => {
|
|
18618
19107
|
if (columns === void 0) return line2;
|
|
18619
19108
|
const visible = graphemes(line2.replace(ANSI3, ""));
|
|
@@ -18674,16 +19163,16 @@ var cached2 = (runtime, paths) => {
|
|
|
18674
19163
|
const raw = readJsonFile(runtime, paths.cache(WRAP_CACHE))?.["sessions"];
|
|
18675
19164
|
return isRecord(raw) ? Object.fromEntries(Object.entries(raw).filter((entry2) => typeof entry2[1] === "string")) : {};
|
|
18676
19165
|
};
|
|
18677
|
-
var remember = (runtime, paths, session,
|
|
18678
|
-
const sessions = Object.entries({ ...cached2(runtime, paths), [session]:
|
|
19166
|
+
var remember = (runtime, paths, session, text9) => {
|
|
19167
|
+
const sessions = Object.entries({ ...cached2(runtime, paths), [session]: text9 }).slice(-KEEP3);
|
|
18679
19168
|
writeJsonFile(runtime, paths.cache(WRAP_CACHE), { sessions: Object.fromEntries(sessions) });
|
|
18680
19169
|
};
|
|
18681
19170
|
var runWrapped = async (runtime, paths, request, shell = nodeShell) => {
|
|
18682
19171
|
const result = await shell(request.command, request.input, request.cwd, request.timeoutMs);
|
|
18683
19172
|
if (result.text !== void 0) {
|
|
18684
|
-
const
|
|
18685
|
-
if (paths !== void 0) remember(runtime, paths, request.session,
|
|
18686
|
-
return
|
|
19173
|
+
const text9 = result.text.replace(/\s+$/, "");
|
|
19174
|
+
if (paths !== void 0) remember(runtime, paths, request.session, text9);
|
|
19175
|
+
return text9;
|
|
18687
19176
|
}
|
|
18688
19177
|
return paths === void 0 ? "" : cached2(runtime, paths)[request.session] ?? "";
|
|
18689
19178
|
};
|
|
@@ -18692,8 +19181,8 @@ var runWrapped = async (runtime, paths, request, shell = nodeShell) => {
|
|
|
18692
19181
|
var STDIN_MS = 300;
|
|
18693
19182
|
var WRAP_MS = 700;
|
|
18694
19183
|
var wrapOf = (ctx, root) => {
|
|
18695
|
-
const
|
|
18696
|
-
const lock =
|
|
19184
|
+
const text9 = ctx.runtime.readText(toAbsolutePath(`${root}/${LOCK_FILE}`));
|
|
19185
|
+
const lock = text9.ok ? parseLock(text9.value) : void 0;
|
|
18697
19186
|
if (!lock?.ok) return void 0;
|
|
18698
19187
|
return lock.value.entries.find((entry2) => entry2.added?.statusLine !== void 0)?.added?.statusLine?.wrap ?? void 0;
|
|
18699
19188
|
};
|
|
@@ -18739,7 +19228,7 @@ var statuslineCommand = {
|
|
|
18739
19228
|
};
|
|
18740
19229
|
|
|
18741
19230
|
// src/task/create.ts
|
|
18742
|
-
import { join as
|
|
19231
|
+
import { join as join50 } from "node:path";
|
|
18743
19232
|
|
|
18744
19233
|
// src/core/yaml/write.ts
|
|
18745
19234
|
var PLAIN_SAFE = /^[A-Za-z_./][A-Za-z0-9_./ ()-]*$/;
|
|
@@ -18828,10 +19317,10 @@ var explicitChecks = (draft) => [
|
|
|
18828
19317
|
var draftChecks = (runtime, paths, draft) => {
|
|
18829
19318
|
const disk = {
|
|
18830
19319
|
read: (relative11) => {
|
|
18831
|
-
const
|
|
18832
|
-
return
|
|
19320
|
+
const text9 = runtime.readText(toAbsolutePath(join50(paths.root, relative11)));
|
|
19321
|
+
return text9.ok ? text9.value : void 0;
|
|
18833
19322
|
},
|
|
18834
|
-
exists: (relative11) => runtime.stat(toAbsolutePath(
|
|
19323
|
+
exists: (relative11) => runtime.stat(toAbsolutePath(join50(paths.root, relative11))) !== void 0
|
|
18835
19324
|
};
|
|
18836
19325
|
const detected = detectChecks(disk);
|
|
18837
19326
|
const base = draft.runs.length > 0 || draft.tests !== void 0 ? explicitChecks(draft) : detected;
|
|
@@ -18846,7 +19335,7 @@ var draftChecks = (runtime, paths, draft) => {
|
|
|
18846
19335
|
{ id: "merge", kind: "merge" },
|
|
18847
19336
|
{ id: "callers", kind: "callers" },
|
|
18848
19337
|
...draft.paths.length > 0 || draft.budget !== void 0 ? [{ id: "diff", kind: "diff" }] : [],
|
|
18849
|
-
...runtime.stat(toAbsolutePath(
|
|
19338
|
+
...runtime.stat(toAbsolutePath(join50(paths.root, LOOK_CONFIG))) === void 0 ? [] : [{ id: "look", kind: "look" }],
|
|
18850
19339
|
...draft.git.length > 0 ? [{ id: "git", kind: "git", require: draft.git }] : [],
|
|
18851
19340
|
{ id: "pr", kind: "pr", requireOpen: draft.git.includes("pr_open") },
|
|
18852
19341
|
...draft.manual.map((description, index) => ({
|
|
@@ -18872,19 +19361,19 @@ var createTask = (runtime, paths, draft) => {
|
|
|
18872
19361
|
decisionsNeeded: [],
|
|
18873
19362
|
implicit: false
|
|
18874
19363
|
};
|
|
18875
|
-
const
|
|
19364
|
+
const text9 = stringifyYaml(contractYaml(shape));
|
|
18876
19365
|
const valid = validateContract(contractYaml(shape));
|
|
18877
19366
|
if (!valid.ok) return reject({ kind: "invalid", issues: valid.error });
|
|
18878
|
-
const wrote = runtime.writeText(paths.task(draft.id),
|
|
19367
|
+
const wrote = runtime.writeText(paths.task(draft.id), text9);
|
|
18879
19368
|
if (!wrote.ok) return reject({ kind: "write", detail: wrote.error });
|
|
18880
19369
|
const lock = readTrust(runtime, paths);
|
|
18881
19370
|
writeTrust(runtime, paths, {
|
|
18882
19371
|
...lock,
|
|
18883
19372
|
contracts: { ...lock.contracts, [draft.id]: contractHash(valid.value) },
|
|
18884
|
-
approved: { ...lock.approved, [draft.id]:
|
|
19373
|
+
approved: { ...lock.approved, [draft.id]: text9 }
|
|
18885
19374
|
});
|
|
18886
19375
|
writeState(runtime, paths, { ...readState(runtime, paths), active: draft.id });
|
|
18887
|
-
return accept({ contract: valid.value, text:
|
|
19376
|
+
return accept({ contract: valid.value, text: text9 });
|
|
18888
19377
|
};
|
|
18889
19378
|
|
|
18890
19379
|
// src/commands/task.ts
|
|
@@ -19035,7 +19524,7 @@ var taskDoneCommand = {
|
|
|
19035
19524
|
};
|
|
19036
19525
|
|
|
19037
19526
|
// src/install/githook-block.ts
|
|
19038
|
-
var withoutHookBlock = (
|
|
19527
|
+
var withoutHookBlock = (text9) => withoutManagedBlock(text9, { begin: BLOCK_BEGIN, end: BLOCK_END });
|
|
19039
19528
|
|
|
19040
19529
|
// src/install/uninstall.ts
|
|
19041
19530
|
var blockless = (kind, current2) => {
|
|
@@ -19044,8 +19533,8 @@ var blockless = (kind, current2) => {
|
|
|
19044
19533
|
};
|
|
19045
19534
|
var structural = (entry2, current2) => {
|
|
19046
19535
|
if (entry2.kind === "gitignore" || entry2.kind === "markdown" || entry2.kind === "githook") {
|
|
19047
|
-
const
|
|
19048
|
-
return
|
|
19536
|
+
const text9 = blockless(entry2.kind, current2);
|
|
19537
|
+
return text9.trim() === "" && !entry2.existed ? void 0 : text9;
|
|
19049
19538
|
}
|
|
19050
19539
|
const parsed = parseSettings(current2);
|
|
19051
19540
|
if (!parsed.ok || entry2.added === void 0) return current2;
|
|
@@ -19126,7 +19615,7 @@ var dataOf3 = (root, plan, applied) => ({
|
|
|
19126
19615
|
kept: plan.items.filter((item2) => item2.action.startsWith("keep")).map(({ path }) => path),
|
|
19127
19616
|
outside: OUTSIDE.map(({ command }) => command)
|
|
19128
19617
|
});
|
|
19129
|
-
var
|
|
19618
|
+
var linesOf3 = (ctx, data) => [
|
|
19130
19619
|
ctx.t("install.uninstall.title", { root: data.root }),
|
|
19131
19620
|
...data.actions.map(({ path, action }) => ` ${action.padEnd(15)}${path}`),
|
|
19132
19621
|
...data.purge ? [ctx.t("install.uninstall.purge")] : [ctx.t("install.uninstall.keeps_state")],
|
|
@@ -19147,16 +19636,16 @@ var runUninstall = (ctx, args2) => {
|
|
|
19147
19636
|
const options = { purge: args2.values["purge"] === true, keepGenerated: args2.values["keep-generated"] === true };
|
|
19148
19637
|
const plan = planRemoval(project, lock.value, options);
|
|
19149
19638
|
const preview = dataOf3(project.root, plan, false);
|
|
19150
|
-
if (ctx.dryRun) return success(preview, [...
|
|
19639
|
+
if (ctx.dryRun) return success(preview, [...linesOf3(ctx, preview), ctx.t("install.dry_run")]);
|
|
19151
19640
|
if (!ctx.yes) {
|
|
19152
19641
|
const next2 = fixFor(args2);
|
|
19153
19642
|
const problem4 = confirmProblem("uninstall", ctx.t("install.uninstall.question"), next2);
|
|
19154
|
-
return { ...failureFrom(ctx.t, { ...problem4, data: preview },
|
|
19643
|
+
return { ...failureFrom(ctx.t, { ...problem4, data: preview }, linesOf3(ctx, preview)), next: next2 };
|
|
19155
19644
|
}
|
|
19156
19645
|
const removed = applyRemoval(project, plan);
|
|
19157
19646
|
if (!removed.ok) return failureFrom(ctx.t, applyProblem(removed.error));
|
|
19158
19647
|
const data = dataOf3(project.root, plan, true);
|
|
19159
|
-
return success(data, [...
|
|
19648
|
+
return success(data, [...linesOf3(ctx, data), ctx.t("install.uninstall.done")]);
|
|
19160
19649
|
};
|
|
19161
19650
|
var uninstallCommand = {
|
|
19162
19651
|
path: ["uninstall"],
|
|
@@ -19236,6 +19725,7 @@ var REGISTRY = [
|
|
|
19236
19725
|
policyFalsePositiveCommand,
|
|
19237
19726
|
offCommand,
|
|
19238
19727
|
onCommand,
|
|
19728
|
+
gainCommand,
|
|
19239
19729
|
secretsIncidentCommand,
|
|
19240
19730
|
secretsLiveCommand,
|
|
19241
19731
|
proseCheckCommand,
|
|
@@ -19250,34 +19740,35 @@ var REGISTRY = [
|
|
|
19250
19740
|
lookCommand,
|
|
19251
19741
|
reviewPacketCommand,
|
|
19252
19742
|
statuslineCommand,
|
|
19743
|
+
critiqueVerifyCommand,
|
|
19253
19744
|
versionCommand,
|
|
19254
19745
|
helpCommand
|
|
19255
19746
|
];
|
|
19256
19747
|
|
|
19257
19748
|
// src/i18n/lang-settings.ts
|
|
19258
|
-
import { join as
|
|
19749
|
+
import { join as join51 } from "node:path";
|
|
19259
19750
|
var read3 = (runtime, path) => {
|
|
19260
|
-
const
|
|
19261
|
-
return
|
|
19751
|
+
const text9 = runtime.readText(toAbsolutePath(path));
|
|
19752
|
+
return text9.ok ? text9.value : void 0;
|
|
19262
19753
|
};
|
|
19263
19754
|
var configText = (tree, ...path) => {
|
|
19264
19755
|
const value = configAt(tree, ...path);
|
|
19265
19756
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
19266
19757
|
};
|
|
19267
|
-
var languageIn = (
|
|
19268
|
-
if (
|
|
19269
|
-
const parsed = parseJsonObject(
|
|
19758
|
+
var languageIn = (text9) => {
|
|
19759
|
+
if (text9 === void 0) return void 0;
|
|
19760
|
+
const parsed = parseJsonObject(text9);
|
|
19270
19761
|
const value = parsed.ok && isRecord(parsed.value) ? parsed.value["language"] : void 0;
|
|
19271
19762
|
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
19272
19763
|
};
|
|
19273
|
-
var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ??
|
|
19764
|
+
var claudeDir = (runtime, env) => env["CLAUDE_CONFIG_DIR"] ?? join51(runtime.homeDir(), ".claude");
|
|
19274
19765
|
var langSettings = (runtime, cwd, env, platform) => {
|
|
19275
19766
|
const root = findGitRoot(cwd, runtime) ?? cwd;
|
|
19276
|
-
const config = parseConfigYaml(read3(runtime,
|
|
19767
|
+
const config = parseConfigYaml(read3(runtime, join51(root, CONFIG_FILE2)) ?? "");
|
|
19277
19768
|
const claudeFiles = [
|
|
19278
|
-
|
|
19279
|
-
|
|
19280
|
-
|
|
19769
|
+
join51(root, ".claude", "settings.local.json"),
|
|
19770
|
+
join51(root, ".claude", "settings.json"),
|
|
19771
|
+
join51(claudeDir(runtime, env), "settings.json")
|
|
19281
19772
|
];
|
|
19282
19773
|
return {
|
|
19283
19774
|
config: configText(config, "lang"),
|
|
@@ -19364,8 +19855,8 @@ var resultFromThrown = (thrown, t) => {
|
|
|
19364
19855
|
};
|
|
19365
19856
|
var block3 = (items) => items.length > 0 ? `${items.join("\n")}
|
|
19366
19857
|
` : "";
|
|
19367
|
-
var write2 = (sink,
|
|
19368
|
-
if (
|
|
19858
|
+
var write2 = (sink, text9) => {
|
|
19859
|
+
if (text9 !== "") sink.write(maskText(text9));
|
|
19369
19860
|
};
|
|
19370
19861
|
var render2 = (ctx, command, result) => {
|
|
19371
19862
|
if (result.raw !== void 0) {
|
|
@@ -19434,9 +19925,9 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
|
19434
19925
|
var UNKNOWN_VERSION = "0.0.0-unknown";
|
|
19435
19926
|
var VERSION_SOURCES = ["../package.json", "../.claude-plugin/plugin.json"];
|
|
19436
19927
|
var versionAt = (readText, moduleUrl, relative11) => {
|
|
19437
|
-
const
|
|
19438
|
-
if (!
|
|
19439
|
-
const parsed = parseJson(
|
|
19928
|
+
const text9 = readText(toAbsolutePath(fileURLToPath4(new URL(relative11, moduleUrl))));
|
|
19929
|
+
if (!text9.ok) return void 0;
|
|
19930
|
+
const parsed = parseJson(text9.value);
|
|
19440
19931
|
if (!parsed.ok || !isRecord(parsed.value)) return void 0;
|
|
19441
19932
|
const version = parsed.value["version"];
|
|
19442
19933
|
return typeof version === "string" ? version : void 0;
|