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