@brainervirus/workit-cursor 8.9.1 → 8.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +956 -643
- package/dist/mcp-server.js +151 -134
- package/dist/workit-hook.js +985 -648
- package/package.json +3 -3
- package/rules/workit-contract.mdc +4 -1
- package/skills/workit-fanout/references/brief.md +1 -1
- package/skills/workit-fanout/references/orchestration.md +16 -6
- package/skills/workit-implement/SKILL.md +3 -2
- package/skills/workit-review/SKILL.md +4 -2
package/dist/workit-hook.js
CHANGED
|
@@ -8377,7 +8377,7 @@ import { realpathSync as realpathSync4 } from "node:fs";
|
|
|
8377
8377
|
|
|
8378
8378
|
// packages/workit-core/src/core/policy/derive.ts
|
|
8379
8379
|
import { existsSync as existsSync2 } from "node:fs";
|
|
8380
|
-
import
|
|
8380
|
+
import path8 from "node:path";
|
|
8381
8381
|
|
|
8382
8382
|
// packages/workit-core/src/git/rev.ts
|
|
8383
8383
|
import { spawnSync } from "node:child_process";
|
|
@@ -8562,9 +8562,9 @@ function pushRemoteName(cwd, branch) {
|
|
|
8562
8562
|
|
|
8563
8563
|
// packages/workit-core/src/ledger.ts
|
|
8564
8564
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
8565
|
-
import { createHash as
|
|
8566
|
-
import
|
|
8567
|
-
import
|
|
8565
|
+
import { createHash as createHash7, randomBytes } from "node:crypto";
|
|
8566
|
+
import fs5 from "node:fs";
|
|
8567
|
+
import path7 from "node:path";
|
|
8568
8568
|
|
|
8569
8569
|
// packages/workit-core/src/core/vcs-config.ts
|
|
8570
8570
|
import fs2 from "node:fs";
|
|
@@ -8792,6 +8792,7 @@ var branchNames = (workspaceRoot) => {
|
|
|
8792
8792
|
return new Set((r.stdout ?? "").split(`
|
|
8793
8793
|
`).filter(Boolean).map((ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.slice("refs/remotes/origin/".length)));
|
|
8794
8794
|
};
|
|
8795
|
+
var productionBranchOf = (names) => names.has("main") ? "main" : names.has("master") ? "master" : "main";
|
|
8795
8796
|
var detectBranchPolicy = (workspaceRoot) => {
|
|
8796
8797
|
const names = branchNames(workspaceRoot);
|
|
8797
8798
|
const develop = names.has("develop");
|
|
@@ -9799,33 +9800,9 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
9799
9800
|
}
|
|
9800
9801
|
|
|
9801
9802
|
// packages/workit-core/src/host-session.ts
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
[
|
|
9806
|
-
"CODEX_THREAD_ID",
|
|
9807
|
-
(env) => env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || env.CODEX_ELECTRON_RESOURCES_PATH ? "codex_desktop" : "codex_cli"
|
|
9808
|
-
]
|
|
9809
|
-
];
|
|
9810
|
-
var family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null;
|
|
9811
|
-
function hostSessionFromEnv(env) {
|
|
9812
|
-
const value = (key) => env[key]?.trim() || null;
|
|
9813
|
-
let inner = null;
|
|
9814
|
-
for (const [key, host] of HOST_SESSION_VARS) {
|
|
9815
|
-
const session = value(key);
|
|
9816
|
-
if (session) {
|
|
9817
|
-
inner = { host: host(env), session };
|
|
9818
|
-
break;
|
|
9819
|
-
}
|
|
9820
|
-
}
|
|
9821
|
-
const declared = value("WORKIT_HOST");
|
|
9822
|
-
if (env.WORKIT_SESSION_ID !== undefined) {
|
|
9823
|
-
const inherited = inner !== null && declared !== null && family(declared) !== family(inner.host);
|
|
9824
|
-
if (!inherited)
|
|
9825
|
-
return { host: declared, session: value("WORKIT_SESSION_ID") };
|
|
9826
|
-
}
|
|
9827
|
-
return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
|
|
9828
|
-
}
|
|
9803
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
9804
|
+
import fs4 from "node:fs";
|
|
9805
|
+
import path6 from "node:path";
|
|
9829
9806
|
|
|
9830
9807
|
// packages/workit-core/src/store/paths.ts
|
|
9831
9808
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
@@ -10107,6 +10084,63 @@ var checkoutSlug = (root) => {
|
|
|
10107
10084
|
return `${base}-${shortHash(canonical)}`;
|
|
10108
10085
|
};
|
|
10109
10086
|
|
|
10087
|
+
// packages/workit-core/src/host-session.ts
|
|
10088
|
+
var HOST_SESSION_VARS = [
|
|
10089
|
+
["OPENCODE_SESSION_ID", () => "opencode"],
|
|
10090
|
+
["PI_SESSION_ID", () => "pi"],
|
|
10091
|
+
[
|
|
10092
|
+
"CODEX_THREAD_ID",
|
|
10093
|
+
(env) => env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || env.CODEX_ELECTRON_RESOURCES_PATH ? "codex_desktop" : "codex_cli"
|
|
10094
|
+
]
|
|
10095
|
+
];
|
|
10096
|
+
var family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null;
|
|
10097
|
+
function hostSessionFromEnv(env) {
|
|
10098
|
+
const value = (key) => env[key]?.trim() || null;
|
|
10099
|
+
let inner = null;
|
|
10100
|
+
for (const [key, host] of HOST_SESSION_VARS) {
|
|
10101
|
+
const session = value(key);
|
|
10102
|
+
if (session) {
|
|
10103
|
+
inner = { host: host(env), session };
|
|
10104
|
+
break;
|
|
10105
|
+
}
|
|
10106
|
+
}
|
|
10107
|
+
const declared = value("WORKIT_HOST");
|
|
10108
|
+
if (env.WORKIT_SESSION_ID !== undefined) {
|
|
10109
|
+
const inherited = inner !== null && declared !== null && family(declared) !== family(inner.host);
|
|
10110
|
+
if (!inherited)
|
|
10111
|
+
return { host: declared, session: value("WORKIT_SESSION_ID") };
|
|
10112
|
+
}
|
|
10113
|
+
return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
|
|
10114
|
+
}
|
|
10115
|
+
var ISSUED_PER_LEAD = 64;
|
|
10116
|
+
var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/1";
|
|
10117
|
+
var hash = (value) => createHash6("sha256").update(value).digest("hex").slice(0, 32);
|
|
10118
|
+
var issuedRoot = (cwd) => {
|
|
10119
|
+
const location = resolveStore(cwd);
|
|
10120
|
+
return location instanceof Error ? null : path6.join(location.dir, "hooks", "issued");
|
|
10121
|
+
};
|
|
10122
|
+
var issuedDir = (cwd, lead) => {
|
|
10123
|
+
const root = issuedRoot(cwd);
|
|
10124
|
+
return root === null ? null : path6.join(root, hash(lead));
|
|
10125
|
+
};
|
|
10126
|
+
function recordIssuedSession(cwd, lead, issued) {
|
|
10127
|
+
try {
|
|
10128
|
+
const dir = issuedDir(cwd, lead);
|
|
10129
|
+
if (!dir)
|
|
10130
|
+
return;
|
|
10131
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
10132
|
+
const own = hash(issued);
|
|
10133
|
+
fs4.writeFileSync(path6.join(dir, own), ISSUED_SESSIONS_MARKER);
|
|
10134
|
+
const entries = fs4.readdirSync(dir);
|
|
10135
|
+
if (entries.length <= ISSUED_PER_LEAD)
|
|
10136
|
+
return;
|
|
10137
|
+
const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs4.statSync(path6.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
|
|
10138
|
+
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD))
|
|
10139
|
+
fs4.rmSync(path6.join(dir, name), { force: true });
|
|
10140
|
+
} catch {}
|
|
10141
|
+
}
|
|
10142
|
+
var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
|
|
10143
|
+
|
|
10110
10144
|
// packages/workit-core/src/ledger.ts
|
|
10111
10145
|
var LEDGER_VERSION = 1;
|
|
10112
10146
|
var MAX_LINE_BYTES = 4096;
|
|
@@ -10212,7 +10246,7 @@ function diffHash(cwd, base, head) {
|
|
|
10212
10246
|
], GIT_TIMEOUTS.worktree);
|
|
10213
10247
|
if (!diff.ok || !diff.stdout)
|
|
10214
10248
|
return null;
|
|
10215
|
-
const hash =
|
|
10249
|
+
const hash = createHash7("sha256");
|
|
10216
10250
|
for (const line of diff.stdout.split(`
|
|
10217
10251
|
`)) {
|
|
10218
10252
|
if (line.startsWith("index "))
|
|
@@ -10224,14 +10258,14 @@ function diffHash(cwd, base, head) {
|
|
|
10224
10258
|
return `sha256:${hash.digest("hex")}`;
|
|
10225
10259
|
}
|
|
10226
10260
|
function storeRoot(cwd) {
|
|
10227
|
-
const location = resolveStore(
|
|
10261
|
+
const location = resolveStore(path7.resolve(cwd));
|
|
10228
10262
|
if (location instanceof Error)
|
|
10229
10263
|
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");
|
|
10230
10264
|
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
10231
10265
|
}
|
|
10232
10266
|
function ledgerPath(cwd) {
|
|
10233
10267
|
const root = storeRoot(cwd);
|
|
10234
|
-
return root.ok ? { ok: true, value:
|
|
10268
|
+
return root.ok ? { ok: true, value: path7.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
10235
10269
|
}
|
|
10236
10270
|
var newId2 = () => `${Date.now().toString(36)}-${randomBytes(6).toString("hex")}`;
|
|
10237
10271
|
var NETWORK_FS_TYPES = new Set([
|
|
@@ -10251,7 +10285,7 @@ var onNetworkFs = (dir) => {
|
|
|
10251
10285
|
if (process.platform !== "linux")
|
|
10252
10286
|
return false;
|
|
10253
10287
|
try {
|
|
10254
|
-
return isNetworkFsType(
|
|
10288
|
+
return isNetworkFsType(fs5.statfsSync(dir).type);
|
|
10255
10289
|
} catch {
|
|
10256
10290
|
return false;
|
|
10257
10291
|
}
|
|
@@ -10261,15 +10295,15 @@ var LOCK_STALE_MS = 30000;
|
|
|
10261
10295
|
var LOCK_OWNER = "owner";
|
|
10262
10296
|
var lockToken = (lock) => {
|
|
10263
10297
|
try {
|
|
10264
|
-
return
|
|
10298
|
+
return fs5.readFileSync(path7.join(lock, LOCK_OWNER), "utf8");
|
|
10265
10299
|
} catch {
|
|
10266
10300
|
return null;
|
|
10267
10301
|
}
|
|
10268
10302
|
};
|
|
10269
10303
|
var lockAgeMs = (lock) => {
|
|
10270
|
-
for (const target of [
|
|
10304
|
+
for (const target of [path7.join(lock, LOCK_OWNER), lock]) {
|
|
10271
10305
|
try {
|
|
10272
|
-
return Date.now() -
|
|
10306
|
+
return Date.now() - fs5.statSync(target).mtimeMs;
|
|
10273
10307
|
} catch {}
|
|
10274
10308
|
}
|
|
10275
10309
|
return null;
|
|
@@ -10277,14 +10311,14 @@ var lockAgeMs = (lock) => {
|
|
|
10277
10311
|
var ledgerLock = {
|
|
10278
10312
|
acquire(lock) {
|
|
10279
10313
|
try {
|
|
10280
|
-
|
|
10314
|
+
fs5.mkdirSync(lock);
|
|
10281
10315
|
} catch (error) {
|
|
10282
10316
|
if (error.code === "EEXIST")
|
|
10283
10317
|
return null;
|
|
10284
10318
|
throw error;
|
|
10285
10319
|
}
|
|
10286
10320
|
const token = `${process.pid}-${randomBytes(8).toString("hex")}`;
|
|
10287
|
-
|
|
10321
|
+
fs5.writeFileSync(path7.join(lock, LOCK_OWNER), token);
|
|
10288
10322
|
return token;
|
|
10289
10323
|
},
|
|
10290
10324
|
release(lock, token) {
|
|
@@ -10292,35 +10326,35 @@ var ledgerLock = {
|
|
|
10292
10326
|
return false;
|
|
10293
10327
|
const tomb = `${lock}.release-${randomBytes(6).toString("hex")}`;
|
|
10294
10328
|
try {
|
|
10295
|
-
|
|
10329
|
+
fs5.renameSync(lock, tomb);
|
|
10296
10330
|
} catch {
|
|
10297
10331
|
return false;
|
|
10298
10332
|
}
|
|
10299
10333
|
if (lockToken(tomb) !== token) {
|
|
10300
10334
|
try {
|
|
10301
|
-
|
|
10335
|
+
fs5.renameSync(tomb, lock);
|
|
10302
10336
|
} catch {}
|
|
10303
10337
|
return false;
|
|
10304
10338
|
}
|
|
10305
|
-
|
|
10339
|
+
fs5.rmSync(tomb, { recursive: true, force: true });
|
|
10306
10340
|
return true;
|
|
10307
10341
|
},
|
|
10308
10342
|
token: lockToken,
|
|
10309
10343
|
reap(lock, observed) {
|
|
10310
10344
|
const tomb = `${lock}.reap-${randomBytes(6).toString("hex")}`;
|
|
10311
10345
|
try {
|
|
10312
|
-
|
|
10346
|
+
fs5.renameSync(lock, tomb);
|
|
10313
10347
|
} catch {
|
|
10314
10348
|
return false;
|
|
10315
10349
|
}
|
|
10316
10350
|
if (lockToken(tomb) === observed) {
|
|
10317
|
-
|
|
10351
|
+
fs5.rmSync(tomb, { recursive: true, force: true });
|
|
10318
10352
|
return true;
|
|
10319
10353
|
}
|
|
10320
10354
|
try {
|
|
10321
|
-
|
|
10355
|
+
fs5.renameSync(tomb, lock);
|
|
10322
10356
|
} catch {
|
|
10323
|
-
|
|
10357
|
+
fs5.rmSync(tomb, { recursive: true, force: true });
|
|
10324
10358
|
}
|
|
10325
10359
|
return false;
|
|
10326
10360
|
}
|
|
@@ -10347,14 +10381,14 @@ function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
|
|
|
10347
10381
|
ledgerLock.release(lock, token);
|
|
10348
10382
|
}
|
|
10349
10383
|
}
|
|
10350
|
-
var sha2562 = (text) =>
|
|
10384
|
+
var sha2562 = (text) => createHash7("sha256").update(text).digest("hex");
|
|
10351
10385
|
var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
|
|
10352
10386
|
${body}`);
|
|
10353
10387
|
var TAIL_BYTES = 64 * 1024;
|
|
10354
10388
|
var lastLine = (fd, size) => {
|
|
10355
10389
|
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
10356
10390
|
const buffer = Buffer.alloc(span);
|
|
10357
|
-
|
|
10391
|
+
fs5.readSync(fd, buffer, 0, span, size - span);
|
|
10358
10392
|
const end = buffer.lastIndexOf(10);
|
|
10359
10393
|
if (end < 0) {
|
|
10360
10394
|
if (span === size)
|
|
@@ -10385,13 +10419,13 @@ var lastLine = (fd, size) => {
|
|
|
10385
10419
|
};
|
|
10386
10420
|
function appendLine(file, build, options) {
|
|
10387
10421
|
const write = () => {
|
|
10388
|
-
const fd =
|
|
10422
|
+
const fd = fs5.openSync(file, "a+");
|
|
10389
10423
|
try {
|
|
10390
|
-
const size =
|
|
10424
|
+
const size = fs5.fstatSync(fd).size;
|
|
10391
10425
|
let fence = "";
|
|
10392
10426
|
if (size > 0) {
|
|
10393
10427
|
const last = Buffer.alloc(1);
|
|
10394
|
-
|
|
10428
|
+
fs5.readSync(fd, last, 0, 1, size - 1);
|
|
10395
10429
|
if (last[0] !== 10)
|
|
10396
10430
|
fence = `
|
|
10397
10431
|
`;
|
|
@@ -10400,17 +10434,17 @@ function appendLine(file, build, options) {
|
|
|
10400
10434
|
if (!line.ok)
|
|
10401
10435
|
return line;
|
|
10402
10436
|
const bytes = Buffer.from(fence + line.value);
|
|
10403
|
-
const written =
|
|
10437
|
+
const written = fs5.writeSync(fd, bytes);
|
|
10404
10438
|
if (written !== bytes.length)
|
|
10405
10439
|
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
10406
10440
|
return { ok: true, value: undefined };
|
|
10407
10441
|
} finally {
|
|
10408
|
-
|
|
10442
|
+
fs5.closeSync(fd);
|
|
10409
10443
|
}
|
|
10410
10444
|
};
|
|
10411
10445
|
try {
|
|
10412
|
-
|
|
10413
|
-
if (!options.forceLock && !onNetworkFs(
|
|
10446
|
+
fs5.mkdirSync(path7.dirname(file), { recursive: true });
|
|
10447
|
+
if (!options.forceLock && !onNetworkFs(path7.dirname(file)))
|
|
10414
10448
|
return write();
|
|
10415
10449
|
const locked = withLedgerLock(file, write);
|
|
10416
10450
|
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
@@ -10482,7 +10516,7 @@ function readLedger(cwd, options = {}) {
|
|
|
10482
10516
|
return file;
|
|
10483
10517
|
let text = "";
|
|
10484
10518
|
try {
|
|
10485
|
-
text =
|
|
10519
|
+
text = fs5.readFileSync(file.value, "utf8");
|
|
10486
10520
|
} catch (error) {
|
|
10487
10521
|
if (error.code === "ENOENT")
|
|
10488
10522
|
return {
|
|
@@ -10676,11 +10710,20 @@ function authorSessions(cwd, rows, branch, range = { base: null, head: null }, s
|
|
|
10676
10710
|
sessions.add(session);
|
|
10677
10711
|
return sessions;
|
|
10678
10712
|
}
|
|
10713
|
+
var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
|
|
10714
|
+
var authoredBy = (authors, session) => {
|
|
10715
|
+
if (authors.has(session))
|
|
10716
|
+
return true;
|
|
10717
|
+
for (const author of authors)
|
|
10718
|
+
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
10719
|
+
return true;
|
|
10720
|
+
return false;
|
|
10721
|
+
};
|
|
10679
10722
|
var independenceReasons = (row, authors) => {
|
|
10680
10723
|
const reasons = [];
|
|
10681
10724
|
if (!row.actor.session)
|
|
10682
10725
|
reasons.push("no_session");
|
|
10683
|
-
else if (authors
|
|
10726
|
+
else if (authoredBy(authors, row.actor.session))
|
|
10684
10727
|
reasons.push("author_session");
|
|
10685
10728
|
if (row.self === true && !reasons.includes("no_session"))
|
|
10686
10729
|
reasons.push("self");
|
|
@@ -10964,7 +11007,7 @@ var productDecision = (task, ledger) => {
|
|
|
10964
11007
|
};
|
|
10965
11008
|
};
|
|
10966
11009
|
var planRecorded = (task, root) => {
|
|
10967
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync2(
|
|
11010
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync2(path8.resolve(root, ref.path)));
|
|
10968
11011
|
if (cited)
|
|
10969
11012
|
return {
|
|
10970
11013
|
met: true,
|
|
@@ -11580,7 +11623,7 @@ var judgeInputSummary = (raw) => {
|
|
|
11580
11623
|
|
|
11581
11624
|
// packages/workit-core/src/autonomy.ts
|
|
11582
11625
|
import os3 from "node:os";
|
|
11583
|
-
import
|
|
11626
|
+
import path9 from "node:path";
|
|
11584
11627
|
var accountHome = () => {
|
|
11585
11628
|
try {
|
|
11586
11629
|
return os3.userInfo().homedir;
|
|
@@ -11589,10 +11632,10 @@ var accountHome = () => {
|
|
|
11589
11632
|
}
|
|
11590
11633
|
};
|
|
11591
11634
|
var grantsHome = { resolve: accountHome };
|
|
11592
|
-
var grantsDir = () =>
|
|
11635
|
+
var grantsDir = () => path9.join(grantsHome.resolve(), ".config", "workit");
|
|
11593
11636
|
var grantsOverride = () => {
|
|
11594
|
-
const configured =
|
|
11595
|
-
return configured ===
|
|
11637
|
+
const configured = path9.resolve(resolveConfigDir());
|
|
11638
|
+
return configured === path9.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 ${path9.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
|
|
11596
11639
|
};
|
|
11597
11640
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
11598
11641
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -11716,9 +11759,9 @@ var suggested = (kind) => kind === "merge" ? "verified" : "true";
|
|
|
11716
11759
|
var grantHint = (workspace, kind) => `ask the user to run, in their own terminal: workit grant set ${workspace ?? "<workspace>"} ${kind}=${suggested(kind)}`;
|
|
11717
11760
|
|
|
11718
11761
|
// packages/workit-core/src/core/task-store.ts
|
|
11719
|
-
import { createHash as
|
|
11720
|
-
import * as
|
|
11721
|
-
import
|
|
11762
|
+
import { createHash as createHash9, randomUUID as randomUUID3 } from "node:crypto";
|
|
11763
|
+
import * as fs11 from "node:fs";
|
|
11764
|
+
import path14 from "node:path";
|
|
11722
11765
|
|
|
11723
11766
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
11724
11767
|
var OPERATIONAL_CODES = new Set([
|
|
@@ -11769,10 +11812,10 @@ function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
11769
11812
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
11770
11813
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
11771
11814
|
import fsSync from "node:fs";
|
|
11772
|
-
import
|
|
11815
|
+
import path10 from "node:path";
|
|
11773
11816
|
|
|
11774
11817
|
// node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
11775
|
-
import
|
|
11818
|
+
import fs6 from "node:fs";
|
|
11776
11819
|
|
|
11777
11820
|
// node_modules/@openclaw/fs-safe/dist/byte-budget.js
|
|
11778
11821
|
function normalizeMaxBytes(value, options = {}) {
|
|
@@ -11809,7 +11852,7 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
|
11809
11852
|
let total = 0;
|
|
11810
11853
|
while (true) {
|
|
11811
11854
|
const length = nextReadLength(total, maxBytes, scratch.length);
|
|
11812
|
-
const bytesRead =
|
|
11855
|
+
const bytesRead = fs6.readSync(fd, scratch, 0, length, null);
|
|
11813
11856
|
if (bytesRead === 0) {
|
|
11814
11857
|
return Buffer.concat(chunks, total);
|
|
11815
11858
|
}
|
|
@@ -11818,9 +11861,9 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
|
11818
11861
|
}
|
|
11819
11862
|
|
|
11820
11863
|
// node_modules/@openclaw/fs-safe/dist/read-open-flags.js
|
|
11821
|
-
import
|
|
11864
|
+
import fs7 from "node:fs";
|
|
11822
11865
|
function resolveReadOpenFlags(options) {
|
|
11823
|
-
const constants = options?.constants ??
|
|
11866
|
+
const constants = options?.constants ?? fs7.constants;
|
|
11824
11867
|
const noFollow = process.platform !== "win32" && options?.followSymlinks !== true && typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
|
|
11825
11868
|
const nonBlocking = process.platform !== "win32" && typeof constants.O_NONBLOCK === "number" ? constants.O_NONBLOCK : 0;
|
|
11826
11869
|
return constants.O_RDONLY | noFollow | nonBlocking;
|
|
@@ -11859,13 +11902,13 @@ ${ownershipToken}
|
|
|
11859
11902
|
return { raw, ownershipToken };
|
|
11860
11903
|
}
|
|
11861
11904
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
11862
|
-
const resolved =
|
|
11863
|
-
const lexicalRelative =
|
|
11864
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
11865
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
11905
|
+
const resolved = path10.resolve(lockPath);
|
|
11906
|
+
const lexicalRelative = path10.relative(lockRoot.rootDir, resolved);
|
|
11907
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path10.sep}`) && !path10.isAbsolute(lexicalRelative) ? lexicalRelative : path10.relative(lockRoot.rootReal, resolved);
|
|
11908
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path10.sep}`) || path10.isAbsolute(relative)) {
|
|
11866
11909
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
11867
11910
|
}
|
|
11868
|
-
return relative.split(
|
|
11911
|
+
return relative.split(path10.sep).join(path10.posix.sep);
|
|
11869
11912
|
}
|
|
11870
11913
|
function parseSidecarLockPayload(raw, parser) {
|
|
11871
11914
|
if (parser) {
|
|
@@ -12011,8 +12054,8 @@ function getFsSafeLockConfig() {
|
|
|
12011
12054
|
}
|
|
12012
12055
|
|
|
12013
12056
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
12014
|
-
import
|
|
12015
|
-
import
|
|
12057
|
+
import fs8 from "node:fs";
|
|
12058
|
+
import path11 from "node:path";
|
|
12016
12059
|
|
|
12017
12060
|
// node_modules/@openclaw/fs-safe/dist/timing.js
|
|
12018
12061
|
function sleepSync(ms) {
|
|
@@ -12042,7 +12085,7 @@ function releaseAllSyncHeldLocks() {
|
|
|
12042
12085
|
}
|
|
12043
12086
|
try {
|
|
12044
12087
|
if (held.fd !== undefined)
|
|
12045
|
-
|
|
12088
|
+
fs8.closeSync(held.fd);
|
|
12046
12089
|
} catch {}
|
|
12047
12090
|
try {
|
|
12048
12091
|
removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
|
|
@@ -12077,7 +12120,7 @@ function releaseSyncHeldLock(held) {
|
|
|
12077
12120
|
if (held.fd !== undefined) {
|
|
12078
12121
|
const fd = held.fd;
|
|
12079
12122
|
held.fd = undefined;
|
|
12080
|
-
|
|
12123
|
+
fs8.closeSync(fd);
|
|
12081
12124
|
}
|
|
12082
12125
|
removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
|
|
12083
12126
|
heldLocks.delete(held.normalizedTargetPath);
|
|
@@ -12100,29 +12143,29 @@ function createSyncHeldLockHandle(held) {
|
|
|
12100
12143
|
};
|
|
12101
12144
|
}
|
|
12102
12145
|
function canonicalLockParentSync(parent) {
|
|
12103
|
-
return process.platform === "win32" ?
|
|
12146
|
+
return process.platform === "win32" ? fs8.realpathSync.native(parent) : fs8.realpathSync(parent);
|
|
12104
12147
|
}
|
|
12105
12148
|
function normalizeTargetPath(targetPath) {
|
|
12106
|
-
const resolved =
|
|
12107
|
-
|
|
12149
|
+
const resolved = path11.resolve(targetPath);
|
|
12150
|
+
fs8.mkdirSync(path11.dirname(resolved), { recursive: true });
|
|
12108
12151
|
try {
|
|
12109
|
-
return
|
|
12152
|
+
return path11.join(canonicalLockParentSync(path11.dirname(resolved)), path11.basename(resolved));
|
|
12110
12153
|
} catch {
|
|
12111
12154
|
return resolved;
|
|
12112
12155
|
}
|
|
12113
12156
|
}
|
|
12114
12157
|
function boundedLockPath(lockPath, lockRoot) {
|
|
12115
|
-
const resolved =
|
|
12158
|
+
const resolved = path11.resolve(lockPath);
|
|
12116
12159
|
if (!lockRoot)
|
|
12117
12160
|
return resolved;
|
|
12118
12161
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
12119
|
-
const parent =
|
|
12162
|
+
const parent = path11.dirname(resolved);
|
|
12120
12163
|
const parentReal = canonicalLockParentSync(parent);
|
|
12121
|
-
const parentRelative =
|
|
12122
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
12164
|
+
const parentRelative = path11.relative(lockRoot.rootReal, parentReal);
|
|
12165
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path11.sep}`) || path11.isAbsolute(parentRelative)) {
|
|
12123
12166
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
12124
12167
|
}
|
|
12125
|
-
return
|
|
12168
|
+
return path11.join(parentReal, path11.basename(resolved));
|
|
12126
12169
|
}
|
|
12127
12170
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
12128
12171
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -12132,7 +12175,7 @@ function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
|
12132
12175
|
}
|
|
12133
12176
|
function reclaimGuardExists(reclaimGuardPath) {
|
|
12134
12177
|
try {
|
|
12135
|
-
|
|
12178
|
+
fs8.lstatSync(reclaimGuardPath);
|
|
12136
12179
|
return true;
|
|
12137
12180
|
} catch (error) {
|
|
12138
12181
|
if (error.code === "ENOENT")
|
|
@@ -12196,19 +12239,19 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
12196
12239
|
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
12197
12240
|
let lockFileCreateDenied = false;
|
|
12198
12241
|
try {
|
|
12199
|
-
const noFollow = process.platform !== "win32" && typeof
|
|
12242
|
+
const noFollow = process.platform !== "win32" && typeof fs8.constants.O_NOFOLLOW === "number" ? fs8.constants.O_NOFOLLOW : 0;
|
|
12200
12243
|
try {
|
|
12201
|
-
fd =
|
|
12244
|
+
fd = fs8.openSync(lockPath, fs8.constants.O_WRONLY | fs8.constants.O_CREAT | fs8.constants.O_EXCL | noFollow, 384);
|
|
12202
12245
|
} catch (error) {
|
|
12203
12246
|
lockFileCreateDenied = isTransientLockFileDenial(error, lockPath);
|
|
12204
12247
|
throw error;
|
|
12205
12248
|
}
|
|
12206
|
-
|
|
12207
|
-
|
|
12249
|
+
fs8.writeFileSync(fd, raw, "utf8");
|
|
12250
|
+
fs8.fsyncSync(fd);
|
|
12208
12251
|
const snapshot = {
|
|
12209
12252
|
raw,
|
|
12210
12253
|
payload,
|
|
12211
|
-
stat:
|
|
12254
|
+
stat: fs8.fstatSync(fd),
|
|
12212
12255
|
ownershipToken
|
|
12213
12256
|
};
|
|
12214
12257
|
const createdHeld = {
|
|
@@ -12243,8 +12286,8 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
12243
12286
|
return returnedHandle;
|
|
12244
12287
|
} catch (error) {
|
|
12245
12288
|
if (fd !== undefined) {
|
|
12246
|
-
const failed = { payload: null, stat:
|
|
12247
|
-
|
|
12289
|
+
const failed = { payload: null, stat: fs8.fstatSync(fd) };
|
|
12290
|
+
fs8.closeSync(fd);
|
|
12248
12291
|
fd = undefined;
|
|
12249
12292
|
removeSidecarLockIfUnchangedSync(lockPath, failed);
|
|
12250
12293
|
}
|
|
@@ -12293,7 +12336,7 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
12293
12336
|
})) {
|
|
12294
12337
|
let ownsReclaimGuard = false;
|
|
12295
12338
|
try {
|
|
12296
|
-
|
|
12339
|
+
fs8.mkdirSync(reclaimGuardPath);
|
|
12297
12340
|
ownsReclaimGuard = true;
|
|
12298
12341
|
if (removeSidecarLockIfUnchangedSync(lockPath, snapshot))
|
|
12299
12342
|
continue;
|
|
@@ -12306,7 +12349,7 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
12306
12349
|
} finally {
|
|
12307
12350
|
if (ownsReclaimGuard) {
|
|
12308
12351
|
try {
|
|
12309
|
-
|
|
12352
|
+
fs8.rmdirSync(reclaimGuardPath);
|
|
12310
12353
|
} catch {}
|
|
12311
12354
|
}
|
|
12312
12355
|
}
|
|
@@ -12323,13 +12366,13 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
12323
12366
|
}
|
|
12324
12367
|
// packages/workit-core/src/core/package-root.ts
|
|
12325
12368
|
import { existsSync as existsSync3 } from "node:fs";
|
|
12326
|
-
import
|
|
12369
|
+
import path12 from "node:path";
|
|
12327
12370
|
import { fileURLToPath } from "node:url";
|
|
12328
12371
|
var packageRoot = () => {
|
|
12329
|
-
let dir =
|
|
12372
|
+
let dir = path12.dirname(fileURLToPath(import.meta.url));
|
|
12330
12373
|
while (true) {
|
|
12331
|
-
const parent =
|
|
12332
|
-
if (existsSync3(
|
|
12374
|
+
const parent = path12.dirname(dir);
|
|
12375
|
+
if (existsSync3(path12.join(dir, "package.json")) || parent === dir)
|
|
12333
12376
|
return dir;
|
|
12334
12377
|
dir = parent;
|
|
12335
12378
|
}
|
|
@@ -12337,7 +12380,7 @@ var packageRoot = () => {
|
|
|
12337
12380
|
|
|
12338
12381
|
// packages/workit-core/src/core/store-lock.ts
|
|
12339
12382
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
12340
|
-
import
|
|
12383
|
+
import fs9 from "node:fs";
|
|
12341
12384
|
import { hostname } from "node:os";
|
|
12342
12385
|
var metadataLockSchema = object({
|
|
12343
12386
|
pid: number2().int().nonnegative().safe(),
|
|
@@ -12379,7 +12422,7 @@ var parseProcStatStart = (stat) => {
|
|
|
12379
12422
|
var processStartOf = (pid) => {
|
|
12380
12423
|
if (process.platform === "linux") {
|
|
12381
12424
|
try {
|
|
12382
|
-
return parseProcStatStart(
|
|
12425
|
+
return parseProcStatStart(fs9.readFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
12383
12426
|
} catch {
|
|
12384
12427
|
return null;
|
|
12385
12428
|
}
|
|
@@ -12400,7 +12443,7 @@ var processStartOf = (pid) => {
|
|
|
12400
12443
|
};
|
|
12401
12444
|
var readTrimmed = (file) => {
|
|
12402
12445
|
try {
|
|
12403
|
-
return
|
|
12446
|
+
return fs9.readFileSync(file, "utf8").trim() || null;
|
|
12404
12447
|
} catch {
|
|
12405
12448
|
return null;
|
|
12406
12449
|
}
|
|
@@ -12411,7 +12454,7 @@ var localLockHost = () => {
|
|
|
12411
12454
|
return cachedLockHost;
|
|
12412
12455
|
let pidns = null;
|
|
12413
12456
|
try {
|
|
12414
|
-
pidns = /\[(\d+)\]/.exec(
|
|
12457
|
+
pidns = /\[(\d+)\]/.exec(fs9.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
|
|
12415
12458
|
} catch {}
|
|
12416
12459
|
const boot = readTrimmed("/proc/sys/kernel/random/boot_id");
|
|
12417
12460
|
cachedLockHost = pidns || boot ? `${hostname()}#${pidns ?? "?"}:${boot ?? "?"}` : hostname();
|
|
@@ -12457,7 +12500,7 @@ var classifyLockOwner = (payload, ageMs, localHost = localLockHost(), options =
|
|
|
12457
12500
|
};
|
|
12458
12501
|
var ageOf = (file, nowMs) => {
|
|
12459
12502
|
try {
|
|
12460
|
-
return nowMs -
|
|
12503
|
+
return nowMs - fs9.lstatSync(file).mtimeMs;
|
|
12461
12504
|
} catch {
|
|
12462
12505
|
return null;
|
|
12463
12506
|
}
|
|
@@ -12468,7 +12511,7 @@ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
|
|
|
12468
12511
|
if (age === null || age <= RECLAIM_GUARD_TTL_MS)
|
|
12469
12512
|
return false;
|
|
12470
12513
|
try {
|
|
12471
|
-
|
|
12514
|
+
fs9.rmdirSync(guard);
|
|
12472
12515
|
return true;
|
|
12473
12516
|
} catch {
|
|
12474
12517
|
return false;
|
|
@@ -12477,8 +12520,8 @@ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
|
|
|
12477
12520
|
|
|
12478
12521
|
// packages/workit-core/src/store/event-log.ts
|
|
12479
12522
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
12480
|
-
import
|
|
12481
|
-
import
|
|
12523
|
+
import fs10 from "node:fs";
|
|
12524
|
+
import path13 from "node:path";
|
|
12482
12525
|
var EVENT_VERSION = 1;
|
|
12483
12526
|
|
|
12484
12527
|
class LogDamage extends Error {
|
|
@@ -12507,7 +12550,7 @@ var parseEvent = (line, file, offset) => {
|
|
|
12507
12550
|
function readLog(file, from = 0, expectSeq = null) {
|
|
12508
12551
|
let fd;
|
|
12509
12552
|
try {
|
|
12510
|
-
fd =
|
|
12553
|
+
fd = fs10.openSync(file, "r");
|
|
12511
12554
|
} catch (error) {
|
|
12512
12555
|
if (error.code === "ENOENT")
|
|
12513
12556
|
return null;
|
|
@@ -12516,13 +12559,13 @@ function readLog(file, from = 0, expectSeq = null) {
|
|
|
12516
12559
|
let bytes;
|
|
12517
12560
|
let size;
|
|
12518
12561
|
try {
|
|
12519
|
-
size =
|
|
12562
|
+
size = fs10.fstatSync(fd).size;
|
|
12520
12563
|
if (from > size)
|
|
12521
12564
|
throw new LogDamage(`${file}: shorter than its snapshot`);
|
|
12522
12565
|
bytes = Buffer.alloc(size - from);
|
|
12523
12566
|
let read = 0;
|
|
12524
12567
|
while (read < bytes.length) {
|
|
12525
|
-
const got =
|
|
12568
|
+
const got = fs10.readSync(fd, bytes, read, bytes.length - read, from + read);
|
|
12526
12569
|
if (got === 0)
|
|
12527
12570
|
break;
|
|
12528
12571
|
read += got;
|
|
@@ -12530,7 +12573,7 @@ function readLog(file, from = 0, expectSeq = null) {
|
|
|
12530
12573
|
bytes = bytes.subarray(0, read);
|
|
12531
12574
|
size = from + read;
|
|
12532
12575
|
} finally {
|
|
12533
|
-
|
|
12576
|
+
fs10.closeSync(fd);
|
|
12534
12577
|
}
|
|
12535
12578
|
const events = [];
|
|
12536
12579
|
let start = 0;
|
|
@@ -12555,11 +12598,11 @@ function readLog(file, from = 0, expectSeq = null) {
|
|
|
12555
12598
|
}
|
|
12556
12599
|
var fsyncDirectory = (dir) => {
|
|
12557
12600
|
try {
|
|
12558
|
-
const fd =
|
|
12601
|
+
const fd = fs10.openSync(dir, "r");
|
|
12559
12602
|
try {
|
|
12560
|
-
|
|
12603
|
+
fs10.fsyncSync(fd);
|
|
12561
12604
|
} finally {
|
|
12562
|
-
|
|
12605
|
+
fs10.closeSync(fd);
|
|
12563
12606
|
}
|
|
12564
12607
|
} catch (error) {
|
|
12565
12608
|
if (process.platform !== "win32")
|
|
@@ -12569,46 +12612,46 @@ var fsyncDirectory = (dir) => {
|
|
|
12569
12612
|
function appendEvent(file, event, validEnd) {
|
|
12570
12613
|
const line = Buffer.from(`${JSON.stringify(event)}
|
|
12571
12614
|
`, "utf8");
|
|
12572
|
-
const created = !
|
|
12573
|
-
if (!created &&
|
|
12574
|
-
const cut =
|
|
12615
|
+
const created = !fs10.existsSync(file);
|
|
12616
|
+
if (!created && fs10.statSync(file).size !== validEnd) {
|
|
12617
|
+
const cut = fs10.openSync(file, "r+");
|
|
12575
12618
|
try {
|
|
12576
|
-
|
|
12577
|
-
|
|
12619
|
+
fs10.ftruncateSync(cut, validEnd);
|
|
12620
|
+
fs10.fsyncSync(cut);
|
|
12578
12621
|
} finally {
|
|
12579
|
-
|
|
12622
|
+
fs10.closeSync(cut);
|
|
12580
12623
|
}
|
|
12581
12624
|
}
|
|
12582
|
-
const fd =
|
|
12625
|
+
const fd = fs10.openSync(file, "a", 384);
|
|
12583
12626
|
try {
|
|
12584
12627
|
let written = 0;
|
|
12585
12628
|
while (written < line.length)
|
|
12586
|
-
written +=
|
|
12587
|
-
|
|
12629
|
+
written += fs10.writeSync(fd, line, written, line.length - written);
|
|
12630
|
+
fs10.fsyncSync(fd);
|
|
12588
12631
|
} finally {
|
|
12589
|
-
|
|
12632
|
+
fs10.closeSync(fd);
|
|
12590
12633
|
}
|
|
12591
12634
|
if (created)
|
|
12592
|
-
fsyncDirectory(
|
|
12635
|
+
fsyncDirectory(path13.dirname(file));
|
|
12593
12636
|
return validEnd;
|
|
12594
12637
|
}
|
|
12595
12638
|
function replaceFile(file, bytes, durable) {
|
|
12596
12639
|
const temporary = `${file}.${process.pid}.${randomUUID2()}.tmp`;
|
|
12597
12640
|
try {
|
|
12598
|
-
const fd =
|
|
12641
|
+
const fd = fs10.openSync(temporary, "wx", 384);
|
|
12599
12642
|
try {
|
|
12600
|
-
|
|
12643
|
+
fs10.writeSync(fd, bytes);
|
|
12601
12644
|
if (durable)
|
|
12602
|
-
|
|
12645
|
+
fs10.fsyncSync(fd);
|
|
12603
12646
|
} finally {
|
|
12604
|
-
|
|
12647
|
+
fs10.closeSync(fd);
|
|
12605
12648
|
}
|
|
12606
12649
|
renameWithRetry(temporary, file);
|
|
12607
12650
|
if (durable)
|
|
12608
|
-
fsyncDirectory(
|
|
12651
|
+
fsyncDirectory(path13.dirname(file));
|
|
12609
12652
|
} catch (error) {
|
|
12610
12653
|
try {
|
|
12611
|
-
|
|
12654
|
+
fs10.unlinkSync(temporary);
|
|
12612
12655
|
} catch {}
|
|
12613
12656
|
throw error;
|
|
12614
12657
|
}
|
|
@@ -12633,7 +12676,7 @@ var retryTransient = (run) => {
|
|
|
12633
12676
|
}
|
|
12634
12677
|
}
|
|
12635
12678
|
};
|
|
12636
|
-
var renameWithRetry = (from, to) => retryTransient(() =>
|
|
12679
|
+
var renameWithRetry = (from, to) => retryTransient(() => fs10.renameSync(from, to));
|
|
12637
12680
|
|
|
12638
12681
|
// packages/workit-core/src/store/patch.ts
|
|
12639
12682
|
var isPlainObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -12746,7 +12789,7 @@ var isPatch = (value) => Array.isArray(value) && value.every((op) => {
|
|
|
12746
12789
|
});
|
|
12747
12790
|
|
|
12748
12791
|
// packages/workit-core/src/store/reduce.ts
|
|
12749
|
-
import { createHash as
|
|
12792
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
12750
12793
|
var OPENING_TYPES = new Set([
|
|
12751
12794
|
"task.opened",
|
|
12752
12795
|
"task.imported",
|
|
@@ -12775,7 +12818,7 @@ var isObject5 = (value) => typeof value === "object" && value !== null && !Array
|
|
|
12775
12818
|
var isKey = (value) => isObject5(value) && typeof value.key === "string" && (value.kind === "branch" || value.kind === "detached" || value.kind === "dir") && (value.branch === null || typeof value.branch === "string");
|
|
12776
12819
|
var BLOB_REF = "$blob";
|
|
12777
12820
|
var blobRef = (value) => isObject5(value) && Object.keys(value).length === 1 && typeof value[BLOB_REF] === "string" && /^[0-9a-f]{64}$/.test(value[BLOB_REF]) ? value[BLOB_REF] : null;
|
|
12778
|
-
var blobDigest = (value) =>
|
|
12821
|
+
var blobDigest = (value) => createHash8("sha256").update(JSON.stringify(value)).digest("hex");
|
|
12779
12822
|
var toBlob = (value, blobs) => {
|
|
12780
12823
|
if (!isObject5(value))
|
|
12781
12824
|
return value;
|
|
@@ -12930,7 +12973,7 @@ var indexEntry = (task, key, legacy, file) => {
|
|
|
12930
12973
|
};
|
|
12931
12974
|
var fileSignature = (file) => {
|
|
12932
12975
|
try {
|
|
12933
|
-
const stat =
|
|
12976
|
+
const stat = fs11.statSync(file, { bigint: true });
|
|
12934
12977
|
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
12935
12978
|
} catch {
|
|
12936
12979
|
return null;
|
|
@@ -12948,7 +12991,7 @@ var cachedRuntimeVersion = null;
|
|
|
12948
12991
|
var runtimeVersion = () => {
|
|
12949
12992
|
if (cachedRuntimeVersion === null) {
|
|
12950
12993
|
try {
|
|
12951
|
-
const pkg = JSON.parse(
|
|
12994
|
+
const pkg = JSON.parse(fs11.readFileSync(path14.join(packageRoot(), "package.json"), "utf8"));
|
|
12952
12995
|
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
12953
12996
|
} catch {
|
|
12954
12997
|
cachedRuntimeVersion = "0.0.0";
|
|
@@ -12974,14 +13017,14 @@ var isNewerVersion = (candidate, current) => {
|
|
|
12974
13017
|
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);
|
|
12975
13018
|
var readText = (file) => {
|
|
12976
13019
|
try {
|
|
12977
|
-
return
|
|
13020
|
+
return fs11.readFileSync(file, "utf8");
|
|
12978
13021
|
} catch {
|
|
12979
13022
|
return "";
|
|
12980
13023
|
}
|
|
12981
13024
|
};
|
|
12982
13025
|
var REFLOG_LINE = /^[0-9a-f]+ ([0-9a-f]+) .* (\d+) [+-]\d{4}\t(.*)$/u;
|
|
12983
13026
|
var branchStart = (location, branch, aliases) => {
|
|
12984
|
-
const first = readText(
|
|
13027
|
+
const first = readText(path14.join(path14.dirname(location.dir), "logs", "refs", "heads", branch)).split(`
|
|
12985
13028
|
`, 1)[0];
|
|
12986
13029
|
const created = REFLOG_LINE.exec(first);
|
|
12987
13030
|
const start = created && /^branch: Created from (\S+)$/u.exec(created[3])?.[1];
|
|
@@ -12991,7 +13034,7 @@ var branchStart = (location, branch, aliases) => {
|
|
|
12991
13034
|
return start;
|
|
12992
13035
|
if (!location.git)
|
|
12993
13036
|
return null;
|
|
12994
|
-
for (const line of readText(
|
|
13037
|
+
for (const line of readText(path14.join(location.git.gitDir, "logs", "HEAD")).split(`
|
|
12995
13038
|
`)) {
|
|
12996
13039
|
const entry = REFLOG_LINE.exec(line);
|
|
12997
13040
|
const moved = entry && /^checkout: moving from (\S+) to (\S+)$/u.exec(entry[3]);
|
|
@@ -13001,23 +13044,23 @@ var branchStart = (location, branch, aliases) => {
|
|
|
13001
13044
|
}
|
|
13002
13045
|
return null;
|
|
13003
13046
|
};
|
|
13004
|
-
var refExists = (common, ref) =>
|
|
13047
|
+
var refExists = (common, ref) => fs11.existsSync(path14.join(common, ref)) || readText(path14.join(common, "packed-refs")).includes(` ${ref}
|
|
13005
13048
|
`);
|
|
13006
13049
|
var origins = new Map;
|
|
13007
13050
|
var sameDirectoryIdentity = (left, right) => {
|
|
13008
|
-
if (!
|
|
13051
|
+
if (!path14.isAbsolute(left) || !path14.isAbsolute(right))
|
|
13009
13052
|
return false;
|
|
13010
|
-
const normalizedLeft =
|
|
13011
|
-
const normalizedRight =
|
|
13053
|
+
const normalizedLeft = path14.resolve(left);
|
|
13054
|
+
const normalizedRight = path14.resolve(right);
|
|
13012
13055
|
try {
|
|
13013
|
-
const a =
|
|
13014
|
-
const b =
|
|
13056
|
+
const a = fs11.statSync(normalizedLeft, { bigint: true });
|
|
13057
|
+
const b = fs11.statSync(normalizedRight, { bigint: true });
|
|
13015
13058
|
if (!a.isDirectory() || !b.isDirectory())
|
|
13016
13059
|
return false;
|
|
13017
13060
|
if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
|
|
13018
13061
|
return true;
|
|
13019
|
-
const canonicalLeft =
|
|
13020
|
-
const canonicalRight =
|
|
13062
|
+
const canonicalLeft = fs11.realpathSync(normalizedLeft);
|
|
13063
|
+
const canonicalRight = fs11.realpathSync(normalizedRight);
|
|
13021
13064
|
if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
|
|
13022
13065
|
return true;
|
|
13023
13066
|
return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
|
|
@@ -13054,8 +13097,8 @@ class TaskStore {
|
|
|
13054
13097
|
migrationChecked = false;
|
|
13055
13098
|
compactDue = new Set;
|
|
13056
13099
|
constructor(root, options = {}) {
|
|
13057
|
-
const resolved =
|
|
13058
|
-
this.origin =
|
|
13100
|
+
const resolved = path14.resolve(root);
|
|
13101
|
+
this.origin = fs11.existsSync(resolved) ? fs11.realpathSync(resolved) : resolved;
|
|
13059
13102
|
this.located = resolveStore(this.origin);
|
|
13060
13103
|
this.root = checkoutRootOf(this.origin, this.located);
|
|
13061
13104
|
this.lockTimeoutMs = options.lockTimeoutMs ?? defaultLockTimeout();
|
|
@@ -13063,7 +13106,7 @@ class TaskStore {
|
|
|
13063
13106
|
this.migrateOnRead = options.migrateOnRead;
|
|
13064
13107
|
}
|
|
13065
13108
|
location() {
|
|
13066
|
-
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared &&
|
|
13109
|
+
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs11.existsSync(path14.join(this.root, ".git"))) {
|
|
13067
13110
|
this.located = undefined;
|
|
13068
13111
|
this.migrationChecked = false;
|
|
13069
13112
|
}
|
|
@@ -13127,7 +13170,7 @@ class TaskStore {
|
|
|
13127
13170
|
const tasksDir = this.paths().tasks;
|
|
13128
13171
|
let names;
|
|
13129
13172
|
try {
|
|
13130
|
-
names =
|
|
13173
|
+
names = fs11.existsSync(tasksDir) ? fs11.readdirSync(tasksDir).filter((name) => validId(name)) : [];
|
|
13131
13174
|
} catch (error) {
|
|
13132
13175
|
return failure2("storage_error", `unable to list tasks: ${String(error)}`, {
|
|
13133
13176
|
path: tasksDir
|
|
@@ -13168,7 +13211,7 @@ class TaskStore {
|
|
|
13168
13211
|
const file = this.paths().workspace;
|
|
13169
13212
|
let bytes;
|
|
13170
13213
|
try {
|
|
13171
|
-
bytes = retryTransient(() =>
|
|
13214
|
+
bytes = retryTransient(() => fs11.readFileSync(file, "utf8"));
|
|
13172
13215
|
} catch (error) {
|
|
13173
13216
|
if (error.code === "ENOENT")
|
|
13174
13217
|
return success(null, null, null);
|
|
@@ -13219,7 +13262,7 @@ class TaskStore {
|
|
|
13219
13262
|
const moved = this.bindLocked(again.data.id, key, input.now);
|
|
13220
13263
|
return moved.ok ? success(moved.data.revision, null, { task: moved.data, created: false, key }) : moved;
|
|
13221
13264
|
}
|
|
13222
|
-
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" :
|
|
13265
|
+
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" : path14.basename(this.root) || this.root;
|
|
13223
13266
|
const intent = {
|
|
13224
13267
|
objective: input.objective?.trim() || `Work on ${label}`,
|
|
13225
13268
|
scope: { description: `the checkout (${label})`, paths: ["."], exclusions: [] },
|
|
@@ -13279,14 +13322,14 @@ class TaskStore {
|
|
|
13279
13322
|
const branch = key.ok ? key.data.branch : null;
|
|
13280
13323
|
if (!branch || !location.ok)
|
|
13281
13324
|
return success(null, null, null);
|
|
13282
|
-
const common =
|
|
13283
|
-
const heads =
|
|
13284
|
-
const headLog = location.data.git ?
|
|
13325
|
+
const common = path14.dirname(location.data.dir);
|
|
13326
|
+
const heads = path14.join(common, "logs", "refs", "heads");
|
|
13327
|
+
const headLog = location.data.git ? path14.join(location.data.git.gitDir, "logs", "HEAD") : "";
|
|
13285
13328
|
const signatures = [
|
|
13286
|
-
|
|
13329
|
+
path14.join(heads, branch),
|
|
13287
13330
|
headLog,
|
|
13288
13331
|
heads,
|
|
13289
|
-
|
|
13332
|
+
path14.join(common, "packed-refs")
|
|
13290
13333
|
].map((file) => fileSignature(file) ?? "-");
|
|
13291
13334
|
const slot = `${location.data.dir}\x00${headLog}\x00${branch}`;
|
|
13292
13335
|
let resolved = origins.get(slot);
|
|
@@ -13298,7 +13341,7 @@ class TaskStore {
|
|
|
13298
13341
|
aliases.push(rename.from);
|
|
13299
13342
|
const start = branchStart(location.data, branch, aliases);
|
|
13300
13343
|
const slash = start?.indexOf("/") ?? -1;
|
|
13301
|
-
const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(
|
|
13344
|
+
const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(path14.join(common, "config")).includes(`[remote "${start.slice(0, slash)}"]`));
|
|
13302
13345
|
const name = start && (remote ? start.slice(slash + 1) : start);
|
|
13303
13346
|
resolved = {
|
|
13304
13347
|
signature: signatures.join("|"),
|
|
@@ -13536,13 +13579,13 @@ class TaskStore {
|
|
|
13536
13579
|
if (!ready.ok)
|
|
13537
13580
|
return ready;
|
|
13538
13581
|
const dirs = this.paths();
|
|
13539
|
-
const recovery =
|
|
13540
|
-
if (
|
|
13582
|
+
const recovery = path14.join(this.root, ".workit", "recovery");
|
|
13583
|
+
if (fs11.existsSync(recovery)) {
|
|
13541
13584
|
let files = 0;
|
|
13542
13585
|
let bytes = 0;
|
|
13543
13586
|
try {
|
|
13544
|
-
for (const name of
|
|
13545
|
-
const stat =
|
|
13587
|
+
for (const name of fs11.readdirSync(recovery)) {
|
|
13588
|
+
const stat = fs11.lstatSync(path14.join(recovery, name));
|
|
13546
13589
|
if (!stat.isFile())
|
|
13547
13590
|
continue;
|
|
13548
13591
|
files += 1;
|
|
@@ -13551,17 +13594,17 @@ class TaskStore {
|
|
|
13551
13594
|
} catch {}
|
|
13552
13595
|
const remove = options.pruneRecovery === true && !dryRun;
|
|
13553
13596
|
if (remove)
|
|
13554
|
-
|
|
13597
|
+
fs11.rmSync(recovery, { recursive: true, force: true });
|
|
13555
13598
|
report.legacyRecovery = { path: recovery, files, bytes, removed: remove };
|
|
13556
13599
|
}
|
|
13557
|
-
if (!
|
|
13600
|
+
if (!fs11.existsSync(dirs.tasks))
|
|
13558
13601
|
return success(null, null, report);
|
|
13559
|
-
const names =
|
|
13602
|
+
const names = fs11.readdirSync(dirs.tasks).filter((name) => validId(name));
|
|
13560
13603
|
for (const name of names) {
|
|
13561
13604
|
const file = this.eventsPath(name);
|
|
13562
13605
|
let size = 0;
|
|
13563
13606
|
try {
|
|
13564
|
-
size =
|
|
13607
|
+
size = fs11.statSync(file).size;
|
|
13565
13608
|
} catch {
|
|
13566
13609
|
continue;
|
|
13567
13610
|
}
|
|
@@ -13589,16 +13632,16 @@ class TaskStore {
|
|
|
13589
13632
|
const references = new Set;
|
|
13590
13633
|
for (const name of names)
|
|
13591
13634
|
try {
|
|
13592
|
-
for (const match of
|
|
13635
|
+
for (const match of fs11.readFileSync(this.eventsPath(name), "utf8").matchAll(/"\$blob":"([0-9a-f]{64})"/g))
|
|
13593
13636
|
references.add(match[1]);
|
|
13594
13637
|
} catch {}
|
|
13595
|
-
if (
|
|
13596
|
-
for (const name of
|
|
13638
|
+
if (fs11.existsSync(dirs.blobs))
|
|
13639
|
+
for (const name of fs11.readdirSync(dirs.blobs)) {
|
|
13597
13640
|
const digest = name.replace(/\.json$/, "");
|
|
13598
|
-
const file =
|
|
13641
|
+
const file = path14.join(dirs.blobs, name);
|
|
13599
13642
|
let stat;
|
|
13600
13643
|
try {
|
|
13601
|
-
stat =
|
|
13644
|
+
stat = fs11.lstatSync(file);
|
|
13602
13645
|
} catch {
|
|
13603
13646
|
continue;
|
|
13604
13647
|
}
|
|
@@ -13609,11 +13652,11 @@ class TaskStore {
|
|
|
13609
13652
|
report.blobs.removed += 1;
|
|
13610
13653
|
report.blobs.removedBytes += stat.size;
|
|
13611
13654
|
if (!dryRun)
|
|
13612
|
-
|
|
13655
|
+
fs11.rmSync(file, { force: true });
|
|
13613
13656
|
}
|
|
13614
13657
|
return success(null, null, null);
|
|
13615
13658
|
};
|
|
13616
|
-
const swept = dryRun ? sweep() : this.withLock(
|
|
13659
|
+
const swept = dryRun ? sweep() : this.withLock(path14.join(dirs.dir, "gc.lock"), sweep);
|
|
13617
13660
|
if (!swept.ok)
|
|
13618
13661
|
return swept;
|
|
13619
13662
|
const temporaryDirs = [
|
|
@@ -13621,17 +13664,17 @@ class TaskStore {
|
|
|
13621
13664
|
dirs.tasks,
|
|
13622
13665
|
dirs.blobs,
|
|
13623
13666
|
dirs.checkout,
|
|
13624
|
-
...names.map((name) =>
|
|
13667
|
+
...names.map((name) => path14.join(dirs.tasks, name))
|
|
13625
13668
|
];
|
|
13626
13669
|
for (const directory of temporaryDirs) {
|
|
13627
|
-
if (!
|
|
13670
|
+
if (!fs11.existsSync(directory))
|
|
13628
13671
|
continue;
|
|
13629
|
-
for (const name of
|
|
13672
|
+
for (const name of fs11.readdirSync(directory)) {
|
|
13630
13673
|
if (!name.endsWith(".tmp") && !name.endsWith(".probe"))
|
|
13631
13674
|
continue;
|
|
13632
|
-
const file =
|
|
13675
|
+
const file = path14.join(directory, name);
|
|
13633
13676
|
try {
|
|
13634
|
-
const stat =
|
|
13677
|
+
const stat = fs11.lstatSync(file);
|
|
13635
13678
|
if (!stat.isFile() || nowMs - stat.mtimeMs <= STALE_TEMPORARY_MS)
|
|
13636
13679
|
continue;
|
|
13637
13680
|
} catch {
|
|
@@ -13639,37 +13682,37 @@ class TaskStore {
|
|
|
13639
13682
|
}
|
|
13640
13683
|
report.temporary.removed += 1;
|
|
13641
13684
|
if (!dryRun)
|
|
13642
|
-
|
|
13685
|
+
fs11.rmSync(file, { force: true });
|
|
13643
13686
|
}
|
|
13644
13687
|
}
|
|
13645
13688
|
return success(null, null, report);
|
|
13646
13689
|
}
|
|
13647
13690
|
paths() {
|
|
13648
13691
|
const location = this.location();
|
|
13649
|
-
const dir = location.ok ? location.data.dir :
|
|
13650
|
-
const checkout =
|
|
13692
|
+
const dir = location.ok ? location.data.dir : path14.join(this.root, ".workit");
|
|
13693
|
+
const checkout = path14.join(dir, "checkouts", checkoutSlug(this.root));
|
|
13651
13694
|
return {
|
|
13652
13695
|
dir,
|
|
13653
13696
|
shared: location.ok ? location.data.shared : false,
|
|
13654
|
-
marker:
|
|
13697
|
+
marker: path14.join(dir, "store.json"),
|
|
13655
13698
|
checkout,
|
|
13656
|
-
workspace:
|
|
13657
|
-
checkoutLock:
|
|
13658
|
-
tasks:
|
|
13659
|
-
index:
|
|
13660
|
-
blobs:
|
|
13661
|
-
legacy:
|
|
13662
|
-
v2:
|
|
13699
|
+
workspace: path14.join(checkout, "workspace.json"),
|
|
13700
|
+
checkoutLock: path14.join(checkout, "metadata.lock"),
|
|
13701
|
+
tasks: path14.join(dir, "tasks"),
|
|
13702
|
+
index: path14.join(dir, "task-index.json"),
|
|
13703
|
+
blobs: path14.join(dir, "blobs", "candidates"),
|
|
13704
|
+
legacy: path14.join(dir, "legacy", checkoutSlug(this.root), "v2"),
|
|
13705
|
+
v2: path14.join(this.root, ".workit")
|
|
13663
13706
|
};
|
|
13664
13707
|
}
|
|
13665
13708
|
eventsPath(taskId) {
|
|
13666
|
-
return
|
|
13709
|
+
return path14.join(this.paths().tasks, taskId, "events.jsonl");
|
|
13667
13710
|
}
|
|
13668
13711
|
snapshotPath(taskId) {
|
|
13669
|
-
return
|
|
13712
|
+
return path14.join(this.paths().tasks, taskId, "snapshot.json");
|
|
13670
13713
|
}
|
|
13671
13714
|
taskLockPath(taskId) {
|
|
13672
|
-
return
|
|
13715
|
+
return path14.join(this.paths().tasks, taskId, "lock");
|
|
13673
13716
|
}
|
|
13674
13717
|
ready(write = false) {
|
|
13675
13718
|
const location = this.location();
|
|
@@ -13679,7 +13722,7 @@ class TaskStore {
|
|
|
13679
13722
|
return success(null, null, null);
|
|
13680
13723
|
const dirs = this.paths();
|
|
13681
13724
|
try {
|
|
13682
|
-
const marker = JSON.parse(
|
|
13725
|
+
const marker = JSON.parse(fs11.readFileSync(dirs.marker, "utf8"));
|
|
13683
13726
|
if (typeof marker.version === "number" && marker.version > STORE_FORMAT)
|
|
13684
13727
|
return failure2("recovery_required", `the workit store at ${dirs.dir} has format ${marker.version}; upgrade Workit to use it`, { path: dirs.marker });
|
|
13685
13728
|
} catch {}
|
|
@@ -13708,21 +13751,21 @@ class TaskStore {
|
|
|
13708
13751
|
return {
|
|
13709
13752
|
read: (digest) => {
|
|
13710
13753
|
try {
|
|
13711
|
-
return JSON.parse(
|
|
13754
|
+
return JSON.parse(fs11.readFileSync(path14.join(dir, `${digest}.json`), "utf8"));
|
|
13712
13755
|
} catch (error) {
|
|
13713
13756
|
throw new LogDamage(`stored candidate ${digest} is missing or unreadable: ${String(error)}`);
|
|
13714
13757
|
}
|
|
13715
13758
|
},
|
|
13716
13759
|
write: (digest, value) => {
|
|
13717
|
-
const file =
|
|
13718
|
-
if (
|
|
13760
|
+
const file = path14.join(dir, `${digest}.json`);
|
|
13761
|
+
if (fs11.existsSync(file)) {
|
|
13719
13762
|
const stamp = new Date;
|
|
13720
13763
|
try {
|
|
13721
|
-
|
|
13764
|
+
fs11.utimesSync(file, stamp, stamp);
|
|
13722
13765
|
return;
|
|
13723
13766
|
} catch {}
|
|
13724
13767
|
}
|
|
13725
|
-
|
|
13768
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
13726
13769
|
replaceFile(file, JSON.stringify(value), true);
|
|
13727
13770
|
}
|
|
13728
13771
|
};
|
|
@@ -13804,7 +13847,7 @@ class TaskStore {
|
|
|
13804
13847
|
}
|
|
13805
13848
|
readSnapshot(taskId) {
|
|
13806
13849
|
try {
|
|
13807
|
-
const value = JSON.parse(
|
|
13850
|
+
const value = JSON.parse(fs11.readFileSync(this.snapshotPath(taskId), "utf8"));
|
|
13808
13851
|
if (!isObject6(value) || value.v !== 1 || value.task !== taskId || typeof value.seq !== "number" || typeof value.lastId !== "string" || typeof value.lastStart !== "number" || typeof value.end !== "number" || !isObject6(value.record))
|
|
13809
13852
|
return null;
|
|
13810
13853
|
return value;
|
|
@@ -13906,8 +13949,8 @@ class TaskStore {
|
|
|
13906
13949
|
}
|
|
13907
13950
|
}
|
|
13908
13951
|
openTask(record, type, key, legacy = null) {
|
|
13909
|
-
const dir =
|
|
13910
|
-
|
|
13952
|
+
const dir = path14.join(this.paths().tasks, record.id);
|
|
13953
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
13911
13954
|
const blobs = this.blobStore();
|
|
13912
13955
|
const bound = key ? { ...key, boundAt: key.boundAt ?? new Date().toISOString() } : null;
|
|
13913
13956
|
const event = this.event(record.id, 1, type, {
|
|
@@ -13959,11 +14002,11 @@ class TaskStore {
|
|
|
13959
14002
|
} catch {}
|
|
13960
14003
|
if (!latest || latest.events[0]?.id !== last.id)
|
|
13961
14004
|
return failure2("busy", "the task log changed during compaction; retry", { taskId });
|
|
13962
|
-
const appended =
|
|
14005
|
+
const appended = fs11.readFileSync(file).subarray(log.end, latest.end);
|
|
13963
14006
|
const bytes = Buffer.concat([Buffer.from(prefix, "utf8"), appended]);
|
|
13964
14007
|
replaceFile(file, bytes.toString("utf8"), true);
|
|
13965
14008
|
try {
|
|
13966
|
-
|
|
14009
|
+
fs11.rmSync(this.snapshotPath(taskId), { force: true });
|
|
13967
14010
|
} catch {}
|
|
13968
14011
|
const reloaded = this.load(taskId);
|
|
13969
14012
|
if (reloaded.ok && reloaded.data)
|
|
@@ -13976,7 +14019,7 @@ class TaskStore {
|
|
|
13976
14019
|
}
|
|
13977
14020
|
countEvents(file) {
|
|
13978
14021
|
try {
|
|
13979
|
-
const bytes =
|
|
14022
|
+
const bytes = fs11.readFileSync(file);
|
|
13980
14023
|
let count = 0;
|
|
13981
14024
|
for (let index = bytes.indexOf(10);index >= 0; index = bytes.indexOf(10, index + 1))
|
|
13982
14025
|
count += 1;
|
|
@@ -13998,8 +14041,8 @@ class TaskStore {
|
|
|
13998
14041
|
return bound.ok ? success(null, null, bound.data[0] ?? null) : bound;
|
|
13999
14042
|
}
|
|
14000
14043
|
withKeyLock(key, operation) {
|
|
14001
|
-
const digest =
|
|
14002
|
-
return this.withLock(
|
|
14044
|
+
const digest = createHash9("sha256").update(key).digest("hex").slice(0, 32);
|
|
14045
|
+
return this.withLock(path14.join(this.paths().dir, "keys", `${digest}.lock`), operation);
|
|
14003
14046
|
}
|
|
14004
14047
|
bindLocked(taskId, key, timestamp) {
|
|
14005
14048
|
const current = this.readWorkspace();
|
|
@@ -14140,7 +14183,7 @@ class TaskStore {
|
|
|
14140
14183
|
writeWorkspace(record) {
|
|
14141
14184
|
const file = this.paths().workspace;
|
|
14142
14185
|
try {
|
|
14143
|
-
|
|
14186
|
+
fs11.mkdirSync(path14.dirname(file), { recursive: true });
|
|
14144
14187
|
replaceFile(file, `${canonicalJson(record)}
|
|
14145
14188
|
`, true);
|
|
14146
14189
|
return success(null, null, null);
|
|
@@ -14150,7 +14193,7 @@ class TaskStore {
|
|
|
14150
14193
|
}
|
|
14151
14194
|
readIndex() {
|
|
14152
14195
|
try {
|
|
14153
|
-
const value = JSON.parse(
|
|
14196
|
+
const value = JSON.parse(fs11.readFileSync(this.paths().index, "utf8"));
|
|
14154
14197
|
if (!isObject6(value) || value.version !== INDEX_VERSION || !isObject6(value.tasks))
|
|
14155
14198
|
return {};
|
|
14156
14199
|
const tasks = {};
|
|
@@ -14164,7 +14207,7 @@ class TaskStore {
|
|
|
14164
14207
|
}
|
|
14165
14208
|
writeIndex(entries) {
|
|
14166
14209
|
try {
|
|
14167
|
-
|
|
14210
|
+
fs11.mkdirSync(this.paths().tasks, { recursive: true });
|
|
14168
14211
|
replaceFile(this.paths().index, JSON.stringify({
|
|
14169
14212
|
version: INDEX_VERSION,
|
|
14170
14213
|
tasks: Object.fromEntries(entries.map((entry) => [entry.id, entry]))
|
|
@@ -14218,23 +14261,23 @@ class TaskStore {
|
|
|
14218
14261
|
return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
|
|
14219
14262
|
}
|
|
14220
14263
|
v2Dirs() {
|
|
14221
|
-
return [...new Set([this.root, this.origin])].map((dir) =>
|
|
14264
|
+
return [...new Set([this.root, this.origin])].map((dir) => path14.join(dir, ".workit"));
|
|
14222
14265
|
}
|
|
14223
14266
|
pendingMigrations() {
|
|
14224
14267
|
return this.v2Dirs().filter((dir) => {
|
|
14225
|
-
const raw = this.readV2Workspace(
|
|
14268
|
+
const raw = this.readV2Workspace(path14.join(dir, "workspace.json"));
|
|
14226
14269
|
return raw !== null && raw !== TOMBSTONE;
|
|
14227
14270
|
});
|
|
14228
14271
|
}
|
|
14229
14272
|
migrateV2(v2) {
|
|
14230
14273
|
const dirs = this.paths();
|
|
14231
|
-
const v2Workspace =
|
|
14232
|
-
const v2Tasks =
|
|
14233
|
-
const legacy =
|
|
14274
|
+
const v2Workspace = path14.join(v2, "workspace.json");
|
|
14275
|
+
const v2Tasks = path14.join(v2, "tasks");
|
|
14276
|
+
const legacy = path14.join(dirs.dir, "legacy", checkoutSlug(path14.dirname(v2)), "v2");
|
|
14234
14277
|
const raw = this.readV2Workspace(v2Workspace);
|
|
14235
14278
|
if (raw === null || raw === TOMBSTONE)
|
|
14236
14279
|
return success(null, null, null);
|
|
14237
|
-
return this.withLock(dirs.checkoutLock, () => this.withLock(
|
|
14280
|
+
return this.withLock(dirs.checkoutLock, () => this.withLock(path14.join(v2, "metadata.lock"), () => {
|
|
14238
14281
|
const workspace = this.readV2Workspace(v2Workspace);
|
|
14239
14282
|
if (workspace === null || workspace === TOMBSTONE)
|
|
14240
14283
|
return success(null, null, null);
|
|
@@ -14243,7 +14286,7 @@ class TaskStore {
|
|
|
14243
14286
|
return failure2("recovery_required", `cannot migrate the 2.x store at ${v2}: ${parsedWorkspace.error}; move it aside to start fresh`, { path: v2Workspace });
|
|
14244
14287
|
let files = [];
|
|
14245
14288
|
try {
|
|
14246
|
-
files =
|
|
14289
|
+
files = fs11.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(path14.join(v2Tasks, name)));
|
|
14247
14290
|
} catch {}
|
|
14248
14291
|
const current = this.readWorkspace();
|
|
14249
14292
|
if (!current.ok)
|
|
@@ -14261,22 +14304,22 @@ class TaskStore {
|
|
|
14261
14304
|
let skipped = 0;
|
|
14262
14305
|
const done = new Map;
|
|
14263
14306
|
try {
|
|
14264
|
-
|
|
14307
|
+
fs11.mkdirSync(path14.join(legacy, "tasks"), { recursive: true });
|
|
14265
14308
|
for (const name of ["workspace.json", "index.json"])
|
|
14266
|
-
if (
|
|
14267
|
-
|
|
14309
|
+
if (fs11.existsSync(path14.join(v2, name)))
|
|
14310
|
+
fs11.copyFileSync(path14.join(v2, name), path14.join(legacy, name));
|
|
14268
14311
|
for (const name of files) {
|
|
14269
14312
|
const id = name.slice(0, -5);
|
|
14270
|
-
const file =
|
|
14271
|
-
const bytes =
|
|
14313
|
+
const file = path14.join(v2Tasks, name);
|
|
14314
|
+
const bytes = fs11.readFileSync(file, "utf8");
|
|
14272
14315
|
const digest = sha256(bytes);
|
|
14273
|
-
|
|
14316
|
+
fs11.writeFileSync(path14.join(legacy, "tasks", name), bytes);
|
|
14274
14317
|
const parsed = this.parseRecord(bytes, taskRecordSchema, "2.x task");
|
|
14275
14318
|
if (!parsed.ok || parsed.data.id !== id)
|
|
14276
14319
|
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 });
|
|
14277
14320
|
const record = { ...parsed.data, workspaceId: target.id };
|
|
14278
|
-
const origin = { path:
|
|
14279
|
-
if (!
|
|
14321
|
+
const origin = { path: path14.join(legacy, "tasks", name), digest };
|
|
14322
|
+
if (!fs11.existsSync(this.eventsPath(id))) {
|
|
14280
14323
|
this.openTask(record, "migrated.from_v2", null, origin);
|
|
14281
14324
|
migrated += 1;
|
|
14282
14325
|
} else {
|
|
@@ -14311,8 +14354,8 @@ class TaskStore {
|
|
|
14311
14354
|
try {
|
|
14312
14355
|
let complete = true;
|
|
14313
14356
|
for (const [name, digest] of done) {
|
|
14314
|
-
const file =
|
|
14315
|
-
if (sha256(
|
|
14357
|
+
const file = path14.join(v2Tasks, name);
|
|
14358
|
+
if (sha256(fs11.readFileSync(file, "utf8")) !== digest) {
|
|
14316
14359
|
complete = false;
|
|
14317
14360
|
continue;
|
|
14318
14361
|
}
|
|
@@ -14322,7 +14365,7 @@ class TaskStore {
|
|
|
14322
14365
|
return failure2("busy", `the 2.x store at ${v2} changed during migration; retry`, {
|
|
14323
14366
|
path: v2
|
|
14324
14367
|
});
|
|
14325
|
-
|
|
14368
|
+
fs11.rmSync(path14.join(v2, "index.json"), { force: true });
|
|
14326
14369
|
this.writeTombstone(v2, target.id);
|
|
14327
14370
|
} catch (error) {
|
|
14328
14371
|
return failure2("storage_error", `cannot finish the 2.x migration: ${String(error)}`, {
|
|
@@ -14342,45 +14385,45 @@ class TaskStore {
|
|
|
14342
14385
|
}
|
|
14343
14386
|
adoptLocalStore() {
|
|
14344
14387
|
const dirs = this.paths();
|
|
14345
|
-
const marker =
|
|
14346
|
-
if (!dirs.shared || !
|
|
14388
|
+
const marker = path14.join(dirs.v2, "store.json");
|
|
14389
|
+
if (!dirs.shared || !fs11.existsSync(marker))
|
|
14347
14390
|
return success(null, null, null);
|
|
14348
14391
|
return this.withLock(dirs.checkoutLock, () => {
|
|
14349
|
-
if (!
|
|
14392
|
+
if (!fs11.existsSync(marker))
|
|
14350
14393
|
return success(null, null, null);
|
|
14351
14394
|
let tasks = 0;
|
|
14352
14395
|
try {
|
|
14353
14396
|
const merge = (from, to) => {
|
|
14354
|
-
if (!
|
|
14355
|
-
|
|
14397
|
+
if (!fs11.existsSync(to)) {
|
|
14398
|
+
fs11.mkdirSync(path14.dirname(to), { recursive: true });
|
|
14356
14399
|
try {
|
|
14357
|
-
|
|
14400
|
+
fs11.renameSync(from, to);
|
|
14358
14401
|
} catch {
|
|
14359
|
-
|
|
14360
|
-
|
|
14402
|
+
fs11.cpSync(from, to, { recursive: true, errorOnExist: true, force: false });
|
|
14403
|
+
fs11.rmSync(from, { recursive: true, force: true });
|
|
14361
14404
|
}
|
|
14362
14405
|
return;
|
|
14363
14406
|
}
|
|
14364
|
-
if (
|
|
14365
|
-
for (const name of
|
|
14366
|
-
merge(
|
|
14407
|
+
if (fs11.lstatSync(from).isDirectory() && fs11.lstatSync(to).isDirectory())
|
|
14408
|
+
for (const name of fs11.readdirSync(from))
|
|
14409
|
+
merge(path14.join(from, name), path14.join(to, name));
|
|
14367
14410
|
};
|
|
14368
14411
|
for (const sub of ["tasks", "checkouts", "blobs", "legacy"]) {
|
|
14369
|
-
const from =
|
|
14370
|
-
if (!
|
|
14412
|
+
const from = path14.join(dirs.v2, sub);
|
|
14413
|
+
if (!fs11.existsSync(from))
|
|
14371
14414
|
continue;
|
|
14372
14415
|
if (sub === "tasks") {
|
|
14373
|
-
for (const name of
|
|
14374
|
-
if (validId(name) && !
|
|
14416
|
+
for (const name of fs11.readdirSync(from))
|
|
14417
|
+
if (validId(name) && !fs11.existsSync(path14.join(dirs.dir, sub, name)))
|
|
14375
14418
|
tasks += 1;
|
|
14376
14419
|
}
|
|
14377
|
-
merge(from,
|
|
14420
|
+
merge(from, path14.join(dirs.dir, sub));
|
|
14378
14421
|
}
|
|
14379
|
-
const ledger =
|
|
14380
|
-
if (
|
|
14381
|
-
merge(ledger,
|
|
14382
|
-
|
|
14383
|
-
|
|
14422
|
+
const ledger = path14.join(dirs.v2, "ledger");
|
|
14423
|
+
if (fs11.existsSync(ledger) && !fs11.existsSync(path14.join(dirs.dir, "ledger")))
|
|
14424
|
+
merge(ledger, path14.join(dirs.dir, "ledger"));
|
|
14425
|
+
fs11.rmSync(path14.join(dirs.v2, "task-index.json"), { force: true });
|
|
14426
|
+
fs11.renameSync(marker, path14.join(dirs.v2, "store.moved.json"));
|
|
14384
14427
|
} catch (error) {
|
|
14385
14428
|
return failure2("storage_error", `cannot move ${dirs.v2} into ${dirs.dir}: ${String(error)}`, {
|
|
14386
14429
|
path: dirs.v2
|
|
@@ -14400,7 +14443,7 @@ class TaskStore {
|
|
|
14400
14443
|
readV2Workspace(file) {
|
|
14401
14444
|
let bytes;
|
|
14402
14445
|
try {
|
|
14403
|
-
bytes =
|
|
14446
|
+
bytes = fs11.readFileSync(file, "utf8");
|
|
14404
14447
|
} catch {
|
|
14405
14448
|
return null;
|
|
14406
14449
|
}
|
|
@@ -14418,7 +14461,7 @@ class TaskStore {
|
|
|
14418
14461
|
schemaVersion: SCHEMA_VERSION,
|
|
14419
14462
|
id: workspaceId ?? newId(),
|
|
14420
14463
|
revision: newRevision(),
|
|
14421
|
-
root:
|
|
14464
|
+
root: path14.dirname(v2),
|
|
14422
14465
|
runtime: { createdWith: version, updatedWith: version },
|
|
14423
14466
|
writer: null,
|
|
14424
14467
|
store: {
|
|
@@ -14429,12 +14472,12 @@ class TaskStore {
|
|
|
14429
14472
|
},
|
|
14430
14473
|
critical: ["store"]
|
|
14431
14474
|
};
|
|
14432
|
-
|
|
14433
|
-
const ignore =
|
|
14434
|
-
if (!
|
|
14435
|
-
|
|
14475
|
+
fs11.mkdirSync(v2, { recursive: true });
|
|
14476
|
+
const ignore = path14.join(v2, ".gitignore");
|
|
14477
|
+
if (!fs11.existsSync(ignore))
|
|
14478
|
+
fs11.writeFileSync(ignore, `*
|
|
14436
14479
|
`);
|
|
14437
|
-
replaceFile(
|
|
14480
|
+
replaceFile(path14.join(v2, "workspace.json"), `${JSON.stringify(marker, null, 2)}
|
|
14438
14481
|
`, true);
|
|
14439
14482
|
}
|
|
14440
14483
|
stubFor(taskId) {
|
|
@@ -14450,7 +14493,7 @@ class TaskStore {
|
|
|
14450
14493
|
}
|
|
14451
14494
|
isStub(file) {
|
|
14452
14495
|
try {
|
|
14453
|
-
const value = JSON.parse(
|
|
14496
|
+
const value = JSON.parse(fs11.readFileSync(file, "utf8"));
|
|
14454
14497
|
return isObject6(value) && isObject6(value.store) && value.store.format === "workit-store";
|
|
14455
14498
|
} catch {
|
|
14456
14499
|
return false;
|
|
@@ -14458,32 +14501,32 @@ class TaskStore {
|
|
|
14458
14501
|
}
|
|
14459
14502
|
writeTombstoneIfMissing() {
|
|
14460
14503
|
try {
|
|
14461
|
-
const v2 =
|
|
14462
|
-
if (
|
|
14504
|
+
const v2 = path14.join(this.root, ".workit");
|
|
14505
|
+
if (fs11.existsSync(path14.join(v2, "workspace.json")))
|
|
14463
14506
|
return;
|
|
14464
14507
|
let id = null;
|
|
14465
14508
|
try {
|
|
14466
|
-
id = JSON.parse(
|
|
14509
|
+
id = JSON.parse(fs11.readFileSync(this.paths().workspace, "utf8")).id ?? null;
|
|
14467
14510
|
} catch {}
|
|
14468
14511
|
this.writeTombstone(v2, id);
|
|
14469
14512
|
} catch {}
|
|
14470
14513
|
}
|
|
14471
14514
|
initializeStorage() {
|
|
14472
14515
|
const dirs = this.paths();
|
|
14473
|
-
retryTransient(() =>
|
|
14474
|
-
retryTransient(() =>
|
|
14475
|
-
if (!
|
|
14516
|
+
retryTransient(() => fs11.mkdirSync(dirs.checkout, { recursive: true }));
|
|
14517
|
+
retryTransient(() => fs11.mkdirSync(dirs.tasks, { recursive: true }));
|
|
14518
|
+
if (!fs11.existsSync(dirs.marker))
|
|
14476
14519
|
replaceFile(dirs.marker, `${JSON.stringify({ format: "workit-store", version: STORE_FORMAT, createdWith: runtimeVersion() })}
|
|
14477
14520
|
`, false);
|
|
14478
14521
|
if (!dirs.shared) {
|
|
14479
|
-
const ignore =
|
|
14522
|
+
const ignore = path14.join(dirs.dir, ".gitignore");
|
|
14480
14523
|
let current = null;
|
|
14481
14524
|
try {
|
|
14482
|
-
current =
|
|
14525
|
+
current = fs11.readFileSync(ignore, "utf8");
|
|
14483
14526
|
} catch {}
|
|
14484
14527
|
if (current !== `*
|
|
14485
14528
|
`)
|
|
14486
|
-
retryTransient(() =>
|
|
14529
|
+
retryTransient(() => fs11.writeFileSync(ignore, `*
|
|
14487
14530
|
`));
|
|
14488
14531
|
}
|
|
14489
14532
|
this.writeTombstoneIfMissing();
|
|
@@ -14499,7 +14542,7 @@ class TaskStore {
|
|
|
14499
14542
|
shouldReclaim: ({ payload, nowMs }) => {
|
|
14500
14543
|
let ageMs = null;
|
|
14501
14544
|
try {
|
|
14502
|
-
ageMs = nowMs -
|
|
14545
|
+
ageMs = nowMs - fs11.lstatSync(lockPath).mtimeMs;
|
|
14503
14546
|
} catch {}
|
|
14504
14547
|
return classifyLockOwner(payload, ageMs, localLockHost(), TASK_STORE_LOCK).state === "stale";
|
|
14505
14548
|
},
|
|
@@ -14536,7 +14579,7 @@ class TaskStore {
|
|
|
14536
14579
|
withLock(lockPath, operation) {
|
|
14537
14580
|
try {
|
|
14538
14581
|
this.initializeStorage();
|
|
14539
|
-
|
|
14582
|
+
fs11.mkdirSync(path14.dirname(lockPath), { recursive: true });
|
|
14540
14583
|
} catch (error) {
|
|
14541
14584
|
return failure2("storage_error", `unable to initialize store: ${String(error)}`, {
|
|
14542
14585
|
path: this.paths().dir
|
|
@@ -14576,18 +14619,18 @@ class TaskStore {
|
|
|
14576
14619
|
lockDirWritable(lockPath) {
|
|
14577
14620
|
const probe = `${lockPath}.${process.pid}.${randomUUID3()}.probe`;
|
|
14578
14621
|
try {
|
|
14579
|
-
|
|
14622
|
+
fs11.closeSync(fs11.openSync(probe, "wx", 384));
|
|
14580
14623
|
} catch {
|
|
14581
14624
|
return false;
|
|
14582
14625
|
}
|
|
14583
14626
|
try {
|
|
14584
|
-
|
|
14627
|
+
fs11.rmSync(probe, { force: true });
|
|
14585
14628
|
} catch {}
|
|
14586
14629
|
return true;
|
|
14587
14630
|
}
|
|
14588
14631
|
lockHolderPresent(lockPath) {
|
|
14589
14632
|
try {
|
|
14590
|
-
|
|
14633
|
+
fs11.lstatSync(lockPath);
|
|
14591
14634
|
return true;
|
|
14592
14635
|
} catch (error) {
|
|
14593
14636
|
return error?.code !== "ENOENT";
|
|
@@ -14599,12 +14642,12 @@ class TaskStore {
|
|
|
14599
14642
|
if (isTransientWindowsError(error) && !this.lockHolderPresent(lockPath) && !this.lockDirWritable(lockPath))
|
|
14600
14643
|
return failure2("storage_error", `cannot create the workit lock (${code}); no other Workit call holds it`, {
|
|
14601
14644
|
path: lockPath,
|
|
14602
|
-
guidance: `Check permissions and the read-only attribute on ${
|
|
14645
|
+
guidance: `Check permissions and the read-only attribute on ${path14.dirname(lockPath)}.`
|
|
14603
14646
|
});
|
|
14604
14647
|
if (code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || isTransientWindowsError(error)) {
|
|
14605
14648
|
let lock = null;
|
|
14606
14649
|
try {
|
|
14607
|
-
lock = parseMetadataLockOrNull(
|
|
14650
|
+
lock = parseMetadataLockOrNull(fs11.readFileSync(lockPath, "utf8"));
|
|
14608
14651
|
} catch {}
|
|
14609
14652
|
return failure2("busy", `workit lock is held by another Workit call${lock ? ` (pid ${lock.pid} on ${lock.host})` : ""}; retry shortly`, {
|
|
14610
14653
|
outcome: "not_started",
|
|
@@ -14623,15 +14666,15 @@ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
|
14623
14666
|
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
14624
14667
|
|
|
14625
14668
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
14626
|
-
import { createHash as
|
|
14627
|
-
import * as
|
|
14669
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
14670
|
+
import * as fs13 from "node:fs";
|
|
14628
14671
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
14629
|
-
import
|
|
14672
|
+
import path16 from "node:path";
|
|
14630
14673
|
|
|
14631
14674
|
// packages/workit-core/src/check-config.ts
|
|
14632
14675
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
14633
|
-
import
|
|
14634
|
-
import
|
|
14676
|
+
import fs12 from "node:fs";
|
|
14677
|
+
import path15 from "node:path";
|
|
14635
14678
|
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
14636
14679
|
var CHECKS_FILE = "workit.checks.json";
|
|
14637
14680
|
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -14684,10 +14727,10 @@ var git4 = (cwd, args) => {
|
|
|
14684
14727
|
return value || null;
|
|
14685
14728
|
};
|
|
14686
14729
|
function checkRoot(cwd) {
|
|
14687
|
-
return
|
|
14730
|
+
return path15.resolve(git4(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
|
|
14688
14731
|
}
|
|
14689
14732
|
var packageManager = (root) => {
|
|
14690
|
-
const has = (file) =>
|
|
14733
|
+
const has = (file) => fs12.existsSync(path15.join(root, file));
|
|
14691
14734
|
if (has("bun.lock") || has("bun.lockb"))
|
|
14692
14735
|
return "bun";
|
|
14693
14736
|
if (has("pnpm-lock.yaml"))
|
|
@@ -14707,7 +14750,7 @@ var CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
|
|
|
14707
14750
|
function detectedChecks(root) {
|
|
14708
14751
|
const read = (file) => {
|
|
14709
14752
|
try {
|
|
14710
|
-
return
|
|
14753
|
+
return fs12.readFileSync(path15.join(root, file), "utf8");
|
|
14711
14754
|
} catch {
|
|
14712
14755
|
return null;
|
|
14713
14756
|
}
|
|
@@ -14746,7 +14789,7 @@ function detectedChecks(root) {
|
|
|
14746
14789
|
var GATES = ["testing", "verification"];
|
|
14747
14790
|
function loadCheckConfig(cwd) {
|
|
14748
14791
|
const root = checkRoot(cwd);
|
|
14749
|
-
const file =
|
|
14792
|
+
const file = path15.join(root, CHECKS_FILE);
|
|
14750
14793
|
const none = (error = null) => ({
|
|
14751
14794
|
source: error ? CHECKS_FILE : "none",
|
|
14752
14795
|
root,
|
|
@@ -14756,7 +14799,7 @@ function loadCheckConfig(cwd) {
|
|
|
14756
14799
|
});
|
|
14757
14800
|
let text = null;
|
|
14758
14801
|
try {
|
|
14759
|
-
text =
|
|
14802
|
+
text = fs12.readFileSync(file, "utf8");
|
|
14760
14803
|
} catch (error) {
|
|
14761
14804
|
if (error.code !== "ENOENT")
|
|
14762
14805
|
return none(`cannot read ${CHECKS_FILE}: ${error.message}`);
|
|
@@ -14819,10 +14862,10 @@ function workitAvailable(env = process.env) {
|
|
|
14819
14862
|
return workitOnPath;
|
|
14820
14863
|
const windows = process.platform === "win32";
|
|
14821
14864
|
const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
14822
|
-
const dirs = (env.PATH ?? env.Path ?? "").split(
|
|
14865
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path15.delimiter).filter(Boolean);
|
|
14823
14866
|
workitOnPath = dirs.some((dir) => exts.some((ext) => {
|
|
14824
14867
|
try {
|
|
14825
|
-
return
|
|
14868
|
+
return fs12.statSync(path15.join(dir, `workit${ext}`)).isFile();
|
|
14826
14869
|
} catch {
|
|
14827
14870
|
return false;
|
|
14828
14871
|
}
|
|
@@ -14834,15 +14877,15 @@ function checkCommand(args, version, onPath = workitAvailable()) {
|
|
|
14834
14877
|
}
|
|
14835
14878
|
|
|
14836
14879
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
14837
|
-
var digestBytes = (value) =>
|
|
14838
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
14839
|
-
var relative = (root, candidate) =>
|
|
14880
|
+
var digestBytes = (value) => createHash10("sha256").update(value).digest("hex");
|
|
14881
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path16.sep}`);
|
|
14882
|
+
var relative = (root, candidate) => path16.relative(root, candidate).split(path16.sep).join("/") || ".";
|
|
14840
14883
|
var canonicalPath = (value) => {
|
|
14841
14884
|
if (typeof value !== "string" || !value)
|
|
14842
14885
|
return null;
|
|
14843
14886
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
14844
14887
|
return null;
|
|
14845
|
-
const normalized =
|
|
14888
|
+
const normalized = path16.posix.normalize(value);
|
|
14846
14889
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
14847
14890
|
return null;
|
|
14848
14891
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -14890,21 +14933,21 @@ var gitPaths = (root) => {
|
|
|
14890
14933
|
git: !/not a git repository/i.test(stderr)
|
|
14891
14934
|
};
|
|
14892
14935
|
}
|
|
14893
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14936
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path16.sep).join("/")) : [];
|
|
14894
14937
|
const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
14895
14938
|
if (stagedDeleted.status !== 0) {
|
|
14896
14939
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
14897
14940
|
if (!/not a git repository/i.test(stderr))
|
|
14898
14941
|
return { paths, uncertain: true, git: true };
|
|
14899
14942
|
} else if (stagedDeleted.stdout) {
|
|
14900
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14943
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path16.sep).join("/")));
|
|
14901
14944
|
}
|
|
14902
14945
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
14903
14946
|
};
|
|
14904
14947
|
var walk = (root, directory, output) => {
|
|
14905
14948
|
let names;
|
|
14906
14949
|
try {
|
|
14907
|
-
names =
|
|
14950
|
+
names = fs13.readdirSync(directory);
|
|
14908
14951
|
} catch {
|
|
14909
14952
|
return true;
|
|
14910
14953
|
}
|
|
@@ -14912,10 +14955,10 @@ var walk = (root, directory, output) => {
|
|
|
14912
14955
|
for (const name of names) {
|
|
14913
14956
|
if (name === ".git" || name === ".workit")
|
|
14914
14957
|
continue;
|
|
14915
|
-
const target =
|
|
14958
|
+
const target = path16.join(directory, name);
|
|
14916
14959
|
let stat;
|
|
14917
14960
|
try {
|
|
14918
|
-
stat =
|
|
14961
|
+
stat = fs13.lstatSync(target);
|
|
14919
14962
|
} catch {
|
|
14920
14963
|
uncertain = true;
|
|
14921
14964
|
continue;
|
|
@@ -14930,14 +14973,14 @@ var walk = (root, directory, output) => {
|
|
|
14930
14973
|
var scopeRoots = (root, scope) => {
|
|
14931
14974
|
const roots = [];
|
|
14932
14975
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
14933
|
-
const target =
|
|
14976
|
+
const target = path16.resolve(root, item);
|
|
14934
14977
|
if (!inside(root, target))
|
|
14935
14978
|
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` }] });
|
|
14936
14979
|
let ancestor = target;
|
|
14937
|
-
while (!
|
|
14938
|
-
ancestor =
|
|
14980
|
+
while (!fs13.existsSync(ancestor) && ancestor !== root)
|
|
14981
|
+
ancestor = path16.dirname(ancestor);
|
|
14939
14982
|
try {
|
|
14940
|
-
if (!inside(root,
|
|
14983
|
+
if (!inside(root, fs13.realpathSync(ancestor)))
|
|
14941
14984
|
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` }] });
|
|
14942
14985
|
} catch {
|
|
14943
14986
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -14949,7 +14992,7 @@ var scopeRoots = (root, scope) => {
|
|
|
14949
14992
|
function captureCandidate(root, scope, environment = []) {
|
|
14950
14993
|
let checkout;
|
|
14951
14994
|
try {
|
|
14952
|
-
checkout =
|
|
14995
|
+
checkout = fs13.realpathSync(root);
|
|
14953
14996
|
} catch {
|
|
14954
14997
|
return failure2("invalid_input", "candidate root does not exist");
|
|
14955
14998
|
}
|
|
@@ -14967,11 +15010,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14967
15010
|
if (item === ".git" || item === ".workit")
|
|
14968
15011
|
continue;
|
|
14969
15012
|
names.add(item);
|
|
14970
|
-
if (!
|
|
15013
|
+
if (!fs13.existsSync(target))
|
|
14971
15014
|
continue;
|
|
14972
15015
|
let stat;
|
|
14973
15016
|
try {
|
|
14974
|
-
stat =
|
|
15017
|
+
stat = fs13.lstatSync(target);
|
|
14975
15018
|
} catch {
|
|
14976
15019
|
uncertain = true;
|
|
14977
15020
|
continue;
|
|
@@ -14987,17 +15030,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14987
15030
|
for (const item of [...names].toSorted()) {
|
|
14988
15031
|
if (!scopeMatches(item, normalizedScope))
|
|
14989
15032
|
continue;
|
|
14990
|
-
const target =
|
|
15033
|
+
const target = path16.join(checkout, item);
|
|
14991
15034
|
try {
|
|
14992
|
-
const stat =
|
|
15035
|
+
const stat = fs13.lstatSync(target);
|
|
14993
15036
|
if (stat.isSymbolicLink()) {
|
|
14994
|
-
const link =
|
|
15037
|
+
const link = fs13.readlinkSync(target);
|
|
14995
15038
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
14996
15039
|
} else if (stat.isFile()) {
|
|
14997
15040
|
files.push({
|
|
14998
15041
|
path: item,
|
|
14999
15042
|
kind: "file",
|
|
15000
|
-
digest: digestBytes(
|
|
15043
|
+
digest: digestBytes(fs13.readFileSync(target)),
|
|
15001
15044
|
executable: (stat.mode & 73) !== 0
|
|
15002
15045
|
});
|
|
15003
15046
|
} else if (!stat.isDirectory()) {
|
|
@@ -15162,7 +15205,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
15162
15205
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
15163
15206
|
let root;
|
|
15164
15207
|
try {
|
|
15165
|
-
root =
|
|
15208
|
+
root = fs13.realpathSync(checkoutRoot);
|
|
15166
15209
|
} catch {
|
|
15167
15210
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
15168
15211
|
}
|
|
@@ -15171,23 +15214,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
15171
15214
|
continue;
|
|
15172
15215
|
if (!reference.digest)
|
|
15173
15216
|
return failure2("invalid_input", "document references require a byte digest");
|
|
15174
|
-
const target =
|
|
15175
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
15217
|
+
const target = path16.resolve(root, reference.path);
|
|
15218
|
+
if (target !== root && !target.startsWith(`${root}${path16.sep}`))
|
|
15176
15219
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
15177
15220
|
fields: [
|
|
15178
15221
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
15179
15222
|
]
|
|
15180
15223
|
});
|
|
15181
15224
|
try {
|
|
15182
|
-
const below =
|
|
15225
|
+
const below = path16.relative(root, target);
|
|
15183
15226
|
let current = root;
|
|
15184
|
-
for (const segment of below.split(
|
|
15227
|
+
for (const segment of below.split(path16.sep)) {
|
|
15185
15228
|
if (!segment)
|
|
15186
15229
|
continue;
|
|
15187
|
-
current =
|
|
15188
|
-
const stat =
|
|
15189
|
-
const real =
|
|
15190
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
15230
|
+
current = path16.join(current, segment);
|
|
15231
|
+
const stat = fs13.lstatSync(current);
|
|
15232
|
+
const real = fs13.realpathSync(current);
|
|
15233
|
+
if (real !== root && !real.startsWith(`${root}${path16.sep}`))
|
|
15191
15234
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
15192
15235
|
fields: [
|
|
15193
15236
|
{
|
|
@@ -15203,7 +15246,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
15203
15246
|
if (current !== target && !stat.isDirectory())
|
|
15204
15247
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
15205
15248
|
}
|
|
15206
|
-
if (digestBytes(
|
|
15249
|
+
if (digestBytes(fs13.readFileSync(target)) !== reference.digest)
|
|
15207
15250
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
15208
15251
|
} catch {
|
|
15209
15252
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -15572,7 +15615,8 @@ Autonomy contract:
|
|
|
15572
15615
|
satisfies a gate; an ad-hoc \`workit check -- <cmd>\` never does.
|
|
15573
15616
|
- Author is not verifier: your own verdict is \`--self\`, shown as
|
|
15574
15617
|
self-reviewed, never verified. High risk, \`verification: independent\` and
|
|
15575
|
-
merge need a verifier with its own session (
|
|
15618
|
+
merge need a verifier with its own host-derived session (a subagent's
|
|
15619
|
+
\`<lead>:<child>\`, set by the host); never hand-mint one such as \`<lead>-v1\`.
|
|
15576
15620
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
15577
15621
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
15578
15622
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
@@ -17400,9 +17444,9 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
17400
17444
|
};
|
|
17401
17445
|
});
|
|
17402
17446
|
// packages/workit-core/src/hooks/context.ts
|
|
17403
|
-
import { createHash as
|
|
17404
|
-
import
|
|
17405
|
-
import
|
|
17447
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
17448
|
+
import fs14 from "node:fs";
|
|
17449
|
+
import path17 from "node:path";
|
|
17406
17450
|
var MIGRATION_PENDING_NOTE = "workit migration pending — run `workit task status`";
|
|
17407
17451
|
var migrationPending = (result) => !result.ok && result.code === "needs_input" && (result.error ?? "").startsWith(MIGRATION_PENDING);
|
|
17408
17452
|
var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
|
|
@@ -17487,7 +17531,7 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
17487
17531
|
return null;
|
|
17488
17532
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
17489
17533
|
const observed = task?.data.evidence.some(isObservedCheck) ?? false;
|
|
17490
|
-
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [
|
|
17534
|
+
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path17.resolve(store.root, ref.path)] : []));
|
|
17491
17535
|
const observedFiles = filesKey(files);
|
|
17492
17536
|
const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
|
|
17493
17537
|
if (!compact.ok || !compact.data)
|
|
@@ -17556,7 +17600,7 @@ var turnContextText = (input, descriptor) => {
|
|
|
17556
17600
|
}
|
|
17557
17601
|
};
|
|
17558
17602
|
var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
17559
|
-
var digestOf = (text) =>
|
|
17603
|
+
var digestOf = (text) => createHash11("sha256").update(text).digest("hex");
|
|
17560
17604
|
var turnMarker = (input) => {
|
|
17561
17605
|
if (!input.session.id)
|
|
17562
17606
|
return null;
|
|
@@ -17565,32 +17609,32 @@ var turnMarker = (input) => {
|
|
|
17565
17609
|
if (location instanceof Error)
|
|
17566
17610
|
return null;
|
|
17567
17611
|
const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
|
|
17568
|
-
return
|
|
17612
|
+
return path17.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
|
|
17569
17613
|
} catch {
|
|
17570
17614
|
return null;
|
|
17571
17615
|
}
|
|
17572
17616
|
};
|
|
17573
17617
|
var writeTurnDigest = (file, digest) => {
|
|
17574
17618
|
try {
|
|
17575
|
-
|
|
17576
|
-
|
|
17619
|
+
fs14.mkdirSync(path17.dirname(file), { recursive: true });
|
|
17620
|
+
fs14.writeFileSync(file, `${JSON.stringify({ digest })}
|
|
17577
17621
|
`);
|
|
17578
17622
|
} catch {}
|
|
17579
17623
|
};
|
|
17580
17624
|
var clearTurnDigest = (file) => {
|
|
17581
17625
|
try {
|
|
17582
|
-
|
|
17626
|
+
fs14.rmSync(file, { force: true });
|
|
17583
17627
|
} catch {}
|
|
17584
17628
|
};
|
|
17585
17629
|
var pruneTurnDigests = (dir, now) => {
|
|
17586
17630
|
try {
|
|
17587
|
-
for (const name of
|
|
17631
|
+
for (const name of fs14.readdirSync(dir)) {
|
|
17588
17632
|
if (!name.startsWith("turn-"))
|
|
17589
17633
|
continue;
|
|
17590
|
-
const file =
|
|
17634
|
+
const file = path17.join(dir, name);
|
|
17591
17635
|
try {
|
|
17592
|
-
if (now -
|
|
17593
|
-
|
|
17636
|
+
if (now - fs14.statSync(file).mtimeMs > TURN_TTL_MS)
|
|
17637
|
+
fs14.rmSync(file, { force: true });
|
|
17594
17638
|
} catch {}
|
|
17595
17639
|
}
|
|
17596
17640
|
} catch {}
|
|
@@ -17606,7 +17650,7 @@ var changedTurnContext = (input, text) => {
|
|
|
17606
17650
|
return text;
|
|
17607
17651
|
const digest = digestOf(text);
|
|
17608
17652
|
try {
|
|
17609
|
-
const previous = JSON.parse(
|
|
17653
|
+
const previous = JSON.parse(fs14.readFileSync(file, "utf8"));
|
|
17610
17654
|
if (previous.digest === digest)
|
|
17611
17655
|
return null;
|
|
17612
17656
|
} catch {}
|
|
@@ -17617,14 +17661,26 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
|
|
|
17617
17661
|
const file = turnMarker(input);
|
|
17618
17662
|
if (!file)
|
|
17619
17663
|
return;
|
|
17620
|
-
pruneTurnDigests(
|
|
17664
|
+
pruneTurnDigests(path17.dirname(file), Date.now());
|
|
17621
17665
|
if (text)
|
|
17622
17666
|
writeTurnDigest(file, digestOf(text));
|
|
17623
17667
|
else
|
|
17624
17668
|
clearTurnDigest(file);
|
|
17625
17669
|
};
|
|
17670
|
+
// packages/workit-core/src/core/route-intent.ts
|
|
17671
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
17672
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
17673
|
+
import path20 from "node:path";
|
|
17674
|
+
|
|
17675
|
+
// packages/workit-core/src/hooks/raw-git.ts
|
|
17676
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
17677
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
17678
|
+
import fs16 from "node:fs";
|
|
17679
|
+
import os4 from "node:os";
|
|
17680
|
+
import path19 from "node:path";
|
|
17681
|
+
|
|
17626
17682
|
// packages/workit-core/src/core/branch.ts
|
|
17627
|
-
import { createHash as
|
|
17683
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
17628
17684
|
var withReleaseTracks = (workspaceRoot, policy) => {
|
|
17629
17685
|
const read = workspaceReleaseTracks(workspaceRoot);
|
|
17630
17686
|
if (read.error)
|
|
@@ -17660,10 +17716,72 @@ function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
|
|
|
17660
17716
|
provenance: { commitPolicy: selected.source }
|
|
17661
17717
|
};
|
|
17662
17718
|
}
|
|
17719
|
+
var KIND_SYNONYMS = {
|
|
17720
|
+
fix: "bugfix",
|
|
17721
|
+
fixes: "bugfix",
|
|
17722
|
+
bug: "bugfix",
|
|
17723
|
+
bugs: "bugfix",
|
|
17724
|
+
bugfixes: "bugfix",
|
|
17725
|
+
feat: "feature",
|
|
17726
|
+
features: "feature",
|
|
17727
|
+
chore: "feature",
|
|
17728
|
+
docs: "feature",
|
|
17729
|
+
doc: "feature",
|
|
17730
|
+
refactor: "feature",
|
|
17731
|
+
test: "feature",
|
|
17732
|
+
tests: "feature",
|
|
17733
|
+
ci: "feature",
|
|
17734
|
+
build: "feature",
|
|
17735
|
+
perf: "feature",
|
|
17736
|
+
style: "feature",
|
|
17737
|
+
deps: "feature",
|
|
17738
|
+
task: "feature",
|
|
17739
|
+
enhancement: "feature",
|
|
17740
|
+
hot: "hotfix",
|
|
17741
|
+
hotfixes: "hotfix",
|
|
17742
|
+
releases: "release",
|
|
17743
|
+
rel: "release"
|
|
17744
|
+
};
|
|
17745
|
+
var allowedKinds = (allowed) => [
|
|
17746
|
+
...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
|
|
17747
|
+
];
|
|
17748
|
+
var editDistance = (a, b) => {
|
|
17749
|
+
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
17750
|
+
for (let i = 1;i <= a.length; i++) {
|
|
17751
|
+
let previous = row[0];
|
|
17752
|
+
row[0] = i;
|
|
17753
|
+
for (let j = 1;j <= b.length; j++) {
|
|
17754
|
+
const saved = row[j];
|
|
17755
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
17756
|
+
previous = saved;
|
|
17757
|
+
}
|
|
17758
|
+
}
|
|
17759
|
+
return row[b.length];
|
|
17760
|
+
};
|
|
17761
|
+
var suggestBranchName = (name, kinds) => {
|
|
17762
|
+
if (!kinds.length)
|
|
17763
|
+
return null;
|
|
17764
|
+
const slash = name.indexOf("/");
|
|
17765
|
+
const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
|
|
17766
|
+
const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
|
|
17767
|
+
const synonym = KIND_SYNONYMS[prefix];
|
|
17768
|
+
const near = kinds.map((kind) => ({ kind, distance: editDistance(prefix, kind) })).filter((entry) => prefix && entry.distance <= 2).toSorted((a, b) => a.distance - b.distance)[0]?.kind;
|
|
17769
|
+
const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
|
|
17770
|
+
return `${kind}/${rest}`;
|
|
17771
|
+
};
|
|
17772
|
+
var kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", ");
|
|
17773
|
+
var aliasNote = (kinds) => {
|
|
17774
|
+
const notes = [
|
|
17775
|
+
["fix", "bugfix"],
|
|
17776
|
+
["chore", "feature"]
|
|
17777
|
+
].filter(([, kind]) => kinds.includes(kind)).map(([alias, kind]) => `${alias}/ is ${kind}/`);
|
|
17778
|
+
return notes.length ? ` (${notes.join(", ")})` : "";
|
|
17779
|
+
};
|
|
17663
17780
|
var validateBranchNamePolicy = (name, policy, source) => {
|
|
17664
17781
|
const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
|
|
17782
|
+
const kinds = allowedKinds(policy.allowed);
|
|
17665
17783
|
if (policy.protected.has(name.toLowerCase())) {
|
|
17666
|
-
const correction = `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
|
|
17784
|
+
const correction = kinds.length ? `choose a non-protected branch of an allowed kind (${kindList(kinds)}), e.g. ${kinds[0]}/<slug>: workit git branch --kind ${kinds[0]} --slug <slug>` : `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
|
|
17667
17785
|
return {
|
|
17668
17786
|
ok: false,
|
|
17669
17787
|
rule: "protected_ref",
|
|
@@ -17674,7 +17792,8 @@ var validateBranchNamePolicy = (name, policy, source) => {
|
|
|
17674
17792
|
};
|
|
17675
17793
|
}
|
|
17676
17794
|
if (!policy.allowed.some((pattern) => pattern.test(name))) {
|
|
17677
|
-
const
|
|
17795
|
+
const suggestion = suggestBranchName(name, kinds);
|
|
17796
|
+
const correction = suggestion && policy.allowed.some((pattern) => pattern.test(suggestion)) ? `use ${JSON.stringify(suggestion)}; the allowed kinds are ${kindList(kinds)} (workit git branch ${suggestion})` : `choose a branch matching one of: ${allowed.join(", ")}`;
|
|
17678
17797
|
return {
|
|
17679
17798
|
ok: false,
|
|
17680
17799
|
rule: "allowed_pattern",
|
|
@@ -17692,7 +17811,7 @@ var branchPolicySnapshotFor = (workspaceRoot) => {
|
|
|
17692
17811
|
const source = resolved.provenance.branchPolicy;
|
|
17693
17812
|
const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
|
|
17694
17813
|
const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
|
|
17695
|
-
const fingerprint =
|
|
17814
|
+
const fingerprint = createHash12("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
|
|
17696
17815
|
return {
|
|
17697
17816
|
branchPolicy,
|
|
17698
17817
|
namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
|
|
@@ -17717,31 +17836,87 @@ var validateBranchNameFor = (workspaceRoot, name) => {
|
|
|
17717
17836
|
}
|
|
17718
17837
|
};
|
|
17719
17838
|
var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
|
|
17720
|
-
|
|
17721
|
-
|
|
17722
|
-
|
|
17723
|
-
|
|
17724
|
-
|
|
17725
|
-
|
|
17726
|
-
|
|
17839
|
+
var gitFlowOf = (root, integration) => {
|
|
17840
|
+
try {
|
|
17841
|
+
if (resolveBranchPolicyFor(root).preset !== "gitflow")
|
|
17842
|
+
return null;
|
|
17843
|
+
const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
|
|
17844
|
+
const names = branchNames(root);
|
|
17845
|
+
const production = productionBranchOf(names);
|
|
17846
|
+
const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
|
|
17847
|
+
return { develop, production, developExists: names.has(develop) };
|
|
17848
|
+
} catch {
|
|
17727
17849
|
return null;
|
|
17728
|
-
|
|
17729
|
-
|
|
17730
|
-
|
|
17850
|
+
}
|
|
17851
|
+
};
|
|
17852
|
+
var releaseBranchesOf = (cwd) => {
|
|
17853
|
+
try {
|
|
17854
|
+
const resolved = resolveBranchPolicyFor(cwd);
|
|
17855
|
+
const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
|
|
17856
|
+
return {
|
|
17857
|
+
preset: resolved.preset,
|
|
17858
|
+
branches: flow ? [flow.production, flow.develop] : [],
|
|
17859
|
+
production: flow ? flow.production : resolved.defaultTargetBranch
|
|
17860
|
+
};
|
|
17861
|
+
} catch {
|
|
17862
|
+
return { preset: "unknown", branches: [], production: null };
|
|
17863
|
+
}
|
|
17731
17864
|
};
|
|
17732
|
-
var
|
|
17733
|
-
|
|
17734
|
-
|
|
17865
|
+
var releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase());
|
|
17866
|
+
var branchGuidance = (cwd) => {
|
|
17867
|
+
try {
|
|
17868
|
+
const patterns = branchPolicySnapshotFor(cwd).namePolicy.allowed;
|
|
17869
|
+
const kinds = allowedKinds(patterns);
|
|
17870
|
+
const kindShaped = new Set(kinds.map((kind) => branchGlobPattern(`${kind}/*`).source));
|
|
17871
|
+
const anyName = patterns.some((pattern) => pattern.source === "^.*$");
|
|
17872
|
+
const others = patterns.filter((pattern) => !kindShaped.has(pattern.source)).map((pattern) => globOf(pattern));
|
|
17873
|
+
const allowed = anyName ? "Any non-protected branch name is allowed here." : `${kinds.length ? `Allowed branch kinds here: ${kindList(kinds)}${aliasNote(kinds)}` : "Allowed branch names here:"}${others.length ? `${kinds.length ? "; also" : ""} names matching ${others.join(", ")}` : ""}; anything else is denied.`;
|
|
17874
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
17875
|
+
const integration = resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
|
|
17876
|
+
const flow = resolved.releaseTrack?.track ? null : gitFlowOf(cwd, integration);
|
|
17877
|
+
if (flow?.developExists)
|
|
17878
|
+
return `${allowed} git-flow: feature/, bugfix/ and release/ start from origin/${flow.develop}, hotfix/ from origin/${flow.production}; feature/ and bugfix/ land on ${flow.develop}, release/ and hotfix/ on ${flow.production}.`;
|
|
17879
|
+
const names = branchNames(cwd);
|
|
17880
|
+
const configured = String(resolved.baseBranch ?? integration ?? "") || null;
|
|
17881
|
+
const trunk = productionBranchOf(names);
|
|
17882
|
+
const base = configured && names.has(configured) ? `Branch from origin/${configured}.` : names.has(trunk) ? `Branch from origin/${trunk}${configured ? ` (the default target ${configured} does not exist here)` : ""}.` : "No base branch exists here yet; pass the base explicitly.";
|
|
17883
|
+
return `${allowed} ${base}`;
|
|
17884
|
+
} catch {
|
|
17885
|
+
return null;
|
|
17886
|
+
}
|
|
17735
17887
|
};
|
|
17888
|
+
var globOf = (pattern) => pattern.source.replace(/^\^|\$$/gu, "").replaceAll(".*", "*").replace(/\\(.)/gu, "$1");
|
|
17736
17889
|
|
|
17737
|
-
// packages/workit-core/src/hooks/
|
|
17738
|
-
|
|
17739
|
-
|
|
17740
|
-
|
|
17890
|
+
// packages/workit-core/src/hooks/session-marker.ts
|
|
17891
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
17892
|
+
import fs15 from "node:fs";
|
|
17893
|
+
import path18 from "node:path";
|
|
17894
|
+
var sessionMarkerFile = (input, session, store) => {
|
|
17895
|
+
let dir = store;
|
|
17896
|
+
if (dir === undefined) {
|
|
17897
|
+
const location = resolveStore(input.cwd);
|
|
17898
|
+
if (location instanceof Error || !location.shared)
|
|
17899
|
+
return null;
|
|
17900
|
+
dir = location.dir;
|
|
17901
|
+
}
|
|
17902
|
+
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
17903
|
+
return path18.join(dir, "hooks", `skills-${name}.json`);
|
|
17904
|
+
};
|
|
17905
|
+
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
17906
|
+
var readSessionMarker = (file) => {
|
|
17907
|
+
try {
|
|
17908
|
+
const value = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
17909
|
+
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
17910
|
+
} catch {
|
|
17911
|
+
return { nudged: [], loaded: [], raw: [] };
|
|
17912
|
+
}
|
|
17913
|
+
};
|
|
17914
|
+
var writeSessionMarker = (file, marker) => {
|
|
17915
|
+
try {
|
|
17916
|
+
fs15.mkdirSync(path18.dirname(file), { recursive: true });
|
|
17917
|
+
fs15.writeFileSync(file, JSON.stringify(marker));
|
|
17918
|
+
} catch {}
|
|
17741
17919
|
};
|
|
17742
|
-
// packages/workit-core/src/hooks/write-gate.ts
|
|
17743
|
-
import { realpathSync as realpathSync5 } from "node:fs";
|
|
17744
|
-
import path17 from "node:path";
|
|
17745
17920
|
|
|
17746
17921
|
// packages/workit-core/src/hooks/shell-words.ts
|
|
17747
17922
|
var closingQuote = (command, start, dialect) => {
|
|
@@ -17866,252 +18041,38 @@ var segmentsOf = (command, dialect = "posix") => {
|
|
|
17866
18041
|
return segments;
|
|
17867
18042
|
};
|
|
17868
18043
|
|
|
17869
|
-
// packages/workit-core/src/hooks/
|
|
18044
|
+
// packages/workit-core/src/hooks/raw-git.ts
|
|
17870
18045
|
var NONE2 = { kind: "none" };
|
|
17871
|
-
var
|
|
17872
|
-
var
|
|
17873
|
-
|
|
17874
|
-
|
|
17875
|
-
|
|
17876
|
-
|
|
17877
|
-
|
|
17878
|
-
|
|
17879
|
-
|
|
17880
|
-
|
|
17881
|
-
|
|
17882
|
-
|
|
18046
|
+
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
18047
|
+
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
18048
|
+
var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
|
|
18049
|
+
var CD = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
18050
|
+
var PUSHD = new Set(["pushd", "push-location"]);
|
|
18051
|
+
var POPD = new Set(["popd", "pop-location"]);
|
|
18052
|
+
var GIT_VALUE_OPTIONS = new Set([
|
|
18053
|
+
"-c",
|
|
18054
|
+
"--git-dir",
|
|
18055
|
+
"--work-tree",
|
|
18056
|
+
"--namespace",
|
|
18057
|
+
"--exec-path",
|
|
18058
|
+
"--config-env",
|
|
18059
|
+
"--super-prefix"
|
|
18060
|
+
]);
|
|
18061
|
+
var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
18062
|
+
var joinDir = (base, next) => {
|
|
18063
|
+
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
18064
|
+
return;
|
|
18065
|
+
const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
|
|
18066
|
+
return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
|
|
17883
18067
|
};
|
|
17884
|
-
var
|
|
17885
|
-
const
|
|
17886
|
-
const
|
|
17887
|
-
if (
|
|
17888
|
-
return
|
|
17889
|
-
|
|
17890
|
-
const segments = posix.split("/");
|
|
17891
|
-
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
18068
|
+
var cdTarget = (words) => {
|
|
18069
|
+
const args = words.slice(1);
|
|
18070
|
+
const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
|
|
18071
|
+
if (flag >= 0)
|
|
18072
|
+
return args[flag + 1] ?? null;
|
|
18073
|
+
return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
|
|
17892
18074
|
};
|
|
17893
|
-
var
|
|
17894
|
-
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
17895
|
-
var editsOf = (input) => {
|
|
17896
|
-
let words = input;
|
|
17897
|
-
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
|
|
17898
|
-
words = words.slice(1);
|
|
17899
|
-
const [verb, ...args] = words;
|
|
17900
|
-
const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
17901
|
-
if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
|
|
17902
|
-
return [];
|
|
17903
|
-
switch (name) {
|
|
17904
|
-
case "touch":
|
|
17905
|
-
case "mkdir":
|
|
17906
|
-
case "rm":
|
|
17907
|
-
case "rmdir":
|
|
17908
|
-
case "tee":
|
|
17909
|
-
case "mv":
|
|
17910
|
-
case "truncate":
|
|
17911
|
-
return operands(args);
|
|
17912
|
-
case "cp":
|
|
17913
|
-
case "ln":
|
|
17914
|
-
case "install": {
|
|
17915
|
-
const target = args.indexOf("-t");
|
|
17916
|
-
if (target >= 0)
|
|
17917
|
-
return args[target + 1] ? [args[target + 1]] : null;
|
|
17918
|
-
const last = operands(args).at(-1);
|
|
17919
|
-
return last ? [last] : null;
|
|
17920
|
-
}
|
|
17921
|
-
case "dd":
|
|
17922
|
-
return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
|
|
17923
|
-
case "sed":
|
|
17924
|
-
case "perl": {
|
|
17925
|
-
if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
|
|
17926
|
-
return [];
|
|
17927
|
-
const scripted = args.some((item) => item === "-e" || item === "-f");
|
|
17928
|
-
const files = [];
|
|
17929
|
-
for (let index = 0;index < args.length; index++) {
|
|
17930
|
-
if (args[index] === "-e" || args[index] === "-f")
|
|
17931
|
-
index++;
|
|
17932
|
-
else if (!args[index].startsWith("-"))
|
|
17933
|
-
files.push(args[index]);
|
|
17934
|
-
}
|
|
17935
|
-
return scripted ? files : files.slice(1);
|
|
17936
|
-
}
|
|
17937
|
-
case "patch":
|
|
17938
|
-
return null;
|
|
17939
|
-
case "set-content":
|
|
17940
|
-
case "add-content":
|
|
17941
|
-
case "out-file":
|
|
17942
|
-
case "new-item":
|
|
17943
|
-
case "ni":
|
|
17944
|
-
case "remove-item":
|
|
17945
|
-
case "del":
|
|
17946
|
-
case "copy-item":
|
|
17947
|
-
case "move-item": {
|
|
17948
|
-
const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
|
|
17949
|
-
if (flag >= 0)
|
|
17950
|
-
return args[flag + 1] ? [args[flag + 1]] : null;
|
|
17951
|
-
const files = operands(args);
|
|
17952
|
-
if (name === "move-item")
|
|
17953
|
-
return files.length ? files : null;
|
|
17954
|
-
const file = name === "copy-item" ? files.at(-1) : files[0];
|
|
17955
|
-
return file ? [file] : null;
|
|
17956
|
-
}
|
|
17957
|
-
case "git": {
|
|
17958
|
-
const sub = operands(args)[0];
|
|
17959
|
-
if (sub === "apply")
|
|
17960
|
-
return null;
|
|
17961
|
-
if (sub === "restore" || sub === "mv" || sub === "rm")
|
|
17962
|
-
return operands(args).slice(1);
|
|
17963
|
-
if (sub === "checkout" && args.includes("--"))
|
|
17964
|
-
return args.slice(args.indexOf("--") + 1);
|
|
17965
|
-
return [];
|
|
17966
|
-
}
|
|
17967
|
-
default:
|
|
17968
|
-
return [];
|
|
17969
|
-
}
|
|
17970
|
-
};
|
|
17971
|
-
var shellWrites = (command) => {
|
|
17972
|
-
const targets = [];
|
|
17973
|
-
let unknown = false;
|
|
17974
|
-
for (const segment of segmentsOf(command)) {
|
|
17975
|
-
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
17976
|
-
const edits = editsOf(segment.words);
|
|
17977
|
-
if (edits === null)
|
|
17978
|
-
unknown = true;
|
|
17979
|
-
else
|
|
17980
|
-
targets.push(...edits);
|
|
17981
|
-
}
|
|
17982
|
-
if (!unknown && targets.length === 0)
|
|
17983
|
-
return { writes: false, targets: null };
|
|
17984
|
-
return { writes: true, targets: unknown ? null : targets };
|
|
17985
|
-
};
|
|
17986
|
-
var cache2 = new Map;
|
|
17987
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path17.resolve(root, file)) ?? "-").join("\x00");
|
|
17988
|
-
var gateFor = (cwd) => {
|
|
17989
|
-
const store = new TaskStore(cwd);
|
|
17990
|
-
const listed = store.listTaskIndex();
|
|
17991
|
-
if (!listed.ok)
|
|
17992
|
-
return null;
|
|
17993
|
-
const key = store.currentKey();
|
|
17994
|
-
if (!key.ok)
|
|
17995
|
-
return null;
|
|
17996
|
-
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
17997
|
-
if (!entry)
|
|
17998
|
-
return { blockers: [], root: store.root, plans: new Set };
|
|
17999
|
-
const ledger = ledgerPath(store.root);
|
|
18000
|
-
const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
|
|
18001
|
-
const hit = cache2.get(store.root);
|
|
18002
|
-
if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
|
|
18003
|
-
return hit;
|
|
18004
|
-
const task = store.readTask(entry.id);
|
|
18005
|
-
if (!task.ok)
|
|
18006
|
-
return null;
|
|
18007
|
-
const workspace = store.readWorkspace();
|
|
18008
|
-
const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
|
|
18009
|
-
const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
|
|
18010
|
-
const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
|
|
18011
|
-
return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
|
|
18012
|
-
});
|
|
18013
|
-
const gate = {
|
|
18014
|
-
blockers,
|
|
18015
|
-
root: store.root,
|
|
18016
|
-
plans,
|
|
18017
|
-
key: cacheKey,
|
|
18018
|
-
planKey: planKeyOf(store.root, plans)
|
|
18019
|
-
};
|
|
18020
|
-
cache2.set(store.root, gate);
|
|
18021
|
-
if (cache2.size > 32)
|
|
18022
|
-
cache2.delete(cache2.keys().next().value);
|
|
18023
|
-
return gate;
|
|
18024
|
-
};
|
|
18025
|
-
function writeGate(cwd, paths) {
|
|
18026
|
-
try {
|
|
18027
|
-
const gate = gateFor(cwd);
|
|
18028
|
-
if (!gate || gate.blockers.length === 0)
|
|
18029
|
-
return NONE2;
|
|
18030
|
-
if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
|
|
18031
|
-
return NONE2;
|
|
18032
|
-
const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
|
|
18033
|
-
return {
|
|
18034
|
-
kind: "deny",
|
|
18035
|
-
reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
|
|
18036
|
-
unblock: gate.blockers[0].reason
|
|
18037
|
-
};
|
|
18038
|
-
} catch {
|
|
18039
|
-
return NONE2;
|
|
18040
|
-
}
|
|
18041
|
-
}
|
|
18042
|
-
// packages/workit-core/src/hooks/stop.ts
|
|
18043
|
-
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
18044
|
-
|
|
18045
|
-
// packages/workit-core/src/hooks/raw-git.ts
|
|
18046
|
-
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
18047
|
-
import { createHash as createHash13 } from "node:crypto";
|
|
18048
|
-
import fs15 from "node:fs";
|
|
18049
|
-
import os4 from "node:os";
|
|
18050
|
-
import path19 from "node:path";
|
|
18051
|
-
|
|
18052
|
-
// packages/workit-core/src/hooks/session-marker.ts
|
|
18053
|
-
import { createHash as createHash12 } from "node:crypto";
|
|
18054
|
-
import fs14 from "node:fs";
|
|
18055
|
-
import path18 from "node:path";
|
|
18056
|
-
var sessionMarkerFile = (input, session, store) => {
|
|
18057
|
-
let dir = store;
|
|
18058
|
-
if (dir === undefined) {
|
|
18059
|
-
const location = resolveStore(input.cwd);
|
|
18060
|
-
if (location instanceof Error || !location.shared)
|
|
18061
|
-
return null;
|
|
18062
|
-
dir = location.dir;
|
|
18063
|
-
}
|
|
18064
|
-
const name = createHash12("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
18065
|
-
return path18.join(dir, "hooks", `skills-${name}.json`);
|
|
18066
|
-
};
|
|
18067
|
-
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
18068
|
-
var readSessionMarker = (file) => {
|
|
18069
|
-
try {
|
|
18070
|
-
const value = JSON.parse(fs14.readFileSync(file, "utf8"));
|
|
18071
|
-
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
18072
|
-
} catch {
|
|
18073
|
-
return { nudged: [], loaded: [], raw: [] };
|
|
18074
|
-
}
|
|
18075
|
-
};
|
|
18076
|
-
var writeSessionMarker = (file, marker) => {
|
|
18077
|
-
try {
|
|
18078
|
-
fs14.mkdirSync(path18.dirname(file), { recursive: true });
|
|
18079
|
-
fs14.writeFileSync(file, JSON.stringify(marker));
|
|
18080
|
-
} catch {}
|
|
18081
|
-
};
|
|
18082
|
-
|
|
18083
|
-
// packages/workit-core/src/hooks/raw-git.ts
|
|
18084
|
-
var NONE3 = { kind: "none" };
|
|
18085
|
-
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
18086
|
-
var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
18087
|
-
var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
|
|
18088
|
-
var CD = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
18089
|
-
var PUSHD = new Set(["pushd", "push-location"]);
|
|
18090
|
-
var POPD = new Set(["popd", "pop-location"]);
|
|
18091
|
-
var GIT_VALUE_OPTIONS = new Set([
|
|
18092
|
-
"-c",
|
|
18093
|
-
"--git-dir",
|
|
18094
|
-
"--work-tree",
|
|
18095
|
-
"--namespace",
|
|
18096
|
-
"--exec-path",
|
|
18097
|
-
"--config-env",
|
|
18098
|
-
"--super-prefix"
|
|
18099
|
-
]);
|
|
18100
|
-
var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
18101
|
-
var joinDir = (base, next) => {
|
|
18102
|
-
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
18103
|
-
return;
|
|
18104
|
-
const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
|
|
18105
|
-
return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
|
|
18106
|
-
};
|
|
18107
|
-
var cdTarget = (words) => {
|
|
18108
|
-
const args = words.slice(1);
|
|
18109
|
-
const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
|
|
18110
|
-
if (flag >= 0)
|
|
18111
|
-
return args[flag + 1] ?? null;
|
|
18112
|
-
return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
|
|
18113
|
-
};
|
|
18114
|
-
var stripPrefixes = (input) => {
|
|
18075
|
+
var stripPrefixes = (input) => {
|
|
18115
18076
|
let words = input;
|
|
18116
18077
|
for (;; ) {
|
|
18117
18078
|
const first = words[0];
|
|
@@ -18119,7 +18080,7 @@ var stripPrefixes = (input) => {
|
|
|
18119
18080
|
return words;
|
|
18120
18081
|
if (/^\w+=/.test(first))
|
|
18121
18082
|
words = words.slice(1);
|
|
18122
|
-
else if (
|
|
18083
|
+
else if (PREFIXES2.has(first)) {
|
|
18123
18084
|
words = words.slice(1);
|
|
18124
18085
|
while (words[0]?.startsWith("-"))
|
|
18125
18086
|
words = words.slice(words[0] === "-u" ? 2 : 1);
|
|
@@ -18234,6 +18195,10 @@ var pushAction = (args) => {
|
|
|
18234
18195
|
let force = false;
|
|
18235
18196
|
let lease = false;
|
|
18236
18197
|
let wholesale = false;
|
|
18198
|
+
let allTags = false;
|
|
18199
|
+
let deletes = false;
|
|
18200
|
+
let allBranches = false;
|
|
18201
|
+
let mirror = false;
|
|
18237
18202
|
let remote = null;
|
|
18238
18203
|
const positionals = [];
|
|
18239
18204
|
for (let index = 1;index < args.length; index++) {
|
|
@@ -18254,14 +18219,22 @@ var pushAction = (args) => {
|
|
|
18254
18219
|
force = true;
|
|
18255
18220
|
else if (arg.startsWith("--force-with-lease") || arg === "--force-if-includes")
|
|
18256
18221
|
lease = true;
|
|
18257
|
-
else if (
|
|
18258
|
-
|
|
18222
|
+
else if (arg === "--delete" || arg === "-d")
|
|
18223
|
+
deletes = true;
|
|
18224
|
+
else if (arg === "--tags")
|
|
18225
|
+
allTags = wholesale = true;
|
|
18226
|
+
else if (arg === "--all" || arg === "--branches")
|
|
18227
|
+
allBranches = wholesale = true;
|
|
18228
|
+
else if (arg === "--mirror")
|
|
18229
|
+
mirror = wholesale = true;
|
|
18259
18230
|
else if (!arg.startsWith("-"))
|
|
18260
18231
|
positionals.push(arg);
|
|
18261
18232
|
}
|
|
18262
18233
|
const specs = remote === null ? positionals.slice(1) : positionals;
|
|
18263
18234
|
remote ??= positionals[0] ?? null;
|
|
18264
18235
|
const targets = [];
|
|
18236
|
+
const tags = [];
|
|
18237
|
+
const deleted = [];
|
|
18265
18238
|
for (const raw of specs) {
|
|
18266
18239
|
if (raw.startsWith("+"))
|
|
18267
18240
|
force = true;
|
|
@@ -18270,16 +18243,29 @@ var pushAction = (args) => {
|
|
|
18270
18243
|
const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
|
|
18271
18244
|
if (!destination)
|
|
18272
18245
|
continue;
|
|
18273
|
-
|
|
18274
|
-
|
|
18275
|
-
|
|
18276
|
-
|
|
18277
|
-
|
|
18278
|
-
|
|
18246
|
+
const name = destination === "HEAD" ? null : destination.startsWith("refs/heads/") ? destination.slice(11) : destination.startsWith("refs/") ? undefined : destination;
|
|
18247
|
+
if (destination.startsWith("refs/tags/"))
|
|
18248
|
+
tags.push(destination.slice(10));
|
|
18249
|
+
if (name === undefined)
|
|
18250
|
+
continue;
|
|
18251
|
+
targets.push(name);
|
|
18252
|
+
if (colon === 0 && name !== null)
|
|
18253
|
+
deleted.push(name);
|
|
18279
18254
|
}
|
|
18280
18255
|
if (specs.length === 0 && !wholesale)
|
|
18281
18256
|
targets.push(null);
|
|
18282
|
-
return {
|
|
18257
|
+
return {
|
|
18258
|
+
kind: "push",
|
|
18259
|
+
targets,
|
|
18260
|
+
blindForce: force && !lease,
|
|
18261
|
+
remote,
|
|
18262
|
+
...tags.length ? { tags } : {},
|
|
18263
|
+
...allTags ? { allTags: true } : {},
|
|
18264
|
+
...deletes ? { deletes: true } : {},
|
|
18265
|
+
...deleted.length ? { deleted } : {},
|
|
18266
|
+
...allBranches ? { allBranches: true } : {},
|
|
18267
|
+
...mirror ? { mirror: true } : {}
|
|
18268
|
+
};
|
|
18283
18269
|
};
|
|
18284
18270
|
var VALUE_OPTIONS = {
|
|
18285
18271
|
git: new Set([
|
|
@@ -18355,7 +18341,7 @@ var repoAt = (dir) => {
|
|
|
18355
18341
|
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
18356
18342
|
};
|
|
18357
18343
|
var isWorkitWorkspace = (repo) => {
|
|
18358
|
-
if (
|
|
18344
|
+
if (fs16.existsSync(repo.location.dir))
|
|
18359
18345
|
return true;
|
|
18360
18346
|
try {
|
|
18361
18347
|
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
@@ -18386,7 +18372,7 @@ var headSha2 = (repo) => {
|
|
|
18386
18372
|
if (!git || git.reftable)
|
|
18387
18373
|
return null;
|
|
18388
18374
|
try {
|
|
18389
|
-
const head =
|
|
18375
|
+
const head = fs16.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
18390
18376
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
18391
18377
|
return head;
|
|
18392
18378
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
@@ -18394,20 +18380,72 @@ var headSha2 = (repo) => {
|
|
|
18394
18380
|
return null;
|
|
18395
18381
|
const common = path19.dirname(repo.location.dir);
|
|
18396
18382
|
try {
|
|
18397
|
-
return
|
|
18383
|
+
return fs16.readFileSync(path19.join(common, ref), "utf8").trim() || null;
|
|
18398
18384
|
} catch {}
|
|
18399
|
-
const packed =
|
|
18385
|
+
const packed = fs16.readFileSync(path19.join(common, "packed-refs"), "utf8");
|
|
18400
18386
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
18401
18387
|
} catch {
|
|
18402
18388
|
return null;
|
|
18403
18389
|
}
|
|
18404
18390
|
};
|
|
18405
18391
|
var quote = (text) => `\`${text}\``;
|
|
18406
|
-
var refusal = (action, branch) => {
|
|
18392
|
+
var refusal = (action, branch, dir) => {
|
|
18407
18393
|
if (action.kind === "merge")
|
|
18408
18394
|
return `workit: raw ${quote(action.forge === "gh" ? "gh pr merge" : "glab mr merge")} bypasses the merge grant and the verdict gate. Run ${quote("workit pr merge")} instead (it checks both; ${quote("workit help pr")}).`;
|
|
18395
|
+
const plainPush = action.kind === "push" && !action.blindForce && !action.deletes;
|
|
18396
|
+
if (dir && branch && plainPush && releaseBranch(dir, branch))
|
|
18397
|
+
return `workit: raw ${quote("git push")} onto ${branch}, a protected release branch, bypasses workit's protected-branch check. To finish a release or hotfix, push it with ${quote(`workit git push --release ${branch} [--release v<version>]`)}: it pushes ${branch} (fast-forward only) and the release tag, verifies the remote and records it, under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}). Feature work goes through ${quote("workit git push")} and ${quote("workit pr merge")}.`;
|
|
18409
18398
|
return `workit: raw ${quote("git push")}${action.kind === "push" && action.blindForce ? " --force" : ""} onto ${branch}, a protected branch or the default target, bypasses workit's protected-branch check. Push your feature branch with ${quote("workit git push")} (${quote("workit git push --force-with-lease")} to rewrite it) and land it with ${quote("workit pr merge")}.`;
|
|
18410
18399
|
};
|
|
18400
|
+
var refFacts = (repo, remote) => {
|
|
18401
|
+
const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
|
|
18402
|
+
const listed = spawnSync11("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
|
|
18403
|
+
const remotePrefix = `refs/remotes/${remote}/`;
|
|
18404
|
+
for (const ref of (listed.stdout ?? "").split(`
|
|
18405
|
+
`)) {
|
|
18406
|
+
if (ref.startsWith("refs/heads/"))
|
|
18407
|
+
facts.heads.add(ref.slice(11));
|
|
18408
|
+
else if (ref.startsWith("refs/tags/"))
|
|
18409
|
+
facts.tags.add(ref.slice(10));
|
|
18410
|
+
else if (ref.startsWith(remotePrefix))
|
|
18411
|
+
facts.remoteHeads.add(ref.slice(remotePrefix.length));
|
|
18412
|
+
}
|
|
18413
|
+
return facts;
|
|
18414
|
+
};
|
|
18415
|
+
var isTag = (facts, name) => facts.tags.has(name) && !facts.heads.has(name);
|
|
18416
|
+
var tagRefusal = (action, facts) => {
|
|
18417
|
+
const tags = [
|
|
18418
|
+
...action.tags ?? [],
|
|
18419
|
+
...action.targets.filter((name) => name !== null && isTag(facts, name))
|
|
18420
|
+
];
|
|
18421
|
+
if (!tags.length && !action.allTags)
|
|
18422
|
+
return null;
|
|
18423
|
+
const named = tags.length ? tags.join(" --release ") : "v<version>";
|
|
18424
|
+
return `workit: raw ${quote(`git push${action.allTags ? " --tags" : ""}`)} of ${tags.length ? `tag ${tags.join(", ")}` : "tags"} bypasses the release grant. Push a release tag with ${quote(`workit git push --release ${named}`)} (v<version> tags whose commit the production branch already has; a tag never moves), under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}).`;
|
|
18425
|
+
};
|
|
18426
|
+
var remoteHas = (repo, remote, branch) => {
|
|
18427
|
+
const listed = spawnSync11("git", ["ls-remote", "--heads", remote, `refs/heads/${branch}`], {
|
|
18428
|
+
cwd: repo.dir,
|
|
18429
|
+
encoding: "utf8",
|
|
18430
|
+
timeout: 3000,
|
|
18431
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
18432
|
+
});
|
|
18433
|
+
return listed.status !== 0 || (listed.stdout ?? "").trim() !== "";
|
|
18434
|
+
};
|
|
18435
|
+
var offPolicyPush = (action, repo, facts, remote) => {
|
|
18436
|
+
const names = action.allBranches ? [...facts.heads] : action.targets.map((target) => target ?? repo.branch);
|
|
18437
|
+
for (const branch of names) {
|
|
18438
|
+
if (!branch || action.deleted?.includes(branch) || isTag(facts, branch))
|
|
18439
|
+
continue;
|
|
18440
|
+
if (facts.remoteHeads.has(branch))
|
|
18441
|
+
continue;
|
|
18442
|
+
const check = validateBranchNameFor(repo.dir, branch);
|
|
18443
|
+
if (check.ok || check.rule !== "allowed_pattern" || remoteHas(repo, remote, branch))
|
|
18444
|
+
continue;
|
|
18445
|
+
return `workit: raw ${quote("git push")} onto ${branch}: ${check.error}`;
|
|
18446
|
+
}
|
|
18447
|
+
return null;
|
|
18448
|
+
};
|
|
18411
18449
|
var nudgeFor = (action, host, session) => {
|
|
18412
18450
|
switch (action.kind) {
|
|
18413
18451
|
case "commit": {
|
|
@@ -18470,7 +18508,7 @@ var unseenNudges = (input, nudges, store) => {
|
|
|
18470
18508
|
function rawGitPre(input, command, options = {}) {
|
|
18471
18509
|
try {
|
|
18472
18510
|
if (!mentionsRawTool(command))
|
|
18473
|
-
return
|
|
18511
|
+
return NONE2;
|
|
18474
18512
|
const dialect = dialectOf(input);
|
|
18475
18513
|
const nudges = new Map;
|
|
18476
18514
|
let store = null;
|
|
@@ -18478,32 +18516,43 @@ function rawGitPre(input, command, options = {}) {
|
|
|
18478
18516
|
store ??= repo.location.dir;
|
|
18479
18517
|
if (action.kind === "merge")
|
|
18480
18518
|
return deny(refusal(action, null));
|
|
18481
|
-
if (action.kind === "push")
|
|
18519
|
+
if (action.kind === "push" && toPushRemote(repo, action.remote)) {
|
|
18520
|
+
if (action.mirror)
|
|
18521
|
+
return deny(`workit: raw ${quote("git push --mirror")} rewrites and deletes every remote ref, protected branches and tags included. Push one branch with ${quote("workit git push")}.`);
|
|
18482
18522
|
for (const target of action.targets) {
|
|
18483
18523
|
const branch = target ?? repo.branch;
|
|
18484
|
-
if (branch && guardedBranch(repo.dir, branch)
|
|
18485
|
-
return deny(refusal(action, branch));
|
|
18524
|
+
if (branch && guardedBranch(repo.dir, branch))
|
|
18525
|
+
return deny(refusal(action, branch, repo.dir));
|
|
18486
18526
|
}
|
|
18527
|
+
const remote = action.remote ?? pushRemoteName(repo.dir, repo.branch) ?? "origin";
|
|
18528
|
+
const facts = refFacts(repo, remote);
|
|
18529
|
+
const tagDeny = tagRefusal(action, facts);
|
|
18530
|
+
if (tagDeny)
|
|
18531
|
+
return deny(tagDeny);
|
|
18532
|
+
const nameDeny = action.deletes ? null : offPolicyPush(action, repo, facts, remote);
|
|
18533
|
+
if (nameDeny)
|
|
18534
|
+
return deny(nameDeny);
|
|
18535
|
+
}
|
|
18487
18536
|
const nudge = nudgeFor(action, input.host, input.session.id);
|
|
18488
18537
|
if (nudge && !nudges.has(nudgeKind(action)))
|
|
18489
18538
|
nudges.set(nudgeKind(action), nudge);
|
|
18490
18539
|
}
|
|
18491
18540
|
if (options.nudge === false || !store || nudges.size === 0)
|
|
18492
|
-
return
|
|
18541
|
+
return NONE2;
|
|
18493
18542
|
if (workitInvocations(command, dialect).length > 0)
|
|
18494
|
-
return
|
|
18543
|
+
return NONE2;
|
|
18495
18544
|
const shown = unseenNudges(input, nudges, store);
|
|
18496
18545
|
return shown.length ? { kind: "context", text: shown.join(`
|
|
18497
|
-
`) } :
|
|
18546
|
+
`) } : NONE2;
|
|
18498
18547
|
} catch {
|
|
18499
|
-
return
|
|
18548
|
+
return NONE2;
|
|
18500
18549
|
}
|
|
18501
18550
|
}
|
|
18502
18551
|
var deny = (reason) => ({ kind: "deny", reason, unblock: null });
|
|
18503
18552
|
var NEXT_COMMAND = "next";
|
|
18504
18553
|
var MARKER_TTL_SECONDS = 3600;
|
|
18505
|
-
var markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${
|
|
18506
|
-
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${
|
|
18554
|
+
var markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
18555
|
+
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
|
|
18507
18556
|
function noteRawCommit(input, command, key) {
|
|
18508
18557
|
try {
|
|
18509
18558
|
if (!input.session.id || !/\bcommit\b/.test(command))
|
|
@@ -18513,8 +18562,8 @@ function noteRawCommit(input, command, key) {
|
|
|
18513
18562
|
if (action.kind !== "commit")
|
|
18514
18563
|
continue;
|
|
18515
18564
|
const file = markerFile(repo, input.session.id, key);
|
|
18516
|
-
|
|
18517
|
-
|
|
18565
|
+
fs16.mkdirSync(path19.dirname(file), { recursive: true });
|
|
18566
|
+
fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
18518
18567
|
`);
|
|
18519
18568
|
}
|
|
18520
18569
|
} catch {}
|
|
@@ -18537,11 +18586,11 @@ var headCommit = (dir) => {
|
|
|
18537
18586
|
var settle2 = (input, repo, file) => {
|
|
18538
18587
|
let marker;
|
|
18539
18588
|
try {
|
|
18540
|
-
marker = JSON.parse(
|
|
18589
|
+
marker = JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
18541
18590
|
} catch {
|
|
18542
18591
|
return;
|
|
18543
18592
|
}
|
|
18544
|
-
|
|
18593
|
+
fs16.rmSync(file, { force: true });
|
|
18545
18594
|
const session = input.session.id;
|
|
18546
18595
|
const now = Date.now() / 1000;
|
|
18547
18596
|
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
@@ -18590,12 +18639,297 @@ function settlePendingCommit(input) {
|
|
|
18590
18639
|
if (!repo)
|
|
18591
18640
|
return;
|
|
18592
18641
|
const file = markerFile(repo, input.session.id, NEXT_COMMAND);
|
|
18593
|
-
if (
|
|
18642
|
+
if (fs16.existsSync(file))
|
|
18594
18643
|
settle2(input, repo, file);
|
|
18595
18644
|
} catch {}
|
|
18596
18645
|
}
|
|
18597
18646
|
|
|
18647
|
+
// packages/workit-core/src/core/route-intent.ts
|
|
18648
|
+
var UNKNOWABLE = /[$`*?[\]{}~]/u;
|
|
18649
|
+
var SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
|
|
18650
|
+
var CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
|
|
18651
|
+
var WORKTREE_CREATE = new Set(["-b", "-B"]);
|
|
18652
|
+
var BRANCH_NO_CREATE = new Set(["-d", "-D", "--delete", "-l", "--list", "-a", "--all", "-r", "--remotes", "--show-current"].concat(["-v", "-vv", "--verbose", "--contains", "--no-contains", "--merged", "--no-merged"]).concat(["--points-at", "--sort", "--format", "--column", "--no-column", "-u"]).concat(["--set-upstream-to", "--unset-upstream", "--edit-description", "-i", "--ignore-case"]));
|
|
18653
|
+
var BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
|
|
18654
|
+
var flagValue = (args, flags) => {
|
|
18655
|
+
for (let index = 1;index < args.length; index++) {
|
|
18656
|
+
const arg = args[index];
|
|
18657
|
+
if (arg === "--")
|
|
18658
|
+
return null;
|
|
18659
|
+
if (flags.has(arg))
|
|
18660
|
+
return args[index + 1] ?? null;
|
|
18661
|
+
const eq = arg.indexOf("=");
|
|
18662
|
+
if (arg.startsWith("--") && eq > 0 && flags.has(arg.slice(0, eq)))
|
|
18663
|
+
return arg.slice(eq + 1);
|
|
18664
|
+
if (!arg.startsWith("--") && arg.length > 2 && flags.has(arg.slice(0, 2)))
|
|
18665
|
+
return arg.slice(2);
|
|
18666
|
+
}
|
|
18667
|
+
return null;
|
|
18668
|
+
};
|
|
18669
|
+
var WORKTREE_VALUES = new Set(["--reason"]);
|
|
18670
|
+
var worktreeTarget = (args) => {
|
|
18671
|
+
const named = flagValue(args, WORKTREE_CREATE);
|
|
18672
|
+
if (named !== null)
|
|
18673
|
+
return named;
|
|
18674
|
+
const positionals = [];
|
|
18675
|
+
for (let index = 1;index < args.length; index++) {
|
|
18676
|
+
const arg = args[index];
|
|
18677
|
+
if (arg === "--detach" || arg === "-d")
|
|
18678
|
+
return null;
|
|
18679
|
+
if (WORKTREE_VALUES.has(arg))
|
|
18680
|
+
index++;
|
|
18681
|
+
else if (!arg.startsWith("-"))
|
|
18682
|
+
positionals.push(arg);
|
|
18683
|
+
}
|
|
18684
|
+
if (positionals.length !== 1)
|
|
18685
|
+
return null;
|
|
18686
|
+
const base = positionals[0].split(/[\\/]/u).findLast(Boolean) ?? "";
|
|
18687
|
+
return base && base !== "." && base !== ".." ? base : null;
|
|
18688
|
+
};
|
|
18689
|
+
var branchCommandTarget = (args) => {
|
|
18690
|
+
let rename = false;
|
|
18691
|
+
const positionals = [];
|
|
18692
|
+
for (let index = 1;index < args.length; index++) {
|
|
18693
|
+
const arg = args[index];
|
|
18694
|
+
if (arg === "--") {
|
|
18695
|
+
positionals.push(...args.slice(index + 1));
|
|
18696
|
+
break;
|
|
18697
|
+
}
|
|
18698
|
+
const flag = arg.split("=", 1)[0];
|
|
18699
|
+
if (BRANCH_NO_CREATE.has(flag))
|
|
18700
|
+
return null;
|
|
18701
|
+
if (BRANCH_RENAME.has(flag))
|
|
18702
|
+
rename = true;
|
|
18703
|
+
else if (!arg.startsWith("-"))
|
|
18704
|
+
positionals.push(arg);
|
|
18705
|
+
}
|
|
18706
|
+
if (positionals.length < 1 || positionals.length > 2)
|
|
18707
|
+
return null;
|
|
18708
|
+
return rename ? positionals.at(-1) ?? null : positionals[0];
|
|
18709
|
+
};
|
|
18710
|
+
var gitBranchCreation = (args) => {
|
|
18711
|
+
const [sub, second] = args;
|
|
18712
|
+
const target = sub === "switch" ? flagValue(args, SWITCH_CREATE) : sub === "checkout" ? flagValue(args, CHECKOUT_CREATE) : sub === "branch" ? branchCommandTarget(args) : sub === "worktree" && second === "add" ? worktreeTarget(args.slice(1)) : null;
|
|
18713
|
+
return target && !target.startsWith("-") && !UNKNOWABLE.test(target) ? target : null;
|
|
18714
|
+
};
|
|
18715
|
+
var shellBranchTargets = (command, dialect = "posix") => {
|
|
18716
|
+
if (!/\bgit\b/u.test(command))
|
|
18717
|
+
return [];
|
|
18718
|
+
const found = [];
|
|
18719
|
+
for (const invocation of rawInvocations(command, dialect)) {
|
|
18720
|
+
if (invocation.tool !== "git")
|
|
18721
|
+
continue;
|
|
18722
|
+
const branch = gitBranchCreation(invocation.args);
|
|
18723
|
+
if (branch)
|
|
18724
|
+
found.push({ branch, dir: invocation.dir });
|
|
18725
|
+
}
|
|
18726
|
+
return found;
|
|
18727
|
+
};
|
|
18728
|
+
var localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
|
|
18729
|
+
cwd: dir,
|
|
18730
|
+
stdio: "ignore",
|
|
18731
|
+
timeout: 5000
|
|
18732
|
+
}).status === 0;
|
|
18733
|
+
var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
18734
|
+
let judged = false;
|
|
18735
|
+
for (const target of shellBranchTargets(command, dialect)) {
|
|
18736
|
+
if (target.dir === undefined)
|
|
18737
|
+
continue;
|
|
18738
|
+
const dir = target.dir === null ? root : path20.resolve(root, target.dir);
|
|
18739
|
+
if (target.dir !== null && (!existsSync6(dir) || !inWorkitWorkspace(dir)))
|
|
18740
|
+
continue;
|
|
18741
|
+
if (localBranchExists(dir, target.branch))
|
|
18742
|
+
continue;
|
|
18743
|
+
judged = true;
|
|
18744
|
+
const check = validateBranchNameFor(dir, target.branch);
|
|
18745
|
+
if (!check.ok)
|
|
18746
|
+
return check;
|
|
18747
|
+
}
|
|
18748
|
+
return judged ? { ok: true } : null;
|
|
18749
|
+
};
|
|
18750
|
+
|
|
18751
|
+
// packages/workit-core/src/hooks/policy.ts
|
|
18752
|
+
var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
18753
|
+
const policy = shellBranchPolicyViolation(cwd, command, dialect);
|
|
18754
|
+
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
18755
|
+
};
|
|
18756
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
18757
|
+
import { realpathSync as realpathSync5 } from "node:fs";
|
|
18758
|
+
import path21 from "node:path";
|
|
18759
|
+
var NONE3 = { kind: "none" };
|
|
18760
|
+
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
18761
|
+
var canonical2 = (file) => {
|
|
18762
|
+
let rest = "";
|
|
18763
|
+
for (let dir = path21.resolve(file);; dir = path21.dirname(dir)) {
|
|
18764
|
+
try {
|
|
18765
|
+
return path21.join(realpathSync5(dir), rest);
|
|
18766
|
+
} catch {
|
|
18767
|
+
if (path21.dirname(dir) === dir)
|
|
18768
|
+
return path21.resolve(file);
|
|
18769
|
+
rest = rest ? path21.join(path21.basename(dir), rest) : path21.basename(dir);
|
|
18770
|
+
}
|
|
18771
|
+
}
|
|
18772
|
+
};
|
|
18773
|
+
var exempt = (cwd, root, file, plans) => {
|
|
18774
|
+
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
18775
|
+
const relative = path21.relative(fold(canonical2(root)), fold(canonical2(path21.resolve(cwd, file))));
|
|
18776
|
+
if (relative.startsWith("..") || path21.isAbsolute(relative))
|
|
18777
|
+
return true;
|
|
18778
|
+
const posix = relative.split(path21.sep).join("/");
|
|
18779
|
+
const segments = posix.split("/");
|
|
18780
|
+
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
18781
|
+
};
|
|
18782
|
+
var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
18783
|
+
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
18784
|
+
var editsOf = (input) => {
|
|
18785
|
+
let words = input;
|
|
18786
|
+
while (words.length && (PREFIXES3.has(words[0]) || /^\w+=/.test(words[0])))
|
|
18787
|
+
words = words.slice(1);
|
|
18788
|
+
const [verb, ...args] = words;
|
|
18789
|
+
const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
18790
|
+
if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
|
|
18791
|
+
return [];
|
|
18792
|
+
switch (name) {
|
|
18793
|
+
case "touch":
|
|
18794
|
+
case "mkdir":
|
|
18795
|
+
case "rm":
|
|
18796
|
+
case "rmdir":
|
|
18797
|
+
case "tee":
|
|
18798
|
+
case "mv":
|
|
18799
|
+
case "truncate":
|
|
18800
|
+
return operands(args);
|
|
18801
|
+
case "cp":
|
|
18802
|
+
case "ln":
|
|
18803
|
+
case "install": {
|
|
18804
|
+
const target = args.indexOf("-t");
|
|
18805
|
+
if (target >= 0)
|
|
18806
|
+
return args[target + 1] ? [args[target + 1]] : null;
|
|
18807
|
+
const last = operands(args).at(-1);
|
|
18808
|
+
return last ? [last] : null;
|
|
18809
|
+
}
|
|
18810
|
+
case "dd":
|
|
18811
|
+
return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
|
|
18812
|
+
case "sed":
|
|
18813
|
+
case "perl": {
|
|
18814
|
+
if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
|
|
18815
|
+
return [];
|
|
18816
|
+
const scripted = args.some((item) => item === "-e" || item === "-f");
|
|
18817
|
+
const files = [];
|
|
18818
|
+
for (let index = 0;index < args.length; index++) {
|
|
18819
|
+
if (args[index] === "-e" || args[index] === "-f")
|
|
18820
|
+
index++;
|
|
18821
|
+
else if (!args[index].startsWith("-"))
|
|
18822
|
+
files.push(args[index]);
|
|
18823
|
+
}
|
|
18824
|
+
return scripted ? files : files.slice(1);
|
|
18825
|
+
}
|
|
18826
|
+
case "patch":
|
|
18827
|
+
return null;
|
|
18828
|
+
case "set-content":
|
|
18829
|
+
case "add-content":
|
|
18830
|
+
case "out-file":
|
|
18831
|
+
case "new-item":
|
|
18832
|
+
case "ni":
|
|
18833
|
+
case "remove-item":
|
|
18834
|
+
case "del":
|
|
18835
|
+
case "copy-item":
|
|
18836
|
+
case "move-item": {
|
|
18837
|
+
const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
|
|
18838
|
+
if (flag >= 0)
|
|
18839
|
+
return args[flag + 1] ? [args[flag + 1]] : null;
|
|
18840
|
+
const files = operands(args);
|
|
18841
|
+
if (name === "move-item")
|
|
18842
|
+
return files.length ? files : null;
|
|
18843
|
+
const file = name === "copy-item" ? files.at(-1) : files[0];
|
|
18844
|
+
return file ? [file] : null;
|
|
18845
|
+
}
|
|
18846
|
+
case "git": {
|
|
18847
|
+
const sub = operands(args)[0];
|
|
18848
|
+
if (sub === "apply")
|
|
18849
|
+
return null;
|
|
18850
|
+
if (sub === "restore" || sub === "mv" || sub === "rm")
|
|
18851
|
+
return operands(args).slice(1);
|
|
18852
|
+
if (sub === "checkout" && args.includes("--"))
|
|
18853
|
+
return args.slice(args.indexOf("--") + 1);
|
|
18854
|
+
return [];
|
|
18855
|
+
}
|
|
18856
|
+
default:
|
|
18857
|
+
return [];
|
|
18858
|
+
}
|
|
18859
|
+
};
|
|
18860
|
+
var shellWrites = (command) => {
|
|
18861
|
+
const targets = [];
|
|
18862
|
+
let unknown = false;
|
|
18863
|
+
for (const segment of segmentsOf(command)) {
|
|
18864
|
+
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
18865
|
+
const edits = editsOf(segment.words);
|
|
18866
|
+
if (edits === null)
|
|
18867
|
+
unknown = true;
|
|
18868
|
+
else
|
|
18869
|
+
targets.push(...edits);
|
|
18870
|
+
}
|
|
18871
|
+
if (!unknown && targets.length === 0)
|
|
18872
|
+
return { writes: false, targets: null };
|
|
18873
|
+
return { writes: true, targets: unknown ? null : targets };
|
|
18874
|
+
};
|
|
18875
|
+
var cache2 = new Map;
|
|
18876
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path21.resolve(root, file)) ?? "-").join("\x00");
|
|
18877
|
+
var gateFor = (cwd) => {
|
|
18878
|
+
const store = new TaskStore(cwd);
|
|
18879
|
+
const listed = store.listTaskIndex();
|
|
18880
|
+
if (!listed.ok)
|
|
18881
|
+
return null;
|
|
18882
|
+
const key = store.currentKey();
|
|
18883
|
+
if (!key.ok)
|
|
18884
|
+
return null;
|
|
18885
|
+
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
18886
|
+
if (!entry)
|
|
18887
|
+
return { blockers: [], root: store.root, plans: new Set };
|
|
18888
|
+
const ledger = ledgerPath(store.root);
|
|
18889
|
+
const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
|
|
18890
|
+
const hit = cache2.get(store.root);
|
|
18891
|
+
if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
|
|
18892
|
+
return hit;
|
|
18893
|
+
const task = store.readTask(entry.id);
|
|
18894
|
+
if (!task.ok)
|
|
18895
|
+
return null;
|
|
18896
|
+
const workspace = store.readWorkspace();
|
|
18897
|
+
const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
|
|
18898
|
+
const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
|
|
18899
|
+
const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
|
|
18900
|
+
return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
|
|
18901
|
+
});
|
|
18902
|
+
const gate = {
|
|
18903
|
+
blockers,
|
|
18904
|
+
root: store.root,
|
|
18905
|
+
plans,
|
|
18906
|
+
key: cacheKey,
|
|
18907
|
+
planKey: planKeyOf(store.root, plans)
|
|
18908
|
+
};
|
|
18909
|
+
cache2.set(store.root, gate);
|
|
18910
|
+
if (cache2.size > 32)
|
|
18911
|
+
cache2.delete(cache2.keys().next().value);
|
|
18912
|
+
return gate;
|
|
18913
|
+
};
|
|
18914
|
+
function writeGate(cwd, paths) {
|
|
18915
|
+
try {
|
|
18916
|
+
const gate = gateFor(cwd);
|
|
18917
|
+
if (!gate || gate.blockers.length === 0)
|
|
18918
|
+
return NONE3;
|
|
18919
|
+
if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
|
|
18920
|
+
return NONE3;
|
|
18921
|
+
const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
|
|
18922
|
+
return {
|
|
18923
|
+
kind: "deny",
|
|
18924
|
+
reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
|
|
18925
|
+
unblock: gate.blockers[0].reason
|
|
18926
|
+
};
|
|
18927
|
+
} catch {
|
|
18928
|
+
return NONE3;
|
|
18929
|
+
}
|
|
18930
|
+
}
|
|
18598
18931
|
// packages/workit-core/src/hooks/stop.ts
|
|
18932
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
18599
18933
|
var NONE4 = { kind: "none" };
|
|
18600
18934
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
18601
18935
|
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;
|
|
@@ -18616,7 +18950,7 @@ var asksUser = (message) => {
|
|
|
18616
18950
|
};
|
|
18617
18951
|
var short = (sha) => (sha ?? "?").slice(0, 12);
|
|
18618
18952
|
var gitList = (cwd, args) => {
|
|
18619
|
-
const run =
|
|
18953
|
+
const run = spawnSync13("git", args, {
|
|
18620
18954
|
cwd,
|
|
18621
18955
|
encoding: "utf8",
|
|
18622
18956
|
timeout: 3000,
|
|
@@ -18979,13 +19313,13 @@ ${line}` };
|
|
|
18979
19313
|
return decision;
|
|
18980
19314
|
};
|
|
18981
19315
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
18982
|
-
import { existsSync as
|
|
18983
|
-
import
|
|
19316
|
+
import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
19317
|
+
import path22 from "node:path";
|
|
18984
19318
|
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18985
19319
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
18986
19320
|
var optionalText = (value) => typeof value === "string" ? value : null;
|
|
18987
19321
|
var existingDirectory = (value) => {
|
|
18988
|
-
if (!nonEmpty2(value) || !
|
|
19322
|
+
if (!nonEmpty2(value) || !path22.isAbsolute(value) || !existsSync7(value))
|
|
18989
19323
|
return null;
|
|
18990
19324
|
try {
|
|
18991
19325
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -19074,17 +19408,20 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
19074
19408
|
const role = workitRole(descriptor, event.agentType);
|
|
19075
19409
|
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
19076
19410
|
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
|
|
19411
|
+
const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
|
|
19077
19412
|
if (role === "implementer" && isolated)
|
|
19078
|
-
return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${ISOLATED_GIT}
|
|
19413
|
+
return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${ISOLATED_GIT} Plain git is expected here, and the branch name is still policy-checked: a raw \`git switch -c\`, \`git checkout -b\`, \`git branch\` or \`git worktree add -b\` onto a protected or non-compliant name is denied, so use a policy-compliant branch.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
19079
19414
|
if (isolated)
|
|
19080
19415
|
return `${ISOLATED_GIT}
|
|
19081
19416
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
19082
19417
|
if (role === "implementer")
|
|
19083
|
-
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (
|
|
19418
|
+
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
19084
19419
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
19085
19420
|
if (role !== "judge" || !event.agentId)
|
|
19086
19421
|
return readOnly;
|
|
19087
19422
|
const own = subagentSession(session, event.agentId);
|
|
19423
|
+
if (session)
|
|
19424
|
+
recordIssuedSession(input.cwd || process.cwd(), session, own);
|
|
19088
19425
|
return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> --session ${own} ...\` so the ledger tells it apart from the author.`;
|
|
19089
19426
|
};
|
|
19090
19427
|
function handleHook(input, deps) {
|
|
@@ -19118,7 +19455,7 @@ function handleHook(input, deps) {
|
|
|
19118
19455
|
if (skillRead)
|
|
19119
19456
|
recordSkillLoad(input, skillRead, "read");
|
|
19120
19457
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
19121
|
-
const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE5;
|
|
19458
|
+
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE5;
|
|
19122
19459
|
if (policy.kind !== "none")
|
|
19123
19460
|
return policy;
|
|
19124
19461
|
const postTool = usable2(descriptor.events["shell.post"].support);
|
|
@@ -19716,12 +20053,12 @@ var codexAdapter = {
|
|
|
19716
20053
|
};
|
|
19717
20054
|
},
|
|
19718
20055
|
render: render2,
|
|
19719
|
-
addendum: (input) => `<workit-codex-mutations>Codex MCP is read-only: unattested callers cannot mutate. Run workit verbs with the workit CLI on the shell (node_modules/.bin/workit, or npx -y @brainervirus/workit-cli): check, git branch|commit|push, pr, ci, stack, ledger, handoff; task-family mutations take --json${input.session.id ? ` --actor ${input.session.id}` : ""}. workit acts as this thread (CODEX_THREAD_ID${input.session.id ? `=${input.session.id}` : ""}) unless WORKIT_SESSION_ID is set. A verifier or reviewer records workit ledger verdict under its own session, never the author's: a workit-verifier or workit-reviewer agent uses the --session its SubagentStart context names; any other
|
|
20056
|
+
addendum: (input) => `<workit-codex-mutations>Codex MCP is read-only: unattested callers cannot mutate. Run workit verbs with the workit CLI on the shell (node_modules/.bin/workit, or npx -y @brainervirus/workit-cli): check, git branch|commit|push, pr, ci, stack, ledger, handoff; task-family mutations take --json${input.session.id ? ` --actor ${input.session.id}` : ""}. workit acts as this thread (CODEX_THREAD_ID${input.session.id ? `=${input.session.id}` : ""}) unless WORKIT_SESSION_ID is set. A verifier or reviewer records workit ledger verdict under its own session, never the author's: a workit-verifier or workit-reviewer agent uses the --session its SubagentStart context names; any other passes --as verifier (an id derived from its own thread). A hand-minted id such as <lead>-v1 is refused as forged_session. Merge and release need a workspace grant (workit grant show); a question answer is not host permission.</workit-codex-mutations>`
|
|
19720
20057
|
};
|
|
19721
20058
|
|
|
19722
20059
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
19723
|
-
import
|
|
19724
|
-
import
|
|
20060
|
+
import fs17 from "node:fs";
|
|
20061
|
+
import path23 from "node:path";
|
|
19725
20062
|
var undocumented = { support: "undocumented", native: null };
|
|
19726
20063
|
var none = { support: "none", native: null };
|
|
19727
20064
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -19918,17 +20255,17 @@ var protocolEvent2 = (input) => {
|
|
|
19918
20255
|
};
|
|
19919
20256
|
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
19920
20257
|
var lastAssistantText = (file) => {
|
|
19921
|
-
if (!file || !
|
|
20258
|
+
if (!file || !path23.isAbsolute(file))
|
|
19922
20259
|
return null;
|
|
19923
20260
|
try {
|
|
19924
|
-
const size =
|
|
20261
|
+
const size = fs17.statSync(file).size;
|
|
19925
20262
|
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
19926
20263
|
const buffer = Buffer.alloc(size - start);
|
|
19927
|
-
const fd =
|
|
20264
|
+
const fd = fs17.openSync(file, "r");
|
|
19928
20265
|
try {
|
|
19929
|
-
|
|
20266
|
+
fs17.readSync(fd, buffer, 0, buffer.length, start);
|
|
19930
20267
|
} finally {
|
|
19931
|
-
|
|
20268
|
+
fs17.closeSync(fd);
|
|
19932
20269
|
}
|
|
19933
20270
|
const lines = buffer.toString("utf8").split(`
|
|
19934
20271
|
`);
|
|
@@ -19955,7 +20292,7 @@ var lastAssistantText = (file) => {
|
|
|
19955
20292
|
return null;
|
|
19956
20293
|
}
|
|
19957
20294
|
};
|
|
19958
|
-
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(
|
|
20295
|
+
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path23.sep) ? root : `${root}${path23.sep}`);
|
|
19959
20296
|
var hookCwd = (input) => {
|
|
19960
20297
|
const cwd = existingDirectory(input.cwd);
|
|
19961
20298
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|