@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
package/dist/workit-hook.js
CHANGED
|
@@ -6903,6 +6903,26 @@ function resolveRef(cwd, ref) {
|
|
|
6903
6903
|
return null;
|
|
6904
6904
|
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
6905
6905
|
}
|
|
6906
|
+
function originHeadRef(cwd) {
|
|
6907
|
+
return line(git(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]));
|
|
6908
|
+
}
|
|
6909
|
+
function ownChangeDrift(cwd, range) {
|
|
6910
|
+
const before = diffRaw(cwd, range.fromFork, range.from);
|
|
6911
|
+
const after = diffRaw(cwd, range.toFork, range.to);
|
|
6912
|
+
if (!before || !after)
|
|
6913
|
+
return null;
|
|
6914
|
+
const judged = new Map(before.map((entry) => [entry.path, entry]));
|
|
6915
|
+
const current = new Map(after.map((entry) => [entry.path, entry]));
|
|
6916
|
+
const same = (a, b) => a !== undefined && b !== undefined && a.oldMode === b.oldMode && a.oldBlob === b.oldBlob && a.newMode === b.newMode && a.newBlob === b.newBlob;
|
|
6917
|
+
return [...new Set([...judged.keys(), ...current.keys()])].filter((file) => !same(judged.get(file), current.get(file))).toSorted();
|
|
6918
|
+
}
|
|
6919
|
+
function sameOwnChange(cwd, range) {
|
|
6920
|
+
const fromFork = mergeBase(cwd, range.fromBase, range.from);
|
|
6921
|
+
const toFork = mergeBase(cwd, range.toBase, range.to);
|
|
6922
|
+
if (!fromFork || !toFork)
|
|
6923
|
+
return false;
|
|
6924
|
+
return ownChangeDrift(cwd, { from: range.from, to: range.to, fromFork, toFork })?.length === 0;
|
|
6925
|
+
}
|
|
6906
6926
|
var LANE_DIFF = [
|
|
6907
6927
|
"-c",
|
|
6908
6928
|
"core.quotePath=false",
|
|
@@ -7045,7 +7065,7 @@ function pushRemoteName(cwd, branch) {
|
|
|
7045
7065
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
7046
7066
|
import { createHash as createHash9, randomBytes as randomBytes2 } from "node:crypto";
|
|
7047
7067
|
import fs7 from "node:fs";
|
|
7048
|
-
import
|
|
7068
|
+
import path10 from "node:path";
|
|
7049
7069
|
|
|
7050
7070
|
// packages/workit-core/src/core/store-lock.ts
|
|
7051
7071
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
@@ -7471,6 +7491,10 @@ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
|
|
|
7471
7491
|
}
|
|
7472
7492
|
};
|
|
7473
7493
|
|
|
7494
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
7495
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
7496
|
+
import path6 from "node:path";
|
|
7497
|
+
|
|
7474
7498
|
// packages/workit-core/src/core/vcs-config.ts
|
|
7475
7499
|
import fs4 from "node:fs";
|
|
7476
7500
|
import path5 from "node:path";
|
|
@@ -8790,2058 +8814,2165 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
8790
8814
|
return out;
|
|
8791
8815
|
}
|
|
8792
8816
|
|
|
8793
|
-
// packages/workit-core/src/
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
if (read.tracks.length === 0 && read.protectedBranches.length === 0)
|
|
8805
|
-
return policy;
|
|
8806
|
-
const known = new Set(policy.allowed.map((pattern) => pattern.source));
|
|
8807
|
-
const allowed = [...policy.allowed];
|
|
8808
|
-
for (const glob of read.tracks.flatMap((track) => Object.values(track.naming).map(namingGlob))) {
|
|
8809
|
-
const pattern = branchGlobPattern(glob);
|
|
8810
|
-
if (!known.has(pattern.source)) {
|
|
8811
|
-
known.add(pattern.source);
|
|
8812
|
-
allowed.push(pattern);
|
|
8813
|
-
}
|
|
8817
|
+
// packages/workit-core/src/git/default-base.ts
|
|
8818
|
+
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
8819
|
+
var trackTrunk = (cwd) => {
|
|
8820
|
+
try {
|
|
8821
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
8822
|
+
if (resolved.ok === false || !resolved.releaseTrack || resolved.releaseTrack.blocking)
|
|
8823
|
+
return null;
|
|
8824
|
+
const target = String(resolved.defaultTargetBranch ?? "").trim();
|
|
8825
|
+
return target && safeRef(target) ? target : null;
|
|
8826
|
+
} catch {
|
|
8827
|
+
return null;
|
|
8814
8828
|
}
|
|
8815
|
-
return {
|
|
8816
|
-
...policy,
|
|
8817
|
-
allowed,
|
|
8818
|
-
protected: new Set([
|
|
8819
|
-
...policy.protected,
|
|
8820
|
-
...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) => name.toLowerCase())
|
|
8821
|
-
])
|
|
8822
|
-
};
|
|
8823
8829
|
};
|
|
8824
|
-
function
|
|
8825
|
-
const
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
}
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8830
|
+
function defaultBase(cwd) {
|
|
8831
|
+
const trunk = trackTrunk(cwd);
|
|
8832
|
+
if (trunk) {
|
|
8833
|
+
for (const candidate of [`origin/${trunk}`, trunk])
|
|
8834
|
+
if (resolveRef(cwd, candidate))
|
|
8835
|
+
return candidate;
|
|
8836
|
+
}
|
|
8837
|
+
const originHead = originHeadRef(cwd);
|
|
8838
|
+
if (originHead && resolveRef(cwd, originHead))
|
|
8839
|
+
return originHead;
|
|
8840
|
+
for (const candidate of [
|
|
8841
|
+
"origin/main",
|
|
8842
|
+
"origin/master",
|
|
8843
|
+
"main",
|
|
8844
|
+
"master",
|
|
8845
|
+
"origin/develop",
|
|
8846
|
+
"develop"
|
|
8847
|
+
])
|
|
8848
|
+
if (resolveRef(cwd, candidate))
|
|
8849
|
+
return candidate;
|
|
8850
|
+
return null;
|
|
8834
8851
|
}
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
}
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
8866
|
-
for (let i = 1;i <= a.length; i++) {
|
|
8867
|
-
let previous = row[0];
|
|
8868
|
-
row[0] = i;
|
|
8869
|
-
for (let j = 1;j <= b.length; j++) {
|
|
8870
|
-
const saved = row[j];
|
|
8871
|
-
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
8872
|
-
previous = saved;
|
|
8873
|
-
}
|
|
8852
|
+
|
|
8853
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
8854
|
+
function patternRegex(dir, pattern) {
|
|
8855
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
8856
|
+
return null;
|
|
8857
|
+
const anchored = pattern.includes("/");
|
|
8858
|
+
const body = pattern.replace(/^\//u, "");
|
|
8859
|
+
let source = "";
|
|
8860
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
8861
|
+
const char = body[index];
|
|
8862
|
+
if (char === "*" && body[index + 1] === "*") {
|
|
8863
|
+
const slash = body[index + 2] === "/";
|
|
8864
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
8865
|
+
index += slash ? 2 : 1;
|
|
8866
|
+
} else if (char === "*")
|
|
8867
|
+
source += "[^/]*";
|
|
8868
|
+
else if (char === "?")
|
|
8869
|
+
source += "[^/]";
|
|
8870
|
+
else if (char === "[") {
|
|
8871
|
+
const end = body.indexOf("]", index + 2);
|
|
8872
|
+
if (end < 0)
|
|
8873
|
+
return null;
|
|
8874
|
+
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
8875
|
+
source += `[${set}]`;
|
|
8876
|
+
index = end;
|
|
8877
|
+
} else if (char === "\\" && index + 1 < body.length) {
|
|
8878
|
+
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
8879
|
+
index += 1;
|
|
8880
|
+
} else
|
|
8881
|
+
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
8874
8882
|
}
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8883
|
+
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
8884
|
+
try {
|
|
8885
|
+
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
8886
|
+
} catch {
|
|
8879
8887
|
return null;
|
|
8880
|
-
const slash = name.indexOf("/");
|
|
8881
|
-
const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
|
|
8882
|
-
const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
|
|
8883
|
-
const synonym = KIND_SYNONYMS[prefix];
|
|
8884
|
-
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;
|
|
8885
|
-
const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
|
|
8886
|
-
return `${kind}/${rest}`;
|
|
8887
|
-
};
|
|
8888
|
-
var kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", ");
|
|
8889
|
-
var aliasNote = (kinds) => {
|
|
8890
|
-
const notes = [
|
|
8891
|
-
["fix", "bugfix"],
|
|
8892
|
-
["chore", "feature"]
|
|
8893
|
-
].filter(([, kind]) => kinds.includes(kind)).map(([alias, kind]) => `${alias}/ is ${kind}/`);
|
|
8894
|
-
return notes.length ? ` (${notes.join(", ")})` : "";
|
|
8895
|
-
};
|
|
8896
|
-
var validateBranchNamePolicy = (name, policy, source) => {
|
|
8897
|
-
const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
|
|
8898
|
-
const kinds = allowedKinds(policy.allowed);
|
|
8899
|
-
if (policy.protected.has(name.toLowerCase())) {
|
|
8900
|
-
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(", ")}`;
|
|
8901
|
-
return {
|
|
8902
|
-
ok: false,
|
|
8903
|
-
rule: "protected_ref",
|
|
8904
|
-
source,
|
|
8905
|
-
attempted: name,
|
|
8906
|
-
correction,
|
|
8907
|
-
error: `protected_ref: branch ${JSON.stringify(name)} is protected by ${source}; ${correction}`
|
|
8908
|
-
};
|
|
8909
8888
|
}
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
|
|
8920
|
-
|
|
8889
|
+
}
|
|
8890
|
+
function rulesOf(dir, text) {
|
|
8891
|
+
const out = [];
|
|
8892
|
+
for (const raw of text.split(`
|
|
8893
|
+
`)) {
|
|
8894
|
+
const line = raw.trim();
|
|
8895
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
8896
|
+
continue;
|
|
8897
|
+
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
8898
|
+
let generated = null;
|
|
8899
|
+
for (const attribute of attributes) {
|
|
8900
|
+
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
8901
|
+
generated = true;
|
|
8902
|
+
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
8903
|
+
generated = false;
|
|
8904
|
+
}
|
|
8905
|
+
if (generated === null)
|
|
8906
|
+
continue;
|
|
8907
|
+
const regex = patternRegex(dir, pattern);
|
|
8908
|
+
if (regex)
|
|
8909
|
+
out.push({ regex, generated });
|
|
8921
8910
|
}
|
|
8922
|
-
return
|
|
8923
|
-
}
|
|
8924
|
-
|
|
8925
|
-
const
|
|
8926
|
-
|
|
8927
|
-
const source = resolved.provenance.branchPolicy;
|
|
8928
|
-
const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
|
|
8929
|
-
const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
|
|
8930
|
-
const fingerprint = createHash6("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
|
|
8931
|
-
return {
|
|
8932
|
-
branchPolicy,
|
|
8933
|
-
namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
|
|
8934
|
-
source,
|
|
8935
|
-
fingerprint
|
|
8936
|
-
};
|
|
8937
|
-
};
|
|
8938
|
-
var validateBranchNameFor = (workspaceRoot, name) => {
|
|
8939
|
-
try {
|
|
8940
|
-
const resolved = branchPolicySnapshotFor(workspaceRoot);
|
|
8941
|
-
return validateBranchNamePolicy(name, resolved.namePolicy, resolved.source);
|
|
8942
|
-
} catch (error) {
|
|
8943
|
-
const correction = "repair or select a valid workspace policy before creating a branch";
|
|
8911
|
+
return out;
|
|
8912
|
+
}
|
|
8913
|
+
function generatedByAttributes(files, paths) {
|
|
8914
|
+
const rules = [...files].map(([file, text]) => {
|
|
8915
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
8944
8916
|
return {
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
source: "workspaces.json",
|
|
8948
|
-
attempted: name,
|
|
8949
|
-
correction,
|
|
8950
|
-
error: `policy_unavailable: cannot validate branch ${JSON.stringify(name)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
|
|
8917
|
+
depth: dir ? dir.split("/").length : 0,
|
|
8918
|
+
rules: rulesOf(dir, text)
|
|
8951
8919
|
};
|
|
8920
|
+
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
8921
|
+
const out = new Set;
|
|
8922
|
+
for (const file of paths) {
|
|
8923
|
+
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
8924
|
+
if (last?.generated)
|
|
8925
|
+
out.add(file);
|
|
8952
8926
|
}
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
var gitFlowOf = (root, integration) => {
|
|
8956
|
-
try {
|
|
8957
|
-
if (resolveBranchPolicyFor(root).preset !== "gitflow")
|
|
8958
|
-
return null;
|
|
8959
|
-
const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
|
|
8960
|
-
const names = branchNames(root);
|
|
8961
|
-
const production = productionBranchOf(names);
|
|
8962
|
-
const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
|
|
8963
|
-
return { develop, production, developExists: names.has(develop) };
|
|
8964
|
-
} catch {
|
|
8965
|
-
return null;
|
|
8966
|
-
}
|
|
8967
|
-
};
|
|
8968
|
-
var releaseBranchesOf = (cwd) => {
|
|
8969
|
-
try {
|
|
8970
|
-
const resolved = resolveBranchPolicyFor(cwd);
|
|
8971
|
-
const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
|
|
8972
|
-
return {
|
|
8973
|
-
preset: resolved.preset,
|
|
8974
|
-
branches: flow ? [flow.production, flow.develop] : [],
|
|
8975
|
-
production: flow ? flow.production : resolved.defaultTargetBranch
|
|
8976
|
-
};
|
|
8977
|
-
} catch {
|
|
8978
|
-
return { preset: "unknown", branches: [], production: null };
|
|
8979
|
-
}
|
|
8980
|
-
};
|
|
8981
|
-
var releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase());
|
|
8982
|
-
var branchGuidance = (cwd) => {
|
|
8983
|
-
try {
|
|
8984
|
-
const patterns = branchPolicySnapshotFor(cwd).namePolicy.allowed;
|
|
8985
|
-
const kinds = allowedKinds(patterns);
|
|
8986
|
-
const kindShaped = new Set(kinds.map((kind) => branchGlobPattern(`${kind}/*`).source));
|
|
8987
|
-
const anyName = patterns.some((pattern) => pattern.source === "^.*$");
|
|
8988
|
-
const others = patterns.filter((pattern) => !kindShaped.has(pattern.source)).map((pattern) => globOf(pattern));
|
|
8989
|
-
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.`;
|
|
8990
|
-
const resolved = vcsConfig("resolve", cwd);
|
|
8991
|
-
const integration = resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
|
|
8992
|
-
const flow = resolved.releaseTrack?.track ? null : gitFlowOf(cwd, integration);
|
|
8993
|
-
if (flow?.developExists)
|
|
8994
|
-
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}.`;
|
|
8995
|
-
const names = branchNames(cwd);
|
|
8996
|
-
const configured = String(resolved.baseBranch ?? integration ?? "") || null;
|
|
8997
|
-
const trunk = productionBranchOf(names);
|
|
8998
|
-
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.";
|
|
8999
|
-
return `${allowed} ${base}`;
|
|
9000
|
-
} catch {
|
|
9001
|
-
return null;
|
|
9002
|
-
}
|
|
9003
|
-
};
|
|
9004
|
-
var globOf = (pattern) => pattern.source.replace(/^\^|\$$/gu, "").replaceAll(".*", "*").replace(/\\(.)/gu, "$1");
|
|
9005
|
-
var policy = (root) => resolveBranchPolicyFor(root);
|
|
9006
|
-
var isProtected = (root, name) => policy(root).protected.has(name.toLowerCase());
|
|
9007
|
-
var isProtectedTarget = (root, name) => {
|
|
9008
|
-
try {
|
|
9009
|
-
return isProtected(root, name);
|
|
9010
|
-
} catch {
|
|
9011
|
-
return true;
|
|
9012
|
-
}
|
|
9013
|
-
};
|
|
8927
|
+
return out;
|
|
8928
|
+
}
|
|
9014
8929
|
|
|
9015
|
-
// packages/workit-core/src/core/
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
var
|
|
9019
|
-
"
|
|
9020
|
-
"
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
"
|
|
9025
|
-
"
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
"
|
|
9030
|
-
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
"
|
|
9034
|
-
"
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
"content",
|
|
9039
|
-
"body",
|
|
9040
|
-
"args",
|
|
9041
|
-
"argument",
|
|
9042
|
-
"result",
|
|
9043
|
-
"results",
|
|
9044
|
-
"output",
|
|
9045
|
-
"issue",
|
|
9046
|
-
"description",
|
|
9047
|
-
"summary",
|
|
9048
|
-
"payload",
|
|
9049
|
-
"script",
|
|
9050
|
-
"command",
|
|
9051
|
-
"text",
|
|
9052
|
-
"query"
|
|
8930
|
+
// packages/workit-core/src/core/policy/substance.ts
|
|
8931
|
+
var SLASH_BLOCK = [["/*", "*/"]];
|
|
8932
|
+
var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
8933
|
+
var SYNTAXES = Object.fromEntries([
|
|
8934
|
+
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
8935
|
+
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
8936
|
+
ext,
|
|
8937
|
+
{ ...C_LIKE, triple: true }
|
|
8938
|
+
]),
|
|
8939
|
+
["go", { ...C_LIKE, rawBacktick: true }],
|
|
8940
|
+
...["css", "scss", "less"].map((ext) => [
|
|
8941
|
+
ext,
|
|
8942
|
+
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
8943
|
+
]),
|
|
8944
|
+
...["py", "pyi"].map((ext) => [
|
|
8945
|
+
ext,
|
|
8946
|
+
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
8947
|
+
]),
|
|
8948
|
+
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
8949
|
+
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
8950
|
+
ext,
|
|
8951
|
+
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
8952
|
+
])
|
|
9053
8953
|
]);
|
|
9054
|
-
var
|
|
9055
|
-
var
|
|
9056
|
-
|
|
9057
|
-
const
|
|
9058
|
-
|
|
9059
|
-
return override;
|
|
9060
|
-
const home = os3.homedir();
|
|
9061
|
-
if (process.env.XDG_STATE_HOME)
|
|
9062
|
-
return path6.join(process.env.XDG_STATE_HOME, "workit");
|
|
9063
|
-
switch (os3.platform()) {
|
|
9064
|
-
case "darwin":
|
|
9065
|
-
return path6.join(home, "Library", "Application Support", "workit");
|
|
9066
|
-
case "win32":
|
|
9067
|
-
return path6.join(process.env.LOCALAPPDATA ?? path6.join(home, "AppData", "Local"), "workit");
|
|
9068
|
-
default:
|
|
9069
|
-
return path6.join(process.env.HOME ?? home, ".local", "state", "workit");
|
|
9070
|
-
}
|
|
8954
|
+
var 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;
|
|
8955
|
+
var extensionOf = (file) => {
|
|
8956
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
8957
|
+
const dot = name.lastIndexOf(".");
|
|
8958
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
9071
8959
|
};
|
|
9072
|
-
var
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
var
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
8960
|
+
var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
8961
|
+
var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
|
|
8962
|
+
var SPACE = /[ \t\r\f\v]/u;
|
|
8963
|
+
var WORD = /[\w$]/u;
|
|
8964
|
+
var NO_SPACE_AFTER = "([{,;";
|
|
8965
|
+
var NO_SPACE_BEFORE = ")]},;";
|
|
8966
|
+
var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
|
|
8967
|
+
var REGEX_KEYWORDS = new Set([
|
|
8968
|
+
"return",
|
|
8969
|
+
"typeof",
|
|
8970
|
+
"instanceof",
|
|
8971
|
+
"case",
|
|
8972
|
+
"do",
|
|
8973
|
+
"else",
|
|
8974
|
+
"in",
|
|
8975
|
+
"of",
|
|
8976
|
+
"new",
|
|
8977
|
+
"delete",
|
|
8978
|
+
"void",
|
|
8979
|
+
"throw",
|
|
8980
|
+
"yield",
|
|
8981
|
+
"await"
|
|
8982
|
+
]);
|
|
8983
|
+
function substanceOf(file, text) {
|
|
8984
|
+
const syntax = syntaxOf(file);
|
|
8985
|
+
if (!syntax)
|
|
8986
|
+
return plainSubstance(text);
|
|
8987
|
+
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
8988
|
+
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
8989
|
+
return plainSubstance(text);
|
|
8990
|
+
return lex(syntax, text);
|
|
9101
8991
|
}
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
var userTokenRoot = () => path7.join(resolveStateDir(), "issued-tokens");
|
|
9107
|
-
function migrateRepoIssued(cwd) {
|
|
9108
|
-
if (cwd === undefined)
|
|
9109
|
-
return;
|
|
9110
|
-
let old;
|
|
9111
|
-
try {
|
|
9112
|
-
const location = resolveStore(cwd);
|
|
9113
|
-
if (location instanceof Error)
|
|
9114
|
-
return;
|
|
9115
|
-
old = path7.join(location.dir, "hooks", "issued");
|
|
9116
|
-
if (!fs5.existsSync(old))
|
|
9117
|
-
return;
|
|
9118
|
-
} catch {
|
|
9119
|
-
return;
|
|
9120
|
-
}
|
|
9121
|
-
for (const lead of safeList(old)) {
|
|
9122
|
-
try {
|
|
9123
|
-
const from = path7.join(old, lead);
|
|
9124
|
-
if (!fs5.statSync(from).isDirectory())
|
|
9125
|
-
continue;
|
|
9126
|
-
const to = path7.join(userIssuedRoot(), lead);
|
|
9127
|
-
fs5.mkdirSync(to, { recursive: true, mode: 448 });
|
|
9128
|
-
for (const id of fs5.readdirSync(from)) {
|
|
9129
|
-
try {
|
|
9130
|
-
fs5.copyFileSync(path7.join(from, id), path7.join(to, id), fs5.constants.COPYFILE_EXCL);
|
|
9131
|
-
} catch (error) {
|
|
9132
|
-
if (error.code !== "EEXIST")
|
|
9133
|
-
throw error;
|
|
9134
|
-
}
|
|
9135
|
-
fs5.rmSync(path7.join(from, id), { force: true });
|
|
9136
|
-
}
|
|
9137
|
-
removeIfEmpty(from);
|
|
9138
|
-
} catch {}
|
|
9139
|
-
}
|
|
9140
|
-
removeIfEmpty(old);
|
|
8992
|
+
function plainSubstance(text) {
|
|
8993
|
+
return text.split(`
|
|
8994
|
+
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
8995
|
+
`);
|
|
9141
8996
|
}
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
8997
|
+
function lex(syntax, text) {
|
|
8998
|
+
const collapse = !syntax.indented;
|
|
8999
|
+
let out = "";
|
|
9000
|
+
let lineStart = 0;
|
|
9001
|
+
let kept = 0;
|
|
9002
|
+
let pendingSpace = false;
|
|
9003
|
+
const frames = [{ kind: "code", braces: 0 }];
|
|
9004
|
+
let index = 0;
|
|
9005
|
+
if (text.startsWith("#!")) {
|
|
9006
|
+
const end = text.indexOf(`
|
|
9007
|
+
`);
|
|
9008
|
+
out = end < 0 ? text : text.slice(0, end);
|
|
9009
|
+
kept = out.length;
|
|
9010
|
+
index = end < 0 ? text.length : end;
|
|
9147
9011
|
}
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
}
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9012
|
+
const emitCode = (chunk) => {
|
|
9013
|
+
if (collapse && pendingSpace && out.length > lineStart) {
|
|
9014
|
+
const before = out[out.length - 1];
|
|
9015
|
+
if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
|
|
9016
|
+
out += " ";
|
|
9017
|
+
}
|
|
9018
|
+
pendingSpace = false;
|
|
9019
|
+
out += chunk;
|
|
9020
|
+
};
|
|
9021
|
+
const emitVerbatim = (chunk) => {
|
|
9022
|
+
emitCode(chunk);
|
|
9023
|
+
kept = out.length;
|
|
9024
|
+
};
|
|
9025
|
+
const newline = () => {
|
|
9026
|
+
if (!collapse) {
|
|
9027
|
+
let end = out.length;
|
|
9028
|
+
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
9029
|
+
end -= 1;
|
|
9030
|
+
out = out.slice(0, end);
|
|
9031
|
+
}
|
|
9032
|
+
pendingSpace = false;
|
|
9033
|
+
if (out.length > lineStart) {
|
|
9034
|
+
out += `
|
|
9035
|
+
`;
|
|
9036
|
+
lineStart = out.length;
|
|
9037
|
+
}
|
|
9038
|
+
};
|
|
9039
|
+
const regexAllowed = () => {
|
|
9040
|
+
let end = out.length;
|
|
9041
|
+
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
9042
|
+
end -= 1;
|
|
9043
|
+
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
9044
|
+
return true;
|
|
9045
|
+
let start = end;
|
|
9046
|
+
while (start > 0 && WORD.test(out[start - 1]))
|
|
9047
|
+
start -= 1;
|
|
9048
|
+
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
9049
|
+
};
|
|
9050
|
+
const scanString = (quote, escapes, multiline) => {
|
|
9051
|
+
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
9052
|
+
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
9053
|
+
let end = index + quote.length;
|
|
9054
|
+
while (end < text.length) {
|
|
9055
|
+
const char = text[end];
|
|
9056
|
+
if (escapes && char === "\\") {
|
|
9057
|
+
end += 2;
|
|
9058
|
+
continue;
|
|
9059
|
+
}
|
|
9060
|
+
if (text.startsWith(quote, end)) {
|
|
9061
|
+
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
9062
|
+
return false;
|
|
9063
|
+
emitVerbatim(text.slice(index, end + quote.length));
|
|
9064
|
+
index = end + quote.length;
|
|
9065
|
+
return true;
|
|
9066
|
+
}
|
|
9067
|
+
if (char === `
|
|
9068
|
+
` && !multiline)
|
|
9069
|
+
return false;
|
|
9070
|
+
end += 1;
|
|
9071
|
+
}
|
|
9072
|
+
return false;
|
|
9073
|
+
};
|
|
9074
|
+
while (index < text.length) {
|
|
9075
|
+
const frame = frames[frames.length - 1];
|
|
9076
|
+
const char = text[index];
|
|
9077
|
+
if (frame.kind === "template") {
|
|
9078
|
+
if (char === "\\") {
|
|
9079
|
+
emitVerbatim(text.slice(index, index + 2));
|
|
9080
|
+
index += 2;
|
|
9081
|
+
} else if (char === "`") {
|
|
9082
|
+
emitVerbatim(char);
|
|
9083
|
+
frames.pop();
|
|
9084
|
+
index += 1;
|
|
9085
|
+
} else if (text.startsWith("${", index)) {
|
|
9086
|
+
emitVerbatim("${");
|
|
9087
|
+
frames.push({ kind: "code", braces: 0 });
|
|
9088
|
+
index += 2;
|
|
9089
|
+
} else {
|
|
9090
|
+
out += char;
|
|
9091
|
+
kept = out.length;
|
|
9092
|
+
index += 1;
|
|
9093
|
+
}
|
|
9094
|
+
continue;
|
|
9095
|
+
}
|
|
9096
|
+
if (char === `
|
|
9097
|
+
`) {
|
|
9098
|
+
newline();
|
|
9099
|
+
index += 1;
|
|
9100
|
+
continue;
|
|
9101
|
+
}
|
|
9102
|
+
if (SPACE.test(char)) {
|
|
9103
|
+
if (collapse)
|
|
9104
|
+
pendingSpace = true;
|
|
9105
|
+
else
|
|
9106
|
+
out += char;
|
|
9107
|
+
index += 1;
|
|
9108
|
+
continue;
|
|
9109
|
+
}
|
|
9110
|
+
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
9111
|
+
if (lineOpener) {
|
|
9112
|
+
const end = text.indexOf(`
|
|
9113
|
+
`, index);
|
|
9114
|
+
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
9115
|
+
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
9116
|
+
emitVerbatim(comment);
|
|
9117
|
+
else
|
|
9118
|
+
pendingSpace = true;
|
|
9119
|
+
index = end < 0 ? text.length : end;
|
|
9120
|
+
continue;
|
|
9121
|
+
}
|
|
9122
|
+
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
9123
|
+
if (block) {
|
|
9124
|
+
const end = text.indexOf(block[1], index + block[0].length);
|
|
9125
|
+
if (end < 0)
|
|
9126
|
+
return null;
|
|
9127
|
+
const comment = text.slice(index, end + block[1].length);
|
|
9128
|
+
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
9129
|
+
emitVerbatim(comment);
|
|
9130
|
+
else
|
|
9131
|
+
pendingSpace = true;
|
|
9132
|
+
if (comment.includes(`
|
|
9133
|
+
`))
|
|
9134
|
+
newline();
|
|
9135
|
+
index = end + block[1].length;
|
|
9136
|
+
continue;
|
|
9137
|
+
}
|
|
9138
|
+
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
9139
|
+
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
9140
|
+
return null;
|
|
9141
|
+
continue;
|
|
9142
|
+
}
|
|
9143
|
+
if (syntax.quotes.includes(char)) {
|
|
9144
|
+
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
9145
|
+
return null;
|
|
9146
|
+
continue;
|
|
9147
|
+
}
|
|
9148
|
+
if (syntax.literal?.includes(char)) {
|
|
9149
|
+
if (!scanString(char, false, false))
|
|
9150
|
+
return null;
|
|
9151
|
+
continue;
|
|
9152
|
+
}
|
|
9153
|
+
if (syntax.rawBacktick && char === "`") {
|
|
9154
|
+
if (!scanString("`", false, true))
|
|
9155
|
+
return null;
|
|
9156
|
+
continue;
|
|
9157
|
+
}
|
|
9158
|
+
if (syntax.js && char === "`") {
|
|
9159
|
+
emitVerbatim(char);
|
|
9160
|
+
frames.push({ kind: "template" });
|
|
9161
|
+
index += 1;
|
|
9162
|
+
continue;
|
|
9163
|
+
}
|
|
9164
|
+
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
9165
|
+
return null;
|
|
9166
|
+
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
9167
|
+
const lineEnd = text.indexOf(`
|
|
9168
|
+
`, index);
|
|
9169
|
+
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
9170
|
+
return null;
|
|
9171
|
+
}
|
|
9172
|
+
if (syntax.js && char === "/" && regexAllowed()) {
|
|
9173
|
+
let end = index + 1;
|
|
9174
|
+
let inClass = false;
|
|
9175
|
+
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
9176
|
+
if (text[end] === `
|
|
9177
|
+
`)
|
|
9178
|
+
return null;
|
|
9179
|
+
if (text[end] === "\\")
|
|
9180
|
+
end += 1;
|
|
9181
|
+
else if (text[end] === "[")
|
|
9182
|
+
inClass = true;
|
|
9183
|
+
else if (text[end] === "]")
|
|
9184
|
+
inClass = false;
|
|
9185
|
+
end += 1;
|
|
9186
|
+
}
|
|
9187
|
+
if (end >= text.length)
|
|
9188
|
+
return null;
|
|
9189
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
9190
|
+
index = end + 1;
|
|
9191
|
+
continue;
|
|
9192
|
+
}
|
|
9193
|
+
if (frames.length > 1 && frame.kind === "code") {
|
|
9194
|
+
if (char === "{")
|
|
9195
|
+
frame.braces += 1;
|
|
9196
|
+
else if (char === "}") {
|
|
9197
|
+
if (frame.braces === 0) {
|
|
9198
|
+
emitVerbatim(char);
|
|
9199
|
+
frames.pop();
|
|
9200
|
+
index += 1;
|
|
9201
|
+
continue;
|
|
9202
|
+
}
|
|
9203
|
+
frame.braces -= 1;
|
|
9204
|
+
}
|
|
9205
|
+
}
|
|
9206
|
+
emitCode(char);
|
|
9207
|
+
index += 1;
|
|
9208
|
+
}
|
|
9209
|
+
if (frames.length !== 1)
|
|
9210
|
+
return null;
|
|
9211
|
+
newline();
|
|
9212
|
+
return out;
|
|
9213
|
+
}
|
|
9214
|
+
function unbalanced(body) {
|
|
9215
|
+
let depth = 0;
|
|
9216
|
+
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
9217
|
+
if (part === "{")
|
|
9218
|
+
depth += 1;
|
|
9219
|
+
else if (part === "}")
|
|
9220
|
+
depth = Math.max(0, depth - 1);
|
|
9221
|
+
}
|
|
9222
|
+
return depth > 0;
|
|
9223
|
+
}
|
|
9224
|
+
function hasOpaqueLineBreak(text) {
|
|
9225
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
9226
|
+
}
|
|
9227
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
9228
|
+
function textOf(bytes) {
|
|
9229
|
+
if (bytes.includes(0))
|
|
9230
|
+
return null;
|
|
9157
9231
|
try {
|
|
9158
|
-
|
|
9159
|
-
const own = hash(issued);
|
|
9160
|
-
const tokens = path7.join(userTokenRoot(), hash(lead));
|
|
9161
|
-
fs5.mkdirSync(tokens, { recursive: true, mode: 448 });
|
|
9162
|
-
fs5.writeFileSync(path7.join(tokens, own), `${tokenHash(token)}
|
|
9163
|
-
`, { mode: 384, flag: "wx" });
|
|
9164
|
-
const dir = path7.join(userIssuedRoot(), hash(lead));
|
|
9165
|
-
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
9166
|
-
fs5.writeFileSync(path7.join(dir, own), `${ISSUED_SESSIONS_MARKER}
|
|
9167
|
-
`, { mode: 384 });
|
|
9168
|
-
pruneIssued(dir, own, tokens);
|
|
9169
|
-
return token;
|
|
9232
|
+
return decoder.decode(bytes);
|
|
9170
9233
|
} catch {
|
|
9171
9234
|
return null;
|
|
9172
9235
|
}
|
|
9173
9236
|
}
|
|
9174
|
-
function
|
|
9175
|
-
const
|
|
9176
|
-
|
|
9237
|
+
function cosmeticChange(file, before, after) {
|
|
9238
|
+
const left = textOf(before);
|
|
9239
|
+
const right = textOf(after);
|
|
9240
|
+
if (left === null || right === null)
|
|
9241
|
+
return false;
|
|
9242
|
+
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
9243
|
+
return false;
|
|
9244
|
+
const a = substanceOf(file, left);
|
|
9245
|
+
return a !== null && a === substanceOf(file, right);
|
|
9177
9246
|
}
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
9247
|
+
|
|
9248
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
9249
|
+
var LANE_THRESHOLDS = {
|
|
9250
|
+
selfMaxLines: 100,
|
|
9251
|
+
selfMaxFiles: 5,
|
|
9252
|
+
fullMinLines: 500
|
|
9253
|
+
};
|
|
9254
|
+
var RISK_TOKENS = new Set([
|
|
9255
|
+
"auth",
|
|
9256
|
+
"authn",
|
|
9257
|
+
"authz",
|
|
9258
|
+
"oauth",
|
|
9259
|
+
"security",
|
|
9260
|
+
"permission",
|
|
9261
|
+
"permissions",
|
|
9262
|
+
"secret",
|
|
9263
|
+
"secrets",
|
|
9264
|
+
"crypto",
|
|
9265
|
+
"gate",
|
|
9266
|
+
"gates",
|
|
9267
|
+
"policy",
|
|
9268
|
+
"policies",
|
|
9269
|
+
"hook",
|
|
9270
|
+
"hooks",
|
|
9271
|
+
"husky",
|
|
9272
|
+
"lefthook",
|
|
9273
|
+
"release",
|
|
9274
|
+
"releases",
|
|
9275
|
+
"migration",
|
|
9276
|
+
"migrations",
|
|
9277
|
+
"migrate",
|
|
9278
|
+
"workflows"
|
|
9279
|
+
]);
|
|
9280
|
+
var RISK_FILES = new Set([
|
|
9281
|
+
".gitlab-ci.yml",
|
|
9282
|
+
"Jenkinsfile",
|
|
9283
|
+
"azure-pipelines.yml",
|
|
9284
|
+
".pre-commit-config.yaml",
|
|
9285
|
+
".workit/risk-paths"
|
|
9286
|
+
]);
|
|
9287
|
+
var RISK_NAMES = new Set([".gitattributes"]);
|
|
9288
|
+
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
9289
|
+
var LOCKFILES = new Set([
|
|
9290
|
+
"bun.lock",
|
|
9291
|
+
"bun.lockb",
|
|
9292
|
+
"package-lock.json",
|
|
9293
|
+
"npm-shrinkwrap.json",
|
|
9294
|
+
"yarn.lock",
|
|
9295
|
+
"pnpm-lock.yaml",
|
|
9296
|
+
"Cargo.lock",
|
|
9297
|
+
"go.sum",
|
|
9298
|
+
"poetry.lock",
|
|
9299
|
+
"uv.lock",
|
|
9300
|
+
"Pipfile.lock",
|
|
9301
|
+
"Gemfile.lock",
|
|
9302
|
+
"composer.lock"
|
|
9303
|
+
]);
|
|
9304
|
+
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc)$/iu;
|
|
9305
|
+
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
9306
|
+
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
9307
|
+
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__)\/|\.(?:test|spec)\.[^/]+$|_test\.[^/]+$/iu;
|
|
9308
|
+
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
9309
|
+
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
9310
|
+
function kindOf(file) {
|
|
9311
|
+
if (file.binary)
|
|
9312
|
+
return "behavior";
|
|
9313
|
+
if (file.generated)
|
|
9314
|
+
return "generated";
|
|
9315
|
+
if (LOCKFILES.has(basename(file.path)))
|
|
9316
|
+
return "lockfile";
|
|
9317
|
+
if (isDoc(file.path))
|
|
9318
|
+
return "doc";
|
|
9319
|
+
if (file.cosmetic)
|
|
9320
|
+
return "cosmetic";
|
|
9321
|
+
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
9188
9322
|
}
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9323
|
+
var globCache = new Map;
|
|
9324
|
+
function globMatch(pattern, file) {
|
|
9325
|
+
let regex = globCache.get(pattern);
|
|
9326
|
+
if (!regex) {
|
|
9327
|
+
let source = "";
|
|
9328
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
9329
|
+
const char = pattern[index];
|
|
9330
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
9331
|
+
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
9332
|
+
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
9333
|
+
} else if (char === "*")
|
|
9334
|
+
source += "[^/]*";
|
|
9335
|
+
else if (char === "?")
|
|
9336
|
+
source += "[^/]";
|
|
9337
|
+
else
|
|
9338
|
+
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
9198
9339
|
}
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
var issuedRecord = (cwd, claimed) => {
|
|
9202
|
-
const cut = claimed.lastIndexOf(":");
|
|
9203
|
-
if (cut <= 0)
|
|
9204
|
-
return null;
|
|
9205
|
-
migrateRepoIssued(cwd);
|
|
9206
|
-
try {
|
|
9207
|
-
return fs5.readFileSync(path7.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
|
|
9208
|
-
} catch {
|
|
9209
|
-
return null;
|
|
9340
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
9341
|
+
globCache.set(pattern, regex);
|
|
9210
9342
|
}
|
|
9211
|
-
};
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9343
|
+
return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
|
|
9344
|
+
}
|
|
9345
|
+
function isRiskPath(file, extra = []) {
|
|
9346
|
+
if (extra.some((pattern) => globMatch(pattern, file)))
|
|
9347
|
+
return true;
|
|
9348
|
+
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
9349
|
+
return true;
|
|
9350
|
+
if (TEST_PATH.test(file) || isDoc(file))
|
|
9351
|
+
return false;
|
|
9352
|
+
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)));
|
|
9353
|
+
}
|
|
9354
|
+
var parseRiskPaths = (text) => text.split(`
|
|
9355
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
|
|
9356
|
+
function computeLane(input) {
|
|
9357
|
+
const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
|
|
9358
|
+
const risky = new Set(riskFiles);
|
|
9359
|
+
const kinds = input.files.map((file) => ({
|
|
9360
|
+
file,
|
|
9361
|
+
kind: risky.has(file.path) ? "behavior" : kindOf(file)
|
|
9362
|
+
}));
|
|
9363
|
+
const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
|
|
9364
|
+
const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
|
|
9365
|
+
const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
|
|
9366
|
+
const result = (lane, reasons) => ({
|
|
9367
|
+
lane,
|
|
9368
|
+
reasons,
|
|
9369
|
+
lines,
|
|
9370
|
+
files: behavior.length,
|
|
9371
|
+
riskFiles
|
|
9372
|
+
});
|
|
9373
|
+
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
9374
|
+
if (input.riskTier === "high")
|
|
9375
|
+
return result("full", ["the task judgment records high risk"]);
|
|
9376
|
+
if (riskFiles.length)
|
|
9377
|
+
return result("full", [
|
|
9378
|
+
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
9379
|
+
]);
|
|
9380
|
+
if (!behavior.length)
|
|
9381
|
+
return result("none", [
|
|
9382
|
+
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
9383
|
+
]);
|
|
9384
|
+
if (lines >= fullMinLines)
|
|
9385
|
+
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
9386
|
+
if (lines > selfMaxLines)
|
|
9387
|
+
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
9388
|
+
if (behavior.length > selfMaxFiles)
|
|
9389
|
+
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
9390
|
+
if (opaque.length)
|
|
9391
|
+
return result("focused", [
|
|
9392
|
+
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
9393
|
+
]);
|
|
9394
|
+
return result("self", [
|
|
9395
|
+
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
9396
|
+
]);
|
|
9397
|
+
}
|
|
9398
|
+
function untrackedStat(cwd, file) {
|
|
9218
9399
|
try {
|
|
9219
|
-
const
|
|
9220
|
-
|
|
9400
|
+
const bytes = readFileSync3(path6.join(cwd, file));
|
|
9401
|
+
if (bytes.includes(0))
|
|
9402
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
9403
|
+
const text = bytes.toString("utf8");
|
|
9404
|
+
if (hasOpaqueLineBreak(text))
|
|
9405
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
9406
|
+
const lines = text.split(`
|
|
9407
|
+
`).length - (text.endsWith(`
|
|
9408
|
+
`) ? 1 : 0);
|
|
9409
|
+
return { path: file, added: lines, deleted: 0 };
|
|
9221
9410
|
} catch {
|
|
9222
|
-
return {
|
|
9411
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
9223
9412
|
}
|
|
9224
|
-
};
|
|
9225
|
-
function issuedBinding(cwd, claimed) {
|
|
9226
|
-
const record = issuedRecord(cwd, claimed);
|
|
9227
|
-
const { stored, leadBound } = issuedToken(claimed);
|
|
9228
|
-
if (stored)
|
|
9229
|
-
return "token";
|
|
9230
|
-
if (record === null)
|
|
9231
|
-
return null;
|
|
9232
|
-
return leadBound ? "token" : "legacy";
|
|
9233
9413
|
}
|
|
9234
|
-
var
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
import path8 from "node:path";
|
|
9240
|
-
var gitStoreDir = (cwd) => {
|
|
9241
|
-
try {
|
|
9242
|
-
for (let dir = path8.resolve(cwd);; dir = path8.dirname(dir)) {
|
|
9243
|
-
const dotGit = path8.join(dir, ".git");
|
|
9244
|
-
if (fs6.existsSync(dotGit)) {
|
|
9245
|
-
let gitDir = dotGit;
|
|
9246
|
-
if (fs6.statSync(dotGit).isFile()) {
|
|
9247
|
-
const target = /^gitdir:\s*(.+)$/mu.exec(fs6.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
9248
|
-
if (!target)
|
|
9249
|
-
return null;
|
|
9250
|
-
gitDir = path8.resolve(dir, target);
|
|
9251
|
-
const common = path8.join(gitDir, "commondir");
|
|
9252
|
-
if (fs6.existsSync(common))
|
|
9253
|
-
gitDir = path8.resolve(gitDir, fs6.readFileSync(common, "utf8").trim());
|
|
9254
|
-
}
|
|
9255
|
-
return path8.join(gitDir, "workit");
|
|
9256
|
-
}
|
|
9257
|
-
if (path8.dirname(dir) === dir)
|
|
9258
|
-
return null;
|
|
9259
|
-
}
|
|
9260
|
-
} catch {
|
|
9414
|
+
var REGULAR = new Set(["100644", "100755"]);
|
|
9415
|
+
var ABSENT = /^0+$/u;
|
|
9416
|
+
function cosmeticFiles(cwd, fork, head) {
|
|
9417
|
+
const raw = diffRaw(cwd, fork, head);
|
|
9418
|
+
if (!raw)
|
|
9261
9419
|
return null;
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
var standingSeenFile = (store, session, agentId) => path8.join(store, "standing", "seen", `${createHash8("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`);
|
|
9266
|
-
var readStandingRev = (store) => {
|
|
9267
|
-
try {
|
|
9268
|
-
return fs6.readFileSync(standingRevFile(store), "utf8").trim() || null;
|
|
9269
|
-
} catch {
|
|
9420
|
+
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
9421
|
+
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
9422
|
+
if (!blobs)
|
|
9270
9423
|
return null;
|
|
9424
|
+
const out = new Set;
|
|
9425
|
+
const opaque = new Set;
|
|
9426
|
+
for (const entry of candidates) {
|
|
9427
|
+
const before = blobs.get(entry.oldBlob);
|
|
9428
|
+
let after = blobs.get(entry.newBlob);
|
|
9429
|
+
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
9430
|
+
try {
|
|
9431
|
+
after = readFileSync3(path6.join(cwd, entry.path));
|
|
9432
|
+
} catch {
|
|
9433
|
+
after = undefined;
|
|
9434
|
+
}
|
|
9435
|
+
if (before && after && cosmeticChange(entry.path, before, after))
|
|
9436
|
+
out.add(entry.path);
|
|
9437
|
+
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
9438
|
+
opaque.add(entry.path);
|
|
9271
9439
|
}
|
|
9272
|
-
};
|
|
9273
|
-
var readStandingSeen = (file) => {
|
|
9274
|
-
try {
|
|
9275
|
-
const value = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
9276
|
-
return {
|
|
9277
|
-
rev: typeof value.rev === "string" ? value.rev : null,
|
|
9278
|
-
ids: Array.isArray(value.ids) ? value.ids.filter((id) => typeof id === "string") : []
|
|
9279
|
-
};
|
|
9280
|
-
} catch {
|
|
9281
|
-
return { rev: null, ids: [] };
|
|
9282
|
-
}
|
|
9283
|
-
};
|
|
9284
|
-
var writeStandingSeen = (file, seen) => {
|
|
9285
|
-
try {
|
|
9286
|
-
fs6.mkdirSync(path8.dirname(file), { recursive: true });
|
|
9287
|
-
fs6.writeFileSync(file, JSON.stringify(seen));
|
|
9288
|
-
} catch {}
|
|
9289
|
-
};
|
|
9290
|
-
|
|
9291
|
-
// packages/workit-core/src/ledger.ts
|
|
9292
|
-
var LEDGER_VERSION = 1;
|
|
9293
|
-
var MAX_LINE_BYTES = 4096;
|
|
9294
|
-
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
9295
|
-
var FAILING = new Set(["failed", "blocked"]);
|
|
9296
|
-
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
9297
|
-
var PR_ROW_TYPES = new Set([
|
|
9298
|
-
"pr.created",
|
|
9299
|
-
"pr.status",
|
|
9300
|
-
"pr.merged",
|
|
9301
|
-
"pr.linked"
|
|
9302
|
-
]);
|
|
9303
|
-
var err = (code, error, unblock) => ({
|
|
9304
|
-
ok: false,
|
|
9305
|
-
code,
|
|
9306
|
-
error,
|
|
9307
|
-
...unblock ? { unblock } : {}
|
|
9308
|
-
});
|
|
9309
|
-
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
9310
|
-
const run = spawnSync8("git", args, {
|
|
9311
|
-
cwd,
|
|
9312
|
-
encoding: "utf8",
|
|
9313
|
-
timeout: timeoutMs,
|
|
9314
|
-
killSignal: "SIGKILL",
|
|
9315
|
-
windowsHide: true,
|
|
9316
|
-
maxBuffer: 256 * 1024 * 1024,
|
|
9317
|
-
env: {
|
|
9318
|
-
...process.env,
|
|
9319
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
9320
|
-
GIT_OPTIONAL_LOCKS: "0",
|
|
9321
|
-
LC_ALL: "C",
|
|
9322
|
-
LANGUAGE: "C"
|
|
9323
|
-
}
|
|
9324
|
-
});
|
|
9325
|
-
return {
|
|
9326
|
-
ok: run.status === 0,
|
|
9327
|
-
stdout: run.stdout ?? "",
|
|
9328
|
-
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
9329
|
-
};
|
|
9330
|
-
};
|
|
9331
|
-
var git3 = (cwd, args) => {
|
|
9332
|
-
const run = gitRun2(cwd, args);
|
|
9333
|
-
const value = run.ok ? run.stdout.trim() : "";
|
|
9334
|
-
return value ? value : null;
|
|
9335
|
-
};
|
|
9336
|
-
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
9337
|
-
function resolveCommit(cwd, ref) {
|
|
9338
|
-
if (!safeRef(ref))
|
|
9339
|
-
return null;
|
|
9340
|
-
return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
9341
|
-
}
|
|
9342
|
-
function branchHead(cwd, branch) {
|
|
9343
|
-
if (!safeRef(branch))
|
|
9344
|
-
return null;
|
|
9345
|
-
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
9440
|
+
return { cosmetic: out, opaque };
|
|
9346
9441
|
}
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
};
|
|
9358
|
-
function defaultBase(cwd) {
|
|
9359
|
-
const trunk = trackTrunk(cwd);
|
|
9360
|
-
if (trunk) {
|
|
9361
|
-
for (const candidate of [`origin/${trunk}`, trunk])
|
|
9362
|
-
if (resolveCommit(cwd, candidate))
|
|
9363
|
-
return candidate;
|
|
9442
|
+
function generatedAt(cwd, fork, paths) {
|
|
9443
|
+
const files = treeFilesNamed(cwd, fork, ".gitattributes");
|
|
9444
|
+
const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
|
|
9445
|
+
if (!files || !blobs)
|
|
9446
|
+
return new Set;
|
|
9447
|
+
const texts = new Map;
|
|
9448
|
+
for (const [file, id] of files) {
|
|
9449
|
+
const bytes = blobs.get(id);
|
|
9450
|
+
if (bytes)
|
|
9451
|
+
texts.set(file, bytes.toString("utf8"));
|
|
9364
9452
|
}
|
|
9365
|
-
|
|
9366
|
-
if (originHead && resolveCommit(cwd, originHead))
|
|
9367
|
-
return originHead;
|
|
9368
|
-
for (const candidate of [
|
|
9369
|
-
"origin/main",
|
|
9370
|
-
"origin/master",
|
|
9371
|
-
"main",
|
|
9372
|
-
"master",
|
|
9373
|
-
"origin/develop",
|
|
9374
|
-
"develop"
|
|
9375
|
-
])
|
|
9376
|
-
if (resolveCommit(cwd, candidate))
|
|
9377
|
-
return candidate;
|
|
9378
|
-
return null;
|
|
9453
|
+
return generatedByAttributes(texts, paths);
|
|
9379
9454
|
}
|
|
9380
|
-
|
|
9381
|
-
|
|
9455
|
+
var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
|
|
9456
|
+
function laneFiles(cwd, fork, head, attributesAt = fork) {
|
|
9457
|
+
const stats = diffNumstat(cwd, fork, head);
|
|
9458
|
+
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
9459
|
+
if (!stats || !cosmetic)
|
|
9382
9460
|
return null;
|
|
9383
|
-
const
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9461
|
+
const files = [
|
|
9462
|
+
...stats.map((entry) => ({
|
|
9463
|
+
...entry,
|
|
9464
|
+
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
9465
|
+
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
9466
|
+
})),
|
|
9467
|
+
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
9468
|
+
];
|
|
9469
|
+
const generated = generatedAt(cwd, attributesAt, files.map((entry) => entry.path));
|
|
9470
|
+
return files.map((entry) => ({
|
|
9471
|
+
...entry,
|
|
9472
|
+
generated: generated.has(entry.path)
|
|
9473
|
+
}));
|
|
9474
|
+
}
|
|
9475
|
+
function driftFiles(cwd, range) {
|
|
9476
|
+
if (range.fromFork === range.toFork)
|
|
9477
|
+
return laneFiles(cwd, range.from, range.to, range.toFork);
|
|
9478
|
+
const drifted = ownChangeDrift(cwd, range);
|
|
9479
|
+
const stats = drifted ? diffNumstat(cwd, range.from, range.to) : null;
|
|
9480
|
+
if (!drifted || !stats)
|
|
9400
9481
|
return null;
|
|
9401
|
-
const
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9482
|
+
const byPath = new Map(stats.map((entry) => [entry.path, entry]));
|
|
9483
|
+
const generated = generatedAt(cwd, range.toFork, drifted);
|
|
9484
|
+
return drifted.map((file) => ({
|
|
9485
|
+
path: file,
|
|
9486
|
+
added: byPath.get(file)?.added ?? 0,
|
|
9487
|
+
deleted: byPath.get(file)?.deleted ?? 0,
|
|
9488
|
+
...byPath.get(file)?.binary ? { binary: true } : {},
|
|
9489
|
+
cosmetic: false,
|
|
9490
|
+
generated: generated.has(file)
|
|
9491
|
+
}));
|
|
9411
9492
|
}
|
|
9412
|
-
function
|
|
9413
|
-
const
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9493
|
+
function branchLane(cwd, options) {
|
|
9494
|
+
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
9495
|
+
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
9496
|
+
const head = options.workingTree ? null : options.head;
|
|
9497
|
+
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
9498
|
+
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
9499
|
+
const riskTier = options.judgment?.riskTier ?? null;
|
|
9500
|
+
if (!fork || !files) {
|
|
9501
|
+
const high = riskTier === "high";
|
|
9502
|
+
return {
|
|
9503
|
+
lane: high ? "full" : "focused",
|
|
9504
|
+
reasons: [
|
|
9505
|
+
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
9506
|
+
...high ? ["the task judgment records high risk"] : []
|
|
9507
|
+
],
|
|
9508
|
+
lines: 0,
|
|
9509
|
+
files: 0,
|
|
9510
|
+
riskFiles: [],
|
|
9511
|
+
base,
|
|
9512
|
+
head: options.head
|
|
9513
|
+
};
|
|
9514
|
+
}
|
|
9515
|
+
const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
|
|
9516
|
+
return {
|
|
9517
|
+
...computeLane({
|
|
9518
|
+
files,
|
|
9519
|
+
riskTier,
|
|
9520
|
+
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
9521
|
+
}),
|
|
9522
|
+
base,
|
|
9523
|
+
head: options.head
|
|
9524
|
+
};
|
|
9417
9525
|
}
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9526
|
+
var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
9527
|
+
|
|
9528
|
+
// packages/workit-core/src/core/policy/carry.ts
|
|
9529
|
+
var TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
|
|
9530
|
+
function verdictDrift(cwd, input) {
|
|
9531
|
+
const since = input.from;
|
|
9532
|
+
const stale = (reason, files = null, focused = false) => ({
|
|
9533
|
+
carries: false,
|
|
9534
|
+
since,
|
|
9535
|
+
files,
|
|
9536
|
+
focused,
|
|
9537
|
+
reason
|
|
9538
|
+
});
|
|
9539
|
+
const fromFork = mergeBase(cwd, input.fromBase, input.from);
|
|
9540
|
+
const toFork = mergeBase(cwd, input.toBase, input.to);
|
|
9541
|
+
const files = fromFork && toFork ? driftFiles(cwd, { from: input.from, to: input.to, fromFork, toFork }) : null;
|
|
9542
|
+
if (!fromFork || !toFork || !files)
|
|
9543
|
+
return stale(`the change since ${since.slice(0, 12)} cannot be read`);
|
|
9544
|
+
const riskPaths = [fromFork, toFork].flatMap((fork) => parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? ""));
|
|
9545
|
+
const lane = computeLane({ files, riskPaths });
|
|
9546
|
+
const paths = files.map((file) => file.path);
|
|
9547
|
+
if (lane.lane === "none")
|
|
9548
|
+
return { carries: true, rule: "lane-none", since, files: paths };
|
|
9549
|
+
if (input.kind === "unit" && lane.lane === "self" && files.every((file) => TEST_ONLY_KINDS.has(kindOf(file))))
|
|
9550
|
+
return { carries: true, rule: "tests-only", since, files: paths };
|
|
9551
|
+
return stale(lane.reasons[0] ?? `lane ${lane.lane}`, paths, lane.lane === "self");
|
|
9421
9552
|
}
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
};
|
|
9445
|
-
var LOCK_WAIT_MS = 5000;
|
|
9446
|
-
var LOCK_STALE_MS = 30000;
|
|
9447
|
-
var LOCK_OWNER = "owner";
|
|
9448
|
-
var lockToken = (lock) => {
|
|
9449
|
-
try {
|
|
9450
|
-
return fs7.readFileSync(path9.join(lock, LOCK_OWNER), "utf8");
|
|
9451
|
-
} catch {
|
|
9452
|
-
return null;
|
|
9453
|
-
}
|
|
9454
|
-
};
|
|
9455
|
-
var lockAgeMs = (lock) => {
|
|
9456
|
-
for (const target of [path9.join(lock, LOCK_OWNER), lock]) {
|
|
9457
|
-
try {
|
|
9458
|
-
return Date.now() - fs7.statSync(target).mtimeMs;
|
|
9459
|
-
} catch {}
|
|
9553
|
+
|
|
9554
|
+
// packages/workit-core/src/host-session.ts
|
|
9555
|
+
import { createHash as createHash7, randomBytes, timingSafeEqual } from "node:crypto";
|
|
9556
|
+
import fs5 from "node:fs";
|
|
9557
|
+
import path8 from "node:path";
|
|
9558
|
+
|
|
9559
|
+
// packages/workit-core/src/core/branch.ts
|
|
9560
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
9561
|
+
var withReleaseTracks = (workspaceRoot, policy) => {
|
|
9562
|
+
const read = workspaceReleaseTracks(workspaceRoot);
|
|
9563
|
+
if (read.error)
|
|
9564
|
+
throw new ReleaseTrackError(read.error);
|
|
9565
|
+
if (read.tracks.length === 0 && read.protectedBranches.length === 0)
|
|
9566
|
+
return policy;
|
|
9567
|
+
const known = new Set(policy.allowed.map((pattern) => pattern.source));
|
|
9568
|
+
const allowed = [...policy.allowed];
|
|
9569
|
+
for (const glob of read.tracks.flatMap((track) => Object.values(track.naming).map(namingGlob))) {
|
|
9570
|
+
const pattern = branchGlobPattern(glob);
|
|
9571
|
+
if (!known.has(pattern.source)) {
|
|
9572
|
+
known.add(pattern.source);
|
|
9573
|
+
allowed.push(pattern);
|
|
9574
|
+
}
|
|
9460
9575
|
}
|
|
9461
|
-
return
|
|
9576
|
+
return {
|
|
9577
|
+
...policy,
|
|
9578
|
+
allowed,
|
|
9579
|
+
protected: new Set([
|
|
9580
|
+
...policy.protected,
|
|
9581
|
+
...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) => name.toLowerCase())
|
|
9582
|
+
])
|
|
9583
|
+
};
|
|
9462
9584
|
};
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
}
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
}
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
|
|
9476
|
-
|
|
9477
|
-
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9585
|
+
function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
|
|
9586
|
+
const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
|
|
9587
|
+
const config = readConfig();
|
|
9588
|
+
return kind === "branch" ? {
|
|
9589
|
+
branchPolicy: withReleaseTracks(workspaceRoot, resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null, () => repoBranchPreset(workspaceRoot))),
|
|
9590
|
+
provenance: { branchPolicy: selected.source }
|
|
9591
|
+
} : {
|
|
9592
|
+
commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
|
|
9593
|
+
provenance: { commitPolicy: selected.source }
|
|
9594
|
+
};
|
|
9595
|
+
}
|
|
9596
|
+
var KIND_SYNONYMS = {
|
|
9597
|
+
fix: "bugfix",
|
|
9598
|
+
fixes: "bugfix",
|
|
9599
|
+
bug: "bugfix",
|
|
9600
|
+
bugs: "bugfix",
|
|
9601
|
+
bugfixes: "bugfix",
|
|
9602
|
+
feat: "feature",
|
|
9603
|
+
features: "feature",
|
|
9604
|
+
chore: "feature",
|
|
9605
|
+
docs: "feature",
|
|
9606
|
+
doc: "feature",
|
|
9607
|
+
refactor: "feature",
|
|
9608
|
+
test: "feature",
|
|
9609
|
+
tests: "feature",
|
|
9610
|
+
ci: "feature",
|
|
9611
|
+
build: "feature",
|
|
9612
|
+
perf: "feature",
|
|
9613
|
+
style: "feature",
|
|
9614
|
+
deps: "feature",
|
|
9615
|
+
task: "feature",
|
|
9616
|
+
enhancement: "feature",
|
|
9617
|
+
hot: "hotfix",
|
|
9618
|
+
hotfixes: "hotfix",
|
|
9619
|
+
releases: "release",
|
|
9620
|
+
rel: "release"
|
|
9621
|
+
};
|
|
9622
|
+
var allowedKinds = (allowed) => [
|
|
9623
|
+
...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
|
|
9624
|
+
];
|
|
9625
|
+
var editDistance = (a, b) => {
|
|
9626
|
+
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
9627
|
+
for (let i = 1;i <= a.length; i++) {
|
|
9628
|
+
let previous = row[0];
|
|
9629
|
+
row[0] = i;
|
|
9630
|
+
for (let j = 1;j <= b.length; j++) {
|
|
9631
|
+
const saved = row[j];
|
|
9632
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
9633
|
+
previous = saved;
|
|
9510
9634
|
}
|
|
9511
|
-
return false;
|
|
9512
9635
|
}
|
|
9636
|
+
return row[b.length];
|
|
9513
9637
|
};
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9638
|
+
var suggestBranchName = (name, kinds) => {
|
|
9639
|
+
if (!kinds.length)
|
|
9640
|
+
return null;
|
|
9641
|
+
const slash = name.indexOf("/");
|
|
9642
|
+
const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
|
|
9643
|
+
const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
|
|
9644
|
+
const synonym = KIND_SYNONYMS[prefix];
|
|
9645
|
+
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;
|
|
9646
|
+
const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
|
|
9647
|
+
return `${kind}/${rest}`;
|
|
9648
|
+
};
|
|
9649
|
+
var kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", ");
|
|
9650
|
+
var aliasNote = (kinds) => {
|
|
9651
|
+
const notes = [
|
|
9652
|
+
["fix", "bugfix"],
|
|
9653
|
+
["chore", "feature"]
|
|
9654
|
+
].filter(([, kind]) => kinds.includes(kind)).map(([alias, kind]) => `${alias}/ is ${kind}/`);
|
|
9655
|
+
return notes.length ? ` (${notes.join(", ")})` : "";
|
|
9656
|
+
};
|
|
9657
|
+
var validateBranchNamePolicy = (name, policy, source) => {
|
|
9658
|
+
const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
|
|
9659
|
+
const kinds = allowedKinds(policy.allowed);
|
|
9660
|
+
if (policy.protected.has(name.toLowerCase())) {
|
|
9661
|
+
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(", ")}`;
|
|
9662
|
+
return {
|
|
9663
|
+
ok: false,
|
|
9664
|
+
rule: "protected_ref",
|
|
9665
|
+
source,
|
|
9666
|
+
attempted: name,
|
|
9667
|
+
correction,
|
|
9668
|
+
error: `protected_ref: branch ${JSON.stringify(name)} is protected by ${source}; ${correction}`
|
|
9669
|
+
};
|
|
9534
9670
|
}
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
if (span === size)
|
|
9547
|
-
return null;
|
|
9548
|
-
continue;
|
|
9549
|
-
}
|
|
9550
|
-
let stop = end;
|
|
9551
|
-
let text = "";
|
|
9552
|
-
while (stop >= 0) {
|
|
9553
|
-
const start = buffer.lastIndexOf(10, stop - 1);
|
|
9554
|
-
text = buffer.toString("utf8", start + 1, stop).trim();
|
|
9555
|
-
if (text) {
|
|
9556
|
-
if (start >= 0 || span === size)
|
|
9557
|
-
return text;
|
|
9558
|
-
break;
|
|
9559
|
-
}
|
|
9560
|
-
if (start < 0) {
|
|
9561
|
-
if (span === size)
|
|
9562
|
-
return null;
|
|
9563
|
-
break;
|
|
9564
|
-
}
|
|
9565
|
-
stop = start;
|
|
9566
|
-
}
|
|
9567
|
-
if (span === size)
|
|
9568
|
-
return text || null;
|
|
9671
|
+
if (!policy.allowed.some((pattern) => pattern.test(name))) {
|
|
9672
|
+
const suggestion = suggestBranchName(name, kinds);
|
|
9673
|
+
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(", ")}`;
|
|
9674
|
+
return {
|
|
9675
|
+
ok: false,
|
|
9676
|
+
rule: "allowed_pattern",
|
|
9677
|
+
source,
|
|
9678
|
+
attempted: name,
|
|
9679
|
+
correction,
|
|
9680
|
+
error: `allowed_pattern: branch ${JSON.stringify(name)} violates ${source} policy; ${correction}`
|
|
9681
|
+
};
|
|
9569
9682
|
}
|
|
9570
|
-
return
|
|
9571
|
-
};
|
|
9572
|
-
var tighten = (fd) => {
|
|
9573
|
-
try {
|
|
9574
|
-
if ((fs7.fstatSync(fd).mode & 63) !== 0)
|
|
9575
|
-
fs7.fchmodSync(fd, 384);
|
|
9576
|
-
} catch {}
|
|
9683
|
+
return { ok: true };
|
|
9577
9684
|
};
|
|
9578
|
-
|
|
9579
|
-
const
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
fence = `
|
|
9591
|
-
`;
|
|
9592
|
-
}
|
|
9593
|
-
const line = build(lastLine(fd, size));
|
|
9594
|
-
if (!line.ok)
|
|
9595
|
-
return line;
|
|
9596
|
-
const bytes = Buffer.from(fence + line.value);
|
|
9597
|
-
const written = fs7.writeSync(fd, bytes);
|
|
9598
|
-
if (written !== bytes.length)
|
|
9599
|
-
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
9600
|
-
return { ok: true, value: undefined };
|
|
9601
|
-
} finally {
|
|
9602
|
-
fs7.closeSync(fd);
|
|
9603
|
-
}
|
|
9685
|
+
var branchPolicySnapshotFor = (workspaceRoot) => {
|
|
9686
|
+
const resolved = effectiveWorkspacePolicy(workspaceRoot, "branch");
|
|
9687
|
+
const branchPolicy = resolved.branchPolicy;
|
|
9688
|
+
const source = resolved.provenance.branchPolicy;
|
|
9689
|
+
const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
|
|
9690
|
+
const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
|
|
9691
|
+
const fingerprint = createHash6("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
|
|
9692
|
+
return {
|
|
9693
|
+
branchPolicy,
|
|
9694
|
+
namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
|
|
9695
|
+
source,
|
|
9696
|
+
fingerprint
|
|
9604
9697
|
};
|
|
9698
|
+
};
|
|
9699
|
+
var validateBranchNameFor = (workspaceRoot, name) => {
|
|
9605
9700
|
try {
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
return write();
|
|
9609
|
-
const locked = withLedgerLock(file, write);
|
|
9610
|
-
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
9701
|
+
const resolved = branchPolicySnapshotFor(workspaceRoot);
|
|
9702
|
+
return validateBranchNamePolicy(name, resolved.namePolicy, resolved.source);
|
|
9611
9703
|
} catch (error) {
|
|
9612
|
-
|
|
9704
|
+
const correction = "repair or select a valid workspace policy before creating a branch";
|
|
9705
|
+
return {
|
|
9706
|
+
ok: false,
|
|
9707
|
+
rule: "policy_unavailable",
|
|
9708
|
+
source: "workspaces.json",
|
|
9709
|
+
attempted: name,
|
|
9710
|
+
correction,
|
|
9711
|
+
error: `policy_unavailable: cannot validate branch ${JSON.stringify(name)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
|
|
9712
|
+
};
|
|
9613
9713
|
}
|
|
9614
|
-
}
|
|
9615
|
-
var sealed = (row, id, at, previous) => {
|
|
9616
|
-
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
9617
|
-
const prevHash = previous === null ? null : sha2562(previous);
|
|
9618
|
-
const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
|
|
9619
|
-
const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
|
|
9620
|
-
return { full, line: `${JSON.stringify(full)}
|
|
9621
|
-
` };
|
|
9622
|
-
};
|
|
9623
|
-
function appendRaw(cwd, row, options) {
|
|
9624
|
-
const file = ledgerPath(cwd);
|
|
9625
|
-
if (!file.ok)
|
|
9626
|
-
return file;
|
|
9627
|
-
const id = newId2();
|
|
9628
|
-
const at = (options.now ?? new Date).toISOString();
|
|
9629
|
-
let full = null;
|
|
9630
|
-
const written = appendLine(file.value, (previous) => {
|
|
9631
|
-
const next = sealed(row, id, at, previous);
|
|
9632
|
-
const bytes = Buffer.byteLength(next.line);
|
|
9633
|
-
if (bytes > MAX_LINE_BYTES)
|
|
9634
|
-
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>`);
|
|
9635
|
-
full = next.full;
|
|
9636
|
-
return { ok: true, value: next.line };
|
|
9637
|
-
}, options);
|
|
9638
|
-
return written.ok ? { ok: true, value: full } : written;
|
|
9639
|
-
}
|
|
9640
|
-
function observedRowBytes(row) {
|
|
9641
|
-
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
9642
|
-
return Buffer.byteLength(sample.line);
|
|
9643
|
-
}
|
|
9644
|
-
function appendObserved(cwd, row, options = {}) {
|
|
9645
|
-
return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
|
|
9646
|
-
}
|
|
9647
|
-
function appendHookObserved(cwd, row) {
|
|
9648
|
-
return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
|
|
9649
|
-
}
|
|
9650
|
-
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9651
|
-
var str = (value) => typeof value === "string" ? value : null;
|
|
9652
|
-
var normalizeActor = (value) => {
|
|
9653
|
-
const actor = isRecord2(value) ? value : {};
|
|
9654
|
-
return {
|
|
9655
|
-
host: str(actor.host) ?? "unknown",
|
|
9656
|
-
session: str(actor.session),
|
|
9657
|
-
agentId: str(actor.agentId)
|
|
9658
|
-
};
|
|
9659
9714
|
};
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
9663
|
-
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
9664
|
-
return "only the session that wrote a row can supersede it";
|
|
9665
|
-
if (!onBranch(target))
|
|
9666
|
-
return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
|
|
9667
|
-
if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
|
|
9668
|
-
return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
|
|
9669
|
-
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
9670
|
-
return "a self verdict cannot replace an independent verdict";
|
|
9671
|
-
return null;
|
|
9672
|
-
}
|
|
9673
|
-
function readLedger(cwd, options = {}) {
|
|
9674
|
-
const file = ledgerPath(cwd);
|
|
9675
|
-
if (!file.ok)
|
|
9676
|
-
return file;
|
|
9677
|
-
let text = "";
|
|
9678
|
-
let stat = null;
|
|
9715
|
+
var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
|
|
9716
|
+
var gitFlowOf = (root, integration) => {
|
|
9679
9717
|
try {
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
skipped: 0,
|
|
9690
|
-
integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
|
|
9691
|
-
}
|
|
9692
|
-
};
|
|
9693
|
-
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
9718
|
+
if (resolveBranchPolicyFor(root).preset !== "gitflow")
|
|
9719
|
+
return null;
|
|
9720
|
+
const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
|
|
9721
|
+
const names = branchNames(root);
|
|
9722
|
+
const production = productionBranchOf(names);
|
|
9723
|
+
const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
|
|
9724
|
+
return { develop, production, developExists: names.has(develop) };
|
|
9725
|
+
} catch {
|
|
9726
|
+
return null;
|
|
9694
9727
|
}
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
|
|
9702
|
-
|
|
9703
|
-
|
|
9704
|
-
if (!trimmed)
|
|
9705
|
-
continue;
|
|
9706
|
-
const linked = (prevHash) => earlier.has(prevHash);
|
|
9707
|
-
if (options.integrity)
|
|
9708
|
-
earlier.add(sha2562(trimmed));
|
|
9709
|
-
let parsed;
|
|
9710
|
-
try {
|
|
9711
|
-
parsed = JSON.parse(trimmed);
|
|
9712
|
-
} catch {
|
|
9713
|
-
skipped += 1;
|
|
9714
|
-
continue;
|
|
9715
|
-
}
|
|
9716
|
-
if (!isRecord2(parsed) || !str(parsed.id) || !str(parsed.type) || !str(parsed.at)) {
|
|
9717
|
-
skipped += 1;
|
|
9718
|
-
continue;
|
|
9719
|
-
}
|
|
9720
|
-
let rowIntegrity = options.integrity ? "verified" : undefined;
|
|
9721
|
-
let reason = null;
|
|
9722
|
-
if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
|
|
9723
|
-
chainStarted = true;
|
|
9724
|
-
const { rowHash, ...body } = parsed;
|
|
9725
|
-
const prevHash = str(parsed.prevHash);
|
|
9726
|
-
if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
|
|
9727
|
-
reason = "hash_mismatch";
|
|
9728
|
-
else if (!linked(prevHash))
|
|
9729
|
-
reason = "broken_link";
|
|
9730
|
-
} else if (chainStarted)
|
|
9731
|
-
reason = "unsigned";
|
|
9732
|
-
else
|
|
9733
|
-
rowIntegrity = "legacy";
|
|
9734
|
-
if (options.integrity && !reason && ids.has(parsed.id))
|
|
9735
|
-
reason = "duplicate_id";
|
|
9736
|
-
if (options.integrity)
|
|
9737
|
-
ids.add(parsed.id);
|
|
9738
|
-
if (reason)
|
|
9739
|
-
rowIntegrity = "unverified";
|
|
9740
|
-
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
9741
|
-
const row = {
|
|
9742
|
-
...parsed,
|
|
9743
|
-
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
9744
|
-
id: parsed.id,
|
|
9745
|
-
at: parsed.at,
|
|
9746
|
-
type: parsed.type,
|
|
9747
|
-
actor: normalizeActor(parsed.actor),
|
|
9748
|
-
branch: str(parsed.branch),
|
|
9749
|
-
head: str(parsed.head),
|
|
9750
|
-
base: str(parsed.base),
|
|
9751
|
-
baseSha: str(parsed.baseSha),
|
|
9752
|
-
patchId: str(parsed.patchId),
|
|
9753
|
-
diffHash: str(parsed.diffHash),
|
|
9754
|
-
tree: str(parsed.tree),
|
|
9755
|
-
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
9756
|
-
...pr === undefined ? {} : { pr },
|
|
9757
|
-
...str(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
9758
|
-
seq: rows.length + 1,
|
|
9759
|
-
superseded: false,
|
|
9760
|
-
supersedeIgnored: false,
|
|
9761
|
-
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
9762
|
-
...reason ? { integrityReason: reason } : {}
|
|
9728
|
+
};
|
|
9729
|
+
var releaseBranchesOf = (cwd) => {
|
|
9730
|
+
try {
|
|
9731
|
+
const resolved = resolveBranchPolicyFor(cwd);
|
|
9732
|
+
const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
|
|
9733
|
+
return {
|
|
9734
|
+
preset: resolved.preset,
|
|
9735
|
+
branches: flow ? [flow.production, flow.develop] : [],
|
|
9736
|
+
production: flow ? flow.production : resolved.defaultTargetBranch
|
|
9763
9737
|
};
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
continue;
|
|
9767
|
-
if (rowIntegrity === "verified")
|
|
9768
|
-
integrity.chained += 1;
|
|
9769
|
-
else if (rowIntegrity === "legacy")
|
|
9770
|
-
integrity.legacy += 1;
|
|
9771
|
-
else if (reason)
|
|
9772
|
-
integrity.unverified.push({
|
|
9773
|
-
seq: row.seq,
|
|
9774
|
-
id: row.id,
|
|
9775
|
-
type: row.type,
|
|
9776
|
-
branch: row.branch,
|
|
9777
|
-
reason
|
|
9778
|
-
});
|
|
9738
|
+
} catch {
|
|
9739
|
+
return { preset: "unknown", branches: [], production: null };
|
|
9779
9740
|
}
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
const
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9741
|
+
};
|
|
9742
|
+
var releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase());
|
|
9743
|
+
var branchGuidance = (cwd) => {
|
|
9744
|
+
try {
|
|
9745
|
+
const patterns = branchPolicySnapshotFor(cwd).namePolicy.allowed;
|
|
9746
|
+
const kinds = allowedKinds(patterns);
|
|
9747
|
+
const kindShaped = new Set(kinds.map((kind) => branchGlobPattern(`${kind}/*`).source));
|
|
9748
|
+
const anyName = patterns.some((pattern) => pattern.source === "^.*$");
|
|
9749
|
+
const others = patterns.filter((pattern) => !kindShaped.has(pattern.source)).map((pattern) => globOf(pattern));
|
|
9750
|
+
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.`;
|
|
9751
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
9752
|
+
const integration = resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
|
|
9753
|
+
const flow = resolved.releaseTrack?.track ? null : gitFlowOf(cwd, integration);
|
|
9754
|
+
if (flow?.developExists)
|
|
9755
|
+
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}.`;
|
|
9756
|
+
const names = branchNames(cwd);
|
|
9757
|
+
const configured = String(resolved.baseBranch ?? integration ?? "") || null;
|
|
9758
|
+
const trunk = productionBranchOf(names);
|
|
9759
|
+
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.";
|
|
9760
|
+
return `${allowed} ${base}`;
|
|
9761
|
+
} catch {
|
|
9762
|
+
return null;
|
|
9796
9763
|
}
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
});
|
|
9803
|
-
return {
|
|
9804
|
-
ok: true,
|
|
9805
|
-
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
9806
|
-
};
|
|
9807
|
-
}
|
|
9808
|
-
var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
9809
|
-
var tamperCache = new Map;
|
|
9810
|
-
var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
9811
|
-
function ledgerTampering(cwd) {
|
|
9812
|
-
const file = ledgerPath(cwd);
|
|
9813
|
-
if (!file.ok)
|
|
9814
|
-
return [];
|
|
9764
|
+
};
|
|
9765
|
+
var globOf = (pattern) => pattern.source.replace(/^\^|\$$/gu, "").replaceAll(".*", "*").replace(/\\(.)/gu, "$1");
|
|
9766
|
+
var policy = (root) => resolveBranchPolicyFor(root);
|
|
9767
|
+
var isProtected = (root, name) => policy(root).protected.has(name.toLowerCase());
|
|
9768
|
+
var isProtectedTarget = (root, name) => {
|
|
9815
9769
|
try {
|
|
9816
|
-
|
|
9817
|
-
const cached = tamperCache.get(file.value);
|
|
9818
|
-
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
9819
|
-
return cached.breaks;
|
|
9770
|
+
return isProtected(root, name);
|
|
9820
9771
|
} catch {
|
|
9821
|
-
return
|
|
9772
|
+
return true;
|
|
9822
9773
|
}
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9855
|
-
|
|
9856
|
-
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9774
|
+
};
|
|
9775
|
+
|
|
9776
|
+
// packages/workit-core/src/core/logger.ts
|
|
9777
|
+
import os3 from "node:os";
|
|
9778
|
+
import path7 from "node:path";
|
|
9779
|
+
var SENSITIVE_WORDS = new Set([
|
|
9780
|
+
"token",
|
|
9781
|
+
"secret",
|
|
9782
|
+
"password",
|
|
9783
|
+
"passwd",
|
|
9784
|
+
"authorization",
|
|
9785
|
+
"credential",
|
|
9786
|
+
"credentials",
|
|
9787
|
+
"cookie",
|
|
9788
|
+
"api",
|
|
9789
|
+
"apikey",
|
|
9790
|
+
"apitoken",
|
|
9791
|
+
"accesstoken",
|
|
9792
|
+
"clientsecret",
|
|
9793
|
+
"refreshtoken",
|
|
9794
|
+
"key",
|
|
9795
|
+
"bearer",
|
|
9796
|
+
"prompt",
|
|
9797
|
+
"message",
|
|
9798
|
+
"messages",
|
|
9799
|
+
"content",
|
|
9800
|
+
"body",
|
|
9801
|
+
"args",
|
|
9802
|
+
"argument",
|
|
9803
|
+
"result",
|
|
9804
|
+
"results",
|
|
9805
|
+
"output",
|
|
9806
|
+
"issue",
|
|
9807
|
+
"description",
|
|
9808
|
+
"summary",
|
|
9809
|
+
"payload",
|
|
9810
|
+
"script",
|
|
9811
|
+
"command",
|
|
9812
|
+
"text",
|
|
9813
|
+
"query"
|
|
9814
|
+
]);
|
|
9815
|
+
var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
|
|
9816
|
+
var escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9817
|
+
var resolveStateDir = () => {
|
|
9818
|
+
const override = process.env.WORKFLOW_TOOLKIT_STATE;
|
|
9819
|
+
if (override)
|
|
9820
|
+
return override;
|
|
9821
|
+
const home = os3.homedir();
|
|
9822
|
+
if (process.env.XDG_STATE_HOME)
|
|
9823
|
+
return path7.join(process.env.XDG_STATE_HOME, "workit");
|
|
9824
|
+
switch (os3.platform()) {
|
|
9825
|
+
case "darwin":
|
|
9826
|
+
return path7.join(home, "Library", "Application Support", "workit");
|
|
9827
|
+
case "win32":
|
|
9828
|
+
return path7.join(process.env.LOCALAPPDATA ?? path7.join(home, "AppData", "Local"), "workit");
|
|
9829
|
+
default:
|
|
9830
|
+
return path7.join(process.env.HOME ?? home, ".local", "state", "workit");
|
|
9831
|
+
}
|
|
9832
|
+
};
|
|
9833
|
+
var homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
|
|
9834
|
+
|
|
9835
|
+
// packages/workit-core/src/host-session.ts
|
|
9836
|
+
var HOST_SESSION_VARS = [
|
|
9837
|
+
["OPENCODE_SESSION_ID", () => "opencode"],
|
|
9838
|
+
["PI_SESSION_ID", () => "pi"],
|
|
9839
|
+
[
|
|
9840
|
+
"CODEX_THREAD_ID",
|
|
9841
|
+
(env) => env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || env.CODEX_ELECTRON_RESOURCES_PATH ? "codex_desktop" : "codex_cli"
|
|
9842
|
+
]
|
|
9843
|
+
];
|
|
9844
|
+
var family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null;
|
|
9845
|
+
function hostSessionFromEnv(env) {
|
|
9846
|
+
const value = (key) => env[key]?.trim() || null;
|
|
9847
|
+
let inner = null;
|
|
9848
|
+
for (const [key, host] of HOST_SESSION_VARS) {
|
|
9849
|
+
const session = value(key);
|
|
9850
|
+
if (session) {
|
|
9851
|
+
inner = { host: host(env), session };
|
|
9852
|
+
break;
|
|
9875
9853
|
}
|
|
9876
9854
|
}
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
|
|
9884
|
-
return at >= since && at <= until;
|
|
9885
|
-
};
|
|
9855
|
+
const declared = value("WORKIT_HOST");
|
|
9856
|
+
if (env.WORKIT_SESSION_ID !== undefined) {
|
|
9857
|
+
const inherited = inner !== null && declared !== null && family(declared) !== family(inner.host);
|
|
9858
|
+
if (!inherited)
|
|
9859
|
+
return { host: declared, session: value("WORKIT_SESSION_ID") };
|
|
9860
|
+
}
|
|
9861
|
+
return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
|
|
9886
9862
|
}
|
|
9887
|
-
var
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
|
|
9895
|
-
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
if (!
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
sessions.add(session);
|
|
9863
|
+
var ISSUED_PER_LEAD = 64;
|
|
9864
|
+
var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3";
|
|
9865
|
+
var hash = (value) => createHash7("sha256").update(value).digest("hex").slice(0, 32);
|
|
9866
|
+
var userIssuedRoot = () => path8.join(resolveStateDir(), "issued");
|
|
9867
|
+
var userTokenRoot = () => path8.join(resolveStateDir(), "issued-tokens");
|
|
9868
|
+
function migrateRepoIssued(cwd) {
|
|
9869
|
+
if (cwd === undefined)
|
|
9870
|
+
return;
|
|
9871
|
+
let old;
|
|
9872
|
+
try {
|
|
9873
|
+
const location = resolveStore(cwd);
|
|
9874
|
+
if (location instanceof Error)
|
|
9875
|
+
return;
|
|
9876
|
+
old = path8.join(location.dir, "hooks", "issued");
|
|
9877
|
+
if (!fs5.existsSync(old))
|
|
9878
|
+
return;
|
|
9879
|
+
} catch {
|
|
9880
|
+
return;
|
|
9906
9881
|
}
|
|
9907
|
-
for (const
|
|
9908
|
-
|
|
9909
|
-
|
|
9882
|
+
for (const lead of safeList(old)) {
|
|
9883
|
+
try {
|
|
9884
|
+
const from = path8.join(old, lead);
|
|
9885
|
+
if (!fs5.statSync(from).isDirectory())
|
|
9886
|
+
continue;
|
|
9887
|
+
const to = path8.join(userIssuedRoot(), lead);
|
|
9888
|
+
fs5.mkdirSync(to, { recursive: true, mode: 448 });
|
|
9889
|
+
for (const id of fs5.readdirSync(from)) {
|
|
9890
|
+
try {
|
|
9891
|
+
fs5.copyFileSync(path8.join(from, id), path8.join(to, id), fs5.constants.COPYFILE_EXCL);
|
|
9892
|
+
} catch (error) {
|
|
9893
|
+
if (error.code !== "EEXIST")
|
|
9894
|
+
throw error;
|
|
9895
|
+
}
|
|
9896
|
+
fs5.rmSync(path8.join(from, id), { force: true });
|
|
9897
|
+
}
|
|
9898
|
+
removeIfEmpty(from);
|
|
9899
|
+
} catch {}
|
|
9900
|
+
}
|
|
9901
|
+
removeIfEmpty(old);
|
|
9910
9902
|
}
|
|
9911
|
-
var
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
return true;
|
|
9918
|
-
return false;
|
|
9903
|
+
var safeList = (dir) => {
|
|
9904
|
+
try {
|
|
9905
|
+
return fs5.readdirSync(dir);
|
|
9906
|
+
} catch {
|
|
9907
|
+
return [];
|
|
9908
|
+
}
|
|
9919
9909
|
};
|
|
9920
|
-
var
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
if (!issuedOnly)
|
|
9910
|
+
var removeIfEmpty = (dir) => {
|
|
9911
|
+
try {
|
|
9912
|
+
fs5.rmdirSync(dir);
|
|
9913
|
+
} catch {}
|
|
9914
|
+
};
|
|
9915
|
+
var tokenHash = (token) => createHash7("sha256").update(token).digest("hex");
|
|
9916
|
+
function recordIssuedSession(cwd, lead, issued) {
|
|
9917
|
+
const token = randomBytes(18).toString("hex");
|
|
9918
|
+
try {
|
|
9919
|
+
migrateRepoIssued(cwd);
|
|
9920
|
+
const own = hash(issued);
|
|
9921
|
+
const tokens = path8.join(userTokenRoot(), hash(lead));
|
|
9922
|
+
fs5.mkdirSync(tokens, { recursive: true, mode: 448 });
|
|
9923
|
+
fs5.writeFileSync(path8.join(tokens, own), `${tokenHash(token)}
|
|
9924
|
+
`, { mode: 384, flag: "wx" });
|
|
9925
|
+
const dir = path8.join(userIssuedRoot(), hash(lead));
|
|
9926
|
+
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
9927
|
+
fs5.writeFileSync(path8.join(dir, own), `${ISSUED_SESSIONS_MARKER}
|
|
9928
|
+
`, { mode: 384 });
|
|
9929
|
+
pruneIssued(dir, own, tokens);
|
|
9930
|
+
return token;
|
|
9931
|
+
} catch {
|
|
9943
9932
|
return null;
|
|
9944
|
-
|
|
9945
|
-
if (binding === null)
|
|
9946
|
-
return "unissued_session";
|
|
9947
|
-
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
9933
|
+
}
|
|
9948
9934
|
}
|
|
9949
|
-
function
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
if (row.integrity !== "verified")
|
|
9953
|
-
return ["unsigned"];
|
|
9954
|
-
const session = sessionTrust(cwd, row);
|
|
9955
|
-
return session ? [session] : [];
|
|
9935
|
+
function judgeBranch(cwd) {
|
|
9936
|
+
const branch = currentBranch(cwd);
|
|
9937
|
+
return branch && !isProtectedTarget(cwd, branch) ? branch : null;
|
|
9956
9938
|
}
|
|
9957
|
-
function
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
});
|
|
9939
|
+
function verdictCommand(options) {
|
|
9940
|
+
const kind = options.judge === "verifier" ? "<live|unit>" : options.judge === "reviewer" ? "review" : "<live|unit|review>";
|
|
9941
|
+
return [
|
|
9942
|
+
"workit ledger verdict <verified|failed|blocked>",
|
|
9943
|
+
`--kind ${kind}`,
|
|
9944
|
+
`--branch ${options.branch ?? "<branch>"}`,
|
|
9945
|
+
options.session ? `--session ${options.session}` : null,
|
|
9946
|
+
options.token ? `--token=${options.token}` : null,
|
|
9947
|
+
'--how "<what you ran and saw>"'
|
|
9948
|
+
].filter(Boolean).join(" ");
|
|
9968
9949
|
}
|
|
9969
|
-
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
return reasons;
|
|
9979
|
-
};
|
|
9980
|
-
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
9981
|
-
let effective = null;
|
|
9982
|
-
for (const row of rows) {
|
|
9983
|
-
if (!effective) {
|
|
9984
|
-
effective = row;
|
|
9985
|
-
continue;
|
|
9950
|
+
function pruneIssued(dir, own, tokens) {
|
|
9951
|
+
try {
|
|
9952
|
+
const entries = fs5.readdirSync(dir);
|
|
9953
|
+
if (entries.length <= ISSUED_PER_LEAD)
|
|
9954
|
+
return;
|
|
9955
|
+
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);
|
|
9956
|
+
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
|
|
9957
|
+
fs5.rmSync(path8.join(dir, name), { force: true });
|
|
9958
|
+
fs5.rmSync(path8.join(tokens, name), { force: true });
|
|
9986
9959
|
}
|
|
9987
|
-
|
|
9988
|
-
continue;
|
|
9989
|
-
const rowIndependent = reasonsOf(row).length === 0;
|
|
9990
|
-
const effIndependent = reasonsOf(effective).length === 0;
|
|
9991
|
-
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
9992
|
-
continue;
|
|
9993
|
-
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
9994
|
-
continue;
|
|
9995
|
-
effective = row;
|
|
9996
|
-
}
|
|
9997
|
-
return effective;
|
|
9960
|
+
} catch {}
|
|
9998
9961
|
}
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
while (queue.length) {
|
|
10009
|
-
const next = queue.shift();
|
|
10010
|
-
if (next === to)
|
|
10011
|
-
return true;
|
|
10012
|
-
for (const target of edges.get(next) ?? [])
|
|
10013
|
-
if (!seen.has(target)) {
|
|
10014
|
-
seen.add(target);
|
|
10015
|
-
queue.push(target);
|
|
10016
|
-
}
|
|
9962
|
+
var issuedRecord = (cwd, claimed) => {
|
|
9963
|
+
const cut = claimed.lastIndexOf(":");
|
|
9964
|
+
if (cut <= 0)
|
|
9965
|
+
return null;
|
|
9966
|
+
migrateRepoIssued(cwd);
|
|
9967
|
+
try {
|
|
9968
|
+
return fs5.readFileSync(path8.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
|
|
9969
|
+
} catch {
|
|
9970
|
+
return null;
|
|
10017
9971
|
}
|
|
10018
|
-
|
|
9972
|
+
};
|
|
9973
|
+
var TOKEN_HASH = /^([0-9a-f]{64})$/mu;
|
|
9974
|
+
var issuedToken = (claimed) => {
|
|
9975
|
+
const cut = claimed.lastIndexOf(":");
|
|
9976
|
+
if (cut <= 0)
|
|
9977
|
+
return { stored: null, leadBound: false };
|
|
9978
|
+
const dir = path8.join(userTokenRoot(), hash(claimed.slice(0, cut)));
|
|
9979
|
+
try {
|
|
9980
|
+
const text = fs5.readFileSync(path8.join(dir, hash(claimed)), "utf8");
|
|
9981
|
+
return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
|
|
9982
|
+
} catch {
|
|
9983
|
+
return { stored: null, leadBound: safeList(dir).length > 0 };
|
|
9984
|
+
}
|
|
9985
|
+
};
|
|
9986
|
+
function issuedBinding(cwd, claimed) {
|
|
9987
|
+
const record = issuedRecord(cwd, claimed);
|
|
9988
|
+
const { stored, leadBound } = issuedToken(claimed);
|
|
9989
|
+
if (stored)
|
|
9990
|
+
return "token";
|
|
9991
|
+
if (record === null)
|
|
9992
|
+
return null;
|
|
9993
|
+
return leadBound ? "token" : "legacy";
|
|
10019
9994
|
}
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
const base = failed.base ?? fallbackBase;
|
|
10046
|
-
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
10047
|
-
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
10048
|
-
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
|
|
10049
|
-
};
|
|
10050
|
-
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
10051
|
-
const failureAuthorCache = new Map;
|
|
10052
|
-
const failureAuthors = (row) => {
|
|
10053
|
-
let set = failureAuthorCache.get(row.seq);
|
|
10054
|
-
if (!set) {
|
|
10055
|
-
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
10056
|
-
failureAuthorCache.set(row.seq, set);
|
|
10057
|
-
}
|
|
10058
|
-
return set;
|
|
10059
|
-
};
|
|
10060
|
-
const reasonCache = new Map;
|
|
10061
|
-
const reasonsOf = (row) => {
|
|
10062
|
-
let reasons = reasonCache.get(row);
|
|
10063
|
-
if (!reasons) {
|
|
10064
|
-
const failing = FAILING.has(String(row.result));
|
|
10065
|
-
reasons = [
|
|
10066
|
-
...trustReasons(cwd, row),
|
|
10067
|
-
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
10068
|
-
];
|
|
10069
|
-
reasonCache.set(row, reasons);
|
|
10070
|
-
}
|
|
10071
|
-
return reasons;
|
|
10072
|
-
};
|
|
10073
|
-
const retired = new Set;
|
|
10074
|
-
for (const row of rows)
|
|
10075
|
-
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
10076
|
-
retired.add(row.supersedes);
|
|
10077
|
-
const byKind = new Map;
|
|
10078
|
-
for (const row of rows)
|
|
10079
|
-
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
10080
|
-
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
10081
|
-
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
10082
|
-
}
|
|
10083
|
-
const verdicts = [];
|
|
10084
|
-
const warnings = [];
|
|
10085
|
-
for (const [kind, list] of byKind) {
|
|
10086
|
-
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
10087
|
-
if (!row)
|
|
10088
|
-
continue;
|
|
10089
|
-
const basis = basisOf(row);
|
|
10090
|
-
const current = basis !== "stale";
|
|
10091
|
-
const independence = reasonsOf(row);
|
|
10092
|
-
const reasons = [
|
|
10093
|
-
...current ? [] : ["stale"],
|
|
10094
|
-
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
10095
|
-
...independence
|
|
10096
|
-
];
|
|
10097
|
-
verdicts.push({
|
|
10098
|
-
kind,
|
|
10099
|
-
basis,
|
|
10100
|
-
current,
|
|
10101
|
-
independent: independence.length === 0,
|
|
10102
|
-
accepted: reasons.length === 0,
|
|
10103
|
-
reasons,
|
|
10104
|
-
verdict: row
|
|
10105
|
-
});
|
|
10106
|
-
if (reasons.length === 0) {
|
|
10107
|
-
for (const later of list)
|
|
10108
|
-
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
10109
|
-
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`);
|
|
9995
|
+
var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
|
|
9996
|
+
|
|
9997
|
+
// packages/workit-core/src/hooks/standing-signal.ts
|
|
9998
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
9999
|
+
import fs6 from "node:fs";
|
|
10000
|
+
import path9 from "node:path";
|
|
10001
|
+
var gitStoreDir = (cwd) => {
|
|
10002
|
+
try {
|
|
10003
|
+
for (let dir = path9.resolve(cwd);; dir = path9.dirname(dir)) {
|
|
10004
|
+
const dotGit = path9.join(dir, ".git");
|
|
10005
|
+
if (fs6.existsSync(dotGit)) {
|
|
10006
|
+
let gitDir = dotGit;
|
|
10007
|
+
if (fs6.statSync(dotGit).isFile()) {
|
|
10008
|
+
const target = /^gitdir:\s*(.+)$/mu.exec(fs6.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
10009
|
+
if (!target)
|
|
10010
|
+
return null;
|
|
10011
|
+
gitDir = path9.resolve(dir, target);
|
|
10012
|
+
const common = path9.join(gitDir, "commondir");
|
|
10013
|
+
if (fs6.existsSync(common))
|
|
10014
|
+
gitDir = path9.resolve(gitDir, fs6.readFileSync(common, "utf8").trim());
|
|
10015
|
+
}
|
|
10016
|
+
return path9.join(gitDir, "workit");
|
|
10017
|
+
}
|
|
10018
|
+
if (path9.dirname(dir) === dir)
|
|
10019
|
+
return null;
|
|
10110
10020
|
}
|
|
10021
|
+
} catch {
|
|
10022
|
+
return null;
|
|
10111
10023
|
}
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
10121
|
-
}
|
|
10024
|
+
};
|
|
10025
|
+
var standingRevFile = (store) => path9.join(store, "standing", "rev");
|
|
10026
|
+
var standingSeenFile = (store, session, agentId) => path9.join(store, "standing", "seen", `${createHash8("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`);
|
|
10027
|
+
var readStandingRev = (store) => {
|
|
10028
|
+
try {
|
|
10029
|
+
return fs6.readFileSync(standingRevFile(store), "utf8").trim() || null;
|
|
10030
|
+
} catch {
|
|
10031
|
+
return null;
|
|
10122
10032
|
}
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
}
|
|
10132
|
-
|
|
10133
|
-
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
10134
|
-
return {
|
|
10135
|
-
branch,
|
|
10136
|
-
head,
|
|
10137
|
-
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
10138
|
-
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
10139
|
-
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
10140
|
-
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
10141
|
-
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
10142
|
-
accepted,
|
|
10143
|
-
verdicts,
|
|
10144
|
-
authors: [...authors].toSorted(),
|
|
10145
|
-
warnings
|
|
10146
|
-
};
|
|
10147
|
-
}
|
|
10148
|
-
var fromLead = (row, leads) => leads === null || row.actor.session !== null && leads.includes(row.actor.session);
|
|
10149
|
-
function activeStanding(rows, fanout, leads = null) {
|
|
10150
|
-
const active = [];
|
|
10151
|
-
for (const row of rows) {
|
|
10152
|
-
if (row.fanout !== fanout || !fromLead(row, leads))
|
|
10153
|
-
continue;
|
|
10154
|
-
if (row.type === "standing" && typeof row.what === "string" && row.what.trim()) {
|
|
10155
|
-
if (!row.superseded)
|
|
10156
|
-
active.push(row);
|
|
10157
|
-
} else if (row.type === "standing.cleared") {
|
|
10158
|
-
const target = str(row.target);
|
|
10159
|
-
if (target === null)
|
|
10160
|
-
active.length = 0;
|
|
10161
|
-
else {
|
|
10162
|
-
const index = active.findIndex((order) => order.id === target);
|
|
10163
|
-
if (index >= 0)
|
|
10164
|
-
active.splice(index, 1);
|
|
10165
|
-
}
|
|
10166
|
-
}
|
|
10033
|
+
};
|
|
10034
|
+
var readStandingSeen = (file) => {
|
|
10035
|
+
try {
|
|
10036
|
+
const value = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
10037
|
+
return {
|
|
10038
|
+
rev: typeof value.rev === "string" ? value.rev : null,
|
|
10039
|
+
ids: Array.isArray(value.ids) ? value.ids.filter((id) => typeof id === "string") : []
|
|
10040
|
+
};
|
|
10041
|
+
} catch {
|
|
10042
|
+
return { rev: null, ids: [] };
|
|
10167
10043
|
}
|
|
10168
|
-
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
|
|
10175
|
-
]);
|
|
10176
|
-
|
|
10177
|
-
// packages/workit-core/src/core/policy/lane.ts
|
|
10178
|
-
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
10179
|
-
import path10 from "node:path";
|
|
10180
|
-
// packages/workit-core/src/forge/redact.ts
|
|
10181
|
-
var 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_.-]*`;
|
|
10182
|
-
var ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
|
|
10183
|
-
var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
|
|
10184
|
-
var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
|
|
10185
|
-
|
|
10186
|
-
// packages/workit-core/src/git/ops.ts
|
|
10187
|
-
var COMMIT_TIMEOUT_MS = 10 * 60000;
|
|
10044
|
+
};
|
|
10045
|
+
var writeStandingSeen = (file, seen) => {
|
|
10046
|
+
try {
|
|
10047
|
+
fs6.mkdirSync(path9.dirname(file), { recursive: true });
|
|
10048
|
+
fs6.writeFileSync(file, JSON.stringify(seen));
|
|
10049
|
+
} catch {}
|
|
10050
|
+
};
|
|
10188
10051
|
|
|
10189
|
-
// packages/workit-core/src/
|
|
10190
|
-
|
|
10191
|
-
|
|
10052
|
+
// packages/workit-core/src/ledger.ts
|
|
10053
|
+
var LEDGER_VERSION = 1;
|
|
10054
|
+
var MAX_LINE_BYTES = 4096;
|
|
10055
|
+
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
10056
|
+
var FAILING = new Set(["failed", "blocked"]);
|
|
10057
|
+
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
10058
|
+
var PR_ROW_TYPES = new Set([
|
|
10059
|
+
"pr.created",
|
|
10060
|
+
"pr.status",
|
|
10061
|
+
"pr.merged",
|
|
10062
|
+
"pr.linked"
|
|
10063
|
+
]);
|
|
10064
|
+
var err = (code, error, unblock) => ({
|
|
10065
|
+
ok: false,
|
|
10066
|
+
code,
|
|
10067
|
+
error,
|
|
10068
|
+
...unblock ? { unblock } : {}
|
|
10069
|
+
});
|
|
10070
|
+
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
10071
|
+
const run = spawnSync8("git", args, {
|
|
10072
|
+
cwd,
|
|
10073
|
+
encoding: "utf8",
|
|
10074
|
+
timeout: timeoutMs,
|
|
10075
|
+
killSignal: "SIGKILL",
|
|
10076
|
+
windowsHide: true,
|
|
10077
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
10078
|
+
env: {
|
|
10079
|
+
...process.env,
|
|
10080
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
10081
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
10082
|
+
LC_ALL: "C",
|
|
10083
|
+
LANGUAGE: "C"
|
|
10084
|
+
}
|
|
10085
|
+
});
|
|
10086
|
+
return {
|
|
10087
|
+
ok: run.status === 0,
|
|
10088
|
+
stdout: run.stdout ?? "",
|
|
10089
|
+
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
10090
|
+
};
|
|
10091
|
+
};
|
|
10092
|
+
var git3 = (cwd, args) => {
|
|
10093
|
+
const run = gitRun2(cwd, args);
|
|
10094
|
+
const value = run.ok ? run.stdout.trim() : "";
|
|
10095
|
+
return value ? value : null;
|
|
10096
|
+
};
|
|
10097
|
+
var safeRef2 = (value) => value.length > 0 && !value.startsWith("-");
|
|
10098
|
+
function resolveCommit(cwd, ref) {
|
|
10099
|
+
if (!safeRef2(ref))
|
|
10192
10100
|
return null;
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
const char = body[index];
|
|
10198
|
-
if (char === "*" && body[index + 1] === "*") {
|
|
10199
|
-
const slash = body[index + 2] === "/";
|
|
10200
|
-
source += slash ? "(?:.*/)?" : ".*";
|
|
10201
|
-
index += slash ? 2 : 1;
|
|
10202
|
-
} else if (char === "*")
|
|
10203
|
-
source += "[^/]*";
|
|
10204
|
-
else if (char === "?")
|
|
10205
|
-
source += "[^/]";
|
|
10206
|
-
else if (char === "[") {
|
|
10207
|
-
const end = body.indexOf("]", index + 2);
|
|
10208
|
-
if (end < 0)
|
|
10209
|
-
return null;
|
|
10210
|
-
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
10211
|
-
source += `[${set}]`;
|
|
10212
|
-
index = end;
|
|
10213
|
-
} else if (char === "\\" && index + 1 < body.length) {
|
|
10214
|
-
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
10215
|
-
index += 1;
|
|
10216
|
-
} else
|
|
10217
|
-
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
10218
|
-
}
|
|
10219
|
-
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
10220
|
-
try {
|
|
10221
|
-
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
10222
|
-
} catch {
|
|
10101
|
+
return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
10102
|
+
}
|
|
10103
|
+
function branchHead(cwd, branch) {
|
|
10104
|
+
if (!safeRef2(branch))
|
|
10223
10105
|
return null;
|
|
10224
|
-
}
|
|
10106
|
+
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
10225
10107
|
}
|
|
10226
|
-
function
|
|
10227
|
-
|
|
10228
|
-
|
|
10108
|
+
function diffHash(cwd, base, head) {
|
|
10109
|
+
if (!safeRef2(base) || !safeRef2(head))
|
|
10110
|
+
return null;
|
|
10111
|
+
const diff = gitRun2(cwd, [
|
|
10112
|
+
"diff",
|
|
10113
|
+
"-U3",
|
|
10114
|
+
"--diff-algorithm=myers",
|
|
10115
|
+
"--indent-heuristic",
|
|
10116
|
+
"--no-color",
|
|
10117
|
+
"--no-ext-diff",
|
|
10118
|
+
"--no-textconv",
|
|
10119
|
+
"--no-renames",
|
|
10120
|
+
"--full-index",
|
|
10121
|
+
"--binary",
|
|
10122
|
+
"--src-prefix=a/",
|
|
10123
|
+
"--dst-prefix=b/",
|
|
10124
|
+
`${base}...${head}`,
|
|
10125
|
+
"--"
|
|
10126
|
+
], GIT_TIMEOUTS.worktree);
|
|
10127
|
+
if (!diff.ok || !diff.stdout)
|
|
10128
|
+
return null;
|
|
10129
|
+
const hash = createHash9("sha256");
|
|
10130
|
+
for (const line of diff.stdout.split(`
|
|
10229
10131
|
`)) {
|
|
10230
|
-
|
|
10231
|
-
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
10232
|
-
continue;
|
|
10233
|
-
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
10234
|
-
let generated = null;
|
|
10235
|
-
for (const attribute of attributes) {
|
|
10236
|
-
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
10237
|
-
generated = true;
|
|
10238
|
-
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
10239
|
-
generated = false;
|
|
10240
|
-
}
|
|
10241
|
-
if (generated === null)
|
|
10132
|
+
if (line.startsWith("index "))
|
|
10242
10133
|
continue;
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10134
|
+
hash.update(line.startsWith("@@ ") ? "@@" : line);
|
|
10135
|
+
hash.update(`
|
|
10136
|
+
`);
|
|
10246
10137
|
}
|
|
10247
|
-
return
|
|
10138
|
+
return `sha256:${hash.digest("hex")}`;
|
|
10248
10139
|
}
|
|
10249
|
-
function
|
|
10250
|
-
const
|
|
10251
|
-
|
|
10252
|
-
return
|
|
10253
|
-
|
|
10254
|
-
rules: rulesOf(dir, text)
|
|
10255
|
-
};
|
|
10256
|
-
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
10257
|
-
const out = new Set;
|
|
10258
|
-
for (const file of paths) {
|
|
10259
|
-
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
10260
|
-
if (last?.generated)
|
|
10261
|
-
out.add(file);
|
|
10262
|
-
}
|
|
10263
|
-
return out;
|
|
10140
|
+
function storeRoot(cwd) {
|
|
10141
|
+
const location = resolveStore(path10.resolve(cwd));
|
|
10142
|
+
if (location instanceof Error)
|
|
10143
|
+
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");
|
|
10144
|
+
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
10264
10145
|
}
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
var
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
ext,
|
|
10282
|
-
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
10283
|
-
]),
|
|
10284
|
-
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
10285
|
-
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
10286
|
-
ext,
|
|
10287
|
-
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
10288
|
-
])
|
|
10146
|
+
function ledgerPath(cwd) {
|
|
10147
|
+
const root = storeRoot(cwd);
|
|
10148
|
+
return root.ok ? { ok: true, value: path10.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
10149
|
+
}
|
|
10150
|
+
var newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`;
|
|
10151
|
+
var NETWORK_FS_TYPES = new Set([
|
|
10152
|
+
26985,
|
|
10153
|
+
20859,
|
|
10154
|
+
4266872130,
|
|
10155
|
+
4283649346,
|
|
10156
|
+
1702057286,
|
|
10157
|
+
1397113167,
|
|
10158
|
+
12805120,
|
|
10159
|
+
16914839,
|
|
10160
|
+
1196443219,
|
|
10161
|
+
198183888
|
|
10289
10162
|
]);
|
|
10290
|
-
var
|
|
10291
|
-
var
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10163
|
+
var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
|
|
10164
|
+
var onNetworkFs = (dir) => {
|
|
10165
|
+
if (process.platform !== "linux")
|
|
10166
|
+
return false;
|
|
10167
|
+
try {
|
|
10168
|
+
return isNetworkFsType(fs7.statfsSync(dir).type);
|
|
10169
|
+
} catch {
|
|
10170
|
+
return false;
|
|
10171
|
+
}
|
|
10295
10172
|
};
|
|
10296
|
-
var
|
|
10297
|
-
var
|
|
10298
|
-
var
|
|
10299
|
-
var
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
"return",
|
|
10305
|
-
"typeof",
|
|
10306
|
-
"instanceof",
|
|
10307
|
-
"case",
|
|
10308
|
-
"do",
|
|
10309
|
-
"else",
|
|
10310
|
-
"in",
|
|
10311
|
-
"of",
|
|
10312
|
-
"new",
|
|
10313
|
-
"delete",
|
|
10314
|
-
"void",
|
|
10315
|
-
"throw",
|
|
10316
|
-
"yield",
|
|
10317
|
-
"await"
|
|
10318
|
-
]);
|
|
10319
|
-
function substanceOf(file, text) {
|
|
10320
|
-
const syntax = syntaxOf(file);
|
|
10321
|
-
if (!syntax)
|
|
10322
|
-
return plainSubstance(text);
|
|
10323
|
-
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
10324
|
-
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
10325
|
-
return plainSubstance(text);
|
|
10326
|
-
return lex(syntax, text);
|
|
10327
|
-
}
|
|
10328
|
-
function plainSubstance(text) {
|
|
10329
|
-
return text.split(`
|
|
10330
|
-
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
10331
|
-
`);
|
|
10332
|
-
}
|
|
10333
|
-
function lex(syntax, text) {
|
|
10334
|
-
const collapse = !syntax.indented;
|
|
10335
|
-
let out = "";
|
|
10336
|
-
let lineStart = 0;
|
|
10337
|
-
let kept = 0;
|
|
10338
|
-
let pendingSpace = false;
|
|
10339
|
-
const frames = [{ kind: "code", braces: 0 }];
|
|
10340
|
-
let index = 0;
|
|
10341
|
-
if (text.startsWith("#!")) {
|
|
10342
|
-
const end = text.indexOf(`
|
|
10343
|
-
`);
|
|
10344
|
-
out = end < 0 ? text : text.slice(0, end);
|
|
10345
|
-
kept = out.length;
|
|
10346
|
-
index = end < 0 ? text.length : end;
|
|
10173
|
+
var LOCK_WAIT_MS = 5000;
|
|
10174
|
+
var LOCK_STALE_MS = 30000;
|
|
10175
|
+
var LOCK_OWNER = "owner";
|
|
10176
|
+
var lockToken = (lock) => {
|
|
10177
|
+
try {
|
|
10178
|
+
return fs7.readFileSync(path10.join(lock, LOCK_OWNER), "utf8");
|
|
10179
|
+
} catch {
|
|
10180
|
+
return null;
|
|
10347
10181
|
}
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
}
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
end -= 1;
|
|
10366
|
-
out = out.slice(0, end);
|
|
10367
|
-
}
|
|
10368
|
-
pendingSpace = false;
|
|
10369
|
-
if (out.length > lineStart) {
|
|
10370
|
-
out += `
|
|
10371
|
-
`;
|
|
10372
|
-
lineStart = out.length;
|
|
10373
|
-
}
|
|
10374
|
-
};
|
|
10375
|
-
const regexAllowed = () => {
|
|
10376
|
-
let end = out.length;
|
|
10377
|
-
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
10378
|
-
end -= 1;
|
|
10379
|
-
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
10380
|
-
return true;
|
|
10381
|
-
let start = end;
|
|
10382
|
-
while (start > 0 && WORD.test(out[start - 1]))
|
|
10383
|
-
start -= 1;
|
|
10384
|
-
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
10385
|
-
};
|
|
10386
|
-
const scanString = (quote, escapes, multiline) => {
|
|
10387
|
-
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
10388
|
-
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
10389
|
-
let end = index + quote.length;
|
|
10390
|
-
while (end < text.length) {
|
|
10391
|
-
const char = text[end];
|
|
10392
|
-
if (escapes && char === "\\") {
|
|
10393
|
-
end += 2;
|
|
10394
|
-
continue;
|
|
10395
|
-
}
|
|
10396
|
-
if (text.startsWith(quote, end)) {
|
|
10397
|
-
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
10398
|
-
return false;
|
|
10399
|
-
emitVerbatim(text.slice(index, end + quote.length));
|
|
10400
|
-
index = end + quote.length;
|
|
10401
|
-
return true;
|
|
10402
|
-
}
|
|
10403
|
-
if (char === `
|
|
10404
|
-
` && !multiline)
|
|
10405
|
-
return false;
|
|
10406
|
-
end += 1;
|
|
10182
|
+
};
|
|
10183
|
+
var lockAgeMs = (lock) => {
|
|
10184
|
+
for (const target of [path10.join(lock, LOCK_OWNER), lock]) {
|
|
10185
|
+
try {
|
|
10186
|
+
return Date.now() - fs7.statSync(target).mtimeMs;
|
|
10187
|
+
} catch {}
|
|
10188
|
+
}
|
|
10189
|
+
return null;
|
|
10190
|
+
};
|
|
10191
|
+
var ledgerLock = {
|
|
10192
|
+
acquire(lock) {
|
|
10193
|
+
try {
|
|
10194
|
+
fs7.mkdirSync(lock);
|
|
10195
|
+
} catch (error) {
|
|
10196
|
+
if (error.code === "EEXIST")
|
|
10197
|
+
return null;
|
|
10198
|
+
throw error;
|
|
10407
10199
|
}
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
if (
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10419
|
-
|
|
10420
|
-
index += 1;
|
|
10421
|
-
} else if (text.startsWith("${", index)) {
|
|
10422
|
-
emitVerbatim("${");
|
|
10423
|
-
frames.push({ kind: "code", braces: 0 });
|
|
10424
|
-
index += 2;
|
|
10425
|
-
} else {
|
|
10426
|
-
out += char;
|
|
10427
|
-
kept = out.length;
|
|
10428
|
-
index += 1;
|
|
10429
|
-
}
|
|
10430
|
-
continue;
|
|
10200
|
+
const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
|
|
10201
|
+
fs7.writeFileSync(path10.join(lock, LOCK_OWNER), token);
|
|
10202
|
+
return token;
|
|
10203
|
+
},
|
|
10204
|
+
release(lock, token) {
|
|
10205
|
+
if (lockToken(lock) !== token)
|
|
10206
|
+
return false;
|
|
10207
|
+
const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
|
|
10208
|
+
try {
|
|
10209
|
+
fs7.renameSync(lock, tomb);
|
|
10210
|
+
} catch {
|
|
10211
|
+
return false;
|
|
10431
10212
|
}
|
|
10432
|
-
if (
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10213
|
+
if (lockToken(tomb) !== token) {
|
|
10214
|
+
try {
|
|
10215
|
+
fs7.renameSync(tomb, lock);
|
|
10216
|
+
} catch {}
|
|
10217
|
+
return false;
|
|
10437
10218
|
}
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
|
|
10444
|
-
|
|
10219
|
+
fs7.rmSync(tomb, { recursive: true, force: true });
|
|
10220
|
+
return true;
|
|
10221
|
+
},
|
|
10222
|
+
token: lockToken,
|
|
10223
|
+
reap(lock, observed) {
|
|
10224
|
+
const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
|
|
10225
|
+
try {
|
|
10226
|
+
fs7.renameSync(lock, tomb);
|
|
10227
|
+
} catch {
|
|
10228
|
+
return false;
|
|
10445
10229
|
}
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
`, index);
|
|
10450
|
-
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
10451
|
-
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
10452
|
-
emitVerbatim(comment);
|
|
10453
|
-
else
|
|
10454
|
-
pendingSpace = true;
|
|
10455
|
-
index = end < 0 ? text.length : end;
|
|
10456
|
-
continue;
|
|
10230
|
+
if (lockToken(tomb) === observed) {
|
|
10231
|
+
fs7.rmSync(tomb, { recursive: true, force: true });
|
|
10232
|
+
return true;
|
|
10457
10233
|
}
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
return null;
|
|
10463
|
-
const comment = text.slice(index, end + block[1].length);
|
|
10464
|
-
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
10465
|
-
emitVerbatim(comment);
|
|
10466
|
-
else
|
|
10467
|
-
pendingSpace = true;
|
|
10468
|
-
if (comment.includes(`
|
|
10469
|
-
`))
|
|
10470
|
-
newline();
|
|
10471
|
-
index = end + block[1].length;
|
|
10472
|
-
continue;
|
|
10234
|
+
try {
|
|
10235
|
+
fs7.renameSync(tomb, lock);
|
|
10236
|
+
} catch {
|
|
10237
|
+
fs7.rmSync(tomb, { recursive: true, force: true });
|
|
10473
10238
|
}
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10239
|
+
return false;
|
|
10240
|
+
}
|
|
10241
|
+
};
|
|
10242
|
+
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
10243
|
+
const lock = `${file}.lock`;
|
|
10244
|
+
const deadline = Date.now() + waitMs;
|
|
10245
|
+
let token = null;
|
|
10246
|
+
for (;; ) {
|
|
10247
|
+
token = ledgerLock.acquire(lock);
|
|
10248
|
+
if (token)
|
|
10249
|
+
break;
|
|
10250
|
+
const observed = lockToken(lock);
|
|
10251
|
+
const age = lockAgeMs(lock);
|
|
10252
|
+
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
10477
10253
|
continue;
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10254
|
+
if (Date.now() >= deadline)
|
|
10255
|
+
return null;
|
|
10256
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
10257
|
+
}
|
|
10258
|
+
try {
|
|
10259
|
+
return fn();
|
|
10260
|
+
} finally {
|
|
10261
|
+
ledgerLock.release(lock, token);
|
|
10262
|
+
}
|
|
10263
|
+
}
|
|
10264
|
+
var sha2562 = (text) => createHash9("sha256").update(text).digest("hex");
|
|
10265
|
+
var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
|
|
10266
|
+
${body}`);
|
|
10267
|
+
var TAIL_BYTES = 64 * 1024;
|
|
10268
|
+
var lastLine = (fd, size) => {
|
|
10269
|
+
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
10270
|
+
const buffer = Buffer.alloc(span);
|
|
10271
|
+
fs7.readSync(fd, buffer, 0, span, size - span);
|
|
10272
|
+
const end = buffer.lastIndexOf(10);
|
|
10273
|
+
if (end < 0) {
|
|
10274
|
+
if (span === size)
|
|
10481
10275
|
return null;
|
|
10482
10276
|
continue;
|
|
10483
10277
|
}
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
frames.push({ kind: "template" });
|
|
10497
|
-
index += 1;
|
|
10498
|
-
continue;
|
|
10499
|
-
}
|
|
10500
|
-
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
10501
|
-
return null;
|
|
10502
|
-
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
10503
|
-
const lineEnd = text.indexOf(`
|
|
10504
|
-
`, index);
|
|
10505
|
-
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
10506
|
-
return null;
|
|
10507
|
-
}
|
|
10508
|
-
if (syntax.js && char === "/" && regexAllowed()) {
|
|
10509
|
-
let end = index + 1;
|
|
10510
|
-
let inClass = false;
|
|
10511
|
-
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
10512
|
-
if (text[end] === `
|
|
10513
|
-
`)
|
|
10278
|
+
let stop = end;
|
|
10279
|
+
let text = "";
|
|
10280
|
+
while (stop >= 0) {
|
|
10281
|
+
const start = buffer.lastIndexOf(10, stop - 1);
|
|
10282
|
+
text = buffer.toString("utf8", start + 1, stop).trim();
|
|
10283
|
+
if (text) {
|
|
10284
|
+
if (start >= 0 || span === size)
|
|
10285
|
+
return text;
|
|
10286
|
+
break;
|
|
10287
|
+
}
|
|
10288
|
+
if (start < 0) {
|
|
10289
|
+
if (span === size)
|
|
10514
10290
|
return null;
|
|
10515
|
-
|
|
10516
|
-
end += 1;
|
|
10517
|
-
else if (text[end] === "[")
|
|
10518
|
-
inClass = true;
|
|
10519
|
-
else if (text[end] === "]")
|
|
10520
|
-
inClass = false;
|
|
10521
|
-
end += 1;
|
|
10291
|
+
break;
|
|
10522
10292
|
}
|
|
10523
|
-
|
|
10524
|
-
return null;
|
|
10525
|
-
emitVerbatim(text.slice(index, end + 1));
|
|
10526
|
-
index = end + 1;
|
|
10527
|
-
continue;
|
|
10293
|
+
stop = start;
|
|
10528
10294
|
}
|
|
10529
|
-
if (
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10295
|
+
if (span === size)
|
|
10296
|
+
return text || null;
|
|
10297
|
+
}
|
|
10298
|
+
return null;
|
|
10299
|
+
};
|
|
10300
|
+
var tighten = (fd) => {
|
|
10301
|
+
try {
|
|
10302
|
+
if ((fs7.fstatSync(fd).mode & 63) !== 0)
|
|
10303
|
+
fs7.fchmodSync(fd, 384);
|
|
10304
|
+
} catch {}
|
|
10305
|
+
};
|
|
10306
|
+
function appendLine(file, build, options) {
|
|
10307
|
+
const write = () => {
|
|
10308
|
+
const fd = fs7.openSync(file, "a+", 384);
|
|
10309
|
+
try {
|
|
10310
|
+
if (process.platform !== "win32")
|
|
10311
|
+
tighten(fd);
|
|
10312
|
+
const size = fs7.fstatSync(fd).size;
|
|
10313
|
+
let fence = "";
|
|
10314
|
+
if (size > 0) {
|
|
10315
|
+
const last = Buffer.alloc(1);
|
|
10316
|
+
fs7.readSync(fd, last, 0, 1, size - 1);
|
|
10317
|
+
if (last[0] !== 10)
|
|
10318
|
+
fence = `
|
|
10319
|
+
`;
|
|
10540
10320
|
}
|
|
10321
|
+
const line = build(lastLine(fd, size));
|
|
10322
|
+
if (!line.ok)
|
|
10323
|
+
return line;
|
|
10324
|
+
const bytes = Buffer.from(fence + line.value);
|
|
10325
|
+
const written = fs7.writeSync(fd, bytes);
|
|
10326
|
+
if (written !== bytes.length)
|
|
10327
|
+
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
10328
|
+
return { ok: true, value: undefined };
|
|
10329
|
+
} finally {
|
|
10330
|
+
fs7.closeSync(fd);
|
|
10541
10331
|
}
|
|
10542
|
-
|
|
10543
|
-
|
|
10332
|
+
};
|
|
10333
|
+
try {
|
|
10334
|
+
fs7.mkdirSync(path10.dirname(file), { recursive: true });
|
|
10335
|
+
if (!options.forceLock && !onNetworkFs(path10.dirname(file)))
|
|
10336
|
+
return write();
|
|
10337
|
+
const locked = withLedgerLock(file, write);
|
|
10338
|
+
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
10339
|
+
} catch (error) {
|
|
10340
|
+
return err("unavailable", `cannot append to ${file}: ${error.message}`);
|
|
10544
10341
|
}
|
|
10545
|
-
if (frames.length !== 1)
|
|
10546
|
-
return null;
|
|
10547
|
-
newline();
|
|
10548
|
-
return out;
|
|
10549
10342
|
}
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
|
-
|
|
10558
|
-
|
|
10343
|
+
var sealed = (row, id, at, previous) => {
|
|
10344
|
+
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
10345
|
+
const prevHash = previous === null ? null : sha2562(previous);
|
|
10346
|
+
const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
|
|
10347
|
+
const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
|
|
10348
|
+
return { full, line: `${JSON.stringify(full)}
|
|
10349
|
+
` };
|
|
10350
|
+
};
|
|
10351
|
+
function appendRaw(cwd, row, options) {
|
|
10352
|
+
const file = ledgerPath(cwd);
|
|
10353
|
+
if (!file.ok)
|
|
10354
|
+
return file;
|
|
10355
|
+
const id = newId2();
|
|
10356
|
+
const at = (options.now ?? new Date).toISOString();
|
|
10357
|
+
let full = null;
|
|
10358
|
+
const written = appendLine(file.value, (previous) => {
|
|
10359
|
+
const next = sealed(row, id, at, previous);
|
|
10360
|
+
const bytes = Buffer.byteLength(next.line);
|
|
10361
|
+
if (bytes > MAX_LINE_BYTES)
|
|
10362
|
+
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>`);
|
|
10363
|
+
full = next.full;
|
|
10364
|
+
return { ok: true, value: next.line };
|
|
10365
|
+
}, options);
|
|
10366
|
+
return written.ok ? { ok: true, value: full } : written;
|
|
10559
10367
|
}
|
|
10560
|
-
function
|
|
10561
|
-
|
|
10368
|
+
function observedRowBytes(row) {
|
|
10369
|
+
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
10370
|
+
return Buffer.byteLength(sample.line);
|
|
10562
10371
|
}
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
if (bytes.includes(0))
|
|
10566
|
-
return null;
|
|
10567
|
-
try {
|
|
10568
|
-
return decoder.decode(bytes);
|
|
10569
|
-
} catch {
|
|
10570
|
-
return null;
|
|
10571
|
-
}
|
|
10372
|
+
function appendObserved(cwd, row, options = {}) {
|
|
10373
|
+
return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
|
|
10572
10374
|
}
|
|
10573
|
-
function
|
|
10574
|
-
|
|
10575
|
-
const right = textOf(after);
|
|
10576
|
-
if (left === null || right === null)
|
|
10577
|
-
return false;
|
|
10578
|
-
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
10579
|
-
return false;
|
|
10580
|
-
const a = substanceOf(file, left);
|
|
10581
|
-
return a !== null && a === substanceOf(file, right);
|
|
10375
|
+
function appendHookObserved(cwd, row) {
|
|
10376
|
+
return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
|
|
10582
10377
|
}
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
var
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10378
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10379
|
+
var str = (value) => typeof value === "string" ? value : null;
|
|
10380
|
+
var normalizeActor = (value) => {
|
|
10381
|
+
const actor = isRecord2(value) ? value : {};
|
|
10382
|
+
return {
|
|
10383
|
+
host: str(actor.host) ?? "unknown",
|
|
10384
|
+
session: str(actor.session),
|
|
10385
|
+
agentId: str(actor.agentId)
|
|
10386
|
+
};
|
|
10589
10387
|
};
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
"
|
|
10598
|
-
|
|
10599
|
-
"
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
"gates",
|
|
10603
|
-
"policy",
|
|
10604
|
-
"policies",
|
|
10605
|
-
"hook",
|
|
10606
|
-
"hooks",
|
|
10607
|
-
"husky",
|
|
10608
|
-
"lefthook",
|
|
10609
|
-
"release",
|
|
10610
|
-
"releases",
|
|
10611
|
-
"migration",
|
|
10612
|
-
"migrations",
|
|
10613
|
-
"migrate",
|
|
10614
|
-
"workflows"
|
|
10615
|
-
]);
|
|
10616
|
-
var RISK_FILES = new Set([
|
|
10617
|
-
".gitlab-ci.yml",
|
|
10618
|
-
"Jenkinsfile",
|
|
10619
|
-
"azure-pipelines.yml",
|
|
10620
|
-
".pre-commit-config.yaml",
|
|
10621
|
-
".workit/risk-paths"
|
|
10622
|
-
]);
|
|
10623
|
-
var RISK_NAMES = new Set([".gitattributes"]);
|
|
10624
|
-
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
10625
|
-
var LOCKFILES = new Set([
|
|
10626
|
-
"bun.lock",
|
|
10627
|
-
"bun.lockb",
|
|
10628
|
-
"package-lock.json",
|
|
10629
|
-
"npm-shrinkwrap.json",
|
|
10630
|
-
"yarn.lock",
|
|
10631
|
-
"pnpm-lock.yaml",
|
|
10632
|
-
"Cargo.lock",
|
|
10633
|
-
"go.sum",
|
|
10634
|
-
"poetry.lock",
|
|
10635
|
-
"uv.lock",
|
|
10636
|
-
"Pipfile.lock",
|
|
10637
|
-
"Gemfile.lock",
|
|
10638
|
-
"composer.lock"
|
|
10639
|
-
]);
|
|
10640
|
-
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
|
|
10641
|
-
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
10642
|
-
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
10643
|
-
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
|
|
10644
|
-
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
10645
|
-
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
10646
|
-
function kindOf(file) {
|
|
10647
|
-
if (file.binary)
|
|
10648
|
-
return "behavior";
|
|
10649
|
-
if (file.generated)
|
|
10650
|
-
return "generated";
|
|
10651
|
-
if (LOCKFILES.has(basename(file.path)))
|
|
10652
|
-
return "lockfile";
|
|
10653
|
-
if (isDoc(file.path))
|
|
10654
|
-
return "doc";
|
|
10655
|
-
if (file.cosmetic)
|
|
10656
|
-
return "cosmetic";
|
|
10657
|
-
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
10388
|
+
function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
|
|
10389
|
+
if (by.type !== target.type)
|
|
10390
|
+
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
10391
|
+
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
10392
|
+
return "only the session that wrote a row can supersede it";
|
|
10393
|
+
if (!onBranch(target))
|
|
10394
|
+
return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
|
|
10395
|
+
if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
|
|
10396
|
+
return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
|
|
10397
|
+
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
10398
|
+
return "a self verdict cannot replace an independent verdict";
|
|
10399
|
+
return null;
|
|
10658
10400
|
}
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
|
|
10665
|
-
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10401
|
+
function readLedger(cwd, options = {}) {
|
|
10402
|
+
const file = ledgerPath(cwd);
|
|
10403
|
+
if (!file.ok)
|
|
10404
|
+
return file;
|
|
10405
|
+
let text = "";
|
|
10406
|
+
let stat = null;
|
|
10407
|
+
try {
|
|
10408
|
+
stat = fs7.statSync(file.value);
|
|
10409
|
+
text = fs7.readFileSync(file.value, "utf8");
|
|
10410
|
+
} catch (error) {
|
|
10411
|
+
if (error.code === "ENOENT")
|
|
10412
|
+
return {
|
|
10413
|
+
ok: true,
|
|
10414
|
+
value: {
|
|
10415
|
+
path: file.value,
|
|
10416
|
+
rows: [],
|
|
10417
|
+
skipped: 0,
|
|
10418
|
+
integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
|
|
10419
|
+
}
|
|
10420
|
+
};
|
|
10421
|
+
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
10422
|
+
}
|
|
10423
|
+
const rows = [];
|
|
10424
|
+
let skipped = 0;
|
|
10425
|
+
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
10426
|
+
const earlier = new Set([null]);
|
|
10427
|
+
let chainStarted = false;
|
|
10428
|
+
const ids = new Set;
|
|
10429
|
+
for (const raw of text.split(`
|
|
10430
|
+
`)) {
|
|
10431
|
+
const trimmed = raw.trim();
|
|
10432
|
+
if (!trimmed)
|
|
10433
|
+
continue;
|
|
10434
|
+
const linked = (prevHash) => earlier.has(prevHash);
|
|
10435
|
+
if (options.integrity)
|
|
10436
|
+
earlier.add(sha2562(trimmed));
|
|
10437
|
+
let parsed;
|
|
10438
|
+
try {
|
|
10439
|
+
parsed = JSON.parse(trimmed);
|
|
10440
|
+
} catch {
|
|
10441
|
+
skipped += 1;
|
|
10442
|
+
continue;
|
|
10675
10443
|
}
|
|
10676
|
-
|
|
10677
|
-
|
|
10444
|
+
if (!isRecord2(parsed) || !str(parsed.id) || !str(parsed.type) || !str(parsed.at)) {
|
|
10445
|
+
skipped += 1;
|
|
10446
|
+
continue;
|
|
10447
|
+
}
|
|
10448
|
+
let rowIntegrity = options.integrity ? "verified" : undefined;
|
|
10449
|
+
let reason = null;
|
|
10450
|
+
if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
|
|
10451
|
+
chainStarted = true;
|
|
10452
|
+
const { rowHash, ...body } = parsed;
|
|
10453
|
+
const prevHash = str(parsed.prevHash);
|
|
10454
|
+
if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
|
|
10455
|
+
reason = "hash_mismatch";
|
|
10456
|
+
else if (!linked(prevHash))
|
|
10457
|
+
reason = "broken_link";
|
|
10458
|
+
} else if (chainStarted)
|
|
10459
|
+
reason = "unsigned";
|
|
10460
|
+
else
|
|
10461
|
+
rowIntegrity = "legacy";
|
|
10462
|
+
if (options.integrity && !reason && ids.has(parsed.id))
|
|
10463
|
+
reason = "duplicate_id";
|
|
10464
|
+
if (options.integrity)
|
|
10465
|
+
ids.add(parsed.id);
|
|
10466
|
+
if (reason)
|
|
10467
|
+
rowIntegrity = "unverified";
|
|
10468
|
+
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
10469
|
+
const row = {
|
|
10470
|
+
...parsed,
|
|
10471
|
+
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
10472
|
+
id: parsed.id,
|
|
10473
|
+
at: parsed.at,
|
|
10474
|
+
type: parsed.type,
|
|
10475
|
+
actor: normalizeActor(parsed.actor),
|
|
10476
|
+
branch: str(parsed.branch),
|
|
10477
|
+
head: str(parsed.head),
|
|
10478
|
+
base: str(parsed.base),
|
|
10479
|
+
baseSha: str(parsed.baseSha),
|
|
10480
|
+
patchId: str(parsed.patchId),
|
|
10481
|
+
diffHash: str(parsed.diffHash),
|
|
10482
|
+
tree: str(parsed.tree),
|
|
10483
|
+
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
10484
|
+
...pr === undefined ? {} : { pr },
|
|
10485
|
+
...str(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
10486
|
+
seq: rows.length + 1,
|
|
10487
|
+
superseded: false,
|
|
10488
|
+
supersedeIgnored: false,
|
|
10489
|
+
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
10490
|
+
...reason ? { integrityReason: reason } : {}
|
|
10491
|
+
};
|
|
10492
|
+
rows.push(row);
|
|
10493
|
+
if (!options.integrity)
|
|
10494
|
+
continue;
|
|
10495
|
+
if (rowIntegrity === "verified")
|
|
10496
|
+
integrity.chained += 1;
|
|
10497
|
+
else if (rowIntegrity === "legacy")
|
|
10498
|
+
integrity.legacy += 1;
|
|
10499
|
+
else if (reason)
|
|
10500
|
+
integrity.unverified.push({
|
|
10501
|
+
seq: row.seq,
|
|
10502
|
+
id: row.id,
|
|
10503
|
+
type: row.type,
|
|
10504
|
+
branch: row.branch,
|
|
10505
|
+
reason
|
|
10506
|
+
});
|
|
10678
10507
|
}
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
files: behavior.length,
|
|
10707
|
-
riskFiles
|
|
10708
|
-
});
|
|
10709
|
-
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
10710
|
-
if (input.riskTier === "high")
|
|
10711
|
-
return result("full", ["the task judgment records high risk"]);
|
|
10712
|
-
if (riskFiles.length)
|
|
10713
|
-
return result("full", [
|
|
10714
|
-
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
10715
|
-
]);
|
|
10716
|
-
if (!behavior.length)
|
|
10717
|
-
return result("none", [
|
|
10718
|
-
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
10719
|
-
]);
|
|
10720
|
-
if (lines >= fullMinLines)
|
|
10721
|
-
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
10722
|
-
if (lines > selfMaxLines)
|
|
10723
|
-
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
10724
|
-
if (behavior.length > selfMaxFiles)
|
|
10725
|
-
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
10726
|
-
if (opaque.length)
|
|
10727
|
-
return result("focused", [
|
|
10728
|
-
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
10729
|
-
]);
|
|
10730
|
-
return result("self", [
|
|
10731
|
-
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
10732
|
-
]);
|
|
10508
|
+
const scopes = new Map;
|
|
10509
|
+
const scopeOf = (branch) => {
|
|
10510
|
+
let scope = scopes.get(branch);
|
|
10511
|
+
if (!scope)
|
|
10512
|
+
scopes.set(branch, scope = branchScope(cwd, branch));
|
|
10513
|
+
return scope;
|
|
10514
|
+
};
|
|
10515
|
+
const byId = new Map;
|
|
10516
|
+
for (const row of rows) {
|
|
10517
|
+
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
10518
|
+
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
10519
|
+
target.superseded = true;
|
|
10520
|
+
else if (row.supersedes)
|
|
10521
|
+
row.supersedeIgnored = true;
|
|
10522
|
+
if (!byId.has(row.id))
|
|
10523
|
+
byId.set(row.id, row);
|
|
10524
|
+
}
|
|
10525
|
+
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
10526
|
+
tamperCache.set(file.value, {
|
|
10527
|
+
size: stat.size,
|
|
10528
|
+
stamp: fileStamp(stat),
|
|
10529
|
+
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
10530
|
+
});
|
|
10531
|
+
return {
|
|
10532
|
+
ok: true,
|
|
10533
|
+
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
10534
|
+
};
|
|
10733
10535
|
}
|
|
10734
|
-
|
|
10536
|
+
var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
10537
|
+
var tamperCache = new Map;
|
|
10538
|
+
var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
10539
|
+
function ledgerTampering(cwd) {
|
|
10540
|
+
const file = ledgerPath(cwd);
|
|
10541
|
+
if (!file.ok)
|
|
10542
|
+
return [];
|
|
10735
10543
|
try {
|
|
10736
|
-
const
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
|
|
10740
|
-
if (hasOpaqueLineBreak(text))
|
|
10741
|
-
return { path: file, added: 0, deleted: 0, binary: true };
|
|
10742
|
-
const lines = text.split(`
|
|
10743
|
-
`).length - (text.endsWith(`
|
|
10744
|
-
`) ? 1 : 0);
|
|
10745
|
-
return { path: file, added: lines, deleted: 0 };
|
|
10544
|
+
const stat = fs7.statSync(file.value);
|
|
10545
|
+
const cached = tamperCache.get(file.value);
|
|
10546
|
+
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
10547
|
+
return cached.breaks;
|
|
10746
10548
|
} catch {
|
|
10747
|
-
return
|
|
10549
|
+
return [];
|
|
10748
10550
|
}
|
|
10551
|
+
const read = readLedger(cwd, { integrity: true });
|
|
10552
|
+
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
10749
10553
|
}
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10554
|
+
function tamperedUnblock(cwd) {
|
|
10555
|
+
const file = ledgerPath(cwd);
|
|
10556
|
+
const where = file.ok ? file.value : "the ledger file";
|
|
10557
|
+
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`;
|
|
10558
|
+
}
|
|
10559
|
+
var 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)}`;
|
|
10560
|
+
function verdictBasis(verdict, current) {
|
|
10561
|
+
if (verdict.head && verdict.head === current.head)
|
|
10562
|
+
return "fresh";
|
|
10563
|
+
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
10564
|
+
return "carried";
|
|
10565
|
+
return "stale";
|
|
10566
|
+
}
|
|
10567
|
+
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
10568
|
+
function trailerSessions(cwd, base, head) {
|
|
10569
|
+
if (!base || !head || !safeRef2(base) || !safeRef2(head))
|
|
10570
|
+
return [];
|
|
10571
|
+
const out = git3(cwd, [
|
|
10572
|
+
"log",
|
|
10573
|
+
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
10574
|
+
`${base}..${head}`
|
|
10575
|
+
]);
|
|
10576
|
+
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
10577
|
+
}
|
|
10578
|
+
var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
10579
|
+
function branchScope(cwd, branch) {
|
|
10580
|
+
const renamedAt = new Map;
|
|
10581
|
+
let since = Number.NEGATIVE_INFINITY;
|
|
10582
|
+
if (safeRef2(branch)) {
|
|
10583
|
+
const log = git3(cwd, [
|
|
10584
|
+
"log",
|
|
10585
|
+
"-g",
|
|
10586
|
+
"--date=unix",
|
|
10587
|
+
"--format=%gd%x09%gs",
|
|
10588
|
+
`refs/heads/${branch}`,
|
|
10589
|
+
"--"
|
|
10590
|
+
]);
|
|
10591
|
+
for (const line of (log ?? "").split(`
|
|
10592
|
+
`)) {
|
|
10593
|
+
const [selector = "", subject = ""] = line.split("\t");
|
|
10594
|
+
const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
|
|
10595
|
+
if (!Number.isFinite(seconds))
|
|
10596
|
+
continue;
|
|
10597
|
+
since = seconds * 1000;
|
|
10598
|
+
const match = RENAMED.exec(subject);
|
|
10599
|
+
if (!match || match[1] === branch)
|
|
10600
|
+
continue;
|
|
10601
|
+
const until = (seconds + 1) * 1000;
|
|
10602
|
+
renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
|
|
10603
|
+
}
|
|
10604
|
+
}
|
|
10605
|
+
return (row) => {
|
|
10606
|
+
if (row.branch === branch)
|
|
10607
|
+
return true;
|
|
10608
|
+
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
10609
|
+
if (until === undefined)
|
|
10610
|
+
return false;
|
|
10611
|
+
const at = Date.parse(row.at);
|
|
10612
|
+
return at >= since && at <= until;
|
|
10613
|
+
};
|
|
10614
|
+
}
|
|
10615
|
+
var renameAwareBranch = (scopeOf, branch) => {
|
|
10616
|
+
return (target) => {
|
|
10617
|
+
const own = typeof branch === "string" ? branch : null;
|
|
10618
|
+
const theirs = typeof target.branch === "string" ? target.branch : null;
|
|
10619
|
+
if (own === theirs)
|
|
10620
|
+
return true;
|
|
10621
|
+
if (own === null || theirs === null || typeof target.at !== "string")
|
|
10622
|
+
return false;
|
|
10623
|
+
return scopeOf(own)({ branch: theirs, at: target.at });
|
|
10624
|
+
};
|
|
10625
|
+
};
|
|
10626
|
+
function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
|
|
10627
|
+
const sessions = new Set;
|
|
10628
|
+
for (const row of rows) {
|
|
10629
|
+
if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
10630
|
+
continue;
|
|
10631
|
+
const session = str(row.session) ?? row.actor.session;
|
|
10632
|
+
if (session)
|
|
10633
|
+
sessions.add(session);
|
|
10634
|
+
}
|
|
10635
|
+
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
10636
|
+
sessions.add(session);
|
|
10637
|
+
return sessions;
|
|
10638
|
+
}
|
|
10639
|
+
var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
|
|
10640
|
+
var authoredBy = (authors, session) => {
|
|
10641
|
+
if (authors.has(session))
|
|
10642
|
+
return true;
|
|
10643
|
+
for (const author of authors)
|
|
10644
|
+
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
10645
|
+
return true;
|
|
10646
|
+
return false;
|
|
10647
|
+
};
|
|
10648
|
+
var TRUST_REASONS = new Set([
|
|
10649
|
+
"unsigned",
|
|
10650
|
+
"hash_mismatch",
|
|
10651
|
+
"broken_link",
|
|
10652
|
+
"duplicate_id",
|
|
10653
|
+
"unissued_session",
|
|
10654
|
+
"unbound_session"
|
|
10655
|
+
]);
|
|
10656
|
+
var SHELL_VOUCHED_HOSTS = new Set([
|
|
10657
|
+
"cli",
|
|
10658
|
+
"opencode",
|
|
10659
|
+
"pi",
|
|
10660
|
+
"codex_cli",
|
|
10661
|
+
"codex_desktop"
|
|
10662
|
+
]);
|
|
10663
|
+
function sessionTrust(cwd, row) {
|
|
10664
|
+
const session = row.actor.session;
|
|
10665
|
+
if (!session || row.self === true)
|
|
10755
10666
|
return null;
|
|
10756
|
-
const
|
|
10757
|
-
const
|
|
10758
|
-
|
|
10667
|
+
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
10668
|
+
const host = row.actor.host;
|
|
10669
|
+
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
10670
|
+
if (!issuedOnly)
|
|
10759
10671
|
return null;
|
|
10760
|
-
const
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
|
|
10672
|
+
const binding = issuedBinding(cwd, session);
|
|
10673
|
+
if (binding === null)
|
|
10674
|
+
return "unissued_session";
|
|
10675
|
+
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
10676
|
+
}
|
|
10677
|
+
function trustReasons(cwd, row) {
|
|
10678
|
+
if (row.integrity === "unverified")
|
|
10679
|
+
return [row.integrityReason ?? "unsigned"];
|
|
10680
|
+
if (row.integrity !== "verified")
|
|
10681
|
+
return ["unsigned"];
|
|
10682
|
+
const session = sessionTrust(cwd, row);
|
|
10683
|
+
return session ? [session] : [];
|
|
10684
|
+
}
|
|
10685
|
+
function withIntegrity(cwd, rows) {
|
|
10686
|
+
if (rows.every((row) => row.integrity !== undefined))
|
|
10687
|
+
return rows;
|
|
10688
|
+
const read = readLedger(cwd, { integrity: true });
|
|
10689
|
+
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
10690
|
+
return rows.map((row) => {
|
|
10691
|
+
if (row.integrity !== undefined)
|
|
10692
|
+
return row;
|
|
10693
|
+
const fresh = bySeq.get(row.seq);
|
|
10694
|
+
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
10695
|
+
});
|
|
10696
|
+
}
|
|
10697
|
+
var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
|
|
10698
|
+
var independenceReasons = (row, authors) => {
|
|
10699
|
+
const reasons = [];
|
|
10700
|
+
if (!row.actor.session)
|
|
10701
|
+
reasons.push("no_session");
|
|
10702
|
+
else if (authoredBy(authors, row.actor.session))
|
|
10703
|
+
reasons.push("author_session");
|
|
10704
|
+
if (row.self === true && !reasons.includes("no_session"))
|
|
10705
|
+
reasons.push("self");
|
|
10706
|
+
return reasons;
|
|
10707
|
+
};
|
|
10708
|
+
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
10709
|
+
let effective = null;
|
|
10710
|
+
for (const row of rows) {
|
|
10711
|
+
if (!effective) {
|
|
10712
|
+
effective = row;
|
|
10713
|
+
continue;
|
|
10714
|
+
}
|
|
10715
|
+
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
10716
|
+
continue;
|
|
10717
|
+
const rowIndependent = reasonsOf(row).length === 0;
|
|
10718
|
+
const effIndependent = reasonsOf(effective).length === 0;
|
|
10719
|
+
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
10720
|
+
continue;
|
|
10721
|
+
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
10722
|
+
continue;
|
|
10723
|
+
effective = row;
|
|
10775
10724
|
}
|
|
10776
|
-
return
|
|
10725
|
+
return effective;
|
|
10777
10726
|
}
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10781
|
-
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
|
|
10785
|
-
const
|
|
10786
|
-
|
|
10787
|
-
|
|
10727
|
+
var restackExact = new WeakMap;
|
|
10728
|
+
function restackCarries(exactIn, rows, branch, from, to) {
|
|
10729
|
+
if (!from || !to)
|
|
10730
|
+
return false;
|
|
10731
|
+
const exact = (row, fromHead, head) => {
|
|
10732
|
+
if (exactIn === null)
|
|
10733
|
+
return true;
|
|
10734
|
+
const cwd = exactIn;
|
|
10735
|
+
let known = restackExact.get(row);
|
|
10736
|
+
if (known === undefined) {
|
|
10737
|
+
known = typeof row.fromBase === "string" && typeof row.toBase === "string" && sameOwnChange(cwd, {
|
|
10738
|
+
from: fromHead,
|
|
10739
|
+
to: head,
|
|
10740
|
+
fromBase: row.fromBase,
|
|
10741
|
+
toBase: row.toBase
|
|
10742
|
+
});
|
|
10743
|
+
restackExact.set(row, known);
|
|
10744
|
+
}
|
|
10745
|
+
return known;
|
|
10746
|
+
};
|
|
10747
|
+
const edges = new Map;
|
|
10748
|
+
for (const row of rows)
|
|
10749
|
+
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))
|
|
10750
|
+
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
10751
|
+
const seen = new Set([from]);
|
|
10752
|
+
const queue = [from];
|
|
10753
|
+
while (queue.length) {
|
|
10754
|
+
const next = queue.shift();
|
|
10755
|
+
if (next === to)
|
|
10756
|
+
return true;
|
|
10757
|
+
for (const target of edges.get(next) ?? [])
|
|
10758
|
+
if (!seen.has(target)) {
|
|
10759
|
+
seen.add(target);
|
|
10760
|
+
queue.push(target);
|
|
10761
|
+
}
|
|
10788
10762
|
}
|
|
10789
|
-
return
|
|
10790
|
-
}
|
|
10791
|
-
var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
|
|
10792
|
-
function laneFiles(cwd, fork, head) {
|
|
10793
|
-
const stats = diffNumstat(cwd, fork, head);
|
|
10794
|
-
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
10795
|
-
if (!stats || !cosmetic)
|
|
10796
|
-
return null;
|
|
10797
|
-
const files = [
|
|
10798
|
-
...stats.map((entry) => ({
|
|
10799
|
-
...entry,
|
|
10800
|
-
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
10801
|
-
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
10802
|
-
})),
|
|
10803
|
-
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
10804
|
-
];
|
|
10805
|
-
const generated = generatedAt(cwd, fork, files.map((entry) => entry.path));
|
|
10806
|
-
return files.map((entry) => ({
|
|
10807
|
-
...entry,
|
|
10808
|
-
generated: generated.has(entry.path)
|
|
10809
|
-
}));
|
|
10763
|
+
return false;
|
|
10810
10764
|
}
|
|
10811
|
-
function
|
|
10812
|
-
const
|
|
10813
|
-
const
|
|
10814
|
-
const
|
|
10815
|
-
const
|
|
10816
|
-
const
|
|
10817
|
-
const
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10765
|
+
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
10766
|
+
const rows = withIntegrity(cwd, ledgerRows);
|
|
10767
|
+
const head = branchHead(cwd, branch);
|
|
10768
|
+
const fallbackBase = defaultBase(cwd);
|
|
10769
|
+
const scope = branchScope(cwd, branch);
|
|
10770
|
+
const keyCache = new Map;
|
|
10771
|
+
const keysAt = (base, at) => {
|
|
10772
|
+
if (!base || !at)
|
|
10773
|
+
return { patchId: null, diffHash: null };
|
|
10774
|
+
const cacheKey = `${base}\x00${at}`;
|
|
10775
|
+
let key = keyCache.get(cacheKey);
|
|
10776
|
+
if (!key) {
|
|
10777
|
+
key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
|
|
10778
|
+
keyCache.set(cacheKey, key);
|
|
10779
|
+
}
|
|
10780
|
+
return key;
|
|
10781
|
+
};
|
|
10782
|
+
const keyFor = (base) => keysAt(base, head);
|
|
10783
|
+
const driftCache = new Map;
|
|
10784
|
+
const driftOf = (row) => {
|
|
10785
|
+
if (driftCache.has(row))
|
|
10786
|
+
return driftCache.get(row) ?? null;
|
|
10787
|
+
let drift = null;
|
|
10788
|
+
const toBase = row.base ?? fallbackBase;
|
|
10789
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
10790
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
10791
|
+
if (row.head && head && row.head !== head && toBaseSha && fromBase && !untrusted(reasonsOf(row)))
|
|
10792
|
+
drift = verdictDrift(cwd, {
|
|
10793
|
+
from: row.head,
|
|
10794
|
+
to: head,
|
|
10795
|
+
fromBase,
|
|
10796
|
+
toBase: toBaseSha,
|
|
10797
|
+
kind: typeof row.kind === "string" ? row.kind : "review"
|
|
10798
|
+
});
|
|
10799
|
+
driftCache.set(row, drift);
|
|
10800
|
+
return drift;
|
|
10801
|
+
};
|
|
10802
|
+
const exactCache = new Map;
|
|
10803
|
+
const exactCarry = (row) => {
|
|
10804
|
+
if (FAILING.has(String(row.result)))
|
|
10805
|
+
return true;
|
|
10806
|
+
let exact = exactCache.get(row);
|
|
10807
|
+
if (exact === undefined) {
|
|
10808
|
+
const toBase = row.base ?? fallbackBase;
|
|
10809
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
10810
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
10811
|
+
exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
|
|
10812
|
+
exactCache.set(row, exact);
|
|
10813
|
+
}
|
|
10814
|
+
return exact;
|
|
10815
|
+
};
|
|
10816
|
+
const keyBasis = (row) => {
|
|
10817
|
+
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
10818
|
+
return basis === "carried" && !exactCarry(row) ? "stale" : basis;
|
|
10819
|
+
};
|
|
10820
|
+
const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
|
|
10821
|
+
const carryOf = (row) => {
|
|
10822
|
+
if (keyBasis(row) !== "stale")
|
|
10823
|
+
return null;
|
|
10824
|
+
if (restacked(row))
|
|
10825
|
+
return null;
|
|
10826
|
+
const drift = driftOf(row);
|
|
10827
|
+
return drift?.carries ? { rule: drift.rule, since: drift.since, files: drift.files } : null;
|
|
10828
|
+
};
|
|
10829
|
+
const basisOf = (row) => {
|
|
10830
|
+
const basis = keyBasis(row);
|
|
10831
|
+
if (basis !== "stale")
|
|
10832
|
+
return basis;
|
|
10833
|
+
return restacked(row) || carryOf(row) ? "carried" : basis;
|
|
10834
|
+
};
|
|
10835
|
+
const sameCode = (failed, row) => {
|
|
10836
|
+
if (failed.head && failed.head === row.head)
|
|
10837
|
+
return true;
|
|
10838
|
+
const base = failed.base ?? fallbackBase;
|
|
10839
|
+
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
10840
|
+
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
10841
|
+
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
|
|
10842
|
+
};
|
|
10843
|
+
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
10844
|
+
const failureAuthorCache = new Map;
|
|
10845
|
+
const failureAuthors = (row) => {
|
|
10846
|
+
let set = failureAuthorCache.get(row.seq);
|
|
10847
|
+
if (!set) {
|
|
10848
|
+
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
10849
|
+
failureAuthorCache.set(row.seq, set);
|
|
10850
|
+
}
|
|
10851
|
+
return set;
|
|
10852
|
+
};
|
|
10853
|
+
const reasonCache = new Map;
|
|
10854
|
+
const reasonsOf = (row) => {
|
|
10855
|
+
let reasons = reasonCache.get(row);
|
|
10856
|
+
if (!reasons) {
|
|
10857
|
+
const failing = FAILING.has(String(row.result));
|
|
10858
|
+
reasons = [
|
|
10859
|
+
...trustReasons(cwd, row),
|
|
10860
|
+
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
10861
|
+
];
|
|
10862
|
+
reasonCache.set(row, reasons);
|
|
10863
|
+
}
|
|
10864
|
+
return reasons;
|
|
10865
|
+
};
|
|
10866
|
+
const retired = new Set;
|
|
10867
|
+
for (const row of rows)
|
|
10868
|
+
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
10869
|
+
retired.add(row.supersedes);
|
|
10870
|
+
const byKind = new Map;
|
|
10871
|
+
for (const row of rows)
|
|
10872
|
+
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
10873
|
+
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
10874
|
+
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
10875
|
+
}
|
|
10876
|
+
const verdicts = [];
|
|
10877
|
+
const warnings = [];
|
|
10878
|
+
for (const [kind, list] of byKind) {
|
|
10879
|
+
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
10880
|
+
if (!row)
|
|
10881
|
+
continue;
|
|
10882
|
+
const basis = basisOf(row);
|
|
10883
|
+
const current = basis !== "stale";
|
|
10884
|
+
const independence = reasonsOf(row);
|
|
10885
|
+
const reasons = [
|
|
10886
|
+
...current ? [] : ["stale"],
|
|
10887
|
+
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
10888
|
+
...independence
|
|
10889
|
+
];
|
|
10890
|
+
verdicts.push({
|
|
10891
|
+
kind,
|
|
10892
|
+
basis,
|
|
10893
|
+
current,
|
|
10894
|
+
independent: independence.length === 0,
|
|
10895
|
+
accepted: reasons.length === 0,
|
|
10896
|
+
reasons,
|
|
10897
|
+
carry: basis === "carried" ? carryOf(row) : null,
|
|
10898
|
+
verdict: row
|
|
10899
|
+
});
|
|
10900
|
+
if (reasons.length === 0) {
|
|
10901
|
+
for (const later of list)
|
|
10902
|
+
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
10903
|
+
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`);
|
|
10904
|
+
}
|
|
10832
10905
|
}
|
|
10833
|
-
|
|
10906
|
+
for (const row of rows)
|
|
10907
|
+
if (row.integrity === "unverified" && scope(row))
|
|
10908
|
+
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)`);
|
|
10909
|
+
const tampered = ledgerTampering(cwd);
|
|
10910
|
+
if (tampered.length) {
|
|
10911
|
+
warnings.push(tamperedWarning(cwd, tampered));
|
|
10912
|
+
for (const entry of verdicts) {
|
|
10913
|
+
entry.accepted = false;
|
|
10914
|
+
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
10915
|
+
}
|
|
10916
|
+
}
|
|
10917
|
+
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
10918
|
+
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
10919
|
+
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
10920
|
+
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
10921
|
+
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
10922
|
+
accepted: false,
|
|
10923
|
+
verdict: failing?.verdict ?? null,
|
|
10924
|
+
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
10925
|
+
};
|
|
10926
|
+
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))));
|
|
10927
|
+
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
10928
|
+
const carryEntry = verdicts.find((entry) => entry.verdict === accepted.verdict && entry.accepted) ?? newestCurrent;
|
|
10929
|
+
const lastPass = accepted.accepted || failing || tampered.length ? undefined : verdicts.findLast((entry) => !entry.current && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
10930
|
+
const drift = lastPass ? driftOf(lastPass.verdict) : null;
|
|
10931
|
+
const reverify = drift && !drift.carries && drift.focused && drift.files ? { scope: "focused", since: drift.since, files: drift.files } : null;
|
|
10834
10932
|
return {
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
base,
|
|
10841
|
-
|
|
10933
|
+
branch,
|
|
10934
|
+
head,
|
|
10935
|
+
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
10936
|
+
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
10937
|
+
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
10938
|
+
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
10939
|
+
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
10940
|
+
carry: carryEntry?.carry ?? null,
|
|
10941
|
+
reverify,
|
|
10942
|
+
accepted,
|
|
10943
|
+
verdicts,
|
|
10944
|
+
authors: [...authors].toSorted(),
|
|
10945
|
+
warnings
|
|
10842
10946
|
};
|
|
10843
10947
|
}
|
|
10844
|
-
var
|
|
10948
|
+
var fromLead = (row, leads) => leads === null || row.actor.session !== null && leads.includes(row.actor.session);
|
|
10949
|
+
function activeStanding(rows, fanout, leads = null) {
|
|
10950
|
+
const active = [];
|
|
10951
|
+
for (const row of rows) {
|
|
10952
|
+
if (row.fanout !== fanout || !fromLead(row, leads))
|
|
10953
|
+
continue;
|
|
10954
|
+
if (row.type === "standing" && typeof row.what === "string" && row.what.trim()) {
|
|
10955
|
+
if (!row.superseded)
|
|
10956
|
+
active.push(row);
|
|
10957
|
+
} else if (row.type === "standing.cleared") {
|
|
10958
|
+
const target = str(row.target);
|
|
10959
|
+
if (target === null)
|
|
10960
|
+
active.length = 0;
|
|
10961
|
+
else {
|
|
10962
|
+
const index = active.findIndex((order) => order.id === target);
|
|
10963
|
+
if (index >= 0)
|
|
10964
|
+
active.splice(index, 1);
|
|
10965
|
+
}
|
|
10966
|
+
}
|
|
10967
|
+
}
|
|
10968
|
+
return active;
|
|
10969
|
+
}
|
|
10970
|
+
var OBSERVED_ONLY = new Set([
|
|
10971
|
+
"check",
|
|
10972
|
+
"merge.unverified",
|
|
10973
|
+
"policy.judged",
|
|
10974
|
+
...PR_ROW_TYPES
|
|
10975
|
+
]);
|
|
10845
10976
|
|
|
10846
10977
|
// packages/workit-core/src/core/policy/derive.ts
|
|
10847
10978
|
var RULES = {
|
|
@@ -14612,7 +14743,7 @@ function splitCommand(command) {
|
|
|
14612
14743
|
argv.push(current);
|
|
14613
14744
|
return argv.length ? argv : new Error("empty command");
|
|
14614
14745
|
}
|
|
14615
|
-
var
|
|
14746
|
+
var git4 = (cwd, args) => {
|
|
14616
14747
|
const run = spawnSync9("git", args, {
|
|
14617
14748
|
cwd,
|
|
14618
14749
|
encoding: "utf8",
|
|
@@ -14624,7 +14755,7 @@ var git5 = (cwd, args) => {
|
|
|
14624
14755
|
return value || null;
|
|
14625
14756
|
};
|
|
14626
14757
|
function checkRoot(cwd) {
|
|
14627
|
-
return path18.resolve(
|
|
14758
|
+
return path18.resolve(git4(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
|
|
14628
14759
|
}
|
|
14629
14760
|
var packageManager = (root) => {
|
|
14630
14761
|
const has = (file) => fs13.existsSync(path18.join(root, file));
|
|
@@ -15576,7 +15707,7 @@ Autonomy contract:
|
|
|
15576
15707
|
CLI first: if a step has one right answer, use the \`workit\` verb instead of
|
|
15577
15708
|
hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
|
|
15578
15709
|
else \`npx -y @brainervirus/workit-cli\`).
|
|
15579
|
-
Other checkout: \`workit --cwd <dir>
|
|
15710
|
+
Other checkout: \`workit <verb> --cwd <dir> …\` (verb first: host rules match it); one workit
|
|
15580
15711
|
verb per shell call, never \`cd <dir> && workit\`. zsh-safe: no \`echo =====\`
|
|
15581
15712
|
(\`printf '%s\\n' ---\`), quote globs and URLs, no variable named \`status\` or \`path\`.
|
|
15582
15713
|
Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
|
|
@@ -15588,7 +15719,7 @@ authorizes an effect. Judge work once with
|
|
|
15588
15719
|
\`workit policy assess --judge risk= behavior= product-choice= plan=\`.
|
|
15589
15720
|
Across repositories, bind items to checkout, branch and endpoint, resolve
|
|
15590
15721
|
competing targets before a mutation, and reconcile an uncertain
|
|
15591
|
-
|
|
15722
|
+
effect before a retry.
|
|
15592
15723
|
|
|
15593
15724
|
Mid-task: never silently drop or resume an objective (workit-continue).
|
|
15594
15725
|
|
|
@@ -17994,6 +18125,18 @@ var substitutions = (body) => {
|
|
|
17994
18125
|
};
|
|
17995
18126
|
var PLAIN_POSIX = /[^\s'"\\`$(){},<>|;&#]+/uy;
|
|
17996
18127
|
var PLAIN_POWERSHELL = /[^\s'"`(){}<>|;&#]+/uy;
|
|
18128
|
+
var expandsInQuotes = (body, dialect) => {
|
|
18129
|
+
const escape = dialect === "posix" ? "\\" : "`";
|
|
18130
|
+
for (let index = 0;index < body.length; index++) {
|
|
18131
|
+
const char = body[index];
|
|
18132
|
+
if (char === escape)
|
|
18133
|
+
index++;
|
|
18134
|
+
else if (char === "$" || dialect === "posix" && char === "`")
|
|
18135
|
+
return true;
|
|
18136
|
+
}
|
|
18137
|
+
return false;
|
|
18138
|
+
};
|
|
18139
|
+
var EXPANDING_RUN = /[*?[]/u;
|
|
17997
18140
|
var arithmeticEnd = (command, start) => {
|
|
17998
18141
|
const end = substitutionEnd(command, start);
|
|
17999
18142
|
return command[end - 1] === ")" && end > start + 2 ? end : 0;
|
|
@@ -18012,6 +18155,7 @@ var tokenize = (command, dialect) => {
|
|
|
18012
18155
|
let current = { words: [], redirects: [] };
|
|
18013
18156
|
let word = "";
|
|
18014
18157
|
let inWord = false;
|
|
18158
|
+
let expanding = false;
|
|
18015
18159
|
let pending = null;
|
|
18016
18160
|
const heredocs = [];
|
|
18017
18161
|
const escape = posix ? "\\" : "`";
|
|
@@ -18026,11 +18170,16 @@ var tokenize = (command, dialect) => {
|
|
|
18026
18170
|
heredocs.push({ delimiter: text.replace(/^-/, ""), owner: current });
|
|
18027
18171
|
else if (pending === "herestring")
|
|
18028
18172
|
(current.stdin ??= []).push(text);
|
|
18029
|
-
else if (pending !== "input")
|
|
18173
|
+
else if (pending !== "input") {
|
|
18174
|
+
const at = current.words.length;
|
|
18030
18175
|
current.words.push(...MARKERS.test(word) ? expandBraces(word) : [word]);
|
|
18176
|
+
if (expanding || !posix && word.startsWith("@"))
|
|
18177
|
+
(current.expands ??= []).push(...current.words.slice(at).map((_, i) => at + i));
|
|
18178
|
+
}
|
|
18031
18179
|
pending = null;
|
|
18032
18180
|
word = "";
|
|
18033
18181
|
inWord = false;
|
|
18182
|
+
expanding = false;
|
|
18034
18183
|
};
|
|
18035
18184
|
const flushSegment = () => {
|
|
18036
18185
|
flushWord();
|
|
@@ -18045,6 +18194,7 @@ var tokenize = (command, dialect) => {
|
|
|
18045
18194
|
};
|
|
18046
18195
|
const substitution = (inner, text) => {
|
|
18047
18196
|
(current.subs ??= []).push(inner);
|
|
18197
|
+
expanding = true;
|
|
18048
18198
|
word += text;
|
|
18049
18199
|
inWord = true;
|
|
18050
18200
|
};
|
|
@@ -18055,6 +18205,8 @@ var tokenize = (command, dialect) => {
|
|
|
18055
18205
|
if (run) {
|
|
18056
18206
|
word += run[0];
|
|
18057
18207
|
inWord = true;
|
|
18208
|
+
if (EXPANDING_RUN.test(run[0]))
|
|
18209
|
+
expanding = true;
|
|
18058
18210
|
index += run[0].length - 1;
|
|
18059
18211
|
continue;
|
|
18060
18212
|
}
|
|
@@ -18083,6 +18235,8 @@ var tokenize = (command, dialect) => {
|
|
|
18083
18235
|
const body = command.slice(index + 1, end < 0 ? undefined : end);
|
|
18084
18236
|
if (posix && char === '"' && /\$\(|`/.test(body))
|
|
18085
18237
|
(current.subs ??= []).push(...substitutions(body));
|
|
18238
|
+
if (char === '"' && expandsInQuotes(body, dialect))
|
|
18239
|
+
expanding = true;
|
|
18086
18240
|
word += unquote(body, char, dialect);
|
|
18087
18241
|
inWord = true;
|
|
18088
18242
|
index = end < 0 ? command.length : end;
|
|
@@ -18128,6 +18282,8 @@ var tokenize = (command, dialect) => {
|
|
|
18128
18282
|
if (char === "(" && inWord && word.endsWith("$")) {
|
|
18129
18283
|
word = word.slice(0, -1);
|
|
18130
18284
|
inWord = word !== "";
|
|
18285
|
+
if (!inWord)
|
|
18286
|
+
expanding = false;
|
|
18131
18287
|
}
|
|
18132
18288
|
group(char === "(" ? "open" : "close");
|
|
18133
18289
|
} else if (!posix && (char === "{" || char === "}")) {
|
|
@@ -18181,6 +18337,8 @@ var tokenize = (command, dialect) => {
|
|
|
18181
18337
|
else if (posix && char === "}" && word.includes(OPEN))
|
|
18182
18338
|
word += CLOSE;
|
|
18183
18339
|
else {
|
|
18340
|
+
if (char === "$")
|
|
18341
|
+
expanding = true;
|
|
18184
18342
|
word += char;
|
|
18185
18343
|
inWord = true;
|
|
18186
18344
|
}
|
|
@@ -18235,7 +18393,7 @@ var GIT_VALUE_OPTIONS = new Set([
|
|
|
18235
18393
|
]);
|
|
18236
18394
|
var GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
|
|
18237
18395
|
var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
|
|
18238
|
-
var
|
|
18396
|
+
var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
|
|
18239
18397
|
var MAX_DEPTH = 4;
|
|
18240
18398
|
var MAX_EXPANSIONS = 16;
|
|
18241
18399
|
var isAbsolute = (value) => path22.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
@@ -18262,7 +18420,7 @@ var stripPrefixes = (input) => {
|
|
|
18262
18420
|
const first = words[0];
|
|
18263
18421
|
if (first === undefined)
|
|
18264
18422
|
return { words, env };
|
|
18265
|
-
const assignment =
|
|
18423
|
+
const assignment = ASSIGNMENT.exec(first);
|
|
18266
18424
|
if (assignment) {
|
|
18267
18425
|
if (GIT_CONFIG_ENV.test(assignment[1]))
|
|
18268
18426
|
env[assignment[1]] = assignment[2];
|
|
@@ -18321,10 +18479,10 @@ var assignments = (words, vars) => {
|
|
|
18321
18479
|
while (rest[0]?.startsWith("-"))
|
|
18322
18480
|
rest = rest.slice(1);
|
|
18323
18481
|
}
|
|
18324
|
-
if (rest.length === 0 || !rest.every((word) =>
|
|
18482
|
+
if (rest.length === 0 || !rest.every((word) => ASSIGNMENT.test(word)))
|
|
18325
18483
|
return null;
|
|
18326
18484
|
return rest.map((word) => {
|
|
18327
|
-
const [, name, value] =
|
|
18485
|
+
const [, name, value] = ASSIGNMENT.exec(word);
|
|
18328
18486
|
return [name, expandWord(value, vars)];
|
|
18329
18487
|
});
|
|
18330
18488
|
};
|
|
@@ -18512,7 +18670,7 @@ var splitString = (line) => {
|
|
|
18512
18670
|
return line.slice(index).join(" ").slice(15);
|
|
18513
18671
|
if (/^-S./.test(word))
|
|
18514
18672
|
return [word.slice(2), ...line.slice(index + 1)].join(" ");
|
|
18515
|
-
if (!word.startsWith("-") && !
|
|
18673
|
+
if (!word.startsWith("-") && !ASSIGNMENT.test(word))
|
|
18516
18674
|
return null;
|
|
18517
18675
|
}
|
|
18518
18676
|
return null;
|
|
@@ -18625,8 +18783,17 @@ function walk2(command, dialect, depth, seen) {
|
|
|
18625
18783
|
visit(next, undefined, hops + 1);
|
|
18626
18784
|
};
|
|
18627
18785
|
const wrapped = seen ? null : workitCheckTail(words);
|
|
18628
|
-
if (wrapped)
|
|
18629
|
-
|
|
18786
|
+
if (wrapped) {
|
|
18787
|
+
const outer = dir;
|
|
18788
|
+
if (wrapped.cwd !== null)
|
|
18789
|
+
dir = joinDir(dir, wrapped.cwd);
|
|
18790
|
+
if ("script" in wrapped)
|
|
18791
|
+
nested(wrapped.script, "posix");
|
|
18792
|
+
else
|
|
18793
|
+
runs([wrapped.argv]);
|
|
18794
|
+
dir = outer;
|
|
18795
|
+
return;
|
|
18796
|
+
}
|
|
18630
18797
|
if (CD.has(name)) {
|
|
18631
18798
|
const target = cdTarget(words);
|
|
18632
18799
|
dir = target === null ? os5.homedir() : joinDir(dir, target);
|
|
@@ -18727,14 +18894,14 @@ function workitCheckTail(words) {
|
|
|
18727
18894
|
return null;
|
|
18728
18895
|
} else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
|
|
18729
18896
|
return null;
|
|
18730
|
-
const args = words.slice(at + 1);
|
|
18897
|
+
const { rest: args, cwd } = skipWorkitGlobals(words.slice(at + 1));
|
|
18731
18898
|
if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
|
|
18732
18899
|
return null;
|
|
18733
18900
|
const split = args.indexOf("--");
|
|
18734
18901
|
const tail = split < 0 ? [] : args.slice(split + 1);
|
|
18735
18902
|
if (!tail.length)
|
|
18736
18903
|
return null;
|
|
18737
|
-
return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
|
|
18904
|
+
return args.slice(0, split).includes("--shell") ? { script: tail.join(" "), cwd } : { argv: tail, cwd };
|
|
18738
18905
|
}
|
|
18739
18906
|
function simpleCommand(command, dialect = "posix", depth = 0) {
|
|
18740
18907
|
let segments;
|
|
@@ -18795,7 +18962,21 @@ function skipWorkitGlobals(args) {
|
|
|
18795
18962
|
} else if (args[at] !== "--json")
|
|
18796
18963
|
break;
|
|
18797
18964
|
}
|
|
18798
|
-
|
|
18965
|
+
const rest = [];
|
|
18966
|
+
for (;at < args.length; at++) {
|
|
18967
|
+
const arg = args[at];
|
|
18968
|
+
if (arg === "--") {
|
|
18969
|
+
rest.push(...args.slice(at));
|
|
18970
|
+
break;
|
|
18971
|
+
}
|
|
18972
|
+
if (arg.startsWith("--cwd="))
|
|
18973
|
+
cwd = arg.slice("--cwd=".length);
|
|
18974
|
+
else if (arg === "--cwd" && args[at + 1] !== undefined)
|
|
18975
|
+
cwd = args[++at];
|
|
18976
|
+
else
|
|
18977
|
+
rest.push(arg);
|
|
18978
|
+
}
|
|
18979
|
+
return { rest, cwd };
|
|
18799
18980
|
}
|
|
18800
18981
|
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
18801
18982
|
var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
|
|
@@ -19241,7 +19422,7 @@ var rewrittenUrl = (url, config) => {
|
|
|
19241
19422
|
}
|
|
19242
19423
|
return url;
|
|
19243
19424
|
};
|
|
19244
|
-
var
|
|
19425
|
+
var pushUrl = (config, remote, dir) => {
|
|
19245
19426
|
const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
|
|
19246
19427
|
return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
|
|
19247
19428
|
};
|
|
@@ -19269,7 +19450,7 @@ var toPushRemote = (repo, remote, config) => {
|
|
|
19269
19450
|
const pushRemote = pushRemoteOf(config, repo.branch);
|
|
19270
19451
|
if (pushRemote === null || pushRemote === remote)
|
|
19271
19452
|
return true;
|
|
19272
|
-
return
|
|
19453
|
+
return pushUrl(config, remote, repo.dir) === pushUrl(config, pushRemote, repo.dir);
|
|
19273
19454
|
};
|
|
19274
19455
|
var quote = (text) => `\`${text}\``;
|
|
19275
19456
|
var refusal = (action, branch, dir) => {
|
|
@@ -19807,6 +19988,53 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
19807
19988
|
}
|
|
19808
19989
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
19809
19990
|
};
|
|
19991
|
+
// packages/workit-core/src/hooks/delivery-verbs.ts
|
|
19992
|
+
var WORKIT_ALLOWED_VERBS = [
|
|
19993
|
+
"git",
|
|
19994
|
+
"pr",
|
|
19995
|
+
"ci",
|
|
19996
|
+
"stack",
|
|
19997
|
+
"release",
|
|
19998
|
+
"task",
|
|
19999
|
+
"ledger",
|
|
20000
|
+
"evidence",
|
|
20001
|
+
"finding",
|
|
20002
|
+
"decision",
|
|
20003
|
+
"worker",
|
|
20004
|
+
"verify-delivery",
|
|
20005
|
+
"policy",
|
|
20006
|
+
"doctor",
|
|
20007
|
+
"help"
|
|
20008
|
+
];
|
|
20009
|
+
var WORKIT_BYPASS_FLAGS = ["--unverified", "--overwrite-unintegrated"];
|
|
20010
|
+
var ALLOWED = new Set(WORKIT_ALLOWED_VERBS);
|
|
20011
|
+
var isBypass = (word) => WORKIT_BYPASS_FLAGS.some((flag) => word === flag || word.startsWith(`${flag}=`));
|
|
20012
|
+
var isWorkitWord = (word) => /^(?:.*[\\/])?workit(?:-cli)?$/u.test(word);
|
|
20013
|
+
var bypassReason = (flag) => `Workit: \`${flag}\` overrides a Workit gate, so the user decides this call (no allow rule covers it).`;
|
|
20014
|
+
var EXPANSION_REASON = "Workit: an expansion before `--` could become a bypass flag, so the user decides this call (no allow rule covers it).";
|
|
20015
|
+
var workitBypassFlag = (command, dialect = "posix") => {
|
|
20016
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
20017
|
+
if (segment.unanalyzable) {
|
|
20018
|
+
const text = command.replaceAll(/['"\\`]/gu, "");
|
|
20019
|
+
if (!/\bworkit\b/u.test(text))
|
|
20020
|
+
return null;
|
|
20021
|
+
const found = WORKIT_BYPASS_FLAGS.find((flag) => text.includes(flag));
|
|
20022
|
+
return found ? bypassReason(found) : null;
|
|
20023
|
+
}
|
|
20024
|
+
const start = segment.words.findIndex(isWorkitWord);
|
|
20025
|
+
if (start < 0)
|
|
20026
|
+
continue;
|
|
20027
|
+
const words = segment.words.slice(start + 1);
|
|
20028
|
+
const end = words.indexOf("--");
|
|
20029
|
+
const flag = (end < 0 ? words : words.slice(0, end)).find(isBypass);
|
|
20030
|
+
if (flag)
|
|
20031
|
+
return bypassReason(flag.split("=")[0] ?? flag);
|
|
20032
|
+
const limit = start + 1 + (end < 0 ? words.length : end);
|
|
20033
|
+
if (segment.expands?.some((index) => index > start && index < limit))
|
|
20034
|
+
return EXPANSION_REASON;
|
|
20035
|
+
}
|
|
20036
|
+
return null;
|
|
20037
|
+
};
|
|
19810
20038
|
// packages/workit-core/src/core/heavy-rules.ts
|
|
19811
20039
|
import fs18 from "node:fs";
|
|
19812
20040
|
import os6 from "node:os";
|
|
@@ -21191,7 +21419,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
21191
21419
|
}
|
|
21192
21420
|
const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
|
|
21193
21421
|
if (inner !== null && dir !== undefined)
|
|
21194
|
-
targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
|
|
21422
|
+
targets.push(...segmentTargets(workitRunDir(dir, words), inner, "posix", depth + 1, strict));
|
|
21195
21423
|
if (strict && dir !== undefined) {
|
|
21196
21424
|
const runIn = dir;
|
|
21197
21425
|
for (const sub of depth < 4 ? segment.subs ?? [] : [])
|
|
@@ -21231,6 +21459,14 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
21231
21459
|
}
|
|
21232
21460
|
return targets;
|
|
21233
21461
|
};
|
|
21462
|
+
var workitRunDir = (dir, words) => {
|
|
21463
|
+
for (const invocation of workitInvocations(words.map(quoteWord2).join(" "), "posix")) {
|
|
21464
|
+
const { at, args } = workitCwd(dir, invocation);
|
|
21465
|
+
if (args[0] === "check" || args[0] === "lock" && args[1] === "run")
|
|
21466
|
+
return at;
|
|
21467
|
+
}
|
|
21468
|
+
return dir;
|
|
21469
|
+
};
|
|
21234
21470
|
var workitCwd = (dir, args) => {
|
|
21235
21471
|
const { rest, cwd } = skipWorkitGlobals(args);
|
|
21236
21472
|
return { at: cwd === null ? dir : path28.resolve(dir, homeExpanded(cwd)), args: rest };
|
|
@@ -22015,8 +22251,13 @@ var standingDelta = (input) => {
|
|
|
22015
22251
|
};
|
|
22016
22252
|
var sinceStart = new Map;
|
|
22017
22253
|
// packages/workit-core/src/hooks/cwd-normalize.ts
|
|
22018
|
-
var
|
|
22254
|
+
var DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
|
|
22255
|
+
var COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
|
|
22256
|
+
var GLOBALS_FIRST = new RegExp(String.raw`^(\s*workit)((?:\s+(?:--json|--cwd(?:=|\s+)(?:${DIR})))+)\s+([a-z][a-z-]*)(?=\s|$)`, "u");
|
|
22257
|
+
var VERB_FIRST = /^((?:--json\s+)*)([a-z][a-z-]*)(?=\s|$)/u;
|
|
22258
|
+
var VERBS = new Set(WORKIT_ALLOWED_VERBS);
|
|
22019
22259
|
var FILTERS = new Set(["tail", "head", "grep"]);
|
|
22260
|
+
var clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
22020
22261
|
var stdinOnly = (words) => {
|
|
22021
22262
|
const [name, ...args] = words;
|
|
22022
22263
|
const operands = args.filter((w) => !w.startsWith("-"));
|
|
@@ -22038,14 +22279,28 @@ var cwdRewrite = (command) => {
|
|
|
22038
22279
|
return null;
|
|
22039
22280
|
if (head.words.some((w) => w === "--cwd" || w.startsWith("--cwd=")))
|
|
22040
22281
|
return null;
|
|
22041
|
-
const clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
22042
22282
|
if (!segments.every(clean))
|
|
22043
22283
|
return null;
|
|
22044
22284
|
if (!segments.slice(0, -1).every((s) => s.pipe))
|
|
22045
22285
|
return null;
|
|
22046
22286
|
if (!filters.every((s) => FILTERS.has(s.words[0] ?? "") && stdinOnly(s.words)))
|
|
22047
22287
|
return null;
|
|
22048
|
-
|
|
22288
|
+
const args = rest.slice("workit".length).trimStart();
|
|
22289
|
+
const verb = VERB_FIRST.exec(args);
|
|
22290
|
+
if (!verb?.[2] || !VERBS.has(verb[2]))
|
|
22291
|
+
return `workit --cwd ${dir} ${args}`.trimEnd();
|
|
22292
|
+
const json = verb[1] ? " --json" : "";
|
|
22293
|
+
return `workit ${verb[2]} --cwd ${dir}${json}${args.slice(verb[0].length)}`.trimEnd();
|
|
22294
|
+
};
|
|
22295
|
+
var verbFirst = (command) => {
|
|
22296
|
+
const match = GLOBALS_FIRST.exec(command);
|
|
22297
|
+
const [whole, lead, globals, verb] = match ?? [];
|
|
22298
|
+
if (!whole || !verb || !VERBS.has(verb) || !globals?.includes("--cwd"))
|
|
22299
|
+
return null;
|
|
22300
|
+
const segments = segmentsOf(command);
|
|
22301
|
+
if (segments.length !== 1 || !segments.every(clean))
|
|
22302
|
+
return null;
|
|
22303
|
+
return `${lead} ${verb}${globals}${command.slice(whole.length)}`;
|
|
22049
22304
|
};
|
|
22050
22305
|
|
|
22051
22306
|
// packages/workit-core/src/hooks/worktree-confine.ts
|
|
@@ -22161,7 +22416,7 @@ var hostIsolated = (cwd, agentId) => {
|
|
|
22161
22416
|
};
|
|
22162
22417
|
var shown = (target) => path31.basename(target) === ".workit-live-target" ? path31.dirname(target) : target;
|
|
22163
22418
|
var unboundDeny = (input, target) => {
|
|
22164
|
-
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>
|
|
22419
|
+
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.";
|
|
22165
22420
|
return {
|
|
22166
22421
|
kind: "deny",
|
|
22167
22422
|
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}`,
|
|
@@ -22169,7 +22424,7 @@ var unboundDeny = (input, target) => {
|
|
|
22169
22424
|
};
|
|
22170
22425
|
};
|
|
22171
22426
|
var outsideDeny = (input, worktree, target) => {
|
|
22172
|
-
const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree}
|
|
22427
|
+
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.`;
|
|
22173
22428
|
return {
|
|
22174
22429
|
kind: "deny",
|
|
22175
22430
|
reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
|
|
@@ -22178,7 +22433,7 @@ var outsideDeny = (input, worktree, target) => {
|
|
|
22178
22433
|
};
|
|
22179
22434
|
var unknownDirDeny = (input, worktree) => {
|
|
22180
22435
|
const wt = worktree ?? "<worktree>";
|
|
22181
|
-
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
|
|
22436
|
+
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit <verb> --cwd ${wt}\`.`;
|
|
22182
22437
|
return {
|
|
22183
22438
|
kind: "deny",
|
|
22184
22439
|
reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
|
|
@@ -22336,7 +22591,7 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
22336
22591
|
return `${isolatedGit(input.cwd)}
|
|
22337
22592
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
22338
22593
|
if (role === "implementer")
|
|
22339
|
-
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>
|
|
22594
|
+
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.`;
|
|
22340
22595
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
22341
22596
|
if (role !== "judge" || !event.agentId)
|
|
22342
22597
|
return readOnly;
|
|
@@ -22353,7 +22608,7 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
22353
22608
|
var PRE_TOOL = new Set(["shell.pre", "write.pre", "tool.pre"]);
|
|
22354
22609
|
function handleHook(input, deps) {
|
|
22355
22610
|
const decision = decide(input, deps);
|
|
22356
|
-
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny")
|
|
22611
|
+
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny" || decision.kind === "ask")
|
|
22357
22612
|
return decision;
|
|
22358
22613
|
const delta = standingDelta(input);
|
|
22359
22614
|
if (!delta)
|
|
@@ -22420,9 +22675,12 @@ function decide(input, deps) {
|
|
|
22420
22675
|
if (plan.kind === "rewrite" && !rewrites && canDeny)
|
|
22421
22676
|
return heavyDenial(event.command, plan);
|
|
22422
22677
|
const lease = plan.kind === "rewrite" && rewrites ? plan : null;
|
|
22423
|
-
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) : null;
|
|
22678
|
+
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) ?? verbFirst(event.command) : null;
|
|
22424
22679
|
const aside = plan.kind === "passthrough" ? plan.note : null;
|
|
22425
22680
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
22681
|
+
const bypass = !lease && input.host === "claude_code" ? workitBypassFlag(event.command, event.dialect) : null;
|
|
22682
|
+
if (bypass)
|
|
22683
|
+
return { kind: "ask", reason: bypass };
|
|
22426
22684
|
const rewrittenTo = lease?.command ?? cwdCommand;
|
|
22427
22685
|
const rewritten = (text) => rewrittenTo ? {
|
|
22428
22686
|
kind: "rewrite",
|
|
@@ -22849,6 +23107,17 @@ var render = (decision, native) => {
|
|
|
22849
23107
|
},
|
|
22850
23108
|
exitCode: 0
|
|
22851
23109
|
};
|
|
23110
|
+
if (decision.kind === "ask" && native === "PreToolUse")
|
|
23111
|
+
return {
|
|
23112
|
+
json: {
|
|
23113
|
+
hookSpecificOutput: {
|
|
23114
|
+
hookEventName: "PreToolUse",
|
|
23115
|
+
permissionDecision: "ask",
|
|
23116
|
+
permissionDecisionReason: decision.reason
|
|
23117
|
+
}
|
|
23118
|
+
},
|
|
23119
|
+
exitCode: 0
|
|
23120
|
+
};
|
|
22852
23121
|
if (decision.kind === "context" && native !== null && CONTEXT_EVENTS.has(native))
|
|
22853
23122
|
return {
|
|
22854
23123
|
json: { hookSpecificOutput: { hookEventName: native, additionalContext: decision.text } },
|