@brainervirus/workit-cursor 8.24.0 → 8.26.0
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/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +2203 -1952
- package/dist/mcp-server.js +1655 -1494
- package/dist/workit-hook.js +2223 -1950
- package/package.json +3 -3
- package/skills/workit-fanout/SKILL.md +4 -4
- package/skills/workit-fanout/references/brief.md +4 -4
- package/skills/workit-fanout/references/orchestration.md +26 -17
- package/skills/workit-implement/SKILL.md +1 -1
- package/skills/workit-review/SKILL.md +4 -1
- package/skills/workit-ship/SKILL.md +1 -1
|
@@ -6981,6 +6981,26 @@ function resolveRef(cwd, ref) {
|
|
|
6981
6981
|
return null;
|
|
6982
6982
|
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
6983
6983
|
}
|
|
6984
|
+
function originHeadRef(cwd) {
|
|
6985
|
+
return line(git(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]));
|
|
6986
|
+
}
|
|
6987
|
+
function ownChangeDrift(cwd, range) {
|
|
6988
|
+
const before = diffRaw(cwd, range.fromFork, range.from);
|
|
6989
|
+
const after = diffRaw(cwd, range.toFork, range.to);
|
|
6990
|
+
if (!before || !after)
|
|
6991
|
+
return null;
|
|
6992
|
+
const judged = new Map(before.map((entry) => [entry.path, entry]));
|
|
6993
|
+
const current = new Map(after.map((entry) => [entry.path, entry]));
|
|
6994
|
+
const same = (a, b) => a !== undefined && b !== undefined && a.oldMode === b.oldMode && a.oldBlob === b.oldBlob && a.newMode === b.newMode && a.newBlob === b.newBlob;
|
|
6995
|
+
return [...new Set([...judged.keys(), ...current.keys()])].filter((file) => !same(judged.get(file), current.get(file))).toSorted();
|
|
6996
|
+
}
|
|
6997
|
+
function sameOwnChange(cwd, range) {
|
|
6998
|
+
const fromFork = mergeBase(cwd, range.fromBase, range.from);
|
|
6999
|
+
const toFork = mergeBase(cwd, range.toBase, range.to);
|
|
7000
|
+
if (!fromFork || !toFork)
|
|
7001
|
+
return false;
|
|
7002
|
+
return ownChangeDrift(cwd, { from: range.from, to: range.to, fromFork, toFork })?.length === 0;
|
|
7003
|
+
}
|
|
6984
7004
|
function diffNumstat(cwd, fork, head) {
|
|
6985
7005
|
const range = laneRange(fork, head);
|
|
6986
7006
|
if (!range)
|
|
@@ -8216,6 +8236,7 @@ var init_workspaces = __esm(() => {
|
|
|
8216
8236
|
trailers: boolean2().optional(),
|
|
8217
8237
|
liveSessionGuard: boolean2().optional(),
|
|
8218
8238
|
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
8239
|
+
models: object({ roles: unknown().optional() }).passthrough().optional(),
|
|
8219
8240
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
8220
8241
|
kind,
|
|
8221
8242
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -8242,7 +8263,10 @@ var init_workspaces = __esm(() => {
|
|
|
8242
8263
|
});
|
|
8243
8264
|
}
|
|
8244
8265
|
});
|
|
8245
|
-
workspacesFileSchema = object({
|
|
8266
|
+
workspacesFileSchema = object({
|
|
8267
|
+
workspaces: array(workspaceConfigSchema).optional(),
|
|
8268
|
+
models: object({ roles: unknown().optional() }).passthrough().optional()
|
|
8269
|
+
}).passthrough().superRefine((file, context) => {
|
|
8246
8270
|
const names = new Set;
|
|
8247
8271
|
for (const [index, workspace] of (file.workspaces ?? []).entries()) {
|
|
8248
8272
|
if (names.has(workspace.name)) {
|
|
@@ -8832,2054 +8856,2144 @@ var init_vcs_config = __esm(() => {
|
|
|
8832
8856
|
TRACKERS = new Set(["github", "gitlab", "youtrack"]);
|
|
8833
8857
|
});
|
|
8834
8858
|
|
|
8835
|
-
// packages/workit-core/src/
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
import { createHash as createHash6 } from "node:crypto";
|
|
8843
|
-
function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
|
|
8844
|
-
const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
|
|
8845
|
-
const config = readConfig();
|
|
8846
|
-
return kind === "branch" ? {
|
|
8847
|
-
branchPolicy: withReleaseTracks(workspaceRoot, resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null, () => repoBranchPreset(workspaceRoot))),
|
|
8848
|
-
provenance: { branchPolicy: selected.source }
|
|
8849
|
-
} : {
|
|
8850
|
-
commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
|
|
8851
|
-
provenance: { commitPolicy: selected.source }
|
|
8852
|
-
};
|
|
8853
|
-
}
|
|
8854
|
-
var withReleaseTracks = (workspaceRoot, policy) => {
|
|
8855
|
-
const read = workspaceReleaseTracks(workspaceRoot);
|
|
8856
|
-
if (read.error)
|
|
8857
|
-
throw new ReleaseTrackError(read.error);
|
|
8858
|
-
if (read.tracks.length === 0 && read.protectedBranches.length === 0)
|
|
8859
|
-
return policy;
|
|
8860
|
-
const known = new Set(policy.allowed.map((pattern) => pattern.source));
|
|
8861
|
-
const allowed = [...policy.allowed];
|
|
8862
|
-
for (const glob of read.tracks.flatMap((track) => Object.values(track.naming).map(namingGlob))) {
|
|
8863
|
-
const pattern = branchGlobPattern(glob);
|
|
8864
|
-
if (!known.has(pattern.source)) {
|
|
8865
|
-
known.add(pattern.source);
|
|
8866
|
-
allowed.push(pattern);
|
|
8867
|
-
}
|
|
8868
|
-
}
|
|
8869
|
-
return {
|
|
8870
|
-
...policy,
|
|
8871
|
-
allowed,
|
|
8872
|
-
protected: new Set([
|
|
8873
|
-
...policy.protected,
|
|
8874
|
-
...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) => name.toLowerCase())
|
|
8875
|
-
])
|
|
8876
|
-
};
|
|
8877
|
-
}, KIND_SYNONYMS, allowedKinds = (allowed) => [
|
|
8878
|
-
...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
|
|
8879
|
-
], editDistance = (a, b) => {
|
|
8880
|
-
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
8881
|
-
for (let i = 1;i <= a.length; i++) {
|
|
8882
|
-
let previous = row[0];
|
|
8883
|
-
row[0] = i;
|
|
8884
|
-
for (let j = 1;j <= b.length; j++) {
|
|
8885
|
-
const saved = row[j];
|
|
8886
|
-
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
8887
|
-
previous = saved;
|
|
8888
|
-
}
|
|
8889
|
-
}
|
|
8890
|
-
return row[b.length];
|
|
8891
|
-
}, suggestBranchName = (name, kinds) => {
|
|
8892
|
-
if (!kinds.length)
|
|
8893
|
-
return null;
|
|
8894
|
-
const slash = name.indexOf("/");
|
|
8895
|
-
const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
|
|
8896
|
-
const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
|
|
8897
|
-
const synonym = KIND_SYNONYMS[prefix];
|
|
8898
|
-
const near = kinds.map((kind) => ({ kind, distance: editDistance(prefix, kind) })).filter((entry) => prefix && entry.distance <= 2).toSorted((a, b) => a.distance - b.distance)[0]?.kind;
|
|
8899
|
-
const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
|
|
8900
|
-
return `${kind}/${rest}`;
|
|
8901
|
-
}, kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", "), aliasNote = (kinds) => {
|
|
8902
|
-
const notes = [
|
|
8903
|
-
["fix", "bugfix"],
|
|
8904
|
-
["chore", "feature"]
|
|
8905
|
-
].filter(([, kind]) => kinds.includes(kind)).map(([alias, kind]) => `${alias}/ is ${kind}/`);
|
|
8906
|
-
return notes.length ? ` (${notes.join(", ")})` : "";
|
|
8907
|
-
}, validateBranchNamePolicy = (name, policy, source) => {
|
|
8908
|
-
const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
|
|
8909
|
-
const kinds = allowedKinds(policy.allowed);
|
|
8910
|
-
if (policy.protected.has(name.toLowerCase())) {
|
|
8911
|
-
const correction = kinds.length ? `choose a non-protected branch of an allowed kind (${kindList(kinds)}), e.g. ${kinds[0]}/<slug>: workit git branch --kind ${kinds[0]} --slug <slug>` : `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
|
|
8912
|
-
return {
|
|
8913
|
-
ok: false,
|
|
8914
|
-
rule: "protected_ref",
|
|
8915
|
-
source,
|
|
8916
|
-
attempted: name,
|
|
8917
|
-
correction,
|
|
8918
|
-
error: `protected_ref: branch ${JSON.stringify(name)} is protected by ${source}; ${correction}`
|
|
8919
|
-
};
|
|
8920
|
-
}
|
|
8921
|
-
if (!policy.allowed.some((pattern) => pattern.test(name))) {
|
|
8922
|
-
const suggestion = suggestBranchName(name, kinds);
|
|
8923
|
-
const correction = suggestion && policy.allowed.some((pattern) => pattern.test(suggestion)) ? `use ${JSON.stringify(suggestion)}; the allowed kinds are ${kindList(kinds)} (workit git branch ${suggestion})` : `choose a branch matching one of: ${allowed.join(", ")}`;
|
|
8924
|
-
return {
|
|
8925
|
-
ok: false,
|
|
8926
|
-
rule: "allowed_pattern",
|
|
8927
|
-
source,
|
|
8928
|
-
attempted: name,
|
|
8929
|
-
correction,
|
|
8930
|
-
error: `allowed_pattern: branch ${JSON.stringify(name)} violates ${source} policy; ${correction}`
|
|
8931
|
-
};
|
|
8932
|
-
}
|
|
8933
|
-
return { ok: true };
|
|
8934
|
-
}, branchPolicySnapshotFor = (workspaceRoot) => {
|
|
8935
|
-
const resolved = effectiveWorkspacePolicy(workspaceRoot, "branch");
|
|
8936
|
-
const branchPolicy = resolved.branchPolicy;
|
|
8937
|
-
const source = resolved.provenance.branchPolicy;
|
|
8938
|
-
const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
|
|
8939
|
-
const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
|
|
8940
|
-
const fingerprint = createHash6("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
|
|
8941
|
-
return {
|
|
8942
|
-
branchPolicy,
|
|
8943
|
-
namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
|
|
8944
|
-
source,
|
|
8945
|
-
fingerprint
|
|
8946
|
-
};
|
|
8947
|
-
}, validateBranchNameFor = (workspaceRoot, name) => {
|
|
8948
|
-
try {
|
|
8949
|
-
const resolved = branchPolicySnapshotFor(workspaceRoot);
|
|
8950
|
-
return validateBranchNamePolicy(name, resolved.namePolicy, resolved.source);
|
|
8951
|
-
} catch (error) {
|
|
8952
|
-
const correction = "repair or select a valid workspace policy before creating a branch";
|
|
8953
|
-
return {
|
|
8954
|
-
ok: false,
|
|
8955
|
-
rule: "policy_unavailable",
|
|
8956
|
-
source: "workspaces.json",
|
|
8957
|
-
attempted: name,
|
|
8958
|
-
correction,
|
|
8959
|
-
error: `policy_unavailable: cannot validate branch ${JSON.stringify(name)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
|
|
8960
|
-
};
|
|
8859
|
+
// packages/workit-core/src/git/default-base.ts
|
|
8860
|
+
function defaultBase(cwd) {
|
|
8861
|
+
const trunk = trackTrunk(cwd);
|
|
8862
|
+
if (trunk) {
|
|
8863
|
+
for (const candidate of [`origin/${trunk}`, trunk])
|
|
8864
|
+
if (resolveRef(cwd, candidate))
|
|
8865
|
+
return candidate;
|
|
8961
8866
|
}
|
|
8962
|
-
|
|
8867
|
+
const originHead = originHeadRef(cwd);
|
|
8868
|
+
if (originHead && resolveRef(cwd, originHead))
|
|
8869
|
+
return originHead;
|
|
8870
|
+
for (const candidate of [
|
|
8871
|
+
"origin/main",
|
|
8872
|
+
"origin/master",
|
|
8873
|
+
"main",
|
|
8874
|
+
"master",
|
|
8875
|
+
"origin/develop",
|
|
8876
|
+
"develop"
|
|
8877
|
+
])
|
|
8878
|
+
if (resolveRef(cwd, candidate))
|
|
8879
|
+
return candidate;
|
|
8880
|
+
return null;
|
|
8881
|
+
}
|
|
8882
|
+
var safeRef = (value) => value.length > 0 && !value.startsWith("-"), trackTrunk = (cwd) => {
|
|
8963
8883
|
try {
|
|
8964
|
-
|
|
8884
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
8885
|
+
if (resolved.ok === false || !resolved.releaseTrack || resolved.releaseTrack.blocking)
|
|
8965
8886
|
return null;
|
|
8966
|
-
const
|
|
8967
|
-
|
|
8968
|
-
const production = productionBranchOf(names);
|
|
8969
|
-
const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
|
|
8970
|
-
return { develop, production, developExists: names.has(develop) };
|
|
8887
|
+
const target = String(resolved.defaultTargetBranch ?? "").trim();
|
|
8888
|
+
return target && safeRef(target) ? target : null;
|
|
8971
8889
|
} catch {
|
|
8972
8890
|
return null;
|
|
8973
8891
|
}
|
|
8974
|
-
}
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8892
|
+
};
|
|
8893
|
+
var init_default_base = __esm(() => {
|
|
8894
|
+
init_vcs_config();
|
|
8895
|
+
init_rev();
|
|
8896
|
+
});
|
|
8897
|
+
|
|
8898
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
8899
|
+
function patternRegex(dir, pattern) {
|
|
8900
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
8901
|
+
return null;
|
|
8902
|
+
const anchored = pattern.includes("/");
|
|
8903
|
+
const body = pattern.replace(/^\//u, "");
|
|
8904
|
+
let source = "";
|
|
8905
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
8906
|
+
const char = body[index];
|
|
8907
|
+
if (char === "*" && body[index + 1] === "*") {
|
|
8908
|
+
const slash = body[index + 2] === "/";
|
|
8909
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
8910
|
+
index += slash ? 2 : 1;
|
|
8911
|
+
} else if (char === "*")
|
|
8912
|
+
source += "[^/]*";
|
|
8913
|
+
else if (char === "?")
|
|
8914
|
+
source += "[^/]";
|
|
8915
|
+
else if (char === "[") {
|
|
8916
|
+
const end = body.indexOf("]", index + 2);
|
|
8917
|
+
if (end < 0)
|
|
8918
|
+
return null;
|
|
8919
|
+
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
8920
|
+
source += `[${set}]`;
|
|
8921
|
+
index = end;
|
|
8922
|
+
} else if (char === "\\" && index + 1 < body.length) {
|
|
8923
|
+
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
8924
|
+
index += 1;
|
|
8925
|
+
} else
|
|
8926
|
+
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
8985
8927
|
}
|
|
8986
|
-
|
|
8928
|
+
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
8987
8929
|
try {
|
|
8988
|
-
|
|
8989
|
-
const kinds = allowedKinds(patterns);
|
|
8990
|
-
const kindShaped = new Set(kinds.map((kind) => branchGlobPattern(`${kind}/*`).source));
|
|
8991
|
-
const anyName = patterns.some((pattern) => pattern.source === "^.*$");
|
|
8992
|
-
const others = patterns.filter((pattern) => !kindShaped.has(pattern.source)).map((pattern) => globOf(pattern));
|
|
8993
|
-
const allowed = anyName ? "Any non-protected branch name is allowed here." : `${kinds.length ? `Allowed branch kinds here: ${kindList(kinds)}${aliasNote(kinds)}` : "Allowed branch names here:"}${others.length ? `${kinds.length ? "; also" : ""} names matching ${others.join(", ")}` : ""}; anything else is denied.`;
|
|
8994
|
-
const resolved = vcsConfig("resolve", cwd);
|
|
8995
|
-
const integration = resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
|
|
8996
|
-
const flow = resolved.releaseTrack?.track ? null : gitFlowOf(cwd, integration);
|
|
8997
|
-
if (flow?.developExists)
|
|
8998
|
-
return `${allowed} git-flow: feature/, bugfix/ and release/ start from origin/${flow.develop}, hotfix/ from origin/${flow.production}; feature/ and bugfix/ land on ${flow.develop}, release/ and hotfix/ on ${flow.production}.`;
|
|
8999
|
-
const names = branchNames(cwd);
|
|
9000
|
-
const configured = String(resolved.baseBranch ?? integration ?? "") || null;
|
|
9001
|
-
const trunk = productionBranchOf(names);
|
|
9002
|
-
const base = configured && names.has(configured) ? `Branch from origin/${configured}.` : names.has(trunk) ? `Branch from origin/${trunk}${configured ? ` (the default target ${configured} does not exist here)` : ""}.` : "No base branch exists here yet; pass the base explicitly.";
|
|
9003
|
-
return `${allowed} ${base}`;
|
|
8930
|
+
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
9004
8931
|
} catch {
|
|
9005
8932
|
return null;
|
|
9006
8933
|
}
|
|
9007
|
-
}
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
8934
|
+
}
|
|
8935
|
+
function rulesOf(dir, text) {
|
|
8936
|
+
const out = [];
|
|
8937
|
+
for (const raw of text.split(`
|
|
8938
|
+
`)) {
|
|
8939
|
+
const line = raw.trim();
|
|
8940
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
8941
|
+
continue;
|
|
8942
|
+
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
8943
|
+
let generated = null;
|
|
8944
|
+
for (const attribute of attributes) {
|
|
8945
|
+
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
8946
|
+
generated = true;
|
|
8947
|
+
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
8948
|
+
generated = false;
|
|
8949
|
+
}
|
|
8950
|
+
if (generated === null)
|
|
8951
|
+
continue;
|
|
8952
|
+
const regex = patternRegex(dir, pattern);
|
|
8953
|
+
if (regex)
|
|
8954
|
+
out.push({ regex, generated });
|
|
9012
8955
|
}
|
|
9013
|
-
|
|
9014
|
-
var init_branch = __esm(() => {
|
|
9015
|
-
init_branch_policy();
|
|
9016
|
-
init_commit_flavors();
|
|
9017
|
-
init_git();
|
|
9018
|
-
init_config();
|
|
9019
|
-
init_workspaces();
|
|
9020
|
-
init_vcs_config();
|
|
9021
|
-
init_release_tracks();
|
|
9022
|
-
KIND_SYNONYMS = {
|
|
9023
|
-
fix: "bugfix",
|
|
9024
|
-
fixes: "bugfix",
|
|
9025
|
-
bug: "bugfix",
|
|
9026
|
-
bugs: "bugfix",
|
|
9027
|
-
bugfixes: "bugfix",
|
|
9028
|
-
feat: "feature",
|
|
9029
|
-
features: "feature",
|
|
9030
|
-
chore: "feature",
|
|
9031
|
-
docs: "feature",
|
|
9032
|
-
doc: "feature",
|
|
9033
|
-
refactor: "feature",
|
|
9034
|
-
test: "feature",
|
|
9035
|
-
tests: "feature",
|
|
9036
|
-
ci: "feature",
|
|
9037
|
-
build: "feature",
|
|
9038
|
-
perf: "feature",
|
|
9039
|
-
style: "feature",
|
|
9040
|
-
deps: "feature",
|
|
9041
|
-
task: "feature",
|
|
9042
|
-
enhancement: "feature",
|
|
9043
|
-
hot: "hotfix",
|
|
9044
|
-
hotfixes: "hotfix",
|
|
9045
|
-
releases: "release",
|
|
9046
|
-
rel: "release"
|
|
9047
|
-
};
|
|
9048
|
-
});
|
|
9049
|
-
|
|
9050
|
-
// packages/workit-core/src/core/logger.ts
|
|
9051
|
-
import os3 from "node:os";
|
|
9052
|
-
import path6 from "node:path";
|
|
9053
|
-
var SENSITIVE_WORDS, STACK_WORDS, escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), resolveStateDir = () => {
|
|
9054
|
-
const override = process.env.WORKFLOW_TOOLKIT_STATE;
|
|
9055
|
-
if (override)
|
|
9056
|
-
return override;
|
|
9057
|
-
const home = os3.homedir();
|
|
9058
|
-
if (process.env.XDG_STATE_HOME)
|
|
9059
|
-
return path6.join(process.env.XDG_STATE_HOME, "workit");
|
|
9060
|
-
switch (os3.platform()) {
|
|
9061
|
-
case "darwin":
|
|
9062
|
-
return path6.join(home, "Library", "Application Support", "workit");
|
|
9063
|
-
case "win32":
|
|
9064
|
-
return path6.join(process.env.LOCALAPPDATA ?? path6.join(home, "AppData", "Local"), "workit");
|
|
9065
|
-
default:
|
|
9066
|
-
return path6.join(process.env.HOME ?? home, ".local", "state", "workit");
|
|
9067
|
-
}
|
|
9068
|
-
}, homePattern;
|
|
9069
|
-
var init_logger = __esm(() => {
|
|
9070
|
-
SENSITIVE_WORDS = new Set([
|
|
9071
|
-
"token",
|
|
9072
|
-
"secret",
|
|
9073
|
-
"password",
|
|
9074
|
-
"passwd",
|
|
9075
|
-
"authorization",
|
|
9076
|
-
"credential",
|
|
9077
|
-
"credentials",
|
|
9078
|
-
"cookie",
|
|
9079
|
-
"api",
|
|
9080
|
-
"apikey",
|
|
9081
|
-
"apitoken",
|
|
9082
|
-
"accesstoken",
|
|
9083
|
-
"clientsecret",
|
|
9084
|
-
"refreshtoken",
|
|
9085
|
-
"key",
|
|
9086
|
-
"bearer",
|
|
9087
|
-
"prompt",
|
|
9088
|
-
"message",
|
|
9089
|
-
"messages",
|
|
9090
|
-
"content",
|
|
9091
|
-
"body",
|
|
9092
|
-
"args",
|
|
9093
|
-
"argument",
|
|
9094
|
-
"result",
|
|
9095
|
-
"results",
|
|
9096
|
-
"output",
|
|
9097
|
-
"issue",
|
|
9098
|
-
"description",
|
|
9099
|
-
"summary",
|
|
9100
|
-
"payload",
|
|
9101
|
-
"script",
|
|
9102
|
-
"command",
|
|
9103
|
-
"text",
|
|
9104
|
-
"query"
|
|
9105
|
-
]);
|
|
9106
|
-
STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
|
|
9107
|
-
homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
|
|
9108
|
-
});
|
|
9109
|
-
|
|
9110
|
-
// packages/workit-core/src/host-session.ts
|
|
9111
|
-
import { createHash as createHash7, randomBytes, timingSafeEqual } from "node:crypto";
|
|
9112
|
-
import fs5 from "node:fs";
|
|
9113
|
-
import path7 from "node:path";
|
|
9114
|
-
function hostSessionFromEnv(env) {
|
|
9115
|
-
const value = (key) => env[key]?.trim() || null;
|
|
9116
|
-
let inner = null;
|
|
9117
|
-
for (const [key, host] of HOST_SESSION_VARS) {
|
|
9118
|
-
const session = value(key);
|
|
9119
|
-
if (session) {
|
|
9120
|
-
inner = { host: host(env), session };
|
|
9121
|
-
break;
|
|
9122
|
-
}
|
|
9123
|
-
}
|
|
9124
|
-
const declared = value("WORKIT_HOST");
|
|
9125
|
-
if (env.WORKIT_SESSION_ID !== undefined) {
|
|
9126
|
-
const inherited = inner !== null && declared !== null && family(declared) !== family(inner.host);
|
|
9127
|
-
if (!inherited)
|
|
9128
|
-
return { host: declared, session: value("WORKIT_SESSION_ID") };
|
|
9129
|
-
}
|
|
9130
|
-
return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
|
|
9131
|
-
}
|
|
9132
|
-
function migrateRepoIssued(cwd) {
|
|
9133
|
-
if (cwd === undefined)
|
|
9134
|
-
return;
|
|
9135
|
-
let old;
|
|
9136
|
-
try {
|
|
9137
|
-
const location = resolveStore(cwd);
|
|
9138
|
-
if (location instanceof Error)
|
|
9139
|
-
return;
|
|
9140
|
-
old = path7.join(location.dir, "hooks", "issued");
|
|
9141
|
-
if (!fs5.existsSync(old))
|
|
9142
|
-
return;
|
|
9143
|
-
} catch {
|
|
9144
|
-
return;
|
|
9145
|
-
}
|
|
9146
|
-
for (const lead of safeList(old)) {
|
|
9147
|
-
try {
|
|
9148
|
-
const from = path7.join(old, lead);
|
|
9149
|
-
if (!fs5.statSync(from).isDirectory())
|
|
9150
|
-
continue;
|
|
9151
|
-
const to = path7.join(userIssuedRoot(), lead);
|
|
9152
|
-
fs5.mkdirSync(to, { recursive: true, mode: 448 });
|
|
9153
|
-
for (const id of fs5.readdirSync(from)) {
|
|
9154
|
-
try {
|
|
9155
|
-
fs5.copyFileSync(path7.join(from, id), path7.join(to, id), fs5.constants.COPYFILE_EXCL);
|
|
9156
|
-
} catch (error) {
|
|
9157
|
-
if (error.code !== "EEXIST")
|
|
9158
|
-
throw error;
|
|
9159
|
-
}
|
|
9160
|
-
fs5.rmSync(path7.join(from, id), { force: true });
|
|
9161
|
-
}
|
|
9162
|
-
removeIfEmpty(from);
|
|
9163
|
-
} catch {}
|
|
9164
|
-
}
|
|
9165
|
-
removeIfEmpty(old);
|
|
9166
|
-
}
|
|
9167
|
-
function recordIssuedSession(cwd, lead, issued) {
|
|
9168
|
-
const token = randomBytes(18).toString("hex");
|
|
9169
|
-
try {
|
|
9170
|
-
migrateRepoIssued(cwd);
|
|
9171
|
-
const own = hash(issued);
|
|
9172
|
-
const tokens = path7.join(userTokenRoot(), hash(lead));
|
|
9173
|
-
fs5.mkdirSync(tokens, { recursive: true, mode: 448 });
|
|
9174
|
-
fs5.writeFileSync(path7.join(tokens, own), `${tokenHash(token)}
|
|
9175
|
-
`, { mode: 384, flag: "wx" });
|
|
9176
|
-
const dir = path7.join(userIssuedRoot(), hash(lead));
|
|
9177
|
-
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
9178
|
-
fs5.writeFileSync(path7.join(dir, own), `${ISSUED_SESSIONS_MARKER}
|
|
9179
|
-
`, { mode: 384 });
|
|
9180
|
-
pruneIssued(dir, own, tokens);
|
|
9181
|
-
return token;
|
|
9182
|
-
} catch {
|
|
9183
|
-
return null;
|
|
9184
|
-
}
|
|
9185
|
-
}
|
|
9186
|
-
function judgeBranch(cwd) {
|
|
9187
|
-
const branch = currentBranch(cwd);
|
|
9188
|
-
return branch && !isProtectedTarget(cwd, branch) ? branch : null;
|
|
9189
|
-
}
|
|
9190
|
-
function verdictCommand(options) {
|
|
9191
|
-
const kind = options.judge === "verifier" ? "<live|unit>" : options.judge === "reviewer" ? "review" : "<live|unit|review>";
|
|
9192
|
-
return [
|
|
9193
|
-
"workit ledger verdict <verified|failed|blocked>",
|
|
9194
|
-
`--kind ${kind}`,
|
|
9195
|
-
`--branch ${options.branch ?? "<branch>"}`,
|
|
9196
|
-
options.session ? `--session ${options.session}` : null,
|
|
9197
|
-
options.token ? `--token=${options.token}` : null,
|
|
9198
|
-
'--how "<what you ran and saw>"'
|
|
9199
|
-
].filter(Boolean).join(" ");
|
|
9200
|
-
}
|
|
9201
|
-
function pruneIssued(dir, own, tokens) {
|
|
9202
|
-
try {
|
|
9203
|
-
const entries = fs5.readdirSync(dir);
|
|
9204
|
-
if (entries.length <= ISSUED_PER_LEAD)
|
|
9205
|
-
return;
|
|
9206
|
-
const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs5.statSync(path7.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
|
|
9207
|
-
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
|
|
9208
|
-
fs5.rmSync(path7.join(dir, name), { force: true });
|
|
9209
|
-
fs5.rmSync(path7.join(tokens, name), { force: true });
|
|
9210
|
-
}
|
|
9211
|
-
} catch {}
|
|
9212
|
-
}
|
|
9213
|
-
function issuedBinding(cwd, claimed) {
|
|
9214
|
-
const record = issuedRecord(cwd, claimed);
|
|
9215
|
-
const { stored, leadBound } = issuedToken(claimed);
|
|
9216
|
-
if (stored)
|
|
9217
|
-
return "token";
|
|
9218
|
-
if (record === null)
|
|
9219
|
-
return null;
|
|
9220
|
-
return leadBound ? "token" : "legacy";
|
|
9221
|
-
}
|
|
9222
|
-
var HOST_SESSION_VARS, family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null, ISSUED_PER_LEAD = 64, ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3", hash = (value) => createHash7("sha256").update(value).digest("hex").slice(0, 32), userIssuedRoot = () => path7.join(resolveStateDir(), "issued"), userTokenRoot = () => path7.join(resolveStateDir(), "issued-tokens"), safeList = (dir) => {
|
|
9223
|
-
try {
|
|
9224
|
-
return fs5.readdirSync(dir);
|
|
9225
|
-
} catch {
|
|
9226
|
-
return [];
|
|
9227
|
-
}
|
|
9228
|
-
}, removeIfEmpty = (dir) => {
|
|
9229
|
-
try {
|
|
9230
|
-
fs5.rmdirSync(dir);
|
|
9231
|
-
} catch {}
|
|
9232
|
-
}, tokenHash = (token) => createHash7("sha256").update(token).digest("hex"), issuedRecord = (cwd, claimed) => {
|
|
9233
|
-
const cut = claimed.lastIndexOf(":");
|
|
9234
|
-
if (cut <= 0)
|
|
9235
|
-
return null;
|
|
9236
|
-
migrateRepoIssued(cwd);
|
|
9237
|
-
try {
|
|
9238
|
-
return fs5.readFileSync(path7.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
|
|
9239
|
-
} catch {
|
|
9240
|
-
return null;
|
|
9241
|
-
}
|
|
9242
|
-
}, TOKEN_HASH, issuedToken = (claimed) => {
|
|
9243
|
-
const cut = claimed.lastIndexOf(":");
|
|
9244
|
-
if (cut <= 0)
|
|
9245
|
-
return { stored: null, leadBound: false };
|
|
9246
|
-
const dir = path7.join(userTokenRoot(), hash(claimed.slice(0, cut)));
|
|
9247
|
-
try {
|
|
9248
|
-
const text = fs5.readFileSync(path7.join(dir, hash(claimed)), "utf8");
|
|
9249
|
-
return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
|
|
9250
|
-
} catch {
|
|
9251
|
-
return { stored: null, leadBound: safeList(dir).length > 0 };
|
|
9252
|
-
}
|
|
9253
|
-
}, OWN_CHILD_SHELL;
|
|
9254
|
-
var init_host_session = __esm(() => {
|
|
9255
|
-
init_branch();
|
|
9256
|
-
init_logger();
|
|
9257
|
-
init_rev();
|
|
9258
|
-
init_paths();
|
|
9259
|
-
HOST_SESSION_VARS = [
|
|
9260
|
-
["OPENCODE_SESSION_ID", () => "opencode"],
|
|
9261
|
-
["PI_SESSION_ID", () => "pi"],
|
|
9262
|
-
[
|
|
9263
|
-
"CODEX_THREAD_ID",
|
|
9264
|
-
(env) => env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || env.CODEX_ELECTRON_RESOURCES_PATH ? "codex_desktop" : "codex_cli"
|
|
9265
|
-
]
|
|
9266
|
-
];
|
|
9267
|
-
TOKEN_HASH = /^([0-9a-f]{64})$/mu;
|
|
9268
|
-
OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
|
|
9269
|
-
});
|
|
9270
|
-
|
|
9271
|
-
// packages/workit-core/src/hooks/standing-signal.ts
|
|
9272
|
-
import { createHash as createHash8 } from "node:crypto";
|
|
9273
|
-
import fs6 from "node:fs";
|
|
9274
|
-
import path8 from "node:path";
|
|
9275
|
-
var gitStoreDir = (cwd) => {
|
|
9276
|
-
try {
|
|
9277
|
-
for (let dir = path8.resolve(cwd);; dir = path8.dirname(dir)) {
|
|
9278
|
-
const dotGit = path8.join(dir, ".git");
|
|
9279
|
-
if (fs6.existsSync(dotGit)) {
|
|
9280
|
-
let gitDir = dotGit;
|
|
9281
|
-
if (fs6.statSync(dotGit).isFile()) {
|
|
9282
|
-
const target = /^gitdir:\s*(.+)$/mu.exec(fs6.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
9283
|
-
if (!target)
|
|
9284
|
-
return null;
|
|
9285
|
-
gitDir = path8.resolve(dir, target);
|
|
9286
|
-
const common = path8.join(gitDir, "commondir");
|
|
9287
|
-
if (fs6.existsSync(common))
|
|
9288
|
-
gitDir = path8.resolve(gitDir, fs6.readFileSync(common, "utf8").trim());
|
|
9289
|
-
}
|
|
9290
|
-
return path8.join(gitDir, "workit");
|
|
9291
|
-
}
|
|
9292
|
-
if (path8.dirname(dir) === dir)
|
|
9293
|
-
return null;
|
|
9294
|
-
}
|
|
9295
|
-
} catch {
|
|
9296
|
-
return null;
|
|
9297
|
-
}
|
|
9298
|
-
}, standingRevFile = (store) => path8.join(store, "standing", "rev"), standingSeenFile = (store, session, agentId) => path8.join(store, "standing", "seen", `${createHash8("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`), readStandingRev = (store) => {
|
|
9299
|
-
try {
|
|
9300
|
-
return fs6.readFileSync(standingRevFile(store), "utf8").trim() || null;
|
|
9301
|
-
} catch {
|
|
9302
|
-
return null;
|
|
9303
|
-
}
|
|
9304
|
-
}, readStandingSeen = (file) => {
|
|
9305
|
-
try {
|
|
9306
|
-
const value = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
9307
|
-
return {
|
|
9308
|
-
rev: typeof value.rev === "string" ? value.rev : null,
|
|
9309
|
-
ids: Array.isArray(value.ids) ? value.ids.filter((id) => typeof id === "string") : []
|
|
9310
|
-
};
|
|
9311
|
-
} catch {
|
|
9312
|
-
return { rev: null, ids: [] };
|
|
9313
|
-
}
|
|
9314
|
-
}, writeStandingSeen = (file, seen) => {
|
|
9315
|
-
try {
|
|
9316
|
-
fs6.mkdirSync(path8.dirname(file), { recursive: true });
|
|
9317
|
-
fs6.writeFileSync(file, JSON.stringify(seen));
|
|
9318
|
-
} catch {}
|
|
9319
|
-
};
|
|
9320
|
-
var init_standing_signal = () => {};
|
|
9321
|
-
|
|
9322
|
-
// packages/workit-core/src/ledger.ts
|
|
9323
|
-
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
9324
|
-
import { createHash as createHash9, randomBytes as randomBytes2 } from "node:crypto";
|
|
9325
|
-
import fs7 from "node:fs";
|
|
9326
|
-
import path9 from "node:path";
|
|
9327
|
-
function resolveCommit(cwd, ref) {
|
|
9328
|
-
if (!safeRef(ref))
|
|
9329
|
-
return null;
|
|
9330
|
-
return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
9331
|
-
}
|
|
9332
|
-
function branchHead(cwd, branch) {
|
|
9333
|
-
if (!safeRef(branch))
|
|
9334
|
-
return null;
|
|
9335
|
-
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
9336
|
-
}
|
|
9337
|
-
function defaultBase(cwd) {
|
|
9338
|
-
const trunk = trackTrunk(cwd);
|
|
9339
|
-
if (trunk) {
|
|
9340
|
-
for (const candidate of [`origin/${trunk}`, trunk])
|
|
9341
|
-
if (resolveCommit(cwd, candidate))
|
|
9342
|
-
return candidate;
|
|
9343
|
-
}
|
|
9344
|
-
const originHead = git3(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
|
|
9345
|
-
if (originHead && resolveCommit(cwd, originHead))
|
|
9346
|
-
return originHead;
|
|
9347
|
-
for (const candidate of [
|
|
9348
|
-
"origin/main",
|
|
9349
|
-
"origin/master",
|
|
9350
|
-
"main",
|
|
9351
|
-
"master",
|
|
9352
|
-
"origin/develop",
|
|
9353
|
-
"develop"
|
|
9354
|
-
])
|
|
9355
|
-
if (resolveCommit(cwd, candidate))
|
|
9356
|
-
return candidate;
|
|
9357
|
-
return null;
|
|
9358
|
-
}
|
|
9359
|
-
function diffHash(cwd, base, head) {
|
|
9360
|
-
if (!safeRef(base) || !safeRef(head))
|
|
9361
|
-
return null;
|
|
9362
|
-
const diff = gitRun2(cwd, [
|
|
9363
|
-
"diff",
|
|
9364
|
-
"-U3",
|
|
9365
|
-
"--diff-algorithm=myers",
|
|
9366
|
-
"--indent-heuristic",
|
|
9367
|
-
"--no-color",
|
|
9368
|
-
"--no-ext-diff",
|
|
9369
|
-
"--no-textconv",
|
|
9370
|
-
"--no-renames",
|
|
9371
|
-
"--full-index",
|
|
9372
|
-
"--binary",
|
|
9373
|
-
"--src-prefix=a/",
|
|
9374
|
-
"--dst-prefix=b/",
|
|
9375
|
-
`${base}...${head}`,
|
|
9376
|
-
"--"
|
|
9377
|
-
], GIT_TIMEOUTS.worktree);
|
|
9378
|
-
if (!diff.ok || !diff.stdout)
|
|
9379
|
-
return null;
|
|
9380
|
-
const hash = createHash9("sha256");
|
|
9381
|
-
for (const line of diff.stdout.split(`
|
|
9382
|
-
`)) {
|
|
9383
|
-
if (line.startsWith("index "))
|
|
9384
|
-
continue;
|
|
9385
|
-
hash.update(line.startsWith("@@ ") ? "@@" : line);
|
|
9386
|
-
hash.update(`
|
|
9387
|
-
`);
|
|
9388
|
-
}
|
|
9389
|
-
return `sha256:${hash.digest("hex")}`;
|
|
9390
|
-
}
|
|
9391
|
-
function storeRoot(cwd) {
|
|
9392
|
-
const location = resolveStore(path9.resolve(cwd));
|
|
9393
|
-
if (location instanceof Error)
|
|
9394
|
-
return err("unavailable", location.message.replace(/; fix the repository.*$/u, ""), "fix the repository (git status should work here) or run from a non-git directory");
|
|
9395
|
-
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
9396
|
-
}
|
|
9397
|
-
function ledgerPath(cwd) {
|
|
9398
|
-
const root = storeRoot(cwd);
|
|
9399
|
-
return root.ok ? { ok: true, value: path9.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
9400
|
-
}
|
|
9401
|
-
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
9402
|
-
const lock = `${file}.lock`;
|
|
9403
|
-
const deadline = Date.now() + waitMs;
|
|
9404
|
-
let token = null;
|
|
9405
|
-
for (;; ) {
|
|
9406
|
-
token = ledgerLock.acquire(lock);
|
|
9407
|
-
if (token)
|
|
9408
|
-
break;
|
|
9409
|
-
const observed = lockToken(lock);
|
|
9410
|
-
const age = lockAgeMs(lock);
|
|
9411
|
-
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
9412
|
-
continue;
|
|
9413
|
-
if (Date.now() >= deadline)
|
|
9414
|
-
return null;
|
|
9415
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
9416
|
-
}
|
|
9417
|
-
try {
|
|
9418
|
-
return fn();
|
|
9419
|
-
} finally {
|
|
9420
|
-
ledgerLock.release(lock, token);
|
|
9421
|
-
}
|
|
9422
|
-
}
|
|
9423
|
-
function appendLine(file, build, options) {
|
|
9424
|
-
const write = () => {
|
|
9425
|
-
const fd = fs7.openSync(file, "a+", 384);
|
|
9426
|
-
try {
|
|
9427
|
-
if (process.platform !== "win32")
|
|
9428
|
-
tighten(fd);
|
|
9429
|
-
const size = fs7.fstatSync(fd).size;
|
|
9430
|
-
let fence = "";
|
|
9431
|
-
if (size > 0) {
|
|
9432
|
-
const last = Buffer.alloc(1);
|
|
9433
|
-
fs7.readSync(fd, last, 0, 1, size - 1);
|
|
9434
|
-
if (last[0] !== 10)
|
|
9435
|
-
fence = `
|
|
9436
|
-
`;
|
|
9437
|
-
}
|
|
9438
|
-
const line = build(lastLine(fd, size));
|
|
9439
|
-
if (!line.ok)
|
|
9440
|
-
return line;
|
|
9441
|
-
const bytes = Buffer.from(fence + line.value);
|
|
9442
|
-
const written = fs7.writeSync(fd, bytes);
|
|
9443
|
-
if (written !== bytes.length)
|
|
9444
|
-
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
9445
|
-
return { ok: true, value: undefined };
|
|
9446
|
-
} finally {
|
|
9447
|
-
fs7.closeSync(fd);
|
|
9448
|
-
}
|
|
9449
|
-
};
|
|
9450
|
-
try {
|
|
9451
|
-
fs7.mkdirSync(path9.dirname(file), { recursive: true });
|
|
9452
|
-
if (!options.forceLock && !onNetworkFs(path9.dirname(file)))
|
|
9453
|
-
return write();
|
|
9454
|
-
const locked = withLedgerLock(file, write);
|
|
9455
|
-
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
9456
|
-
} catch (error) {
|
|
9457
|
-
return err("unavailable", `cannot append to ${file}: ${error.message}`);
|
|
9458
|
-
}
|
|
9459
|
-
}
|
|
9460
|
-
function appendRaw(cwd, row, options) {
|
|
9461
|
-
const file = ledgerPath(cwd);
|
|
9462
|
-
if (!file.ok)
|
|
9463
|
-
return file;
|
|
9464
|
-
const id = newId2();
|
|
9465
|
-
const at = (options.now ?? new Date).toISOString();
|
|
9466
|
-
let full = null;
|
|
9467
|
-
const written = appendLine(file.value, (previous) => {
|
|
9468
|
-
const next = sealed(row, id, at, previous);
|
|
9469
|
-
const bytes = Buffer.byteLength(next.line);
|
|
9470
|
-
if (bytes > MAX_LINE_BYTES)
|
|
9471
|
-
return err("invalid_input", `ledger row is ${bytes} bytes; the limit is ${MAX_LINE_BYTES}. Shorten the text and pass details by --ref <path|url>`);
|
|
9472
|
-
full = next.full;
|
|
9473
|
-
return { ok: true, value: next.line };
|
|
9474
|
-
}, options);
|
|
9475
|
-
return written.ok ? { ok: true, value: full } : written;
|
|
9476
|
-
}
|
|
9477
|
-
function observedRowBytes(row) {
|
|
9478
|
-
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
9479
|
-
return Buffer.byteLength(sample.line);
|
|
8956
|
+
return out;
|
|
9480
8957
|
}
|
|
9481
|
-
function
|
|
9482
|
-
|
|
8958
|
+
function generatedByAttributes(files, paths) {
|
|
8959
|
+
const rules = [...files].map(([file, text]) => {
|
|
8960
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
8961
|
+
return {
|
|
8962
|
+
depth: dir ? dir.split("/").length : 0,
|
|
8963
|
+
rules: rulesOf(dir, text)
|
|
8964
|
+
};
|
|
8965
|
+
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
8966
|
+
const out = new Set;
|
|
8967
|
+
for (const file of paths) {
|
|
8968
|
+
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
8969
|
+
if (last?.generated)
|
|
8970
|
+
out.add(file);
|
|
8971
|
+
}
|
|
8972
|
+
return out;
|
|
9483
8973
|
}
|
|
9484
|
-
|
|
9485
|
-
|
|
8974
|
+
|
|
8975
|
+
// packages/workit-core/src/core/policy/substance.ts
|
|
8976
|
+
function substanceOf(file, text) {
|
|
8977
|
+
const syntax = syntaxOf(file);
|
|
8978
|
+
if (!syntax)
|
|
8979
|
+
return plainSubstance(text);
|
|
8980
|
+
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
8981
|
+
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
8982
|
+
return plainSubstance(text);
|
|
8983
|
+
return lex(syntax, text);
|
|
9486
8984
|
}
|
|
9487
|
-
function
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
return "only the session that wrote a row can supersede it";
|
|
9492
|
-
if (!onBranch(target))
|
|
9493
|
-
return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
|
|
9494
|
-
if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
|
|
9495
|
-
return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
|
|
9496
|
-
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
9497
|
-
return "a self verdict cannot replace an independent verdict";
|
|
9498
|
-
return null;
|
|
8985
|
+
function plainSubstance(text) {
|
|
8986
|
+
return text.split(`
|
|
8987
|
+
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
8988
|
+
`);
|
|
9499
8989
|
}
|
|
9500
|
-
function
|
|
9501
|
-
const
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
let
|
|
9505
|
-
let
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
path: file.value,
|
|
9515
|
-
rows: [],
|
|
9516
|
-
skipped: 0,
|
|
9517
|
-
integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
|
|
9518
|
-
}
|
|
9519
|
-
};
|
|
9520
|
-
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
8990
|
+
function lex(syntax, text) {
|
|
8991
|
+
const collapse = !syntax.indented;
|
|
8992
|
+
let out = "";
|
|
8993
|
+
let lineStart = 0;
|
|
8994
|
+
let kept = 0;
|
|
8995
|
+
let pendingSpace = false;
|
|
8996
|
+
const frames = [{ kind: "code", braces: 0 }];
|
|
8997
|
+
let index = 0;
|
|
8998
|
+
if (text.startsWith("#!")) {
|
|
8999
|
+
const end = text.indexOf(`
|
|
9000
|
+
`);
|
|
9001
|
+
out = end < 0 ? text : text.slice(0, end);
|
|
9002
|
+
kept = out.length;
|
|
9003
|
+
index = end < 0 ? text.length : end;
|
|
9521
9004
|
}
|
|
9522
|
-
const
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9005
|
+
const emitCode = (chunk) => {
|
|
9006
|
+
if (collapse && pendingSpace && out.length > lineStart) {
|
|
9007
|
+
const before = out[out.length - 1];
|
|
9008
|
+
if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
|
|
9009
|
+
out += " ";
|
|
9010
|
+
}
|
|
9011
|
+
pendingSpace = false;
|
|
9012
|
+
out += chunk;
|
|
9013
|
+
};
|
|
9014
|
+
const emitVerbatim = (chunk) => {
|
|
9015
|
+
emitCode(chunk);
|
|
9016
|
+
kept = out.length;
|
|
9017
|
+
};
|
|
9018
|
+
const newline = () => {
|
|
9019
|
+
if (!collapse) {
|
|
9020
|
+
let end = out.length;
|
|
9021
|
+
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
9022
|
+
end -= 1;
|
|
9023
|
+
out = out.slice(0, end);
|
|
9024
|
+
}
|
|
9025
|
+
pendingSpace = false;
|
|
9026
|
+
if (out.length > lineStart) {
|
|
9027
|
+
out += `
|
|
9028
|
+
`;
|
|
9029
|
+
lineStart = out.length;
|
|
9030
|
+
}
|
|
9031
|
+
};
|
|
9032
|
+
const regexAllowed = () => {
|
|
9033
|
+
let end = out.length;
|
|
9034
|
+
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
9035
|
+
end -= 1;
|
|
9036
|
+
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
9037
|
+
return true;
|
|
9038
|
+
let start = end;
|
|
9039
|
+
while (start > 0 && WORD.test(out[start - 1]))
|
|
9040
|
+
start -= 1;
|
|
9041
|
+
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
9042
|
+
};
|
|
9043
|
+
const scanString = (quote, escapes, multiline) => {
|
|
9044
|
+
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
9045
|
+
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
9046
|
+
let end = index + quote.length;
|
|
9047
|
+
while (end < text.length) {
|
|
9048
|
+
const char = text[end];
|
|
9049
|
+
if (escapes && char === "\\") {
|
|
9050
|
+
end += 2;
|
|
9051
|
+
continue;
|
|
9052
|
+
}
|
|
9053
|
+
if (text.startsWith(quote, end)) {
|
|
9054
|
+
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
9055
|
+
return false;
|
|
9056
|
+
emitVerbatim(text.slice(index, end + quote.length));
|
|
9057
|
+
index = end + quote.length;
|
|
9058
|
+
return true;
|
|
9059
|
+
}
|
|
9060
|
+
if (char === `
|
|
9061
|
+
` && !multiline)
|
|
9062
|
+
return false;
|
|
9063
|
+
end += 1;
|
|
9064
|
+
}
|
|
9065
|
+
return false;
|
|
9066
|
+
};
|
|
9067
|
+
while (index < text.length) {
|
|
9068
|
+
const frame = frames[frames.length - 1];
|
|
9069
|
+
const char = text[index];
|
|
9070
|
+
if (frame.kind === "template") {
|
|
9071
|
+
if (char === "\\") {
|
|
9072
|
+
emitVerbatim(text.slice(index, index + 2));
|
|
9073
|
+
index += 2;
|
|
9074
|
+
} else if (char === "`") {
|
|
9075
|
+
emitVerbatim(char);
|
|
9076
|
+
frames.pop();
|
|
9077
|
+
index += 1;
|
|
9078
|
+
} else if (text.startsWith("${", index)) {
|
|
9079
|
+
emitVerbatim("${");
|
|
9080
|
+
frames.push({ kind: "code", braces: 0 });
|
|
9081
|
+
index += 2;
|
|
9082
|
+
} else {
|
|
9083
|
+
out += char;
|
|
9084
|
+
kept = out.length;
|
|
9085
|
+
index += 1;
|
|
9086
|
+
}
|
|
9532
9087
|
continue;
|
|
9533
|
-
|
|
9534
|
-
if (
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
parsed = JSON.parse(trimmed);
|
|
9539
|
-
} catch {
|
|
9540
|
-
skipped += 1;
|
|
9088
|
+
}
|
|
9089
|
+
if (char === `
|
|
9090
|
+
`) {
|
|
9091
|
+
newline();
|
|
9092
|
+
index += 1;
|
|
9541
9093
|
continue;
|
|
9542
9094
|
}
|
|
9543
|
-
if (
|
|
9544
|
-
|
|
9095
|
+
if (SPACE.test(char)) {
|
|
9096
|
+
if (collapse)
|
|
9097
|
+
pendingSpace = true;
|
|
9098
|
+
else
|
|
9099
|
+
out += char;
|
|
9100
|
+
index += 1;
|
|
9101
|
+
continue;
|
|
9102
|
+
}
|
|
9103
|
+
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
9104
|
+
if (lineOpener) {
|
|
9105
|
+
const end = text.indexOf(`
|
|
9106
|
+
`, index);
|
|
9107
|
+
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
9108
|
+
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
9109
|
+
emitVerbatim(comment);
|
|
9110
|
+
else
|
|
9111
|
+
pendingSpace = true;
|
|
9112
|
+
index = end < 0 ? text.length : end;
|
|
9113
|
+
continue;
|
|
9114
|
+
}
|
|
9115
|
+
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
9116
|
+
if (block) {
|
|
9117
|
+
const end = text.indexOf(block[1], index + block[0].length);
|
|
9118
|
+
if (end < 0)
|
|
9119
|
+
return null;
|
|
9120
|
+
const comment = text.slice(index, end + block[1].length);
|
|
9121
|
+
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
9122
|
+
emitVerbatim(comment);
|
|
9123
|
+
else
|
|
9124
|
+
pendingSpace = true;
|
|
9125
|
+
if (comment.includes(`
|
|
9126
|
+
`))
|
|
9127
|
+
newline();
|
|
9128
|
+
index = end + block[1].length;
|
|
9129
|
+
continue;
|
|
9130
|
+
}
|
|
9131
|
+
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
9132
|
+
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
9133
|
+
return null;
|
|
9134
|
+
continue;
|
|
9135
|
+
}
|
|
9136
|
+
if (syntax.quotes.includes(char)) {
|
|
9137
|
+
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
9138
|
+
return null;
|
|
9139
|
+
continue;
|
|
9140
|
+
}
|
|
9141
|
+
if (syntax.literal?.includes(char)) {
|
|
9142
|
+
if (!scanString(char, false, false))
|
|
9143
|
+
return null;
|
|
9144
|
+
continue;
|
|
9145
|
+
}
|
|
9146
|
+
if (syntax.rawBacktick && char === "`") {
|
|
9147
|
+
if (!scanString("`", false, true))
|
|
9148
|
+
return null;
|
|
9149
|
+
continue;
|
|
9150
|
+
}
|
|
9151
|
+
if (syntax.js && char === "`") {
|
|
9152
|
+
emitVerbatim(char);
|
|
9153
|
+
frames.push({ kind: "template" });
|
|
9154
|
+
index += 1;
|
|
9545
9155
|
continue;
|
|
9546
9156
|
}
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
if (
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
|
|
9566
|
-
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
actor: normalizeActor(parsed.actor),
|
|
9575
|
-
branch: str(parsed.branch),
|
|
9576
|
-
head: str(parsed.head),
|
|
9577
|
-
base: str(parsed.base),
|
|
9578
|
-
baseSha: str(parsed.baseSha),
|
|
9579
|
-
patchId: str(parsed.patchId),
|
|
9580
|
-
diffHash: str(parsed.diffHash),
|
|
9581
|
-
tree: str(parsed.tree),
|
|
9582
|
-
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
9583
|
-
...pr === undefined ? {} : { pr },
|
|
9584
|
-
...str(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
9585
|
-
seq: rows.length + 1,
|
|
9586
|
-
superseded: false,
|
|
9587
|
-
supersedeIgnored: false,
|
|
9588
|
-
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
9589
|
-
...reason ? { integrityReason: reason } : {}
|
|
9590
|
-
};
|
|
9591
|
-
rows.push(row);
|
|
9592
|
-
if (!options.integrity)
|
|
9157
|
+
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
9158
|
+
return null;
|
|
9159
|
+
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
9160
|
+
const lineEnd = text.indexOf(`
|
|
9161
|
+
`, index);
|
|
9162
|
+
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
9163
|
+
return null;
|
|
9164
|
+
}
|
|
9165
|
+
if (syntax.js && char === "/" && regexAllowed()) {
|
|
9166
|
+
let end = index + 1;
|
|
9167
|
+
let inClass = false;
|
|
9168
|
+
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
9169
|
+
if (text[end] === `
|
|
9170
|
+
`)
|
|
9171
|
+
return null;
|
|
9172
|
+
if (text[end] === "\\")
|
|
9173
|
+
end += 1;
|
|
9174
|
+
else if (text[end] === "[")
|
|
9175
|
+
inClass = true;
|
|
9176
|
+
else if (text[end] === "]")
|
|
9177
|
+
inClass = false;
|
|
9178
|
+
end += 1;
|
|
9179
|
+
}
|
|
9180
|
+
if (end >= text.length)
|
|
9181
|
+
return null;
|
|
9182
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
9183
|
+
index = end + 1;
|
|
9593
9184
|
continue;
|
|
9594
|
-
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9185
|
+
}
|
|
9186
|
+
if (frames.length > 1 && frame.kind === "code") {
|
|
9187
|
+
if (char === "{")
|
|
9188
|
+
frame.braces += 1;
|
|
9189
|
+
else if (char === "}") {
|
|
9190
|
+
if (frame.braces === 0) {
|
|
9191
|
+
emitVerbatim(char);
|
|
9192
|
+
frames.pop();
|
|
9193
|
+
index += 1;
|
|
9194
|
+
continue;
|
|
9195
|
+
}
|
|
9196
|
+
frame.braces -= 1;
|
|
9197
|
+
}
|
|
9198
|
+
}
|
|
9199
|
+
emitCode(char);
|
|
9200
|
+
index += 1;
|
|
9606
9201
|
}
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
const
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
if (
|
|
9618
|
-
|
|
9619
|
-
else if (row.supersedes)
|
|
9620
|
-
row.supersedeIgnored = true;
|
|
9621
|
-
if (!byId.has(row.id))
|
|
9622
|
-
byId.set(row.id, row);
|
|
9202
|
+
if (frames.length !== 1)
|
|
9203
|
+
return null;
|
|
9204
|
+
newline();
|
|
9205
|
+
return out;
|
|
9206
|
+
}
|
|
9207
|
+
function unbalanced(body) {
|
|
9208
|
+
let depth = 0;
|
|
9209
|
+
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
9210
|
+
if (part === "{")
|
|
9211
|
+
depth += 1;
|
|
9212
|
+
else if (part === "}")
|
|
9213
|
+
depth = Math.max(0, depth - 1);
|
|
9623
9214
|
}
|
|
9624
|
-
|
|
9625
|
-
tamperCache.set(file.value, {
|
|
9626
|
-
size: stat.size,
|
|
9627
|
-
stamp: fileStamp(stat),
|
|
9628
|
-
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
9629
|
-
});
|
|
9630
|
-
return {
|
|
9631
|
-
ok: true,
|
|
9632
|
-
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
9633
|
-
};
|
|
9215
|
+
return depth > 0;
|
|
9634
9216
|
}
|
|
9635
|
-
function
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9217
|
+
function hasOpaqueLineBreak(text) {
|
|
9218
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
9219
|
+
}
|
|
9220
|
+
function textOf(bytes) {
|
|
9221
|
+
if (bytes.includes(0))
|
|
9222
|
+
return null;
|
|
9639
9223
|
try {
|
|
9640
|
-
|
|
9641
|
-
const cached = tamperCache.get(file.value);
|
|
9642
|
-
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
9643
|
-
return cached.breaks;
|
|
9224
|
+
return decoder.decode(bytes);
|
|
9644
9225
|
} catch {
|
|
9645
|
-
return
|
|
9226
|
+
return null;
|
|
9646
9227
|
}
|
|
9647
|
-
const read = readLedger(cwd, { integrity: true });
|
|
9648
|
-
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
9649
9228
|
}
|
|
9650
|
-
function
|
|
9651
|
-
const
|
|
9652
|
-
const
|
|
9653
|
-
|
|
9229
|
+
function cosmeticChange(file, before, after) {
|
|
9230
|
+
const left = textOf(before);
|
|
9231
|
+
const right = textOf(after);
|
|
9232
|
+
if (left === null || right === null)
|
|
9233
|
+
return false;
|
|
9234
|
+
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
9235
|
+
return false;
|
|
9236
|
+
const a = substanceOf(file, left);
|
|
9237
|
+
return a !== null && a === substanceOf(file, right);
|
|
9654
9238
|
}
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
|
|
9239
|
+
var SLASH_BLOCK, C_LIKE, SYNTAXES, DIRECTIVE, extensionOf = (file) => {
|
|
9240
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
9241
|
+
const dot = name.lastIndexOf(".");
|
|
9242
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
9243
|
+
}, CI_PATH, syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null, SPACE, WORD, NO_SPACE_AFTER = "([{,;", NO_SPACE_BEFORE = ")]},;", REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^", REGEX_KEYWORDS, decoder;
|
|
9244
|
+
var init_substance = __esm(() => {
|
|
9245
|
+
SLASH_BLOCK = [["/*", "*/"]];
|
|
9246
|
+
C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
9247
|
+
SYNTAXES = Object.fromEntries([
|
|
9248
|
+
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
9249
|
+
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
9250
|
+
ext,
|
|
9251
|
+
{ ...C_LIKE, triple: true }
|
|
9252
|
+
]),
|
|
9253
|
+
["go", { ...C_LIKE, rawBacktick: true }],
|
|
9254
|
+
...["css", "scss", "less"].map((ext) => [
|
|
9255
|
+
ext,
|
|
9256
|
+
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
9257
|
+
]),
|
|
9258
|
+
...["py", "pyi"].map((ext) => [
|
|
9259
|
+
ext,
|
|
9260
|
+
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
9261
|
+
]),
|
|
9262
|
+
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
9263
|
+
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
9264
|
+
ext,
|
|
9265
|
+
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
9266
|
+
])
|
|
9267
|
+
]);
|
|
9268
|
+
DIRECTIVE = /@ts-|eslint|prettier|biome|oxlint|istanbul|\bc8\b|\bv8\b|@jsx|@flow|@vite|webpack|__PURE__|__NO_SIDE_EFFECTS__|source(?:Mapping)?URL|^\/\s*<|go:|\+build|nolint|noqa|type:|pyright|mypy|pylint|pragma|coding[:=]|fmt:|NOSONAR|@license|@preserve|@refresh|environment|language=|cython|ruff|isort|deno-|@vitest|@jest|swiftlint|ktlint|checkstyle|suppress|nullable|#(?:end)?region/iu;
|
|
9269
|
+
CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
9270
|
+
SPACE = /[ \t\r\f\v]/u;
|
|
9271
|
+
WORD = /[\w$]/u;
|
|
9272
|
+
REGEX_KEYWORDS = new Set([
|
|
9273
|
+
"return",
|
|
9274
|
+
"typeof",
|
|
9275
|
+
"instanceof",
|
|
9276
|
+
"case",
|
|
9277
|
+
"do",
|
|
9278
|
+
"else",
|
|
9279
|
+
"in",
|
|
9280
|
+
"of",
|
|
9281
|
+
"new",
|
|
9282
|
+
"delete",
|
|
9283
|
+
"void",
|
|
9284
|
+
"throw",
|
|
9285
|
+
"yield",
|
|
9286
|
+
"await"
|
|
9287
|
+
]);
|
|
9288
|
+
decoder = new TextDecoder("utf-8", { fatal: true });
|
|
9289
|
+
});
|
|
9290
|
+
|
|
9291
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
9292
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
9293
|
+
import path6 from "node:path";
|
|
9294
|
+
function kindOf(file) {
|
|
9295
|
+
if (file.binary)
|
|
9296
|
+
return "behavior";
|
|
9297
|
+
if (file.generated)
|
|
9298
|
+
return "generated";
|
|
9299
|
+
if (LOCKFILES.has(basename(file.path)))
|
|
9300
|
+
return "lockfile";
|
|
9301
|
+
if (isDoc(file.path))
|
|
9302
|
+
return "doc";
|
|
9303
|
+
if (file.cosmetic)
|
|
9304
|
+
return "cosmetic";
|
|
9305
|
+
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
9306
|
+
}
|
|
9307
|
+
function globMatch(pattern, file) {
|
|
9308
|
+
let regex = globCache.get(pattern);
|
|
9309
|
+
if (!regex) {
|
|
9310
|
+
let source = "";
|
|
9311
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
9312
|
+
const char = pattern[index];
|
|
9313
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
9314
|
+
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
9315
|
+
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
9316
|
+
} else if (char === "*")
|
|
9317
|
+
source += "[^/]*";
|
|
9318
|
+
else if (char === "?")
|
|
9319
|
+
source += "[^/]";
|
|
9320
|
+
else
|
|
9321
|
+
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
9322
|
+
}
|
|
9323
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
9324
|
+
globCache.set(pattern, regex);
|
|
9325
|
+
}
|
|
9326
|
+
return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
|
|
9661
9327
|
}
|
|
9662
|
-
function
|
|
9663
|
-
if (
|
|
9664
|
-
return
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
]);
|
|
9670
|
-
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
9328
|
+
function isRiskPath(file, extra = []) {
|
|
9329
|
+
if (extra.some((pattern) => globMatch(pattern, file)))
|
|
9330
|
+
return true;
|
|
9331
|
+
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
9332
|
+
return true;
|
|
9333
|
+
if (TEST_PATH.test(file) || isDoc(file))
|
|
9334
|
+
return false;
|
|
9335
|
+
return file.split("/").map((segment, index, all) => index === all.length - 1 ? segment.replace(/\.[^.]+$/u, "") : segment).some((segment) => segment.toLowerCase().split(/[-_.]/u).some((token) => RISK_TOKENS.has(token)));
|
|
9671
9336
|
}
|
|
9672
|
-
function
|
|
9673
|
-
const
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9337
|
+
function computeLane(input) {
|
|
9338
|
+
const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
|
|
9339
|
+
const risky = new Set(riskFiles);
|
|
9340
|
+
const kinds = input.files.map((file) => ({
|
|
9341
|
+
file,
|
|
9342
|
+
kind: risky.has(file.path) ? "behavior" : kindOf(file)
|
|
9343
|
+
}));
|
|
9344
|
+
const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
|
|
9345
|
+
const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
|
|
9346
|
+
const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
|
|
9347
|
+
const result = (lane, reasons) => ({
|
|
9348
|
+
lane,
|
|
9349
|
+
reasons,
|
|
9350
|
+
lines,
|
|
9351
|
+
files: behavior.length,
|
|
9352
|
+
riskFiles
|
|
9353
|
+
});
|
|
9354
|
+
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
9355
|
+
if (input.riskTier === "high")
|
|
9356
|
+
return result("full", ["the task judgment records high risk"]);
|
|
9357
|
+
if (riskFiles.length)
|
|
9358
|
+
return result("full", [
|
|
9359
|
+
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
9683
9360
|
]);
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9698
|
-
return (
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
9702
|
-
if (until === undefined)
|
|
9703
|
-
return false;
|
|
9704
|
-
const at = Date.parse(row.at);
|
|
9705
|
-
return at >= since && at <= until;
|
|
9706
|
-
};
|
|
9361
|
+
if (!behavior.length)
|
|
9362
|
+
return result("none", [
|
|
9363
|
+
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
9364
|
+
]);
|
|
9365
|
+
if (lines >= fullMinLines)
|
|
9366
|
+
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
9367
|
+
if (lines > selfMaxLines)
|
|
9368
|
+
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
9369
|
+
if (behavior.length > selfMaxFiles)
|
|
9370
|
+
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
9371
|
+
if (opaque.length)
|
|
9372
|
+
return result("focused", [
|
|
9373
|
+
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
9374
|
+
]);
|
|
9375
|
+
return result("self", [
|
|
9376
|
+
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
9377
|
+
]);
|
|
9707
9378
|
}
|
|
9708
|
-
function
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
if (
|
|
9712
|
-
|
|
9713
|
-
const
|
|
9714
|
-
if (
|
|
9715
|
-
|
|
9379
|
+
function untrackedStat(cwd, file) {
|
|
9380
|
+
try {
|
|
9381
|
+
const bytes = readFileSync3(path6.join(cwd, file));
|
|
9382
|
+
if (bytes.includes(0))
|
|
9383
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
9384
|
+
const text = bytes.toString("utf8");
|
|
9385
|
+
if (hasOpaqueLineBreak(text))
|
|
9386
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
9387
|
+
const lines = text.split(`
|
|
9388
|
+
`).length - (text.endsWith(`
|
|
9389
|
+
`) ? 1 : 0);
|
|
9390
|
+
return { path: file, added: lines, deleted: 0 };
|
|
9391
|
+
} catch {
|
|
9392
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
9716
9393
|
}
|
|
9717
|
-
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
9718
|
-
sessions.add(session);
|
|
9719
|
-
return sessions;
|
|
9720
9394
|
}
|
|
9721
|
-
function
|
|
9722
|
-
const
|
|
9723
|
-
if (!
|
|
9395
|
+
function cosmeticFiles(cwd, fork, head) {
|
|
9396
|
+
const raw = diffRaw(cwd, fork, head);
|
|
9397
|
+
if (!raw)
|
|
9724
9398
|
return null;
|
|
9725
|
-
const
|
|
9726
|
-
const
|
|
9727
|
-
|
|
9728
|
-
if (!issuedOnly)
|
|
9399
|
+
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
9400
|
+
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
9401
|
+
if (!blobs)
|
|
9729
9402
|
return null;
|
|
9730
|
-
const
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9403
|
+
const out = new Set;
|
|
9404
|
+
const opaque = new Set;
|
|
9405
|
+
for (const entry of candidates) {
|
|
9406
|
+
const before = blobs.get(entry.oldBlob);
|
|
9407
|
+
let after = blobs.get(entry.newBlob);
|
|
9408
|
+
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
9409
|
+
try {
|
|
9410
|
+
after = readFileSync3(path6.join(cwd, entry.path));
|
|
9411
|
+
} catch {
|
|
9412
|
+
after = undefined;
|
|
9413
|
+
}
|
|
9414
|
+
if (before && after && cosmeticChange(entry.path, before, after))
|
|
9415
|
+
out.add(entry.path);
|
|
9416
|
+
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
9417
|
+
opaque.add(entry.path);
|
|
9418
|
+
}
|
|
9419
|
+
return { cosmetic: out, opaque };
|
|
9734
9420
|
}
|
|
9735
|
-
function
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
if (
|
|
9739
|
-
return
|
|
9740
|
-
const
|
|
9741
|
-
|
|
9421
|
+
function generatedAt(cwd, fork, paths) {
|
|
9422
|
+
const files = treeFilesNamed(cwd, fork, ".gitattributes");
|
|
9423
|
+
const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
|
|
9424
|
+
if (!files || !blobs)
|
|
9425
|
+
return new Set;
|
|
9426
|
+
const texts = new Map;
|
|
9427
|
+
for (const [file, id] of files) {
|
|
9428
|
+
const bytes = blobs.get(id);
|
|
9429
|
+
if (bytes)
|
|
9430
|
+
texts.set(file, bytes.toString("utf8"));
|
|
9431
|
+
}
|
|
9432
|
+
return generatedByAttributes(texts, paths);
|
|
9742
9433
|
}
|
|
9743
|
-
function
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9434
|
+
function laneFiles(cwd, fork, head, attributesAt = fork) {
|
|
9435
|
+
const stats = diffNumstat(cwd, fork, head);
|
|
9436
|
+
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
9437
|
+
if (!stats || !cosmetic)
|
|
9438
|
+
return null;
|
|
9439
|
+
const files = [
|
|
9440
|
+
...stats.map((entry) => ({
|
|
9441
|
+
...entry,
|
|
9442
|
+
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
9443
|
+
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
9444
|
+
})),
|
|
9445
|
+
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
9446
|
+
];
|
|
9447
|
+
const generated = generatedAt(cwd, attributesAt, files.map((entry) => entry.path));
|
|
9448
|
+
return files.map((entry) => ({
|
|
9449
|
+
...entry,
|
|
9450
|
+
generated: generated.has(entry.path)
|
|
9451
|
+
}));
|
|
9754
9452
|
}
|
|
9755
|
-
function
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
}
|
|
9772
|
-
return effective;
|
|
9453
|
+
function driftFiles(cwd, range) {
|
|
9454
|
+
if (range.fromFork === range.toFork)
|
|
9455
|
+
return laneFiles(cwd, range.from, range.to, range.toFork);
|
|
9456
|
+
const drifted = ownChangeDrift(cwd, range);
|
|
9457
|
+
const stats = drifted ? diffNumstat(cwd, range.from, range.to) : null;
|
|
9458
|
+
if (!drifted || !stats)
|
|
9459
|
+
return null;
|
|
9460
|
+
const byPath = new Map(stats.map((entry) => [entry.path, entry]));
|
|
9461
|
+
const generated = generatedAt(cwd, range.toFork, drifted);
|
|
9462
|
+
return drifted.map((file) => ({
|
|
9463
|
+
path: file,
|
|
9464
|
+
added: byPath.get(file)?.added ?? 0,
|
|
9465
|
+
deleted: byPath.get(file)?.deleted ?? 0,
|
|
9466
|
+
...byPath.get(file)?.binary ? { binary: true } : {},
|
|
9467
|
+
cosmetic: false,
|
|
9468
|
+
generated: generated.has(file)
|
|
9469
|
+
}));
|
|
9773
9470
|
}
|
|
9774
|
-
function
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
const
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9471
|
+
function branchLane(cwd, options) {
|
|
9472
|
+
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
9473
|
+
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
9474
|
+
const head = options.workingTree ? null : options.head;
|
|
9475
|
+
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
9476
|
+
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
9477
|
+
const riskTier = options.judgment?.riskTier ?? null;
|
|
9478
|
+
if (!fork || !files) {
|
|
9479
|
+
const high = riskTier === "high";
|
|
9480
|
+
return {
|
|
9481
|
+
lane: high ? "full" : "focused",
|
|
9482
|
+
reasons: [
|
|
9483
|
+
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
9484
|
+
...high ? ["the task judgment records high risk"] : []
|
|
9485
|
+
],
|
|
9486
|
+
lines: 0,
|
|
9487
|
+
files: 0,
|
|
9488
|
+
riskFiles: [],
|
|
9489
|
+
base,
|
|
9490
|
+
head: options.head
|
|
9491
|
+
};
|
|
9792
9492
|
}
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
if (!base || !at)
|
|
9803
|
-
return { patchId: null, diffHash: null };
|
|
9804
|
-
const cacheKey = `${base}\x00${at}`;
|
|
9805
|
-
let key = keyCache.get(cacheKey);
|
|
9806
|
-
if (!key) {
|
|
9807
|
-
key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
|
|
9808
|
-
keyCache.set(cacheKey, key);
|
|
9809
|
-
}
|
|
9810
|
-
return key;
|
|
9811
|
-
};
|
|
9812
|
-
const keyFor = (base) => keysAt(base, head);
|
|
9813
|
-
const basisOf = (row) => {
|
|
9814
|
-
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
9815
|
-
return basis === "stale" && restackCarries(rows, branch, row.head, head) ? "carried" : basis;
|
|
9816
|
-
};
|
|
9817
|
-
const sameCode = (failed, row) => {
|
|
9818
|
-
if (failed.head && failed.head === row.head)
|
|
9819
|
-
return true;
|
|
9820
|
-
const base = failed.base ?? fallbackBase;
|
|
9821
|
-
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
9822
|
-
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
9823
|
-
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
|
|
9493
|
+
const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
|
|
9494
|
+
return {
|
|
9495
|
+
...computeLane({
|
|
9496
|
+
files,
|
|
9497
|
+
riskTier,
|
|
9498
|
+
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
9499
|
+
}),
|
|
9500
|
+
base,
|
|
9501
|
+
head: options.head
|
|
9824
9502
|
};
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9503
|
+
}
|
|
9504
|
+
var LANE_THRESHOLDS, RISK_TOKENS, RISK_FILES, RISK_NAMES, RISK_PATHS_FILE = ".workit/risk-paths", LOCKFILES, DOC_EXT, DOC_NAMES, INSTRUCTIONS, TEST_PATH, basename = (file) => file.slice(file.lastIndexOf("/") + 1), isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file))), globCache, parseRiskPaths = (text) => text.split(`
|
|
9505
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, "")), REGULAR, ABSENT, forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD"), laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
9506
|
+
var init_lane = __esm(() => {
|
|
9507
|
+
init_rev();
|
|
9508
|
+
init_default_base();
|
|
9509
|
+
init_substance();
|
|
9510
|
+
LANE_THRESHOLDS = {
|
|
9511
|
+
selfMaxLines: 100,
|
|
9512
|
+
selfMaxFiles: 5,
|
|
9513
|
+
fullMinLines: 500
|
|
9834
9514
|
};
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9515
|
+
RISK_TOKENS = new Set([
|
|
9516
|
+
"auth",
|
|
9517
|
+
"authn",
|
|
9518
|
+
"authz",
|
|
9519
|
+
"oauth",
|
|
9520
|
+
"security",
|
|
9521
|
+
"permission",
|
|
9522
|
+
"permissions",
|
|
9523
|
+
"secret",
|
|
9524
|
+
"secrets",
|
|
9525
|
+
"crypto",
|
|
9526
|
+
"gate",
|
|
9527
|
+
"gates",
|
|
9528
|
+
"policy",
|
|
9529
|
+
"policies",
|
|
9530
|
+
"hook",
|
|
9531
|
+
"hooks",
|
|
9532
|
+
"husky",
|
|
9533
|
+
"lefthook",
|
|
9534
|
+
"release",
|
|
9535
|
+
"releases",
|
|
9536
|
+
"migration",
|
|
9537
|
+
"migrations",
|
|
9538
|
+
"migrate",
|
|
9539
|
+
"workflows"
|
|
9540
|
+
]);
|
|
9541
|
+
RISK_FILES = new Set([
|
|
9542
|
+
".gitlab-ci.yml",
|
|
9543
|
+
"Jenkinsfile",
|
|
9544
|
+
"azure-pipelines.yml",
|
|
9545
|
+
".pre-commit-config.yaml",
|
|
9546
|
+
".workit/risk-paths"
|
|
9547
|
+
]);
|
|
9548
|
+
RISK_NAMES = new Set([".gitattributes"]);
|
|
9549
|
+
LOCKFILES = new Set([
|
|
9550
|
+
"bun.lock",
|
|
9551
|
+
"bun.lockb",
|
|
9552
|
+
"package-lock.json",
|
|
9553
|
+
"npm-shrinkwrap.json",
|
|
9554
|
+
"yarn.lock",
|
|
9555
|
+
"pnpm-lock.yaml",
|
|
9556
|
+
"Cargo.lock",
|
|
9557
|
+
"go.sum",
|
|
9558
|
+
"poetry.lock",
|
|
9559
|
+
"uv.lock",
|
|
9560
|
+
"Pipfile.lock",
|
|
9561
|
+
"Gemfile.lock",
|
|
9562
|
+
"composer.lock"
|
|
9563
|
+
]);
|
|
9564
|
+
DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc)$/iu;
|
|
9565
|
+
DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
9566
|
+
INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
9567
|
+
TEST_PATH = /(?:^|\/)(?:test|tests|__tests__)\/|\.(?:test|spec)\.[^/]+$|_test\.[^/]+$/iu;
|
|
9568
|
+
globCache = new Map;
|
|
9569
|
+
REGULAR = new Set(["100644", "100755"]);
|
|
9570
|
+
ABSENT = /^0+$/u;
|
|
9571
|
+
});
|
|
9572
|
+
|
|
9573
|
+
// packages/workit-core/src/core/policy/carry.ts
|
|
9574
|
+
function verdictDrift(cwd, input) {
|
|
9575
|
+
const since = input.from;
|
|
9576
|
+
const stale = (reason, files = null, focused = false) => ({
|
|
9577
|
+
carries: false,
|
|
9578
|
+
since,
|
|
9579
|
+
files,
|
|
9580
|
+
focused,
|
|
9581
|
+
reason
|
|
9582
|
+
});
|
|
9583
|
+
const fromFork = mergeBase(cwd, input.fromBase, input.from);
|
|
9584
|
+
const toFork = mergeBase(cwd, input.toBase, input.to);
|
|
9585
|
+
const files = fromFork && toFork ? driftFiles(cwd, { from: input.from, to: input.to, fromFork, toFork }) : null;
|
|
9586
|
+
if (!fromFork || !toFork || !files)
|
|
9587
|
+
return stale(`the change since ${since.slice(0, 12)} cannot be read`);
|
|
9588
|
+
const riskPaths = [fromFork, toFork].flatMap((fork) => parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? ""));
|
|
9589
|
+
const lane = computeLane({ files, riskPaths });
|
|
9590
|
+
const paths = files.map((file) => file.path);
|
|
9591
|
+
if (lane.lane === "none")
|
|
9592
|
+
return { carries: true, rule: "lane-none", since, files: paths };
|
|
9593
|
+
if (input.kind === "unit" && lane.lane === "self" && files.every((file) => TEST_ONLY_KINDS.has(kindOf(file))))
|
|
9594
|
+
return { carries: true, rule: "tests-only", since, files: paths };
|
|
9595
|
+
return stale(lane.reasons[0] ?? `lane ${lane.lane}`, paths, lane.lane === "self");
|
|
9596
|
+
}
|
|
9597
|
+
var TEST_ONLY_KINDS;
|
|
9598
|
+
var init_carry = __esm(() => {
|
|
9599
|
+
init_rev();
|
|
9600
|
+
init_lane();
|
|
9601
|
+
TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
|
|
9602
|
+
});
|
|
9603
|
+
|
|
9604
|
+
// packages/workit-core/src/core/commit-flavors.ts
|
|
9605
|
+
var init_commit_flavors = () => {};
|
|
9606
|
+
|
|
9607
|
+
// packages/workit-core/src/core/git.ts
|
|
9608
|
+
var init_git = () => {};
|
|
9609
|
+
|
|
9610
|
+
// packages/workit-core/src/core/branch.ts
|
|
9611
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
9612
|
+
function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
|
|
9613
|
+
const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
|
|
9614
|
+
const config = readConfig();
|
|
9615
|
+
return kind === "branch" ? {
|
|
9616
|
+
branchPolicy: withReleaseTracks(workspaceRoot, resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null, () => repoBranchPreset(workspaceRoot))),
|
|
9617
|
+
provenance: { branchPolicy: selected.source }
|
|
9618
|
+
} : {
|
|
9619
|
+
commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
|
|
9620
|
+
provenance: { commitPolicy: selected.source }
|
|
9847
9621
|
};
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9855
|
-
|
|
9856
|
-
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
if (!row)
|
|
9863
|
-
continue;
|
|
9864
|
-
const basis = basisOf(row);
|
|
9865
|
-
const current = basis !== "stale";
|
|
9866
|
-
const independence = reasonsOf(row);
|
|
9867
|
-
const reasons = [
|
|
9868
|
-
...current ? [] : ["stale"],
|
|
9869
|
-
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
9870
|
-
...independence
|
|
9871
|
-
];
|
|
9872
|
-
verdicts.push({
|
|
9873
|
-
kind,
|
|
9874
|
-
basis,
|
|
9875
|
-
current,
|
|
9876
|
-
independent: independence.length === 0,
|
|
9877
|
-
accepted: reasons.length === 0,
|
|
9878
|
-
reasons,
|
|
9879
|
-
verdict: row
|
|
9880
|
-
});
|
|
9881
|
-
if (reasons.length === 0) {
|
|
9882
|
-
for (const later of list)
|
|
9883
|
-
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
9884
|
-
warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
|
|
9885
|
-
}
|
|
9886
|
-
}
|
|
9887
|
-
for (const row of rows)
|
|
9888
|
-
if (row.integrity === "unverified" && scope(row))
|
|
9889
|
-
warnings.push(`ledger row ${row.id} (${row.type}) on ${row.branch ?? branch} is unverified: the workit CLI did not write it or it changed afterwards (workit ledger verify-integrity)`);
|
|
9890
|
-
const tampered = ledgerTampering(cwd);
|
|
9891
|
-
if (tampered.length) {
|
|
9892
|
-
warnings.push(tamperedWarning(cwd, tampered));
|
|
9893
|
-
for (const entry of verdicts) {
|
|
9894
|
-
entry.accepted = false;
|
|
9895
|
-
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
9622
|
+
}
|
|
9623
|
+
var withReleaseTracks = (workspaceRoot, policy) => {
|
|
9624
|
+
const read = workspaceReleaseTracks(workspaceRoot);
|
|
9625
|
+
if (read.error)
|
|
9626
|
+
throw new ReleaseTrackError(read.error);
|
|
9627
|
+
if (read.tracks.length === 0 && read.protectedBranches.length === 0)
|
|
9628
|
+
return policy;
|
|
9629
|
+
const known = new Set(policy.allowed.map((pattern) => pattern.source));
|
|
9630
|
+
const allowed = [...policy.allowed];
|
|
9631
|
+
for (const glob of read.tracks.flatMap((track) => Object.values(track.naming).map(namingGlob))) {
|
|
9632
|
+
const pattern = branchGlobPattern(glob);
|
|
9633
|
+
if (!known.has(pattern.source)) {
|
|
9634
|
+
known.add(pattern.source);
|
|
9635
|
+
allowed.push(pattern);
|
|
9896
9636
|
}
|
|
9897
9637
|
}
|
|
9898
|
-
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
9899
|
-
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
9900
|
-
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
9901
|
-
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
9902
|
-
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
9903
|
-
accepted: false,
|
|
9904
|
-
verdict: failing?.verdict ?? null,
|
|
9905
|
-
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
9906
|
-
};
|
|
9907
|
-
const latestSelf = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && (STRONG_RESULTS.has(String(entry.verdict.result)) || FAILING.has(String(entry.verdict.result))));
|
|
9908
|
-
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
9909
9638
|
return {
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
if (row.type === "standing" && typeof row.what === "string" && row.what.trim()) {
|
|
9929
|
-
if (!row.superseded)
|
|
9930
|
-
active.push(row);
|
|
9931
|
-
} else if (row.type === "standing.cleared") {
|
|
9932
|
-
const target = str(row.target);
|
|
9933
|
-
if (target === null)
|
|
9934
|
-
active.length = 0;
|
|
9935
|
-
else {
|
|
9936
|
-
const index = active.findIndex((order) => order.id === target);
|
|
9937
|
-
if (index >= 0)
|
|
9938
|
-
active.splice(index, 1);
|
|
9939
|
-
}
|
|
9639
|
+
...policy,
|
|
9640
|
+
allowed,
|
|
9641
|
+
protected: new Set([
|
|
9642
|
+
...policy.protected,
|
|
9643
|
+
...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) => name.toLowerCase())
|
|
9644
|
+
])
|
|
9645
|
+
};
|
|
9646
|
+
}, KIND_SYNONYMS, allowedKinds = (allowed) => [
|
|
9647
|
+
...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
|
|
9648
|
+
], editDistance = (a, b) => {
|
|
9649
|
+
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
9650
|
+
for (let i = 1;i <= a.length; i++) {
|
|
9651
|
+
let previous = row[0];
|
|
9652
|
+
row[0] = i;
|
|
9653
|
+
for (let j = 1;j <= b.length; j++) {
|
|
9654
|
+
const saved = row[j];
|
|
9655
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
9656
|
+
previous = saved;
|
|
9940
9657
|
}
|
|
9941
9658
|
}
|
|
9942
|
-
return
|
|
9943
|
-
}
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
|
|
9950
|
-
const
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
}
|
|
9964
|
-
|
|
9659
|
+
return row[b.length];
|
|
9660
|
+
}, suggestBranchName = (name, kinds) => {
|
|
9661
|
+
if (!kinds.length)
|
|
9662
|
+
return null;
|
|
9663
|
+
const slash = name.indexOf("/");
|
|
9664
|
+
const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
|
|
9665
|
+
const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
|
|
9666
|
+
const synonym = KIND_SYNONYMS[prefix];
|
|
9667
|
+
const near = kinds.map((kind) => ({ kind, distance: editDistance(prefix, kind) })).filter((entry) => prefix && entry.distance <= 2).toSorted((a, b) => a.distance - b.distance)[0]?.kind;
|
|
9668
|
+
const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
|
|
9669
|
+
return `${kind}/${rest}`;
|
|
9670
|
+
}, kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", "), aliasNote = (kinds) => {
|
|
9671
|
+
const notes = [
|
|
9672
|
+
["fix", "bugfix"],
|
|
9673
|
+
["chore", "feature"]
|
|
9674
|
+
].filter(([, kind]) => kinds.includes(kind)).map(([alias, kind]) => `${alias}/ is ${kind}/`);
|
|
9675
|
+
return notes.length ? ` (${notes.join(", ")})` : "";
|
|
9676
|
+
}, validateBranchNamePolicy = (name, policy, source) => {
|
|
9677
|
+
const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
|
|
9678
|
+
const kinds = allowedKinds(policy.allowed);
|
|
9679
|
+
if (policy.protected.has(name.toLowerCase())) {
|
|
9680
|
+
const correction = kinds.length ? `choose a non-protected branch of an allowed kind (${kindList(kinds)}), e.g. ${kinds[0]}/<slug>: workit git branch --kind ${kinds[0]} --slug <slug>` : `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
|
|
9681
|
+
return {
|
|
9682
|
+
ok: false,
|
|
9683
|
+
rule: "protected_ref",
|
|
9684
|
+
source,
|
|
9685
|
+
attempted: name,
|
|
9686
|
+
correction,
|
|
9687
|
+
error: `protected_ref: branch ${JSON.stringify(name)} is protected by ${source}; ${correction}`
|
|
9688
|
+
};
|
|
9689
|
+
}
|
|
9690
|
+
if (!policy.allowed.some((pattern) => pattern.test(name))) {
|
|
9691
|
+
const suggestion = suggestBranchName(name, kinds);
|
|
9692
|
+
const correction = suggestion && policy.allowed.some((pattern) => pattern.test(suggestion)) ? `use ${JSON.stringify(suggestion)}; the allowed kinds are ${kindList(kinds)} (workit git branch ${suggestion})` : `choose a branch matching one of: ${allowed.join(", ")}`;
|
|
9693
|
+
return {
|
|
9694
|
+
ok: false,
|
|
9695
|
+
rule: "allowed_pattern",
|
|
9696
|
+
source,
|
|
9697
|
+
attempted: name,
|
|
9698
|
+
correction,
|
|
9699
|
+
error: `allowed_pattern: branch ${JSON.stringify(name)} violates ${source} policy; ${correction}`
|
|
9700
|
+
};
|
|
9701
|
+
}
|
|
9702
|
+
return { ok: true };
|
|
9703
|
+
}, branchPolicySnapshotFor = (workspaceRoot) => {
|
|
9704
|
+
const resolved = effectiveWorkspacePolicy(workspaceRoot, "branch");
|
|
9705
|
+
const branchPolicy = resolved.branchPolicy;
|
|
9706
|
+
const source = resolved.provenance.branchPolicy;
|
|
9707
|
+
const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
|
|
9708
|
+
const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
|
|
9709
|
+
const fingerprint = createHash6("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
|
|
9965
9710
|
return {
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
|
|
9711
|
+
branchPolicy,
|
|
9712
|
+
namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
|
|
9713
|
+
source,
|
|
9714
|
+
fingerprint
|
|
9969
9715
|
};
|
|
9970
|
-
},
|
|
9971
|
-
const run = gitRun2(cwd, args);
|
|
9972
|
-
const value = run.ok ? run.stdout.trim() : "";
|
|
9973
|
-
return value ? value : null;
|
|
9974
|
-
}, safeRef = (value) => value.length > 0 && !value.startsWith("-"), trackTrunk = (cwd) => {
|
|
9716
|
+
}, validateBranchNameFor = (workspaceRoot, name) => {
|
|
9975
9717
|
try {
|
|
9976
|
-
const resolved =
|
|
9977
|
-
|
|
9718
|
+
const resolved = branchPolicySnapshotFor(workspaceRoot);
|
|
9719
|
+
return validateBranchNamePolicy(name, resolved.namePolicy, resolved.source);
|
|
9720
|
+
} catch (error) {
|
|
9721
|
+
const correction = "repair or select a valid workspace policy before creating a branch";
|
|
9722
|
+
return {
|
|
9723
|
+
ok: false,
|
|
9724
|
+
rule: "policy_unavailable",
|
|
9725
|
+
source: "workspaces.json",
|
|
9726
|
+
attempted: name,
|
|
9727
|
+
correction,
|
|
9728
|
+
error: `policy_unavailable: cannot validate branch ${JSON.stringify(name)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
|
|
9729
|
+
};
|
|
9730
|
+
}
|
|
9731
|
+
}, resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy, gitFlowOf = (root, integration) => {
|
|
9732
|
+
try {
|
|
9733
|
+
if (resolveBranchPolicyFor(root).preset !== "gitflow")
|
|
9978
9734
|
return null;
|
|
9979
|
-
const
|
|
9980
|
-
|
|
9735
|
+
const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
|
|
9736
|
+
const names = branchNames(root);
|
|
9737
|
+
const production = productionBranchOf(names);
|
|
9738
|
+
const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
|
|
9739
|
+
return { develop, production, developExists: names.has(develop) };
|
|
9740
|
+
} catch {
|
|
9741
|
+
return null;
|
|
9742
|
+
}
|
|
9743
|
+
}, releaseBranchesOf = (cwd) => {
|
|
9744
|
+
try {
|
|
9745
|
+
const resolved = resolveBranchPolicyFor(cwd);
|
|
9746
|
+
const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
|
|
9747
|
+
return {
|
|
9748
|
+
preset: resolved.preset,
|
|
9749
|
+
branches: flow ? [flow.production, flow.develop] : [],
|
|
9750
|
+
production: flow ? flow.production : resolved.defaultTargetBranch
|
|
9751
|
+
};
|
|
9752
|
+
} catch {
|
|
9753
|
+
return { preset: "unknown", branches: [], production: null };
|
|
9754
|
+
}
|
|
9755
|
+
}, releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase()), branchGuidance = (cwd) => {
|
|
9756
|
+
try {
|
|
9757
|
+
const patterns = branchPolicySnapshotFor(cwd).namePolicy.allowed;
|
|
9758
|
+
const kinds = allowedKinds(patterns);
|
|
9759
|
+
const kindShaped = new Set(kinds.map((kind) => branchGlobPattern(`${kind}/*`).source));
|
|
9760
|
+
const anyName = patterns.some((pattern) => pattern.source === "^.*$");
|
|
9761
|
+
const others = patterns.filter((pattern) => !kindShaped.has(pattern.source)).map((pattern) => globOf(pattern));
|
|
9762
|
+
const allowed = anyName ? "Any non-protected branch name is allowed here." : `${kinds.length ? `Allowed branch kinds here: ${kindList(kinds)}${aliasNote(kinds)}` : "Allowed branch names here:"}${others.length ? `${kinds.length ? "; also" : ""} names matching ${others.join(", ")}` : ""}; anything else is denied.`;
|
|
9763
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
9764
|
+
const integration = resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
|
|
9765
|
+
const flow = resolved.releaseTrack?.track ? null : gitFlowOf(cwd, integration);
|
|
9766
|
+
if (flow?.developExists)
|
|
9767
|
+
return `${allowed} git-flow: feature/, bugfix/ and release/ start from origin/${flow.develop}, hotfix/ from origin/${flow.production}; feature/ and bugfix/ land on ${flow.develop}, release/ and hotfix/ on ${flow.production}.`;
|
|
9768
|
+
const names = branchNames(cwd);
|
|
9769
|
+
const configured = String(resolved.baseBranch ?? integration ?? "") || null;
|
|
9770
|
+
const trunk = productionBranchOf(names);
|
|
9771
|
+
const base = configured && names.has(configured) ? `Branch from origin/${configured}.` : names.has(trunk) ? `Branch from origin/${trunk}${configured ? ` (the default target ${configured} does not exist here)` : ""}.` : "No base branch exists here yet; pass the base explicitly.";
|
|
9772
|
+
return `${allowed} ${base}`;
|
|
9981
9773
|
} catch {
|
|
9982
9774
|
return null;
|
|
9983
9775
|
}
|
|
9984
|
-
},
|
|
9985
|
-
|
|
9986
|
-
return
|
|
9987
|
-
|
|
9988
|
-
return
|
|
9989
|
-
}
|
|
9990
|
-
|
|
9776
|
+
}, globOf = (pattern) => pattern.source.replace(/^\^|\$$/gu, "").replaceAll(".*", "*").replace(/\\(.)/gu, "$1"), policy = (root) => resolveBranchPolicyFor(root), isProtected = (root, name) => policy(root).protected.has(name.toLowerCase()), isProtectedTarget = (root, name) => {
|
|
9777
|
+
try {
|
|
9778
|
+
return isProtected(root, name);
|
|
9779
|
+
} catch {
|
|
9780
|
+
return true;
|
|
9781
|
+
}
|
|
9782
|
+
};
|
|
9783
|
+
var init_branch = __esm(() => {
|
|
9784
|
+
init_branch_policy();
|
|
9785
|
+
init_commit_flavors();
|
|
9786
|
+
init_git();
|
|
9787
|
+
init_config();
|
|
9788
|
+
init_workspaces();
|
|
9789
|
+
init_vcs_config();
|
|
9790
|
+
init_release_tracks();
|
|
9791
|
+
KIND_SYNONYMS = {
|
|
9792
|
+
fix: "bugfix",
|
|
9793
|
+
fixes: "bugfix",
|
|
9794
|
+
bug: "bugfix",
|
|
9795
|
+
bugs: "bugfix",
|
|
9796
|
+
bugfixes: "bugfix",
|
|
9797
|
+
feat: "feature",
|
|
9798
|
+
features: "feature",
|
|
9799
|
+
chore: "feature",
|
|
9800
|
+
docs: "feature",
|
|
9801
|
+
doc: "feature",
|
|
9802
|
+
refactor: "feature",
|
|
9803
|
+
test: "feature",
|
|
9804
|
+
tests: "feature",
|
|
9805
|
+
ci: "feature",
|
|
9806
|
+
build: "feature",
|
|
9807
|
+
perf: "feature",
|
|
9808
|
+
style: "feature",
|
|
9809
|
+
deps: "feature",
|
|
9810
|
+
task: "feature",
|
|
9811
|
+
enhancement: "feature",
|
|
9812
|
+
hot: "hotfix",
|
|
9813
|
+
hotfixes: "hotfix",
|
|
9814
|
+
releases: "release",
|
|
9815
|
+
rel: "release"
|
|
9816
|
+
};
|
|
9817
|
+
});
|
|
9818
|
+
|
|
9819
|
+
// packages/workit-core/src/core/logger.ts
|
|
9820
|
+
import os3 from "node:os";
|
|
9821
|
+
import path7 from "node:path";
|
|
9822
|
+
var SENSITIVE_WORDS, STACK_WORDS, escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), resolveStateDir = () => {
|
|
9823
|
+
const override = process.env.WORKFLOW_TOOLKIT_STATE;
|
|
9824
|
+
if (override)
|
|
9825
|
+
return override;
|
|
9826
|
+
const home = os3.homedir();
|
|
9827
|
+
if (process.env.XDG_STATE_HOME)
|
|
9828
|
+
return path7.join(process.env.XDG_STATE_HOME, "workit");
|
|
9829
|
+
switch (os3.platform()) {
|
|
9830
|
+
case "darwin":
|
|
9831
|
+
return path7.join(home, "Library", "Application Support", "workit");
|
|
9832
|
+
case "win32":
|
|
9833
|
+
return path7.join(process.env.LOCALAPPDATA ?? path7.join(home, "AppData", "Local"), "workit");
|
|
9834
|
+
default:
|
|
9835
|
+
return path7.join(process.env.HOME ?? home, ".local", "state", "workit");
|
|
9836
|
+
}
|
|
9837
|
+
}, homePattern;
|
|
9838
|
+
var init_logger = __esm(() => {
|
|
9839
|
+
SENSITIVE_WORDS = new Set([
|
|
9840
|
+
"token",
|
|
9841
|
+
"secret",
|
|
9842
|
+
"password",
|
|
9843
|
+
"passwd",
|
|
9844
|
+
"authorization",
|
|
9845
|
+
"credential",
|
|
9846
|
+
"credentials",
|
|
9847
|
+
"cookie",
|
|
9848
|
+
"api",
|
|
9849
|
+
"apikey",
|
|
9850
|
+
"apitoken",
|
|
9851
|
+
"accesstoken",
|
|
9852
|
+
"clientsecret",
|
|
9853
|
+
"refreshtoken",
|
|
9854
|
+
"key",
|
|
9855
|
+
"bearer",
|
|
9856
|
+
"prompt",
|
|
9857
|
+
"message",
|
|
9858
|
+
"messages",
|
|
9859
|
+
"content",
|
|
9860
|
+
"body",
|
|
9861
|
+
"args",
|
|
9862
|
+
"argument",
|
|
9863
|
+
"result",
|
|
9864
|
+
"results",
|
|
9865
|
+
"output",
|
|
9866
|
+
"issue",
|
|
9867
|
+
"description",
|
|
9868
|
+
"summary",
|
|
9869
|
+
"payload",
|
|
9870
|
+
"script",
|
|
9871
|
+
"command",
|
|
9872
|
+
"text",
|
|
9873
|
+
"query"
|
|
9874
|
+
]);
|
|
9875
|
+
STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
|
|
9876
|
+
homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
|
|
9877
|
+
});
|
|
9878
|
+
|
|
9879
|
+
// packages/workit-core/src/host-session.ts
|
|
9880
|
+
import { createHash as createHash7, randomBytes, timingSafeEqual } from "node:crypto";
|
|
9881
|
+
import fs5 from "node:fs";
|
|
9882
|
+
import path8 from "node:path";
|
|
9883
|
+
function hostSessionFromEnv(env) {
|
|
9884
|
+
const value = (key) => env[key]?.trim() || null;
|
|
9885
|
+
let inner = null;
|
|
9886
|
+
for (const [key, host] of HOST_SESSION_VARS) {
|
|
9887
|
+
const session = value(key);
|
|
9888
|
+
if (session) {
|
|
9889
|
+
inner = { host: host(env), session };
|
|
9890
|
+
break;
|
|
9891
|
+
}
|
|
9892
|
+
}
|
|
9893
|
+
const declared = value("WORKIT_HOST");
|
|
9894
|
+
if (env.WORKIT_SESSION_ID !== undefined) {
|
|
9895
|
+
const inherited = inner !== null && declared !== null && family(declared) !== family(inner.host);
|
|
9896
|
+
if (!inherited)
|
|
9897
|
+
return { host: declared, session: value("WORKIT_SESSION_ID") };
|
|
9991
9898
|
}
|
|
9992
|
-
|
|
9899
|
+
return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
|
|
9900
|
+
}
|
|
9901
|
+
function migrateRepoIssued(cwd) {
|
|
9902
|
+
if (cwd === undefined)
|
|
9903
|
+
return;
|
|
9904
|
+
let old;
|
|
9993
9905
|
try {
|
|
9994
|
-
|
|
9906
|
+
const location = resolveStore(cwd);
|
|
9907
|
+
if (location instanceof Error)
|
|
9908
|
+
return;
|
|
9909
|
+
old = path8.join(location.dir, "hooks", "issued");
|
|
9910
|
+
if (!fs5.existsSync(old))
|
|
9911
|
+
return;
|
|
9995
9912
|
} catch {
|
|
9996
|
-
return
|
|
9913
|
+
return;
|
|
9997
9914
|
}
|
|
9998
|
-
|
|
9999
|
-
for (const target of [path9.join(lock, LOCK_OWNER), lock]) {
|
|
9915
|
+
for (const lead of safeList(old)) {
|
|
10000
9916
|
try {
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
continue;
|
|
10015
|
-
}
|
|
10016
|
-
let stop = end;
|
|
10017
|
-
let text = "";
|
|
10018
|
-
while (stop >= 0) {
|
|
10019
|
-
const start = buffer.lastIndexOf(10, stop - 1);
|
|
10020
|
-
text = buffer.toString("utf8", start + 1, stop).trim();
|
|
10021
|
-
if (text) {
|
|
10022
|
-
if (start >= 0 || span === size)
|
|
10023
|
-
return text;
|
|
10024
|
-
break;
|
|
10025
|
-
}
|
|
10026
|
-
if (start < 0) {
|
|
10027
|
-
if (span === size)
|
|
10028
|
-
return null;
|
|
10029
|
-
break;
|
|
9917
|
+
const from = path8.join(old, lead);
|
|
9918
|
+
if (!fs5.statSync(from).isDirectory())
|
|
9919
|
+
continue;
|
|
9920
|
+
const to = path8.join(userIssuedRoot(), lead);
|
|
9921
|
+
fs5.mkdirSync(to, { recursive: true, mode: 448 });
|
|
9922
|
+
for (const id of fs5.readdirSync(from)) {
|
|
9923
|
+
try {
|
|
9924
|
+
fs5.copyFileSync(path8.join(from, id), path8.join(to, id), fs5.constants.COPYFILE_EXCL);
|
|
9925
|
+
} catch (error) {
|
|
9926
|
+
if (error.code !== "EEXIST")
|
|
9927
|
+
throw error;
|
|
9928
|
+
}
|
|
9929
|
+
fs5.rmSync(path8.join(from, id), { force: true });
|
|
10030
9930
|
}
|
|
10031
|
-
|
|
10032
|
-
}
|
|
10033
|
-
if (span === size)
|
|
10034
|
-
return text || null;
|
|
9931
|
+
removeIfEmpty(from);
|
|
9932
|
+
} catch {}
|
|
10035
9933
|
}
|
|
10036
|
-
|
|
10037
|
-
}
|
|
9934
|
+
removeIfEmpty(old);
|
|
9935
|
+
}
|
|
9936
|
+
function recordIssuedSession(cwd, lead, issued) {
|
|
9937
|
+
const token = randomBytes(18).toString("hex");
|
|
10038
9938
|
try {
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
return (
|
|
10058
|
-
|
|
10059
|
-
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
release(lock, token) {
|
|
10125
|
-
if (lockToken(lock) !== token)
|
|
10126
|
-
return false;
|
|
10127
|
-
const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
|
|
10128
|
-
try {
|
|
10129
|
-
fs7.renameSync(lock, tomb);
|
|
10130
|
-
} catch {
|
|
10131
|
-
return false;
|
|
10132
|
-
}
|
|
10133
|
-
if (lockToken(tomb) !== token) {
|
|
10134
|
-
try {
|
|
10135
|
-
fs7.renameSync(tomb, lock);
|
|
10136
|
-
} catch {}
|
|
10137
|
-
return false;
|
|
10138
|
-
}
|
|
10139
|
-
fs7.rmSync(tomb, { recursive: true, force: true });
|
|
10140
|
-
return true;
|
|
10141
|
-
},
|
|
10142
|
-
token: lockToken,
|
|
10143
|
-
reap(lock, observed) {
|
|
10144
|
-
const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
|
|
10145
|
-
try {
|
|
10146
|
-
fs7.renameSync(lock, tomb);
|
|
10147
|
-
} catch {
|
|
10148
|
-
return false;
|
|
10149
|
-
}
|
|
10150
|
-
if (lockToken(tomb) === observed) {
|
|
10151
|
-
fs7.rmSync(tomb, { recursive: true, force: true });
|
|
10152
|
-
return true;
|
|
10153
|
-
}
|
|
10154
|
-
try {
|
|
10155
|
-
fs7.renameSync(tomb, lock);
|
|
10156
|
-
} catch {
|
|
10157
|
-
fs7.rmSync(tomb, { recursive: true, force: true });
|
|
10158
|
-
}
|
|
10159
|
-
return false;
|
|
10160
|
-
}
|
|
10161
|
-
};
|
|
10162
|
-
TAIL_BYTES = 64 * 1024;
|
|
10163
|
-
TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
10164
|
-
tamperCache = new Map;
|
|
10165
|
-
RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
10166
|
-
TRUST_REASONS = new Set([
|
|
10167
|
-
"unsigned",
|
|
10168
|
-
"hash_mismatch",
|
|
10169
|
-
"broken_link",
|
|
10170
|
-
"duplicate_id",
|
|
10171
|
-
"unissued_session",
|
|
10172
|
-
"unbound_session"
|
|
10173
|
-
]);
|
|
10174
|
-
SHELL_VOUCHED_HOSTS = new Set([
|
|
10175
|
-
"cli",
|
|
10176
|
-
"opencode",
|
|
10177
|
-
"pi",
|
|
10178
|
-
"codex_cli",
|
|
10179
|
-
"codex_desktop"
|
|
10180
|
-
]);
|
|
10181
|
-
OBSERVED_ONLY = new Set([
|
|
10182
|
-
"check",
|
|
10183
|
-
"merge.unverified",
|
|
10184
|
-
"policy.judged",
|
|
10185
|
-
...PR_ROW_TYPES
|
|
10186
|
-
]);
|
|
10187
|
-
});
|
|
10188
|
-
|
|
10189
|
-
// packages/workit-core/src/forge/pr-body.ts
|
|
10190
|
-
var init_pr_body = __esm(() => {
|
|
10191
|
-
init_vcs_config();
|
|
10192
|
-
});
|
|
10193
|
-
|
|
10194
|
-
// packages/workit-core/src/core/pr-create.ts
|
|
10195
|
-
var init_pr_create = __esm(() => {
|
|
10196
|
-
init_vcs_config();
|
|
10197
|
-
init_vcs_config();
|
|
10198
|
-
init_branch();
|
|
10199
|
-
init_pr_body();
|
|
10200
|
-
});
|
|
10201
|
-
|
|
10202
|
-
// packages/workit-core/src/forge/redact.ts
|
|
10203
|
-
var KEY, ASSIGNMENT, QUOTED_ASSIGNMENT, ANSI;
|
|
10204
|
-
var init_redact = __esm(() => {
|
|
10205
|
-
KEY = String.raw`[A-Za-z0-9_.-]*?(?:token|secret|password|passwd|pwd|api[_-]?key|access[_-]?key|account[_-]?key|private[_-]?key|client[_-]?secret|credentials?|authorization|cookie|signature)[A-Za-z0-9_.-]*`;
|
|
10206
|
-
ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
|
|
10207
|
-
QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
|
|
10208
|
-
ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
|
|
10209
|
-
});
|
|
10210
|
-
// packages/workit-core/src/git/ops.ts
|
|
10211
|
-
var COMMIT_TIMEOUT_MS;
|
|
10212
|
-
var init_ops = __esm(() => {
|
|
9939
|
+
migrateRepoIssued(cwd);
|
|
9940
|
+
const own = hash(issued);
|
|
9941
|
+
const tokens = path8.join(userTokenRoot(), hash(lead));
|
|
9942
|
+
fs5.mkdirSync(tokens, { recursive: true, mode: 448 });
|
|
9943
|
+
fs5.writeFileSync(path8.join(tokens, own), `${tokenHash(token)}
|
|
9944
|
+
`, { mode: 384, flag: "wx" });
|
|
9945
|
+
const dir = path8.join(userIssuedRoot(), hash(lead));
|
|
9946
|
+
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
9947
|
+
fs5.writeFileSync(path8.join(dir, own), `${ISSUED_SESSIONS_MARKER}
|
|
9948
|
+
`, { mode: 384 });
|
|
9949
|
+
pruneIssued(dir, own, tokens);
|
|
9950
|
+
return token;
|
|
9951
|
+
} catch {
|
|
9952
|
+
return null;
|
|
9953
|
+
}
|
|
9954
|
+
}
|
|
9955
|
+
function judgeBranch(cwd) {
|
|
9956
|
+
const branch = currentBranch(cwd);
|
|
9957
|
+
return branch && !isProtectedTarget(cwd, branch) ? branch : null;
|
|
9958
|
+
}
|
|
9959
|
+
function verdictCommand(options) {
|
|
9960
|
+
const kind = options.judge === "verifier" ? "<live|unit>" : options.judge === "reviewer" ? "review" : "<live|unit|review>";
|
|
9961
|
+
return [
|
|
9962
|
+
"workit ledger verdict <verified|failed|blocked>",
|
|
9963
|
+
`--kind ${kind}`,
|
|
9964
|
+
`--branch ${options.branch ?? "<branch>"}`,
|
|
9965
|
+
options.session ? `--session ${options.session}` : null,
|
|
9966
|
+
options.token ? `--token=${options.token}` : null,
|
|
9967
|
+
'--how "<what you ran and saw>"'
|
|
9968
|
+
].filter(Boolean).join(" ");
|
|
9969
|
+
}
|
|
9970
|
+
function pruneIssued(dir, own, tokens) {
|
|
9971
|
+
try {
|
|
9972
|
+
const entries = fs5.readdirSync(dir);
|
|
9973
|
+
if (entries.length <= ISSUED_PER_LEAD)
|
|
9974
|
+
return;
|
|
9975
|
+
const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs5.statSync(path8.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
|
|
9976
|
+
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
|
|
9977
|
+
fs5.rmSync(path8.join(dir, name), { force: true });
|
|
9978
|
+
fs5.rmSync(path8.join(tokens, name), { force: true });
|
|
9979
|
+
}
|
|
9980
|
+
} catch {}
|
|
9981
|
+
}
|
|
9982
|
+
function issuedBinding(cwd, claimed) {
|
|
9983
|
+
const record = issuedRecord(cwd, claimed);
|
|
9984
|
+
const { stored, leadBound } = issuedToken(claimed);
|
|
9985
|
+
if (stored)
|
|
9986
|
+
return "token";
|
|
9987
|
+
if (record === null)
|
|
9988
|
+
return null;
|
|
9989
|
+
return leadBound ? "token" : "legacy";
|
|
9990
|
+
}
|
|
9991
|
+
var HOST_SESSION_VARS, family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null, ISSUED_PER_LEAD = 64, ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3", hash = (value) => createHash7("sha256").update(value).digest("hex").slice(0, 32), userIssuedRoot = () => path8.join(resolveStateDir(), "issued"), userTokenRoot = () => path8.join(resolveStateDir(), "issued-tokens"), safeList = (dir) => {
|
|
9992
|
+
try {
|
|
9993
|
+
return fs5.readdirSync(dir);
|
|
9994
|
+
} catch {
|
|
9995
|
+
return [];
|
|
9996
|
+
}
|
|
9997
|
+
}, removeIfEmpty = (dir) => {
|
|
9998
|
+
try {
|
|
9999
|
+
fs5.rmdirSync(dir);
|
|
10000
|
+
} catch {}
|
|
10001
|
+
}, tokenHash = (token) => createHash7("sha256").update(token).digest("hex"), issuedRecord = (cwd, claimed) => {
|
|
10002
|
+
const cut = claimed.lastIndexOf(":");
|
|
10003
|
+
if (cut <= 0)
|
|
10004
|
+
return null;
|
|
10005
|
+
migrateRepoIssued(cwd);
|
|
10006
|
+
try {
|
|
10007
|
+
return fs5.readFileSync(path8.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
|
|
10008
|
+
} catch {
|
|
10009
|
+
return null;
|
|
10010
|
+
}
|
|
10011
|
+
}, TOKEN_HASH, issuedToken = (claimed) => {
|
|
10012
|
+
const cut = claimed.lastIndexOf(":");
|
|
10013
|
+
if (cut <= 0)
|
|
10014
|
+
return { stored: null, leadBound: false };
|
|
10015
|
+
const dir = path8.join(userTokenRoot(), hash(claimed.slice(0, cut)));
|
|
10016
|
+
try {
|
|
10017
|
+
const text = fs5.readFileSync(path8.join(dir, hash(claimed)), "utf8");
|
|
10018
|
+
return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
|
|
10019
|
+
} catch {
|
|
10020
|
+
return { stored: null, leadBound: safeList(dir).length > 0 };
|
|
10021
|
+
}
|
|
10022
|
+
}, OWN_CHILD_SHELL;
|
|
10023
|
+
var init_host_session = __esm(() => {
|
|
10213
10024
|
init_branch();
|
|
10214
|
-
|
|
10215
|
-
init_commit_flavors();
|
|
10216
|
-
init_pr_create();
|
|
10217
|
-
init_release_tracks();
|
|
10218
|
-
init_vcs_config();
|
|
10219
|
-
init_workspaces();
|
|
10220
|
-
init_redact();
|
|
10221
|
-
init_host_session();
|
|
10222
|
-
init_ledger();
|
|
10025
|
+
init_logger();
|
|
10223
10026
|
init_rev();
|
|
10224
|
-
|
|
10027
|
+
init_paths();
|
|
10028
|
+
HOST_SESSION_VARS = [
|
|
10029
|
+
["OPENCODE_SESSION_ID", () => "opencode"],
|
|
10030
|
+
["PI_SESSION_ID", () => "pi"],
|
|
10031
|
+
[
|
|
10032
|
+
"CODEX_THREAD_ID",
|
|
10033
|
+
(env) => env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || env.CODEX_ELECTRON_RESOURCES_PATH ? "codex_desktop" : "codex_cli"
|
|
10034
|
+
]
|
|
10035
|
+
];
|
|
10036
|
+
TOKEN_HASH = /^([0-9a-f]{64})$/mu;
|
|
10037
|
+
OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
|
|
10225
10038
|
});
|
|
10226
10039
|
|
|
10227
|
-
// packages/workit-core/src/
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10040
|
+
// packages/workit-core/src/hooks/standing-signal.ts
|
|
10041
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
10042
|
+
import fs6 from "node:fs";
|
|
10043
|
+
import path9 from "node:path";
|
|
10044
|
+
var gitStoreDir = (cwd) => {
|
|
10045
|
+
try {
|
|
10046
|
+
for (let dir = path9.resolve(cwd);; dir = path9.dirname(dir)) {
|
|
10047
|
+
const dotGit = path9.join(dir, ".git");
|
|
10048
|
+
if (fs6.existsSync(dotGit)) {
|
|
10049
|
+
let gitDir = dotGit;
|
|
10050
|
+
if (fs6.statSync(dotGit).isFile()) {
|
|
10051
|
+
const target = /^gitdir:\s*(.+)$/mu.exec(fs6.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
10052
|
+
if (!target)
|
|
10053
|
+
return null;
|
|
10054
|
+
gitDir = path9.resolve(dir, target);
|
|
10055
|
+
const common = path9.join(gitDir, "commondir");
|
|
10056
|
+
if (fs6.existsSync(common))
|
|
10057
|
+
gitDir = path9.resolve(gitDir, fs6.readFileSync(common, "utf8").trim());
|
|
10058
|
+
}
|
|
10059
|
+
return path9.join(gitDir, "workit");
|
|
10060
|
+
}
|
|
10061
|
+
if (path9.dirname(dir) === dir)
|
|
10247
10062
|
return null;
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
} else if (char === "\\" && index + 1 < body.length) {
|
|
10252
|
-
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
10253
|
-
index += 1;
|
|
10254
|
-
} else
|
|
10255
|
-
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
10063
|
+
}
|
|
10064
|
+
} catch {
|
|
10065
|
+
return null;
|
|
10256
10066
|
}
|
|
10257
|
-
|
|
10067
|
+
}, standingRevFile = (store) => path9.join(store, "standing", "rev"), standingSeenFile = (store, session, agentId) => path9.join(store, "standing", "seen", `${createHash8("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`), readStandingRev = (store) => {
|
|
10258
10068
|
try {
|
|
10259
|
-
return
|
|
10069
|
+
return fs6.readFileSync(standingRevFile(store), "utf8").trim() || null;
|
|
10260
10070
|
} catch {
|
|
10261
10071
|
return null;
|
|
10262
10072
|
}
|
|
10073
|
+
}, readStandingSeen = (file) => {
|
|
10074
|
+
try {
|
|
10075
|
+
const value = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
10076
|
+
return {
|
|
10077
|
+
rev: typeof value.rev === "string" ? value.rev : null,
|
|
10078
|
+
ids: Array.isArray(value.ids) ? value.ids.filter((id) => typeof id === "string") : []
|
|
10079
|
+
};
|
|
10080
|
+
} catch {
|
|
10081
|
+
return { rev: null, ids: [] };
|
|
10082
|
+
}
|
|
10083
|
+
}, writeStandingSeen = (file, seen) => {
|
|
10084
|
+
try {
|
|
10085
|
+
fs6.mkdirSync(path9.dirname(file), { recursive: true });
|
|
10086
|
+
fs6.writeFileSync(file, JSON.stringify(seen));
|
|
10087
|
+
} catch {}
|
|
10088
|
+
};
|
|
10089
|
+
var init_standing_signal = () => {};
|
|
10090
|
+
|
|
10091
|
+
// packages/workit-core/src/ledger.ts
|
|
10092
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
10093
|
+
import { createHash as createHash9, randomBytes as randomBytes2 } from "node:crypto";
|
|
10094
|
+
import fs7 from "node:fs";
|
|
10095
|
+
import path10 from "node:path";
|
|
10096
|
+
function resolveCommit(cwd, ref) {
|
|
10097
|
+
if (!safeRef2(ref))
|
|
10098
|
+
return null;
|
|
10099
|
+
return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
10263
10100
|
}
|
|
10264
|
-
function
|
|
10265
|
-
|
|
10266
|
-
|
|
10101
|
+
function branchHead(cwd, branch) {
|
|
10102
|
+
if (!safeRef2(branch))
|
|
10103
|
+
return null;
|
|
10104
|
+
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
10105
|
+
}
|
|
10106
|
+
function diffHash(cwd, base, head) {
|
|
10107
|
+
if (!safeRef2(base) || !safeRef2(head))
|
|
10108
|
+
return null;
|
|
10109
|
+
const diff = gitRun2(cwd, [
|
|
10110
|
+
"diff",
|
|
10111
|
+
"-U3",
|
|
10112
|
+
"--diff-algorithm=myers",
|
|
10113
|
+
"--indent-heuristic",
|
|
10114
|
+
"--no-color",
|
|
10115
|
+
"--no-ext-diff",
|
|
10116
|
+
"--no-textconv",
|
|
10117
|
+
"--no-renames",
|
|
10118
|
+
"--full-index",
|
|
10119
|
+
"--binary",
|
|
10120
|
+
"--src-prefix=a/",
|
|
10121
|
+
"--dst-prefix=b/",
|
|
10122
|
+
`${base}...${head}`,
|
|
10123
|
+
"--"
|
|
10124
|
+
], GIT_TIMEOUTS.worktree);
|
|
10125
|
+
if (!diff.ok || !diff.stdout)
|
|
10126
|
+
return null;
|
|
10127
|
+
const hash = createHash9("sha256");
|
|
10128
|
+
for (const line of diff.stdout.split(`
|
|
10267
10129
|
`)) {
|
|
10268
|
-
|
|
10269
|
-
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
10130
|
+
if (line.startsWith("index "))
|
|
10270
10131
|
continue;
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10132
|
+
hash.update(line.startsWith("@@ ") ? "@@" : line);
|
|
10133
|
+
hash.update(`
|
|
10134
|
+
`);
|
|
10135
|
+
}
|
|
10136
|
+
return `sha256:${hash.digest("hex")}`;
|
|
10137
|
+
}
|
|
10138
|
+
function storeRoot(cwd) {
|
|
10139
|
+
const location = resolveStore(path10.resolve(cwd));
|
|
10140
|
+
if (location instanceof Error)
|
|
10141
|
+
return err("unavailable", location.message.replace(/; fix the repository.*$/u, ""), "fix the repository (git status should work here) or run from a non-git directory");
|
|
10142
|
+
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
10143
|
+
}
|
|
10144
|
+
function ledgerPath(cwd) {
|
|
10145
|
+
const root = storeRoot(cwd);
|
|
10146
|
+
return root.ok ? { ok: true, value: path10.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
10147
|
+
}
|
|
10148
|
+
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
10149
|
+
const lock = `${file}.lock`;
|
|
10150
|
+
const deadline = Date.now() + waitMs;
|
|
10151
|
+
let token = null;
|
|
10152
|
+
for (;; ) {
|
|
10153
|
+
token = ledgerLock.acquire(lock);
|
|
10154
|
+
if (token)
|
|
10155
|
+
break;
|
|
10156
|
+
const observed = lockToken(lock);
|
|
10157
|
+
const age = lockAgeMs(lock);
|
|
10158
|
+
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
10159
|
+
continue;
|
|
10160
|
+
if (Date.now() >= deadline)
|
|
10161
|
+
return null;
|
|
10162
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
10163
|
+
}
|
|
10164
|
+
try {
|
|
10165
|
+
return fn();
|
|
10166
|
+
} finally {
|
|
10167
|
+
ledgerLock.release(lock, token);
|
|
10168
|
+
}
|
|
10169
|
+
}
|
|
10170
|
+
function appendLine(file, build, options) {
|
|
10171
|
+
const write = () => {
|
|
10172
|
+
const fd = fs7.openSync(file, "a+", 384);
|
|
10173
|
+
try {
|
|
10174
|
+
if (process.platform !== "win32")
|
|
10175
|
+
tighten(fd);
|
|
10176
|
+
const size = fs7.fstatSync(fd).size;
|
|
10177
|
+
let fence = "";
|
|
10178
|
+
if (size > 0) {
|
|
10179
|
+
const last = Buffer.alloc(1);
|
|
10180
|
+
fs7.readSync(fd, last, 0, 1, size - 1);
|
|
10181
|
+
if (last[0] !== 10)
|
|
10182
|
+
fence = `
|
|
10183
|
+
`;
|
|
10184
|
+
}
|
|
10185
|
+
const line = build(lastLine(fd, size));
|
|
10186
|
+
if (!line.ok)
|
|
10187
|
+
return line;
|
|
10188
|
+
const bytes = Buffer.from(fence + line.value);
|
|
10189
|
+
const written = fs7.writeSync(fd, bytes);
|
|
10190
|
+
if (written !== bytes.length)
|
|
10191
|
+
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
10192
|
+
return { ok: true, value: undefined };
|
|
10193
|
+
} finally {
|
|
10194
|
+
fs7.closeSync(fd);
|
|
10278
10195
|
}
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
if (
|
|
10283
|
-
|
|
10196
|
+
};
|
|
10197
|
+
try {
|
|
10198
|
+
fs7.mkdirSync(path10.dirname(file), { recursive: true });
|
|
10199
|
+
if (!options.forceLock && !onNetworkFs(path10.dirname(file)))
|
|
10200
|
+
return write();
|
|
10201
|
+
const locked = withLedgerLock(file, write);
|
|
10202
|
+
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
10203
|
+
} catch (error) {
|
|
10204
|
+
return err("unavailable", `cannot append to ${file}: ${error.message}`);
|
|
10284
10205
|
}
|
|
10285
|
-
return out;
|
|
10286
10206
|
}
|
|
10287
|
-
function
|
|
10288
|
-
const
|
|
10289
|
-
|
|
10290
|
-
return
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10207
|
+
function appendRaw(cwd, row, options) {
|
|
10208
|
+
const file = ledgerPath(cwd);
|
|
10209
|
+
if (!file.ok)
|
|
10210
|
+
return file;
|
|
10211
|
+
const id = newId2();
|
|
10212
|
+
const at = (options.now ?? new Date).toISOString();
|
|
10213
|
+
let full = null;
|
|
10214
|
+
const written = appendLine(file.value, (previous) => {
|
|
10215
|
+
const next = sealed(row, id, at, previous);
|
|
10216
|
+
const bytes = Buffer.byteLength(next.line);
|
|
10217
|
+
if (bytes > MAX_LINE_BYTES)
|
|
10218
|
+
return err("invalid_input", `ledger row is ${bytes} bytes; the limit is ${MAX_LINE_BYTES}. Shorten the text and pass details by --ref <path|url>`);
|
|
10219
|
+
full = next.full;
|
|
10220
|
+
return { ok: true, value: next.line };
|
|
10221
|
+
}, options);
|
|
10222
|
+
return written.ok ? { ok: true, value: full } : written;
|
|
10302
10223
|
}
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
const syntax = syntaxOf(file);
|
|
10307
|
-
if (!syntax)
|
|
10308
|
-
return plainSubstance(text);
|
|
10309
|
-
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
10310
|
-
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
10311
|
-
return plainSubstance(text);
|
|
10312
|
-
return lex(syntax, text);
|
|
10224
|
+
function observedRowBytes(row) {
|
|
10225
|
+
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
10226
|
+
return Buffer.byteLength(sample.line);
|
|
10313
10227
|
}
|
|
10314
|
-
function
|
|
10315
|
-
return
|
|
10316
|
-
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
10317
|
-
`);
|
|
10228
|
+
function appendObserved(cwd, row, options = {}) {
|
|
10229
|
+
return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
|
|
10318
10230
|
}
|
|
10319
|
-
function
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
if (
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10231
|
+
function appendHookObserved(cwd, row) {
|
|
10232
|
+
return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
|
|
10233
|
+
}
|
|
10234
|
+
function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
|
|
10235
|
+
if (by.type !== target.type)
|
|
10236
|
+
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
10237
|
+
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
10238
|
+
return "only the session that wrote a row can supersede it";
|
|
10239
|
+
if (!onBranch(target))
|
|
10240
|
+
return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
|
|
10241
|
+
if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
|
|
10242
|
+
return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
|
|
10243
|
+
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
10244
|
+
return "a self verdict cannot replace an independent verdict";
|
|
10245
|
+
return null;
|
|
10246
|
+
}
|
|
10247
|
+
function readLedger(cwd, options = {}) {
|
|
10248
|
+
const file = ledgerPath(cwd);
|
|
10249
|
+
if (!file.ok)
|
|
10250
|
+
return file;
|
|
10251
|
+
let text = "";
|
|
10252
|
+
let stat = null;
|
|
10253
|
+
try {
|
|
10254
|
+
stat = fs7.statSync(file.value);
|
|
10255
|
+
text = fs7.readFileSync(file.value, "utf8");
|
|
10256
|
+
} catch (error) {
|
|
10257
|
+
if (error.code === "ENOENT")
|
|
10258
|
+
return {
|
|
10259
|
+
ok: true,
|
|
10260
|
+
value: {
|
|
10261
|
+
path: file.value,
|
|
10262
|
+
rows: [],
|
|
10263
|
+
skipped: 0,
|
|
10264
|
+
integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
|
|
10265
|
+
}
|
|
10266
|
+
};
|
|
10267
|
+
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
10333
10268
|
}
|
|
10334
|
-
const
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
|
|
10352
|
-
|
|
10269
|
+
const rows = [];
|
|
10270
|
+
let skipped = 0;
|
|
10271
|
+
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
10272
|
+
const earlier = new Set([null]);
|
|
10273
|
+
let chainStarted = false;
|
|
10274
|
+
const ids = new Set;
|
|
10275
|
+
for (const raw of text.split(`
|
|
10276
|
+
`)) {
|
|
10277
|
+
const trimmed = raw.trim();
|
|
10278
|
+
if (!trimmed)
|
|
10279
|
+
continue;
|
|
10280
|
+
const linked = (prevHash) => earlier.has(prevHash);
|
|
10281
|
+
if (options.integrity)
|
|
10282
|
+
earlier.add(sha2562(trimmed));
|
|
10283
|
+
let parsed;
|
|
10284
|
+
try {
|
|
10285
|
+
parsed = JSON.parse(trimmed);
|
|
10286
|
+
} catch {
|
|
10287
|
+
skipped += 1;
|
|
10288
|
+
continue;
|
|
10353
10289
|
}
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
`;
|
|
10358
|
-
lineStart = out.length;
|
|
10290
|
+
if (!isRecord2(parsed) || !str(parsed.id) || !str(parsed.type) || !str(parsed.at)) {
|
|
10291
|
+
skipped += 1;
|
|
10292
|
+
continue;
|
|
10359
10293
|
}
|
|
10294
|
+
let rowIntegrity = options.integrity ? "verified" : undefined;
|
|
10295
|
+
let reason = null;
|
|
10296
|
+
if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
|
|
10297
|
+
chainStarted = true;
|
|
10298
|
+
const { rowHash, ...body } = parsed;
|
|
10299
|
+
const prevHash = str(parsed.prevHash);
|
|
10300
|
+
if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
|
|
10301
|
+
reason = "hash_mismatch";
|
|
10302
|
+
else if (!linked(prevHash))
|
|
10303
|
+
reason = "broken_link";
|
|
10304
|
+
} else if (chainStarted)
|
|
10305
|
+
reason = "unsigned";
|
|
10306
|
+
else
|
|
10307
|
+
rowIntegrity = "legacy";
|
|
10308
|
+
if (options.integrity && !reason && ids.has(parsed.id))
|
|
10309
|
+
reason = "duplicate_id";
|
|
10310
|
+
if (options.integrity)
|
|
10311
|
+
ids.add(parsed.id);
|
|
10312
|
+
if (reason)
|
|
10313
|
+
rowIntegrity = "unverified";
|
|
10314
|
+
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
10315
|
+
const row = {
|
|
10316
|
+
...parsed,
|
|
10317
|
+
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
10318
|
+
id: parsed.id,
|
|
10319
|
+
at: parsed.at,
|
|
10320
|
+
type: parsed.type,
|
|
10321
|
+
actor: normalizeActor(parsed.actor),
|
|
10322
|
+
branch: str(parsed.branch),
|
|
10323
|
+
head: str(parsed.head),
|
|
10324
|
+
base: str(parsed.base),
|
|
10325
|
+
baseSha: str(parsed.baseSha),
|
|
10326
|
+
patchId: str(parsed.patchId),
|
|
10327
|
+
diffHash: str(parsed.diffHash),
|
|
10328
|
+
tree: str(parsed.tree),
|
|
10329
|
+
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
10330
|
+
...pr === undefined ? {} : { pr },
|
|
10331
|
+
...str(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
10332
|
+
seq: rows.length + 1,
|
|
10333
|
+
superseded: false,
|
|
10334
|
+
supersedeIgnored: false,
|
|
10335
|
+
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
10336
|
+
...reason ? { integrityReason: reason } : {}
|
|
10337
|
+
};
|
|
10338
|
+
rows.push(row);
|
|
10339
|
+
if (!options.integrity)
|
|
10340
|
+
continue;
|
|
10341
|
+
if (rowIntegrity === "verified")
|
|
10342
|
+
integrity.chained += 1;
|
|
10343
|
+
else if (rowIntegrity === "legacy")
|
|
10344
|
+
integrity.legacy += 1;
|
|
10345
|
+
else if (reason)
|
|
10346
|
+
integrity.unverified.push({
|
|
10347
|
+
seq: row.seq,
|
|
10348
|
+
id: row.id,
|
|
10349
|
+
type: row.type,
|
|
10350
|
+
branch: row.branch,
|
|
10351
|
+
reason
|
|
10352
|
+
});
|
|
10353
|
+
}
|
|
10354
|
+
const scopes = new Map;
|
|
10355
|
+
const scopeOf = (branch) => {
|
|
10356
|
+
let scope = scopes.get(branch);
|
|
10357
|
+
if (!scope)
|
|
10358
|
+
scopes.set(branch, scope = branchScope(cwd, branch));
|
|
10359
|
+
return scope;
|
|
10360
10360
|
};
|
|
10361
|
-
const
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10361
|
+
const byId = new Map;
|
|
10362
|
+
for (const row of rows) {
|
|
10363
|
+
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
10364
|
+
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
10365
|
+
target.superseded = true;
|
|
10366
|
+
else if (row.supersedes)
|
|
10367
|
+
row.supersedeIgnored = true;
|
|
10368
|
+
if (!byId.has(row.id))
|
|
10369
|
+
byId.set(row.id, row);
|
|
10370
|
+
}
|
|
10371
|
+
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
10372
|
+
tamperCache.set(file.value, {
|
|
10373
|
+
size: stat.size,
|
|
10374
|
+
stamp: fileStamp(stat),
|
|
10375
|
+
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
10376
|
+
});
|
|
10377
|
+
return {
|
|
10378
|
+
ok: true,
|
|
10379
|
+
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
10371
10380
|
};
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10381
|
+
}
|
|
10382
|
+
function ledgerTampering(cwd) {
|
|
10383
|
+
const file = ledgerPath(cwd);
|
|
10384
|
+
if (!file.ok)
|
|
10385
|
+
return [];
|
|
10386
|
+
try {
|
|
10387
|
+
const stat = fs7.statSync(file.value);
|
|
10388
|
+
const cached = tamperCache.get(file.value);
|
|
10389
|
+
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
10390
|
+
return cached.breaks;
|
|
10391
|
+
} catch {
|
|
10392
|
+
return [];
|
|
10393
|
+
}
|
|
10394
|
+
const read = readLedger(cwd, { integrity: true });
|
|
10395
|
+
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
10396
|
+
}
|
|
10397
|
+
function tamperedUnblock(cwd) {
|
|
10398
|
+
const file = ledgerPath(cwd);
|
|
10399
|
+
const where = file.ok ? file.value : "the ledger file";
|
|
10400
|
+
return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
|
|
10401
|
+
}
|
|
10402
|
+
function verdictBasis(verdict, current) {
|
|
10403
|
+
if (verdict.head && verdict.head === current.head)
|
|
10404
|
+
return "fresh";
|
|
10405
|
+
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
10406
|
+
return "carried";
|
|
10407
|
+
return "stale";
|
|
10408
|
+
}
|
|
10409
|
+
function trailerSessions(cwd, base, head) {
|
|
10410
|
+
if (!base || !head || !safeRef2(base) || !safeRef2(head))
|
|
10411
|
+
return [];
|
|
10412
|
+
const out = git3(cwd, [
|
|
10413
|
+
"log",
|
|
10414
|
+
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
10415
|
+
`${base}..${head}`
|
|
10416
|
+
]);
|
|
10417
|
+
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
10418
|
+
}
|
|
10419
|
+
function branchScope(cwd, branch) {
|
|
10420
|
+
const renamedAt = new Map;
|
|
10421
|
+
let since = Number.NEGATIVE_INFINITY;
|
|
10422
|
+
if (safeRef2(branch)) {
|
|
10423
|
+
const log = git3(cwd, [
|
|
10424
|
+
"log",
|
|
10425
|
+
"-g",
|
|
10426
|
+
"--date=unix",
|
|
10427
|
+
"--format=%gd%x09%gs",
|
|
10428
|
+
`refs/heads/${branch}`,
|
|
10429
|
+
"--"
|
|
10430
|
+
]);
|
|
10431
|
+
for (const line of (log ?? "").split(`
|
|
10432
|
+
`)) {
|
|
10433
|
+
const [selector = "", subject = ""] = line.split("\t");
|
|
10434
|
+
const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
|
|
10435
|
+
if (!Number.isFinite(seconds))
|
|
10380
10436
|
continue;
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
return true;
|
|
10388
|
-
}
|
|
10389
|
-
if (char === `
|
|
10390
|
-
` && !multiline)
|
|
10391
|
-
return false;
|
|
10392
|
-
end += 1;
|
|
10437
|
+
since = seconds * 1000;
|
|
10438
|
+
const match = RENAMED.exec(subject);
|
|
10439
|
+
if (!match || match[1] === branch)
|
|
10440
|
+
continue;
|
|
10441
|
+
const until = (seconds + 1) * 1000;
|
|
10442
|
+
renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
|
|
10393
10443
|
}
|
|
10394
|
-
|
|
10444
|
+
}
|
|
10445
|
+
return (row) => {
|
|
10446
|
+
if (row.branch === branch)
|
|
10447
|
+
return true;
|
|
10448
|
+
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
10449
|
+
if (until === undefined)
|
|
10450
|
+
return false;
|
|
10451
|
+
const at = Date.parse(row.at);
|
|
10452
|
+
return at >= since && at <= until;
|
|
10395
10453
|
};
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
|
|
10400
|
-
|
|
10401
|
-
emitVerbatim(text.slice(index, index + 2));
|
|
10402
|
-
index += 2;
|
|
10403
|
-
} else if (char === "`") {
|
|
10404
|
-
emitVerbatim(char);
|
|
10405
|
-
frames.pop();
|
|
10406
|
-
index += 1;
|
|
10407
|
-
} else if (text.startsWith("${", index)) {
|
|
10408
|
-
emitVerbatim("${");
|
|
10409
|
-
frames.push({ kind: "code", braces: 0 });
|
|
10410
|
-
index += 2;
|
|
10411
|
-
} else {
|
|
10412
|
-
out += char;
|
|
10413
|
-
kept = out.length;
|
|
10414
|
-
index += 1;
|
|
10415
|
-
}
|
|
10416
|
-
continue;
|
|
10417
|
-
}
|
|
10418
|
-
if (char === `
|
|
10419
|
-
`) {
|
|
10420
|
-
newline();
|
|
10421
|
-
index += 1;
|
|
10422
|
-
continue;
|
|
10423
|
-
}
|
|
10424
|
-
if (SPACE.test(char)) {
|
|
10425
|
-
if (collapse)
|
|
10426
|
-
pendingSpace = true;
|
|
10427
|
-
else
|
|
10428
|
-
out += char;
|
|
10429
|
-
index += 1;
|
|
10454
|
+
}
|
|
10455
|
+
function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
|
|
10456
|
+
const sessions = new Set;
|
|
10457
|
+
for (const row of rows) {
|
|
10458
|
+
if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
10430
10459
|
continue;
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
|
|
10460
|
+
const session = str(row.session) ?? row.actor.session;
|
|
10461
|
+
if (session)
|
|
10462
|
+
sessions.add(session);
|
|
10463
|
+
}
|
|
10464
|
+
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
10465
|
+
sessions.add(session);
|
|
10466
|
+
return sessions;
|
|
10467
|
+
}
|
|
10468
|
+
function sessionTrust(cwd, row) {
|
|
10469
|
+
const session = row.actor.session;
|
|
10470
|
+
if (!session || row.self === true)
|
|
10471
|
+
return null;
|
|
10472
|
+
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
10473
|
+
const host = row.actor.host;
|
|
10474
|
+
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
10475
|
+
if (!issuedOnly)
|
|
10476
|
+
return null;
|
|
10477
|
+
const binding = issuedBinding(cwd, session);
|
|
10478
|
+
if (binding === null)
|
|
10479
|
+
return "unissued_session";
|
|
10480
|
+
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
10481
|
+
}
|
|
10482
|
+
function trustReasons(cwd, row) {
|
|
10483
|
+
if (row.integrity === "unverified")
|
|
10484
|
+
return [row.integrityReason ?? "unsigned"];
|
|
10485
|
+
if (row.integrity !== "verified")
|
|
10486
|
+
return ["unsigned"];
|
|
10487
|
+
const session = sessionTrust(cwd, row);
|
|
10488
|
+
return session ? [session] : [];
|
|
10489
|
+
}
|
|
10490
|
+
function withIntegrity(cwd, rows) {
|
|
10491
|
+
if (rows.every((row) => row.integrity !== undefined))
|
|
10492
|
+
return rows;
|
|
10493
|
+
const read = readLedger(cwd, { integrity: true });
|
|
10494
|
+
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
10495
|
+
return rows.map((row) => {
|
|
10496
|
+
if (row.integrity !== undefined)
|
|
10497
|
+
return row;
|
|
10498
|
+
const fresh = bySeq.get(row.seq);
|
|
10499
|
+
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
10500
|
+
});
|
|
10501
|
+
}
|
|
10502
|
+
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
10503
|
+
let effective = null;
|
|
10504
|
+
for (const row of rows) {
|
|
10505
|
+
if (!effective) {
|
|
10506
|
+
effective = row;
|
|
10442
10507
|
continue;
|
|
10443
10508
|
}
|
|
10444
|
-
|
|
10445
|
-
if (block) {
|
|
10446
|
-
const end = text.indexOf(block[1], index + block[0].length);
|
|
10447
|
-
if (end < 0)
|
|
10448
|
-
return null;
|
|
10449
|
-
const comment = text.slice(index, end + block[1].length);
|
|
10450
|
-
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
10451
|
-
emitVerbatim(comment);
|
|
10452
|
-
else
|
|
10453
|
-
pendingSpace = true;
|
|
10454
|
-
if (comment.includes(`
|
|
10455
|
-
`))
|
|
10456
|
-
newline();
|
|
10457
|
-
index = end + block[1].length;
|
|
10509
|
+
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
10458
10510
|
continue;
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
return null;
|
|
10511
|
+
const rowIndependent = reasonsOf(row).length === 0;
|
|
10512
|
+
const effIndependent = reasonsOf(effective).length === 0;
|
|
10513
|
+
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
10463
10514
|
continue;
|
|
10464
|
-
|
|
10465
|
-
if (syntax.quotes.includes(char)) {
|
|
10466
|
-
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
10467
|
-
return null;
|
|
10515
|
+
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
10468
10516
|
continue;
|
|
10517
|
+
effective = row;
|
|
10518
|
+
}
|
|
10519
|
+
return effective;
|
|
10520
|
+
}
|
|
10521
|
+
function restackCarries(exactIn, rows, branch, from, to) {
|
|
10522
|
+
if (!from || !to)
|
|
10523
|
+
return false;
|
|
10524
|
+
const exact = (row, fromHead, head) => {
|
|
10525
|
+
if (exactIn === null)
|
|
10526
|
+
return true;
|
|
10527
|
+
const cwd = exactIn;
|
|
10528
|
+
let known = restackExact.get(row);
|
|
10529
|
+
if (known === undefined) {
|
|
10530
|
+
known = typeof row.fromBase === "string" && typeof row.toBase === "string" && sameOwnChange(cwd, {
|
|
10531
|
+
from: fromHead,
|
|
10532
|
+
to: head,
|
|
10533
|
+
fromBase: row.fromBase,
|
|
10534
|
+
toBase: row.toBase
|
|
10535
|
+
});
|
|
10536
|
+
restackExact.set(row, known);
|
|
10469
10537
|
}
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10538
|
+
return known;
|
|
10539
|
+
};
|
|
10540
|
+
const edges = new Map;
|
|
10541
|
+
for (const row of rows)
|
|
10542
|
+
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head && exact(row, row.fromHead, row.head))
|
|
10543
|
+
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
10544
|
+
const seen = new Set([from]);
|
|
10545
|
+
const queue = [from];
|
|
10546
|
+
while (queue.length) {
|
|
10547
|
+
const next = queue.shift();
|
|
10548
|
+
if (next === to)
|
|
10549
|
+
return true;
|
|
10550
|
+
for (const target of edges.get(next) ?? [])
|
|
10551
|
+
if (!seen.has(target)) {
|
|
10552
|
+
seen.add(target);
|
|
10553
|
+
queue.push(target);
|
|
10554
|
+
}
|
|
10555
|
+
}
|
|
10556
|
+
return false;
|
|
10557
|
+
}
|
|
10558
|
+
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
10559
|
+
const rows = withIntegrity(cwd, ledgerRows);
|
|
10560
|
+
const head = branchHead(cwd, branch);
|
|
10561
|
+
const fallbackBase = defaultBase(cwd);
|
|
10562
|
+
const scope = branchScope(cwd, branch);
|
|
10563
|
+
const keyCache = new Map;
|
|
10564
|
+
const keysAt = (base, at) => {
|
|
10565
|
+
if (!base || !at)
|
|
10566
|
+
return { patchId: null, diffHash: null };
|
|
10567
|
+
const cacheKey = `${base}\x00${at}`;
|
|
10568
|
+
let key = keyCache.get(cacheKey);
|
|
10569
|
+
if (!key) {
|
|
10570
|
+
key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
|
|
10571
|
+
keyCache.set(cacheKey, key);
|
|
10474
10572
|
}
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10573
|
+
return key;
|
|
10574
|
+
};
|
|
10575
|
+
const keyFor = (base) => keysAt(base, head);
|
|
10576
|
+
const driftCache = new Map;
|
|
10577
|
+
const driftOf = (row) => {
|
|
10578
|
+
if (driftCache.has(row))
|
|
10579
|
+
return driftCache.get(row) ?? null;
|
|
10580
|
+
let drift = null;
|
|
10581
|
+
const toBase = row.base ?? fallbackBase;
|
|
10582
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
10583
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
10584
|
+
if (row.head && head && row.head !== head && toBaseSha && fromBase && !untrusted(reasonsOf(row)))
|
|
10585
|
+
drift = verdictDrift(cwd, {
|
|
10586
|
+
from: row.head,
|
|
10587
|
+
to: head,
|
|
10588
|
+
fromBase,
|
|
10589
|
+
toBase: toBaseSha,
|
|
10590
|
+
kind: typeof row.kind === "string" ? row.kind : "review"
|
|
10591
|
+
});
|
|
10592
|
+
driftCache.set(row, drift);
|
|
10593
|
+
return drift;
|
|
10594
|
+
};
|
|
10595
|
+
const exactCache = new Map;
|
|
10596
|
+
const exactCarry = (row) => {
|
|
10597
|
+
if (FAILING.has(String(row.result)))
|
|
10598
|
+
return true;
|
|
10599
|
+
let exact = exactCache.get(row);
|
|
10600
|
+
if (exact === undefined) {
|
|
10601
|
+
const toBase = row.base ?? fallbackBase;
|
|
10602
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
10603
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
10604
|
+
exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
|
|
10605
|
+
exactCache.set(row, exact);
|
|
10606
|
+
}
|
|
10607
|
+
return exact;
|
|
10608
|
+
};
|
|
10609
|
+
const keyBasis = (row) => {
|
|
10610
|
+
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
10611
|
+
return basis === "carried" && !exactCarry(row) ? "stale" : basis;
|
|
10612
|
+
};
|
|
10613
|
+
const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
|
|
10614
|
+
const carryOf = (row) => {
|
|
10615
|
+
if (keyBasis(row) !== "stale")
|
|
10616
|
+
return null;
|
|
10617
|
+
if (restacked(row))
|
|
10618
|
+
return null;
|
|
10619
|
+
const drift = driftOf(row);
|
|
10620
|
+
return drift?.carries ? { rule: drift.rule, since: drift.since, files: drift.files } : null;
|
|
10621
|
+
};
|
|
10622
|
+
const basisOf = (row) => {
|
|
10623
|
+
const basis = keyBasis(row);
|
|
10624
|
+
if (basis !== "stale")
|
|
10625
|
+
return basis;
|
|
10626
|
+
return restacked(row) || carryOf(row) ? "carried" : basis;
|
|
10627
|
+
};
|
|
10628
|
+
const sameCode = (failed, row) => {
|
|
10629
|
+
if (failed.head && failed.head === row.head)
|
|
10630
|
+
return true;
|
|
10631
|
+
const base = failed.base ?? fallbackBase;
|
|
10632
|
+
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
10633
|
+
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
10634
|
+
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
|
|
10635
|
+
};
|
|
10636
|
+
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
10637
|
+
const failureAuthorCache = new Map;
|
|
10638
|
+
const failureAuthors = (row) => {
|
|
10639
|
+
let set = failureAuthorCache.get(row.seq);
|
|
10640
|
+
if (!set) {
|
|
10641
|
+
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
10642
|
+
failureAuthorCache.set(row.seq, set);
|
|
10479
10643
|
}
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
10644
|
+
return set;
|
|
10645
|
+
};
|
|
10646
|
+
const reasonCache = new Map;
|
|
10647
|
+
const reasonsOf = (row) => {
|
|
10648
|
+
let reasons = reasonCache.get(row);
|
|
10649
|
+
if (!reasons) {
|
|
10650
|
+
const failing = FAILING.has(String(row.result));
|
|
10651
|
+
reasons = [
|
|
10652
|
+
...trustReasons(cwd, row),
|
|
10653
|
+
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
10654
|
+
];
|
|
10655
|
+
reasonCache.set(row, reasons);
|
|
10485
10656
|
}
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10657
|
+
return reasons;
|
|
10658
|
+
};
|
|
10659
|
+
const retired = new Set;
|
|
10660
|
+
for (const row of rows)
|
|
10661
|
+
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
10662
|
+
retired.add(row.supersedes);
|
|
10663
|
+
const byKind = new Map;
|
|
10664
|
+
for (const row of rows)
|
|
10665
|
+
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
10666
|
+
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
10667
|
+
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
10493
10668
|
}
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
`)
|
|
10500
|
-
return null;
|
|
10501
|
-
if (text[end] === "\\")
|
|
10502
|
-
end += 1;
|
|
10503
|
-
else if (text[end] === "[")
|
|
10504
|
-
inClass = true;
|
|
10505
|
-
else if (text[end] === "]")
|
|
10506
|
-
inClass = false;
|
|
10507
|
-
end += 1;
|
|
10508
|
-
}
|
|
10509
|
-
if (end >= text.length)
|
|
10510
|
-
return null;
|
|
10511
|
-
emitVerbatim(text.slice(index, end + 1));
|
|
10512
|
-
index = end + 1;
|
|
10669
|
+
const verdicts = [];
|
|
10670
|
+
const warnings = [];
|
|
10671
|
+
for (const [kind, list] of byKind) {
|
|
10672
|
+
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
10673
|
+
if (!row)
|
|
10513
10674
|
continue;
|
|
10675
|
+
const basis = basisOf(row);
|
|
10676
|
+
const current = basis !== "stale";
|
|
10677
|
+
const independence = reasonsOf(row);
|
|
10678
|
+
const reasons = [
|
|
10679
|
+
...current ? [] : ["stale"],
|
|
10680
|
+
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
10681
|
+
...independence
|
|
10682
|
+
];
|
|
10683
|
+
verdicts.push({
|
|
10684
|
+
kind,
|
|
10685
|
+
basis,
|
|
10686
|
+
current,
|
|
10687
|
+
independent: independence.length === 0,
|
|
10688
|
+
accepted: reasons.length === 0,
|
|
10689
|
+
reasons,
|
|
10690
|
+
carry: basis === "carried" ? carryOf(row) : null,
|
|
10691
|
+
verdict: row
|
|
10692
|
+
});
|
|
10693
|
+
if (reasons.length === 0) {
|
|
10694
|
+
for (const later of list)
|
|
10695
|
+
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
10696
|
+
warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
|
|
10514
10697
|
}
|
|
10515
|
-
if (frames.length > 1 && frame.kind === "code") {
|
|
10516
|
-
if (char === "{")
|
|
10517
|
-
frame.braces += 1;
|
|
10518
|
-
else if (char === "}") {
|
|
10519
|
-
if (frame.braces === 0) {
|
|
10520
|
-
emitVerbatim(char);
|
|
10521
|
-
frames.pop();
|
|
10522
|
-
index += 1;
|
|
10523
|
-
continue;
|
|
10524
|
-
}
|
|
10525
|
-
frame.braces -= 1;
|
|
10526
|
-
}
|
|
10527
|
-
}
|
|
10528
|
-
emitCode(char);
|
|
10529
|
-
index += 1;
|
|
10530
|
-
}
|
|
10531
|
-
if (frames.length !== 1)
|
|
10532
|
-
return null;
|
|
10533
|
-
newline();
|
|
10534
|
-
return out;
|
|
10535
|
-
}
|
|
10536
|
-
function unbalanced(body) {
|
|
10537
|
-
let depth = 0;
|
|
10538
|
-
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
10539
|
-
if (part === "{")
|
|
10540
|
-
depth += 1;
|
|
10541
|
-
else if (part === "}")
|
|
10542
|
-
depth = Math.max(0, depth - 1);
|
|
10543
|
-
}
|
|
10544
|
-
return depth > 0;
|
|
10545
|
-
}
|
|
10546
|
-
function hasOpaqueLineBreak(text) {
|
|
10547
|
-
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
10548
|
-
}
|
|
10549
|
-
function textOf(bytes) {
|
|
10550
|
-
if (bytes.includes(0))
|
|
10551
|
-
return null;
|
|
10552
|
-
try {
|
|
10553
|
-
return decoder.decode(bytes);
|
|
10554
|
-
} catch {
|
|
10555
|
-
return null;
|
|
10556
10698
|
}
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
|
|
10560
|
-
const
|
|
10561
|
-
if (
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
return a !== null && a === substanceOf(file, right);
|
|
10567
|
-
}
|
|
10568
|
-
var SLASH_BLOCK, C_LIKE, SYNTAXES, DIRECTIVE, extensionOf = (file) => {
|
|
10569
|
-
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
10570
|
-
const dot = name.lastIndexOf(".");
|
|
10571
|
-
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
10572
|
-
}, CI_PATH, syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null, SPACE, WORD, NO_SPACE_AFTER = "([{,;", NO_SPACE_BEFORE = ")]},;", REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^", REGEX_KEYWORDS, decoder;
|
|
10573
|
-
var init_substance = __esm(() => {
|
|
10574
|
-
SLASH_BLOCK = [["/*", "*/"]];
|
|
10575
|
-
C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
10576
|
-
SYNTAXES = Object.fromEntries([
|
|
10577
|
-
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
10578
|
-
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
10579
|
-
ext,
|
|
10580
|
-
{ ...C_LIKE, triple: true }
|
|
10581
|
-
]),
|
|
10582
|
-
["go", { ...C_LIKE, rawBacktick: true }],
|
|
10583
|
-
...["css", "scss", "less"].map((ext) => [
|
|
10584
|
-
ext,
|
|
10585
|
-
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
10586
|
-
]),
|
|
10587
|
-
...["py", "pyi"].map((ext) => [
|
|
10588
|
-
ext,
|
|
10589
|
-
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
10590
|
-
]),
|
|
10591
|
-
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
10592
|
-
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
10593
|
-
ext,
|
|
10594
|
-
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
10595
|
-
])
|
|
10596
|
-
]);
|
|
10597
|
-
DIRECTIVE = /@ts-|eslint|prettier|biome|oxlint|istanbul|\bc8\b|\bv8\b|@jsx|@flow|@vite|webpack|__PURE__|__NO_SIDE_EFFECTS__|source(?:Mapping)?URL|^\/\s*<|go:|\+build|nolint|noqa|type:|pyright|mypy|pylint|pragma|coding[:=]|fmt:|NOSONAR|@license|@preserve|@refresh|environment|language=|cython|ruff|isort|deno-|@vitest|@jest|swiftlint|ktlint|checkstyle|suppress|nullable|#(?:end)?region/iu;
|
|
10598
|
-
CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
10599
|
-
SPACE = /[ \t\r\f\v]/u;
|
|
10600
|
-
WORD = /[\w$]/u;
|
|
10601
|
-
REGEX_KEYWORDS = new Set([
|
|
10602
|
-
"return",
|
|
10603
|
-
"typeof",
|
|
10604
|
-
"instanceof",
|
|
10605
|
-
"case",
|
|
10606
|
-
"do",
|
|
10607
|
-
"else",
|
|
10608
|
-
"in",
|
|
10609
|
-
"of",
|
|
10610
|
-
"new",
|
|
10611
|
-
"delete",
|
|
10612
|
-
"void",
|
|
10613
|
-
"throw",
|
|
10614
|
-
"yield",
|
|
10615
|
-
"await"
|
|
10616
|
-
]);
|
|
10617
|
-
decoder = new TextDecoder("utf-8", { fatal: true });
|
|
10618
|
-
});
|
|
10619
|
-
|
|
10620
|
-
// packages/workit-core/src/core/policy/lane.ts
|
|
10621
|
-
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
10622
|
-
import path10 from "node:path";
|
|
10623
|
-
function kindOf(file) {
|
|
10624
|
-
if (file.binary)
|
|
10625
|
-
return "behavior";
|
|
10626
|
-
if (file.generated)
|
|
10627
|
-
return "generated";
|
|
10628
|
-
if (LOCKFILES.has(basename(file.path)))
|
|
10629
|
-
return "lockfile";
|
|
10630
|
-
if (isDoc(file.path))
|
|
10631
|
-
return "doc";
|
|
10632
|
-
if (file.cosmetic)
|
|
10633
|
-
return "cosmetic";
|
|
10634
|
-
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
10635
|
-
}
|
|
10636
|
-
function globMatch(pattern, file) {
|
|
10637
|
-
let regex = globCache.get(pattern);
|
|
10638
|
-
if (!regex) {
|
|
10639
|
-
let source = "";
|
|
10640
|
-
for (let index = 0;index < pattern.length; index += 1) {
|
|
10641
|
-
const char = pattern[index];
|
|
10642
|
-
if (char === "*" && pattern[index + 1] === "*") {
|
|
10643
|
-
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
10644
|
-
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
10645
|
-
} else if (char === "*")
|
|
10646
|
-
source += "[^/]*";
|
|
10647
|
-
else if (char === "?")
|
|
10648
|
-
source += "[^/]";
|
|
10649
|
-
else
|
|
10650
|
-
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
10699
|
+
for (const row of rows)
|
|
10700
|
+
if (row.integrity === "unverified" && scope(row))
|
|
10701
|
+
warnings.push(`ledger row ${row.id} (${row.type}) on ${row.branch ?? branch} is unverified: the workit CLI did not write it or it changed afterwards (workit ledger verify-integrity)`);
|
|
10702
|
+
const tampered = ledgerTampering(cwd);
|
|
10703
|
+
if (tampered.length) {
|
|
10704
|
+
warnings.push(tamperedWarning(cwd, tampered));
|
|
10705
|
+
for (const entry of verdicts) {
|
|
10706
|
+
entry.accepted = false;
|
|
10707
|
+
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
10651
10708
|
}
|
|
10652
|
-
regex = new RegExp(`^${source}$`, "u");
|
|
10653
|
-
globCache.set(pattern, regex);
|
|
10654
10709
|
}
|
|
10655
|
-
|
|
10710
|
+
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
10711
|
+
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
10712
|
+
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
10713
|
+
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
10714
|
+
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
10715
|
+
accepted: false,
|
|
10716
|
+
verdict: failing?.verdict ?? null,
|
|
10717
|
+
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
10718
|
+
};
|
|
10719
|
+
const latestSelf = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && (STRONG_RESULTS.has(String(entry.verdict.result)) || FAILING.has(String(entry.verdict.result))));
|
|
10720
|
+
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
10721
|
+
const carryEntry = verdicts.find((entry) => entry.verdict === accepted.verdict && entry.accepted) ?? newestCurrent;
|
|
10722
|
+
const lastPass = accepted.accepted || failing || tampered.length ? undefined : verdicts.findLast((entry) => !entry.current && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
10723
|
+
const drift = lastPass ? driftOf(lastPass.verdict) : null;
|
|
10724
|
+
const reverify = drift && !drift.carries && drift.focused && drift.files ? { scope: "focused", since: drift.since, files: drift.files } : null;
|
|
10725
|
+
return {
|
|
10726
|
+
branch,
|
|
10727
|
+
head,
|
|
10728
|
+
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
10729
|
+
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
10730
|
+
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
10731
|
+
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
10732
|
+
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
10733
|
+
carry: carryEntry?.carry ?? null,
|
|
10734
|
+
reverify,
|
|
10735
|
+
accepted,
|
|
10736
|
+
verdicts,
|
|
10737
|
+
authors: [...authors].toSorted(),
|
|
10738
|
+
warnings
|
|
10739
|
+
};
|
|
10656
10740
|
}
|
|
10657
|
-
function
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
|
|
10741
|
+
function activeStanding(rows, fanout, leads = null) {
|
|
10742
|
+
const active = [];
|
|
10743
|
+
for (const row of rows) {
|
|
10744
|
+
if (row.fanout !== fanout || !fromLead(row, leads))
|
|
10745
|
+
continue;
|
|
10746
|
+
if (row.type === "standing" && typeof row.what === "string" && row.what.trim()) {
|
|
10747
|
+
if (!row.superseded)
|
|
10748
|
+
active.push(row);
|
|
10749
|
+
} else if (row.type === "standing.cleared") {
|
|
10750
|
+
const target = str(row.target);
|
|
10751
|
+
if (target === null)
|
|
10752
|
+
active.length = 0;
|
|
10753
|
+
else {
|
|
10754
|
+
const index = active.findIndex((order) => order.id === target);
|
|
10755
|
+
if (index >= 0)
|
|
10756
|
+
active.splice(index, 1);
|
|
10757
|
+
}
|
|
10758
|
+
}
|
|
10759
|
+
}
|
|
10760
|
+
return active;
|
|
10665
10761
|
}
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10762
|
+
var LEDGER_VERSION = 1, MAX_LINE_BYTES = 4096, PASSING, FAILING, STRONG_RESULTS, PR_ROW_TYPES, err = (code, error, unblock) => ({
|
|
10763
|
+
ok: false,
|
|
10764
|
+
code,
|
|
10765
|
+
error,
|
|
10766
|
+
...unblock ? { unblock } : {}
|
|
10767
|
+
}), gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
10768
|
+
const run = spawnSync8("git", args, {
|
|
10769
|
+
cwd,
|
|
10770
|
+
encoding: "utf8",
|
|
10771
|
+
timeout: timeoutMs,
|
|
10772
|
+
killSignal: "SIGKILL",
|
|
10773
|
+
windowsHide: true,
|
|
10774
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
10775
|
+
env: {
|
|
10776
|
+
...process.env,
|
|
10777
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
10778
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
10779
|
+
LC_ALL: "C",
|
|
10780
|
+
LANGUAGE: "C"
|
|
10781
|
+
}
|
|
10682
10782
|
});
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
10696
|
-
if (lines > selfMaxLines)
|
|
10697
|
-
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
10698
|
-
if (behavior.length > selfMaxFiles)
|
|
10699
|
-
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
10700
|
-
if (opaque.length)
|
|
10701
|
-
return result("focused", [
|
|
10702
|
-
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
10703
|
-
]);
|
|
10704
|
-
return result("self", [
|
|
10705
|
-
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
10706
|
-
]);
|
|
10707
|
-
}
|
|
10708
|
-
function untrackedStat(cwd, file) {
|
|
10783
|
+
return {
|
|
10784
|
+
ok: run.status === 0,
|
|
10785
|
+
stdout: run.stdout ?? "",
|
|
10786
|
+
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
10787
|
+
};
|
|
10788
|
+
}, git3 = (cwd, args) => {
|
|
10789
|
+
const run = gitRun2(cwd, args);
|
|
10790
|
+
const value = run.ok ? run.stdout.trim() : "";
|
|
10791
|
+
return value ? value : null;
|
|
10792
|
+
}, safeRef2 = (value) => value.length > 0 && !value.startsWith("-"), newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`, NETWORK_FS_TYPES, isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0), onNetworkFs = (dir) => {
|
|
10793
|
+
if (process.platform !== "linux")
|
|
10794
|
+
return false;
|
|
10709
10795
|
try {
|
|
10710
|
-
|
|
10711
|
-
if (bytes.includes(0))
|
|
10712
|
-
return { path: file, added: 0, deleted: 0, binary: true };
|
|
10713
|
-
const text = bytes.toString("utf8");
|
|
10714
|
-
if (hasOpaqueLineBreak(text))
|
|
10715
|
-
return { path: file, added: 0, deleted: 0, binary: true };
|
|
10716
|
-
const lines = text.split(`
|
|
10717
|
-
`).length - (text.endsWith(`
|
|
10718
|
-
`) ? 1 : 0);
|
|
10719
|
-
return { path: file, added: lines, deleted: 0 };
|
|
10796
|
+
return isNetworkFsType(fs7.statfsSync(dir).type);
|
|
10720
10797
|
} catch {
|
|
10721
|
-
return
|
|
10798
|
+
return false;
|
|
10722
10799
|
}
|
|
10723
|
-
}
|
|
10724
|
-
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
return null;
|
|
10728
|
-
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
10729
|
-
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
10730
|
-
if (!blobs)
|
|
10800
|
+
}, LOCK_WAIT_MS = 5000, LOCK_STALE_MS = 30000, LOCK_OWNER = "owner", lockToken = (lock) => {
|
|
10801
|
+
try {
|
|
10802
|
+
return fs7.readFileSync(path10.join(lock, LOCK_OWNER), "utf8");
|
|
10803
|
+
} catch {
|
|
10731
10804
|
return null;
|
|
10732
|
-
const out = new Set;
|
|
10733
|
-
const opaque = new Set;
|
|
10734
|
-
for (const entry of candidates) {
|
|
10735
|
-
const before = blobs.get(entry.oldBlob);
|
|
10736
|
-
let after = blobs.get(entry.newBlob);
|
|
10737
|
-
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
10738
|
-
try {
|
|
10739
|
-
after = readFileSync3(path10.join(cwd, entry.path));
|
|
10740
|
-
} catch {
|
|
10741
|
-
after = undefined;
|
|
10742
|
-
}
|
|
10743
|
-
if (before && after && cosmeticChange(entry.path, before, after))
|
|
10744
|
-
out.add(entry.path);
|
|
10745
|
-
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
10746
|
-
opaque.add(entry.path);
|
|
10747
10805
|
}
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
if (!files || !blobs)
|
|
10754
|
-
return new Set;
|
|
10755
|
-
const texts = new Map;
|
|
10756
|
-
for (const [file, id] of files) {
|
|
10757
|
-
const bytes = blobs.get(id);
|
|
10758
|
-
if (bytes)
|
|
10759
|
-
texts.set(file, bytes.toString("utf8"));
|
|
10806
|
+
}, lockAgeMs = (lock) => {
|
|
10807
|
+
for (const target of [path10.join(lock, LOCK_OWNER), lock]) {
|
|
10808
|
+
try {
|
|
10809
|
+
return Date.now() - fs7.statSync(target).mtimeMs;
|
|
10810
|
+
} catch {}
|
|
10760
10811
|
}
|
|
10761
|
-
return
|
|
10762
|
-
}
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
|
|
10775
|
-
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10781
|
-
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
lane: high ? "full" : "focused",
|
|
10793
|
-
reasons: [
|
|
10794
|
-
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
10795
|
-
...high ? ["the task judgment records high risk"] : []
|
|
10796
|
-
],
|
|
10797
|
-
lines: 0,
|
|
10798
|
-
files: 0,
|
|
10799
|
-
riskFiles: [],
|
|
10800
|
-
base,
|
|
10801
|
-
head: options.head
|
|
10802
|
-
};
|
|
10812
|
+
return null;
|
|
10813
|
+
}, ledgerLock, sha2562 = (text) => createHash9("sha256").update(text).digest("hex"), chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
|
|
10814
|
+
${body}`), TAIL_BYTES, lastLine = (fd, size) => {
|
|
10815
|
+
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
10816
|
+
const buffer = Buffer.alloc(span);
|
|
10817
|
+
fs7.readSync(fd, buffer, 0, span, size - span);
|
|
10818
|
+
const end = buffer.lastIndexOf(10);
|
|
10819
|
+
if (end < 0) {
|
|
10820
|
+
if (span === size)
|
|
10821
|
+
return null;
|
|
10822
|
+
continue;
|
|
10823
|
+
}
|
|
10824
|
+
let stop = end;
|
|
10825
|
+
let text = "";
|
|
10826
|
+
while (stop >= 0) {
|
|
10827
|
+
const start = buffer.lastIndexOf(10, stop - 1);
|
|
10828
|
+
text = buffer.toString("utf8", start + 1, stop).trim();
|
|
10829
|
+
if (text) {
|
|
10830
|
+
if (start >= 0 || span === size)
|
|
10831
|
+
return text;
|
|
10832
|
+
break;
|
|
10833
|
+
}
|
|
10834
|
+
if (start < 0) {
|
|
10835
|
+
if (span === size)
|
|
10836
|
+
return null;
|
|
10837
|
+
break;
|
|
10838
|
+
}
|
|
10839
|
+
stop = start;
|
|
10840
|
+
}
|
|
10841
|
+
if (span === size)
|
|
10842
|
+
return text || null;
|
|
10803
10843
|
}
|
|
10804
|
-
|
|
10844
|
+
return null;
|
|
10845
|
+
}, tighten = (fd) => {
|
|
10846
|
+
try {
|
|
10847
|
+
if ((fs7.fstatSync(fd).mode & 63) !== 0)
|
|
10848
|
+
fs7.fchmodSync(fd, 384);
|
|
10849
|
+
} catch {}
|
|
10850
|
+
}, sealed = (row, id, at, previous) => {
|
|
10851
|
+
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
10852
|
+
const prevHash = previous === null ? null : sha2562(previous);
|
|
10853
|
+
const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
|
|
10854
|
+
const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
|
|
10855
|
+
return { full, line: `${JSON.stringify(full)}
|
|
10856
|
+
` };
|
|
10857
|
+
}, isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str = (value) => typeof value === "string" ? value : null, normalizeActor = (value) => {
|
|
10858
|
+
const actor = isRecord2(value) ? value : {};
|
|
10805
10859
|
return {
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
10810
|
-
}),
|
|
10811
|
-
base,
|
|
10812
|
-
head: options.head
|
|
10860
|
+
host: str(actor.host) ?? "unknown",
|
|
10861
|
+
session: str(actor.session),
|
|
10862
|
+
agentId: str(actor.agentId)
|
|
10813
10863
|
};
|
|
10814
|
-
}
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
|
|
10864
|
+
}, TAMPER_REASONS, tamperCache, fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`, tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`, isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id), RENAMED, renameAwareBranch = (scopeOf, branch) => {
|
|
10865
|
+
return (target) => {
|
|
10866
|
+
const own = typeof branch === "string" ? branch : null;
|
|
10867
|
+
const theirs = typeof target.branch === "string" ? target.branch : null;
|
|
10868
|
+
if (own === theirs)
|
|
10869
|
+
return true;
|
|
10870
|
+
if (own === null || theirs === null || typeof target.at !== "string")
|
|
10871
|
+
return false;
|
|
10872
|
+
return scopeOf(own)({ branch: theirs, at: target.at });
|
|
10873
|
+
};
|
|
10874
|
+
}, derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`), authoredBy = (authors, session) => {
|
|
10875
|
+
if (authors.has(session))
|
|
10876
|
+
return true;
|
|
10877
|
+
for (const author of authors)
|
|
10878
|
+
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
10879
|
+
return true;
|
|
10880
|
+
return false;
|
|
10881
|
+
}, TRUST_REASONS, SHELL_VOUCHED_HOSTS, untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason)), independenceReasons = (row, authors) => {
|
|
10882
|
+
const reasons = [];
|
|
10883
|
+
if (!row.actor.session)
|
|
10884
|
+
reasons.push("no_session");
|
|
10885
|
+
else if (authoredBy(authors, row.actor.session))
|
|
10886
|
+
reasons.push("author_session");
|
|
10887
|
+
if (row.self === true && !reasons.includes("no_session"))
|
|
10888
|
+
reasons.push("self");
|
|
10889
|
+
return reasons;
|
|
10890
|
+
}, restackExact, fromLead = (row, leads) => leads === null || row.actor.session !== null && leads.includes(row.actor.session), OBSERVED_ONLY;
|
|
10891
|
+
var init_ledger = __esm(() => {
|
|
10892
|
+
init_store_lock();
|
|
10893
|
+
init_carry();
|
|
10894
|
+
init_default_base();
|
|
10818
10895
|
init_rev();
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
|
|
10896
|
+
init_host_session();
|
|
10897
|
+
init_standing_signal();
|
|
10898
|
+
init_paths();
|
|
10899
|
+
PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
10900
|
+
FAILING = new Set(["failed", "blocked"]);
|
|
10901
|
+
STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
10902
|
+
PR_ROW_TYPES = new Set([
|
|
10903
|
+
"pr.created",
|
|
10904
|
+
"pr.status",
|
|
10905
|
+
"pr.merged",
|
|
10906
|
+
"pr.linked"
|
|
10907
|
+
]);
|
|
10908
|
+
NETWORK_FS_TYPES = new Set([
|
|
10909
|
+
26985,
|
|
10910
|
+
20859,
|
|
10911
|
+
4266872130,
|
|
10912
|
+
4283649346,
|
|
10913
|
+
1702057286,
|
|
10914
|
+
1397113167,
|
|
10915
|
+
12805120,
|
|
10916
|
+
16914839,
|
|
10917
|
+
1196443219,
|
|
10918
|
+
198183888
|
|
10919
|
+
]);
|
|
10920
|
+
ledgerLock = {
|
|
10921
|
+
acquire(lock) {
|
|
10922
|
+
try {
|
|
10923
|
+
fs7.mkdirSync(lock);
|
|
10924
|
+
} catch (error) {
|
|
10925
|
+
if (error.code === "EEXIST")
|
|
10926
|
+
return null;
|
|
10927
|
+
throw error;
|
|
10928
|
+
}
|
|
10929
|
+
const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
|
|
10930
|
+
fs7.writeFileSync(path10.join(lock, LOCK_OWNER), token);
|
|
10931
|
+
return token;
|
|
10932
|
+
},
|
|
10933
|
+
release(lock, token) {
|
|
10934
|
+
if (lockToken(lock) !== token)
|
|
10935
|
+
return false;
|
|
10936
|
+
const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
|
|
10937
|
+
try {
|
|
10938
|
+
fs7.renameSync(lock, tomb);
|
|
10939
|
+
} catch {
|
|
10940
|
+
return false;
|
|
10941
|
+
}
|
|
10942
|
+
if (lockToken(tomb) !== token) {
|
|
10943
|
+
try {
|
|
10944
|
+
fs7.renameSync(tomb, lock);
|
|
10945
|
+
} catch {}
|
|
10946
|
+
return false;
|
|
10947
|
+
}
|
|
10948
|
+
fs7.rmSync(tomb, { recursive: true, force: true });
|
|
10949
|
+
return true;
|
|
10950
|
+
},
|
|
10951
|
+
token: lockToken,
|
|
10952
|
+
reap(lock, observed) {
|
|
10953
|
+
const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
|
|
10954
|
+
try {
|
|
10955
|
+
fs7.renameSync(lock, tomb);
|
|
10956
|
+
} catch {
|
|
10957
|
+
return false;
|
|
10958
|
+
}
|
|
10959
|
+
if (lockToken(tomb) === observed) {
|
|
10960
|
+
fs7.rmSync(tomb, { recursive: true, force: true });
|
|
10961
|
+
return true;
|
|
10962
|
+
}
|
|
10963
|
+
try {
|
|
10964
|
+
fs7.renameSync(tomb, lock);
|
|
10965
|
+
} catch {
|
|
10966
|
+
fs7.rmSync(tomb, { recursive: true, force: true });
|
|
10967
|
+
}
|
|
10968
|
+
return false;
|
|
10969
|
+
}
|
|
10826
10970
|
};
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
"
|
|
10833
|
-
"
|
|
10834
|
-
"
|
|
10835
|
-
"
|
|
10836
|
-
"
|
|
10837
|
-
"
|
|
10838
|
-
"gate",
|
|
10839
|
-
"gates",
|
|
10840
|
-
"policy",
|
|
10841
|
-
"policies",
|
|
10842
|
-
"hook",
|
|
10843
|
-
"hooks",
|
|
10844
|
-
"husky",
|
|
10845
|
-
"lefthook",
|
|
10846
|
-
"release",
|
|
10847
|
-
"releases",
|
|
10848
|
-
"migration",
|
|
10849
|
-
"migrations",
|
|
10850
|
-
"migrate",
|
|
10851
|
-
"workflows"
|
|
10971
|
+
TAIL_BYTES = 64 * 1024;
|
|
10972
|
+
TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
10973
|
+
tamperCache = new Map;
|
|
10974
|
+
RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
10975
|
+
TRUST_REASONS = new Set([
|
|
10976
|
+
"unsigned",
|
|
10977
|
+
"hash_mismatch",
|
|
10978
|
+
"broken_link",
|
|
10979
|
+
"duplicate_id",
|
|
10980
|
+
"unissued_session",
|
|
10981
|
+
"unbound_session"
|
|
10852
10982
|
]);
|
|
10853
|
-
|
|
10854
|
-
"
|
|
10855
|
-
"
|
|
10856
|
-
"
|
|
10857
|
-
"
|
|
10858
|
-
"
|
|
10983
|
+
SHELL_VOUCHED_HOSTS = new Set([
|
|
10984
|
+
"cli",
|
|
10985
|
+
"opencode",
|
|
10986
|
+
"pi",
|
|
10987
|
+
"codex_cli",
|
|
10988
|
+
"codex_desktop"
|
|
10859
10989
|
]);
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
"
|
|
10863
|
-
"
|
|
10864
|
-
"
|
|
10865
|
-
|
|
10866
|
-
"yarn.lock",
|
|
10867
|
-
"pnpm-lock.yaml",
|
|
10868
|
-
"Cargo.lock",
|
|
10869
|
-
"go.sum",
|
|
10870
|
-
"poetry.lock",
|
|
10871
|
-
"uv.lock",
|
|
10872
|
-
"Pipfile.lock",
|
|
10873
|
-
"Gemfile.lock",
|
|
10874
|
-
"composer.lock"
|
|
10990
|
+
restackExact = new WeakMap;
|
|
10991
|
+
OBSERVED_ONLY = new Set([
|
|
10992
|
+
"check",
|
|
10993
|
+
"merge.unverified",
|
|
10994
|
+
"policy.judged",
|
|
10995
|
+
...PR_ROW_TYPES
|
|
10875
10996
|
]);
|
|
10876
|
-
DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
|
|
10877
|
-
DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
10878
|
-
INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
10879
|
-
TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
|
|
10880
|
-
globCache = new Map;
|
|
10881
|
-
REGULAR = new Set(["100644", "100755"]);
|
|
10882
|
-
ABSENT = /^0+$/u;
|
|
10883
10997
|
});
|
|
10884
10998
|
|
|
10885
10999
|
// packages/workit-core/src/core/policy/derive.ts
|
|
@@ -14625,7 +14739,7 @@ function splitCommand(command) {
|
|
|
14625
14739
|
return argv.length ? argv : new Error("empty command");
|
|
14626
14740
|
}
|
|
14627
14741
|
function checkRoot(cwd) {
|
|
14628
|
-
return path18.resolve(
|
|
14742
|
+
return path18.resolve(git4(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
|
|
14629
14743
|
}
|
|
14630
14744
|
function detectedChecks(root) {
|
|
14631
14745
|
const read = (file) => {
|
|
@@ -14758,7 +14872,7 @@ function workitAvailable(env = process.env) {
|
|
|
14758
14872
|
function checkCommand(args, version, onPath = workitAvailable()) {
|
|
14759
14873
|
return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
|
|
14760
14874
|
}
|
|
14761
|
-
var SCRIPT_CHECKS, CHECKS_FILE = ".workit/checks.json", LEGACY_CHECKS_FILE = "workit.checks.json", isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META,
|
|
14875
|
+
var SCRIPT_CHECKS, CHECKS_FILE = ".workit/checks.json", LEGACY_CHECKS_FILE = "workit.checks.json", isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META, git4 = (cwd, args) => {
|
|
14762
14876
|
const run = spawnSync9("git", args, {
|
|
14763
14877
|
cwd,
|
|
14764
14878
|
encoding: "utf8",
|
|
@@ -15554,7 +15668,7 @@ Autonomy contract:
|
|
|
15554
15668
|
CLI first: if a step has one right answer, use the \`workit\` verb instead of
|
|
15555
15669
|
hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
|
|
15556
15670
|
else \`npx -y @brainervirus/workit-cli\`).
|
|
15557
|
-
Other checkout: \`workit --cwd <dir>
|
|
15671
|
+
Other checkout: \`workit <verb> --cwd <dir> …\` (verb first: host rules match it); one workit
|
|
15558
15672
|
verb per shell call, never \`cd <dir> && workit\`. zsh-safe: no \`echo =====\`
|
|
15559
15673
|
(\`printf '%s\\n' ---\`), quote globs and URLs, no variable named \`status\` or \`path\`.
|
|
15560
15674
|
Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
|
|
@@ -15566,7 +15680,7 @@ authorizes an effect. Judge work once with
|
|
|
15566
15680
|
\`workit policy assess --judge risk= behavior= product-choice= plan=\`.
|
|
15567
15681
|
Across repositories, bind items to checkout, branch and endpoint, resolve
|
|
15568
15682
|
competing targets before a mutation, and reconcile an uncertain
|
|
15569
|
-
|
|
15683
|
+
effect before a retry.
|
|
15570
15684
|
|
|
15571
15685
|
Mid-task: never silently drop or resume an objective (workit-continue).
|
|
15572
15686
|
|
|
@@ -17911,7 +18025,17 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
|
|
|
17911
18025
|
}
|
|
17912
18026
|
}
|
|
17913
18027
|
return found;
|
|
17914
|
-
}, PLAIN_POSIX, PLAIN_POWERSHELL,
|
|
18028
|
+
}, PLAIN_POSIX, PLAIN_POWERSHELL, expandsInQuotes = (body, dialect) => {
|
|
18029
|
+
const escape = dialect === "posix" ? "\\" : "`";
|
|
18030
|
+
for (let index = 0;index < body.length; index++) {
|
|
18031
|
+
const char = body[index];
|
|
18032
|
+
if (char === escape)
|
|
18033
|
+
index++;
|
|
18034
|
+
else if (char === "$" || dialect === "posix" && char === "`")
|
|
18035
|
+
return true;
|
|
18036
|
+
}
|
|
18037
|
+
return false;
|
|
18038
|
+
}, EXPANDING_RUN, arithmeticEnd = (command, start) => {
|
|
17915
18039
|
const end = substitutionEnd(command, start);
|
|
17916
18040
|
return command[end - 1] === ")" && end > start + 2 ? end : 0;
|
|
17917
18041
|
}, segmentsOf = (command, dialect = "posix") => {
|
|
@@ -17927,6 +18051,7 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
|
|
|
17927
18051
|
let current = { words: [], redirects: [] };
|
|
17928
18052
|
let word = "";
|
|
17929
18053
|
let inWord = false;
|
|
18054
|
+
let expanding = false;
|
|
17930
18055
|
let pending = null;
|
|
17931
18056
|
const heredocs = [];
|
|
17932
18057
|
const escape = posix ? "\\" : "`";
|
|
@@ -17941,11 +18066,16 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
|
|
|
17941
18066
|
heredocs.push({ delimiter: text.replace(/^-/, ""), owner: current });
|
|
17942
18067
|
else if (pending === "herestring")
|
|
17943
18068
|
(current.stdin ??= []).push(text);
|
|
17944
|
-
else if (pending !== "input")
|
|
18069
|
+
else if (pending !== "input") {
|
|
18070
|
+
const at = current.words.length;
|
|
17945
18071
|
current.words.push(...MARKERS.test(word) ? expandBraces(word) : [word]);
|
|
18072
|
+
if (expanding || !posix && word.startsWith("@"))
|
|
18073
|
+
(current.expands ??= []).push(...current.words.slice(at).map((_, i) => at + i));
|
|
18074
|
+
}
|
|
17946
18075
|
pending = null;
|
|
17947
18076
|
word = "";
|
|
17948
18077
|
inWord = false;
|
|
18078
|
+
expanding = false;
|
|
17949
18079
|
};
|
|
17950
18080
|
const flushSegment = () => {
|
|
17951
18081
|
flushWord();
|
|
@@ -17960,6 +18090,7 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
|
|
|
17960
18090
|
};
|
|
17961
18091
|
const substitution = (inner, text) => {
|
|
17962
18092
|
(current.subs ??= []).push(inner);
|
|
18093
|
+
expanding = true;
|
|
17963
18094
|
word += text;
|
|
17964
18095
|
inWord = true;
|
|
17965
18096
|
};
|
|
@@ -17970,6 +18101,8 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
|
|
|
17970
18101
|
if (run) {
|
|
17971
18102
|
word += run[0];
|
|
17972
18103
|
inWord = true;
|
|
18104
|
+
if (EXPANDING_RUN.test(run[0]))
|
|
18105
|
+
expanding = true;
|
|
17973
18106
|
index += run[0].length - 1;
|
|
17974
18107
|
continue;
|
|
17975
18108
|
}
|
|
@@ -17998,6 +18131,8 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
|
|
|
17998
18131
|
const body = command.slice(index + 1, end < 0 ? undefined : end);
|
|
17999
18132
|
if (posix && char === '"' && /\$\(|`/.test(body))
|
|
18000
18133
|
(current.subs ??= []).push(...substitutions(body));
|
|
18134
|
+
if (char === '"' && expandsInQuotes(body, dialect))
|
|
18135
|
+
expanding = true;
|
|
18001
18136
|
word += unquote(body, char, dialect);
|
|
18002
18137
|
inWord = true;
|
|
18003
18138
|
index = end < 0 ? command.length : end;
|
|
@@ -18043,6 +18178,8 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
|
|
|
18043
18178
|
if (char === "(" && inWord && word.endsWith("$")) {
|
|
18044
18179
|
word = word.slice(0, -1);
|
|
18045
18180
|
inWord = word !== "";
|
|
18181
|
+
if (!inWord)
|
|
18182
|
+
expanding = false;
|
|
18046
18183
|
}
|
|
18047
18184
|
group(char === "(" ? "open" : "close");
|
|
18048
18185
|
} else if (!posix && (char === "{" || char === "}")) {
|
|
@@ -18096,6 +18233,8 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
|
|
|
18096
18233
|
else if (posix && char === "}" && word.includes(OPEN))
|
|
18097
18234
|
word += CLOSE;
|
|
18098
18235
|
else {
|
|
18236
|
+
if (char === "$")
|
|
18237
|
+
expanding = true;
|
|
18099
18238
|
word += char;
|
|
18100
18239
|
inWord = true;
|
|
18101
18240
|
}
|
|
@@ -18121,6 +18260,7 @@ var init_shell_words = __esm(() => {
|
|
|
18121
18260
|
};
|
|
18122
18261
|
PLAIN_POSIX = /[^\s'"\\`$(){},<>|;&#]+/uy;
|
|
18123
18262
|
PLAIN_POWERSHELL = /[^\s'"`(){}<>|;&#]+/uy;
|
|
18263
|
+
EXPANDING_RUN = /[*?[]/u;
|
|
18124
18264
|
});
|
|
18125
18265
|
|
|
18126
18266
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
@@ -18189,8 +18329,17 @@ function walk2(command, dialect, depth, seen) {
|
|
|
18189
18329
|
visit(next, undefined, hops + 1);
|
|
18190
18330
|
};
|
|
18191
18331
|
const wrapped = seen ? null : workitCheckTail(words);
|
|
18192
|
-
if (wrapped)
|
|
18193
|
-
|
|
18332
|
+
if (wrapped) {
|
|
18333
|
+
const outer = dir;
|
|
18334
|
+
if (wrapped.cwd !== null)
|
|
18335
|
+
dir = joinDir(dir, wrapped.cwd);
|
|
18336
|
+
if ("script" in wrapped)
|
|
18337
|
+
nested(wrapped.script, "posix");
|
|
18338
|
+
else
|
|
18339
|
+
runs([wrapped.argv]);
|
|
18340
|
+
dir = outer;
|
|
18341
|
+
return;
|
|
18342
|
+
}
|
|
18194
18343
|
if (CD.has(name)) {
|
|
18195
18344
|
const target = cdTarget(words);
|
|
18196
18345
|
dir = target === null ? os5.homedir() : joinDir(dir, target);
|
|
@@ -18275,14 +18424,14 @@ function workitCheckTail(words) {
|
|
|
18275
18424
|
return null;
|
|
18276
18425
|
} else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
|
|
18277
18426
|
return null;
|
|
18278
|
-
const args = words.slice(at + 1);
|
|
18427
|
+
const { rest: args, cwd } = skipWorkitGlobals(words.slice(at + 1));
|
|
18279
18428
|
if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
|
|
18280
18429
|
return null;
|
|
18281
18430
|
const split = args.indexOf("--");
|
|
18282
18431
|
const tail = split < 0 ? [] : args.slice(split + 1);
|
|
18283
18432
|
if (!tail.length)
|
|
18284
18433
|
return null;
|
|
18285
|
-
return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
|
|
18434
|
+
return args.slice(0, split).includes("--shell") ? { script: tail.join(" "), cwd } : { argv: tail, cwd };
|
|
18286
18435
|
}
|
|
18287
18436
|
function simpleCommand(command, dialect = "posix", depth = 0) {
|
|
18288
18437
|
let segments;
|
|
@@ -18342,7 +18491,21 @@ function skipWorkitGlobals(args) {
|
|
|
18342
18491
|
} else if (args[at] !== "--json")
|
|
18343
18492
|
break;
|
|
18344
18493
|
}
|
|
18345
|
-
|
|
18494
|
+
const rest = [];
|
|
18495
|
+
for (;at < args.length; at++) {
|
|
18496
|
+
const arg = args[at];
|
|
18497
|
+
if (arg === "--") {
|
|
18498
|
+
rest.push(...args.slice(at));
|
|
18499
|
+
break;
|
|
18500
|
+
}
|
|
18501
|
+
if (arg.startsWith("--cwd="))
|
|
18502
|
+
cwd = arg.slice("--cwd=".length);
|
|
18503
|
+
else if (arg === "--cwd" && args[at + 1] !== undefined)
|
|
18504
|
+
cwd = args[++at];
|
|
18505
|
+
else
|
|
18506
|
+
rest.push(arg);
|
|
18507
|
+
}
|
|
18508
|
+
return { rest, cwd };
|
|
18346
18509
|
}
|
|
18347
18510
|
function rawAction(invocation) {
|
|
18348
18511
|
const args = invocation.args;
|
|
@@ -18454,7 +18617,7 @@ function settlePendingCommit(input) {
|
|
|
18454
18617
|
settle2(input, repo, file);
|
|
18455
18618
|
} catch {}
|
|
18456
18619
|
}
|
|
18457
|
-
var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLARATIONS, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, GIT_CONFIG_OPTIONS, GIT_CONFIG_ENV,
|
|
18620
|
+
var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLARATIONS, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, GIT_CONFIG_OPTIONS, GIT_CONFIG_ENV, ASSIGNMENT, MAX_DEPTH = 4, MAX_EXPANSIONS = 16, isAbsolute = (value) => path22.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\"), HERE, joinDir = (base, raw) => {
|
|
18458
18621
|
const next = raw.replace(HERE, ".");
|
|
18459
18622
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
18460
18623
|
return;
|
|
@@ -18473,7 +18636,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
18473
18636
|
const first = words[0];
|
|
18474
18637
|
if (first === undefined)
|
|
18475
18638
|
return { words, env };
|
|
18476
|
-
const assignment =
|
|
18639
|
+
const assignment = ASSIGNMENT.exec(first);
|
|
18477
18640
|
if (assignment) {
|
|
18478
18641
|
if (GIT_CONFIG_ENV.test(assignment[1]))
|
|
18479
18642
|
env[assignment[1]] = assignment[2];
|
|
@@ -18527,10 +18690,10 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
18527
18690
|
while (rest[0]?.startsWith("-"))
|
|
18528
18691
|
rest = rest.slice(1);
|
|
18529
18692
|
}
|
|
18530
|
-
if (rest.length === 0 || !rest.every((word) =>
|
|
18693
|
+
if (rest.length === 0 || !rest.every((word) => ASSIGNMENT.test(word)))
|
|
18531
18694
|
return null;
|
|
18532
18695
|
return rest.map((word) => {
|
|
18533
|
-
const [, name, value] =
|
|
18696
|
+
const [, name, value] = ASSIGNMENT.exec(word);
|
|
18534
18697
|
return [name, expandWord(value, vars)];
|
|
18535
18698
|
});
|
|
18536
18699
|
}, literalOutput = (segment) => {
|
|
@@ -18695,7 +18858,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
18695
18858
|
return line.slice(index).join(" ").slice(15);
|
|
18696
18859
|
if (/^-S./.test(word))
|
|
18697
18860
|
return [word.slice(2), ...line.slice(index + 1)].join(" ");
|
|
18698
|
-
if (!word.startsWith("-") && !
|
|
18861
|
+
if (!word.startsWith("-") && !ASSIGNMENT.test(word))
|
|
18699
18862
|
return null;
|
|
18700
18863
|
}
|
|
18701
18864
|
return null;
|
|
@@ -19067,7 +19230,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
19067
19230
|
return best.base + url.slice(best.prefix.length);
|
|
19068
19231
|
}
|
|
19069
19232
|
return url;
|
|
19070
|
-
},
|
|
19233
|
+
}, pushUrl = (config, remote, dir) => {
|
|
19071
19234
|
const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
|
|
19072
19235
|
return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
|
|
19073
19236
|
}, pushRemoteOf = (config, branch) => {
|
|
@@ -19093,7 +19256,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
|
|
|
19093
19256
|
const pushRemote = pushRemoteOf(config, repo.branch);
|
|
19094
19257
|
if (pushRemote === null || pushRemote === remote)
|
|
19095
19258
|
return true;
|
|
19096
|
-
return
|
|
19259
|
+
return pushUrl(config, remote, repo.dir) === pushUrl(config, pushRemote, repo.dir);
|
|
19097
19260
|
}, quote = (text) => `\`${text}\``, refusal = (action, branch, dir) => {
|
|
19098
19261
|
if (action.kind === "merge")
|
|
19099
19262
|
return `workit: raw ${quote(action.via === "api" ? `${action.forge} api` : action.forge === "gh" ? "gh pr merge" : "glab mr merge")}${action.via === "api" ? " merge" : ""} bypasses the merge grant and the verdict gate. Run ${quote("workit pr merge")} instead (it checks both; ${quote("workit help pr")}).`;
|
|
@@ -19405,7 +19568,7 @@ var init_raw_git = __esm(() => {
|
|
|
19405
19568
|
]);
|
|
19406
19569
|
GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
|
|
19407
19570
|
GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
|
|
19408
|
-
|
|
19571
|
+
ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
|
|
19409
19572
|
HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
|
|
19410
19573
|
VARIABLE = /\$(?:\{(\w+)\}|(\w+))/g;
|
|
19411
19574
|
XARGS_VALUES = new Set(["-n", "-L", "-l", "-P", "-d", "-E", "-e", "-s", "-a", "--arg-file", "--delimiter"].concat([
|
|
@@ -19677,6 +19840,53 @@ var init_policy = __esm(() => {
|
|
|
19677
19840
|
init_shell_words();
|
|
19678
19841
|
});
|
|
19679
19842
|
|
|
19843
|
+
// packages/workit-core/src/hooks/delivery-verbs.ts
|
|
19844
|
+
var WORKIT_ALLOWED_VERBS, WORKIT_BYPASS_FLAGS, ALLOWED, isBypass = (word) => WORKIT_BYPASS_FLAGS.some((flag) => word === flag || word.startsWith(`${flag}=`)), isWorkitWord = (word) => /^(?:.*[\\/])?workit(?:-cli)?$/u.test(word), bypassReason = (flag) => `Workit: \`${flag}\` overrides a Workit gate, so the user decides this call (no allow rule covers it).`, EXPANSION_REASON = "Workit: an expansion before `--` could become a bypass flag, so the user decides this call (no allow rule covers it).", workitBypassFlag = (command, dialect = "posix") => {
|
|
19845
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
19846
|
+
if (segment.unanalyzable) {
|
|
19847
|
+
const text = command.replaceAll(/['"\\`]/gu, "");
|
|
19848
|
+
if (!/\bworkit\b/u.test(text))
|
|
19849
|
+
return null;
|
|
19850
|
+
const found = WORKIT_BYPASS_FLAGS.find((flag) => text.includes(flag));
|
|
19851
|
+
return found ? bypassReason(found) : null;
|
|
19852
|
+
}
|
|
19853
|
+
const start = segment.words.findIndex(isWorkitWord);
|
|
19854
|
+
if (start < 0)
|
|
19855
|
+
continue;
|
|
19856
|
+
const words = segment.words.slice(start + 1);
|
|
19857
|
+
const end = words.indexOf("--");
|
|
19858
|
+
const flag = (end < 0 ? words : words.slice(0, end)).find(isBypass);
|
|
19859
|
+
if (flag)
|
|
19860
|
+
return bypassReason(flag.split("=")[0] ?? flag);
|
|
19861
|
+
const limit = start + 1 + (end < 0 ? words.length : end);
|
|
19862
|
+
if (segment.expands?.some((index) => index > start && index < limit))
|
|
19863
|
+
return EXPANSION_REASON;
|
|
19864
|
+
}
|
|
19865
|
+
return null;
|
|
19866
|
+
};
|
|
19867
|
+
var init_delivery_verbs = __esm(() => {
|
|
19868
|
+
init_shell_words();
|
|
19869
|
+
WORKIT_ALLOWED_VERBS = [
|
|
19870
|
+
"git",
|
|
19871
|
+
"pr",
|
|
19872
|
+
"ci",
|
|
19873
|
+
"stack",
|
|
19874
|
+
"release",
|
|
19875
|
+
"task",
|
|
19876
|
+
"ledger",
|
|
19877
|
+
"evidence",
|
|
19878
|
+
"finding",
|
|
19879
|
+
"decision",
|
|
19880
|
+
"worker",
|
|
19881
|
+
"verify-delivery",
|
|
19882
|
+
"policy",
|
|
19883
|
+
"doctor",
|
|
19884
|
+
"help"
|
|
19885
|
+
];
|
|
19886
|
+
WORKIT_BYPASS_FLAGS = ["--unverified", "--overwrite-unintegrated"];
|
|
19887
|
+
ALLOWED = new Set(WORKIT_ALLOWED_VERBS);
|
|
19888
|
+
});
|
|
19889
|
+
|
|
19680
19890
|
// packages/workit-core/src/core/heavy-rules.ts
|
|
19681
19891
|
import fs18 from "node:fs";
|
|
19682
19892
|
import os6 from "node:os";
|
|
@@ -21038,7 +21248,7 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
21038
21248
|
}
|
|
21039
21249
|
const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
|
|
21040
21250
|
if (inner !== null && dir !== undefined)
|
|
21041
|
-
targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
|
|
21251
|
+
targets.push(...segmentTargets(workitRunDir(dir, words), inner, "posix", depth + 1, strict));
|
|
21042
21252
|
if (strict && dir !== undefined) {
|
|
21043
21253
|
const runIn = dir;
|
|
21044
21254
|
for (const sub of depth < 4 ? segment.subs ?? [] : [])
|
|
@@ -21077,6 +21287,13 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
|
21077
21287
|
}
|
|
21078
21288
|
}
|
|
21079
21289
|
return targets;
|
|
21290
|
+
}, workitRunDir = (dir, words) => {
|
|
21291
|
+
for (const invocation of workitInvocations(words.map(quoteWord2).join(" "), "posix")) {
|
|
21292
|
+
const { at, args } = workitCwd(dir, invocation);
|
|
21293
|
+
if (args[0] === "check" || args[0] === "lock" && args[1] === "run")
|
|
21294
|
+
return at;
|
|
21295
|
+
}
|
|
21296
|
+
return dir;
|
|
21080
21297
|
}, workitCwd = (dir, args) => {
|
|
21081
21298
|
const { rest, cwd } = skipWorkitGlobals(args);
|
|
21082
21299
|
return { at: cwd === null ? dir : path28.resolve(dir, homeExpanded(cwd)), args: rest };
|
|
@@ -21824,7 +22041,7 @@ var init_standing = __esm(() => {
|
|
|
21824
22041
|
});
|
|
21825
22042
|
|
|
21826
22043
|
// packages/workit-core/src/hooks/cwd-normalize.ts
|
|
21827
|
-
var COMPOUND, FILTERS, stdinOnly = (words) => {
|
|
22044
|
+
var DIR, COMPOUND, GLOBALS_FIRST, VERB_FIRST, VERBS, FILTERS, clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length, stdinOnly = (words) => {
|
|
21828
22045
|
const [name, ...args] = words;
|
|
21829
22046
|
const operands = args.filter((w) => !w.startsWith("-"));
|
|
21830
22047
|
if (name === "grep")
|
|
@@ -21844,18 +22061,36 @@ var COMPOUND, FILTERS, stdinOnly = (words) => {
|
|
|
21844
22061
|
return null;
|
|
21845
22062
|
if (head.words.some((w) => w === "--cwd" || w.startsWith("--cwd=")))
|
|
21846
22063
|
return null;
|
|
21847
|
-
const clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
21848
22064
|
if (!segments.every(clean))
|
|
21849
22065
|
return null;
|
|
21850
22066
|
if (!segments.slice(0, -1).every((s) => s.pipe))
|
|
21851
22067
|
return null;
|
|
21852
22068
|
if (!filters.every((s) => FILTERS.has(s.words[0] ?? "") && stdinOnly(s.words)))
|
|
21853
22069
|
return null;
|
|
21854
|
-
|
|
22070
|
+
const args = rest.slice("workit".length).trimStart();
|
|
22071
|
+
const verb = VERB_FIRST.exec(args);
|
|
22072
|
+
if (!verb?.[2] || !VERBS.has(verb[2]))
|
|
22073
|
+
return `workit --cwd ${dir} ${args}`.trimEnd();
|
|
22074
|
+
const json = verb[1] ? " --json" : "";
|
|
22075
|
+
return `workit ${verb[2]} --cwd ${dir}${json}${args.slice(verb[0].length)}`.trimEnd();
|
|
22076
|
+
}, verbFirst = (command) => {
|
|
22077
|
+
const match = GLOBALS_FIRST.exec(command);
|
|
22078
|
+
const [whole, lead, globals, verb] = match ?? [];
|
|
22079
|
+
if (!whole || !verb || !VERBS.has(verb) || !globals?.includes("--cwd"))
|
|
22080
|
+
return null;
|
|
22081
|
+
const segments = segmentsOf(command);
|
|
22082
|
+
if (segments.length !== 1 || !segments.every(clean))
|
|
22083
|
+
return null;
|
|
22084
|
+
return `${lead} ${verb}${globals}${command.slice(whole.length)}`;
|
|
21855
22085
|
};
|
|
21856
22086
|
var init_cwd_normalize = __esm(() => {
|
|
22087
|
+
init_delivery_verbs();
|
|
21857
22088
|
init_shell_words();
|
|
21858
|
-
|
|
22089
|
+
DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
|
|
22090
|
+
COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
|
|
22091
|
+
GLOBALS_FIRST = new RegExp(String.raw`^(\s*workit)((?:\s+(?:--json|--cwd(?:=|\s+)(?:${DIR})))+)\s+([a-z][a-z-]*)(?=\s|$)`, "u");
|
|
22092
|
+
VERB_FIRST = /^((?:--json\s+)*)([a-z][a-z-]*)(?=\s|$)/u;
|
|
22093
|
+
VERBS = new Set(WORKIT_ALLOWED_VERBS);
|
|
21859
22094
|
FILTERS = new Set(["tail", "head", "grep"]);
|
|
21860
22095
|
});
|
|
21861
22096
|
|
|
@@ -21995,14 +22230,14 @@ var FANOUT_TOKEN_FILE = "workit-fanout-token", NONE5, CASE_INSENSITIVE2, canonic
|
|
|
21995
22230
|
return false;
|
|
21996
22231
|
}
|
|
21997
22232
|
}, shown = (target) => path31.basename(target) === ".workit-live-target" ? path31.dirname(target) : target, unboundDeny = (input, target) => {
|
|
21998
|
-
const unblock = "Work only in the worktree your brief names, which the lead made with `workit fanout worktree create <slice>`: edit files under it and run one verb per call against it (`workit --cwd <worktree>
|
|
22233
|
+
const unblock = "Work only in the worktree your brief names, which the lead made with `workit fanout worktree create <slice>`: edit files under it and run one verb per call against it (`workit git --cwd <worktree> commit -m … -- <paths>`, `workit check --cwd <worktree> <name>`). If your brief names no worktree, stop and report that to the lead.";
|
|
21999
22234
|
return {
|
|
22000
22235
|
kind: "deny",
|
|
22001
22236
|
reason: `workit worktree confinement: implementer ${input.session.agentId} is not working in a Workit worktree yet, and ${shown(target)} is outside every worktree \`workit fanout worktree create\` made, so it would change the lead's checkout. ${unblock}`,
|
|
22002
22237
|
unblock
|
|
22003
22238
|
};
|
|
22004
22239
|
}, outsideDeny = (input, worktree, target) => {
|
|
22005
|
-
const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree}
|
|
22240
|
+
const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit git --cwd ${worktree} commit -m … -- <paths>\`, \`workit check --cwd ${worktree} <name>\` (or \`git -C ${worktree} …\`); a command whose writes Workit cannot see is judged by the directory it runs in. Anything outside it is a follow-up for your report.`;
|
|
22006
22241
|
return {
|
|
22007
22242
|
kind: "deny",
|
|
22008
22243
|
reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
|
|
@@ -22010,7 +22245,7 @@ var FANOUT_TOKEN_FILE = "workit-fanout-token", NONE5, CASE_INSENSITIVE2, canonic
|
|
|
22010
22245
|
};
|
|
22011
22246
|
}, unknownDirDeny = (input, worktree) => {
|
|
22012
22247
|
const wt = worktree ?? "<worktree>";
|
|
22013
|
-
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
|
|
22248
|
+
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit <verb> --cwd ${wt}\`.`;
|
|
22014
22249
|
return {
|
|
22015
22250
|
kind: "deny",
|
|
22016
22251
|
reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
|
|
@@ -22108,7 +22343,7 @@ var init_worktree_confine = __esm(() => {
|
|
|
22108
22343
|
// packages/workit-core/src/hooks/handle.ts
|
|
22109
22344
|
function handleHook(input, deps) {
|
|
22110
22345
|
const decision = decide(input, deps);
|
|
22111
|
-
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny")
|
|
22346
|
+
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny" || decision.kind === "ask")
|
|
22112
22347
|
return decision;
|
|
22113
22348
|
const delta = standingDelta(input);
|
|
22114
22349
|
if (!delta)
|
|
@@ -22175,9 +22410,12 @@ function decide(input, deps) {
|
|
|
22175
22410
|
if (plan.kind === "rewrite" && !rewrites && canDeny)
|
|
22176
22411
|
return heavyDenial(event.command, plan);
|
|
22177
22412
|
const lease = plan.kind === "rewrite" && rewrites ? plan : null;
|
|
22178
|
-
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) : null;
|
|
22413
|
+
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) ?? verbFirst(event.command) : null;
|
|
22179
22414
|
const aside = plan.kind === "passthrough" ? plan.note : null;
|
|
22180
22415
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
22416
|
+
const bypass = !lease && input.host === "claude_code" ? workitBypassFlag(event.command, event.dialect) : null;
|
|
22417
|
+
if (bypass)
|
|
22418
|
+
return { kind: "ask", reason: bypass };
|
|
22181
22419
|
const rewrittenTo = lease?.command ?? cwdCommand;
|
|
22182
22420
|
const rewritten = (text) => rewrittenTo ? {
|
|
22183
22421
|
kind: "rewrite",
|
|
@@ -22249,7 +22487,7 @@ var usable2 = (value) => value === "native" || value === "partial", NONE6, resen
|
|
|
22249
22487
|
return `${isolatedGit(input.cwd)}
|
|
22250
22488
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
22251
22489
|
if (role === "implementer")
|
|
22252
|
-
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit --cwd <worktree>
|
|
22490
|
+
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit git --cwd <worktree> commit -m "<message>" -- <paths>\`, \`workit check --cwd <worktree> <name>\` (verb first, so host allow rules match). The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit git --cwd <worktree> branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
22253
22491
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
22254
22492
|
if (role !== "judge" || !event.agentId)
|
|
22255
22493
|
return readOnly;
|
|
@@ -22296,6 +22534,7 @@ var init_handle = __esm(() => {
|
|
|
22296
22534
|
init_workspaces();
|
|
22297
22535
|
init_host_session();
|
|
22298
22536
|
init_cwd_normalize();
|
|
22537
|
+
init_delivery_verbs();
|
|
22299
22538
|
init_heavy_gate();
|
|
22300
22539
|
init_policy();
|
|
22301
22540
|
init_raw_git();
|
|
@@ -22534,6 +22773,17 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, dialectOf2 = (value) =
|
|
|
22534
22773
|
},
|
|
22535
22774
|
exitCode: 0
|
|
22536
22775
|
};
|
|
22776
|
+
if (decision.kind === "ask" && native === "PreToolUse")
|
|
22777
|
+
return {
|
|
22778
|
+
json: {
|
|
22779
|
+
hookSpecificOutput: {
|
|
22780
|
+
hookEventName: "PreToolUse",
|
|
22781
|
+
permissionDecision: "ask",
|
|
22782
|
+
permissionDecisionReason: decision.reason
|
|
22783
|
+
}
|
|
22784
|
+
},
|
|
22785
|
+
exitCode: 0
|
|
22786
|
+
};
|
|
22537
22787
|
if (decision.kind === "context" && native !== null && CONTEXT_EVENTS.has(native))
|
|
22538
22788
|
return {
|
|
22539
22789
|
json: { hookSpecificOutput: { hookEventName: native, additionalContext: decision.text } },
|
|
@@ -23315,6 +23565,7 @@ var init_pi = () => {};
|
|
|
23315
23565
|
var init_hooks = __esm(async () => {
|
|
23316
23566
|
init_context();
|
|
23317
23567
|
init_policy();
|
|
23568
|
+
init_delivery_verbs();
|
|
23318
23569
|
init_heavy_gate();
|
|
23319
23570
|
init_write_gate();
|
|
23320
23571
|
init_live_session();
|