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