@brainervirus/workit-cli 8.11.0 → 8.12.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 +1156 -257
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var package_default;
|
|
|
65
65
|
var init_package = __esm(() => {
|
|
66
66
|
package_default = {
|
|
67
67
|
name: "@brainervirus/workit-cli",
|
|
68
|
-
version: "8.
|
|
68
|
+
version: "8.12.0",
|
|
69
69
|
private: false,
|
|
70
70
|
description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
|
|
71
71
|
keywords: [
|
|
@@ -103,9 +103,9 @@ var init_package = __esm(() => {
|
|
|
103
103
|
},
|
|
104
104
|
dependencies: {
|
|
105
105
|
"@babel/parser": "7.29.9",
|
|
106
|
-
"@brainervirus/workit-core": "^8.
|
|
107
|
-
"@brainervirus/workit-cursor": "^8.
|
|
108
|
-
"@brainervirus/workit-opencode": "^8.
|
|
106
|
+
"@brainervirus/workit-core": "^8.12.0",
|
|
107
|
+
"@brainervirus/workit-cursor": "^8.12.0",
|
|
108
|
+
"@brainervirus/workit-opencode": "^8.12.0",
|
|
109
109
|
"@inkjs/ui": "2.0.0",
|
|
110
110
|
ink: "7.1.1",
|
|
111
111
|
react: "19.2.8"
|
|
@@ -298,7 +298,7 @@ function loadCheckConfig(cwd) {
|
|
|
298
298
|
gates[gate] = names;
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
|
-
return { source: CHECKS_FILE, root, checks, gates, error: null };
|
|
301
|
+
return { source: CHECKS_FILE, root, checks, gates, error: null, resources: parsed.resources };
|
|
302
302
|
}
|
|
303
303
|
function matchesNamedCheck(config, name, argv, cwd) {
|
|
304
304
|
if (config.error || name === null || cwd !== ".")
|
|
@@ -13690,8 +13690,8 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
|
|
|
13690
13690
|
profiles: record(nonBlank, profileSchema).optional()
|
|
13691
13691
|
}).passthrough();
|
|
13692
13692
|
return entrySchema;
|
|
13693
|
-
}, resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs"), runtimeWorkspaceIndexSchema, matchRuntimeWorkspace = (cwd) => {
|
|
13694
|
-
const file = path12.join(
|
|
13693
|
+
}, resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs"), runtimeWorkspaceIndexSchema, matchRuntimeWorkspace = (cwd, dir = configDir()) => {
|
|
13694
|
+
const file = path12.join(dir, "workspaces.json");
|
|
13695
13695
|
let raw;
|
|
13696
13696
|
try {
|
|
13697
13697
|
raw = readFileSync3(file, "utf8");
|
|
@@ -13720,10 +13720,27 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
|
|
|
13720
13720
|
const matches = matchingEntries(cwd, entries ?? []);
|
|
13721
13721
|
const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
|
|
13722
13722
|
const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
|
|
13723
|
-
return selected ? { file, selected } : null;
|
|
13723
|
+
return selected ? { file, selected, matches } : null;
|
|
13724
13724
|
}, resolveRuntimeReleaseTracks = (cwd) => {
|
|
13725
13725
|
const matched = matchRuntimeWorkspace(cwd);
|
|
13726
13726
|
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
13727
|
+
}, workspaceTrailers = (cwd, dir) => {
|
|
13728
|
+
let matched;
|
|
13729
|
+
try {
|
|
13730
|
+
matched = matchRuntimeWorkspace(cwd, dir);
|
|
13731
|
+
} catch {
|
|
13732
|
+
return { enabled: true, workspace: null, from: null };
|
|
13733
|
+
}
|
|
13734
|
+
if (!matched)
|
|
13735
|
+
return { enabled: true, workspace: null, from: null };
|
|
13736
|
+
const { selected, matches } = matched;
|
|
13737
|
+
const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
13738
|
+
const source = [selected, ...enclosing].find((entry) => typeof entry.trailers === "boolean");
|
|
13739
|
+
return {
|
|
13740
|
+
enabled: source?.trailers !== false,
|
|
13741
|
+
workspace: selected.name,
|
|
13742
|
+
from: source?.name ?? null
|
|
13743
|
+
};
|
|
13727
13744
|
}, resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
13728
13745
|
const matched = matchRuntimeWorkspace(cwd);
|
|
13729
13746
|
if (!matched)
|
|
@@ -13874,6 +13891,7 @@ var init_workspaces = __esm(() => {
|
|
|
13874
13891
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
13875
13892
|
defaultEndpoint: unknown().optional(),
|
|
13876
13893
|
verification: _enum(["self", "independent"]).optional(),
|
|
13894
|
+
trailers: boolean2().optional(),
|
|
13877
13895
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
13878
13896
|
kind,
|
|
13879
13897
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -64447,6 +64465,26 @@ function installSessionHook(cwd, env = process.env) {
|
|
|
64447
64465
|
detail: `${verb} the Workit ${SESSION_HOOK_NAME} hook at ${status.hookPath}${helper}`
|
|
64448
64466
|
};
|
|
64449
64467
|
}
|
|
64468
|
+
function removeSessionHook(cwd, env = process.env) {
|
|
64469
|
+
const status = inspectSessionHook(cwd, env);
|
|
64470
|
+
if (status.state === "not_git")
|
|
64471
|
+
return { action: "skipped", removed: [], status, detail: "not inside a git work tree" };
|
|
64472
|
+
const removed = [];
|
|
64473
|
+
if (workitHookIn(status)) {
|
|
64474
|
+
rmSync2(status.hookPath, { force: true });
|
|
64475
|
+
removed.push(status.hookPath);
|
|
64476
|
+
}
|
|
64477
|
+
if (helperIn(status)) {
|
|
64478
|
+
rmSync2(status.helperPath, { force: true });
|
|
64479
|
+
removed.push(status.helperPath);
|
|
64480
|
+
}
|
|
64481
|
+
return {
|
|
64482
|
+
action: removed.length ? "removed" : "unchanged",
|
|
64483
|
+
removed,
|
|
64484
|
+
status: inspectSessionHook(cwd, env),
|
|
64485
|
+
detail: removed.length ? `removed ${removed.join(" and ")}` : "no Workit session hook or helper to remove"
|
|
64486
|
+
};
|
|
64487
|
+
}
|
|
64450
64488
|
var SESSION_HOOK_NAME = "prepare-commit-msg", SESSION_HOOK_MARKER = "# workit-session-trailer-hook", HELPER, LEGACY_CHAINED = "commit-msg.workit-chained", SESSION_TRAILER_SCRIPT = `#!/bin/sh
|
|
64451
64489
|
# Workit (https://github.com/BrainerVirus/workit) wrote this file with
|
|
64452
64490
|
# \`workit doctor --fix\`. A prepare-commit-msg hook runs it with its own
|
|
@@ -64474,6 +64512,19 @@ fi
|
|
|
64474
64512
|
[ -n "$session" ] || exit 0
|
|
64475
64513
|
[ \${#session} -le 128 ] || exit 0
|
|
64476
64514
|
case "$session" in *[!A-Za-z0-9_.:@/+-]*) exit 0 ;; esac
|
|
64515
|
+
# A workspace with \`trailers: false\` keeps authorship in the ledger only.
|
|
64516
|
+
# \`workit git commit\` decides it already (WORKIT_TRAILERS); otherwise ask
|
|
64517
|
+
# workit, bounded, and add the trailer when it cannot answer.
|
|
64518
|
+
case "\${WORKIT_TRAILERS-}" in
|
|
64519
|
+
off) exit 0 ;;
|
|
64520
|
+
on) ;;
|
|
64521
|
+
*)
|
|
64522
|
+
if command -v workit >/dev/null 2>&1; then
|
|
64523
|
+
t=; ! command -v timeout >/dev/null 2>&1 || t="timeout 5"
|
|
64524
|
+
[ "$($t workit git trailers 2>/dev/null)" != off ] || exit 0
|
|
64525
|
+
fi
|
|
64526
|
+
;;
|
|
64527
|
+
esac
|
|
64477
64528
|
add_trailer() {
|
|
64478
64529
|
[ -f "$1" ] && [ -r "$1" ] || return 0
|
|
64479
64530
|
body=$(sed '/^# -\\{24\\} >8 -\\{24\\}$/,$d' "$1" | git stripspace --strip-comments)
|
|
@@ -64617,7 +64668,7 @@ exit 0
|
|
|
64617
64668
|
} finally {
|
|
64618
64669
|
rmSync2(staged, { force: true });
|
|
64619
64670
|
}
|
|
64620
|
-
};
|
|
64671
|
+
}, workitHookIn = (status) => status.hookPath !== null && (readText2(status.hookPath)?.includes(SESSION_HOOK_MARKER) ?? false), helperIn = (status) => status.helperPath !== null && exists(status.helperPath);
|
|
64621
64672
|
var init_session_hook = __esm(() => {
|
|
64622
64673
|
init_rev();
|
|
64623
64674
|
HELPER = ["workit", "session-trailer.sh"];
|
|
@@ -66052,12 +66103,34 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
66052
66103
|
};
|
|
66053
66104
|
}
|
|
66054
66105
|
}, checkSessionHook = (res) => {
|
|
66055
|
-
|
|
66106
|
+
const workspace = matchedWorkspace(res);
|
|
66107
|
+
if (!workspace)
|
|
66056
66108
|
return {
|
|
66057
66109
|
id: "session_hook",
|
|
66058
66110
|
status: "pass",
|
|
66059
66111
|
detail: `not in a Workit workspace — skipping the ${SESSION_HOOK_NAME} session hook`
|
|
66060
66112
|
};
|
|
66113
|
+
const trailers = workspaceTrailers(res.cwd, res.configDir);
|
|
66114
|
+
if (!trailers.enabled) {
|
|
66115
|
+
const setting = `workspace ${trailers.from ?? workspace.name} sets trailers: false`;
|
|
66116
|
+
const left = inspectSessionHook(res.cwd, res.env);
|
|
66117
|
+
const files = [
|
|
66118
|
+
...workitHookIn(left) ? [left.hookPath] : [],
|
|
66119
|
+
...helperIn(left) ? [left.helperPath] : []
|
|
66120
|
+
];
|
|
66121
|
+
if (files.length)
|
|
66122
|
+
return {
|
|
66123
|
+
id: "session_hook",
|
|
66124
|
+
status: "warn",
|
|
66125
|
+
detail: `${setting}, but Workit's ${files.join(" and ")} ${files.length > 1 ? "are" : "is"} still installed: plain git commits still get a Workit-Session trailer whenever the helper cannot ask workit (it fails open)`,
|
|
66126
|
+
fix: SESSION_HOOK_FIX
|
|
66127
|
+
};
|
|
66128
|
+
return {
|
|
66129
|
+
id: "session_hook",
|
|
66130
|
+
status: "pass",
|
|
66131
|
+
detail: `${setting} (authorship in the ledger only) — no ${SESSION_HOOK_NAME} session hook needed`
|
|
66132
|
+
};
|
|
66133
|
+
}
|
|
66061
66134
|
const hook = inspectSessionHook(res.cwd, res.env);
|
|
66062
66135
|
const finding = sessionHookFinding(hook);
|
|
66063
66136
|
if (!hook.leftover)
|
|
@@ -66077,6 +66150,14 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
66077
66150
|
detail: `${res.cwd} is not in a Workit workspace; no hook installed`,
|
|
66078
66151
|
workspace: null
|
|
66079
66152
|
};
|
|
66153
|
+
if (!workspaceTrailers(res.cwd, res.configDir).enabled) {
|
|
66154
|
+
const removal = removeSessionHook(res.cwd, res.env);
|
|
66155
|
+
return {
|
|
66156
|
+
...removal,
|
|
66157
|
+
detail: `trailers: false; no hook installed (${removal.detail})`,
|
|
66158
|
+
workspace: workspace.name
|
|
66159
|
+
};
|
|
66160
|
+
}
|
|
66080
66161
|
return { ...installSessionHook(res.cwd, res.env), workspace: workspace.name };
|
|
66081
66162
|
}, isPlaceholder = (p) => {
|
|
66082
66163
|
try {
|
|
@@ -72515,7 +72596,10 @@ function runCheckCommand(argv, options) {
|
|
|
72515
72596
|
const buffer = new TailBuffer(MAX_LOG_BYTES);
|
|
72516
72597
|
const redactor = new StreamRedactor((text) => buffer.push(text));
|
|
72517
72598
|
const env = options.env ?? process.env;
|
|
72518
|
-
const
|
|
72599
|
+
const wrapped = Boolean(options.wrapper?.length) && platform !== "win32";
|
|
72600
|
+
const command = wrapped ? [...options.wrapper, ...options.shell ? ["/bin/sh", "-c", argv[0]] : argv] : argv;
|
|
72601
|
+
const shell = wrapped ? false : options.shell;
|
|
72602
|
+
const plan = planSpawn(command, { platform, env, cwd: options.cwd, shell });
|
|
72519
72603
|
return new Promise((resolve) => {
|
|
72520
72604
|
let settled = false;
|
|
72521
72605
|
let timedOut = false;
|
|
@@ -72578,6 +72662,8 @@ function runCheckCommand(argv, options) {
|
|
|
72578
72662
|
finish(null, null, error.code ?? error.message);
|
|
72579
72663
|
return;
|
|
72580
72664
|
}
|
|
72665
|
+
if (child.pid)
|
|
72666
|
+
options.onSpawn?.(child.pid);
|
|
72581
72667
|
if (!windows)
|
|
72582
72668
|
for (const name of FORWARDED_SIGNALS) {
|
|
72583
72669
|
const handler = () => {
|
|
@@ -72915,18 +73001,503 @@ var init_grant = __esm(() => {
|
|
|
72915
73001
|
};
|
|
72916
73002
|
});
|
|
72917
73003
|
|
|
73004
|
+
// packages/workit-core/src/core/resources.ts
|
|
73005
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
73006
|
+
import fs20 from "node:fs";
|
|
73007
|
+
import os14 from "node:os";
|
|
73008
|
+
import path40 from "node:path";
|
|
73009
|
+
function memoryValue(value, host) {
|
|
73010
|
+
const text = typeof value === "number" ? String(value) : typeof value === "string" ? value : "";
|
|
73011
|
+
const percent = PERCENT.exec(text.trim());
|
|
73012
|
+
if (percent) {
|
|
73013
|
+
const share = Number(percent[1]);
|
|
73014
|
+
if (share <= 0 || share > 100)
|
|
73015
|
+
return new Error(`memoryMax ${text} must be 1-100%`);
|
|
73016
|
+
return Math.floor(host.totalMemBytes * share / 100);
|
|
73017
|
+
}
|
|
73018
|
+
const size = SIZE.exec(text.trim());
|
|
73019
|
+
if (size && Number(size[1]) > 0)
|
|
73020
|
+
return Math.floor(Number(size[1]) * UNITS[size[2].toUpperCase()]);
|
|
73021
|
+
return new Error(`memoryMax must be a percent of RAM ("40%") or a size ("8G"), got ${text}`);
|
|
73022
|
+
}
|
|
73023
|
+
function cpuValue(value, host) {
|
|
73024
|
+
if (typeof value === "number" && value > 0)
|
|
73025
|
+
return Math.round(value * 100);
|
|
73026
|
+
const percent = typeof value === "string" ? PERCENT.exec(value.trim()) : null;
|
|
73027
|
+
if (percent) {
|
|
73028
|
+
const share = Number(percent[1]);
|
|
73029
|
+
if (share <= 0 || share > 100)
|
|
73030
|
+
return new Error(`cpuQuota ${percent[0]} must be 1-100%`);
|
|
73031
|
+
return Math.max(1, Math.round(host.cpus * share));
|
|
73032
|
+
}
|
|
73033
|
+
return new Error(`cpuQuota must be a percent of the machine ("70%") or a CPU count, got ${String(value)}`);
|
|
73034
|
+
}
|
|
73035
|
+
function resolveResourceSettings(sources, host = hostShape()) {
|
|
73036
|
+
let memory = Math.floor(host.totalMemBytes * DEFAULT_MEMORY_SHARE);
|
|
73037
|
+
let cpu = Math.max(1, Math.round(host.cpus * DEFAULT_CPU_SHARE * 100));
|
|
73038
|
+
const settings = {
|
|
73039
|
+
slots: DEFAULT_SLOTS,
|
|
73040
|
+
heavy: [...DEFAULT_HEAVY_CHECKS],
|
|
73041
|
+
memoryMax: "",
|
|
73042
|
+
cpuQuota: "",
|
|
73043
|
+
waitMs: DEFAULT_WAIT_S * 1000,
|
|
73044
|
+
scope: true
|
|
73045
|
+
};
|
|
73046
|
+
const warnings = [];
|
|
73047
|
+
const ordered = [
|
|
73048
|
+
...sources.filter((source) => source.trust === "machine"),
|
|
73049
|
+
...sources.filter((source) => source.trust === "repo")
|
|
73050
|
+
];
|
|
73051
|
+
for (const { label, trust, value } of ordered) {
|
|
73052
|
+
if (value === undefined || value === null)
|
|
73053
|
+
continue;
|
|
73054
|
+
if (!isRecord7(value)) {
|
|
73055
|
+
warnings.push(`${label}: resources must be an object`);
|
|
73056
|
+
continue;
|
|
73057
|
+
}
|
|
73058
|
+
const repo = trust === "repo";
|
|
73059
|
+
const warn = (field, message) => warnings.push(`${label}: ${field}: ${message}`);
|
|
73060
|
+
const machineOnly = (field) => warn(field, "is machine-wide; set it in your workspaces.json, not in the repo");
|
|
73061
|
+
if (value.slots !== undefined) {
|
|
73062
|
+
const slots = value.slots;
|
|
73063
|
+
if (repo)
|
|
73064
|
+
machineOnly("slots");
|
|
73065
|
+
else if (typeof slots === "number" && Number.isInteger(slots) && slots >= 1 && slots <= MAX_SLOTS)
|
|
73066
|
+
settings.slots = slots;
|
|
73067
|
+
else
|
|
73068
|
+
warn("slots", `must be an integer 1-${MAX_SLOTS}`);
|
|
73069
|
+
}
|
|
73070
|
+
if (value.scope !== undefined) {
|
|
73071
|
+
if (repo)
|
|
73072
|
+
machineOnly("scope");
|
|
73073
|
+
else if (typeof value.scope === "boolean")
|
|
73074
|
+
settings.scope = value.scope;
|
|
73075
|
+
else
|
|
73076
|
+
warn("scope", "must be true or false");
|
|
73077
|
+
}
|
|
73078
|
+
if (value.heavy !== undefined) {
|
|
73079
|
+
const heavy = isRecord7(value.heavy) ? Object.keys(value.heavy) : value.heavy;
|
|
73080
|
+
if (!Array.isArray(heavy) || !heavy.every((item) => typeof item === "string"))
|
|
73081
|
+
warn("heavy", "must be a list of check names");
|
|
73082
|
+
else
|
|
73083
|
+
settings.heavy = repo ? [...new Set([...settings.heavy, ...heavy])] : heavy;
|
|
73084
|
+
}
|
|
73085
|
+
if (value.memoryMax !== undefined) {
|
|
73086
|
+
const next = memoryValue(value.memoryMax, host);
|
|
73087
|
+
if (next instanceof Error)
|
|
73088
|
+
warn("memoryMax", next.message);
|
|
73089
|
+
else if (repo && next > memory)
|
|
73090
|
+
warn("memoryMax", "a repo can only lower it");
|
|
73091
|
+
else
|
|
73092
|
+
memory = next;
|
|
73093
|
+
}
|
|
73094
|
+
if (value.cpuQuota !== undefined) {
|
|
73095
|
+
const next = cpuValue(value.cpuQuota, host);
|
|
73096
|
+
if (next instanceof Error)
|
|
73097
|
+
warn("cpuQuota", next.message);
|
|
73098
|
+
else if (repo && next > cpu)
|
|
73099
|
+
warn("cpuQuota", "a repo can only lower it");
|
|
73100
|
+
else
|
|
73101
|
+
cpu = next;
|
|
73102
|
+
}
|
|
73103
|
+
if (value.wait !== undefined) {
|
|
73104
|
+
if (typeof value.wait === "number" && value.wait >= 0)
|
|
73105
|
+
settings.waitMs = value.wait * 1000;
|
|
73106
|
+
else
|
|
73107
|
+
warn("wait", "must be a number of seconds");
|
|
73108
|
+
}
|
|
73109
|
+
}
|
|
73110
|
+
settings.memoryMax = String(memory);
|
|
73111
|
+
settings.cpuQuota = `${cpu}%`;
|
|
73112
|
+
return { settings, warnings };
|
|
73113
|
+
}
|
|
73114
|
+
function stateDir(env, platform = process.platform) {
|
|
73115
|
+
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
73116
|
+
return env.WORKFLOW_TOOLKIT_STATE;
|
|
73117
|
+
if (env.XDG_STATE_HOME)
|
|
73118
|
+
return path40.join(env.XDG_STATE_HOME, "workit");
|
|
73119
|
+
const home = env.HOME || os14.homedir();
|
|
73120
|
+
if (platform === "darwin")
|
|
73121
|
+
return path40.join(home, "Library", "Application Support", "workit");
|
|
73122
|
+
if (platform === "win32")
|
|
73123
|
+
return path40.join(env.LOCALAPPDATA ?? path40.join(home, "AppData", "Local"), "workit");
|
|
73124
|
+
return path40.join(home, ".local", "state", "workit");
|
|
73125
|
+
}
|
|
73126
|
+
function scopeBusy(unit) {
|
|
73127
|
+
if (process.platform !== "linux" || !SCOPE_UNIT.test(unit))
|
|
73128
|
+
return false;
|
|
73129
|
+
const uid = process.getuid?.() ?? 0;
|
|
73130
|
+
const manager = `/sys/fs/cgroup/user.slice/user-${uid}.slice/user@${uid}.service`;
|
|
73131
|
+
return [`${manager}/app.slice/${unit}`, `${manager}/${unit}`].some((dir) => (readText3(`${dir}/cgroup.procs`) ?? "").trim() !== "");
|
|
73132
|
+
}
|
|
73133
|
+
function isAncestor(pid) {
|
|
73134
|
+
if (process.platform !== "linux")
|
|
73135
|
+
return true;
|
|
73136
|
+
let current = process.pid;
|
|
73137
|
+
for (let step = 0;step < 128 && current > 1; step += 1) {
|
|
73138
|
+
if (current === pid)
|
|
73139
|
+
return true;
|
|
73140
|
+
const ppid = Number(statFields(current)?.[1]);
|
|
73141
|
+
if (!Number.isInteger(ppid) || ppid <= 0)
|
|
73142
|
+
return false;
|
|
73143
|
+
current = ppid;
|
|
73144
|
+
}
|
|
73145
|
+
return current === pid;
|
|
73146
|
+
}
|
|
73147
|
+
function staleReason(lease, probe = {}) {
|
|
73148
|
+
const boot = probe.boot === undefined ? bootId() : probe.boot;
|
|
73149
|
+
if (lease.bootId && boot) {
|
|
73150
|
+
if (lease.bootId !== boot)
|
|
73151
|
+
return "taken before a reboot";
|
|
73152
|
+
} else if (typeof lease.bootTime === "number") {
|
|
73153
|
+
const now = probe.bootTime === undefined ? bootTime() : probe.bootTime;
|
|
73154
|
+
if (now !== null && Math.abs(now - lease.bootTime) > BOOT_SLACK_MS)
|
|
73155
|
+
return "taken before a reboot";
|
|
73156
|
+
}
|
|
73157
|
+
const start = lease.processStart ? (probe.start ?? processStart)(lease.pid) : null;
|
|
73158
|
+
const age = (probe.now ?? Date.now()) - Date.parse(lease.startedAt);
|
|
73159
|
+
if (!start && typeof lease.maxAgeMs === "number" && age > lease.maxAgeMs)
|
|
73160
|
+
return `older than its ${Math.round(lease.maxAgeMs / 1000)} s bound`;
|
|
73161
|
+
const holder = !(probe.alive ?? pidAlive2)(lease.pid) ? `holder pid ${lease.pid} is gone` : start && start !== lease.processStart ? `pid ${lease.pid} now belongs to another process` : null;
|
|
73162
|
+
if (!holder)
|
|
73163
|
+
return null;
|
|
73164
|
+
if (typeof lease.pgid === "number" && lease.pgid > 0 && (probe.group ?? groupAlive)(lease.pgid))
|
|
73165
|
+
return null;
|
|
73166
|
+
if (lease.scope && (probe.scope ?? scopeBusy)(lease.scope))
|
|
73167
|
+
return null;
|
|
73168
|
+
return holder;
|
|
73169
|
+
}
|
|
73170
|
+
function reclaim(file, stale) {
|
|
73171
|
+
const guard = `${file}.reclaim`;
|
|
73172
|
+
try {
|
|
73173
|
+
fs20.mkdirSync(guard);
|
|
73174
|
+
} catch {
|
|
73175
|
+
try {
|
|
73176
|
+
if (Date.now() - fs20.statSync(guard).mtimeMs > GUARD_TTL_MS)
|
|
73177
|
+
fs20.rmSync(guard, { force: true, recursive: true });
|
|
73178
|
+
} catch {}
|
|
73179
|
+
return false;
|
|
73180
|
+
}
|
|
73181
|
+
try {
|
|
73182
|
+
let ageMs = 0;
|
|
73183
|
+
try {
|
|
73184
|
+
ageMs = Date.now() - fs20.statSync(file).mtimeMs;
|
|
73185
|
+
} catch {
|
|
73186
|
+
return true;
|
|
73187
|
+
}
|
|
73188
|
+
if (!stale(parseLease(readText3(file)), ageMs))
|
|
73189
|
+
return false;
|
|
73190
|
+
fs20.rmSync(file, { force: true });
|
|
73191
|
+
return true;
|
|
73192
|
+
} finally {
|
|
73193
|
+
fs20.rmSync(guard, { force: true, recursive: true });
|
|
73194
|
+
}
|
|
73195
|
+
}
|
|
73196
|
+
function tryOnce(options, record, reclaimed) {
|
|
73197
|
+
const holders = [];
|
|
73198
|
+
const text = `${JSON.stringify(record)}
|
|
73199
|
+
`;
|
|
73200
|
+
for (let slot = 0;slot < options.slots; slot += 1) {
|
|
73201
|
+
const file = path40.join(options.dir, `slot-${slot}.json`);
|
|
73202
|
+
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
73203
|
+
try {
|
|
73204
|
+
const fd = fs20.openSync(file, "wx", 384);
|
|
73205
|
+
try {
|
|
73206
|
+
fs20.writeSync(fd, text);
|
|
73207
|
+
} finally {
|
|
73208
|
+
fs20.closeSync(fd);
|
|
73209
|
+
}
|
|
73210
|
+
return { slot, file };
|
|
73211
|
+
} catch (error) {
|
|
73212
|
+
if (error.code !== "EEXIST")
|
|
73213
|
+
throw error;
|
|
73214
|
+
}
|
|
73215
|
+
const lease = parseLease(readText3(file));
|
|
73216
|
+
let reason = null;
|
|
73217
|
+
const removed = reclaim(file, (current, ageMs) => {
|
|
73218
|
+
reason = current ? staleReason(current, options.probe) : ageMs > UNREADABLE_TTL_MS ? "unreadable" : null;
|
|
73219
|
+
return reason !== null;
|
|
73220
|
+
});
|
|
73221
|
+
if (removed) {
|
|
73222
|
+
if (lease && reason)
|
|
73223
|
+
reclaimed.push({ ...holderOf(lease), reason });
|
|
73224
|
+
continue;
|
|
73225
|
+
}
|
|
73226
|
+
if (lease)
|
|
73227
|
+
holders.push(holderOf(lease));
|
|
73228
|
+
break;
|
|
73229
|
+
}
|
|
73230
|
+
}
|
|
73231
|
+
return { holders };
|
|
73232
|
+
}
|
|
73233
|
+
function removeOwn(file, id) {
|
|
73234
|
+
if (parseLease(readText3(file))?.id === id)
|
|
73235
|
+
fs20.rmSync(file, { force: true });
|
|
73236
|
+
}
|
|
73237
|
+
async function acquireLease(options) {
|
|
73238
|
+
const pid = options.pid ?? process.pid;
|
|
73239
|
+
const boot = bootId();
|
|
73240
|
+
const record = {
|
|
73241
|
+
id: randomUUID5(),
|
|
73242
|
+
pid,
|
|
73243
|
+
processStart: processStart(pid),
|
|
73244
|
+
bootId: boot,
|
|
73245
|
+
bootTime: boot ? null : bootTime(),
|
|
73246
|
+
pgid: null,
|
|
73247
|
+
maxAgeMs: options.maxAgeMs ?? null,
|
|
73248
|
+
check: options.check,
|
|
73249
|
+
cwd: options.cwd,
|
|
73250
|
+
session: options.session,
|
|
73251
|
+
startedAt: new Date().toISOString(),
|
|
73252
|
+
scope: null
|
|
73253
|
+
};
|
|
73254
|
+
try {
|
|
73255
|
+
fs20.mkdirSync(options.dir, { recursive: true, mode: 448 });
|
|
73256
|
+
} catch (error) {
|
|
73257
|
+
return { ok: false, reason: "unavailable", error: error.message };
|
|
73258
|
+
}
|
|
73259
|
+
const started = Date.now();
|
|
73260
|
+
const reclaimed = [];
|
|
73261
|
+
let waitedOn = [];
|
|
73262
|
+
for (;; ) {
|
|
73263
|
+
let outcome;
|
|
73264
|
+
try {
|
|
73265
|
+
outcome = tryOnce(options, record, reclaimed);
|
|
73266
|
+
} catch (error) {
|
|
73267
|
+
return { ok: false, reason: "unavailable", error: error.message };
|
|
73268
|
+
}
|
|
73269
|
+
if ("slot" in outcome) {
|
|
73270
|
+
const { file, slot } = outcome;
|
|
73271
|
+
let released = false;
|
|
73272
|
+
const release = () => {
|
|
73273
|
+
if (released)
|
|
73274
|
+
return;
|
|
73275
|
+
released = true;
|
|
73276
|
+
process.off("exit", release);
|
|
73277
|
+
removeOwn(file, record.id);
|
|
73278
|
+
};
|
|
73279
|
+
process.on("exit", release);
|
|
73280
|
+
return {
|
|
73281
|
+
ok: true,
|
|
73282
|
+
lease: {
|
|
73283
|
+
record,
|
|
73284
|
+
slot,
|
|
73285
|
+
file,
|
|
73286
|
+
waitedOn,
|
|
73287
|
+
waitedMs: waitedOn.length ? Date.now() - started : 0,
|
|
73288
|
+
reclaimed,
|
|
73289
|
+
release
|
|
73290
|
+
}
|
|
73291
|
+
};
|
|
73292
|
+
}
|
|
73293
|
+
const waited = Date.now() - started;
|
|
73294
|
+
if (!waitedOn.length) {
|
|
73295
|
+
waitedOn = outcome.holders;
|
|
73296
|
+
options.onWait?.(outcome.holders);
|
|
73297
|
+
}
|
|
73298
|
+
if (waited >= options.waitMs)
|
|
73299
|
+
return { ok: false, reason: "timeout", holders: outcome.holders, waitedMs: waited };
|
|
73300
|
+
await sleep(Math.min(options.pollMs ?? 2000, Math.max(1, options.waitMs - waited)));
|
|
73301
|
+
}
|
|
73302
|
+
}
|
|
73303
|
+
function updateLease(lease, patch) {
|
|
73304
|
+
Object.assign(lease.record, patch);
|
|
73305
|
+
try {
|
|
73306
|
+
if (parseLease(readText3(lease.file))?.id !== lease.record.id)
|
|
73307
|
+
return;
|
|
73308
|
+
const temp = `${lease.file}.${process.pid}.tmp`;
|
|
73309
|
+
fs20.writeFileSync(temp, `${JSON.stringify(lease.record)}
|
|
73310
|
+
`, { mode: 384 });
|
|
73311
|
+
fs20.renameSync(temp, lease.file);
|
|
73312
|
+
} catch {}
|
|
73313
|
+
}
|
|
73314
|
+
function heldLease(dir, id, options = {}) {
|
|
73315
|
+
if (!id || !/^[0-9a-f-]{36}$/u.test(id))
|
|
73316
|
+
return null;
|
|
73317
|
+
let names;
|
|
73318
|
+
try {
|
|
73319
|
+
names = fs20.readdirSync(dir);
|
|
73320
|
+
} catch {
|
|
73321
|
+
return null;
|
|
73322
|
+
}
|
|
73323
|
+
for (const name of names) {
|
|
73324
|
+
if (!/^slot-\d+\.json$/u.test(name))
|
|
73325
|
+
continue;
|
|
73326
|
+
const lease = parseLease(readText3(path40.join(dir, name)));
|
|
73327
|
+
if (lease?.id === id && !staleReason(lease, options.probe) && (options.ancestor ?? isAncestor)(lease.pid))
|
|
73328
|
+
return lease;
|
|
73329
|
+
}
|
|
73330
|
+
return null;
|
|
73331
|
+
}
|
|
73332
|
+
function describeHolder(holder, now = Date.now()) {
|
|
73333
|
+
const seconds = Math.max(0, Math.round((now - Date.parse(holder.startedAt)) / 1000));
|
|
73334
|
+
const age = seconds >= 60 ? `${Math.floor(seconds / 60)}m ${seconds % 60}s` : `${seconds}s`;
|
|
73335
|
+
return `${holder.check}, pid ${holder.pid}${holder.session ? `, session ${holder.session}` : ""}, ${age}, ${holder.cwd}`;
|
|
73336
|
+
}
|
|
73337
|
+
function scopeSupport(env, platform = process.platform, read = readText3) {
|
|
73338
|
+
if (platform !== "linux")
|
|
73339
|
+
return { ok: false, reason: `no systemd scopes on ${platform}` };
|
|
73340
|
+
if (!onPath("systemd-run", env))
|
|
73341
|
+
return { ok: false, reason: "systemd-run is not on PATH" };
|
|
73342
|
+
const uid = process.getuid?.() ?? 0;
|
|
73343
|
+
const runtime = env.XDG_RUNTIME_DIR || `/run/user/${uid}`;
|
|
73344
|
+
if (!fs20.existsSync(path40.join(runtime, "systemd", "private")))
|
|
73345
|
+
return { ok: false, reason: "no user systemd manager is running" };
|
|
73346
|
+
const controllers = read(`/sys/fs/cgroup/user.slice/user-${uid}.slice/user@${uid}.service/cgroup.controllers`);
|
|
73347
|
+
if (controllers === null)
|
|
73348
|
+
return { ok: false, reason: "cgroup v2 is not mounted for the user manager" };
|
|
73349
|
+
const missing = ["memory", "cpu"].filter((name) => !controllers.split(/\s+/u).includes(name));
|
|
73350
|
+
if (missing.length)
|
|
73351
|
+
return { ok: false, reason: `the user manager is not delegated ${missing.join(" and ")}` };
|
|
73352
|
+
return { ok: true };
|
|
73353
|
+
}
|
|
73354
|
+
function scopePrefix(unit, settings) {
|
|
73355
|
+
return [
|
|
73356
|
+
"systemd-run",
|
|
73357
|
+
"--user",
|
|
73358
|
+
"--scope",
|
|
73359
|
+
"--quiet",
|
|
73360
|
+
"--collect",
|
|
73361
|
+
`--unit=${unit}`,
|
|
73362
|
+
"-p",
|
|
73363
|
+
`MemoryMax=${settings.memoryMax}`,
|
|
73364
|
+
"-p",
|
|
73365
|
+
`CPUQuota=${settings.cpuQuota}`,
|
|
73366
|
+
"--"
|
|
73367
|
+
];
|
|
73368
|
+
}
|
|
73369
|
+
async function runScoped(run, wrapper, sinks) {
|
|
73370
|
+
if (!wrapper)
|
|
73371
|
+
return { result: await run(null, sinks), fallback: null };
|
|
73372
|
+
let state = "deciding";
|
|
73373
|
+
let firstLine = "";
|
|
73374
|
+
const held = [];
|
|
73375
|
+
const flush = () => {
|
|
73376
|
+
state = "passing";
|
|
73377
|
+
for (const [sink, text] of held.splice(0))
|
|
73378
|
+
sinks[sink](text);
|
|
73379
|
+
};
|
|
73380
|
+
const result = await run(wrapper, {
|
|
73381
|
+
onStdout: (text) => {
|
|
73382
|
+
if (state === "passing")
|
|
73383
|
+
return sinks.onStdout(text);
|
|
73384
|
+
held.push(["onStdout", text]);
|
|
73385
|
+
if (state === "deciding")
|
|
73386
|
+
flush();
|
|
73387
|
+
},
|
|
73388
|
+
onStderr: (text) => {
|
|
73389
|
+
if (state === "passing")
|
|
73390
|
+
return sinks.onStderr(text);
|
|
73391
|
+
held.push(["onStderr", text]);
|
|
73392
|
+
if (state === "held")
|
|
73393
|
+
return;
|
|
73394
|
+
firstLine += text;
|
|
73395
|
+
const newline = firstLine.indexOf(`
|
|
73396
|
+
`);
|
|
73397
|
+
if (newline < 0 && firstLine.length < 4096)
|
|
73398
|
+
return;
|
|
73399
|
+
if (SCOPE_START_FAILURE.test(firstLine.slice(0, newline < 0 ? undefined : newline)))
|
|
73400
|
+
state = "held";
|
|
73401
|
+
else
|
|
73402
|
+
flush();
|
|
73403
|
+
}
|
|
73404
|
+
});
|
|
73405
|
+
const lines = tailLines(result.log, 2);
|
|
73406
|
+
if (state !== "passing" && result.exitCode !== 0 && lines.length === 1 && SCOPE_START_FAILURE.test(lines[0]))
|
|
73407
|
+
return { result: await run(null, sinks), fallback: lines[0] };
|
|
73408
|
+
flush();
|
|
73409
|
+
return { result, fallback: null };
|
|
73410
|
+
}
|
|
73411
|
+
function firstNotice(env, session, key, now = Date.now()) {
|
|
73412
|
+
const id = (session ?? `ppid-${process.ppid}`).replace(/[^A-Za-z0-9_.-]/gu, "_").slice(0, 120);
|
|
73413
|
+
const dir = path40.join(stateDir(env), "notices");
|
|
73414
|
+
try {
|
|
73415
|
+
fs20.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
73416
|
+
fs20.writeFileSync(path40.join(dir, `${key}-${id}`), `${new Date(now).toISOString()}
|
|
73417
|
+
`, {
|
|
73418
|
+
flag: "wx",
|
|
73419
|
+
mode: 384
|
|
73420
|
+
});
|
|
73421
|
+
} catch (error) {
|
|
73422
|
+
return error.code !== "EEXIST";
|
|
73423
|
+
}
|
|
73424
|
+
try {
|
|
73425
|
+
for (const name of fs20.readdirSync(dir)) {
|
|
73426
|
+
const file = path40.join(dir, name);
|
|
73427
|
+
if (now - fs20.statSync(file).mtimeMs > NOTICE_TTL_MS)
|
|
73428
|
+
fs20.rmSync(file, { force: true });
|
|
73429
|
+
}
|
|
73430
|
+
} catch {}
|
|
73431
|
+
return true;
|
|
73432
|
+
}
|
|
73433
|
+
var DEFAULT_HEAVY_CHECKS, DEFAULT_SLOTS = 1, DEFAULT_WAIT_S = 600, DEFAULT_MEMORY_SHARE = 0.4, DEFAULT_CPU_SHARE = 0.7, MAX_SLOTS = 64, UNREADABLE_TTL_MS = 30000, GUARD_TTL_MS = 30000, hostShape = () => ({
|
|
73434
|
+
totalMemBytes: os14.totalmem(),
|
|
73435
|
+
cpus: Math.max(1, os14.availableParallelism?.() ?? os14.cpus().length)
|
|
73436
|
+
}), isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SIZE, PERCENT, UNITS, leaseDir = (env) => path40.join(stateDir(env), "leases"), readText3 = (file) => {
|
|
73437
|
+
try {
|
|
73438
|
+
return fs20.readFileSync(file, "utf8");
|
|
73439
|
+
} catch {
|
|
73440
|
+
return null;
|
|
73441
|
+
}
|
|
73442
|
+
}, bootId = () => readText3("/proc/sys/kernel/random/boot_id")?.trim() || null, bootTime = () => Math.round((Date.now() - os14.uptime() * 1000) / 1000) * 1000, BOOT_SLACK_MS = 120000, statFields = (pid) => {
|
|
73443
|
+
const stat = readText3(`/proc/${pid}/stat`);
|
|
73444
|
+
return stat ? stat.slice(stat.lastIndexOf(")") + 2).split(" ") : null;
|
|
73445
|
+
}, processStart = (pid) => statFields(pid)?.[19] ?? null, pidAlive2 = (pid) => {
|
|
73446
|
+
try {
|
|
73447
|
+
process.kill(pid, 0);
|
|
73448
|
+
return true;
|
|
73449
|
+
} catch (error) {
|
|
73450
|
+
return error.code === "EPERM";
|
|
73451
|
+
}
|
|
73452
|
+
}, groupAlive = (pgid) => process.platform === "win32" ? pidAlive2(pgid) : pidAlive2(-pgid), SCOPE_UNIT, parseLease = (text) => {
|
|
73453
|
+
if (!text)
|
|
73454
|
+
return null;
|
|
73455
|
+
try {
|
|
73456
|
+
const value = JSON.parse(text);
|
|
73457
|
+
return isRecord7(value) && typeof value.id === "string" && typeof value.pid === "number" ? value : null;
|
|
73458
|
+
} catch {
|
|
73459
|
+
return null;
|
|
73460
|
+
}
|
|
73461
|
+
}, holderOf = (lease) => ({
|
|
73462
|
+
pid: lease.pid,
|
|
73463
|
+
check: lease.check,
|
|
73464
|
+
cwd: lease.cwd,
|
|
73465
|
+
session: lease.session,
|
|
73466
|
+
startedAt: lease.startedAt
|
|
73467
|
+
}), sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), onPath = (command, env) => (env.PATH ?? "").split(path40.delimiter).filter(Boolean).some((dir) => {
|
|
73468
|
+
try {
|
|
73469
|
+
fs20.accessSync(path40.join(dir, command), fs20.constants.X_OK);
|
|
73470
|
+
return true;
|
|
73471
|
+
} catch {
|
|
73472
|
+
return false;
|
|
73473
|
+
}
|
|
73474
|
+
}), SCOPE_START_FAILURE, NOTICE_TTL_MS;
|
|
73475
|
+
var init_resources = __esm(() => {
|
|
73476
|
+
init_checks3();
|
|
73477
|
+
DEFAULT_HEAVY_CHECKS = ["test", "build", "typecheck"];
|
|
73478
|
+
SIZE = /^(\d+(?:\.\d+)?)([KMGT]?)$/iu;
|
|
73479
|
+
PERCENT = /^(\d+(?:\.\d+)?)%$/u;
|
|
73480
|
+
UNITS = { "": 1, K: 1024, M: 1024 ** 2, G: 1024 ** 3, T: 1024 ** 4 };
|
|
73481
|
+
SCOPE_UNIT = /^[\w.@-]+\.scope$/u;
|
|
73482
|
+
SCOPE_START_FAILURE = /^Failed to (?:connect to bus|create bus connection|start transient scope unit|parse \S+?=)/u;
|
|
73483
|
+
NOTICE_TTL_MS = 7 * 86400000;
|
|
73484
|
+
});
|
|
73485
|
+
|
|
72918
73486
|
// packages/workit-cli/src/verbs/check.ts
|
|
72919
73487
|
var exports_check = {};
|
|
72920
73488
|
__export(exports_check, {
|
|
72921
73489
|
run: () => run10
|
|
72922
73490
|
});
|
|
72923
|
-
import
|
|
73491
|
+
import os15 from "node:os";
|
|
73492
|
+
import path41 from "node:path";
|
|
72924
73493
|
function parse5(argv) {
|
|
72925
73494
|
const split = argv.indexOf("--");
|
|
72926
73495
|
const head = split >= 0 ? argv.slice(0, split) : [...argv];
|
|
72927
73496
|
const options = {
|
|
72928
73497
|
name: null,
|
|
72929
73498
|
shell: false,
|
|
73499
|
+
heavy: false,
|
|
73500
|
+
noLease: false,
|
|
72930
73501
|
timeoutMs: 0,
|
|
72931
73502
|
task: null,
|
|
72932
73503
|
base: null,
|
|
@@ -72946,6 +73517,13 @@ function parse5(argv) {
|
|
|
72946
73517
|
options.shell = true;
|
|
72947
73518
|
continue;
|
|
72948
73519
|
}
|
|
73520
|
+
if (flag === "--heavy" || flag === "--no-lease") {
|
|
73521
|
+
if (flag === "--heavy")
|
|
73522
|
+
options.heavy = true;
|
|
73523
|
+
else
|
|
73524
|
+
options.noLease = true;
|
|
73525
|
+
continue;
|
|
73526
|
+
}
|
|
72949
73527
|
if (flag === "--name" || flag === "--task" || flag === "--base" || flag === "--timeout") {
|
|
72950
73528
|
const got = value();
|
|
72951
73529
|
if (got instanceof Error)
|
|
@@ -73069,6 +73647,90 @@ function boundedArgv(argv) {
|
|
|
73069
73647
|
const truncated = cut.length !== argv.length || cut.some((item, index) => item !== argv[index]);
|
|
73070
73648
|
return { argv: cut, truncated };
|
|
73071
73649
|
}
|
|
73650
|
+
async function workspaceResources(io) {
|
|
73651
|
+
const env = io.env;
|
|
73652
|
+
const dir = env.WORKFLOW_TOOLKIT_CONFIG ?? env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path41.join(env.XDG_CONFIG_HOME || path41.join(env.HOME || os15.homedir(), ".config"), "workit");
|
|
73653
|
+
try {
|
|
73654
|
+
await Promise.resolve().then(() => init_workspaces());
|
|
73655
|
+
const workspace = resolveWorkspaceFrom(io.cwd, dir, env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined);
|
|
73656
|
+
return { value: workspace?.resources, warning: null };
|
|
73657
|
+
} catch (error) {
|
|
73658
|
+
return { value: undefined, warning: `workspaces.json: ${error.message}` };
|
|
73659
|
+
}
|
|
73660
|
+
}
|
|
73661
|
+
async function prepareHeavy(io, settings, label, runCwd, timeoutMs) {
|
|
73662
|
+
const session = hostSessionFromEnv(io.env).session;
|
|
73663
|
+
const dir = leaseDir(io.env);
|
|
73664
|
+
const outer = heldLease(dir, io.env.WORKIT_LEASE);
|
|
73665
|
+
if (outer)
|
|
73666
|
+
return {
|
|
73667
|
+
lease: null,
|
|
73668
|
+
leaseReport: { slot: null, waitedMs: 0, holder: null, reclaimed: [], inherited: true },
|
|
73669
|
+
scope: { unit: outer.scope, reason: "inherited from the outer heavy check" },
|
|
73670
|
+
wrapper: null,
|
|
73671
|
+
env: io.env
|
|
73672
|
+
};
|
|
73673
|
+
const acquired = await acquireLease({
|
|
73674
|
+
dir,
|
|
73675
|
+
slots: settings.slots,
|
|
73676
|
+
check: label,
|
|
73677
|
+
cwd: runCwd,
|
|
73678
|
+
session,
|
|
73679
|
+
waitMs: settings.waitMs,
|
|
73680
|
+
pollMs: 1000,
|
|
73681
|
+
maxAgeMs: (timeoutMs || MAX_UNVERIFIED_RUN_MS) + 60000,
|
|
73682
|
+
onWait: (holders) => io.stderr(`workit: waiting for the heavy-check lease (${settings.slots} slot${settings.slots === 1 ? "" : "s"}): held by ${holders.map((holder) => describeHolder(holder)).join("; ")}
|
|
73683
|
+
`)
|
|
73684
|
+
});
|
|
73685
|
+
if (!acquired.ok && acquired.reason === "timeout") {
|
|
73686
|
+
const holders = acquired.holders.map((holder) => describeHolder(holder)).join("; ");
|
|
73687
|
+
emit(io, fail("busy", `the heavy-check lease stayed busy for ${Math.round(acquired.waitedMs / 1000)} s`, {
|
|
73688
|
+
data: { lease: { waitedMs: acquired.waitedMs, holders: acquired.holders } },
|
|
73689
|
+
unblock: `wait for ${holders || "the holders"} to finish, then run it again`
|
|
73690
|
+
}));
|
|
73691
|
+
return 4;
|
|
73692
|
+
}
|
|
73693
|
+
let lease = null;
|
|
73694
|
+
if (acquired.ok)
|
|
73695
|
+
lease = acquired.lease;
|
|
73696
|
+
else
|
|
73697
|
+
io.stderr(`workit: running without the heavy-check lease: ${acquired.error}
|
|
73698
|
+
`);
|
|
73699
|
+
for (const stale of lease?.reclaimed ?? [])
|
|
73700
|
+
io.stderr(`workit: reclaimed a stale heavy-check lease (${stale.reason}): ${describeHolder(stale)}
|
|
73701
|
+
`);
|
|
73702
|
+
const leaseReport = lease ? {
|
|
73703
|
+
slot: lease.slot,
|
|
73704
|
+
waitedMs: lease.waitedMs,
|
|
73705
|
+
holder: lease.waitedOn[0] ?? null,
|
|
73706
|
+
reclaimed: lease.reclaimed,
|
|
73707
|
+
inherited: false
|
|
73708
|
+
} : null;
|
|
73709
|
+
const env = lease ? { ...io.env, WORKIT_LEASE: lease.record.id } : io.env;
|
|
73710
|
+
const support = settings.scope ? scopeSupport(io.env) : { ok: false, reason: "resources.scope is false" };
|
|
73711
|
+
if (!support.ok) {
|
|
73712
|
+
if (settings.scope && firstNotice(io.env, session, "no-scope"))
|
|
73713
|
+
io.stderr(`workit: heavy checks run without a memory/CPU limit here: ${support.reason} (said once per session)
|
|
73714
|
+
`);
|
|
73715
|
+
return {
|
|
73716
|
+
lease,
|
|
73717
|
+
leaseReport,
|
|
73718
|
+
scope: { unit: null, reason: support.reason },
|
|
73719
|
+
wrapper: null,
|
|
73720
|
+
env
|
|
73721
|
+
};
|
|
73722
|
+
}
|
|
73723
|
+
const unit = `workit-check-${(lease?.record.id ?? "nolease0").slice(0, 8)}-${process.pid}`;
|
|
73724
|
+
if (lease)
|
|
73725
|
+
updateLease(lease, { scope: `${unit}.scope` });
|
|
73726
|
+
return {
|
|
73727
|
+
lease,
|
|
73728
|
+
leaseReport,
|
|
73729
|
+
scope: { unit: `${unit}.scope`, memoryMax: settings.memoryMax, cpuQuota: settings.cpuQuota },
|
|
73730
|
+
wrapper: scopePrefix(unit, settings),
|
|
73731
|
+
env
|
|
73732
|
+
};
|
|
73733
|
+
}
|
|
73072
73734
|
async function run10(argv, io) {
|
|
73073
73735
|
const options = parse5(argv);
|
|
73074
73736
|
if (options instanceof Error)
|
|
@@ -73103,21 +73765,52 @@ async function run10(argv, io) {
|
|
|
73103
73765
|
if (options.shell && command.length !== 1)
|
|
73104
73766
|
return emit(io, fail("invalid_input", '--shell takes the command as one string: workit check --shell -- "<cmd>"'));
|
|
73105
73767
|
}
|
|
73106
|
-
const relCwd = posix(
|
|
73768
|
+
const relCwd = posix(path41.relative(top, runCwd));
|
|
73107
73769
|
const configured = !options.shell && matchesNamedCheck(config, name, command, relCwd);
|
|
73108
73770
|
const actor = actorFromEnv(io.env);
|
|
73109
73771
|
const branchKey = codeKey(io.cwd, { base: options.base });
|
|
73110
73772
|
const detached = branchKey.tree === null ? worktreeTree(io.cwd) : null;
|
|
73111
73773
|
const key = detached ? { ...branchKey, tree: detached.key, dirty: detached.dirty } : branchKey;
|
|
73112
73774
|
const signal = key.tree === null ? null : worktreeSignal(io.cwd);
|
|
73113
|
-
|
|
73114
|
-
|
|
73115
|
-
|
|
73116
|
-
|
|
73117
|
-
|
|
73118
|
-
|
|
73119
|
-
|
|
73120
|
-
|
|
73775
|
+
let heavy = null;
|
|
73776
|
+
if (!options.noLease && (options.heavy || options.command === null)) {
|
|
73777
|
+
const workspace = await workspaceResources(io);
|
|
73778
|
+
const { settings, warnings } = resolveResourceSettings([
|
|
73779
|
+
{ label: "workspaces.json", trust: "machine", value: workspace.value },
|
|
73780
|
+
{ label: CHECKS_FILE, trust: "repo", value: config.resources }
|
|
73781
|
+
]);
|
|
73782
|
+
for (const warning of [workspace.warning, ...warnings])
|
|
73783
|
+
if (warning)
|
|
73784
|
+
io.stderr(`workit: resources: ${warning}
|
|
73785
|
+
`);
|
|
73786
|
+
if (options.heavy || name !== null && settings.heavy.includes(name)) {
|
|
73787
|
+
const prepared = await prepareHeavy(io, settings, name ?? command.join(" "), runCwd, options.timeoutMs);
|
|
73788
|
+
if (typeof prepared === "number")
|
|
73789
|
+
return prepared;
|
|
73790
|
+
heavy = prepared;
|
|
73791
|
+
}
|
|
73792
|
+
}
|
|
73793
|
+
const lease = heavy?.lease ?? null;
|
|
73794
|
+
let result;
|
|
73795
|
+
try {
|
|
73796
|
+
const scoped = await runScoped((wrapper, sinks) => runCheckCommand(command, {
|
|
73797
|
+
cwd: runCwd,
|
|
73798
|
+
shell: options.shell,
|
|
73799
|
+
timeoutMs: options.timeoutMs,
|
|
73800
|
+
env: heavy?.env ?? io.env,
|
|
73801
|
+
...sinks,
|
|
73802
|
+
...wrapper ? { wrapper } : {},
|
|
73803
|
+
...lease ? { onSpawn: (pid) => updateLease(lease, { pgid: pid }) } : {}
|
|
73804
|
+
}), heavy?.wrapper ?? null, { onStdout: io.json ? io.stderr : io.stdout, onStderr: io.stderr });
|
|
73805
|
+
result = scoped.result;
|
|
73806
|
+
if (heavy && scoped.fallback) {
|
|
73807
|
+
io.stderr(`workit: the memory/CPU scope could not start (${scoped.fallback}); ran without it
|
|
73808
|
+
`);
|
|
73809
|
+
heavy.scope = { unit: null, reason: `systemd-run failed: ${scoped.fallback}` };
|
|
73810
|
+
}
|
|
73811
|
+
} finally {
|
|
73812
|
+
lease?.release();
|
|
73813
|
+
}
|
|
73121
73814
|
const treeAfter = key.tree === null ? null : worktreeTree(io.cwd)?.key ?? null;
|
|
73122
73815
|
const modifiedWorktree = key.tree !== null && treeAfter !== key.tree;
|
|
73123
73816
|
const store = storeRoot(io.cwd);
|
|
@@ -73198,6 +73891,8 @@ async function run10(argv, io) {
|
|
|
73198
73891
|
outputBytes: result.outputBytes,
|
|
73199
73892
|
logTruncated: result.truncated,
|
|
73200
73893
|
task: task.task,
|
|
73894
|
+
lease: heavy?.leaseReport ?? null,
|
|
73895
|
+
scope: heavy?.scope ?? null,
|
|
73201
73896
|
...task.note ? { taskNote: task.note } : {},
|
|
73202
73897
|
satisfies: task.satisfies,
|
|
73203
73898
|
stillUnsatisfied: task.stillUnsatisfied,
|
|
@@ -73227,7 +73922,7 @@ async function run10(argv, io) {
|
|
|
73227
73922
|
return recordError.code === "busy" ? 4 : recordError.code === "invalid_input" ? 2 : 5;
|
|
73228
73923
|
return 0;
|
|
73229
73924
|
}
|
|
73230
|
-
var USAGE3 = "workit check <name> | workit check [--name <n>] [--shell] [--timeout <s>] [--task <id>] [--json] -- <cmd…>", RECORD_TAIL_LINES = 20, RECORD_TAIL_CHARS = 200, posix = (value) => value.split(
|
|
73925
|
+
var USAGE3 = "workit check <name> [--no-lease] | workit check [--name <n>] [--shell] [--heavy] [--timeout <s>] [--task <id>] [--json] -- <cmd…>", MAX_UNVERIFIED_RUN_MS, RECORD_TAIL_LINES = 20, RECORD_TAIL_CHARS = 200, posix = (value) => value.split(path41.sep).join("/") || ".", ENGINE_CODES2, FINGERPRINT_VARS;
|
|
73231
73926
|
var init_check = __esm(() => {
|
|
73232
73927
|
init_check_config();
|
|
73233
73928
|
init_checks3();
|
|
@@ -73236,6 +73931,8 @@ var init_check = __esm(() => {
|
|
|
73236
73931
|
init_ledger();
|
|
73237
73932
|
init_output();
|
|
73238
73933
|
init_host_session();
|
|
73934
|
+
init_resources();
|
|
73935
|
+
MAX_UNVERIFIED_RUN_MS = 6 * 3600000;
|
|
73239
73936
|
ENGINE_CODES2 = {
|
|
73240
73937
|
busy: "busy",
|
|
73241
73938
|
not_found: "not_found",
|
|
@@ -73251,7 +73948,7 @@ var success2 = (data) => ({ ok: true, data }), failure3 = (code, error, unblock)
|
|
|
73251
73948
|
// packages/workit-core/src/git/ops.ts
|
|
73252
73949
|
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
73253
73950
|
import { existsSync as existsSync19, readFileSync as readFileSync19 } from "node:fs";
|
|
73254
|
-
import
|
|
73951
|
+
import path42 from "node:path";
|
|
73255
73952
|
function operationInProgress(cwd) {
|
|
73256
73953
|
const markers = [
|
|
73257
73954
|
["rebase-merge", "rebase"],
|
|
@@ -73264,18 +73961,18 @@ function operationInProgress(cwd) {
|
|
|
73264
73961
|
if (!run.ok)
|
|
73265
73962
|
return null;
|
|
73266
73963
|
const found = run.stdout.split(`
|
|
73267
|
-
`).map((line) =>
|
|
73964
|
+
`).map((line) => path42.resolve(cwd, line.trim()));
|
|
73268
73965
|
const conflicted = () => git5(cwd, ["ls-files", "-u"]).stdout.trim() !== "";
|
|
73269
73966
|
for (const [index, [, operation]] of markers.entries()) {
|
|
73270
73967
|
const marker = found[index];
|
|
73271
73968
|
if (!marker || !existsSync19(marker))
|
|
73272
73969
|
continue;
|
|
73273
|
-
if (index === 1 && existsSync19(
|
|
73970
|
+
if (index === 1 && existsSync19(path42.join(marker, "applying")))
|
|
73274
73971
|
return { operation: "am", commitAllowed: false, branch: null };
|
|
73275
|
-
if (index === 0 && existsSync19(
|
|
73972
|
+
if (index === 0 && existsSync19(path42.join(marker, "amend")) && !conflicted()) {
|
|
73276
73973
|
let headName = "";
|
|
73277
73974
|
try {
|
|
73278
|
-
headName = readFileSync19(
|
|
73975
|
+
headName = readFileSync19(path42.join(marker, "head-name"), "utf8").trim();
|
|
73279
73976
|
} catch {}
|
|
73280
73977
|
const branch = headName.startsWith("refs/heads/") ? headName.slice("refs/heads/".length) : null;
|
|
73281
73978
|
return { operation, commitAllowed: branch !== null, branch };
|
|
@@ -73425,6 +74122,40 @@ function lintCommitMessage(cwd, message) {
|
|
|
73425
74122
|
correction: `use a ${detected} commit subject`
|
|
73426
74123
|
};
|
|
73427
74124
|
}
|
|
74125
|
+
function stripSessionTrailers(text, options) {
|
|
74126
|
+
const end = text.length - (/\s*$/u.exec(text)?.[0].length ?? 0);
|
|
74127
|
+
const lines = text.slice(0, end).split(`
|
|
74128
|
+
`);
|
|
74129
|
+
let start = lines.length;
|
|
74130
|
+
while (start > 0 && lines[start - 1]?.trim() !== "")
|
|
74131
|
+
start -= 1;
|
|
74132
|
+
if (start === 0 && options.subject)
|
|
74133
|
+
return text;
|
|
74134
|
+
const block = lines.slice(start);
|
|
74135
|
+
if (!block.length || !TRAILER_LINE.test(block[0] ?? ""))
|
|
74136
|
+
return text;
|
|
74137
|
+
if (!block.every((line) => TRAILER_LINE.test(line) || /^\s+\S/u.test(line)))
|
|
74138
|
+
return text;
|
|
74139
|
+
const kept = [];
|
|
74140
|
+
let dropping = false;
|
|
74141
|
+
for (const line of block) {
|
|
74142
|
+
if (TRAILER_LINE.test(line))
|
|
74143
|
+
dropping = SESSION_KEY.test(line);
|
|
74144
|
+
if (!dropping)
|
|
74145
|
+
kept.push(line);
|
|
74146
|
+
}
|
|
74147
|
+
if (kept.length === block.length)
|
|
74148
|
+
return text;
|
|
74149
|
+
let head = lines.slice(0, start);
|
|
74150
|
+
if (!kept.length) {
|
|
74151
|
+
let cut = head.length;
|
|
74152
|
+
while (cut > 0 && head[cut - 1]?.trim() === "")
|
|
74153
|
+
cut -= 1;
|
|
74154
|
+
head = head.slice(0, cut);
|
|
74155
|
+
}
|
|
74156
|
+
return [...head, ...kept].join(`
|
|
74157
|
+
`) + text.slice(end);
|
|
74158
|
+
}
|
|
73428
74159
|
function messageTrailers(cwd, message) {
|
|
73429
74160
|
const run = git5(cwd, [...TRAILER_CONFIG, "interpret-trailers", "--parse", "--no-divider"], {
|
|
73430
74161
|
input: `${message}
|
|
@@ -73484,6 +74215,12 @@ function gitCommit(cwd, input) {
|
|
|
73484
74215
|
const given = input.message?.trim() ?? "";
|
|
73485
74216
|
if (!given && !input.amend)
|
|
73486
74217
|
return failure3("invalid_input", "a commit message is required (-m <msg> or -F <file>)");
|
|
74218
|
+
const extra = (input.trailers ?? []).map((trailer) => trailer.trim());
|
|
74219
|
+
const malformed = extra.find((trailer) => !EXTRA_TRAILER.test(trailer));
|
|
74220
|
+
if (malformed !== undefined)
|
|
74221
|
+
return failure3("invalid_input", `--trailer ${JSON.stringify(malformed)} is not one "Key: value" line`, `workit git commit -m '<msg>' --trailer "Co-Authored-By: Name <email>"`);
|
|
74222
|
+
if (extra.some((trailer) => /^workit-session:/iu.test(trailer)))
|
|
74223
|
+
return failure3("invalid_input", "--trailer cannot set Workit-Session: workit writes it from the acting session");
|
|
73487
74224
|
const paths = input.paths ?? [];
|
|
73488
74225
|
if (input.all && paths.length)
|
|
73489
74226
|
return failure3("invalid_input", "pass --all or paths, not both");
|
|
@@ -73505,6 +74242,7 @@ function gitCommit(cwd, input) {
|
|
|
73505
74242
|
}
|
|
73506
74243
|
let message = given;
|
|
73507
74244
|
let carried = [];
|
|
74245
|
+
let previousTrailers = [];
|
|
73508
74246
|
if (given) {
|
|
73509
74247
|
const lint = lintCommitMessage(cwd, given);
|
|
73510
74248
|
if (!lint.ok)
|
|
@@ -73514,8 +74252,9 @@ function gitCommit(cwd, input) {
|
|
|
73514
74252
|
const kept = git5(cwd, ["log", "-1", "--format=%B", previous]);
|
|
73515
74253
|
if (!kept.ok)
|
|
73516
74254
|
return failure3("failed", `could not read the message of ${branch}: ${gitError(kept)}`);
|
|
74255
|
+
previousTrailers = messageTrailers(cwd, kept.stdout.trim());
|
|
73517
74256
|
if (given)
|
|
73518
|
-
carried =
|
|
74257
|
+
carried = previousTrailers;
|
|
73519
74258
|
else
|
|
73520
74259
|
message = kept.stdout.trim();
|
|
73521
74260
|
}
|
|
@@ -73529,12 +74268,31 @@ function gitCommit(cwd, input) {
|
|
|
73529
74268
|
args.push("--amend");
|
|
73530
74269
|
if (input.allowEmpty || emptyAmend)
|
|
73531
74270
|
args.push("--allow-empty");
|
|
73532
|
-
|
|
74271
|
+
const trailersOn = workspaceTrailers(cwd).enabled;
|
|
74272
|
+
const sessionTrailer = Boolean(session) && trailersOn;
|
|
74273
|
+
if (!trailersOn) {
|
|
74274
|
+
carried = carried.filter((trailer) => !SESSION_KEY.test(trailer));
|
|
74275
|
+
if (!given)
|
|
74276
|
+
message = stripSessionTrailers(message, { subject: true });
|
|
74277
|
+
}
|
|
74278
|
+
const finalMessage = withTrailers(cwd, message, [
|
|
74279
|
+
...carried,
|
|
74280
|
+
...extra,
|
|
74281
|
+
...sessionTrailer ? [`Workit-Session: ${session}`] : []
|
|
74282
|
+
]);
|
|
74283
|
+
const allowed = new Set([
|
|
74284
|
+
...trailersOn ? sessionValues(previousTrailers) : [],
|
|
74285
|
+
...sessionTrailer && session ? [session] : []
|
|
74286
|
+
]);
|
|
74287
|
+
const forged = sessionValues(messageTrailers(cwd, finalMessage)).find((value) => !allowed.has(value));
|
|
74288
|
+
if (forged !== undefined)
|
|
74289
|
+
return failure3("invalid_input", `forged_session_trailer: the message would carry "Workit-Session: ${forged}", which workit did not add; workit writes that trailer itself`, "drop the Workit-Session line (or the trailer.<alias>.key config that expands into it)");
|
|
74290
|
+
args.push("-m", finalMessage);
|
|
73533
74291
|
if (paths.length) {
|
|
73534
74292
|
if (paths.some((value) => value.startsWith("-") || !value.trim()))
|
|
73535
74293
|
return failure3("invalid_input", "paths must not be empty or start with -");
|
|
73536
74294
|
const tracked = new Set(nulList(git5(cwd, ["ls-files", "-z", "--", ...paths]).stdout));
|
|
73537
|
-
const addable = paths.filter((value) => existsSync19(
|
|
74295
|
+
const addable = paths.filter((value) => existsSync19(path42.resolve(cwd, value)) || [...tracked].some((file) => file === value || file.startsWith(`${value}/`)));
|
|
73538
74296
|
if (addable.length) {
|
|
73539
74297
|
const added = git5(cwd, ["add", "-A", "--", ...addable]);
|
|
73540
74298
|
if (!added.ok)
|
|
@@ -73560,7 +74318,7 @@ function gitCommit(cwd, input) {
|
|
|
73560
74318
|
}
|
|
73561
74319
|
const committed = git5(cwd, args, {
|
|
73562
74320
|
timeoutMs: COMMIT_TIMEOUT_MS,
|
|
73563
|
-
env: input.env
|
|
74321
|
+
env: { ...input.env ?? process.env, WORKIT_TRAILERS: trailersOn ? "on" : "off" }
|
|
73564
74322
|
});
|
|
73565
74323
|
if (!committed.ok)
|
|
73566
74324
|
return failure3("failed", `git commit failed (a hook or git refused it):
|
|
@@ -73604,6 +74362,7 @@ ${gitOutput(committed)}`);
|
|
|
73604
74362
|
files,
|
|
73605
74363
|
leftDirty,
|
|
73606
74364
|
session,
|
|
74365
|
+
sessionTrailer,
|
|
73607
74366
|
amended: previous,
|
|
73608
74367
|
notes,
|
|
73609
74368
|
recorded: row.ok ? { id: String(row.value.id) } : { error: row.error }
|
|
@@ -73967,18 +74726,22 @@ ${run.stdout ?? ""}`).split(/\r?\n/u).map((value) => value.trimEnd()).filter((va
|
|
|
73967
74726
|
...lines.slice(-half)
|
|
73968
74727
|
].join(`
|
|
73969
74728
|
`);
|
|
73970
|
-
}, validRefName = (cwd, name) => !name.startsWith("-") && git5(cwd, ["check-ref-format", "--branch", name]).ok, nulList = (text) => text.split("\x00").filter(Boolean), SESSION_SAFE, TRAILER_CONFIG, nothingToCommit = (what) => failure3("failed", `nothing_to_commit: ${what}`, "workit git commit --allow-empty -m '<msg>' # only to record an empty commit on purpose"), SHA, SHA_PREFIX, RELEASE_TAG;
|
|
74729
|
+
}, validRefName = (cwd, name) => !name.startsWith("-") && git5(cwd, ["check-ref-format", "--branch", name]).ok, nulList = (text) => text.split("\x00").filter(Boolean), SESSION_SAFE, EXTRA_TRAILER, SESSION_KEY, TRAILER_LINE, sessionValues = (trailers) => trailers.filter((trailer) => SESSION_KEY.test(trailer)).map((trailer) => trailer.slice(trailer.indexOf(":") + 1).trim()), TRAILER_CONFIG, nothingToCommit = (what) => failure3("failed", `nothing_to_commit: ${what}`, "workit git commit --allow-empty -m '<msg>' # only to record an empty commit on purpose"), SHA, SHA_PREFIX, RELEASE_TAG;
|
|
73971
74730
|
var init_ops = __esm(() => {
|
|
73972
74731
|
init_branch();
|
|
73973
74732
|
init_commit_flavors();
|
|
73974
74733
|
init_pr_create();
|
|
73975
74734
|
init_release_tracks();
|
|
73976
74735
|
init_vcs_config();
|
|
74736
|
+
init_workspaces();
|
|
73977
74737
|
init_redact();
|
|
73978
74738
|
init_ledger();
|
|
73979
74739
|
init_rev();
|
|
73980
74740
|
COMMIT_TIMEOUT_MS = 10 * 60000;
|
|
73981
74741
|
SESSION_SAFE = /^[\w.:@/+-]{1,128}$/u;
|
|
74742
|
+
EXTRA_TRAILER = /^[A-Za-z0-9][A-Za-z0-9-]*: *\S[^\n\r]*$/u;
|
|
74743
|
+
SESSION_KEY = /^workit-session\s*:/iu;
|
|
74744
|
+
TRAILER_LINE = /^[A-Za-z0-9][A-Za-z0-9-]*\s*:/u;
|
|
73982
74745
|
TRAILER_CONFIG = ["-c", "trailer.separators=:", "-c", "trailer.where=end"];
|
|
73983
74746
|
SHA = /^[0-9a-f]{40,64}$/u;
|
|
73984
74747
|
SHA_PREFIX = /^[0-9a-f]{7,39}$/u;
|
|
@@ -73986,11 +74749,11 @@ var init_ops = __esm(() => {
|
|
|
73986
74749
|
});
|
|
73987
74750
|
|
|
73988
74751
|
// packages/workit-core/src/forge/reruns.ts
|
|
73989
|
-
import
|
|
73990
|
-
import
|
|
74752
|
+
import fs21 from "node:fs";
|
|
74753
|
+
import path43 from "node:path";
|
|
73991
74754
|
function rerunLogPath(cwd) {
|
|
73992
74755
|
const common = gitCommonDir(cwd);
|
|
73993
|
-
return common ?
|
|
74756
|
+
return common ? path43.join(common, "workit", "ci-reruns.jsonl") : null;
|
|
73994
74757
|
}
|
|
73995
74758
|
function rerunCounts(cwd, key) {
|
|
73996
74759
|
const counts = new Map;
|
|
@@ -73999,7 +74762,7 @@ function rerunCounts(cwd, key) {
|
|
|
73999
74762
|
return counts;
|
|
74000
74763
|
let text;
|
|
74001
74764
|
try {
|
|
74002
|
-
text =
|
|
74765
|
+
text = fs21.readFileSync(file, "utf8");
|
|
74003
74766
|
} catch {
|
|
74004
74767
|
return counts;
|
|
74005
74768
|
}
|
|
@@ -74025,8 +74788,8 @@ function recordReruns(cwd, rows) {
|
|
|
74025
74788
|
if (!file)
|
|
74026
74789
|
return false;
|
|
74027
74790
|
try {
|
|
74028
|
-
|
|
74029
|
-
|
|
74791
|
+
fs21.mkdirSync(path43.dirname(file), { recursive: true });
|
|
74792
|
+
fs21.appendFileSync(file, rows.map((row) => `${JSON.stringify(row)}
|
|
74030
74793
|
`).join(""));
|
|
74031
74794
|
return true;
|
|
74032
74795
|
} catch {
|
|
@@ -74038,18 +74801,18 @@ function withRerunLock(cwd, fn) {
|
|
|
74038
74801
|
if (!file)
|
|
74039
74802
|
return fn();
|
|
74040
74803
|
const lock = `${file.slice(0, -".jsonl".length)}.lock`;
|
|
74041
|
-
|
|
74804
|
+
fs21.mkdirSync(path43.dirname(lock), { recursive: true });
|
|
74042
74805
|
const until = Date.now() + LOCK_WAIT_MS2;
|
|
74043
74806
|
for (;; ) {
|
|
74044
74807
|
try {
|
|
74045
|
-
|
|
74808
|
+
fs21.closeSync(fs21.openSync(lock, "wx"));
|
|
74046
74809
|
break;
|
|
74047
74810
|
} catch (error) {
|
|
74048
74811
|
if (error.code !== "EEXIST")
|
|
74049
74812
|
throw error;
|
|
74050
74813
|
try {
|
|
74051
|
-
if (Date.now() -
|
|
74052
|
-
|
|
74814
|
+
if (Date.now() - fs21.statSync(lock).mtimeMs > LOCK_STALE_MS2) {
|
|
74815
|
+
fs21.rmSync(lock, { force: true });
|
|
74053
74816
|
continue;
|
|
74054
74817
|
}
|
|
74055
74818
|
} catch {
|
|
@@ -74063,7 +74826,7 @@ function withRerunLock(cwd, fn) {
|
|
|
74063
74826
|
try {
|
|
74064
74827
|
return fn();
|
|
74065
74828
|
} finally {
|
|
74066
|
-
|
|
74829
|
+
fs21.rmSync(lock, { force: true });
|
|
74067
74830
|
}
|
|
74068
74831
|
}
|
|
74069
74832
|
var isRow = (value) => {
|
|
@@ -74647,6 +75410,56 @@ function nextHint(next, number) {
|
|
|
74647
75410
|
return null;
|
|
74648
75411
|
}
|
|
74649
75412
|
}
|
|
75413
|
+
function squashMessageFor(cwd, resolved, input, doc) {
|
|
75414
|
+
if (input.method !== "squash")
|
|
75415
|
+
return null;
|
|
75416
|
+
if (input.subject === undefined && input.body === undefined && workspaceTrailers(cwd).enabled)
|
|
75417
|
+
return null;
|
|
75418
|
+
let subject = input.subject?.trim() ?? "";
|
|
75419
|
+
if (!subject) {
|
|
75420
|
+
const meta = resolved.forge.prMeta(doc.number);
|
|
75421
|
+
if (!meta.ok)
|
|
75422
|
+
return meta;
|
|
75423
|
+
subject = resolved.forge.kind === "github" ? `${meta.data.title} (#${doc.number})` : meta.data.title;
|
|
75424
|
+
}
|
|
75425
|
+
const body = input.body === undefined ? squashCommitList(cwd, resolved, doc.base, doc.head.sha) : stripSessionTrailers(input.body, { subject: false });
|
|
75426
|
+
return success2({ subject, body });
|
|
75427
|
+
}
|
|
75428
|
+
function squashCommitList(cwd, resolved, base, head) {
|
|
75429
|
+
const from = resolveRef(cwd, `refs/remotes/${resolved.baseRemote}/${base}`) ?? resolveRef(cwd, `refs/heads/${base}`);
|
|
75430
|
+
if (!from)
|
|
75431
|
+
return "";
|
|
75432
|
+
const log = spawnSync19("git", ["log", "--reverse", "--no-merges", "--format=%B%x1e", `${from}..${head}`], {
|
|
75433
|
+
cwd,
|
|
75434
|
+
encoding: "utf8",
|
|
75435
|
+
timeout: GIT_TIMEOUTS.local,
|
|
75436
|
+
killSignal: "SIGKILL",
|
|
75437
|
+
maxBuffer: 64 * 1024 * 1024
|
|
75438
|
+
});
|
|
75439
|
+
if (log.status !== 0)
|
|
75440
|
+
return "";
|
|
75441
|
+
const entries = log.stdout.split("\x1E").map((message) => stripSessionTrailers(message.replace(/^\n+/u, ""), { subject: true })).filter((message) => message.trim()).map((message) => {
|
|
75442
|
+
const [subject = "", ...rest] = message.trimEnd().split(`
|
|
75443
|
+
`);
|
|
75444
|
+
const body = rest.join(`
|
|
75445
|
+
`).replace(/^\n+/u, "");
|
|
75446
|
+
return body ? `* ${subject}
|
|
75447
|
+
|
|
75448
|
+
${body}` : `* ${subject}`;
|
|
75449
|
+
});
|
|
75450
|
+
const listed = [];
|
|
75451
|
+
let size = 0;
|
|
75452
|
+
for (const entry of entries) {
|
|
75453
|
+
if (listed.length >= SQUASH_LIST_MAX_COMMITS || size + entry.length > SQUASH_LIST_MAX_CHARS)
|
|
75454
|
+
break;
|
|
75455
|
+
listed.push(entry);
|
|
75456
|
+
size += entry.length + 2;
|
|
75457
|
+
}
|
|
75458
|
+
const more = entries.length - listed.length;
|
|
75459
|
+
return [...listed, ...more ? [`… and ${more} more commit${more === 1 ? "" : "s"}`] : []].join(`
|
|
75460
|
+
|
|
75461
|
+
`);
|
|
75462
|
+
}
|
|
74650
75463
|
async function mergePullRequest(cwd, resolved, input, sleep) {
|
|
74651
75464
|
const grant = requireGrant(cwd, "merge");
|
|
74652
75465
|
if (!grant.allowed)
|
|
@@ -74723,7 +75536,14 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
|
|
|
74723
75536
|
return refuse(`head_mismatch: the verdict check reads ${branch} at ${check.head?.slice(0, 12) ?? "(missing)"} but ${label} is at ${head.slice(0, 12)}`, `git fetch && git switch ${branch} && git merge --ff-only @{u} # then re-verify that head`, { reason: "head_mismatch", verdict: summary });
|
|
74724
75537
|
else if (!covered)
|
|
74725
75538
|
return refuse(`NEEDS_VERDICT: ${label} has no accepted independent verdict for ${head.slice(0, 12)} (${check.accepted.reasons.join(", ")})`, `an independent session verifies the head and runs: workit ledger verdict verified --how "<what was exercised>" --branch ${branch}${grant.allowUnverified ? `; or, only if the user asks to merge unverified: workit pr merge --unverified --reason "<why>"` : ""}`, { reason: "needs_verdict", verdict: summary });
|
|
74726
|
-
const
|
|
75539
|
+
const squashMessage = squashMessageFor(cwd, resolved, input, doc);
|
|
75540
|
+
if (squashMessage && !squashMessage.ok)
|
|
75541
|
+
return squashMessage;
|
|
75542
|
+
const merged = resolved.forge.merge(doc.number, {
|
|
75543
|
+
sha: head,
|
|
75544
|
+
method: input.method,
|
|
75545
|
+
...squashMessage ? { squashMessage: squashMessage.data } : {}
|
|
75546
|
+
});
|
|
74727
75547
|
if (!merged.ok)
|
|
74728
75548
|
return merged;
|
|
74729
75549
|
let state = "open";
|
|
@@ -74813,11 +75633,12 @@ var VERIFY_DELAYS_MS, noun = (resolved) => resolved.forge.kind === "github" ? "p
|
|
|
74813
75633
|
error,
|
|
74814
75634
|
unblock,
|
|
74815
75635
|
refusal
|
|
74816
|
-
});
|
|
75636
|
+
}), SQUASH_LIST_MAX_COMMITS = 100, SQUASH_LIST_MAX_CHARS = 60000;
|
|
74817
75637
|
var init_pr_ops = __esm(() => {
|
|
74818
75638
|
init_autonomy();
|
|
74819
75639
|
init_branch();
|
|
74820
75640
|
init_vcs_config();
|
|
75641
|
+
init_workspaces();
|
|
74821
75642
|
init_release_tracks();
|
|
74822
75643
|
init_ops();
|
|
74823
75644
|
init_rev();
|
|
@@ -74836,8 +75657,8 @@ var init_pr_ops = __esm(() => {
|
|
|
74836
75657
|
// packages/workit-core/src/stack.ts
|
|
74837
75658
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
74838
75659
|
import { createHash as createHash17, randomBytes as randomBytes3 } from "node:crypto";
|
|
74839
|
-
import
|
|
74840
|
-
import
|
|
75660
|
+
import fs22 from "node:fs";
|
|
75661
|
+
import path44 from "node:path";
|
|
74841
75662
|
function worktreeOf(cwd, branch) {
|
|
74842
75663
|
const list = gitRun3(cwd, ["worktree", "list", "--porcelain"]);
|
|
74843
75664
|
if (!list.ok)
|
|
@@ -74860,7 +75681,7 @@ function rebasingBranch2(dir) {
|
|
|
74860
75681
|
if (!found.ok)
|
|
74861
75682
|
continue;
|
|
74862
75683
|
try {
|
|
74863
|
-
return
|
|
75684
|
+
return fs22.readFileSync(found.stdout.trim(), "utf8").trim();
|
|
74864
75685
|
} catch {}
|
|
74865
75686
|
}
|
|
74866
75687
|
return null;
|
|
@@ -74868,7 +75689,7 @@ function rebasingBranch2(dir) {
|
|
|
74868
75689
|
function rebaseInProgress(dir) {
|
|
74869
75690
|
for (const name of ["rebase-merge", "rebase-apply"]) {
|
|
74870
75691
|
const found = gitRun3(dir, ["rev-parse", "--path-format=absolute", "--git-path", name]);
|
|
74871
|
-
if (found.ok &&
|
|
75692
|
+
if (found.ok && fs22.existsSync(found.stdout.trim()))
|
|
74872
75693
|
return true;
|
|
74873
75694
|
}
|
|
74874
75695
|
return false;
|
|
@@ -74880,8 +75701,8 @@ function gitRestack(cwd, input) {
|
|
|
74880
75701
|
const scratch = scratchDir(cwd);
|
|
74881
75702
|
if (!scratch)
|
|
74882
75703
|
return { ok: false, conflict: false, worktree: cwd, error: "no store root" };
|
|
74883
|
-
|
|
74884
|
-
dir =
|
|
75704
|
+
fs22.mkdirSync(scratch, { recursive: true });
|
|
75705
|
+
dir = path44.join(scratch, `${input.branch.replace(/[^A-Za-z0-9._-]+/gu, "_").slice(0, 40)}-${randomBytes3(4).toString("hex")}`);
|
|
74885
75706
|
const added = gitRun3(cwd, ["worktree", "add", "-q", dir, input.branch], GIT_TIMEOUTS.worktree);
|
|
74886
75707
|
if (!added.ok)
|
|
74887
75708
|
return { ok: false, conflict: false, worktree: cwd, error: firstLine(added) };
|
|
@@ -74919,10 +75740,10 @@ function gitRestack(cwd, input) {
|
|
|
74919
75740
|
}
|
|
74920
75741
|
function pruneScratch(cwd) {
|
|
74921
75742
|
const scratch = scratchDir(cwd);
|
|
74922
|
-
if (!scratch || !
|
|
75743
|
+
if (!scratch || !fs22.existsSync(scratch))
|
|
74923
75744
|
return;
|
|
74924
|
-
for (const name of
|
|
74925
|
-
const dir =
|
|
75745
|
+
for (const name of fs22.readdirSync(scratch)) {
|
|
75746
|
+
const dir = path44.join(scratch, name);
|
|
74926
75747
|
if (!rebaseInProgress(dir) && !trackedDirt(dir))
|
|
74927
75748
|
gitRun3(cwd, ["worktree", "remove", "--force", dir]);
|
|
74928
75749
|
}
|
|
@@ -74932,10 +75753,10 @@ function stacksDir(cwd) {
|
|
|
74932
75753
|
const root = storeRoot(cwd);
|
|
74933
75754
|
if (!root.ok)
|
|
74934
75755
|
return stackFail(root.code, root.error, root.unblock);
|
|
74935
|
-
return { ok: true, data:
|
|
75756
|
+
return { ok: true, data: path44.join(root.value.root, "stacks") };
|
|
74936
75757
|
}
|
|
74937
75758
|
function parseStack(raw) {
|
|
74938
|
-
if (!
|
|
75759
|
+
if (!isRecord8(raw) || !Array.isArray(raw.branches))
|
|
74939
75760
|
return null;
|
|
74940
75761
|
const name = str3(raw.name);
|
|
74941
75762
|
const trunk = str3(raw.trunk);
|
|
@@ -74943,12 +75764,12 @@ function parseStack(raw) {
|
|
|
74943
75764
|
return null;
|
|
74944
75765
|
const branches = [];
|
|
74945
75766
|
for (const item of raw.branches) {
|
|
74946
|
-
if (!
|
|
75767
|
+
if (!isRecord8(item))
|
|
74947
75768
|
return null;
|
|
74948
75769
|
const branch = str3(item.branch);
|
|
74949
75770
|
if (!branch)
|
|
74950
75771
|
return null;
|
|
74951
|
-
const merged =
|
|
75772
|
+
const merged = isRecord8(item.merged) ? {
|
|
74952
75773
|
pr: typeof item.merged.pr === "number" ? item.merged.pr : null,
|
|
74953
75774
|
mergeSha: str3(item.merged.mergeSha),
|
|
74954
75775
|
at: str3(item.merged.at) ?? ""
|
|
@@ -74961,7 +75782,7 @@ function parseStack(raw) {
|
|
|
74961
75782
|
lastParentHead: str3(item.lastParentHead),
|
|
74962
75783
|
patchId: str3(item.patchId),
|
|
74963
75784
|
merged,
|
|
74964
|
-
...
|
|
75785
|
+
...isRecord8(item.pendingRestack) && str3(item.pendingRestack.from) && str3(item.pendingRestack.tip) && str3(item.pendingRestack.onto) ? {
|
|
74965
75786
|
pendingRestack: {
|
|
74966
75787
|
from: str3(item.pendingRestack.from),
|
|
74967
75788
|
tip: str3(item.pendingRestack.tip),
|
|
@@ -74984,10 +75805,10 @@ function readStack(cwd, name) {
|
|
|
74984
75805
|
const dir = stacksDir(cwd);
|
|
74985
75806
|
if (!dir.ok)
|
|
74986
75807
|
return dir;
|
|
74987
|
-
const file =
|
|
75808
|
+
const file = path44.join(dir.data, fileFor(name));
|
|
74988
75809
|
let text;
|
|
74989
75810
|
try {
|
|
74990
|
-
text =
|
|
75811
|
+
text = fs22.readFileSync(file, "utf8");
|
|
74991
75812
|
} catch (error) {
|
|
74992
75813
|
if (error.code === "ENOENT")
|
|
74993
75814
|
return { ok: true, data: null };
|
|
@@ -75005,17 +75826,17 @@ function writeStack(cwd, stack, now = new Date) {
|
|
|
75005
75826
|
const dir = stacksDir(cwd);
|
|
75006
75827
|
if (!dir.ok)
|
|
75007
75828
|
return dir;
|
|
75008
|
-
const file =
|
|
75829
|
+
const file = path44.join(dir.data, fileFor(stack.name));
|
|
75009
75830
|
const temp = `${file}.${process.pid}.tmp`;
|
|
75010
75831
|
try {
|
|
75011
|
-
|
|
75832
|
+
fs22.mkdirSync(dir.data, { recursive: true });
|
|
75012
75833
|
stack.updatedAt = now.toISOString();
|
|
75013
|
-
|
|
75834
|
+
fs22.writeFileSync(temp, `${JSON.stringify(stack, null, 2)}
|
|
75014
75835
|
`);
|
|
75015
|
-
|
|
75836
|
+
fs22.renameSync(temp, file);
|
|
75016
75837
|
return { ok: true, data: undefined };
|
|
75017
75838
|
} catch (error) {
|
|
75018
|
-
|
|
75839
|
+
fs22.rmSync(temp, { force: true });
|
|
75019
75840
|
return stackFail("unavailable", `cannot write ${file}: ${error.message}`);
|
|
75020
75841
|
}
|
|
75021
75842
|
}
|
|
@@ -75025,7 +75846,7 @@ function listStacks(cwd) {
|
|
|
75025
75846
|
return dir;
|
|
75026
75847
|
let names = [];
|
|
75027
75848
|
try {
|
|
75028
|
-
names =
|
|
75849
|
+
names = fs22.readdirSync(dir.data).filter((name) => name.endsWith(".json"));
|
|
75029
75850
|
} catch {
|
|
75030
75851
|
return { ok: true, data: [] };
|
|
75031
75852
|
}
|
|
@@ -75033,7 +75854,7 @@ function listStacks(cwd) {
|
|
|
75033
75854
|
for (const name of names) {
|
|
75034
75855
|
let parsed = null;
|
|
75035
75856
|
try {
|
|
75036
|
-
parsed = parseStack(JSON.parse(
|
|
75857
|
+
parsed = parseStack(JSON.parse(fs22.readFileSync(path44.join(dir.data, name), "utf8")));
|
|
75037
75858
|
} catch {
|
|
75038
75859
|
parsed = null;
|
|
75039
75860
|
}
|
|
@@ -75080,7 +75901,7 @@ function stackLockStale(raw, age) {
|
|
|
75080
75901
|
}
|
|
75081
75902
|
function takeLock(lock) {
|
|
75082
75903
|
try {
|
|
75083
|
-
|
|
75904
|
+
fs22.mkdirSync(lock);
|
|
75084
75905
|
} catch (error) {
|
|
75085
75906
|
if (error.code === "EEXIST")
|
|
75086
75907
|
return null;
|
|
@@ -75092,24 +75913,24 @@ function takeLock(lock) {
|
|
|
75092
75913
|
host: localLockHost(),
|
|
75093
75914
|
nonce: randomBytes3(8).toString("hex")
|
|
75094
75915
|
};
|
|
75095
|
-
|
|
75916
|
+
fs22.writeFileSync(path44.join(lock, OWNER), JSON.stringify(owner));
|
|
75096
75917
|
return owner;
|
|
75097
75918
|
}
|
|
75098
75919
|
function reapLock(lock, observed) {
|
|
75099
75920
|
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
75100
75921
|
try {
|
|
75101
|
-
|
|
75922
|
+
fs22.renameSync(lock, tomb);
|
|
75102
75923
|
} catch {
|
|
75103
75924
|
return false;
|
|
75104
75925
|
}
|
|
75105
75926
|
if (readOwner(tomb).raw === observed) {
|
|
75106
|
-
|
|
75927
|
+
fs22.rmSync(tomb, { recursive: true, force: true });
|
|
75107
75928
|
return true;
|
|
75108
75929
|
}
|
|
75109
75930
|
try {
|
|
75110
|
-
|
|
75931
|
+
fs22.renameSync(tomb, lock);
|
|
75111
75932
|
} catch {
|
|
75112
|
-
|
|
75933
|
+
fs22.rmSync(tomb, { recursive: true, force: true });
|
|
75113
75934
|
}
|
|
75114
75935
|
return false;
|
|
75115
75936
|
}
|
|
@@ -75119,8 +75940,8 @@ function releaseLock(lock, owner) {
|
|
|
75119
75940
|
return;
|
|
75120
75941
|
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
75121
75942
|
try {
|
|
75122
|
-
|
|
75123
|
-
|
|
75943
|
+
fs22.renameSync(lock, tomb);
|
|
75944
|
+
fs22.rmSync(tomb, { recursive: true, force: true });
|
|
75124
75945
|
} catch {}
|
|
75125
75946
|
}
|
|
75126
75947
|
async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
|
|
@@ -75129,9 +75950,9 @@ async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
|
|
|
75129
75950
|
return dir;
|
|
75130
75951
|
if (!validName(name))
|
|
75131
75952
|
return stackFail("invalid_input", "a stack name must be 1-200 printable characters");
|
|
75132
|
-
const lock =
|
|
75953
|
+
const lock = path44.join(dir.data, `${fileFor(name)}.lock`);
|
|
75133
75954
|
try {
|
|
75134
|
-
|
|
75955
|
+
fs22.mkdirSync(dir.data, { recursive: true });
|
|
75135
75956
|
} catch (error) {
|
|
75136
75957
|
return stackFail("unavailable", `cannot create ${dir.data}: ${error.message}`);
|
|
75137
75958
|
}
|
|
@@ -75159,7 +75980,7 @@ async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
|
|
|
75159
75980
|
const touch = () => {
|
|
75160
75981
|
try {
|
|
75161
75982
|
const now = new Date;
|
|
75162
|
-
|
|
75983
|
+
fs22.utimesSync(path44.join(lock, OWNER), now, now);
|
|
75163
75984
|
} catch {}
|
|
75164
75985
|
};
|
|
75165
75986
|
const beat = setInterval(touch, HEARTBEAT_MS);
|
|
@@ -75192,7 +76013,7 @@ function deriveChain(cwd, trunk, base) {
|
|
|
75192
76013
|
const [name, sha] = line.trim().split(" ");
|
|
75193
76014
|
if (!name || !sha || name === branch || name === trunk)
|
|
75194
76015
|
continue;
|
|
75195
|
-
if (sha === head ||
|
|
76016
|
+
if (sha === head || isAncestor2(cwd, sha, base) || !isAncestor2(cwd, sha, head))
|
|
75196
76017
|
continue;
|
|
75197
76018
|
const count = gitRun3(cwd, ["rev-list", "--count", `${base}..${sha}`]);
|
|
75198
76019
|
below.push({ branch: name, depth: Number(count.stdout.trim()) || 0 });
|
|
@@ -75238,17 +76059,17 @@ function planStack(cwd, resolved, input, existing) {
|
|
|
75238
76059
|
const status = resolved.forge.prStatus(before.pr);
|
|
75239
76060
|
still = status.ok && status.data.state === "merged";
|
|
75240
76061
|
} else
|
|
75241
|
-
still = hasOwnCommits(cwd, tip, before.lastParentHead) &&
|
|
76062
|
+
still = hasOwnCommits(cwd, tip, before.lastParentHead) && isAncestor2(cwd, tip, trunkSha);
|
|
75242
76063
|
if (still) {
|
|
75243
76064
|
entries.push(before);
|
|
75244
76065
|
continue;
|
|
75245
76066
|
}
|
|
75246
76067
|
notes.push(`${branch} was recorded as merged but is not; it is back in the stack`);
|
|
75247
76068
|
}
|
|
75248
|
-
const onParent =
|
|
76069
|
+
const onParent = isAncestor2(cwd, parentSha, tip);
|
|
75249
76070
|
let builtOn = parentSha;
|
|
75250
76071
|
if (!onParent) {
|
|
75251
|
-
if (before?.lastParentHead &&
|
|
76072
|
+
if (before?.lastParentHead && isAncestor2(cwd, before.lastParentHead, tip))
|
|
75252
76073
|
builtOn = before.lastParentHead;
|
|
75253
76074
|
else {
|
|
75254
76075
|
const old = oldParentVersion(cwd, parent, parentSha, tip) ?? oldParentLookalike(cwd, parentSha, tip);
|
|
@@ -75323,7 +76144,7 @@ function planStack(cwd, resolved, input, existing) {
|
|
|
75323
76144
|
function hasOwnCommits(cwd, tip, base) {
|
|
75324
76145
|
if (!base)
|
|
75325
76146
|
return false;
|
|
75326
|
-
return tip !== base && !
|
|
76147
|
+
return tip !== base && !isAncestor2(cwd, tip, base);
|
|
75327
76148
|
}
|
|
75328
76149
|
function oldParentVersion(cwd, parent, parentSha, child) {
|
|
75329
76150
|
if (!safeRef2(parent))
|
|
@@ -75335,7 +76156,7 @@ function oldParentVersion(cwd, parent, parentSha, child) {
|
|
|
75335
76156
|
`).map((line) => line.trim())) {
|
|
75336
76157
|
if (!sha || sha === parentSha)
|
|
75337
76158
|
continue;
|
|
75338
|
-
if (
|
|
76159
|
+
if (isAncestor2(cwd, sha, child) && !isAncestor2(cwd, sha, parentSha))
|
|
75339
76160
|
return sha;
|
|
75340
76161
|
}
|
|
75341
76162
|
return null;
|
|
@@ -75469,7 +76290,7 @@ function stackStatus(ctx, stack) {
|
|
|
75469
76290
|
pr: entry.pr,
|
|
75470
76291
|
merged,
|
|
75471
76292
|
head,
|
|
75472
|
-
onParent: merged || !head || !parentSha ? null :
|
|
76293
|
+
onParent: merged || !head || !parentSha ? null : isAncestor2(ctx.cwd, parentSha, head),
|
|
75473
76294
|
prState: doc?.state ?? null,
|
|
75474
76295
|
prBase: doc?.base ?? null,
|
|
75475
76296
|
baseMatches: doc && !merged ? doc.base === parentName : null,
|
|
@@ -75532,7 +76353,7 @@ function detectMerged(ctx, cwd, stack, trunkSha, state) {
|
|
|
75532
76353
|
merged = read.data.state === "merged";
|
|
75533
76354
|
} else {
|
|
75534
76355
|
const tip = branchTip(cwd, entry.branch);
|
|
75535
|
-
merged = tip !== null && hasOwnCommits(cwd, tip, entry.lastParentHead) &&
|
|
76356
|
+
merged = tip !== null && hasOwnCommits(cwd, tip, entry.lastParentHead) && isAncestor2(cwd, tip, trunkSha);
|
|
75536
76357
|
}
|
|
75537
76358
|
if (merged) {
|
|
75538
76359
|
entry.merged = { pr: entry.pr, mergeSha: null, at: new Date().toISOString() };
|
|
@@ -75553,7 +76374,7 @@ function pushBranch(cwd, actor, branch, priorTip) {
|
|
|
75553
76374
|
const grant = requireGrant(cwd, "push", { forge: !plan.data.local });
|
|
75554
76375
|
if (!grant.allowed)
|
|
75555
76376
|
return stackFail("blocked", grant.error, grant.unblock, { reason: grant.reason });
|
|
75556
|
-
const force = remote.sha !== null && !
|
|
76377
|
+
const force = remote.sha !== null && !isAncestor2(cwd, remote.sha, plan.data.sha);
|
|
75557
76378
|
const expect = !force ? null : remote.sha === priorTip ? priorTip : recordedRemoteTip(cwd, plan.data) ?? null;
|
|
75558
76379
|
const pushed = executePush(cwd, plan.data, { forceWithLease: force, expect, actor });
|
|
75559
76380
|
if (!pushed.ok)
|
|
@@ -75630,9 +76451,9 @@ function syncStack(cwd, ctx, stack, options, actor) {
|
|
|
75630
76451
|
carried: null
|
|
75631
76452
|
};
|
|
75632
76453
|
newTip = null;
|
|
75633
|
-
} else if (!
|
|
76454
|
+
} else if (!isAncestor2(cwd, parentTip, tip)) {
|
|
75634
76455
|
const pending = entry.pendingRestack ?? null;
|
|
75635
|
-
const from = pending &&
|
|
76456
|
+
const from = pending && isAncestor2(cwd, pending.onto, tip) ? pending.onto : entry.lastParentHead && isAncestor2(cwd, entry.lastParentHead, tip) ? entry.lastParentHead : mergeBase(cwd, entry.lastParentHead ?? parentTip, tip) ?? parentTip;
|
|
75636
76457
|
if (options.dryRun) {
|
|
75637
76458
|
step.restack = { from, onto: parentTip, head: null, carried: null };
|
|
75638
76459
|
newTip = null;
|
|
@@ -75680,7 +76501,7 @@ function syncStack(cwd, ctx, stack, options, actor) {
|
|
|
75680
76501
|
}
|
|
75681
76502
|
}
|
|
75682
76503
|
if (!options.dryRun && newTip) {
|
|
75683
|
-
const check = entry.pendingRestack && parentTip &&
|
|
76504
|
+
const check = entry.pendingRestack && parentTip && isAncestor2(cwd, parentTip, newTip) ? entry.pendingRestack : null;
|
|
75684
76505
|
let forced = false;
|
|
75685
76506
|
if (check && parentTip) {
|
|
75686
76507
|
const before = {
|
|
@@ -75994,12 +76815,12 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
|
|
|
75994
76815
|
stderr: run.error ? String(run.error.message) : run.stderr ?? "",
|
|
75995
76816
|
timedOut: failure?.code === "ETIMEDOUT"
|
|
75996
76817
|
};
|
|
75997
|
-
}, safeRef2 = (value) => value.length > 0 && !value.startsWith("-"),
|
|
76818
|
+
}, safeRef2 = (value) => value.length > 0 && !value.startsWith("-"), isAncestor2 = (cwd, ancestor, descendant) => safeRef2(ancestor) && safeRef2(descendant) && gitRun3(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]).ok, branchTip = (cwd, branch) => resolveRef(cwd, `refs/heads/${branch}`), trackedDirt = (dir) => {
|
|
75998
76819
|
const status = gitRun3(dir, ["status", "--porcelain", "--untracked-files=no"]);
|
|
75999
76820
|
return !status.ok || status.stdout.trim() !== "";
|
|
76000
76821
|
}, scratchDir = (cwd) => {
|
|
76001
76822
|
const dir = stacksDir(cwd);
|
|
76002
|
-
return dir.ok ?
|
|
76823
|
+
return dir.ok ? path44.join(dir.data, "worktrees") : null;
|
|
76003
76824
|
}, firstLine = (run) => (`${run.stderr}
|
|
76004
76825
|
${run.stdout}`.split(`
|
|
76005
76826
|
`).find((line) => line.trim()) ?? "git failed").trim().slice(0, 300), stackFileName = (name) => `${name.replace(/[^A-Za-z0-9._-]+/gu, "_").replace(/^[._]+/u, "").slice(0, 48).toLowerCase()}-${createHash17("sha256").update(name).digest("hex").slice(0, 12)}.json`, fileFor, validName = (name) => {
|
|
@@ -76011,17 +76832,17 @@ ${run.stdout}`.split(`
|
|
|
76011
76832
|
return false;
|
|
76012
76833
|
}
|
|
76013
76834
|
return true;
|
|
76014
|
-
},
|
|
76835
|
+
}, isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str3 = (value) => typeof value === "string" && value ? value : null, LOCK_WAIT_MS3 = 2000, HEARTBEAT_MS = 20000, HEARTBEAT_STALE_MS, OWNERLESS_STALE_MS = 30000, OWNER = "owner", readOwner = (lock) => {
|
|
76015
76836
|
let raw = null;
|
|
76016
76837
|
try {
|
|
76017
|
-
raw =
|
|
76838
|
+
raw = fs22.readFileSync(path44.join(lock, OWNER), "utf8");
|
|
76018
76839
|
} catch {
|
|
76019
76840
|
raw = null;
|
|
76020
76841
|
}
|
|
76021
76842
|
let age = null;
|
|
76022
|
-
for (const target of [
|
|
76843
|
+
for (const target of [path44.join(lock, OWNER), lock])
|
|
76023
76844
|
try {
|
|
76024
|
-
age = Date.now() -
|
|
76845
|
+
age = Date.now() - fs22.statSync(target).mtimeMs;
|
|
76025
76846
|
break;
|
|
76026
76847
|
} catch {}
|
|
76027
76848
|
return { raw, age };
|
|
@@ -76238,16 +77059,16 @@ var init_pr_edit = __esm(() => {
|
|
|
76238
77059
|
|
|
76239
77060
|
// packages/workit-core/src/forge/exec.ts
|
|
76240
77061
|
import { spawnSync as spawnSync22 } from "node:child_process";
|
|
76241
|
-
import
|
|
76242
|
-
import
|
|
77062
|
+
import fs23 from "node:fs";
|
|
77063
|
+
import path45 from "node:path";
|
|
76243
77064
|
function findExecutable(bin, env) {
|
|
76244
|
-
const dirs = (env.PATH ?? env.Path ?? "").split(
|
|
77065
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path45.delimiter).filter(Boolean);
|
|
76245
77066
|
const exts = process.platform === "win32" ? [".exe"] : [""];
|
|
76246
77067
|
for (const dir of dirs)
|
|
76247
77068
|
for (const ext of exts) {
|
|
76248
|
-
const candidate =
|
|
77069
|
+
const candidate = path45.join(dir, `${bin}${ext}`);
|
|
76249
77070
|
try {
|
|
76250
|
-
const stat =
|
|
77071
|
+
const stat = fs23.statSync(candidate);
|
|
76251
77072
|
if (stat.isFile())
|
|
76252
77073
|
return candidate;
|
|
76253
77074
|
} catch {}
|
|
@@ -76279,7 +77100,7 @@ function systemRunner(kind, apiHost, env = process.env) {
|
|
|
76279
77100
|
timeout: options.timeoutMs,
|
|
76280
77101
|
killSignal: "SIGKILL",
|
|
76281
77102
|
windowsHide: true,
|
|
76282
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
77103
|
+
...options.input === undefined ? { stdio: ["ignore", "pipe", "pipe"] } : { input: options.input, stdio: ["pipe", "pipe", "pipe"] }
|
|
76283
77104
|
});
|
|
76284
77105
|
const error = result.error;
|
|
76285
77106
|
return {
|
|
@@ -76355,8 +77176,8 @@ function apiText(runner, bin, apiHost, args, what, timeoutMs = FORGE_TIMEOUTS.lo
|
|
|
76355
77176
|
return cliFailure(bin, apiHost, run, what, timeoutMs);
|
|
76356
77177
|
return { ok: true, data: run.stdout };
|
|
76357
77178
|
}
|
|
76358
|
-
function apiWrite(runner, bin, apiHost, args, what, timeoutMs = FORGE_TIMEOUTS.api) {
|
|
76359
|
-
const run = runner(bin, args, { timeoutMs });
|
|
77179
|
+
function apiWrite(runner, bin, apiHost, args, what, timeoutMs = FORGE_TIMEOUTS.api, input) {
|
|
77180
|
+
const run = runner(bin, args, input === undefined ? { timeoutMs } : { timeoutMs, input });
|
|
76360
77181
|
if (run.status !== 0) {
|
|
76361
77182
|
const text = redactText(`${run.stderr}
|
|
76362
77183
|
${run.stdout}`).trim();
|
|
@@ -76658,16 +77479,19 @@ function createGitHubForge(options) {
|
|
|
76658
77479
|
});
|
|
76659
77480
|
},
|
|
76660
77481
|
merge(number, mergeOptions) {
|
|
77482
|
+
const squash = mergeOptions.method === "squash" ? mergeOptions.squashMessage : undefined;
|
|
76661
77483
|
const merged = apiWrite(runner, "gh", apiHost, [
|
|
76662
77484
|
"api",
|
|
76663
77485
|
"-X",
|
|
76664
77486
|
"PUT",
|
|
76665
77487
|
`repos/${repo}/pulls/${number}/merge`,
|
|
76666
|
-
"-f",
|
|
76667
|
-
|
|
76668
|
-
|
|
76669
|
-
|
|
76670
|
-
|
|
77488
|
+
...squash ? ["--input", "-"] : ["-f", `sha=${mergeOptions.sha}`, "-f", `merge_method=${mergeOptions.method}`]
|
|
77489
|
+
], `merge of pull request #${number}`, undefined, squash ? JSON.stringify({
|
|
77490
|
+
sha: mergeOptions.sha,
|
|
77491
|
+
merge_method: mergeOptions.method,
|
|
77492
|
+
commit_title: squash.subject,
|
|
77493
|
+
commit_message: squash.body
|
|
77494
|
+
}) : undefined);
|
|
76671
77495
|
if (!merged.ok)
|
|
76672
77496
|
return merged;
|
|
76673
77497
|
if (merged.data.merged === false)
|
|
@@ -77025,6 +77849,7 @@ function createGitLabForge(options) {
|
|
|
77025
77849
|
});
|
|
77026
77850
|
},
|
|
77027
77851
|
merge(iid, mergeOptions) {
|
|
77852
|
+
const squash = mergeOptions.method === "squash" ? mergeOptions.squashMessage : undefined;
|
|
77028
77853
|
if (mergeOptions.method === "rebase")
|
|
77029
77854
|
return failure3("invalid_input", "GitLab merges with the project's merge method; pass --method squash or merge");
|
|
77030
77855
|
const merged = apiWrite(runner, "glab", apiHost, [
|
|
@@ -77032,11 +77857,19 @@ function createGitLabForge(options) {
|
|
|
77032
77857
|
"-X",
|
|
77033
77858
|
"PUT",
|
|
77034
77859
|
`${project}/merge_requests/${iid}/merge`,
|
|
77035
|
-
"-
|
|
77036
|
-
|
|
77037
|
-
|
|
77038
|
-
|
|
77039
|
-
|
|
77860
|
+
...squash ? ["--input", "-"] : [
|
|
77861
|
+
"-f",
|
|
77862
|
+
`sha=${mergeOptions.sha}`,
|
|
77863
|
+
"-F",
|
|
77864
|
+
`squash=${mergeOptions.method === "squash" ? "true" : "false"}`
|
|
77865
|
+
]
|
|
77866
|
+
], `merge of merge request !${iid}`, undefined, squash ? JSON.stringify({
|
|
77867
|
+
sha: mergeOptions.sha,
|
|
77868
|
+
squash: true,
|
|
77869
|
+
squash_commit_message: squash.body ? `${squash.subject}
|
|
77870
|
+
|
|
77871
|
+
${squash.body}` : squash.subject
|
|
77872
|
+
}) : undefined);
|
|
77040
77873
|
if (!merged.ok)
|
|
77041
77874
|
return merged;
|
|
77042
77875
|
const value = merged.data;
|
|
@@ -77181,7 +78014,7 @@ var init_gitlab = __esm(() => {
|
|
|
77181
78014
|
});
|
|
77182
78015
|
|
|
77183
78016
|
// packages/workit-core/src/forge/resolve.ts
|
|
77184
|
-
import
|
|
78017
|
+
import fs24 from "node:fs";
|
|
77185
78018
|
function memo4(scope, key, compute, now = Date.now) {
|
|
77186
78019
|
let entries = memoScopes.get(scope.owner);
|
|
77187
78020
|
if (!entries)
|
|
@@ -77226,7 +78059,7 @@ function resolveCredential(workspace, derived, run, scope) {
|
|
|
77226
78059
|
return failure3("blocked", `token_host_unverified: push host ${derived.host} only looks like ${kind}; workspace ${workspace?.name ?? "?"} vcs.tokenFile is not sent to it`, `if ${apiHost} is your ${kind} server, set ${kind}.host in ~/.config/workit/vcs.json (or vcs.host for the workspace); otherwise fix the push remote`);
|
|
77227
78060
|
let token = "";
|
|
77228
78061
|
try {
|
|
77229
|
-
token =
|
|
78062
|
+
token = fs24.readFileSync(tokenFile.trim(), "utf8").trim();
|
|
77230
78063
|
} catch {
|
|
77231
78064
|
return failure3("blocked", `workspace ${workspace?.name ?? "?"} vcs.tokenFile cannot be read`, "fix vcs.tokenFile in ~/.config/workit/workspaces.json");
|
|
77232
78065
|
}
|
|
@@ -77424,16 +78257,16 @@ function parseDuration2(value) {
|
|
|
77424
78257
|
return null;
|
|
77425
78258
|
let total = 0;
|
|
77426
78259
|
for (const [, amount, unit] of parts) {
|
|
77427
|
-
const factor =
|
|
78260
|
+
const factor = UNITS2.find(([pattern]) => pattern.test(unit))?.[1];
|
|
77428
78261
|
if (factor === undefined)
|
|
77429
78262
|
return null;
|
|
77430
78263
|
total += Number(amount) * factor;
|
|
77431
78264
|
}
|
|
77432
78265
|
return Math.round(total);
|
|
77433
78266
|
}
|
|
77434
|
-
var
|
|
78267
|
+
var UNITS2;
|
|
77435
78268
|
var init_duration = __esm(() => {
|
|
77436
|
-
|
|
78269
|
+
UNITS2 = [
|
|
77437
78270
|
[/^(?:ms|milliseconds?)$/u, 1],
|
|
77438
78271
|
[/^(?:s|secs?|seconds?)$/u, 1000],
|
|
77439
78272
|
[/^(?:m|mins?|minutes?)$/u, 60000],
|
|
@@ -77598,11 +78431,11 @@ __export(exports_pr, {
|
|
|
77598
78431
|
run: () => run11
|
|
77599
78432
|
});
|
|
77600
78433
|
import { readFileSync as readFileSync21 } from "node:fs";
|
|
77601
|
-
import
|
|
78434
|
+
import path46 from "node:path";
|
|
77602
78435
|
function readBodyFile(io, file) {
|
|
77603
78436
|
try {
|
|
77604
78437
|
return {
|
|
77605
|
-
body: file === "-" ? forgeDeps.readStdin() : readFileSync21(
|
|
78438
|
+
body: file === "-" ? forgeDeps.readStdin() : readFileSync21(path46.resolve(io.cwd, file), "utf8")
|
|
77606
78439
|
};
|
|
77607
78440
|
} catch {
|
|
77608
78441
|
return `cannot read --body-file ${file}`;
|
|
@@ -77780,7 +78613,10 @@ async function merge3(argv, io) {
|
|
|
77780
78613
|
method: "value",
|
|
77781
78614
|
"delete-branch": "boolean",
|
|
77782
78615
|
unverified: "boolean",
|
|
77783
|
-
reason: "value"
|
|
78616
|
+
reason: "value",
|
|
78617
|
+
subject: "value",
|
|
78618
|
+
body: "value",
|
|
78619
|
+
"body-file": "value"
|
|
77784
78620
|
});
|
|
77785
78621
|
if (typeof flags === "string")
|
|
77786
78622
|
return usage3(io, flags, MERGE_USAGE);
|
|
@@ -77795,6 +78631,21 @@ async function merge3(argv, io) {
|
|
|
77795
78631
|
const method = flags.values.method ?? "squash";
|
|
77796
78632
|
if (!METHODS2.has(method))
|
|
77797
78633
|
return usage3(io, "--method must be squash, merge or rebase", MERGE_USAGE);
|
|
78634
|
+
const { subject } = flags.values;
|
|
78635
|
+
if (flags.values.body !== undefined && flags.values["body-file"] !== undefined)
|
|
78636
|
+
return usage3(io, "pass --body or --body-file, not both", MERGE_USAGE);
|
|
78637
|
+
let body = flags.values.body;
|
|
78638
|
+
if (flags.values["body-file"] !== undefined) {
|
|
78639
|
+
const read = readBodyFile(io, flags.values["body-file"]);
|
|
78640
|
+
if (typeof read === "string")
|
|
78641
|
+
return usage3(io, read, MERGE_USAGE);
|
|
78642
|
+
body = read.body;
|
|
78643
|
+
}
|
|
78644
|
+
if ((subject !== undefined || body !== undefined) && method !== "squash")
|
|
78645
|
+
return usage3(io, "--subject and --body set a squash message; use --method squash", MERGE_USAGE);
|
|
78646
|
+
if (subject !== undefined && (!subject.trim() || subject.includes(`
|
|
78647
|
+
`)))
|
|
78648
|
+
return usage3(io, "--subject must be one non-empty line", MERGE_USAGE);
|
|
77798
78649
|
const connected = connect(io);
|
|
77799
78650
|
if (!connected.ok)
|
|
77800
78651
|
return forgeFail(io, connected);
|
|
@@ -77803,7 +78654,9 @@ async function merge3(argv, io) {
|
|
|
77803
78654
|
method,
|
|
77804
78655
|
deleteBranch: flags.booleans.has("delete-branch"),
|
|
77805
78656
|
actor: actorFromEnv(io.env),
|
|
77806
|
-
...unverified ? { unverified } : {}
|
|
78657
|
+
...unverified ? { unverified } : {},
|
|
78658
|
+
...subject !== undefined ? { subject } : {},
|
|
78659
|
+
...body !== undefined ? { body } : {}
|
|
77807
78660
|
}, forgeDeps.sleep);
|
|
77808
78661
|
if (!result.ok)
|
|
77809
78662
|
return emit(io, fail(result.code, result.error, {
|
|
@@ -77967,7 +78820,7 @@ async function run11(argv, io) {
|
|
|
77967
78820
|
return reply(rest, io);
|
|
77968
78821
|
return usage3(io, sub && !sub.startsWith("-") ? `unknown pr subcommand "${sub}"` : "missing pr subcommand", USAGE4);
|
|
77969
78822
|
}
|
|
77970
|
-
var STATUS_USAGE = "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]", CREATE_USAGE = "workit pr create [--base <b> | --track <t>] (--title <t> [--body <text> | --body-file <f|->] | --fill) [--label <l>]… [--reviewer <login>]… [--draft] [--json]", MERGE_USAGE = "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--delete-branch] [--unverified --reason <why>] [--json]", READY_USAGE = "workit pr ready [--pr <n>] [--undo] [--json]", EDIT_USAGE = "workit pr edit [--pr <n>] [--title <t>] [--body-file <f|->] [--add-label <l>]… [--remove-label <l>]… [--add-reviewer <login>]… [--base <b>] [--json]", THREADS_USAGE = "workit pr threads [--pr <n>] [--json]", REPLY_USAGE = "workit pr reply [--pr <n>] --thread <id> [--body-file <f|->] [--resolve] [--json]", USAGE4 = "workit pr status|create|merge|ready|edit|threads|reply ... (workit help pr)", prLabel = (kind, number) => `${kind === "github" ? "PR #" : "MR !"}${number}`, mergedVerdictLine = (landed) => landed === "untracked" ? "merged outside workit (no merge record)" : landed.unverified ? `merged unverified at ${(landed.head ?? "?").slice(0, 12)} (recorded bypass)` : `merged with accepted verdict ${landed.verdictId ?? "?"} at ${(landed.head ?? "?").slice(0, 12)}`, METHODS2;
|
|
78823
|
+
var STATUS_USAGE = "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]", CREATE_USAGE = "workit pr create [--base <b> | --track <t>] (--title <t> [--body <text> | --body-file <f|->] | --fill) [--label <l>]… [--reviewer <login>]… [--draft] [--json]", MERGE_USAGE = "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--subject <s>] [--body <b> | --body-file <f|->] [--delete-branch] [--unverified --reason <why>] [--json]", READY_USAGE = "workit pr ready [--pr <n>] [--undo] [--json]", EDIT_USAGE = "workit pr edit [--pr <n>] [--title <t>] [--body-file <f|->] [--add-label <l>]… [--remove-label <l>]… [--add-reviewer <login>]… [--base <b>] [--json]", THREADS_USAGE = "workit pr threads [--pr <n>] [--json]", REPLY_USAGE = "workit pr reply [--pr <n>] --thread <id> [--body-file <f|->] [--resolve] [--json]", USAGE4 = "workit pr status|create|merge|ready|edit|threads|reply ... (workit help pr)", prLabel = (kind, number) => `${kind === "github" ? "PR #" : "MR !"}${number}`, mergedVerdictLine = (landed) => landed === "untracked" ? "merged outside workit (no merge record)" : landed.unverified ? `merged unverified at ${(landed.head ?? "?").slice(0, 12)} (recorded bypass)` : `merged with accepted verdict ${landed.verdictId ?? "?"} at ${(landed.head ?? "?").slice(0, 12)}`, METHODS2;
|
|
77971
78824
|
var init_pr = __esm(() => {
|
|
77972
78825
|
init_pr_body();
|
|
77973
78826
|
init_pr_edit();
|
|
@@ -78205,7 +79058,7 @@ __export(exports_git, {
|
|
|
78205
79058
|
run: () => run13
|
|
78206
79059
|
});
|
|
78207
79060
|
import { readFileSync as readFileSync22 } from "node:fs";
|
|
78208
|
-
import
|
|
79061
|
+
import path47 from "node:path";
|
|
78209
79062
|
function kindHint(cwd, kind) {
|
|
78210
79063
|
let kinds = [...BRANCH_KINDS];
|
|
78211
79064
|
try {
|
|
@@ -78256,6 +79109,7 @@ function parseCommit(argv) {
|
|
|
78256
79109
|
const out = {
|
|
78257
79110
|
messages: [],
|
|
78258
79111
|
files: [],
|
|
79112
|
+
trailers: [],
|
|
78259
79113
|
all: false,
|
|
78260
79114
|
amend: false,
|
|
78261
79115
|
noEdit: false,
|
|
@@ -78277,14 +79131,15 @@ function parseCommit(argv) {
|
|
|
78277
79131
|
}
|
|
78278
79132
|
const valued = [
|
|
78279
79133
|
["-m", "--message", out.messages],
|
|
78280
|
-
["-F", "--file", out.files]
|
|
78281
|
-
|
|
79134
|
+
["-F", "--file", out.files],
|
|
79135
|
+
[null, "--trailer", out.trailers]
|
|
79136
|
+
].find(([shortName, longName]) => arg === shortName || arg === longName || arg.startsWith(`${longName}=`) || shortName !== null && arg.startsWith(shortName) && arg.length > 2);
|
|
78282
79137
|
if (valued) {
|
|
78283
79138
|
const [shortName, longName, into] = valued;
|
|
78284
79139
|
if (arg === shortName || arg === longName) {
|
|
78285
79140
|
const value = argv[++index];
|
|
78286
79141
|
if (value === undefined)
|
|
78287
|
-
return `${arg} requires a ${shortName === "-m" ? "message" : "file"}`;
|
|
79142
|
+
return `${arg} requires a ${shortName === "-m" ? "message" : shortName === "-F" ? "file" : "Key: value trailer"}`;
|
|
78288
79143
|
into.push(value);
|
|
78289
79144
|
} else
|
|
78290
79145
|
into.push(arg.startsWith(`${longName}=`) ? arg.slice(longName.length + 1) : arg.slice(2));
|
|
@@ -78307,7 +79162,7 @@ function commitMessage(parsed, io) {
|
|
|
78307
79162
|
|
|
78308
79163
|
`);
|
|
78309
79164
|
try {
|
|
78310
|
-
return file === "-" ? readFileSync22(0, "utf8") : readFileSync22(
|
|
79165
|
+
return file === "-" ? readFileSync22(0, "utf8") : readFileSync22(path47.resolve(io.cwd, file), "utf8");
|
|
78311
79166
|
} catch (error) {
|
|
78312
79167
|
return {
|
|
78313
79168
|
error: `cannot read -F ${file}: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -78338,6 +79193,7 @@ async function commit(argv, io) {
|
|
|
78338
79193
|
paths: parsed.paths,
|
|
78339
79194
|
amend: parsed.amend,
|
|
78340
79195
|
allowEmpty: parsed.allowEmpty,
|
|
79196
|
+
trailers: parsed.trailers,
|
|
78341
79197
|
actor: actorFromEnv(io.env),
|
|
78342
79198
|
env: io.env
|
|
78343
79199
|
});
|
|
@@ -78450,6 +79306,13 @@ async function releasePush(names, io) {
|
|
|
78450
79306
|
..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : []
|
|
78451
79307
|
]);
|
|
78452
79308
|
}
|
|
79309
|
+
function trailers(argv, io) {
|
|
79310
|
+
const extra = argv.find((arg) => arg !== "--json");
|
|
79311
|
+
if (extra !== undefined)
|
|
79312
|
+
return usage3(io, `unexpected argument ${extra}`, "workit git trailers");
|
|
79313
|
+
const state = workspaceTrailers(io.cwd);
|
|
79314
|
+
return emit(io, ok(state), (data) => data.enabled ? "on" : "off");
|
|
79315
|
+
}
|
|
78453
79316
|
async function run13(argv, io) {
|
|
78454
79317
|
const [sub, ...rest] = argv;
|
|
78455
79318
|
if (sub === "branch")
|
|
@@ -78458,13 +79321,16 @@ async function run13(argv, io) {
|
|
|
78458
79321
|
return commit(rest, io);
|
|
78459
79322
|
if (sub === "push")
|
|
78460
79323
|
return push(rest, io);
|
|
79324
|
+
if (sub === "trailers")
|
|
79325
|
+
return trailers(rest, io);
|
|
78461
79326
|
return usage3(io, sub && !sub.startsWith("-") ? `unknown git subcommand "${sub}"` : "missing git subcommand", USAGE5);
|
|
78462
79327
|
}
|
|
78463
|
-
var BRANCH_USAGE = "workit git branch <name> | --kind feature|bugfix|release|hotfix --slug <s> [--base <b>] [--track <t>] [--carry] [--json]", COMMIT_USAGE = "workit git commit -m|--message <msg>… | -F|--file <file|-> [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>] [--json]", PUSH_USAGE = "workit git push [--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>…] [--json]", USAGE5 = "workit git branch|commit|push ... (workit help git)", short2 = (sha) => sha ? sha.slice(0, 12) : "(none)", COMMIT_SWITCHES;
|
|
79328
|
+
var BRANCH_USAGE = "workit git branch <name> | --kind feature|bugfix|release|hotfix --slug <s> [--base <b>] [--track <t>] [--carry] [--json]", COMMIT_USAGE = "workit git commit -m|--message <msg>… | -F|--file <file|-> [--trailer <key: value>]… [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>] [--json]", PUSH_USAGE = "workit git push [--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>…] [--json]", USAGE5 = "workit git branch|commit|push|trailers ... (workit help git)", short2 = (sha) => sha ? sha.slice(0, 12) : "(none)", COMMIT_SWITCHES;
|
|
78464
79329
|
var init_git2 = __esm(() => {
|
|
78465
79330
|
init_autonomy();
|
|
78466
79331
|
init_branch();
|
|
78467
79332
|
init_ops();
|
|
79333
|
+
init_workspaces();
|
|
78468
79334
|
init_ledger();
|
|
78469
79335
|
init_implicit_task();
|
|
78470
79336
|
init_output();
|
|
@@ -79015,8 +79881,8 @@ var init_stack2 = __esm(() => {
|
|
|
79015
79881
|
// packages/workit-core/src/fanout.ts
|
|
79016
79882
|
import { spawnSync as spawnSync24 } from "node:child_process";
|
|
79017
79883
|
import { createHash as createHash18 } from "node:crypto";
|
|
79018
|
-
import
|
|
79019
|
-
import
|
|
79884
|
+
import fs25 from "node:fs";
|
|
79885
|
+
import path48 from "node:path";
|
|
79020
79886
|
function expandBraces(pattern, limit = 64) {
|
|
79021
79887
|
for (let index = 0;index < pattern.length; index += 1) {
|
|
79022
79888
|
if (pattern[index] === "\\") {
|
|
@@ -79162,7 +80028,7 @@ function inScope(plan, slice, file) {
|
|
|
79162
80028
|
}
|
|
79163
80029
|
function parsePlan(raw, defaults, name) {
|
|
79164
80030
|
const problems = [];
|
|
79165
|
-
if (!
|
|
80031
|
+
if (!isRecord9(raw))
|
|
79166
80032
|
return { ok: false, problems: ["the plan must be a JSON object"] };
|
|
79167
80033
|
const planName = name ?? (typeof raw.name === "string" ? raw.name.trim() : "");
|
|
79168
80034
|
if (!planName || planName.length > 200 || /\p{Cc}/u.test(planName))
|
|
@@ -79185,8 +80051,8 @@ function parsePlan(raw, defaults, name) {
|
|
|
79185
80051
|
const seenIds = new Set;
|
|
79186
80052
|
const seenBranches = new Map;
|
|
79187
80053
|
raw.slices.forEach((item, index) => {
|
|
79188
|
-
const where =
|
|
79189
|
-
if (!
|
|
80054
|
+
const where = isRecord9(item) && typeof item.id === "string" ? `slice ${item.id}` : `slice #${index + 1}`;
|
|
80055
|
+
if (!isRecord9(item)) {
|
|
79190
80056
|
problems.push(`${where}: must be an object`);
|
|
79191
80057
|
return;
|
|
79192
80058
|
}
|
|
@@ -79250,7 +80116,7 @@ function parsePlan(raw, defaults, name) {
|
|
|
79250
80116
|
id,
|
|
79251
80117
|
branch,
|
|
79252
80118
|
base: optional("base") ?? "",
|
|
79253
|
-
worktree:
|
|
80119
|
+
worktree: path48.resolve(defaults.root, optional("worktree") ?? path48.join(defaults.worktreeRoot, id)),
|
|
79254
80120
|
tier,
|
|
79255
80121
|
dependsOn: [...new Set(dependsOn ?? [])],
|
|
79256
80122
|
scope,
|
|
@@ -79479,11 +80345,11 @@ function defaultTrunk(cwd) {
|
|
|
79479
80345
|
}
|
|
79480
80346
|
function mainCheckout(cwd) {
|
|
79481
80347
|
const common = gitCommonDir(cwd);
|
|
79482
|
-
return common &&
|
|
80348
|
+
return common && path48.basename(common) === ".git" ? path48.dirname(common) : path48.resolve(cwd);
|
|
79483
80349
|
}
|
|
79484
80350
|
function worktreeRoot(cwd) {
|
|
79485
80351
|
const main = mainCheckout(cwd);
|
|
79486
|
-
return
|
|
80352
|
+
return path48.join(path48.dirname(main), `${path48.basename(main)}-wt`);
|
|
79487
80353
|
}
|
|
79488
80354
|
function sliceHash(slice) {
|
|
79489
80355
|
const {
|
|
@@ -79501,10 +80367,10 @@ function fanoutsDir(cwd) {
|
|
|
79501
80367
|
const root = storeRoot(cwd);
|
|
79502
80368
|
if (!root.ok)
|
|
79503
80369
|
return fanoutFail(root.code, root.error, root.unblock);
|
|
79504
|
-
return { ok: true, data:
|
|
80370
|
+
return { ok: true, data: path48.join(root.value.root, "fanouts") };
|
|
79505
80371
|
}
|
|
79506
80372
|
function parseStored(raw) {
|
|
79507
|
-
if (!
|
|
80373
|
+
if (!isRecord9(raw) || typeof raw.name !== "string" || typeof raw.trunk !== "string")
|
|
79508
80374
|
return null;
|
|
79509
80375
|
if (!Array.isArray(raw.slices))
|
|
79510
80376
|
return null;
|
|
@@ -79521,7 +80387,7 @@ function parseStored(raw) {
|
|
|
79521
80387
|
});
|
|
79522
80388
|
const slices = [];
|
|
79523
80389
|
for (const item of raw.slices) {
|
|
79524
|
-
if (!
|
|
80390
|
+
if (!isRecord9(item) || typeof item.id !== "string" || typeof item.branch !== "string")
|
|
79525
80391
|
return null;
|
|
79526
80392
|
slices.push({
|
|
79527
80393
|
id: item.id,
|
|
@@ -79567,10 +80433,10 @@ function readFanout(cwd, name) {
|
|
|
79567
80433
|
const dir = fanoutsDir(cwd);
|
|
79568
80434
|
if (!dir.ok)
|
|
79569
80435
|
return dir;
|
|
79570
|
-
const file =
|
|
80436
|
+
const file = path48.join(dir.data, stackFileName(name));
|
|
79571
80437
|
let parsed = null;
|
|
79572
80438
|
try {
|
|
79573
|
-
parsed = parseStored(JSON.parse(
|
|
80439
|
+
parsed = parseStored(JSON.parse(fs25.readFileSync(file, "utf8")));
|
|
79574
80440
|
} catch (error) {
|
|
79575
80441
|
if (error.code === "ENOENT")
|
|
79576
80442
|
return { ok: true, data: null };
|
|
@@ -79584,7 +80450,7 @@ function listFanouts(cwd) {
|
|
|
79584
80450
|
return dir;
|
|
79585
80451
|
let names = [];
|
|
79586
80452
|
try {
|
|
79587
|
-
names =
|
|
80453
|
+
names = fs25.readdirSync(dir.data).filter((entry) => entry.endsWith(".json"));
|
|
79588
80454
|
} catch {
|
|
79589
80455
|
return { ok: true, data: [] };
|
|
79590
80456
|
}
|
|
@@ -79592,7 +80458,7 @@ function listFanouts(cwd) {
|
|
|
79592
80458
|
for (const entry of names) {
|
|
79593
80459
|
let parsed = null;
|
|
79594
80460
|
try {
|
|
79595
|
-
parsed = parseStored(JSON.parse(
|
|
80461
|
+
parsed = parseStored(JSON.parse(fs25.readFileSync(path48.join(dir.data, entry), "utf8")));
|
|
79596
80462
|
} catch {
|
|
79597
80463
|
parsed = null;
|
|
79598
80464
|
}
|
|
@@ -79622,16 +80488,16 @@ function writeFanout(cwd, plan) {
|
|
|
79622
80488
|
const dir = fanoutsDir(cwd);
|
|
79623
80489
|
if (!dir.ok)
|
|
79624
80490
|
return dir;
|
|
79625
|
-
const file =
|
|
80491
|
+
const file = path48.join(dir.data, stackFileName(plan.name));
|
|
79626
80492
|
const temp = `${file}.${process.pid}.tmp`;
|
|
79627
80493
|
try {
|
|
79628
|
-
|
|
79629
|
-
|
|
80494
|
+
fs25.mkdirSync(dir.data, { recursive: true });
|
|
80495
|
+
fs25.writeFileSync(temp, `${JSON.stringify(plan, null, 2)}
|
|
79630
80496
|
`);
|
|
79631
|
-
|
|
80497
|
+
fs25.renameSync(temp, file);
|
|
79632
80498
|
return { ok: true, data: file };
|
|
79633
80499
|
} catch (error) {
|
|
79634
|
-
|
|
80500
|
+
fs25.rmSync(temp, { force: true });
|
|
79635
80501
|
return fanoutFail("unavailable", `cannot write ${file}: ${error.message}`);
|
|
79636
80502
|
}
|
|
79637
80503
|
}
|
|
@@ -79644,7 +80510,7 @@ function planFanout(cwd, input, now = new Date) {
|
|
|
79644
80510
|
return fanoutFail("blocked", "--take-lead needs a session: WORKIT_SESSION_ID is not set", "export WORKIT_SESSION_ID=<your session id>, then plan again with --take-lead");
|
|
79645
80511
|
if (input.takeLead && isDelegateSession(session))
|
|
79646
80512
|
return fanoutFail("blocked", `session ${session} is a worker or verifier id; only a lead takes the lead`, "run --take-lead from the lead's own session");
|
|
79647
|
-
const parsed = parsePlan(input.trunk &&
|
|
80513
|
+
const parsed = parsePlan(input.trunk && isRecord9(input.raw) ? { ...input.raw, trunk: input.trunk } : input.raw, {
|
|
79648
80514
|
trunk: input.trunk ?? defaultTrunk(cwd),
|
|
79649
80515
|
root: mainCheckout(cwd),
|
|
79650
80516
|
worktreeRoot: worktreeRoot(cwd)
|
|
@@ -79767,7 +80633,7 @@ var FANOUT_VERSION = 1, TIERS, FAN_IN_MODES, fanoutFail = (code, error, unblock,
|
|
|
79767
80633
|
error,
|
|
79768
80634
|
...unblock ? { unblock } : {},
|
|
79769
80635
|
...data ? { data } : {}
|
|
79770
|
-
}), DEFAULT_SHARED, escapeRegExp4 = (text) => text.replace(/[.+^$()|[\]\\{}*?]/gu, "\\$&"), unescape2 = (pattern) => pattern.replace(/\\(.)/gu, "$1"), hasWildcard = (pattern) => /[*?{]/u.test(pattern.replace(/\\./gu, "")), compiled, matchesAny = (patterns, file) => patterns.some((pattern) => matchesScope(pattern, file)),
|
|
80636
|
+
}), DEFAULT_SHARED, escapeRegExp4 = (text) => text.replace(/[.+^$()|[\]\\{}*?]/gu, "\\$&"), unescape2 = (pattern) => pattern.replace(/\\(.)/gu, "$1"), hasWildcard = (pattern) => /[*?{]/u.test(pattern.replace(/\\./gu, "")), compiled, matchesAny = (patterns, file) => patterns.some((pattern) => matchesScope(pattern, file)), isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), PLACEHOLDER3, filled = (text) => text.trim() !== "" && !PLACEHOLDER3.test(text.trim()), textList = (value) => {
|
|
79771
80637
|
if (typeof value === "string")
|
|
79772
80638
|
return [value];
|
|
79773
80639
|
if (Array.isArray(value) && value.every((item) => typeof item === "string"))
|
|
@@ -79825,8 +80691,8 @@ var init_fanout = __esm(() => {
|
|
|
79825
80691
|
|
|
79826
80692
|
// packages/workit-core/src/fanout-worktree.ts
|
|
79827
80693
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
79828
|
-
import
|
|
79829
|
-
import
|
|
80694
|
+
import fs26 from "node:fs";
|
|
80695
|
+
import path49 from "node:path";
|
|
79830
80696
|
function rowSample(lines) {
|
|
79831
80697
|
const out = [];
|
|
79832
80698
|
let bytes = 0;
|
|
@@ -79886,7 +80752,7 @@ function adminDir(worktree) {
|
|
|
79886
80752
|
const file = dir.stdout.trim();
|
|
79887
80753
|
let token = null;
|
|
79888
80754
|
try {
|
|
79889
|
-
token =
|
|
80755
|
+
token = fs26.readFileSync(path49.join(file, TOKEN_FILE), "utf8").trim() || null;
|
|
79890
80756
|
} catch {
|
|
79891
80757
|
token = null;
|
|
79892
80758
|
}
|
|
@@ -79894,7 +80760,7 @@ function adminDir(worktree) {
|
|
|
79894
80760
|
const stat = worktreeDeps.stat(file);
|
|
79895
80761
|
return {
|
|
79896
80762
|
path: file,
|
|
79897
|
-
id:
|
|
80763
|
+
id: path49.basename(file),
|
|
79898
80764
|
ino: stat.ino.toString(),
|
|
79899
80765
|
token,
|
|
79900
80766
|
birthMs: stat.birthtimeNs > 0n ? Number(stat.birthtimeMs) : null,
|
|
@@ -79910,7 +80776,7 @@ function markAdminDir(worktree) {
|
|
|
79910
80776
|
return null;
|
|
79911
80777
|
const token = randomBytes4(16).toString("hex");
|
|
79912
80778
|
try {
|
|
79913
|
-
|
|
80779
|
+
fs26.writeFileSync(path49.join(admin.path, TOKEN_FILE), `${token}
|
|
79914
80780
|
`);
|
|
79915
80781
|
} catch {
|
|
79916
80782
|
return null;
|
|
@@ -79925,25 +80791,25 @@ function excludeScratch(cwd) {
|
|
|
79925
80791
|
const common = gitCommonDir(cwd);
|
|
79926
80792
|
if (!common)
|
|
79927
80793
|
return;
|
|
79928
|
-
const file =
|
|
80794
|
+
const file = path49.join(common, "info", "exclude");
|
|
79929
80795
|
let text = "";
|
|
79930
80796
|
try {
|
|
79931
|
-
text =
|
|
80797
|
+
text = fs26.readFileSync(file, "utf8");
|
|
79932
80798
|
} catch {
|
|
79933
80799
|
text = "";
|
|
79934
80800
|
}
|
|
79935
80801
|
if (text.split(/\r?\n/u).includes(EXCLUDE_LINE))
|
|
79936
80802
|
return;
|
|
79937
|
-
|
|
79938
|
-
|
|
80803
|
+
fs26.mkdirSync(path49.dirname(file), { recursive: true });
|
|
80804
|
+
fs26.appendFileSync(file, `${text && !text.endsWith(`
|
|
79939
80805
|
`) ? `
|
|
79940
80806
|
` : ""}# workit fanout: per-worker scratch dirs
|
|
79941
80807
|
${EXCLUDE_LINE}
|
|
79942
80808
|
`);
|
|
79943
80809
|
}
|
|
79944
80810
|
function ensureScratch(worktree) {
|
|
79945
|
-
const scratch =
|
|
79946
|
-
|
|
80811
|
+
const scratch = path49.join(worktree, SCRATCH_DIR);
|
|
80812
|
+
fs26.mkdirSync(scratch, { recursive: true });
|
|
79947
80813
|
return scratch;
|
|
79948
80814
|
}
|
|
79949
80815
|
function createSliceWorktree(cwd, plan, input) {
|
|
@@ -79987,8 +80853,8 @@ function createSliceWorktree(cwd, plan, input) {
|
|
|
79987
80853
|
return fanoutFail("blocked", `${slice.branch} is checked out at ${elsewhere.path}: never two live workers on one branch`, `stop that worker, then workit fanout worktree release ${slice.id} (or git worktree remove ${elsewhere.path})`);
|
|
79988
80854
|
let dirExisted = false;
|
|
79989
80855
|
try {
|
|
79990
|
-
const stat =
|
|
79991
|
-
if (!stat.isDirectory() ||
|
|
80856
|
+
const stat = fs26.statSync(target);
|
|
80857
|
+
if (!stat.isDirectory() || fs26.readdirSync(target).length > 0)
|
|
79992
80858
|
return fanoutFail("blocked", `${target} exists and is ${stat.isDirectory() ? "not empty" : "not a directory"} (not a worktree of this repository)`, `move it away, or set slice ${slice.id}'s worktree to another path`);
|
|
79993
80859
|
dirExisted = true;
|
|
79994
80860
|
} catch (error) {
|
|
@@ -80070,7 +80936,7 @@ function releaseSliceWorktree(cwd, plan, input) {
|
|
|
80070
80936
|
notes: []
|
|
80071
80937
|
};
|
|
80072
80938
|
if (index < 0) {
|
|
80073
|
-
if (
|
|
80939
|
+
if (fs26.existsSync(target))
|
|
80074
80940
|
return fanoutFail("blocked", `${target} is not a worktree of this repository; nothing was removed`, "remove it yourself once you know what it holds");
|
|
80075
80941
|
out.notes.push(`no worktree at ${target}; nothing to release`);
|
|
80076
80942
|
return { ok: true, data: out };
|
|
@@ -80132,7 +80998,7 @@ function releaseSliceWorktree(cwd, plan, input) {
|
|
|
80132
80998
|
`)[0]}`, entry.locked ? `git worktree unlock ${entry.path}, then release again` : undefined, { ...out });
|
|
80133
80999
|
out.removed = true;
|
|
80134
81000
|
if (made.dirExisted === true)
|
|
80135
|
-
|
|
81001
|
+
fs26.mkdirSync(entry.path, { recursive: true });
|
|
80136
81002
|
if (out.unpushed)
|
|
80137
81003
|
out.notes.push(`${slice.branch} is kept with ${out.unpushed} commit(s) not on the remote`);
|
|
80138
81004
|
return { ok: true, data: out };
|
|
@@ -80160,32 +81026,32 @@ function unpushedCount(cwd, branch) {
|
|
|
80160
81026
|
return count.ok ? Number(count.stdout.trim()) : null;
|
|
80161
81027
|
}
|
|
80162
81028
|
function removeScratch(worktree) {
|
|
80163
|
-
const scratch =
|
|
81029
|
+
const scratch = path49.join(worktree, SCRATCH_DIR);
|
|
80164
81030
|
let stat;
|
|
80165
81031
|
try {
|
|
80166
|
-
stat =
|
|
81032
|
+
stat = fs26.lstatSync(scratch);
|
|
80167
81033
|
} catch {
|
|
80168
81034
|
return;
|
|
80169
81035
|
}
|
|
80170
81036
|
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
80171
|
-
|
|
81037
|
+
fs26.unlinkSync(scratch);
|
|
80172
81038
|
else
|
|
80173
|
-
|
|
81039
|
+
fs26.rmSync(scratch, { recursive: true, force: true, maxRetries: 2 });
|
|
80174
81040
|
}
|
|
80175
81041
|
var SLOW, SCRATCH_DIR = ".workit-scratch", EXCLUDE_LINE, DIRTY_ROW_BYTES = 2000, canonical2 = (target) => {
|
|
80176
81042
|
try {
|
|
80177
|
-
return
|
|
81043
|
+
return fs26.realpathSync.native(target);
|
|
80178
81044
|
} catch {
|
|
80179
|
-
return
|
|
81045
|
+
return path49.resolve(target);
|
|
80180
81046
|
}
|
|
80181
81047
|
}, samePath = (a, b) => {
|
|
80182
81048
|
const left = canonical2(a);
|
|
80183
81049
|
const right = canonical2(b);
|
|
80184
81050
|
return process.platform === "win32" || process.platform === "darwin" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
80185
81051
|
}, inside3 = (child, parent) => {
|
|
80186
|
-
const relative =
|
|
80187
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
80188
|
-
}, slicePath = (cwd, slice) =>
|
|
81052
|
+
const relative = path49.relative(canonical2(parent), canonical2(child));
|
|
81053
|
+
return relative === "" || !relative.startsWith("..") && !path49.isAbsolute(relative);
|
|
81054
|
+
}, slicePath = (cwd, slice) => path49.isAbsolute(slice.worktree) ? slice.worktree : path49.resolve(mainCheckout(cwd), slice.worktree), CLOCK_SLACK_MS = 2000, TOKEN_FILE = "workit-fanout-token", worktreeDeps, worktreeFailed = (stderr) => fanoutFail("failed", `git worktree add failed: ${stderr.trim().split(`
|
|
80189
81055
|
`)[0] || "unknown error"}`);
|
|
80190
81056
|
var init_fanout_worktree = __esm(() => {
|
|
80191
81057
|
init_ops();
|
|
@@ -80195,7 +81061,7 @@ var init_fanout_worktree = __esm(() => {
|
|
|
80195
81061
|
SLOW = GIT_TIMEOUTS.worktree;
|
|
80196
81062
|
EXCLUDE_LINE = `/${SCRATCH_DIR}/`;
|
|
80197
81063
|
worktreeDeps = {
|
|
80198
|
-
stat: (file) =>
|
|
81064
|
+
stat: (file) => fs26.statSync(file, { bigint: true })
|
|
80199
81065
|
};
|
|
80200
81066
|
});
|
|
80201
81067
|
|
|
@@ -80236,7 +81102,9 @@ function renderBrief(cwd, plan, input) {
|
|
|
80236
81102
|
id: row.id,
|
|
80237
81103
|
what: String(row.what)
|
|
80238
81104
|
}));
|
|
80239
|
-
const
|
|
81105
|
+
const identity = identityOf(input.actor.host);
|
|
81106
|
+
const minted = workerSession(sessionSafe(input.actor.session), slice.id, input.attempt);
|
|
81107
|
+
const session = identity === "declared" ? minted : null;
|
|
80240
81108
|
const fanIn = plan.fanIn === "integration" ? [
|
|
80241
81109
|
`FAN-IN: integration branch ${plan.trunk}. Before you report, merge its tip into your branch`,
|
|
80242
81110
|
` (\`git merge --no-edit ${plan.trunk}\`), resolve conflicts inside SCOPE and re-run VERIFY, so the`,
|
|
@@ -80265,8 +81133,8 @@ function renderBrief(cwd, plan, input) {
|
|
|
80265
81133
|
...REPORT,
|
|
80266
81134
|
"STANDING:",
|
|
80267
81135
|
...standing.length ? standing.map((order) => ` - ${order.what}`) : [" - none recorded beyond this brief"],
|
|
80268
|
-
|
|
80269
|
-
...rules(mode, branch, slice.base)
|
|
81136
|
+
...identityLines(identity, minted),
|
|
81137
|
+
...rules(identity, mode, branch, slice.base)
|
|
80270
81138
|
];
|
|
80271
81139
|
return {
|
|
80272
81140
|
ok: true,
|
|
@@ -80287,13 +81155,38 @@ function renderBrief(cwd, plan, input) {
|
|
|
80287
81155
|
}
|
|
80288
81156
|
};
|
|
80289
81157
|
}
|
|
80290
|
-
var DEFAULT_TIMEBOX = "30 minutes", REPORT,
|
|
80291
|
-
"
|
|
80292
|
-
|
|
80293
|
-
|
|
80294
|
-
|
|
80295
|
-
|
|
80296
|
-
|
|
81158
|
+
var DEFAULT_TIMEBOX = "30 minutes", REPORT, identityOf = (host) => {
|
|
81159
|
+
const family = host?.replace(/_(?:cli|desktop)$/, "") ?? "";
|
|
81160
|
+
if (family === "claude_code")
|
|
81161
|
+
return "issued";
|
|
81162
|
+
if (family === "opencode" || family === "pi" || family === "codex")
|
|
81163
|
+
return "derived";
|
|
81164
|
+
return "declared";
|
|
81165
|
+
}, identityLines = (identity, session) => {
|
|
81166
|
+
if (identity === "issued")
|
|
81167
|
+
return [
|
|
81168
|
+
" Session: your env already has WORKIT_SESSION_ID (the lead's id; SubagentStart issues ids only to verifiers and reviewers). Use it as is, in your Workit-Session trailer and workit commands; never export or hand-mint one."
|
|
81169
|
+
];
|
|
81170
|
+
if (identity === "derived")
|
|
81171
|
+
return [
|
|
81172
|
+
" Session: your host already sets WORKIT_SESSION_ID in your shell; export nothing and never hand-mint one."
|
|
81173
|
+
];
|
|
81174
|
+
return [` export WORKIT_SESSION_ID=${session}`];
|
|
81175
|
+
}, rules = (identity, mode, branch, base) => {
|
|
81176
|
+
const plain = identity === "issued";
|
|
81177
|
+
let first = ` 1. First command: \`workit git branch ${branch} --base ${base}\` (your worktree may start on another name).`;
|
|
81178
|
+
if (mode === "resume")
|
|
81179
|
+
first = ` 1. First command: \`git switch ${branch}\`, then continue from its head.`;
|
|
81180
|
+
else if (plain)
|
|
81181
|
+
first = ` 1. First command: \`git fetch origin && git switch -c ${branch} origin/${base}\` (plain git: worktree isolation refuses every \`workit git\` form).`;
|
|
81182
|
+
return [
|
|
81183
|
+
"RULES:",
|
|
81184
|
+
first,
|
|
81185
|
+
' 2. Decide ambiguities yourself: `workit ledger ruling "<what>" --why "<why>" --cost-if-wrong "<cost>"`; stop only for an irreversible, security-sensitive or out-of-worktree action.',
|
|
81186
|
+
plain ? ' 3. Commit with plain `git commit -m <msg> --trailer "Workit-Session: <the WORKIT_SESSION_ID in your env>" --trailer "<your host attribution trailer>" -- <paths in SCOPE>`; push only if this brief says so.' : " 3. Commit with `workit git commit -m <msg> -- <paths in SCOPE>`; push only if this brief says so.",
|
|
81187
|
+
" 4. Never record a verdict on your own work."
|
|
81188
|
+
];
|
|
81189
|
+
}, sessionSafe = (text) => text.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-"), SESSION_MAX = 128;
|
|
80297
81190
|
var init_fanout_brief = __esm(() => {
|
|
80298
81191
|
init_ledger();
|
|
80299
81192
|
init_fanout();
|
|
@@ -80349,9 +81242,9 @@ function hasOwnCommits2(cwd, branch, start, head) {
|
|
|
80349
81242
|
const onto = /^rebase(?: -i)? \(finish\): \S+ onto ([0-9a-f]+)/u.exec(subject)?.[1];
|
|
80350
81243
|
if (!made && !(onto && !sha.startsWith(onto) && !onto.startsWith(sha)))
|
|
80351
81244
|
continue;
|
|
80352
|
-
if (sha !== head && !
|
|
81245
|
+
if (sha !== head && !isAncestor3(cwd, sha, head))
|
|
80353
81246
|
continue;
|
|
80354
|
-
if (start && (sha === start ||
|
|
81247
|
+
if (start && (sha === start || isAncestor3(cwd, sha, start)))
|
|
80355
81248
|
continue;
|
|
80356
81249
|
return true;
|
|
80357
81250
|
}
|
|
@@ -80361,7 +81254,7 @@ function reverted(cwd, trunk, landedHead, start) {
|
|
|
80361
81254
|
const fork = mergeBase(cwd, landedHead, trunk);
|
|
80362
81255
|
if (fork && fork !== landedHead)
|
|
80363
81256
|
return unchangedSince(cwd, fork, landedHead, trunk);
|
|
80364
|
-
const before = start && start !== landedHead &&
|
|
81257
|
+
const before = start && start !== landedHead && isAncestor3(cwd, start, landedHead) ? start : gitRun4(cwd, ["rev-parse", "--verify", "-q", `${landedHead}^1`]).stdout.trim();
|
|
80365
81258
|
return before ? unchangedSince(cwd, before, landedHead, trunk) : false;
|
|
80366
81259
|
}
|
|
80367
81260
|
function unchangedSince(cwd, before, after, trunk) {
|
|
@@ -80469,9 +81362,9 @@ function detectLanding(ctx, slice, input) {
|
|
|
80469
81362
|
}
|
|
80470
81363
|
return merged ? landed("pr_merged", merged) : { ...out, landed: true, how: "pr_merged" };
|
|
80471
81364
|
}
|
|
80472
|
-
if (!merged || head === merged ||
|
|
81365
|
+
if (!merged || head === merged || isAncestor3(ctx.cwd, head, merged))
|
|
80473
81366
|
return landed("pr_merged", merged ?? head);
|
|
80474
|
-
out.note =
|
|
81367
|
+
out.note = isAncestor3(ctx.cwd, merged, head) ? `${label} merged at ${merged.slice(0, 12)}; ${slice.branch} is ahead of it with newer commits` : `${label} merged at ${merged.slice(0, 12)}; ${slice.branch} has diverged from it (rewritten after the merge)`;
|
|
80475
81368
|
return out;
|
|
80476
81369
|
}
|
|
80477
81370
|
}
|
|
@@ -80488,7 +81381,7 @@ function detectLanding(ctx, slice, input) {
|
|
|
80488
81381
|
if (trunkIds.size === 0)
|
|
80489
81382
|
return out;
|
|
80490
81383
|
const froms = [fork];
|
|
80491
|
-
if (input.parentHead && input.parentHead !== head &&
|
|
81384
|
+
if (input.parentHead && input.parentHead !== head && isAncestor3(ctx.cwd, input.parentHead, head))
|
|
80492
81385
|
froms.push(input.parentHead);
|
|
80493
81386
|
for (const from of froms) {
|
|
80494
81387
|
const id = patchId(ctx.cwd, from, head);
|
|
@@ -80504,7 +81397,7 @@ function sliceStart(cwd, rows, fanout, slice) {
|
|
|
80504
81397
|
const row = rows.findLast((candidate) => candidate.type === "fanout.worktree.created" && candidate.fanout === fanout && candidate.slice === slice.id && typeof candidate.baseSha === "string" && inSliceRun(candidate, slice));
|
|
80505
81398
|
return row?.baseSha ?? null;
|
|
80506
81399
|
}
|
|
80507
|
-
var
|
|
81400
|
+
var isAncestor3 = (cwd, a, b) => gitRun4(cwd, ["merge-base", "--is-ancestor", a, b]).ok, TRUNK_SCAN = 500;
|
|
80508
81401
|
var init_fanout_landed = __esm(() => {
|
|
80509
81402
|
init_rev();
|
|
80510
81403
|
init_fanout();
|
|
@@ -80561,7 +81454,7 @@ function checkFanout(cwd, plan, options = {}) {
|
|
|
80561
81454
|
const b = tips.get(second);
|
|
80562
81455
|
if (landings.get(first)?.landed || landings.get(second)?.landed)
|
|
80563
81456
|
continue;
|
|
80564
|
-
if (!a || !b ||
|
|
81457
|
+
if (!a || !b || isAncestor4(cwd, a, b) || isAncestor4(cwd, b, a))
|
|
80565
81458
|
continue;
|
|
80566
81459
|
const check = checks.get(second);
|
|
80567
81460
|
const theirs = new Map((changedBy.get(first) ?? []).map((file) => [file.toLowerCase(), file]));
|
|
@@ -80741,7 +81634,7 @@ function unblockFor(check, plan) {
|
|
|
80741
81634
|
return `unblock ${check.waitsFor.join(", ")} first`;
|
|
80742
81635
|
return check.reasons[0] ?? "workit fanout check";
|
|
80743
81636
|
}
|
|
80744
|
-
var SAFE = (value) => Boolean(value) && !value?.startsWith("-"),
|
|
81637
|
+
var SAFE = (value) => Boolean(value) && !value?.startsWith("-"), isAncestor4 = (cwd, a, b) => gitRun4(cwd, ["merge-base", "--is-ancestor", a, b]).ok, resolves = (cwd, ref) => gitRun4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]).ok, blank2 = (slice, head) => ({
|
|
80745
81638
|
id: slice.id,
|
|
80746
81639
|
branch: slice.branch,
|
|
80747
81640
|
head,
|
|
@@ -80960,8 +81853,8 @@ var exports_fanout = {};
|
|
|
80960
81853
|
__export(exports_fanout, {
|
|
80961
81854
|
run: () => run16
|
|
80962
81855
|
});
|
|
80963
|
-
import
|
|
80964
|
-
import
|
|
81856
|
+
import fs27 from "node:fs";
|
|
81857
|
+
import path50 from "node:path";
|
|
80965
81858
|
function forgeFor(io, offline) {
|
|
80966
81859
|
if (offline)
|
|
80967
81860
|
return { forge: null, error: "--offline" };
|
|
@@ -81022,10 +81915,10 @@ function plan2(argv, io) {
|
|
|
81022
81915
|
return usage3(io, "pass --trunk or --track, not both (--track picks the trunk)", PLAN_USAGE2);
|
|
81023
81916
|
if (flags.positionals.length !== 1)
|
|
81024
81917
|
return usage3(io, flags.positionals.length ? `unexpected argument ${flags.positionals[1]}` : "missing <plan.json>", PLAN_USAGE2);
|
|
81025
|
-
const file =
|
|
81918
|
+
const file = path50.resolve(io.cwd, flags.positionals[0]);
|
|
81026
81919
|
let raw;
|
|
81027
81920
|
try {
|
|
81028
|
-
raw = JSON.parse(
|
|
81921
|
+
raw = JSON.parse(fs27.readFileSync(file, "utf8"));
|
|
81029
81922
|
} catch (error) {
|
|
81030
81923
|
return usage3(io, `cannot read ${file} as JSON: ${error.message}`, PLAN_USAGE2);
|
|
81031
81924
|
}
|
|
@@ -96510,7 +97403,7 @@ var init_ast = __esm(() => {
|
|
|
96510
97403
|
|
|
96511
97404
|
// packages/workit-cli/src/test-audit/rules.ts
|
|
96512
97405
|
import { builtinModules } from "node:module";
|
|
96513
|
-
import
|
|
97406
|
+
import path51 from "node:path";
|
|
96514
97407
|
function testCallee(call) {
|
|
96515
97408
|
let callee = call.callee;
|
|
96516
97409
|
if (callee.type === "CallExpression")
|
|
@@ -97003,12 +97896,12 @@ function checkConstantsOnly(ctx, emit) {
|
|
|
97003
97896
|
}
|
|
97004
97897
|
function checkOverMocking(ctx, emit) {
|
|
97005
97898
|
const { program } = ctx.parsed;
|
|
97006
|
-
const unit =
|
|
97899
|
+
const unit = path51.basename(ctx.file).replace(/\.(?:[cm]?[jt]sx?)$/, "").replace(/\.(?:test|spec)$/, "");
|
|
97007
97900
|
const mocked = find2(program, (node) => node.type === "CallExpression" && MODULE_MOCK.test(calleeName(node.callee) ?? "") && node.arguments[0]?.type === "StringLiteral");
|
|
97008
97901
|
const internal = mocked.filter((call) => /^(?:\.|@\/|~\/)/.test(call.arguments[0].value));
|
|
97009
97902
|
for (const call of mocked) {
|
|
97010
97903
|
const spec = call.arguments[0].value;
|
|
97011
|
-
const base =
|
|
97904
|
+
const base = path51.basename(spec.replace(/\/index(?:\.[cm]?[jt]sx?)?$/, "")).replace(/\.(?:[cm]?[jt]sx?)$/, "");
|
|
97012
97905
|
if (base === unit)
|
|
97013
97906
|
emit({
|
|
97014
97907
|
rule: "over-mocking",
|
|
@@ -97398,7 +98291,7 @@ var init_rules = __esm(() => {
|
|
|
97398
98291
|
// packages/workit-cli/src/test-audit/audit.ts
|
|
97399
98292
|
import { spawnSync as spawnSync26 } from "node:child_process";
|
|
97400
98293
|
import { existsSync as existsSync20, readdirSync as readdirSync9, readFileSync as readFileSync23, statSync as statSync8 } from "node:fs";
|
|
97401
|
-
import
|
|
98294
|
+
import path52 from "node:path";
|
|
97402
98295
|
function git7(cwd, args) {
|
|
97403
98296
|
const run = spawnSync26("git", ["-c", "core.quotepath=off", ...args], {
|
|
97404
98297
|
cwd,
|
|
@@ -97411,7 +98304,7 @@ function walkTests(dir, out) {
|
|
|
97411
98304
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
97412
98305
|
if (entry.name.startsWith(".") && entry.name !== "." && entry.isDirectory())
|
|
97413
98306
|
continue;
|
|
97414
|
-
const full =
|
|
98307
|
+
const full = path52.join(dir, entry.name);
|
|
97415
98308
|
if (entry.isDirectory()) {
|
|
97416
98309
|
if (!SKIP_DIRS.has(entry.name))
|
|
97417
98310
|
walkTests(full, out);
|
|
@@ -97423,7 +98316,7 @@ function expandPaths(cwd, inputs) {
|
|
|
97423
98316
|
const files = [];
|
|
97424
98317
|
const missing = [];
|
|
97425
98318
|
for (const input of inputs.length > 0 ? inputs : ["."]) {
|
|
97426
|
-
const full =
|
|
98319
|
+
const full = path52.resolve(cwd, input);
|
|
97427
98320
|
if (!existsSync20(full))
|
|
97428
98321
|
missing.push(input);
|
|
97429
98322
|
else if (statSync8(full).isDirectory())
|
|
@@ -97460,11 +98353,11 @@ function changedLines(cwd, base) {
|
|
|
97460
98353
|
for (let offset = 0;offset < count; offset += 1)
|
|
97461
98354
|
lines.add(start + offset);
|
|
97462
98355
|
}
|
|
97463
|
-
changed.set(
|
|
98356
|
+
changed.set(path52.join(root, file), lines);
|
|
97464
98357
|
}
|
|
97465
98358
|
const untracked = git7(root, ["ls-files", "--others", "--exclude-standard", "-z"]);
|
|
97466
98359
|
for (const file of untracked.stdout.split("\x00").filter(Boolean))
|
|
97467
|
-
changed.set(
|
|
98360
|
+
changed.set(path52.join(root, file), "all");
|
|
97468
98361
|
return changed;
|
|
97469
98362
|
}
|
|
97470
98363
|
function auditFiles(files, display) {
|
|
@@ -97526,7 +98419,7 @@ function auditFiles(files, display) {
|
|
|
97526
98419
|
durationMs: Math.round(performance.now() - started)
|
|
97527
98420
|
};
|
|
97528
98421
|
}
|
|
97529
|
-
var TEST_FILE, CODE_FILE, SKIP_DIRS, toPosix = (file) => file.split(
|
|
98422
|
+
var TEST_FILE, CODE_FILE, SKIP_DIRS, toPosix = (file) => file.split(path52.sep).join("/"), SEVERITY_ORDER, ruleIds;
|
|
97530
98423
|
var init_audit = __esm(() => {
|
|
97531
98424
|
init_ast();
|
|
97532
98425
|
init_rules();
|
|
@@ -97555,8 +98448,8 @@ import {
|
|
|
97555
98448
|
writeFileSync as writeFileSync11,
|
|
97556
98449
|
copyFileSync as copyFileSync6
|
|
97557
98450
|
} from "node:fs";
|
|
97558
|
-
import
|
|
97559
|
-
import
|
|
98451
|
+
import os16 from "node:os";
|
|
98452
|
+
import path53 from "node:path";
|
|
97560
98453
|
function mutantsFor(source, file, lines) {
|
|
97561
98454
|
const parsed = parseSource(source, file);
|
|
97562
98455
|
const out = [];
|
|
@@ -97629,7 +98522,7 @@ function relatedTests(source, testFiles) {
|
|
|
97629
98522
|
for (const match of text.matchAll(SPECIFIER)) {
|
|
97630
98523
|
const spec = match[1];
|
|
97631
98524
|
if (spec.startsWith(".")) {
|
|
97632
|
-
if (stripExt(
|
|
98525
|
+
if (stripExt(path53.posix.join(path53.posix.dirname(file), spec)) === target)
|
|
97633
98526
|
return true;
|
|
97634
98527
|
} else if (spec.includes("/") && stripExt(spec).split("/").slice(-2).join("/") === tail)
|
|
97635
98528
|
return true;
|
|
@@ -97637,7 +98530,7 @@ function relatedTests(source, testFiles) {
|
|
|
97637
98530
|
return false;
|
|
97638
98531
|
}).map((entry) => entry.file);
|
|
97639
98532
|
}
|
|
97640
|
-
function removeStaleCopies(tmp =
|
|
98533
|
+
function removeStaleCopies(tmp = os16.tmpdir()) {
|
|
97641
98534
|
let removed = 0;
|
|
97642
98535
|
let names = [];
|
|
97643
98536
|
try {
|
|
@@ -97649,12 +98542,12 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
|
|
|
97649
98542
|
for (const name of names) {
|
|
97650
98543
|
if (!name.startsWith(TEMP_PREFIX))
|
|
97651
98544
|
continue;
|
|
97652
|
-
const dir =
|
|
98545
|
+
const dir = path53.join(tmp, name);
|
|
97653
98546
|
try {
|
|
97654
98547
|
const stat = lstatSync8(dir);
|
|
97655
98548
|
if (!stat.isDirectory() || uid !== undefined && stat.uid !== uid)
|
|
97656
98549
|
continue;
|
|
97657
|
-
const pid = Number(readFileSync24(
|
|
98550
|
+
const pid = Number(readFileSync24(path53.join(dir, PID_FILE), "utf8"));
|
|
97658
98551
|
if (Number.isInteger(pid) && pid > 0 && alive(pid))
|
|
97659
98552
|
continue;
|
|
97660
98553
|
} catch (error) {
|
|
@@ -97671,8 +98564,8 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
|
|
|
97671
98564
|
function linkNodeModules(origin, target, repo, copy) {
|
|
97672
98565
|
mkdirSync10(target, { recursive: true });
|
|
97673
98566
|
for (const entry of readdirSync10(origin, { withFileTypes: true })) {
|
|
97674
|
-
const from =
|
|
97675
|
-
const to =
|
|
98567
|
+
const from = path53.join(origin, entry.name);
|
|
98568
|
+
const to = path53.join(target, entry.name);
|
|
97676
98569
|
if (entry.name.startsWith("@") && entry.isDirectory()) {
|
|
97677
98570
|
linkNodeModules(from, to, repo, copy);
|
|
97678
98571
|
continue;
|
|
@@ -97688,9 +98581,9 @@ function linkNodeModules(origin, target, repo, copy) {
|
|
|
97688
98581
|
} catch {
|
|
97689
98582
|
continue;
|
|
97690
98583
|
}
|
|
97691
|
-
const relative =
|
|
97692
|
-
if (!relative.startsWith("..") && !
|
|
97693
|
-
symlinkSync(
|
|
98584
|
+
const relative = path53.relative(repo, real);
|
|
98585
|
+
if (!relative.startsWith("..") && !path53.isAbsolute(relative) && !relative.split(path53.sep).includes("node_modules")) {
|
|
98586
|
+
symlinkSync(path53.join(copy, relative), to, "junction");
|
|
97694
98587
|
continue;
|
|
97695
98588
|
}
|
|
97696
98589
|
}
|
|
@@ -97706,7 +98599,7 @@ function copySymlink(repo, tree, from, to) {
|
|
|
97706
98599
|
}
|
|
97707
98600
|
const realRepo = realpathSync9(repo);
|
|
97708
98601
|
if (real && inside4(realRepo, real)) {
|
|
97709
|
-
symlinkSync(
|
|
98602
|
+
symlinkSync(path53.relative(path53.dirname(to), path53.join(tree, path53.relative(realRepo, real))), to);
|
|
97710
98603
|
return;
|
|
97711
98604
|
}
|
|
97712
98605
|
if (real && statSync9(real).isFile())
|
|
@@ -97715,10 +98608,10 @@ function copySymlink(repo, tree, from, to) {
|
|
|
97715
98608
|
symlinkSync(readlinkSync3(from), to);
|
|
97716
98609
|
}
|
|
97717
98610
|
function writeInside(copy, file, content) {
|
|
97718
|
-
const target =
|
|
98611
|
+
const target = path53.join(copy, file);
|
|
97719
98612
|
try {
|
|
97720
98613
|
const stat = lstatSync8(target);
|
|
97721
|
-
if (!stat.isFile() || !inside4(realpathSync9(copy), realpathSync9(
|
|
98614
|
+
if (!stat.isFile() || !inside4(realpathSync9(copy), realpathSync9(path53.dirname(target))))
|
|
97722
98615
|
return false;
|
|
97723
98616
|
} catch {
|
|
97724
98617
|
return false;
|
|
@@ -97729,9 +98622,9 @@ function writeInside(copy, file, content) {
|
|
|
97729
98622
|
return true;
|
|
97730
98623
|
}
|
|
97731
98624
|
function copyWorkingTree(repo) {
|
|
97732
|
-
const dir = mkdtempSync4(
|
|
97733
|
-
writeFileSync11(
|
|
97734
|
-
const tree =
|
|
98625
|
+
const dir = mkdtempSync4(path53.join(os16.tmpdir(), TEMP_PREFIX));
|
|
98626
|
+
writeFileSync11(path53.join(dir, PID_FILE), String(process.pid));
|
|
98627
|
+
const tree = path53.join(dir, "tree");
|
|
97735
98628
|
const head = git7(repo, ["rev-parse", "--verify", "--quiet", "HEAD"]).stdout.trim();
|
|
97736
98629
|
if (head && git7(dir, ["clone", "-q", "--shared", "--no-checkout", repo, tree]).ok) {
|
|
97737
98630
|
const branch = git7(repo, ["symbolic-ref", "--quiet", "--short", "HEAD"]).stdout.trim();
|
|
@@ -97748,29 +98641,29 @@ function copyWorkingTree(repo) {
|
|
|
97748
98641
|
for (const relative of listed) {
|
|
97749
98642
|
if (!relative)
|
|
97750
98643
|
continue;
|
|
97751
|
-
const from =
|
|
98644
|
+
const from = path53.join(repo, relative);
|
|
97752
98645
|
let stat;
|
|
97753
98646
|
try {
|
|
97754
98647
|
stat = lstatSync8(from);
|
|
97755
98648
|
} catch {
|
|
97756
98649
|
continue;
|
|
97757
98650
|
}
|
|
97758
|
-
const to =
|
|
97759
|
-
mkdirSync10(
|
|
98651
|
+
const to = path53.join(tree, relative);
|
|
98652
|
+
mkdirSync10(path53.dirname(to), { recursive: true });
|
|
97760
98653
|
if (stat.isSymbolicLink())
|
|
97761
98654
|
copySymlink(repo, tree, from, to);
|
|
97762
98655
|
else if (stat.isFile())
|
|
97763
98656
|
copyFileSync6(from, to);
|
|
97764
|
-
let parent =
|
|
98657
|
+
let parent = path53.dirname(relative);
|
|
97765
98658
|
while (parent !== "." && !dirs.has(parent)) {
|
|
97766
98659
|
dirs.add(parent);
|
|
97767
|
-
parent =
|
|
98660
|
+
parent = path53.dirname(parent);
|
|
97768
98661
|
}
|
|
97769
98662
|
}
|
|
97770
98663
|
for (const parent of dirs) {
|
|
97771
|
-
const modules =
|
|
97772
|
-
if (existsSync21(modules) && !existsSync21(
|
|
97773
|
-
linkNodeModules(modules,
|
|
98664
|
+
const modules = path53.join(repo, parent, "node_modules");
|
|
98665
|
+
if (existsSync21(modules) && !existsSync21(path53.join(tree, parent, "node_modules")))
|
|
98666
|
+
linkNodeModules(modules, path53.join(tree, parent, "node_modules"), repo, tree);
|
|
97774
98667
|
}
|
|
97775
98668
|
return { dir, tree };
|
|
97776
98669
|
}
|
|
@@ -97809,14 +98702,14 @@ async function runMutation(options) {
|
|
|
97809
98702
|
const tracked = git7(repo, ["ls-files", "-co", "--exclude-standard", "-z"]).stdout.split("\x00").filter(Boolean);
|
|
97810
98703
|
const testFiles = tracked.filter((file) => TEST_FILE.test(file)).map((file) => {
|
|
97811
98704
|
try {
|
|
97812
|
-
return { file, text: readFileSync24(
|
|
98705
|
+
return { file, text: readFileSync24(path53.join(repo, file), "utf8") };
|
|
97813
98706
|
} catch {
|
|
97814
98707
|
return { file, text: "" };
|
|
97815
98708
|
}
|
|
97816
98709
|
});
|
|
97817
98710
|
const candidates = [];
|
|
97818
98711
|
for (const [absolute, lines] of [...options.changed].toSorted(([a], [b]) => a.localeCompare(b))) {
|
|
97819
|
-
const relative = toPosix(
|
|
98712
|
+
const relative = toPosix(path53.relative(repo, absolute));
|
|
97820
98713
|
if (!CODE_FILE.test(relative) || TEST_FILE.test(relative) || relative.endsWith(".d.ts"))
|
|
97821
98714
|
continue;
|
|
97822
98715
|
if (NOT_SOURCE.test(relative) || !existsSync21(absolute))
|
|
@@ -97830,9 +98723,9 @@ async function runMutation(options) {
|
|
|
97830
98723
|
for (const mutant of mutants)
|
|
97831
98724
|
if (!related.has(mutant.file))
|
|
97832
98725
|
related.set(mutant.file, relatedTests(mutant.file, testFiles));
|
|
97833
|
-
const cwdRelative =
|
|
98726
|
+
const cwdRelative = path53.relative(repo, options.cwd);
|
|
97834
98727
|
const asArg = (file) => {
|
|
97835
|
-
const relative = toPosix(
|
|
98728
|
+
const relative = toPosix(path53.relative(cwdRelative, file));
|
|
97836
98729
|
return shellQuote(relative.startsWith(".") ? relative : `./${relative}`);
|
|
97837
98730
|
};
|
|
97838
98731
|
const commandFor = (tests) => options.command.replaceAll("{files}", () => tests.map(asArg).join(" "));
|
|
@@ -97867,7 +98760,7 @@ async function runMutation(options) {
|
|
|
97867
98760
|
return finish2(options, 0, results, counts, started);
|
|
97868
98761
|
removeStaleCopies();
|
|
97869
98762
|
const { dir: copyDir, tree: copy } = copyWorkingTree(repo);
|
|
97870
|
-
const copyCwd =
|
|
98763
|
+
const copyCwd = path53.join(copy, cwdRelative);
|
|
97871
98764
|
let current = null;
|
|
97872
98765
|
const restore = () => {
|
|
97873
98766
|
if (current)
|
|
@@ -97902,7 +98795,7 @@ async function runMutation(options) {
|
|
|
97902
98795
|
record({ ...mutant, status: "skipped", durationMs: 0, tests, reason: "budget" });
|
|
97903
98796
|
continue;
|
|
97904
98797
|
}
|
|
97905
|
-
const original = readFileSync24(
|
|
98798
|
+
const original = readFileSync24(path53.join(copy, mutant.file), "utf8");
|
|
97906
98799
|
const mutated = original.slice(0, mutant.start) + mutant.replacement + original.slice(mutant.end);
|
|
97907
98800
|
current = { file: mutant.file, original };
|
|
97908
98801
|
if (!writeInside(copy, mutant.file, mutated)) {
|
|
@@ -97959,8 +98852,8 @@ var NOT_SOURCE, FLIPS, isStringy = (node) => node.type === "StringLiteral" || no
|
|
|
97959
98852
|
return error.code === "EPERM";
|
|
97960
98853
|
}
|
|
97961
98854
|
}, inside4 = (root, file) => {
|
|
97962
|
-
const relative =
|
|
97963
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
98855
|
+
const relative = path53.relative(root, file);
|
|
98856
|
+
return relative === "" || !relative.startsWith("..") && !path53.isAbsolute(relative);
|
|
97964
98857
|
}, active, killTree = (child) => {
|
|
97965
98858
|
try {
|
|
97966
98859
|
if (child.pid && process.platform !== "win32")
|
|
@@ -98009,7 +98902,7 @@ __export(exports_test_audit, {
|
|
|
98009
98902
|
run: () => run18
|
|
98010
98903
|
});
|
|
98011
98904
|
import { existsSync as existsSync22 } from "node:fs";
|
|
98012
|
-
import
|
|
98905
|
+
import path54 from "node:path";
|
|
98013
98906
|
function parse7(argv) {
|
|
98014
98907
|
const options = {
|
|
98015
98908
|
paths: [],
|
|
@@ -98113,8 +99006,8 @@ async function run18(argv, io) {
|
|
|
98113
99006
|
return emit(io, fail("invalid_input", `${options}
|
|
98114
99007
|
usage: ${USAGE10}`));
|
|
98115
99008
|
const top = git7(io.cwd, ["rev-parse", "--show-toplevel"]);
|
|
98116
|
-
const repo = top.ok ?
|
|
98117
|
-
const display = (file) => toPosix(
|
|
99009
|
+
const repo = top.ok ? path54.resolve(top.stdout.trim()) : null;
|
|
99010
|
+
const display = (file) => toPosix(path54.relative(io.cwd, file) || file);
|
|
98118
99011
|
let base = null;
|
|
98119
99012
|
let changed = null;
|
|
98120
99013
|
if (options.diff || options.mutate) {
|
|
@@ -98357,7 +99250,7 @@ __export(exports_youtrack, {
|
|
|
98357
99250
|
run: () => run20
|
|
98358
99251
|
});
|
|
98359
99252
|
import { readFileSync as readFileSync25 } from "node:fs";
|
|
98360
|
-
import
|
|
99253
|
+
import path55 from "node:path";
|
|
98361
99254
|
async function run20(argv, io) {
|
|
98362
99255
|
const [sub, ...rest] = argv;
|
|
98363
99256
|
if (sub !== "note" && sub !== "time" && sub !== "meeting")
|
|
@@ -98403,7 +99296,7 @@ async function run20(argv, io) {
|
|
|
98403
99296
|
let markdown = flags.values.markdown ?? "";
|
|
98404
99297
|
if (flags.values.file)
|
|
98405
99298
|
try {
|
|
98406
|
-
markdown = readFileSync25(
|
|
99299
|
+
markdown = readFileSync25(path55.resolve(io.cwd, flags.values.file), "utf8");
|
|
98407
99300
|
} catch (error) {
|
|
98408
99301
|
return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`, USAGE12);
|
|
98409
99302
|
}
|
|
@@ -98472,7 +99365,7 @@ __export(exports_changelog, {
|
|
|
98472
99365
|
run: () => run21
|
|
98473
99366
|
});
|
|
98474
99367
|
import { readFileSync as readFileSync26 } from "node:fs";
|
|
98475
|
-
import
|
|
99368
|
+
import path56 from "node:path";
|
|
98476
99369
|
async function run21(argv, io) {
|
|
98477
99370
|
const [sub, ...rest] = argv;
|
|
98478
99371
|
if (sub !== "apply")
|
|
@@ -98518,7 +99411,7 @@ async function run21(argv, io) {
|
|
|
98518
99411
|
]);
|
|
98519
99412
|
}
|
|
98520
99413
|
var USAGE13 = "workit changelog apply (--entries <JSON|@file|-> | --normalize-only) [--path CHANGELOG.md] [--preview]", readEntries = (raw, cwd) => {
|
|
98521
|
-
const text = raw === "-" ? readFileSync26(0, "utf8") : raw.startsWith("@") ? readFileSync26(
|
|
99414
|
+
const text = raw === "-" ? readFileSync26(0, "utf8") : raw.startsWith("@") ? readFileSync26(path56.resolve(cwd, raw.slice(1)), "utf8") : raw;
|
|
98522
99415
|
return JSON.parse(text);
|
|
98523
99416
|
};
|
|
98524
99417
|
var init_changelog2 = __esm(() => {
|
|
@@ -98614,7 +99507,7 @@ var init_handoff = __esm(() => {
|
|
|
98614
99507
|
|
|
98615
99508
|
// packages/workit-cli/src/main.ts
|
|
98616
99509
|
init_package();
|
|
98617
|
-
import
|
|
99510
|
+
import path57 from "node:path";
|
|
98618
99511
|
|
|
98619
99512
|
// packages/workit-cli/src/help.ts
|
|
98620
99513
|
var HELP_FLAGS = new Set(["--help", "-h"]);
|
|
@@ -98879,7 +99772,7 @@ var VERBS = [
|
|
|
98879
99772
|
{
|
|
98880
99773
|
name: "check",
|
|
98881
99774
|
group: "delivery",
|
|
98882
|
-
usage: "workit check <name> [--task <id>] [--base <ref>] | workit check [--name <n>] [--shell] [--timeout <s>] [--task <id>] [--base <ref>] -- <cmd…>",
|
|
99775
|
+
usage: "workit check <name> [--task <id>] [--base <ref>] [--no-lease] | workit check [--name <n>] [--shell] [--timeout <s>] [--task <id>] [--base <ref>] [--heavy] [--no-lease] -- <cmd…>",
|
|
98883
99776
|
summary: "Run a configured or ad-hoc check and record CLI-observed evidence (exit = the command's)",
|
|
98884
99777
|
examples: ["workit check test", "workit check --name unit -- bun test test/unit"],
|
|
98885
99778
|
load: () => Promise.resolve().then(() => (init_check(), exports_check))
|
|
@@ -98921,8 +99814,8 @@ var VERBS = [
|
|
|
98921
99814
|
},
|
|
98922
99815
|
{
|
|
98923
99816
|
name: "merge",
|
|
98924
|
-
usage: "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--delete-branch] [--unverified --reason <why>]",
|
|
98925
|
-
summary: "Merge when READY, verified by a non-author and granted"
|
|
99817
|
+
usage: "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--subject <s>] [--body <b> | --body-file <f|->] [--delete-branch] [--unverified --reason <why>]",
|
|
99818
|
+
summary: "Merge when READY, verified by a non-author and granted; --subject/--body set the squash message"
|
|
98926
99819
|
}
|
|
98927
99820
|
],
|
|
98928
99821
|
examples: [
|
|
@@ -98962,18 +99855,24 @@ var VERBS = [
|
|
|
98962
99855
|
},
|
|
98963
99856
|
{
|
|
98964
99857
|
name: "commit",
|
|
98965
|
-
usage: "workit git commit -m|--message <msg>… | -F|--file <file|-> [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>]",
|
|
98966
|
-
summary: "Commit (or --amend) with a linted Conventional Commit message
|
|
99858
|
+
usage: "workit git commit -m|--message <msg>… | -F|--file <file|-> [--trailer <key: value>]… [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>]",
|
|
99859
|
+
summary: "Commit (or --amend) with a linted Conventional Commit message, the session trailer (unless the workspace sets trailers: false) and any --trailer"
|
|
98967
99860
|
},
|
|
98968
99861
|
{
|
|
98969
99862
|
name: "push",
|
|
98970
99863
|
usage: "workit git push [-u|--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--release <main|develop|v-tag>…]",
|
|
98971
99864
|
summary: "Push the current branch and verify the remote tip (never a blind --force); --release finishes a git-flow release"
|
|
99865
|
+
},
|
|
99866
|
+
{
|
|
99867
|
+
name: "trailers",
|
|
99868
|
+
usage: "workit git trailers",
|
|
99869
|
+
summary: "Print on or off: whether commits in this workspace get the Workit-Session trailer"
|
|
98972
99870
|
}
|
|
98973
99871
|
],
|
|
98974
99872
|
examples: [
|
|
98975
99873
|
"workit git branch --kind feature --slug help-everywhere",
|
|
98976
99874
|
'workit git commit -m "fix(cli): answer help flags first" --all',
|
|
99875
|
+
'workit git commit -m "feat: x" --trailer "Co-Authored-By: Name <email>" --all',
|
|
98977
99876
|
"workit git commit --amend --no-edit -- src/forgotten.ts",
|
|
98978
99877
|
"workit git push --set-upstream"
|
|
98979
99878
|
],
|
|
@@ -99260,7 +100159,7 @@ var defaultIo = (json) => ({
|
|
|
99260
100159
|
});
|
|
99261
100160
|
async function main(argv, overrides = {}, options = {}) {
|
|
99262
100161
|
const parsed = parseGlobals(argv);
|
|
99263
|
-
const cwd =
|
|
100162
|
+
const cwd = path57.resolve(overrides.cwd ?? process.cwd(), parsed.cwd ?? ".");
|
|
99264
100163
|
const io = { ...defaultIo(parsed.json), ...overrides, json: parsed.json, cwd };
|
|
99265
100164
|
if (parsed.error)
|
|
99266
100165
|
return emit(io, fail("invalid_input", parsed.error));
|