@brainervirus/workit-cli 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1174 -16
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -73071,15 +73071,1106 @@ var init_stack = __esm(() => {
|
|
|
73071
73071
|
run17 = notImplemented("stack", "S12");
|
|
73072
73072
|
});
|
|
73073
73073
|
|
|
73074
|
+
// packages/workit-core/src/git/rev.ts
|
|
73075
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
73076
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
73077
|
+
import fs15 from "node:fs";
|
|
73078
|
+
import os12 from "node:os";
|
|
73079
|
+
import path34 from "node:path";
|
|
73080
|
+
function headSha(cwd) {
|
|
73081
|
+
return line(git(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
|
|
73082
|
+
}
|
|
73083
|
+
function worktreeTree(cwd, options = {}) {
|
|
73084
|
+
const top = line(git(cwd, ["rev-parse", "--show-toplevel"]));
|
|
73085
|
+
const indexPath = line(git(cwd, ["rev-parse", "--path-format=absolute", "--git-path", "index"]));
|
|
73086
|
+
if (!top || !indexPath)
|
|
73087
|
+
return null;
|
|
73088
|
+
const cap = options.maxUntrackedBytes ?? MAX_UNTRACKED_BYTES;
|
|
73089
|
+
const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.worktree;
|
|
73090
|
+
const scratch = fs15.mkdtempSync(path34.join(os12.tmpdir(), "workit-tree-"));
|
|
73091
|
+
try {
|
|
73092
|
+
const tempIndex = path34.join(scratch, "index");
|
|
73093
|
+
if (fs15.existsSync(indexPath))
|
|
73094
|
+
seedIndex(indexPath, tempIndex);
|
|
73095
|
+
const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
|
|
73096
|
+
const untracked = git(top, ["ls-files", "--others", "--exclude-standard", "-z"], {
|
|
73097
|
+
env,
|
|
73098
|
+
timeoutMs
|
|
73099
|
+
});
|
|
73100
|
+
if (!untracked.ok)
|
|
73101
|
+
return null;
|
|
73102
|
+
const skipped = [];
|
|
73103
|
+
for (const file of untracked.stdout.split("\x00").filter(Boolean)) {
|
|
73104
|
+
try {
|
|
73105
|
+
const stat = fs15.statSync(path34.join(top, file));
|
|
73106
|
+
if (stat.isFile() && stat.size > cap)
|
|
73107
|
+
skipped.push({ path: file, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) });
|
|
73108
|
+
} catch {}
|
|
73109
|
+
}
|
|
73110
|
+
skipped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
73111
|
+
const added = skipped.length ? git(top, ["add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"], {
|
|
73112
|
+
env,
|
|
73113
|
+
timeoutMs,
|
|
73114
|
+
input: [".", ...skipped.map((file) => `:(exclude,literal)${file.path}`)].join("\x00")
|
|
73115
|
+
}) : git(top, ["add", "-A"], { env, timeoutMs });
|
|
73116
|
+
if (!added.ok)
|
|
73117
|
+
return null;
|
|
73118
|
+
const tree = line(git(top, ["write-tree"], { env, timeoutMs }));
|
|
73119
|
+
if (!tree)
|
|
73120
|
+
return null;
|
|
73121
|
+
const base = line(git(top, ["rev-parse", "--verify", "-q", "HEAD^{tree}"])) ?? emptyTree(top);
|
|
73122
|
+
const key = skipped.length ? `sha256:${createHash12("sha256").update(JSON.stringify({ tree, skipped })).digest("hex")}` : tree;
|
|
73123
|
+
return { tree, key, dirty: tree !== base || skipped.length > 0, skipped };
|
|
73124
|
+
} finally {
|
|
73125
|
+
fs15.rmSync(scratch, { recursive: true, force: true });
|
|
73126
|
+
}
|
|
73127
|
+
}
|
|
73128
|
+
function patchId(cwd, base, head) {
|
|
73129
|
+
if (!safeArg(base) || !safeArg(head))
|
|
73130
|
+
return null;
|
|
73131
|
+
const diff = git(cwd, [
|
|
73132
|
+
"diff",
|
|
73133
|
+
"-U3",
|
|
73134
|
+
"--diff-algorithm=myers",
|
|
73135
|
+
"--indent-heuristic",
|
|
73136
|
+
"--no-color",
|
|
73137
|
+
"--no-ext-diff",
|
|
73138
|
+
"--no-textconv",
|
|
73139
|
+
"--no-renames",
|
|
73140
|
+
"--full-index",
|
|
73141
|
+
"--src-prefix=a/",
|
|
73142
|
+
"--dst-prefix=b/",
|
|
73143
|
+
`${base}...${head}`,
|
|
73144
|
+
"--"
|
|
73145
|
+
]);
|
|
73146
|
+
if (!diff.ok || !diff.stdout.trim())
|
|
73147
|
+
return null;
|
|
73148
|
+
const id = git(cwd, ["patch-id", "--stable"], { input: diff.stdout });
|
|
73149
|
+
return line(id)?.split(/\s+/u)[0] ?? null;
|
|
73150
|
+
}
|
|
73151
|
+
function currentBranch2(cwd) {
|
|
73152
|
+
return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
|
|
73153
|
+
}
|
|
73154
|
+
var MAX_BUFFER, GIT_TIMEOUTS, git = (cwd, args, options = {}) => {
|
|
73155
|
+
const result = spawnSync13("git", args, {
|
|
73156
|
+
cwd,
|
|
73157
|
+
encoding: "utf8",
|
|
73158
|
+
env: options.env ?? process.env,
|
|
73159
|
+
input: options.input,
|
|
73160
|
+
maxBuffer: MAX_BUFFER,
|
|
73161
|
+
timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
|
|
73162
|
+
killSignal: "SIGKILL",
|
|
73163
|
+
windowsHide: true
|
|
73164
|
+
});
|
|
73165
|
+
const error = result.error;
|
|
73166
|
+
return {
|
|
73167
|
+
ok: result.status === 0,
|
|
73168
|
+
stdout: result.stdout ?? "",
|
|
73169
|
+
stderr: result.stderr ?? "",
|
|
73170
|
+
timedOut: error?.code === "ETIMEDOUT"
|
|
73171
|
+
};
|
|
73172
|
+
}, line = (run) => {
|
|
73173
|
+
if (!run.ok)
|
|
73174
|
+
return null;
|
|
73175
|
+
const value = run.stdout.trim();
|
|
73176
|
+
return value ? value : null;
|
|
73177
|
+
}, safeArg = (value) => value.length > 0 && !value.startsWith("-"), emptyTree = (cwd) => line(git(cwd, ["mktree"], { input: "" })), MAX_UNTRACKED_BYTES, seedIndex = (from, to) => {
|
|
73178
|
+
fs15.copyFileSync(from, to);
|
|
73179
|
+
const { atimeNs, mtimeNs } = fs15.statSync(from, { bigint: true });
|
|
73180
|
+
const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
|
|
73181
|
+
fs15.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
|
|
73182
|
+
};
|
|
73183
|
+
var init_rev = __esm(() => {
|
|
73184
|
+
MAX_BUFFER = 256 * 1024 * 1024;
|
|
73185
|
+
GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
|
|
73186
|
+
MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
|
|
73187
|
+
});
|
|
73188
|
+
|
|
73189
|
+
// packages/workit-core/src/ledger.ts
|
|
73190
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
73191
|
+
import { createHash as createHash13, randomBytes as randomBytes2 } from "node:crypto";
|
|
73192
|
+
import fs16 from "node:fs";
|
|
73193
|
+
import path35 from "node:path";
|
|
73194
|
+
function resolveCommit(cwd, ref) {
|
|
73195
|
+
if (!safeRef(ref))
|
|
73196
|
+
return null;
|
|
73197
|
+
return git2(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
73198
|
+
}
|
|
73199
|
+
function branchHead(cwd, branch) {
|
|
73200
|
+
if (!safeRef(branch))
|
|
73201
|
+
return null;
|
|
73202
|
+
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
73203
|
+
}
|
|
73204
|
+
function defaultBase2(cwd) {
|
|
73205
|
+
const originHead = git2(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
|
|
73206
|
+
if (originHead && resolveCommit(cwd, originHead))
|
|
73207
|
+
return originHead;
|
|
73208
|
+
for (const candidate of ["origin/main", "origin/master", "main", "master"])
|
|
73209
|
+
if (resolveCommit(cwd, candidate))
|
|
73210
|
+
return candidate;
|
|
73211
|
+
return null;
|
|
73212
|
+
}
|
|
73213
|
+
function diffHash(cwd, base, head) {
|
|
73214
|
+
if (!safeRef(base) || !safeRef(head))
|
|
73215
|
+
return null;
|
|
73216
|
+
const diff = gitRun(cwd, [
|
|
73217
|
+
"diff",
|
|
73218
|
+
"-U3",
|
|
73219
|
+
"--diff-algorithm=myers",
|
|
73220
|
+
"--indent-heuristic",
|
|
73221
|
+
"--no-color",
|
|
73222
|
+
"--no-ext-diff",
|
|
73223
|
+
"--no-textconv",
|
|
73224
|
+
"--no-renames",
|
|
73225
|
+
"--full-index",
|
|
73226
|
+
"--binary",
|
|
73227
|
+
"--src-prefix=a/",
|
|
73228
|
+
"--dst-prefix=b/",
|
|
73229
|
+
`${base}...${head}`,
|
|
73230
|
+
"--"
|
|
73231
|
+
], GIT_TIMEOUTS.worktree);
|
|
73232
|
+
if (!diff.ok || !diff.stdout)
|
|
73233
|
+
return null;
|
|
73234
|
+
const hash = createHash13("sha256");
|
|
73235
|
+
for (const line of diff.stdout.split(`
|
|
73236
|
+
`)) {
|
|
73237
|
+
if (line.startsWith("index "))
|
|
73238
|
+
continue;
|
|
73239
|
+
hash.update(line.startsWith("@@ ") ? "@@" : line);
|
|
73240
|
+
hash.update(`
|
|
73241
|
+
`);
|
|
73242
|
+
}
|
|
73243
|
+
return `sha256:${hash.digest("hex")}`;
|
|
73244
|
+
}
|
|
73245
|
+
function storeRoot(cwd) {
|
|
73246
|
+
const run = gitRun(cwd, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
73247
|
+
const common = run.stdout.trim();
|
|
73248
|
+
if (run.ok && common)
|
|
73249
|
+
return {
|
|
73250
|
+
ok: true,
|
|
73251
|
+
value: { root: path35.join(path35.resolve(cwd, common), "workit"), shared: true }
|
|
73252
|
+
};
|
|
73253
|
+
if (!run.ok && NOT_A_REPO.test(run.stderr))
|
|
73254
|
+
return { ok: true, value: { root: path35.join(path35.resolve(cwd), ".workit"), shared: false } };
|
|
73255
|
+
return err("unavailable", `cannot locate the workit store: git rev-parse failed (${run.stderr.trim().split(`
|
|
73256
|
+
`)[0] || "no output"})`, "fix the repository (git status should work here) or run from a non-git directory");
|
|
73257
|
+
}
|
|
73258
|
+
function ledgerPath(cwd) {
|
|
73259
|
+
const root = storeRoot(cwd);
|
|
73260
|
+
return root.ok ? { ok: true, value: path35.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
73261
|
+
}
|
|
73262
|
+
function actorFromEnv(env) {
|
|
73263
|
+
const value = (key) => {
|
|
73264
|
+
const raw = env[key]?.trim();
|
|
73265
|
+
return raw ? raw : null;
|
|
73266
|
+
};
|
|
73267
|
+
return {
|
|
73268
|
+
host: value("WORKIT_HOST") ?? "cli",
|
|
73269
|
+
session: value("WORKIT_SESSION_ID"),
|
|
73270
|
+
agentId: value("WORKIT_AGENT_ID")
|
|
73271
|
+
};
|
|
73272
|
+
}
|
|
73273
|
+
function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
|
|
73274
|
+
const lock = `${file}.lock`;
|
|
73275
|
+
const deadline = Date.now() + waitMs;
|
|
73276
|
+
let token = null;
|
|
73277
|
+
for (;; ) {
|
|
73278
|
+
token = ledgerLock.acquire(lock);
|
|
73279
|
+
if (token)
|
|
73280
|
+
break;
|
|
73281
|
+
const observed = lockToken(lock);
|
|
73282
|
+
const age = lockAgeMs(lock);
|
|
73283
|
+
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
73284
|
+
continue;
|
|
73285
|
+
if (Date.now() >= deadline)
|
|
73286
|
+
return null;
|
|
73287
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
73288
|
+
}
|
|
73289
|
+
try {
|
|
73290
|
+
return fn();
|
|
73291
|
+
} finally {
|
|
73292
|
+
ledgerLock.release(lock, token);
|
|
73293
|
+
}
|
|
73294
|
+
}
|
|
73295
|
+
function appendLine(file, line, options) {
|
|
73296
|
+
const write = () => {
|
|
73297
|
+
const fd = fs16.openSync(file, "a+");
|
|
73298
|
+
try {
|
|
73299
|
+
const size = fs16.fstatSync(fd).size;
|
|
73300
|
+
let fence = "";
|
|
73301
|
+
if (size > 0) {
|
|
73302
|
+
const last = Buffer.alloc(1);
|
|
73303
|
+
fs16.readSync(fd, last, 0, 1, size - 1);
|
|
73304
|
+
if (last[0] !== 10)
|
|
73305
|
+
fence = `
|
|
73306
|
+
`;
|
|
73307
|
+
}
|
|
73308
|
+
const bytes = Buffer.from(fence + line);
|
|
73309
|
+
const written = fs16.writeSync(fd, bytes);
|
|
73310
|
+
if (written !== bytes.length)
|
|
73311
|
+
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
73312
|
+
return { ok: true, value: undefined };
|
|
73313
|
+
} finally {
|
|
73314
|
+
fs16.closeSync(fd);
|
|
73315
|
+
}
|
|
73316
|
+
};
|
|
73317
|
+
try {
|
|
73318
|
+
fs16.mkdirSync(path35.dirname(file), { recursive: true });
|
|
73319
|
+
if (!options.forceLock && !onNetworkFs(path35.dirname(file)))
|
|
73320
|
+
return write();
|
|
73321
|
+
const locked = withLedgerLock(file, write);
|
|
73322
|
+
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
73323
|
+
} catch (error) {
|
|
73324
|
+
return err("unavailable", `cannot append to ${file}: ${error.message}`);
|
|
73325
|
+
}
|
|
73326
|
+
}
|
|
73327
|
+
function appendRaw(cwd, row, options) {
|
|
73328
|
+
const { full, line } = serialize(row, options.now ?? new Date);
|
|
73329
|
+
const bytes = Buffer.byteLength(line);
|
|
73330
|
+
if (bytes > MAX_LINE_BYTES)
|
|
73331
|
+
return err("invalid_input", `ledger row is ${bytes} bytes; the limit is ${MAX_LINE_BYTES}. Shorten the text and pass details by --ref <path|url>`);
|
|
73332
|
+
const file = ledgerPath(cwd);
|
|
73333
|
+
if (!file.ok)
|
|
73334
|
+
return file;
|
|
73335
|
+
const written = appendLine(file.value, line, options);
|
|
73336
|
+
return written.ok ? { ok: true, value: full } : written;
|
|
73337
|
+
}
|
|
73338
|
+
function appendRow(cwd, row, options = {}) {
|
|
73339
|
+
if (row.observer === "workit_cli")
|
|
73340
|
+
return err("invalid_input", 'observer "workit_cli" is reserved for appendObserved');
|
|
73341
|
+
return appendRaw(cwd, row, options);
|
|
73342
|
+
}
|
|
73343
|
+
function supersedeAllowed(by, target) {
|
|
73344
|
+
if (by.type !== target.type)
|
|
73345
|
+
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
73346
|
+
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
73347
|
+
return "only the session that wrote a row can supersede it";
|
|
73348
|
+
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
73349
|
+
return "a self verdict cannot replace an independent verdict";
|
|
73350
|
+
return null;
|
|
73351
|
+
}
|
|
73352
|
+
function readLedger(cwd) {
|
|
73353
|
+
const file = ledgerPath(cwd);
|
|
73354
|
+
if (!file.ok)
|
|
73355
|
+
return file;
|
|
73356
|
+
let text = "";
|
|
73357
|
+
try {
|
|
73358
|
+
text = fs16.readFileSync(file.value, "utf8");
|
|
73359
|
+
} catch (error) {
|
|
73360
|
+
if (error.code === "ENOENT")
|
|
73361
|
+
return { ok: true, value: { path: file.value, rows: [], skipped: 0 } };
|
|
73362
|
+
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
73363
|
+
}
|
|
73364
|
+
const rows = [];
|
|
73365
|
+
let skipped = 0;
|
|
73366
|
+
for (const raw of text.split(`
|
|
73367
|
+
`)) {
|
|
73368
|
+
const trimmed = raw.trim();
|
|
73369
|
+
if (!trimmed)
|
|
73370
|
+
continue;
|
|
73371
|
+
let parsed;
|
|
73372
|
+
try {
|
|
73373
|
+
parsed = JSON.parse(trimmed);
|
|
73374
|
+
} catch {
|
|
73375
|
+
skipped += 1;
|
|
73376
|
+
continue;
|
|
73377
|
+
}
|
|
73378
|
+
if (!isRecord5(parsed) || !str(parsed.id) || !str(parsed.type) || !str(parsed.at)) {
|
|
73379
|
+
skipped += 1;
|
|
73380
|
+
continue;
|
|
73381
|
+
}
|
|
73382
|
+
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
73383
|
+
rows.push({
|
|
73384
|
+
...parsed,
|
|
73385
|
+
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
73386
|
+
id: parsed.id,
|
|
73387
|
+
at: parsed.at,
|
|
73388
|
+
type: parsed.type,
|
|
73389
|
+
actor: normalizeActor(parsed.actor),
|
|
73390
|
+
branch: str(parsed.branch),
|
|
73391
|
+
head: str(parsed.head),
|
|
73392
|
+
base: str(parsed.base),
|
|
73393
|
+
baseSha: str(parsed.baseSha),
|
|
73394
|
+
patchId: str(parsed.patchId),
|
|
73395
|
+
diffHash: str(parsed.diffHash),
|
|
73396
|
+
tree: str(parsed.tree),
|
|
73397
|
+
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
73398
|
+
...pr === undefined ? {} : { pr },
|
|
73399
|
+
...str(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
73400
|
+
seq: rows.length + 1,
|
|
73401
|
+
superseded: false,
|
|
73402
|
+
supersedeIgnored: false
|
|
73403
|
+
});
|
|
73404
|
+
}
|
|
73405
|
+
const byId = new Map;
|
|
73406
|
+
for (const row of rows) {
|
|
73407
|
+
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
73408
|
+
if (target && supersedeAllowed(row, target) === null)
|
|
73409
|
+
target.superseded = true;
|
|
73410
|
+
else if (row.supersedes)
|
|
73411
|
+
row.supersedeIgnored = true;
|
|
73412
|
+
if (!byId.has(row.id))
|
|
73413
|
+
byId.set(row.id, row);
|
|
73414
|
+
}
|
|
73415
|
+
return { ok: true, value: { path: file.value, rows, skipped } };
|
|
73416
|
+
}
|
|
73417
|
+
function filterRows(rows, filter) {
|
|
73418
|
+
const matched = rows.filter((row) => (filter.branch === undefined || row.branch === filter.branch) && (filter.pr === undefined || row.pr === filter.pr) && (filter.type === undefined || row.type === filter.type));
|
|
73419
|
+
return filter.last === undefined ? matched : matched.slice(Math.max(0, matched.length - filter.last));
|
|
73420
|
+
}
|
|
73421
|
+
function branchForPr(cwd, rows, pr) {
|
|
73422
|
+
for (let index = rows.length - 1;index >= 0; index -= 1) {
|
|
73423
|
+
const row = rows[index];
|
|
73424
|
+
if (row.pr === pr && row.branch && PR_ROW_TYPES.has(row.type) && row.observer === "workit_cli")
|
|
73425
|
+
return { ok: true, branch: row.branch, source: "pr_row" };
|
|
73426
|
+
}
|
|
73427
|
+
const tips = git2(cwd, [
|
|
73428
|
+
"for-each-ref",
|
|
73429
|
+
"--format=%(objectname)",
|
|
73430
|
+
`refs/pull/${pr}/head`,
|
|
73431
|
+
`refs/merge-requests/${pr}/head`,
|
|
73432
|
+
`refs/remotes/*/pull/${pr}/head`,
|
|
73433
|
+
`refs/remotes/*/merge-requests/${pr}/head`
|
|
73434
|
+
]);
|
|
73435
|
+
const shas = [...new Set((tips ?? "").split(`
|
|
73436
|
+
`).filter(Boolean))];
|
|
73437
|
+
if (shas.length === 0)
|
|
73438
|
+
return { ok: false, reason: "unknown", candidates: [] };
|
|
73439
|
+
if (shas.length > 1)
|
|
73440
|
+
return { ok: false, reason: "ambiguous", candidates: shas };
|
|
73441
|
+
const branches = (git2(cwd, ["for-each-ref", "--format=%(refname:short)", "--points-at", shas[0], "refs/heads"]) ?? "").split(`
|
|
73442
|
+
`).filter(Boolean);
|
|
73443
|
+
if (branches.length === 1)
|
|
73444
|
+
return { ok: true, branch: branches[0], source: "forge_ref" };
|
|
73445
|
+
return branches.length === 0 ? { ok: false, reason: "unknown", candidates: [] } : { ok: false, reason: "ambiguous", candidates: branches };
|
|
73446
|
+
}
|
|
73447
|
+
function codeKey(cwd, options = {}) {
|
|
73448
|
+
const checkedOut = currentBranch2(cwd);
|
|
73449
|
+
const branch = options.branch ?? checkedOut;
|
|
73450
|
+
const head = branch ? branchHead(cwd, branch) : headSha(cwd);
|
|
73451
|
+
const base = options.base ?? defaultBase2(cwd);
|
|
73452
|
+
const baseSha = base ? resolveCommit(cwd, base) : null;
|
|
73453
|
+
const onTree = branch !== null && branch === checkedOut;
|
|
73454
|
+
const tree = onTree ? worktreeTree(cwd) : null;
|
|
73455
|
+
return {
|
|
73456
|
+
branch,
|
|
73457
|
+
head,
|
|
73458
|
+
base,
|
|
73459
|
+
baseSha,
|
|
73460
|
+
patchId: base && head ? patchId(cwd, base, head) : null,
|
|
73461
|
+
diffHash: base && head ? diffHash(cwd, base, head) : null,
|
|
73462
|
+
tree: tree?.key ?? null,
|
|
73463
|
+
dirty: tree ? tree.dirty : null
|
|
73464
|
+
};
|
|
73465
|
+
}
|
|
73466
|
+
function verdictBasis(verdict, current) {
|
|
73467
|
+
if (verdict.head && verdict.head === current.head)
|
|
73468
|
+
return "fresh";
|
|
73469
|
+
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
73470
|
+
return "carried";
|
|
73471
|
+
return "stale";
|
|
73472
|
+
}
|
|
73473
|
+
function trailerSessions(cwd, base, head) {
|
|
73474
|
+
if (!base || !head || !safeRef(base) || !safeRef(head))
|
|
73475
|
+
return [];
|
|
73476
|
+
const out = git2(cwd, [
|
|
73477
|
+
"log",
|
|
73478
|
+
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
73479
|
+
`${base}..${head}`
|
|
73480
|
+
]);
|
|
73481
|
+
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
73482
|
+
}
|
|
73483
|
+
function authorSessions(cwd, rows, branch, range = { base: null, head: null }) {
|
|
73484
|
+
const sessions = new Set;
|
|
73485
|
+
for (const row of rows) {
|
|
73486
|
+
if (row.branch !== branch || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
73487
|
+
continue;
|
|
73488
|
+
const session = str(row.session) ?? row.actor.session;
|
|
73489
|
+
if (session)
|
|
73490
|
+
sessions.add(session);
|
|
73491
|
+
}
|
|
73492
|
+
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
73493
|
+
sessions.add(session);
|
|
73494
|
+
return sessions;
|
|
73495
|
+
}
|
|
73496
|
+
function effectiveOf(rows, authors) {
|
|
73497
|
+
let effective = null;
|
|
73498
|
+
for (const row of rows) {
|
|
73499
|
+
if (!effective) {
|
|
73500
|
+
effective = row;
|
|
73501
|
+
continue;
|
|
73502
|
+
}
|
|
73503
|
+
const rowIndependent = independenceReasons(row, authors).length === 0;
|
|
73504
|
+
const effIndependent = independenceReasons(effective, authors).length === 0;
|
|
73505
|
+
if (effIndependent && !rowIndependent)
|
|
73506
|
+
continue;
|
|
73507
|
+
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session)
|
|
73508
|
+
continue;
|
|
73509
|
+
effective = row;
|
|
73510
|
+
}
|
|
73511
|
+
return effective;
|
|
73512
|
+
}
|
|
73513
|
+
function checkVerdicts(cwd, branch, rows) {
|
|
73514
|
+
const head = branchHead(cwd, branch);
|
|
73515
|
+
const fallbackBase = defaultBase2(cwd);
|
|
73516
|
+
const keyCache = new Map;
|
|
73517
|
+
const keyFor = (base) => {
|
|
73518
|
+
if (!base || !head)
|
|
73519
|
+
return { patchId: null, diffHash: null };
|
|
73520
|
+
let key = keyCache.get(base);
|
|
73521
|
+
if (!key) {
|
|
73522
|
+
key = { patchId: patchId(cwd, base, head), diffHash: diffHash(cwd, base, head) };
|
|
73523
|
+
keyCache.set(base, key);
|
|
73524
|
+
}
|
|
73525
|
+
return key;
|
|
73526
|
+
};
|
|
73527
|
+
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head });
|
|
73528
|
+
const byKind = new Map;
|
|
73529
|
+
for (const row of rows)
|
|
73530
|
+
if (row.branch === branch && isLiveVerdict(row)) {
|
|
73531
|
+
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
73532
|
+
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
73533
|
+
}
|
|
73534
|
+
const verdicts = [];
|
|
73535
|
+
for (const [kind, list] of byKind) {
|
|
73536
|
+
const row = effectiveOf(list, authors);
|
|
73537
|
+
if (!row)
|
|
73538
|
+
continue;
|
|
73539
|
+
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
73540
|
+
const current = basis !== "stale";
|
|
73541
|
+
const independence = independenceReasons(row, authors);
|
|
73542
|
+
const reasons = [
|
|
73543
|
+
...current ? [] : ["stale"],
|
|
73544
|
+
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
73545
|
+
...independence
|
|
73546
|
+
];
|
|
73547
|
+
verdicts.push({
|
|
73548
|
+
kind,
|
|
73549
|
+
basis,
|
|
73550
|
+
current,
|
|
73551
|
+
independent: independence.length === 0,
|
|
73552
|
+
accepted: reasons.length === 0,
|
|
73553
|
+
reasons,
|
|
73554
|
+
verdict: row
|
|
73555
|
+
});
|
|
73556
|
+
}
|
|
73557
|
+
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
73558
|
+
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
73559
|
+
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
73560
|
+
const newestAccepted = verdicts.findLast((entry) => entry.accepted) ?? null;
|
|
73561
|
+
const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
73562
|
+
accepted: false,
|
|
73563
|
+
verdict: failing?.verdict ?? null,
|
|
73564
|
+
reasons: failing ? ["failing_verdict"] : newestCurrent?.reasons ?? ["no_verdict"]
|
|
73565
|
+
};
|
|
73566
|
+
return {
|
|
73567
|
+
branch,
|
|
73568
|
+
head,
|
|
73569
|
+
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
73570
|
+
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
73571
|
+
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
73572
|
+
accepted,
|
|
73573
|
+
verdicts,
|
|
73574
|
+
authors: [...authors].toSorted()
|
|
73575
|
+
};
|
|
73576
|
+
}
|
|
73577
|
+
function checkSupersede(context, type, self2) {
|
|
73578
|
+
if (!context.supersedes)
|
|
73579
|
+
return { ok: true, value: undefined };
|
|
73580
|
+
const ledger = readLedger(context.cwd);
|
|
73581
|
+
if (!ledger.ok)
|
|
73582
|
+
return ledger;
|
|
73583
|
+
const target = ledger.value.rows.find((row) => row.id === context.supersedes);
|
|
73584
|
+
if (!target)
|
|
73585
|
+
return err("not_found", `no ledger row ${context.supersedes} to supersede`);
|
|
73586
|
+
const problem = supersedeAllowed({ type, actor: context.actor, self: self2 }, target);
|
|
73587
|
+
return problem ? err("invalid_input", `--supersedes: ${problem}`) : { ok: true, value: undefined };
|
|
73588
|
+
}
|
|
73589
|
+
function recordDecision(context, input) {
|
|
73590
|
+
const what = required2(input.what, "<what>");
|
|
73591
|
+
if (!what.ok)
|
|
73592
|
+
return what;
|
|
73593
|
+
const why = required2(input.why, "--why");
|
|
73594
|
+
if (!why.ok)
|
|
73595
|
+
return why;
|
|
73596
|
+
const link = checkSupersede(context, "decision");
|
|
73597
|
+
if (!link.ok)
|
|
73598
|
+
return link;
|
|
73599
|
+
return appendRow(context.cwd, {
|
|
73600
|
+
...common2(context, noteKey(context)),
|
|
73601
|
+
type: "decision",
|
|
73602
|
+
what: what.value,
|
|
73603
|
+
why: why.value,
|
|
73604
|
+
refs: input.refs ?? []
|
|
73605
|
+
}, { now: context.now });
|
|
73606
|
+
}
|
|
73607
|
+
function recordRuling(context, input) {
|
|
73608
|
+
const what = required2(input.what, "<what>");
|
|
73609
|
+
if (!what.ok)
|
|
73610
|
+
return what;
|
|
73611
|
+
const why = required2(input.why, "--why");
|
|
73612
|
+
if (!why.ok)
|
|
73613
|
+
return why;
|
|
73614
|
+
const cost = required2(input.costIfWrong, "--cost-if-wrong");
|
|
73615
|
+
if (!cost.ok)
|
|
73616
|
+
return cost;
|
|
73617
|
+
const link = checkSupersede(context, "ruling");
|
|
73618
|
+
if (!link.ok)
|
|
73619
|
+
return link;
|
|
73620
|
+
return appendRow(context.cwd, {
|
|
73621
|
+
...common2(context, noteKey(context)),
|
|
73622
|
+
type: "ruling",
|
|
73623
|
+
what: what.value,
|
|
73624
|
+
why: why.value,
|
|
73625
|
+
costIfWrong: cost.value,
|
|
73626
|
+
refs: input.refs ?? []
|
|
73627
|
+
}, { now: context.now });
|
|
73628
|
+
}
|
|
73629
|
+
function recordVerdict(context, input) {
|
|
73630
|
+
if (!VERDICT_RESULTS.includes(input.result))
|
|
73631
|
+
return err("invalid_input", `unknown verdict "${input.result}"; expected ${VERDICT_RESULTS.join("|")}`);
|
|
73632
|
+
const kind = input.kind ?? "review";
|
|
73633
|
+
if (!VERDICT_KINDS.includes(kind))
|
|
73634
|
+
return err("invalid_input", `--kind must be ${VERDICT_KINDS.join("|")}`);
|
|
73635
|
+
const surface = input.surface ?? null;
|
|
73636
|
+
if (surface !== null && !VERDICT_SURFACES.includes(surface))
|
|
73637
|
+
return err("invalid_input", `--surface must be ${VERDICT_SURFACES.join("|")}`);
|
|
73638
|
+
const how = required2(input.how, "--how");
|
|
73639
|
+
if (!how.ok)
|
|
73640
|
+
return how;
|
|
73641
|
+
const branch = context.branch ?? currentBranch2(context.cwd);
|
|
73642
|
+
if (!branch)
|
|
73643
|
+
return err("invalid_input", "HEAD is detached; name the branch the verdict is for", "pass --branch <branch>");
|
|
73644
|
+
const ledger = readLedger(context.cwd);
|
|
73645
|
+
if (!ledger.ok)
|
|
73646
|
+
return ledger;
|
|
73647
|
+
const key = codeKey(context.cwd, { branch, base: context.base });
|
|
73648
|
+
if (!key.head)
|
|
73649
|
+
return err("not_found", `branch "${branch}" has no commit to judge`);
|
|
73650
|
+
if (key.dirty === true)
|
|
73651
|
+
return err("blocked", `dirty_worktree: ${branch} has uncommitted changes, so its head is not what was judged`, "commit or stash the changes, re-check, then record the verdict");
|
|
73652
|
+
const session = context.actor.session;
|
|
73653
|
+
const selfReason = input.self === true ? "flag" : session ? null : "no_session";
|
|
73654
|
+
const self2 = selfReason !== null;
|
|
73655
|
+
if (!self2 && session && authorSessions(context.cwd, ledger.value.rows, branch, { base: key.base, head: key.head }).has(session))
|
|
73656
|
+
return err("blocked", `author_verdict: session ${session} authored ${branch}; a verdict must come from a different session`, 'have a non-author session record the verdict, or pass --self (a self verdict is never accepted by merge:"verified")');
|
|
73657
|
+
const link = checkSupersede(context, "verdict", self2);
|
|
73658
|
+
if (!link.ok)
|
|
73659
|
+
return link;
|
|
73660
|
+
return appendRow(context.cwd, {
|
|
73661
|
+
...common2(context, key),
|
|
73662
|
+
type: "verdict",
|
|
73663
|
+
kind,
|
|
73664
|
+
result: input.result,
|
|
73665
|
+
how: how.value,
|
|
73666
|
+
surface,
|
|
73667
|
+
observer: "agent_asserted",
|
|
73668
|
+
self: self2,
|
|
73669
|
+
selfReason,
|
|
73670
|
+
evidenceRefs: input.evidenceRefs ?? []
|
|
73671
|
+
}, { now: context.now });
|
|
73672
|
+
}
|
|
73673
|
+
function recordHandoff(context, input) {
|
|
73674
|
+
return appendRow(context.cwd, { ...common2(context, noteKey(context)), type: "handoff", note: input.note, next: input.next }, { now: context.now });
|
|
73675
|
+
}
|
|
73676
|
+
function rowLabels(row) {
|
|
73677
|
+
const labels = [];
|
|
73678
|
+
if (row.superseded)
|
|
73679
|
+
labels.push("superseded");
|
|
73680
|
+
if (row.supersedeIgnored || OBSERVED_ONLY.has(row.type) && row.observer !== "workit_cli")
|
|
73681
|
+
labels.push("ignored");
|
|
73682
|
+
if (row.type === "verdict" && row.self === true)
|
|
73683
|
+
labels.push("self");
|
|
73684
|
+
return labels;
|
|
73685
|
+
}
|
|
73686
|
+
function summarizeRow(row) {
|
|
73687
|
+
const text = (key) => str(row[key]) ?? "";
|
|
73688
|
+
let summary;
|
|
73689
|
+
switch (row.type) {
|
|
73690
|
+
case "decision":
|
|
73691
|
+
summary = `${text("what")} (why: ${text("why")})`;
|
|
73692
|
+
break;
|
|
73693
|
+
case "ruling":
|
|
73694
|
+
summary = `${text("what")} (why: ${text("why")}; cost if wrong: ${text("costIfWrong")})`;
|
|
73695
|
+
break;
|
|
73696
|
+
case "verdict":
|
|
73697
|
+
summary = `${text("result")} [${text("kind") || "review"}] at ${(row.head ?? "?").slice(0, 12)}${row.self === true ? ` (self${text("selfReason") === "no_session" ? ": no session" : ""})` : ""}: ${text("how")}`;
|
|
73698
|
+
break;
|
|
73699
|
+
case "handoff":
|
|
73700
|
+
summary = `next: ${text("next")}${text("note") ? ` (note: ${text("note")})` : ""}`;
|
|
73701
|
+
break;
|
|
73702
|
+
default:
|
|
73703
|
+
summary = text("what") || text("name") || row.type;
|
|
73704
|
+
}
|
|
73705
|
+
return {
|
|
73706
|
+
seq: row.seq,
|
|
73707
|
+
id: row.id,
|
|
73708
|
+
at: row.at,
|
|
73709
|
+
type: row.type,
|
|
73710
|
+
branch: row.branch,
|
|
73711
|
+
summary,
|
|
73712
|
+
labels: rowLabels(row)
|
|
73713
|
+
};
|
|
73714
|
+
}
|
|
73715
|
+
function stackPosition(root, branch) {
|
|
73716
|
+
const dir = path35.join(root, "stacks");
|
|
73717
|
+
let names = [];
|
|
73718
|
+
try {
|
|
73719
|
+
names = fs16.readdirSync(dir).filter((name) => name.endsWith(".json"));
|
|
73720
|
+
} catch {
|
|
73721
|
+
return null;
|
|
73722
|
+
}
|
|
73723
|
+
for (const name of names.toSorted()) {
|
|
73724
|
+
let parsed;
|
|
73725
|
+
try {
|
|
73726
|
+
parsed = JSON.parse(fs16.readFileSync(path35.join(dir, name), "utf8"));
|
|
73727
|
+
} catch {
|
|
73728
|
+
continue;
|
|
73729
|
+
}
|
|
73730
|
+
if (!isRecord5(parsed) || !Array.isArray(parsed.branches))
|
|
73731
|
+
continue;
|
|
73732
|
+
const branches = parsed.branches.filter(isRecord5);
|
|
73733
|
+
const index = branches.findIndex((entry) => entry.branch === branch);
|
|
73734
|
+
if (index < 0)
|
|
73735
|
+
continue;
|
|
73736
|
+
const entry = branches[index];
|
|
73737
|
+
return {
|
|
73738
|
+
name: str(parsed.name) ?? name.replace(/\.json$/u, ""),
|
|
73739
|
+
index: index + 1,
|
|
73740
|
+
size: branches.length,
|
|
73741
|
+
parent: str(entry.parent),
|
|
73742
|
+
pr: typeof entry.pr === "number" ? entry.pr : null
|
|
73743
|
+
};
|
|
73744
|
+
}
|
|
73745
|
+
return null;
|
|
73746
|
+
}
|
|
73747
|
+
function buildHandoff(cwd, options = {}) {
|
|
73748
|
+
const store = storeRoot(cwd);
|
|
73749
|
+
if (!store.ok)
|
|
73750
|
+
return store;
|
|
73751
|
+
const read = readLedger(cwd);
|
|
73752
|
+
if (!read.ok)
|
|
73753
|
+
return read;
|
|
73754
|
+
const ledger = read.value;
|
|
73755
|
+
const branch = currentBranch2(cwd);
|
|
73756
|
+
const head = headSha(cwd);
|
|
73757
|
+
const base = defaultBase2(cwd);
|
|
73758
|
+
let ahead = null;
|
|
73759
|
+
let behind = null;
|
|
73760
|
+
if (base && head) {
|
|
73761
|
+
const counts = git2(cwd, ["rev-list", "--left-right", "--count", `${base}...HEAD`]);
|
|
73762
|
+
const [left, right] = (counts ?? "").split(/\s+/u).map(Number);
|
|
73763
|
+
if (Number.isFinite(left) && Number.isFinite(right)) {
|
|
73764
|
+
behind = left;
|
|
73765
|
+
ahead = right;
|
|
73766
|
+
}
|
|
73767
|
+
}
|
|
73768
|
+
const mine = branch ? filterRows(ledger.rows, { branch }) : [];
|
|
73769
|
+
const stack = branch ? stackPosition(store.value.root, branch) : null;
|
|
73770
|
+
const pr = stack?.pr ?? mine.findLast((row) => row.pr !== undefined && PR_ROW_TYPES.has(row.type) && row.observer === "workit_cli")?.pr ?? null;
|
|
73771
|
+
const check = branch ? checkVerdicts(cwd, branch, ledger.rows) : null;
|
|
73772
|
+
const checkRows = mine.filter((row) => row.type === "check" && row.observer === "workit_cli" && str(row.name));
|
|
73773
|
+
const latestChecks = new Map;
|
|
73774
|
+
for (const row of checkRows)
|
|
73775
|
+
latestChecks.set(str(row.name), row);
|
|
73776
|
+
const tree = latestChecks.size ? worktreeTree(cwd)?.key ?? null : null;
|
|
73777
|
+
const checks = [...latestChecks.entries()].map(([name, row]) => ({
|
|
73778
|
+
name,
|
|
73779
|
+
result: str(row.result),
|
|
73780
|
+
fresh: tree !== null && row.tree === tree,
|
|
73781
|
+
at: row.at
|
|
73782
|
+
}));
|
|
73783
|
+
const lastHandoffRow = mine.findLast((row) => row.type === "handoff");
|
|
73784
|
+
const partial = {
|
|
73785
|
+
store: { root: store.value.root, shared: store.value.shared, ledger: ledger.path },
|
|
73786
|
+
branch,
|
|
73787
|
+
head,
|
|
73788
|
+
subject: head ? git2(cwd, ["log", "-1", "--format=%s", head]) : null,
|
|
73789
|
+
base,
|
|
73790
|
+
ahead,
|
|
73791
|
+
behind,
|
|
73792
|
+
dirty: dirtyState(cwd),
|
|
73793
|
+
pr,
|
|
73794
|
+
stack,
|
|
73795
|
+
verdict: {
|
|
73796
|
+
current: check?.current.basis ?? "none",
|
|
73797
|
+
accepted: check?.accepted.accepted ?? false,
|
|
73798
|
+
reasons: check?.accepted.reasons ?? ["no_verdict"],
|
|
73799
|
+
verdicts: (check?.verdicts ?? []).map(({ verdict: row, ...rest }) => ({
|
|
73800
|
+
...rest,
|
|
73801
|
+
summary: summarizeRow(row).summary
|
|
73802
|
+
}))
|
|
73803
|
+
},
|
|
73804
|
+
checks,
|
|
73805
|
+
rulings: mine.filter((row) => row.type === "ruling" && !row.superseded).slice(-5).map(summarizeRow),
|
|
73806
|
+
recent: ledger.rows.slice(-(options.last ?? 10)).map(summarizeRow),
|
|
73807
|
+
lastHandoff: lastHandoffRow ? {
|
|
73808
|
+
at: lastHandoffRow.at,
|
|
73809
|
+
note: str(lastHandoffRow.note),
|
|
73810
|
+
next: str(lastHandoffRow.next) ?? ""
|
|
73811
|
+
} : null,
|
|
73812
|
+
note: options.note?.trim() || null,
|
|
73813
|
+
skippedRows: ledger.skipped
|
|
73814
|
+
};
|
|
73815
|
+
const nextCommand = nextCommandFor(partial);
|
|
73816
|
+
return {
|
|
73817
|
+
ok: true,
|
|
73818
|
+
value: { ...partial, nextCommand, next: options.next?.trim() || nextCommand }
|
|
73819
|
+
};
|
|
73820
|
+
}
|
|
73821
|
+
var LEDGER_VERSION = 1, MAX_LINE_BYTES = 4096, VERDICT_RESULTS, PASSING, FAILING, VERDICT_KINDS, VERDICT_SURFACES, PR_ROW_TYPES, err = (code, error, unblock) => ({
|
|
73822
|
+
ok: false,
|
|
73823
|
+
code,
|
|
73824
|
+
error,
|
|
73825
|
+
...unblock ? { unblock } : {}
|
|
73826
|
+
}), gitRun = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
73827
|
+
const run = spawnSync14("git", args, {
|
|
73828
|
+
cwd,
|
|
73829
|
+
encoding: "utf8",
|
|
73830
|
+
timeout: timeoutMs,
|
|
73831
|
+
killSignal: "SIGKILL",
|
|
73832
|
+
windowsHide: true,
|
|
73833
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
73834
|
+
env: {
|
|
73835
|
+
...process.env,
|
|
73836
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
73837
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
73838
|
+
LC_ALL: "C",
|
|
73839
|
+
LANGUAGE: "C"
|
|
73840
|
+
}
|
|
73841
|
+
});
|
|
73842
|
+
return {
|
|
73843
|
+
ok: run.status === 0,
|
|
73844
|
+
stdout: run.stdout ?? "",
|
|
73845
|
+
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
73846
|
+
};
|
|
73847
|
+
}, git2 = (cwd, args) => {
|
|
73848
|
+
const run = gitRun(cwd, args);
|
|
73849
|
+
const value = run.ok ? run.stdout.trim() : "";
|
|
73850
|
+
return value ? value : null;
|
|
73851
|
+
}, safeRef = (value) => value.length > 0 && !value.startsWith("-"), NOT_A_REPO, newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`, NETWORK_FS_TYPES, isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0), onNetworkFs = (dir) => {
|
|
73852
|
+
if (process.platform !== "linux")
|
|
73853
|
+
return false;
|
|
73854
|
+
try {
|
|
73855
|
+
return isNetworkFsType(fs16.statfsSync(dir).type);
|
|
73856
|
+
} catch {
|
|
73857
|
+
return false;
|
|
73858
|
+
}
|
|
73859
|
+
}, LOCK_WAIT_MS = 5000, LOCK_STALE_MS = 30000, LOCK_OWNER = "owner", lockToken = (lock) => {
|
|
73860
|
+
try {
|
|
73861
|
+
return fs16.readFileSync(path35.join(lock, LOCK_OWNER), "utf8");
|
|
73862
|
+
} catch {
|
|
73863
|
+
return null;
|
|
73864
|
+
}
|
|
73865
|
+
}, lockAgeMs = (lock) => {
|
|
73866
|
+
for (const target of [path35.join(lock, LOCK_OWNER), lock]) {
|
|
73867
|
+
try {
|
|
73868
|
+
return Date.now() - fs16.statSync(target).mtimeMs;
|
|
73869
|
+
} catch {}
|
|
73870
|
+
}
|
|
73871
|
+
return null;
|
|
73872
|
+
}, ledgerLock, serialize = (row, now) => {
|
|
73873
|
+
const full = { v: LEDGER_VERSION, id: newId2(), at: now.toISOString(), ...row };
|
|
73874
|
+
return { full, line: `${JSON.stringify(full)}
|
|
73875
|
+
` };
|
|
73876
|
+
}, isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str = (value) => typeof value === "string" ? value : null, normalizeActor = (value) => {
|
|
73877
|
+
const actor = isRecord5(value) ? value : {};
|
|
73878
|
+
return {
|
|
73879
|
+
host: str(actor.host) ?? "unknown",
|
|
73880
|
+
session: str(actor.session),
|
|
73881
|
+
agentId: str(actor.agentId)
|
|
73882
|
+
};
|
|
73883
|
+
}, isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded, independenceReasons = (row, authors) => {
|
|
73884
|
+
const reasons = [];
|
|
73885
|
+
if (!row.actor.session)
|
|
73886
|
+
reasons.push("no_session");
|
|
73887
|
+
else if (authors.has(row.actor.session))
|
|
73888
|
+
reasons.push("author_session");
|
|
73889
|
+
if (row.self === true && !reasons.includes("no_session"))
|
|
73890
|
+
reasons.push("self");
|
|
73891
|
+
return reasons;
|
|
73892
|
+
}, common2 = (context, key) => ({
|
|
73893
|
+
...key,
|
|
73894
|
+
actor: context.actor,
|
|
73895
|
+
...context.pr === undefined ? {} : { pr: context.pr },
|
|
73896
|
+
...context.supersedes ? { supersedes: context.supersedes } : {}
|
|
73897
|
+
}), noteKey = (context) => {
|
|
73898
|
+
const branch = context.branch ?? currentBranch2(context.cwd);
|
|
73899
|
+
return {
|
|
73900
|
+
branch,
|
|
73901
|
+
head: branch ? branchHead(context.cwd, branch) : headSha(context.cwd),
|
|
73902
|
+
base: null,
|
|
73903
|
+
baseSha: null,
|
|
73904
|
+
patchId: null,
|
|
73905
|
+
diffHash: null,
|
|
73906
|
+
tree: null,
|
|
73907
|
+
dirty: null
|
|
73908
|
+
};
|
|
73909
|
+
}, required2 = (value, flag) => {
|
|
73910
|
+
const text = value?.trim();
|
|
73911
|
+
return text ? { ok: true, value: text } : err("invalid_input", `${flag} is required`);
|
|
73912
|
+
}, OBSERVED_ONLY, MAX_DIRTY_PATHS = 10, dirtyState = (cwd) => {
|
|
73913
|
+
const run = gitRun(cwd, ["status", "--porcelain=v1", "-z", "--untracked-files=normal"], GIT_TIMEOUTS.worktree);
|
|
73914
|
+
if (!run.ok)
|
|
73915
|
+
return { dirty: false, files: 0, paths: [] };
|
|
73916
|
+
const paths = [];
|
|
73917
|
+
const entries = run.stdout.split("\x00");
|
|
73918
|
+
for (let index = 0;index < entries.length; index += 1) {
|
|
73919
|
+
const entry = entries[index];
|
|
73920
|
+
if (entry.length < 4)
|
|
73921
|
+
continue;
|
|
73922
|
+
paths.push(entry.slice(3));
|
|
73923
|
+
if (entry[0] === "R" || entry[0] === "C")
|
|
73924
|
+
index += 1;
|
|
73925
|
+
}
|
|
73926
|
+
return { dirty: paths.length > 0, files: paths.length, paths: paths.slice(0, MAX_DIRTY_PATHS) };
|
|
73927
|
+
}, nextCommandFor = (brief) => {
|
|
73928
|
+
const branchFlag = brief.branch ? ` --branch ${brief.branch}` : "";
|
|
73929
|
+
const verify = `workit ledger verdict verified${branchFlag} --how "<method and evidence>" (from a session that did not author the branch, with WORKIT_SESSION_ID set)`;
|
|
73930
|
+
if (!brief.head)
|
|
73931
|
+
return "make the first commit on this branch, then: workit handoff";
|
|
73932
|
+
if (brief.dirty.dirty)
|
|
73933
|
+
return `commit or stash the ${brief.dirty.files} changed file(s), then: workit handoff`;
|
|
73934
|
+
const stale = brief.checks.find((check) => !check.fresh);
|
|
73935
|
+
if (stale)
|
|
73936
|
+
return `workit check ${stale.name}`;
|
|
73937
|
+
const { verdict } = brief;
|
|
73938
|
+
if (verdict.reasons.includes("failing_verdict"))
|
|
73939
|
+
return `fix what the failing verdict found, then get a new independent verdict: ${verify}`;
|
|
73940
|
+
if (!verdict.accepted)
|
|
73941
|
+
return verify;
|
|
73942
|
+
return brief.pr === null ? "workit pr create" : `workit pr status --pr ${brief.pr}`;
|
|
73943
|
+
};
|
|
73944
|
+
var init_ledger = __esm(() => {
|
|
73945
|
+
init_rev();
|
|
73946
|
+
VERDICT_RESULTS = [
|
|
73947
|
+
"verified",
|
|
73948
|
+
"tests-verified",
|
|
73949
|
+
"type-check-only",
|
|
73950
|
+
"blocked",
|
|
73951
|
+
"failed"
|
|
73952
|
+
];
|
|
73953
|
+
PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
73954
|
+
FAILING = new Set(["failed", "blocked"]);
|
|
73955
|
+
VERDICT_KINDS = ["unit", "live", "perf", "review"];
|
|
73956
|
+
VERDICT_SURFACES = ["ui", "cli", "api"];
|
|
73957
|
+
PR_ROW_TYPES = new Set(["pr.created", "pr.status"]);
|
|
73958
|
+
NOT_A_REPO = /not a git repository \(or any (of the parent directories|parent up to)/u;
|
|
73959
|
+
NETWORK_FS_TYPES = new Set([
|
|
73960
|
+
26985,
|
|
73961
|
+
20859,
|
|
73962
|
+
4266872130,
|
|
73963
|
+
4283649346,
|
|
73964
|
+
1702057286,
|
|
73965
|
+
1397113167,
|
|
73966
|
+
12805120,
|
|
73967
|
+
16914839,
|
|
73968
|
+
1196443219,
|
|
73969
|
+
198183888
|
|
73970
|
+
]);
|
|
73971
|
+
ledgerLock = {
|
|
73972
|
+
acquire(lock) {
|
|
73973
|
+
try {
|
|
73974
|
+
fs16.mkdirSync(lock);
|
|
73975
|
+
} catch (error) {
|
|
73976
|
+
if (error.code === "EEXIST")
|
|
73977
|
+
return null;
|
|
73978
|
+
throw error;
|
|
73979
|
+
}
|
|
73980
|
+
const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
|
|
73981
|
+
fs16.writeFileSync(path35.join(lock, LOCK_OWNER), token);
|
|
73982
|
+
return token;
|
|
73983
|
+
},
|
|
73984
|
+
release(lock, token) {
|
|
73985
|
+
if (lockToken(lock) !== token)
|
|
73986
|
+
return false;
|
|
73987
|
+
const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
|
|
73988
|
+
try {
|
|
73989
|
+
fs16.renameSync(lock, tomb);
|
|
73990
|
+
} catch {
|
|
73991
|
+
return false;
|
|
73992
|
+
}
|
|
73993
|
+
if (lockToken(tomb) !== token) {
|
|
73994
|
+
try {
|
|
73995
|
+
fs16.renameSync(tomb, lock);
|
|
73996
|
+
} catch {}
|
|
73997
|
+
return false;
|
|
73998
|
+
}
|
|
73999
|
+
fs16.rmSync(tomb, { recursive: true, force: true });
|
|
74000
|
+
return true;
|
|
74001
|
+
},
|
|
74002
|
+
token: lockToken,
|
|
74003
|
+
reap(lock, observed) {
|
|
74004
|
+
const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
|
|
74005
|
+
try {
|
|
74006
|
+
fs16.renameSync(lock, tomb);
|
|
74007
|
+
} catch {
|
|
74008
|
+
return false;
|
|
74009
|
+
}
|
|
74010
|
+
if (lockToken(tomb) === observed) {
|
|
74011
|
+
fs16.rmSync(tomb, { recursive: true, force: true });
|
|
74012
|
+
return true;
|
|
74013
|
+
}
|
|
74014
|
+
try {
|
|
74015
|
+
fs16.renameSync(tomb, lock);
|
|
74016
|
+
} catch {
|
|
74017
|
+
fs16.rmSync(tomb, { recursive: true, force: true });
|
|
74018
|
+
}
|
|
74019
|
+
return false;
|
|
74020
|
+
}
|
|
74021
|
+
};
|
|
74022
|
+
OBSERVED_ONLY = new Set(["check", ...PR_ROW_TYPES]);
|
|
74023
|
+
});
|
|
74024
|
+
|
|
73074
74025
|
// packages/workit-cli/src/verbs/ledger.ts
|
|
73075
74026
|
var exports_ledger = {};
|
|
73076
74027
|
__export(exports_ledger, {
|
|
73077
74028
|
run: () => run18
|
|
73078
74029
|
});
|
|
73079
|
-
|
|
73080
|
-
|
|
73081
|
-
|
|
73082
|
-
|
|
74030
|
+
import { parseArgs } from "node:util";
|
|
74031
|
+
function targetBranch(io, rows, branch, pr) {
|
|
74032
|
+
if (pr !== undefined) {
|
|
74033
|
+
const resolved = branchForPr(io.cwd, rows, pr);
|
|
74034
|
+
if (!resolved.ok)
|
|
74035
|
+
return {
|
|
74036
|
+
ok: false,
|
|
74037
|
+
code: "invalid_input",
|
|
74038
|
+
error: resolved.reason === "ambiguous" ? `cannot resolve PR ${pr} to a branch: ambiguous (${resolved.candidates.join(", ")})` : `cannot resolve PR ${pr} to a branch: no workit pr row records it and no fetched forge ref (refs/pull/${pr}/head) points at a local branch tip`,
|
|
74039
|
+
unblock: "pass --branch <branch> instead, or fetch the PR ref"
|
|
74040
|
+
};
|
|
74041
|
+
if (branch !== undefined && branch !== resolved.branch)
|
|
74042
|
+
return {
|
|
74043
|
+
ok: false,
|
|
74044
|
+
code: "invalid_input",
|
|
74045
|
+
error: `PR ${pr} is branch ${resolved.branch}, not ${branch}`
|
|
74046
|
+
};
|
|
74047
|
+
return { ok: true, value: resolved.branch };
|
|
74048
|
+
}
|
|
74049
|
+
return { ok: true, value: branch ?? null };
|
|
74050
|
+
}
|
|
74051
|
+
async function run18(argv, io) {
|
|
74052
|
+
let parsed;
|
|
74053
|
+
try {
|
|
74054
|
+
parsed = parseArgs({ args: argv, options: OPTIONS, allowPositionals: true, strict: true });
|
|
74055
|
+
} catch (error) {
|
|
74056
|
+
return usage3(io, error.message);
|
|
74057
|
+
}
|
|
74058
|
+
const { values } = parsed;
|
|
74059
|
+
let positionals = parsed.positionals;
|
|
74060
|
+
if (positionals[0] === "add") {
|
|
74061
|
+
positionals = positionals.slice(1);
|
|
74062
|
+
if (!["decision", "ruling", "verdict"].includes(positionals[0] ?? ""))
|
|
74063
|
+
return usage3(io, "ledger add takes decision|ruling|verdict");
|
|
74064
|
+
}
|
|
74065
|
+
const [sub, ...rest] = positionals;
|
|
74066
|
+
const pr = positiveInt(values.pr, "--pr");
|
|
74067
|
+
if (pr instanceof Error)
|
|
74068
|
+
return usage3(io, pr.message);
|
|
74069
|
+
const last = positiveInt(values.last, "--last");
|
|
74070
|
+
if (last instanceof Error)
|
|
74071
|
+
return usage3(io, last.message);
|
|
74072
|
+
const text = rest.join(" ");
|
|
74073
|
+
if (sub === undefined)
|
|
74074
|
+
return usage3(io, "missing subcommand");
|
|
74075
|
+
if (!["decision", "ruling", "verdict", "list", "show", "check"].includes(sub))
|
|
74076
|
+
return usage3(io, `unknown ledger subcommand "${sub}"`);
|
|
74077
|
+
const ledger = readLedger(io.cwd);
|
|
74078
|
+
if (!ledger.ok)
|
|
74079
|
+
return failed(io, ledger);
|
|
74080
|
+
const { rows } = ledger.value;
|
|
74081
|
+
if (sub === "list" || sub === "show") {
|
|
74082
|
+
if (rest.length)
|
|
74083
|
+
return usage3(io, `unexpected argument: ${rest[0]}`);
|
|
74084
|
+
const listed = filterRows(rows, {
|
|
74085
|
+
branch: values.branch,
|
|
74086
|
+
pr,
|
|
74087
|
+
type: values.type,
|
|
74088
|
+
last: last ?? 50
|
|
74089
|
+
});
|
|
74090
|
+
return emit(io, ok({ path: ledger.value.path, skipped: ledger.value.skipped, rows: listed }), (data) => data.rows.length ? data.rows.map((row) => {
|
|
74091
|
+
const line = summarizeRow(row);
|
|
74092
|
+
return `#${line.seq} ${line.at} ${line.type}${line.branch ? ` [${line.branch}]` : ""}${line.labels.length ? ` (${line.labels.join(", ")})` : ""} ${line.summary}`;
|
|
74093
|
+
}) : "ledger is empty");
|
|
74094
|
+
}
|
|
74095
|
+
const isRead = sub === "check" || sub === "verdict" && values.how === undefined && !VERDICT_RESULTS.includes(rest[0] ?? "");
|
|
74096
|
+
if (isRead) {
|
|
74097
|
+
if (rest.length > (sub === "verdict" ? 1 : 0))
|
|
74098
|
+
return usage3(io, `unexpected argument: ${rest.at(-1)}`);
|
|
74099
|
+
const target = targetBranch(io, rows, rest[0] ?? values.branch, pr);
|
|
74100
|
+
if (!target.ok)
|
|
74101
|
+
return failed(io, target);
|
|
74102
|
+
const branch = target.value ?? currentBranch2(io.cwd);
|
|
74103
|
+
if (!branch)
|
|
74104
|
+
return emit(io, fail("invalid_input", "HEAD is detached; name the branch", {
|
|
74105
|
+
unblock: "pass --branch <branch>"
|
|
74106
|
+
}));
|
|
74107
|
+
return emit(io, ok(checkVerdicts(io.cwd, branch, rows)), verdictLines);
|
|
74108
|
+
}
|
|
74109
|
+
const target = targetBranch(io, rows, values.branch, pr);
|
|
74110
|
+
if (!target.ok)
|
|
74111
|
+
return failed(io, target);
|
|
74112
|
+
const context = {
|
|
74113
|
+
cwd: io.cwd,
|
|
74114
|
+
actor: actorFromEnv(io.env),
|
|
74115
|
+
branch: target.value,
|
|
74116
|
+
base: values.base ?? null,
|
|
74117
|
+
...pr === undefined ? {} : { pr },
|
|
74118
|
+
...values.supersedes ? { supersedes: values.supersedes } : {}
|
|
74119
|
+
};
|
|
74120
|
+
if (sub === "decision")
|
|
74121
|
+
return fromResult(io, recordDecision(context, { what: text, why: values.why, refs: values.ref }), (row) => `recorded decision ${row.id}`);
|
|
74122
|
+
if (sub === "ruling")
|
|
74123
|
+
return fromResult(io, recordRuling(context, {
|
|
74124
|
+
what: text,
|
|
74125
|
+
why: values.why,
|
|
74126
|
+
costIfWrong: values["cost-if-wrong"],
|
|
74127
|
+
refs: values.ref
|
|
74128
|
+
}), (row) => `recorded ruling ${row.id}`);
|
|
74129
|
+
if (rest.length !== 1)
|
|
74130
|
+
return usage3(io, `ledger verdict takes one result: ${VERDICT_RESULTS.join("|")}`);
|
|
74131
|
+
return fromResult(io, recordVerdict(context, {
|
|
74132
|
+
result: rest[0],
|
|
74133
|
+
kind: values.kind,
|
|
74134
|
+
how: values.how,
|
|
74135
|
+
surface: values.surface ?? null,
|
|
74136
|
+
self: values.self === true,
|
|
74137
|
+
evidenceRefs: values.evidence
|
|
74138
|
+
}), (row) => `recorded verdict ${row.id}: ${row.result} [${row.kind}] for ${row.branch} @ ${(row.head ?? "").slice(0, 12)}${row.self ? ` (self${row.selfReason === "no_session" ? ": WORKIT_SESSION_ID unset" : ""}; never accepted)` : ""}`);
|
|
74139
|
+
}
|
|
74140
|
+
var USAGE = "workit ledger decision|ruling|verdict|list|check ... (workit help ledger for the grammar)", OPTIONS, positiveInt = (value, flag) => {
|
|
74141
|
+
if (value === undefined)
|
|
74142
|
+
return;
|
|
74143
|
+
const parsed = Number(value);
|
|
74144
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : new Error(`${flag} must be a positive integer`);
|
|
74145
|
+
}, failed = (io, result) => emit(io, fail(result.code, result.error, result.unblock ? { unblock: result.unblock } : {})), fromResult = (io, result, human) => result.ok ? emit(io, ok(result.value), human) : failed(io, result), usage3 = (io, error) => emit(io, fail("invalid_input", error, { unblock: USAGE })), verdictLines = (check) => {
|
|
74146
|
+
const lines = [
|
|
74147
|
+
`${check.branch} @ ${(check.head ?? "no commit").slice(0, 12)}: current: ${check.current.basis}${typeof check.current.verdict?.result === "string" ? ` (${check.current.verdict.result})` : ""}; ${check.accepted.accepted ? "accepted" : `not accepted (${check.accepted.reasons.join(", ")})`}`
|
|
74148
|
+
];
|
|
74149
|
+
for (const entry of check.verdicts)
|
|
74150
|
+
lines.push(` ${entry.kind}: ${entry.basis}${entry.accepted ? ", accepted" : ` (${entry.reasons.join(", ")})`} ${summarizeRow(entry.verdict).summary}`);
|
|
74151
|
+
return lines;
|
|
74152
|
+
};
|
|
74153
|
+
var init_ledger2 = __esm(() => {
|
|
74154
|
+
init_ledger();
|
|
74155
|
+
init_rev();
|
|
74156
|
+
init_output();
|
|
74157
|
+
OPTIONS = {
|
|
74158
|
+
why: { type: "string" },
|
|
74159
|
+
"cost-if-wrong": { type: "string" },
|
|
74160
|
+
ref: { type: "string", multiple: true },
|
|
74161
|
+
how: { type: "string" },
|
|
74162
|
+
kind: { type: "string" },
|
|
74163
|
+
surface: { type: "string" },
|
|
74164
|
+
evidence: { type: "string", multiple: true },
|
|
74165
|
+
self: { type: "boolean" },
|
|
74166
|
+
branch: { type: "string" },
|
|
74167
|
+
base: { type: "string" },
|
|
74168
|
+
pr: { type: "string" },
|
|
74169
|
+
type: { type: "string" },
|
|
74170
|
+
last: { type: "string" },
|
|
74171
|
+
supersedes: { type: "string" },
|
|
74172
|
+
json: { type: "boolean" }
|
|
74173
|
+
};
|
|
73083
74174
|
});
|
|
73084
74175
|
|
|
73085
74176
|
// packages/workit-cli/src/verbs/handoff.ts
|
|
@@ -73087,24 +74178,92 @@ var exports_handoff = {};
|
|
|
73087
74178
|
__export(exports_handoff, {
|
|
73088
74179
|
run: () => run19
|
|
73089
74180
|
});
|
|
74181
|
+
import { parseArgs as parseArgs2 } from "node:util";
|
|
74182
|
+
function human(brief) {
|
|
74183
|
+
const lines = [
|
|
74184
|
+
`branch: ${brief.branch ?? "(detached)"} @ ${short(brief.head)}${brief.subject ? ` ${brief.subject}` : ""}`
|
|
74185
|
+
];
|
|
74186
|
+
if (brief.base)
|
|
74187
|
+
lines.push(`base: ${brief.base}${brief.ahead === null ? "" : ` (ahead ${brief.ahead}, behind ${brief.behind})`}`);
|
|
74188
|
+
lines.push(brief.dirty.dirty ? `dirty: ${brief.dirty.files} file(s): ${brief.dirty.paths.join(", ")}${brief.dirty.files > brief.dirty.paths.length ? ", …" : ""}` : "dirty: clean");
|
|
74189
|
+
if (brief.stack)
|
|
74190
|
+
lines.push(`stack: ${brief.stack.name} ${brief.stack.index}/${brief.stack.size}${brief.stack.parent ? ` on ${brief.stack.parent}` : ""}`);
|
|
74191
|
+
if (brief.pr !== null)
|
|
74192
|
+
lines.push(`pr: #${brief.pr}`);
|
|
74193
|
+
lines.push(`verdict: current ${brief.verdict.current}; ${brief.verdict.accepted ? "accepted" : `not accepted (${brief.verdict.reasons.join(", ")})`}`, ...brief.verdict.verdicts.map((entry) => ` ${entry.kind}: ${entry.basis}${entry.accepted ? ", accepted" : ` (${entry.reasons.join(", ")})`} ${entry.summary}`));
|
|
74194
|
+
if (brief.checks.length)
|
|
74195
|
+
lines.push("checks:", ...brief.checks.map((check) => ` ${check.name}: ${check.result ?? "?"} (${check.fresh ? "fresh" : "stale"})`));
|
|
74196
|
+
if (brief.rulings.length)
|
|
74197
|
+
lines.push("rulings:", ...brief.rulings.map((row) => ` #${row.seq} ${row.summary}`));
|
|
74198
|
+
if (brief.lastHandoff)
|
|
74199
|
+
lines.push(`last handoff (${brief.lastHandoff.at}): ${brief.lastHandoff.next}${brief.lastHandoff.note ? ` (note: ${brief.lastHandoff.note})` : ""}`);
|
|
74200
|
+
if (brief.recent.length)
|
|
74201
|
+
lines.push("recent ledger:", ...brief.recent.map((row) => ` #${row.seq} ${row.type}${row.branch ? ` [${row.branch}]` : ""}${row.labels.length ? ` (${row.labels.join(", ")})` : ""} ${row.summary}`));
|
|
74202
|
+
if (brief.note)
|
|
74203
|
+
lines.push(`note: ${brief.note}`);
|
|
74204
|
+
lines.push(`next: ${brief.next}`);
|
|
74205
|
+
if (brief.next !== brief.nextCommand)
|
|
74206
|
+
lines.push(`next command: ${brief.nextCommand}`);
|
|
74207
|
+
if (brief.recorded)
|
|
74208
|
+
lines.push(`recorded handoff ${brief.recorded}`);
|
|
74209
|
+
return lines;
|
|
74210
|
+
}
|
|
74211
|
+
async function resumeBrief(argv, io) {
|
|
74212
|
+
let values;
|
|
74213
|
+
try {
|
|
74214
|
+
({ values } = parseArgs2({
|
|
74215
|
+
args: argv,
|
|
74216
|
+
options: {
|
|
74217
|
+
note: { type: "string" },
|
|
74218
|
+
next: { type: "string" },
|
|
74219
|
+
record: { type: "boolean" },
|
|
74220
|
+
last: { type: "string" },
|
|
74221
|
+
json: { type: "boolean" }
|
|
74222
|
+
},
|
|
74223
|
+
allowPositionals: false,
|
|
74224
|
+
strict: true
|
|
74225
|
+
}));
|
|
74226
|
+
} catch (error) {
|
|
74227
|
+
return emit(io, fail("invalid_input", error.message, { unblock: USAGE2 }));
|
|
74228
|
+
}
|
|
74229
|
+
const last = values.last === undefined ? 10 : Number(values.last);
|
|
74230
|
+
if (!Number.isInteger(last) || last < 1)
|
|
74231
|
+
return emit(io, fail("invalid_input", "--last must be a positive integer", { unblock: USAGE2 }));
|
|
74232
|
+
const built = buildHandoff(io.cwd, {
|
|
74233
|
+
last,
|
|
74234
|
+
note: values.note ?? null,
|
|
74235
|
+
next: values.next ?? null
|
|
74236
|
+
});
|
|
74237
|
+
if (!built.ok)
|
|
74238
|
+
return emit(io, fail(built.code, built.error, built.unblock ? { unblock: built.unblock } : {}));
|
|
74239
|
+
const data = built.value;
|
|
74240
|
+
let recorded = null;
|
|
74241
|
+
if (values.record) {
|
|
74242
|
+
const row = recordHandoff({ cwd: io.cwd, actor: actorFromEnv(io.env), branch: data.branch }, { note: data.note, next: data.next });
|
|
74243
|
+
if (!row.ok)
|
|
74244
|
+
return emit(io, fail(row.code, row.error, { data: { ...data, recorded } }));
|
|
74245
|
+
recorded = row.value.id;
|
|
74246
|
+
}
|
|
74247
|
+
return emit(io, ok({ ...data, recorded }), human);
|
|
74248
|
+
}
|
|
73090
74249
|
async function run19(argv, io) {
|
|
73091
74250
|
if (!argv.includes("--task"))
|
|
73092
|
-
return
|
|
74251
|
+
return resumeBrief(argv, io);
|
|
73093
74252
|
await Promise.resolve().then(() => init_task());
|
|
73094
74253
|
return runTaskCommand(["handoff", ...argv]);
|
|
73095
74254
|
}
|
|
73096
|
-
var
|
|
74255
|
+
var USAGE2 = "workit handoff [--note <text>] [--next <text>] [--record] [--last <n>] [--json]", short = (sha) => sha ? sha.slice(0, 12) : "no commit";
|
|
73097
74256
|
var init_handoff = __esm(() => {
|
|
73098
|
-
|
|
73099
|
-
|
|
74257
|
+
init_ledger();
|
|
74258
|
+
init_output();
|
|
73100
74259
|
});
|
|
73101
74260
|
|
|
73102
74261
|
// packages/workit-cli/src/main.ts
|
|
73103
|
-
import
|
|
74262
|
+
import path36 from "node:path";
|
|
73104
74263
|
// packages/workit-cli/package.json
|
|
73105
74264
|
var package_default = {
|
|
73106
74265
|
name: "@brainervirus/workit-cli",
|
|
73107
|
-
version: "2.2.
|
|
74266
|
+
version: "2.2.2",
|
|
73108
74267
|
private: false,
|
|
73109
74268
|
description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
|
|
73110
74269
|
keywords: [
|
|
@@ -73284,15 +74443,14 @@ var VERBS = [
|
|
|
73284
74443
|
name: "ledger",
|
|
73285
74444
|
group: "delivery",
|
|
73286
74445
|
usage: "workit ledger decision|ruling|verdict|list|check ...",
|
|
73287
|
-
summary: "Record and query decisions, rulings and verdicts",
|
|
73288
|
-
|
|
73289
|
-
load: () => Promise.resolve().then(() => (init_ledger(), exports_ledger))
|
|
74446
|
+
summary: "Record and query decisions, rulings and SHA-keyed verdicts (patch-id carry-over across rebases)",
|
|
74447
|
+
load: () => Promise.resolve().then(() => (init_ledger2(), exports_ledger))
|
|
73290
74448
|
},
|
|
73291
74449
|
{
|
|
73292
74450
|
name: "handoff",
|
|
73293
74451
|
group: "delivery",
|
|
73294
|
-
usage: "workit handoff --task <id>",
|
|
73295
|
-
summary: "
|
|
74452
|
+
usage: "workit handoff [--note <t>] [--next <t>] [--record] | workit handoff --task <id>",
|
|
74453
|
+
summary: "Print a resume brief (branch, HEAD, dirty state, verdict, rulings, next command); --task exports v1 task state",
|
|
73296
74454
|
load: () => Promise.resolve().then(() => (init_handoff(), exports_handoff))
|
|
73297
74455
|
}
|
|
73298
74456
|
];
|
|
@@ -73378,7 +74536,7 @@ var defaultIo = (json) => ({
|
|
|
73378
74536
|
});
|
|
73379
74537
|
async function main(argv, overrides = {}, options = {}) {
|
|
73380
74538
|
const parsed = parseGlobals(argv);
|
|
73381
|
-
const cwd =
|
|
74539
|
+
const cwd = path36.resolve(overrides.cwd ?? process.cwd(), parsed.cwd ?? ".");
|
|
73382
74540
|
const io = { ...defaultIo(parsed.json), ...overrides, json: parsed.json, cwd };
|
|
73383
74541
|
if (parsed.error)
|
|
73384
74542
|
return emit(io, fail("invalid_input", parsed.error));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
|
|
6
6
|
"keywords": [
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"typecheck": "tsc --noEmit"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@brainervirus/workit-core": "^2.
|
|
41
|
-
"@brainervirus/workit-cursor": "^2.
|
|
42
|
-
"@brainervirus/workit-opencode": "^2.
|
|
40
|
+
"@brainervirus/workit-core": "^2.3.0",
|
|
41
|
+
"@brainervirus/workit-cursor": "^2.3.0",
|
|
42
|
+
"@brainervirus/workit-opencode": "^2.3.0",
|
|
43
43
|
"@inkjs/ui": "2.0.0",
|
|
44
44
|
"ink": "7.1.1",
|
|
45
45
|
"react": "19.2.8"
|