@brainervirus/workit-opencode 8.13.0 → 8.14.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/dist/plugin.js
CHANGED
|
@@ -6,7 +6,7 @@ function define(plugin) {
|
|
|
6
6
|
// packages/workit-core/src/core.ts
|
|
7
7
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
8
8
|
import { existsSync as existsSync8, realpathSync as realpathSync5 } from "node:fs";
|
|
9
|
-
import
|
|
9
|
+
import path24 from "node:path";
|
|
10
10
|
|
|
11
11
|
// packages/workit-core/src/core/task-contract.ts
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -8992,9 +8992,9 @@ function parseAdvertisedOperation(family, input, host) {
|
|
|
8992
8992
|
});
|
|
8993
8993
|
}
|
|
8994
8994
|
// packages/workit-core/src/core/task-store.ts
|
|
8995
|
-
import { createHash as
|
|
8996
|
-
import * as
|
|
8997
|
-
import
|
|
8995
|
+
import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
|
|
8996
|
+
import * as fs8 from "node:fs";
|
|
8997
|
+
import path8 from "node:path";
|
|
8998
8998
|
|
|
8999
8999
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
9000
9000
|
var OPERATIONAL_CODES = new Set([
|
|
@@ -9597,8 +9597,14 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
9597
9597
|
}
|
|
9598
9598
|
}
|
|
9599
9599
|
}
|
|
9600
|
+
// packages/workit-core/src/host-session.ts
|
|
9601
|
+
import { createHash as createHash3, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
|
|
9602
|
+
import fs5 from "node:fs";
|
|
9603
|
+
import path5 from "node:path";
|
|
9604
|
+
|
|
9600
9605
|
// packages/workit-core/src/core/logger.ts
|
|
9601
9606
|
import os from "node:os";
|
|
9607
|
+
import path3 from "node:path";
|
|
9602
9608
|
var SENSITIVE_WORDS = new Set([
|
|
9603
9609
|
"token",
|
|
9604
9610
|
"secret",
|
|
@@ -9637,13 +9643,29 @@ var SENSITIVE_WORDS = new Set([
|
|
|
9637
9643
|
]);
|
|
9638
9644
|
var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
|
|
9639
9645
|
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9646
|
+
var resolveStateDir = () => {
|
|
9647
|
+
const override = process.env.WORKFLOW_TOOLKIT_STATE;
|
|
9648
|
+
if (override)
|
|
9649
|
+
return override;
|
|
9650
|
+
const home = os.homedir();
|
|
9651
|
+
if (process.env.XDG_STATE_HOME)
|
|
9652
|
+
return path3.join(process.env.XDG_STATE_HOME, "workit");
|
|
9653
|
+
switch (os.platform()) {
|
|
9654
|
+
case "darwin":
|
|
9655
|
+
return path3.join(home, "Library", "Application Support", "workit");
|
|
9656
|
+
case "win32":
|
|
9657
|
+
return path3.join(process.env.LOCALAPPDATA ?? path3.join(home, "AppData", "Local"), "workit");
|
|
9658
|
+
default:
|
|
9659
|
+
return path3.join(process.env.HOME ?? home, ".local", "state", "workit");
|
|
9660
|
+
}
|
|
9661
|
+
};
|
|
9640
9662
|
var homePattern = new RegExp(`${escapeRegExp(os.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
|
|
9641
9663
|
|
|
9642
9664
|
// packages/workit-core/src/store/paths.ts
|
|
9643
9665
|
import { spawnSync } from "node:child_process";
|
|
9644
9666
|
import { createHash as createHash2 } from "node:crypto";
|
|
9645
9667
|
import fs4 from "node:fs";
|
|
9646
|
-
import
|
|
9668
|
+
import path4 from "node:path";
|
|
9647
9669
|
var NOT_A_REPO = /not a git repository \(or any (of the parent directories|parent up to)/u;
|
|
9648
9670
|
var gitRun = (cwd, args) => {
|
|
9649
9671
|
const run = spawnSync("git", args, {
|
|
@@ -9685,14 +9707,14 @@ var DISCOVERY_ENV = [
|
|
|
9685
9707
|
];
|
|
9686
9708
|
var isGitDir = (dir) => {
|
|
9687
9709
|
try {
|
|
9688
|
-
return fs4.statSync(
|
|
9710
|
+
return fs4.statSync(path4.join(dir, "HEAD")).isFile() && fs4.statSync(path4.join(dir, "objects")).isDirectory() && fs4.statSync(path4.join(dir, "refs")).isDirectory();
|
|
9689
9711
|
} catch {
|
|
9690
9712
|
return false;
|
|
9691
9713
|
}
|
|
9692
9714
|
};
|
|
9693
9715
|
var reftableRefs = (common) => {
|
|
9694
9716
|
try {
|
|
9695
|
-
return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs4.readFileSync(
|
|
9717
|
+
return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs4.readFileSync(path4.join(common, "config"), "utf8"));
|
|
9696
9718
|
} catch {
|
|
9697
9719
|
return false;
|
|
9698
9720
|
}
|
|
@@ -9712,7 +9734,7 @@ function fastGit(root) {
|
|
|
9712
9734
|
if (device !== null && stat.dev !== device)
|
|
9713
9735
|
return { kind: "none" };
|
|
9714
9736
|
device = stat.dev;
|
|
9715
|
-
const dotGit =
|
|
9737
|
+
const dotGit = path4.join(dir, ".git");
|
|
9716
9738
|
let entry = null;
|
|
9717
9739
|
try {
|
|
9718
9740
|
entry = fs4.statSync(dotGit);
|
|
@@ -9733,19 +9755,19 @@ function fastGit(root) {
|
|
|
9733
9755
|
const match = /^gitdir: (.+)$/mu.exec(text);
|
|
9734
9756
|
if (!match)
|
|
9735
9757
|
return null;
|
|
9736
|
-
gitDir =
|
|
9758
|
+
gitDir = path4.resolve(dir, match[1].trim());
|
|
9737
9759
|
} else
|
|
9738
9760
|
return null;
|
|
9739
|
-
if (!fs4.existsSync(
|
|
9761
|
+
if (!fs4.existsSync(path4.join(gitDir, "HEAD")))
|
|
9740
9762
|
return null;
|
|
9741
9763
|
} else if (isGitDir(dir))
|
|
9742
9764
|
gitDir = dir;
|
|
9743
9765
|
if (gitDir) {
|
|
9744
9766
|
let common = gitDir;
|
|
9745
9767
|
try {
|
|
9746
|
-
common =
|
|
9768
|
+
common = path4.resolve(gitDir, fs4.readFileSync(path4.join(gitDir, "commondir"), "utf8").trim());
|
|
9747
9769
|
} catch {}
|
|
9748
|
-
if (!fs4.existsSync(
|
|
9770
|
+
if (!fs4.existsSync(path4.join(common, "objects")))
|
|
9749
9771
|
return null;
|
|
9750
9772
|
return {
|
|
9751
9773
|
kind: "repo",
|
|
@@ -9755,26 +9777,26 @@ function fastGit(root) {
|
|
|
9755
9777
|
reftable: reftableRefs(common)
|
|
9756
9778
|
};
|
|
9757
9779
|
}
|
|
9758
|
-
const parent =
|
|
9780
|
+
const parent = path4.dirname(dir);
|
|
9759
9781
|
if (parent === dir)
|
|
9760
9782
|
return { kind: "none" };
|
|
9761
9783
|
dir = parent;
|
|
9762
9784
|
}
|
|
9763
9785
|
}
|
|
9764
9786
|
function resolveStore(root) {
|
|
9765
|
-
root =
|
|
9787
|
+
root = path4.resolve(root);
|
|
9766
9788
|
let directory = false;
|
|
9767
9789
|
try {
|
|
9768
9790
|
directory = fs4.statSync(root).isDirectory();
|
|
9769
9791
|
} catch {}
|
|
9770
9792
|
if (!directory)
|
|
9771
|
-
return { dir:
|
|
9793
|
+
return { dir: path4.join(root, ".workit"), shared: false, top: null };
|
|
9772
9794
|
const fast = fastGit(root);
|
|
9773
9795
|
if (fast?.kind === "none")
|
|
9774
|
-
return { dir:
|
|
9796
|
+
return { dir: path4.join(root, ".workit"), shared: false, top: null };
|
|
9775
9797
|
if (fast?.kind === "repo")
|
|
9776
9798
|
return {
|
|
9777
|
-
dir:
|
|
9799
|
+
dir: path4.join(fast.common, "workit"),
|
|
9778
9800
|
shared: true,
|
|
9779
9801
|
top: fast.top,
|
|
9780
9802
|
git: { gitDir: fast.gitDir, top: fast.top, reftable: fast.reftable }
|
|
@@ -9784,20 +9806,20 @@ function resolveStore(root) {
|
|
|
9784
9806
|
if (run.ok && common) {
|
|
9785
9807
|
const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
|
|
9786
9808
|
return {
|
|
9787
|
-
dir:
|
|
9809
|
+
dir: path4.join(real(path4.resolve(root, common)), "workit"),
|
|
9788
9810
|
shared: true,
|
|
9789
9811
|
top: top.ok && top.stdout.trim() ? real(top.stdout.trim()) : null
|
|
9790
9812
|
};
|
|
9791
9813
|
}
|
|
9792
9814
|
if (run.missing || !run.ok && NOT_A_REPO.test(run.stderr))
|
|
9793
|
-
return { dir:
|
|
9815
|
+
return { dir: path4.join(root, ".workit"), shared: false, top: null };
|
|
9794
9816
|
return new Error(`cannot locate the workit store: git rev-parse failed (${run.stderr.trim().split(`
|
|
9795
9817
|
`)[0] || "no output"}); fix the repository (git status should work here)`);
|
|
9796
9818
|
}
|
|
9797
9819
|
var rebasingBranch = (gitDir) => {
|
|
9798
9820
|
for (const state of ["rebase-merge", "rebase-apply"]) {
|
|
9799
9821
|
try {
|
|
9800
|
-
const ref = fs4.readFileSync(
|
|
9822
|
+
const ref = fs4.readFileSync(path4.join(gitDir, state, "head-name"), "utf8").trim();
|
|
9801
9823
|
const match = /^refs\/heads\/(.+)$/u.exec(ref);
|
|
9802
9824
|
if (match)
|
|
9803
9825
|
return match[1];
|
|
@@ -9812,7 +9834,7 @@ function resolveTaskKey(root, location) {
|
|
|
9812
9834
|
if (location.git && !location.git.reftable) {
|
|
9813
9835
|
let head = "";
|
|
9814
9836
|
try {
|
|
9815
|
-
head = fs4.readFileSync(
|
|
9837
|
+
head = fs4.readFileSync(path4.join(location.git.gitDir, "HEAD"), "utf8").trim();
|
|
9816
9838
|
} catch {}
|
|
9817
9839
|
const ref = /^ref: refs\/heads\/(.+)$/u.exec(head);
|
|
9818
9840
|
if (ref && ref[1] !== ".invalid")
|
|
@@ -9843,7 +9865,7 @@ function resolveTaskKey(root, location) {
|
|
|
9843
9865
|
return { key: `detached-${shortHash(real(worktree))}`, kind: "detached", branch: null };
|
|
9844
9866
|
}
|
|
9845
9867
|
function checkoutRootOf(root, location) {
|
|
9846
|
-
const resolved =
|
|
9868
|
+
const resolved = path4.resolve(root);
|
|
9847
9869
|
let canonical = resolved;
|
|
9848
9870
|
try {
|
|
9849
9871
|
canonical = fs4.realpathSync(resolved);
|
|
@@ -9874,13 +9896,13 @@ function branchRenames(location) {
|
|
|
9874
9896
|
}
|
|
9875
9897
|
for (const entry of entries)
|
|
9876
9898
|
if (entry.isDirectory())
|
|
9877
|
-
walk(
|
|
9899
|
+
walk(path4.join(dir, entry.name));
|
|
9878
9900
|
else
|
|
9879
|
-
files.push(
|
|
9901
|
+
files.push(path4.join(dir, entry.name));
|
|
9880
9902
|
};
|
|
9881
|
-
walk(
|
|
9903
|
+
walk(path4.join(path4.dirname(location.dir), "logs", "refs", "heads"));
|
|
9882
9904
|
if (location.git)
|
|
9883
|
-
files.push(
|
|
9905
|
+
files.push(path4.join(location.git.gitDir, "logs", "HEAD"));
|
|
9884
9906
|
const seen = new Set;
|
|
9885
9907
|
const renames = [];
|
|
9886
9908
|
for (const file of files) {
|
|
@@ -9915,7 +9937,7 @@ var checkoutSlug = (root) => {
|
|
|
9915
9937
|
let canonical = real(root);
|
|
9916
9938
|
if (process.platform === "win32")
|
|
9917
9939
|
canonical = canonical.toLowerCase();
|
|
9918
|
-
const base =
|
|
9940
|
+
const base = path4.basename(canonical).replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 40) || "root";
|
|
9919
9941
|
return `${base}-${shortHash(canonical)}`;
|
|
9920
9942
|
};
|
|
9921
9943
|
|
|
@@ -9947,6 +9969,89 @@ function hostSessionFromEnv(env) {
|
|
|
9947
9969
|
}
|
|
9948
9970
|
return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
|
|
9949
9971
|
}
|
|
9972
|
+
var hash = (value) => createHash3("sha256").update(value).digest("hex").slice(0, 32);
|
|
9973
|
+
var userIssuedRoot = () => path5.join(resolveStateDir(), "issued");
|
|
9974
|
+
var userTokenRoot = () => path5.join(resolveStateDir(), "issued-tokens");
|
|
9975
|
+
function migrateRepoIssued(cwd) {
|
|
9976
|
+
if (cwd === undefined)
|
|
9977
|
+
return;
|
|
9978
|
+
let old;
|
|
9979
|
+
try {
|
|
9980
|
+
const location = resolveStore(cwd);
|
|
9981
|
+
if (location instanceof Error)
|
|
9982
|
+
return;
|
|
9983
|
+
old = path5.join(location.dir, "hooks", "issued");
|
|
9984
|
+
if (!fs5.existsSync(old))
|
|
9985
|
+
return;
|
|
9986
|
+
} catch {
|
|
9987
|
+
return;
|
|
9988
|
+
}
|
|
9989
|
+
for (const lead of safeList(old)) {
|
|
9990
|
+
try {
|
|
9991
|
+
const from = path5.join(old, lead);
|
|
9992
|
+
if (!fs5.statSync(from).isDirectory())
|
|
9993
|
+
continue;
|
|
9994
|
+
const to = path5.join(userIssuedRoot(), lead);
|
|
9995
|
+
fs5.mkdirSync(to, { recursive: true, mode: 448 });
|
|
9996
|
+
for (const id of fs5.readdirSync(from)) {
|
|
9997
|
+
try {
|
|
9998
|
+
fs5.copyFileSync(path5.join(from, id), path5.join(to, id), fs5.constants.COPYFILE_EXCL);
|
|
9999
|
+
} catch (error) {
|
|
10000
|
+
if (error.code !== "EEXIST")
|
|
10001
|
+
throw error;
|
|
10002
|
+
}
|
|
10003
|
+
fs5.rmSync(path5.join(from, id), { force: true });
|
|
10004
|
+
}
|
|
10005
|
+
removeIfEmpty(from);
|
|
10006
|
+
} catch {}
|
|
10007
|
+
}
|
|
10008
|
+
removeIfEmpty(old);
|
|
10009
|
+
}
|
|
10010
|
+
var safeList = (dir) => {
|
|
10011
|
+
try {
|
|
10012
|
+
return fs5.readdirSync(dir);
|
|
10013
|
+
} catch {
|
|
10014
|
+
return [];
|
|
10015
|
+
}
|
|
10016
|
+
};
|
|
10017
|
+
var removeIfEmpty = (dir) => {
|
|
10018
|
+
try {
|
|
10019
|
+
fs5.rmdirSync(dir);
|
|
10020
|
+
} catch {}
|
|
10021
|
+
};
|
|
10022
|
+
var issuedRecord = (cwd, claimed) => {
|
|
10023
|
+
const cut = claimed.lastIndexOf(":");
|
|
10024
|
+
if (cut <= 0)
|
|
10025
|
+
return null;
|
|
10026
|
+
migrateRepoIssued(cwd);
|
|
10027
|
+
try {
|
|
10028
|
+
return fs5.readFileSync(path5.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
|
|
10029
|
+
} catch {
|
|
10030
|
+
return null;
|
|
10031
|
+
}
|
|
10032
|
+
};
|
|
10033
|
+
var TOKEN_HASH = /^([0-9a-f]{64})$/mu;
|
|
10034
|
+
var issuedToken = (claimed) => {
|
|
10035
|
+
const cut = claimed.lastIndexOf(":");
|
|
10036
|
+
if (cut <= 0)
|
|
10037
|
+
return { stored: null, leadBound: false };
|
|
10038
|
+
const dir = path5.join(userTokenRoot(), hash(claimed.slice(0, cut)));
|
|
10039
|
+
try {
|
|
10040
|
+
const text = fs5.readFileSync(path5.join(dir, hash(claimed)), "utf8");
|
|
10041
|
+
return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
|
|
10042
|
+
} catch {
|
|
10043
|
+
return { stored: null, leadBound: safeList(dir).length > 0 };
|
|
10044
|
+
}
|
|
10045
|
+
};
|
|
10046
|
+
function issuedBinding(cwd, claimed) {
|
|
10047
|
+
const record = issuedRecord(cwd, claimed);
|
|
10048
|
+
const { stored, leadBound } = issuedToken(claimed);
|
|
10049
|
+
if (stored)
|
|
10050
|
+
return "token";
|
|
10051
|
+
if (record === null)
|
|
10052
|
+
return null;
|
|
10053
|
+
return leadBound ? "token" : "legacy";
|
|
10054
|
+
}
|
|
9950
10055
|
var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
|
|
9951
10056
|
var SESSION_UNSAFE = /[^A-Za-z0-9_.:@/+-]/g;
|
|
9952
10057
|
function childSessionId(lead, child) {
|
|
@@ -9957,26 +10062,26 @@ function childSessionId(lead, child) {
|
|
|
9957
10062
|
|
|
9958
10063
|
// packages/workit-core/src/core/package-root.ts
|
|
9959
10064
|
import { existsSync } from "node:fs";
|
|
9960
|
-
import
|
|
10065
|
+
import path6 from "node:path";
|
|
9961
10066
|
import { fileURLToPath } from "node:url";
|
|
9962
10067
|
var packageRoot = () => {
|
|
9963
|
-
let dir =
|
|
10068
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
9964
10069
|
while (true) {
|
|
9965
|
-
const parent =
|
|
9966
|
-
if (existsSync(
|
|
10070
|
+
const parent = path6.dirname(dir);
|
|
10071
|
+
if (existsSync(path6.join(dir, "package.json")) || parent === dir)
|
|
9967
10072
|
return dir;
|
|
9968
10073
|
dir = parent;
|
|
9969
10074
|
}
|
|
9970
10075
|
};
|
|
9971
10076
|
var assetRoot = () => {
|
|
9972
10077
|
const root = packageRoot();
|
|
9973
|
-
const assets =
|
|
10078
|
+
const assets = path6.join(root, "assets");
|
|
9974
10079
|
return existsSync(assets) ? assets : root;
|
|
9975
10080
|
};
|
|
9976
10081
|
|
|
9977
10082
|
// packages/workit-core/src/core/store-lock.ts
|
|
9978
10083
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
9979
|
-
import
|
|
10084
|
+
import fs6 from "node:fs";
|
|
9980
10085
|
import { hostname } from "node:os";
|
|
9981
10086
|
var metadataLockSchema = object({
|
|
9982
10087
|
pid: number2().int().nonnegative().safe(),
|
|
@@ -9988,8 +10093,10 @@ var metadataLockSchema = object({
|
|
|
9988
10093
|
var FOREIGN_LOCK_TTL_MS = 10 * 60000;
|
|
9989
10094
|
var UNREADABLE_LOCK_TTL_MS = 30000;
|
|
9990
10095
|
var RECLAIM_GUARD_TTL_MS = 30000;
|
|
9991
|
-
var
|
|
10096
|
+
var IN_PROCESS_LOCK_TIMEOUT_MS = 250;
|
|
10097
|
+
var defaultLockTimeoutMs = IN_PROCESS_LOCK_TIMEOUT_MS;
|
|
9992
10098
|
var defaultLockTimeout = () => defaultLockTimeoutMs;
|
|
10099
|
+
var hostAwareLockTimeout = (cliMs) => defaultLockTimeoutMs > IN_PROCESS_LOCK_TIMEOUT_MS ? cliMs : IN_PROCESS_LOCK_TIMEOUT_MS;
|
|
9993
10100
|
var parseMetadataLock = (raw) => {
|
|
9994
10101
|
let value;
|
|
9995
10102
|
try {
|
|
@@ -10018,7 +10125,7 @@ var parseProcStatStart = (stat) => {
|
|
|
10018
10125
|
var processStartOf = (pid) => {
|
|
10019
10126
|
if (process.platform === "linux") {
|
|
10020
10127
|
try {
|
|
10021
|
-
return parseProcStatStart(
|
|
10128
|
+
return parseProcStatStart(fs6.readFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
10022
10129
|
} catch {
|
|
10023
10130
|
return null;
|
|
10024
10131
|
}
|
|
@@ -10039,7 +10146,7 @@ var processStartOf = (pid) => {
|
|
|
10039
10146
|
};
|
|
10040
10147
|
var readTrimmed = (file) => {
|
|
10041
10148
|
try {
|
|
10042
|
-
return
|
|
10149
|
+
return fs6.readFileSync(file, "utf8").trim() || null;
|
|
10043
10150
|
} catch {
|
|
10044
10151
|
return null;
|
|
10045
10152
|
}
|
|
@@ -10050,7 +10157,7 @@ var localLockHost = () => {
|
|
|
10050
10157
|
return cachedLockHost;
|
|
10051
10158
|
let pidns = null;
|
|
10052
10159
|
try {
|
|
10053
|
-
pidns = /\[(\d+)\]/.exec(
|
|
10160
|
+
pidns = /\[(\d+)\]/.exec(fs6.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
|
|
10054
10161
|
} catch {}
|
|
10055
10162
|
const boot = readTrimmed("/proc/sys/kernel/random/boot_id");
|
|
10056
10163
|
cachedLockHost = pidns || boot ? `${hostname()}#${pidns ?? "?"}:${boot ?? "?"}` : hostname();
|
|
@@ -10096,7 +10203,7 @@ var classifyLockOwner = (payload, ageMs, localHost = localLockHost(), options =
|
|
|
10096
10203
|
};
|
|
10097
10204
|
var ageOf = (file, nowMs) => {
|
|
10098
10205
|
try {
|
|
10099
|
-
return nowMs -
|
|
10206
|
+
return nowMs - fs6.lstatSync(file).mtimeMs;
|
|
10100
10207
|
} catch {
|
|
10101
10208
|
return null;
|
|
10102
10209
|
}
|
|
@@ -10107,7 +10214,7 @@ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
|
|
|
10107
10214
|
if (age === null || age <= RECLAIM_GUARD_TTL_MS)
|
|
10108
10215
|
return false;
|
|
10109
10216
|
try {
|
|
10110
|
-
|
|
10217
|
+
fs6.rmdirSync(guard);
|
|
10111
10218
|
return true;
|
|
10112
10219
|
} catch {
|
|
10113
10220
|
return false;
|
|
@@ -10116,8 +10223,8 @@ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
|
|
|
10116
10223
|
|
|
10117
10224
|
// packages/workit-core/src/store/event-log.ts
|
|
10118
10225
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
10119
|
-
import
|
|
10120
|
-
import
|
|
10226
|
+
import fs7 from "node:fs";
|
|
10227
|
+
import path7 from "node:path";
|
|
10121
10228
|
var EVENT_VERSION = 1;
|
|
10122
10229
|
|
|
10123
10230
|
class LogDamage extends Error {
|
|
@@ -10146,7 +10253,7 @@ var parseEvent = (line, file, offset) => {
|
|
|
10146
10253
|
function readLog(file, from = 0, expectSeq = null) {
|
|
10147
10254
|
let fd;
|
|
10148
10255
|
try {
|
|
10149
|
-
fd =
|
|
10256
|
+
fd = fs7.openSync(file, "r");
|
|
10150
10257
|
} catch (error) {
|
|
10151
10258
|
if (error.code === "ENOENT")
|
|
10152
10259
|
return null;
|
|
@@ -10155,13 +10262,13 @@ function readLog(file, from = 0, expectSeq = null) {
|
|
|
10155
10262
|
let bytes;
|
|
10156
10263
|
let size;
|
|
10157
10264
|
try {
|
|
10158
|
-
size =
|
|
10265
|
+
size = fs7.fstatSync(fd).size;
|
|
10159
10266
|
if (from > size)
|
|
10160
10267
|
throw new LogDamage(`${file}: shorter than its snapshot`);
|
|
10161
10268
|
bytes = Buffer.alloc(size - from);
|
|
10162
10269
|
let read = 0;
|
|
10163
10270
|
while (read < bytes.length) {
|
|
10164
|
-
const got =
|
|
10271
|
+
const got = fs7.readSync(fd, bytes, read, bytes.length - read, from + read);
|
|
10165
10272
|
if (got === 0)
|
|
10166
10273
|
break;
|
|
10167
10274
|
read += got;
|
|
@@ -10169,7 +10276,7 @@ function readLog(file, from = 0, expectSeq = null) {
|
|
|
10169
10276
|
bytes = bytes.subarray(0, read);
|
|
10170
10277
|
size = from + read;
|
|
10171
10278
|
} finally {
|
|
10172
|
-
|
|
10279
|
+
fs7.closeSync(fd);
|
|
10173
10280
|
}
|
|
10174
10281
|
const events = [];
|
|
10175
10282
|
let start = 0;
|
|
@@ -10194,11 +10301,11 @@ function readLog(file, from = 0, expectSeq = null) {
|
|
|
10194
10301
|
}
|
|
10195
10302
|
var fsyncDirectory = (dir) => {
|
|
10196
10303
|
try {
|
|
10197
|
-
const fd =
|
|
10304
|
+
const fd = fs7.openSync(dir, "r");
|
|
10198
10305
|
try {
|
|
10199
|
-
|
|
10306
|
+
fs7.fsyncSync(fd);
|
|
10200
10307
|
} finally {
|
|
10201
|
-
|
|
10308
|
+
fs7.closeSync(fd);
|
|
10202
10309
|
}
|
|
10203
10310
|
} catch (error) {
|
|
10204
10311
|
if (process.platform !== "win32")
|
|
@@ -10208,46 +10315,46 @@ var fsyncDirectory = (dir) => {
|
|
|
10208
10315
|
function appendEvent(file, event, validEnd) {
|
|
10209
10316
|
const line = Buffer.from(`${JSON.stringify(event)}
|
|
10210
10317
|
`, "utf8");
|
|
10211
|
-
const created = !
|
|
10212
|
-
if (!created &&
|
|
10213
|
-
const cut =
|
|
10318
|
+
const created = !fs7.existsSync(file);
|
|
10319
|
+
if (!created && fs7.statSync(file).size !== validEnd) {
|
|
10320
|
+
const cut = fs7.openSync(file, "r+");
|
|
10214
10321
|
try {
|
|
10215
|
-
|
|
10216
|
-
|
|
10322
|
+
fs7.ftruncateSync(cut, validEnd);
|
|
10323
|
+
fs7.fsyncSync(cut);
|
|
10217
10324
|
} finally {
|
|
10218
|
-
|
|
10325
|
+
fs7.closeSync(cut);
|
|
10219
10326
|
}
|
|
10220
10327
|
}
|
|
10221
|
-
const fd =
|
|
10328
|
+
const fd = fs7.openSync(file, "a", 384);
|
|
10222
10329
|
try {
|
|
10223
10330
|
let written = 0;
|
|
10224
10331
|
while (written < line.length)
|
|
10225
|
-
written +=
|
|
10226
|
-
|
|
10332
|
+
written += fs7.writeSync(fd, line, written, line.length - written);
|
|
10333
|
+
fs7.fsyncSync(fd);
|
|
10227
10334
|
} finally {
|
|
10228
|
-
|
|
10335
|
+
fs7.closeSync(fd);
|
|
10229
10336
|
}
|
|
10230
10337
|
if (created)
|
|
10231
|
-
fsyncDirectory(
|
|
10338
|
+
fsyncDirectory(path7.dirname(file));
|
|
10232
10339
|
return validEnd;
|
|
10233
10340
|
}
|
|
10234
10341
|
function replaceFile(file, bytes, durable) {
|
|
10235
10342
|
const temporary = `${file}.${process.pid}.${randomUUID2()}.tmp`;
|
|
10236
10343
|
try {
|
|
10237
|
-
const fd =
|
|
10344
|
+
const fd = fs7.openSync(temporary, "wx", 384);
|
|
10238
10345
|
try {
|
|
10239
|
-
|
|
10346
|
+
fs7.writeSync(fd, bytes);
|
|
10240
10347
|
if (durable)
|
|
10241
|
-
|
|
10348
|
+
fs7.fsyncSync(fd);
|
|
10242
10349
|
} finally {
|
|
10243
|
-
|
|
10350
|
+
fs7.closeSync(fd);
|
|
10244
10351
|
}
|
|
10245
10352
|
renameWithRetry(temporary, file);
|
|
10246
10353
|
if (durable)
|
|
10247
|
-
fsyncDirectory(
|
|
10354
|
+
fsyncDirectory(path7.dirname(file));
|
|
10248
10355
|
} catch (error) {
|
|
10249
10356
|
try {
|
|
10250
|
-
|
|
10357
|
+
fs7.unlinkSync(temporary);
|
|
10251
10358
|
} catch {}
|
|
10252
10359
|
throw error;
|
|
10253
10360
|
}
|
|
@@ -10272,7 +10379,7 @@ var retryTransient = (run) => {
|
|
|
10272
10379
|
}
|
|
10273
10380
|
}
|
|
10274
10381
|
};
|
|
10275
|
-
var renameWithRetry = (from, to) => retryTransient(() =>
|
|
10382
|
+
var renameWithRetry = (from, to) => retryTransient(() => fs7.renameSync(from, to));
|
|
10276
10383
|
|
|
10277
10384
|
// packages/workit-core/src/store/patch.ts
|
|
10278
10385
|
var isPlainObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -10385,7 +10492,7 @@ var isPatch = (value) => Array.isArray(value) && value.every((op) => {
|
|
|
10385
10492
|
});
|
|
10386
10493
|
|
|
10387
10494
|
// packages/workit-core/src/store/reduce.ts
|
|
10388
|
-
import { createHash as
|
|
10495
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
10389
10496
|
var OPENING_TYPES = new Set([
|
|
10390
10497
|
"task.opened",
|
|
10391
10498
|
"task.imported",
|
|
@@ -10414,7 +10521,7 @@ var isObject4 = (value) => typeof value === "object" && value !== null && !Array
|
|
|
10414
10521
|
var isKey = (value) => isObject4(value) && typeof value.key === "string" && (value.kind === "branch" || value.kind === "detached" || value.kind === "dir") && (value.branch === null || typeof value.branch === "string");
|
|
10415
10522
|
var BLOB_REF = "$blob";
|
|
10416
10523
|
var blobRef = (value) => isObject4(value) && Object.keys(value).length === 1 && typeof value[BLOB_REF] === "string" && /^[0-9a-f]{64}$/.test(value[BLOB_REF]) ? value[BLOB_REF] : null;
|
|
10417
|
-
var blobDigest = (value) =>
|
|
10524
|
+
var blobDigest = (value) => createHash4("sha256").update(JSON.stringify(value)).digest("hex");
|
|
10418
10525
|
var toBlob = (value, blobs) => {
|
|
10419
10526
|
if (!isObject4(value))
|
|
10420
10527
|
return value;
|
|
@@ -10569,7 +10676,7 @@ var indexEntry = (task, key, legacy, file) => {
|
|
|
10569
10676
|
};
|
|
10570
10677
|
var fileSignature = (file) => {
|
|
10571
10678
|
try {
|
|
10572
|
-
const stat =
|
|
10679
|
+
const stat = fs8.statSync(file, { bigint: true });
|
|
10573
10680
|
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
10574
10681
|
} catch {
|
|
10575
10682
|
return null;
|
|
@@ -10587,7 +10694,7 @@ var cachedRuntimeVersion = null;
|
|
|
10587
10694
|
var runtimeVersion = () => {
|
|
10588
10695
|
if (cachedRuntimeVersion === null) {
|
|
10589
10696
|
try {
|
|
10590
|
-
const pkg = JSON.parse(
|
|
10697
|
+
const pkg = JSON.parse(fs8.readFileSync(path8.join(packageRoot(), "package.json"), "utf8"));
|
|
10591
10698
|
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
10592
10699
|
} catch {
|
|
10593
10700
|
cachedRuntimeVersion = "0.0.0";
|
|
@@ -10613,14 +10720,14 @@ var isNewerVersion = (candidate, current) => {
|
|
|
10613
10720
|
var validId = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value);
|
|
10614
10721
|
var readText = (file) => {
|
|
10615
10722
|
try {
|
|
10616
|
-
return
|
|
10723
|
+
return fs8.readFileSync(file, "utf8");
|
|
10617
10724
|
} catch {
|
|
10618
10725
|
return "";
|
|
10619
10726
|
}
|
|
10620
10727
|
};
|
|
10621
10728
|
var REFLOG_LINE = /^[0-9a-f]+ ([0-9a-f]+) .* (\d+) [+-]\d{4}\t(.*)$/u;
|
|
10622
10729
|
var branchStart = (location, branch, aliases) => {
|
|
10623
|
-
const first = readText(
|
|
10730
|
+
const first = readText(path8.join(path8.dirname(location.dir), "logs", "refs", "heads", branch)).split(`
|
|
10624
10731
|
`, 1)[0];
|
|
10625
10732
|
const created = REFLOG_LINE.exec(first);
|
|
10626
10733
|
const start = created && /^branch: Created from (\S+)$/u.exec(created[3])?.[1];
|
|
@@ -10630,7 +10737,7 @@ var branchStart = (location, branch, aliases) => {
|
|
|
10630
10737
|
return start;
|
|
10631
10738
|
if (!location.git)
|
|
10632
10739
|
return null;
|
|
10633
|
-
for (const line of readText(
|
|
10740
|
+
for (const line of readText(path8.join(location.git.gitDir, "logs", "HEAD")).split(`
|
|
10634
10741
|
`)) {
|
|
10635
10742
|
const entry = REFLOG_LINE.exec(line);
|
|
10636
10743
|
const moved = entry && /^checkout: moving from (\S+) to (\S+)$/u.exec(entry[3]);
|
|
@@ -10640,23 +10747,23 @@ var branchStart = (location, branch, aliases) => {
|
|
|
10640
10747
|
}
|
|
10641
10748
|
return null;
|
|
10642
10749
|
};
|
|
10643
|
-
var refExists = (common, ref) =>
|
|
10750
|
+
var refExists = (common, ref) => fs8.existsSync(path8.join(common, ref)) || readText(path8.join(common, "packed-refs")).includes(` ${ref}
|
|
10644
10751
|
`);
|
|
10645
10752
|
var origins = new Map;
|
|
10646
10753
|
var sameDirectoryIdentity = (left, right) => {
|
|
10647
|
-
if (!
|
|
10754
|
+
if (!path8.isAbsolute(left) || !path8.isAbsolute(right))
|
|
10648
10755
|
return false;
|
|
10649
|
-
const normalizedLeft =
|
|
10650
|
-
const normalizedRight =
|
|
10756
|
+
const normalizedLeft = path8.resolve(left);
|
|
10757
|
+
const normalizedRight = path8.resolve(right);
|
|
10651
10758
|
try {
|
|
10652
|
-
const a =
|
|
10653
|
-
const b =
|
|
10759
|
+
const a = fs8.statSync(normalizedLeft, { bigint: true });
|
|
10760
|
+
const b = fs8.statSync(normalizedRight, { bigint: true });
|
|
10654
10761
|
if (!a.isDirectory() || !b.isDirectory())
|
|
10655
10762
|
return false;
|
|
10656
10763
|
if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
|
|
10657
10764
|
return true;
|
|
10658
|
-
const canonicalLeft =
|
|
10659
|
-
const canonicalRight =
|
|
10765
|
+
const canonicalLeft = fs8.realpathSync(normalizedLeft);
|
|
10766
|
+
const canonicalRight = fs8.realpathSync(normalizedRight);
|
|
10660
10767
|
if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
|
|
10661
10768
|
return true;
|
|
10662
10769
|
return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
|
|
@@ -10693,8 +10800,8 @@ class TaskStore {
|
|
|
10693
10800
|
migrationChecked = false;
|
|
10694
10801
|
compactDue = new Set;
|
|
10695
10802
|
constructor(root, options = {}) {
|
|
10696
|
-
const resolved =
|
|
10697
|
-
this.origin =
|
|
10803
|
+
const resolved = path8.resolve(root);
|
|
10804
|
+
this.origin = fs8.existsSync(resolved) ? fs8.realpathSync(resolved) : resolved;
|
|
10698
10805
|
this.located = resolveStore(this.origin);
|
|
10699
10806
|
this.root = checkoutRootOf(this.origin, this.located);
|
|
10700
10807
|
this.lockTimeoutMs = options.lockTimeoutMs ?? defaultLockTimeout();
|
|
@@ -10702,7 +10809,7 @@ class TaskStore {
|
|
|
10702
10809
|
this.migrateOnRead = options.migrateOnRead;
|
|
10703
10810
|
}
|
|
10704
10811
|
location() {
|
|
10705
|
-
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared &&
|
|
10812
|
+
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs8.existsSync(path8.join(this.root, ".git"))) {
|
|
10706
10813
|
this.located = undefined;
|
|
10707
10814
|
this.migrationChecked = false;
|
|
10708
10815
|
}
|
|
@@ -10766,7 +10873,7 @@ class TaskStore {
|
|
|
10766
10873
|
const tasksDir = this.paths().tasks;
|
|
10767
10874
|
let names;
|
|
10768
10875
|
try {
|
|
10769
|
-
names =
|
|
10876
|
+
names = fs8.existsSync(tasksDir) ? fs8.readdirSync(tasksDir).filter((name) => validId(name)) : [];
|
|
10770
10877
|
} catch (error) {
|
|
10771
10878
|
return failure2("storage_error", `unable to list tasks: ${String(error)}`, {
|
|
10772
10879
|
path: tasksDir
|
|
@@ -10807,7 +10914,7 @@ class TaskStore {
|
|
|
10807
10914
|
const file = this.paths().workspace;
|
|
10808
10915
|
let bytes;
|
|
10809
10916
|
try {
|
|
10810
|
-
bytes = retryTransient(() =>
|
|
10917
|
+
bytes = retryTransient(() => fs8.readFileSync(file, "utf8"));
|
|
10811
10918
|
} catch (error) {
|
|
10812
10919
|
if (error.code === "ENOENT")
|
|
10813
10920
|
return success(null, null, null);
|
|
@@ -10858,7 +10965,7 @@ class TaskStore {
|
|
|
10858
10965
|
const moved = this.bindLocked(again.data.id, key, input.now);
|
|
10859
10966
|
return moved.ok ? success(moved.data.revision, null, { task: moved.data, created: false, key }) : moved;
|
|
10860
10967
|
}
|
|
10861
|
-
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" :
|
|
10968
|
+
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" : path8.basename(this.root) || this.root;
|
|
10862
10969
|
const intent = {
|
|
10863
10970
|
objective: input.objective?.trim() || `Work on ${label}`,
|
|
10864
10971
|
scope: { description: `the checkout (${label})`, paths: ["."], exclusions: [] },
|
|
@@ -10918,14 +11025,14 @@ class TaskStore {
|
|
|
10918
11025
|
const branch = key.ok ? key.data.branch : null;
|
|
10919
11026
|
if (!branch || !location.ok)
|
|
10920
11027
|
return success(null, null, null);
|
|
10921
|
-
const common =
|
|
10922
|
-
const heads =
|
|
10923
|
-
const headLog = location.data.git ?
|
|
11028
|
+
const common = path8.dirname(location.data.dir);
|
|
11029
|
+
const heads = path8.join(common, "logs", "refs", "heads");
|
|
11030
|
+
const headLog = location.data.git ? path8.join(location.data.git.gitDir, "logs", "HEAD") : "";
|
|
10924
11031
|
const signatures = [
|
|
10925
|
-
|
|
11032
|
+
path8.join(heads, branch),
|
|
10926
11033
|
headLog,
|
|
10927
11034
|
heads,
|
|
10928
|
-
|
|
11035
|
+
path8.join(common, "packed-refs")
|
|
10929
11036
|
].map((file) => fileSignature(file) ?? "-");
|
|
10930
11037
|
const slot = `${location.data.dir}\x00${headLog}\x00${branch}`;
|
|
10931
11038
|
let resolved = origins.get(slot);
|
|
@@ -10937,7 +11044,7 @@ class TaskStore {
|
|
|
10937
11044
|
aliases.push(rename.from);
|
|
10938
11045
|
const start = branchStart(location.data, branch, aliases);
|
|
10939
11046
|
const slash = start?.indexOf("/") ?? -1;
|
|
10940
|
-
const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(
|
|
11047
|
+
const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(path8.join(common, "config")).includes(`[remote "${start.slice(0, slash)}"]`));
|
|
10941
11048
|
const name = start && (remote ? start.slice(slash + 1) : start);
|
|
10942
11049
|
resolved = {
|
|
10943
11050
|
signature: signatures.join("|"),
|
|
@@ -11175,13 +11282,13 @@ class TaskStore {
|
|
|
11175
11282
|
if (!ready.ok)
|
|
11176
11283
|
return ready;
|
|
11177
11284
|
const dirs = this.paths();
|
|
11178
|
-
const recovery =
|
|
11179
|
-
if (
|
|
11285
|
+
const recovery = path8.join(this.root, ".workit", "recovery");
|
|
11286
|
+
if (fs8.existsSync(recovery)) {
|
|
11180
11287
|
let files = 0;
|
|
11181
11288
|
let bytes = 0;
|
|
11182
11289
|
try {
|
|
11183
|
-
for (const name of
|
|
11184
|
-
const stat =
|
|
11290
|
+
for (const name of fs8.readdirSync(recovery)) {
|
|
11291
|
+
const stat = fs8.lstatSync(path8.join(recovery, name));
|
|
11185
11292
|
if (!stat.isFile())
|
|
11186
11293
|
continue;
|
|
11187
11294
|
files += 1;
|
|
@@ -11190,17 +11297,17 @@ class TaskStore {
|
|
|
11190
11297
|
} catch {}
|
|
11191
11298
|
const remove = options.pruneRecovery === true && !dryRun;
|
|
11192
11299
|
if (remove)
|
|
11193
|
-
|
|
11300
|
+
fs8.rmSync(recovery, { recursive: true, force: true });
|
|
11194
11301
|
report.legacyRecovery = { path: recovery, files, bytes, removed: remove };
|
|
11195
11302
|
}
|
|
11196
|
-
if (!
|
|
11303
|
+
if (!fs8.existsSync(dirs.tasks))
|
|
11197
11304
|
return success(null, null, report);
|
|
11198
|
-
const names =
|
|
11305
|
+
const names = fs8.readdirSync(dirs.tasks).filter((name) => validId(name));
|
|
11199
11306
|
for (const name of names) {
|
|
11200
11307
|
const file = this.eventsPath(name);
|
|
11201
11308
|
let size = 0;
|
|
11202
11309
|
try {
|
|
11203
|
-
size =
|
|
11310
|
+
size = fs8.statSync(file).size;
|
|
11204
11311
|
} catch {
|
|
11205
11312
|
continue;
|
|
11206
11313
|
}
|
|
@@ -11228,16 +11335,16 @@ class TaskStore {
|
|
|
11228
11335
|
const references = new Set;
|
|
11229
11336
|
for (const name of names)
|
|
11230
11337
|
try {
|
|
11231
|
-
for (const match of
|
|
11338
|
+
for (const match of fs8.readFileSync(this.eventsPath(name), "utf8").matchAll(/"\$blob":"([0-9a-f]{64})"/g))
|
|
11232
11339
|
references.add(match[1]);
|
|
11233
11340
|
} catch {}
|
|
11234
|
-
if (
|
|
11235
|
-
for (const name of
|
|
11341
|
+
if (fs8.existsSync(dirs.blobs))
|
|
11342
|
+
for (const name of fs8.readdirSync(dirs.blobs)) {
|
|
11236
11343
|
const digest = name.replace(/\.json$/, "");
|
|
11237
|
-
const file =
|
|
11344
|
+
const file = path8.join(dirs.blobs, name);
|
|
11238
11345
|
let stat;
|
|
11239
11346
|
try {
|
|
11240
|
-
stat =
|
|
11347
|
+
stat = fs8.lstatSync(file);
|
|
11241
11348
|
} catch {
|
|
11242
11349
|
continue;
|
|
11243
11350
|
}
|
|
@@ -11248,11 +11355,11 @@ class TaskStore {
|
|
|
11248
11355
|
report.blobs.removed += 1;
|
|
11249
11356
|
report.blobs.removedBytes += stat.size;
|
|
11250
11357
|
if (!dryRun)
|
|
11251
|
-
|
|
11358
|
+
fs8.rmSync(file, { force: true });
|
|
11252
11359
|
}
|
|
11253
11360
|
return success(null, null, null);
|
|
11254
11361
|
};
|
|
11255
|
-
const swept = dryRun ? sweep() : this.withLock(
|
|
11362
|
+
const swept = dryRun ? sweep() : this.withLock(path8.join(dirs.dir, "gc.lock"), sweep);
|
|
11256
11363
|
if (!swept.ok)
|
|
11257
11364
|
return swept;
|
|
11258
11365
|
const temporaryDirs = [
|
|
@@ -11260,17 +11367,17 @@ class TaskStore {
|
|
|
11260
11367
|
dirs.tasks,
|
|
11261
11368
|
dirs.blobs,
|
|
11262
11369
|
dirs.checkout,
|
|
11263
|
-
...names.map((name) =>
|
|
11370
|
+
...names.map((name) => path8.join(dirs.tasks, name))
|
|
11264
11371
|
];
|
|
11265
11372
|
for (const directory of temporaryDirs) {
|
|
11266
|
-
if (!
|
|
11373
|
+
if (!fs8.existsSync(directory))
|
|
11267
11374
|
continue;
|
|
11268
|
-
for (const name of
|
|
11375
|
+
for (const name of fs8.readdirSync(directory)) {
|
|
11269
11376
|
if (!name.endsWith(".tmp") && !name.endsWith(".probe"))
|
|
11270
11377
|
continue;
|
|
11271
|
-
const file =
|
|
11378
|
+
const file = path8.join(directory, name);
|
|
11272
11379
|
try {
|
|
11273
|
-
const stat =
|
|
11380
|
+
const stat = fs8.lstatSync(file);
|
|
11274
11381
|
if (!stat.isFile() || nowMs - stat.mtimeMs <= STALE_TEMPORARY_MS)
|
|
11275
11382
|
continue;
|
|
11276
11383
|
} catch {
|
|
@@ -11278,37 +11385,37 @@ class TaskStore {
|
|
|
11278
11385
|
}
|
|
11279
11386
|
report.temporary.removed += 1;
|
|
11280
11387
|
if (!dryRun)
|
|
11281
|
-
|
|
11388
|
+
fs8.rmSync(file, { force: true });
|
|
11282
11389
|
}
|
|
11283
11390
|
}
|
|
11284
11391
|
return success(null, null, report);
|
|
11285
11392
|
}
|
|
11286
11393
|
paths() {
|
|
11287
11394
|
const location = this.location();
|
|
11288
|
-
const dir = location.ok ? location.data.dir :
|
|
11289
|
-
const checkout =
|
|
11395
|
+
const dir = location.ok ? location.data.dir : path8.join(this.root, ".workit");
|
|
11396
|
+
const checkout = path8.join(dir, "checkouts", checkoutSlug(this.root));
|
|
11290
11397
|
return {
|
|
11291
11398
|
dir,
|
|
11292
11399
|
shared: location.ok ? location.data.shared : false,
|
|
11293
|
-
marker:
|
|
11400
|
+
marker: path8.join(dir, "store.json"),
|
|
11294
11401
|
checkout,
|
|
11295
|
-
workspace:
|
|
11296
|
-
checkoutLock:
|
|
11297
|
-
tasks:
|
|
11298
|
-
index:
|
|
11299
|
-
blobs:
|
|
11300
|
-
legacy:
|
|
11301
|
-
v2:
|
|
11402
|
+
workspace: path8.join(checkout, "workspace.json"),
|
|
11403
|
+
checkoutLock: path8.join(checkout, "metadata.lock"),
|
|
11404
|
+
tasks: path8.join(dir, "tasks"),
|
|
11405
|
+
index: path8.join(dir, "task-index.json"),
|
|
11406
|
+
blobs: path8.join(dir, "blobs", "candidates"),
|
|
11407
|
+
legacy: path8.join(dir, "legacy", checkoutSlug(this.root), "v2"),
|
|
11408
|
+
v2: path8.join(this.root, ".workit")
|
|
11302
11409
|
};
|
|
11303
11410
|
}
|
|
11304
11411
|
eventsPath(taskId) {
|
|
11305
|
-
return
|
|
11412
|
+
return path8.join(this.paths().tasks, taskId, "events.jsonl");
|
|
11306
11413
|
}
|
|
11307
11414
|
snapshotPath(taskId) {
|
|
11308
|
-
return
|
|
11415
|
+
return path8.join(this.paths().tasks, taskId, "snapshot.json");
|
|
11309
11416
|
}
|
|
11310
11417
|
taskLockPath(taskId) {
|
|
11311
|
-
return
|
|
11418
|
+
return path8.join(this.paths().tasks, taskId, "lock");
|
|
11312
11419
|
}
|
|
11313
11420
|
ready(write = false) {
|
|
11314
11421
|
const location = this.location();
|
|
@@ -11318,7 +11425,7 @@ class TaskStore {
|
|
|
11318
11425
|
return success(null, null, null);
|
|
11319
11426
|
const dirs = this.paths();
|
|
11320
11427
|
try {
|
|
11321
|
-
const marker = JSON.parse(
|
|
11428
|
+
const marker = JSON.parse(fs8.readFileSync(dirs.marker, "utf8"));
|
|
11322
11429
|
if (typeof marker.version === "number" && marker.version > STORE_FORMAT)
|
|
11323
11430
|
return failure2("recovery_required", `the workit store at ${dirs.dir} has format ${marker.version}; upgrade Workit to use it`, { path: dirs.marker });
|
|
11324
11431
|
} catch {}
|
|
@@ -11347,21 +11454,21 @@ class TaskStore {
|
|
|
11347
11454
|
return {
|
|
11348
11455
|
read: (digest) => {
|
|
11349
11456
|
try {
|
|
11350
|
-
return JSON.parse(
|
|
11457
|
+
return JSON.parse(fs8.readFileSync(path8.join(dir, `${digest}.json`), "utf8"));
|
|
11351
11458
|
} catch (error) {
|
|
11352
11459
|
throw new LogDamage(`stored candidate ${digest} is missing or unreadable: ${String(error)}`);
|
|
11353
11460
|
}
|
|
11354
11461
|
},
|
|
11355
11462
|
write: (digest, value) => {
|
|
11356
|
-
const file =
|
|
11357
|
-
if (
|
|
11463
|
+
const file = path8.join(dir, `${digest}.json`);
|
|
11464
|
+
if (fs8.existsSync(file)) {
|
|
11358
11465
|
const stamp = new Date;
|
|
11359
11466
|
try {
|
|
11360
|
-
|
|
11467
|
+
fs8.utimesSync(file, stamp, stamp);
|
|
11361
11468
|
return;
|
|
11362
11469
|
} catch {}
|
|
11363
11470
|
}
|
|
11364
|
-
|
|
11471
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
11365
11472
|
replaceFile(file, JSON.stringify(value), true);
|
|
11366
11473
|
}
|
|
11367
11474
|
};
|
|
@@ -11443,7 +11550,7 @@ class TaskStore {
|
|
|
11443
11550
|
}
|
|
11444
11551
|
readSnapshot(taskId) {
|
|
11445
11552
|
try {
|
|
11446
|
-
const value = JSON.parse(
|
|
11553
|
+
const value = JSON.parse(fs8.readFileSync(this.snapshotPath(taskId), "utf8"));
|
|
11447
11554
|
if (!isObject5(value) || value.v !== 1 || value.task !== taskId || typeof value.seq !== "number" || typeof value.lastId !== "string" || typeof value.lastStart !== "number" || typeof value.end !== "number" || !isObject5(value.record))
|
|
11448
11555
|
return null;
|
|
11449
11556
|
return value;
|
|
@@ -11545,8 +11652,8 @@ class TaskStore {
|
|
|
11545
11652
|
}
|
|
11546
11653
|
}
|
|
11547
11654
|
openTask(record, type, key, legacy = null) {
|
|
11548
|
-
const dir =
|
|
11549
|
-
|
|
11655
|
+
const dir = path8.join(this.paths().tasks, record.id);
|
|
11656
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
11550
11657
|
const blobs = this.blobStore();
|
|
11551
11658
|
const bound = key ? { ...key, boundAt: key.boundAt ?? new Date().toISOString() } : null;
|
|
11552
11659
|
const event = this.event(record.id, 1, type, {
|
|
@@ -11598,11 +11705,11 @@ class TaskStore {
|
|
|
11598
11705
|
} catch {}
|
|
11599
11706
|
if (!latest || latest.events[0]?.id !== last.id)
|
|
11600
11707
|
return failure2("busy", "the task log changed during compaction; retry", { taskId });
|
|
11601
|
-
const appended =
|
|
11708
|
+
const appended = fs8.readFileSync(file).subarray(log.end, latest.end);
|
|
11602
11709
|
const bytes = Buffer.concat([Buffer.from(prefix, "utf8"), appended]);
|
|
11603
11710
|
replaceFile(file, bytes.toString("utf8"), true);
|
|
11604
11711
|
try {
|
|
11605
|
-
|
|
11712
|
+
fs8.rmSync(this.snapshotPath(taskId), { force: true });
|
|
11606
11713
|
} catch {}
|
|
11607
11714
|
const reloaded = this.load(taskId);
|
|
11608
11715
|
if (reloaded.ok && reloaded.data)
|
|
@@ -11615,7 +11722,7 @@ class TaskStore {
|
|
|
11615
11722
|
}
|
|
11616
11723
|
countEvents(file) {
|
|
11617
11724
|
try {
|
|
11618
|
-
const bytes =
|
|
11725
|
+
const bytes = fs8.readFileSync(file);
|
|
11619
11726
|
let count = 0;
|
|
11620
11727
|
for (let index = bytes.indexOf(10);index >= 0; index = bytes.indexOf(10, index + 1))
|
|
11621
11728
|
count += 1;
|
|
@@ -11637,8 +11744,8 @@ class TaskStore {
|
|
|
11637
11744
|
return bound.ok ? success(null, null, bound.data[0] ?? null) : bound;
|
|
11638
11745
|
}
|
|
11639
11746
|
withKeyLock(key, operation) {
|
|
11640
|
-
const digest =
|
|
11641
|
-
return this.withLock(
|
|
11747
|
+
const digest = createHash5("sha256").update(key).digest("hex").slice(0, 32);
|
|
11748
|
+
return this.withLock(path8.join(this.paths().dir, "keys", `${digest}.lock`), operation);
|
|
11642
11749
|
}
|
|
11643
11750
|
bindLocked(taskId, key, timestamp) {
|
|
11644
11751
|
const current = this.readWorkspace();
|
|
@@ -11779,7 +11886,7 @@ class TaskStore {
|
|
|
11779
11886
|
writeWorkspace(record) {
|
|
11780
11887
|
const file = this.paths().workspace;
|
|
11781
11888
|
try {
|
|
11782
|
-
|
|
11889
|
+
fs8.mkdirSync(path8.dirname(file), { recursive: true });
|
|
11783
11890
|
replaceFile(file, `${canonicalJson(record)}
|
|
11784
11891
|
`, true);
|
|
11785
11892
|
return success(null, null, null);
|
|
@@ -11789,7 +11896,7 @@ class TaskStore {
|
|
|
11789
11896
|
}
|
|
11790
11897
|
readIndex() {
|
|
11791
11898
|
try {
|
|
11792
|
-
const value = JSON.parse(
|
|
11899
|
+
const value = JSON.parse(fs8.readFileSync(this.paths().index, "utf8"));
|
|
11793
11900
|
if (!isObject5(value) || value.version !== INDEX_VERSION || !isObject5(value.tasks))
|
|
11794
11901
|
return {};
|
|
11795
11902
|
const tasks = {};
|
|
@@ -11803,7 +11910,7 @@ class TaskStore {
|
|
|
11803
11910
|
}
|
|
11804
11911
|
writeIndex(entries) {
|
|
11805
11912
|
try {
|
|
11806
|
-
|
|
11913
|
+
fs8.mkdirSync(this.paths().tasks, { recursive: true });
|
|
11807
11914
|
replaceFile(this.paths().index, JSON.stringify({
|
|
11808
11915
|
version: INDEX_VERSION,
|
|
11809
11916
|
tasks: Object.fromEntries(entries.map((entry) => [entry.id, entry]))
|
|
@@ -11857,23 +11964,23 @@ class TaskStore {
|
|
|
11857
11964
|
return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
|
|
11858
11965
|
}
|
|
11859
11966
|
v2Dirs() {
|
|
11860
|
-
return [...new Set([this.root, this.origin])].map((dir) =>
|
|
11967
|
+
return [...new Set([this.root, this.origin])].map((dir) => path8.join(dir, ".workit"));
|
|
11861
11968
|
}
|
|
11862
11969
|
pendingMigrations() {
|
|
11863
11970
|
return this.v2Dirs().filter((dir) => {
|
|
11864
|
-
const raw = this.readV2Workspace(
|
|
11971
|
+
const raw = this.readV2Workspace(path8.join(dir, "workspace.json"));
|
|
11865
11972
|
return raw !== null && raw !== TOMBSTONE;
|
|
11866
11973
|
});
|
|
11867
11974
|
}
|
|
11868
11975
|
migrateV2(v2) {
|
|
11869
11976
|
const dirs = this.paths();
|
|
11870
|
-
const v2Workspace =
|
|
11871
|
-
const v2Tasks =
|
|
11872
|
-
const legacy =
|
|
11977
|
+
const v2Workspace = path8.join(v2, "workspace.json");
|
|
11978
|
+
const v2Tasks = path8.join(v2, "tasks");
|
|
11979
|
+
const legacy = path8.join(dirs.dir, "legacy", checkoutSlug(path8.dirname(v2)), "v2");
|
|
11873
11980
|
const raw = this.readV2Workspace(v2Workspace);
|
|
11874
11981
|
if (raw === null || raw === TOMBSTONE)
|
|
11875
11982
|
return success(null, null, null);
|
|
11876
|
-
return this.withLock(dirs.checkoutLock, () => this.withLock(
|
|
11983
|
+
return this.withLock(dirs.checkoutLock, () => this.withLock(path8.join(v2, "metadata.lock"), () => {
|
|
11877
11984
|
const workspace = this.readV2Workspace(v2Workspace);
|
|
11878
11985
|
if (workspace === null || workspace === TOMBSTONE)
|
|
11879
11986
|
return success(null, null, null);
|
|
@@ -11882,7 +11989,7 @@ class TaskStore {
|
|
|
11882
11989
|
return failure2("recovery_required", `cannot migrate the 2.x store at ${v2}: ${parsedWorkspace.error}; move it aside to start fresh`, { path: v2Workspace });
|
|
11883
11990
|
let files = [];
|
|
11884
11991
|
try {
|
|
11885
|
-
files =
|
|
11992
|
+
files = fs8.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(path8.join(v2Tasks, name)));
|
|
11886
11993
|
} catch {}
|
|
11887
11994
|
const current = this.readWorkspace();
|
|
11888
11995
|
if (!current.ok)
|
|
@@ -11900,22 +12007,22 @@ class TaskStore {
|
|
|
11900
12007
|
let skipped = 0;
|
|
11901
12008
|
const done = new Map;
|
|
11902
12009
|
try {
|
|
11903
|
-
|
|
12010
|
+
fs8.mkdirSync(path8.join(legacy, "tasks"), { recursive: true });
|
|
11904
12011
|
for (const name of ["workspace.json", "index.json"])
|
|
11905
|
-
if (
|
|
11906
|
-
|
|
12012
|
+
if (fs8.existsSync(path8.join(v2, name)))
|
|
12013
|
+
fs8.copyFileSync(path8.join(v2, name), path8.join(legacy, name));
|
|
11907
12014
|
for (const name of files) {
|
|
11908
12015
|
const id = name.slice(0, -5);
|
|
11909
|
-
const file =
|
|
11910
|
-
const bytes =
|
|
12016
|
+
const file = path8.join(v2Tasks, name);
|
|
12017
|
+
const bytes = fs8.readFileSync(file, "utf8");
|
|
11911
12018
|
const digest = sha256(bytes);
|
|
11912
|
-
|
|
12019
|
+
fs8.writeFileSync(path8.join(legacy, "tasks", name), bytes);
|
|
11913
12020
|
const parsed = this.parseRecord(bytes, taskRecordSchema, "2.x task");
|
|
11914
12021
|
if (!parsed.ok || parsed.data.id !== id)
|
|
11915
12022
|
return failure2("recovery_required", `cannot migrate 2.x task ${id}: ${parsed.ok ? "file name and record ID differ" : parsed.error}; move ${file} aside to continue`, { path: file });
|
|
11916
12023
|
const record = { ...parsed.data, workspaceId: target.id };
|
|
11917
|
-
const origin = { path:
|
|
11918
|
-
if (!
|
|
12024
|
+
const origin = { path: path8.join(legacy, "tasks", name), digest };
|
|
12025
|
+
if (!fs8.existsSync(this.eventsPath(id))) {
|
|
11919
12026
|
this.openTask(record, "migrated.from_v2", null, origin);
|
|
11920
12027
|
migrated += 1;
|
|
11921
12028
|
} else {
|
|
@@ -11950,8 +12057,8 @@ class TaskStore {
|
|
|
11950
12057
|
try {
|
|
11951
12058
|
let complete = true;
|
|
11952
12059
|
for (const [name, digest] of done) {
|
|
11953
|
-
const file =
|
|
11954
|
-
if (sha256(
|
|
12060
|
+
const file = path8.join(v2Tasks, name);
|
|
12061
|
+
if (sha256(fs8.readFileSync(file, "utf8")) !== digest) {
|
|
11955
12062
|
complete = false;
|
|
11956
12063
|
continue;
|
|
11957
12064
|
}
|
|
@@ -11961,7 +12068,7 @@ class TaskStore {
|
|
|
11961
12068
|
return failure2("busy", `the 2.x store at ${v2} changed during migration; retry`, {
|
|
11962
12069
|
path: v2
|
|
11963
12070
|
});
|
|
11964
|
-
|
|
12071
|
+
fs8.rmSync(path8.join(v2, "index.json"), { force: true });
|
|
11965
12072
|
this.writeTombstone(v2, target.id);
|
|
11966
12073
|
} catch (error) {
|
|
11967
12074
|
return failure2("storage_error", `cannot finish the 2.x migration: ${String(error)}`, {
|
|
@@ -11981,45 +12088,45 @@ class TaskStore {
|
|
|
11981
12088
|
}
|
|
11982
12089
|
adoptLocalStore() {
|
|
11983
12090
|
const dirs = this.paths();
|
|
11984
|
-
const marker =
|
|
11985
|
-
if (!dirs.shared || !
|
|
12091
|
+
const marker = path8.join(dirs.v2, "store.json");
|
|
12092
|
+
if (!dirs.shared || !fs8.existsSync(marker))
|
|
11986
12093
|
return success(null, null, null);
|
|
11987
12094
|
return this.withLock(dirs.checkoutLock, () => {
|
|
11988
|
-
if (!
|
|
12095
|
+
if (!fs8.existsSync(marker))
|
|
11989
12096
|
return success(null, null, null);
|
|
11990
12097
|
let tasks = 0;
|
|
11991
12098
|
try {
|
|
11992
12099
|
const merge = (from, to) => {
|
|
11993
|
-
if (!
|
|
11994
|
-
|
|
12100
|
+
if (!fs8.existsSync(to)) {
|
|
12101
|
+
fs8.mkdirSync(path8.dirname(to), { recursive: true });
|
|
11995
12102
|
try {
|
|
11996
|
-
|
|
12103
|
+
fs8.renameSync(from, to);
|
|
11997
12104
|
} catch {
|
|
11998
|
-
|
|
11999
|
-
|
|
12105
|
+
fs8.cpSync(from, to, { recursive: true, errorOnExist: true, force: false });
|
|
12106
|
+
fs8.rmSync(from, { recursive: true, force: true });
|
|
12000
12107
|
}
|
|
12001
12108
|
return;
|
|
12002
12109
|
}
|
|
12003
|
-
if (
|
|
12004
|
-
for (const name of
|
|
12005
|
-
merge(
|
|
12110
|
+
if (fs8.lstatSync(from).isDirectory() && fs8.lstatSync(to).isDirectory())
|
|
12111
|
+
for (const name of fs8.readdirSync(from))
|
|
12112
|
+
merge(path8.join(from, name), path8.join(to, name));
|
|
12006
12113
|
};
|
|
12007
12114
|
for (const sub of ["tasks", "checkouts", "blobs", "legacy"]) {
|
|
12008
|
-
const from =
|
|
12009
|
-
if (!
|
|
12115
|
+
const from = path8.join(dirs.v2, sub);
|
|
12116
|
+
if (!fs8.existsSync(from))
|
|
12010
12117
|
continue;
|
|
12011
12118
|
if (sub === "tasks") {
|
|
12012
|
-
for (const name of
|
|
12013
|
-
if (validId(name) && !
|
|
12119
|
+
for (const name of fs8.readdirSync(from))
|
|
12120
|
+
if (validId(name) && !fs8.existsSync(path8.join(dirs.dir, sub, name)))
|
|
12014
12121
|
tasks += 1;
|
|
12015
12122
|
}
|
|
12016
|
-
merge(from,
|
|
12123
|
+
merge(from, path8.join(dirs.dir, sub));
|
|
12017
12124
|
}
|
|
12018
|
-
const ledger =
|
|
12019
|
-
if (
|
|
12020
|
-
merge(ledger,
|
|
12021
|
-
|
|
12022
|
-
|
|
12125
|
+
const ledger = path8.join(dirs.v2, "ledger");
|
|
12126
|
+
if (fs8.existsSync(ledger) && !fs8.existsSync(path8.join(dirs.dir, "ledger")))
|
|
12127
|
+
merge(ledger, path8.join(dirs.dir, "ledger"));
|
|
12128
|
+
fs8.rmSync(path8.join(dirs.v2, "task-index.json"), { force: true });
|
|
12129
|
+
fs8.renameSync(marker, path8.join(dirs.v2, "store.moved.json"));
|
|
12023
12130
|
} catch (error) {
|
|
12024
12131
|
return failure2("storage_error", `cannot move ${dirs.v2} into ${dirs.dir}: ${String(error)}`, {
|
|
12025
12132
|
path: dirs.v2
|
|
@@ -12039,7 +12146,7 @@ class TaskStore {
|
|
|
12039
12146
|
readV2Workspace(file) {
|
|
12040
12147
|
let bytes;
|
|
12041
12148
|
try {
|
|
12042
|
-
bytes =
|
|
12149
|
+
bytes = fs8.readFileSync(file, "utf8");
|
|
12043
12150
|
} catch {
|
|
12044
12151
|
return null;
|
|
12045
12152
|
}
|
|
@@ -12057,7 +12164,7 @@ class TaskStore {
|
|
|
12057
12164
|
schemaVersion: SCHEMA_VERSION,
|
|
12058
12165
|
id: workspaceId ?? newId(),
|
|
12059
12166
|
revision: newRevision(),
|
|
12060
|
-
root:
|
|
12167
|
+
root: path8.dirname(v2),
|
|
12061
12168
|
runtime: { createdWith: version, updatedWith: version },
|
|
12062
12169
|
writer: null,
|
|
12063
12170
|
store: {
|
|
@@ -12068,12 +12175,12 @@ class TaskStore {
|
|
|
12068
12175
|
},
|
|
12069
12176
|
critical: ["store"]
|
|
12070
12177
|
};
|
|
12071
|
-
|
|
12072
|
-
const ignore =
|
|
12073
|
-
if (!
|
|
12074
|
-
|
|
12178
|
+
fs8.mkdirSync(v2, { recursive: true });
|
|
12179
|
+
const ignore = path8.join(v2, ".gitignore");
|
|
12180
|
+
if (!fs8.existsSync(ignore))
|
|
12181
|
+
fs8.writeFileSync(ignore, `*
|
|
12075
12182
|
`);
|
|
12076
|
-
replaceFile(
|
|
12183
|
+
replaceFile(path8.join(v2, "workspace.json"), `${JSON.stringify(marker, null, 2)}
|
|
12077
12184
|
`, true);
|
|
12078
12185
|
}
|
|
12079
12186
|
stubFor(taskId) {
|
|
@@ -12089,7 +12196,7 @@ class TaskStore {
|
|
|
12089
12196
|
}
|
|
12090
12197
|
isStub(file) {
|
|
12091
12198
|
try {
|
|
12092
|
-
const value = JSON.parse(
|
|
12199
|
+
const value = JSON.parse(fs8.readFileSync(file, "utf8"));
|
|
12093
12200
|
return isObject5(value) && isObject5(value.store) && value.store.format === "workit-store";
|
|
12094
12201
|
} catch {
|
|
12095
12202
|
return false;
|
|
@@ -12097,32 +12204,32 @@ class TaskStore {
|
|
|
12097
12204
|
}
|
|
12098
12205
|
writeTombstoneIfMissing() {
|
|
12099
12206
|
try {
|
|
12100
|
-
const v2 =
|
|
12101
|
-
if (
|
|
12207
|
+
const v2 = path8.join(this.root, ".workit");
|
|
12208
|
+
if (fs8.existsSync(path8.join(v2, "workspace.json")))
|
|
12102
12209
|
return;
|
|
12103
12210
|
let id = null;
|
|
12104
12211
|
try {
|
|
12105
|
-
id = JSON.parse(
|
|
12212
|
+
id = JSON.parse(fs8.readFileSync(this.paths().workspace, "utf8")).id ?? null;
|
|
12106
12213
|
} catch {}
|
|
12107
12214
|
this.writeTombstone(v2, id);
|
|
12108
12215
|
} catch {}
|
|
12109
12216
|
}
|
|
12110
12217
|
initializeStorage() {
|
|
12111
12218
|
const dirs = this.paths();
|
|
12112
|
-
retryTransient(() =>
|
|
12113
|
-
retryTransient(() =>
|
|
12114
|
-
if (!
|
|
12219
|
+
retryTransient(() => fs8.mkdirSync(dirs.checkout, { recursive: true }));
|
|
12220
|
+
retryTransient(() => fs8.mkdirSync(dirs.tasks, { recursive: true }));
|
|
12221
|
+
if (!fs8.existsSync(dirs.marker))
|
|
12115
12222
|
replaceFile(dirs.marker, `${JSON.stringify({ format: "workit-store", version: STORE_FORMAT, createdWith: runtimeVersion() })}
|
|
12116
12223
|
`, false);
|
|
12117
12224
|
if (!dirs.shared) {
|
|
12118
|
-
const ignore =
|
|
12225
|
+
const ignore = path8.join(dirs.dir, ".gitignore");
|
|
12119
12226
|
let current = null;
|
|
12120
12227
|
try {
|
|
12121
|
-
current =
|
|
12228
|
+
current = fs8.readFileSync(ignore, "utf8");
|
|
12122
12229
|
} catch {}
|
|
12123
12230
|
if (current !== `*
|
|
12124
12231
|
`)
|
|
12125
|
-
retryTransient(() =>
|
|
12232
|
+
retryTransient(() => fs8.writeFileSync(ignore, `*
|
|
12126
12233
|
`));
|
|
12127
12234
|
}
|
|
12128
12235
|
this.writeTombstoneIfMissing();
|
|
@@ -12138,7 +12245,7 @@ class TaskStore {
|
|
|
12138
12245
|
shouldReclaim: ({ payload, nowMs }) => {
|
|
12139
12246
|
let ageMs = null;
|
|
12140
12247
|
try {
|
|
12141
|
-
ageMs = nowMs -
|
|
12248
|
+
ageMs = nowMs - fs8.lstatSync(lockPath).mtimeMs;
|
|
12142
12249
|
} catch {}
|
|
12143
12250
|
return classifyLockOwner(payload, ageMs, localLockHost(), TASK_STORE_LOCK).state === "stale";
|
|
12144
12251
|
},
|
|
@@ -12175,7 +12282,7 @@ class TaskStore {
|
|
|
12175
12282
|
withLock(lockPath, operation) {
|
|
12176
12283
|
try {
|
|
12177
12284
|
this.initializeStorage();
|
|
12178
|
-
|
|
12285
|
+
fs8.mkdirSync(path8.dirname(lockPath), { recursive: true });
|
|
12179
12286
|
} catch (error) {
|
|
12180
12287
|
return failure2("storage_error", `unable to initialize store: ${String(error)}`, {
|
|
12181
12288
|
path: this.paths().dir
|
|
@@ -12215,18 +12322,18 @@ class TaskStore {
|
|
|
12215
12322
|
lockDirWritable(lockPath) {
|
|
12216
12323
|
const probe = `${lockPath}.${process.pid}.${randomUUID3()}.probe`;
|
|
12217
12324
|
try {
|
|
12218
|
-
|
|
12325
|
+
fs8.closeSync(fs8.openSync(probe, "wx", 384));
|
|
12219
12326
|
} catch {
|
|
12220
12327
|
return false;
|
|
12221
12328
|
}
|
|
12222
12329
|
try {
|
|
12223
|
-
|
|
12330
|
+
fs8.rmSync(probe, { force: true });
|
|
12224
12331
|
} catch {}
|
|
12225
12332
|
return true;
|
|
12226
12333
|
}
|
|
12227
12334
|
lockHolderPresent(lockPath) {
|
|
12228
12335
|
try {
|
|
12229
|
-
|
|
12336
|
+
fs8.lstatSync(lockPath);
|
|
12230
12337
|
return true;
|
|
12231
12338
|
} catch (error) {
|
|
12232
12339
|
return error?.code !== "ENOENT";
|
|
@@ -12238,12 +12345,12 @@ class TaskStore {
|
|
|
12238
12345
|
if (isTransientWindowsError(error) && !this.lockHolderPresent(lockPath) && !this.lockDirWritable(lockPath))
|
|
12239
12346
|
return failure2("storage_error", `cannot create the workit lock (${code}); no other Workit call holds it`, {
|
|
12240
12347
|
path: lockPath,
|
|
12241
|
-
guidance: `Check permissions and the read-only attribute on ${
|
|
12348
|
+
guidance: `Check permissions and the read-only attribute on ${path8.dirname(lockPath)}.`
|
|
12242
12349
|
});
|
|
12243
12350
|
if (code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || isTransientWindowsError(error)) {
|
|
12244
12351
|
let lock = null;
|
|
12245
12352
|
try {
|
|
12246
|
-
lock = parseMetadataLockOrNull(
|
|
12353
|
+
lock = parseMetadataLockOrNull(fs8.readFileSync(lockPath, "utf8"));
|
|
12247
12354
|
} catch {}
|
|
12248
12355
|
return failure2("busy", `workit lock is held by another Workit call${lock ? ` (pid ${lock.pid} on ${lock.host})` : ""}; retry shortly`, {
|
|
12249
12356
|
outcome: "not_started",
|
|
@@ -12261,15 +12368,15 @@ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
|
12261
12368
|
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
12262
12369
|
|
|
12263
12370
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
12264
|
-
import { createHash as
|
|
12265
|
-
import * as
|
|
12371
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
12372
|
+
import * as fs13 from "node:fs";
|
|
12266
12373
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
12267
|
-
import
|
|
12374
|
+
import path16 from "node:path";
|
|
12268
12375
|
|
|
12269
12376
|
// packages/workit-core/src/check-config.ts
|
|
12270
12377
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
12271
|
-
import
|
|
12272
|
-
import
|
|
12378
|
+
import fs9 from "node:fs";
|
|
12379
|
+
import path9 from "node:path";
|
|
12273
12380
|
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
12274
12381
|
var CHECKS_FILE = "workit.checks.json";
|
|
12275
12382
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -12322,10 +12429,10 @@ var git = (cwd, args) => {
|
|
|
12322
12429
|
return value || null;
|
|
12323
12430
|
};
|
|
12324
12431
|
function checkRoot(cwd) {
|
|
12325
|
-
return
|
|
12432
|
+
return path9.resolve(git(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
|
|
12326
12433
|
}
|
|
12327
12434
|
var packageManager = (root) => {
|
|
12328
|
-
const has = (file) =>
|
|
12435
|
+
const has = (file) => fs9.existsSync(path9.join(root, file));
|
|
12329
12436
|
if (has("bun.lock") || has("bun.lockb"))
|
|
12330
12437
|
return "bun";
|
|
12331
12438
|
if (has("pnpm-lock.yaml"))
|
|
@@ -12345,7 +12452,7 @@ var CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
|
|
|
12345
12452
|
function detectedChecks(root) {
|
|
12346
12453
|
const read = (file) => {
|
|
12347
12454
|
try {
|
|
12348
|
-
return
|
|
12455
|
+
return fs9.readFileSync(path9.join(root, file), "utf8");
|
|
12349
12456
|
} catch {
|
|
12350
12457
|
return null;
|
|
12351
12458
|
}
|
|
@@ -12384,7 +12491,7 @@ function detectedChecks(root) {
|
|
|
12384
12491
|
var GATES = ["testing", "verification"];
|
|
12385
12492
|
function loadCheckConfig(cwd) {
|
|
12386
12493
|
const root = checkRoot(cwd);
|
|
12387
|
-
const file =
|
|
12494
|
+
const file = path9.join(root, CHECKS_FILE);
|
|
12388
12495
|
const none = (error = null) => ({
|
|
12389
12496
|
source: error ? CHECKS_FILE : "none",
|
|
12390
12497
|
root,
|
|
@@ -12394,7 +12501,7 @@ function loadCheckConfig(cwd) {
|
|
|
12394
12501
|
});
|
|
12395
12502
|
let text = null;
|
|
12396
12503
|
try {
|
|
12397
|
-
text =
|
|
12504
|
+
text = fs9.readFileSync(file, "utf8");
|
|
12398
12505
|
} catch (error) {
|
|
12399
12506
|
if (error.code !== "ENOENT")
|
|
12400
12507
|
return none(`cannot read ${CHECKS_FILE}: ${error.message}`);
|
|
@@ -12457,10 +12564,10 @@ function workitAvailable(env = process.env) {
|
|
|
12457
12564
|
return workitOnPath;
|
|
12458
12565
|
const windows = process.platform === "win32";
|
|
12459
12566
|
const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
12460
|
-
const dirs = (env.PATH ?? env.Path ?? "").split(
|
|
12567
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path9.delimiter).filter(Boolean);
|
|
12461
12568
|
workitOnPath = dirs.some((dir) => exts.some((ext) => {
|
|
12462
12569
|
try {
|
|
12463
|
-
return
|
|
12570
|
+
return fs9.statSync(path9.join(dir, `workit${ext}`)).isFile();
|
|
12464
12571
|
} catch {
|
|
12465
12572
|
return false;
|
|
12466
12573
|
}
|
|
@@ -12473,10 +12580,10 @@ function checkCommand(args, version, onPath = workitAvailable()) {
|
|
|
12473
12580
|
|
|
12474
12581
|
// packages/workit-core/src/git/rev.ts
|
|
12475
12582
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
12476
|
-
import { createHash as
|
|
12477
|
-
import
|
|
12583
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
12584
|
+
import fs10 from "node:fs";
|
|
12478
12585
|
import os2 from "node:os";
|
|
12479
|
-
import
|
|
12586
|
+
import path10 from "node:path";
|
|
12480
12587
|
var MAX_BUFFER = 256 * 1024 * 1024;
|
|
12481
12588
|
var GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
|
|
12482
12589
|
var git2 = (cwd, args, options = {}) => {
|
|
@@ -12511,10 +12618,10 @@ function headSha(cwd) {
|
|
|
12511
12618
|
var emptyTree = (cwd) => line(git2(cwd, ["mktree"], { input: "" }));
|
|
12512
12619
|
var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
|
|
12513
12620
|
var seedIndex = (from, to) => {
|
|
12514
|
-
|
|
12515
|
-
const { atimeNs, mtimeNs } =
|
|
12621
|
+
fs10.copyFileSync(from, to);
|
|
12622
|
+
const { atimeNs, mtimeNs } = fs10.statSync(from, { bigint: true });
|
|
12516
12623
|
const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
|
|
12517
|
-
|
|
12624
|
+
fs10.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
|
|
12518
12625
|
};
|
|
12519
12626
|
function worktreeTree(cwd, options = {}) {
|
|
12520
12627
|
const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
|
|
@@ -12523,10 +12630,10 @@ function worktreeTree(cwd, options = {}) {
|
|
|
12523
12630
|
return null;
|
|
12524
12631
|
const cap = options.maxUntrackedBytes ?? MAX_UNTRACKED_BYTES;
|
|
12525
12632
|
const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.worktree;
|
|
12526
|
-
const scratch =
|
|
12633
|
+
const scratch = fs10.mkdtempSync(path10.join(os2.tmpdir(), "workit-tree-"));
|
|
12527
12634
|
try {
|
|
12528
|
-
const tempIndex =
|
|
12529
|
-
if (
|
|
12635
|
+
const tempIndex = path10.join(scratch, "index");
|
|
12636
|
+
if (fs10.existsSync(indexPath))
|
|
12530
12637
|
seedIndex(indexPath, tempIndex);
|
|
12531
12638
|
const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
|
|
12532
12639
|
const untracked = git2(top, ["ls-files", "--others", "--exclude-standard", "-z"], {
|
|
@@ -12538,7 +12645,7 @@ function worktreeTree(cwd, options = {}) {
|
|
|
12538
12645
|
const skipped = [];
|
|
12539
12646
|
for (const file of untracked.stdout.split("\x00").filter(Boolean)) {
|
|
12540
12647
|
try {
|
|
12541
|
-
const stat =
|
|
12648
|
+
const stat = fs10.statSync(path10.join(top, file));
|
|
12542
12649
|
if (stat.isFile() && stat.size > cap)
|
|
12543
12650
|
skipped.push({ path: file, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) });
|
|
12544
12651
|
} catch {}
|
|
@@ -12555,10 +12662,10 @@ function worktreeTree(cwd, options = {}) {
|
|
|
12555
12662
|
if (!tree)
|
|
12556
12663
|
return null;
|
|
12557
12664
|
const base = line(git2(top, ["rev-parse", "--verify", "-q", "HEAD^{tree}"])) ?? emptyTree(top);
|
|
12558
|
-
const key = skipped.length ? `sha256:${
|
|
12665
|
+
const key = skipped.length ? `sha256:${createHash6("sha256").update(JSON.stringify({ tree, skipped })).digest("hex")}` : tree;
|
|
12559
12666
|
return { tree, key, dirty: tree !== base || skipped.length > 0, skipped };
|
|
12560
12667
|
} finally {
|
|
12561
|
-
|
|
12668
|
+
fs10.rmSync(scratch, { recursive: true, force: true });
|
|
12562
12669
|
}
|
|
12563
12670
|
}
|
|
12564
12671
|
function worktreeSignal(cwd, options = {}) {
|
|
@@ -12578,16 +12685,16 @@ function worktreeSignal(cwd, options = {}) {
|
|
|
12578
12685
|
});
|
|
12579
12686
|
if (!status.ok)
|
|
12580
12687
|
return null;
|
|
12581
|
-
const hash =
|
|
12688
|
+
const hash = createHash6("sha256");
|
|
12582
12689
|
hash.update(`${headSha(top) ?? "unborn"}\x00`);
|
|
12583
12690
|
for (const entry of status.stdout.split("\x00")) {
|
|
12584
12691
|
if (entry.length < 4)
|
|
12585
12692
|
continue;
|
|
12586
12693
|
const file = entry.slice(3);
|
|
12587
|
-
const absolute =
|
|
12694
|
+
const absolute = path10.join(top, file);
|
|
12588
12695
|
let stat = "-";
|
|
12589
12696
|
try {
|
|
12590
|
-
const info =
|
|
12697
|
+
const info = fs10.lstatSync(absolute, { bigint: true });
|
|
12591
12698
|
stat = `${info.mode.toString(8)}:${info.size}:${info.mtimeNs}`;
|
|
12592
12699
|
if (info.isDirectory())
|
|
12593
12700
|
stat += `:${headSha(absolute) ?? "-"}`;
|
|
@@ -12654,17 +12761,17 @@ function pushRemoteName(cwd, branch) {
|
|
|
12654
12761
|
|
|
12655
12762
|
// packages/workit-core/src/core/policy/derive.ts
|
|
12656
12763
|
import { existsSync as existsSync4 } from "node:fs";
|
|
12657
|
-
import
|
|
12764
|
+
import path15 from "node:path";
|
|
12658
12765
|
|
|
12659
12766
|
// packages/workit-core/src/ledger.ts
|
|
12660
12767
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
12661
|
-
import { createHash as
|
|
12662
|
-
import
|
|
12663
|
-
import
|
|
12768
|
+
import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
|
|
12769
|
+
import fs12 from "node:fs";
|
|
12770
|
+
import path14 from "node:path";
|
|
12664
12771
|
|
|
12665
12772
|
// packages/workit-core/src/core/vcs-config.ts
|
|
12666
|
-
import
|
|
12667
|
-
import
|
|
12773
|
+
import fs11 from "node:fs";
|
|
12774
|
+
import path13 from "node:path";
|
|
12668
12775
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
12669
12776
|
|
|
12670
12777
|
// packages/workit-core/src/core/config.ts
|
|
@@ -12678,7 +12785,7 @@ import {
|
|
|
12678
12785
|
writeFileSync as writeFileSync2
|
|
12679
12786
|
} from "node:fs";
|
|
12680
12787
|
import os3 from "node:os";
|
|
12681
|
-
import
|
|
12788
|
+
import path11 from "node:path";
|
|
12682
12789
|
|
|
12683
12790
|
// packages/workit-core/src/core/boundary.ts
|
|
12684
12791
|
import { statSync as statSync2 } from "node:fs";
|
|
@@ -12737,7 +12844,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
12737
12844
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
12738
12845
|
};
|
|
12739
12846
|
};
|
|
12740
|
-
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
12847
|
+
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path11.join(process.env.XDG_CONFIG_HOME || path11.join(os3.homedir(), ".config"), "workit");
|
|
12741
12848
|
var migratedDir = null;
|
|
12742
12849
|
var migrationFailed = false;
|
|
12743
12850
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -12747,7 +12854,7 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
12747
12854
|
migratedDir = dir;
|
|
12748
12855
|
return dir;
|
|
12749
12856
|
}
|
|
12750
|
-
const legacy =
|
|
12857
|
+
const legacy = path11.join(process.env.XDG_CONFIG_HOME || path11.join(os3.homedir(), ".config"), "workflow-toolkit");
|
|
12751
12858
|
if (!existsSync3(legacy)) {
|
|
12752
12859
|
migratedDir = dir;
|
|
12753
12860
|
return dir;
|
|
@@ -12760,8 +12867,8 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
12760
12867
|
mkdirSync2(dir, { recursive: true });
|
|
12761
12868
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
12762
12869
|
for (const entry of readdirSync2(legacy, { withFileTypes: true })) {
|
|
12763
|
-
const src =
|
|
12764
|
-
const dest =
|
|
12870
|
+
const src = path11.join(legacy, entry.name);
|
|
12871
|
+
const dest = path11.join(dir, entry.name);
|
|
12765
12872
|
if (existsSync3(dest))
|
|
12766
12873
|
continue;
|
|
12767
12874
|
try {
|
|
@@ -12833,7 +12940,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
12833
12940
|
return { status: "valid", path: file, config };
|
|
12834
12941
|
};
|
|
12835
12942
|
var readConfigTyped = (dir) => {
|
|
12836
|
-
const file =
|
|
12943
|
+
const file = path11.join(dir ?? configDir(), "config.json");
|
|
12837
12944
|
return parseConfigResult(readSafe(file), file);
|
|
12838
12945
|
};
|
|
12839
12946
|
var readConfig = () => {
|
|
@@ -12851,10 +12958,10 @@ var mergeConfigValues = (input, current) => ({
|
|
|
12851
12958
|
var writeConfig = (config) => {
|
|
12852
12959
|
const dir = configDir();
|
|
12853
12960
|
mkdirSync2(dir, { recursive: true });
|
|
12854
|
-
writeFileSync2(
|
|
12961
|
+
writeFileSync2(path11.join(dir, "config.json"), JSON.stringify(config, null, 2) + `
|
|
12855
12962
|
`, "utf8");
|
|
12856
12963
|
};
|
|
12857
|
-
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ??
|
|
12964
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path11.join(configDir(), "vcs.json");
|
|
12858
12965
|
var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
|
|
12859
12966
|
var resolveBranchPolicy = (config, workspace, repoPreset) => {
|
|
12860
12967
|
const wp = workspace?.branchPolicy ?? {};
|
|
@@ -12896,9 +13003,9 @@ var resolveCommitPolicy = (config, workspace) => {
|
|
|
12896
13003
|
|
|
12897
13004
|
// packages/workit-core/src/core/workspaces.ts
|
|
12898
13005
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
12899
|
-
import { createHash as
|
|
13006
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
12900
13007
|
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
12901
|
-
import
|
|
13008
|
+
import path12 from "node:path";
|
|
12902
13009
|
|
|
12903
13010
|
// packages/workit-core/src/core/branch-policy.ts
|
|
12904
13011
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
@@ -13033,7 +13140,7 @@ var releaseTrackSchema = object({
|
|
|
13033
13140
|
context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
|
|
13034
13141
|
}
|
|
13035
13142
|
}
|
|
13036
|
-
if (track.versionSource?.kind === "package-json" && (
|
|
13143
|
+
if (track.versionSource?.kind === "package-json" && (path12.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
|
|
13037
13144
|
context.addIssue({
|
|
13038
13145
|
code: "custom",
|
|
13039
13146
|
path: ["versionSource", "path"],
|
|
@@ -13054,6 +13161,8 @@ var workspaceConfigSchema = object({
|
|
|
13054
13161
|
defaultEndpoint: unknown().optional(),
|
|
13055
13162
|
verification: _enum(["self", "independent"]).optional(),
|
|
13056
13163
|
trailers: boolean2().optional(),
|
|
13164
|
+
liveSessionGuard: boolean2().optional(),
|
|
13165
|
+
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
13057
13166
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
13058
13167
|
kind,
|
|
13059
13168
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -13094,7 +13203,7 @@ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).opt
|
|
|
13094
13203
|
names.add(workspace.name);
|
|
13095
13204
|
}
|
|
13096
13205
|
});
|
|
13097
|
-
var workspacesRevision = (contents) =>
|
|
13206
|
+
var workspacesRevision = (contents) => createHash7("sha256").update(contents ?? "<missing>").digest("hex");
|
|
13098
13207
|
var validateWorkspacesDocument = (parsed, file) => {
|
|
13099
13208
|
if (!isConfigObject(parsed)) {
|
|
13100
13209
|
return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
|
|
@@ -13132,7 +13241,7 @@ var validateWorkspacesDocument = (parsed, file) => {
|
|
|
13132
13241
|
};
|
|
13133
13242
|
};
|
|
13134
13243
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
13135
|
-
const file =
|
|
13244
|
+
const file = path12.join(dir, "workspaces.json");
|
|
13136
13245
|
let raw;
|
|
13137
13246
|
try {
|
|
13138
13247
|
raw = readFileSync3(file, "utf8");
|
|
@@ -13243,9 +13352,9 @@ var mainCheckoutOf = (cwd) => {
|
|
|
13243
13352
|
let main = null;
|
|
13244
13353
|
const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
|
|
13245
13354
|
const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
|
|
13246
|
-
if (gitDir && common &&
|
|
13355
|
+
if (gitDir && common && path12.resolve(gitDir) !== path12.resolve(common)) {
|
|
13247
13356
|
const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
|
|
13248
|
-
main = listed?.trim() || (
|
|
13357
|
+
main = listed?.trim() || (path12.basename(common) === ".git" ? path12.dirname(common) : common);
|
|
13249
13358
|
}
|
|
13250
13359
|
mainCheckouts.set(cwd, main);
|
|
13251
13360
|
return main;
|
|
@@ -13336,7 +13445,7 @@ var runtimeWorkspaceIndexSchema = object({
|
|
|
13336
13445
|
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
13337
13446
|
}).passthrough();
|
|
13338
13447
|
var matchRuntimeWorkspace = (cwd, dir = configDir()) => {
|
|
13339
|
-
const file =
|
|
13448
|
+
const file = path12.join(dir, "workspaces.json");
|
|
13340
13449
|
let raw;
|
|
13341
13450
|
try {
|
|
13342
13451
|
raw = readFileSync3(file, "utf8");
|
|
@@ -13371,6 +13480,31 @@ var resolveRuntimeReleaseTracks = (cwd) => {
|
|
|
13371
13480
|
const matched = matchRuntimeWorkspace(cwd);
|
|
13372
13481
|
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
13373
13482
|
};
|
|
13483
|
+
var inheritedSetting = (cwd, dir, has) => {
|
|
13484
|
+
let matched;
|
|
13485
|
+
try {
|
|
13486
|
+
matched = matchRuntimeWorkspace(cwd, dir);
|
|
13487
|
+
} catch {
|
|
13488
|
+
return null;
|
|
13489
|
+
}
|
|
13490
|
+
if (!matched)
|
|
13491
|
+
return null;
|
|
13492
|
+
const { selected, matches } = matched;
|
|
13493
|
+
const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
13494
|
+
return { workspace: selected.name, source: [selected, ...enclosing].find(has) ?? null };
|
|
13495
|
+
};
|
|
13496
|
+
var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
|
|
13497
|
+
var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
|
|
13498
|
+
var TIER_HOSTS = new Set([
|
|
13499
|
+
"claude_code",
|
|
13500
|
+
"opencode",
|
|
13501
|
+
"codex",
|
|
13502
|
+
"codex_cli",
|
|
13503
|
+
"codex_desktop",
|
|
13504
|
+
"cursor",
|
|
13505
|
+
"pi",
|
|
13506
|
+
"default"
|
|
13507
|
+
]);
|
|
13374
13508
|
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
13375
13509
|
const matched = matchRuntimeWorkspace(cwd);
|
|
13376
13510
|
if (!matched)
|
|
@@ -13411,7 +13545,7 @@ function workspaceDefaultTarget(cwd, globalDefault) {
|
|
|
13411
13545
|
}
|
|
13412
13546
|
|
|
13413
13547
|
// packages/workit-core/src/core/release-tracks.ts
|
|
13414
|
-
import { createHash as
|
|
13548
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
13415
13549
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
13416
13550
|
class ReleaseTrackError extends Error {
|
|
13417
13551
|
}
|
|
@@ -13794,7 +13928,7 @@ function resolveTrackFor(cwd, options) {
|
|
|
13794
13928
|
]) ?? "",
|
|
13795
13929
|
git3(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
|
|
13796
13930
|
] : [];
|
|
13797
|
-
key =
|
|
13931
|
+
key = createHash8("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
|
|
13798
13932
|
const hit = memo2.get(key);
|
|
13799
13933
|
if (hit)
|
|
13800
13934
|
return hit;
|
|
@@ -13809,7 +13943,7 @@ function resolveTrackFor(cwd, options) {
|
|
|
13809
13943
|
}
|
|
13810
13944
|
|
|
13811
13945
|
// packages/workit-core/src/core/vcs-config.ts
|
|
13812
|
-
var workspacesPath = () =>
|
|
13946
|
+
var workspacesPath = () => path13.join(configDir(), "workspaces.json");
|
|
13813
13947
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
13814
13948
|
var remoteProvider = (cwd) => {
|
|
13815
13949
|
const r = spawnSync8("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
@@ -13899,7 +14033,7 @@ var readVcsConfig = () => {
|
|
|
13899
14033
|
const file = vcsConfigPath();
|
|
13900
14034
|
let raw;
|
|
13901
14035
|
try {
|
|
13902
|
-
raw =
|
|
14036
|
+
raw = fs11.readFileSync(file, "utf8");
|
|
13903
14037
|
} catch {
|
|
13904
14038
|
return { status: "missing", path: file, config: {} };
|
|
13905
14039
|
}
|
|
@@ -13984,7 +14118,7 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
13984
14118
|
const prov = cfg[provider] ?? {};
|
|
13985
14119
|
const out = {
|
|
13986
14120
|
ok: true,
|
|
13987
|
-
configPath:
|
|
14121
|
+
configPath: path13.resolve(cfgPath),
|
|
13988
14122
|
provider,
|
|
13989
14123
|
defaultTargetBranch: workspaceDefault,
|
|
13990
14124
|
pr: cfg.pr ?? {},
|
|
@@ -14195,7 +14329,7 @@ function diffHash(cwd, base, head) {
|
|
|
14195
14329
|
], GIT_TIMEOUTS.worktree);
|
|
14196
14330
|
if (!diff.ok || !diff.stdout)
|
|
14197
14331
|
return null;
|
|
14198
|
-
const hash =
|
|
14332
|
+
const hash = createHash9("sha256");
|
|
14199
14333
|
for (const line of diff.stdout.split(`
|
|
14200
14334
|
`)) {
|
|
14201
14335
|
if (line.startsWith("index "))
|
|
@@ -14207,16 +14341,16 @@ function diffHash(cwd, base, head) {
|
|
|
14207
14341
|
return `sha256:${hash.digest("hex")}`;
|
|
14208
14342
|
}
|
|
14209
14343
|
function storeRoot(cwd) {
|
|
14210
|
-
const location = resolveStore(
|
|
14344
|
+
const location = resolveStore(path14.resolve(cwd));
|
|
14211
14345
|
if (location instanceof Error)
|
|
14212
14346
|
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");
|
|
14213
14347
|
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
14214
14348
|
}
|
|
14215
14349
|
function ledgerPath(cwd) {
|
|
14216
14350
|
const root = storeRoot(cwd);
|
|
14217
|
-
return root.ok ? { ok: true, value:
|
|
14351
|
+
return root.ok ? { ok: true, value: path14.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
14218
14352
|
}
|
|
14219
|
-
var newId2 = () => `${Date.now().toString(36)}-${
|
|
14353
|
+
var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
|
|
14220
14354
|
var NETWORK_FS_TYPES = new Set([
|
|
14221
14355
|
26985,
|
|
14222
14356
|
20859,
|
|
@@ -14234,7 +14368,7 @@ var onNetworkFs = (dir) => {
|
|
|
14234
14368
|
if (process.platform !== "linux")
|
|
14235
14369
|
return false;
|
|
14236
14370
|
try {
|
|
14237
|
-
return isNetworkFsType(
|
|
14371
|
+
return isNetworkFsType(fs12.statfsSync(dir).type);
|
|
14238
14372
|
} catch {
|
|
14239
14373
|
return false;
|
|
14240
14374
|
}
|
|
@@ -14244,15 +14378,15 @@ var LOCK_STALE_MS = 30000;
|
|
|
14244
14378
|
var LOCK_OWNER = "owner";
|
|
14245
14379
|
var lockToken = (lock) => {
|
|
14246
14380
|
try {
|
|
14247
|
-
return
|
|
14381
|
+
return fs12.readFileSync(path14.join(lock, LOCK_OWNER), "utf8");
|
|
14248
14382
|
} catch {
|
|
14249
14383
|
return null;
|
|
14250
14384
|
}
|
|
14251
14385
|
};
|
|
14252
14386
|
var lockAgeMs = (lock) => {
|
|
14253
|
-
for (const target of [
|
|
14387
|
+
for (const target of [path14.join(lock, LOCK_OWNER), lock]) {
|
|
14254
14388
|
try {
|
|
14255
|
-
return Date.now() -
|
|
14389
|
+
return Date.now() - fs12.statSync(target).mtimeMs;
|
|
14256
14390
|
} catch {}
|
|
14257
14391
|
}
|
|
14258
14392
|
return null;
|
|
@@ -14260,55 +14394,55 @@ var lockAgeMs = (lock) => {
|
|
|
14260
14394
|
var ledgerLock = {
|
|
14261
14395
|
acquire(lock) {
|
|
14262
14396
|
try {
|
|
14263
|
-
|
|
14397
|
+
fs12.mkdirSync(lock);
|
|
14264
14398
|
} catch (error) {
|
|
14265
14399
|
if (error.code === "EEXIST")
|
|
14266
14400
|
return null;
|
|
14267
14401
|
throw error;
|
|
14268
14402
|
}
|
|
14269
|
-
const token = `${process.pid}-${
|
|
14270
|
-
|
|
14403
|
+
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
14404
|
+
fs12.writeFileSync(path14.join(lock, LOCK_OWNER), token);
|
|
14271
14405
|
return token;
|
|
14272
14406
|
},
|
|
14273
14407
|
release(lock, token) {
|
|
14274
14408
|
if (lockToken(lock) !== token)
|
|
14275
14409
|
return false;
|
|
14276
|
-
const tomb = `${lock}.release-${
|
|
14410
|
+
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
14277
14411
|
try {
|
|
14278
|
-
|
|
14412
|
+
fs12.renameSync(lock, tomb);
|
|
14279
14413
|
} catch {
|
|
14280
14414
|
return false;
|
|
14281
14415
|
}
|
|
14282
14416
|
if (lockToken(tomb) !== token) {
|
|
14283
14417
|
try {
|
|
14284
|
-
|
|
14418
|
+
fs12.renameSync(tomb, lock);
|
|
14285
14419
|
} catch {}
|
|
14286
14420
|
return false;
|
|
14287
14421
|
}
|
|
14288
|
-
|
|
14422
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
14289
14423
|
return true;
|
|
14290
14424
|
},
|
|
14291
14425
|
token: lockToken,
|
|
14292
14426
|
reap(lock, observed) {
|
|
14293
|
-
const tomb = `${lock}.reap-${
|
|
14427
|
+
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
14294
14428
|
try {
|
|
14295
|
-
|
|
14429
|
+
fs12.renameSync(lock, tomb);
|
|
14296
14430
|
} catch {
|
|
14297
14431
|
return false;
|
|
14298
14432
|
}
|
|
14299
14433
|
if (lockToken(tomb) === observed) {
|
|
14300
|
-
|
|
14434
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
14301
14435
|
return true;
|
|
14302
14436
|
}
|
|
14303
14437
|
try {
|
|
14304
|
-
|
|
14438
|
+
fs12.renameSync(tomb, lock);
|
|
14305
14439
|
} catch {
|
|
14306
|
-
|
|
14440
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
14307
14441
|
}
|
|
14308
14442
|
return false;
|
|
14309
14443
|
}
|
|
14310
14444
|
};
|
|
14311
|
-
function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
|
|
14445
|
+
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
14312
14446
|
const lock = `${file}.lock`;
|
|
14313
14447
|
const deadline = Date.now() + waitMs;
|
|
14314
14448
|
let token = null;
|
|
@@ -14330,14 +14464,14 @@ function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
|
|
|
14330
14464
|
ledgerLock.release(lock, token);
|
|
14331
14465
|
}
|
|
14332
14466
|
}
|
|
14333
|
-
var sha2562 = (text) =>
|
|
14467
|
+
var sha2562 = (text) => createHash9("sha256").update(text).digest("hex");
|
|
14334
14468
|
var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
|
|
14335
14469
|
${body}`);
|
|
14336
14470
|
var TAIL_BYTES = 64 * 1024;
|
|
14337
14471
|
var lastLine = (fd, size) => {
|
|
14338
14472
|
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
14339
14473
|
const buffer = Buffer.alloc(span);
|
|
14340
|
-
|
|
14474
|
+
fs12.readSync(fd, buffer, 0, span, size - span);
|
|
14341
14475
|
const end = buffer.lastIndexOf(10);
|
|
14342
14476
|
if (end < 0) {
|
|
14343
14477
|
if (span === size)
|
|
@@ -14366,15 +14500,23 @@ var lastLine = (fd, size) => {
|
|
|
14366
14500
|
}
|
|
14367
14501
|
return null;
|
|
14368
14502
|
};
|
|
14503
|
+
var tighten = (fd) => {
|
|
14504
|
+
try {
|
|
14505
|
+
if ((fs12.fstatSync(fd).mode & 63) !== 0)
|
|
14506
|
+
fs12.fchmodSync(fd, 384);
|
|
14507
|
+
} catch {}
|
|
14508
|
+
};
|
|
14369
14509
|
function appendLine(file, build, options) {
|
|
14370
14510
|
const write = () => {
|
|
14371
|
-
const fd =
|
|
14511
|
+
const fd = fs12.openSync(file, "a+", 384);
|
|
14372
14512
|
try {
|
|
14373
|
-
|
|
14513
|
+
if (process.platform !== "win32")
|
|
14514
|
+
tighten(fd);
|
|
14515
|
+
const size = fs12.fstatSync(fd).size;
|
|
14374
14516
|
let fence = "";
|
|
14375
14517
|
if (size > 0) {
|
|
14376
14518
|
const last = Buffer.alloc(1);
|
|
14377
|
-
|
|
14519
|
+
fs12.readSync(fd, last, 0, 1, size - 1);
|
|
14378
14520
|
if (last[0] !== 10)
|
|
14379
14521
|
fence = `
|
|
14380
14522
|
`;
|
|
@@ -14383,17 +14525,17 @@ function appendLine(file, build, options) {
|
|
|
14383
14525
|
if (!line.ok)
|
|
14384
14526
|
return line;
|
|
14385
14527
|
const bytes = Buffer.from(fence + line.value);
|
|
14386
|
-
const written =
|
|
14528
|
+
const written = fs12.writeSync(fd, bytes);
|
|
14387
14529
|
if (written !== bytes.length)
|
|
14388
14530
|
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
14389
14531
|
return { ok: true, value: undefined };
|
|
14390
14532
|
} finally {
|
|
14391
|
-
|
|
14533
|
+
fs12.closeSync(fd);
|
|
14392
14534
|
}
|
|
14393
14535
|
};
|
|
14394
14536
|
try {
|
|
14395
|
-
|
|
14396
|
-
if (!options.forceLock && !onNetworkFs(
|
|
14537
|
+
fs12.mkdirSync(path14.dirname(file), { recursive: true });
|
|
14538
|
+
if (!options.forceLock && !onNetworkFs(path14.dirname(file)))
|
|
14397
14539
|
return write();
|
|
14398
14540
|
const locked = withLedgerLock(file, write);
|
|
14399
14541
|
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
@@ -14464,8 +14606,10 @@ function readLedger(cwd, options = {}) {
|
|
|
14464
14606
|
if (!file.ok)
|
|
14465
14607
|
return file;
|
|
14466
14608
|
let text = "";
|
|
14609
|
+
let stat = null;
|
|
14467
14610
|
try {
|
|
14468
|
-
|
|
14611
|
+
stat = fs12.statSync(file.value);
|
|
14612
|
+
text = fs12.readFileSync(file.value, "utf8");
|
|
14469
14613
|
} catch (error) {
|
|
14470
14614
|
if (error.code === "ENOENT")
|
|
14471
14615
|
return {
|
|
@@ -14484,6 +14628,7 @@ function readLedger(cwd, options = {}) {
|
|
|
14484
14628
|
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
14485
14629
|
const earlier = new Set([null]);
|
|
14486
14630
|
let chainStarted = false;
|
|
14631
|
+
const ids = new Set;
|
|
14487
14632
|
for (const raw of text.split(`
|
|
14488
14633
|
`)) {
|
|
14489
14634
|
const trimmed = raw.trim();
|
|
@@ -14517,6 +14662,10 @@ function readLedger(cwd, options = {}) {
|
|
|
14517
14662
|
reason = "unsigned";
|
|
14518
14663
|
else
|
|
14519
14664
|
rowIntegrity = "legacy";
|
|
14665
|
+
if (options.integrity && !reason && ids.has(parsed.id))
|
|
14666
|
+
reason = "duplicate_id";
|
|
14667
|
+
if (options.integrity)
|
|
14668
|
+
ids.add(parsed.id);
|
|
14520
14669
|
if (reason)
|
|
14521
14670
|
rowIntegrity = "unverified";
|
|
14522
14671
|
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
@@ -14540,7 +14689,8 @@ function readLedger(cwd, options = {}) {
|
|
|
14540
14689
|
seq: rows.length + 1,
|
|
14541
14690
|
superseded: false,
|
|
14542
14691
|
supersedeIgnored: false,
|
|
14543
|
-
...rowIntegrity ? { integrity: rowIntegrity } : {}
|
|
14692
|
+
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
14693
|
+
...reason ? { integrityReason: reason } : {}
|
|
14544
14694
|
};
|
|
14545
14695
|
rows.push(row);
|
|
14546
14696
|
if (!options.integrity)
|
|
@@ -14568,18 +14718,48 @@ function readLedger(cwd, options = {}) {
|
|
|
14568
14718
|
const byId = new Map;
|
|
14569
14719
|
for (const row of rows) {
|
|
14570
14720
|
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
14571
|
-
if (target && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
14721
|
+
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
14572
14722
|
target.superseded = true;
|
|
14573
14723
|
else if (row.supersedes)
|
|
14574
14724
|
row.supersedeIgnored = true;
|
|
14575
14725
|
if (!byId.has(row.id))
|
|
14576
14726
|
byId.set(row.id, row);
|
|
14577
14727
|
}
|
|
14728
|
+
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
14729
|
+
tamperCache.set(file.value, {
|
|
14730
|
+
size: stat.size,
|
|
14731
|
+
stamp: fileStamp(stat),
|
|
14732
|
+
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
14733
|
+
});
|
|
14578
14734
|
return {
|
|
14579
14735
|
ok: true,
|
|
14580
14736
|
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
14581
14737
|
};
|
|
14582
14738
|
}
|
|
14739
|
+
var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
14740
|
+
var tamperCache = new Map;
|
|
14741
|
+
var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
14742
|
+
function ledgerTampering(cwd) {
|
|
14743
|
+
const file = ledgerPath(cwd);
|
|
14744
|
+
if (!file.ok)
|
|
14745
|
+
return [];
|
|
14746
|
+
try {
|
|
14747
|
+
const stat = fs12.statSync(file.value);
|
|
14748
|
+
const cached = tamperCache.get(file.value);
|
|
14749
|
+
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
14750
|
+
return cached.breaks;
|
|
14751
|
+
} catch {
|
|
14752
|
+
return [];
|
|
14753
|
+
}
|
|
14754
|
+
const read = readLedger(cwd, { integrity: true });
|
|
14755
|
+
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
14756
|
+
}
|
|
14757
|
+
function tamperedUnblock(cwd) {
|
|
14758
|
+
const file = ledgerPath(cwd);
|
|
14759
|
+
const where = file.ok ? file.value : "the ledger file";
|
|
14760
|
+
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`;
|
|
14761
|
+
}
|
|
14762
|
+
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)}`;
|
|
14583
14763
|
function verdictBasis(verdict, current) {
|
|
14584
14764
|
if (verdict.head && verdict.head === current.head)
|
|
14585
14765
|
return "fresh";
|
|
@@ -14587,7 +14767,7 @@ function verdictBasis(verdict, current) {
|
|
|
14587
14767
|
return "carried";
|
|
14588
14768
|
return "stale";
|
|
14589
14769
|
}
|
|
14590
|
-
var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.
|
|
14770
|
+
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
14591
14771
|
function trailerSessions(cwd, base, head) {
|
|
14592
14772
|
if (!base || !head || !safeRef(base) || !safeRef(head))
|
|
14593
14773
|
return [];
|
|
@@ -14668,6 +14848,56 @@ var authoredBy = (authors, session) => {
|
|
|
14668
14848
|
return true;
|
|
14669
14849
|
return false;
|
|
14670
14850
|
};
|
|
14851
|
+
var TRUST_REASONS = new Set([
|
|
14852
|
+
"unsigned",
|
|
14853
|
+
"hash_mismatch",
|
|
14854
|
+
"broken_link",
|
|
14855
|
+
"duplicate_id",
|
|
14856
|
+
"unissued_session",
|
|
14857
|
+
"unbound_session"
|
|
14858
|
+
]);
|
|
14859
|
+
var SHELL_VOUCHED_HOSTS = new Set([
|
|
14860
|
+
"cli",
|
|
14861
|
+
"opencode",
|
|
14862
|
+
"pi",
|
|
14863
|
+
"codex_cli",
|
|
14864
|
+
"codex_desktop"
|
|
14865
|
+
]);
|
|
14866
|
+
function sessionTrust(cwd, row) {
|
|
14867
|
+
const session = row.actor.session;
|
|
14868
|
+
if (!session || row.self === true)
|
|
14869
|
+
return null;
|
|
14870
|
+
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
14871
|
+
const host = row.actor.host;
|
|
14872
|
+
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
14873
|
+
if (!issuedOnly)
|
|
14874
|
+
return null;
|
|
14875
|
+
const binding = issuedBinding(cwd, session);
|
|
14876
|
+
if (binding === null)
|
|
14877
|
+
return "unissued_session";
|
|
14878
|
+
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
14879
|
+
}
|
|
14880
|
+
function trustReasons(cwd, row) {
|
|
14881
|
+
if (row.integrity === "unverified")
|
|
14882
|
+
return [row.integrityReason ?? "unsigned"];
|
|
14883
|
+
if (row.integrity !== "verified")
|
|
14884
|
+
return ["unsigned"];
|
|
14885
|
+
const session = sessionTrust(cwd, row);
|
|
14886
|
+
return session ? [session] : [];
|
|
14887
|
+
}
|
|
14888
|
+
function withIntegrity(cwd, rows) {
|
|
14889
|
+
if (rows.every((row) => row.integrity !== undefined))
|
|
14890
|
+
return rows;
|
|
14891
|
+
const read = readLedger(cwd, { integrity: true });
|
|
14892
|
+
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
14893
|
+
return rows.map((row) => {
|
|
14894
|
+
if (row.integrity !== undefined)
|
|
14895
|
+
return row;
|
|
14896
|
+
const fresh = bySeq.get(row.seq);
|
|
14897
|
+
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
14898
|
+
});
|
|
14899
|
+
}
|
|
14900
|
+
var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
|
|
14671
14901
|
var independenceReasons = (row, authors) => {
|
|
14672
14902
|
const reasons = [];
|
|
14673
14903
|
if (!row.actor.session)
|
|
@@ -14678,15 +14908,17 @@ var independenceReasons = (row, authors) => {
|
|
|
14678
14908
|
reasons.push("self");
|
|
14679
14909
|
return reasons;
|
|
14680
14910
|
};
|
|
14681
|
-
function effectiveOf(rows,
|
|
14911
|
+
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
14682
14912
|
let effective = null;
|
|
14683
14913
|
for (const row of rows) {
|
|
14684
14914
|
if (!effective) {
|
|
14685
14915
|
effective = row;
|
|
14686
14916
|
continue;
|
|
14687
14917
|
}
|
|
14688
|
-
|
|
14689
|
-
|
|
14918
|
+
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
14919
|
+
continue;
|
|
14920
|
+
const rowIndependent = reasonsOf(row).length === 0;
|
|
14921
|
+
const effIndependent = reasonsOf(effective).length === 0;
|
|
14690
14922
|
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
14691
14923
|
continue;
|
|
14692
14924
|
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
@@ -14700,7 +14932,7 @@ function restackCarries(rows, branch, from, to) {
|
|
|
14700
14932
|
return false;
|
|
14701
14933
|
const edges = new Map;
|
|
14702
14934
|
for (const row of rows)
|
|
14703
|
-
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
14935
|
+
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
14704
14936
|
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
14705
14937
|
const seen = new Set([from]);
|
|
14706
14938
|
const queue = [from];
|
|
@@ -14716,7 +14948,8 @@ function restackCarries(rows, branch, from, to) {
|
|
|
14716
14948
|
}
|
|
14717
14949
|
return false;
|
|
14718
14950
|
}
|
|
14719
|
-
function checkVerdicts(cwd, branch,
|
|
14951
|
+
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
14952
|
+
const rows = withIntegrity(cwd, ledgerRows);
|
|
14720
14953
|
const head = branchHead(cwd, branch);
|
|
14721
14954
|
const fallbackBase = defaultBase(cwd);
|
|
14722
14955
|
const scope = branchScope(cwd, branch);
|
|
@@ -14746,21 +14979,47 @@ function checkVerdicts(cwd, branch, rows) {
|
|
|
14746
14979
|
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
|
|
14747
14980
|
};
|
|
14748
14981
|
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
14982
|
+
const failureAuthorCache = new Map;
|
|
14983
|
+
const failureAuthors = (row) => {
|
|
14984
|
+
let set = failureAuthorCache.get(row.seq);
|
|
14985
|
+
if (!set) {
|
|
14986
|
+
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
14987
|
+
failureAuthorCache.set(row.seq, set);
|
|
14988
|
+
}
|
|
14989
|
+
return set;
|
|
14990
|
+
};
|
|
14991
|
+
const reasonCache = new Map;
|
|
14992
|
+
const reasonsOf = (row) => {
|
|
14993
|
+
let reasons = reasonCache.get(row);
|
|
14994
|
+
if (!reasons) {
|
|
14995
|
+
const failing = FAILING.has(String(row.result));
|
|
14996
|
+
reasons = [
|
|
14997
|
+
...trustReasons(cwd, row),
|
|
14998
|
+
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
14999
|
+
];
|
|
15000
|
+
reasonCache.set(row, reasons);
|
|
15001
|
+
}
|
|
15002
|
+
return reasons;
|
|
15003
|
+
};
|
|
15004
|
+
const retired = new Set;
|
|
15005
|
+
for (const row of rows)
|
|
15006
|
+
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
15007
|
+
retired.add(row.supersedes);
|
|
14749
15008
|
const byKind = new Map;
|
|
14750
15009
|
for (const row of rows)
|
|
14751
|
-
if (isLiveVerdict(row) && scope(row)) {
|
|
15010
|
+
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
14752
15011
|
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
14753
15012
|
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
14754
15013
|
}
|
|
14755
15014
|
const verdicts = [];
|
|
14756
15015
|
const warnings = [];
|
|
14757
15016
|
for (const [kind, list] of byKind) {
|
|
14758
|
-
const row = effectiveOf(list,
|
|
15017
|
+
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
14759
15018
|
if (!row)
|
|
14760
15019
|
continue;
|
|
14761
15020
|
const basis = basisOf(row);
|
|
14762
15021
|
const current = basis !== "stale";
|
|
14763
|
-
const independence =
|
|
15022
|
+
const independence = reasonsOf(row);
|
|
14764
15023
|
const reasons = [
|
|
14765
15024
|
...current ? [] : ["stale"],
|
|
14766
15025
|
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
@@ -14777,23 +15036,31 @@ function checkVerdicts(cwd, branch, rows) {
|
|
|
14777
15036
|
});
|
|
14778
15037
|
if (reasons.length === 0) {
|
|
14779
15038
|
for (const later of list)
|
|
14780
|
-
if (later.seq > row.seq &&
|
|
15039
|
+
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
14781
15040
|
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`);
|
|
14782
15041
|
}
|
|
14783
15042
|
}
|
|
14784
15043
|
for (const row of rows)
|
|
14785
15044
|
if (row.integrity === "unverified" && scope(row))
|
|
14786
15045
|
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)`);
|
|
15046
|
+
const tampered = ledgerTampering(cwd);
|
|
15047
|
+
if (tampered.length) {
|
|
15048
|
+
warnings.push(tamperedWarning(cwd, tampered));
|
|
15049
|
+
for (const entry of verdicts) {
|
|
15050
|
+
entry.accepted = false;
|
|
15051
|
+
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
15052
|
+
}
|
|
15053
|
+
}
|
|
14787
15054
|
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
14788
15055
|
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
14789
15056
|
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
14790
15057
|
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
14791
|
-
const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
15058
|
+
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
14792
15059
|
accepted: false,
|
|
14793
15060
|
verdict: failing?.verdict ?? null,
|
|
14794
15061
|
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
14795
15062
|
};
|
|
14796
|
-
const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
15063
|
+
const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
14797
15064
|
return {
|
|
14798
15065
|
branch,
|
|
14799
15066
|
head,
|
|
@@ -14956,7 +15223,7 @@ var productDecision = (task, ledger) => {
|
|
|
14956
15223
|
};
|
|
14957
15224
|
};
|
|
14958
15225
|
var planRecorded = (task, root) => {
|
|
14959
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(
|
|
15226
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(path15.resolve(root, ref.path)));
|
|
14960
15227
|
if (cited)
|
|
14961
15228
|
return {
|
|
14962
15229
|
met: true,
|
|
@@ -15015,15 +15282,15 @@ function writeBlockers(task, root) {
|
|
|
15015
15282
|
}
|
|
15016
15283
|
|
|
15017
15284
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
15018
|
-
var digestBytes = (value) =>
|
|
15019
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
15020
|
-
var relative = (root, candidate) =>
|
|
15285
|
+
var digestBytes = (value) => createHash10("sha256").update(value).digest("hex");
|
|
15286
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path16.sep}`);
|
|
15287
|
+
var relative = (root, candidate) => path16.relative(root, candidate).split(path16.sep).join("/") || ".";
|
|
15021
15288
|
var canonicalPath = (value) => {
|
|
15022
15289
|
if (typeof value !== "string" || !value)
|
|
15023
15290
|
return null;
|
|
15024
15291
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
15025
15292
|
return null;
|
|
15026
|
-
const normalized =
|
|
15293
|
+
const normalized = path16.posix.normalize(value);
|
|
15027
15294
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
15028
15295
|
return null;
|
|
15029
15296
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -15071,21 +15338,21 @@ var gitPaths = (root) => {
|
|
|
15071
15338
|
git: !/not a git repository/i.test(stderr)
|
|
15072
15339
|
};
|
|
15073
15340
|
}
|
|
15074
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
15341
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path16.sep).join("/")) : [];
|
|
15075
15342
|
const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
15076
15343
|
if (stagedDeleted.status !== 0) {
|
|
15077
15344
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
15078
15345
|
if (!/not a git repository/i.test(stderr))
|
|
15079
15346
|
return { paths, uncertain: true, git: true };
|
|
15080
15347
|
} else if (stagedDeleted.stdout) {
|
|
15081
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
15348
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path16.sep).join("/")));
|
|
15082
15349
|
}
|
|
15083
15350
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
15084
15351
|
};
|
|
15085
15352
|
var walk = (root, directory, output) => {
|
|
15086
15353
|
let names;
|
|
15087
15354
|
try {
|
|
15088
|
-
names =
|
|
15355
|
+
names = fs13.readdirSync(directory);
|
|
15089
15356
|
} catch {
|
|
15090
15357
|
return true;
|
|
15091
15358
|
}
|
|
@@ -15093,10 +15360,10 @@ var walk = (root, directory, output) => {
|
|
|
15093
15360
|
for (const name of names) {
|
|
15094
15361
|
if (name === ".git" || name === ".workit")
|
|
15095
15362
|
continue;
|
|
15096
|
-
const target =
|
|
15363
|
+
const target = path16.join(directory, name);
|
|
15097
15364
|
let stat;
|
|
15098
15365
|
try {
|
|
15099
|
-
stat =
|
|
15366
|
+
stat = fs13.lstatSync(target);
|
|
15100
15367
|
} catch {
|
|
15101
15368
|
uncertain = true;
|
|
15102
15369
|
continue;
|
|
@@ -15111,14 +15378,14 @@ var walk = (root, directory, output) => {
|
|
|
15111
15378
|
var scopeRoots = (root, scope) => {
|
|
15112
15379
|
const roots = [];
|
|
15113
15380
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
15114
|
-
const target =
|
|
15381
|
+
const target = path16.resolve(root, item);
|
|
15115
15382
|
if (!inside(root, target))
|
|
15116
15383
|
return failure2("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
|
|
15117
15384
|
let ancestor = target;
|
|
15118
|
-
while (!
|
|
15119
|
-
ancestor =
|
|
15385
|
+
while (!fs13.existsSync(ancestor) && ancestor !== root)
|
|
15386
|
+
ancestor = path16.dirname(ancestor);
|
|
15120
15387
|
try {
|
|
15121
|
-
if (!inside(root,
|
|
15388
|
+
if (!inside(root, fs13.realpathSync(ancestor)))
|
|
15122
15389
|
return failure2("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
|
|
15123
15390
|
} catch {
|
|
15124
15391
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -15130,7 +15397,7 @@ var scopeRoots = (root, scope) => {
|
|
|
15130
15397
|
function captureCandidate(root, scope, environment = []) {
|
|
15131
15398
|
let checkout;
|
|
15132
15399
|
try {
|
|
15133
|
-
checkout =
|
|
15400
|
+
checkout = fs13.realpathSync(root);
|
|
15134
15401
|
} catch {
|
|
15135
15402
|
return failure2("invalid_input", "candidate root does not exist");
|
|
15136
15403
|
}
|
|
@@ -15148,11 +15415,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
15148
15415
|
if (item === ".git" || item === ".workit")
|
|
15149
15416
|
continue;
|
|
15150
15417
|
names.add(item);
|
|
15151
|
-
if (!
|
|
15418
|
+
if (!fs13.existsSync(target))
|
|
15152
15419
|
continue;
|
|
15153
15420
|
let stat;
|
|
15154
15421
|
try {
|
|
15155
|
-
stat =
|
|
15422
|
+
stat = fs13.lstatSync(target);
|
|
15156
15423
|
} catch {
|
|
15157
15424
|
uncertain = true;
|
|
15158
15425
|
continue;
|
|
@@ -15168,17 +15435,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
15168
15435
|
for (const item of [...names].toSorted()) {
|
|
15169
15436
|
if (!scopeMatches(item, normalizedScope))
|
|
15170
15437
|
continue;
|
|
15171
|
-
const target =
|
|
15438
|
+
const target = path16.join(checkout, item);
|
|
15172
15439
|
try {
|
|
15173
|
-
const stat =
|
|
15440
|
+
const stat = fs13.lstatSync(target);
|
|
15174
15441
|
if (stat.isSymbolicLink()) {
|
|
15175
|
-
const link =
|
|
15442
|
+
const link = fs13.readlinkSync(target);
|
|
15176
15443
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
15177
15444
|
} else if (stat.isFile()) {
|
|
15178
15445
|
files.push({
|
|
15179
15446
|
path: item,
|
|
15180
15447
|
kind: "file",
|
|
15181
|
-
digest: digestBytes(
|
|
15448
|
+
digest: digestBytes(fs13.readFileSync(target)),
|
|
15182
15449
|
executable: (stat.mode & 73) !== 0
|
|
15183
15450
|
});
|
|
15184
15451
|
} else if (!stat.isDirectory()) {
|
|
@@ -15343,7 +15610,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
15343
15610
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
15344
15611
|
let root;
|
|
15345
15612
|
try {
|
|
15346
|
-
root =
|
|
15613
|
+
root = fs13.realpathSync(checkoutRoot);
|
|
15347
15614
|
} catch {
|
|
15348
15615
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
15349
15616
|
}
|
|
@@ -15352,23 +15619,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
15352
15619
|
continue;
|
|
15353
15620
|
if (!reference.digest)
|
|
15354
15621
|
return failure2("invalid_input", "document references require a byte digest");
|
|
15355
|
-
const target =
|
|
15356
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
15622
|
+
const target = path16.resolve(root, reference.path);
|
|
15623
|
+
if (target !== root && !target.startsWith(`${root}${path16.sep}`))
|
|
15357
15624
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
15358
15625
|
fields: [
|
|
15359
15626
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
15360
15627
|
]
|
|
15361
15628
|
});
|
|
15362
15629
|
try {
|
|
15363
|
-
const below =
|
|
15630
|
+
const below = path16.relative(root, target);
|
|
15364
15631
|
let current = root;
|
|
15365
|
-
for (const segment of below.split(
|
|
15632
|
+
for (const segment of below.split(path16.sep)) {
|
|
15366
15633
|
if (!segment)
|
|
15367
15634
|
continue;
|
|
15368
|
-
current =
|
|
15369
|
-
const stat =
|
|
15370
|
-
const real =
|
|
15371
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
15635
|
+
current = path16.join(current, segment);
|
|
15636
|
+
const stat = fs13.lstatSync(current);
|
|
15637
|
+
const real = fs13.realpathSync(current);
|
|
15638
|
+
if (real !== root && !real.startsWith(`${root}${path16.sep}`))
|
|
15372
15639
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
15373
15640
|
fields: [
|
|
15374
15641
|
{
|
|
@@ -15384,7 +15651,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
15384
15651
|
if (current !== target && !stat.isDirectory())
|
|
15385
15652
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
15386
15653
|
}
|
|
15387
|
-
if (digestBytes(
|
|
15654
|
+
if (digestBytes(fs13.readFileSync(target)) !== reference.digest)
|
|
15388
15655
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
15389
15656
|
} catch {
|
|
15390
15657
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -15916,17 +16183,17 @@ var exportDigest = (bundle) => sha256({
|
|
|
15916
16183
|
// packages/workit-core/src/core/route-intent.ts
|
|
15917
16184
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
15918
16185
|
import { existsSync as existsSync6 } from "node:fs";
|
|
15919
|
-
import
|
|
16186
|
+
import path19 from "node:path";
|
|
15920
16187
|
|
|
15921
16188
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
15922
16189
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
15923
|
-
import { createHash as
|
|
15924
|
-
import
|
|
16190
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
16191
|
+
import fs15 from "node:fs";
|
|
15925
16192
|
import os4 from "node:os";
|
|
15926
|
-
import
|
|
16193
|
+
import path18 from "node:path";
|
|
15927
16194
|
|
|
15928
16195
|
// packages/workit-core/src/core/branch.ts
|
|
15929
|
-
import { createHash as
|
|
16196
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
15930
16197
|
|
|
15931
16198
|
// packages/workit-core/src/core/git.ts
|
|
15932
16199
|
import { execFileSync } from "node:child_process";
|
|
@@ -16120,7 +16387,7 @@ var branchPolicySnapshotFor = (workspaceRoot) => {
|
|
|
16120
16387
|
const source = resolved.provenance.branchPolicy;
|
|
16121
16388
|
const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
|
|
16122
16389
|
const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
|
|
16123
|
-
const fingerprint =
|
|
16390
|
+
const fingerprint = createHash11("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
|
|
16124
16391
|
return {
|
|
16125
16392
|
branchPolicy,
|
|
16126
16393
|
namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
|
|
@@ -16174,9 +16441,9 @@ var releaseBranchesOf = (cwd) => {
|
|
|
16174
16441
|
var releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase());
|
|
16175
16442
|
|
|
16176
16443
|
// packages/workit-core/src/hooks/session-marker.ts
|
|
16177
|
-
import { createHash as
|
|
16178
|
-
import
|
|
16179
|
-
import
|
|
16444
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
16445
|
+
import fs14 from "node:fs";
|
|
16446
|
+
import path17 from "node:path";
|
|
16180
16447
|
var sessionMarkerFile = (input, session, store) => {
|
|
16181
16448
|
let dir = store;
|
|
16182
16449
|
if (dir === undefined) {
|
|
@@ -16185,13 +16452,13 @@ var sessionMarkerFile = (input, session, store) => {
|
|
|
16185
16452
|
return null;
|
|
16186
16453
|
dir = location.dir;
|
|
16187
16454
|
}
|
|
16188
|
-
const name =
|
|
16189
|
-
return
|
|
16455
|
+
const name = createHash12("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
16456
|
+
return path17.join(dir, "hooks", `skills-${name}.json`);
|
|
16190
16457
|
};
|
|
16191
16458
|
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
16192
16459
|
var readSessionMarker = (file) => {
|
|
16193
16460
|
try {
|
|
16194
|
-
const value = JSON.parse(
|
|
16461
|
+
const value = JSON.parse(fs14.readFileSync(file, "utf8"));
|
|
16195
16462
|
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
16196
16463
|
} catch {
|
|
16197
16464
|
return { nudged: [], loaded: [], raw: [] };
|
|
@@ -16199,8 +16466,8 @@ var readSessionMarker = (file) => {
|
|
|
16199
16466
|
};
|
|
16200
16467
|
var writeSessionMarker = (file, marker) => {
|
|
16201
16468
|
try {
|
|
16202
|
-
|
|
16203
|
-
|
|
16469
|
+
fs14.mkdirSync(path17.dirname(file), { recursive: true });
|
|
16470
|
+
fs14.writeFileSync(file, JSON.stringify(marker));
|
|
16204
16471
|
} catch {}
|
|
16205
16472
|
};
|
|
16206
16473
|
|
|
@@ -16697,14 +16964,14 @@ var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
|
|
|
16697
16964
|
var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
|
|
16698
16965
|
var MAX_DEPTH = 4;
|
|
16699
16966
|
var MAX_EXPANSIONS = 16;
|
|
16700
|
-
var isAbsolute = (value) =>
|
|
16967
|
+
var isAbsolute = (value) => path18.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
16701
16968
|
var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
|
|
16702
16969
|
var joinDir = (base, raw) => {
|
|
16703
16970
|
const next = raw.replace(HERE, ".");
|
|
16704
16971
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
16705
16972
|
return;
|
|
16706
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
16707
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
16973
|
+
const expanded = next === "~" || next.startsWith("~/") ? path18.join(os4.homedir(), next.slice(1)) : next;
|
|
16974
|
+
return base === null || isAbsolute(expanded) ? expanded : path18.join(base, expanded);
|
|
16708
16975
|
};
|
|
16709
16976
|
var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
|
|
16710
16977
|
var cdTarget = (words) => {
|
|
@@ -17455,7 +17722,7 @@ var repoAt = (dir) => {
|
|
|
17455
17722
|
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
17456
17723
|
};
|
|
17457
17724
|
var isWorkitWorkspace = (repo) => {
|
|
17458
|
-
if (
|
|
17725
|
+
if (fs15.existsSync(repo.location.dir))
|
|
17459
17726
|
return true;
|
|
17460
17727
|
try {
|
|
17461
17728
|
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
@@ -17476,17 +17743,17 @@ var headSha2 = (repo) => {
|
|
|
17476
17743
|
if (!git || git.reftable)
|
|
17477
17744
|
return null;
|
|
17478
17745
|
try {
|
|
17479
|
-
const head =
|
|
17746
|
+
const head = fs15.readFileSync(path18.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
17480
17747
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
17481
17748
|
return head;
|
|
17482
17749
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
17483
17750
|
if (!ref)
|
|
17484
17751
|
return null;
|
|
17485
|
-
const common =
|
|
17752
|
+
const common = path18.dirname(repo.location.dir);
|
|
17486
17753
|
try {
|
|
17487
|
-
return
|
|
17754
|
+
return fs15.readFileSync(path18.join(common, ref), "utf8").trim() || null;
|
|
17488
17755
|
} catch {}
|
|
17489
|
-
const packed =
|
|
17756
|
+
const packed = fs15.readFileSync(path18.join(common, "packed-refs"), "utf8");
|
|
17490
17757
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
17491
17758
|
} catch {
|
|
17492
17759
|
return null;
|
|
@@ -17574,8 +17841,8 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
17574
17841
|
const sub = invocation.args[0];
|
|
17575
17842
|
if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
|
|
17576
17843
|
return [invocation];
|
|
17577
|
-
const dir =
|
|
17578
|
-
if (!
|
|
17844
|
+
const dir = path18.resolve(root, invocation.dir ?? ".");
|
|
17845
|
+
if (!fs15.existsSync(dir))
|
|
17579
17846
|
return [invocation];
|
|
17580
17847
|
const config = gitConfig(dir, invocation, cache);
|
|
17581
17848
|
const alias = configValue(config, `alias.${sub.toLowerCase()}`);
|
|
@@ -17597,11 +17864,11 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
17597
17864
|
var normalizedUrl = (url, dir) => {
|
|
17598
17865
|
const trimmed = url.trim();
|
|
17599
17866
|
if (/^file:\/\//i.test(trimmed))
|
|
17600
|
-
return
|
|
17867
|
+
return path18.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
17601
17868
|
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
|
|
17602
17869
|
const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
|
|
17603
17870
|
if (!scheme && !scp)
|
|
17604
|
-
return
|
|
17871
|
+
return path18.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
17605
17872
|
const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
|
|
17606
17873
|
return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
17607
17874
|
};
|
|
@@ -17825,7 +18092,7 @@ var actionsIn = (cwd, command, dialect, cache = new Map) => {
|
|
|
17825
18092
|
const out = [];
|
|
17826
18093
|
const repos = new Map;
|
|
17827
18094
|
const repoFor = (relative) => {
|
|
17828
|
-
const dir =
|
|
18095
|
+
const dir = path18.resolve(cwd, relative ?? ".");
|
|
17829
18096
|
if (!repos.has(dir)) {
|
|
17830
18097
|
const repo = repoAt(dir);
|
|
17831
18098
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -17910,8 +18177,8 @@ var analysisDenial = (error) => ({
|
|
|
17910
18177
|
unblock: SPLIT_IT
|
|
17911
18178
|
});
|
|
17912
18179
|
var MARKER_TTL_SECONDS = 3600;
|
|
17913
|
-
var markerFile = (repo, session, key) =>
|
|
17914
|
-
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${
|
|
18180
|
+
var markerFile = (repo, session, key) => path18.join(repo.location.dir, "hooks", `commit-${createHash13("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
18181
|
+
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash13("sha256").update(command).digest("hex")}`;
|
|
17915
18182
|
function noteRawCommit(input, command, key) {
|
|
17916
18183
|
try {
|
|
17917
18184
|
if (!input.session.id || !/\bcommit\b/.test(command))
|
|
@@ -17921,8 +18188,8 @@ function noteRawCommit(input, command, key) {
|
|
|
17921
18188
|
if (action.kind !== "commit")
|
|
17922
18189
|
continue;
|
|
17923
18190
|
const file = markerFile(repo, input.session.id, key);
|
|
17924
|
-
|
|
17925
|
-
|
|
18191
|
+
fs15.mkdirSync(path18.dirname(file), { recursive: true });
|
|
18192
|
+
fs15.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
17926
18193
|
`);
|
|
17927
18194
|
}
|
|
17928
18195
|
} catch {}
|
|
@@ -17945,11 +18212,11 @@ var headCommit = (dir) => {
|
|
|
17945
18212
|
var settle2 = (input, repo, file) => {
|
|
17946
18213
|
let marker;
|
|
17947
18214
|
try {
|
|
17948
|
-
marker = JSON.parse(
|
|
18215
|
+
marker = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
17949
18216
|
} catch {
|
|
17950
18217
|
return;
|
|
17951
18218
|
}
|
|
17952
|
-
|
|
18219
|
+
fs15.rmSync(file, { force: true });
|
|
17953
18220
|
const session = input.session.id;
|
|
17954
18221
|
const now = Date.now() / 1000;
|
|
17955
18222
|
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
@@ -18140,7 +18407,7 @@ var judgedDir = (root, dir) => {
|
|
|
18140
18407
|
return null;
|
|
18141
18408
|
if (dir === null)
|
|
18142
18409
|
return root;
|
|
18143
|
-
const resolved =
|
|
18410
|
+
const resolved = path19.resolve(root, dir);
|
|
18144
18411
|
return existsSync6(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
|
|
18145
18412
|
};
|
|
18146
18413
|
var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
@@ -18376,7 +18643,7 @@ var judgeInputSummary = (raw) => {
|
|
|
18376
18643
|
|
|
18377
18644
|
// packages/workit-core/src/autonomy.ts
|
|
18378
18645
|
import os5 from "node:os";
|
|
18379
|
-
import
|
|
18646
|
+
import path20 from "node:path";
|
|
18380
18647
|
|
|
18381
18648
|
// packages/workit-core/src/core/safe-write.ts
|
|
18382
18649
|
import {
|
|
@@ -18410,10 +18677,10 @@ var accountHome = () => {
|
|
|
18410
18677
|
}
|
|
18411
18678
|
};
|
|
18412
18679
|
var grantsHome = { resolve: accountHome };
|
|
18413
|
-
var grantsDir = () =>
|
|
18680
|
+
var grantsDir = () => path20.join(grantsHome.resolve(), ".config", "workit");
|
|
18414
18681
|
var grantsOverride = () => {
|
|
18415
|
-
const configured =
|
|
18416
|
-
return configured ===
|
|
18682
|
+
const configured = path20.resolve(resolveConfigDir());
|
|
18683
|
+
return configured === path20.resolve(grantsDir()) ? null : `the config directory is redirected to ${configured} (WORKFLOW_TOOLKIT_CONFIG, WORKFLOW_TOOLKIT_CONFIG_DIR or XDG_CONFIG_HOME); grants are read only from ${path20.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
|
|
18417
18684
|
};
|
|
18418
18685
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
18419
18686
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -20185,7 +20452,7 @@ class WorkitCore {
|
|
|
20185
20452
|
// packages/workit-core/src/core/repo-context.ts
|
|
20186
20453
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
20187
20454
|
import { existsSync as existsSync7, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
|
|
20188
|
-
import
|
|
20455
|
+
import path21 from "node:path";
|
|
20189
20456
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
20190
20457
|
try {
|
|
20191
20458
|
gitRevisionParts(value);
|
|
@@ -20319,7 +20586,7 @@ var findPrTemplate = (cwd) => {
|
|
|
20319
20586
|
]) {
|
|
20320
20587
|
const parts = rel.split("/");
|
|
20321
20588
|
const base = parts[parts.length - 1];
|
|
20322
|
-
const dir = parts.length > 1 ?
|
|
20589
|
+
const dir = parts.length > 1 ? path21.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
20323
20590
|
const actual = probeCaseInsensitive(dir, base);
|
|
20324
20591
|
if (actual)
|
|
20325
20592
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -20350,7 +20617,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
20350
20617
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
20351
20618
|
};
|
|
20352
20619
|
var packageScripts = (cwd, keys) => {
|
|
20353
|
-
const pkgPath =
|
|
20620
|
+
const pkgPath = path21.join(cwd, "package.json");
|
|
20354
20621
|
if (!existsSync7(pkgPath))
|
|
20355
20622
|
return [];
|
|
20356
20623
|
try {
|
|
@@ -20376,9 +20643,9 @@ var documentationFiles = (cwd) => {
|
|
|
20376
20643
|
if (entry.isDirectory()) {
|
|
20377
20644
|
if (excluded.has(entry.name))
|
|
20378
20645
|
continue;
|
|
20379
|
-
walk(
|
|
20646
|
+
walk(path21.join(dir, entry.name), depth + 1);
|
|
20380
20647
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
20381
|
-
matches.push("./" +
|
|
20648
|
+
matches.push("./" + path21.relative(cwd, path21.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
20382
20649
|
}
|
|
20383
20650
|
}
|
|
20384
20651
|
};
|
|
@@ -20394,7 +20661,7 @@ var readTrimmed2 = (file, maxLines) => {
|
|
|
20394
20661
|
`);
|
|
20395
20662
|
};
|
|
20396
20663
|
function prReadyContext(root, range) {
|
|
20397
|
-
const cwd =
|
|
20664
|
+
const cwd = path21.resolve(repoRoot(root));
|
|
20398
20665
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
20399
20666
|
return invalidRange(cwd);
|
|
20400
20667
|
let stdout = "";
|
|
@@ -20452,7 +20719,7 @@ ${status.stderr}` : "");
|
|
|
20452
20719
|
stdout += `template_path: ${template}
|
|
20453
20720
|
|
|
20454
20721
|
`;
|
|
20455
|
-
stdout += readTrimmed2(
|
|
20722
|
+
stdout += readTrimmed2(path21.join(cwd, template), 220);
|
|
20456
20723
|
} else {
|
|
20457
20724
|
stdout += `template_path: none
|
|
20458
20725
|
|
|
@@ -20462,7 +20729,7 @@ ${status.stderr}` : "");
|
|
|
20462
20729
|
stdout += `
|
|
20463
20730
|
`;
|
|
20464
20731
|
stdout += printSection("Recent Validation Signals");
|
|
20465
|
-
if (existsSync7(
|
|
20732
|
+
if (existsSync7(path21.join(cwd, "package.json"))) {
|
|
20466
20733
|
stdout += `package.json detected. Common scripts:
|
|
20467
20734
|
`;
|
|
20468
20735
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -20471,7 +20738,7 @@ ${status.stderr}` : "");
|
|
|
20471
20738
|
`) + `
|
|
20472
20739
|
`;
|
|
20473
20740
|
}
|
|
20474
|
-
if (existsSync7(
|
|
20741
|
+
if (existsSync7(path21.join(cwd, "Cargo.toml")) || existsSync7(path21.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
20475
20742
|
stdout += `Rust project detected.
|
|
20476
20743
|
`;
|
|
20477
20744
|
}
|
|
@@ -20504,7 +20771,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
20504
20771
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
20505
20772
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
20506
20773
|
function changelogContext(root, range) {
|
|
20507
|
-
const cwd =
|
|
20774
|
+
const cwd = path21.resolve(repoRoot(root));
|
|
20508
20775
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
20509
20776
|
return invalidRange(cwd);
|
|
20510
20777
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -20522,7 +20789,7 @@ function changelogContext(root, range) {
|
|
|
20522
20789
|
stdout += CHANGELOG_RULES + `
|
|
20523
20790
|
`;
|
|
20524
20791
|
stdout += printSection("Existing CHANGELOG.md");
|
|
20525
|
-
const changelogPath =
|
|
20792
|
+
const changelogPath = path21.join(cwd, "CHANGELOG.md");
|
|
20526
20793
|
if (existsSync7(changelogPath)) {
|
|
20527
20794
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
20528
20795
|
`;
|
|
@@ -20542,7 +20809,7 @@ function changelogContext(root, range) {
|
|
|
20542
20809
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
20543
20810
|
}
|
|
20544
20811
|
function docsRefreshContext(root, range) {
|
|
20545
|
-
const cwd =
|
|
20812
|
+
const cwd = path21.resolve(repoRoot(root));
|
|
20546
20813
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
20547
20814
|
return invalidRange(cwd);
|
|
20548
20815
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -20564,7 +20831,7 @@ function docsRefreshContext(root, range) {
|
|
|
20564
20831
|
`) + `
|
|
20565
20832
|
`;
|
|
20566
20833
|
stdout += printSection("README Preview");
|
|
20567
|
-
const readme =
|
|
20834
|
+
const readme = path21.join(cwd, "README.md");
|
|
20568
20835
|
if (existsSync7(readme)) {
|
|
20569
20836
|
stdout += readTrimmed2(readme, 220) + `
|
|
20570
20837
|
`;
|
|
@@ -20573,7 +20840,7 @@ function docsRefreshContext(root, range) {
|
|
|
20573
20840
|
`;
|
|
20574
20841
|
}
|
|
20575
20842
|
stdout += printSection("Package Scripts");
|
|
20576
|
-
const pkgPath =
|
|
20843
|
+
const pkgPath = path21.join(cwd, "package.json");
|
|
20577
20844
|
if (existsSync7(pkgPath)) {
|
|
20578
20845
|
try {
|
|
20579
20846
|
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
@@ -20587,7 +20854,7 @@ function docsRefreshContext(root, range) {
|
|
|
20587
20854
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
20588
20855
|
}
|
|
20589
20856
|
function releaseNotesContext(root, rangeOrTag) {
|
|
20590
|
-
const cwd =
|
|
20857
|
+
const cwd = path21.resolve(repoRoot(root));
|
|
20591
20858
|
if (!rangeOrTag) {
|
|
20592
20859
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
20593
20860
|
`, exitCode: 1, cwd };
|
|
@@ -20625,7 +20892,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
20625
20892
|
`;
|
|
20626
20893
|
stdout += printSection("Existing Release Files");
|
|
20627
20894
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
20628
|
-
if (existsSync7(
|
|
20895
|
+
if (existsSync7(path21.join(cwd, rel)))
|
|
20629
20896
|
stdout += rel + `
|
|
20630
20897
|
`;
|
|
20631
20898
|
}
|
|
@@ -20656,8 +20923,8 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
20656
20923
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
20657
20924
|
|
|
20658
20925
|
// packages/workit-core/src/core/pr-create.ts
|
|
20659
|
-
import
|
|
20660
|
-
import
|
|
20926
|
+
import fs16 from "node:fs";
|
|
20927
|
+
import path22 from "node:path";
|
|
20661
20928
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
20662
20929
|
// packages/workit-core/src/forge/pr-body.ts
|
|
20663
20930
|
function parseGhIssue(value) {
|
|
@@ -20668,13 +20935,13 @@ function parseGhIssue(value) {
|
|
|
20668
20935
|
// packages/workit-core/src/core/pr-create.ts
|
|
20669
20936
|
function whichOnPath(tool) {
|
|
20670
20937
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
20671
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
20938
|
+
for (const dir of (process.env.PATH ?? "").split(path22.delimiter)) {
|
|
20672
20939
|
if (!dir)
|
|
20673
20940
|
continue;
|
|
20674
20941
|
for (const name of names) {
|
|
20675
|
-
const candidate =
|
|
20942
|
+
const candidate = path22.join(dir, name);
|
|
20676
20943
|
try {
|
|
20677
|
-
|
|
20944
|
+
fs16.accessSync(candidate, fs16.constants.X_OK);
|
|
20678
20945
|
return candidate;
|
|
20679
20946
|
} catch {}
|
|
20680
20947
|
}
|
|
@@ -20792,8 +21059,8 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
20792
21059
|
};
|
|
20793
21060
|
|
|
20794
21061
|
// packages/workit-core/src/core/youtrack.ts
|
|
20795
|
-
import
|
|
20796
|
-
import
|
|
21062
|
+
import fs17 from "node:fs";
|
|
21063
|
+
import path23 from "node:path";
|
|
20797
21064
|
|
|
20798
21065
|
// packages/workit-core/src/core/templates.ts
|
|
20799
21066
|
var repoRoot2 = assetRoot();
|
|
@@ -20806,22 +21073,22 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
20806
21073
|
// packages/workit-core/src/core/youtrack.ts
|
|
20807
21074
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
20808
21075
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
20809
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
20810
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
21076
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path23.join(configDir(), "youtrack.json");
|
|
21077
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path23.join(resolveConfigDir(), "youtrack.json");
|
|
20811
21078
|
var youTrackTokenModeOk = (p) => {
|
|
20812
21079
|
if (process.platform === "win32")
|
|
20813
21080
|
return true;
|
|
20814
|
-
const mode =
|
|
21081
|
+
const mode = fs17.statSync(p).mode & 511;
|
|
20815
21082
|
return mode === 384;
|
|
20816
21083
|
};
|
|
20817
21084
|
function readYouTrackConfig(required) {
|
|
20818
21085
|
const cfgPath = youTrackConfigPath();
|
|
20819
|
-
if (!
|
|
21086
|
+
if (!fs17.existsSync(cfgPath)) {
|
|
20820
21087
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
20821
21088
|
}
|
|
20822
21089
|
let parsed;
|
|
20823
21090
|
try {
|
|
20824
|
-
parsed = JSON.parse(
|
|
21091
|
+
parsed = JSON.parse(fs17.readFileSync(cfgPath, "utf8"));
|
|
20825
21092
|
} catch {
|
|
20826
21093
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
20827
21094
|
}
|
|
@@ -20832,11 +21099,11 @@ function readYouTrackConfig(required) {
|
|
|
20832
21099
|
}
|
|
20833
21100
|
var readYouTrackContextConfig = () => {
|
|
20834
21101
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
20835
|
-
if (!
|
|
21102
|
+
if (!fs17.existsSync(cfgPath))
|
|
20836
21103
|
return { data: {} };
|
|
20837
21104
|
let parsed;
|
|
20838
21105
|
try {
|
|
20839
|
-
parsed = JSON.parse(
|
|
21106
|
+
parsed = JSON.parse(fs17.readFileSync(cfgPath, "utf8"));
|
|
20840
21107
|
} catch {
|
|
20841
21108
|
return { error: "YouTrack configuration is unavailable" };
|
|
20842
21109
|
}
|
|
@@ -20875,12 +21142,12 @@ var youTrackToken = () => {
|
|
|
20875
21142
|
if ("error" in loaded)
|
|
20876
21143
|
return loaded;
|
|
20877
21144
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
20878
|
-
const tokenPath = tokenFile ?
|
|
20879
|
-
if (!tokenPath || !
|
|
21145
|
+
const tokenPath = tokenFile ? path23.isAbsolute(tokenFile) ? path23.resolve(tokenFile) : path23.resolve(process.cwd(), tokenFile) : "";
|
|
21146
|
+
if (!tokenPath || !fs17.existsSync(tokenPath))
|
|
20880
21147
|
return { error: "missing youtrack.token" };
|
|
20881
21148
|
if (!youTrackTokenModeOk(tokenPath))
|
|
20882
21149
|
return { error: "youtrack.token mode must be 0600" };
|
|
20883
|
-
const token =
|
|
21150
|
+
const token = fs17.readFileSync(tokenPath, "utf8").trim();
|
|
20884
21151
|
if (!token)
|
|
20885
21152
|
return { error: "empty token file" };
|
|
20886
21153
|
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
@@ -20926,7 +21193,7 @@ function youTrackTokenCreateUrl() {
|
|
|
20926
21193
|
const desc = String(defaults.description ?? "OpenCode workit — /wk-issue-update and /wk-meetings");
|
|
20927
21194
|
const scopes = Array.isArray(defaults.scopes) ? defaults.scopes : ["YouTrack"];
|
|
20928
21195
|
const base = String(config.baseUrl ?? "").replace(/\/+$/, "");
|
|
20929
|
-
const tokenFile = String(config.tokenFile ??
|
|
21196
|
+
const tokenFile = String(config.tokenFile ?? path23.join(path23.dirname(loaded.path), "youtrack.token"));
|
|
20930
21197
|
const tab = String(defaults.profileTab ?? "account-security");
|
|
20931
21198
|
const createUrl = base ? `${base}/users/me?${new URLSearchParams({ tab })}` : null;
|
|
20932
21199
|
const docsUrl = "https://www.jetbrains.com/help/youtrack/cloud/manage-permanent-token.html";
|
|
@@ -20935,7 +21202,7 @@ function youTrackTokenCreateUrl() {
|
|
|
20935
21202
|
tokenName: name,
|
|
20936
21203
|
tokenDescription: desc,
|
|
20937
21204
|
scopes,
|
|
20938
|
-
tokenFile:
|
|
21205
|
+
tokenFile: path23.resolve(tokenFile),
|
|
20939
21206
|
createUrl,
|
|
20940
21207
|
docsUrl,
|
|
20941
21208
|
prefillSupported: false,
|
|
@@ -20981,9 +21248,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
20981
21248
|
} catch {
|
|
20982
21249
|
continue;
|
|
20983
21250
|
}
|
|
20984
|
-
if (!
|
|
21251
|
+
if (!fs17.existsSync(full))
|
|
20985
21252
|
continue;
|
|
20986
|
-
const text =
|
|
21253
|
+
const text = fs17.readFileSync(full, "utf8");
|
|
20987
21254
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
20988
21255
|
if (m)
|
|
20989
21256
|
return m[1];
|
|
@@ -21275,15 +21542,15 @@ function resolveGitRevision(root, value) {
|
|
|
21275
21542
|
}
|
|
21276
21543
|
function resolveInside(root, candidate) {
|
|
21277
21544
|
const base = realpathSync5(root);
|
|
21278
|
-
const target =
|
|
21279
|
-
if (target !== base && !target.startsWith(base +
|
|
21545
|
+
const target = path24.resolve(base, candidate);
|
|
21546
|
+
if (target !== base && !target.startsWith(base + path24.sep)) {
|
|
21280
21547
|
throw new Error("path must stay inside repository root");
|
|
21281
21548
|
}
|
|
21282
21549
|
let ancestor = target;
|
|
21283
21550
|
while (!existsSync8(ancestor))
|
|
21284
|
-
ancestor =
|
|
21551
|
+
ancestor = path24.dirname(ancestor);
|
|
21285
21552
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
21286
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
21553
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path24.sep)) {
|
|
21287
21554
|
throw new Error("path must stay inside repository root");
|
|
21288
21555
|
}
|
|
21289
21556
|
return target;
|
|
@@ -21327,7 +21594,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
21327
21594
|
};
|
|
21328
21595
|
});
|
|
21329
21596
|
// packages/workit-core/src/hooks/context.ts
|
|
21330
|
-
import
|
|
21597
|
+
import path25 from "node:path";
|
|
21331
21598
|
var MIGRATION_PENDING_NOTE = "workit migration pending — run `workit task status`";
|
|
21332
21599
|
var migrationPending = (result) => !result.ok && result.code === "needs_input" && (result.error ?? "").startsWith(MIGRATION_PENDING);
|
|
21333
21600
|
var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
|
|
@@ -21412,7 +21679,7 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
21412
21679
|
return null;
|
|
21413
21680
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
21414
21681
|
const observed = task?.data.evidence.some(isObservedCheck) ?? false;
|
|
21415
|
-
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [
|
|
21682
|
+
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path25.resolve(store.root, ref.path)] : []));
|
|
21416
21683
|
const observedFiles = filesKey(files);
|
|
21417
21684
|
const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
|
|
21418
21685
|
if (!compact.ok || !compact.data)
|
|
@@ -21444,27 +21711,231 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
21444
21711
|
};
|
|
21445
21712
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
21446
21713
|
import { realpathSync as realpathSync6 } from "node:fs";
|
|
21447
|
-
import
|
|
21714
|
+
import os7 from "node:os";
|
|
21715
|
+
import path27 from "node:path";
|
|
21716
|
+
|
|
21717
|
+
// packages/workit-core/src/core/live-session.ts
|
|
21718
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
21719
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
21720
|
+
import fs18 from "node:fs";
|
|
21721
|
+
import os6 from "node:os";
|
|
21722
|
+
import path26 from "node:path";
|
|
21723
|
+
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
21724
|
+
var HEARTBEAT_EVERY_MS = 30000;
|
|
21725
|
+
var liveProbe = {
|
|
21726
|
+
platform: process.platform,
|
|
21727
|
+
run: (args) => spawnSync17("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
|
|
21728
|
+
start: (pid) => processStartOf(pid)
|
|
21729
|
+
};
|
|
21730
|
+
var leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session;
|
|
21731
|
+
var familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, "");
|
|
21732
|
+
var processAlive = (pid) => {
|
|
21733
|
+
try {
|
|
21734
|
+
process.kill(pid, 0);
|
|
21735
|
+
return true;
|
|
21736
|
+
} catch (error) {
|
|
21737
|
+
return error.code === "EPERM";
|
|
21738
|
+
}
|
|
21739
|
+
};
|
|
21740
|
+
var processInfo = (pid) => {
|
|
21741
|
+
try {
|
|
21742
|
+
if (liveProbe.platform === "linux") {
|
|
21743
|
+
const stat = fs18.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
21744
|
+
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
21745
|
+
const command = fs18.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
|
|
21746
|
+
return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
|
|
21747
|
+
}
|
|
21748
|
+
if (liveProbe.platform === "win32")
|
|
21749
|
+
return null;
|
|
21750
|
+
const match = /^\s*(\d+)\s+(.*)$/su.exec(liveProbe.run(["-o", "ppid=,command=", "-p", String(pid)]));
|
|
21751
|
+
return match ? { ppid: Number(match[1]), command: match[2].trim() } : null;
|
|
21752
|
+
} catch {
|
|
21753
|
+
return null;
|
|
21754
|
+
}
|
|
21755
|
+
};
|
|
21756
|
+
var WRAPPER = /^(?:\S*\/)?(?:sh|bash|dash|zsh|ash|ksh|fish|env|timeout|npx|npm|bunx|cmd\.exe|pwsh|powershell)(?:\s|$)|launch\.mjs|npx-cli\.js/u;
|
|
21757
|
+
var PIDS = new Map;
|
|
21758
|
+
var hostPid = (transport) => {
|
|
21759
|
+
if (transport !== "hook-process")
|
|
21760
|
+
return process.pid;
|
|
21761
|
+
const key = `${liveProbe.platform}\x00${process.ppid}`;
|
|
21762
|
+
if (PIDS.has(key))
|
|
21763
|
+
return PIDS.get(key) ?? null;
|
|
21764
|
+
if (liveProbe.platform === "win32") {
|
|
21765
|
+
PIDS.set(key, null);
|
|
21766
|
+
return null;
|
|
21767
|
+
}
|
|
21768
|
+
let pid = process.ppid;
|
|
21769
|
+
for (let hop = 0;hop < 4; hop++) {
|
|
21770
|
+
const info = processInfo(pid);
|
|
21771
|
+
if (!info || !WRAPPER.test(info.command) || info.ppid <= 1)
|
|
21772
|
+
break;
|
|
21773
|
+
pid = info.ppid;
|
|
21774
|
+
}
|
|
21775
|
+
PIDS.set(key, pid);
|
|
21776
|
+
return pid;
|
|
21777
|
+
};
|
|
21778
|
+
var sessionSafe = (value) => value.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-");
|
|
21779
|
+
var liveOwner = (input, transport, env = process.env) => {
|
|
21780
|
+
const session = input.session.id?.trim();
|
|
21781
|
+
if (!session)
|
|
21782
|
+
return null;
|
|
21783
|
+
const leads = new Set([leadOf(sessionSafe(session))]);
|
|
21784
|
+
if (input.session.parentId)
|
|
21785
|
+
leads.add(leadOf(sessionSafe(input.session.parentId)));
|
|
21786
|
+
if (env.WORKIT_PI_WORKER_SESSION && env.WORKIT_SESSION_ID)
|
|
21787
|
+
leads.add(leadOf(sessionSafe(env.WORKIT_SESSION_ID)));
|
|
21788
|
+
return {
|
|
21789
|
+
host: input.host,
|
|
21790
|
+
session: sessionSafe(session),
|
|
21791
|
+
leads: [...leads],
|
|
21792
|
+
pid: () => hostPid(transport)
|
|
21793
|
+
};
|
|
21794
|
+
};
|
|
21795
|
+
var checkoutOf = (target) => {
|
|
21796
|
+
let dir = path26.resolve(target);
|
|
21797
|
+
while (!fs18.existsSync(dir) && path26.dirname(dir) !== dir)
|
|
21798
|
+
dir = path26.dirname(dir);
|
|
21799
|
+
if (fs18.existsSync(dir) && !fs18.statSync(dir).isDirectory())
|
|
21800
|
+
dir = path26.dirname(dir);
|
|
21801
|
+
const parts = dir.split(path26.sep);
|
|
21802
|
+
const dotGit = parts.lastIndexOf(".git");
|
|
21803
|
+
if (dotGit > 0)
|
|
21804
|
+
dir = parts.slice(0, dotGit).join(path26.sep) || path26.sep;
|
|
21805
|
+
const location = resolveStore(dir);
|
|
21806
|
+
if (location instanceof Error || !location.shared || !location.top)
|
|
21807
|
+
return null;
|
|
21808
|
+
let top = location.top;
|
|
21809
|
+
try {
|
|
21810
|
+
top = fs18.realpathSync.native(top);
|
|
21811
|
+
} catch {}
|
|
21812
|
+
return top;
|
|
21813
|
+
};
|
|
21814
|
+
var markerFile2 = (top) => path26.join(resolveStateDir(), "live-sessions", `${createHash14("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
21815
|
+
var checkoutKey = (top, platform = process.platform) => {
|
|
21816
|
+
let key = path26.resolve(top);
|
|
21817
|
+
try {
|
|
21818
|
+
key = fs18.realpathSync.native(key);
|
|
21819
|
+
} catch {}
|
|
21820
|
+
return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
|
|
21821
|
+
};
|
|
21822
|
+
var readMarker = (file) => {
|
|
21823
|
+
try {
|
|
21824
|
+
const value = JSON.parse(fs18.readFileSync(file, "utf8"));
|
|
21825
|
+
if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
|
|
21826
|
+
return null;
|
|
21827
|
+
return {
|
|
21828
|
+
version: 1,
|
|
21829
|
+
session: value.session,
|
|
21830
|
+
leads: Array.isArray(value.leads) ? value.leads.filter((item) => typeof item === "string") : [],
|
|
21831
|
+
host: value.host,
|
|
21832
|
+
pid: typeof value.pid === "number" ? value.pid : null,
|
|
21833
|
+
pidStart: typeof value.pidStart === "string" ? value.pidStart : null,
|
|
21834
|
+
machine: typeof value.machine === "string" ? value.machine : "",
|
|
21835
|
+
checkout: typeof value.checkout === "string" ? value.checkout : "",
|
|
21836
|
+
heartbeatAt: value.heartbeatAt
|
|
21837
|
+
};
|
|
21838
|
+
} catch {
|
|
21839
|
+
return null;
|
|
21840
|
+
}
|
|
21841
|
+
};
|
|
21842
|
+
var ageOf2 = (marker, now) => now.getTime() - Date.parse(marker.heartbeatAt);
|
|
21843
|
+
var isLive = (marker, deps) => {
|
|
21844
|
+
const age = ageOf2(marker, deps.now ?? new Date);
|
|
21845
|
+
if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
|
|
21846
|
+
return false;
|
|
21847
|
+
if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
|
|
21848
|
+
return true;
|
|
21849
|
+
if (!(deps.alive ?? processAlive)(marker.pid))
|
|
21850
|
+
return false;
|
|
21851
|
+
if (marker.pidStart === null)
|
|
21852
|
+
return true;
|
|
21853
|
+
const start = liveProbe.start(marker.pid);
|
|
21854
|
+
return start === null || start === marker.pidStart;
|
|
21855
|
+
};
|
|
21856
|
+
var sameLead = (marker, owner) => marker.session === owner.session || owner.leads.some((lead) => lead === marker.session || marker.leads.includes(lead));
|
|
21857
|
+
var sameOwner = (marker, owner, machine) => {
|
|
21858
|
+
if (sameLead(marker, owner))
|
|
21859
|
+
return true;
|
|
21860
|
+
if (marker.machine !== machine)
|
|
21861
|
+
return false;
|
|
21862
|
+
const pid = owner.pid();
|
|
21863
|
+
if (pid === null || marker.pid === null || marker.pidStart === null)
|
|
21864
|
+
return familyOf(marker.host) === familyOf(owner.host);
|
|
21865
|
+
return marker.pid === pid;
|
|
21866
|
+
};
|
|
21867
|
+
var liveConflict = (target, owner, deps = {}) => {
|
|
21868
|
+
try {
|
|
21869
|
+
const checkout = checkoutOf(target);
|
|
21870
|
+
if (!checkout)
|
|
21871
|
+
return null;
|
|
21872
|
+
const file = markerFile2(checkout);
|
|
21873
|
+
const marker = readMarker(file);
|
|
21874
|
+
if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
|
|
21875
|
+
return null;
|
|
21876
|
+
if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
|
|
21877
|
+
return null;
|
|
21878
|
+
return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
|
|
21879
|
+
} catch {
|
|
21880
|
+
return null;
|
|
21881
|
+
}
|
|
21882
|
+
};
|
|
21883
|
+
var claimLiveSession = (target, owner, deps = {}) => {
|
|
21884
|
+
try {
|
|
21885
|
+
const checkout = checkoutOf(target);
|
|
21886
|
+
if (!checkout)
|
|
21887
|
+
return;
|
|
21888
|
+
const file = markerFile2(checkout);
|
|
21889
|
+
const now = deps.now ?? new Date;
|
|
21890
|
+
const machine = deps.machine ?? os6.hostname();
|
|
21891
|
+
const current = readMarker(file);
|
|
21892
|
+
if (current) {
|
|
21893
|
+
const age = ageOf2(current, now);
|
|
21894
|
+
if (sameLead(current, owner) && age >= 0 && age < HEARTBEAT_EVERY_MS)
|
|
21895
|
+
return;
|
|
21896
|
+
if (!sameLead(current, owner) && isLive(current, deps) && !sameOwner(current, owner, machine))
|
|
21897
|
+
return;
|
|
21898
|
+
}
|
|
21899
|
+
const pid = owner.pid();
|
|
21900
|
+
const marker = {
|
|
21901
|
+
version: 1,
|
|
21902
|
+
session: owner.session,
|
|
21903
|
+
leads: owner.leads,
|
|
21904
|
+
host: owner.host,
|
|
21905
|
+
pid,
|
|
21906
|
+
pidStart: pid === null ? null : liveProbe.start(pid),
|
|
21907
|
+
machine,
|
|
21908
|
+
checkout,
|
|
21909
|
+
heartbeatAt: now.toISOString()
|
|
21910
|
+
};
|
|
21911
|
+
fs18.mkdirSync(path26.dirname(file), { recursive: true });
|
|
21912
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
21913
|
+
fs18.writeFileSync(temp, JSON.stringify(marker));
|
|
21914
|
+
fs18.renameSync(temp, file);
|
|
21915
|
+
} catch {}
|
|
21916
|
+
};
|
|
21917
|
+
|
|
21918
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
21448
21919
|
var NONE3 = { kind: "none" };
|
|
21449
21920
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
21450
21921
|
var canonical2 = (file) => {
|
|
21451
21922
|
let rest = "";
|
|
21452
|
-
for (let dir =
|
|
21923
|
+
for (let dir = path27.resolve(file);; dir = path27.dirname(dir)) {
|
|
21453
21924
|
try {
|
|
21454
|
-
return
|
|
21925
|
+
return path27.join(realpathSync6(dir), rest);
|
|
21455
21926
|
} catch {
|
|
21456
|
-
if (
|
|
21457
|
-
return
|
|
21458
|
-
rest = rest ?
|
|
21927
|
+
if (path27.dirname(dir) === dir)
|
|
21928
|
+
return path27.resolve(file);
|
|
21929
|
+
rest = rest ? path27.join(path27.basename(dir), rest) : path27.basename(dir);
|
|
21459
21930
|
}
|
|
21460
21931
|
}
|
|
21461
21932
|
};
|
|
21462
21933
|
var exempt = (cwd, root, file, plans) => {
|
|
21463
21934
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
21464
|
-
const relative =
|
|
21465
|
-
if (relative.startsWith("..") ||
|
|
21935
|
+
const relative = path27.relative(fold(canonical2(root)), fold(canonical2(path27.resolve(cwd, file))));
|
|
21936
|
+
if (relative.startsWith("..") || path27.isAbsolute(relative))
|
|
21466
21937
|
return true;
|
|
21467
|
-
const posix = relative.split(
|
|
21938
|
+
const posix = relative.split(path27.sep).join("/");
|
|
21468
21939
|
const segments = posix.split("/");
|
|
21469
21940
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
21470
21941
|
};
|
|
@@ -21562,7 +22033,7 @@ var shellWrites = (command) => {
|
|
|
21562
22033
|
return { writes: true, targets: unknown ? null : targets };
|
|
21563
22034
|
};
|
|
21564
22035
|
var cache2 = new Map;
|
|
21565
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
22036
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path27.resolve(root, file)) ?? "-").join("\x00");
|
|
21566
22037
|
var gateFor = (cwd) => {
|
|
21567
22038
|
const store = new TaskStore(cwd);
|
|
21568
22039
|
const listed = store.listTaskIndex();
|
|
@@ -21600,7 +22071,218 @@ var gateFor = (cwd) => {
|
|
|
21600
22071
|
cache2.delete(cache2.keys().next().value);
|
|
21601
22072
|
return gate;
|
|
21602
22073
|
};
|
|
21603
|
-
|
|
22074
|
+
var MUTATING_GIT = new Set([
|
|
22075
|
+
"add",
|
|
22076
|
+
"am",
|
|
22077
|
+
"apply",
|
|
22078
|
+
"checkout",
|
|
22079
|
+
"cherry-pick",
|
|
22080
|
+
"clean",
|
|
22081
|
+
"commit",
|
|
22082
|
+
"merge",
|
|
22083
|
+
"mv",
|
|
22084
|
+
"pull",
|
|
22085
|
+
"rebase",
|
|
22086
|
+
"reset",
|
|
22087
|
+
"restore",
|
|
22088
|
+
"revert",
|
|
22089
|
+
"rm",
|
|
22090
|
+
"stash",
|
|
22091
|
+
"switch"
|
|
22092
|
+
]);
|
|
22093
|
+
var DRY_N = new Set(["add", "clean", "rm", "mv"]);
|
|
22094
|
+
var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
|
|
22095
|
+
var DIR_TARGET = ".workit-live-target";
|
|
22096
|
+
var OVERRIDE = "WORKIT_LIVE_SESSION=override";
|
|
22097
|
+
var inert = (args, sub) => args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run" || DRY_N.has(sub) && /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg) && !arg.startsWith("--"));
|
|
22098
|
+
var gitMutationDirs = (cwd, command, dialect) => {
|
|
22099
|
+
let invocations;
|
|
22100
|
+
try {
|
|
22101
|
+
invocations = rawInvocations(command, dialect);
|
|
22102
|
+
} catch {
|
|
22103
|
+
return [];
|
|
22104
|
+
}
|
|
22105
|
+
return invocations.flatMap((invocation) => {
|
|
22106
|
+
if (invocation.tool !== "git" || invocation.dir === undefined)
|
|
22107
|
+
return [];
|
|
22108
|
+
const at = invocation.args.findIndex((arg) => !arg.startsWith("-"));
|
|
22109
|
+
const sub = invocation.args[at] ?? "";
|
|
22110
|
+
const rest = invocation.args.slice(at + 1);
|
|
22111
|
+
if (!MUTATING_GIT.has(sub) || inert(rest, sub))
|
|
22112
|
+
return [];
|
|
22113
|
+
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
22114
|
+
return [];
|
|
22115
|
+
return [path27.resolve(cwd, invocation.dir ?? ".")];
|
|
22116
|
+
});
|
|
22117
|
+
};
|
|
22118
|
+
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
22119
|
+
var PUSHD2 = new Set(["pushd", "push-location"]);
|
|
22120
|
+
var POPD2 = new Set(["popd", "pop-location"]);
|
|
22121
|
+
var cdInto = (dir, words) => {
|
|
22122
|
+
const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
22123
|
+
if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
|
|
22124
|
+
return;
|
|
22125
|
+
return target === undefined ? os7.homedir() : path27.resolve(dir, target);
|
|
22126
|
+
};
|
|
22127
|
+
var quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
22128
|
+
var segmentTargets = (cwd, command, dialect) => {
|
|
22129
|
+
const targets = [];
|
|
22130
|
+
let dir = cwd;
|
|
22131
|
+
let stack = [];
|
|
22132
|
+
const groups = [];
|
|
22133
|
+
const at = (file) => path27.isAbsolute(file) ? file : dir === undefined ? null : path27.resolve(dir, file);
|
|
22134
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
22135
|
+
if (segment.group === "open") {
|
|
22136
|
+
groups.push({ dir, stack: [...stack] });
|
|
22137
|
+
continue;
|
|
22138
|
+
}
|
|
22139
|
+
if (segment.group === "close") {
|
|
22140
|
+
const saved = groups.pop();
|
|
22141
|
+
if (saved)
|
|
22142
|
+
({ dir, stack } = saved);
|
|
22143
|
+
continue;
|
|
22144
|
+
}
|
|
22145
|
+
const lead = segment.words.findIndex((word) => !/^\w+=/.test(word));
|
|
22146
|
+
const words = lead < 0 ? [] : segment.words.slice(lead);
|
|
22147
|
+
const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
22148
|
+
if (CD2.has(name)) {
|
|
22149
|
+
dir = cdInto(dir, words);
|
|
22150
|
+
continue;
|
|
22151
|
+
}
|
|
22152
|
+
if (PUSHD2.has(name)) {
|
|
22153
|
+
stack.push(dir);
|
|
22154
|
+
dir = cdInto(dir, words);
|
|
22155
|
+
continue;
|
|
22156
|
+
}
|
|
22157
|
+
if (POPD2.has(name)) {
|
|
22158
|
+
dir = stack.length ? stack.pop() : undefined;
|
|
22159
|
+
continue;
|
|
22160
|
+
}
|
|
22161
|
+
for (const file of segment.redirects)
|
|
22162
|
+
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
22163
|
+
const resolved = at(file);
|
|
22164
|
+
if (resolved)
|
|
22165
|
+
targets.push(resolved);
|
|
22166
|
+
}
|
|
22167
|
+
const edits = editsOf(segment.words);
|
|
22168
|
+
if (edits === null) {
|
|
22169
|
+
if (dir !== undefined)
|
|
22170
|
+
targets.push(path27.join(dir, DIR_TARGET));
|
|
22171
|
+
} else
|
|
22172
|
+
for (const file of edits) {
|
|
22173
|
+
const resolved = at(file);
|
|
22174
|
+
if (resolved)
|
|
22175
|
+
targets.push(resolved);
|
|
22176
|
+
}
|
|
22177
|
+
if (dir !== undefined && workitInvocations(segment.words.map(quoteWord).join(" "), "posix").some((args) => args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), "")))
|
|
22178
|
+
targets.push(path27.join(dir, DIR_TARGET));
|
|
22179
|
+
}
|
|
22180
|
+
return targets;
|
|
22181
|
+
};
|
|
22182
|
+
var overridden = (command, dialect) => {
|
|
22183
|
+
const segments = segmentsOf(command, dialect);
|
|
22184
|
+
if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
|
|
22185
|
+
return false;
|
|
22186
|
+
const words = segments[0].words;
|
|
22187
|
+
const lead = words.findIndex((word) => !/^\w+=/.test(word));
|
|
22188
|
+
return lead > 0 && words.slice(0, lead).includes(OVERRIDE);
|
|
22189
|
+
};
|
|
22190
|
+
var HOST_LABELS = {
|
|
22191
|
+
claude_code: "Claude Code",
|
|
22192
|
+
opencode: "OpenCode",
|
|
22193
|
+
codex_cli: "Codex CLI",
|
|
22194
|
+
codex_desktop: "Codex Desktop",
|
|
22195
|
+
cursor: "Cursor",
|
|
22196
|
+
pi: "Pi"
|
|
22197
|
+
};
|
|
22198
|
+
var ageText = (ms) => {
|
|
22199
|
+
const seconds = Math.max(0, Math.round(ms / 1000));
|
|
22200
|
+
return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} min`;
|
|
22201
|
+
};
|
|
22202
|
+
var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
22203
|
+
const unblock = `Work in your own worktree (\`git worktree add <path> -b <branch>\`, or \`workit fanout worktree create <slice>\`). Only if the user confirms that session is finished: release it with \`rm '${file}'\`, or prefix one command with ${OVERRIDE}.`;
|
|
22204
|
+
return {
|
|
22205
|
+
kind: "deny",
|
|
22206
|
+
reason: `workit live-session guard: ${checkout} belongs to another live agent session, ${HOST_LABELS[marker.host] ?? marker.host} session ${marker.session}${marker.pid === null ? "" : ` (pid ${marker.pid})`}, last active ${ageText(ageMs)} ago. Do not edit a checkout another session is working in; read-only commands stay allowed. ${unblock}`,
|
|
22207
|
+
unblock
|
|
22208
|
+
};
|
|
22209
|
+
};
|
|
22210
|
+
var guarded = (targets) => {
|
|
22211
|
+
const seen = new Set;
|
|
22212
|
+
return targets.filter((target) => {
|
|
22213
|
+
const dir = path27.dirname(target);
|
|
22214
|
+
if (seen.has(dir))
|
|
22215
|
+
return false;
|
|
22216
|
+
seen.add(dir);
|
|
22217
|
+
return true;
|
|
22218
|
+
});
|
|
22219
|
+
};
|
|
22220
|
+
var liveGate = (owner, targets, deps) => {
|
|
22221
|
+
try {
|
|
22222
|
+
for (const target of guarded(targets)) {
|
|
22223
|
+
const conflict = liveConflict(target, owner, deps);
|
|
22224
|
+
if (conflict && workspaceLiveSessionGuard(conflict.checkout))
|
|
22225
|
+
return liveDeny(conflict);
|
|
22226
|
+
}
|
|
22227
|
+
} catch {}
|
|
22228
|
+
return NONE3;
|
|
22229
|
+
};
|
|
22230
|
+
var claimAll = (owner, targets, deps) => {
|
|
22231
|
+
for (const target of guarded(targets))
|
|
22232
|
+
claimLiveSession(target, owner, deps);
|
|
22233
|
+
};
|
|
22234
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path27.resolve(cwd, file)) : [path27.join(cwd, DIR_TARGET)];
|
|
22235
|
+
function writeGate(cwd, paths, owner, deps) {
|
|
22236
|
+
if (!owner)
|
|
22237
|
+
return beforeWriteGate(cwd, paths);
|
|
22238
|
+
const targets = liveTargets(cwd, paths);
|
|
22239
|
+
const live = liveGate(owner, targets, deps);
|
|
22240
|
+
if (live.kind !== "none")
|
|
22241
|
+
return live;
|
|
22242
|
+
const decision = beforeWriteGate(cwd, paths);
|
|
22243
|
+
if (decision.kind === "none")
|
|
22244
|
+
claimAll(owner, targets, deps);
|
|
22245
|
+
return decision;
|
|
22246
|
+
}
|
|
22247
|
+
function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
22248
|
+
const writes = shellWrites(command);
|
|
22249
|
+
let targets = [];
|
|
22250
|
+
if (owner) {
|
|
22251
|
+
try {
|
|
22252
|
+
targets = [
|
|
22253
|
+
...segmentTargets(cwd, command, dialect),
|
|
22254
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path27.join(dir, DIR_TARGET))
|
|
22255
|
+
];
|
|
22256
|
+
} catch {
|
|
22257
|
+
targets = [];
|
|
22258
|
+
}
|
|
22259
|
+
}
|
|
22260
|
+
if (owner && targets.length) {
|
|
22261
|
+
const live = liveGate(owner, targets, deps);
|
|
22262
|
+
if (live.kind !== "none") {
|
|
22263
|
+
if (!overridden(command, dialect))
|
|
22264
|
+
return live;
|
|
22265
|
+
noteOverride(cwd, owner, command);
|
|
22266
|
+
}
|
|
22267
|
+
}
|
|
22268
|
+
const decision = writes.writes ? beforeWriteGate(cwd, writes.targets) : NONE3;
|
|
22269
|
+
if (owner && targets.length && decision.kind === "none")
|
|
22270
|
+
claimAll(owner, targets, deps);
|
|
22271
|
+
return decision;
|
|
22272
|
+
}
|
|
22273
|
+
var noteOverride = (cwd, owner, command) => {
|
|
22274
|
+
try {
|
|
22275
|
+
if (!inWorkitWorkspace(cwd))
|
|
22276
|
+
return;
|
|
22277
|
+
appendHookObserved(cwd, {
|
|
22278
|
+
type: "live_session.override",
|
|
22279
|
+
actor: { host: owner.host, session: owner.session, agentId: null },
|
|
22280
|
+
session: owner.session,
|
|
22281
|
+
command: command.slice(0, 200)
|
|
22282
|
+
});
|
|
22283
|
+
} catch {}
|
|
22284
|
+
};
|
|
22285
|
+
function beforeWriteGate(cwd, paths) {
|
|
21604
22286
|
try {
|
|
21605
22287
|
const gate = gateFor(cwd);
|
|
21606
22288
|
if (!gate || gate.blockers.length === 0)
|
|
@@ -21618,7 +22300,7 @@ function writeGate(cwd, paths) {
|
|
|
21618
22300
|
}
|
|
21619
22301
|
}
|
|
21620
22302
|
// packages/workit-core/src/hooks/stop.ts
|
|
21621
|
-
import { spawnSync as
|
|
22303
|
+
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
21622
22304
|
var NONE4 = { kind: "none" };
|
|
21623
22305
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
21624
22306
|
var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
|
|
@@ -21639,7 +22321,7 @@ var asksUser = (message) => {
|
|
|
21639
22321
|
};
|
|
21640
22322
|
var short = (sha) => (sha ?? "?").slice(0, 12);
|
|
21641
22323
|
var gitList = (cwd, args) => {
|
|
21642
|
-
const run =
|
|
22324
|
+
const run = spawnSync18("git", args, {
|
|
21643
22325
|
cwd,
|
|
21644
22326
|
encoding: "utf8",
|
|
21645
22327
|
timeout: 3000,
|
|
@@ -21730,10 +22412,10 @@ function stopObligation(input, descriptor) {
|
|
|
21730
22412
|
const branch = currentBranch(input.cwd);
|
|
21731
22413
|
if (!branch || guardedBranch(input.cwd, branch))
|
|
21732
22414
|
return null;
|
|
21733
|
-
const ledger = readLedger(input.cwd);
|
|
22415
|
+
const ledger = readLedger(input.cwd, { integrity: true });
|
|
21734
22416
|
if (!ledger.ok)
|
|
21735
22417
|
return null;
|
|
21736
|
-
const all = ledger.value.rows;
|
|
22418
|
+
const all = ledger.value.rows.filter((row) => row.integrity !== "unverified");
|
|
21737
22419
|
const rows = all.filter((row) => row.branch === branch);
|
|
21738
22420
|
const mine = rows.filter((row) => sessionOf(row) === session);
|
|
21739
22421
|
const commits = new Set(mine.filter((row) => row.type === "commit.recorded").map((row) => typeof row.sha === "string" ? row.sha : row.head).filter((sha) => typeof sha === "string"));
|
|
@@ -22101,11 +22783,11 @@ var OPENCODE_DESCRIPTOR = {
|
|
|
22101
22783
|
]
|
|
22102
22784
|
};
|
|
22103
22785
|
// packages/workit-opencode/src/shared/init-apply.ts
|
|
22104
|
-
import
|
|
22786
|
+
import path32 from "node:path";
|
|
22105
22787
|
|
|
22106
22788
|
// packages/workit-core/src/core/gitignore.ts
|
|
22107
22789
|
import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
22108
|
-
import
|
|
22790
|
+
import path28 from "node:path";
|
|
22109
22791
|
var GITIGNORE_ENTRIES = [
|
|
22110
22792
|
"# workit: SDD working state (never commit)",
|
|
22111
22793
|
"docs/*/sdd/",
|
|
@@ -22125,7 +22807,7 @@ var GITIGNORE_ENTRIES = [
|
|
|
22125
22807
|
var ensureProjectGitignore = (workspaceRoot, confirmed) => {
|
|
22126
22808
|
if (!confirmed)
|
|
22127
22809
|
return { ok: false, error: "confirmed: true required" };
|
|
22128
|
-
const file =
|
|
22810
|
+
const file = path28.join(workspaceRoot, ".gitignore");
|
|
22129
22811
|
const existing = existsSync9(file) ? readFileSync6(file, "utf8") : "";
|
|
22130
22812
|
const existingLines = new Set(existing.split(`
|
|
22131
22813
|
`).map((l) => l.trim()).filter(Boolean));
|
|
@@ -22152,26 +22834,26 @@ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
|
|
|
22152
22834
|
};
|
|
22153
22835
|
|
|
22154
22836
|
// packages/workit-core/src/core/hygiene.ts
|
|
22155
|
-
import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
22156
|
-
import
|
|
22837
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
22838
|
+
import path29 from "node:path";
|
|
22157
22839
|
var repoRoot3 = assetRoot();
|
|
22158
|
-
var templatesDir = () =>
|
|
22840
|
+
var templatesDir = () => path29.join(repoRoot3, "templates", "hygiene");
|
|
22159
22841
|
var packageJson = (root) => {
|
|
22160
|
-
if (!existsSync10(
|
|
22842
|
+
if (!existsSync10(path29.join(root, "package.json")))
|
|
22161
22843
|
return null;
|
|
22162
22844
|
try {
|
|
22163
|
-
return JSON.parse(readFileSync7(
|
|
22845
|
+
return JSON.parse(readFileSync7(path29.join(root, "package.json"), "utf8"));
|
|
22164
22846
|
} catch {
|
|
22165
22847
|
return null;
|
|
22166
22848
|
}
|
|
22167
22849
|
};
|
|
22168
22850
|
var isOpenSource = (root) => {
|
|
22169
|
-
if (existsSync10(
|
|
22851
|
+
if (existsSync10(path29.join(root, "LICENSE")))
|
|
22170
22852
|
return true;
|
|
22171
22853
|
const pkg = packageJson(root);
|
|
22172
22854
|
if (pkg && !pkg.private)
|
|
22173
22855
|
return true;
|
|
22174
|
-
return
|
|
22856
|
+
return path29.basename(root) === "workflow-toolkit";
|
|
22175
22857
|
};
|
|
22176
22858
|
var licenseHolder = (root) => {
|
|
22177
22859
|
const pkg = packageJson(root);
|
|
@@ -22194,13 +22876,13 @@ var planHygieneFiles = (root, opts = {}) => {
|
|
|
22194
22876
|
files.push("LICENSE", "CONTRIBUTING.md");
|
|
22195
22877
|
const planned = [];
|
|
22196
22878
|
for (const file of files) {
|
|
22197
|
-
if (existsSync10(
|
|
22879
|
+
if (existsSync10(path29.join(root, file)))
|
|
22198
22880
|
continue;
|
|
22199
|
-
const tpl =
|
|
22881
|
+
const tpl = path29.join(templatesDir(), file);
|
|
22200
22882
|
if (!existsSync10(tpl))
|
|
22201
22883
|
continue;
|
|
22202
|
-
const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g,
|
|
22203
|
-
planned.push({ path:
|
|
22884
|
+
const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g, path29.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
|
|
22885
|
+
planned.push({ path: path29.join(root, file), content });
|
|
22204
22886
|
}
|
|
22205
22887
|
return planned;
|
|
22206
22888
|
};
|
|
@@ -22210,19 +22892,19 @@ var ensureHygieneFiles = (root, opts) => {
|
|
|
22210
22892
|
const created = [];
|
|
22211
22893
|
for (const planned of planHygieneFiles(root, opts)) {
|
|
22212
22894
|
writeFileSync5(planned.path, planned.content, "utf8");
|
|
22213
|
-
created.push(
|
|
22895
|
+
created.push(path29.basename(planned.path));
|
|
22214
22896
|
}
|
|
22215
22897
|
return { ok: true, created };
|
|
22216
22898
|
};
|
|
22217
22899
|
|
|
22218
22900
|
// packages/workit-core/src/core/init.ts
|
|
22219
|
-
import
|
|
22220
|
-
import
|
|
22221
|
-
import { spawnSync as
|
|
22901
|
+
import fs19 from "node:fs";
|
|
22902
|
+
import path31 from "node:path";
|
|
22903
|
+
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
22222
22904
|
|
|
22223
22905
|
// packages/workit-core/src/core/workspace-branch-policy.ts
|
|
22224
22906
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
22225
|
-
import
|
|
22907
|
+
import path30 from "node:path";
|
|
22226
22908
|
import { isDeepStrictEqual } from "node:util";
|
|
22227
22909
|
var readFileSafe = (p) => {
|
|
22228
22910
|
try {
|
|
@@ -22233,7 +22915,7 @@ var readFileSafe = (p) => {
|
|
|
22233
22915
|
};
|
|
22234
22916
|
function applyWorkspaceBranchPolicy(opts) {
|
|
22235
22917
|
const { workspace_root, env = process.env } = opts;
|
|
22236
|
-
const dir =
|
|
22918
|
+
const dir = path30.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
22237
22919
|
const {
|
|
22238
22920
|
status,
|
|
22239
22921
|
path: wsPath,
|
|
@@ -22245,7 +22927,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
22245
22927
|
if (status === "malformed" || status === "invalid")
|
|
22246
22928
|
return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
|
|
22247
22929
|
const detection = detectBranchPolicy(workspace_root);
|
|
22248
|
-
const name = String(env.WORKFLOW_BP_NAME ??
|
|
22930
|
+
const name = String(env.WORKFLOW_BP_NAME ?? path30.basename(workspace_root));
|
|
22249
22931
|
const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
|
|
22250
22932
|
const policy = {
|
|
22251
22933
|
preset: detection.preset,
|
|
@@ -22294,7 +22976,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
22294
22976
|
ok: false,
|
|
22295
22977
|
error: `workspaces.json changed while applying the branch policy: ${wsPath}`
|
|
22296
22978
|
};
|
|
22297
|
-
mkdirSync4(
|
|
22979
|
+
mkdirSync4(path30.dirname(wsPath), { recursive: true });
|
|
22298
22980
|
const tmp = `${wsPath}.${process.pid}.tmp`;
|
|
22299
22981
|
try {
|
|
22300
22982
|
writeFileSync6(tmp, JSON.stringify({ ...document, workspaces: next }, null, 2) + `
|
|
@@ -22329,7 +23011,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
22329
23011
|
var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
|
|
22330
23012
|
var readJson = (p) => {
|
|
22331
23013
|
try {
|
|
22332
|
-
const parsed = JSON.parse(
|
|
23014
|
+
const parsed = JSON.parse(fs19.readFileSync(p, "utf8"));
|
|
22333
23015
|
return isConfigObject(parsed) ? parsed : null;
|
|
22334
23016
|
} catch {
|
|
22335
23017
|
return null;
|
|
@@ -22341,7 +23023,7 @@ var resolveInitProvider = (cwd) => {
|
|
|
22341
23023
|
return env.toLowerCase();
|
|
22342
23024
|
const root = cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
22343
23025
|
try {
|
|
22344
|
-
const out =
|
|
23026
|
+
const out = spawnSync19("git", ["remote", "get-url", "origin"], {
|
|
22345
23027
|
cwd: root,
|
|
22346
23028
|
encoding: "utf8"
|
|
22347
23029
|
});
|
|
@@ -22359,7 +23041,7 @@ var youtrackJsonContent = (dir) => {
|
|
|
22359
23041
|
const meetingIssue = process.env.WORKFLOW_YT_MEETING_ISSUE;
|
|
22360
23042
|
return {
|
|
22361
23043
|
baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://youtrack.example.com",
|
|
22362
|
-
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ??
|
|
23044
|
+
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path31.join(dir, "youtrack.token"),
|
|
22363
23045
|
...meetingIssue ? {
|
|
22364
23046
|
meetingIssue,
|
|
22365
23047
|
meetingIssues: { general: { issue: meetingIssue, label: "General meetings" } }
|
|
@@ -22389,19 +23071,19 @@ var vcsJsonContent = () => {
|
|
|
22389
23071
|
};
|
|
22390
23072
|
function initApplyData(action, env = process.env) {
|
|
22391
23073
|
const dir = String(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
22392
|
-
|
|
23074
|
+
fs19.mkdirSync(dir, { recursive: true });
|
|
22393
23075
|
switch (action) {
|
|
22394
23076
|
case "youtrack_json": {
|
|
22395
|
-
const out =
|
|
22396
|
-
|
|
23077
|
+
const out = path31.join(dir, "youtrack.json");
|
|
23078
|
+
fs19.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
22397
23079
|
`, "utf8");
|
|
22398
23080
|
return { action, ok: true, path: out };
|
|
22399
23081
|
}
|
|
22400
23082
|
case "youtrack_token_placeholder": {
|
|
22401
|
-
const p =
|
|
23083
|
+
const p = path31.join(dir, "youtrack.token");
|
|
22402
23084
|
const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER2 + `
|
|
22403
23085
|
`, 384) === "preserved";
|
|
22404
|
-
const abs =
|
|
23086
|
+
const abs = path31.resolve(p);
|
|
22405
23087
|
return {
|
|
22406
23088
|
action,
|
|
22407
23089
|
ok: true,
|
|
@@ -22413,14 +23095,14 @@ function initApplyData(action, env = process.env) {
|
|
|
22413
23095
|
};
|
|
22414
23096
|
}
|
|
22415
23097
|
case "youtrack_scaffold": {
|
|
22416
|
-
const jsonOut =
|
|
22417
|
-
const tokenOut =
|
|
22418
|
-
|
|
23098
|
+
const jsonOut = path31.join(dir, "youtrack.json");
|
|
23099
|
+
const tokenOut = path31.join(dir, "youtrack.token");
|
|
23100
|
+
fs19.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
22419
23101
|
`, "utf8");
|
|
22420
23102
|
const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER2 + `
|
|
22421
23103
|
`, 384) === "preserved";
|
|
22422
|
-
const configPath =
|
|
22423
|
-
const tokenPath =
|
|
23104
|
+
const configPath = path31.resolve(jsonOut);
|
|
23105
|
+
const tokenPath = path31.resolve(tokenOut);
|
|
22424
23106
|
const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
|
|
22425
23107
|
process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
|
|
22426
23108
|
let tokenCreate = {};
|
|
@@ -22472,10 +23154,10 @@ function initApplyData(action, env = process.env) {
|
|
|
22472
23154
|
}
|
|
22473
23155
|
}
|
|
22474
23156
|
case "vcs_scaffold": {
|
|
22475
|
-
const jsonOut =
|
|
22476
|
-
|
|
23157
|
+
const jsonOut = path31.join(dir, "vcs.json");
|
|
23158
|
+
fs19.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
|
|
22477
23159
|
`, "utf8");
|
|
22478
|
-
const configPath =
|
|
23160
|
+
const configPath = path31.resolve(jsonOut);
|
|
22479
23161
|
const prev = process.env.WORKFLOW_VCS_CONFIG;
|
|
22480
23162
|
process.env.WORKFLOW_VCS_CONFIG = configPath;
|
|
22481
23163
|
try {
|
|
@@ -22594,7 +23276,7 @@ var executeInitApply = (args, directory, runtime) => {
|
|
|
22594
23276
|
protectedNames: args.branch_policy_protected
|
|
22595
23277
|
}, current);
|
|
22596
23278
|
writeConfig(next);
|
|
22597
|
-
return output(ok({ action: "config", path:
|
|
23279
|
+
return output(ok({ action: "config", path: path32.join(configDir(), "config.json"), ...next }));
|
|
22598
23280
|
}
|
|
22599
23281
|
const env = Object.fromEntries(Object.entries({
|
|
22600
23282
|
WORKFLOW_YT_BASE_URL: args.base_url,
|
|
@@ -22611,14 +23293,14 @@ var executeInitApply = (args, directory, runtime) => {
|
|
|
22611
23293
|
|
|
22612
23294
|
// packages/workit-opencode/src/shared/session.ts
|
|
22613
23295
|
import { realpathSync as realpathSync7 } from "node:fs";
|
|
22614
|
-
import
|
|
23296
|
+
import path33 from "node:path";
|
|
22615
23297
|
var sameWorkspace = (expected, observed) => {
|
|
22616
23298
|
if (typeof observed !== "string" || !observed)
|
|
22617
23299
|
return false;
|
|
22618
23300
|
try {
|
|
22619
23301
|
return realpathSync7(expected) === realpathSync7(observed);
|
|
22620
23302
|
} catch {
|
|
22621
|
-
return
|
|
23303
|
+
return path33.resolve(expected) === path33.resolve(observed);
|
|
22622
23304
|
}
|
|
22623
23305
|
};
|
|
22624
23306
|
|
|
@@ -22690,7 +23372,7 @@ var workitFamilyOf = (toolName) => {
|
|
|
22690
23372
|
|
|
22691
23373
|
// packages/workit-opencode/src/tools/context.ts
|
|
22692
23374
|
import { realpathSync as realpathSync8 } from "node:fs";
|
|
22693
|
-
import
|
|
23375
|
+
import path34 from "node:path";
|
|
22694
23376
|
var createContextTool = () => ({
|
|
22695
23377
|
description: "Read Git, pull request, YouTrack, issue, changelog, release, or affected-file context.",
|
|
22696
23378
|
execute: async (args, context) => {
|
|
@@ -22700,7 +23382,7 @@ var createContextTool = () => ({
|
|
|
22700
23382
|
let root;
|
|
22701
23383
|
try {
|
|
22702
23384
|
const cwd = parsed.data.cwd;
|
|
22703
|
-
root = realpathSync8(cwd ?
|
|
23385
|
+
root = realpathSync8(cwd ? path34.resolve(context.directory, cwd) : context.directory);
|
|
22704
23386
|
} catch {
|
|
22705
23387
|
return JSON.stringify(failure2("invalid_input", "context target directory cannot be resolved"), null, 2);
|
|
22706
23388
|
}
|
|
@@ -23244,8 +23926,9 @@ var evaluateShellPermission = (root, event, workdirs = new Map) => {
|
|
|
23244
23926
|
event.message = reason;
|
|
23245
23927
|
};
|
|
23246
23928
|
const resources = event.resources.filter((resource) => typeof resource === "string");
|
|
23929
|
+
const owner = liveOwner({ host: "opencode", session: { id: event.sessionID ?? "", parentId: null } }, "in-process-plugin");
|
|
23247
23930
|
if (WRITE_ACTIONS.has(event.action)) {
|
|
23248
|
-
const decision = writeGate(root, resources);
|
|
23931
|
+
const decision = writeGate(root, resources, owner);
|
|
23249
23932
|
if (decision.kind === "deny")
|
|
23250
23933
|
deny(decision.reason);
|
|
23251
23934
|
return;
|
|
@@ -23267,9 +23950,8 @@ var evaluateShellPermission = (root, event, workdirs = new Map) => {
|
|
|
23267
23950
|
}, resource, { nudge: false });
|
|
23268
23951
|
if (raw.kind === "deny")
|
|
23269
23952
|
return deny(raw.reason);
|
|
23270
|
-
const
|
|
23271
|
-
|
|
23272
|
-
if (gated?.kind === "deny")
|
|
23953
|
+
const gated = shellGate(cwd, resource, owner);
|
|
23954
|
+
if (gated.kind === "deny")
|
|
23273
23955
|
return deny(gated.reason);
|
|
23274
23956
|
}
|
|
23275
23957
|
};
|
|
@@ -23293,7 +23975,7 @@ var childTaskMissing = (store, input) => {
|
|
|
23293
23975
|
var childDenial = (identity) => `OpenCode child session has no validated Workit worker: as ${identity} it may only record evidence or findings (workit_evidence/workit_finding action "record") on an existing task; for anything else the lead assigns it a worker with workit_worker before launching it`;
|
|
23294
23976
|
|
|
23295
23977
|
// packages/workit-opencode/src/v2/shell.ts
|
|
23296
|
-
import
|
|
23978
|
+
import path35 from "node:path";
|
|
23297
23979
|
var commandOf = (input) => {
|
|
23298
23980
|
if (input === null || typeof input !== "object")
|
|
23299
23981
|
return null;
|
|
@@ -23302,7 +23984,7 @@ var commandOf = (input) => {
|
|
|
23302
23984
|
};
|
|
23303
23985
|
var hookInput = (root, sessionID, id, shell) => ({
|
|
23304
23986
|
host: "opencode",
|
|
23305
|
-
cwd: shell.workdir ?
|
|
23987
|
+
cwd: shell.workdir ? path35.resolve(root, shell.workdir) : root,
|
|
23306
23988
|
session: { id: sessionID, agentId: null, agentType: null, parentId: null },
|
|
23307
23989
|
permissionMode: null,
|
|
23308
23990
|
transcriptPath: null,
|
|
@@ -23487,37 +24169,37 @@ var createStopControl = (root, port) => {
|
|
|
23487
24169
|
|
|
23488
24170
|
// packages/workit-opencode/src/v2/registry.ts
|
|
23489
24171
|
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
|
|
23490
|
-
import
|
|
24172
|
+
import path37 from "node:path";
|
|
23491
24173
|
|
|
23492
24174
|
// packages/workit-opencode/src/shared/assets.ts
|
|
23493
24175
|
import { existsSync as existsSync11, realpathSync as realpathSync9 } from "node:fs";
|
|
23494
|
-
import
|
|
24176
|
+
import path36 from "node:path";
|
|
23495
24177
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
23496
24178
|
var moduleDir = () => {
|
|
23497
24179
|
try {
|
|
23498
|
-
return realpathSync9(
|
|
24180
|
+
return realpathSync9(path36.dirname(fileURLToPath2(import.meta.url)));
|
|
23499
24181
|
} catch {
|
|
23500
|
-
return
|
|
24182
|
+
return path36.dirname(fileURLToPath2(import.meta.url));
|
|
23501
24183
|
}
|
|
23502
24184
|
};
|
|
23503
24185
|
var packageRoot2 = () => {
|
|
23504
24186
|
const dir = moduleDir();
|
|
23505
24187
|
for (const candidate of [
|
|
23506
|
-
|
|
23507
|
-
|
|
23508
|
-
|
|
24188
|
+
path36.resolve(dir, ".."),
|
|
24189
|
+
path36.resolve(dir, "..", ".."),
|
|
24190
|
+
path36.resolve(dir, "..", "..", "..")
|
|
23509
24191
|
]) {
|
|
23510
|
-
if (existsSync11(
|
|
24192
|
+
if (existsSync11(path36.join(candidate, "package.json")))
|
|
23511
24193
|
return candidate;
|
|
23512
24194
|
}
|
|
23513
|
-
return
|
|
24195
|
+
return path36.resolve(dir, "..");
|
|
23514
24196
|
};
|
|
23515
24197
|
var skillsRoot = () => {
|
|
23516
24198
|
const root = packageRoot2();
|
|
23517
|
-
const canonical =
|
|
23518
|
-
if (existsSync11(
|
|
24199
|
+
const canonical = path36.resolve(root, "..", "workit-core", "skills");
|
|
24200
|
+
if (existsSync11(path36.join(root, "src")) && existsSync11(canonical))
|
|
23519
24201
|
return canonical;
|
|
23520
|
-
return
|
|
24202
|
+
return path36.join(root, "assets", "skills");
|
|
23521
24203
|
};
|
|
23522
24204
|
|
|
23523
24205
|
// packages/workit-opencode/src/v2/registry.ts
|
|
@@ -23544,11 +24226,11 @@ var registerSkills = async (ctx) => {
|
|
|
23544
24226
|
if (!existsSync12(skillsDir))
|
|
23545
24227
|
return new Set;
|
|
23546
24228
|
const listed = await ctx.skill.list().catch(() => ({ data: [] }));
|
|
23547
|
-
const own = (skill) => typeof skill.path === "string" &&
|
|
24229
|
+
const own = (skill) => typeof skill.path === "string" && path37.resolve(skill.path).startsWith(skillsDir + path37.sep);
|
|
23548
24230
|
const userSkills = new Set((listed?.data ?? []).filter((skill) => !own(skill)).map((skill) => skill.id));
|
|
23549
24231
|
const packaged = new Map;
|
|
23550
24232
|
for (const id of WORKIT_METHOD_SKILLS) {
|
|
23551
|
-
const file =
|
|
24233
|
+
const file = path37.join(skillsDir, id, "SKILL.md");
|
|
23552
24234
|
if (!userSkills.has(id) && existsSync12(file))
|
|
23553
24235
|
packaged.set(id, file);
|
|
23554
24236
|
}
|
|
@@ -23595,23 +24277,23 @@ var registerCommands = async (ctx, registeredSkills) => {
|
|
|
23595
24277
|
};
|
|
23596
24278
|
|
|
23597
24279
|
// packages/workit-opencode/src/stale-sources.ts
|
|
23598
|
-
import { existsSync as existsSync13, readdirSync as
|
|
23599
|
-
import
|
|
24280
|
+
import { existsSync as existsSync13, readdirSync as readdirSync6 } from "node:fs";
|
|
24281
|
+
import path38 from "node:path";
|
|
23600
24282
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
23601
24283
|
var typescriptFiles = (directory) => {
|
|
23602
24284
|
if (!existsSync13(directory))
|
|
23603
24285
|
return [];
|
|
23604
|
-
return
|
|
23605
|
-
const file =
|
|
24286
|
+
return readdirSync6(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
24287
|
+
const file = path38.join(directory, entry.name);
|
|
23606
24288
|
return entry.isDirectory() ? typescriptFiles(file) : entry.isFile() && file.endsWith(".ts") ? [file] : [];
|
|
23607
24289
|
});
|
|
23608
24290
|
};
|
|
23609
24291
|
var pluginSourceFiles = [
|
|
23610
24292
|
...new Set([
|
|
23611
24293
|
fileURLToPath3(import.meta.url),
|
|
23612
|
-
...typescriptFiles(
|
|
23613
|
-
|
|
23614
|
-
...typescriptFiles(
|
|
24294
|
+
...typescriptFiles(path38.join(packageRoot2(), "src")),
|
|
24295
|
+
path38.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
|
|
24296
|
+
...typescriptFiles(path38.join(packageRoot2(), "..", "workit-core", "src", "core"))
|
|
23615
24297
|
])
|
|
23616
24298
|
].filter((file) => existsSync13(file)).toSorted();
|
|
23617
24299
|
|