@brainervirus/workit-cli 8.38.4 → 8.38.5
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 +271 -92
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -9317,7 +9317,7 @@ var package_default;
|
|
|
9317
9317
|
var init_package = __esm(() => {
|
|
9318
9318
|
package_default = {
|
|
9319
9319
|
name: "@brainervirus/workit-cli",
|
|
9320
|
-
version: "8.38.
|
|
9320
|
+
version: "8.38.5",
|
|
9321
9321
|
private: false,
|
|
9322
9322
|
description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
|
|
9323
9323
|
keywords: [
|
|
@@ -9355,13 +9355,13 @@ var init_package = __esm(() => {
|
|
|
9355
9355
|
},
|
|
9356
9356
|
dependencies: {
|
|
9357
9357
|
"@babel/parser": "7.29.9",
|
|
9358
|
-
"@brainervirus/workit-cursor": "^8.38.
|
|
9358
|
+
"@brainervirus/workit-cursor": "^8.38.5",
|
|
9359
9359
|
"@inkjs/ui": "2.0.0",
|
|
9360
9360
|
ink: "7.1.1",
|
|
9361
9361
|
react: "19.2.8"
|
|
9362
9362
|
},
|
|
9363
9363
|
devDependencies: {
|
|
9364
|
-
"@brainervirus/workit-core": "^8.38.
|
|
9364
|
+
"@brainervirus/workit-core": "^8.38.5"
|
|
9365
9365
|
},
|
|
9366
9366
|
engines: {
|
|
9367
9367
|
node: ">=24"
|
|
@@ -77869,6 +77869,7 @@ function resolveResourceSettings(sources, host = hostShape()) {
|
|
|
77869
77869
|
const settings = {
|
|
77870
77870
|
slots: DEFAULT_SLOTS,
|
|
77871
77871
|
heavy: [...DEFAULT_HEAVY_CHECKS],
|
|
77872
|
+
light: [...DEFAULT_LIGHT_CHECKS],
|
|
77872
77873
|
memoryMax: "",
|
|
77873
77874
|
cpuQuota: "",
|
|
77874
77875
|
waitMs: DEFAULT_WAIT_S * 1000,
|
|
@@ -77910,8 +77911,21 @@ function resolveResourceSettings(sources, host = hostShape()) {
|
|
|
77910
77911
|
const heavy = isRecord9(value.heavy) ? Object.keys(value.heavy) : value.heavy;
|
|
77911
77912
|
if (!Array.isArray(heavy) || !heavy.every((item) => typeof item === "string"))
|
|
77912
77913
|
warn("heavy", "must be a list of check names");
|
|
77913
|
-
else
|
|
77914
|
+
else {
|
|
77914
77915
|
settings.heavy = repo ? [...new Set([...settings.heavy, ...heavy])] : heavy;
|
|
77916
|
+
settings.light = settings.light.filter((name) => !heavy.includes(name));
|
|
77917
|
+
}
|
|
77918
|
+
}
|
|
77919
|
+
if (value.light !== undefined) {
|
|
77920
|
+
const light = value.light;
|
|
77921
|
+
if (repo)
|
|
77922
|
+
machineOnly("light");
|
|
77923
|
+
else if (!Array.isArray(light) || !light.every((item) => typeof item === "string"))
|
|
77924
|
+
warn("light", "must be a list of check names");
|
|
77925
|
+
else {
|
|
77926
|
+
settings.light = light;
|
|
77927
|
+
settings.heavy = settings.heavy.filter((name) => !light.includes(name));
|
|
77928
|
+
}
|
|
77915
77929
|
}
|
|
77916
77930
|
if (value.memoryMax !== undefined) {
|
|
77917
77931
|
const next = memoryValue(value.memoryMax, host);
|
|
@@ -77932,10 +77946,11 @@ function resolveResourceSettings(sources, host = hostShape()) {
|
|
|
77932
77946
|
cpu = next;
|
|
77933
77947
|
}
|
|
77934
77948
|
if (value.wait !== undefined) {
|
|
77935
|
-
|
|
77936
|
-
|
|
77949
|
+
const wait = typeof value.wait === "number" ? value.wait >= 0 ? value.wait * 1000 : null : typeof value.wait === "string" ? parseWait(value.wait) : null;
|
|
77950
|
+
if (wait !== null)
|
|
77951
|
+
settings.waitMs = wait;
|
|
77937
77952
|
else
|
|
77938
|
-
warn("wait",
|
|
77953
|
+
warn("wait", 'must be a number of seconds or a duration ("30m")');
|
|
77939
77954
|
}
|
|
77940
77955
|
}
|
|
77941
77956
|
settings.memoryMax = String(memory);
|
|
@@ -78053,7 +78068,65 @@ function removeOwn(file, id) {
|
|
|
78053
78068
|
if (parseLease(readText5(file))?.id === id)
|
|
78054
78069
|
fs24.rmSync(file, { force: true });
|
|
78055
78070
|
}
|
|
78071
|
+
function leaseQueue(dir, probe = {}) {
|
|
78072
|
+
let names;
|
|
78073
|
+
try {
|
|
78074
|
+
names = fs24.readdirSync(queueDir(dir));
|
|
78075
|
+
} catch {
|
|
78076
|
+
return [];
|
|
78077
|
+
}
|
|
78078
|
+
const entries = [];
|
|
78079
|
+
for (const name of names.filter((item) => item.endsWith(".json")).toSorted()) {
|
|
78080
|
+
const file = path52.join(queueDir(dir), name);
|
|
78081
|
+
const record = parseLease(readText5(file));
|
|
78082
|
+
if (!record)
|
|
78083
|
+
continue;
|
|
78084
|
+
entries.push({
|
|
78085
|
+
file,
|
|
78086
|
+
holder: holderOf(record),
|
|
78087
|
+
id: record.id,
|
|
78088
|
+
stale: staleReason(record, probe)
|
|
78089
|
+
});
|
|
78090
|
+
}
|
|
78091
|
+
return entries;
|
|
78092
|
+
}
|
|
78093
|
+
function waitersAhead(options, own) {
|
|
78094
|
+
let ahead = 0;
|
|
78095
|
+
for (const entry of leaseQueue(options.dir, options.probe)) {
|
|
78096
|
+
if (path52.basename(entry.file) >= own)
|
|
78097
|
+
break;
|
|
78098
|
+
if (!TICKET.test(path52.basename(entry.file)))
|
|
78099
|
+
continue;
|
|
78100
|
+
if (entry.stale && reclaim(entry.file, (current) => !current || staleReason(current, options.probe) !== null))
|
|
78101
|
+
continue;
|
|
78102
|
+
ahead += 1;
|
|
78103
|
+
}
|
|
78104
|
+
return ahead;
|
|
78105
|
+
}
|
|
78106
|
+
function liveHolders(options) {
|
|
78107
|
+
return leaseSlots(options.dir, options.slots, options.probe).filter((slot) => slot.holder && !slot.stale).map((slot) => slot.holder);
|
|
78108
|
+
}
|
|
78109
|
+
function writeTicket(file, record) {
|
|
78110
|
+
fs24.mkdirSync(path52.dirname(file), { recursive: true, mode: 448 });
|
|
78111
|
+
fs24.writeFileSync(file, `${JSON.stringify(record)}
|
|
78112
|
+
`, { mode: 384 });
|
|
78113
|
+
}
|
|
78114
|
+
function parseWait(text) {
|
|
78115
|
+
const match = /^(\d+(?:\.\d+)?)([smh]?)$/u.exec(text.trim());
|
|
78116
|
+
if (!match)
|
|
78117
|
+
return null;
|
|
78118
|
+
const unit = { "": 1000, s: 1000, m: 60000, h: 3600000 }[match[2]] ?? 1000;
|
|
78119
|
+
return Math.round(Number(match[1]) * unit);
|
|
78120
|
+
}
|
|
78121
|
+
function formatWait(ms) {
|
|
78122
|
+
const seconds = Math.max(0, Math.round(ms / 1000));
|
|
78123
|
+
if (seconds < 60)
|
|
78124
|
+
return `${seconds}s`;
|
|
78125
|
+
const minutes = Math.floor(seconds / 60);
|
|
78126
|
+
return seconds % 60 ? `${minutes}m ${seconds % 60}s` : `${minutes}m`;
|
|
78127
|
+
}
|
|
78056
78128
|
async function acquireLease(options) {
|
|
78129
|
+
const clock = options.clock ?? realClock;
|
|
78057
78130
|
const pid = options.pid ?? process.pid;
|
|
78058
78131
|
const boot = bootId();
|
|
78059
78132
|
const record = {
|
|
@@ -78067,7 +78140,7 @@ async function acquireLease(options) {
|
|
|
78067
78140
|
check: options.check,
|
|
78068
78141
|
cwd: options.cwd,
|
|
78069
78142
|
session: options.session,
|
|
78070
|
-
startedAt: new Date().toISOString(),
|
|
78143
|
+
startedAt: new Date(clock.now()).toISOString(),
|
|
78071
78144
|
scope: null
|
|
78072
78145
|
};
|
|
78073
78146
|
try {
|
|
@@ -78075,48 +78148,104 @@ async function acquireLease(options) {
|
|
|
78075
78148
|
} catch (error) {
|
|
78076
78149
|
return { ok: false, reason: "unavailable", error: error.message };
|
|
78077
78150
|
}
|
|
78078
|
-
const started =
|
|
78151
|
+
const started = clock.now();
|
|
78079
78152
|
const reclaimed = [];
|
|
78080
78153
|
let waitedOn = [];
|
|
78081
|
-
|
|
78082
|
-
|
|
78083
|
-
|
|
78084
|
-
|
|
78085
|
-
|
|
78086
|
-
|
|
78087
|
-
|
|
78088
|
-
|
|
78089
|
-
|
|
78090
|
-
|
|
78091
|
-
|
|
78092
|
-
|
|
78093
|
-
|
|
78094
|
-
|
|
78095
|
-
|
|
78096
|
-
|
|
78097
|
-
|
|
78098
|
-
|
|
78099
|
-
|
|
78100
|
-
|
|
78101
|
-
|
|
78102
|
-
|
|
78103
|
-
|
|
78104
|
-
|
|
78105
|
-
|
|
78106
|
-
|
|
78107
|
-
|
|
78108
|
-
|
|
78154
|
+
const name = ticketName(started, record.id);
|
|
78155
|
+
const ticket = path52.join(queueDir(options.dir), name);
|
|
78156
|
+
const ticketRecord = {
|
|
78157
|
+
...record,
|
|
78158
|
+
maxAgeMs: options.waitMs + 60000
|
|
78159
|
+
};
|
|
78160
|
+
let queued = false;
|
|
78161
|
+
const onSignal = (signal) => {
|
|
78162
|
+
leave();
|
|
78163
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
78164
|
+
};
|
|
78165
|
+
const leave = () => {
|
|
78166
|
+
process.off("exit", leave);
|
|
78167
|
+
process.off("SIGINT", onSignal);
|
|
78168
|
+
process.off("SIGTERM", onSignal);
|
|
78169
|
+
if (queued)
|
|
78170
|
+
removeOwn(ticket, record.id);
|
|
78171
|
+
queued = false;
|
|
78172
|
+
};
|
|
78173
|
+
let nextStatus = 0;
|
|
78174
|
+
try {
|
|
78175
|
+
for (;; ) {
|
|
78176
|
+
const waited = clock.now() - started;
|
|
78177
|
+
const ahead = queued ? waitersAhead(options, name) : waitersAhead(options, "~");
|
|
78178
|
+
const free = options.slots - liveHolders(options).length;
|
|
78179
|
+
if (ahead < free) {
|
|
78180
|
+
const outcome = tryOnce(options, record, reclaimed);
|
|
78181
|
+
if ("slot" in outcome) {
|
|
78182
|
+
const waitedMs = queued ? waited : 0;
|
|
78183
|
+
leave();
|
|
78184
|
+
const { file, slot } = outcome;
|
|
78185
|
+
let released = false;
|
|
78186
|
+
const release = () => {
|
|
78187
|
+
if (released)
|
|
78188
|
+
return;
|
|
78189
|
+
released = true;
|
|
78190
|
+
process.off("exit", release);
|
|
78191
|
+
removeOwn(file, record.id);
|
|
78192
|
+
};
|
|
78193
|
+
process.on("exit", release);
|
|
78194
|
+
return {
|
|
78195
|
+
ok: true,
|
|
78196
|
+
lease: {
|
|
78197
|
+
record,
|
|
78198
|
+
slot,
|
|
78199
|
+
file,
|
|
78200
|
+
waitedOn,
|
|
78201
|
+
waitedMs,
|
|
78202
|
+
reclaimed,
|
|
78203
|
+
release
|
|
78204
|
+
}
|
|
78205
|
+
};
|
|
78109
78206
|
}
|
|
78110
|
-
}
|
|
78111
|
-
|
|
78112
|
-
|
|
78113
|
-
|
|
78114
|
-
|
|
78115
|
-
|
|
78207
|
+
}
|
|
78208
|
+
const holders = liveHolders(options);
|
|
78209
|
+
if (!waitedOn.length) {
|
|
78210
|
+
waitedOn = holders;
|
|
78211
|
+
options.onWait?.(holders);
|
|
78212
|
+
}
|
|
78213
|
+
if (waited >= options.waitMs)
|
|
78214
|
+
return {
|
|
78215
|
+
ok: false,
|
|
78216
|
+
reason: "timeout",
|
|
78217
|
+
holders,
|
|
78218
|
+
waitedMs: waited,
|
|
78219
|
+
position: ahead + 1
|
|
78220
|
+
};
|
|
78221
|
+
if (!queued) {
|
|
78222
|
+
writeTicket(ticket, ticketRecord);
|
|
78223
|
+
queued = true;
|
|
78224
|
+
process.on("exit", leave);
|
|
78225
|
+
process.on("SIGINT", onSignal);
|
|
78226
|
+
process.on("SIGTERM", onSignal);
|
|
78227
|
+
} else if (!fs24.existsSync(ticket)) {
|
|
78228
|
+
writeTicket(ticket, ticketRecord);
|
|
78229
|
+
}
|
|
78230
|
+
if (waited >= nextStatus) {
|
|
78231
|
+
options.onStatus?.({
|
|
78232
|
+
position: ahead + 1,
|
|
78233
|
+
ahead,
|
|
78234
|
+
holders,
|
|
78235
|
+
waitedMs: waited
|
|
78236
|
+
});
|
|
78237
|
+
nextStatus = waited + (options.statusEveryMs ?? 30000);
|
|
78238
|
+
}
|
|
78239
|
+
await clock.sleep(Math.min(options.pollMs ?? 2000, Math.max(1, options.waitMs - waited)));
|
|
78116
78240
|
}
|
|
78117
|
-
|
|
78118
|
-
|
|
78119
|
-
|
|
78241
|
+
} catch (error) {
|
|
78242
|
+
return {
|
|
78243
|
+
ok: false,
|
|
78244
|
+
reason: "unavailable",
|
|
78245
|
+
error: error.message
|
|
78246
|
+
};
|
|
78247
|
+
} finally {
|
|
78248
|
+
leave();
|
|
78120
78249
|
}
|
|
78121
78250
|
}
|
|
78122
78251
|
function updateLease(lease, patch) {
|
|
@@ -78229,7 +78358,7 @@ function leaseSlots(dir, slots, probe = {}) {
|
|
|
78229
78358
|
}
|
|
78230
78359
|
return states;
|
|
78231
78360
|
}
|
|
78232
|
-
var DEFAULT_HEAVY_CHECKS, DEFAULT_SLOTS = 1, DEFAULT_WAIT_S =
|
|
78361
|
+
var DEFAULT_HEAVY_CHECKS, DEFAULT_LIGHT_CHECKS, DEFAULT_SLOTS = 1, DEFAULT_WAIT_S = 1800, DEFAULT_MEMORY_SHARE = 0.4, DEFAULT_CPU_SHARE = 0.7, MAX_SLOTS = 64, UNREADABLE_TTL_MS = 30000, GUARD_TTL_MS = 30000, hostShape = () => ({
|
|
78233
78362
|
totalMemBytes: os20.totalmem(),
|
|
78234
78363
|
cpus: Math.max(1, os20.availableParallelism?.() ?? os20.cpus().length)
|
|
78235
78364
|
}), isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SIZE, PERCENT, UNITS, leaseDir = (env) => path52.join(stateDir2(env), "leases"), readText5 = (file) => {
|
|
@@ -78263,16 +78392,22 @@ var DEFAULT_HEAVY_CHECKS, DEFAULT_SLOTS = 1, DEFAULT_WAIT_S = 600, DEFAULT_MEMOR
|
|
|
78263
78392
|
cwd: lease.cwd,
|
|
78264
78393
|
session: lease.session,
|
|
78265
78394
|
startedAt: lease.startedAt
|
|
78266
|
-
}),
|
|
78395
|
+
}), realClock, queueDir = (dir) => path52.join(dir, "queue"), TICKET, ticketName = (now, id) => `${String(now).padStart(15, "0")}-${String(process.hrtime.bigint() % 10n ** 16n).padStart(16, "0")}-${id}.json`, SCOPE_START_FAILURE, NOTICE_TTL_MS;
|
|
78267
78396
|
var init_resources = __esm(() => {
|
|
78268
78397
|
init_checks3();
|
|
78269
78398
|
init_heavy();
|
|
78270
78399
|
init_heavy_scope();
|
|
78271
|
-
DEFAULT_HEAVY_CHECKS = ["test", "build"
|
|
78400
|
+
DEFAULT_HEAVY_CHECKS = ["test", "build"];
|
|
78401
|
+
DEFAULT_LIGHT_CHECKS = ["lint", "typecheck", "format"];
|
|
78272
78402
|
SIZE = /^(\d+(?:\.\d+)?)([KMGT]?)$/iu;
|
|
78273
78403
|
PERCENT = /^(\d+(?:\.\d+)?)%$/u;
|
|
78274
78404
|
UNITS = { "": 1, K: 1024, M: 1024 ** 2, G: 1024 ** 3, T: 1024 ** 4 };
|
|
78275
78405
|
SCOPE_UNIT = /^[\w.@-]+\.scope$/u;
|
|
78406
|
+
realClock = {
|
|
78407
|
+
now: () => Date.now(),
|
|
78408
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
78409
|
+
};
|
|
78410
|
+
TICKET = /^\d{15}-\d{16}-[\w-]+\.json$/u;
|
|
78276
78411
|
SCOPE_START_FAILURE = /^Failed to (?:connect to bus|create bus connection|start transient scope unit|parse \S+?=)/u;
|
|
78277
78412
|
NOTICE_TTL_MS = 7 * 86400000;
|
|
78278
78413
|
});
|
|
@@ -78283,7 +78418,6 @@ __export(exports_check, {
|
|
|
78283
78418
|
OUTPUT_LINES: () => OUTPUT_LINES,
|
|
78284
78419
|
boundedOutput: () => boundedOutput,
|
|
78285
78420
|
countWarnings: () => countWarnings,
|
|
78286
|
-
hostToolWaitCapMs: () => hostToolWaitCapMs,
|
|
78287
78421
|
run: () => run12,
|
|
78288
78422
|
workspaceResources: () => workspaceResources
|
|
78289
78423
|
});
|
|
@@ -78306,6 +78440,7 @@ function parse6(argv) {
|
|
|
78306
78440
|
positionals: [],
|
|
78307
78441
|
command: split >= 0 ? argv.slice(split + 1) : null
|
|
78308
78442
|
};
|
|
78443
|
+
let noWait = false;
|
|
78309
78444
|
for (let index = 0;index < head.length; index += 1) {
|
|
78310
78445
|
const arg = head[index];
|
|
78311
78446
|
const [flag, inline] = arg.startsWith("--") && arg.includes("=") ? arg.split(/=(.*)/su) : [arg];
|
|
@@ -78329,6 +78464,13 @@ function parse6(argv) {
|
|
|
78329
78464
|
options.noLease = true;
|
|
78330
78465
|
continue;
|
|
78331
78466
|
}
|
|
78467
|
+
if (flag === "--no-wait") {
|
|
78468
|
+
if (options.waitMs !== null && !noWait)
|
|
78469
|
+
return new Error("--no-wait goes without --wait");
|
|
78470
|
+
noWait = true;
|
|
78471
|
+
options.waitMs = 0;
|
|
78472
|
+
continue;
|
|
78473
|
+
}
|
|
78332
78474
|
if (flag === "--name" || flag === "--task" || flag === "--base" || flag === "--timeout" || flag === "--wait" || flag === "--reason") {
|
|
78333
78475
|
const got = value();
|
|
78334
78476
|
if (got instanceof Error)
|
|
@@ -78338,10 +78480,12 @@ function parse6(argv) {
|
|
|
78338
78480
|
else if (flag === "--reason")
|
|
78339
78481
|
options.reason = got.trim() || null;
|
|
78340
78482
|
else if (flag === "--wait") {
|
|
78341
|
-
|
|
78342
|
-
|
|
78343
|
-
|
|
78344
|
-
|
|
78483
|
+
if (noWait)
|
|
78484
|
+
return new Error("--no-wait goes without --wait");
|
|
78485
|
+
const waitMs = parseWait(got);
|
|
78486
|
+
if (waitMs === null)
|
|
78487
|
+
return new Error('--wait must be seconds or a duration ("90", "90s", "30m", "1h")');
|
|
78488
|
+
options.waitMs = waitMs;
|
|
78345
78489
|
} else if (flag === "--task")
|
|
78346
78490
|
options.task = got;
|
|
78347
78491
|
else if (flag === "--base")
|
|
@@ -78471,22 +78615,30 @@ function countWarnings(log) {
|
|
|
78471
78615
|
return redactLog(log).replace(ANSI_COLOR, "").split(`
|
|
78472
78616
|
`).filter((line) => WARNING_LINE.test(line) && !NO_WARNINGS.test(line)).length;
|
|
78473
78617
|
}
|
|
78474
|
-
function boundedOutput(log, lines) {
|
|
78618
|
+
function boundedOutput(log, lines, stderr = "") {
|
|
78475
78619
|
const all = tailLines(log, Number.MAX_SAFE_INTEGER);
|
|
78476
78620
|
if (all.length === 0)
|
|
78477
78621
|
return "";
|
|
78478
|
-
|
|
78622
|
+
let shown = all.slice(-lines);
|
|
78623
|
+
const errors = tailLines(redactLog(stderr), STDERR_TAIL_LINES);
|
|
78624
|
+
let missing = [];
|
|
78625
|
+
if (errors.length) {
|
|
78626
|
+
const tail = new Set(shown);
|
|
78627
|
+
if (errors.some((line) => !tail.has(line))) {
|
|
78628
|
+
shown = all.slice(-Math.max(1, lines - errors.length - 2));
|
|
78629
|
+
const kept = new Set(shown);
|
|
78630
|
+
missing = errors.filter((line) => !kept.has(line));
|
|
78631
|
+
}
|
|
78632
|
+
}
|
|
78479
78633
|
const omitted = all.length - shown.length;
|
|
78634
|
+
const footer = missing.length ? `… the last stderr lines, which the merged log holds earlier:
|
|
78635
|
+
${missing.join(`
|
|
78636
|
+
`)}
|
|
78637
|
+
` : "";
|
|
78480
78638
|
return `${omitted ? `… ${omitted} earlier line${omitted === 1 ? "" : "s"} omitted (the full log is named below; --full streams it)
|
|
78481
78639
|
` : ""}${shown.join(`
|
|
78482
78640
|
`)}
|
|
78483
|
-
`;
|
|
78484
|
-
}
|
|
78485
|
-
function hostToolWaitCapMs(env) {
|
|
78486
|
-
if (!env.CLAUDECODE && !env.OPENCODE && !env.OPENCODE_SESSION_ID)
|
|
78487
|
-
return null;
|
|
78488
|
-
const override = Number(env.WORKIT_HOST_WAIT_CAP_MS);
|
|
78489
|
-
return Number.isFinite(override) && override >= 0 && env.WORKIT_HOST_WAIT_CAP_MS ? override : HOST_TOOL_WAIT_CAP_MS;
|
|
78641
|
+
${footer}`;
|
|
78490
78642
|
}
|
|
78491
78643
|
async function workspaceResources(io) {
|
|
78492
78644
|
const env = io.env;
|
|
@@ -78520,14 +78672,26 @@ async function prepareHeavy(io, settings, label, runCwd, timeoutMs) {
|
|
|
78520
78672
|
waitMs: settings.waitMs,
|
|
78521
78673
|
pollMs: 1000,
|
|
78522
78674
|
maxAgeMs: (timeoutMs || MAX_UNVERIFIED_RUN_MS) + 60000,
|
|
78523
|
-
|
|
78524
|
-
|
|
78675
|
+
onStatus: (status) => {
|
|
78676
|
+
const slots = `${settings.slots} slot${settings.slots === 1 ? "" : "s"}`;
|
|
78677
|
+
const holders = status.holders.map((holder) => describeHolder(holder)).join("; ");
|
|
78678
|
+
io.stderr(status.waitedMs === 0 ? `workit: waiting for the heavy-check lease (${slots}): held by ${holders || "nobody; waiters ahead"}; queued at position ${status.position}, waiting up to ${formatWait(settings.waitMs)} (--wait <dur> sets it)
|
|
78679
|
+
` : `workit: still queued for the heavy-check lease: position ${status.position}, ${formatWait(status.waitedMs)} of ${formatWait(settings.waitMs)} waited; held by ${holders || "nobody; waiters ahead"}
|
|
78680
|
+
`);
|
|
78681
|
+
}
|
|
78525
78682
|
});
|
|
78526
78683
|
if (!acquired.ok && acquired.reason === "timeout") {
|
|
78527
78684
|
const holders = acquired.holders.map((holder) => describeHolder(holder)).join("; ");
|
|
78528
|
-
|
|
78529
|
-
|
|
78530
|
-
|
|
78685
|
+
const waited = formatWait(acquired.waitedMs);
|
|
78686
|
+
emit(io, fail2("busy", settings.waitMs === 0 ? `the heavy-check lease is busy (--no-wait): held by ${holders || "nobody; waiters ahead"}` : `the heavy-check lease stayed busy for ${waited} (position ${acquired.position} in the queue)`, {
|
|
78687
|
+
data: {
|
|
78688
|
+
lease: {
|
|
78689
|
+
waitedMs: acquired.waitedMs,
|
|
78690
|
+
holders: acquired.holders,
|
|
78691
|
+
position: acquired.position
|
|
78692
|
+
}
|
|
78693
|
+
},
|
|
78694
|
+
unblock: `wait for ${holders || "the waiters ahead"} to finish (each holder's age is shown; a full test suite takes minutes), then run it again, or queue longer with --wait 30m`
|
|
78531
78695
|
}));
|
|
78532
78696
|
return 4;
|
|
78533
78697
|
}
|
|
@@ -78627,11 +78791,8 @@ async function run12(argv, io) {
|
|
|
78627
78791
|
{ label: "workspaces.json", trust: "machine", value: workspace.value },
|
|
78628
78792
|
{ label: CHECKS_FILE, trust: "repo", value: config.resources }
|
|
78629
78793
|
]);
|
|
78630
|
-
const cap = hostToolWaitCapMs(io.env);
|
|
78631
78794
|
if (options.waitMs !== null)
|
|
78632
78795
|
settings.waitMs = options.waitMs;
|
|
78633
|
-
else if (cap !== null)
|
|
78634
|
-
settings.waitMs = Math.min(settings.waitMs, cap);
|
|
78635
78796
|
for (const warning of [workspace.warning, ...warnings])
|
|
78636
78797
|
if (warning)
|
|
78637
78798
|
io.stderr(`workit: resources: ${warning}
|
|
@@ -78648,6 +78809,7 @@ async function run12(argv, io) {
|
|
|
78648
78809
|
`);
|
|
78649
78810
|
const lease = heavy?.lease ?? null;
|
|
78650
78811
|
let result;
|
|
78812
|
+
let stderrTail = "";
|
|
78651
78813
|
try {
|
|
78652
78814
|
const scoped = await runScoped((wrapper, sinks) => runCheckCommand(command, {
|
|
78653
78815
|
cwd: runCwd,
|
|
@@ -78657,7 +78819,12 @@ async function run12(argv, io) {
|
|
|
78657
78819
|
...sinks,
|
|
78658
78820
|
...wrapper ? { wrapper } : {},
|
|
78659
78821
|
...lease ? { onSpawn: (pid) => updateLease(lease, { pgid: pid }) } : {}
|
|
78660
|
-
}), heavy?.wrapper ?? null, options.full ? { onStdout: io.json ? io.stderr : io.stdout, onStderr: io.stderr } : {
|
|
78822
|
+
}), heavy?.wrapper ?? null, options.full ? { onStdout: io.json ? io.stderr : io.stdout, onStderr: io.stderr } : {
|
|
78823
|
+
onStdout: () => {},
|
|
78824
|
+
onStderr: (text) => {
|
|
78825
|
+
stderrTail = (stderrTail + text).slice(-STDERR_KEEP_CHARS);
|
|
78826
|
+
}
|
|
78827
|
+
});
|
|
78661
78828
|
result = scoped.result;
|
|
78662
78829
|
if (heavy && scoped.fallback) {
|
|
78663
78830
|
io.stderr(`workit: the memory/CPU scope could not start (${scoped.fallback}); ran without it
|
|
@@ -78769,7 +78936,7 @@ async function run12(argv, io) {
|
|
|
78769
78936
|
`);
|
|
78770
78937
|
else {
|
|
78771
78938
|
if (!options.full)
|
|
78772
|
-
io.stdout(boundedOutput(result.log, OUTPUT_LINES));
|
|
78939
|
+
io.stdout(boundedOutput(result.log, OUTPUT_LINES, stderrTail));
|
|
78773
78940
|
io.stderr(`${summary}
|
|
78774
78941
|
`);
|
|
78775
78942
|
if (recordError && passed)
|
|
@@ -78786,7 +78953,7 @@ async function run12(argv, io) {
|
|
|
78786
78953
|
return recordError.code === "busy" ? 4 : recordError.code === "invalid_input" ? 2 : 5;
|
|
78787
78954
|
return 0;
|
|
78788
78955
|
}
|
|
78789
|
-
var USAGE6 = 'workit check <name> [--full] [--no-lease --reason "<why>"] | workit check [--name <n>] [--shell] [--heavy] [--full] [--wait <
|
|
78956
|
+
var USAGE6 = 'workit check <name> [--full] [--wait <dur> | --no-wait] [--no-lease --reason "<why>"] | workit check [--name <n>] [--shell] [--heavy] [--full] [--wait <dur> | --no-wait] [--timeout <s>] [--task <id>] [--json] -- <cmd…>', OUTPUT_LINES = 60, MAX_UNVERIFIED_RUN_MS, RECORD_TAIL_LINES = 20, RECORD_TAIL_CHARS = 200, posix = (value) => value.split(path53.sep).join("/") || ".", ENGINE_CODES2, FINGERPRINT_VARS, ANSI_COLOR, WARNING_LINE, NO_WARNINGS, STDERR_TAIL_LINES = 10, STDERR_KEEP_CHARS = 65536;
|
|
78790
78957
|
var init_check = __esm(() => {
|
|
78791
78958
|
init_check_config();
|
|
78792
78959
|
init_checks3();
|
|
@@ -78824,25 +78991,35 @@ async function settingsFor(io) {
|
|
|
78824
78991
|
{ label: CHECKS_FILE, trust: "repo", value: config.resources }
|
|
78825
78992
|
]).settings;
|
|
78826
78993
|
}
|
|
78827
|
-
function slotLines(slots) {
|
|
78994
|
+
function slotLines(slots, queue = []) {
|
|
78828
78995
|
const busy = held(slots).length;
|
|
78996
|
+
const waiting = queue.filter((entry) => !entry.stale);
|
|
78829
78997
|
return [
|
|
78830
78998
|
`heavy-check lease: ${busy} of ${slots.length} slot${slots.length === 1 ? "" : "s"} held`,
|
|
78831
|
-
...slots.map((slot) => slot.holder ? ` slot ${slot.slot}: ${describeHolder(slot.holder)}${slot.stale ? ` (stale: ${slot.stale}; the next heavy check reclaims it)` : ""}` : ` slot ${slot.slot}: free`)
|
|
78999
|
+
...slots.map((slot) => slot.holder ? ` slot ${slot.slot}: ${describeHolder(slot.holder)}${slot.stale ? ` (stale: ${slot.stale}; the next heavy check reclaims it)` : ""}` : ` slot ${slot.slot}: free`),
|
|
79000
|
+
`queue: ${waiting.length ? `${waiting.length} waiting, first come, first served` : "empty"}`,
|
|
79001
|
+
...waiting.map((entry, index) => ` ${index + 1}. ${describeHolder(entry.holder)}`)
|
|
78832
79002
|
];
|
|
78833
79003
|
}
|
|
78834
79004
|
async function status(io) {
|
|
78835
79005
|
const settings = await settingsFor(io);
|
|
78836
79006
|
const slots = leaseSlots(leaseDir(io.env), settings.slots);
|
|
78837
|
-
|
|
79007
|
+
const queue = leaseQueue(leaseDir(io.env));
|
|
79008
|
+
const waiting = queue.filter((entry) => !entry.stale).map((entry) => entry.holder);
|
|
79009
|
+
return emit(io, ok2({
|
|
79010
|
+
dir: leaseDir(io.env),
|
|
79011
|
+
slots,
|
|
79012
|
+
queue: waiting,
|
|
79013
|
+
waitMs: settings.waitMs,
|
|
79014
|
+
light: settings.light
|
|
79015
|
+
}), () => slotLines(slots, queue));
|
|
78838
79016
|
}
|
|
78839
79017
|
function timeoutOf(args) {
|
|
78840
79018
|
const at = args.findIndex((arg) => arg === "--timeout" || arg.startsWith("--timeout="));
|
|
78841
79019
|
if (at < 0)
|
|
78842
79020
|
return null;
|
|
78843
79021
|
const raw = args[at].includes("=") ? args[at].split("=")[1] : args[at + 1];
|
|
78844
|
-
|
|
78845
|
-
return Number.isFinite(seconds) && seconds >= 0 ? Math.round(seconds * 1000) : new Error("--timeout must be a number of seconds");
|
|
79022
|
+
return parseWait(raw ?? "") ?? new Error('--timeout must be seconds or a duration ("90", "90s", "30m", "1h")');
|
|
78846
79023
|
}
|
|
78847
79024
|
async function wait(args, io) {
|
|
78848
79025
|
const timeout = timeoutOf(args);
|
|
@@ -78859,10 +79036,10 @@ async function wait(args, io) {
|
|
|
78859
79036
|
const waitedMs = Date.now() - started;
|
|
78860
79037
|
if (busy.length < slots.length)
|
|
78861
79038
|
return emit(io, ok2({ free: true, waitedMs, slots }), () => [
|
|
78862
|
-
`workit: the heavy-check lease has a free slot${waitedMs ? ` after ${
|
|
79039
|
+
`workit: the heavy-check lease has a free slot${waitedMs ? ` after ${formatWait(waitedMs)}` : ""}`
|
|
78863
79040
|
]);
|
|
78864
79041
|
if (waitedMs >= limit)
|
|
78865
|
-
return emit(io, fail2("busy", `the heavy-check lease stayed busy for ${
|
|
79042
|
+
return emit(io, fail2("busy", `the heavy-check lease stayed busy for ${formatWait(waitedMs)}`, {
|
|
78866
79043
|
data: { free: false, waitedMs, slots },
|
|
78867
79044
|
unblock: "workit lock status"
|
|
78868
79045
|
}));
|
|
@@ -78871,16 +79048,16 @@ async function wait(args, io) {
|
|
|
78871
79048
|
io.stderr(`workit: waiting for the heavy-check lease: held by ${busy.flatMap((slot) => slot.holder ? [describeHolder(slot.holder)] : []).join("; ")}
|
|
78872
79049
|
`);
|
|
78873
79050
|
}
|
|
78874
|
-
await
|
|
79051
|
+
await sleep(Math.min(1000, Math.max(1, limit - waitedMs)));
|
|
78875
79052
|
}
|
|
78876
79053
|
}
|
|
78877
79054
|
async function runHeavy(args, io) {
|
|
78878
79055
|
const split = args.indexOf("--");
|
|
78879
79056
|
const head = split < 0 ? args : args.slice(0, split);
|
|
78880
|
-
const unknown = head.find((arg, index) => arg !== "--shell" && arg !== "--json" && !arg.startsWith("--timeout") && !arg.startsWith("--wait") && !((head[index - 1] === "--timeout" || head[index - 1] === "--wait") && !arg.startsWith("-")));
|
|
79057
|
+
const unknown = head.find((arg, index) => arg !== "--shell" && arg !== "--json" && arg !== "--no-wait" && !arg.startsWith("--timeout") && !arg.startsWith("--wait") && !((head[index - 1] === "--timeout" || head[index - 1] === "--wait") && !arg.startsWith("-")));
|
|
78881
79058
|
if (split < 0 || unknown)
|
|
78882
79059
|
return emit(io, fail2("invalid_input", unknown ? `unexpected ${unknown}` : "no command after --", {
|
|
78883
|
-
unblock: "workit lock run [--shell] [--wait <
|
|
79060
|
+
unblock: "workit lock run [--shell] [--wait <dur> | --no-wait] [--timeout <s>] -- <cmd…>"
|
|
78884
79061
|
}));
|
|
78885
79062
|
const check = await Promise.resolve().then(() => (init_check(), {}));
|
|
78886
79063
|
return run12([`--heavy`, ...head, ...args.slice(split)], io);
|
|
@@ -78966,7 +79143,7 @@ async function run13(argv, io) {
|
|
|
78966
79143
|
return heavy(rest, io);
|
|
78967
79144
|
return emit(io, fail2("invalid_input", `unknown lock subcommand ${sub ?? "(none)"}`, { unblock: USAGE7 }));
|
|
78968
79145
|
}
|
|
78969
|
-
var USAGE7 = "workit lock status | wait [--timeout <
|
|
79146
|
+
var USAGE7 = "workit lock status | wait [--timeout <dur>] | run [--shell] [--wait <dur> | --no-wait] [--timeout <s>] -- <cmd…> | heavy list|add|remove|test", held = (slots) => slots.filter((slot) => slot.holder && !slot.stale), sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), commandText = (args) => {
|
|
78970
79147
|
const split = args.indexOf("--");
|
|
78971
79148
|
return (split < 0 ? args : args.slice(split + 1)).join(" ").trim();
|
|
78972
79149
|
};
|
|
@@ -107041,9 +107218,10 @@ function withSubcommands(entry, options = {}) {
|
|
|
107041
107218
|
};
|
|
107042
107219
|
}
|
|
107043
107220
|
var LEASE_NOTES = [
|
|
107044
|
-
"The heavy-check lease: heavy work (tests, builds,
|
|
107045
|
-
"
|
|
107046
|
-
"
|
|
107221
|
+
"The heavy-check lease: heavy work (tests, builds, installs, image builds) shares one machine-wide lease, so parallel agents and worktrees never exhaust this machine's memory and CPU. `slots` (default 1) heavy checks run at once; on Linux each also runs in a systemd scope with MemoryMax and CPUQuota.",
|
|
107222
|
+
"Queueing: a heavy check that finds every slot held joins a first-come, first-served queue and prints `waiting for the heavy-check lease` naming the holder (check, pid, session, age, directory) and its position, then a status line every 30 s. It waits `--wait <dur>` (90, 90s, 30m, 1h), else `resources.wait` (default 30 min), and exits 4 (busy) only past that wait or at once with --no-wait. A waiter that is killed gives up its place. A wait longer than your shell tool's timeout belongs in a background call. `workit lock status` shows the holders and the queue.",
|
|
107223
|
+
"Light checks: the named checks lint, typecheck and format (`resources.light`, set in workspaces.json) never take the lease, so they run at once beside a long suite.",
|
|
107224
|
+
"--heavy: an ad-hoc `workit check -- <cmd>` takes the lease only with --heavy; the named checks test and build always do. The shell hook runs a raw heavy command (`npx jest`, `npm run test:ci`, `cargo test`, `docker build`) as `workit check --heavy -- <cmd>` on Claude Code, OpenCode and Pi, and denies it with that exact command on Codex and Cursor.",
|
|
107047
107225
|
'--no-lease skips the lease and needs --reason "<why>". Never hand-roll a flock: the lease already queues every agent on the machine.',
|
|
107048
107226
|
"Output: a check prints the last 60 lines of its output and one summary line with `log: <path>` to the full log; --full streams all of it."
|
|
107049
107227
|
];
|
|
@@ -107160,7 +107338,7 @@ var VERBS = [
|
|
|
107160
107338
|
{
|
|
107161
107339
|
name: "check",
|
|
107162
107340
|
group: "delivery",
|
|
107163
|
-
usage: 'workit check <name> [--full] [--task <id>] [--base <ref>] [--no-lease --reason "<why>"] | workit check [--name <n>] [--shell] [--full] [--timeout <s>] [--task <id>] [--base <ref>] [--heavy [--wait <
|
|
107341
|
+
usage: 'workit check <name> [--full] [--task <id>] [--base <ref>] [--wait <dur> | --no-wait] [--no-lease --reason "<why>"] | workit check [--name <n>] [--shell] [--full] [--timeout <s>] [--task <id>] [--base <ref>] [--heavy [--wait <dur> | --no-wait]] [--no-lease --reason "<why>"] -- <cmd…>',
|
|
107164
107342
|
flagHints: {
|
|
107165
107343
|
list: "workit check <name> runs a configured check; a name that is not configured prints the valid ones (workit check test|lint|typecheck|check)"
|
|
107166
107344
|
},
|
|
@@ -107168,6 +107346,7 @@ var VERBS = [
|
|
|
107168
107346
|
examples: [
|
|
107169
107347
|
"workit check test",
|
|
107170
107348
|
"workit check --heavy -- npx jest",
|
|
107349
|
+
"workit check test --wait 45m",
|
|
107171
107350
|
"workit check --name unit -- bun test test/unit",
|
|
107172
107351
|
'workit check test --no-lease --reason "machine is idle, debugging a hang"'
|
|
107173
107352
|
],
|
|
@@ -107182,16 +107361,16 @@ var VERBS = [
|
|
|
107182
107361
|
{
|
|
107183
107362
|
name: "status",
|
|
107184
107363
|
usage: "workit lock status",
|
|
107185
|
-
summary: "Show each lease slot
|
|
107364
|
+
summary: "Show each lease slot (free, held by check, pid, session, age, directory, or stale) and the queue of waiting checks in arrival order"
|
|
107186
107365
|
},
|
|
107187
107366
|
{
|
|
107188
107367
|
name: "wait",
|
|
107189
|
-
usage: "workit lock wait [--timeout <
|
|
107368
|
+
usage: "workit lock wait [--timeout <dur>]",
|
|
107190
107369
|
summary: "Wait until a lease slot is free, without taking it (exit 0 free, 4 still busy at the timeout)"
|
|
107191
107370
|
},
|
|
107192
107371
|
{
|
|
107193
107372
|
name: "run",
|
|
107194
|
-
usage: "workit lock run [--shell] [--wait <
|
|
107373
|
+
usage: "workit lock run [--shell] [--wait <dur> | --no-wait] [--timeout <s>] -- <cmd…>",
|
|
107195
107374
|
summary: "Run a command under the lease: the same as workit check --heavy -- <cmd…>"
|
|
107196
107375
|
},
|
|
107197
107376
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-cli",
|
|
3
|
-
"version": "8.38.
|
|
3
|
+
"version": "8.38.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
|
|
6
6
|
"keywords": [
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@babel/parser": "7.29.9",
|
|
41
|
-
"@brainervirus/workit-cursor": "^8.38.
|
|
41
|
+
"@brainervirus/workit-cursor": "^8.38.5",
|
|
42
42
|
"@inkjs/ui": "2.0.0",
|
|
43
43
|
"ink": "7.1.1",
|
|
44
44
|
"react": "19.2.8"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@brainervirus/workit-core": "^8.38.
|
|
47
|
+
"@brainervirus/workit-core": "^8.38.5"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">=24"
|